Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/WolfireGames/overgrowth.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/Source
diff options
context:
space:
mode:
authorMax Danielsson <max@autious.net>2022-03-30 20:52:17 +0300
committerMax Danielsson <max@autious.net>2022-04-12 14:26:13 +0300
commita3b61392037e6447640202cb5351271b3ad9b539 (patch)
tree9167eba40b7c4476c174d2d392b6e97ecb6625c0 /Source
parent61fcc6713854bf39dee0d7dac90d9fce8e3dfba7 (diff)
Add codebase from main repo, git hash 6844db6a1bb5
Diffstat (limited to 'Source')
-rw-r--r--Source/AI/chunky_tri_mesh.cpp325
-rw-r--r--Source/AI/chunky_tri_mesh.h68
-rw-r--r--Source/AI/input_geom.cpp288
-rw-r--r--Source/AI/input_geom.h160
-rw-r--r--Source/AI/mesh_loader_obj.cpp243
-rw-r--r--Source/AI/mesh_loader_obj.h67
-rw-r--r--Source/AI/navmesh.cpp1225
-rw-r--r--Source/AI/navmesh.h101
-rw-r--r--Source/AI/navmeshparameters.cpp148
-rw-r--r--Source/AI/navmeshparameters.h45
-rw-r--r--Source/AI/pathfind.cpp36
-rw-r--r--Source/AI/pathfind.h41
-rw-r--r--Source/AI/recast_dump.cpp460
-rw-r--r--Source/AI/recast_dump.h52
-rw-r--r--Source/AI/sample.cpp246
-rw-r--r--Source/AI/sample.h194
-rw-r--r--Source/AI/sample_interfaces.cpp186
-rw-r--r--Source/AI/sample_interfaces.h89
-rw-r--r--Source/AI/tilemesh.cpp1001
-rw-r--r--Source/AI/tilemesh.h131
-rw-r--r--Source/AI/tilemeshtestertool.cpp802
-rw-r--r--Source/AI/tilemeshtestertool.h120
-rw-r--r--Source/Asset/Asset/actorfile.cpp113
-rw-r--r--Source/Asset/Asset/actorfile.h62
-rw-r--r--Source/Asset/Asset/ambientsounds.cpp135
-rw-r--r--Source/Asset/Asset/ambientsounds.h69
-rw-r--r--Source/Asset/Asset/animation.cpp1701
-rw-r--r--Source/Asset/Asset/animation.h253
-rw-r--r--Source/Asset/Asset/animationeffect.cpp224
-rw-r--r--Source/Asset/Asset/animationeffect.h90
-rw-r--r--Source/Asset/Asset/attachmentasset.cpp98
-rw-r--r--Source/Asset/Asset/attachmentasset.h61
-rw-r--r--Source/Asset/Asset/attacks.cpp244
-rw-r--r--Source/Asset/Asset/attacks.h102
-rw-r--r--Source/Asset/Asset/averagecolorasset.cpp124
-rw-r--r--Source/Asset/Asset/averagecolorasset.h66
-rw-r--r--Source/Asset/Asset/character.cpp330
-rw-r--r--Source/Asset/Asset/character.h109
-rw-r--r--Source/Asset/Asset/decalfile.cpp117
-rw-r--r--Source/Asset/Asset/decalfile.h61
-rw-r--r--Source/Asset/Asset/filehash.cpp58
-rw-r--r--Source/Asset/Asset/filehash.h53
-rw-r--r--Source/Asset/Asset/fzx_file.cpp180
-rw-r--r--Source/Asset/Asset/fzx_file.h62
-rw-r--r--Source/Asset/Asset/hotspotfile.cpp96
-rw-r--r--Source/Asset/Asset/hotspotfile.h58
-rw-r--r--Source/Asset/Asset/image_sampler.cpp221
-rw-r--r--Source/Asset/Asset/image_sampler.h80
-rw-r--r--Source/Asset/Asset/item.cpp451
-rw-r--r--Source/Asset/Asset/item.h126
-rw-r--r--Source/Asset/Asset/levelinfo.cpp89
-rw-r--r--Source/Asset/Asset/levelinfo.h56
-rw-r--r--Source/Asset/Asset/levelset.cpp96
-rw-r--r--Source/Asset/Asset/levelset.h58
-rw-r--r--Source/Asset/Asset/lipsyncfile.cpp180
-rw-r--r--Source/Asset/Asset/lipsyncfile.h85
-rw-r--r--Source/Asset/Asset/material.cpp246
-rw-r--r--Source/Asset/Asset/material.h87
-rw-r--r--Source/Asset/Asset/objectfile.cpp312
-rw-r--r--Source/Asset/Asset/objectfile.h96
-rw-r--r--Source/Asset/Asset/particletype.cpp221
-rw-r--r--Source/Asset/Asset/particletype.h90
-rw-r--r--Source/Asset/Asset/reactions.cpp114
-rw-r--r--Source/Asset/Asset/reactions.h56
-rw-r--r--Source/Asset/Asset/skeletonasset.cpp391
-rw-r--r--Source/Asset/Asset/skeletonasset.h109
-rw-r--r--Source/Asset/Asset/songlistfile.cpp91
-rw-r--r--Source/Asset/Asset/songlistfile.h49
-rw-r--r--Source/Asset/Asset/soundgroup.cpp113
-rw-r--r--Source/Asset/Asset/soundgroup.h61
-rw-r--r--Source/Asset/Asset/soundgroupinfo.cpp148
-rw-r--r--Source/Asset/Asset/soundgroupinfo.h62
-rw-r--r--Source/Asset/Asset/spawnpointinfo.h37
-rw-r--r--Source/Asset/Asset/syncedanimation.cpp490
-rw-r--r--Source/Asset/Asset/syncedanimation.h103
-rw-r--r--Source/Asset/Asset/texture.cpp68
-rw-r--r--Source/Asset/Asset/texture.h68
-rw-r--r--Source/Asset/Asset/texturedummy.cpp33
-rw-r--r--Source/Asset/Asset/texturedummy.h48
-rw-r--r--Source/Asset/Asset/voicefile.cpp106
-rw-r--r--Source/Asset/Asset/voicefile.h55
-rw-r--r--Source/Asset/AssetLoader/fallbackassetloader.cpp25
-rw-r--r--Source/Asset/AssetLoader/fallbackassetloader.h114
-rw-r--r--Source/Asset/assetbase.cpp81
-rw-r--r--Source/Asset/assetbase.h86
-rw-r--r--Source/Asset/assetdatabase.h31
-rw-r--r--Source/Asset/assetinfobase.cpp28
-rw-r--r--Source/Asset/assetinfobase.h37
-rw-r--r--Source/Asset/assetloaderbase.cpp32
-rw-r--r--Source/Asset/assetloaderbase.h63
-rw-r--r--Source/Asset/assetloaderrors.cpp55
-rw-r--r--Source/Asset/assetloaderrors.h39
-rw-r--r--Source/Asset/assetmanager.cpp541
-rw-r--r--Source/Asset/assetmanager.h408
-rw-r--r--Source/Asset/assetmanagerthreadhandler.cpp128
-rw-r--r--Source/Asset/assetmanagerthreadhandler.h84
-rw-r--r--Source/Asset/assetref.cpp32
-rw-r--r--Source/Asset/assetref.h118
-rw-r--r--Source/Asset/assets.h32
-rw-r--r--Source/Asset/assettypes.cpp124
-rw-r--r--Source/Asset/assettypes.h60
-rw-r--r--Source/Compat/Linux/linux_compat.cpp67
-rw-r--r--Source/Compat/Linux/linux_compat.h23
-rw-r--r--Source/Compat/Linux/linux_hardware_info.cpp27
-rw-r--r--Source/Compat/Linux/linux_time.cpp38
-rw-r--r--Source/Compat/Linux/os_dialogs_linux.cpp95
-rw-r--r--Source/Compat/Mac/mac_compat.h29
-rw-r--r--Source/Compat/Mac/mac_compat.mm39
-rw-r--r--Source/Compat/Mac/mac_hardware_info.cpp112
-rw-r--r--Source/Compat/Mac/mac_time.cpp35
-rw-r--r--Source/Compat/Mac/os_dialogs_mac.mm66
-rw-r--r--Source/Compat/Mac/os_file_dialogs_mac.h32
-rw-r--r--Source/Compat/Mac/os_file_dialogs_mac.mm163
-rw-r--r--Source/Compat/UNIX/unix_compat.cpp482
-rw-r--r--Source/Compat/UNIX/unix_compat.h56
-rw-r--r--Source/Compat/UNIX/unix_filepath.cpp54
-rw-r--r--Source/Compat/Win/os_dialogs_win.cpp93
-rw-r--r--Source/Compat/Win/win_compat.cpp631
-rw-r--r--Source/Compat/Win/win_compat.h52
-rw-r--r--Source/Compat/Win/win_filepath.cpp146
-rw-r--r--Source/Compat/Win/win_hardware_info.cpp79
-rw-r--r--Source/Compat/Win/win_time.cpp42
-rw-r--r--Source/Compat/compat.h68
-rw-r--r--Source/Compat/fileio.cpp109
-rw-r--r--Source/Compat/fileio.h42
-rw-r--r--Source/Compat/filepath.h32
-rw-r--r--Source/Compat/hardware_info.h27
-rw-r--r--Source/Compat/os_dialogs.h41
-rw-r--r--Source/Compat/platformsetup.cpp184
-rw-r--r--Source/Compat/platformsetup.h43
-rw-r--r--Source/Compat/processpool.cpp725
-rw-r--r--Source/Compat/processpool.h138
-rw-r--r--Source/Compat/time.h28
-rw-r--r--Source/Editors/actors_editor.cpp946
-rw-r--r--Source/Editors/actors_editor.h49
-rw-r--r--Source/Editors/editor_tools.cpp101
-rw-r--r--Source/Editors/editor_tools.h54
-rw-r--r--Source/Editors/editor_types.h53
-rw-r--r--Source/Editors/editor_utilities.cpp94
-rw-r--r--Source/Editors/editor_utilities.h53
-rw-r--r--Source/Editors/entity_type.cpp53
-rw-r--r--Source/Editors/entity_type.h52
-rw-r--r--Source/Editors/map_editor.cpp3855
-rw-r--r--Source/Editors/map_editor.h333
-rw-r--r--Source/Editors/mem_read_entity_description.cpp61
-rw-r--r--Source/Editors/mem_read_entity_description.h36
-rw-r--r--Source/Editors/object_sanity_state.cpp120
-rw-r--r--Source/Editors/object_sanity_state.h54
-rw-r--r--Source/Editors/save_state.cpp61
-rw-r--r--Source/Editors/save_state.h69
-rw-r--r--Source/Editors/sky_editor.cpp564
-rw-r--r--Source/Editors/sky_editor.h136
-rw-r--r--Source/GUI/IMUI/im_behaviors.cpp754
-rw-r--r--Source/GUI/IMUI/im_behaviors.h740
-rw-r--r--Source/GUI/IMUI/im_container.cpp916
-rw-r--r--Source/GUI/IMUI/im_container.h412
-rw-r--r--Source/GUI/IMUI/im_divider.cpp656
-rw-r--r--Source/GUI/IMUI/im_divider.h255
-rw-r--r--Source/GUI/IMUI/im_element.cpp1737
-rw-r--r--Source/GUI/IMUI/im_element.h1460
-rw-r--r--Source/GUI/IMUI/im_events.cpp53
-rw-r--r--Source/GUI/IMUI/im_events.h48
-rw-r--r--Source/GUI/IMUI/im_image.cpp345
-rw-r--r--Source/GUI/IMUI/im_image.h200
-rw-r--r--Source/GUI/IMUI/im_message.h190
-rw-r--r--Source/GUI/IMUI/im_selection_list.cpp165
-rw-r--r--Source/GUI/IMUI/im_selection_list.h126
-rw-r--r--Source/GUI/IMUI/im_spacer.cpp97
-rw-r--r--Source/GUI/IMUI/im_spacer.h89
-rw-r--r--Source/GUI/IMUI/im_support.cpp270
-rw-r--r--Source/GUI/IMUI/im_support.h160
-rw-r--r--Source/GUI/IMUI/im_text.cpp361
-rw-r--r--Source/GUI/IMUI/im_text.h287
-rw-r--r--Source/GUI/IMUI/im_tween.cpp246
-rw-r--r--Source/GUI/IMUI/im_tween.h482
-rw-r--r--Source/GUI/IMUI/imgui.cpp1190
-rw-r--r--Source/GUI/IMUI/imgui.h415
-rw-r--r--Source/GUI/IMUI/imui.cpp917
-rw-r--r--Source/GUI/IMUI/imui.h496
-rw-r--r--Source/GUI/IMUI/imui_script.cpp1119
-rw-r--r--Source/GUI/IMUI/imui_script.h29
-rw-r--r--Source/GUI/IMUI/imui_state.h49
-rw-r--r--Source/GUI/dimgui/chat_window.cpp23
-rw-r--r--Source/GUI/dimgui/chat_window.h23
-rw-r--r--Source/GUI/dimgui/dimgui.cpp5329
-rw-r--r--Source/GUI/dimgui/dimgui.h65
-rw-r--r--Source/GUI/dimgui/dimgui_graph.cpp23
-rw-r--r--Source/GUI/dimgui/dimgui_graph.h78
-rw-r--r--Source/GUI/dimgui/dimgui_impl.cpp33
-rw-r--r--Source/GUI/dimgui/editor.cpp24
-rw-r--r--Source/GUI/dimgui/editor.h24
-rw-r--r--Source/GUI/dimgui/imgui_impl_sdl_gl3.cpp452
-rw-r--r--Source/GUI/dimgui/imgui_impl_sdl_gl3.h50
-rw-r--r--Source/GUI/dimgui/modmenu.cpp1203
-rw-r--r--Source/GUI/dimgui/modmenu.h30
-rw-r--r--Source/GUI/dimgui/settings_screen.cpp1258
-rw-r--r--Source/GUI/dimgui/settings_screen.h30
-rw-r--r--Source/GUI/gui.cpp35
-rw-r--r--Source/GUI/gui.h42
-rw-r--r--Source/GUI/scriptable_ui.cpp200
-rw-r--r--Source/GUI/scriptable_ui.h92
-rw-r--r--Source/GUI/widgetframework.cpp56
-rw-r--r--Source/GUI/widgetframework.h43
-rw-r--r--Source/Game/EntityDescription.cpp1374
-rw-r--r--Source/Game/EntityDescription.h175
-rw-r--r--Source/Game/attachment_type.h26
-rw-r--r--Source/Game/attackscript.cpp280
-rw-r--r--Source/Game/attackscript.h78
-rw-r--r--Source/Game/avatar_control_manager.cpp227
-rw-r--r--Source/Game/avatar_control_manager.h57
-rw-r--r--Source/Game/characterscript.cpp168
-rw-r--r--Source/Game/characterscript.h60
-rw-r--r--Source/Game/color_tint_component.cpp98
-rw-r--r--Source/Game/color_tint_component.h39
-rw-r--r--Source/Game/component.h55
-rw-r--r--Source/Game/connection_closed_reason.h75
-rw-r--r--Source/Game/detailobjectlayer.cpp102
-rw-r--r--Source/Game/detailobjectlayer.h56
-rw-r--r--Source/Game/hardcoded_assets.h45
-rw-r--r--Source/Game/level.cpp999
-rw-r--r--Source/Game/level.h193
-rw-r--r--Source/Game/levelinfo.cpp96
-rw-r--r--Source/Game/levelinfo.h69
-rw-r--r--Source/Game/levelset_script.cpp83
-rw-r--r--Source/Game/levelset_script.h26
-rw-r--r--Source/Game/loadingscreeninfo.h30
-rw-r--r--Source/Game/outofdateinfo.cpp41
-rw-r--r--Source/Game/outofdateinfo.h42
-rw-r--r--Source/Game/paths.cpp186
-rw-r--r--Source/Game/paths.h73
-rw-r--r--Source/Game/reactionscript.cpp86
-rw-r--r--Source/Game/reactionscript.h46
-rw-r--r--Source/Game/savefile.cpp567
-rw-r--r--Source/Game/savefile.h110
-rw-r--r--Source/Game/savefile_script.cpp161
-rw-r--r--Source/Game/savefile_script.h29
-rw-r--r--Source/Game/scriptablecampaign.cpp155
-rw-r--r--Source/Game/scriptablecampaign.h79
-rw-r--r--Source/Game/skyinfo.cpp47
-rw-r--r--Source/Game/skyinfo.h42
-rw-r--r--Source/Game/terraininfo.cpp85
-rw-r--r--Source/Game/terraininfo.h48
-rw-r--r--Source/Global/global_config.h24
-rw-r--r--Source/Graphics/Billboard.cpp131
-rw-r--r--Source/Graphics/Billboard.h35
-rw-r--r--Source/Graphics/ColorWheel.cpp130
-rw-r--r--Source/Graphics/ColorWheel.h37
-rw-r--r--Source/Graphics/Cursor.cpp177
-rw-r--r--Source/Graphics/Cursor.h68
-rw-r--r--Source/Graphics/animationclient.cpp577
-rw-r--r--Source/Graphics/animationclient.h133
-rw-r--r--Source/Graphics/animationeffectsystem.cpp64
-rw-r--r--Source/Graphics/animationeffectsystem.h46
-rw-r--r--Source/Graphics/animationflags.h32
-rw-r--r--Source/Graphics/animationreader.cpp380
-rw-r--r--Source/Graphics/animationreader.h86
-rw-r--r--Source/Graphics/atlasnodetree.cpp259
-rw-r--r--Source/Graphics/atlasnodetree.h121
-rw-r--r--Source/Graphics/bloodsurface.cpp871
-rw-r--r--Source/Graphics/bloodsurface.h105
-rw-r--r--Source/Graphics/bonetransform.cpp113
-rw-r--r--Source/Graphics/bonetransform.h56
-rw-r--r--Source/Graphics/bytecolor.cpp73
-rw-r--r--Source/Graphics/bytecolor.h43
-rw-r--r--Source/Graphics/camera.cpp1162
-rw-r--r--Source/Graphics/camera.h463
-rw-r--r--Source/Graphics/converttexture.cpp145
-rw-r--r--Source/Graphics/converttexture.h34
-rw-r--r--Source/Graphics/csg.cpp1320
-rw-r--r--Source/Graphics/csg.h53
-rw-r--r--Source/Graphics/cubemap.cpp143
-rw-r--r--Source/Graphics/cubemap.h36
-rw-r--r--Source/Graphics/decaltextures.cpp194
-rw-r--r--Source/Graphics/decaltextures.h74
-rw-r--r--Source/Graphics/detailmapinfo.cpp51
-rw-r--r--Source/Graphics/detailmapinfo.h50
-rw-r--r--Source/Graphics/detailobjectsurface.cpp1050
-rw-r--r--Source/Graphics/detailobjectsurface.h198
-rw-r--r--Source/Graphics/drawbatch.cpp520
-rw-r--r--Source/Graphics/drawbatch.h152
-rw-r--r--Source/Graphics/dynamiclightcollection.cpp96
-rw-r--r--Source/Graphics/dynamiclightcollection.hpp72
-rw-r--r--Source/Graphics/flares.cpp481
-rw-r--r--Source/Graphics/flares.h114
-rw-r--r--Source/Graphics/font_renderer.cpp264
-rw-r--r--Source/Graphics/font_renderer.h58
-rw-r--r--Source/Graphics/geometry.cpp809
-rw-r--r--Source/Graphics/geometry.h189
-rw-r--r--Source/Graphics/glstate.cpp37
-rw-r--r--Source/Graphics/glstate.h38
-rw-r--r--Source/Graphics/graphics.cpp1932
-rw-r--r--Source/Graphics/graphics.h445
-rw-r--r--Source/Graphics/halfedge.cpp283
-rw-r--r--Source/Graphics/halfedge.h82
-rw-r--r--Source/Graphics/heightmap.cpp198
-rw-r--r--Source/Graphics/heightmap.h69
-rw-r--r--Source/Graphics/hudimage.cpp235
-rw-r--r--Source/Graphics/hudimage.h63
-rw-r--r--Source/Graphics/ikbone.h47
-rw-r--r--Source/Graphics/kdtreecluster.cpp389
-rw-r--r--Source/Graphics/kdtreecluster.h44
-rw-r--r--Source/Graphics/lightprobecollection.cpp1020
-rw-r--r--Source/Graphics/lightprobecollection.hpp109
-rw-r--r--Source/Graphics/lipsyncsystem.cpp79
-rw-r--r--Source/Graphics/lipsyncsystem.h37
-rw-r--r--Source/Graphics/model.cpp2504
-rw-r--r--Source/Graphics/model.h165
-rw-r--r--Source/Graphics/models.cpp235
-rw-r--r--Source/Graphics/models.h81
-rw-r--r--Source/Graphics/modelsurfacewalker.cpp351
-rw-r--r--Source/Graphics/modelsurfacewalker.h73
-rw-r--r--Source/Graphics/navmeshrenderer.cpp325
-rw-r--r--Source/Graphics/navmeshrenderer.h60
-rw-r--r--Source/Graphics/palette.cpp100
-rw-r--r--Source/Graphics/palette.h47
-rw-r--r--Source/Graphics/particles.cpp1342
-rw-r--r--Source/Graphics/particles.h107
-rw-r--r--Source/Graphics/pxdebugdraw.cpp1143
-rw-r--r--Source/Graphics/pxdebugdraw.h419
-rw-r--r--Source/Graphics/retargetfile.cpp111
-rw-r--r--Source/Graphics/retargetfile.h46
-rw-r--r--Source/Graphics/shaders.cpp1298
-rw-r--r--Source/Graphics/shaders.h278
-rw-r--r--Source/Graphics/simplify.cpp756
-rw-r--r--Source/Graphics/simplify.hpp42
-rw-r--r--Source/Graphics/simplify_types.h62
-rw-r--r--Source/Graphics/skeleton.cpp1603
-rw-r--r--Source/Graphics/skeleton.h148
-rw-r--r--Source/Graphics/sky.cpp636
-rw-r--r--Source/Graphics/sky.h94
-rw-r--r--Source/Graphics/terrain.cpp1061
-rw-r--r--Source/Graphics/terrain.h122
-rw-r--r--Source/Graphics/text.cpp1154
-rw-r--r--Source/Graphics/text.h194
-rw-r--r--Source/Graphics/textureatlas.cpp119
-rw-r--r--Source/Graphics/textureatlas.h63
-rw-r--r--Source/Graphics/texturepack.cpp264
-rw-r--r--Source/Graphics/texturepack.h70
-rw-r--r--Source/Graphics/textureref.cpp74
-rw-r--r--Source/Graphics/textureref.h46
-rw-r--r--Source/Graphics/textures.cpp2818
-rw-r--r--Source/Graphics/textures.h300
-rw-r--r--Source/Graphics/vbocontainer.cpp198
-rw-r--r--Source/Graphics/vbocontainer.h55
-rw-r--r--Source/Graphics/vboenums.h33
-rw-r--r--Source/Graphics/vboringcontainer.cpp268
-rw-r--r--Source/Graphics/vboringcontainer.h96
-rw-r--r--Source/Images/ddsformat.hpp26
-rw-r--r--Source/Images/freeimage_wrapper.cpp267
-rw-r--r--Source/Images/freeimage_wrapper.h129
-rw-r--r--Source/Images/image_export.cpp208
-rw-r--r--Source/Images/image_export.hpp49
-rw-r--r--Source/Images/nv_image.cpp497
-rw-r--r--Source/Images/nv_image.h135
-rw-r--r--Source/Images/nv_image_dds.cpp663
-rw-r--r--Source/Images/texture_data.cpp647
-rw-r--r--Source/Images/texture_data.h118
-rw-r--r--Source/Internal/SIMD.h706
-rw-r--r--Source/Internal/assetmanifest.h24
-rw-r--r--Source/Internal/assetpreload.cpp61
-rw-r--r--Source/Internal/assetpreload.h51
-rw-r--r--Source/Internal/buildver.cpp75
-rw-r--r--Source/Internal/cachefile.cpp85
-rw-r--r--Source/Internal/cachefile.h43
-rw-r--r--Source/Internal/callstack.cpp36
-rw-r--r--Source/Internal/callstack.h36
-rw-r--r--Source/Internal/casecorrectpath.cpp46
-rw-r--r--Source/Internal/casecorrectpath.h30
-rw-r--r--Source/Internal/checksum.cpp118
-rw-r--r--Source/Internal/checksum.h28
-rw-r--r--Source/Internal/collisiondetection.cpp1394
-rw-r--r--Source/Internal/collisiondetection.h82
-rw-r--r--Source/Internal/comma_separated_list.h64
-rw-r--r--Source/Internal/common.cpp68
-rw-r--r--Source/Internal/common.h43
-rw-r--r--Source/Internal/config.cpp631
-rw-r--r--Source/Internal/config.h190
-rw-r--r--Source/Internal/crashreport.cpp499
-rw-r--r--Source/Internal/crashreport.h25
-rw-r--r--Source/Internal/datemodified.cpp190
-rw-r--r--Source/Internal/datemodified.h31
-rw-r--r--Source/Internal/detect_settings.cpp56
-rw-r--r--Source/Internal/detect_settings.h26
-rw-r--r--Source/Internal/dialogues.cpp438
-rw-r--r--Source/Internal/dialogues.h52
-rw-r--r--Source/Internal/error.cpp295
-rw-r--r--Source/Internal/error.h69
-rw-r--r--Source/Internal/error.mm153
-rw-r--r--Source/Internal/error_response.h29
-rw-r--r--Source/Internal/file_descriptor.cpp94
-rw-r--r--Source/Internal/file_descriptor.h82
-rw-r--r--Source/Internal/filesystem.cpp1472
-rw-r--r--Source/Internal/filesystem.h167
-rw-r--r--Source/Internal/hardware_specs.cpp360
-rw-r--r--Source/Internal/hardware_specs.h38
-rw-r--r--Source/Internal/integer.h56
-rw-r--r--Source/Internal/levelxml.cpp402
-rw-r--r--Source/Internal/levelxml.h35
-rw-r--r--Source/Internal/levelxml_script.cpp60
-rw-r--r--Source/Internal/levelxml_script.h26
-rw-r--r--Source/Internal/locale.cpp125
-rw-r--r--Source/Internal/locale.h37
-rw-r--r--Source/Internal/memwrite.cpp51
-rw-r--r--Source/Internal/memwrite.h30
-rw-r--r--Source/Internal/message.h25
-rw-r--r--Source/Internal/modid.cpp125
-rw-r--r--Source/Internal/modid.h119
-rw-r--r--Source/Internal/modloading.cpp2677
-rw-r--r--Source/Internal/modloading.h485
-rw-r--r--Source/Internal/path.cpp96
-rw-r--r--Source/Internal/path.h68
-rw-r--r--Source/Internal/path_set.cpp59
-rw-r--r--Source/Internal/path_set.h32
-rw-r--r--Source/Internal/path_utility.cpp67
-rw-r--r--Source/Internal/path_utility.h42
-rw-r--r--Source/Internal/profiler.cpp207
-rw-r--r--Source/Internal/profiler.h193
-rw-r--r--Source/Internal/referencecounter.h148
-rw-r--r--Source/Internal/returnpathutil.cpp199
-rw-r--r--Source/Internal/returnpathutil.h30
-rw-r--r--Source/Internal/scoped_buffer.h42
-rw-r--r--Source/Internal/snprintf.c1028
-rw-r--r--Source/Internal/snprintf.h41
-rw-r--r--Source/Internal/spawneritem.cpp38
-rw-r--r--Source/Internal/spawneritem.h36
-rw-r--r--Source/Internal/stdafx.cpp3
-rw-r--r--Source/Internal/stopwatch.cpp75
-rw-r--r--Source/Internal/stopwatch.h56
-rw-r--r--Source/Internal/textfile.cpp82
-rw-r--r--Source/Internal/textfile.h19
-rw-r--r--Source/Internal/timer.cpp191
-rw-r--r--Source/Internal/timer.h72
-rw-r--r--Source/Internal/treestructure.cpp195
-rw-r--r--Source/Internal/treestructure.h36
-rw-r--r--Source/Internal/varstring.cpp39
-rw-r--r--Source/Internal/varstring.h27
-rw-r--r--Source/Internal/win_mem_track.cpp127
-rw-r--r--Source/Internal/win_mem_track.h29
-rw-r--r--Source/Internal/worker.cpp82
-rw-r--r--Source/Internal/zip_util.cpp483
-rw-r--r--Source/Internal/zip_util.h56
-rw-r--r--Source/JSON/json-forwards.h262
-rw-r--r--Source/JSON/json.h1975
-rw-r--r--Source/JSON/jsoncpp.cpp4956
-rw-r--r--Source/JSON/jsonhelper.cpp120
-rw-r--r--Source/JSON/jsonhelper.h98
-rw-r--r--Source/Logging/consolehandler.cpp129
-rw-r--r--Source/Logging/consolehandler.h41
-rw-r--r--Source/Logging/filehandler.cpp179
-rw-r--r--Source/Logging/filehandler.h56
-rw-r--r--Source/Logging/logdata.cpp399
-rw-r--r--Source/Logging/logdata.h250
-rw-r--r--Source/Logging/loghandler.cpp35
-rw-r--r--Source/Logging/loghandler.h43
-rw-r--r--Source/Logging/ramhandler.cpp111
-rw-r--r--Source/Logging/ramhandler.h70
-rw-r--r--Source/Main/altmain.cpp218
-rw-r--r--Source/Main/altmain.h26
-rw-r--r--Source/Main/debuglevelload.h90
-rw-r--r--Source/Main/engine.cpp6808
-rw-r--r--Source/Main/engine.h326
-rw-r--r--Source/Main/main.cpp511
-rw-r--r--Source/Main/scenegraph.cpp3179
-rw-r--r--Source/Main/scenegraph.h320
-rw-r--r--Source/Math/enginemath.cpp161
-rw-r--r--Source/Math/enginemath.h123
-rw-r--r--Source/Math/ivec2.h66
-rw-r--r--Source/Math/ivec2math.h119
-rw-r--r--Source/Math/ivec3.h98
-rw-r--r--Source/Math/ivec4.h124
-rw-r--r--Source/Math/mat3.cpp73
-rw-r--r--Source/Math/mat3.h52
-rw-r--r--Source/Math/mat4.cpp1123
-rw-r--r--Source/Math/mat4.h174
-rw-r--r--Source/Math/overgrowth_geometry.cpp251
-rw-r--r--Source/Math/overgrowth_geometry.h59
-rw-r--r--Source/Math/quaternions.cpp579
-rw-r--r--Source/Math/quaternions.h107
-rw-r--r--Source/Math/simd_mat4.cpp22
-rw-r--r--Source/Math/simd_mat4.h157
-rw-r--r--Source/Math/triangle.c16006
-rw-r--r--Source/Math/triangle.h309
-rw-r--r--Source/Math/vec2.h106
-rw-r--r--Source/Math/vec2math.cpp51
-rw-r--r--Source/Math/vec2math.h132
-rw-r--r--Source/Math/vec3.h98
-rw-r--r--Source/Math/vec3math.cpp43
-rw-r--r--Source/Math/vec3math.h204
-rw-r--r--Source/Math/vec4.h134
-rw-r--r--Source/Math/vec4math.cpp31
-rw-r--r--Source/Math/vec4math.h144
-rw-r--r--Source/Memory/allocation.cpp200
-rw-r--r--Source/Memory/allocation.h79
-rw-r--r--Source/Memory/bitarray.cpp133
-rw-r--r--Source/Memory/bitarray.h50
-rw-r--r--Source/Memory/block_allocator.cpp150
-rw-r--r--Source/Memory/block_allocator.h57
-rw-r--r--Source/Memory/blockallocation.cpp39
-rw-r--r--Source/Memory/blockallocation.h39
-rw-r--r--Source/Memory/stack_allocator.cpp133
-rw-r--r--Source/Memory/stack_allocator.h48
-rw-r--r--Source/Memory/ts_block_allocator.cpp22
-rw-r--r--Source/Memory/ts_block_allocator.h22
-rw-r--r--Source/Network/Basic/basic_net_framework.cpp344
-rw-r--r--Source/Network/Basic/basic_net_framework.h123
-rw-r--r--Source/Network/Steam/steam_net_framework.cpp346
-rw-r--r--Source/Network/Steam/steam_net_framework.h116
-rw-r--r--Source/Network/asnetwork.cpp190
-rw-r--r--Source/Network/asnetwork.h75
-rw-r--r--Source/Network/net_framework.cpp22
-rw-r--r--Source/Network/net_framework.h38
-rw-r--r--Source/Network/net_framework_common.cpp40
-rw-r--r--Source/Network/net_framework_common.h57
-rw-r--r--Source/Network/net_socket_connection.cpp22
-rw-r--r--Source/Network/net_socket_connection.h22
-rw-r--r--Source/Objects/ambientsoundobject.cpp145
-rw-r--r--Source/Objects/ambientsoundobject.h59
-rw-r--r--Source/Objects/cameraobject.cpp149
-rw-r--r--Source/Objects/cameraobject.h93
-rw-r--r--Source/Objects/decalobject.cpp182
-rw-r--r--Source/Objects/decalobject.h61
-rw-r--r--Source/Objects/dynamiclightobject.cpp183
-rw-r--r--Source/Objects/dynamiclightobject.h54
-rw-r--r--Source/Objects/editorcameraobject.cpp50
-rw-r--r--Source/Objects/editorcameraobject.h52
-rw-r--r--Source/Objects/envobject.cpp1945
-rw-r--r--Source/Objects/envobject.h217
-rw-r--r--Source/Objects/envobjectattach.cpp60
-rw-r--r--Source/Objects/envobjectattach.h47
-rw-r--r--Source/Objects/envobjecttype.h26
-rw-r--r--Source/Objects/group.cpp417
-rw-r--r--Source/Objects/group.h75
-rw-r--r--Source/Objects/hotspot.cpp815
-rw-r--r--Source/Objects/hotspot.h152
-rw-r--r--Source/Objects/itemobject.cpp1705
-rw-r--r--Source/Objects/itemobject.h235
-rw-r--r--Source/Objects/itemobjectscriptreader.cpp161
-rw-r--r--Source/Objects/itemobjectscriptreader.h78
-rw-r--r--Source/Objects/lightprobeobject.cpp116
-rw-r--r--Source/Objects/lightprobeobject.h47
-rw-r--r--Source/Objects/lightvolume.cpp67
-rw-r--r--Source/Objects/lightvolume.h47
-rw-r--r--Source/Objects/movementobject.cpp4994
-rw-r--r--Source/Objects/movementobject.h361
-rw-r--r--Source/Objects/navmeshconnectionobject.cpp368
-rw-r--r--Source/Objects/navmeshconnectionobject.h69
-rw-r--r--Source/Objects/navmeshhintobject.cpp120
-rw-r--r--Source/Objects/navmeshhintobject.h46
-rw-r--r--Source/Objects/navmeshregionobject.cpp77
-rw-r--r--Source/Objects/navmeshregionobject.h47
-rw-r--r--Source/Objects/object.cpp538
-rw-r--r--Source/Objects/object.h311
-rw-r--r--Source/Objects/object_msg.h41
-rw-r--r--Source/Objects/pathpointobject.cpp201
-rw-r--r--Source/Objects/pathpointobject.h59
-rw-r--r--Source/Objects/placeholderobject.cpp386
-rw-r--r--Source/Objects/placeholderobject.h86
-rw-r--r--Source/Objects/prefab.cpp97
-rw-r--r--Source/Objects/prefab.h55
-rw-r--r--Source/Objects/reflectioncaptureobject.cpp146
-rw-r--r--Source/Objects/reflectioncaptureobject.h55
-rw-r--r--Source/Objects/riggedobject.cpp4922
-rw-r--r--Source/Objects/riggedobject.h454
-rw-r--r--Source/Objects/softinfo.h32
-rw-r--r--Source/Objects/terrainobject.cpp629
-rw-r--r--Source/Objects/terrainobject.h87
-rw-r--r--Source/Ogda/Builders/actionbase.h40
-rw-r--r--Source/Ogda/Builders/builder.cpp71
-rw-r--r--Source/Ogda/Builders/builder.h47
-rw-r--r--Source/Ogda/Builders/builderfactory.cpp90
-rw-r--r--Source/Ogda/Builders/builderfactory.h65
-rw-r--r--Source/Ogda/Builders/copyaction.cpp49
-rw-r--r--Source/Ogda/Builders/copyaction.h37
-rw-r--r--Source/Ogda/Builders/crunchaction.cpp45
-rw-r--r--Source/Ogda/Builders/crunchaction.h37
-rw-r--r--Source/Ogda/Builders/dxt5action.cpp45
-rw-r--r--Source/Ogda/Builders/dxt5action.h37
-rw-r--r--Source/Ogda/Builders/voidaction.cpp31
-rw-r--r--Source/Ogda/Builders/voidaction.h39
-rw-r--r--Source/Ogda/Generators/creatorbase.h39
-rw-r--r--Source/Ogda/Generators/generator.cpp43
-rw-r--r--Source/Ogda/Generators/generator.h41
-rw-r--r--Source/Ogda/Generators/generatorfactory.cpp77
-rw-r--r--Source/Ogda/Generators/generatorfactory.h59
-rw-r--r--Source/Ogda/Generators/levellistcreator.cpp55
-rw-r--r--Source/Ogda/Generators/levellistcreator.h36
-rw-r--r--Source/Ogda/Generators/shortversioncreator.cpp49
-rw-r--r--Source/Ogda/Generators/shortversioncreator.h36
-rw-r--r--Source/Ogda/Generators/versionxmlcreator.cpp97
-rw-r--r--Source/Ogda/Generators/versionxmlcreator.h36
-rw-r--r--Source/Ogda/Generators/voidcreator.cpp28
-rw-r--r--Source/Ogda/Generators/voidcreator.h36
-rw-r--r--Source/Ogda/Searchers/Seekers/SeekerTools/attributescanner.cpp101
-rw-r--r--Source/Ogda/Searchers/Seekers/SeekerTools/attributescanner.h50
-rw-r--r--Source/Ogda/Searchers/Seekers/SeekerTools/elementscanner.cpp91
-rw-r--r--Source/Ogda/Searchers/Seekers/SeekerTools/elementscanner.h47
-rw-r--r--Source/Ogda/Searchers/Seekers/SeekerTools/parameterscanner.cpp87
-rw-r--r--Source/Ogda/Searchers/Seekers/SeekerTools/parameterscanner.h45
-rw-r--r--Source/Ogda/Searchers/Seekers/actorobjectlevelseeker.cpp568
-rw-r--r--Source/Ogda/Searchers/Seekers/actorobjectlevelseeker.h39
-rw-r--r--Source/Ogda/Searchers/Seekers/actorseeker.cpp123
-rw-r--r--Source/Ogda/Searchers/Seekers/actorseeker.h39
-rw-r--r--Source/Ogda/Searchers/Seekers/ambientsoundlevelseeker.cpp77
-rw-r--r--Source/Ogda/Searchers/Seekers/ambientsoundlevelseeker.h38
-rw-r--r--Source/Ogda/Searchers/Seekers/animationretargetseeker.cpp80
-rw-r--r--Source/Ogda/Searchers/Seekers/animationretargetseeker.h40
-rw-r--r--Source/Ogda/Searchers/Seekers/attackseeker.cpp126
-rw-r--r--Source/Ogda/Searchers/Seekers/attackseeker.h39
-rw-r--r--Source/Ogda/Searchers/Seekers/characterseeker.cpp155
-rw-r--r--Source/Ogda/Searchers/Seekers/characterseeker.h39
-rw-r--r--Source/Ogda/Searchers/Seekers/decalseeker.cpp75
-rw-r--r--Source/Ogda/Searchers/Seekers/decalseeker.h38
-rw-r--r--Source/Ogda/Searchers/Seekers/hotspotseeker.cpp48
-rw-r--r--Source/Ogda/Searchers/Seekers/hotspotseeker.h39
-rw-r--r--Source/Ogda/Searchers/Seekers/itemseeker.cpp196
-rw-r--r--Source/Ogda/Searchers/Seekers/itemseeker.h39
-rw-r--r--Source/Ogda/Searchers/Seekers/jsonseekerbase.cpp52
-rw-r--r--Source/Ogda/Searchers/Seekers/jsonseekerbase.h39
-rw-r--r--Source/Ogda/Searchers/Seekers/levelnormseeker.cpp37
-rw-r--r--Source/Ogda/Searchers/Seekers/levelnormseeker.h35
-rw-r--r--Source/Ogda/Searchers/Seekers/levelseekerbase.cpp43
-rw-r--r--Source/Ogda/Searchers/Seekers/levelseekerbase.h33
-rw-r--r--Source/Ogda/Searchers/Seekers/materialseeker.cpp191
-rw-r--r--Source/Ogda/Searchers/Seekers/materialseeker.h39
-rw-r--r--Source/Ogda/Searchers/Seekers/objcolseeker.cpp43
-rw-r--r--Source/Ogda/Searchers/Seekers/objcolseeker.h35
-rw-r--r--Source/Ogda/Searchers/Seekers/objectseeker.cpp177
-rw-r--r--Source/Ogda/Searchers/Seekers/objectseeker.h39
-rw-r--r--Source/Ogda/Searchers/Seekers/objhullseeker.cpp48
-rw-r--r--Source/Ogda/Searchers/Seekers/objhullseeker.h35
-rw-r--r--Source/Ogda/Searchers/Seekers/particleseeker.cpp122
-rw-r--r--Source/Ogda/Searchers/Seekers/particleseeker.h39
-rw-r--r--Source/Ogda/Searchers/Seekers/preconvertedddsshadowseeker.cpp40
-rw-r--r--Source/Ogda/Searchers/Seekers/preconvertedddsshadowseeker.h38
-rw-r--r--Source/Ogda/Searchers/Seekers/prefabseeker.cpp929
-rw-r--r--Source/Ogda/Searchers/Seekers/prefabseeker.h39
-rw-r--r--Source/Ogda/Searchers/Seekers/seekerbase.h36
-rw-r--r--Source/Ogda/Searchers/Seekers/skeletonseeker.cpp69
-rw-r--r--Source/Ogda/Searchers/Seekers/skeletonseeker.h39
-rw-r--r--Source/Ogda/Searchers/Seekers/skylevelseeker.cpp45
-rw-r--r--Source/Ogda/Searchers/Seekers/skylevelseeker.h38
-rw-r--r--Source/Ogda/Searchers/Seekers/spawnerlistseeker.cpp84
-rw-r--r--Source/Ogda/Searchers/Seekers/spawnerlistseeker.h37
-rw-r--r--Source/Ogda/Searchers/Seekers/syncedanimationgroupseeker.cpp116
-rw-r--r--Source/Ogda/Searchers/Seekers/syncedanimationgroupseeker.h39
-rw-r--r--Source/Ogda/Searchers/Seekers/terrainlevelseeker.cpp137
-rw-r--r--Source/Ogda/Searchers/Seekers/terrainlevelseeker.h38
-rw-r--r--Source/Ogda/Searchers/Seekers/voidseeker.cpp31
-rw-r--r--Source/Ogda/Searchers/Seekers/voidseeker.h38
-rw-r--r--Source/Ogda/Searchers/Seekers/xmlseekerbase.cpp52
-rw-r--r--Source/Ogda/Searchers/Seekers/xmlseekerbase.h41
-rw-r--r--Source/Ogda/Searchers/searcher.cpp97
-rw-r--r--Source/Ogda/Searchers/searcher.h50
-rw-r--r--Source/Ogda/Searchers/searcherfactory.cpp119
-rw-r--r--Source/Ogda/Searchers/searcherfactory.h57
-rw-r--r--Source/Ogda/database_manifest.cpp244
-rw-r--r--Source/Ogda/database_manifest.h48
-rw-r--r--Source/Ogda/databasemanifestresult.cpp48
-rw-r--r--Source/Ogda/databasemanifestresult.h53
-rw-r--r--Source/Ogda/filterbase.h27
-rw-r--r--Source/Ogda/item.cpp247
-rw-r--r--Source/Ogda/item.h85
-rw-r--r--Source/Ogda/jobhandler.cpp707
-rw-r--r--Source/Ogda/jobhandler.h65
-rw-r--r--Source/Ogda/jobhandlerthreadpool.cpp119
-rw-r--r--Source/Ogda/jobhandlerthreadpool.h40
-rw-r--r--Source/Ogda/levelreader.h30
-rw-r--r--Source/Ogda/main.cpp179
-rw-r--r--Source/Ogda/main.h24
-rw-r--r--Source/Ogda/manifest.cpp456
-rw-r--r--Source/Ogda/manifest.h54
-rw-r--r--Source/Ogda/manifestresult.cpp84
-rw-r--r--Source/Ogda/manifestresult.h72
-rw-r--r--Source/Ogda/manifestthreadpool.cpp121
-rw-r--r--Source/Ogda/manifestthreadpool.h40
-rw-r--r--Source/Ogda/ogda_config.cpp33
-rw-r--r--Source/Ogda/ogda_config.h33
-rw-r--r--Source/Ogda/ogda_hash.cpp44
-rw-r--r--Source/Ogda/ogda_hash.h25
-rw-r--r--Source/Ogda/readerbase.h28
-rw-r--r--Source/Online/Message/angelscript_data.cpp88
-rw-r--r--Source/Online/Message/angelscript_data.h51
-rw-r--r--Source/Online/Message/angelscript_object_data.cpp89
-rw-r--r--Source/Online/Message/angelscript_object_data.h50
-rw-r--r--Source/Online/Message/attach_to_message.cpp124
-rw-r--r--Source/Online/Message/attach_to_message.h45
-rw-r--r--Source/Online/Message/audio_play_group_priority_message.cpp73
-rw-r--r--Source/Online/Message/audio_play_group_priority_message.h43
-rw-r--r--Source/Online/Message/audio_play_sound_group_gain_message.cpp73
-rw-r--r--Source/Online/Message/audio_play_sound_group_gain_message.h43
-rw-r--r--Source/Online/Message/audio_play_sound_group_message.cpp69
-rw-r--r--Source/Online/Message/audio_play_sound_group_message.h42
-rw-r--r--Source/Online/Message/audio_play_sound_group_relative_gain_message.cpp73
-rw-r--r--Source/Online/Message/audio_play_sound_group_relative_gain_message.h42
-rw-r--r--Source/Online/Message/audio_play_sound_group_relative_message.cpp71
-rw-r--r--Source/Online/Message/audio_play_sound_group_relative_message.h41
-rw-r--r--Source/Online/Message/audio_play_sound_group_voice_message.cpp81
-rw-r--r--Source/Online/Message/audio_play_sound_group_voice_message.h43
-rw-r--r--Source/Online/Message/audio_play_sound_impact_item_message.cpp87
-rw-r--r--Source/Online/Message/audio_play_sound_impact_item_message.h44
-rw-r--r--Source/Online/Message/audio_play_sound_location_message.cpp71
-rw-r--r--Source/Online/Message/audio_play_sound_location_message.h42
-rw-r--r--Source/Online/Message/audio_play_sound_loop_at_location_message.cpp75
-rw-r--r--Source/Online/Message/audio_play_sound_loop_at_location_message.h43
-rw-r--r--Source/Online/Message/audio_play_sound_loop_message.cpp73
-rw-r--r--Source/Online/Message/audio_play_sound_loop_message.h42
-rw-r--r--Source/Online/Message/audio_play_sound_message.cpp69
-rw-r--r--Source/Online/Message/audio_play_sound_message.h41
-rw-r--r--Source/Online/Message/camera_transform_message.cpp91
-rw-r--r--Source/Online/Message/camera_transform_message.h46
-rw-r--r--Source/Online/Message/chat_entry_message.cpp78
-rw-r--r--Source/Online/Message/chat_entry_message.h41
-rw-r--r--Source/Online/Message/create_entity.cpp79
-rw-r--r--Source/Online/Message/create_entity.h55
-rw-r--r--Source/Online/Message/cut_line.cpp105
-rw-r--r--Source/Online/Message/cut_line.h66
-rw-r--r--Source/Online/Message/editor_transform_change.cpp88
-rw-r--r--Source/Online/Message/editor_transform_change.h45
-rw-r--r--Source/Online/Message/env_object_update.cpp90
-rw-r--r--Source/Online/Message/env_object_update.h50
-rw-r--r--Source/Online/Message/host_session_flag.cpp74
-rw-r--r--Source/Online/Message/host_session_flag.h43
-rw-r--r--Source/Online/Message/item_update.cpp94
-rw-r--r--Source/Online/Message/item_update.h46
-rw-r--r--Source/Online/Message/material_sound_event.cpp75
-rw-r--r--Source/Online/Message/material_sound_event.h52
-rw-r--r--Source/Online/Message/morph_target_update.cpp98
-rw-r--r--Source/Online/Message/morph_target_update.h50
-rw-r--r--Source/Online/Message/movement_object_update.cpp121
-rw-r--r--Source/Online/Message/movement_object_update.h52
-rw-r--r--Source/Online/Message/online_message_base.cpp26
-rw-r--r--Source/Online/Message/online_message_base.h51
-rw-r--r--Source/Online/Message/pcs_assign_player_id.cpp64
-rw-r--r--Source/Online/Message/pcs_assign_player_id.h41
-rw-r--r--Source/Online/Message/pcs_build_version_message.cpp66
-rw-r--r--Source/Online/Message/pcs_build_version_message.h41
-rw-r--r--Source/Online/Message/pcs_build_version_request_message.cpp57
-rw-r--r--Source/Online/Message/pcs_build_version_request_message.h40
-rw-r--r--Source/Online/Message/pcs_client_parameters_message.cpp68
-rw-r--r--Source/Online/Message/pcs_client_parameters_message.h42
-rw-r--r--Source/Online/Message/pcs_file_transfer_metadata_message.cpp87
-rw-r--r--Source/Online/Message/pcs_file_transfer_metadata_message.h42
-rw-r--r--Source/Online/Message/pcs_loading_completed_message.cpp74
-rw-r--r--Source/Online/Message/pcs_loading_completed_message.h41
-rw-r--r--Source/Online/Message/pcs_session_parameters_message.cpp100
-rw-r--r--Source/Online/Message/pcs_session_parameters_message.h41
-rw-r--r--Source/Online/Message/ping.cpp66
-rw-r--r--Source/Online/Message/ping.h41
-rw-r--r--Source/Online/Message/player_input_message.cpp78
-rw-r--r--Source/Online/Message/player_input_message.h44
-rw-r--r--Source/Online/Message/pong.cpp71
-rw-r--r--Source/Online/Message/pong.h41
-rw-r--r--Source/Online/Message/remove_object.cpp81
-rw-r--r--Source/Online/Message/remove_object.h43
-rw-r--r--Source/Online/Message/remove_player_state.cpp67
-rw-r--r--Source/Online/Message/remove_player_state.h41
-rw-r--r--Source/Online/Message/send_level_message.cpp72
-rw-r--r--Source/Online/Message/send_level_message.h45
-rw-r--r--Source/Online/Message/set_avatar_palette.cpp80
-rw-r--r--Source/Online/Message/set_avatar_palette.h44
-rw-r--r--Source/Online/Message/set_object_enabled_message.cpp77
-rw-r--r--Source/Online/Message/set_object_enabled_message.h42
-rw-r--r--Source/Online/Message/set_player_state.cpp94
-rw-r--r--Source/Online/Message/set_player_state.h50
-rw-r--r--Source/Online/Message/sp_remove_message.cpp73
-rw-r--r--Source/Online/Message/sp_remove_message.h42
-rw-r--r--Source/Online/Message/sp_rename_message.cpp80
-rw-r--r--Source/Online/Message/sp_rename_message.h43
-rw-r--r--Source/Online/Message/sp_string_message.cpp90
-rw-r--r--Source/Online/Message/sp_string_message.h45
-rw-r--r--Source/Online/Message/sp_union_message.cpp116
-rw-r--r--Source/Online/Message/sp_union_message.h52
-rw-r--r--Source/Online/Message/test_message.cpp73
-rw-r--r--Source/Online/Message/test_message.h43
-rw-r--r--Source/Online/Message/timed_slow_motion.cpp70
-rw-r--r--Source/Online/Message/timed_slow_motion.h45
-rw-r--r--Source/Online/Message/whoosh_sound_message.cpp68
-rw-r--r--Source/Online/Message/whoosh_sound_message.h42
-rw-r--r--Source/Online/online.cpp1594
-rw-r--r--Source/Online/online.h417
-rw-r--r--Source/Online/online_client_connection_manager.cpp101
-rw-r--r--Source/Online/online_client_connection_manager.h57
-rw-r--r--Source/Online/online_connection_states.cpp163
-rw-r--r--Source/Online/online_connection_states.h74
-rw-r--r--Source/Online/online_datastructures.cpp81
-rw-r--r--Source/Online/online_datastructures.h133
-rw-r--r--Source/Online/online_file_transfer_handler.cpp207
-rw-r--r--Source/Online/online_file_transfer_handler.h71
-rw-r--r--Source/Online/online_message_handler.cpp26
-rw-r--r--Source/Online/online_message_handler.h296
-rw-r--r--Source/Online/online_peer.h49
-rw-r--r--Source/Online/online_session.h119
-rw-r--r--Source/Online/online_utility.cpp141
-rw-r--r--Source/Online/online_utility.h42
-rw-r--r--Source/Online/package_header.cpp23
-rw-r--r--Source/Online/package_header.h48
-rw-r--r--Source/Online/state_machine.h101
-rw-r--r--Source/Online/time_interpolator.cpp163
-rw-r--r--Source/Online/time_interpolator.h63
-rw-r--r--Source/Physics/bulletcollision.cpp267
-rw-r--r--Source/Physics/bulletcollision.h185
-rw-r--r--Source/Physics/bulletobject.cpp588
-rw-r--r--Source/Physics/bulletobject.h137
-rw-r--r--Source/Physics/bulletworld.cpp2127
-rw-r--r--Source/Physics/bulletworld.h222
-rw-r--r--Source/Physics/physics.cpp47
-rw-r--r--Source/Physics/physics.h48
-rw-r--r--Source/Scripting/angelscript/add_on/autowrapper/aswrappedcall.h581
-rw-r--r--Source/Scripting/angelscript/add_on/autowrapper/generator/generateheader.cpp166
-rw-r--r--Source/Scripting/angelscript/add_on/autowrapper/generator/generator.sln20
-rw-r--r--Source/Scripting/angelscript/add_on/autowrapper/generator/generator.vcproj236
-rw-r--r--Source/Scripting/angelscript/add_on/contextmgr/contextmgr.cpp401
-rw-r--r--Source/Scripting/angelscript/add_on/contextmgr/contextmgr.h99
-rw-r--r--Source/Scripting/angelscript/add_on/datetime/datetime.cpp109
-rw-r--r--Source/Scripting/angelscript/add_on/datetime/datetime.h43
-rw-r--r--Source/Scripting/angelscript/add_on/debugger/debugger.cpp841
-rw-r--r--Source/Scripting/angelscript/add_on/debugger/debugger.h87
-rw-r--r--Source/Scripting/angelscript/add_on/scriptany/scriptany.cpp480
-rw-r--r--Source/Scripting/angelscript/add_on/scriptany/scriptany.h76
-rw-r--r--Source/Scripting/angelscript/add_on/scriptarray/scriptarray.cpp2144
-rw-r--r--Source/Scripting/angelscript/add_on/scriptarray/scriptarray.h137
-rw-r--r--Source/Scripting/angelscript/add_on/scriptbuilder/scriptbuilder.cpp1100
-rw-r--r--Source/Scripting/angelscript/add_on/scriptbuilder/scriptbuilder.h202
-rw-r--r--Source/Scripting/angelscript/add_on/scriptdictionary/scriptdictionary.cpp1222
-rw-r--r--Source/Scripting/angelscript/add_on/scriptdictionary/scriptdictionary.h240
-rw-r--r--Source/Scripting/angelscript/add_on/scriptfile/scriptfile.cpp660
-rw-r--r--Source/Scripting/angelscript/add_on/scriptfile/scriptfile.h101
-rw-r--r--Source/Scripting/angelscript/add_on/scriptfile/scriptfilesystem.cpp267
-rw-r--r--Source/Scripting/angelscript/add_on/scriptfile/scriptfilesystem.h49
-rw-r--r--Source/Scripting/angelscript/add_on/scriptgrid/scriptgrid.cpp792
-rw-r--r--Source/Scripting/angelscript/add_on/scriptgrid/scriptgrid.h82
-rw-r--r--Source/Scripting/angelscript/add_on/scripthandle/scripthandle.cpp329
-rw-r--r--Source/Scripting/angelscript/add_on/scripthandle/scripthandle.h65
-rw-r--r--Source/Scripting/angelscript/add_on/scripthelper/scripthelper.cpp956
-rw-r--r--Source/Scripting/angelscript/add_on/scripthelper/scripthelper.h48
-rw-r--r--Source/Scripting/angelscript/add_on/scriptmath/scriptmath.cpp347
-rw-r--r--Source/Scripting/angelscript/add_on/scriptmath/scriptmath.h26
-rw-r--r--Source/Scripting/angelscript/add_on/scriptmath/scriptmathcomplex.cpp222
-rw-r--r--Source/Scripting/angelscript/add_on/scriptmath/scriptmathcomplex.h61
-rw-r--r--Source/Scripting/angelscript/add_on/scriptstdstring/scriptstdstring.cpp1299
-rw-r--r--Source/Scripting/angelscript/add_on/scriptstdstring/scriptstdstring.h43
-rw-r--r--Source/Scripting/angelscript/add_on/scriptstdstring/scriptstdstring_utils.cpp129
-rw-r--r--Source/Scripting/angelscript/add_on/serializer/serializer.cpp548
-rw-r--r--Source/Scripting/angelscript/add_on/serializer/serializer.h193
-rw-r--r--Source/Scripting/angelscript/add_on/weakref/weakref.cpp375
-rw-r--r--Source/Scripting/angelscript/add_on/weakref/weakref.h58
-rw-r--r--Source/Scripting/angelscript/asarglist.h129
-rw-r--r--Source/Scripting/angelscript/ascollisions.cpp493
-rw-r--r--Source/Scripting/angelscript/ascollisions.h98
-rw-r--r--Source/Scripting/angelscript/ascontext.cpp866
-rw-r--r--Source/Scripting/angelscript/ascontext.h163
-rw-r--r--Source/Scripting/angelscript/ascrashdump.cpp95
-rw-r--r--Source/Scripting/angelscript/ascrashdump.h30
-rw-r--r--Source/Scripting/angelscript/asdebugger.cpp795
-rw-r--r--Source/Scripting/angelscript/asdebugger.h112
-rw-r--r--Source/Scripting/angelscript/asfuncs.cpp7707
-rw-r--r--Source/Scripting/angelscript/asfuncs.h88
-rw-r--r--Source/Scripting/angelscript/asmodule.cpp1993
-rw-r--r--Source/Scripting/angelscript/asmodule.h255
-rw-r--r--Source/Scripting/angelscript/asprofiler.cpp255
-rw-r--r--Source/Scripting/angelscript/asprofiler.h89
-rw-r--r--Source/Scripting/angelscript/scriptstdstring_extend.cpp73
-rw-r--r--Source/Scripting/angelscript/scriptstdstring_extend.h26
-rw-r--r--Source/Scripting/scriptfile.cpp429
-rw-r--r--Source/Scripting/scriptfile.h108
-rw-r--r--Source/Scripting/scriptlogging.h55
-rw-r--r--Source/Scripting/scriptparams.cpp713
-rw-r--r--Source/Scripting/scriptparams.h172
-rw-r--r--Source/Sound/AudioFilters/limiter_audio_filter.cpp126
-rw-r--r--Source/Sound/AudioFilters/limiter_audio_filter.h42
-rw-r--r--Source/Sound/AudioFilters/transition_mixer.cpp141
-rw-r--r--Source/Sound/AudioFilters/transition_mixer.h46
-rw-r--r--Source/Sound/Loader/base_loader.cpp28
-rw-r--r--Source/Sound/Loader/base_loader.h45
-rw-r--r--Source/Sound/Loader/ogg_loader.cpp168
-rw-r--r--Source/Sound/Loader/ogg_loader.h61
-rw-r--r--Source/Sound/Loader/void_loader.cpp76
-rw-r--r--Source/Sound/Loader/void_loader.h42
-rw-r--r--Source/Sound/al_audio.cpp1610
-rw-r--r--Source/Sound/al_audio.h342
-rw-r--r--Source/Sound/al_audio_buffer.cpp85
-rw-r--r--Source/Sound/al_audio_buffer.h45
-rw-r--r--Source/Sound/al_audio_source.cpp182
-rw-r--r--Source/Sound/al_audio_source.h60
-rw-r--r--Source/Sound/ambient_sound.cpp87
-rw-r--r--Source/Sound/ambient_sound.h58
-rw-r--r--Source/Sound/ambienttriangle.h34
-rw-r--r--Source/Sound/buffer_segment.cpp44
-rw-r--r--Source/Sound/buffer_segment.h48
-rw-r--r--Source/Sound/high_res_buffer_segment.cpp44
-rw-r--r--Source/Sound/high_res_buffer_segment.h48
-rw-r--r--Source/Sound/sound.cpp436
-rw-r--r--Source/Sound/sound.h155
-rw-r--r--Source/Sound/soundlogging.h55
-rw-r--r--Source/Sound/soundplayinfo.cpp97
-rw-r--r--Source/Sound/soundplayinfo.h108
-rw-r--r--Source/Sound/soundtrack.cpp1448
-rw-r--r--Source/Sound/soundtrack.h350
-rw-r--r--Source/Sound/threaded_sound_wrapper.cpp1113
-rw-r--r--Source/Sound/threaded_sound_wrapper.h309
-rw-r--r--Source/Steam/steam_appid.h26
-rw-r--r--Source/Steam/steamworks.cpp242
-rw-r--r--Source/Steam/steamworks.h75
-rw-r--r--Source/Steam/steamworks_friends.cpp60
-rw-r--r--Source/Steam/steamworks_friends.h71
-rw-r--r--Source/Steam/steamworks_matchmaking.cpp300
-rw-r--r--Source/Steam/steamworks_matchmaking.h96
-rw-r--r--Source/Steam/steamworks_util.cpp141
-rw-r--r--Source/Steam/steamworks_util.h40
-rw-r--r--Source/Steam/ugc.cpp320
-rw-r--r--Source/Steam/ugc.h91
-rw-r--r--Source/Steam/ugc_id.cpp47
-rw-r--r--Source/Steam/ugc_id.h42
-rw-r--r--Source/Steam/ugc_item.cpp642
-rw-r--r--Source/Steam/ugc_item.h193
-rw-r--r--Source/Threading/rand.cpp51
-rw-r--r--Source/Threading/rand.h29
-rw-r--r--Source/Threading/sdl_wrapper.cpp40
-rw-r--r--Source/Threading/sdl_wrapper.h29
-rw-r--r--Source/Threading/thread_name.cpp64
-rw-r--r--Source/Threading/thread_name.h25
-rw-r--r--Source/Threading/thread_sanity.cpp56
-rw-r--r--Source/Threading/thread_sanity.h27
-rw-r--r--Source/Threading/threadsafequeue.h57
-rw-r--r--Source/Timing/gl_query_perf.cpp166
-rw-r--r--Source/Timing/gl_query_perf.h108
-rw-r--r--Source/Timing/intel_gl_perf.cpp293
-rw-r--r--Source/Timing/intel_gl_perf.h140
-rw-r--r--Source/Timing/timestamp.h44
-rw-r--r--Source/Timing/timingevent.cpp521
-rw-r--r--Source/Timing/timingevent.h479
-rw-r--r--Source/UnitTests/atlastest.cpp186
-rw-r--r--Source/UnitTests/block_allocator_tester.cpp253
-rw-r--r--Source/UnitTests/modloading_test.cpp143
-rw-r--r--Source/UnitTests/string_test.cpp89
-rw-r--r--Source/UnitTests/testmain.cpp47
-rw-r--r--Source/UnitTests/testmain.h25
-rw-r--r--Source/UserInput/input.cpp1077
-rw-r--r--Source/UserInput/input.h163
-rw-r--r--Source/UserInput/joystick.cpp163
-rw-r--r--Source/UserInput/joystick.h91
-rw-r--r--Source/UserInput/keyTranslator.cpp1106
-rw-r--r--Source/UserInput/keyTranslator.h92
-rw-r--r--Source/UserInput/keyboard.cpp439
-rw-r--r--Source/UserInput/keyboard.h130
-rw-r--r--Source/UserInput/keycommands.cpp406
-rw-r--r--Source/UserInput/keycommands.h133
-rw-r--r--Source/UserInput/mouse.cpp126
-rw-r--r--Source/UserInput/mouse.h79
-rw-r--r--Source/Utility/assert.h76
-rw-r--r--Source/Utility/binn_util.cpp154
-rw-r--r--Source/Utility/binn_util.h49
-rw-r--r--Source/Utility/block_allocator.cpp143
-rw-r--r--Source/Utility/block_allocator.h153
-rw-r--r--Source/Utility/commonregex.cpp83
-rw-r--r--Source/Utility/commonregex.h34
-rw-r--r--Source/Utility/compiler_macros.h33
-rw-r--r--Source/Utility/disallow_copy_and_assign.h32
-rw-r--r--Source/Utility/fixed_array.h53
-rw-r--r--Source/Utility/fixed_heap_string.h106
-rw-r--r--Source/Utility/fixed_string.h79
-rw-r--r--Source/Utility/flat_hash_map.hpp1507
-rw-r--r--Source/Utility/fqms_simplify.hpp1053
-rw-r--r--Source/Utility/gl_util.cpp99
-rw-r--r--Source/Utility/gl_util.h30
-rw-r--r--Source/Utility/hash.cpp56
-rw-r--r--Source/Utility/hash.h34
-rw-r--r--Source/Utility/ieee.h29
-rw-r--r--Source/Utility/imgui_macros.cpp36
-rw-r--r--Source/Utility/imgui_macros.h25
-rw-r--r--Source/Utility/math_macro.h25
-rw-r--r--Source/Utility/pcg_basic.cpp121
-rw-r--r--Source/Utility/pcg_basic.h88
-rw-r--r--Source/Utility/sdl_util.h38
-rw-r--r--Source/Utility/serialize.cpp78
-rw-r--r--Source/Utility/serialize.h31
-rw-r--r--Source/Utility/set.h32
-rw-r--r--Source/Utility/simple_vector.h99
-rw-r--r--Source/Utility/stacktrace.h105
-rw-r--r--Source/Utility/strings.cpp366
-rw-r--r--Source/Utility/strings.h138
-rw-r--r--Source/Utility/timing.h46
-rw-r--r--Source/Utility/waveform_obj_serializer.cpp44
-rw-r--r--Source/Utility/waveform_obj_serializer.h32
-rw-r--r--Source/Version/fallback_version.c27
-rw-r--r--Source/Version/version.cpp149
-rw-r--r--Source/Version/version.h41
-rw-r--r--Source/Wrappers/crn.h34
-rw-r--r--Source/Wrappers/glm.h36
-rw-r--r--Source/Wrappers/linux_gtk.h32
-rw-r--r--Source/Wrappers/openal.h60
-rw-r--r--Source/Wrappers/tut.h34
-rw-r--r--Source/Wrappers/vorbis.h30
-rw-r--r--Source/XML/Parsers/activemodsparser.cpp218
-rw-r--r--Source/XML/Parsers/activemodsparser.h73
-rw-r--r--Source/XML/Parsers/assetloadwarningparser.cpp113
-rw-r--r--Source/XML/Parsers/assetloadwarningparser.h57
-rw-r--r--Source/XML/Parsers/assetmanifestparser.cpp73
-rw-r--r--Source/XML/Parsers/assetmanifestparser.h48
-rw-r--r--Source/XML/Parsers/jobxmlparser.cpp323
-rw-r--r--Source/XML/Parsers/jobxmlparser.h146
-rw-r--r--Source/XML/Parsers/levelassetspreloadparser.cpp81
-rw-r--r--Source/XML/Parsers/levelassetspreloadparser.h48
-rw-r--r--Source/XML/Parsers/levelxmlparser.cpp210
-rw-r--r--Source/XML/Parsers/levelxmlparser.h65
-rw-r--r--Source/XML/Parsers/manifestparser.cpp233
-rw-r--r--Source/XML/Parsers/manifestparser.h80
-rw-r--r--Source/XML/Parsers/musicxmlparser.cpp212
-rw-r--r--Source/XML/Parsers/musicxmlparser.h185
-rw-r--r--Source/XML/Parsers/xmlparserbase.cpp24
-rw-r--r--Source/XML/Parsers/xmlparserbase.h34
-rw-r--r--Source/XML/level_loader.cpp815
-rw-r--r--Source/XML/level_loader.h41
-rw-r--r--Source/XML/xml_helper.cpp292
-rw-r--r--Source/XML/xml_helper.h64
1015 files changed, 254765 insertions, 0 deletions
diff --git a/Source/AI/chunky_tri_mesh.cpp b/Source/AI/chunky_tri_mesh.cpp
new file mode 100644
index 00000000..0d5b6d1d
--- /dev/null
+++ b/Source/AI/chunky_tri_mesh.cpp
@@ -0,0 +1,325 @@
+//-----------------------------------------------------------------------------
+// Name: chunky_tri_mesh.cpp
+// Developer: External
+// Author:
+// Description: This is a utility file from the Recast project which has been
+// extracted and modified by Wolfire Games LLC
+// License: Read below
+//-----------------------------------------------------------------------------
+
+//
+// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
+//
+// 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.
+//
+
+#include "chunky_tri_mesh.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+struct BoundsItem
+{
+ float bmin[2];
+ float bmax[2];
+ int i;
+};
+
+static int compareItemX(const void* va, const void* vb)
+{
+ const BoundsItem* a = (const BoundsItem*)va;
+ const BoundsItem* b = (const BoundsItem*)vb;
+ if (a->bmin[0] < b->bmin[0])
+ return -1;
+ if (a->bmin[0] > b->bmin[0])
+ return 1;
+ return 0;
+}
+
+static int compareItemY(const void* va, const void* vb)
+{
+ const BoundsItem* a = (const BoundsItem*)va;
+ const BoundsItem* b = (const BoundsItem*)vb;
+ if (a->bmin[1] < b->bmin[1])
+ return -1;
+ if (a->bmin[1] > b->bmin[1])
+ return 1;
+ return 0;
+}
+
+static void calcExtends(const BoundsItem* items, const int /*nitems*/,
+ const int imin, const int imax,
+ float* bmin, float* bmax)
+{
+ bmin[0] = items[imin].bmin[0];
+ bmin[1] = items[imin].bmin[1];
+
+ bmax[0] = items[imin].bmax[0];
+ bmax[1] = items[imin].bmax[1];
+
+ for (int i = imin+1; i < imax; ++i)
+ {
+ const BoundsItem& it = items[i];
+ if (it.bmin[0] < bmin[0]) bmin[0] = it.bmin[0];
+ if (it.bmin[1] < bmin[1]) bmin[1] = it.bmin[1];
+
+ if (it.bmax[0] > bmax[0]) bmax[0] = it.bmax[0];
+ if (it.bmax[1] > bmax[1]) bmax[1] = it.bmax[1];
+ }
+}
+
+inline int longestAxis(float x, float y)
+{
+ return y > x ? 1 : 0;
+}
+
+static void subdivide(BoundsItem* items, int nitems, int imin, int imax, int trisPerChunk,
+ int& curNode, rcChunkyTriMeshNode* nodes, const int maxNodes,
+ int& curTri, int* outTris, const int* inTris)
+{
+ int inum = imax - imin;
+ int icur = curNode;
+
+ if (curNode > maxNodes)
+ return;
+
+ rcChunkyTriMeshNode& node = nodes[curNode++];
+
+ if (inum <= trisPerChunk)
+ {
+ // Leaf
+ calcExtends(items, nitems, imin, imax, node.bmin, node.bmax);
+
+ // Copy triangles.
+ node.i = curTri;
+ node.n = inum;
+
+ for (int i = imin; i < imax; ++i)
+ {
+ const int* src = &inTris[items[i].i*3];
+ int* dst = &outTris[curTri*3];
+ curTri++;
+ dst[0] = src[0];
+ dst[1] = src[1];
+ dst[2] = src[2];
+ }
+ }
+ else
+ {
+ // Split
+ calcExtends(items, nitems, imin, imax, node.bmin, node.bmax);
+
+ int axis = longestAxis(node.bmax[0] - node.bmin[0],
+ node.bmax[1] - node.bmin[1]);
+
+ if (axis == 0)
+ {
+ // Sort along x-axis
+ qsort(items+imin, static_cast<size_t>(inum), sizeof(BoundsItem), compareItemX);
+ }
+ else if (axis == 1)
+ {
+ // Sort along y-axis
+ qsort(items+imin, static_cast<size_t>(inum), sizeof(BoundsItem), compareItemY);
+ }
+
+ int isplit = imin+inum/2;
+
+ // Left
+ subdivide(items, nitems, imin, isplit, trisPerChunk, curNode, nodes, maxNodes, curTri, outTris, inTris);
+ // Right
+ subdivide(items, nitems, isplit, imax, trisPerChunk, curNode, nodes, maxNodes, curTri, outTris, inTris);
+
+ int iescape = curNode - icur;
+ // Negative index means escape.
+ node.i = -iescape;
+ }
+}
+
+bool rcCreateChunkyTriMesh(const float* verts, const int* tris, int ntris,
+ int trisPerChunk, rcChunkyTriMesh* cm)
+{
+ int nchunks = (ntris + trisPerChunk-1) / trisPerChunk;
+
+ cm->nodes = new rcChunkyTriMeshNode[nchunks*4];
+ if (!cm->nodes)
+ return false;
+
+ cm->tris = new int[ntris*3];
+ if (!cm->tris)
+ return false;
+
+ cm->ntris = ntris;
+
+ // Build tree
+ BoundsItem* items = new BoundsItem[ntris];
+ if (!items)
+ return false;
+
+ for (int i = 0; i < ntris; i++)
+ {
+ const int* t = &tris[i*3];
+ BoundsItem& it = items[i];
+ it.i = i;
+ // Calc triangle XZ bounds.
+ it.bmin[0] = it.bmax[0] = verts[t[0]*3+0];
+ it.bmin[1] = it.bmax[1] = verts[t[0]*3+2];
+ for (int j = 1; j < 3; ++j)
+ {
+ const float* v = &verts[t[j]*3];
+ if (v[0] < it.bmin[0]) it.bmin[0] = v[0];
+ if (v[2] < it.bmin[1]) it.bmin[1] = v[2];
+
+ if (v[0] > it.bmax[0]) it.bmax[0] = v[0];
+ if (v[2] > it.bmax[1]) it.bmax[1] = v[2];
+ }
+ }
+
+ int curTri = 0;
+ int curNode = 0;
+ subdivide(items, ntris, 0, ntris, trisPerChunk, curNode, cm->nodes, nchunks*4, curTri, cm->tris, tris);
+
+ delete [] items;
+
+ cm->nnodes = curNode;
+
+ // Calc max tris per node.
+ cm->maxTrisPerChunk = 0;
+ for (int i = 0; i < cm->nnodes; ++i)
+ {
+ rcChunkyTriMeshNode& node = cm->nodes[i];
+ const bool isLeaf = node.i >= 0;
+ if (!isLeaf) continue;
+ if (node.n > cm->maxTrisPerChunk)
+ cm->maxTrisPerChunk = node.n;
+ }
+
+ return true;
+}
+
+
+inline bool checkOverlapRect(const float amin[2], const float amax[2],
+ const float bmin[2], const float bmax[2])
+{
+ bool overlap = true;
+ overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
+ overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
+ return overlap;
+}
+
+int rcGetChunksOverlappingRect(const rcChunkyTriMesh* cm,
+ float bmin[2], float bmax[2],
+ int* ids, const int maxIds)
+{
+ // Traverse tree
+ int i = 0;
+ int n = 0;
+ while (i < cm->nnodes)
+ {
+ const rcChunkyTriMeshNode* node = &cm->nodes[i];
+ const bool overlap = checkOverlapRect(bmin, bmax, node->bmin, node->bmax);
+ const bool isLeafNode = node->i >= 0;
+
+ if (isLeafNode && overlap)
+ {
+ if (n < maxIds)
+ {
+ ids[n] = i;
+ n++;
+ }
+ }
+
+ if (overlap || isLeafNode)
+ i++;
+ else
+ {
+ const int escapeIndex = -node->i;
+ i += escapeIndex;
+ }
+ }
+
+ return n;
+}
+
+
+
+static bool checkOverlapSegment(const float p[2], const float q[2],
+ const float bmin[2], const float bmax[2])
+{
+ static const float EPSILON = 1e-6f;
+
+ float tmin = 0;
+ float tmax = 1;
+ float d[2];
+ d[0] = q[0] - p[0];
+ d[1] = q[1] - p[1];
+
+ for (int i = 0; i < 2; i++)
+ {
+ if (fabsf(d[i]) < EPSILON)
+ {
+ // Ray is parallel to slab. No hit if origin not within slab
+ if (p[i] < bmin[i] || p[i] > bmax[i])
+ return false;
+ }
+ else
+ {
+ // Compute intersection t value of ray with near and far plane of slab
+ float ood = 1.0f / d[i];
+ float t1 = (bmin[i] - p[i]) * ood;
+ float t2 = (bmax[i] - p[i]) * ood;
+ if (t1 > t2) { float tmp = t1; t1 = t2; t2 = tmp; }
+ if (t1 > tmin) tmin = t1;
+ if (t2 < tmax) tmax = t2;
+ if (tmin > tmax) return false;
+ }
+ }
+ return true;
+}
+
+int rcGetChunksOverlappingSegment(const rcChunkyTriMesh* cm,
+ float p[2], float q[2],
+ int* ids, const int maxIds)
+{
+ // Traverse tree
+ int i = 0;
+ int n = 0;
+ while (i < cm->nnodes)
+ {
+ const rcChunkyTriMeshNode* node = &cm->nodes[i];
+ const bool overlap = checkOverlapSegment(p, q, node->bmin, node->bmax);
+ const bool isLeafNode = node->i >= 0;
+
+ if (isLeafNode && overlap)
+ {
+ if (n < maxIds)
+ {
+ ids[n] = i;
+ n++;
+ }
+ }
+
+ if (overlap || isLeafNode)
+ i++;
+ else
+ {
+ const int escapeIndex = -node->i;
+ i += escapeIndex;
+ }
+ }
+
+ return n;
+}
diff --git a/Source/AI/chunky_tri_mesh.h b/Source/AI/chunky_tri_mesh.h
new file mode 100644
index 00000000..9ad5ac23
--- /dev/null
+++ b/Source/AI/chunky_tri_mesh.h
@@ -0,0 +1,68 @@
+//-----------------------------------------------------------------------------
+// Name: chunky_tri_mesh.h
+// Developer: External
+// Author:
+// Description: This is a utility file from the Recast project which has been
+// extracted and modified by Wolfire Games LLC
+// License: Read below
+//-----------------------------------------------------------------------------
+
+//
+// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
+//
+// 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.
+//
+
+#ifndef CHUNKYTRIMESH_H
+#define CHUNKYTRIMESH_H
+
+struct rcChunkyTriMeshNode
+{
+ float bmin[2];
+ float bmax[2];
+ int i;
+ int n;
+};
+
+struct rcChunkyTriMesh
+{
+ inline rcChunkyTriMesh() : nodes(0), tris(0) {};
+ inline ~rcChunkyTriMesh() { delete [] nodes; delete [] tris; }
+
+ rcChunkyTriMeshNode* nodes;
+ int nnodes;
+ int* tris;
+ int ntris;
+ int maxTrisPerChunk;
+
+private:
+ // Explicitly disabled copy constructor and copy assignment operator.
+ rcChunkyTriMesh(const rcChunkyTriMesh&);
+ rcChunkyTriMesh& operator=(const rcChunkyTriMesh&);
+};
+
+/// Creates partitioned triangle mesh (AABB tree),
+/// where each node contains at max trisPerChunk triangles.
+bool rcCreateChunkyTriMesh(const float* verts, const int* tris, int ntris,
+ int trisPerChunk, rcChunkyTriMesh* cm);
+
+/// Returns the chunk indices which overlap the input rectable.
+int rcGetChunksOverlappingRect(const rcChunkyTriMesh* cm, float bmin[2], float bmax[2], int* ids, const int maxIds);
+
+/// Returns the chunk indices which overlap the input segment.
+int rcGetChunksOverlappingSegment(const rcChunkyTriMesh* cm, float p[2], float q[2], int* ids, const int maxIds);
+
+
+#endif // CHUNKYTRIMESH_H
diff --git a/Source/AI/input_geom.cpp b/Source/AI/input_geom.cpp
new file mode 100644
index 00000000..097790d5
--- /dev/null
+++ b/Source/AI/input_geom.cpp
@@ -0,0 +1,288 @@
+//-----------------------------------------------------------------------------
+// Name: input_geom.cpp
+// Developer: External
+// Author:
+// Description: This is a utility file from the Recast project which has been
+// extracted and modified by Wolfire Games LLC
+// License: Read below
+//-----------------------------------------------------------------------------
+
+//
+// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
+//
+// 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.
+//
+
+#define _USE_MATH_DEFINES
+#include <math.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <algorithm>
+#include "Recast.h"
+#include "input_geom.h"
+#include "chunky_tri_mesh.h"
+#include "mesh_loader_obj.h"
+#include "DetourNavMesh.h"
+
+#include <Logging/logdata.h>
+
+using std::string;
+using std::endl;
+
+static bool intersectSegmentTriangle(const float* sp, const float* sq,
+ const float* a, const float* b, const float* c,
+ float &t)
+{
+ float v, w;
+ float ab[3], ac[3], qp[3], ap[3], norm[3], e[3];
+ rcVsub(ab, b, a);
+ rcVsub(ac, c, a);
+ rcVsub(qp, sp, sq);
+
+ // Compute triangle normal. Can be precalculated or cached if
+ // intersecting multiple segments against the same triangle
+ rcVcross(norm, ab, ac);
+
+ // Compute denominator d. If d <= 0, segment is parallel to or points
+ // away from triangle, so exit early
+ float d = rcVdot(qp, norm);
+ if (d <= 0.0f) return false;
+
+ // Compute intersection t value of pq with plane of triangle. A ray
+ // intersects iff 0 <= t. Segment intersects iff 0 <= t <= 1. Delay
+ // dividing by d until intersection has been found to pierce triangle
+ rcVsub(ap, sp, a);
+ t = rcVdot(ap, norm);
+ if (t < 0.0f) return false;
+ if (t > d) return false; // For segment; exclude this code line for a ray test
+
+ // Compute barycentric coordinate components and test if within bounds
+ rcVcross(e, qp, ap);
+ v = rcVdot(ac, e);
+ if (v < 0.0f || v > d) return false;
+ w = -rcVdot(ab, e);
+ if (w < 0.0f || v + w > d) return false;
+
+ // Segment/ray intersects triangle. Perform delayed division
+ t /= d;
+
+ return true;
+}
+
+InputGeom::InputGeom() :
+ m_chunkyMesh(0),
+ m_mesh(0),
+ m_hasBuildSettings(false),
+ m_offMeshConCount(0),
+ m_volumeCount(0)
+{
+}
+
+InputGeom::~InputGeom()
+{
+ delete m_chunkyMesh;
+ delete m_mesh;
+}
+
+bool InputGeom::loadMesh(rcContext* ctx, const string& filepath)
+{
+ if (m_mesh)
+ {
+ delete m_chunkyMesh;
+ m_chunkyMesh = 0;
+ delete m_mesh;
+ m_mesh = 0;
+ }
+
+ //I believe we wish to retain these, not reset just because the mesh is changed.
+ //deleteAllOffMeshConnections();
+ //deleteAllConvesVolumes();
+
+ m_mesh = new rcMeshLoaderObj;
+ if (!m_mesh)
+ {
+ ctx->log(RC_LOG_ERROR, "loadMesh: Out of memory 'm_mesh'.");
+ return false;
+ }
+ if (!m_mesh->load(filepath))
+ {
+ ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Could not load '%s'", filepath.c_str());
+ return false;
+ }
+
+ rcCalcBounds(m_mesh->getVerts(), m_mesh->getVertCount(), m_meshBMin, m_meshBMax);
+
+ m_chunkyMesh = new rcChunkyTriMesh;
+ if (!m_chunkyMesh)
+ {
+ ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Out of memory 'm_chunkyMesh'.");
+ return false;
+ }
+ if (!rcCreateChunkyTriMesh(m_mesh->getVerts(), m_mesh->getTris(), m_mesh->getTriCount(), 256, m_chunkyMesh))
+ {
+ ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Failed to build chunky mesh.");
+ return false;
+ }
+
+ return true;
+}
+
+static bool isectSegAABB(const float* sp, const float* sq,
+ const float* amin, const float* amax,
+ float& tmin, float& tmax)
+{
+ static const float EPS = 1e-6f;
+
+ float d[3];
+ d[0] = sq[0] - sp[0];
+ d[1] = sq[1] - sp[1];
+ d[2] = sq[2] - sp[2];
+ tmin = 0.0;
+ tmax = 1.0f;
+
+ for (int i = 0; i < 3; i++)
+ {
+ if (fabsf(d[i]) < EPS)
+ {
+ if (sp[i] < amin[i] || sp[i] > amax[i])
+ return false;
+ }
+ else
+ {
+ const float ood = 1.0f / d[i];
+ float t1 = (amin[i] - sp[i]) * ood;
+ float t2 = (amax[i] - sp[i]) * ood;
+ if (t1 > t2) { float tmp = t1; t1 = t2; t2 = tmp; }
+ if (t1 > tmin) tmin = t1;
+ if (t2 < tmax) tmax = t2;
+ if (tmin > tmax) return false;
+ }
+ }
+
+ return true;
+}
+
+
+bool InputGeom::raycastMesh(float* src, float* dst, float& tmin)
+{
+ float dir[3];
+ rcVsub(dir, dst, src);
+
+ // Prune hit ray.
+ float btmin, btmax;
+ if (!isectSegAABB(src, dst, m_meshBMin, m_meshBMax, btmin, btmax))
+ return false;
+ float p[2], q[2];
+ p[0] = src[0] + (dst[0]-src[0])*btmin;
+ p[1] = src[2] + (dst[2]-src[2])*btmin;
+ q[0] = src[0] + (dst[0]-src[0])*btmax;
+ q[1] = src[2] + (dst[2]-src[2])*btmax;
+
+ int cid[512];
+ const int ncid = rcGetChunksOverlappingSegment(m_chunkyMesh, p, q, cid, 512);
+ if (!ncid)
+ return false;
+
+ tmin = 1.0f;
+ bool hit = false;
+ const float* verts = m_mesh->getVerts();
+
+ for (int i = 0; i < ncid; ++i)
+ {
+ const rcChunkyTriMeshNode& node = m_chunkyMesh->nodes[cid[i]];
+ const int* tris = &m_chunkyMesh->tris[node.i*3];
+ const int ntris = node.n;
+
+ for (int j = 0; j < ntris*3; j += 3)
+ {
+ float t = 1;
+ if (intersectSegmentTriangle(src, dst,
+ &verts[tris[j]*3],
+ &verts[tris[j+1]*3],
+ &verts[tris[j+2]*3], t))
+ {
+ if (t < tmin)
+ tmin = t;
+ hit = true;
+ }
+ }
+ }
+
+ return hit;
+}
+
+void InputGeom::addOffMeshConnection(const vec3& spos, const vec3& epos, const float rad,
+ unsigned char bidir, unsigned char area, unsigned short flags, int userid)
+{
+ if (m_offMeshConCount >= MAX_OFFMESH_CONNECTIONS)
+ {
+ LOGW << "Reached the limit for off mesh connections " << endl;
+ return;
+ }
+
+ float* v = &m_offMeshConVerts[m_offMeshConCount*3*2];
+ m_offMeshConRads[m_offMeshConCount] = rad;
+ m_offMeshConDirs[m_offMeshConCount] = bidir;
+ m_offMeshConAreas[m_offMeshConCount] = area;
+ m_offMeshConFlags[m_offMeshConCount] = flags;
+ m_offMeshConId[m_offMeshConCount] = userid;
+ rcVcopy(&v[0], &(spos.entries[0]));
+ rcVcopy(&v[3], &(epos.entries[0]));
+ m_offMeshConCount++;
+}
+
+void InputGeom::deleteAllOffMeshConnections()
+{
+ m_offMeshConCount = 0;
+}
+
+void InputGeom::deleteOffMeshConnection(int i)
+{
+ m_offMeshConCount--;
+ float* src = &m_offMeshConVerts[m_offMeshConCount*3*2];
+ float* dst = &m_offMeshConVerts[i*3*2];
+ rcVcopy(&dst[0], &src[0]);
+ rcVcopy(&dst[3], &src[3]);
+ m_offMeshConRads[i] = m_offMeshConRads[m_offMeshConCount];
+ m_offMeshConDirs[i] = m_offMeshConDirs[m_offMeshConCount];
+ m_offMeshConAreas[i] = m_offMeshConAreas[m_offMeshConCount];
+ m_offMeshConFlags[i] = m_offMeshConFlags[m_offMeshConCount];
+}
+
+void InputGeom::addConvexVolume(const float* verts, const int nverts,
+ const float minh, const float maxh, unsigned char area)
+{
+ if (m_volumeCount >= MAX_VOLUMES) return;
+ ConvexVolume* vol = &m_volumes[m_volumeCount++];
+ memset(vol, 0, sizeof(ConvexVolume));
+ memcpy(vol->verts, verts, sizeof(float)*3*nverts);
+ vol->hmin = minh;
+ vol->hmax = maxh;
+ vol->nverts = nverts;
+ vol->area = area;
+}
+
+void InputGeom::deleteConvexVolume(int i)
+{
+ m_volumeCount--;
+ m_volumes[i] = m_volumes[m_volumeCount];
+}
+
+void InputGeom::deleteAllConvexVolumes()
+{
+ m_volumeCount = 0;
+}
+
diff --git a/Source/AI/input_geom.h b/Source/AI/input_geom.h
new file mode 100644
index 00000000..dfbbcf4f
--- /dev/null
+++ b/Source/AI/input_geom.h
@@ -0,0 +1,160 @@
+//-----------------------------------------------------------------------------
+// Name: input_geom.h
+// Developer: External
+// Author:
+// Description: This is a utility file from the Recast project which has been
+// extracted and modified by Wolfire Games LLC
+// License: Read below
+//-----------------------------------------------------------------------------
+
+//
+// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
+//
+// 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.
+//
+
+#ifndef INPUTGEOM_H
+#define INPUTGEOM_H
+
+#include "chunky_tri_mesh.h"
+#include "mesh_loader_obj.h"
+#include <Math/vec3.h>
+
+#include <string>
+
+using std::string;
+
+static const int MAX_CONVEXVOL_PTS = 12;
+struct ConvexVolume
+{
+ float verts[MAX_CONVEXVOL_PTS*3];
+ float hmin, hmax;
+ int nverts;
+ int area;
+};
+
+struct BuildSettings
+{
+ // Cell size in world units
+ float cellSize;
+ // Cell height in world units
+ float cellHeight;
+ // Agent height in world units
+ float agentHeight;
+ // Agent radius in world units
+ float agentRadius;
+ // Agent max climb in world units
+ float agentMaxClimb;
+ // Agent max slope in degrees
+ float agentMaxSlope;
+ // Region minimum size in voxels.
+ // regionMinSize = sqrt(regionMinArea)
+ float regionMinSize;
+ // Region merge size in voxels.
+ // regionMergeSize = sqrt(regionMergeArea)
+ float regionMergeSize;
+ // Edge max length in world units
+ float edgeMaxLen;
+ // Edge max error in voxels
+ float edgeMaxError;
+ float vertsPerPoly;
+ // Detail sample distance in voxels
+ float detailSampleDist;
+ // Detail sample max error in voxel heights.
+ float detailSampleMaxError;
+ // Partition type, see SamplePartitionType
+ int partitionType;
+ // Bounds of the area to mesh
+ float navMeshBMin[3];
+ float navMeshBMax[3];
+ // Size of the tiles in voxels
+ float tileSize;
+};
+
+class InputGeom
+{
+ rcChunkyTriMesh* m_chunkyMesh;
+ rcMeshLoaderObj* m_mesh;
+ float m_meshBMin[3], m_meshBMax[3];
+ BuildSettings m_buildSettings;
+ bool m_hasBuildSettings;
+
+ /// @name Off-Mesh connections.
+ ///@{
+ static const int MAX_OFFMESH_CONNECTIONS = 512;
+ float m_offMeshConVerts[MAX_OFFMESH_CONNECTIONS*3*2];
+ float m_offMeshConRads[MAX_OFFMESH_CONNECTIONS];
+ unsigned char m_offMeshConDirs[MAX_OFFMESH_CONNECTIONS];
+ unsigned char m_offMeshConAreas[MAX_OFFMESH_CONNECTIONS];
+ unsigned short m_offMeshConFlags[MAX_OFFMESH_CONNECTIONS];
+ unsigned int m_offMeshConId[MAX_OFFMESH_CONNECTIONS];
+ int m_offMeshConCount;
+ ///@}
+
+ /// @name Convex Volumes.
+ ///@{
+ static const int MAX_VOLUMES = 256;
+ ConvexVolume m_volumes[MAX_VOLUMES];
+ int m_volumeCount;
+ ///@}
+public:
+ bool loadMesh(class rcContext* ctx, const string& filepath);
+public:
+ InputGeom();
+ ~InputGeom();
+
+
+ /// Method to return static mesh data.
+ const rcMeshLoaderObj* getMesh() const { return m_mesh; }
+ const float* getMeshBoundsMin() const { return m_meshBMin; }
+ const float* getMeshBoundsMax() const { return m_meshBMax; }
+ const float* getNavMeshBoundsMin() const { return m_hasBuildSettings ? m_buildSettings.navMeshBMin : m_meshBMin; }
+ const float* getNavMeshBoundsMax() const { return m_hasBuildSettings ? m_buildSettings.navMeshBMax : m_meshBMax; }
+ const rcChunkyTriMesh* getChunkyMesh() const { return m_chunkyMesh; }
+ const BuildSettings* getBuildSettings() const { return m_hasBuildSettings ? &m_buildSettings : 0; }
+ bool raycastMesh(float* src, float* dst, float& tmin);
+
+ /// @name Off-Mesh connections.
+ ///@{
+ int getOffMeshConnectionCount() const { return m_offMeshConCount; }
+ const float* getOffMeshConnectionVerts() const { return m_offMeshConVerts; }
+ const float* getOffMeshConnectionRads() const { return m_offMeshConRads; }
+ const unsigned char* getOffMeshConnectionDirs() const { return m_offMeshConDirs; }
+ const unsigned char* getOffMeshConnectionAreas() const { return m_offMeshConAreas; }
+ const unsigned short* getOffMeshConnectionFlags() const { return m_offMeshConFlags; }
+ const unsigned int* getOffMeshConnectionId() const { return m_offMeshConId; }
+ void addOffMeshConnection(const vec3& spos, const vec3& epos, const float rad,
+ unsigned char bidir, unsigned char area, unsigned short flags, int userid);
+ void deleteOffMeshConnection(int i);
+ void deleteAllOffMeshConnections();
+ ///@}
+
+ /// @name Box Volumes.
+ ///@{
+ int getConvexVolumeCount() const { return m_volumeCount; }
+ const ConvexVolume* getConvexVolumes() const { return m_volumes; }
+ void addConvexVolume(const float* verts, const int nverts,
+ const float minh, const float maxh, unsigned char area);
+ void deleteConvexVolume(int i);
+ void deleteAllConvexVolumes();
+ ///@}
+
+private:
+ // Explicitly disabled copy constructor and copy assignment operator.
+ InputGeom(const InputGeom&);
+ InputGeom& operator=(const InputGeom&);
+};
+
+#endif // INPUTGEOM_H
diff --git a/Source/AI/mesh_loader_obj.cpp b/Source/AI/mesh_loader_obj.cpp
new file mode 100644
index 00000000..50052a35
--- /dev/null
+++ b/Source/AI/mesh_loader_obj.cpp
@@ -0,0 +1,243 @@
+//-----------------------------------------------------------------------------
+// Name: mesh_loader_obj.cpp
+// Developer: External
+// Author:
+// Description: This is a utility file from the Recast project which has been
+// extracted and modified by Wolfire Games LLC
+// License: Read below
+//-----------------------------------------------------------------------------
+
+//
+// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
+//
+// 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.
+//
+
+#include "mesh_loader_obj.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <cstring>
+#define _USE_MATH_DEFINES
+#include <math.h>
+#include <Compat/fileio.h>
+
+using std::string;
+
+rcMeshLoaderObj::rcMeshLoaderObj() :
+ m_scale(1.0f),
+ m_verts(0),
+ m_tris(0),
+ m_normals(0),
+ m_vertCount(0),
+ m_triCount(0)
+{
+}
+
+rcMeshLoaderObj::~rcMeshLoaderObj()
+{
+ delete [] m_verts;
+ delete [] m_normals;
+ delete [] m_tris;
+}
+
+void rcMeshLoaderObj::addVertex(float x, float y, float z, int& cap)
+{
+ if (m_vertCount+1 > cap)
+ {
+ cap = !cap ? 8 : cap*2;
+ float* nv = new float[cap*3];
+ if (m_vertCount)
+ memcpy(nv, m_verts, m_vertCount*3*sizeof(float));
+ delete [] m_verts;
+ m_verts = nv;
+ }
+ float* dst = &m_verts[m_vertCount*3];
+ *dst++ = x*m_scale;
+ *dst++ = y*m_scale;
+ *dst++ = z*m_scale;
+ m_vertCount++;
+}
+
+void rcMeshLoaderObj::addTriangle(int a, int b, int c, int& cap)
+{
+ if (m_triCount+1 > cap)
+ {
+ cap = !cap ? 8 : cap*2;
+ int* nv = new int[cap*3];
+ if (m_triCount)
+ memcpy(nv, m_tris, m_triCount*3*sizeof(int));
+ delete [] m_tris;
+ m_tris = nv;
+ }
+ int* dst = &m_tris[m_triCount*3];
+ *dst++ = a;
+ *dst++ = b;
+ *dst++ = c;
+ m_triCount++;
+}
+
+static char* parseRow(char* buf, char* bufEnd, char* row, int len)
+{
+ bool start = true;
+ bool done = false;
+ int n = 0;
+ while (!done && buf < bufEnd)
+ {
+ char c = *buf;
+ buf++;
+ // multirow
+ switch (c)
+ {
+ case '\\':
+ break;
+ case '\n':
+ if (start) break;
+ done = true;
+ break;
+ case '\r':
+ break;
+ case '\t':
+ case ' ':
+ if (start) break;
+ default:
+ start = false;
+ row[n++] = c;
+ if (n >= len-1)
+ done = true;
+ break;
+ }
+ }
+ row[n] = '\0';
+ return buf;
+}
+
+static int parseFace(char* row, int* data, int n, int vcnt)
+{
+ int j = 0;
+ while (*row != '\0')
+ {
+ // Skip initial white space
+ while (*row != '\0' && (*row == ' ' || *row == '\t'))
+ row++;
+ char* s = row;
+ // Find vertex delimiter and terminated the string there for conversion.
+ while (*row != '\0' && *row != ' ' && *row != '\t')
+ {
+ if (*row == '/') *row = '\0';
+ row++;
+ }
+ if (*s == '\0')
+ continue;
+ int vi = atoi(s);
+ data[j++] = vi < 0 ? vi+vcnt : vi-1;
+ if (j >= n) return j;
+ }
+ return j;
+}
+
+bool rcMeshLoaderObj::load(const string& filename)
+{
+ char* buf = 0;
+ FILE* fp = my_fopen(filename.c_str(), "rb");
+ if (!fp)
+ return false;
+ fseek(fp, 0, SEEK_END);
+ int bufSize = ftell(fp);
+ fseek(fp, 0, SEEK_SET);
+ buf = new char[bufSize];
+ if (!buf)
+ {
+ fclose(fp);
+ return false;
+ }
+ size_t readLen = fread(buf, bufSize, 1, fp);
+ fclose(fp);
+
+ if (readLen != 1)
+ {
+ delete[] buf;
+ return false;
+ }
+
+ char* src = buf;
+ char* srcEnd = buf + bufSize;
+ char row[512];
+ int face[32];
+ float x,y,z;
+ int nv;
+ int vcap = 0;
+ int tcap = 0;
+
+ while (src < srcEnd)
+ {
+ // Parse one row
+ row[0] = '\0';
+ src = parseRow(src, srcEnd, row, sizeof(row)/sizeof(char));
+ // Skip comments
+ if (row[0] == '#') continue;
+ if (row[0] == 'v' && row[1] != 'n' && row[1] != 't')
+ {
+ // Vertex pos
+ sscanf(row+1, "%f %f %f", &x, &y, &z);
+ addVertex(x, y, z, vcap);
+ }
+ if (row[0] == 'f')
+ {
+ // Faces
+ nv = parseFace(row+1, face, 32, m_vertCount);
+ for (int i = 2; i < nv; ++i)
+ {
+ const int a = face[0];
+ const int b = face[i-1];
+ const int c = face[i];
+ if (a < 0 || a >= m_vertCount || b < 0 || b >= m_vertCount || c < 0 || c >= m_vertCount)
+ continue;
+ addTriangle(a, b, c, tcap);
+ }
+ }
+ }
+
+ delete [] buf;
+
+ // Calculate normals.
+ m_normals = new float[m_triCount*3];
+ for (int i = 0; i < m_triCount*3; i += 3)
+ {
+ const float* v0 = &m_verts[m_tris[i]*3];
+ const float* v1 = &m_verts[m_tris[i+1]*3];
+ const float* v2 = &m_verts[m_tris[i+2]*3];
+ float e0[3], e1[3];
+ for (int j = 0; j < 3; ++j)
+ {
+ e0[j] = v1[j] - v0[j];
+ e1[j] = v2[j] - v0[j];
+ }
+ float* n = &m_normals[i];
+ n[0] = e0[1]*e1[2] - e0[2]*e1[1];
+ n[1] = e0[2]*e1[0] - e0[0]*e1[2];
+ n[2] = e0[0]*e1[1] - e0[1]*e1[0];
+ float d = sqrtf(n[0]*n[0] + n[1]*n[1] + n[2]*n[2]);
+ if (d > 0)
+ {
+ d = 1.0f/d;
+ n[0] *= d;
+ n[1] *= d;
+ n[2] *= d;
+ }
+ }
+
+ m_filename = filename;
+ return true;
+}
diff --git a/Source/AI/mesh_loader_obj.h b/Source/AI/mesh_loader_obj.h
new file mode 100644
index 00000000..f0e72a72
--- /dev/null
+++ b/Source/AI/mesh_loader_obj.h
@@ -0,0 +1,67 @@
+//-----------------------------------------------------------------------------
+// Name: mesh_loader_obj.h
+// Developer: External
+// Author:
+// Description: This is a utility file from the Recast project which has been
+// extracted and modified by Wolfire Games LLC
+// License: Read below
+//-----------------------------------------------------------------------------
+
+//
+// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
+//
+// 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.
+//
+
+#ifndef MESHLOADER_OBJ
+#define MESHLOADER_OBJ
+
+#include <string>
+
+using std::string;
+
+class rcMeshLoaderObj
+{
+public:
+ rcMeshLoaderObj();
+ ~rcMeshLoaderObj();
+
+ bool load(const string& fileName);
+
+ const float* getVerts() const { return m_verts; }
+ const float* getNormals() const { return m_normals; }
+ const int* getTris() const { return m_tris; }
+ int getVertCount() const { return m_vertCount; }
+ int getTriCount() const { return m_triCount; }
+ const string& getFileName() const { return m_filename; }
+
+private:
+ // Explicitly disabled copy constructor and copy assignment operator.
+ rcMeshLoaderObj(const rcMeshLoaderObj&);
+ rcMeshLoaderObj& operator=(const rcMeshLoaderObj&);
+
+ void addVertex(float x, float y, float z, int& cap);
+ void addTriangle(int a, int b, int c, int& cap);
+
+ string m_filename;
+ float m_scale;
+ float* m_verts;
+ int* m_tris;
+ float* m_normals;
+ int m_vertCount;
+ int m_triCount;
+};
+
+#endif // MESHLOADER_OBJ
diff --git a/Source/AI/navmesh.cpp b/Source/AI/navmesh.cpp
new file mode 100644
index 00000000..756f2486
--- /dev/null
+++ b/Source/AI/navmesh.cpp
@@ -0,0 +1,1225 @@
+//-----------------------------------------------------------------------------
+// Name: navmesh.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "navmesh.h"
+
+#include <Graphics/graphics.h>
+#include <Graphics/camera.h>
+#include <Graphics/pxdebugdraw.h>
+
+#include <Internal/common.h>
+#include <Internal/datemodified.h>
+#include <Internal/filesystem.h>
+
+#include <Math/vec3math.h>
+#include <Math/vec2math.h>
+#include <Math/enginemath.h>
+#include <Math/vec3math.h>
+
+#include <XML/xml_helper.h>
+#include <XML/level_loader.h>
+
+#include <Utility/commonregex.h>
+#include <Utility/hash.h>
+#include <Utility/strings.h>
+
+#include <Internal/profiler.h>
+#include <Internal/path.h>
+#include <Internal/config.h>
+#include <Internal/zip_util.h>
+
+#include <Asset/Asset/filehash.h>
+#include <Version/version.h>
+#include <Compat/fileio.h>
+#include <UserInput/input.h>
+#include <Logging/logdata.h>
+#include <Main/engine.h>
+
+#include <tinyxml.h>
+#include <Recast.h>
+#include <DetourNavMeshQuery.h>
+
+#include <float.h>
+#include <cmath>
+#include <string>
+#include <vector>
+#include <list>
+#include <iostream>
+
+
+using std::string;
+using std::vector;
+using std::ofstream;
+using std::list;
+using std::swap;
+using std::endl;
+
+extern Config config;
+
+//So, while developing, we can assume that each new version of the game _might_ involve a code change in the navmesh, it's the conservative assumption.
+//static const char* NAVMESH_VERSION = "1";
+//#define NAVMESH_VERSION GetBuildVersion()
+#define NAVMESH_VERSION "1"
+
+NavPoint::NavPoint( const NavPoint& other ) {
+ this->pos = other.pos;
+ this->valid = other.valid;
+}
+
+NavPoint::NavPoint( vec3 pos ) :
+ pos(pos), valid(true) {}
+
+NavPoint::NavPoint( vec3 pos, bool success ) :
+ pos(pos), valid(success) {}
+
+NavPoint::NavPoint( ) :
+ pos(), valid(false) {}
+
+vec3 NavPoint::GetPoint() {
+ return pos;
+}
+
+bool NavPoint::IsSuccess() {
+ return valid;
+}
+
+
+namespace NavMeshTemp {
+ int compareInt (const void * a, const void * b) {
+ return ( *(int*)a - *(int*)b );
+ }
+
+ struct SortableVert {
+ float coords[3];
+ int used_id;
+ int true_id;
+ };
+
+ inline bool rcVequal(const float* v1, const float* v2) {
+ return v1[0] == v2[0] && v1[1] == v2[1] && v1[2] == v2[2];
+ }
+
+ static int compareSortableVert(const void* va, const void* vb) {
+ const SortableVert* a = (const SortableVert*)va;
+ const SortableVert* b = (const SortableVert*)vb;
+ for(int i=0; i<3; ++i){
+ if (a->coords[i] < b->coords[i])
+ return -1;
+ if (a->coords[i] > b->coords[i])
+ return 1;
+ }
+ return 0;
+ }
+
+ struct IntTuple {
+ int val[2];
+ };
+
+ static int compareTupleFirst(const void* va, const void* vb) {
+ const IntTuple* a = (const IntTuple*)va;
+ const IntTuple* b = (const IntTuple*)vb;
+ return a->val[0] - b->val[0];
+ }
+
+ static int compareTupleSecond(const void* va, const void* vb) {
+ const IntTuple* a = (const IntTuple*)va;
+ const IntTuple* b = (const IntTuple*)vb;
+ return a->val[1] - b->val[1];
+ }
+
+ void MergeOverlappingVerts( const float *verts_in, int n_verts_in, const unsigned *tris_in, int n_tris_in, int *tris_out ) {
+ // Create an array storing the vertex indices that are referred to by the
+ // triangles, and then sort it and remove duplicates to get an array of
+ // used vertices. This is important when using a tile mesh, so we don't
+ // have to merge all the overlapping verts in the whole scene for each
+ // tile.
+ int n_tri_indices = n_tris_in * 3;
+ int *used_verts = new int[n_tri_indices];
+ for(int i=0; i<n_tri_indices; ++i){
+ used_verts[i] = tris_in[i];
+ }
+
+ qsort(used_verts, n_tri_indices, sizeof(int), compareInt);
+
+ int n_used_verts = 0;
+ int last_vert = -1;
+ for(int i=0; i<n_tri_indices; ++i){
+ if(used_verts[i] != last_vert){
+ used_verts[n_used_verts] = used_verts[i];
+ last_vert = used_verts[i];
+ ++n_used_verts;
+ }
+ }
+
+ // Create an array storing the coordinates and ids of every used vertex,
+ // and then sort it so duplicates are next to each other.
+ SortableVert *sort_verts = new SortableVert[n_used_verts];
+
+ for(int i=0; i<n_used_verts; ++i){
+ rcVcopy(sort_verts[i].coords, &verts_in[used_verts[i]*3]);
+ sort_verts[i].used_id = i;
+ sort_verts[i].true_id = used_verts[i];
+ }
+
+ qsort(sort_verts, n_used_verts, sizeof(SortableVert), compareSortableVert);
+
+ // Create an array storing the id of the first of any set of duplicate
+ // vertices. For example, if vertices 6 and 15 are duplicates, then
+ // base_vert[6] == 6 and base_vert[15] == 6.
+ int *base_vert = new int[n_used_verts];
+
+ int num_merged = 0;
+ base_vert[sort_verts[0].used_id] = sort_verts[0].true_id;
+ for(int i=1; i<n_used_verts; ++i){
+ if(rcVequal(sort_verts[i].coords, sort_verts[i-1].coords)){
+ base_vert[sort_verts[i].used_id] = base_vert[sort_verts[i-1].used_id];
+ ++num_merged;
+ } else {
+ base_vert[sort_verts[i].used_id] = sort_verts[i].true_id;
+ }
+ }
+
+ IntTuple* tri_remap = new IntTuple[n_tri_indices];
+ for(int i=0; i<n_tri_indices; ++i){
+ tri_remap[i].val[0] = tris_in[i];
+ tri_remap[i].val[1] = i;
+ }
+
+ qsort(tri_remap, n_tri_indices, sizeof(IntTuple), compareTupleFirst);
+ int a_index = 0;
+ int b_index = 0;
+ while(a_index < n_tri_indices && b_index < n_used_verts){
+ if(tri_remap[a_index].val[0] < used_verts[b_index]){
+ ++a_index;
+ } else if(tri_remap[a_index].val[0] > used_verts[b_index]){
+ ++b_index;
+ } else {
+ tri_remap[a_index].val[0] = b_index;
+ ++a_index;
+ }
+ }
+ qsort(tri_remap, n_tri_indices, sizeof(IntTuple), compareTupleSecond);
+
+ for(int i=0; i<n_tri_indices; ++i){
+ tris_out[i] = base_vert[tri_remap[i].val[0]];
+ }
+
+ delete[] sort_verts;
+ delete[] used_verts;
+ delete[] tri_remap;
+ delete[] base_vert;
+ }
+}
+
+void NavMesh::AddMesh( const vector<float>& vertices, const vector<unsigned>& faces, const mat4 &transform ) {
+ size_t face_index_offset = vertices_.size()/3;
+ vector<int> merge_tris(faces.size());
+ NavMeshTemp::MergeOverlappingVerts(&vertices[0], (int)vertices.size()/3, &faces[0], (int)faces.size()/3, &merge_tris[0]);
+ // Add transformed vertices to vertices_
+ vertices_.reserve(vertices_.size() + vertices.size());
+ for(size_t i=0, len=vertices.size(); i<len; i+=3){
+ vec3 temp;
+ for(int j=0; j<3; ++j){
+ temp[j] = vertices[i+j];
+ }
+ temp = transform * temp;
+ for(int j=0; j<3; ++j){
+ vertices_.push_back(temp[j]);
+ }
+ }
+ // Add offset faces
+ faces_.reserve(faces_.size() + faces.size());
+ for(size_t i=0; i<faces.size(); i++){
+ faces_.push_back((uint32_t)(merge_tris[i] + face_index_offset));
+ }
+}
+
+void NavMesh::SetNavMeshParameters(NavMeshParameters& nmp) {
+ nav_mesh_parameters_ = nmp;
+}
+
+void NavMesh::Update() {
+ Mouse &mouse = Input::Instance()->getMouse();
+ Keyboard &keyboard = Input::Instance()->getKeyboard();
+ if(keyboard.isKeycodeDown(SDLK_CTRL, KIMF_LEVEL_EDITOR_GENERAL) && (mouse.mouse_down_[Mouse::LEFT] == 1 || mouse.mouse_down_[Mouse::RIGHT] == 1)) {
+ bool shift = (mouse.mouse_down_[Mouse::RIGHT] == 1);
+ vec3 start = ActiveCameras::Get()->GetPos();
+ vec3 end = start + ActiveCameras::Get()->GetMouseRay()*1000.0f;
+ float rays[] = {start[0], start[1], start[2]};
+ float raye[] = {end[0], end[1], end[2]};
+ float t;
+ if (geom_.raycastMesh(rays, raye, t)){
+ float pos[3];
+ pos[0] = rays[0] + (raye[0] - rays[0])*t;
+ pos[1] = rays[1] + (raye[1] - rays[1])*t;
+ pos[2] = rays[2] + (raye[2] - rays[2])*t;
+ //nav_mesh_tester_.handleClick(NULL, pos, shift);
+
+ if(!shift){
+ m_start_ = vec3(pos[0], pos[1], pos[2]);
+ } else {
+ m_end_ = vec3(pos[0], pos[1], pos[2]);
+ }
+
+ NavPath path = FindPath(m_start_, m_end_, SAMPLE_POLYFLAGS_ALL, SAMPLE_POLYFLAGS_NONE);
+ path_points_ = path.waypoints;
+ }
+ }
+}
+
+void WriteObj(string path,
+ vector<float> vertices,
+ vector<unsigned> faces)
+{
+ CreateParentDirs(path.c_str()); //the Levels folder in users home folder might be missing.
+ ofstream file;
+ my_ofstream_open(file, path.c_str());
+
+ for(unsigned i=0; i<vertices.size(); i+=3){
+ file << "v " << vertices[i+0] << " "
+ << vertices[i+1] << " "
+ << vertices[i+2] << "\n";
+ }
+ for(unsigned i=0; i<faces.size(); i+=3){
+ file << "f " << (int)faces[i+0]+1 << " "
+ << (int)faces[i+1]+1 << " "
+ << (int)faces[i+2]+1 << "\n";
+ }
+
+ file.close();
+}
+
+namespace {
+ // see http://fgiesen.wordpress.com/2013/02/08/triangle-rasterization-in-practice/
+ /*
+ float orient2d(const float *a, const float *b, const float *c) {
+ return (b[0]-a[0])*(c[1]-a[1]) - (b[1]-a[1])*(c[0]-a[0]);
+ }
+ */
+
+ bool Convex2DCollide(const vector<vec2> points[2], const vector<vec2> &axes) {
+ bool intersects = true;
+ for(size_t i=0, len=axes.size(); i<len; ++i){
+ const vec2& axis = axes[i];
+ float proj_bounds[2][2];
+ for(size_t i=0; i<2; ++i){
+ const vector<vec2> &point_vec = points[i];
+ for(size_t j=0, len=point_vec.size(); j<len; ++j){
+ float proj = dot(point_vec[j], axis);
+ if(j==0 || proj < proj_bounds[i][0]){
+ proj_bounds[i][0] = proj;
+ }
+ if(j==0 || proj > proj_bounds[i][1]){
+ proj_bounds[i][1] = proj;
+ }
+ }
+ }
+ if(proj_bounds[0][1] < proj_bounds[1][0] || proj_bounds[0][0] > proj_bounds[1][1]){
+ intersects = false;
+ }
+ }
+ return intersects;
+ }
+
+ void ClipPolygon(vector<vec3> *points_a, vector<vec3> *points_b, vector<vec3> plane_normals, vector<float> plane_ds){
+ vector<vec3> *old_points = points_a;
+ vector<vec3> *new_points = points_b;
+ for(size_t i=0, len=plane_normals.size(); i<len; ++i){
+ const vec3& plane_normal = plane_normals[i];
+ float plane_d = plane_ds[i];
+ new_points->clear();
+ for(size_t j=0, len=old_points->size(); j<len; ++j){
+ const vec3& point = old_points->at(j);
+ const vec3& next_point = old_points->at((j+1)%len);
+ float plane_offset = dot(point, plane_normal)-plane_d;
+ float next_plane_offset = dot(next_point, plane_normal)-plane_d;
+ if(plane_offset > 0) {
+ new_points->push_back(point);
+ }
+ if((plane_offset > 0) != (next_plane_offset > 0)){
+ float t = (plane_offset) / (plane_offset - next_plane_offset);
+ vec3 new_point = mix(point, next_point, t);
+ new_points->push_back(new_point);
+ }
+ }
+ swap(new_points, old_points);
+ }
+ swap(new_points, old_points);
+ if(new_points != points_b){
+ *points_b = *new_points;
+ }
+ }
+
+ void AddVoxelDraw(const vec3 &pos, vector<float> *debug_line_vertices, vector<unsigned> *debug_line_indices, float size) {
+ for(int j=0; j<3; ++j){
+ debug_line_indices->push_back((uint32_t)debug_line_vertices->size()/3);
+ for(int i=0; i<3; ++i){
+ debug_line_vertices->push_back(pos[i]);
+ }
+ debug_line_indices->push_back((uint32_t)debug_line_vertices->size()/3);
+ for(int i=0; i<3; ++i){
+ if(i!=j){
+ debug_line_vertices->push_back(pos[i]);
+ } else {
+ debug_line_vertices->push_back(pos[i] + size);
+ }
+ }
+ }
+ }
+
+ struct ColumnEntry {
+ int tri;
+ int y;
+ };
+} //namespace ""
+
+//This function is only for vizualization purpose, it's not used for logic.
+void VoxellizeMesh(const vector<unsigned int> &faces, const vector<float> &vertices){
+ vector<float> debug_line_vertices;
+ vector<unsigned> debug_line_indices;
+ if(vertices.size() < 3 || faces.size() < 3){
+ DisplayError("Error","Invalid input to VoxellizeMesh");
+ }
+ // Calculate bounding box for every vertex
+ float bounds[6];
+ if(vertices.size() >= 3){
+ for(int i=0; i<3; ++i){
+ bounds[i] = vertices[i];
+ bounds[i+3] = vertices[i];
+ }
+ }
+ for(size_t i=3, len=vertices.size(); i<len; ++i){
+ size_t axis = i%3;
+ bounds[axis] = min(vertices[i], bounds[axis]);
+ bounds[axis+3] = max(vertices[i], bounds[axis+3]);
+ }
+ // Calculate voxel dimensions
+ int voxel_dim[3];
+ static const float VOXEL_SIZE = 0.5f;
+ for(int i=0; i<3; ++i){
+ voxel_dim[i] = (int)ceil((bounds[i+3]-bounds[i])/VOXEL_SIZE)+1;
+ }
+ // Create space for column lists
+ typedef list<ColumnEntry> ColumnEntryList;
+ vector<ColumnEntryList> columns(voxel_dim[0] * voxel_dim[2]);
+ // Rasterize triangles
+ for(size_t face_index=0, len=faces.size(); face_index<len; face_index+=3){
+ // Get pointers for quick access to tri vertices
+ const float* vert[3];
+ for(int j=0; j<3; ++j){
+ vert[j] = &vertices[faces[face_index+j]*3];
+ }
+ // Get bounding box of triangle
+ float tri_bounds[6];
+ for(int j=0; j<3; ++j){
+ tri_bounds[j] = vert[0][j];
+ tri_bounds[j+3] = vert[0][j];
+ }
+ for(int k=1; k<3; ++k){
+ for(int j=0; j<3; ++j){
+ tri_bounds[j] = min(tri_bounds[j], vert[k][j]);
+ tri_bounds[j+3] = max(tri_bounds[j+3], vert[k][j]);
+ }
+ }
+ // Get triangle boundaries in voxel coordinates
+ int vox_bounds[6];
+ for(int j=0; j<3; ++j){
+ vox_bounds[j] = (int)((tri_bounds[j]-bounds[j])/VOXEL_SIZE);
+ vox_bounds[j+3] = (int)((tri_bounds[j+3]-bounds[j])/VOXEL_SIZE);
+ }
+ // Rasterize triangle into voxels
+ vector<vec2> points[2];
+ vector<vec2> axes;
+ vector<vec3> old_points;
+ vector<vec3> new_points;
+ for(int vox_z = vox_bounds[2]; vox_z <= vox_bounds[5]; ++vox_z){
+ for(int vox_x = vox_bounds[0]; vox_x <= vox_bounds[3]; ++vox_x){
+ // Does column intersect triangle?
+ points[0].clear();
+ for(int i=0; i<4; ++i){
+ points[0].push_back(vec2((vox_x+i%2)*VOXEL_SIZE+bounds[0], (vox_z+i/2)*VOXEL_SIZE+bounds[2]));
+ }
+ points[1].clear();
+ for(int i=0; i<3; ++i){
+ points[1].push_back(vec2(vert[i][0], vert[i][2]));
+ }
+ axes.clear();
+ axes.push_back(vec2(1,0));
+ axes.push_back(vec2(0,1));
+ // Add axes perpendicular to each triangle side
+ for(int i=0; i<3; ++i){
+ vec2 temp = points[1][(i+1)%3]-points[1][i];
+ axes.push_back(vec2(-temp[1], temp[0]));
+ }
+ if(Convex2DCollide(points, axes)){
+ old_points.clear();
+ new_points.clear();
+ for(int i=0; i<3; ++i){
+ old_points.push_back(vec3(vert[i][0], vert[i][1], vert[i][2]));
+ }
+ vector<vec3> plane_normal;
+ vector<float> plane_d;
+ plane_normal.push_back(vec3(1,0,0));
+ plane_d.push_back((vox_x)*VOXEL_SIZE+bounds[0]);
+ plane_normal.push_back(vec3(-1,0,0));
+ plane_d.push_back(((vox_x+1)*VOXEL_SIZE+bounds[0])*-1.0f);
+ plane_normal.push_back(vec3(0,0,1));
+ plane_d.push_back((vox_z)*VOXEL_SIZE+bounds[2]);
+ plane_normal.push_back(vec3(0,0,-1));
+ plane_d.push_back(((vox_z+1)*VOXEL_SIZE+bounds[2])*-1.0f);
+ ClipPolygon(&old_points, &new_points, plane_normal, plane_d);
+ if(!new_points.empty()){
+ float clip_tri_bounds[2] = {0.0f,0.0f};
+ for(size_t i=0, len=new_points.size(); i<len; ++i){
+ float y = new_points[i][1];
+ if(i==0 || y < clip_tri_bounds[0]){
+ clip_tri_bounds[0] = y;
+ }
+ if(i==0 || y > clip_tri_bounds[1]){
+ clip_tri_bounds[1] = y;
+ }
+ }
+ int clip_tri_vox_bounds[2];
+ clip_tri_vox_bounds[0] = (int)((clip_tri_bounds[0]-bounds[1])/VOXEL_SIZE);
+ clip_tri_vox_bounds[1] = (int)((clip_tri_bounds[1]-bounds[1])/VOXEL_SIZE);
+ int column_index = vox_z * voxel_dim[0] + vox_x;
+ for(int i=0; i<1; ++i){
+ ColumnEntry entry;
+ entry.tri = -1;
+ entry.y = clip_tri_vox_bounds[i];
+ columns[column_index].push_back(entry);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Draw voxels
+ for(int vox_z = 0; vox_z < voxel_dim[2]; ++vox_z){
+ for(int vox_x = 0; vox_x < voxel_dim[0]; ++vox_x){
+ int column_index = vox_z * voxel_dim[0] + vox_x;
+ ColumnEntryList list = columns[column_index];
+ for(ColumnEntryList::iterator iter = list.begin(); iter != list.end(); ++iter){
+ const ColumnEntry &entry = *iter;
+ vec3 pos;
+ pos[0] = vox_x * VOXEL_SIZE + bounds[0];
+ pos[1] = entry.y * VOXEL_SIZE + bounds[1];
+ pos[2] = vox_z * VOXEL_SIZE + bounds[2];
+ AddVoxelDraw(pos, &debug_line_vertices, &debug_line_indices, VOXEL_SIZE);
+ }
+ }
+ }
+ if(!debug_line_indices.empty()){
+ DebugDraw::Instance()->AddLines(debug_line_vertices, debug_line_indices, vec4(1.0f), _persistent, _DD_NO_FLAG);
+ }
+}
+
+void NavMesh::CalcNavMesh() {
+ //VoxellizeMesh(faces_, vertices_);
+ //return;
+
+ LOGI << "Navmesh: Writing out object..." << endl;
+ char path[kPathSize];
+ FormatString(path, kPathSize, "%sData/Temp/navmesh.obj", GetWritePath(CoreGameModID).c_str());
+ WriteObj(path, vertices_, faces_);
+ //CalcFaceNormals(face_normals_);
+
+ BuildContext ctx;
+ LOGI << "Navmesh: Loading object into geom..." << endl;
+ geom_.loadMesh(&ctx, path);
+
+ sample_tile_mesh_.applySettings(nav_mesh_parameters_);
+ sample_tile_mesh_.setContext(&ctx);
+ LOGI << "Navmesh: Adding mesh to sample..." << endl;
+ sample_tile_mesh_.handleMeshChanged(&geom_);
+
+ sample_tile_mesh_.handleSettings();
+
+ LOGI << "Navmesh: Building sample..." << endl;
+ sample_tile_mesh_.handleBuild();
+
+ LOGI << "Navmesh: Done building sample..." << endl;
+
+ //nav_mesh_tester_.init(&sample_tile_mesh_);
+ loaded_ = true;
+
+}
+
+void NavMesh::Save( const string &level_name, const Path &level_path )
+{
+
+ LOGW << level_name << endl;
+
+ if(!loaded_) {
+ return;
+ }
+
+ string nav_path = GenerateParallelPath("Data/Levels", "Data/LevelNavmeshes", ".nav", level_path);
+
+ if(config["allow_game_dir_save"].toBool() == false) {
+ nav_path = AssemblePath(GetWritePath(level_path.GetModsource()),nav_path);
+ }
+
+ char zip_path[kPathSize];
+ FormatString(zip_path, kPathSize, "%s.zip", nav_path.c_str());
+
+ char in_zip_file_name[kPathSize];
+ FormatString(in_zip_file_name, kPathSize, "%s.zip", level_name.c_str());
+
+ LOGI << "Saving nav mesh to " << zip_path << endl;
+
+ string temp_navmesh_path = AssemblePath(GetWritePath(CoreGameModID),"Data/Temp/navmesh.nav");
+
+ CreateParentDirs(temp_navmesh_path);
+
+ sample_tile_mesh_.Save(temp_navmesh_path.c_str());
+
+ CreateParentDirs(zip_path);
+
+ Zip(temp_navmesh_path, string(zip_path), in_zip_file_name, _YES_OVERWRITE);
+
+ if(!vertices_.empty() && !faces_.empty()) {
+ char model_path[kPathSize];
+ FormatString(model_path, kPathSize, "%s.obj", nav_path.c_str());
+ WriteObj(model_path, vertices_, faces_);
+ }
+
+ char meta_path[kPathSize];
+ FormatString(meta_path, kPathSize, "%s.xml", nav_path.c_str());
+
+ {
+ TiXmlDocument meta_doc;
+
+ TiXmlDeclaration decl( "2.0", "", "" );
+
+ TiXmlElement meta_root("NavMeshMeta");
+
+ {
+ TiXmlElement e("Level");
+ TiXmlText et(level_name.c_str());
+ e.InsertEndChild(et);
+ meta_root.InsertEndChild(e);
+ }
+
+ {
+ TiXmlElement e("IgnoreHash");
+ TiXmlText et("false");
+ e.InsertEndChild(et);
+ meta_root.InsertEndChild(e);
+ }
+
+ {
+ TiXmlElement e("LevelHash");
+
+ FileHashAssetRef fha = Engine::Instance()->GetAssetManager()->LoadSync<FileHashAsset>(level_path.GetOriginalPath());
+ TiXmlText et(fha->hash.ToString());
+ e.InsertEndChild(et);
+
+ meta_root.InsertEndChild(e);
+ }
+
+ {
+ TiXmlElement e("IgnoreVersion");
+ TiXmlText et("false");
+ e.InsertEndChild(et);
+ meta_root.InsertEndChild(e);
+ }
+
+ {
+ TiXmlElement e("Version");
+ TiXmlText et(NAVMESH_VERSION);
+ e.InsertEndChild(et);
+ meta_root.InsertEndChild(e);
+ }
+
+ meta_doc.InsertEndChild(decl);
+ meta_doc.InsertEndChild(meta_root);
+
+ meta_doc.SaveFile(meta_path);
+ }
+}
+
+bool NavMesh::Load( const string &level_name, const Path &level_path ) {
+ rcContext ctx;
+ char rel_model_path[kPathSize];
+ char rel_nav_path[kPathSize];
+ char rel_meta_path[kPathSize];
+ char rel_zip_path[kPathSize];
+
+ char abs_model_path[kPathSize];
+ char abs_nav_path[kPathSize];
+ char abs_meta_path[kPathSize];
+ char abs_zip_path[kPathSize];
+
+ string base_path = GenerateParallelPath("Data/Levels", "Data/LevelNavmeshes", ".nav", level_path );
+
+ FormatString(rel_model_path, kPathSize, "%s.obj", base_path.c_str());
+ FormatString(rel_nav_path, kPathSize, "%s", base_path.c_str());
+ FormatString(rel_meta_path, kPathSize, "%s.xml", base_path.c_str());
+ FormatString(rel_zip_path, kPathSize, "%s.zip", base_path.c_str());
+
+ bool valid_path_group = true;
+ bool has_zip = false;
+
+ if(FindFilePath(rel_meta_path, abs_meta_path, kPathSize, kWriteDir|kModWriteDirs, false) == -1)
+ {
+ LOGE << "Missing file for navmesh " << abs_meta_path << endl;
+ valid_path_group = false;
+ } else {
+ LOGI << "Found:" << abs_meta_path << endl;
+ }
+
+ if(FindFilePath(rel_model_path, abs_model_path, kPathSize, kWriteDir|kModWriteDirs, false) == -1)
+ {
+ LOGI << "Missing file for navmesh " << abs_model_path << endl;
+ //return false;
+ } else {
+ LOGI << "Found:" << abs_model_path << endl;
+ }
+
+ if(FindFilePath(rel_zip_path, abs_zip_path, kPathSize, kWriteDir|kModWriteDirs, false) == -1)
+ {
+ has_zip = false;
+ } else {
+ has_zip = true;
+ LOGI << "Found Zip:" << abs_zip_path << endl;
+ }
+
+ if(FindFilePath(rel_nav_path, abs_nav_path, kPathSize, kWriteDir|kModWriteDirs, false) == -1)
+ {
+ if( has_zip == false ) {
+ LOGE << "Missing file for navmesh " << abs_nav_path << endl;
+ valid_path_group = false;
+ }
+ } else {
+ LOGI << "Found:" << abs_nav_path << endl;
+ }
+
+ if(valid_path_group) {
+ if(LoadFromAbs(level_path,abs_meta_path,abs_model_path,abs_nav_path,abs_zip_path,has_zip)) {
+ return true;
+ }
+ }
+
+ has_zip = false;
+ valid_path_group = true;
+
+ if(FindFilePath(rel_meta_path, abs_meta_path, kPathSize, kModPaths|kDataPaths, false) == -1)
+ {
+ LOGE << "Missing file for navmesh " << abs_meta_path << endl;
+ valid_path_group = false;
+ } else {
+ LOGI << "Found:" << abs_meta_path << endl;
+ }
+
+ if(FindFilePath(rel_model_path, abs_model_path, kPathSize, kModPaths|kDataPaths, false) == -1)
+ {
+ LOGI << "Missing file for navmesh " << abs_model_path << endl;
+ //return false;
+ } else {
+ LOGI << "Found:" << abs_model_path << endl;
+ }
+
+ if(FindFilePath(rel_zip_path, abs_zip_path, kPathSize, kModPaths|kDataPaths, false) == -1)
+ {
+ has_zip = false;
+ } else {
+ has_zip = true;
+ LOGI << "Found Zip:" << abs_zip_path << endl;
+ }
+
+ if(FindFilePath(rel_nav_path, abs_nav_path, kPathSize, kModPaths|kDataPaths, false) == -1)
+ {
+ if( has_zip == false ) {
+ LOGE << "Missing file for navmesh " << abs_nav_path << endl;
+ valid_path_group = false;
+ }
+ } else {
+ LOGI << "Found:" << abs_nav_path << endl;
+ }
+
+ if(valid_path_group) {
+ if(LoadFromAbs(level_path,abs_meta_path,abs_model_path,abs_nav_path,abs_zip_path,has_zip)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool NavMesh::LoadFromAbs(const Path& level_path, const char* abs_meta_path, const char* abs_model_path, const char * abs_nav_path, const char* abs_zip_path, bool has_zip ) {
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Loading XML file");
+ TiXmlDocument doc( abs_meta_path );
+ doc.LoadFile();
+
+ CommonRegex cr;
+
+ TiXmlElement* pRoot = doc.RootElement();
+
+ if( pRoot )
+ {
+ TiXmlHandle hRoot(pRoot);
+
+ TiXmlElement* pIgnoreVersion = hRoot.FirstChild("IgnoreVersion").Element();
+ if( pIgnoreVersion )
+ {
+ string ignoreversion = pIgnoreVersion->GetText();
+
+ if(cr.saysFalse(ignoreversion))
+ {
+ TiXmlElement* pVersion = hRoot.FirstChild("Version").Element();
+
+ if( pVersion )
+ {
+ string version = pVersion->GetText();
+
+ //If the version for the navmesh mismatches, recalculate
+ if( version != string( NAVMESH_VERSION ) )
+ {
+ LOGW << "Mismatching navmesh version" << endl;
+ return false;
+ }
+ }
+ else
+ {
+ LOGE << "Missing Version" << endl;
+ return false;
+ }
+ }
+ else if( cr.saysTrue(ignoreversion) )
+ {
+ //Do nothing continue with loading
+ }
+ else
+ {
+ LOGE << "Value in IgnoreVersion in file \"" << abs_meta_path << "\" is invalid." << endl;
+ return false;
+ }
+ }
+ else
+ {
+ LOGE << "Missing IgnoreVersion" << endl;
+ return false;
+ }
+
+ TiXmlElement* pIgnoreHash = hRoot.FirstChild("IgnoreHash").Element();
+
+ if( pIgnoreHash )
+ {
+ string ignorehash = pIgnoreHash->GetText();
+
+ if(cr.saysFalse(ignorehash))
+ {
+ TiXmlElement* pLevelHash = hRoot.FirstChild("LevelHash").Element();
+ if( pLevelHash )
+ {
+ string levelHash = pLevelHash->GetText();
+ FileHashAssetRef file_hash = Engine::Instance()->GetAssetManager()->LoadSync<FileHashAsset>(level_path.GetOriginalPath());
+ if(levelHash != file_hash->hash.ToString())
+ {
+ LOGW << "Mismatching LevelHash in navmesh" << endl;
+ return false;
+ }
+ }
+ else
+ {
+ LOGE << "Missing LevelHash" << endl;
+ return false;
+ }
+ }
+ else if(cr.saysTrue( ignorehash ))
+ {
+ //Do nothing, just continue with loading.
+ }
+ else
+ {
+ LOGE << "Value in IgnoreHash in file \"" << abs_meta_path << "\" is invalid." << endl;
+ return false;
+ }
+ }
+ else
+ {
+ LOGE << "Missing IgnoreHash" << endl;
+ return false;
+ }
+ }
+ else
+ {
+ LOGE << "Error parsing meta file for \"" << abs_meta_path << "\"" << endl;
+ return false;
+ }
+ }
+
+ {
+ //PROFILER_ZONE(g_profiler_ctx, "Loading mesh");
+ //geom_.loadMesh(&ctx, abs_model_path);
+ }
+ LOGI << "Navmesh: Adding mesh to sample..." << endl;
+ {
+ PROFILER_ZONE(g_profiler_ctx, "handleMeshChanged");
+ sample_tile_mesh_.handleMeshChanged(&geom_);
+ }
+ {
+ if( has_zip ) {
+ ExpandedZipFile ezf;
+ UnZip(abs_zip_path,ezf);
+
+ if( ezf.GetNumEntries() == 1 ) {
+ const char* filename;
+ const char* data;
+ unsigned size;
+ ezf.GetEntry(0,filename,data,size);
+
+ if( data && size > 0 ) {
+ sample_tile_mesh_.LoadMem(data,size);
+ } else {
+ LOGE << "Zip file data or size is NULL/zero" << endl;
+ }
+ } else {
+ LOGE << "Zip contains more than one item, this is unexpected" << endl;
+ }
+
+ } else {
+ PROFILER_ZONE(g_profiler_ctx, "Load");
+ sample_tile_mesh_.Load(abs_nav_path);
+ }
+
+ }
+ //nav_mesh_tester_.init(&sample_tile_mesh_);
+ loaded_ = true;
+ return true;
+
+}
+
+NavPath NavMesh::FindPath( const vec3 &start, const vec3 &end, uint16_t include_filter, uint16_t exclude_filter ) {
+ NavPath nav_path;
+ nav_path.success = false;
+ if(!loaded_){
+ nav_path.waypoints.push_back(end);
+ nav_path.success = true;
+ return nav_path;
+ }
+
+ const float extents[] = {2.0f,4.0f,2.0f};
+
+ dtQueryFilter filter;
+ filter.setIncludeFlags(include_filter);
+ filter.setExcludeFlags(exclude_filter);
+
+ dtNavMeshQuery* mesh = sample_tile_mesh_.getNavMeshQuery();
+ dtPolyRef start_poly_ref;
+ dtStatus status;
+ status = mesh->findNearestPoly(start.entries,
+ extents,
+ &filter,
+ &start_poly_ref,
+ 0);
+
+ dtPolyRef end_poly_ref;
+ status = mesh->findNearestPoly(end.entries,
+ extents,
+ &filter,
+ &end_poly_ref,
+ 0);
+
+ if (start_poly_ref && end_poly_ref) {
+ static const int MAX_POLYS = 256;
+ dtPolyRef polys[MAX_POLYS];
+ int num_path_polys;
+ status = mesh->findPath(start_poly_ref,
+ end_poly_ref,
+ start.entries,
+ end.entries,
+ &filter,
+ polys,
+ &num_path_polys,
+ MAX_POLYS);
+ float straight_path_points[MAX_POLYS*3];
+ unsigned char straight_path_flags[MAX_POLYS];
+ dtPolyRef straight_path_polys[MAX_POLYS];
+
+ if (dtStatusSucceed(status) && num_path_polys) {
+ int straight_path_length;
+ status = mesh->findStraightPath(start.entries,
+ end.entries,
+ polys,
+ num_path_polys,
+ straight_path_points,
+ straight_path_flags,
+ straight_path_polys,
+ &straight_path_length,
+ MAX_POLYS);
+ if(straight_path_length){
+ nav_path.waypoints.resize(straight_path_length);
+
+ unsigned index = 0;
+ for(int i=0; i<straight_path_length; ++i){
+ nav_path.waypoints[i][0] = straight_path_points[index++];
+ nav_path.waypoints[i][1] = straight_path_points[index++];
+ nav_path.waypoints[i][2] = straight_path_points[index++];
+
+ nav_path.flags.push_back(straight_path_flags[i]);
+ }
+ }
+ if(dtStatusSucceed(status)){
+ nav_path.success = true;
+ }
+ if(dtStatusDetail(status, DT_PARTIAL_RESULT)){
+ nav_path.success = false;
+ }
+ if(polys[num_path_polys-1] != end_poly_ref){
+ nav_path.success = false;
+ }
+ }
+ }
+
+ return nav_path;
+}
+
+vec3 NavMesh::RayCast( const vec3 &start, const vec3 &end ) {
+ if(!loaded_){
+ return vec3(0.0f);
+ }
+
+ const float extents[] = {2.0f,4.0f,2.0f};
+
+ dtQueryFilter filter;
+ filter.setIncludeFlags(SAMPLE_POLYFLAGS_ALL);
+ filter.setExcludeFlags(0);
+
+ dtNavMeshQuery* mesh = sample_tile_mesh_.getNavMeshQuery();
+ dtPolyRef start_poly_ref;
+ dtStatus status;
+ status = mesh->findNearestPoly(start.entries, extents, &filter, &start_poly_ref,0);
+
+ if(!start_poly_ref){
+ return vec3(0.0f);
+ }
+ static const int MAX_POLYS = 256;
+ dtPolyRef polys[MAX_POLYS];
+ int num_path_polys;
+
+ float t_val;
+ vec3 hit_normal;
+ status = mesh->raycast(start_poly_ref,
+ start.entries,
+ end.entries,
+ &filter,
+ &t_val,
+ hit_normal.entries,
+ polys,
+ &num_path_polys,
+ MAX_POLYS);
+
+ if(status != DT_SUCCESS){
+ return vec3(0.0f);
+ }
+
+
+ vec3 result;
+ if(t_val == FLT_MAX){
+ result = end;
+ } else {
+ result = mix(start, end, t_val);
+ }
+
+ float height;
+ status = mesh->getPolyHeight(polys[num_path_polys-1], result.entries, &height);
+ if(status == DT_SUCCESS){
+ result[1] = height;
+ }
+ return result;
+
+ /*vec3 result;
+ status = mesh->moveAlongSurface(start_poly_ref, start.entries, end.entries, &filter, result.entries, polys, &num_path_polys, MAX_POLYS);
+ return result;*/
+}
+
+vec3 NavMesh::RayCastSlide( const vec3 &start, const vec3 &end, int depth ) {
+ if(!loaded_){
+ return vec3(0.0f);
+ }
+
+ const float extents[] = {2.0f,4.0f,2.0f};
+
+ dtQueryFilter filter;
+ filter.setIncludeFlags(SAMPLE_POLYFLAGS_ALL);
+ filter.setExcludeFlags(0);
+
+ dtNavMeshQuery* mesh = sample_tile_mesh_.getNavMeshQuery();
+ dtPolyRef start_poly_ref;
+ dtStatus status;
+ status = mesh->findNearestPoly(start.entries, extents, &filter, &start_poly_ref,0);
+
+ if(!start_poly_ref){
+ return vec3(0.0f);
+ }
+ static const int MAX_POLYS = 256;
+ dtPolyRef polys[MAX_POLYS];
+ int num_path_polys;
+
+ float t_val;
+ vec3 hit_normal;
+ status = mesh->raycast(start_poly_ref,
+ start.entries,
+ end.entries,
+ &filter,
+ &t_val,
+ hit_normal.entries,
+ polys,
+ &num_path_polys,
+ MAX_POLYS);
+
+ if(status != DT_SUCCESS){
+ return vec3(0.0f);
+ }
+
+ vec3 result;
+ if(t_val == FLT_MAX){
+ result = end;
+ } else {
+ result = mix(start, end, t_val);
+ float wall_d = dot(hit_normal, result);
+ float end_d = dot(hit_normal, end);
+ vec3 slide_result = end + hit_normal * (wall_d - end_d);
+ if(depth > 0){
+ return RayCastSlide(result, slide_result, depth - 1);
+ }
+ }
+
+ float height;
+ status = mesh->getPolyHeight(polys[num_path_polys-1], result.entries, &height);
+ if(status == DT_SUCCESS){
+ result[1] = height;
+ }
+ return result;
+
+ /*vec3 result;
+ status = mesh->moveAlongSurface(start_poly_ref, start.entries, end.entries, &filter, result.entries, polys, &num_path_polys, MAX_POLYS);
+ return result;*/
+}
+
+NavPoint NavMesh::GetNavPoint( const vec3 &point ) {
+ if(!loaded_){
+ return NavPoint();
+ }
+
+ const float extents[] = {2.0f,4.0f,2.0f};
+ const float second_extents[] = {4.0f,16.0f,4.0f};
+
+ dtQueryFilter filter;
+ filter.setIncludeFlags(SAMPLE_POLYFLAGS_ALL);
+ filter.setExcludeFlags(0);
+
+ dtNavMeshQuery* mesh = sample_tile_mesh_.getNavMeshQuery();
+ dtPolyRef start_poly_ref;
+ dtStatus status;
+ status = mesh->findNearestPoly(point.entries, extents, &filter, &start_poly_ref, 0);
+
+ if(status != DT_SUCCESS){
+ status = mesh->findNearestPoly(point.entries, second_extents, &filter, &start_poly_ref, 0);
+ if(status != DT_SUCCESS){
+ return NavPoint();
+ }
+ }
+
+ vec3 output;
+ bool isOverPoly;
+ status = mesh->closestPointOnPoly(start_poly_ref, point.entries, output.entries, &isOverPoly);
+
+ if(status == DT_SUCCESS)
+ {
+ return NavPoint(output);
+ }
+ else
+ {
+ return NavPoint(output, false);
+ }
+}
+
+const rcMeshLoaderObj* NavMesh::getCollisionMesh() const
+{
+ return geom_.getMesh();
+}
+
+const dtNavMesh* NavMesh::getNavMesh() const
+{
+ return sample_tile_mesh_.getNavMesh();
+}
+
+const rcPolyMesh* NavMesh::getPolyMesh() const
+{
+ return sample_tile_mesh_.getPolyMesh();
+}
+
+InputGeom &NavMesh::getInputGeom()
+{
+ return geom_;
+}
+
+void NavMesh::SetExplicitBounderies(vec3 min, vec3 max )
+{
+ sample_tile_mesh_.SetExplicitBounderies(min,max);
+}
+
+unsigned short NavMesh::GetOffMeshConnectionFlag( int userid )
+{
+ unsigned short ret = 0U;
+
+ const dtNavMesh* mesh = sample_tile_mesh_.getNavMesh();
+
+ if( mesh )
+ {
+ for (int i = 0; i < mesh->getMaxTiles(); ++i)
+ {
+ const dtMeshTile* tile = mesh->getTile(i);
+ if (!tile->header) continue;
+
+ for (int j = 0; j < tile->header->offMeshConCount; j++ )
+ {
+ const dtOffMeshConnection* off_mesh_con = &tile->offMeshCons[j];
+
+ if( off_mesh_con )
+ {
+ if( (int)off_mesh_con->userId == userid )
+ {
+ dtPoly *mesh_poly = &tile->polys[off_mesh_con->poly];
+
+ if( mesh_poly && mesh_poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION)
+ {
+ return mesh_poly->flags;
+ }
+ else
+ {
+ LOGE << "Unable to get the poly associated with offmesh connection" << endl;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+}
diff --git a/Source/AI/navmesh.h b/Source/AI/navmesh.h
new file mode 100644
index 00000000..e10d1524
--- /dev/null
+++ b/Source/AI/navmesh.h
@@ -0,0 +1,101 @@
+//-----------------------------------------------------------------------------
+// Name: navmesh.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <AI/pathfind.h>
+#include <AI/tilemesh.h>
+#include <AI/input_geom.h>
+#include <AI/navmeshparameters.h>
+
+#include <Math/mat4.h>
+#include <Math/vec3.h>
+
+#include <Internal/path.h>
+
+#include <vector>
+#include <string>
+
+using std::vector;
+using std::string;
+
+class rcMeshLoaderObj;
+
+class NavPoint
+{
+private:
+ vec3 pos;
+ bool valid;
+public:
+ NavPoint( vec3 pos );
+ NavPoint( vec3 pos, bool success );
+ NavPoint( );
+ NavPoint( const NavPoint& other );
+
+ vec3 GetPoint();
+ bool IsSuccess();
+};
+
+class NavMesh {
+public:
+ NavMesh():loaded_(false){}
+ void AddMesh( const vector<float>& vertices, const vector<unsigned>& faces, const mat4 &transform );
+ void SetNavMeshParameters(NavMeshParameters& nmp);
+ void CalcNavMesh();
+ void Save( const string &level_name, const Path &level_path );
+ bool Load( const string &level_name, const Path &level_path );
+ bool LoadFromAbs(const Path& level_path, const char* abs_meta_path, const char* abs_model_path, const char * abs_nav_path, const char* abs_zip_path, bool has_zip );
+ void Update();
+ NavPath FindPath(const vec3 &start, const vec3 &end, uint16_t include_filter, uint16_t exclude_filter);
+ vec3 RayCast( const vec3 &start, const vec3 &end );
+ vec3 RayCastSlide( const vec3 &start, const vec3 &end, int depth );
+ NavPoint GetNavPoint( const vec3 &point );
+
+ const rcMeshLoaderObj* getCollisionMesh() const;
+ const dtNavMesh* getNavMesh() const;
+ const rcPolyMesh* getPolyMesh() const;
+ InputGeom &getInputGeom();
+ void SetExplicitBounderies(vec3 min, vec3 max );
+
+ unsigned short GetOffMeshConnectionFlag( int userid );
+private:
+ bool loaded_;
+ TileMesh sample_tile_mesh_;
+ InputGeom geom_;
+ NavMeshParameters nav_mesh_parameters_;
+ //TileMeshTesterTool nav_mesh_tester_;
+
+ //TODO: Replace this middle storage with direct append into InputGeom
+ //These arrays are only used for loading storage and don't represent internal state.
+ vector<float> vertices_;
+ vector<unsigned> faces_;
+
+ float meshBMin_[3];
+ float meshBMax_[3];
+
+ vec3 m_start_;
+ vec3 m_end_;
+ vector<vec3> path_points_;
+
+ void CalcFaceNormals(vector<float> &face_normals);
+};
diff --git a/Source/AI/navmeshparameters.cpp b/Source/AI/navmeshparameters.cpp
new file mode 100644
index 00000000..d34a5a1b
--- /dev/null
+++ b/Source/AI/navmeshparameters.cpp
@@ -0,0 +1,148 @@
+//-----------------------------------------------------------------------------
+// Name: navmeshparameters.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "navmeshparameters.h"
+
+#include <Utility/assert.h>
+
+#include <tinyxml.h>
+
+#include <string>
+
+using std::string;
+
+NavMeshParameters::NavMeshParameters() :
+generate(true),
+m_cellSize(0.3f),
+m_cellHeight(0.2f),
+m_agentHeight(1.7f),
+m_agentRadius(0.4f),
+m_agentMaxClimb(1.5f),
+m_agentMaxSlope(60.0f) {
+
+}
+
+void WriteNavMeshParametersToXML(const NavMeshParameters& nmp, TiXmlElement* elem) {
+ LOG_ASSERT(elem);
+ //Make sure our assumptions about how many elements there are stays true for future changes.
+ // Padding makes the struct 7*4 bytes big rather than 6*4 + 1
+ LOG_ASSERT(sizeof(float)*7 == sizeof(NavMeshParameters));
+
+ elem->SetAttribute("generate", nmp.generate ? "true" : "false");
+ elem->SetDoubleAttribute("cell_size", nmp.m_cellSize);
+ elem->SetDoubleAttribute("cell_height", nmp.m_cellHeight);
+ elem->SetDoubleAttribute("agent_height", nmp.m_agentHeight);
+ elem->SetDoubleAttribute("agent_radius", nmp.m_agentRadius);
+ elem->SetDoubleAttribute("agent_max_climb", nmp.m_agentMaxClimb);
+ elem->SetDoubleAttribute("agent_max_slope", nmp.m_agentMaxSlope);
+}
+
+void ReadNavMeshParametersFromXML(NavMeshParameters& nmp, const TiXmlElement* elem) {
+ LOG_ASSERT(elem);
+ //Make sure our assumptions about how many elements there are stays true for future changes.
+ // Padding makes the struct 7*4 bytes big rather than 6*4 + 1
+ LOG_ASSERT(sizeof(float)*7 == sizeof(NavMeshParameters));
+
+ string generate_value;
+ elem->QueryStringAttribute("generate", &generate_value);
+ if(generate_value == "false") {
+ nmp.generate = false;
+ } else {
+ nmp.generate = true;
+ }
+ elem->QueryFloatAttribute("cell_size", &nmp.m_cellSize);
+ elem->QueryFloatAttribute("cell_height", &nmp.m_cellHeight);
+ elem->QueryFloatAttribute("agent_height", &nmp.m_agentHeight);
+ elem->QueryFloatAttribute("agent_radius", &nmp.m_agentRadius);
+ elem->QueryFloatAttribute("agent_max_climb", &nmp.m_agentMaxClimb);
+ elem->QueryFloatAttribute("agent_max_slope", &nmp.m_agentMaxSlope);
+}
+
+//adler32 hash
+uint32_t HashNavMeshParameters(NavMeshParameters& nmp) {
+ LOG_ASSERT(sizeof(float)==sizeof(uint8_t)*4);
+ // Padding makes the struct 7*4 bytes big rather than 6*4 + 1
+ LOG_ASSERT(sizeof(float)*7 == sizeof(NavMeshParameters));
+
+ // "generate" intentionally not included to not force navmesh regen
+
+ uint32_t s1 = 1;
+ uint32_t s2 = 0;
+
+ s1 = (s1 + ((uint8_t*)&nmp.m_cellSize)[0]) % 65521;
+ s2 = (s2 + s1) % 65521;
+ s1 = (s1 + ((uint8_t*)&nmp.m_cellSize)[1]) % 65521;
+ s2 = (s2 + s1) % 65521;
+ s1 = (s1 + ((uint8_t*)&nmp.m_cellSize)[2]) % 65521;
+ s2 = (s2 + s1) % 65521;
+ s1 = (s1 + ((uint8_t*)&nmp.m_cellSize)[3]) % 65521;
+ s2 = (s2 + s1) % 65521;
+
+ s1 = (s1 + ((uint8_t*)&nmp.m_cellHeight)[0]) % 65521;
+ s2 = (s2 + s1) % 65521;
+ s1 = (s1 + ((uint8_t*)&nmp.m_cellHeight)[1]) % 65521;
+ s2 = (s2 + s1) % 65521;
+ s1 = (s1 + ((uint8_t*)&nmp.m_cellHeight)[2]) % 65521;
+ s2 = (s2 + s1) % 65521;
+ s1 = (s1 + ((uint8_t*)&nmp.m_cellHeight)[3]) % 65521;
+ s2 = (s2 + s1) % 65521;
+
+ s1 = (s1 + ((uint8_t*)&nmp.m_agentHeight)[0]) % 65521;
+ s2 = (s2 + s1) % 65521;
+ s1 = (s1 + ((uint8_t*)&nmp.m_agentHeight)[1]) % 65521;
+ s2 = (s2 + s1) % 65521;
+ s1 = (s1 + ((uint8_t*)&nmp.m_agentHeight)[2]) % 65521;
+ s2 = (s2 + s1) % 65521;
+ s1 = (s1 + ((uint8_t*)&nmp.m_agentHeight)[3]) % 65521;
+ s2 = (s2 + s1) % 65521;
+
+ s1 = (s1 + ((uint8_t*)&nmp.m_agentRadius)[0]) % 65521;
+ s2 = (s2 + s1) % 65521;
+ s1 = (s1 + ((uint8_t*)&nmp.m_agentRadius)[1]) % 65521;
+ s2 = (s2 + s1) % 65521;
+ s1 = (s1 + ((uint8_t*)&nmp.m_agentRadius)[2]) % 65521;
+ s2 = (s2 + s1) % 65521;
+ s1 = (s1 + ((uint8_t*)&nmp.m_agentRadius)[3]) % 65521;
+ s2 = (s2 + s1) % 65521;
+
+ s1 = (s1 + ((uint8_t*)&nmp.m_agentMaxClimb)[0]) % 65521;
+ s2 = (s2 + s1) % 65521;
+ s1 = (s1 + ((uint8_t*)&nmp.m_agentMaxClimb)[1]) % 65521;
+ s2 = (s2 + s1) % 65521;
+ s1 = (s1 + ((uint8_t*)&nmp.m_agentMaxClimb)[2]) % 65521;
+ s2 = (s2 + s1) % 65521;
+ s1 = (s1 + ((uint8_t*)&nmp.m_agentMaxClimb)[3]) % 65521;
+ s2 = (s2 + s1) % 65521;
+
+ s1 = (s1 + ((uint8_t*)&nmp.m_agentMaxSlope)[0]) % 65521;
+ s2 = (s2 + s1) % 65521;
+ s1 = (s1 + ((uint8_t*)&nmp.m_agentMaxSlope)[1]) % 65521;
+ s2 = (s2 + s1) % 65521;
+ s1 = (s1 + ((uint8_t*)&nmp.m_agentMaxSlope)[2]) % 65521;
+ s2 = (s2 + s1) % 65521;
+ s1 = (s1 + ((uint8_t*)&nmp.m_agentMaxSlope)[3]) % 65521;
+ s2 = (s2 + s1) % 65521;
+
+ return (s2 << 16) | s1;
+}
+
diff --git a/Source/AI/navmeshparameters.h b/Source/AI/navmeshparameters.h
new file mode 100644
index 00000000..95c29323
--- /dev/null
+++ b/Source/AI/navmeshparameters.h
@@ -0,0 +1,45 @@
+//-----------------------------------------------------------------------------
+// Name: navmeshparameters.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/integer.h>
+
+class TiXmlElement;
+
+struct NavMeshParameters {
+ NavMeshParameters();
+
+ bool generate;
+ float m_cellSize;
+ float m_cellHeight;
+ float m_agentHeight;
+ float m_agentRadius;
+ float m_agentMaxClimb;
+ float m_agentMaxSlope;
+};
+
+void WriteNavMeshParametersToXML(const NavMeshParameters& nmp, TiXmlElement* elem);
+void ReadNavMeshParametersFromXML(NavMeshParameters& nmp, const TiXmlElement* params);
+
+uint32_t HashNavMeshParameters(NavMeshParameters& nmp);
diff --git a/Source/AI/pathfind.cpp b/Source/AI/pathfind.cpp
new file mode 100644
index 00000000..bffe1b0f
--- /dev/null
+++ b/Source/AI/pathfind.cpp
@@ -0,0 +1,36 @@
+//-----------------------------------------------------------------------------
+// Name: pathfind.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "pathfind.h"
+
+size_t NavPath::NumPoints() {
+ return waypoints.size();
+}
+
+vec3 NavPath::GetPoint(size_t which) {
+ return waypoints[which];
+}
+
+uint8_t NavPath::GetFlag(size_t which) {
+ return flags[which];
+}
diff --git a/Source/AI/pathfind.h b/Source/AI/pathfind.h
new file mode 100644
index 00000000..1dcb5b51
--- /dev/null
+++ b/Source/AI/pathfind.h
@@ -0,0 +1,41 @@
+//-----------------------------------------------------------------------------
+// Name: pathfind.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+#include <Internal/integer.h>
+
+#include <vector>
+
+using std::vector;
+
+struct NavPath {
+ vector<vec3> waypoints;
+ vector<uint8_t> flags;
+ bool success;
+
+ size_t NumPoints();
+ vec3 GetPoint(size_t which);
+ uint8_t GetFlag(size_t which);
+};
diff --git a/Source/AI/recast_dump.cpp b/Source/AI/recast_dump.cpp
new file mode 100644
index 00000000..ec02ed24
--- /dev/null
+++ b/Source/AI/recast_dump.cpp
@@ -0,0 +1,460 @@
+//-----------------------------------------------------------------------------
+// Name: recast_dump.cpp
+// Developer: External
+// Author:
+// Description: This is a utility file from the Recast project which has been
+// extracted and modified by Wolfire Games LLC
+// License: Read below
+//-----------------------------------------------------------------------------
+
+//
+// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
+//
+// 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.
+//
+
+#define _USE_MATH_DEFINES
+#include <math.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include "Recast.h"
+#include "RecastAlloc.h"
+#include "recast_dump.h"
+
+
+duFileIO::~duFileIO()
+{
+ // Empty
+}
+
+static void ioprintf(duFileIO* io, const char* format, ...)
+{
+ char line[256];
+ va_list ap;
+ va_start(ap, format);
+ const int n = vsnprintf(line, sizeof(line), format, ap);
+ va_end(ap);
+ if (n > 0)
+ io->write(line, sizeof(char)*n);
+}
+
+bool duDumpPolyMeshToObj(rcPolyMesh& pmesh, duFileIO* io)
+{
+ if (!io)
+ {
+ printf("duDumpPolyMeshToObj: input IO is null.\n");
+ return false;
+ }
+ if (!io->isWriting())
+ {
+ printf("duDumpPolyMeshToObj: input IO not writing.\n");
+ return false;
+ }
+
+ const int nvp = pmesh.nvp;
+ const float cs = pmesh.cs;
+ const float ch = pmesh.ch;
+ const float* orig = pmesh.bmin;
+
+ ioprintf(io, "# Recast Navmesh\n");
+ ioprintf(io, "o NavMesh\n");
+
+ ioprintf(io, "\n");
+
+ for (int i = 0; i < pmesh.nverts; ++i)
+ {
+ const unsigned short* v = &pmesh.verts[i*3];
+ const float x = orig[0] + v[0]*cs;
+ const float y = orig[1] + (v[1]+1)*ch + 0.1f;
+ const float z = orig[2] + v[2]*cs;
+ ioprintf(io, "v %f %f %f\n", x,y,z);
+ }
+
+ ioprintf(io, "\n");
+
+ for (int i = 0; i < pmesh.npolys; ++i)
+ {
+ const unsigned short* p = &pmesh.polys[i*nvp*2];
+ for (int j = 2; j < nvp; ++j)
+ {
+ if (p[j] == RC_MESH_NULL_IDX) break;
+ ioprintf(io, "f %d %d %d\n", p[0]+1, p[j-1]+1, p[j]+1);
+ }
+ }
+
+ return true;
+}
+
+bool duDumpPolyMeshDetailToObj(rcPolyMeshDetail& dmesh, duFileIO* io)
+{
+ if (!io)
+ {
+ printf("duDumpPolyMeshDetailToObj: input IO is null.\n");
+ return false;
+ }
+ if (!io->isWriting())
+ {
+ printf("duDumpPolyMeshDetailToObj: input IO not writing.\n");
+ return false;
+ }
+
+ ioprintf(io, "# Recast Navmesh\n");
+ ioprintf(io, "o NavMesh\n");
+
+ ioprintf(io, "\n");
+
+ for (int i = 0; i < dmesh.nverts; ++i)
+ {
+ const float* v = &dmesh.verts[i*3];
+ ioprintf(io, "v %f %f %f\n", v[0],v[1],v[2]);
+ }
+
+ ioprintf(io, "\n");
+
+ for (int i = 0; i < dmesh.nmeshes; ++i)
+ {
+ const unsigned int* m = &dmesh.meshes[i*4];
+ const unsigned int bverts = m[0];
+ const unsigned int btris = m[2];
+ const unsigned int ntris = m[3];
+ const unsigned char* tris = &dmesh.tris[btris*4];
+ for (unsigned int j = 0; j < ntris; ++j)
+ {
+ ioprintf(io, "f %d %d %d\n",
+ (int)(bverts+tris[j*4+0])+1,
+ (int)(bverts+tris[j*4+1])+1,
+ (int)(bverts+tris[j*4+2])+1);
+ }
+ }
+
+ return true;
+}
+
+static const int CSET_MAGIC = ('c' << 24) | ('s' << 16) | ('e' << 8) | 't';
+static const int CSET_VERSION = 2;
+
+bool duDumpContourSet(struct rcContourSet& cset, duFileIO* io)
+{
+ if (!io)
+ {
+ printf("duDumpContourSet: input IO is null.\n");
+ return false;
+ }
+ if (!io->isWriting())
+ {
+ printf("duDumpContourSet: input IO not writing.\n");
+ return false;
+ }
+
+ io->write(&CSET_MAGIC, sizeof(CSET_MAGIC));
+ io->write(&CSET_VERSION, sizeof(CSET_VERSION));
+
+ io->write(&cset.nconts, sizeof(cset.nconts));
+
+ io->write(cset.bmin, sizeof(cset.bmin));
+ io->write(cset.bmax, sizeof(cset.bmax));
+
+ io->write(&cset.cs, sizeof(cset.cs));
+ io->write(&cset.ch, sizeof(cset.ch));
+
+ io->write(&cset.width, sizeof(cset.width));
+ io->write(&cset.height, sizeof(cset.height));
+ io->write(&cset.borderSize, sizeof(cset.borderSize));
+
+ for (int i = 0; i < cset.nconts; ++i)
+ {
+ const rcContour& cont = cset.conts[i];
+ io->write(&cont.nverts, sizeof(cont.nverts));
+ io->write(&cont.nrverts, sizeof(cont.nrverts));
+ io->write(&cont.reg, sizeof(cont.reg));
+ io->write(&cont.area, sizeof(cont.area));
+ io->write(cont.verts, sizeof(int)*4*cont.nverts);
+ io->write(cont.rverts, sizeof(int)*4*cont.nrverts);
+ }
+
+ return true;
+}
+
+bool duReadContourSet(struct rcContourSet& cset, duFileIO* io)
+{
+ if (!io)
+ {
+ printf("duReadContourSet: input IO is null.\n");
+ return false;
+ }
+ if (!io->isReading())
+ {
+ printf("duReadContourSet: input IO not reading.\n");
+ return false;
+ }
+
+ int magic = 0;
+ int version = 0;
+
+ io->read(&magic, sizeof(magic));
+ io->read(&version, sizeof(version));
+
+ if (magic != CSET_MAGIC)
+ {
+ printf("duReadContourSet: Bad voodoo.\n");
+ return false;
+ }
+ if (version != CSET_VERSION)
+ {
+ printf("duReadContourSet: Bad version.\n");
+ return false;
+ }
+
+ io->read(&cset.nconts, sizeof(cset.nconts));
+
+ cset.conts = (rcContour*)rcAlloc(sizeof(rcContour)*cset.nconts, RC_ALLOC_PERM);
+ if (!cset.conts)
+ {
+ printf("duReadContourSet: Could not alloc contours (%d)\n", cset.nconts);
+ return false;
+ }
+ memset(cset.conts, 0, sizeof(rcContour)*cset.nconts);
+
+ io->read(cset.bmin, sizeof(cset.bmin));
+ io->read(cset.bmax, sizeof(cset.bmax));
+
+ io->read(&cset.cs, sizeof(cset.cs));
+ io->read(&cset.ch, sizeof(cset.ch));
+
+ io->read(&cset.width, sizeof(cset.width));
+ io->read(&cset.height, sizeof(cset.height));
+ io->read(&cset.borderSize, sizeof(cset.borderSize));
+
+ for (int i = 0; i < cset.nconts; ++i)
+ {
+ rcContour& cont = cset.conts[i];
+ io->read(&cont.nverts, sizeof(cont.nverts));
+ io->read(&cont.nrverts, sizeof(cont.nrverts));
+ io->read(&cont.reg, sizeof(cont.reg));
+ io->read(&cont.area, sizeof(cont.area));
+
+ cont.verts = (int*)rcAlloc(sizeof(int)*4*cont.nverts, RC_ALLOC_PERM);
+ if (!cont.verts)
+ {
+ printf("duReadContourSet: Could not alloc contour verts (%d)\n", cont.nverts);
+ return false;
+ }
+ cont.rverts = (int*)rcAlloc(sizeof(int)*4*cont.nrverts, RC_ALLOC_PERM);
+ if (!cont.rverts)
+ {
+ printf("duReadContourSet: Could not alloc contour rverts (%d)\n", cont.nrverts);
+ return false;
+ }
+
+ io->read(cont.verts, sizeof(int)*4*cont.nverts);
+ io->read(cont.rverts, sizeof(int)*4*cont.nrverts);
+ }
+
+ return true;
+}
+
+
+static const int CHF_MAGIC = ('r' << 24) | ('c' << 16) | ('h' << 8) | 'f';
+static const int CHF_VERSION = 3;
+
+bool duDumpCompactHeightfield(struct rcCompactHeightfield& chf, duFileIO* io)
+{
+ if (!io)
+ {
+ printf("duDumpCompactHeightfield: input IO is null.\n");
+ return false;
+ }
+ if (!io->isWriting())
+ {
+ printf("duDumpCompactHeightfield: input IO not writing.\n");
+ return false;
+ }
+
+ io->write(&CHF_MAGIC, sizeof(CHF_MAGIC));
+ io->write(&CHF_VERSION, sizeof(CHF_VERSION));
+
+ io->write(&chf.width, sizeof(chf.width));
+ io->write(&chf.height, sizeof(chf.height));
+ io->write(&chf.spanCount, sizeof(chf.spanCount));
+
+ io->write(&chf.walkableHeight, sizeof(chf.walkableHeight));
+ io->write(&chf.walkableClimb, sizeof(chf.walkableClimb));
+ io->write(&chf.borderSize, sizeof(chf.borderSize));
+
+ io->write(&chf.maxDistance, sizeof(chf.maxDistance));
+ io->write(&chf.maxRegions, sizeof(chf.maxRegions));
+
+ io->write(chf.bmin, sizeof(chf.bmin));
+ io->write(chf.bmax, sizeof(chf.bmax));
+
+ io->write(&chf.cs, sizeof(chf.cs));
+ io->write(&chf.ch, sizeof(chf.ch));
+
+ int tmp = 0;
+ if (chf.cells) tmp |= 1;
+ if (chf.spans) tmp |= 2;
+ if (chf.dist) tmp |= 4;
+ if (chf.areas) tmp |= 8;
+
+ io->write(&tmp, sizeof(tmp));
+
+ if (chf.cells)
+ io->write(chf.cells, sizeof(rcCompactCell)*chf.width*chf.height);
+ if (chf.spans)
+ io->write(chf.spans, sizeof(rcCompactSpan)*chf.spanCount);
+ if (chf.dist)
+ io->write(chf.dist, sizeof(unsigned short)*chf.spanCount);
+ if (chf.areas)
+ io->write(chf.areas, sizeof(unsigned char)*chf.spanCount);
+
+ return true;
+}
+
+bool duReadCompactHeightfield(struct rcCompactHeightfield& chf, duFileIO* io)
+{
+ if (!io)
+ {
+ printf("duReadCompactHeightfield: input IO is null.\n");
+ return false;
+ }
+ if (!io->isReading())
+ {
+ printf("duReadCompactHeightfield: input IO not reading.\n");
+ return false;
+ }
+
+ int magic = 0;
+ int version = 0;
+
+ io->read(&magic, sizeof(magic));
+ io->read(&version, sizeof(version));
+
+ if (magic != CHF_MAGIC)
+ {
+ printf("duReadCompactHeightfield: Bad voodoo.\n");
+ return false;
+ }
+ if (version != CHF_VERSION)
+ {
+ printf("duReadCompactHeightfield: Bad version.\n");
+ return false;
+ }
+
+ io->read(&chf.width, sizeof(chf.width));
+ io->read(&chf.height, sizeof(chf.height));
+ io->read(&chf.spanCount, sizeof(chf.spanCount));
+
+ io->read(&chf.walkableHeight, sizeof(chf.walkableHeight));
+ io->read(&chf.walkableClimb, sizeof(chf.walkableClimb));
+ io->read(&chf.borderSize, sizeof(chf.borderSize));
+
+ io->read(&chf.maxDistance, sizeof(chf.maxDistance));
+ io->read(&chf.maxRegions, sizeof(chf.maxRegions));
+
+ io->read(chf.bmin, sizeof(chf.bmin));
+ io->read(chf.bmax, sizeof(chf.bmax));
+
+ io->read(&chf.cs, sizeof(chf.cs));
+ io->read(&chf.ch, sizeof(chf.ch));
+
+ int tmp = 0;
+ io->read(&tmp, sizeof(tmp));
+
+ if (tmp & 1)
+ {
+ chf.cells = (rcCompactCell*)rcAlloc(sizeof(rcCompactCell)*chf.width*chf.height, RC_ALLOC_PERM);
+ if (!chf.cells)
+ {
+ printf("duReadCompactHeightfield: Could not alloc cells (%d)\n", chf.width*chf.height);
+ return false;
+ }
+ io->read(chf.cells, sizeof(rcCompactCell)*chf.width*chf.height);
+ }
+ if (tmp & 2)
+ {
+ chf.spans = (rcCompactSpan*)rcAlloc(sizeof(rcCompactSpan)*chf.spanCount, RC_ALLOC_PERM);
+ if (!chf.spans)
+ {
+ printf("duReadCompactHeightfield: Could not alloc spans (%d)\n", chf.spanCount);
+ return false;
+ }
+ io->read(chf.spans, sizeof(rcCompactSpan)*chf.spanCount);
+ }
+ if (tmp & 4)
+ {
+ chf.dist = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount, RC_ALLOC_PERM);
+ if (!chf.dist)
+ {
+ printf("duReadCompactHeightfield: Could not alloc dist (%d)\n", chf.spanCount);
+ return false;
+ }
+ io->read(chf.dist, sizeof(unsigned short)*chf.spanCount);
+ }
+ if (tmp & 8)
+ {
+ chf.areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_PERM);
+ if (!chf.areas)
+ {
+ printf("duReadCompactHeightfield: Could not alloc areas (%d)\n", chf.spanCount);
+ return false;
+ }
+ io->read(chf.areas, sizeof(unsigned char)*chf.spanCount);
+ }
+
+ return true;
+}
+
+
+static void logLine(rcContext& ctx, rcTimerLabel label, const char* name, const float pc)
+{
+ const int t = ctx.getAccumulatedTime(label);
+ if (t < 0) return;
+ ctx.log(RC_LOG_PROGRESS, "%s:\t%.2fms\t(%.1f%%)", name, t/1000.0f, t*pc);
+}
+
+void duLogBuildTimes(rcContext& ctx, const int totalTimeUsec)
+{
+ const float pc = 100.0f / totalTimeUsec;
+
+ ctx.log(RC_LOG_PROGRESS, "Build Times");
+ logLine(ctx, RC_TIMER_RASTERIZE_TRIANGLES, "- Rasterize", pc);
+ logLine(ctx, RC_TIMER_BUILD_COMPACTHEIGHTFIELD, "- Build Compact", pc);
+ logLine(ctx, RC_TIMER_FILTER_BORDER, "- Filter Border", pc);
+ logLine(ctx, RC_TIMER_FILTER_WALKABLE, "- Filter Walkable", pc);
+ logLine(ctx, RC_TIMER_ERODE_AREA, "- Erode Area", pc);
+ logLine(ctx, RC_TIMER_MEDIAN_AREA, "- Median Area", pc);
+ logLine(ctx, RC_TIMER_MARK_BOX_AREA, "- Mark Box Area", pc);
+ logLine(ctx, RC_TIMER_MARK_CONVEXPOLY_AREA, "- Mark Convex Area", pc);
+ logLine(ctx, RC_TIMER_MARK_CYLINDER_AREA, "- Mark Cylinder Area", pc);
+ logLine(ctx, RC_TIMER_BUILD_DISTANCEFIELD, "- Build Distance Field", pc);
+ logLine(ctx, RC_TIMER_BUILD_DISTANCEFIELD_DIST, " - Distance", pc);
+ logLine(ctx, RC_TIMER_BUILD_DISTANCEFIELD_BLUR, " - Blur", pc);
+ logLine(ctx, RC_TIMER_BUILD_REGIONS, "- Build Regions", pc);
+ logLine(ctx, RC_TIMER_BUILD_REGIONS_WATERSHED, " - Watershed", pc);
+ logLine(ctx, RC_TIMER_BUILD_REGIONS_EXPAND, " - Expand", pc);
+ logLine(ctx, RC_TIMER_BUILD_REGIONS_FLOOD, " - Find Basins", pc);
+ logLine(ctx, RC_TIMER_BUILD_REGIONS_FILTER, " - Filter", pc);
+ logLine(ctx, RC_TIMER_BUILD_LAYERS, "- Build Layers", pc);
+ logLine(ctx, RC_TIMER_BUILD_CONTOURS, "- Build Contours", pc);
+ logLine(ctx, RC_TIMER_BUILD_CONTOURS_TRACE, " - Trace", pc);
+ logLine(ctx, RC_TIMER_BUILD_CONTOURS_SIMPLIFY, " - Simplify", pc);
+ logLine(ctx, RC_TIMER_BUILD_POLYMESH, "- Build Polymesh", pc);
+ logLine(ctx, RC_TIMER_BUILD_POLYMESHDETAIL, "- Build Polymesh Detail", pc);
+ logLine(ctx, RC_TIMER_MERGE_POLYMESH, "- Merge Polymeshes", pc);
+ logLine(ctx, RC_TIMER_MERGE_POLYMESHDETAIL, "- Merge Polymesh Details", pc);
+ ctx.log(RC_LOG_PROGRESS, "=== TOTAL:\t%.2fms", totalTimeUsec/1000.0f);
+}
+
diff --git a/Source/AI/recast_dump.h b/Source/AI/recast_dump.h
new file mode 100644
index 00000000..e0ed130c
--- /dev/null
+++ b/Source/AI/recast_dump.h
@@ -0,0 +1,52 @@
+//-----------------------------------------------------------------------------
+// Name: recast_dump.h
+// Developer: External
+// Author:
+// Description: This is a utility file from the Recast project which has been
+// extracted and modified by Wolfire Games LLC
+// License: Read below
+//-----------------------------------------------------------------------------
+
+//
+// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
+//
+// 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.
+//
+
+#ifndef RECAST_DUMP_H
+#define RECAST_DUMP_H
+
+struct duFileIO
+{
+ virtual ~duFileIO() = 0;
+ virtual bool isWriting() const = 0;
+ virtual bool isReading() const = 0;
+ virtual bool write(const void* ptr, const size_t size) = 0;
+ virtual bool read(void* ptr, const size_t size) = 0;
+};
+
+bool duDumpPolyMeshToObj(struct rcPolyMesh& pmesh, duFileIO* io);
+bool duDumpPolyMeshDetailToObj(struct rcPolyMeshDetail& dmesh, duFileIO* io);
+
+bool duDumpContourSet(struct rcContourSet& cset, duFileIO* io);
+bool duReadContourSet(struct rcContourSet& cset, duFileIO* io);
+
+bool duDumpCompactHeightfield(struct rcCompactHeightfield& chf, duFileIO* io);
+bool duReadCompactHeightfield(struct rcCompactHeightfield& chf, duFileIO* io);
+
+void duLogBuildTimes(rcContext& ctx, const int totalTileUsec);
+
+
+#endif // RECAST_DUMP_H
diff --git a/Source/AI/sample.cpp b/Source/AI/sample.cpp
new file mode 100644
index 00000000..6c65668f
--- /dev/null
+++ b/Source/AI/sample.cpp
@@ -0,0 +1,246 @@
+//-----------------------------------------------------------------------------
+// Name: sample.cpp
+// Developer: External
+// Author:
+// Description: This is a utility file from the Recast project which has been
+// extracted and modified by Wolfire Games LLC
+// License: Read below
+//-----------------------------------------------------------------------------
+
+//
+// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
+//
+// 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.
+//
+
+#define _USE_MATH_DEFINES
+#include <math.h>
+#include <stdio.h>
+#include "sample.h"
+#include "input_geom.h"
+#include "Recast.h"
+#include "DetourNavMesh.h"
+#include "DetourNavMeshQuery.h"
+#include "DetourCrowd.h"
+
+#ifdef WIN32
+# define snprintf _snprintf
+#endif
+
+Sample::Sample() :
+ m_geom(0),
+ m_navMesh(0),
+ m_navQuery(0),
+ m_crowd(0),
+ m_tool(0),
+ m_ctx(0)
+{
+ resetCommonSettings();
+ m_navQuery = dtAllocNavMeshQuery();
+ m_crowd = dtAllocCrowd();
+
+ for (int i = 0; i < MAX_TOOLS; i++)
+ m_toolStates[i] = 0;
+}
+
+Sample::~Sample()
+{
+ dtFreeNavMeshQuery(m_navQuery);
+ dtFreeNavMesh(m_navMesh);
+ dtFreeCrowd(m_crowd);
+ delete m_tool;
+ for (int i = 0; i < MAX_TOOLS; i++)
+ delete m_toolStates[i];
+}
+
+void Sample::setTool(SampleTool* tool)
+{
+ delete m_tool;
+ m_tool = tool;
+ if (tool)
+ m_tool->init(this);
+}
+
+void Sample::handleSettings()
+{
+}
+
+void Sample::handleTools()
+{
+}
+
+void Sample::handleDebugMode()
+{
+}
+
+void Sample::handleRender()
+{
+}
+
+void Sample::handleRenderOverlay(double* /*proj*/, double* /*model*/, int* /*view*/)
+{
+}
+
+void Sample::handleMeshChanged(InputGeom* geom)
+{
+ m_geom = geom;
+
+ const BuildSettings* buildSettings = geom->getBuildSettings();
+ if (buildSettings)
+ {
+ m_cellSize = buildSettings->cellSize;
+ m_cellHeight = buildSettings->cellHeight;
+ m_agentHeight = buildSettings->agentHeight;
+ m_agentRadius = buildSettings->agentRadius;
+ m_agentMaxClimb = buildSettings->agentMaxClimb;
+ m_agentMaxSlope = buildSettings->agentMaxSlope;
+ m_regionMinSize = buildSettings->regionMinSize;
+ m_regionMergeSize = buildSettings->regionMergeSize;
+ m_edgeMaxLen = buildSettings->edgeMaxLen;
+ m_edgeMaxError = buildSettings->edgeMaxError;
+ m_vertsPerPoly = buildSettings->vertsPerPoly;
+ m_detailSampleDist = buildSettings->detailSampleDist;
+ m_detailSampleMaxError = buildSettings->detailSampleMaxError;
+ m_partitionType = buildSettings->partitionType;
+ }
+}
+
+void Sample::collectSettings(BuildSettings& settings)
+{
+ settings.cellSize = m_cellSize;
+ settings.cellHeight = m_cellHeight;
+ settings.agentHeight = m_agentHeight;
+ settings.agentRadius = m_agentRadius;
+ settings.agentMaxClimb = m_agentMaxClimb;
+ settings.agentMaxSlope = m_agentMaxSlope;
+ settings.regionMinSize = m_regionMinSize;
+ settings.regionMergeSize = m_regionMergeSize;
+ settings.edgeMaxLen = m_edgeMaxLen;
+ settings.edgeMaxError = m_edgeMaxError;
+ settings.vertsPerPoly = m_vertsPerPoly;
+ settings.detailSampleDist = m_detailSampleDist;
+ settings.detailSampleMaxError = m_detailSampleMaxError;
+ settings.partitionType = m_partitionType;
+}
+
+
+void Sample::resetCommonSettings()
+{
+ m_cellSize = 0.3f;
+ m_cellHeight = 0.2f;
+ m_agentHeight = 2.0f;
+ m_agentRadius = 0.6f;
+ m_agentMaxClimb = 0.9f;
+ m_agentMaxSlope = 45.0f;
+ m_regionMinSize = 8;
+ m_regionMergeSize = 20;
+ m_edgeMaxLen = 12.0f;
+ m_edgeMaxError = 1.3f;
+ m_vertsPerPoly = 6.0f;
+ m_detailSampleDist = 6.0f;
+ m_detailSampleMaxError = 1.0f;
+ m_partitionType = SAMPLE_PARTITION_WATERSHED;
+}
+
+void Sample::handleCommonSettings()
+{
+ if (m_geom)
+ {
+ const float* bmin = m_geom->getNavMeshBoundsMin();
+ const float* bmax = m_geom->getNavMeshBoundsMax();
+ int gw = 0, gh = 0;
+ rcCalcGridSize(bmin, bmax, m_cellSize, &gw, &gh);
+ char text[64];
+ snprintf(text, 64, "Voxels %d x %d", gw, gh);
+ }
+
+}
+
+void Sample::handleClick(const float* s, const float* p, bool shift)
+{
+ if (m_tool)
+ m_tool->handleClick(s, p, shift);
+}
+
+void Sample::handleToggle()
+{
+ if (m_tool)
+ m_tool->handleToggle();
+}
+
+void Sample::handleStep()
+{
+ if (m_tool)
+ m_tool->handleStep();
+}
+
+bool Sample::handleBuild()
+{
+ return true;
+}
+
+void Sample::handleUpdate(const float dt)
+{
+ if (m_tool)
+ m_tool->handleUpdate(dt);
+ updateToolStates(dt);
+}
+
+
+void Sample::updateToolStates(const float dt)
+{
+ for (int i = 0; i < MAX_TOOLS; i++)
+ {
+ if (m_toolStates[i])
+ m_toolStates[i]->handleUpdate(dt);
+ }
+}
+
+void Sample::initToolStates(Sample* sample)
+{
+ for (int i = 0; i < MAX_TOOLS; i++)
+ {
+ if (m_toolStates[i])
+ m_toolStates[i]->init(sample);
+ }
+}
+
+void Sample::resetToolStates()
+{
+ for (int i = 0; i < MAX_TOOLS; i++)
+ {
+ if (m_toolStates[i])
+ m_toolStates[i]->reset();
+ }
+}
+
+void Sample::renderToolStates()
+{
+ for (int i = 0; i < MAX_TOOLS; i++)
+ {
+ if (m_toolStates[i])
+ m_toolStates[i]->handleRender();
+ }
+}
+
+void Sample::renderOverlayToolStates(double* proj, double* model, int* view)
+{
+ for (int i = 0; i < MAX_TOOLS; i++)
+ {
+ if (m_toolStates[i])
+ m_toolStates[i]->handleRenderOverlay(proj, model, view);
+ }
+}
+
diff --git a/Source/AI/sample.h b/Source/AI/sample.h
new file mode 100644
index 00000000..02bf8573
--- /dev/null
+++ b/Source/AI/sample.h
@@ -0,0 +1,194 @@
+//-----------------------------------------------------------------------------
+// Name: sample.h
+// Developer: External
+// Author:
+// Description: This is a utility file from the Recast project which has been
+// extracted and modified by Wolfire Games LLC
+// License: Read below
+//-----------------------------------------------------------------------------
+
+//
+// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
+//
+// 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.
+//
+
+#ifndef RECASTSAMPLE_H
+#define RECASTSAMPLE_H
+
+#include "Recast.h"
+#include "sample_interfaces.h"
+
+
+/// Tool types.
+enum SampleToolType
+{
+ TOOL_NONE = 0,
+ TOOL_TILE_EDIT,
+ TOOL_TILE_HIGHLIGHT,
+ TOOL_TEMP_OBSTACLE,
+ TOOL_NAVMESH_TESTER,
+ TOOL_NAVMESH_PRUNE,
+ TOOL_OFFMESH_CONNECTION,
+ TOOL_CONVEX_VOLUME,
+ TOOL_CROWD,
+ MAX_TOOLS
+};
+
+/// These are just sample areas to use consistent values across the samples.
+/// The use should specify these base on his needs.
+enum SamplePolyAreas
+{
+ SAMPLE_POLYAREA_GROUND = 1,
+ SAMPLE_POLYAREA_WATER = 2,
+ SAMPLE_POLYAREA_ROAD = 3,
+ SAMPLE_POLYAREA_DOOR = 4,
+ SAMPLE_POLYAREA_GRASS = 5,
+ SAMPLE_POLYAREA_JUMP1 = 6,
+ SAMPLE_POLYAREA_JUMP2 = 7,
+ SAMPLE_POLYAREA_JUMP3 = 8,
+ SAMPLE_POLYAREA_JUMP4 = 9,
+ SAMPLE_POLYAREA_JUMP5 = 10,
+ SAMPLE_POLYAREA_DISABLED = 11
+};
+
+enum SamplePolyFlags
+{
+ SAMPLE_POLYFLAGS_NONE = 0x00, // Ability to walk (ground, grass, road)
+ SAMPLE_POLYFLAGS_WALK = 0x01, // Ability to walk (ground, grass, road)
+ SAMPLE_POLYFLAGS_SWIM = 0x02, // Ability to swim (water).
+ SAMPLE_POLYFLAGS_DOOR = 0x04, // Ability to move through doors.
+ SAMPLE_POLYFLAGS_JUMP1 = 0x08, // Ability to jump.
+ SAMPLE_POLYFLAGS_JUMP2 = 0x10, // Ability to jump.
+ SAMPLE_POLYFLAGS_JUMP3 = 0x20, // Ability to jump.
+ SAMPLE_POLYFLAGS_JUMP4 = 0x40, // Ability to jump.
+ SAMPLE_POLYFLAGS_JUMP5 = 0x80, // Ability to jump.
+ SAMPLE_POLYFLAGS_JUMP_ALL = 0xf8, // All jumps.
+ SAMPLE_POLYFLAGS_DISABLED = 0x100, // Disabled polygon
+ SAMPLE_POLYFLAGS_ALL = 0x00ff // All abilities.
+};
+
+enum SamplePartitionType
+{
+ SAMPLE_PARTITION_WATERSHED,
+ SAMPLE_PARTITION_MONOTONE,
+ SAMPLE_PARTITION_LAYERS
+};
+
+struct SampleTool
+{
+ virtual ~SampleTool() {}
+ virtual int type() = 0;
+ virtual void init(class Sample* sample) = 0;
+ virtual void reset() = 0;
+ virtual void handleMenu() = 0;
+ virtual void handleClick(const float* s, const float* p, bool shift) = 0;
+ virtual void handleRender() = 0;
+ virtual void handleRenderOverlay(double* proj, double* model, int* view) = 0;
+ virtual void handleToggle() = 0;
+ virtual void handleStep() = 0;
+ virtual void handleUpdate(const float dt) = 0;
+};
+
+struct SampleToolState {
+ virtual ~SampleToolState() {}
+ virtual void init(class Sample* sample) = 0;
+ virtual void reset() = 0;
+ virtual void handleRender() = 0;
+ virtual void handleRenderOverlay(double* proj, double* model, int* view) = 0;
+ virtual void handleUpdate(const float dt) = 0;
+};
+
+class Sample
+{
+protected:
+ class InputGeom* m_geom;
+ class dtNavMesh* m_navMesh;
+ class dtNavMeshQuery* m_navQuery;
+ class dtCrowd* m_crowd;
+
+ unsigned char m_navMeshDrawFlags;
+
+ float m_cellSize;
+ float m_cellHeight;
+ float m_agentHeight;
+ float m_agentRadius;
+ float m_agentMaxClimb;
+ float m_agentMaxSlope;
+ float m_regionMinSize;
+ float m_regionMergeSize;
+ float m_edgeMaxLen;
+ float m_edgeMaxError;
+ float m_vertsPerPoly;
+ float m_detailSampleDist;
+ float m_detailSampleMaxError;
+ int m_partitionType;
+
+ SampleTool* m_tool;
+ SampleToolState* m_toolStates[MAX_TOOLS];
+
+ BuildContext* m_ctx;
+
+public:
+ Sample();
+ virtual ~Sample();
+
+ void setContext(BuildContext* ctx) { m_ctx = ctx; }
+
+ void setTool(SampleTool* tool);
+ SampleToolState* getToolState(int type) { return m_toolStates[type]; }
+ void setToolState(int type, SampleToolState* s) { m_toolStates[type] = s; }
+
+ virtual void handleSettings();
+ virtual void handleTools();
+ virtual void handleDebugMode();
+ virtual void handleClick(const float* s, const float* p, bool shift);
+ virtual void handleToggle();
+ virtual void handleStep();
+ virtual void handleRender();
+ virtual void handleRenderOverlay(double* proj, double* model, int* view);
+ virtual void handleMeshChanged(class InputGeom* geom);
+ virtual bool handleBuild();
+ virtual void handleUpdate(const float dt);
+ virtual void collectSettings(struct BuildSettings& settings);
+
+ virtual class InputGeom* getInputGeom() { return m_geom; }
+ virtual class dtNavMesh* getNavMesh() { return m_navMesh; }
+ virtual class dtNavMeshQuery* getNavMeshQuery() { return m_navQuery; }
+ virtual class dtCrowd* getCrowd() { return m_crowd; }
+ virtual float getAgentRadius() { return m_agentRadius; }
+ virtual float getAgentHeight() { return m_agentHeight; }
+ virtual float getAgentClimb() { return m_agentMaxClimb; }
+
+ unsigned char getNavMeshDrawFlags() const { return m_navMeshDrawFlags; }
+ void setNavMeshDrawFlags(unsigned char flags) { m_navMeshDrawFlags = flags; }
+
+ void updateToolStates(const float dt);
+ void initToolStates(Sample* sample);
+ void resetToolStates();
+ void renderToolStates();
+ void renderOverlayToolStates(double* proj, double* model, int* view);
+
+ void resetCommonSettings();
+ void handleCommonSettings();
+
+private:
+ // Explicitly disabled copy constructor and copy assignment operator.
+ Sample(const Sample&);
+ Sample& operator=(const Sample&);
+};
+
+
+#endif // RECASTSAMPLE_H
diff --git a/Source/AI/sample_interfaces.cpp b/Source/AI/sample_interfaces.cpp
new file mode 100644
index 00000000..d42109b8
--- /dev/null
+++ b/Source/AI/sample_interfaces.cpp
@@ -0,0 +1,186 @@
+//-----------------------------------------------------------------------------
+// Name: sample_interfaces.h
+// Developer: Wolfire Games LLC
+// Author:
+// Description:
+// License: zlib
+//-----------------------------------------------------------------------------
+
+#define _USE_MATH_DEFINES
+#include <math.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include "sample_interfaces.h"
+#include "Recast.h"
+#include <cstring>
+#include <Compat/fileio.h>
+
+#ifdef WIN32
+# define snprintf _snprintf
+#endif
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+BuildContext::BuildContext() :
+ m_messageCount(0),
+ m_textPoolSize(0)
+{
+ resetTimers();
+}
+
+// Virtual functions for custom implementations.
+void BuildContext::doResetLog()
+{
+ m_messageCount = 0;
+ m_textPoolSize = 0;
+}
+
+void BuildContext::doLog(const rcLogCategory category, const char* msg, const int len)
+{
+ if (!len) return;
+ if (m_messageCount >= MAX_MESSAGES)
+ return;
+ char* dst = &m_textPool[m_textPoolSize];
+ int n = TEXT_POOL_SIZE - m_textPoolSize;
+ if (n < 2)
+ return;
+ char* cat = dst;
+ char* text = dst+1;
+ const int maxtext = n-1;
+ // Store category
+ *cat = (char)category;
+ // Store message
+ const int count = rcMin(len+1, maxtext);
+ memcpy(text, msg, count);
+ text[count-1] = '\0';
+ m_textPoolSize += 1 + count;
+ m_messages[m_messageCount++] = dst;
+}
+
+void BuildContext::doResetTimers()
+{
+}
+
+void BuildContext::doStartTimer(const rcTimerLabel label)
+{
+}
+
+void BuildContext::doStopTimer(const rcTimerLabel label)
+{
+}
+
+int BuildContext::doGetAccumulatedTime(const rcTimerLabel label) const
+{
+ return 0;
+}
+
+void BuildContext::dumpLog(const char* format, ...)
+{
+ // Print header.
+ va_list ap;
+ va_start(ap, format);
+ vprintf(format, ap);
+ va_end(ap);
+ printf("\n");
+
+ // Print messages
+ const int TAB_STOPS[4] = { 28, 36, 44, 52 };
+ for (int i = 0; i < m_messageCount; ++i)
+ {
+ const char* msg = m_messages[i]+1;
+ int n = 0;
+ while (*msg)
+ {
+ if (*msg == '\t')
+ {
+ int count = 1;
+ for (int j = 0; j < 4; ++j)
+ {
+ if (n < TAB_STOPS[j])
+ {
+ count = TAB_STOPS[j] - n;
+ break;
+ }
+ }
+ while (--count)
+ {
+ putchar(' ');
+ n++;
+ }
+ }
+ else
+ {
+ putchar(*msg);
+ n++;
+ }
+ msg++;
+ }
+ putchar('\n');
+ }
+}
+
+int BuildContext::getLogCount() const
+{
+ return m_messageCount;
+}
+
+const char* BuildContext::getLogText(const int i) const
+{
+ return m_messages[i]+1;
+}
+
+
+FileIO::FileIO() :
+ m_fp(0),
+ m_mode(-1)
+{
+}
+
+FileIO::~FileIO()
+{
+ if (m_fp) fclose(m_fp);
+}
+
+bool FileIO::openForWrite(const char* path)
+{
+ if (m_fp) return false;
+ m_fp = my_fopen(path, "wb");
+ if (!m_fp) return false;
+ m_mode = 1;
+ return true;
+}
+
+bool FileIO::openForRead(const char* path)
+{
+ if (m_fp) return false;
+ m_fp = my_fopen(path, "rb");
+ if (!m_fp) return false;
+ m_mode = 2;
+ return true;
+}
+
+bool FileIO::isWriting() const
+{
+ return m_mode == 1;
+}
+
+bool FileIO::isReading() const
+{
+ return m_mode == 2;
+}
+
+bool FileIO::write(const void* ptr, const size_t size)
+{
+ if (!m_fp || m_mode != 1) return false;
+ fwrite(ptr, size, 1, m_fp);
+ return true;
+}
+
+bool FileIO::read(void* ptr, const size_t size)
+{
+ if (!m_fp || m_mode != 2) return false;
+ size_t readLen = fread(ptr, size, 1, m_fp);
+ return readLen == 1;
+}
+
+
diff --git a/Source/AI/sample_interfaces.h b/Source/AI/sample_interfaces.h
new file mode 100644
index 00000000..5f6dd16a
--- /dev/null
+++ b/Source/AI/sample_interfaces.h
@@ -0,0 +1,89 @@
+//-----------------------------------------------------------------------------
+// Name: sample_interfaces.h
+// Developer: External
+// Author:
+// Description: This is a utility file from the Recast project which has been
+// extracted and modified by Wolfire Games LLC
+// License: Read below
+//-----------------------------------------------------------------------------
+
+//
+// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
+//
+// 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.
+//
+
+#ifndef SAMPLEINTERFACES_H
+#define SAMPLEINTERFACES_H
+
+#include "Recast.h"
+#include "recast_dump.h"
+
+// These are example implementations of various interfaces used in Recast and Detour.
+
+/// Recast build context.
+class BuildContext : public rcContext
+{
+ static const int MAX_MESSAGES = 1000;
+ const char* m_messages[MAX_MESSAGES];
+ int m_messageCount;
+ static const int TEXT_POOL_SIZE = 8000;
+ char m_textPool[TEXT_POOL_SIZE];
+ int m_textPoolSize;
+
+public:
+ BuildContext();
+
+ /// Dumps the log to stdout.
+ void dumpLog(const char* format, ...);
+ /// Returns number of log messages.
+ int getLogCount() const;
+ /// Returns log message text.
+ const char* getLogText(const int i) const;
+
+protected:
+ /// Virtual functions for custom implementations.
+ ///@{
+ virtual void doResetLog();
+ virtual void doLog(const rcLogCategory category, const char* msg, const int len);
+ virtual void doResetTimers();
+ virtual void doStartTimer(const rcTimerLabel label);
+ virtual void doStopTimer(const rcTimerLabel label);
+ virtual int doGetAccumulatedTime(const rcTimerLabel label) const;
+ ///@}
+};
+
+/// stdio file implementation.
+class FileIO : public duFileIO
+{
+ FILE* m_fp;
+ int m_mode;
+public:
+ FileIO();
+ virtual ~FileIO();
+ bool openForWrite(const char* path);
+ bool openForRead(const char* path);
+ virtual bool isWriting() const;
+ virtual bool isReading() const;
+ virtual bool write(const void* ptr, const size_t size);
+ virtual bool read(void* ptr, const size_t size);
+private:
+ // Explicitly disabled copy constructor and copy assignment operator.
+ FileIO(const FileIO&);
+ FileIO& operator=(const FileIO&);
+};
+
+#endif // SAMPLEINTERFACES_H
+
diff --git a/Source/AI/tilemesh.cpp b/Source/AI/tilemesh.cpp
new file mode 100644
index 00000000..b97e020a
--- /dev/null
+++ b/Source/AI/tilemesh.cpp
@@ -0,0 +1,1001 @@
+//-----------------------------------------------------------------------------
+// Name: tilemesh.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#define _USE_MATH_DEFINES
+
+#include "tilemesh.h"
+
+#include <AI/input_geom.h>
+#include <Logging/logdata.h>
+#include <Utility/assert.h>
+#include <Compat/fileio.h>
+#include <GUI/widgetframework.h>
+
+#include <SDL.h>
+
+#include <opengl.h>
+
+#include <Recast.h>
+#include <DetourNavMesh.h>
+#include <DetourNavMeshBuilder.h>
+
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+#include <iomanip>
+#include <iostream>
+
+#ifdef WIN32
+# define snprintf _snprintf
+#endif
+
+using std::endl;
+using std::pair;
+using std::string;
+
+inline unsigned int nextPow2(unsigned int v)
+{
+ v--;
+ v |= v >> 1;
+ v |= v >> 2;
+ v |= v >> 4;
+ v |= v >> 8;
+ v |= v >> 16;
+ v++;
+ return v;
+}
+
+inline unsigned int ilog2(unsigned int v)
+{
+ unsigned int r;
+ unsigned int shift;
+ r = (v > 0xffff) << 4; v >>= r;
+ shift = (v > 0xff) << 3; v >>= shift; r |= shift;
+ shift = (v > 0xf) << 2; v >>= shift; r |= shift;
+ shift = (v > 0x3) << 1; v >>= shift; r |= shift;
+ r |= (v >> 1);
+ return r;
+}
+
+TileMesh::TileMesh() :
+ m_keepInterResults(false),
+ m_buildAll(true),
+ m_totalBuildTimeMs(0),
+ m_triareas(0),
+ m_solid(0),
+ m_chf(0),
+ m_cset(0),
+ m_pmesh(0),
+ m_dmesh(0),
+ m_maxTiles(0),
+ m_maxPolysPerTile(0),
+ m_tileSize(0),
+ m_tileBuildTime(0),
+ m_tileMemUsage(0),
+ m_tileTriCount(0),
+ //m_Bmin(-100,-1000,-100),
+ //m_Bmax(100,1000,100),
+ m_use_explicit_bounderies(false)
+{
+ resetCommonSettings();
+ m_navQuery = dtAllocNavMeshQuery();
+ m_crowd = dtAllocCrowd();
+ m_navMesh = dtAllocNavMesh();
+
+ memset(m_tileBmin, 0, sizeof(m_tileBmin));
+ memset(m_tileBmax, 0, sizeof(m_tileBmax));
+}
+
+TileMesh::~TileMesh()
+{
+ dtFreeNavMeshQuery(m_navQuery);
+ dtFreeNavMesh(m_navMesh);
+ dtFreeCrowd(m_crowd);
+ cleanup();
+ m_navMesh = 0;
+}
+
+void TileMesh::resetCommonSettings() {
+ //m_cellSize = 0.3f;
+ //m_cellHeight = 0.2f;
+ //m_agentHeight = 2.0f;
+ //m_agentRadius = 0.6f;
+ //m_agentMaxClimb = 0.9f;
+ //m_agentMaxSlope = 45.0f;
+ m_regionMinSize = 8;
+ m_regionMergeSize = 20;
+ m_monotonePartitioning = false;
+ m_edgeMaxLen = 12.0f;
+ m_edgeMaxError = 1.3f;
+ m_vertsPerPoly = 6.0f;
+ m_detailSampleDist = 6.0f;
+ m_detailSampleMaxError = 1.0f;
+ m_tileSize = 128;
+
+ NavMeshParameters nmpd;
+ applySettings(nmpd);
+}
+
+void TileMesh::applySettings(NavMeshParameters& nmp) {
+ //Check if we are assuming right in how the struct is constructed internally
+ // Padding makes the struct 7*4 bytes big rather than 6*4 + 1
+ LOG_ASSERT(sizeof(float)*7 == sizeof(NavMeshParameters));
+
+ m_cellSize = nmp.m_cellSize;
+ m_cellHeight = nmp.m_cellHeight;
+ m_agentHeight = nmp.m_agentHeight;
+ m_agentRadius = nmp.m_agentRadius;
+ m_agentMaxClimb = nmp.m_agentMaxClimb;
+ m_agentMaxSlope = nmp.m_agentMaxSlope;
+}
+
+void TileMesh::cleanup()
+{
+ delete [] m_triareas;
+ m_triareas = 0;
+ rcFreeHeightField(m_solid);
+ m_solid = 0;
+ rcFreeCompactHeightfield(m_chf);
+ m_chf = 0;
+ rcFreeContourSet(m_cset);
+ m_cset = 0;
+ rcFreePolyMesh(m_pmesh);
+ m_pmesh = 0;
+ rcFreePolyMeshDetail(m_dmesh);
+ m_dmesh = 0;
+}
+
+
+static const int NAVMESHSET_MAGIC = 'M'<<24 | 'S'<<16 | 'E'<<8 | 'T'; //'MSET';
+static const int NAVMESHSET_VERSION = 1;
+
+struct NavMeshSetHeader
+{
+ int magic;
+ int version;
+ int numTiles;
+ dtNavMeshParams params;
+};
+
+struct NavMeshTileHeader
+{
+ dtTileRef tileRef;
+ int dataSize;
+};
+
+void TileMesh::saveAll(const char* path, const dtNavMesh* mesh)
+{
+ if (!mesh) return;
+
+ FILE* fp = my_fopen(path, "wb");
+ if (!fp)
+ return;
+
+ // Store header.
+ NavMeshSetHeader header;
+ header.magic = NAVMESHSET_MAGIC;
+ header.version = NAVMESHSET_VERSION;
+ header.numTiles = 0;
+ for (int i = 0; i < mesh->getMaxTiles(); ++i)
+ {
+ const dtMeshTile* tile = mesh->getTile(i);
+ if (!tile || !tile->header || !tile->dataSize) continue;
+ header.numTiles++;
+ }
+ memcpy(&header.params, mesh->getParams(), sizeof(dtNavMeshParams));
+ fwrite(&header, sizeof(NavMeshSetHeader), 1, fp);
+
+ // Store tiles.
+ for (int i = 0; i < mesh->getMaxTiles(); ++i)
+ {
+ const dtMeshTile* tile = mesh->getTile(i);
+ if (!tile || !tile->header || !tile->dataSize) continue;
+
+ NavMeshTileHeader tileHeader;
+ tileHeader.tileRef = mesh->getTileRef(tile);
+ tileHeader.dataSize = tile->dataSize;
+ fwrite(&tileHeader, sizeof(tileHeader), 1, fp);
+
+ fwrite(tile->data, tile->dataSize, 1, fp);
+ }
+
+ fclose(fp);
+}
+
+dtNavMesh* TileMesh::loadAllMem(const char* data, size_t size)
+{
+ if( data && size > 0 ) {
+ const char* pos = data;
+
+ NavMeshSetHeader header;
+ LOG_ASSERT(pos+sizeof(NavMeshSetHeader) <= data+size);
+ memcpy(&header, pos, sizeof(NavMeshSetHeader));
+ pos += sizeof(NavMeshSetHeader);
+
+ if (header.magic != NAVMESHSET_MAGIC)
+ {
+ return 0;
+ }
+ if (header.version != NAVMESHSET_VERSION)
+ {
+ return 0;
+ }
+
+ dtNavMesh* mesh = dtAllocNavMesh();
+ if (!mesh)
+ {
+ return 0;
+ }
+ dtStatus status = mesh->init(&header.params);
+ if (dtStatusFailed(status))
+ {
+ return 0;
+ }
+
+ // Read tiles.
+ for (int i = 0; i < header.numTiles; ++i)
+ {
+ NavMeshTileHeader tileHeader;
+
+ LOG_ASSERT(pos+sizeof(tileHeader) <= data+size);
+ memcpy(&tileHeader, pos, sizeof(tileHeader));
+ pos += sizeof(tileHeader);
+ if (!tileHeader.tileRef || !tileHeader.dataSize)
+ break;
+
+ unsigned char* tile_data = (unsigned char*)dtAlloc(tileHeader.dataSize, DT_ALLOC_PERM);
+ if (!data) break;
+ memset(tile_data, 0, tileHeader.dataSize);
+
+ LOG_ASSERT(pos+tileHeader.dataSize <= data+size);
+ memcpy(tile_data, pos, tileHeader.dataSize);
+ pos += tileHeader.dataSize;
+
+ mesh->addTile(tile_data, tileHeader.dataSize, DT_TILE_FREE_DATA, tileHeader.tileRef, 0);
+ }
+
+ return mesh;
+ } else {
+ LOGE << "Invalid data to load for tilemesh" << endl;
+ return NULL;
+ }
+}
+
+dtNavMesh* TileMesh::loadAll(const char* path)
+{
+ FILE* fp = my_fopen(path, "rb");
+ if (!fp) return 0;
+
+ // Read header.
+ NavMeshSetHeader header;
+ fread(&header, sizeof(NavMeshSetHeader), 1, fp);
+ if (header.magic != NAVMESHSET_MAGIC)
+ {
+ fclose(fp);
+ return 0;
+ }
+ if (header.version != NAVMESHSET_VERSION)
+ {
+ fclose(fp);
+ return 0;
+ }
+
+ dtNavMesh* mesh = dtAllocNavMesh();
+ if (!mesh)
+ {
+ fclose(fp);
+ return 0;
+ }
+ dtStatus status = mesh->init(&header.params);
+ if (dtStatusFailed(status))
+ {
+ fclose(fp);
+ return 0;
+ }
+
+ // Read tiles.
+ for (int i = 0; i < header.numTiles; ++i)
+ {
+ NavMeshTileHeader tileHeader;
+ fread(&tileHeader, sizeof(tileHeader), 1, fp);
+ if (!tileHeader.tileRef || !tileHeader.dataSize)
+ break;
+
+ unsigned char* data = (unsigned char*)dtAlloc(tileHeader.dataSize, DT_ALLOC_PERM);
+ if (!data) break;
+ memset(data, 0, tileHeader.dataSize);
+ fread(data, tileHeader.dataSize, 1, fp);
+
+ mesh->addTile(data, tileHeader.dataSize, DT_TILE_FREE_DATA, tileHeader.tileRef, 0);
+ }
+
+ fclose(fp);
+
+ return mesh;
+}
+
+void TileMesh::Save(const char *path) {
+ saveAll(path, m_navMesh);
+}
+
+void TileMesh::Load(const char *path){
+ dtFreeNavMesh(m_navMesh);
+ m_navMesh = loadAll(path);
+ m_navQuery->init(m_navMesh, 2048);
+}
+
+void TileMesh::LoadMem(const char *data, size_t size){
+ dtFreeNavMesh(m_navMesh);
+ m_navMesh = loadAllMem(data,size);
+ m_navQuery->init(m_navMesh, 2048);
+}
+
+bool TileMesh::handleBuild()
+{
+ if (!m_geom || !m_geom->getMesh())
+ {
+ LOGE << "buildTiledNavigation: No vertices and triangles." << endl;
+ return false;
+ }
+
+ dtFreeNavMesh(m_navMesh);
+
+ m_navMesh = dtAllocNavMesh();
+ if (!m_navMesh)
+ {
+ LOGE << "buildTiledNavigation: Could not allocate navmesh." << endl;
+ return false;
+ }
+
+ dtNavMeshParams params;
+
+ pair<vec3,vec3> meshBounds = GetBoundaries();
+ const float* bmin = meshBounds.first.entries;
+
+ rcVcopy(params.orig, bmin);
+ params.tileWidth = m_tileSize*m_cellSize;
+ params.tileHeight = m_tileSize*m_cellSize;
+ params.maxTiles = m_maxTiles;
+ params.maxPolys = m_maxPolysPerTile;
+
+ dtStatus status;
+
+ status = m_navMesh->init(&params);
+ if (dtStatusFailed(status))
+ {
+ LOGE << "buildTiledNavigation: Could not init navmesh." << endl;
+ return false;
+ }
+
+ status = m_navQuery->init(m_navMesh, 2048);
+ if (dtStatusFailed(status))
+ {
+ LOGE << "buildTiledNavigation: Could not init Detour navmesh query" << endl;
+ return false;
+ }
+
+ if (m_buildAll)
+ buildAllTiles();
+
+ return true;
+}
+
+// Analyze the mesh and flag each vertex if it belongs to a watertight mesh
+// island that must be filled in.
+// This method no longer seems necessary since i updated recast, as the functions don't exist.
+// A search for the function name online doesn't return anything, and the
+// new reference implementation has no mention of a mesh filling.
+/*
+static bool* GetVertsNeedFillFromMesh(const rcMeshLoaderObj* mesh){
+ const int nverts = mesh->getVertCount();
+ const int ntris = mesh->getTriCount();
+ const int* tris = mesh->getTris();
+
+ bool *verts_fill = new bool[nverts];
+ memset(verts_fill, false, sizeof(bool)*nverts);
+ rcGetVertsNeedFill(tris, ntris, verts_fill);
+ return verts_fill;
+}
+*/
+
+void TileMesh::buildTile(const float* pos)
+{
+ if (!m_geom) return;
+ if (!m_navMesh) return;
+
+ pair<vec3,vec3> meshBounds = GetBoundaries();
+ const float* bmin = meshBounds.first.entries;
+ const float* bmax = meshBounds.second.entries;
+
+ const float ts = m_tileSize*m_cellSize;
+ const int tx = (int)((pos[0] - bmin[0]) / ts);
+ const int ty = (int)((pos[2] - bmin[2]) / ts);
+
+ m_tileBmin[0] = bmin[0] + tx*ts;
+ m_tileBmin[1] = bmin[1];
+ m_tileBmin[2] = bmin[2] + ty*ts;
+
+ m_tileBmax[0] = bmin[0] + (tx+1)*ts;
+ m_tileBmax[1] = bmax[1];
+ m_tileBmax[2] = bmin[2] + (ty+1)*ts;
+
+ int dataSize = 0;
+ unsigned char* data = buildTileMesh(tx, ty, m_tileBmin, m_tileBmax, dataSize);
+
+ if (data)
+ {
+ // Remove any previous data (navmesh owns and deletes the data).
+ m_navMesh->removeTile(m_navMesh->getTileRefAt(tx,ty,0),0,0);
+
+ // Let the navmesh own the data.
+ dtStatus status = m_navMesh->addTile(data,dataSize,DT_TILE_FREE_DATA,0,0);
+ if (dtStatusFailed(status))
+ dtFree(data);
+ }
+}
+
+void TileMesh::getTilePos(const float* pos, int& tx, int& ty)
+{
+ if (!m_geom) return;
+
+ pair<vec3,vec3> meshBounds = GetBoundaries();
+ const float* bmin = meshBounds.first.entries;
+
+ const float ts = m_tileSize*m_cellSize;
+ tx = (int)((pos[0] - bmin[0]) / ts);
+ ty = (int)((pos[2] - bmin[2]) / ts);
+}
+
+void TileMesh::removeTile(const float* pos)
+{
+ if (!m_geom) return;
+ if (!m_navMesh) return;
+
+ pair<vec3,vec3> meshBounds = GetBoundaries();
+ const float* bmin = meshBounds.first.entries;
+ const float* bmax = meshBounds.second.entries;
+
+ const float ts = m_tileSize*m_cellSize;
+ const int tx = (int)((pos[0] - bmin[0]) / ts);
+ const int ty = (int)((pos[2] - bmin[2]) / ts);
+
+ m_tileBmin[0] = bmin[0] + tx*ts;
+ m_tileBmin[1] = bmin[1];
+ m_tileBmin[2] = bmin[2] + ty*ts;
+
+ m_tileBmax[0] = bmin[0] + (tx+1)*ts;
+ m_tileBmax[1] = bmax[1];
+ m_tileBmax[2] = bmin[2] + (ty+1)*ts;
+
+ m_navMesh->removeTile(m_navMesh->getTileRefAt(tx,ty,0),0,0);
+}
+
+void TileMesh::buildAllTiles()
+{
+ if (!m_geom) return;
+ if (!m_navMesh) return;
+
+ pair<vec3,vec3> meshBounds = GetBoundaries();
+ const float* bmin = meshBounds.first.entries;
+ const float* bmax = meshBounds.second.entries;
+ int gw = 0, gh = 0;
+ rcCalcGridSize(bmin, bmax, m_cellSize, &gw, &gh);
+ const int ts = (int)m_tileSize;
+ const int tw = (gw + ts-1) / ts;
+ const int th = (gh + ts-1) / ts;
+ const float tcs = m_tileSize*m_cellSize;
+
+ LOGI << "Navmesh: tile dimensions are " << tw << "x" << th << endl;
+
+ int last_percent = 0;
+ time_t last_time = time(0);
+ time_t start_time = last_time;
+ for (int y = 0; y < th; ++y)
+ {
+ for (int x = 0; x < tw; ++x)
+ {
+ m_tileBmin[0] = bmin[0] + x*tcs;
+ m_tileBmin[1] = bmin[1];
+ m_tileBmin[2] = bmin[2] + y*tcs;
+
+ m_tileBmax[0] = bmin[0] + (x+1)*tcs;
+ m_tileBmax[1] = bmax[1];
+ m_tileBmax[2] = bmin[2] + (y+1)*tcs;
+
+ int dataSize = 0;
+ unsigned char* data = buildTileMesh(x, y, m_tileBmin, m_tileBmax, dataSize);
+ if (data)
+ {
+ // Remove any previous data (navmesh owns and deletes the data).
+ m_navMesh->removeTile(m_navMesh->getTileRefAt(x,y,0),0,0);
+ // Let the navmesh own the data.
+ dtStatus status = m_navMesh->addTile(data,dataSize,DT_TILE_FREE_DATA,0,0);
+ if (dtStatusFailed(status))
+ dtFree(data);
+ }
+ }
+
+ float percent = y / (float)th * 100.0f;
+ time_t current_time = time(0);
+ int seconds = (int) difftime(current_time, last_time);
+ if(((int)percent != last_percent && (int)percent % 5 == 0) || seconds >= 15) {
+ char buffer[512];
+ sprintf(buffer, "Navmesh: %d seconds elapsed, %.2f%% done", (int)difftime(current_time, start_time), percent);
+ last_percent = (int) percent;
+ last_time = current_time;
+ AddLoadingText(string(buffer));
+ }
+ }
+}
+
+void TileMesh::removeAllTiles()
+{
+ pair<vec3,vec3> meshBounds = GetBoundaries();
+ const float* bmin = meshBounds.first.entries;
+ const float* bmax = meshBounds.second.entries;
+ int gw = 0, gh = 0;
+ rcCalcGridSize(bmin, bmax, m_cellSize, &gw, &gh);
+ const int ts = (int)m_tileSize;
+ const int tw = (gw + ts-1) / ts;
+ const int th = (gh + ts-1) / ts;
+
+ for (int y = 0; y < th; ++y)
+ for (int x = 0; x < tw; ++x)
+ m_navMesh->removeTile(m_navMesh->getTileRefAt(x,y,0),0,0);
+}
+
+
+unsigned char* TileMesh::buildTileMesh(const int tx, const int ty, const float* bmin, const float* bmax, int& dataSize)
+{
+ if (!m_geom || !m_geom->getMesh() || !m_geom->getChunkyMesh())
+ {
+ LOGE << "buildNavigation: Input mesh is not specified." << endl;
+ return 0;
+ }
+
+ m_tileMemUsage = 0;
+ m_tileBuildTime = 0;
+
+ cleanup();
+
+ const float* verts = m_geom->getMesh()->getVerts();
+ const int nverts = m_geom->getMesh()->getVertCount();
+ const rcChunkyTriMesh* chunkyMesh = m_geom->getChunkyMesh();
+
+ // Init build configuration from GUI
+ memset(&m_cfg, 0, sizeof(m_cfg));
+ m_cfg.cs = m_cellSize;
+ m_cfg.ch = m_cellHeight;
+ m_cfg.walkableSlopeAngle = m_agentMaxSlope;
+ m_cfg.walkableHeight = (int)ceilf(m_agentHeight / m_cfg.ch);
+ m_cfg.walkableClimb = (int)floorf(m_agentMaxClimb / m_cfg.ch);
+ m_cfg.walkableRadius = (int)ceilf(m_agentRadius / m_cfg.cs);
+ m_cfg.maxEdgeLen = (int)(m_edgeMaxLen / m_cellSize);
+ m_cfg.maxSimplificationError = m_edgeMaxError;
+ m_cfg.minRegionArea = (int)rcSqr(m_regionMinSize); // Note: area = size*size
+ m_cfg.mergeRegionArea = (int)rcSqr(m_regionMergeSize); // Note: area = size*size
+ m_cfg.maxVertsPerPoly = (int)m_vertsPerPoly;
+ m_cfg.tileSize = (int)m_tileSize;
+ m_cfg.borderSize = m_cfg.walkableRadius + 3; // Reserve enough padding.
+ m_cfg.width = m_cfg.tileSize + m_cfg.borderSize*2;
+ m_cfg.height = m_cfg.tileSize + m_cfg.borderSize*2;
+ m_cfg.detailSampleDist = m_detailSampleDist < 0.9f ? 0 : m_cellSize * m_detailSampleDist;
+ m_cfg.detailSampleMaxError = m_cellHeight * m_detailSampleMaxError;
+
+ rcVcopy(m_cfg.bmin, bmin);
+ rcVcopy(m_cfg.bmax, bmax);
+ m_cfg.bmin[0] -= m_cfg.borderSize*m_cfg.cs;
+ m_cfg.bmin[2] -= m_cfg.borderSize*m_cfg.cs;
+ m_cfg.bmax[0] += m_cfg.borderSize*m_cfg.cs;
+ m_cfg.bmax[2] += m_cfg.borderSize*m_cfg.cs;
+
+ //LOGI << "Building navigation:" << endl;
+ //LOGI << m_cfg.width << " x " << m_cfg.height << " cells" << endl;
+ //LOGI.Format( " - %.1fK verts, %.1fK tris\n", nverts/1000.0f, ntris/1000.0f);
+
+ // Allocate voxel heightfield where we rasterize our input data to.
+ m_solid = rcAllocHeightfield();
+ if (!m_solid)
+ {
+ LOGE << "buildNavigation: Out of memory 'solid'." << endl;
+ return 0;
+ }
+ if (!rcCreateHeightfield(m_ctx, *m_solid, m_cfg.width, m_cfg.height, m_cfg.bmin, m_cfg.bmax, m_cfg.cs, m_cfg.ch))
+ {
+ LOGE << "buildNavigation: Could not create solid heightfield." << endl;
+ return 0;
+ }
+
+ // Allocate array that can hold triangle flags.
+ // If you have multiple meshes you need to process, allocate
+ // and array which can hold the max number of triangles you need to process.
+ m_triareas = new unsigned char[chunkyMesh->maxTrisPerChunk];
+ if (!m_triareas)
+ {
+ LOGE.Format("buildNavigation: Out of memory 'm_triareas' (%d).", chunkyMesh->maxTrisPerChunk);
+ return 0;
+ }
+
+ float tbmin[2], tbmax[2];
+ tbmin[0] = m_cfg.bmin[0];
+ tbmin[1] = m_cfg.bmin[2];
+ tbmax[0] = m_cfg.bmax[0];
+ tbmax[1] = m_cfg.bmax[2];
+
+ const int kMaxCID = 4096;
+ int cid[kMaxCID];// TODO: Make grow when returning too many items.
+ const int ncid = rcGetChunksOverlappingRect(chunkyMesh, tbmin, tbmax, cid, kMaxCID);
+ if (!ncid)
+ return 0;
+ if(ncid == kMaxCID){
+ printf("Need to increase kMaxCID.\n");
+ }
+
+ m_tileTriCount = 0;
+
+ // Count the total number of triangles in the relevant chunky mesh nodes,
+ // and then put them in an array so that FillTriangles can process them
+ // all at once.
+ int n_total_tris = 0;
+ for (int i = 0; i < ncid; ++i)
+ {
+ const rcChunkyTriMeshNode& node = chunkyMesh->nodes[cid[i]];
+ const int nctris = node.n;
+ n_total_tris += nctris;
+ }
+
+ int *total_tris = new int[n_total_tris*3];
+ n_total_tris = 0;
+
+ for (int i = 0; i < ncid; ++i)
+ {
+ const rcChunkyTriMeshNode& node = chunkyMesh->nodes[cid[i]];
+ const int* ctris = &chunkyMesh->tris[node.i*3];
+ const int nctris = node.n;
+
+ memcpy(&total_tris[n_total_tris*3], ctris, nctris * sizeof(int) * 3);
+ n_total_tris += nctris;
+ }
+
+ delete[] total_tris;
+
+ for (int i = 0; i < ncid; ++i)
+ {
+ const rcChunkyTriMeshNode& node = chunkyMesh->nodes[cid[i]];
+ const int* ctris = &chunkyMesh->tris[node.i*3];
+ const int nctris = node.n;
+
+ m_tileTriCount += nctris;
+
+ memset(m_triareas, 0, nctris*sizeof(unsigned char));
+ rcMarkWalkableTriangles(m_ctx, m_cfg.walkableSlopeAngle,
+ verts, nverts, ctris, nctris, m_triareas);
+
+ rcRasterizeTriangles(m_ctx, verts, nverts, ctris, m_triareas, nctris, *m_solid, m_cfg.walkableClimb);
+ }
+
+ if (!m_keepInterResults)
+ {
+ delete [] m_triareas;
+ m_triareas = 0;
+ }
+
+ // Once all geometry is rasterized, we do initial pass of filtering to
+ // remove unwanted overhangs caused by the conservative rasterization
+ // as well as filter spans where the character cannot possibly stand.
+ rcFilterLowHangingWalkableObstacles(m_ctx, m_cfg.walkableClimb, *m_solid);
+ rcFilterLedgeSpans(m_ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid);
+ rcFilterWalkableLowHeightSpans(m_ctx, m_cfg.walkableHeight, *m_solid);
+
+ // Compact the heightfield so that it is faster to handle from now on.
+ // This will result more cache coherent data as well as the neighbours
+ // between walkable cells will be calculated.
+ m_chf = rcAllocCompactHeightfield();
+ if (!m_chf)
+ {
+ LOGE.Format("buildNavigation: Out of memory 'chf'.\n");
+ return 0;
+ }
+ if (!rcBuildCompactHeightfield(m_ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid, *m_chf))
+ {
+ LOGE.Format("buildNavigation: Could not build compact data.\n");
+ return 0;
+ }
+
+ if (!m_keepInterResults)
+ {
+ rcFreeHeightField(m_solid);
+ m_solid = 0;
+ }
+
+ // Erode the walkable area by agent radius.
+ if (!rcErodeWalkableArea(m_ctx, m_cfg.walkableRadius, *m_chf))
+ {
+ LOGE.Format("buildNavigation: Could not erode.\n");
+ return 0;
+ }
+
+ // (Optional) Mark areas.
+ const ConvexVolume* vols = m_geom->getConvexVolumes();
+ for (int i = 0; i < m_geom->getConvexVolumeCount(); ++i)
+ rcMarkConvexPolyArea(m_ctx, vols[i].verts, vols[i].nverts, vols[i].hmin, vols[i].hmax, (unsigned char)vols[i].area, *m_chf);
+
+ if (m_monotonePartitioning)
+ {
+ // Partition the walkable surface into simple regions without holes.
+ if (!rcBuildRegionsMonotone(m_ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea, m_cfg.mergeRegionArea))
+ {
+ LOGE.Format("buildNavigation: Could not build regions.\n");
+ return 0;
+ }
+ }
+ else
+ {
+ // Prepare for region partitioning, by calculating distance field along the walkable surface.
+ if (!rcBuildDistanceField(m_ctx, *m_chf))
+ {
+ LOGE.Format("buildNavigation: Could not build distance field.\n");
+ return 0;
+ }
+
+ // Partition the walkable surface into simple regions without holes.
+ if (!rcBuildRegions(m_ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea, m_cfg.mergeRegionArea))
+ {
+ LOGE.Format("buildNavigation: Could not build regions.\n");
+ return 0;
+ }
+ }
+
+ // Create contours.
+ m_cset = rcAllocContourSet();
+ if (!m_cset)
+ {
+ LOGE.Format("buildNavigation: Out of memory 'cset'.\n");
+ return 0;
+ }
+ if (!rcBuildContours(m_ctx, *m_chf, m_cfg.maxSimplificationError, m_cfg.maxEdgeLen, *m_cset))
+ {
+ LOGE << "buildNavigation: Could not create contours." << endl;
+ return 0;
+ }
+
+ if (m_cset->nconts == 0)
+ {
+ return 0;
+ }
+
+ // Build polygon navmesh from the contours.
+ m_pmesh = rcAllocPolyMesh();
+ if (!m_pmesh)
+ {
+ LOGE << "buildNavigation: Out of memory 'pmesh'." << endl;
+ return 0;
+ }
+ if (!rcBuildPolyMesh(m_ctx, *m_cset, m_cfg.maxVertsPerPoly, *m_pmesh))
+ {
+ LOGE << "buildNavigation: Could not triangulate contours." << endl;
+ return 0;
+ }
+
+ // Build detail mesh.
+ m_dmesh = rcAllocPolyMeshDetail();
+ if (!m_dmesh)
+ {
+ LOGE << "buildNavigation: Out of memory 'dmesh'." << endl;
+ return 0;
+ }
+
+ if (!rcBuildPolyMeshDetail(m_ctx, *m_pmesh, *m_chf,
+ m_cfg.detailSampleDist, m_cfg.detailSampleMaxError,
+ *m_dmesh))
+ {
+ LOGE << "buildNavigation: Could build polymesh detail." << endl;
+ return 0;
+ }
+
+ if (!m_keepInterResults)
+ {
+ rcFreeCompactHeightfield(m_chf);
+ m_chf = 0;
+ rcFreeContourSet(m_cset);
+ m_cset = 0;
+ }
+
+ unsigned char* navData = 0;
+ int navDataSize = 0;
+ if (m_cfg.maxVertsPerPoly <= DT_VERTS_PER_POLYGON)
+ {
+ if (m_pmesh->nverts >= 0xffff)
+ {
+ // The vertex indices are ushorts, and cannot point to more than 0xffff vertices.
+ LOGE.Format("Too many vertices per tile %d (max: %d).\n", m_pmesh->nverts, 0xffff);
+ return 0;
+ }
+
+ // Update poly flags from areas.
+ for (int i = 0; i < m_pmesh->npolys; ++i)
+ {
+ if (m_pmesh->areas[i] == RC_WALKABLE_AREA)
+ m_pmesh->areas[i] = SAMPLE_POLYAREA_GROUND;
+
+ if (m_pmesh->areas[i] == SAMPLE_POLYAREA_GROUND ||
+ m_pmesh->areas[i] == SAMPLE_POLYAREA_GRASS ||
+ m_pmesh->areas[i] == SAMPLE_POLYAREA_ROAD)
+ {
+ m_pmesh->flags[i] = SAMPLE_POLYFLAGS_WALK;
+ }
+ else if (m_pmesh->areas[i] == SAMPLE_POLYAREA_WATER)
+ {
+ m_pmesh->flags[i] = SAMPLE_POLYFLAGS_SWIM;
+ }
+ else if (m_pmesh->areas[i] == SAMPLE_POLYAREA_DOOR)
+ {
+ m_pmesh->flags[i] = SAMPLE_POLYFLAGS_WALK | SAMPLE_POLYFLAGS_DOOR;
+ }
+ }
+
+ dtNavMeshCreateParams params;
+ memset(&params, 0, sizeof(params));
+ params.verts = m_pmesh->verts;
+ params.vertCount = m_pmesh->nverts;
+ params.polys = m_pmesh->polys;
+ params.polyAreas = m_pmesh->areas;
+ params.polyFlags = m_pmesh->flags;
+ params.polyCount = m_pmesh->npolys;
+ params.nvp = m_pmesh->nvp;
+ params.detailMeshes = m_dmesh->meshes;
+ params.detailVerts = m_dmesh->verts;
+ params.detailVertsCount = m_dmesh->nverts;
+ params.detailTris = m_dmesh->tris;
+ params.detailTriCount = m_dmesh->ntris;
+ params.offMeshConVerts = m_geom->getOffMeshConnectionVerts();
+ params.offMeshConRad = m_geom->getOffMeshConnectionRads();
+ params.offMeshConDir = m_geom->getOffMeshConnectionDirs();
+ params.offMeshConAreas = m_geom->getOffMeshConnectionAreas();
+ params.offMeshConFlags = m_geom->getOffMeshConnectionFlags();
+ params.offMeshConUserID = m_geom->getOffMeshConnectionId();
+ params.offMeshConCount = m_geom->getOffMeshConnectionCount();
+ params.walkableHeight = m_agentHeight;
+ params.walkableRadius = m_agentRadius;
+ params.walkableClimb = m_agentMaxClimb;
+ params.tileX = tx;
+ params.tileY = ty;
+ params.tileLayer = 0;
+ rcVcopy(params.bmin, m_pmesh->bmin);
+ rcVcopy(params.bmax, m_pmesh->bmax);
+ params.cs = m_cfg.cs;
+ params.ch = m_cfg.ch;
+ params.buildBvTree = true;
+
+ if (!dtCreateNavMeshData(&params, &navData, &navDataSize))
+ {
+ LOGE << "Could not build Detour navmesh." << endl;
+ return 0;
+ }
+ }
+ m_tileMemUsage = navDataSize/1024.0f;
+
+ //LOGI.Format(">> Polymesh: %d vertices %d polygons\n", m_pmesh->nverts, m_pmesh->npolys);
+
+ dataSize = navDataSize;
+ return navData;
+}
+
+const rcPolyMesh* TileMesh::getPolyMesh() const
+{
+ return m_pmesh;
+}
+
+void TileMesh::handleMeshChanged(class InputGeom* geom)
+{
+ m_geom = geom;
+
+ cleanup();
+
+ dtFreeNavMesh(m_navMesh);
+ m_navMesh = 0;
+}
+
+void TileMesh::SetExplicitBounderies(vec3 min, vec3 max )
+{
+ m_use_explicit_bounderies = true;
+ m_Bmin = min;
+ m_Bmax = max;
+}
+
+pair<vec3,vec3> TileMesh::GetBoundaries()
+{
+ if( m_use_explicit_bounderies )
+ {
+ return pair<vec3,vec3>(m_Bmin,m_Bmax);
+ }
+ else
+ {
+ const float* bmin = m_geom->getMeshBoundsMin();
+ const float* bmax = m_geom->getMeshBoundsMax();
+
+ return pair<vec3,vec3>(
+ vec3(bmin[0],bmin[1],bmin[2]),
+ vec3(bmax[0],bmax[1],bmax[2]));
+ }
+}
+
+void TileMesh::RemoveExplicitBounderies()
+{
+ m_use_explicit_bounderies = false;
+}
+
+const dtNavMesh* TileMesh::getNavMesh() const
+{
+ return m_navMesh;
+}
+
+void TileMesh::handleSettings()
+{
+ handleCommonSettings();
+
+ if (m_geom)
+ {
+ const float* bmin = m_geom->getMeshBoundsMin();
+ const float* bmax = m_geom->getMeshBoundsMax();
+ char text[64];
+ int gw = 0, gh = 0;
+ rcCalcGridSize(bmin, bmax, m_cellSize, &gw, &gh);
+ const int ts = (int)m_tileSize;
+ const int tw = (gw + ts-1) / ts;
+ const int th = (gh + ts-1) / ts;
+ snprintf(text, 64, "Tiles %d x %d", tw, th);
+
+ // Max tiles and max polys affect how the tile IDs are caculated.
+ // There are 22 bits available for identifying a tile and a polygon.
+ int tileBits = rcMin((int)ilog2(nextPow2(tw*th)), 14);
+ if (tileBits > 14) tileBits = 14;
+ int polyBits = 22 - tileBits;
+ m_maxTiles = 1 << tileBits;
+ m_maxPolysPerTile = 1 << polyBits;
+ snprintf(text, 64, "Max Tiles %d", m_maxTiles);
+ snprintf(text, 64, "Max Polys %d", m_maxPolysPerTile);
+ }
+ else
+ {
+ m_maxTiles = 0;
+ m_maxPolysPerTile = 0;
+ }
+
+
+ char msg[64];
+ snprintf(msg, 64, "Build Time: %.1fms", m_totalBuildTimeMs);
+}
+
+void TileMesh::handleCommonSettings()
+{
+ if (m_geom)
+ {
+ const float* bmin = m_geom->getMeshBoundsMin();
+ const float* bmax = m_geom->getMeshBoundsMax();
+ int gw = 0, gh = 0;
+ rcCalcGridSize(bmin, bmax, m_cellSize, &gw, &gh);
+ char text[64];
+ snprintf(text, 64, "Voxels %d x %d", gw, gh);
+ }
+}
diff --git a/Source/AI/tilemesh.h b/Source/AI/tilemesh.h
new file mode 100644
index 00000000..e1fb22f3
--- /dev/null
+++ b/Source/AI/tilemesh.h
@@ -0,0 +1,131 @@
+//-----------------------------------------------------------------------------
+// Name: tilemesh.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+#include <AI/navmeshparameters.h>
+#include <AI/sample.h>
+#include <AI/chunky_tri_mesh.h>
+
+#include <Recast.h>
+#include <DetourNavMesh.h>
+#include <DetourCrowd.h>
+
+using std::pair;
+
+class TileMesh
+{
+protected:
+ bool m_keepInterResults;
+ bool m_buildAll;
+ float m_totalBuildTimeMs;
+
+ unsigned char* m_triareas;
+ rcHeightfield* m_solid;
+ rcCompactHeightfield* m_chf;
+ rcContourSet* m_cset;
+ rcPolyMesh* m_pmesh;
+ rcPolyMeshDetail* m_dmesh;
+ rcConfig m_cfg;
+
+ BuildContext* m_ctx;
+
+ class InputGeom* m_geom;
+ class dtNavMesh* m_navMesh;
+ class dtNavMeshQuery* m_navQuery;
+ class dtCrowd* m_crowd;
+
+ unsigned char m_navMeshDrawFlags;
+
+ float m_cellSize;
+ float m_cellHeight;
+ float m_agentHeight;
+ float m_agentRadius;
+ float m_agentMaxClimb;
+ float m_agentMaxSlope;
+ float m_regionMinSize;
+ float m_regionMergeSize;
+ bool m_monotonePartitioning;
+ float m_edgeMaxLen;
+ float m_edgeMaxError;
+ float m_vertsPerPoly;
+ float m_detailSampleDist;
+ float m_detailSampleMaxError;
+
+ int m_maxTiles;
+ int m_maxPolysPerTile;
+ float m_tileSize;
+
+ unsigned int m_tileCol;
+ float m_tileBmin[3];
+ float m_tileBmax[3];
+ float m_tileBuildTime;
+ float m_tileMemUsage;
+ int m_tileTriCount;
+
+ bool m_use_explicit_bounderies;
+ vec3 m_Bmin;
+ vec3 m_Bmax;
+
+ unsigned char* buildTileMesh(const int tx, const int ty, const float* bmin, const float* bmax, int& dataSize);
+
+ void cleanup();
+
+ void saveAll(const char* path, const dtNavMesh* mesh);
+ dtNavMesh* loadAll(const char* path);
+
+ dtNavMesh* loadAllMem(const char* data, size_t size);
+public:
+ TileMesh();
+ virtual ~TileMesh();
+
+ bool handleBuild();
+
+ void getTilePos(const float* pos, int& tx, int& ty);
+
+ void buildTile(const float* pos);
+ void removeTile(const float* pos);
+ void buildAllTiles();
+ void removeAllTiles();
+ void Save(const char *path);
+ void Load(const char *path);
+ void LoadMem(const char* data, size_t size);
+
+ const rcPolyMesh* getPolyMesh() const;
+ class dtNavMeshQuery* getNavMeshQuery() { return m_navQuery; }
+
+ void resetCommonSettings();
+ void applySettings(NavMeshParameters& nmp);
+ void setContext(BuildContext* ctx) { m_ctx = ctx; }
+
+ void handleMeshChanged(class InputGeom* geom);
+
+ void SetExplicitBounderies(vec3 min, vec3 max );
+ void RemoveExplicitBounderies();
+ pair<vec3,vec3> GetBoundaries();
+
+ const dtNavMesh* getNavMesh() const;
+ void handleSettings();
+ void handleCommonSettings();
+};
diff --git a/Source/AI/tilemeshtestertool.cpp b/Source/AI/tilemeshtestertool.cpp
new file mode 100644
index 00000000..f6c5bfc7
--- /dev/null
+++ b/Source/AI/tilemeshtestertool.cpp
@@ -0,0 +1,802 @@
+//-----------------------------------------------------------------------------
+// Name: tilemeshtestertool.cpp
+// Developer: External
+// Author:
+// Description: This is a utility file from the Recast project which has been
+// extracted and modified by Wolfire Games LLC
+// License: Read below
+//-----------------------------------------------------------------------------
+
+//
+// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
+//
+// 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.
+//
+
+#define _USE_MATH_DEFINES
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "SDL.h"
+#include "opengl.h"
+#include "Recast.h"
+#include "DetourNavMesh.h"
+#include "DetourNavMeshBuilder.h"
+#include "DetourCommon.h"
+
+#include "tilemeshtestertool.h"
+
+#ifdef WIN32
+# define snprintf _snprintf
+#endif
+
+// Uncomment this to dump all the requests in stdout.
+#define DUMP_REQS
+
+/*
+// Returns a random number [0..1)
+static float frand()
+{
+// return ((float)(rand() & 0xffff)/(float)0xffff);
+ return (float)rand()/(float)RAND_MAX;
+}
+*/
+
+inline bool inRange(const float* v1, const float* v2, const float r, const float h)
+{
+ const float dx = v2[0] - v1[0];
+ const float dy = v2[1] - v1[1];
+ const float dz = v2[2] - v1[2];
+ return (dx*dx + dz*dz) < r*r && fabsf(dy) < h;
+}
+
+
+static int fixupCorridor(dtPolyRef* path, const int npath, const int maxPath,
+ const dtPolyRef* visited, const int nvisited)
+{
+ int furthestPath = -1;
+ int furthestVisited = -1;
+
+ // Find furthest common polygon.
+ for (int i = npath-1; i >= 0; --i)
+ {
+ bool found = false;
+ for (int j = nvisited-1; j >= 0; --j)
+ {
+ if (path[i] == visited[j])
+ {
+ furthestPath = i;
+ furthestVisited = j;
+ found = true;
+ }
+ }
+ if (found)
+ break;
+ }
+
+ // If no intersection found just return current path.
+ if (furthestPath == -1 || furthestVisited == -1)
+ return npath;
+
+ // Concatenate paths.
+
+ // Adjust beginning of the buffer to include the visited.
+ const int req = nvisited - furthestVisited;
+ const int orig = rcMin(furthestPath+1, npath);
+ int size = rcMax(0, npath-orig);
+ if (req+size > maxPath)
+ size = maxPath-req;
+ if (size)
+ memmove(path+req, path+orig, size*sizeof(dtPolyRef));
+
+ // Store visited
+ for (int i = 0; i < req; ++i)
+ path[i] = visited[(nvisited-1)-i];
+
+ return req+size;
+}
+
+static bool getSteerTarget(dtNavMeshQuery* navQuery, const float* startPos, const float* endPos,
+ const float minTargetDist,
+ const dtPolyRef* path, const int pathSize,
+ float* steerPos, unsigned char& steerPosFlag, dtPolyRef& steerPosRef,
+ float* outPoints = 0, int* outPointCount = 0)
+{
+ // Find steer target.
+ static const int MAX_STEER_POINTS = 3;
+ float steerPath[MAX_STEER_POINTS*3];
+ unsigned char steerPathFlags[MAX_STEER_POINTS];
+ dtPolyRef steerPathPolys[MAX_STEER_POINTS];
+ int nsteerPath = 0;
+ navQuery->findStraightPath(startPos, endPos, path, pathSize,
+ steerPath, steerPathFlags, steerPathPolys, &nsteerPath, MAX_STEER_POINTS);
+ if (!nsteerPath)
+ return false;
+
+ if (outPoints && outPointCount)
+ {
+ *outPointCount = nsteerPath;
+ for (int i = 0; i < nsteerPath; ++i)
+ dtVcopy(&outPoints[i*3], &steerPath[i*3]);
+ }
+
+
+ // Find vertex far enough to steer to.
+ int ns = 0;
+ while (ns < nsteerPath)
+ {
+ // Stop at Off-Mesh link or when point is further than slop away.
+ if ((steerPathFlags[ns] & DT_STRAIGHTPATH_OFFMESH_CONNECTION) ||
+ !inRange(&steerPath[ns*3], startPos, minTargetDist, 1000.0f))
+ break;
+ ns++;
+ }
+ // Failed to find good point to steer to.
+ if (ns >= nsteerPath)
+ return false;
+
+ dtVcopy(steerPos, &steerPath[ns*3]);
+ steerPos[1] = startPos[1];
+ steerPosFlag = steerPathFlags[ns];
+ steerPosRef = steerPathPolys[ns];
+
+ return true;
+}
+
+
+TileMeshTesterTool::TileMeshTesterTool() :
+ m_sample(0),
+ m_navMesh(0),
+ m_navQuery(0),
+ m_pathFindStatus(DT_FAILURE),
+ m_toolMode(TOOLMODE_PATHFIND_FOLLOW),
+ m_startRef(0),
+ m_endRef(0),
+ m_npolys(0),
+ m_nstraightPath(0),
+ m_nsmoothPath(0),
+ m_nrandPoints(0),
+ m_randPointsInCircle(false),
+ m_hitResult(false),
+ m_distanceToWall(0),
+ m_sposSet(false),
+ m_eposSet(false),
+ m_pathIterNum(0),
+ m_steerPointCount(0)
+{
+ m_filter.setIncludeFlags(SAMPLE_POLYFLAGS_ALL ^ SAMPLE_POLYFLAGS_DISABLED);
+ m_filter.setExcludeFlags(0);
+
+ m_polyPickExt[0] = 2;
+ m_polyPickExt[1] = 4;
+ m_polyPickExt[2] = 2;
+
+ m_neighbourhoodRadius = 2.5f;
+ m_randomRadius = 5.0f;
+}
+
+TileMeshTesterTool::~TileMeshTesterTool()
+{
+}
+
+void TileMeshTesterTool::init(Sample* sample)
+{
+ m_sample = sample;
+ m_navMesh = sample->getNavMesh();
+ m_navQuery = sample->getNavMeshQuery();
+ recalc();
+
+ if (m_navQuery)
+ {
+ // Change costs.
+ m_filter.setAreaCost(SAMPLE_POLYAREA_GROUND, 1.0f);
+ m_filter.setAreaCost(SAMPLE_POLYAREA_WATER, 10.0f);
+ m_filter.setAreaCost(SAMPLE_POLYAREA_ROAD, 1.0f);
+ m_filter.setAreaCost(SAMPLE_POLYAREA_DOOR, 1.0f);
+ m_filter.setAreaCost(SAMPLE_POLYAREA_GRASS, 2.0f);
+ m_filter.setAreaCost(SAMPLE_POLYAREA_JUMP1, 1.5f);
+ m_filter.setAreaCost(SAMPLE_POLYAREA_JUMP2, 1.5f);
+ m_filter.setAreaCost(SAMPLE_POLYAREA_JUMP3, 1.5f);
+ m_filter.setAreaCost(SAMPLE_POLYAREA_JUMP4, 1.5f);
+ m_filter.setAreaCost(SAMPLE_POLYAREA_JUMP5, 1.5f);
+ }
+
+ m_neighbourhoodRadius = sample->getAgentRadius() * 20.0f;
+ m_randomRadius = sample->getAgentRadius() * 30.0f;
+}
+
+void TileMeshTesterTool::handleMenu()
+{
+}
+
+void TileMeshTesterTool::handleClick(const float* /*s*/, const float* p, bool shift)
+{
+ if (shift)
+ {
+ m_sposSet = true;
+ dtVcopy(m_spos, p);
+ }
+ else
+ {
+ m_eposSet = true;
+ dtVcopy(m_epos, p);
+ }
+ recalc();
+}
+
+void TileMeshTesterTool::handleStep()
+{
+}
+
+void TileMeshTesterTool::handleToggle()
+{
+ // TODO: merge separate to a path iterator. Use same code in recalc() too.
+ if (m_toolMode != TOOLMODE_PATHFIND_FOLLOW)
+ return;
+
+ if (!m_sposSet || !m_eposSet || !m_startRef || !m_endRef)
+ return;
+
+ static const float STEP_SIZE = 0.5f;
+ static const float SLOP = 0.01f;
+
+ if (m_pathIterNum == 0)
+ {
+ m_navQuery->findPath(m_startRef, m_endRef, m_spos, m_epos, &m_filter, m_polys, &m_npolys, MAX_POLYS);
+ m_nsmoothPath = 0;
+
+ m_pathIterPolyCount = m_npolys;
+ if (m_pathIterPolyCount)
+ memcpy(m_pathIterPolys, m_polys, sizeof(dtPolyRef)*m_pathIterPolyCount);
+
+ if (m_pathIterPolyCount)
+ {
+ // Iterate over the path to find smooth path on the detail mesh surface.
+
+ m_navQuery->closestPointOnPolyBoundary(m_startRef, m_spos, m_iterPos);
+ m_navQuery->closestPointOnPolyBoundary(m_pathIterPolys[m_pathIterPolyCount-1], m_epos, m_targetPos);
+
+ m_nsmoothPath = 0;
+
+ dtVcopy(&m_smoothPath[m_nsmoothPath*3], m_iterPos);
+ m_nsmoothPath++;
+ }
+ }
+
+ dtVcopy(m_prevIterPos, m_iterPos);
+
+ m_pathIterNum++;
+
+ if (!m_pathIterPolyCount)
+ return;
+
+ if (m_nsmoothPath >= MAX_SMOOTH)
+ return;
+
+ // Move towards target a small advancement at a time until target reached or
+ // when ran out of memory to store the path.
+
+ // Find location to steer towards.
+ float steerPos[3];
+ unsigned char steerPosFlag;
+ dtPolyRef steerPosRef;
+
+ if (!getSteerTarget(m_navQuery, m_iterPos, m_targetPos, SLOP,
+ m_pathIterPolys, m_pathIterPolyCount, steerPos, steerPosFlag, steerPosRef,
+ m_steerPoints, &m_steerPointCount))
+ return;
+
+ dtVcopy(m_steerPos, steerPos);
+
+ bool endOfPath = (steerPosFlag & DT_STRAIGHTPATH_END) ? true : false;
+ bool offMeshConnection = (steerPosFlag & DT_STRAIGHTPATH_OFFMESH_CONNECTION) ? true : false;
+
+ // Find movement delta.
+ float delta[3], len;
+ dtVsub(delta, steerPos, m_iterPos);
+ len = sqrtf(dtVdot(delta,delta));
+ // If the steer target is end of path or off-mesh link, do not move past the location.
+ if ((endOfPath || offMeshConnection) && len < STEP_SIZE)
+ len = 1;
+ else
+ len = STEP_SIZE / len;
+ float moveTgt[3];
+ dtVmad(moveTgt, m_iterPos, delta, len);
+
+ // Move
+ float result[3];
+ dtPolyRef visited[16];
+ int nvisited = 0;
+ m_navQuery->moveAlongSurface(m_pathIterPolys[0], m_iterPos, moveTgt, &m_filter,
+ result, visited, &nvisited, 16);
+ m_pathIterPolyCount = fixupCorridor(m_pathIterPolys, m_pathIterPolyCount, MAX_POLYS, visited, nvisited);
+ float h = 0;
+ m_navQuery->getPolyHeight(m_pathIterPolys[0], result, &h);
+ result[1] = h;
+ dtVcopy(m_iterPos, result);
+
+ // Handle end of path and off-mesh links when close enough.
+ if (endOfPath && inRange(m_iterPos, steerPos, SLOP, 1.0f))
+ {
+ // Reached end of path.
+ dtVcopy(m_iterPos, m_targetPos);
+ if (m_nsmoothPath < MAX_SMOOTH)
+ {
+ dtVcopy(&m_smoothPath[m_nsmoothPath*3], m_iterPos);
+ m_nsmoothPath++;
+ }
+ return;
+ }
+ else if (offMeshConnection && inRange(m_iterPos, steerPos, SLOP, 1.0f))
+ {
+ // Reached off-mesh connection.
+ float startPos[3], endPos[3];
+
+ // Advance the path up to and over the off-mesh connection.
+ dtPolyRef prevRef = 0, polyRef = m_pathIterPolys[0];
+ int npos = 0;
+ while (npos < m_pathIterPolyCount && polyRef != steerPosRef)
+ {
+ prevRef = polyRef;
+ polyRef = m_pathIterPolys[npos];
+ npos++;
+ }
+ for (int i = npos; i < m_pathIterPolyCount; ++i)
+ m_pathIterPolys[i-npos] = m_pathIterPolys[i];
+ m_pathIterPolyCount -= npos;
+
+ // Handle the connection.
+ dtStatus status = m_navMesh->getOffMeshConnectionPolyEndPoints(prevRef, polyRef, startPos, endPos);
+ if (dtStatusSucceed(status))
+ {
+ if (m_nsmoothPath < MAX_SMOOTH)
+ {
+ dtVcopy(&m_smoothPath[m_nsmoothPath*3], startPos);
+ m_nsmoothPath++;
+ // Hack to make the dotted path not visible during off-mesh connection.
+ if (m_nsmoothPath & 1)
+ {
+ dtVcopy(&m_smoothPath[m_nsmoothPath*3], startPos);
+ m_nsmoothPath++;
+ }
+ }
+ // Move position at the other side of the off-mesh link.
+ dtVcopy(m_iterPos, endPos);
+ float eh = 0.0f;
+ m_navQuery->getPolyHeight(m_pathIterPolys[0], m_iterPos, &eh);
+ m_iterPos[1] = eh;
+ }
+ }
+
+ // Store results.
+ if (m_nsmoothPath < MAX_SMOOTH)
+ {
+ dtVcopy(&m_smoothPath[m_nsmoothPath*3], m_iterPos);
+ m_nsmoothPath++;
+ }
+
+}
+
+void TileMeshTesterTool::handleUpdate(const float /*dt*/)
+{
+ if (m_toolMode == TOOLMODE_PATHFIND_SLICED)
+ {
+ if (dtStatusInProgress(m_pathFindStatus))
+ {
+ m_pathFindStatus = m_navQuery->updateSlicedFindPath(1,0);
+ }
+ if (dtStatusSucceed(m_pathFindStatus))
+ {
+ m_navQuery->finalizeSlicedFindPath(m_polys, &m_npolys, MAX_POLYS);
+ m_nstraightPath = 0;
+ if (m_npolys)
+ {
+ // In case of partial path, make sure the end point is clamped to the last polygon.
+ float epos[3];
+ bool isOnPoly;
+ dtVcopy(epos, m_epos);
+ if (m_polys[m_npolys-1] != m_endRef)
+ m_navQuery->closestPointOnPoly(m_polys[m_npolys-1], m_epos, epos, &isOnPoly);
+
+ m_navQuery->findStraightPath(m_spos, epos, m_polys, m_npolys,
+ m_straightPath, m_straightPathFlags,
+ m_straightPathPolys, &m_nstraightPath, MAX_POLYS);
+ }
+
+ m_pathFindStatus = DT_FAILURE;
+ }
+ }
+}
+
+void TileMeshTesterTool::reset()
+{
+ m_startRef = 0;
+ m_endRef = 0;
+ m_npolys = 0;
+ m_nstraightPath = 0;
+ m_nsmoothPath = 0;
+ memset(m_hitPos, 0, sizeof(m_hitPos));
+ memset(m_hitNormal, 0, sizeof(m_hitNormal));
+ m_distanceToWall = 0;
+}
+
+void TileMeshTesterTool::recalc()
+{
+ if (!m_navMesh)
+ return;
+
+ if (m_sposSet)
+ m_navQuery->findNearestPoly(m_spos, m_polyPickExt, &m_filter, &m_startRef, 0);
+ else
+ m_startRef = 0;
+
+ if (m_eposSet)
+ m_navQuery->findNearestPoly(m_epos, m_polyPickExt, &m_filter, &m_endRef, 0);
+ else
+ m_endRef = 0;
+
+ m_pathFindStatus = DT_FAILURE;
+
+ if (m_toolMode == TOOLMODE_PATHFIND_FOLLOW)
+ {
+ m_pathIterNum = 0;
+ if (m_sposSet && m_eposSet && m_startRef && m_endRef)
+ {
+#ifdef DUMP_REQS
+ printf("pi %f %f %f %f %f %f 0x%x 0x%x\n",
+ m_spos[0],m_spos[1],m_spos[2], m_epos[0],m_epos[1],m_epos[2],
+ m_filter.getIncludeFlags(), m_filter.getExcludeFlags());
+#endif
+
+ m_navQuery->findPath(m_startRef, m_endRef, m_spos, m_epos, &m_filter, m_polys, &m_npolys, MAX_POLYS);
+
+ m_nsmoothPath = 0;
+
+ if (m_npolys)
+ {
+ // Iterate over the path to find smooth path on the detail mesh surface.
+ dtPolyRef polys[MAX_POLYS];
+ memcpy(polys, m_polys, sizeof(dtPolyRef)*m_npolys);
+ int npolys = m_npolys;
+
+ float iterPos[3], targetPos[3];
+ m_navQuery->closestPointOnPolyBoundary(m_startRef, m_spos, iterPos);
+ m_navQuery->closestPointOnPolyBoundary(polys[npolys-1], m_epos, targetPos);
+
+ static const float STEP_SIZE = 0.5f;
+ static const float SLOP = 0.01f;
+
+ m_nsmoothPath = 0;
+
+ dtVcopy(&m_smoothPath[m_nsmoothPath*3], iterPos);
+ m_nsmoothPath++;
+
+ // Move towards target a small advancement at a time until target reached or
+ // when ran out of memory to store the path.
+ while (npolys && m_nsmoothPath < MAX_SMOOTH)
+ {
+ // Find location to steer towards.
+ float steerPos[3];
+ unsigned char steerPosFlag;
+ dtPolyRef steerPosRef;
+
+ if (!getSteerTarget(m_navQuery, iterPos, targetPos, SLOP,
+ polys, npolys, steerPos, steerPosFlag, steerPosRef))
+ break;
+
+ bool endOfPath = (steerPosFlag & DT_STRAIGHTPATH_END) ? true : false;
+ bool offMeshConnection = (steerPosFlag & DT_STRAIGHTPATH_OFFMESH_CONNECTION) ? true : false;
+
+ // Find movement delta.
+ float delta[3], len;
+ dtVsub(delta, steerPos, iterPos);
+ len = (float)sqrt(dtVdot(delta,delta));
+ // If the steer target is end of path or off-mesh link, do not move past the location.
+ if ((endOfPath || offMeshConnection) && len < STEP_SIZE)
+ len = 1;
+ else
+ len = STEP_SIZE / len;
+ float moveTgt[3];
+ dtVmad(moveTgt, iterPos, delta, len);
+
+ // Move
+ float result[3];
+ dtPolyRef visited[16];
+ int nvisited = 0;
+ m_navQuery->moveAlongSurface(polys[0], iterPos, moveTgt, &m_filter,
+ result, visited, &nvisited, 16);
+
+ npolys = fixupCorridor(polys, npolys, MAX_POLYS, visited, nvisited);
+ float h = 0;
+ m_navQuery->getPolyHeight(polys[0], result, &h);
+ result[1] = h;
+ dtVcopy(iterPos, result);
+
+ // Handle end of path and off-mesh links when close enough.
+ if (endOfPath && inRange(iterPos, steerPos, SLOP, 1.0f))
+ {
+ // Reached end of path.
+ dtVcopy(iterPos, targetPos);
+ if (m_nsmoothPath < MAX_SMOOTH)
+ {
+ dtVcopy(&m_smoothPath[m_nsmoothPath*3], iterPos);
+ m_nsmoothPath++;
+ }
+ break;
+ }
+ else if (offMeshConnection && inRange(iterPos, steerPos, SLOP, 1.0f))
+ {
+ // Reached off-mesh connection.
+ float startPos[3], endPos[3];
+
+ // Advance the path up to and over the off-mesh connection.
+ dtPolyRef prevRef = 0, polyRef = polys[0];
+ int npos = 0;
+ while (npos < npolys && polyRef != steerPosRef)
+ {
+ prevRef = polyRef;
+ polyRef = polys[npos];
+ npos++;
+ }
+ for (int i = npos; i < npolys; ++i)
+ polys[i-npos] = polys[i];
+ npolys -= npos;
+
+ // Handle the connection.
+ dtStatus status = m_navMesh->getOffMeshConnectionPolyEndPoints(prevRef, polyRef, startPos, endPos);
+ if (dtStatusSucceed(status))
+ {
+ if (m_nsmoothPath < MAX_SMOOTH)
+ {
+ dtVcopy(&m_smoothPath[m_nsmoothPath*3], startPos);
+ m_nsmoothPath++;
+ // Hack to make the dotted path not visible during off-mesh connection.
+ if (m_nsmoothPath & 1)
+ {
+ dtVcopy(&m_smoothPath[m_nsmoothPath*3], startPos);
+ m_nsmoothPath++;
+ }
+ }
+ // Move position at the other side of the off-mesh link.
+ dtVcopy(iterPos, endPos);
+ float eh = 0.0f;
+ m_navQuery->getPolyHeight(polys[0], iterPos, &eh);
+ iterPos[1] = eh;
+ }
+ }
+
+ // Store results.
+ if (m_nsmoothPath < MAX_SMOOTH)
+ {
+ dtVcopy(&m_smoothPath[m_nsmoothPath*3], iterPos);
+ m_nsmoothPath++;
+ }
+ }
+ }
+
+ }
+ else
+ {
+ m_npolys = 0;
+ m_nsmoothPath = 0;
+ }
+ }
+ else if (m_toolMode == TOOLMODE_PATHFIND_STRAIGHT)
+ {
+ if (m_sposSet && m_eposSet && m_startRef && m_endRef)
+ {
+#ifdef DUMP_REQS
+ printf("ps %f %f %f %f %f %f 0x%x 0x%x\n",
+ m_spos[0],m_spos[1],m_spos[2], m_epos[0],m_epos[1],m_epos[2],
+ m_filter.getIncludeFlags(), m_filter.getExcludeFlags());
+#endif
+ m_navQuery->findPath(m_startRef, m_endRef, m_spos, m_epos, &m_filter, m_polys, &m_npolys, MAX_POLYS);
+ m_nstraightPath = 0;
+ if (m_npolys)
+ {
+ // In case of partial path, make sure the end point is clamped to the last polygon.
+ float epos[3];
+ dtVcopy(epos, m_epos);
+ bool isOnPoly;
+ if (m_polys[m_npolys-1] != m_endRef)
+ m_navQuery->closestPointOnPoly(m_polys[m_npolys-1], m_epos, epos, &isOnPoly);
+
+ m_navQuery->findStraightPath(m_spos, epos, m_polys, m_npolys,
+ m_straightPath, m_straightPathFlags,
+ m_straightPathPolys, &m_nstraightPath, MAX_POLYS);
+ }
+ }
+ else
+ {
+ m_npolys = 0;
+ m_nstraightPath = 0;
+ }
+ }
+ else if (m_toolMode == TOOLMODE_PATHFIND_SLICED)
+ {
+ if (m_sposSet && m_eposSet && m_startRef && m_endRef)
+ {
+#ifdef DUMP_REQS
+ printf("ps %f %f %f %f %f %f 0x%x 0x%x\n",
+ m_spos[0],m_spos[1],m_spos[2], m_epos[0],m_epos[1],m_epos[2],
+ m_filter.getIncludeFlags(), m_filter.getExcludeFlags());
+#endif
+ m_npolys = 0;
+ m_nstraightPath = 0;
+
+ m_pathFindStatus = m_navQuery->initSlicedFindPath(m_startRef, m_endRef, m_spos, m_epos, &m_filter);
+ }
+ else
+ {
+ m_npolys = 0;
+ m_nstraightPath = 0;
+ }
+ }
+ else if (m_toolMode == TOOLMODE_RAYCAST)
+ {
+ m_nstraightPath = 0;
+ if (m_sposSet && m_eposSet && m_startRef)
+ {
+#ifdef DUMP_REQS
+ printf("rc %f %f %f %f %f %f 0x%x 0x%x\n",
+ m_spos[0],m_spos[1],m_spos[2], m_epos[0],m_epos[1],m_epos[2],
+ m_filter.getIncludeFlags(), m_filter.getExcludeFlags());
+#endif
+ float t = 0;
+ m_npolys = 0;
+ m_nstraightPath = 2;
+ m_straightPath[0] = m_spos[0];
+ m_straightPath[1] = m_spos[1];
+ m_straightPath[2] = m_spos[2];
+ m_navQuery->raycast(m_startRef, m_spos, m_epos, &m_filter, &t, m_hitNormal, m_polys, &m_npolys, MAX_POLYS);
+ if (t > 1)
+ {
+ // No hit
+ dtVcopy(m_hitPos, m_epos);
+ m_hitResult = false;
+ }
+ else
+ {
+ // Hit
+ m_hitPos[0] = m_spos[0] + (m_epos[0] - m_spos[0]) * t;
+ m_hitPos[1] = m_spos[1] + (m_epos[1] - m_spos[1]) * t;
+ m_hitPos[2] = m_spos[2] + (m_epos[2] - m_spos[2]) * t;
+ if (m_npolys)
+ {
+ float h = 0;
+ m_navQuery->getPolyHeight(m_polys[m_npolys-1], m_hitPos, &h);
+ m_hitPos[1] = h;
+ }
+ m_hitResult = true;
+ }
+ dtVcopy(&m_straightPath[3], m_hitPos);
+ }
+ }
+ else if (m_toolMode == TOOLMODE_DISTANCE_TO_WALL)
+ {
+ m_distanceToWall = 0;
+ if (m_sposSet && m_startRef)
+ {
+#ifdef DUMP_REQS
+ printf("dw %f %f %f %f 0x%x 0x%x\n",
+ m_spos[0],m_spos[1],m_spos[2], 100.0f,
+ m_filter.getIncludeFlags(), m_filter.getExcludeFlags());
+#endif
+ m_distanceToWall = 0.0f;
+ m_navQuery->findDistanceToWall(m_startRef, m_spos, 100.0f, &m_filter, &m_distanceToWall, m_hitPos, m_hitNormal);
+ }
+ }
+ else if (m_toolMode == TOOLMODE_FIND_POLYS_IN_CIRCLE)
+ {
+ if (m_sposSet && m_startRef && m_eposSet)
+ {
+ const float dx = m_epos[0] - m_spos[0];
+ const float dz = m_epos[2] - m_spos[2];
+ float dist = sqrtf(dx*dx + dz*dz);
+#ifdef DUMP_REQS
+ printf("fpc %f %f %f %f 0x%x 0x%x\n",
+ m_spos[0],m_spos[1],m_spos[2], dist,
+ m_filter.getIncludeFlags(), m_filter.getExcludeFlags());
+#endif
+ m_navQuery->findPolysAroundCircle(m_startRef, m_spos, dist, &m_filter,
+ m_polys, m_parent, 0, &m_npolys, MAX_POLYS);
+
+ }
+ }
+ else if (m_toolMode == TOOLMODE_FIND_POLYS_IN_SHAPE)
+ {
+ if (m_sposSet && m_startRef && m_eposSet)
+ {
+ const float nx = (m_epos[2] - m_spos[2])*0.25f;
+ const float nz = -(m_epos[0] - m_spos[0])*0.25f;
+ const float agentHeight = m_sample ? m_sample->getAgentHeight() : 0;
+
+ m_queryPoly[0] = m_spos[0] + nx*1.2f;
+ m_queryPoly[1] = m_spos[1] + agentHeight/2;
+ m_queryPoly[2] = m_spos[2] + nz*1.2f;
+
+ m_queryPoly[3] = m_spos[0] - nx*1.3f;
+ m_queryPoly[4] = m_spos[1] + agentHeight/2;
+ m_queryPoly[5] = m_spos[2] - nz*1.3f;
+
+ m_queryPoly[6] = m_epos[0] - nx*0.8f;
+ m_queryPoly[7] = m_epos[1] + agentHeight/2;
+ m_queryPoly[8] = m_epos[2] - nz*0.8f;
+
+ m_queryPoly[9] = m_epos[0] + nx;
+ m_queryPoly[10] = m_epos[1] + agentHeight/2;
+ m_queryPoly[11] = m_epos[2] + nz;
+
+#ifdef DUMP_REQS
+ printf("fpp %f %f %f %f %f %f %f %f %f %f %f %f 0x%x 0x%x\n",
+ m_queryPoly[0],m_queryPoly[1],m_queryPoly[2],
+ m_queryPoly[3],m_queryPoly[4],m_queryPoly[5],
+ m_queryPoly[6],m_queryPoly[7],m_queryPoly[8],
+ m_queryPoly[9],m_queryPoly[10],m_queryPoly[11],
+ m_filter.getIncludeFlags(), m_filter.getExcludeFlags());
+#endif
+ m_navQuery->findPolysAroundShape(m_startRef, m_queryPoly, 4, &m_filter,
+ m_polys, m_parent, 0, &m_npolys, MAX_POLYS);
+ }
+ }
+ else if (m_toolMode == TOOLMODE_FIND_LOCAL_NEIGHBOURHOOD)
+ {
+ if (m_sposSet && m_startRef)
+ {
+#ifdef DUMP_REQS
+ printf("fln %f %f %f %f 0x%x 0x%x\n",
+ m_spos[0],m_spos[1],m_spos[2], m_neighbourhoodRadius,
+ m_filter.getIncludeFlags(), m_filter.getExcludeFlags());
+#endif
+ m_navQuery->findLocalNeighbourhood(m_startRef, m_spos, m_neighbourhoodRadius, &m_filter,
+ m_polys, m_parent, &m_npolys, MAX_POLYS);
+ }
+ }
+}
+/*
+static void getPolyCenter(dtNavMesh* navMesh, dtPolyRef ref, float* center)
+{
+ center[0] = 0;
+ center[1] = 0;
+ center[2] = 0;
+
+ const dtMeshTile* tile = 0;
+ const dtPoly* poly = 0;
+ dtStatus status = navMesh->getTileAndPolyByRef(ref, &tile, &poly);
+ if (dtStatusFailed(status))
+ return;
+
+ for (int i = 0; i < (int)poly->vertCount; ++i)
+ {
+ const float* v = &tile->verts[poly->verts[i]*3];
+ center[0] += v[0];
+ center[1] += v[1];
+ center[2] += v[2];
+ }
+ const float s = 1.0f / poly->vertCount;
+ center[0] *= s;
+ center[1] *= s;
+ center[2] *= s;
+}
+*/
+
+void TileMeshTesterTool::handleRenderOverlay(double* proj, double* model, int* view)
+{
+}
+
+void TileMeshTesterTool::drawAgent(const float* pos, float r, float h, float c, const unsigned int col)
+{
+}
diff --git a/Source/AI/tilemeshtestertool.h b/Source/AI/tilemeshtestertool.h
new file mode 100644
index 00000000..e391f76e
--- /dev/null
+++ b/Source/AI/tilemeshtestertool.h
@@ -0,0 +1,120 @@
+//-----------------------------------------------------------------------------
+// Name: tilemeshtestertool.h
+// Developer: External
+// Author:
+// Description: This is a utility file from the Recast project which has been
+// extracted and modified by Wolfire Games LLC
+// License: Read below
+//-----------------------------------------------------------------------------
+
+//
+// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
+//
+// 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.
+//
+
+#ifndef NAVMESHTESTERTOOL_H
+#define NAVMESHTESTERTOOL_H
+
+#include "sample.h"
+#include "DetourNavMesh.h"
+#include "DetourNavMeshQuery.h"
+
+class TileMeshTesterTool
+{
+ Sample* m_sample;
+
+ dtNavMesh* m_navMesh;
+ dtNavMeshQuery* m_navQuery;
+
+ dtQueryFilter m_filter;
+
+ dtStatus m_pathFindStatus;
+
+ enum ToolMode
+ {
+ TOOLMODE_PATHFIND_FOLLOW,
+ TOOLMODE_PATHFIND_STRAIGHT,
+ TOOLMODE_PATHFIND_SLICED,
+ TOOLMODE_RAYCAST,
+ TOOLMODE_DISTANCE_TO_WALL,
+ TOOLMODE_FIND_POLYS_IN_CIRCLE,
+ TOOLMODE_FIND_POLYS_IN_SHAPE,
+ TOOLMODE_FIND_LOCAL_NEIGHBOURHOOD
+ };
+
+ ToolMode m_toolMode;
+
+ static const int MAX_POLYS = 256;
+ static const int MAX_SMOOTH = 2048;
+
+ dtPolyRef m_startRef;
+ dtPolyRef m_endRef;
+ dtPolyRef m_polys[MAX_POLYS];
+ dtPolyRef m_parent[MAX_POLYS];
+ int m_npolys;
+ float m_straightPath[MAX_POLYS*3];
+ unsigned char m_straightPathFlags[MAX_POLYS];
+ dtPolyRef m_straightPathPolys[MAX_POLYS];
+ int m_nstraightPath;
+ float m_polyPickExt[3];
+ float m_smoothPath[MAX_SMOOTH*3];
+ int m_nsmoothPath;
+ float m_queryPoly[4*3];
+
+ static const int MAX_RAND_POINTS = 64;
+ float m_randPoints[MAX_RAND_POINTS*3];
+ int m_nrandPoints;
+ bool m_randPointsInCircle;
+
+ float m_spos[3];
+ float m_epos[3];
+ float m_hitPos[3];
+ float m_hitNormal[3];
+ bool m_hitResult;
+ float m_distanceToWall;
+ float m_neighbourhoodRadius;
+ float m_randomRadius;
+ bool m_sposSet;
+ bool m_eposSet;
+
+ int m_pathIterNum;
+ dtPolyRef m_pathIterPolys[MAX_POLYS];
+ int m_pathIterPolyCount;
+ float m_prevIterPos[3], m_iterPos[3], m_steerPos[3], m_targetPos[3];
+
+ static const int MAX_STEER_POINTS = 10;
+ float m_steerPoints[MAX_STEER_POINTS*3];
+ int m_steerPointCount;
+
+public:
+ TileMeshTesterTool();
+ ~TileMeshTesterTool();
+
+ virtual int type() { return TOOL_NAVMESH_TESTER; }
+ virtual void init(Sample* sample);
+ virtual void reset();
+ virtual void handleMenu();
+ virtual void handleClick(const float* s, const float* p, bool shift);
+ virtual void handleToggle();
+ virtual void handleStep();
+ virtual void handleUpdate(const float dt);
+ virtual void handleRenderOverlay(double* proj, double* model, int* view);
+
+ void recalc();
+ void drawAgent(const float* pos, float r, float h, float c, const unsigned int col);
+};
+
+#endif // NAVMESHTESTERTOOL_H
diff --git a/Source/Asset/Asset/actorfile.cpp b/Source/Asset/Asset/actorfile.cpp
new file mode 100644
index 00000000..6635a354
--- /dev/null
+++ b/Source/Asset/Asset/actorfile.cpp
@@ -0,0 +1,113 @@
+//-----------------------------------------------------------------------------
+// Name: actorfile.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include "actorfile.h"
+
+#include <Asset/Asset/character.h>
+#include <Asset/Asset/material.h>
+#include <Asset/AssetLoader/fallbackassetloader.h>
+#include <Asset/assetloaderrors.h>
+
+#include <XML/xml_helper.h>
+#include <Internal/filesystem.h>
+#include <Graphics/shaders.h>
+#include <Scripting/scriptfile.h>
+#include <Logging/logdata.h>
+#include <Main/engine.h>
+
+#include <tinyxml.h>
+
+#include <cmath>
+#include <map>
+#include <string>
+
+using std::string;
+
+extern string script_dir_path;
+
+ActorFile::ActorFile( AssetManager* owner, uint32_t asset_id ) : AssetInfo( owner, asset_id ), load_error(0) {
+
+}
+
+int ActorFile::Load( const string &rel_path, uint32_t load_flags ) {
+ load_error = 0;
+ char abs_path[kPathSize];
+ if (FindFilePath(rel_path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths | kAbsPath) == -1) {
+ DisplayFormatError(_ok_cancel, true, "Error", "Actor file %s not found", rel_path.c_str());
+ return kLoadErrorNoFile;
+ }
+
+ TiXmlDocument doc(abs_path);
+ doc.LoadFile();
+
+ if( doc.Error() ) {
+ return kLoadErrorCorruptFile;
+ }
+
+ if (!XmlHelper::getNodeValue(doc, "Actor/Character", character)) {
+ DisplayError("Error","Character name not found. Aborting actor load.");
+ load_error = 1;
+ return kLoadErrorIncompleteXML;
+ }
+
+ if (!XmlHelper::getNodeValue(doc, "Actor/ControlScript", script)) {
+ DisplayError("Error","Control script not found. Aborting actor load.");
+ load_error = 2;
+ return kLoadErrorIncompleteXML;
+ }
+
+ return kLoadOk;
+}
+
+const char* ActorFile::GetLoadErrorString() {
+ switch( load_error ) {
+ case 0: return "";
+ case 1: return "Character name not found";
+ case 2: return "Control script not found";
+ default: return "undefined error code";
+ }
+}
+
+void ActorFile::Unload() {
+
+}
+
+void ActorFile::Reload() {
+
+}
+
+void ActorFile::ReportLoad() {
+
+}
+
+void ActorFile::ReturnPaths( PathSet& path_set ) {
+ path_set.insert("actor "+path_);
+ //Characters::Instance()->ReturnRef(character)->ReturnPaths(path_set);
+ Engine::Instance()->GetAssetManager()->LoadSync<Character>(character)->ReturnPaths(path_set);
+ Path script_path = FindFilePath(script_dir_path+script, kDataPaths | kModPaths);
+ ScriptFileUtil::ReturnPaths(script_path, path_set);
+}
+
+AssetLoaderBase* ActorFile::NewLoader() {
+ return new FallbackAssetLoader<ActorFile>();
+}
diff --git a/Source/Asset/Asset/actorfile.h b/Source/Asset/Asset/actorfile.h
new file mode 100644
index 00000000..0a30e0c9
--- /dev/null
+++ b/Source/Asset/Asset/actorfile.h
@@ -0,0 +1,62 @@
+//-----------------------------------------------------------------------------
+// Name: actorfile.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#pragma once
+
+#include <Math/vec3.h>
+#include <Graphics/palette.h>
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+
+#include <string>
+#include <vector>
+
+using std::string;
+
+class ActorFile : public AssetInfo {
+public:
+ ActorFile( AssetManager *owner, uint32_t asset_id );
+ static AssetType GetType() { return ACTOR_FILE_ASSET; }
+ static const char* GetTypeName() { return "ACTOR_FILE_ASSET"; }
+
+ string script;
+ string character;
+
+ int load_error;
+
+ int Load(const string &path, uint32_t load_flags);
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+
+ void Unload();
+ void Reload();
+ virtual void ReportLoad();
+
+ void ReturnPaths(PathSet& path_set);
+
+ virtual AssetLoaderBase* NewLoader();
+
+ static bool AssetWarning() { return true; }
+};
+
+typedef AssetRef<ActorFile> ActorFileRef;
diff --git a/Source/Asset/Asset/ambientsounds.cpp b/Source/Asset/Asset/ambientsounds.cpp
new file mode 100644
index 00000000..cfeb5010
--- /dev/null
+++ b/Source/Asset/Asset/ambientsounds.cpp
@@ -0,0 +1,135 @@
+//-----------------------------------------------------------------------------
+// Name: ambientsounds.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include "ambientsounds.h"
+
+#include <Asset/AssetLoader/fallbackassetloader.h>
+#include <Asset/Asset/soundgroup.h>
+
+#include <XML/xml_helper.h>
+#include <Math/enginemath.h>
+#include <Logging/logdata.h>
+#include <Main/engine.h>
+
+#include <tinyxml.h>
+#include <string>
+
+using std::string;
+
+void AmbientSound::Unload() {
+
+}
+
+void AmbientSound::Reload() {
+ Load(path_,0x0);
+}
+
+AmbientSound::AmbientSound( AssetManager *owner, uint32_t asset_id ) : AssetInfo( owner, asset_id ), sub_error(0) {
+
+}
+
+int AmbientSound::Load( const string &path, uint32_t load_flags ) {
+ sub_error = 0;
+ TiXmlDocument doc;
+ if( LoadXMLRetryable(doc, path, "Ambient sound") ) {
+ sound_path.clear();
+ type = _continuous;
+
+ TiXmlHandle h_doc(&doc);
+ if( doc.Error() ) {
+ return kLoadErrorCorruptFile;
+ }
+
+ TiXmlHandle h_root = h_doc.FirstChildElement();
+ TiXmlElement* field = h_root.ToElement();
+
+ const char* c_str;
+ c_str = field->Attribute("path");
+ if(c_str){
+ sound_path = c_str;
+ } else {
+ sub_error = 1;
+ return kLoadErrorIncompleteXML;
+ }
+ c_str = field->Attribute("type");
+ if(c_str){
+ if(strcmp(c_str, "continuous") == 0){
+ type = _continuous;
+ } else if(strcmp(c_str, "occasional") == 0){
+ type = _occasional;
+ }
+ } else {
+ sub_error = 2;
+ return kLoadErrorIncompleteXML;
+ }
+
+ field->QueryFloatAttribute("delay_min", &delay_min);
+ field->QueryFloatAttribute("delay_max", &delay_max);
+ } else {
+ return kLoadErrorMissingFile;
+ }
+ return kLoadOk;
+}
+
+const char* AmbientSound::GetLoadErrorString() {
+ switch(sub_error) {
+ case 0: return "";
+ case 1: return "path attribute missing in xml";
+ case 2: return "type attribute missing in xml";
+ default: return "Undefined error";
+ }
+}
+
+void AmbientSound::ReportLoad() {
+
+}
+
+const string & AmbientSound::GetPath() {
+ return sound_path;
+}
+
+AmbientSoundType AmbientSound::GetSoundType() {
+ return type;
+}
+
+float AmbientSound::GetDelay()
+{
+ return RangedRandomFloat(delay_min, delay_max);
+}
+
+float AmbientSound::GetDelayNoLower()
+{
+ return RangedRandomFloat(0.0f, delay_max);
+}
+
+void AmbientSound::ReturnPaths( PathSet &path_set )
+{
+ path_set.insert("ambientsound "+path_);
+ //SoundGroupInfoCollection::Instance()->ReturnRef(sound_path)->ReturnPaths(path_set);
+ Engine::Instance()->GetAssetManager()->LoadSync<SoundGroupInfo>(sound_path)->ReturnPaths(path_set);
+}
+
+AssetLoaderBase* AmbientSound::NewLoader() {
+ return new FallbackAssetLoader<AmbientSound>();
+}
diff --git a/Source/Asset/Asset/ambientsounds.h b/Source/Asset/Asset/ambientsounds.h
new file mode 100644
index 00000000..b2fed8ce
--- /dev/null
+++ b/Source/Asset/Asset/ambientsounds.h
@@ -0,0 +1,69 @@
+//-----------------------------------------------------------------------------
+// Name: ambientsounds.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+
+#include <string>
+
+using std::string;
+
+enum AmbientSoundType {
+ _continuous = 0,
+ _occasional = 1
+};
+
+class AmbientSound : public AssetInfo {
+ string sound_path;
+ AmbientSoundType type;
+ float delay_min;
+ float delay_max;
+
+public:
+ AmbientSound( AssetManager* owner, uint32_t asset_id );
+ static AssetType GetType() { return AMBIENT_SOUND_ASSET; }
+ static const char* GetTypeName() { return "AMBIENT_SOUND_ASSET"; }
+
+ float GetDelay();
+ const string &GetPath();
+ AmbientSoundType GetSoundType();
+
+ int sub_error;
+ int Load(const string &path, uint32_t load_flags);
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+ void Unload();
+
+ void Reload();
+ virtual void ReportLoad();
+
+ float GetDelayNoLower();
+ void ReturnPaths(PathSet &path_set);
+
+ virtual AssetLoaderBase* NewLoader();
+ static bool AssetWarning() { return true; }
+};
+
+typedef AssetRef<AmbientSound> AmbientSoundRef;
diff --git a/Source/Asset/Asset/animation.cpp b/Source/Asset/Asset/animation.cpp
new file mode 100644
index 00000000..9ad6dd95
--- /dev/null
+++ b/Source/Asset/Asset/animation.cpp
@@ -0,0 +1,1701 @@
+//-----------------------------------------------------------------------------
+// Name: animation.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "animation.h"
+
+#include <Asset/Asset/skeletonasset.h>
+#include <Asset/AssetLoader/fallbackassetloader.h>
+#include <Asset/Asset/syncedanimation.h>
+
+#include <Compat/fileio.h>
+#include <Compat/filepath.h>
+
+#include <Internal/filesystem.h>
+#include <Internal/memwrite.h>
+#include <Internal/profiler.h>
+#include <Internal/timer.h>
+#include <Internal/error.h>
+#include <Internal/checksum.h>
+
+#include <Math/enginemath.h>
+#include <Math/vec3math.h>
+#include <Math/vec4math.h>
+
+#include <Graphics/retargetfile.h>
+#include <Graphics/models.h>
+
+#include <Logging/logdata.h>
+#include <Main/engine.h>
+#include <Utility/assert.h>
+
+#include <algorithm>
+#include <cmath>
+#include <cassert>
+#include <sstream>
+
+using std::sort;
+using std::swap;
+using std::endl;
+using std::vector;
+using std::string;
+using std::map;
+using std::ostringstream;
+
+static const int _anm_cache_version = 17;
+
+AnimationConfig animation_config;
+
+Animation::Animation( AssetManager * owner, uint32_t asset_id ) : AnimationAsset( owner, asset_id ), sub_error(0)
+{
+ clear();
+}
+
+struct DepthSorter {
+ int depth;
+ int bone_id;
+};
+
+class DepthSorterCompare {
+public:
+ bool operator()(const DepthSorter &a, const DepthSorter &b) {
+ return a.depth < b.depth;
+ }
+};
+
+static void SwitchStringRightLeft(string &the_string){
+ size_t index = 0;
+ while(1) {
+ size_t left_index;
+ size_t right_index;
+ right_index = the_string.find("right",index);
+ left_index = the_string.find("left",index);
+ if(left_index == string::npos && right_index == string::npos){
+ break;
+ }
+ bool choose_left;
+ if(left_index == string::npos){
+ choose_left = false;
+ } else if(right_index == string::npos){
+ choose_left = true;
+ } else {
+ choose_left = left_index < right_index;
+ }
+ if(choose_left){
+ the_string.replace(left_index, 4, "right");
+ index = left_index + 5;
+ } else {
+ the_string.replace(right_index, 5, "left");
+ index = right_index + 4;
+ }
+ }
+}
+
+static void SwitchStringRL(string &the_string) {
+ LOGD << "Old string: " << the_string << endl;
+ /*for(unsigned i=0; i<10; ++i){
+ ostringstream rfs;
+ ostringstream lfs;
+ rfs << "_r" << i;
+ lfs << "_l" << i;
+ string rfindstring = rfs.str();
+ string lfindstring = lfs.str();
+ if(the_string.find(rfindstring) != string::npos){
+ the_string.replace(the_string.find(rfindstring), 3, lfindstring);
+ } else if(the_string.find(lfindstring) != string::npos) {
+ the_string.replace(the_string.find(lfindstring), 3, rfindstring);
+ }
+ }*/
+ size_t end = the_string.size();
+ if(the_string[end-1] == 'l' && the_string[end-2] == '_'){
+ the_string[end-1] = 'r';
+ } else if(the_string[end-1] == 'r' && the_string[end-2] == '_'){
+ the_string[end-1] = 'l';
+ }
+ LOGD << "New string: " << the_string << endl;
+}
+
+void Animation::CalcInvertBoneMats() {
+ size_t num_keyframes = keyframes.size();
+ for(size_t i=0; i<num_keyframes; i++){
+ Keyframe &k = keyframes[i];
+ vector<BoneTransform>& bm = k.bone_mats;
+ vector<BoneTransform>& ibm = k.invert_bone_mats;
+ size_t num_bones = bm.size();
+ ibm.resize(num_bones);
+ for(int j=0; j<num_bones; j++){
+ ibm[j] = invert(bm[j]);
+ }
+ }
+}
+
+void Animation::UpdateIKBones() {
+ for(unsigned i=0; i<keyframes.size(); i++){
+ Keyframe& k = keyframes[i];
+
+ // Initialize uses_ik vector
+ size_t num_bones = k.bone_mats.size();
+ k.uses_ik.resize(num_bones);
+ for(size_t i=0; i<num_bones; i++){
+ k.uses_ik[i] = false;
+ }
+ // Travel down each ik chain to specify that each bone uses ik
+ size_t num_ik_bones = k.ik_bones.size();
+ for(size_t i=0; i<num_ik_bones; i++){
+ const BonePath &bone_path = k.ik_bones[i].bone_path;
+ size_t bone_path_len = bone_path.size();
+ for(int j=0; j<bone_path_len; j++){
+ k.uses_ik[bone_path[j]] = true;
+ }
+ }
+ }
+}
+
+void Animation::WriteToFile( FILE * file ) {
+ int version = _animation_version;
+ fwrite(&version, sizeof(int), 1, file);
+ bool centered = true;
+ fwrite(&centered, sizeof(bool), 1, file);
+ fwrite(&looping, sizeof(bool), 1, file);
+
+ fwrite(&length, sizeof(int), 1, file);
+
+ int num_keyframes = (int)keyframes.size();;
+ fwrite(&num_keyframes, sizeof(int), 1, file);
+
+ for(unsigned i=0; i<keyframes.size(); i++){
+ fwrite(&keyframes[i].time, sizeof(int), 1, file);
+
+ size_t num_weights = keyframes[i].weights.size();
+ fwrite(&num_weights, sizeof(int), 1, file);
+ if(num_weights){
+ fwrite(&keyframes[i].weights[0], sizeof(float), num_weights, file);
+ }
+
+ int num_bone_mats = (int)keyframes[i].bone_mats.size();
+ fwrite(&num_bone_mats, sizeof(int), 1, file);
+ mat4 temp;
+ for(unsigned j=0; j<keyframes[i].bone_mats.size(); j++){
+ temp = keyframes[i].bone_mats[j].GetMat4();
+ fwrite(&temp.entries, sizeof(float), 16, file);
+ }
+
+ int num_weapon_mats = (int)keyframes[i].weapon_mats.size();
+ fwrite(&num_weapon_mats, sizeof(int), 1, file);
+ for(unsigned j=0; j<keyframes[i].weapon_mats.size(); j++){
+ temp = keyframes[i].weapon_mats[j].GetMat4();
+ fwrite(&temp.entries, sizeof(float), 16, file);
+ fwrite(&keyframes[i].weapon_relative_id[j], sizeof(int), 1, file);
+ fwrite(&keyframes[i].weapon_relative_weight[j], sizeof(float), 1, file);
+ }
+
+ fwrite(&keyframes[i].use_mobility, sizeof(bool), 1, file);
+ if(keyframes[i].use_mobility){
+ mat4 temp_mat;
+ temp_mat.SetColumn(0, keyframes[i].mobility_mat.GetColumn(0));
+ temp_mat.SetColumn(1, keyframes[i].mobility_mat.GetColumn(2)*-1.0f);
+ temp_mat.SetColumn(2, keyframes[i].mobility_mat.GetColumn(1));
+ temp_mat.SetColumn(3, keyframes[i].mobility_mat.GetColumn(3));
+ fwrite(&temp_mat.entries, sizeof(float), 16, file);
+ }
+
+ int num_events = (int)keyframes[i].events.size();
+ fwrite(&num_events, sizeof(int), 1, file);
+
+ for(size_t j=0; j<keyframes[i].events.size(); j++){
+ fwrite(&keyframes[i].events[j].which_bone, sizeof(int), 1, file);
+ const string &event_string = keyframes[i].events[j].event_string;
+ size_t string_size = event_string.size();
+ fwrite(&string_size, sizeof(int), 1, file);
+ fwrite(event_string.c_str(), sizeof(char), string_size, file);
+ }
+
+ int num_ik_bones = (int)keyframes[i].ik_bones.size();
+ fwrite(&num_ik_bones, sizeof(int), 1, file);
+
+ for(size_t j=0; j<keyframes[i].ik_bones.size(); j++){
+ // Used to be ik_bones.start and end
+ vec3 filler;
+ fwrite(&filler, sizeof(vec3), 1, file);
+ fwrite(&filler, sizeof(vec3), 1, file);
+ size_t path_length = keyframes[i].ik_bones[j].bone_path.size();
+ fwrite(&path_length, sizeof(int), 1, file);
+ fwrite(&keyframes[i].ik_bones[j].bone_path[0], sizeof(int), path_length, file);
+ const string &label_string = keyframes[i].ik_bones[j].label;
+ size_t string_size = label_string.size();
+ fwrite(&string_size, sizeof(int), 1, file);
+ fwrite(label_string.c_str(),sizeof(char), string_size, file);
+ }
+
+ int num_shape_keys = (int)keyframes[i].shape_keys.size();
+ fwrite(&num_shape_keys, sizeof(int), 1, file);
+
+ for(size_t j=0; j<keyframes[i].shape_keys.size(); j++){
+ fwrite(&keyframes[i].shape_keys[j].weight, sizeof(float), 1, file);
+ const string &label_string = keyframes[i].shape_keys[j].label;
+ size_t string_size = label_string.size();
+ fwrite(&string_size, sizeof(int), 1, file);
+ fwrite(label_string.c_str(),sizeof(char), string_size, file);
+ }
+
+ int num_status_keys = (int)keyframes[i].status_keys.size();
+ fwrite(&num_status_keys, sizeof(int), 1, file);
+
+ for(size_t j=0; j<keyframes[i].status_keys.size(); j++){
+ fwrite(&keyframes[i].status_keys[j].weight, sizeof(float), 1, file);
+ const string &label_string = keyframes[i].status_keys[j].label;
+ size_t string_size = label_string.size();
+ fwrite(&string_size, sizeof(int), 1, file);
+ fwrite(label_string.c_str(),sizeof(char), string_size, file);
+ }
+
+ if(centered){
+ fwrite(&keyframes[i].rotation, sizeof(float), 1, file);
+ fwrite(&keyframes[i].center_offset, sizeof(float), 3, file);
+ }
+ }
+}
+
+void Animation::Center() {
+ size_t num_keyframes = keyframes.size();
+ // Find the center offset for each keyframe, and subtract it from
+ // the translation of each bone
+ for(size_t i=0; i<num_keyframes; i++){
+ Keyframe &k = keyframes[i];
+ vec3 center(0.0f);
+ if(k.use_mobility){
+ // Get rotation and xz translation from the mobility bone
+ center = k.mobility_mat.GetTranslationPart();
+ center[0] *= -1.0f;
+ center[2] *= -1.0f;
+
+ vec3 dir = k.mobility_mat.GetRotatedvec3(vec3(0.0f,0.0f,1.0f));
+ k.rotation = atan2f(dir[0], dir[2]);
+ if(i != 0){
+ Keyframe &prev_key = keyframes[i-1];
+ if(k.rotation > prev_key.rotation + (float)PI){
+ k.rotation -= (float)PI * 2.0f;
+ } else if(k.rotation < prev_key.rotation - (float)PI){
+ k.rotation += (float)PI * 2.0f;
+ }
+ }
+ } else {
+ // If not using mobility bone, determine center of mass
+ float total_mass = 0.0f;
+ for(unsigned j=0; j<k.bone_mats.size(); j++){
+ center += k.bone_mats[j].origin;
+ total_mass += 1.0f;
+ }
+ LOG_ASSERT(total_mass != 0.0f);
+ center /= total_mass;
+ }
+ // Subtract the center from the position of each bone
+ for(unsigned j=0; j<k.bone_mats.size(); j++){
+ k.bone_mats[j].origin -= center;
+ }
+ for(unsigned j=0; j<k.weapon_mats.size(); j++){
+ k.weapon_mats[j].origin -= center;
+ }
+ k.center_offset = center;
+ }
+ // Get the center offset for the first keyframe
+ vec3 base_offset;
+ if(!keyframes[0].use_mobility){
+ base_offset = keyframes[0].center_offset;
+ }
+ // Add the first keyframe's offset to each frame
+ vec3 temp_offset;
+ for(int i=0; i<num_keyframes; i++){
+ Keyframe &k = keyframes[i];
+ temp_offset = base_offset;
+ for(unsigned j=0; j<k.bone_mats.size(); j++){
+ k.bone_mats[j].origin += temp_offset;
+ }
+ for(unsigned j=0; j<k.weapon_mats.size(); j++){
+ k.weapon_mats[j].origin += temp_offset;
+ }
+ k.center_offset -= temp_offset;
+ }
+ // Apply the inverse of each keyframe's rotation to each bone
+ for(int i=0; i<num_keyframes; i++){
+ Keyframe &k = keyframes[i];
+ mat4 rotation_mat;
+ rotation_mat.SetRotationY(-k.rotation);
+ for(unsigned j=0; j<k.bone_mats.size(); j++){
+ k.bone_mats[j] = rotation_mat * k.bone_mats[j];
+ }
+ for(unsigned j=0; j<k.weapon_mats.size(); j++){
+ k.weapon_mats[j] = rotation_mat * k.weapon_mats[j];
+ }
+ }
+ // Apply the inverse rotation to center offset changes
+ vec3 last_offset = keyframes[0].center_offset;
+ for(int i=1; i<num_keyframes; i++){
+ Keyframe &k = keyframes[i];
+ mat4 rotation_mat;
+ rotation_mat.SetRotationY(-k.rotation);
+ vec3 vec = k.center_offset - last_offset;
+ vec = rotation_mat * vec;
+ vec3 new_co = keyframes[i-1].center_offset + vec;
+ last_offset = k.center_offset;
+ k.center_offset = new_co;
+ }
+ RecalcCaches();
+}
+
+void Retarget(const AnimInput& anim_input, AnimOutput & anim_output, const string& old_path)
+{
+ PROFILER_ZONE(g_profiler_ctx, "Retargeting");
+ PROFILER_ENTER(g_profiler_ctx, "Get SkeletonAssetRefs");
+ SkeletonAssetRef old_sar = Engine::Instance()->GetAssetManager()->LoadSync<SkeletonAsset>(old_path);
+ SkeletonAssetRef new_sar = Engine::Instance()->GetAssetManager()->LoadSync<SkeletonAsset>(anim_input.retarget_new);
+ PROFILER_LEAVE(g_profiler_ctx);
+ PROFILER_ENTER(g_profiler_ctx, "Get SkeletonFileData");
+ const SkeletonFileData& old_data = old_sar->GetData();
+ const SkeletonFileData& new_data = new_sar->GetData();
+ PROFILER_LEAVE(g_profiler_ctx);
+ // Add bones that don't exist in original skeleton
+ anim_output.matrices.resize(new_data.bone_mats.size());
+ anim_output.physics_weights.resize(new_data.bone_mats.size());
+ for(size_t i=old_data.bone_mats.size(), len=anim_output.matrices.size(); i<len; ++i){
+ anim_output.physics_weights[i] = 0.0f;
+ }
+
+ // Find new and old leg length
+ float old_leg_length = 0.0f;
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Find old leg length");
+ SkeletonFileData::IKBoneMap::const_iterator iter = old_data.simple_ik_bones.find("left_leg");
+ if(iter != old_data.simple_ik_bones.end()){
+ const SimpleIKBone &leg_bone = iter->second;
+ int bone_id = leg_bone.bone_id;
+ for(int i=0; i<leg_bone.chain_length-1; ++i){
+ old_leg_length += distance(old_data.points[old_data.bone_ends[bone_id*2+0]],
+ old_data.points[old_data.bone_ends[bone_id*2+1]]);
+ bone_id = old_data.hier_parents[bone_id];
+ }
+ } else {
+ FatalError("Error", "\"left_leg\" not found in simple ik bones");
+ }
+ }
+
+ float new_leg_length = 0.0f;
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Find new leg length");
+ SkeletonFileData::IKBoneMap::const_iterator iter = new_data.simple_ik_bones.find("left_leg");
+ if(iter != new_data.simple_ik_bones.end()){
+ const SimpleIKBone &leg_bone = iter->second;
+ int bone_id = leg_bone.bone_id;
+ for(int i=0; i<leg_bone.chain_length-1; ++i){
+ new_leg_length += distance(new_data.points[new_data.bone_ends[bone_id*2+0]],
+ new_data.points[new_data.bone_ends[bone_id*2+1]]);
+ bone_id = new_data.hier_parents[bone_id];
+ }
+ } else {
+ FatalError("Error", "\"left_leg\" not found in simple ik bones");
+ }
+ }
+
+ float ratio = new_leg_length / old_leg_length;
+
+ // Shift root bone based on hip point
+ vec3 old_root_offset, new_root_offset;
+ {
+ SkeletonFileData::IKBoneMap::const_iterator iter = old_data.simple_ik_bones.find("torso");
+ if(iter != old_data.simple_ik_bones.end()){
+ const SimpleIKBone &torso_ik_bone = iter->second;
+ int hip_bone = old_data.bone_parents[old_data.bone_parents[torso_ik_bone.bone_id]];
+ int point_id = old_data.bone_ends[hip_bone*2+0];
+ old_root_offset = old_data.points[point_id];
+ old_root_offset += old_data.old_model_center;
+ }
+ }
+
+ // Shift root bone based on leg length ratio
+ for(size_t i=0, len=new_data.bone_mats.size(); i<len; ++i){
+ if(new_data.hier_parents[i] == -1){
+ anim_output.matrices[i].origin += old_root_offset;
+ anim_output.matrices[i].origin *= ratio;
+ }
+ }
+ for(size_t i=0, len=anim_output.ik_bones.size(); i<len; ++i){
+ BlendedBonePath& bbp = anim_output.ik_bones[i];
+ bbp.transform.origin += old_root_offset;
+ bbp.transform.origin *= ratio;
+ }
+ anim_output.center_offset *= ratio;
+
+ // Calculate new bone lengths
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Calculate new_bone_lengths");
+ vector<float> new_bone_length(new_data.bone_mats.size());
+ for(size_t i=0, len=new_data.bone_mats.size(); i<len; ++i){
+ new_bone_length[i] =
+ distance(new_data.points[new_data.bone_ends[i*2+0]],
+ new_data.points[new_data.bone_ends[i*2+1]]);
+ }
+ }
+
+ // Calculate each bone's depth in hierarchy (root = 0)
+ vector<DepthSorter> depth_sorter(new_data.bone_mats.size());
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Calculate bone depths in hierarchy");
+ for(size_t i=0, len=new_data.bone_mats.size(); i<len; ++i){
+ int parent = new_data.hier_parents[i];
+ int depth = 0;
+ while(parent != -1){
+ ++depth;
+ parent = new_data.hier_parents[parent];
+ }
+ depth_sorter[i].depth = depth;
+ depth_sorter[i].bone_id = (int) i;
+ }
+ }
+
+ // Create bone list sorted by depth
+ sort(depth_sorter.begin(), depth_sorter.end(), DepthSorterCompare());
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Calculate new bone positions using sorted bone list");
+ for(size_t i=0, len=depth_sorter.size(); i<len; ++i){
+ int bone_id = depth_sorter[i].bone_id;
+ int parent = new_data.hier_parents[bone_id];
+
+ // Set rotation for new bones
+ if(bone_id > (int)old_data.bone_mats.size()){
+ if(anim_input.parents->at(bone_id) < (int)old_data.bone_mats.size()){
+ anim_output.matrices[bone_id].rotation = invert(QuaternionFromMat4(old_data.bone_mats[anim_input.parents->at(bone_id)])) * QuaternionFromMat4(new_data.bone_mats[bone_id]);
+ } else {
+ anim_output.matrices[bone_id].rotation = invert(QuaternionFromMat4(new_data.bone_mats[anim_input.parents->at(bone_id)])) * QuaternionFromMat4(new_data.bone_mats[bone_id]);
+ }
+ }
+
+ // Enforce bone length
+ if(bone_id < (int)anim_input.parents->size() && anim_input.parents->at(bone_id) != -1){
+ vec3 parent_bone = invert(QuaternionFromMat4(new_data.bone_mats[anim_input.parents->at(bone_id)])) * ((new_data.points[new_data.bone_ends[anim_input.parents->at(bone_id)*2+1]] - new_data.points[new_data.bone_ends[anim_input.parents->at(bone_id)*2+0]]) * 0.5f +
+ (new_data.points[new_data.bone_ends[bone_id*2+0]] - new_data.points[new_data.bone_ends[anim_input.parents->at(bone_id)*2+1]]));
+ vec3 child_bone = anim_output.matrices[bone_id].rotation * invert(QuaternionFromMat4(new_data.bone_mats[bone_id])) * (new_data.points[new_data.bone_ends[bone_id*2+1]] - new_data.points[new_data.bone_ends[bone_id*2+0]]);
+ anim_output.matrices[bone_id].origin = parent_bone + child_bone * 0.5f;
+ } else if(parent != -1){
+ vec3 parent_bone = invert(QuaternionFromMat4(new_data.bone_mats[parent])) * (new_data.points[new_data.bone_ends[parent*2+1]] - new_data.points[new_data.bone_ends[parent*2+0]]) * 0.5f;
+ vec3 child_bone = anim_output.matrices[bone_id].rotation * invert(QuaternionFromMat4(new_data.bone_mats[bone_id])) * (new_data.points[new_data.bone_ends[bone_id*2+1]] - new_data.points[new_data.bone_ends[bone_id*2+0]]);
+ anim_output.matrices[bone_id].origin = parent_bone + child_bone * 0.5f;
+ }
+ }
+ }
+
+ if(anim_input.mirrored){
+ PROFILER_ZONE(g_profiler_ctx, "Mirror animation");
+ SkeletonAssetRef sar = Engine::Instance()->GetAssetManager()->LoadSync<SkeletonAsset>(AnimationRetargeter::Instance()->GetSkeletonFile(anim_input.retarget_new));
+ //SkeletonAssets::Instance()->ReturnRef(AnimationRetargeter::Instance()->GetSkeletonFile(anim_input.retarget_new));
+
+ const SkeletonFileData& skeleton_file_data = sar->GetData();
+ const vector<int>& symmetry = skeleton_file_data.symmetry;
+
+ // Mirror all rotations
+ vector<BoneTransform> &matrices = anim_output.matrices;
+
+ for(size_t i=0, len=new_data.bone_mats.size(); i<len; ++i){
+ int bone_id = depth_sorter[i].bone_id;
+ //int parent = new_data.hier_parents[bone_id];
+
+ if(bone_id < (int)anim_input.parents->size() && anim_input.parents->at(bone_id) != -1){
+ matrices[bone_id] = matrices[anim_input.parents->at(bone_id)] * matrices[bone_id];
+ }
+ }
+
+ for(size_t i=0; i<skeleton_file_data.bone_mats.size(); ++i){
+ matrices[i] = matrices[i] * invert(skeleton_file_data.bone_mats[i]);
+ }
+
+ for(size_t j=0; j<matrices.size(); j++){
+ matrices[j].rotation[0] *= -1.0f;
+ matrices[j].rotation[3] *= -1.0f;
+ matrices[j].origin[0] *= -1.0f;
+ }
+
+ vector<BoneTransform> &weapon_matrices = anim_output.weapon_matrices;
+ for(unsigned j=0; j<weapon_matrices.size(); j++){
+ MirrorBT(weapon_matrices[j], true);
+ }
+
+ // Swap weapon relative ids (so knife in right hand is now in left hand)
+ for(unsigned j=0; j<anim_output.weapon_relative_ids.size(); j++){
+ if(anim_output.weapon_relative_ids[j] != -1){
+ anim_output.weapon_relative_ids[j] = symmetry[anim_output.weapon_relative_ids[j]];
+ }
+ }
+ for(unsigned j=0; j<symmetry.size(); j++){
+ if(symmetry[j] > (int)j){
+ swap(matrices[j], matrices[symmetry[j]]);
+ }
+ }
+
+
+ for(unsigned i=0; i<skeleton_file_data.bone_mats.size(); ++i){
+ matrices[i] = matrices[i] * skeleton_file_data.bone_mats[i];
+ }
+
+ for(int i=(int)new_data.bone_mats.size()-1; i>=0; --i){
+ int bone_id = depth_sorter[i].bone_id;
+ //int parent = new_data.hier_parents[bone_id];
+
+ if(bone_id < (int)anim_input.parents->size() && anim_input.parents->at(bone_id) != -1){
+ matrices[bone_id] = invert(matrices[anim_input.parents->at(bone_id)]) * matrices[bone_id];
+ }
+ }
+
+ for(unsigned j=0; j<symmetry.size(); j++){
+ if(symmetry[j] > (int)j && symmetry[j] < (int)anim_output.physics_weights.size()){
+ swap(anim_output.physics_weights[j], anim_output.physics_weights[symmetry[j]]);
+ }
+ }
+
+ anim_output.center_offset[0] *= -1.0f;
+ anim_output.delta_rotation *= -1.0f;
+ anim_output.rotation *= -1.0f;
+
+ for(unsigned j=0; j<anim_output.status_keys.size(); ++j){
+ StatusKeyBlend& skey = anim_output.status_keys[j];
+ SwitchStringRightLeft(skey.label);
+ }
+ for(unsigned j=0; j<anim_output.shape_keys.size(); ++j){
+ ShapeKeyBlend& skey = anim_output.shape_keys[j];
+ SwitchStringRL(skey.label);
+ }
+
+ for(unsigned j=0; j<anim_output.ik_bones.size(); ++j){
+ string &label = anim_output.ik_bones[j].ik_bone.label;
+ SwitchStringRightLeft(label);
+ BonePath& bone_path = anim_output.ik_bones[j].ik_bone.bone_path;
+ anim_output.ik_bones[j].transform = anim_output.ik_bones[j].transform * skeleton_file_data.bone_mats[bone_path[0]];
+ anim_output.ik_bones[j].transform.origin[0] *= -1.0f;
+ anim_output.ik_bones[j].transform.rotation[0] *= -1.0f;
+ anim_output.ik_bones[j].transform.rotation[3] *= -1.0f;
+ for(unsigned k=0; k<bone_path.size(); ++k){
+ if(bone_path[k] < (int)symmetry.size() && symmetry[bone_path[k]] != -1){
+ bone_path[k] = symmetry[bone_path[k]];
+ }
+ }
+ anim_output.ik_bones[j].transform = anim_output.ik_bones[j].transform * invert(skeleton_file_data.bone_mats[bone_path[0]]);
+ if(label == "left_leg" || label == "right_leg"){
+ anim_output.ik_bones[j].transform.rotation = anim_output.ik_bones[j].transform.rotation * quaternion(vec4(0.0f,0.0f,1.0f,3.14f));
+ }
+ }
+ }
+
+ PROFILER_ZONE(g_profiler_ctx, "Check for flipped toe");
+ {
+ // Sometimes the left toe bone would be flipped upside down, especially on Josh/Akazi's models
+ // This checks for that condition and flips it back
+ for(int side=0; side<2; ++side){
+ const SimpleIKBone& ik_bone = new_data.simple_ik_bones.find(side?"left_leg":"right_leg")->second;
+ vec3 test = QuaternionFromMat4(old_data.bone_mats[ik_bone.bone_id]) * invert(QuaternionFromMat4(new_data.bone_mats[ik_bone.bone_id])) * vec3(0.0f,1.0f,0.0f);
+ if(test[1] < 0.0f){
+ anim_output.matrices[ik_bone.bone_id].rotation = anim_output.matrices[ik_bone.bone_id].rotation * quaternion(vec4(0.0f,0.0f,1.0f,3.14f));
+ for(size_t i=0, len=anim_output.ik_bones.size(); i<len; ++i){
+ BlendedBonePath& bbp = anim_output.ik_bones[i];
+ if(bbp.ik_bone.label == "left_leg"){
+ bbp.transform.rotation = bbp.transform.rotation * quaternion(vec4(0.0f,0.0f,1.0f,3.14f));
+ }
+ }
+ }
+ }
+ }
+
+ if((anim_input.mirrored) != (anim_input.retarget_new == old_path)){
+ // Fix twisted tail
+ {
+ SkeletonFileData::IKBoneMap::const_iterator iter = new_data.simple_ik_bones.find("tail");
+ if(iter != new_data.simple_ik_bones.end()){
+ const SimpleIKBone& ik_bone = iter->second;
+ int bone_id = ik_bone.bone_id;
+ for(int i=0; i<ik_bone.chain_length; ++i){
+ anim_output.matrices[bone_id].rotation[0] *= -1.0f;
+ anim_output.matrices[bone_id].rotation[3] *= -1.0f;
+ bone_id = new_data.hier_parents[bone_id];
+ }
+ }
+ }
+ }
+}
+
+int Animation::ReadFromFile( FILE * file ) {
+ PROFILER_ZONE(g_profiler_ctx, "Animation::ReadFromFile");
+ clear();
+
+ int version;
+ fread(&version, sizeof(int), 1, file);
+
+ int start = 0;
+ if(version > _animation_version || version < 0){
+ start = version;
+ version = 0;
+ return kLoadErrorIncompatibleFileVersion;
+ }
+
+ bool centered;
+
+ if(version >= 10){
+ fread(&centered, sizeof(bool), 1, file);
+ } else {
+ centered = false;
+ }
+
+ if(version >= 1){
+ fread(&looping, sizeof(bool), 1, file);
+ } else {
+ looping = true;
+ }
+
+ if(version>0 && version<11){
+ fread(&start, sizeof(int), 1, file);
+ }
+ fread(&length, sizeof(int), 1, file);
+ length -= start;
+
+ int num_keyframes;
+ fread(&num_keyframes, sizeof(int), 1, file);
+
+ if(num_keyframes <= 0){
+ //FatalError("Error", "Animation has %d keyframes.", num_keyframes);
+ sub_error = 1;
+ return kLoadErrorCorruptFile;
+ }
+
+ for(int i=0; i<num_keyframes; i++){
+ int time;
+ fread(&time, sizeof(int), 1, file);
+ time -= start;
+
+ keyframes.resize(keyframes.size()+1);
+ keyframes.back().time = time;
+ Keyframe* keyframe = &keyframes.back();
+
+ keyframe->rotation = 0.0f;
+
+ if(version >= 4){
+ int num_weights;
+ fread(&num_weights, sizeof(int), 1, file);
+ keyframe->weights.resize(num_weights);
+ if(num_weights){
+ fread(&keyframe->weights[0], sizeof(float), num_weights, file);
+ }
+ }
+
+ int num_bone_mats;
+ fread(&num_bone_mats, sizeof(int), 1, file);
+ keyframe->bone_mats.resize(num_bone_mats);
+
+ mat4 temp;
+ for(unsigned j=0; j<keyframe->bone_mats.size(); j++){
+ fread(&temp.entries, sizeof(float), 16, file);
+ keyframe->bone_mats[j] = temp;
+ }
+
+ if(version>=7){
+ int num_weapon_mats;
+ fread(&num_weapon_mats, sizeof(int), 1, file);
+ keyframe->weapon_mats.resize(num_weapon_mats);
+ keyframe->weapon_relative_id.resize(num_weapon_mats);
+ keyframe->weapon_relative_weight.resize(num_weapon_mats);
+
+ mat4 temp;
+ for(unsigned j=0; j<keyframe->weapon_mats.size(); j++){
+ fread(&temp.entries, sizeof(float), 16, file);
+ keyframe->weapon_mats[j] = temp;
+ if(version>=8){
+ fread(&keyframe->weapon_relative_id[j], sizeof(int), 1, file);
+ fread(&keyframe->weapon_relative_weight[j], sizeof(float), 1, file);
+ LOGD << "Relative ID and Weight: " << keyframe->weapon_relative_id[j] << " " << keyframe->weapon_relative_weight[j] << endl;
+ } else {
+ keyframe->weapon_relative_id[j] = -1;
+ keyframe->weapon_relative_weight[j] = 0.0f;
+ }
+ }
+ }
+
+ if(version>=9){
+ fread(&keyframe->use_mobility, sizeof(bool), 1, file);
+ if(keyframe->use_mobility){
+ mat4 temp_mat;
+ fread(&temp_mat.entries, sizeof(float), 16, file);
+ keyframe->mobility_mat.SetColumn(0,temp_mat.GetColumn(0));
+ keyframe->mobility_mat.SetColumn(1,temp_mat.GetColumn(2));
+ keyframe->mobility_mat.SetColumn(2,temp_mat.GetColumn(1)*-1.0f);
+ keyframe->mobility_mat.SetColumn(3,temp_mat.GetColumn(3));
+ }
+ } else {
+ keyframe->use_mobility = false;
+ }
+
+ if(version>=2){
+ int num_events;
+ fread(&num_events, sizeof(int), 1, file);
+ keyframe->events.resize(num_events);
+
+ for(unsigned j=0; j<keyframe->events.size(); j++){
+ fread(&keyframe->events[j].which_bone, sizeof(int), 1, file);
+ int string_size;
+ fread(&string_size, sizeof(int), 1, file);
+ vector<char> string_buffer(string_size+1,'\0');
+ fread(&string_buffer[0], sizeof(char), string_size, file);
+ keyframe->events[j].event_string = &string_buffer[0];
+ }
+ }
+
+ if(version>=3){
+ int num_ik_bones;
+ fread(&num_ik_bones, sizeof(int), 1, file);
+ keyframe->ik_bones.resize(num_ik_bones);
+
+ for(unsigned j=0; j<keyframe->ik_bones.size(); j++){
+ // used to be ik_bones start and end
+ vec3 filler;
+ fread(&filler, sizeof(vec3), 1, file);
+ fread(&filler, sizeof(vec3), 1, file);
+ int path_length;
+ fread(&path_length, sizeof(int), 1, file);
+ keyframe->ik_bones[j].bone_path.resize(path_length);
+ fread(&keyframe->ik_bones[j].bone_path[0], sizeof(int), path_length, file);
+ int string_size;
+ fread(&string_size, sizeof(int), 1, file);
+ vector<char> string_buffer(string_size+1,'\0');
+ fread(&string_buffer[0], sizeof(char), string_size, file);
+ keyframe->ik_bones[j].label = &string_buffer[0];
+ }
+ }
+
+ if(version>=5){
+ int num_shape_keys;
+ fread(&num_shape_keys, sizeof(int), 1, file);
+ keyframe->shape_keys.resize(num_shape_keys);
+
+ for(unsigned j=0; j<keyframe->shape_keys.size(); j++){
+ fread(&keyframe->shape_keys[j].weight, sizeof(float), 1, file);
+ int string_size;
+ fread(&string_size, sizeof(int), 1, file);
+ vector<char> string_buffer(string_size+1,'\0');
+ fread(&string_buffer[0], sizeof(char), string_size, file);
+ keyframe->shape_keys[j].label = &string_buffer[0];
+ }
+ }
+ if(version>=6){
+ int num_status_keys;
+ fread(&num_status_keys, sizeof(int), 1, file);
+ keyframe->status_keys.resize(num_status_keys);
+
+ for(unsigned j=0; j<keyframe->status_keys.size(); j++){
+ fread(&keyframe->status_keys[j].weight, sizeof(float), 1, file);
+ int string_size;
+ fread(&string_size, sizeof(int), 1, file);
+ vector<char> string_buffer(string_size+1,'\0');
+ fread(&string_buffer[0], sizeof(char), string_size, file);
+ keyframe->status_keys[j].label = &string_buffer[0];
+ }
+ }
+ if(centered){
+ fread(&keyframe->rotation, sizeof(float), 1, file);
+ fread(&keyframe->center_offset, sizeof(float), 3, file);
+ }
+ }
+
+ if(!centered){
+ Center();
+ } else {
+ RecalcCaches();
+ }
+
+ return kLoadOk;
+}
+
+float Animation::LocalFromNormalized(float normalized_time) const{
+ return length * normalized_time;
+}
+
+void GetCubicSplineWeights(float interp, float *weights) {
+ float interp_squared = interp*interp;
+ float interp_cubed = interp_squared*interp;
+ weights[0] = 0.5f * (-interp_cubed + 2.0f * interp_squared - interp);
+ weights[1] = 0.5f * (3.0f * interp_cubed - 5.0f * interp_squared + 2.0f);
+ weights[2] = 0.5f * (-3.0f * interp_cubed + 4.0f * interp_squared + interp);
+ weights[3] = 0.5f * (interp_cubed - interp_squared);
+}
+
+BoneTransform BlendFourBones(BoneTransform* transforms, float *weights) {
+ BoneTransform transform;
+ float total_weight = weights[0] + weights[1];
+ if(total_weight > 0.0f){
+ transform = mix(transforms[0],transforms[1],weights[1]/total_weight);
+ }
+ total_weight += weights[2];
+ if(total_weight > 0.0f){
+ transform = mix(transform,transforms[2],weights[2]/total_weight);
+ }
+ total_weight += weights[3];
+ if(total_weight > 0.0f){
+ transform = mix(transform,transforms[3],weights[3]/total_weight);
+ }
+ return transform;
+}
+
+AnimOutput mix( const AnimOutput &a, const AnimOutput &b, float alpha )
+{
+ LOG_ASSERT(a.matrices.size() == b.matrices.size());
+
+ AnimOutput result;
+ size_t num_bones = a.matrices.size();
+ result.matrices.resize(num_bones);
+ for(unsigned i=0; i<num_bones; i++){
+ result.matrices[i] = mix(a.matrices[i], b.matrices[i], alpha);
+ }
+ result.center_offset = mix(a.center_offset, b.center_offset, alpha);
+ result.rotation = mix(a.rotation, b.rotation, alpha);
+ result.delta_offset = mix(a.delta_offset, b.delta_offset, alpha);
+ result.delta_rotation = mix(a.delta_rotation, b.delta_rotation, alpha);
+
+ size_t num_weapon_bones = max(a.weapon_matrices.size(),
+ b.weapon_matrices.size());
+ result.weapon_matrices.resize(num_weapon_bones);
+ result.weapon_weights.resize(num_weapon_bones);
+ result.weapon_weight_weights.resize(num_weapon_bones);
+ result.weapon_relative_ids.resize(num_weapon_bones);
+ result.weapon_relative_weights.resize(num_weapon_bones);
+ for(size_t i=0; i<num_weapon_bones; i++){
+ if(i >= b.weapon_matrices.size()){
+ result.weapon_matrices[i] = a.weapon_matrices[i];
+ result.weapon_weights[i] = a.weapon_weights[i] * (1.0f - alpha);
+ result.weapon_weight_weights[i] = a.weapon_weight_weights[i] * (1.0f - alpha);
+ result.weapon_relative_ids[i] = a.weapon_relative_ids[i];
+ result.weapon_relative_weights[i] = a.weapon_relative_weights[i];
+ } else if(i >= a.weapon_matrices.size()){
+ result.weapon_matrices[i] = b.weapon_matrices[i];
+ result.weapon_weights[i] = b.weapon_weights[i] * alpha;
+ result.weapon_weight_weights[i] = b.weapon_weight_weights[i] * alpha;
+ result.weapon_relative_ids[i] = b.weapon_relative_ids[i];
+ result.weapon_relative_weights[i] = b.weapon_relative_weights[i];
+ } else {
+ result.weapon_matrices[i] =
+ mix(a.weapon_matrices[i], b.weapon_matrices[i], alpha);
+ result.weapon_weights[i] =
+ mix(a.weapon_weights[i], b.weapon_weights[i], alpha);
+ result.weapon_weight_weights[i] =
+ mix(a.weapon_weight_weights[i], b.weapon_weight_weights[i], alpha);
+ result.weapon_relative_ids[i] = max(a.weapon_relative_ids[i], b.weapon_relative_ids[i]);
+ result.weapon_relative_weights[i] = a.weapon_relative_weights[i];
+ }
+ }
+
+ // Add ik bone paths from both sources, and adjust their weights
+ {
+ map<string, BlendedBonePath> ik_bones;
+ map<string, BlendedBonePath>::iterator iter;
+
+ for(unsigned i=0; i<a.ik_bones.size(); ++i){
+ const string& label = a.ik_bones[i].ik_bone.label;
+ ik_bones[label] = a.ik_bones[i];
+ ik_bones[label].weight *= 1.0f-alpha;
+ }
+
+ for(unsigned i=0; i<b.ik_bones.size(); ++i){
+ const string& label = b.ik_bones[i].ik_bone.label;
+ iter = ik_bones.find(label);
+ if(iter == ik_bones.end()){
+ ik_bones[label] = b.ik_bones[i];
+ ik_bones[label].weight *= alpha;
+ } else {
+ ik_bones[label].weight = ik_bones[label].weight + b.ik_bones[i].weight * alpha;
+ ik_bones[label].transform = mix(ik_bones[label].transform, b.ik_bones[i].transform, alpha);
+ }
+ }
+
+ result.ik_bones.resize(ik_bones.size());
+ iter = ik_bones.begin();
+ int index = 0;
+ for(;iter != ik_bones.end(); ++iter){
+ result.ik_bones[index++] = (*iter).second;
+ }
+ }
+
+ size_t num_joints = max(a.physics_weights.size(), b.physics_weights.size());
+ size_t min_num_joints = min(a.physics_weights.size(), b.physics_weights.size());
+ result.physics_weights.resize(num_joints, 1.0f);
+
+ if(a.physics_weights.empty()){
+ for(unsigned i=0; i<num_joints; i++){
+ result.physics_weights[i] = mix(1.0f,
+ b.physics_weights[i],
+ alpha);
+ }
+ } else if(b.physics_weights.empty()){
+ for(unsigned i=0; i<num_joints; i++){
+ result.physics_weights[i] = mix(a.physics_weights[i],
+ 1.0f,
+ alpha);
+ }
+ } else {
+ for(unsigned i=0; i<min_num_joints; i++){
+ result.physics_weights[i] = mix(a.physics_weights[i],
+ b.physics_weights[i],
+ alpha);
+ }
+ }
+
+ // Add shape keys from both sources, and adjust their weights
+ {
+ map<string, ShapeKeyBlend> shape_keys;
+ map<string, ShapeKeyBlend>::iterator iter;
+
+ // Add shape keys from A
+ for(unsigned i=0; i<a.shape_keys.size(); ++i){
+ const string& label = a.shape_keys[i].label;
+ shape_keys[label] = a.shape_keys[i];
+ shape_keys[label].weight_weight *= 1.0f-alpha;
+ }
+
+ // Merge shape keys from B
+ for(unsigned i=0; i<b.shape_keys.size(); ++i){
+ const string& label = b.shape_keys[i].label;
+ iter = shape_keys.find(label);
+ if(iter == shape_keys.end()){
+ shape_keys[label] = b.shape_keys[i];
+ shape_keys[label].weight_weight *= alpha;
+ } else {
+ shape_keys[label].weight = mix(shape_keys[label].weight, b.shape_keys[i].weight, alpha);
+ shape_keys[label].weight_weight = shape_keys[label].weight_weight + b.shape_keys[i].weight_weight * alpha;
+ }
+ }
+
+ // Add shape keys from merging map into result array
+ result.shape_keys.resize(shape_keys.size());
+ iter = shape_keys.begin();
+ int index = 0;
+ for(;iter != shape_keys.end(); ++iter){
+ result.shape_keys[index++] = (*iter).second;
+ }
+ }
+
+
+ // Add status keys from both sources, and adjust their weights
+ {
+ map<string, StatusKeyBlend> status_keys;
+ map<string, StatusKeyBlend>::iterator iter;
+
+ for(unsigned i=0; i<a.status_keys.size(); ++i){
+ const string& label = a.status_keys[i].label;
+ status_keys[label] = a.status_keys[i];
+ status_keys[label].weight_weight *= 1.0f-alpha;
+ }
+
+ for(unsigned i=0; i<b.status_keys.size(); ++i){
+ const string& label = b.status_keys[i].label;
+ iter = status_keys.find(label);
+ if(iter == status_keys.end()){
+ status_keys[label] = b.status_keys[i];
+ status_keys[label].weight_weight *= alpha;
+ } else {
+ status_keys[label].weight = mix(status_keys[label].weight, b.status_keys[i].weight, alpha);
+ status_keys[label].weight_weight = status_keys[label].weight_weight + b.status_keys[i].weight_weight * alpha;
+ }
+ }
+
+ result.status_keys.resize(status_keys.size());
+ iter = status_keys.begin();
+ int index = 0;
+ for(;iter != status_keys.end(); ++iter){
+ result.status_keys[index++] = (*iter).second;
+ }
+
+ if(result.status_keys.empty() && (!a.status_keys.empty() || !b.status_keys.empty())){
+ LOGD << "Lost status keys." << endl;
+ }
+ }
+
+ map<int, WeapAnimInfo>::const_iterator iter_a = a.weap_anim_info_map.begin();
+ map<int, WeapAnimInfo>::const_iterator iter_b = b.weap_anim_info_map.begin();
+
+ while(iter_a != a.weap_anim_info_map.end() ||
+ iter_b != b.weap_anim_info_map.end())
+ {
+ int iter_a_id = -1;
+ int iter_b_id = -1;
+ if(iter_a != a.weap_anim_info_map.end()){
+ iter_a_id = iter_a->first;
+ }
+ if(iter_b != b.weap_anim_info_map.end()){
+ iter_b_id = iter_b->first;
+ }
+ if(iter_b_id != -1 && (iter_a_id == -1 || iter_a_id > iter_b_id)) {
+ result.weap_anim_info_map[iter_b_id] = iter_b->second;
+ result.weap_anim_info_map[iter_b_id].weight *= alpha;
+ ++iter_b;
+ } else if(iter_b_id == -1 || iter_b_id > iter_a_id) {
+ result.weap_anim_info_map[iter_a_id] = iter_a->second;
+ result.weap_anim_info_map[iter_a_id].weight *= (1.0f - alpha);
+ ++iter_a;
+ } else {
+ result.weap_anim_info_map[iter_a_id] = mix(iter_a->second, iter_b->second, alpha);
+ ++iter_a;
+ ++iter_b;
+ }
+ }
+
+ return result;
+}
+
+WeapAnimInfo mix( const WeapAnimInfo &a, const WeapAnimInfo &b, float b_weight ) {
+ WeapAnimInfo result;
+ result.bone_transform = mix(a.bone_transform, b.bone_transform, b_weight);
+ result.weight = mix(a.weight, b.weight, b_weight);
+ result.relative_id = max(a.relative_id, b.relative_id);
+ result.relative_weight = a.relative_weight;
+ return result;
+}
+
+AnimOutput add_mix( const AnimOutput &a, const AnimOutput &b, float alpha ) {
+ LOG_ASSERT(a.matrices.size() == b.matrices.size());
+
+ AnimOutput result = a;
+ size_t num_bones = a.matrices.size();
+
+ vector<float> bone_weights = b.physics_weights;
+ bone_weights.resize(num_bones, 1.0f);
+
+ float clamped_alpha = clamp(alpha, 0.0f, 1.0f);
+
+ result.matrices.resize(num_bones);
+ for(size_t i=0; i<num_bones; i++){
+ result.matrices[i] = mix(a.matrices[i], b.matrices[i], alpha * (bone_weights[i]));
+ }
+
+
+ // Add shape keys from both sources, and adjust their weights
+ {
+ map<string, ShapeKeyBlend> shape_keys;
+ map<string, ShapeKeyBlend>::iterator iter;
+
+ // Add shape keys from A
+ for(unsigned i=0; i<a.shape_keys.size(); ++i){
+ const string& label = a.shape_keys[i].label;
+ shape_keys[label] = a.shape_keys[i];
+ }
+
+ // Merge shape keys from B
+ for(unsigned i=0; i<b.shape_keys.size(); ++i){
+ const string& label = b.shape_keys[i].label;
+ iter = shape_keys.find(label);
+ if(iter == shape_keys.end()){
+ shape_keys[label] = b.shape_keys[i];
+ shape_keys[label].weight_weight *= clamped_alpha;
+ } else {
+ ShapeKeyBlend& a_key = shape_keys[label];
+ const ShapeKeyBlend& b_key = b.shape_keys[i];
+ a_key.weight = mix(a_key.weight, b_key.weight, b_key.weight_weight * clamped_alpha);
+ a_key.weight_weight = max(a_key.weight_weight, b_key.weight_weight * clamped_alpha);
+ }
+ }
+
+ // Add shape keys from merging map into result array
+ result.shape_keys.resize(shape_keys.size());
+ iter = shape_keys.begin();
+ int index = 0;
+ for(;iter != shape_keys.end(); ++iter){
+ result.shape_keys[index++] = (*iter).second;
+ }
+ }
+
+ // Add status keys from both sources, and adjust their weights
+ {
+ map<string, StatusKeyBlend> status_keys;
+ map<string, StatusKeyBlend>::iterator iter;
+
+ for(unsigned i=0; i<a.status_keys.size(); ++i){
+ const string& label = a.status_keys[i].label;
+ status_keys[label] = a.status_keys[i];
+ }
+
+ for(unsigned i=0; i<b.status_keys.size(); ++i){
+ const string& label = b.status_keys[i].label;
+ iter = status_keys.find(label);
+ if(iter == status_keys.end()){
+ status_keys[label] = b.status_keys[i];
+ status_keys[label].weight_weight *= clamped_alpha;
+ } else {
+ StatusKeyBlend& a_key = status_keys[label];
+ const StatusKeyBlend& b_key = b.status_keys[i];
+ a_key.weight = mix(a_key.weight, b_key.weight, b_key.weight_weight * clamped_alpha);
+ a_key.weight_weight = max(a_key.weight_weight, b_key.weight_weight * clamped_alpha);
+ }
+ }
+
+ result.status_keys.resize(status_keys.size());
+ iter = status_keys.begin();
+ int index = 0;
+ for(;iter != status_keys.end(); ++iter){
+ result.status_keys[index++] = (*iter).second;
+ }
+ }
+
+ size_t num_weapon_bones = max(a.weapon_matrices.size(),
+ b.weapon_matrices.size());
+ result.weapon_matrices.resize(num_weapon_bones);
+ result.weapon_weights.resize(num_weapon_bones);
+ result.weapon_weight_weights.resize(num_weapon_bones);
+ result.weapon_relative_ids.resize(num_weapon_bones);
+ result.weapon_relative_weights.resize(num_weapon_bones);
+ for(unsigned i=0; i<num_weapon_bones; i++){
+ if(i >= b.weapon_matrices.size()){
+ result.weapon_matrices[i] = a.weapon_matrices[i];
+ result.weapon_weights[i] = a.weapon_weights[i];
+ result.weapon_weight_weights[i] = a.weapon_weight_weights[i];
+ result.weapon_relative_ids[i] = a.weapon_relative_ids[i];
+ result.weapon_relative_weights[i] = a.weapon_relative_weights[i];
+ } else if(i >= a.weapon_matrices.size()){
+ result.weapon_matrices[i] = b.weapon_matrices[i];
+ result.weapon_weights[i] = b.weapon_weights[i] * clamped_alpha;
+ result.weapon_weight_weights[i] = b.weapon_weight_weights[i] * clamped_alpha;
+ result.weapon_relative_ids[i] = b.weapon_relative_ids[i];
+ result.weapon_relative_weights[i] = b.weapon_relative_weights[i];
+ } else {
+ result.weapon_matrices[i] =
+ mix(a.weapon_matrices[i], b.weapon_matrices[i], alpha * b.weapon_weight_weights[i]);
+ result.weapon_weights[i] =
+ mix(a.weapon_weights[i], b.weapon_weights[i], clamped_alpha * b.weapon_weight_weights[i]);
+ result.weapon_weight_weights[i] = max(a.weapon_weight_weights[i], b.weapon_weight_weights[i] * clamped_alpha);
+ result.weapon_relative_ids[i] = max(a.weapon_relative_ids[i], b.weapon_relative_ids[i]);
+ result.weapon_relative_weights[i] = a.weapon_relative_weights[i];
+ }
+ }
+
+ size_t num_joints = max(a.physics_weights.size(),
+ b.physics_weights.size());
+ size_t min_num_joints = min(a.physics_weights.size(),
+ b.physics_weights.size());
+ result.physics_weights.resize(num_joints, 1.0f);
+
+ if(a.physics_weights.empty()){
+ for(size_t i=0; i<num_joints; i++){
+ result.physics_weights[i] = mix(0.0f,
+ b.physics_weights[i],
+ clamped_alpha);
+ }
+ } else if(b.physics_weights.empty()){
+ for(size_t i=0; i<num_joints; i++){
+ result.physics_weights[i] = mix(a.physics_weights[i],
+ 0.0f,
+ clamped_alpha);
+ }
+ } else {
+ for(size_t i=0; i<min_num_joints; i++){
+ result.physics_weights[i] = max(a.physics_weights[i],
+ b.physics_weights[i]*clamped_alpha);
+ }
+ }
+
+ map<int, WeapAnimInfo>::const_iterator iter_a = a.weap_anim_info_map.begin();
+ map<int, WeapAnimInfo>::const_iterator iter_b = b.weap_anim_info_map.begin();
+
+ while(iter_a != a.weap_anim_info_map.end() ||
+ iter_b != b.weap_anim_info_map.end())
+ {
+ int iter_a_id = -1;
+ int iter_b_id = -1;
+ if(iter_a != a.weap_anim_info_map.end()){
+ iter_a_id = iter_a->first;
+ }
+ if(iter_b != b.weap_anim_info_map.end()){
+ iter_b_id = iter_b->first;
+ }
+ if(iter_b_id != -1 && (iter_a_id == -1 || iter_a_id > iter_b_id)) {
+ result.weap_anim_info_map[iter_b_id] = iter_b->second;
+ result.weap_anim_info_map[iter_b_id].weight *= clamped_alpha;
+ ++iter_b;
+ } else if(iter_b_id == -1 || iter_b_id > iter_a_id) {
+ result.weap_anim_info_map[iter_a_id] = iter_a->second;
+ ++iter_a;
+ } else {
+ result.weap_anim_info_map[iter_a_id] = mix(iter_a->second, iter_b->second, clamped_alpha);
+ ++iter_a;
+ ++iter_b;
+ }
+ }
+
+ return result;
+}
+
+/*
+void Animation::SaveCache(unsigned short checksum) {
+ FILE *file = my_fopen((GetWritePath(CoreGameModID)+path_+".anmcache2").c_str(), "wb");
+ fwrite(&checksum, sizeof(unsigned short), 1, file);
+ fwrite(&_anm_cache_version, sizeof(int), 1, file);
+ WriteToFile(file);
+ fclose(file);
+}
+bool Animation::LoadCache(unsigned short checksum) {
+
+ PROFILER_ZONE(g_profiler_ctx, "Loading cache");
+ FILE *file = my_fopen((GetWritePath(CoreGameModID)+path_+".anmcache2").c_str(), "rb");
+ if(!file){
+ return false;
+ }
+ //
+ //fclose(file);
+ //return false;
+ //
+ unsigned short file_checksum;
+ fread(&file_checksum, sizeof(unsigned short), 1, file);
+ if(file_checksum != checksum){
+ fclose(file);
+ return false;
+ }
+ int cache_version;
+ fread(&cache_version, sizeof(int), 1, file);
+ if(cache_version != _anm_cache_version){
+ fclose(file);
+ return false;
+ }
+ ReadFromFile(file);
+ fclose(file);
+ return true;
+}
+*/
+
+void Animation::GetMatrices( float normalized_time, AnimOutput &anim_output, const AnimInput& anim_input) const {
+ PROFILER_ZONE(g_profiler_ctx, "Animation::GetMatrices");
+ float local_time = LocalFromNormalized(normalized_time);
+
+ anim_output.weap_anim_info_map.clear();
+
+ // Get the id of the previous frame
+ int frame_id = -1;
+ int num_keyframes = (int) keyframes.size();
+ if( num_keyframes > 0 ) {
+ for(int i=0; i<num_keyframes; i++){
+ if(keyframes[i].time <= local_time){
+ frame_id = i;
+ }
+ }
+
+ // Get the 4 frames for interpolation:
+ // two frames ahead of the current time and two frames behind
+ const Keyframe *frames[4];
+ if(looping){
+ for(int i=0; i<4; ++i){
+ frames[i] = &keyframes[(frame_id+num_keyframes+i-1)%num_keyframes];
+ }
+ } else {
+ int last_frame = num_keyframes-1;
+ for(int i=0; i<4; ++i){
+ frames[i] = &keyframes[min(last_frame,max(0,frame_id+i-1))];
+ }
+ }
+
+ // Get bilinear time weight between the last frame and the next frame
+ float interp = 0.0f;
+ if(frames[1] != frames[2]) {
+ float frame_time = (float)frames[1]->time;
+ if(frame_time > local_time){
+ frame_time -= length;
+ }
+ float next_frame_time = (float)frames[2]->time;
+ if(next_frame_time < local_time){
+ next_frame_time += length;
+ }
+ interp = (local_time-frame_time)/(float)(next_frame_time - frame_time);
+ }
+ interp = max(0.0f, min(1.0f,interp));
+
+ // Get all four cubic spline weights from bilinear time weight
+ float weights[4];
+ GetCubicSplineWeights(interp, weights);
+ const bool kForceBilinear = false;
+ if(kForceBilinear){
+ weights[0] = 0.0f;
+ weights[1] = 1.0f-interp;
+ weights[2] = interp;
+ weights[3] = 0.0f;
+ }
+ if(animation_config.kDisableInterpolation){
+ weights[0] = 0.0f;
+ if(interp < 0.5f){
+ weights[1] = 1.0f;
+ weights[2] = 0.0f;
+ interp = 0.0f;
+ } else {
+ weights[1] = 0.0f;
+ weights[2] = 1.0f;
+ interp = 1.0f;
+ }
+ weights[3] = 0.0f;
+ }
+
+ // Get per-bone info
+ size_t num_matrices = keyframes[0].bone_mats.size();
+ anim_output.matrices.resize(num_matrices);
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Getting bone transforms");
+ for(uint32_t i=0; i<num_matrices; i++){
+ // Get bone transforms (linear or angular)
+ if(anim_input.parents && i < anim_input.parents->size() && anim_input.parents->at(i) != -1){
+ BoneTransform bone_transforms[4];
+ for(int j=0; j<4; ++j){
+ bone_transforms[j] = frames[j]->invert_bone_mats[anim_input.parents->at(i)] * frames[j]->bone_mats[i];
+ }
+ BoneTransform transform = BlendFourBones(bone_transforms, weights);
+ anim_output.matrices[i] = transform;
+ } else {
+ if(anim_input.parents && i >= anim_input.parents->size())
+ LOGW_ONCE("Incompatible animation");
+
+ BoneTransform bone_transforms[4];
+ for(int j=0; j<4; ++j){
+ bone_transforms[j] = frames[j]->bone_mats[i];
+ }
+ BoneTransform transform = BlendFourBones(bone_transforms, weights);
+ anim_output.matrices[i] = transform;
+ }
+ }
+ }
+
+ // Get per-weapon info
+ size_t num_weapon_matrices = keyframes[0].weapon_mats.size();
+ anim_output.weapon_weights.resize(num_weapon_matrices);
+ anim_output.weapon_weight_weights.resize(num_weapon_matrices);
+ for(int i=0; i<num_weapon_matrices; ++i) {
+ anim_output.weapon_weights[i] = 1.0f;
+ anim_output.weapon_weight_weights[i] = 1.0f;
+ }
+ anim_output.weapon_relative_ids = keyframes[0].weapon_relative_id;
+ anim_output.weapon_relative_weights = keyframes[0].weapon_relative_weight;
+ anim_output.weapon_matrices.resize(num_weapon_matrices);
+ for(size_t i=0; i<num_weapon_matrices; i++){
+ BoneTransform bone_transforms[4];
+ for(int j=0; j<4; ++j){
+ bone_transforms[j] = frames[j]->weapon_mats[i];
+ if(frames[j]->weapon_relative_weight[i] > 0.0f){
+ bone_transforms[j] = invert(frames[j]->bone_mats[frames[j]->weapon_relative_id[i]]) * bone_transforms[j];
+ }
+ }
+ BoneTransform transform = BlendFourBones(bone_transforms, weights);
+ anim_output.weapon_matrices[i] = transform;
+ }
+
+ // Get physics weights
+ size_t num_weights = keyframes[0].weights.size();
+ anim_output.physics_weights.resize(num_weights);
+ for(size_t i=0; i<num_weights; i++){
+ anim_output.physics_weights[i] = 0.0f;
+ for(int j=0; j<4; ++j){
+ anim_output.physics_weights[i] += frames[j]->weights[i] * weights[j];
+ }
+ }
+
+ anim_output.center_offset = frames[1]->center_offset * (1.0f-interp) + frames[2]->center_offset * interp;
+ anim_output.rotation = frames[1]->rotation * (1.0f-interp) + frames[2]->rotation * interp;
+
+ typedef map<string, BlendedBonePath> IKBoneMap;
+ IKBoneMap ik_bone_weights;
+ for(int j=0; j<4; ++j){
+ for(unsigned i=0; i<frames[j]->ik_bones.size(); ++i){
+ BlendedBonePath& weighted_ik_bone = ik_bone_weights[frames[j]->ik_bones[i].label];
+ weighted_ik_bone.weight += weights[j];
+ weighted_ik_bone.ik_bone = frames[j]->ik_bones[i];
+ }
+ }
+
+ for(IKBoneMap::iterator iter = ik_bone_weights.begin(); iter != ik_bone_weights.end(); ++iter){
+ BlendedBonePath &blended_bone_path = iter->second;
+ int bone = blended_bone_path.ik_bone.bone_path.back();
+ BoneTransform bone_transforms[4];
+ for(int j=0; j<4; ++j){
+ bone_transforms[j] = frames[j]->bone_mats[bone];
+ }
+ BoneTransform transform = mix(bone_transforms[1], bone_transforms[2], interp);//BlendFourBones(bone_transforms, weights);
+ blended_bone_path.transform = transform;
+ }
+
+ anim_output.ik_bones.resize(ik_bone_weights.size());
+ int index = 0;
+ for(IKBoneMap::iterator iter = ik_bone_weights.begin(); iter != ik_bone_weights.end(); ++iter){
+ anim_output.ik_bones[index] = (*iter).second;
+ ++index;
+ }
+
+ anim_output.shape_keys.resize(frames[0]->shape_keys.size());
+ for(unsigned i=0; i<anim_output.shape_keys.size(); i++){
+ anim_output.shape_keys[i].weight = 0.0f;
+ anim_output.shape_keys[i].weight_weight = 1.0f;
+ for(int j=0; j<4; ++j){
+ anim_output.shape_keys[i].weight += frames[j]->shape_keys[i].weight * weights[j];
+ }
+ anim_output.shape_keys[i].label = frames[0]->shape_keys[i].label;
+ }
+
+ anim_output.status_keys.resize(frames[1]->status_keys.size());
+ for(unsigned i=0; i<anim_output.status_keys.size(); i++){
+ anim_output.status_keys[i].weight = frames[1]->status_keys[i].weight * (1.0f-interp) +
+ frames[2]->status_keys[i].weight * interp;
+ anim_output.status_keys[i].weight_weight = 1.0f;
+ anim_output.status_keys[i].label = frames[1]->status_keys[i].label;
+ }
+
+
+ anim_output.old_path = AnimationRetargeter::Instance()->GetSkeletonFile(path_);
+ // Retarget if needed
+ /*if(!anim_input.retarget_new.empty())
+ {
+ Retarget(anim_input, anim_output, anim_output.old_path);
+ anim_output.old_path = "retargeted";
+ }*/
+ } else {
+ LOGE << "There are no keyframes in this animation" << endl;
+ }
+}
+
+int Animation::Load(const string &rel_path, uint32_t load_flags){
+ sub_error = 0;
+ char abs_path[kPathSize];
+ ModID modsource;
+ if( FindFilePath(rel_path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths, true, NULL, &modsource) == -1 ){
+ return kLoadErrorMissingFile;
+ }
+ FILE *file = my_fopen(abs_path, "rb");
+ if(file == NULL){
+ return kLoadErrorCouldNotOpen;
+ }
+
+ LOGD << "Loading animation " << rel_path << endl;
+ modsource_ = modsource;
+ int error = ReadFromFile(file);
+ fclose(file);
+ return error;
+}
+
+void Animation::Unload() {
+
+}
+
+const char* Animation::GetLoadErrorString() {
+ switch(sub_error) {
+ case 0: return "";
+ case 1: return "Animation data has a negative number of keyframes.";
+ default: return "Undefined error";
+ }
+}
+
+void Animation::clear()
+{
+ looping = false;
+ keyframes.clear();
+}
+
+float Animation::GetFrequency(const BlendMap& blendmap) const
+{
+ float frequency = 1.0f/GetPeriod(blendmap);
+ return frequency;
+}
+
+float Animation::GetPeriod(const BlendMap& blendmap) const
+{
+ float period = length/1000.0f;
+ return period;
+}
+
+
+float Animation::NormalizedFromLocal( float local_time ) const {
+ if(length == 0){
+ return 0.0f;
+ }
+ return local_time/length;
+}
+
+bool Animation::IsLooping() const {
+ return looping;
+}
+
+float Animation::GetGroundSpeed( const BlendMap& blendmap ) const {
+ return 1.0f;
+}
+
+vector<NormalizedAnimationEvent> Animation::GetEvents(int& anim_id, bool mirror) const
+{
+ vector<NormalizedAnimationEvent> normalized_events;
+ for(unsigned i=0; i<keyframes.size(); i++){
+ for(unsigned j=0; j<keyframes[i].events.size(); j++){
+ const AnimationEvent &the_event = keyframes[i].events[j];
+
+ normalized_events.resize(normalized_events.size()+1);
+ NormalizedAnimationEvent &normalized_event =
+ normalized_events.back();
+
+ normalized_event.event = the_event;
+ normalized_event.anim_id = anim_id;
+ normalized_event.time = NormalizedFromLocal((float)keyframes[i].time);
+ }
+ }
+
+ if(mirror){
+ const string& skeleton_file_path = AnimationRetargeter::Instance()->GetSkeletonFile(path_);
+ //SkeletonAssets::Instance()->ReturnRef(skeleton_file_path);
+ SkeletonAssetRef sar = Engine::Instance()->GetAssetManager()->LoadSync<SkeletonAsset>(skeleton_file_path);
+ const SkeletonFileData& skeleton_file_data = sar->GetData();
+ const vector<int>& symmetry = skeleton_file_data.symmetry;
+ for(size_t i=0, len=normalized_events.size(); i<len; ++i){
+ AnimationEvent& event = normalized_events[i].event;
+ SwitchStringRightLeft(event.event_string);
+ if(symmetry[event.which_bone] != -1){
+ event.which_bone = symmetry[event.which_bone];
+ }
+ }
+ }
+
+ ++anim_id;
+ return normalized_events;
+}
+
+void Animation::RecalcCaches() {
+ // Cache which bones use IK in each frame
+ for(unsigned i=0; i<keyframes.size(); i++){
+ Keyframe& k = keyframes[i];
+
+ // Initialize uses_ik vector
+ size_t num_bones = k.bone_mats.size();
+ k.uses_ik.resize(num_bones);
+ for(int i=0; i<num_bones; i++){
+ k.uses_ik[i] = false;
+ }
+ // Travel down each ik chain to specify that each bone uses ik
+ size_t num_ik_bones = k.ik_bones.size();
+ for(int i=0; i<num_ik_bones; i++){
+ const BonePath &bone_path = k.ik_bones[i].bone_path;
+ size_t bone_path_len = bone_path.size();
+ for(size_t j=0; j<bone_path_len; j++){
+ k.uses_ik[bone_path[j]] = true;
+ }
+ }
+ }
+ CalcInvertBoneMats();
+}
+
+void Animation::Reload() {
+ Load(path_, 0x0);
+}
+
+void Animation::ReportLoad() {
+
+}
+
+void MirrorBT(BoneTransform &bt, bool xy_flip) {
+ bt.origin[0] *= -1.0f;
+
+ bt.rotation[1] *= -1.0f;
+ bt.rotation[2] *= -1.0f;
+
+ for(int i=0; i<4; ++i){
+ LOG_ASSERT(bt.rotation[i] == bt.rotation[i]);
+ }
+
+ if(xy_flip){
+ mat4 mat = Mat4FromQuaternion(bt.rotation);
+ mat.SetColumn(0,mat.GetColumn(0)*-1.0f);
+ mat.SetColumn(1,mat.GetColumn(1)*-1.0f);
+ bt.rotation = QuaternionFromMat4(mat);
+ }
+}
+
+string filename(const string &path) {
+ size_t last_slash_pos = path.rfind('/');
+ return path.substr(last_slash_pos+1);
+}
+
+string extension(const string &path) {
+ size_t last_slash_pos = path.rfind('.');
+ return path.substr(last_slash_pos+1);
+}
+
+AnimationAsset::AnimationAsset( AssetManager* owner, uint32_t asset_id ) : AssetInfo( owner, asset_id )
+{
+
+}
+
+AnimationAssetRef ReturnAnimationAssetRef(const string &path) {
+ AnimationAssetRef new_ref;
+ if(extension(path) == "xml"){
+ SyncedAnimationGroupRef ref = Engine::Instance()->GetAssetManager()->LoadSync<SyncedAnimationGroup>(path);
+ new_ref = AnimationAssetRef(&(*ref));
+ } else if(extension(path) == "anm"){
+ AnimationRef ref = Engine::Instance()->GetAssetManager()->LoadSync<Animation>(path);
+ new_ref = AnimationAssetRef(&(*ref));
+ }
+ return new_ref;
+}
+
+float GetAnimationEventTime( const string& anim_path, const string& event_str ) {
+ AnimationAssetRef aar = ReturnAnimationAssetRef(anim_path);
+ int anim_id = 0;
+ vector<NormalizedAnimationEvent> events = aar->GetEvents(anim_id,false);
+ for(vector<NormalizedAnimationEvent>::const_iterator iter = events.begin();
+ iter != events.end(); ++iter)
+ {
+ const NormalizedAnimationEvent& event = (*iter);
+ if(event_str == event.event.event_string){
+ return aar->AbsoluteTimeFromNormalized(event.time) * 0.001f;
+ }
+ }
+ return -1.0f;
+}
+
+
+void Animation::ReturnPaths( PathSet& path_set ) {
+ path_set.insert("animation "+path_);
+}
+
+Animation::~Animation() {
+}
+
+int Animation::GetActiveID( const BlendMap& blendmap, int &anim_id ) const {
+ int curr_id = anim_id;
+ ++anim_id;
+ return curr_id;
+}
+
+float Animation::AbsoluteTimeFromNormalized( float normalized_time ) const {
+ return normalized_time * length;
+}
+
+AnimationConfig::AnimationConfig() :
+ kDisableIK(false),
+ kDisableModifiers(false),
+ kDisableSoftAnimation(false),
+ kDisableInterpolation(false),
+ kDisableAnimationLayers(false),
+ kDisableAnimationMix(false),
+ kDisableAnimationTransition(false),
+ kDisablePhysicsInterpolation(false),
+ kForceIdleAnim(false)
+{
+
+}
+
+AssetLoaderBase* Animation::NewLoader() {
+ return new FallbackAssetLoader<Animation>();
+}
diff --git a/Source/Asset/Asset/animation.h b/Source/Asset/Asset/animation.h
new file mode 100644
index 00000000..bcfb2f7b
--- /dev/null
+++ b/Source/Asset/Asset/animation.h
@@ -0,0 +1,253 @@
+//-----------------------------------------------------------------------------
+// Name: animation.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Graphics/bonetransform.h>
+#include <Graphics/ikbone.h>
+
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+#include <Asset/assettypes.h>
+
+#include <map>
+#include <string>
+
+using std::vector;
+using std::map;
+using std::string;
+
+struct AnimationConfig {
+ bool kDisableIK;
+ bool kDisableModifiers;
+ bool kDisableSoftAnimation;
+ bool kDisableInterpolation;
+ bool kDisableAnimationLayers;
+ bool kDisableAnimationMix;
+ bool kDisableAnimationTransition;
+ bool kDisablePhysicsInterpolation;
+ bool kForceIdleAnim;
+ AnimationConfig();
+};
+
+class Skeleton;
+struct SkeletonFileData;
+
+typedef map<string, float> BlendMap;
+
+// Animation event, for example "Punch impact" at right hand bone
+struct AnimationEvent {
+ int which_bone;
+ string event_string;
+};
+
+struct NormalizedAnimationEvent {
+ AnimationEvent event;
+ float time;
+ unsigned anim_id;
+};
+
+class NormalizedAnimationEventCompare {
+ public:
+ bool operator()(const NormalizedAnimationEvent &a,
+ const NormalizedAnimationEvent &b) {
+ return a.time < b.time;
+ }
+};
+
+struct ShapeKey {
+ float weight;
+ string label;
+};
+
+struct StatusKey {
+ float weight;
+ string label;
+};
+
+struct ShapeKeyBlend {
+ float weight;
+ float weight_weight;
+ string label;
+};
+
+struct StatusKeyBlend {
+ float weight;
+ float weight_weight;
+ string label;
+};
+
+struct WeapAnimInfo {
+ int relative_id;
+ float relative_weight;
+ float weight;
+ BoneTransform bone_transform;
+ mat4 matrix;
+};
+
+WeapAnimInfo mix(const WeapAnimInfo &a, const WeapAnimInfo &b, float b_weight);
+
+struct Keyframe {
+ // Per-bone info
+ vector<BoneTransform> bone_mats;
+ vector<BoneTransform> invert_bone_mats;
+ vector<bool> uses_ik;
+ vector<float> weights;
+ // Per-weapon info
+ vector<BoneTransform> weapon_mats;
+ vector<int> weapon_relative_id;
+ vector<float> weapon_relative_weight;
+ // Misc
+ vector<IKBone> ik_bones;
+ vector<ShapeKey> shape_keys;
+ vector<StatusKey> status_keys;
+ bool use_mobility;
+ mat4 mobility_mat;
+ vec3 center_offset;
+ float rotation;
+ vector<AnimationEvent> events;
+ int time;
+};
+
+const int _animation_version = 11;
+
+struct AnimInput {
+ const BlendMap& blendmap;
+ const vector<int> *parents;
+ bool mirrored;
+ string retarget_new;
+ AnimInput(const BlendMap &_blendmap, const vector<int> *_parents)
+ :blendmap(_blendmap),
+ parents(_parents),
+ mirrored(false)
+ {}
+};
+
+struct AnimOutput {
+ // Get directly from animations
+ vector<BoneTransform> weapon_matrices;
+ vector<float> weapon_weight_weights;
+ vector<float> weapon_weights; //
+ vector<float> weapon_relative_weights; // How much is this weapon relative to the bone, and how much absolute
+ vector<int> weapon_relative_ids; // What bone is this weapon relative to?
+ vector<BoneTransform> matrices;
+ vector<float> physics_weights;
+ vector<BlendedBonePath> ik_bones;
+ vector<ShapeKeyBlend> shape_keys;
+ vector<StatusKeyBlend> status_keys;
+ string old_path;
+ vec3 center_offset;
+ float rotation;
+
+ // Used for blending animations
+ map<int, WeapAnimInfo> weap_anim_info_map;
+
+ // Added by animation client
+ vector<BoneTransform> unmodified_matrices;
+
+ // Added by animation reader
+ vec3 delta_offset;
+ float delta_rotation;
+
+ AnimOutput()
+ {}
+};
+
+AnimOutput mix(const AnimOutput &a, const AnimOutput &b, float alpha);
+AnimOutput add_mix( const AnimOutput &a, const AnimOutput &b, float alpha );
+void MirrorBT(BoneTransform &bt, bool xy_flip);
+
+class AnimationAsset:public AssetInfo {
+public:
+ AnimationAsset( AssetManager* owner, uint32_t asset_id );
+ virtual void GetMatrices(float time,
+ AnimOutput &anim_output,
+ const AnimInput &anim_input) const =0;
+
+ virtual float GetFrequency(const BlendMap& blendmap) const =0;
+ virtual float GetPeriod(const BlendMap& blendmap) const =0;
+ virtual float GetGroundSpeed(const BlendMap& blendmap) const =0;
+ virtual int GetActiveID(const BlendMap& blendmap, int &anim_id) const =0;
+ virtual vector<NormalizedAnimationEvent> GetEvents(int& anim_id, bool mirrored) const =0;
+ virtual bool IsLooping() const =0;
+ virtual float AbsoluteTimeFromNormalized(float normalized_time) const=0;
+
+ static AssetType GetType() { return ANIMATION_ASSET; }
+ static const char* GetTypeName() { return "ANIMATION_ASSET"; }
+ static bool AssetWarning() { return true; }
+};
+
+class Animation:public AnimationAsset {
+public:
+ Animation( AssetManager * owner, uint32_t asset_id );
+ virtual ~Animation();
+
+ int GetActiveID( const BlendMap& blendmap, int &anim_id ) const;
+ void clear();
+ void GetMatrices( float normalized_time, AnimOutput &anim_output, const AnimInput& anim_input) const;
+ void WriteToFile( FILE * file );
+ int ReadFromFile( FILE * file );
+
+ int sub_error;
+ int Load(const string& path, uint32_t load_flags);
+
+ void Unload();
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+
+
+ float GetGroundSpeed(const BlendMap& blendmap) const;
+ float GetFrequency(const BlendMap& blendmap) const;
+ float GetPeriod(const BlendMap& blendmap) const;
+ vector<NormalizedAnimationEvent> GetEvents(int& anim_id, bool mirrored) const;
+ float LocalFromNormalized(float normalized_time) const;
+ float NormalizedFromLocal(float local_time) const;
+ bool IsLooping() const;
+ void UpdateIKBones();
+ void Reload();
+ virtual void ReportLoad();
+ float AbsoluteTimeFromNormalized(float normalized_time) const;
+ void ReturnPaths( PathSet& path_set );
+
+ virtual AssetLoaderBase* NewLoader();
+private:
+ int length; // in ms
+ vector<Keyframe> keyframes;
+ bool looping;
+ ModID modsource_;
+
+ void CalcInvertBoneMats();
+ void RecalcCaches();
+ //void SaveCache(unsigned short checksum);
+ //bool LoadCache(unsigned short checksum);
+ void Center();
+};
+
+typedef AssetRef<Animation> AnimationRef;
+typedef AssetRef<AnimationAsset> AnimationAssetRef;
+
+float GetAnimationEventTime(const string& anim_path, const string& event_str);
+AnimationAssetRef ReturnAnimationAssetRef(const string &path);
+string extension(const string &path);
+string filename(const string &path);
+void Retarget(const AnimInput& anim_input, AnimOutput & anim_output, const string& old_path);
diff --git a/Source/Asset/Asset/animationeffect.cpp b/Source/Asset/Asset/animationeffect.cpp
new file mode 100644
index 00000000..171c1df8
--- /dev/null
+++ b/Source/Asset/Asset/animationeffect.cpp
@@ -0,0 +1,224 @@
+//-----------------------------------------------------------------------------
+// Name: animationeffect.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include "animationeffect.h"
+
+#include <Internal/timer.h>
+#include <Internal/common.h>
+#include <Internal/filesystem.h>
+
+#include <TheoraPlayer/TheoraVideoManager.h>
+#include <TheoraPlayer/TheoraDataSource.h>
+#include <TheoraPlayer/TheoraVideoFrame.h>
+
+#include <Graphics/textures.h>
+#include <Graphics/animationeffectsystem.h>
+
+#include <XML/xml_helper.h>
+#include <Logging/logdata.h>
+#include <Main/engine.h>
+#include <Asset/AssetLoader/fallbackassetloader.h>
+
+#include <tinyxml.h>
+
+#include <cstdio>
+#include <string>
+
+using std::string;
+
+int AnimationEffect::Load( const string &path, uint32_t load_flags ) {
+ sub_error = 0;
+ clear();
+ video_path = path.substr(0,path.size()-4)+".ogv";
+
+ //I guess this is some kind of fallback onto pure .ogv file if it exists, rather than xml loaded?
+ if(!FileExists(video_path.c_str(), kDataPaths|kModPaths)) {
+ video_path.clear();
+
+ TiXmlDocument doc;
+ if( LoadXMLRetryable(doc, path, "Animation Effect") )
+ {
+ TiXmlHandle hDoc(&doc);
+ TiXmlHandle hRoot = hDoc.FirstChildElement();
+ TiXmlElement* root = hRoot.ToElement();
+
+ int num_frames;
+ if(root->QueryIntAttribute("frames", &num_frames) != TIXML_SUCCESS){
+ num_frames = 0;
+ }
+ if(root->QueryIntAttribute("framerate", &frame_rate) != TIXML_SUCCESS){
+ frame_rate = 30;
+ }
+
+ int num_digits = (int)(logf((float)num_frames)/ logf(10))+1;
+ const int FORMAT_BUF_SIZE = 256;
+ char format[FORMAT_BUF_SIZE];
+ string new_path = path.substr(0,path.size()-4);
+ FormatString(format, FORMAT_BUF_SIZE, "%s%%0%dd.tga", new_path.c_str(), num_digits);
+
+ const int TEX_PATH_BUF_SIZE = 256;
+ char tex_path[TEX_PATH_BUF_SIZE];
+ frames.resize(num_frames);
+ for(int i=0; i<num_frames; ++i){
+ FormatString(tex_path, TEX_PATH_BUF_SIZE, format, i);
+ frames[i] = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(tex_path,PX_SRGB,0x0);
+ }
+ }
+ else
+ {
+ return kLoadErrorMissingFile;
+ }
+ }
+
+ return kLoadOk;
+}
+
+const char* AnimationEffect::GetLoadErrorString() {
+ return "";
+}
+
+void AnimationEffect::Unload()
+{
+
+}
+
+void AnimationEffect::Reload()
+{
+ Load(path_,0x0);
+}
+
+void AnimationEffect::ReportLoad()
+{
+
+}
+
+void AnimationEffect::clear()
+{
+ frames.clear();
+}
+
+AnimationEffect::~AnimationEffect()
+{
+ clear();
+}
+
+AnimationEffect::AnimationEffect( AssetManager* owner, uint32_t asset_id ) : Asset(owner, asset_id), sub_error(0)
+{
+
+}
+
+AssetLoaderBase* AnimationEffect::NewLoader() {
+ return new FallbackAssetLoader<AnimationEffect>();
+}
+
+void AnimationEffectReader::Update(float timestep){
+ if(!use_theora){
+ time += timestep;
+ int frame = (int)(time * ae_ref->frame_rate);
+ if(frame >= (int)ae_ref->frames.size()){
+ ae_ref.clear();
+ }
+ }
+}
+
+bool AnimationEffectReader::valid()
+{
+ return ae_ref.valid();
+}
+
+void AnimationEffectReader::AttachTo( const AnimationEffectRef& _ae_ref )
+{
+ ae_ref = _ae_ref;
+ time = 0.0f;
+ if(clip){
+ Dispose();
+ }
+ use_theora = !ae_ref->video_path.empty();
+}
+
+TextureRef& AnimationEffectReader::GetTextureAssetRef()
+{
+ //int frame = (int)(time * ae_ref->frame_rate)%((int)ae_ref->frames.size());
+ //return ae_ref->frames[frame];
+
+ if(!clip && use_theora){
+ clip_mem = new TheoraMemoryFileDataSource(ae_ref->video_path);
+ TheoraVideoManager *mgr = Engine::Instance()->GetAnimationEffectSystem()->mgr;
+ clip = mgr->createVideoClip(clip_mem,TH_BGRA);
+ clip->setAutoRestart(0);
+ clip_tex = Textures::Instance()->makeTextureColor(
+ clip->getWidth(), clip->getHeight(), GL_RGBA, GL_RGBA, 0.0f, 0.0f, 0.0f, 0.0f, false);
+ Textures::Instance()->SetTextureName(clip_tex, "Animation Effect Clip");
+ }
+
+ TheoraVideoFrame* f=clip->getNextFrame();
+ if (f)
+ {
+ Textures::Instance()->bindTexture(clip_tex);
+ glTexSubImage2D(GL_TEXTURE_2D,0,0,0,clip->getWidth(),f->getHeight(),GL_BGRA,GL_UNSIGNED_BYTE,f->getBuffer());
+ clip->popFrame();
+ }
+ return clip_tex;
+}
+
+AnimationEffectReader::~AnimationEffectReader()
+{
+ Dispose();
+}
+
+AnimationEffectReader::AnimationEffectReader():
+use_theora(false),
+clip(NULL),
+clip_mem(NULL)
+{
+}
+
+AnimationEffectReader::AnimationEffectReader( const AnimationEffectReader &other )
+{
+ time = other.time;
+ ae_ref = other.ae_ref;
+ use_theora = other.use_theora;
+ clip = NULL;
+ clip_mem = NULL;
+}
+
+void AnimationEffectReader::Dispose()
+{
+ if(use_theora){
+ Engine::Instance()->GetAnimationEffectSystem()->mgr->destroyVideoClip(clip);
+ if(clip_mem){
+ delete clip_mem;
+ clip_mem = NULL;
+ }
+ }
+}
+
+bool AnimationEffectReader::Done()
+{
+ if(clip){
+ return clip->isDone();
+ }
+ return false;
+}
+
diff --git a/Source/Asset/Asset/animationeffect.h b/Source/Asset/Asset/animationeffect.h
new file mode 100644
index 00000000..523bba3b
--- /dev/null
+++ b/Source/Asset/Asset/animationeffect.h
@@ -0,0 +1,90 @@
+//-----------------------------------------------------------------------------
+// Name: animationeffect.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/Asset/texture.h>
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+
+#include <Math/quaternions.h>
+
+#include <vector>
+#include <string>
+
+using std::string;
+using std::vector;
+
+class TheoraVideoClip;
+class TheoraMemoryFileDataSource;
+class AssetManager;
+
+class AnimationEffect : public Asset {
+public:
+ string video_path;
+ vector<TextureAssetRef> frames;
+ int frame_rate;
+
+ void clear();
+
+ int sub_error;
+ int Load(const string &path, uint32_t load_flags);
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+
+ void Unload();
+ void Reload();
+ virtual void ReportLoad();
+
+ ~AnimationEffect();
+ AnimationEffect(AssetManager* owner, uint32_t asset_id);
+
+ static AssetType GetType() { return ANIMATION_EFFECT_ASSET; }
+ static const char* GetTypeName() { return "ANIMATION_EFFECT_ASSET"; }
+
+ virtual AssetLoaderBase* NewLoader();
+ static bool AssetWarning() { return true; }
+};
+
+typedef AssetRef<AnimationEffect> AnimationEffectRef;
+class ASContext;
+
+class AnimationEffectReader {
+ float time;
+ AnimationEffectRef ae_ref;
+ bool use_theora;
+ TheoraVideoClip *clip;
+ TheoraMemoryFileDataSource* clip_mem;
+ TextureRef clip_tex;
+public:
+ bool Done();
+ void Dispose();
+ void Update(float timestep);
+ bool valid();
+ void AttachTo(const AnimationEffectRef& _ae_ref);
+ TextureRef &GetTextureAssetRef();
+ AnimationEffectReader();
+ AnimationEffectReader(const AnimationEffectReader& other);
+ ~AnimationEffectReader();
+};
+
diff --git a/Source/Asset/Asset/attachmentasset.cpp b/Source/Asset/Asset/attachmentasset.cpp
new file mode 100644
index 00000000..e345c6af
--- /dev/null
+++ b/Source/Asset/Asset/attachmentasset.cpp
@@ -0,0 +1,98 @@
+//-----------------------------------------------------------------------------
+// Name: attachementasset.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "attachmentasset.h"
+
+#include <XML/xml_helper.h>
+#include <Logging/logdata.h>
+#include <Asset/AssetLoader/fallbackassetloader.h>
+
+#include <tinyxml.h>
+
+#include <string>
+
+using std::string;
+
+Attachment::Attachment( AssetManager* owner, uint32_t asset_id ) : Asset( owner, asset_id ) {
+
+}
+
+void Attachment::clear() {
+ anim.clear();
+ ik_chain_label.clear();
+ ik_chain_bone = -1;
+ mirror_allowed = false;
+}
+
+int Attachment::Load( const string &path, uint32_t load_flags ) {
+ TiXmlDocument doc;
+ if( LoadXMLRetryable(doc, path, "Attachment") ) {
+ clear();
+
+ TiXmlHandle h_doc(&doc);
+ TiXmlHandle h_root = h_doc.FirstChildElement();
+ TiXmlElement* field = h_root.ToElement()->FirstChildElement();
+ for( ; field; field = field->NextSiblingElement()) {
+ string field_str(field->Value());
+ if(field_str == "anim"){
+ anim = field->GetText();
+ } else if(field_str == "attach"){
+ const char* tf;
+ tf = field->Attribute("ik_chain");
+ if(tf){
+ ik_chain_label = tf;
+ }
+ field->QueryIntAttribute("bone", &ik_chain_bone);
+ } else if(field_str == "mirror"){
+ const char* tf;
+ tf = field->Attribute("allow");
+ if(tf && strcmp(tf, "true") == 0){
+ mirror_allowed = true;
+ }
+ }
+ }
+ } else {
+ return kLoadErrorMissingFile;
+ }
+ return kLoadOk;
+}
+
+const char* Attachment::GetLoadErrorString() {
+ return "";
+}
+
+void Attachment::Unload() {
+
+}
+
+void Attachment::Reload() {
+ Load(path_, 0x0);
+}
+
+void Attachment::ReportLoad() {
+
+}
+
+AssetLoaderBase* Attachment::NewLoader() {
+ return new FallbackAssetLoader<Attachment>();
+}
diff --git a/Source/Asset/Asset/attachmentasset.h b/Source/Asset/Asset/attachmentasset.h
new file mode 100644
index 00000000..7fb983e6
--- /dev/null
+++ b/Source/Asset/Asset/attachmentasset.h
@@ -0,0 +1,61 @@
+//-----------------------------------------------------------------------------
+// Name: attachmentasset.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+#include <Math/vec4.h>
+
+#include <Asset/assetbase.h>
+#include <Asset/assetref.h>
+
+#include <string>
+
+using std::string;
+
+class Attachment : public Asset {
+public:
+ Attachment( AssetManager* owner, uint32_t asset_id );
+ static AssetType GetType() { return ATTACHMENT_ASSET; }
+ static const char* GetTypeName() { return "ATTACHMENT_ASSET"; }
+
+ string anim;
+ string ik_chain_label;
+ int ik_chain_bone;
+ bool mirror_allowed;
+
+ int Load(const string &path, uint32_t load_flags);
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+
+ void Unload();
+ void Reload();
+ virtual void ReportLoad();
+
+ void clear();
+
+ virtual AssetLoaderBase* NewLoader();
+ static bool AssetWarning() { return true; }
+};
+
+typedef AssetRef<Attachment> AttachmentRef;
diff --git a/Source/Asset/Asset/attacks.cpp b/Source/Asset/Asset/attacks.cpp
new file mode 100644
index 00000000..3d572f3f
--- /dev/null
+++ b/Source/Asset/Asset/attacks.cpp
@@ -0,0 +1,244 @@
+//-----------------------------------------------------------------------------
+// Name: attacks.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "attacks.h"
+
+#include <Asset/AssetLoader/fallbackassetloader.h>
+#include <Asset/Asset/animation.h>
+#include <Asset/Asset/reactions.h>
+
+#include <XML/xml_helper.h>
+#include <Math/vec3math.h>
+#include <Logging/logdata.h>
+#include <Main/engine.h>
+
+#include <tinyxml.h>
+
+#include <string>
+
+using std::string;
+
+Attack::Attack(AssetManager* owner, uint32_t asset_id) : AssetInfo( owner, asset_id ) {
+
+}
+
+void Attack::clear() {
+ unblocked_anim_path.clear();
+ blocked_anim_path.clear();
+ height = _medium;
+ direction = _front;
+ swap_stance = false;
+ swap_stance_blocked = false;
+ reaction.clear();
+ impact_dir = vec3(0.0f,0.0f,1.0f);
+ has_cut_plane = false;
+ has_stab_dir = false;
+ cut_plane_type = _heavy;
+ stab_type = _heavy;
+ sharp_damage[0] = 0.0f;
+ sharp_damage[1] = 0.0f;
+ block_damage[0] = 0.0f;
+ block_damage[1] = 0.0f;
+ damage[0] = 0.0f;
+ damage[1] = 0.0f;
+ force[0] = 0.0f;
+ force[1] = 0.0f;
+ unblockable = false;
+ flesh_unblockable = false;
+ mobile = false;
+ materialevent.clear();
+}
+
+int Attack::Load( const string &path, uint32_t load_flags )
+{
+ TiXmlDocument doc;
+ if( LoadXMLRetryable(doc, path, "Attack") )
+ {
+ clear();
+
+ TiXmlHandle h_doc(&doc);
+ TiXmlHandle h_root = h_doc.FirstChildElement();
+ TiXmlElement* field = h_root.ToElement()->FirstChildElement();
+ for( ; field; field = field->NextSiblingElement()) {
+ string field_str(field->Value());
+ if(field_str == "animations"){
+ const char* tf;
+ tf = field->Attribute("unblocked");
+ if(tf){
+ unblocked_anim_path = tf;
+ }
+ tf = field->Attribute("blocked");
+ if(tf){
+ blocked_anim_path = tf;
+ }
+ tf = field->Attribute("throw");
+ if(tf){
+ throw_anim_path = tf;
+ }
+ tf = field->Attribute("thrown");
+ if(tf){
+ thrown_anim_path = tf;
+ }
+ tf = field->Attribute("thrown_counter");
+ if(tf){
+ thrown_counter_anim_path = tf;
+ }
+ } else if(field_str == "flags"){
+ const char* tf;
+ tf = field->Attribute("swap_stance");
+ swap_stance = (tf && strcmp(tf, "true")==0);
+ tf = field->Attribute("swap_stance_blocked");
+ swap_stance_blocked = (tf && strcmp(tf, "true")==0);
+ tf = field->Attribute("unblockable");
+ unblockable = (tf && strcmp(tf, "true")==0);
+ tf = field->Attribute("flesh_unblockable");
+ flesh_unblockable = (tf && strcmp(tf, "true")==0);
+ tf = field->Attribute("as_layer");
+ as_layer = (tf && strcmp(tf, "true")==0);
+ tf = field->Attribute("alternate");
+ if(tf){
+ alternate = tf;
+ }
+ tf = field->Attribute("mobile");
+ mobile = (tf && strcmp(tf, "true")==0);
+ tf = field->Attribute("height");
+ if(tf){
+ if(strcmp(tf, "high") == 0){
+ height = _high;
+ } else if(strcmp(tf, "medium") == 0){
+ height = _medium;
+ } if(strcmp(tf, "low") == 0){
+ height = _low;
+ }
+ }
+ tf = field->Attribute("direction");
+ if(tf){
+ if(strcmp(tf, "right") == 0){
+ direction = _right;
+ } else if(strcmp(tf, "front") == 0){
+ direction = _front;
+ } if(strcmp(tf, "left") == 0){
+ direction = _left;
+ }
+ }
+ tf = field->Attribute("special");
+ if(tf){
+ special = tf;
+ }
+ } else if(field_str == "reaction"){
+ reaction = field->GetText();
+ } else if(field_str == "materialevent"){
+ const char* tf;
+ tf = field->GetText();
+ if(tf){
+ materialevent = tf;
+ }
+ } else if(field_str == "impact_dir"){
+ field->QueryFloatAttribute("x", &impact_dir[0]);
+ field->QueryFloatAttribute("y", &impact_dir[1]);
+ field->QueryFloatAttribute("z", &impact_dir[2]);
+ } else if(field_str == "cut_plane"){
+ has_cut_plane = true;
+ field->QueryFloatAttribute("x", &cut_plane[0]);
+ field->QueryFloatAttribute("y", &cut_plane[1]);
+ field->QueryFloatAttribute("z", &cut_plane[2]);
+ const char* tf = field->Attribute("type");
+ if(tf && strcmp(tf, "light")){
+ cut_plane_type = _light;
+ } else if(tf && strcmp(tf, "heavy")){
+ cut_plane_type = _heavy;
+ }
+ cut_plane = normalize(cut_plane);
+ } else if(field_str == "stab_dir"){
+ has_stab_dir = true;
+ field->QueryFloatAttribute("x", &stab_dir[0]);
+ field->QueryFloatAttribute("y", &stab_dir[1]);
+ field->QueryFloatAttribute("z", &stab_dir[2]);
+ const char* tf = field->Attribute("type");
+ if(tf && strcmp(tf, "light")){
+ stab_type = _light;
+ } else if(tf && strcmp(tf, "heavy")){
+ stab_type = _heavy;
+ }
+ stab_dir = normalize(stab_dir);
+ } else if(field_str == "block_damage"){
+ GetRange(field, "val", "min", "max",
+ block_damage[0], block_damage[1]);
+ } else if(field_str == "sharp_damage"){
+ GetRange(field, "val", "min", "max",
+ sharp_damage[0], sharp_damage[1]);
+ } else if(field_str == "damage"){
+ GetRange(field, "val", "min", "max", damage[0], damage[1]);
+ } else if(field_str == "force"){
+ GetRange(field, "val", "min", "max", force[0], force[1]);
+ }
+ }
+ } else {
+ return kLoadErrorMissingFile;
+ }
+ return kLoadOk;
+}
+
+const char* Attack::GetLoadErrorString() {
+ return "";
+}
+
+void Attack::Unload() {
+
+}
+
+void Attack::Reload() {
+ Load(path_,0x0);
+}
+
+void Attack::ReportLoad() {
+
+}
+
+void Attack::ReturnPaths( PathSet &path_set )
+{
+ path_set.insert("attack "+path_);
+ if(!unblocked_anim_path.empty()){
+ ReturnAnimationAssetRef(unblocked_anim_path)->ReturnPaths(path_set);
+ }
+ if(!blocked_anim_path.empty()){
+ ReturnAnimationAssetRef(blocked_anim_path)->ReturnPaths(path_set);
+ }
+ if(!throw_anim_path.empty()){
+ ReturnAnimationAssetRef(throw_anim_path)->ReturnPaths(path_set);
+ }
+ if(!thrown_anim_path.empty()){
+ ReturnAnimationAssetRef(thrown_anim_path)->ReturnPaths(path_set);
+ }
+ if(!thrown_counter_anim_path.empty()){
+ ReturnAnimationAssetRef(thrown_counter_anim_path)->ReturnPaths(path_set);
+ }
+ if(!reaction.empty()){
+ //Reactions::Instance()->ReturnRef(reaction)->ReturnPaths(path_set);
+ Engine::Instance()->GetAssetManager()->LoadSync<Reaction>(reaction)->ReturnPaths(path_set);
+ }
+}
+
+AssetLoaderBase* Attack::NewLoader() {
+ return new FallbackAssetLoader<Attack>();
+}
diff --git a/Source/Asset/Asset/attacks.h b/Source/Asset/Asset/attacks.h
new file mode 100644
index 00000000..04ab2871
--- /dev/null
+++ b/Source/Asset/Asset/attacks.h
@@ -0,0 +1,102 @@
+//-----------------------------------------------------------------------------
+// Name: attacks.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+
+#include <Math/vec3.h>
+
+#include <string>
+
+using std::string;
+
+enum AttackHeight {
+ _high = 0,
+ _medium = 1,
+ _low = 2
+};
+
+enum AttackDirection {
+ _right = 0,
+ _front = 1,
+ _left = 2
+};
+
+enum CutPlaneType {
+ _heavy = 0,
+ _light = 1
+};
+
+class AssetManager;
+
+class Attack : public AssetInfo {
+public:
+ string special;
+ string unblocked_anim_path;
+ string blocked_anim_path;
+ string throw_anim_path;
+ string thrown_anim_path;
+ string thrown_counter_anim_path;
+ AttackHeight height;
+ AttackDirection direction;
+ bool swap_stance;
+ bool swap_stance_blocked;
+ bool unblockable;
+ bool flesh_unblockable;
+ bool mobile;
+ bool as_layer;
+ string alternate;
+ string reaction;
+ vec3 impact_dir;
+ float sharp_damage[2];
+ float block_damage[2];
+ float damage[2];
+ float force[2];
+ string materialevent;
+ vec3 cut_plane;
+ bool has_cut_plane;
+ CutPlaneType cut_plane_type;
+ bool has_stab_dir;
+ vec3 stab_dir;
+ CutPlaneType stab_type;
+
+ Attack(AssetManager* owner, uint32_t asset_id);
+ void ReturnPaths(PathSet &path_set);
+ int Load(const string &path, uint32_t load_flags);
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+
+ void Unload();
+ void Reload();
+ virtual void ReportLoad();
+ void clear();
+ static AssetType GetType() { return ATTACK_ASSET; }
+ static const char* GetTypeName() { return "ATTACK_ASSET"; }
+ static bool AssetWarning() { return true; }
+
+ virtual AssetLoaderBase* NewLoader();
+};
+
+typedef AssetRef<Attack> AttackRef;
diff --git a/Source/Asset/Asset/averagecolorasset.cpp b/Source/Asset/Asset/averagecolorasset.cpp
new file mode 100644
index 00000000..374b0cce
--- /dev/null
+++ b/Source/Asset/Asset/averagecolorasset.cpp
@@ -0,0 +1,124 @@
+//-----------------------------------------------------------------------------
+// Name: averagecolorasset.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "averagecolorasset.h"
+
+#include <Internal/integer.h>
+#include <Internal/cachefile.h>
+#include <Internal/error.h>
+
+#include <Asset/AssetLoader/fallbackassetloader.h>
+#include <Compat/fileio.h>
+#include <Graphics/bytecolor.h>
+
+#include <string>
+#include <cstdio>
+#include <cstdlib>
+
+using std::string;
+
+AverageColor::AverageColor( AssetManager* owner, uint32_t asset_id ) : Asset( owner, asset_id )
+{
+
+}
+
+int AverageColor::Load( const string& rel_path, uint32_t load_flags ) {
+ string load_path;
+ string suffix = "_avg_color_cache";
+ uint16_t checksum;
+ ModID cached_modsource;
+ if(CacheFile::CheckForCache(path_, suffix, &load_path,&cached_modsource,&checksum)){
+ if(ReadCacheFile(load_path, checksum)){
+ modsource_ = cached_modsource;
+ return kLoadOk;
+ }
+ }
+
+ char abs_path[kPathSize];
+ ModID modsource;
+ if(FindImagePath(rel_path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths, true, NULL, true, &modsource) == -1){
+ //FatalError("Error", "Could not get average color of %s", rel_path.c_str());
+ return kLoadErrorMissingFile;
+ }
+ modsource_ = modsource;
+
+ vec4 byte_color = GetAverageColor(abs_path);
+ color_ = vec4(byte_color[2]/255.0f,
+ byte_color[1]/255.0f,
+ byte_color[0]/255.0f,
+ byte_color[3]/255.0f);
+
+ //TODO: this checksum is never set, calculate it!
+ WriteCacheFile(GetWritePath(modsource)+rel_path + suffix, checksum);
+
+ return kLoadOk;
+}
+
+const char* AverageColor::GetLoadErrorString() {
+ return "";
+}
+
+void AverageColor::Unload() {
+
+}
+
+void AverageColor::Reload() {
+
+}
+
+void AverageColor::ReportLoad() {
+
+}
+
+bool AverageColor::ReadCacheFile(const string& path, uint16_t checksum) {
+ FILE* file = my_fopen(path.c_str(),"rb");
+ if(!file){
+ return false;
+ }
+ CacheFile::ScopedFileCloser file_closer(file);
+ unsigned char version;
+ fread(&version, sizeof(version), 1, file);
+ if(version != kAverageColorCacheVersion){
+ return false;
+ }
+ uint16_t file_checksum;
+ fread(&file_checksum, sizeof(file_checksum), 1, file);
+ if(checksum != 0 && checksum != file_checksum){
+ return false;
+ }
+ fread(&color_, sizeof(color_), 1, file);
+ return true;
+}
+
+void AverageColor::WriteCacheFile(const string& path, uint16_t checksum) {
+ FILE* file = my_fopen(path.c_str(),"wb");
+ CacheFile::ScopedFileCloser file_closer(file);
+ unsigned char version = kAverageColorCacheVersion;
+ fwrite(&version, sizeof(version), 1, file);
+ fwrite(&checksum, sizeof(checksum), 1, file);
+ fwrite(&color_, sizeof(color_), 1, file);
+}
+
+AssetLoaderBase* AverageColor::NewLoader() {
+ return new FallbackAssetLoader<AverageColor>();
+}
diff --git a/Source/Asset/Asset/averagecolorasset.h b/Source/Asset/Asset/averagecolorasset.h
new file mode 100644
index 00000000..bbbb2a42
--- /dev/null
+++ b/Source/Asset/Asset/averagecolorasset.h
@@ -0,0 +1,66 @@
+//-----------------------------------------------------------------------------
+// Name: averagecolorasset.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+#include <Math/vec4.h>
+
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+
+#include <Internal/modid.h>
+
+#include <string>
+
+using std::string;
+
+class AverageColor : public Asset {
+ public:
+ AverageColor(AssetManager* owner, uint32_t asset_id);
+ static AssetType GetType() { return AVERAGE_COLOR_ASSET; }
+ static const char* GetTypeName() { return "AVERAGE_COLOR_ASSET"; }
+ static bool AssetWarning() { return true; }
+
+ int Load( const string &path, uint32_t load_flags );
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+ void Unload();
+ void Reload();
+ virtual void ReportLoad();
+
+ inline const vec4& color() const {return color_;}
+
+ virtual AssetLoaderBase* NewLoader();
+
+ ModID modsource_;
+ private:
+ static const int kAverageColorCacheVersion = 2;
+ bool ReadCacheFile(const string& path, uint16_t checksum);
+ void WriteCacheFile(const string& path, uint16_t checksum);
+
+
+ vec4 color_;
+};
+
+typedef AssetRef<AverageColor> AverageColorRef;
diff --git a/Source/Asset/Asset/character.cpp b/Source/Asset/Asset/character.cpp
new file mode 100644
index 00000000..a6d84e9d
--- /dev/null
+++ b/Source/Asset/Asset/character.cpp
@@ -0,0 +1,330 @@
+//-----------------------------------------------------------------------------
+// Name: character.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "character.h"
+
+#include <Asset/Asset/objectfile.h>
+#include <Asset/AssetLoader/fallbackassetloader.h>
+#include <Asset/Asset/attacks.h>
+#include <Asset/Asset/animation.h>
+#include <Asset/Asset/voicefile.h>
+#include <Asset/Asset/material.h>
+
+#include <XML/xml_helper.h>
+#include <Logging/logdata.h>
+#include <Internal/error.h>
+#include <Main/engine.h>
+
+#include <tinyxml.h>
+
+#include <vector>
+#include <sstream>
+#include <string>
+#include <map>
+#include <set>
+
+using std::string;
+using std::ostringstream;
+using std::set;
+using std::vector;
+using std::map;
+using std::endl;
+
+Character::Character( AssetManager* owner, uint32_t asset_id ) : AssetInfo(owner,asset_id)
+{
+
+}
+
+void Character::Reload() {
+ Load(path_,0x0);
+}
+
+void Character::ReportLoad() {
+
+}
+
+int Character::Load( const string &path, uint32_t load_flags ) {
+ TiXmlDocument doc;
+
+ if( LoadXMLRetryable(doc, path, "Character") )
+ {
+ obj_path.clear();
+ skeleton_path.clear();
+ anim_paths.clear();
+ attack_paths.clear();
+ fur_path.clear();
+ morph_path.clear();
+ morph_info.clear();
+ team.clear();
+ voice_pitch = 1.0f;
+ model_scale = 1.0f;
+ default_scale = 1.0f;
+
+ TiXmlHandle h_doc(&doc);
+ TiXmlHandle h_root = h_doc.FirstChildElement();
+ TiXmlElement* field = h_root.ToElement()->FirstChildElement();
+ for( ; field; field = field->NextSiblingElement()) {
+ string field_str(field->Value());
+ if(field_str == "appearance"){
+ obj_path = field->Attribute("obj_path");
+ skeleton_path = field->Attribute("skeleton");
+ const char *fur_path_cstr = field->Attribute("fur");
+ if(fur_path_cstr){
+ fur_path = fur_path_cstr;
+ }
+ const char *morph_path_cstr = field->Attribute("morph");
+ if(morph_path_cstr){
+ morph_path = morph_path_cstr;
+ }
+ for(int i=0; i<4; ++i){
+ ostringstream oss;
+ oss << "channel_" << i;
+ const char *channel_cstr = field->Attribute(oss.str().c_str());
+ if(channel_cstr){
+ channels[i] = channel_cstr;
+ }
+ }
+ field->QueryFloatAttribute("model_scale", &model_scale);
+ field->QueryFloatAttribute("default_scale", &default_scale);
+ } else if(field_str == "animations"){
+ TiXmlAttribute* attrib = field->FirstAttribute();
+ while(attrib){
+ anim_paths[attrib->Name()] = attrib->Value();
+ attrib = attrib->Next();
+ }
+ } else if(field_str == "attacks"){
+ TiXmlAttribute* attrib = field->FirstAttribute();
+ while(attrib){
+ attack_paths[attrib->Name()] = attrib->Value();
+ attrib = attrib->Next();
+ }
+ } else if(field_str == "tags"){
+ TiXmlAttribute* attrib = field->FirstAttribute();
+ while(attrib){
+ tags[attrib->Name()] = attrib->Value();
+ attrib = attrib->Next();
+ }
+ } else if(field_str == "voice"){
+ voice_path = field->Attribute("path");
+ field->QueryFloatAttribute("pitch", &voice_pitch);
+ } else if(field_str == "team"){
+ team.insert(field->GetText());
+ } else if(field_str == "clothing"){
+ clothing_path = field->GetText();
+ } else if(field_str == "soundmod"){
+ sound_mod = field->GetText();
+ } else if(field_str == "morphs"){
+ MorphInfo new_morph_info;
+ TiXmlElement* morph = field->FirstChildElement();
+ while(morph){
+ const char* default_str = morph->Attribute("default");
+ if(!default_str || strcmp(morph->Attribute("default"), "true") != 0){
+ new_morph_info.label = morph->Value();
+ new_morph_info.num_steps = 1;
+ morph->QueryIntAttribute("num_steps",&new_morph_info.num_steps);
+ morph_info.push_back(new_morph_info);
+ }
+ vec3 start, end;
+ if(morph->QueryFloatAttribute("start_x",&start[0]) == TIXML_SUCCESS){
+ morph->QueryFloatAttribute("start_y",&start[1]);
+ morph->QueryFloatAttribute("start_z",&start[2]);
+ morph->QueryFloatAttribute("end_x",&end[0]);
+ morph->QueryFloatAttribute("end_y",&end[1]);
+ morph->QueryFloatAttribute("end_z",&end[2]);
+ MorphMetaInfo meta_info;
+ meta_info.label = morph->Value();
+ meta_info.start = start;
+ meta_info.end = end;
+ morph_meta.push_back(meta_info);
+ }
+ morph = morph->NextSiblingElement();
+ }
+ } else if(field_str == "visemes"){
+ TiXmlElement* viseme = field->FirstChildElement();
+ string viseme_morph;
+ while(viseme){
+ viseme_morph = viseme->GetText();
+ vis2morph[viseme->Value()] = viseme_morph;
+ viseme = viseme->NextSiblingElement();
+ }
+ }
+ }
+ }
+ else
+ {
+ return kLoadErrorMissingFile;
+ }
+ return kLoadOk;
+}
+
+const char* Character::GetLoadErrorString() {
+ return "";
+}
+
+void Character::Unload() {
+
+}
+
+const string & Character::GetAnimPath( const string &action ) {
+ return anim_paths[action];
+}
+
+const string & Character::GetTag( const string &action ) {
+ return tags[action];
+}
+
+const string & Character::GetObjPath() {
+ return obj_path;
+}
+
+const string & Character::GetClothingPath() {
+ return clothing_path;
+}
+
+const string & Character::GetSkeletonPath() {
+ return skeleton_path;
+}
+
+const string & Character::GetAttackPath( const string &action ) {
+ return attack_paths[action];
+}
+
+bool Character::IsOnTeam( const string &_team ) {
+ /*
+ LOGI << "Checking if " << _team << " is on team:" << endl;
+ for(set<string>::const_iterator iter = team.begin();
+ iter != team.end();
+ ++iter)
+ {
+ LOGI << (*iter) << endl;
+ }
+ bool found = (team.find(_team) != team.end());
+ if(found){
+ LOGI << "It is." << endl;
+ return true;
+ }
+ LOGI << "It is not." << endl;
+ return false;
+ */
+ return (team.find(_team) != team.end());
+}
+
+const set<string> & Character::GetTeamSet() {
+ return team;
+}
+
+const string& Character::GetSoundMod() {
+ return sound_mod;
+}
+
+const vector<MorphInfo>& Character::GetMorphs() {
+ return morph_info;
+}
+
+const map<string, string> & Character::GetVisemeMorphs() {
+ return vis2morph;
+}
+
+const string & Character::GetVoicePath() {
+ return voice_path;
+}
+
+const float & Character::GetVoicePitch() {
+ return voice_pitch;
+}
+
+float Character::GetHearing() {
+ return 0.4f;
+}
+
+void Character::ReturnPaths( PathSet &path_set ) {
+ path_set.insert("character "+path_);
+ //ObjectFiles::Instance()->ReturnRef(obj_path);
+ ObjectFileRef ofr = Engine::Instance()->GetAssetManager()->LoadSync<ObjectFile>(obj_path);
+ ofr->ReturnPaths(path_set);
+ path_set.insert("skeleton "+skeleton_path);
+ if(!clothing_path.empty()){
+ //Materials::Instance()->ReturnRef(clothing_path)->ReturnPaths(path_set);
+ Engine::Instance()->GetAssetManager()->LoadSync<Material>(clothing_path)->ReturnPaths(path_set);
+ }
+ for(unsigned i=0; i<morph_info.size(); ++i){
+ if(morph_info[i].num_steps == 1){
+ path_set.insert("morph "+GetMorphPath(ofr->model_name, morph_info[i].label));
+ } else {
+ for(int j=0; j<morph_info[i].num_steps; ++j){
+ ostringstream oss;
+ oss << morph_info[i].label << j;
+ path_set.insert("morph "+GetMorphPath(ofr->model_name, oss.str()));
+ }
+ }
+ }
+ for(StringMap::iterator iter = anim_paths.begin(); iter != anim_paths.end(); ++iter) {
+ ReturnAnimationAssetRef(iter->second)->ReturnPaths(path_set);
+ }
+ for(StringMap::iterator iter = attack_paths.begin(); iter != attack_paths.end(); ++iter) {
+ //Attacks::Instance()->ReturnRef(iter->second)->ReturnPaths(path_set);
+ Engine::Instance()->GetAssetManager()->LoadSync<Attack>(iter->second)->ReturnPaths(path_set);
+ }
+ if(!voice_path.empty()){
+ //VoiceFiles::Instance()->ReturnRef(voice_path)->ReturnPaths(path_set);
+ Engine::Instance()->GetAssetManager()->LoadSync<VoiceFile>(voice_path)->ReturnPaths(path_set);
+ }
+}
+
+const string & Character::GetFurPath() {
+ return fur_path;
+}
+
+const string & Character::GetPermanentMorphPath() {
+ return morph_path;
+}
+
+const string & Character::GetChannel( int which ) const {
+ if(which < 0 || which > 3){
+ FatalError("Error", "Can only check channels 0-3");
+ }
+ return channels[which];
+}
+
+float Character::GetModelScale() {
+ return model_scale;
+}
+
+float Character::GetDefaultScale() {
+ return default_scale;
+}
+
+bool Character::GetMorphMeta(const string& label, vec3 & start, vec3 & end) {
+ for(size_t i=0, len=morph_meta.size(); i<len; ++i){
+ if(morph_meta[i].label == label){
+ start = morph_meta[i].start;
+ end = morph_meta[i].end;
+ return true;
+ }
+ }
+ return false;
+}
+
+AssetLoaderBase* Character::NewLoader() {
+ return new FallbackAssetLoader<Character>();
+}
diff --git a/Source/Asset/Asset/character.h b/Source/Asset/Asset/character.h
new file mode 100644
index 00000000..cf15c46a
--- /dev/null
+++ b/Source/Asset/Asset/character.h
@@ -0,0 +1,109 @@
+//-----------------------------------------------------------------------------
+// Name: character.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assetinfobase.h>
+#include <Asset/assetbase.h>
+
+#include <Math/vec3.h>
+
+#include <set>
+#include <map>
+#include <string>
+#include <vector>
+
+using std::string;
+using std::map;
+using std::vector;
+using std::set;
+
+struct MorphInfo {
+ string label;
+ int num_steps;
+};
+
+struct MorphMetaInfo {
+ string label;
+ vec3 start, end;
+};
+
+class Character : public AssetInfo {
+ typedef map<string, string> StringMap;
+ StringMap vis2morph;
+ StringMap tags;
+ string obj_path;
+ string skeleton_path;
+ string clothing_path;
+ vector<MorphInfo> morph_info;
+ vector<MorphMetaInfo> morph_meta;
+ StringMap anim_paths;
+ StringMap attack_paths;
+ string fur_path;
+ string morph_path;
+ set<string> team;
+ string sound_mod;
+ string voice_path;
+ string channels[4];
+ float voice_pitch;
+ float default_scale;
+ float model_scale;
+
+public:
+ Character( AssetManager* asset, uint32_t asset_id );
+ static AssetType GetType() { return CHARACTER_ASSET; }
+ static const char* GetTypeName() { return "CHARACTER_ASSET"; }
+ static bool AssetWarning() { return true; }
+
+ bool IsOnTeam(const string &_team);
+ const set<string> &GetTeamSet();
+ float GetModelScale();
+ float GetDefaultScale();
+ const string &GetObjPath();
+ const string &GetVoicePath();
+ const float &GetVoicePitch();
+ const string &GetClothingPath();
+ const string &GetSkeletonPath();
+ const string &GetFurPath();
+ const string &GetPermanentMorphPath();
+ const string &GetAnimPath(const string &action);
+ const string &GetAttackPath(const string &action);
+ const map<string, string> &GetVisemeMorphs();
+ int Load(const string &path, uint32_t load_flags);
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+ void Unload();
+ void Reload();
+ virtual void ReportLoad();
+ const string& GetSoundMod();
+ const vector<MorphInfo>& GetMorphs();
+ float GetHearing();
+ void ReturnPaths(PathSet &path_set);
+ const string & GetChannel( int which ) const;
+ const string & GetTag( const string &action );
+ bool GetMorphMeta(const string& label, vec3 & start, vec3 & end);
+
+ virtual AssetLoaderBase* NewLoader();
+};
+
+typedef AssetRef<Character> CharacterRef;
diff --git a/Source/Asset/Asset/decalfile.cpp b/Source/Asset/Asset/decalfile.cpp
new file mode 100644
index 00000000..2edd6d48
--- /dev/null
+++ b/Source/Asset/Asset/decalfile.cpp
@@ -0,0 +1,117 @@
+//-----------------------------------------------------------------------------
+// Name: decalfile.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "decalfile.h"
+
+#include <Asset/Asset/character.h>
+#include <Asset/Asset/material.h>
+#include <Asset/AssetLoader/fallbackassetloader.h>
+
+#include <XML/xml_helper.h>
+#include <Internal/filesystem.h>
+#include <Graphics/shaders.h>
+#include <Scripting/scriptfile.h>
+#include <Logging/logdata.h>
+
+#include <tinyxml.h>
+
+#include <cmath>
+#include <map>
+#include <string>
+
+DecalFile::DecalFile( AssetManager* owner, uint32_t asset_id ) : AssetInfo( owner, asset_id ), sub_error(0) {
+
+}
+
+int DecalFile::Load( const std::string &path, uint32_t load_flags ) {
+ sub_error = 0;
+ TiXmlDocument doc;
+ char abs_path[kPathSize];
+ if(FindFilePath(path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths) == -1){
+ FatalError("Error", "Could not find decal file: %s", path.c_str());
+ }
+ doc = TiXmlDocument(abs_path);
+ doc.LoadFile();
+ special_type = 0;
+
+ if (!XmlHelper::getNodeValue(doc, "DecalObject/ColorMap", color_map)) {
+ sub_error = 1;
+ return kLoadErrorMissingSubFile;
+ }
+ if (!XmlHelper::getNodeValue(doc, "DecalObject/NormalMap", normal_map)) {
+ sub_error = 2;
+ return kLoadErrorMissingSubFile;
+ }
+ if (!XmlHelper::getNodeValue(doc, "DecalObject/ShaderName", shader_name)) {
+ sub_error = 3;
+ return kLoadErrorMissingSubFile;
+ }
+
+ float val;
+ if (XmlHelper::getNodeValue(doc, "DecalObject/SpecialType", val)) {
+ special_type = (int)(val+0.5f);
+ }
+ return kLoadOk;
+}
+
+const char* DecalFile::GetLoadErrorString() {
+ switch(sub_error) {
+ case 0: return "";
+ case 1: return "Color map not found";
+ case 2: return "Normal map not found";
+ case 3: return "Shader file not found";
+ default: return "Undefined error";
+ }
+}
+
+void DecalFile::Unload() {
+
+}
+
+
+void DecalFile::Reload() {
+ Load(path_, 0x0);
+}
+
+void DecalFile::ReportLoad() {
+
+}
+
+void DecalFile::ReturnPaths( PathSet& path_set ) {
+ path_set.insert("decal "+path_);
+ if(!color_map.empty()){
+ path_set.insert("texture "+color_map);
+ }
+ if(!normal_map.empty()){
+ path_set.insert("texture "+normal_map);
+ }
+ if(!shader_name.empty()){
+ path_set.insert("shader "+GetShaderPath(shader_name, Shaders::Instance()->shader_dir_path, _vertex));
+ path_set.insert("shader "+GetShaderPath(shader_name, Shaders::Instance()->shader_dir_path, _fragment));
+ }
+}
+
+AssetLoaderBase* DecalFile::NewLoader() {
+ return new FallbackAssetLoader<DecalFile>();
+}
+
diff --git a/Source/Asset/Asset/decalfile.h b/Source/Asset/Asset/decalfile.h
new file mode 100644
index 00000000..4a985f13
--- /dev/null
+++ b/Source/Asset/Asset/decalfile.h
@@ -0,0 +1,61 @@
+//-----------------------------------------------------------------------------
+// Name: decalfile.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+
+#include <Math/vec3.h>
+#include <Graphics/palette.h>
+#include <Game/detailobjectlayer.h>
+
+#include <string>
+#include <vector>
+
+class DecalFile : public AssetInfo {
+public:
+ std::string color_map;
+ std::string normal_map;
+ std::string shader_name;
+ int special_type;
+
+ DecalFile( AssetManager* owner, uint32_t asset_id );
+ static AssetType GetType() { return DECAL_FILE_ASSET; }
+ static const char* GetTypeName() { return "DECAL_FILE_ASSET"; }
+ static bool AssetWarning() { return true; }
+
+ int sub_error;
+ int Load(const std::string &path, uint32_t load_flags);
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+ void Unload();
+ void Reload();
+ virtual void ReportLoad();
+
+ void ReturnPaths(PathSet& path_set);
+
+ virtual AssetLoaderBase* NewLoader();
+};
+
+typedef AssetRef<DecalFile> DecalFileRef;
diff --git a/Source/Asset/Asset/filehash.cpp b/Source/Asset/Asset/filehash.cpp
new file mode 100644
index 00000000..38d4a237
--- /dev/null
+++ b/Source/Asset/Asset/filehash.cpp
@@ -0,0 +1,58 @@
+//-----------------------------------------------------------------------------
+// Name: filehash.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include "filehash.h"
+
+#include <Asset/AssetLoader/fallbackassetloader.h>
+
+FileHashAsset::FileHashAsset(AssetManager* owner, uint32_t asset_id) : Asset(owner, asset_id), sub_error(0) {
+
+}
+
+int FileHashAsset::Load(const std::string &path, uint32_t load_flags) {
+ Path file = FindFilePath(path, kAnyPath);
+
+ if( file.isValid() ) {
+ hash = GetFileHash(file.GetAbsPathStr().c_str());
+ return kLoadOk;
+ } else {
+ return kLoadErrorMissingFile;
+ }
+}
+
+const char* FileHashAsset::GetLoadErrorString() {
+ return "no error known";
+}
+
+void FileHashAsset::Unload() {
+
+}
+
+void FileHashAsset::Reload() {
+
+}
+
+AssetLoaderBase* FileHashAsset::NewLoader() {
+ return new FallbackAssetLoader<FileHashAsset>();
+}
diff --git a/Source/Asset/Asset/filehash.h b/Source/Asset/Asset/filehash.h
new file mode 100644
index 00000000..b968240e
--- /dev/null
+++ b/Source/Asset/Asset/filehash.h
@@ -0,0 +1,53 @@
+//-----------------------------------------------------------------------------
+// Name: filehash.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+
+#include <Utility/hash.h>
+
+#include <string>
+#include <vector>
+
+class FileHashAsset : public Asset {
+public:
+ FileHashAsset(AssetManager* owner, uint32_t asset_id);
+ static AssetType GetType() { return FILE_HASH_ASSET; }
+ static const char* GetTypeName() { return "FILE_HASH_ASSET"; }
+ static bool AssetWarning() { return false; }
+
+ MurmurHash hash;
+
+ int sub_error;
+ int Load(const std::string &path, uint32_t load_flags);
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+ void Unload();
+ void Reload();
+
+ virtual AssetLoaderBase* NewLoader();
+};
+
+typedef AssetRef<FileHashAsset> FileHashAssetRef;
diff --git a/Source/Asset/Asset/fzx_file.cpp b/Source/Asset/Asset/fzx_file.cpp
new file mode 100644
index 00000000..a3cd68c0
--- /dev/null
+++ b/Source/Asset/Asset/fzx_file.cpp
@@ -0,0 +1,180 @@
+//-----------------------------------------------------------------------------
+// Name: fzx_file.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "fzx_file.h"
+
+#include <Memory/allocation.h>
+
+#include <Asset/AssetLoader/fallbackassetloader.h>
+#include <Asset/assetloaderrors.h>
+
+#include <Internal/error.h>
+#include <Internal/filesystem.h>
+#include <Internal/error.h>
+
+#include <Compat/filepath.h>
+#include <Compat/fileio.h>
+
+#include <Math/quaternions.h>
+#include <Math/vec3.h>
+
+#include <cstdio>
+#include <cstdlib>
+
+const int FZX_VERSION = 1;
+
+FZXAsset::FZXAsset( AssetManager* owner, uint32_t asset_id ) : Asset( owner, asset_id ), sub_error(0) {
+}
+
+int FZXAsset::Load(const std::string &path, uint32_t load_flags) {
+ sub_error = 0;
+ char abs_path[kPathSize];
+ if(FindFilePath(path.c_str(), abs_path, kPathSize, kDataPaths|kModPaths) == -1) {
+ return kLoadErrorMissingFile;
+ }
+ FILE *file = my_fopen(abs_path, "rb");
+ if(file == NULL){
+ return kLoadErrorCouldNotOpen;
+ }
+
+ // obtain file size:
+ fseek (file , 0 , SEEK_END);
+ int bytes_in_file = ftell(file);
+ rewind(file);
+
+ // allocate memory to contain the whole file
+ StackMem buffer_mem(alloc.stack.Alloc(bytes_in_file));
+ unsigned char *buffer = (unsigned char*)buffer_mem.ptr();
+ if (buffer == NULL) {
+ FatalError("Error", "Failed to allocate RAM for FZX file");
+ }
+
+ // copy the file into the buffer:
+ size_t bytes_read = fread(buffer,1,bytes_in_file, file);
+ if (bytes_read != (size_t)bytes_in_file) {
+ sub_error = 1;
+ return kLoadErrorCouldNotRead;
+ }
+
+ // close file
+ fclose (file);
+
+ // Check file header
+ const unsigned char header[] = {211,'F','Z','X','\r','\n',32,'\n'};
+ const unsigned int header_len = sizeof(header);
+ if(bytes_read < header_len){
+ sub_error = 2;
+ return kLoadErrorCorruptFile;
+ }
+ for(unsigned i=0; i<header_len; ++i){
+ if(header[i] != buffer[i]){
+ sub_error = 3;
+ return kLoadErrorCorruptFile;
+ }
+ }
+ // Check version
+ int read_pos = header_len;
+ const int version = *((int*)(&buffer[read_pos]));
+ read_pos += sizeof(int);
+ if(version != FZX_VERSION){
+ sub_error = 4;
+ return kLoadErrorIncompatibleFileVersion;
+ }
+ // Check file footer
+ const unsigned char footer[] = {'F','Z','X'};
+ const int footer_len = sizeof(footer);
+ for(int i=0; i<footer_len; ++i){
+ if(footer[i] != buffer[bytes_read-footer_len+i]){
+ sub_error = 5;
+ return kLoadErrorCorruptFile;
+ }
+ }
+ // Check file size
+ const int expected_file_size = *((int*)&buffer[bytes_read-footer_len-sizeof(int)]);
+ if(expected_file_size != (int)(bytes_read-footer_len-sizeof(int))) {
+ sub_error = 6;
+ return kLoadErrorCorruptFile;
+ }
+ // Everything's good! Read the actual data
+ int num_objects = *((int*)&buffer[read_pos]);
+ objects.resize(num_objects);
+ read_pos += sizeof(int);
+ for(int i=0; i<num_objects; ++i){
+ FZXObject &object = objects[i];
+ // Read label string
+ int str_len = *((int*)&buffer[read_pos]);
+ read_pos += sizeof(int);
+ const int BUF_SIZE = 256;
+ char str_buf[BUF_SIZE];
+ if(str_len > BUF_SIZE-1){
+ sub_error = 7;
+ return kLoadErrorCorruptFile;
+ }
+ for(int j=0; j<str_len; ++j){
+ str_buf[j] = *((char*)(&buffer[read_pos+j]));
+ }
+ str_buf[str_len] = '\0';
+ object.label = str_buf;
+ read_pos += str_len;
+ for(int j=0; j<4; ++j){
+ object.rotation[j] = *((float*)(&buffer[read_pos]));
+ read_pos += sizeof(float);
+ }
+ vec3 location;
+ for(int j=0; j<3; ++j){
+ object.location[j] = *((float*)(&buffer[read_pos]));
+ read_pos += sizeof(float);
+ }
+ for(int j=0; j<3; ++j){
+ object.scale[j] = *((float*)(&buffer[read_pos]));
+ read_pos += sizeof(float);
+ }
+ }
+ return kLoadOk;
+}
+
+const char* FZXAsset::GetLoadErrorString() {
+ switch(sub_error) {
+ case 0: return "";
+ case 1: return "Failed to read FZX file into buffer";
+ case 2: return "FZX file too small for header";
+ case 3: return "FZX header is incorrect";
+ case 4: return "FZX version incorrect";
+ case 5: return "FZX footer incorrect";
+ case 6: return "FZX file has unexpected file size";
+ case 7: return "String is too long in FZX file";
+ default: return "Undefined error";
+ }
+}
+
+void FZXAsset::Unload() {
+
+}
+
+void FZXAsset::Reload() {
+
+}
+
+AssetLoaderBase* FZXAsset::NewLoader() {
+ return new FallbackAssetLoader<FZXAsset>();
+}
diff --git a/Source/Asset/Asset/fzx_file.h b/Source/Asset/Asset/fzx_file.h
new file mode 100644
index 00000000..bc008534
--- /dev/null
+++ b/Source/Asset/Asset/fzx_file.h
@@ -0,0 +1,62 @@
+//-----------------------------------------------------------------------------
+// Name: fzx_file.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+
+#include <Math/vec3.h>
+#include <Math/quaternions.h>
+
+#include <string>
+#include <vector>
+
+struct FZXObject {
+ std::string label;
+ vec3 location;
+ vec3 scale;
+ quaternion rotation;
+};
+
+class FZXAsset : public Asset {
+public:
+ FZXAsset(AssetManager* owner, uint32_t asset_id);
+ static AssetType GetType() { return FZX_ASSET; }
+ static const char* GetTypeName() { return "FZX_ASSET"; }
+ static bool AssetWarning() { return true; }
+
+ std::vector<FZXObject> objects;
+
+ int sub_error;
+ int Load(const std::string &path, uint32_t load_flags);
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+ void Unload();
+ void Reload();
+
+ virtual AssetLoaderBase* NewLoader();
+};
+
+typedef AssetRef<FZXAsset> FZXAssetRef;
+
diff --git a/Source/Asset/Asset/hotspotfile.cpp b/Source/Asset/Asset/hotspotfile.cpp
new file mode 100644
index 00000000..636b6dae
--- /dev/null
+++ b/Source/Asset/Asset/hotspotfile.cpp
@@ -0,0 +1,96 @@
+//-----------------------------------------------------------------------------
+// Name: hotspotfile.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "hotspotfile.h"
+
+#include <Asset/Asset/character.h>
+#include <Asset/Asset/material.h>
+
+#include <XML/xml_helper.h>
+#include <Internal/filesystem.h>
+#include <Graphics/shaders.h>
+#include <Scripting/scriptfile.h>
+#include <Logging/logdata.h>
+#include <Asset/AssetLoader/fallbackassetloader.h>
+
+#include <tinyxml.h>
+
+#include <map>
+#include <string>
+#include <cmath>
+
+HotspotFile::HotspotFile( AssetManager* owner, uint32_t asset_id ) : AssetInfo( owner, asset_id ), sub_error(0) {
+
+}
+
+void HotspotFile::ReturnPaths( PathSet& path_set ) {
+ path_set.insert("hotspot "+path_);
+ path_set.insert("texture "+billboard_color_map);
+ path_set.insert("script "+script);
+ // Commented out so that 'loadlevel' works properly
+ //ScriptFileUtil::ReturnPaths("Data/Scripts/"+script, path_set);
+}
+
+int HotspotFile::Load( const std::string &rel_path, uint32_t load_flags ) {
+ sub_error = 0;
+ char abs_path[kPathSize];
+ if (FindFilePath(rel_path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths | kAbsPath) == -1) {
+ return kLoadErrorMissingFile;
+ }
+ TiXmlDocument doc(abs_path);
+ doc.LoadFile();
+
+ if (!XmlHelper::getNodeValue(doc, "Hotspot/BillboardColorMap", billboard_color_map)) {
+ sub_error = 1;
+ return kLoadErrorMissingSubFile;
+ } if (!XmlHelper::getNodeValue(doc, "Hotspot/Script", script)) {
+ sub_error = 2;
+ return kLoadErrorMissingSubFile;
+ }
+ return kLoadOk;
+}
+
+const char* HotspotFile::GetLoadErrorString() {
+ switch(sub_error) {
+ case 0: return "";
+ case 1: return "BillboardColorMap not found. Aborting hotspot load.";
+ case 2: return "Script not found. Aborting hotspot load.";
+ default: return "Undefined error";
+ }
+}
+
+void HotspotFile::Unload() {
+
+}
+
+void HotspotFile::Reload() {
+
+}
+
+void HotspotFile::ReportLoad() {
+
+}
+
+AssetLoaderBase* HotspotFile::NewLoader() {
+ return new FallbackAssetLoader<HotspotFile>();
+}
diff --git a/Source/Asset/Asset/hotspotfile.h b/Source/Asset/Asset/hotspotfile.h
new file mode 100644
index 00000000..b5c8dafb
--- /dev/null
+++ b/Source/Asset/Asset/hotspotfile.h
@@ -0,0 +1,58 @@
+//-----------------------------------------------------------------------------
+// Name: hotspotfile.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+
+#include <Math/vec3.h>
+#include <Graphics/palette.h>
+
+#include <string>
+#include <vector>
+
+class HotspotFile : public AssetInfo {
+public:
+ HotspotFile( AssetManager* owner, uint32_t asset_id );
+ static AssetType GetType() { return HOTSPOT_FILE_ASSET; }
+ static const char* GetTypeName() { return "HOTSPOT_FILE_ASSET"; }
+ static bool AssetWarning() { return true; }
+
+ std::string billboard_color_map;
+ std::string script;
+
+ int sub_error;
+ int Load(const std::string &path, uint32_t load_flags);
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+ void Unload();
+ void Reload();
+ virtual void ReportLoad();
+
+ void ReturnPaths(PathSet& path_set);
+
+ virtual AssetLoaderBase* NewLoader();
+};
+
+typedef AssetRef<HotspotFile> HotspotFileRef;
diff --git a/Source/Asset/Asset/image_sampler.cpp b/Source/Asset/Asset/image_sampler.cpp
new file mode 100644
index 00000000..a1f5d27f
--- /dev/null
+++ b/Source/Asset/Asset/image_sampler.cpp
@@ -0,0 +1,221 @@
+//-----------------------------------------------------------------------------
+// Name: image_sampler.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "image_sampler.h"
+
+#include <Asset/AssetLoader/fallbackassetloader.h>
+#include <Asset/assetloaderrors.h>
+
+#include <Internal/cachefile.h>
+#include <Internal/common.h>
+#include <Internal/filesystem.h>
+#include <Internal/error.h>
+
+#include <Compat/fileio.h>
+
+#include <cerrno>
+#include <sstream>
+#include <cstring>
+#include <algorithm>
+
+using std::max;
+using std::min;
+
+namespace {
+ const char* suffix = "_image_sample_cache";
+}
+
+ImageSampler::ImageSampler( AssetManager* owner, uint32_t asset_id ) : Asset( owner, asset_id )
+{
+
+}
+
+vec4 ImageSampler::GetInterpolatedColorUV(float x, float y) const {
+ x = max(0.01f,min(0.99f, x));
+ y = max(0.01f,min(0.99f, y));
+ return GetInterpolatedColor(x*width_+0.5f, y*height_+1.0f);
+}
+
+vec4 ImageSampler::GetInterpolatedColor(float x, float y) const {
+ byte4 top_left = GetPixel((int)x, (int)y);
+ byte4 top_right = GetPixel((int)(x+1), (int)y);
+ byte4 bottom_left = GetPixel((int)x, (int)(y+1));
+ byte4 bottom_right = GetPixel((int)(x+1), (int)(y+1));;
+
+ float x_weight = x - (int)x;
+ float y_weight = y - (int)y;
+
+ vec3 left;
+ left[0] = (uint8_t)(top_left[0] * (1-y_weight) + bottom_left[0] * (y_weight));
+ left[1] = (uint8_t)(top_left[1] * (1-y_weight) + bottom_left[1] * (y_weight));
+ left[2] = (uint8_t)(top_left[2] * (1-y_weight) + bottom_left[2] * (y_weight));
+ vec3 right;
+ right[0] = (uint8_t)(top_right[0] * (1-y_weight) + bottom_right[0] * (y_weight));
+ right[1] = (uint8_t)(top_right[1] * (1-y_weight) + bottom_right[1] * (y_weight));
+ right[2] = (uint8_t)(top_right[2] * (1-y_weight) + bottom_right[2] * (y_weight));
+
+ vec3 value(0.0f);
+ value.r() = ((uint8_t)(left[0] * (1-x_weight) + right[0] * (x_weight)))/255.0f;
+ value.g() = ((uint8_t)(left[1] * (1-x_weight) + right[1] * (x_weight)))/255.0f;
+ value.b() = ((uint8_t)(left[2] * (1-x_weight) + right[2] * (x_weight)))/255.0f;
+
+ return value;
+}
+
+ImageSampler::byte4 ImageSampler::GetPixel( int x, int y ) const {
+ x = max(0,min(width_-1,x));
+ y = max(0,min(height_-1,y));
+ return pixels_[x*height_ + y];
+}
+
+void ImageSampler::LoadDataFromFIBitmap( FIBITMAP* image ) {
+ width_ = getWidth(image);
+ height_ = getHeight(image);
+ pixels_.resize(width_*height_);
+ for(int i=0; i<width_; ++i){
+ for(int j=0; j<height_; ++j){
+ FIquad val;
+ getPixelColor(image, i, j, &val);
+ pixels_[i*height_+j][0] = val.rgbRed;
+ pixels_[i*height_+j][1] = val.rgbGreen;
+ pixels_[i*height_+j][2] = val.rgbBlue;
+ pixels_[i*height_+j][3] = val.rgbReserved;
+ }
+ }
+}
+
+int ImageSampler::Load( const std::string& path, uint32_t load_flags ) {
+ std::string load_path;
+ ModID cache_modsource;
+ bool loaded_cache = false;
+
+ if(CacheFile::CheckForCache(path_, suffix, &load_path, &cache_modsource, &checksum_)) {
+ if(ReadCacheFile(load_path, checksum_)) {
+ loaded_cache = true;
+ LOGI << "Loaded cache file " << load_path << std::endl;
+ modsource_ = cache_modsource;
+ }
+ }
+
+ if( loaded_cache == false ) {
+ char abs_path[kPathSize];
+ ModID modsource;
+ if(FindImagePath(path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths | kWriteDir | kModWriteDirs, true, NULL, false, &modsource) == -1){
+ return kLoadErrorMissingFile;
+ }
+ LOGI << "Loading " << abs_path << std::endl;
+ /*
+ if(FindFilePath(path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths | kWriteDir) == -1){
+ FatalError("Error", "Could not find image sampler file \"%s\"", path_.c_str());
+ }
+ */
+ FIBITMAP* fibitmap = GenericLoader(abs_path);
+
+ if(!fibitmap){
+ return kLoadErrorGeneralFileError;
+ }
+
+ LoadDataFromFIBitmap(fibitmap);
+ UnloadBitmap(fibitmap);
+ char write_path[kPathSize];
+ FormatString(write_path, kPathSize, "%s%s%s", GetWritePath(modsource).c_str(), path.c_str(), suffix);
+ modsource_ = modsource;
+ WriteCacheFile(write_path);
+ }
+ return kLoadOk;
+}
+
+const char* ImageSampler::GetLoadErrorString() {
+ switch(sub_error){
+ case 0: return "";
+ default: return "Unknown error";
+ }
+}
+
+void ImageSampler::Unload() {
+
+}
+
+void ImageSampler::Reload() {
+
+}
+
+void ImageSampler::ReportLoad() {
+
+}
+
+bool ImageSampler::GetCachePath( std::string* dst ) {
+ if(!CacheFile::CheckForCache(path_, suffix, dst, &checksum_)){
+ DisplayError("Error",("Could not find cache file for "+path_).c_str());
+ return false;
+ }
+ return true;
+}
+
+
+bool ImageSampler::ReadCacheFile(const std::string& path, uint16_t checksum) {
+ FILE* file = my_fopen(path.c_str(),"rb");
+ if(!file){
+ return false;
+ }
+ CacheFile::ScopedFileCloser file_closer(file);
+ unsigned char version;
+ fread(&version, sizeof(version), 1, file);
+ if(version != kImageSamplerCacheVersion){
+ return false;
+ }
+ uint16_t file_checksum;
+ fread(&file_checksum, sizeof(file_checksum), 1, file);
+ if(checksum != 0 && checksum != file_checksum){
+ return false;
+ }
+
+ fread(&width_, sizeof(width_), 1, file);
+ fread(&height_, sizeof(height_), 1, file);
+ if( width_ <= 0 || height_ <= 0 ) {
+ return false;
+ }
+
+ pixels_.resize(width_ * height_);
+ fread(&pixels_[0], sizeof(pixels_[0])*width_*height_, 1, file);
+ return true;
+}
+
+void ImageSampler::WriteCacheFile(const std::string& path) {
+ FILE* file = my_fopen(path.c_str(),"wb");
+ if(!file){
+ FatalError("Error", "Could not write file \"%s\" because of error \"%s\"", path.c_str(), strerror(errno));
+ }
+ CacheFile::ScopedFileCloser file_closer(file);
+ unsigned char version = kImageSamplerCacheVersion;
+ fwrite(&version, sizeof(version), 1, file);
+ fwrite(&checksum_, sizeof(checksum_), 1, file);
+ fwrite(&width_, sizeof(width_), 1, file);
+ fwrite(&height_, sizeof(height_), 1, file);
+ fwrite(&pixels_[0], sizeof(pixels_[0]), width_*height_, file);
+}
+
+AssetLoaderBase* ImageSampler::NewLoader() {
+ return new FallbackAssetLoader<ImageSampler>();
+}
+
diff --git a/Source/Asset/Asset/image_sampler.h b/Source/Asset/Asset/image_sampler.h
new file mode 100644
index 00000000..3b43d4f1
--- /dev/null
+++ b/Source/Asset/Asset/image_sampler.h
@@ -0,0 +1,80 @@
+//-----------------------------------------------------------------------------
+// Name: image_sampler.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/integer.h>
+#include <Internal/modid.h>
+
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+#include <Asset/assets.h>
+
+#include <Math/vec4.h>
+#include <Images/freeimage_wrapper.h>
+
+#include <vector>
+
+class ImageSampler : public Asset {
+ struct byte4 {
+ unsigned char entries[4];
+ inline unsigned char& operator[](int which){return entries[which];}
+ };
+public:
+ ImageSampler( AssetManager* owner, uint32_t asset_id );
+
+ static AssetType GetType() { return IMAGE_SAMPLER_ASSET; }
+ static const char* GetTypeName() { return "IMAGE_SAMPLER_ASSET"; }
+ static bool AssetWarning() { return true; }
+
+ int sub_error;
+ int Load(const std::string& path, uint32_t load_flags);
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+ void Unload();
+ void Reload();
+ virtual void ReportLoad();
+
+ vec4 GetInterpolatedColorUV(float x, float y) const;
+ bool GetCachePath(std::string* dst);
+
+ virtual AssetLoaderBase* NewLoader();
+
+ ModID modsource_;
+
+private:
+ static const int kImageSamplerCacheVersion = 2;
+
+ byte4 GetPixel( int x, int y ) const;
+ void LoadDataFromFIBitmap(FIBITMAP* image);
+ vec4 GetInterpolatedColor(float x, float y) const;
+ bool ReadCacheFile(const std::string& path, uint16_t checksum);
+ void WriteCacheFile(const std::string& path);
+
+ std::vector<byte4> pixels_;
+ int width_, height_;
+ uint16_t checksum_;
+
+};
+
+typedef AssetRef<ImageSampler> ImageSamplerRef;
diff --git a/Source/Asset/Asset/item.cpp b/Source/Asset/Asset/item.cpp
new file mode 100644
index 00000000..c66a8892
--- /dev/null
+++ b/Source/Asset/Asset/item.cpp
@@ -0,0 +1,451 @@
+//-----------------------------------------------------------------------------
+// Name: item.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "item.h"
+
+#include <Asset/Asset/animation.h>
+#include <Asset/Asset/attacks.h>
+#include <Asset/Asset/reactions.h>
+#include <Asset/Asset/objectfile.h>
+#include <Asset/AssetLoader/fallbackassetloader.h>
+
+#include <XML/xml_helper.h>
+#include <Graphics/animationflags.h>
+#include <Logging/logdata.h>
+#include <Main/engine.h>
+
+#include <tinyxml.h>
+
+#include <algorithm>
+
+Item::Item( AssetManager* owner, uint32_t asset_id ) : AssetInfo( owner, asset_id ), sub_error(0) {
+
+}
+
+void Item::Reload() {
+ Load(path_,0x0);
+}
+
+int Item::Load( const std::string &path, uint32_t load_flags ) {
+ TiXmlDocument doc;
+
+ if( LoadXMLRetryable(doc, path, "Item") ) {
+ obj_path.clear();
+ type = _item_no_type;
+ mass = 1.0f;
+ range_extender = 0.0f;
+ range_multiplier = 1.0f;
+ points.clear();
+ hands = 1;
+ sound_modifier_.clear();
+
+ TiXmlHandle h_doc(&doc);
+ TiXmlHandle h_root = h_doc.FirstChildElement();
+ TiXmlElement* field = h_root.ToElement()->FirstChildElement();
+ for( ; field; field = field->NextSiblingElement()) {
+ std::string field_str(field->Value());
+ switch(field_str[0]){
+ case 'a':
+ switch(field_str[1]){
+ case 'n':
+ if(field_str == "anim_override_flags"){
+ TiXmlAttribute* attrib = field->FirstAttribute();
+ while(attrib){
+ std::string val = attrib->Value();
+ char flags = 0;
+ if(val == "mirror"){
+ flags = _ANM_MIRRORED;
+ }
+ anim_override_flags[attrib->Name()] = flags;
+ attrib = attrib->Next();
+ }
+ } else if(field_str == "anim_blend"){
+ TiXmlAttribute* attrib = field->FirstAttribute();
+ while(attrib){
+ anim_blend[attrib->Name()] = attrib->Value();
+ attrib = attrib->Next();
+ }
+ } else if(field_str == "anim_override"){
+ TiXmlAttribute* attrib = field->FirstAttribute();
+ while(attrib){
+ anim_overrides[attrib->Name()] = attrib->Value();
+ attrib = attrib->Next();
+ }
+ }
+ break;
+ case 'p':
+ if(field_str == "appearance"){
+ obj_path = field->Attribute("obj_path");
+ }
+ break;
+ case 't':
+ if(field_str == "attack_override"){
+ TiXmlAttribute* attrib = field->FirstAttribute();
+ while(attrib){
+ attack_overrides[attrib->Name()] = attrib->Value();
+ attrib = attrib->Next();
+ }
+ } else if(field_str == "attachments"){
+ TiXmlElement* attachment_el = field->FirstChildElement();
+ while(attachment_el){
+ attachments.push_back(attachment_el->GetText());
+ attachment_el = attachment_el->NextSiblingElement();
+ }
+ }
+ break;
+ }
+ break;
+ case 'g':
+ if(field_str == "grip"){
+ attachment[_at_grip].ik_label = field->Attribute("ik_attach");
+ attachment[_at_grip].anim = field->Attribute("anim");
+ attachment[_at_grip].exists = true;
+ const char *str = field->Attribute("anim_base");
+ if(str){
+ attachment[_at_grip].anim_base = str;
+ }
+ field->QueryIntAttribute("hands", &hands);
+ }
+ break;
+ case 'l':
+ if(field_str == "lines"){
+ TiXmlElement* line_field = field->FirstChildElement();
+ for( ; line_field; line_field = line_field->NextSiblingElement()) {
+ std::string material(line_field->Value());
+ std::string start(line_field->Attribute("start"));
+ std::string end(line_field->Attribute("end"));
+ lines.resize(lines.size()+1);
+ WeaponLine &line = lines.back();
+ line.material = material;
+ line.start = start;
+ line.end = end;
+ }
+ } else if(field_str == "label"){
+ const char* label_cstr = field->GetText();
+ if(label_cstr){
+ label = field->GetText();
+ }
+ }
+ break;
+ case 'p':
+ if(field_str == "points"){
+ TiXmlElement* point_field = field->FirstChildElement();
+ for( ; point_field; point_field = point_field->NextSiblingElement()) {
+ std::string point_name(point_field->Value());
+ vec3 point;
+ point_field->QueryFloatAttribute("x", &point[0]);
+ point_field->QueryFloatAttribute("y", &point[1]);
+ point_field->QueryFloatAttribute("z", &point[2]);
+ points.insert(std::pair<std::string, vec3>(point_name, point));
+ }
+ } else if(field_str == "physics"){
+ const char *sound_modifier_cstring = field->Attribute("sound_modifier");
+ if(sound_modifier_cstring){
+ sound_modifier_ = sound_modifier_cstring;
+ }
+ std::string mass_str = field->Attribute("mass");
+ std::transform(mass_str.begin(),
+ mass_str.end(),
+ mass_str.begin(),
+ tolower);
+ size_t first_space = mass_str.find(' ');
+ std::string num_str;
+ if(first_space != std::string::npos){
+ num_str = mass_str.substr(0,first_space);
+ } else {
+ num_str = mass_str;
+ }
+ std::string type_str;
+ float convert_mult = 1.0f;
+ if(first_space != std::string::npos){
+ type_str = mass_str.substr(first_space+1);
+ if(type_str[0] == 'l'){
+ const float _lb_to_kg = 0.454f;
+ convert_mult = _lb_to_kg;
+ }
+ }
+ mass = (float)atof(num_str.c_str()) * convert_mult;
+ }
+ break;
+ case 'r':
+ if(field_str == "range"){
+ field->QueryFloatAttribute("extend", &range_extender);
+ field->QueryFloatAttribute("multiply", &range_multiplier);
+ } else if(field_str == "reaction_override"){
+ TiXmlElement* child = field->FirstChildElement();
+ for( ; child; child = child->NextSiblingElement()) {
+ TiXmlAttribute* attrib = child->FirstAttribute();
+ std::string old_path, new_path;
+ while(attrib){
+ if(strcmp(attrib->Name(), "old") == 0){
+ old_path = attrib->Value();
+ } else if(strcmp(attrib->Name(), "new") == 0){
+ new_path = attrib->Value();
+ }
+ attrib = attrib->Next();
+ }
+ reaction_overrides[old_path] = new_path;
+ }
+ }
+ break;
+ case 's':
+ if(field_str == "sheathe"){
+ attachment[_at_sheathe].ik_label = field->Attribute("ik_attach");
+ attachment[_at_sheathe].anim = field->Attribute("anim");
+ const char *str2 = field->Attribute("contains");
+ if(str2){
+ contains = str2;
+ }
+ attachment[_at_sheathe].exists = true;
+ const char *str = field->Attribute("anim_base");
+ if(str){
+ attachment[_at_sheathe].anim_base = str;
+ }
+ }
+ break;
+ case 't':
+ if(field_str == "type"){
+ if(strcmp(field->GetText(),"weapon")==0){
+ type = _weapon;
+ } else if(strcmp(field->GetText(),"misc")==0){
+ type = _misc;
+ } else if(strcmp(field->GetText(),"collectable")==0){
+ type = _collectable;
+ }
+ }
+ break;
+ }
+ }
+ return kLoadOk;
+ } else {
+ return kLoadErrorMissingFile;
+ }
+}
+
+const char* Item::GetLoadErrorString() {
+ switch(sub_error) {
+ case 0: return "";
+ default: return "Unknown error";
+ }
+}
+
+void Item::Unload() {
+
+}
+
+void Item::ReportLoad()
+{
+
+}
+
+const std::string & Item::GetObjPath() {
+ return obj_path;
+}
+
+ItemType Item::GetItemType() {
+ return type;
+}
+
+int Item::GetNumHands() {
+ return hands;
+}
+
+bool Item::HasAttachment(AttachmentType type)
+{
+ return attachment[type].exists;
+}
+
+const std::string & Item::GetIKAttach(AttachmentType type) const
+{
+ return attachment[type].ik_label;
+}
+
+const std::string & Item::GetContains() const
+{
+ return contains;
+}
+
+const std::string & Item::GetAttachAnimPath(AttachmentType type)
+{
+ return attachment[type].anim;
+}
+
+float Item::GetMass()
+{
+ return mass;
+}
+
+const std::string & Item::GetAnimOverride( const std::string &anim )
+{
+ return anim_overrides[anim];
+}
+
+bool Item::HasAnimOverride( const std::string &anim )
+{
+ return anim_overrides.find(anim) != anim_overrides.end();
+}
+
+const std::string & Item::GetAnimBlend( const std::string &anim )
+{
+ return anim_blend[anim];
+}
+
+bool Item::HasAnimBlend( const std::string &anim )
+{
+ return anim_blend.find(anim) != anim_blend.end();
+}
+
+bool Item::HasAttackOverride( const std::string &anim )
+{
+ return attack_overrides.find(anim) != attack_overrides.end();
+}
+
+bool Item::HasReactionOverride( const std::string &anim )
+{
+ return reaction_overrides.find(anim) != reaction_overrides.end();
+}
+
+const std::string & Item::GetAttackOverride( const std::string &anim )
+{
+ return attack_overrides[anim];
+}
+
+const std::string & Item::GetReactionOverride( const std::string &anim )
+{
+ return reaction_overrides[anim];
+}
+
+float Item::GetRangeExtender()
+{
+ return range_extender;
+}
+
+float Item::GetRangeMultiplier()
+{
+ return range_multiplier;
+}
+
+const std::string &Item::GetAttachAnimBasePath(AttachmentType type)
+{
+ return attachment[type].anim_base;
+}
+
+vec3 Item::GetPoint( const std::string &name )
+{
+ std::map<std::string, vec3>::const_iterator iter;
+ iter = points.find(name);
+ if(iter != points.end()){
+ return iter->second;
+ } else {
+ return vec3(0.0f,0.0f,0.0f);
+ }
+}
+
+size_t Item::GetNumLines()
+{
+ return lines.size();
+}
+
+const vec3& Item::GetLineStart( int which )
+{
+ return points[lines[which].start];
+}
+
+const vec3& Item::GetLineEnd( int which )
+{
+ return points[lines[which].end];
+}
+
+const std::string& Item::GetLineMaterial( int which )
+{
+ return lines[which].material;
+}
+
+char Item::GetAnimOverrideFlags( const std::string &anim )
+{
+ std::map<std::string, char>::const_iterator iter = anim_override_flags.find(anim);
+ if(iter != anim_override_flags.end()){
+ return anim_override_flags[anim];
+ } else {
+ return 0;
+ }
+}
+
+void Item::ReturnPaths( PathSet &path_set )
+{
+ path_set.insert("item "+path_);
+ for(int i=0; i<kMaxAttachments; ++i){
+ if(attachment[i].exists){
+ ReturnAnimationAssetRef(attachment[i].anim)->ReturnPaths(path_set);
+ if(!attachment[i].anim_base.empty()){
+ ReturnAnimationAssetRef(attachment[i].anim_base)->ReturnPaths(path_set);
+ }
+ }
+ }
+ //ObjectFiles::Instance()->ReturnRef(obj_path)->ReturnPaths(path_set);
+ Engine::Instance()->GetAssetManager()->LoadSync<ObjectFile>(obj_path)->ReturnPaths(path_set);
+ for(std::map<std::string, std::string>::iterator iter = anim_overrides.begin();
+ iter != anim_overrides.end(); ++iter)
+ {
+ ReturnAnimationAssetRef(iter->second)->ReturnPaths(path_set);
+ }
+ for(std::map<std::string, std::string>::iterator iter = anim_blend.begin();
+ iter != anim_blend.end(); ++iter)
+ {
+ ReturnAnimationAssetRef(iter->second)->ReturnPaths(path_set);
+ }
+ for(std::map<std::string, std::string>::iterator iter = attack_overrides.begin();
+ iter != attack_overrides.end(); ++iter)
+ {
+ //Attacks::Instance()->ReturnRef(iter->second)->ReturnPaths(path_set);
+ Engine::Instance()->GetAssetManager()->LoadSync<Attack>(iter->second)->ReturnPaths(path_set);
+ }
+ for(std::map<std::string, std::string>::iterator iter = reaction_overrides.begin();
+ iter != reaction_overrides.end(); ++iter)
+ {
+ //Reactions::Instance()->ReturnRef(iter->second)->ReturnPaths(path_set);
+ Engine::Instance()->GetAssetManager()->LoadSync<Reaction>(iter->second)->ReturnPaths(path_set);
+ }
+}
+
+const std::string& Item::GetSoundModifier() {
+ return sound_modifier_;
+}
+
+const std::list<std::string> & Item::GetAttachmentList() {
+ return attachments;
+}
+
+const std::string & Item::GetLabel() {
+ return label;
+}
+
+AssetLoaderBase* Item::NewLoader() {
+ return new FallbackAssetLoader<Item>();
+}
+
+ItemAttachment::ItemAttachment():
+ exists(false)
+{
+
+}
diff --git a/Source/Asset/Asset/item.h b/Source/Asset/Asset/item.h
new file mode 100644
index 00000000..4cbd7d98
--- /dev/null
+++ b/Source/Asset/Asset/item.h
@@ -0,0 +1,126 @@
+//-----------------------------------------------------------------------------
+// Name: item.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+
+#include <Math/vec3.h>
+#include <Game/attachment_type.h>
+
+#include <string>
+#include <map>
+#include <vector>
+
+enum ItemType {
+ _item_no_type,
+ _weapon,
+ _misc,
+ _collectable
+};
+
+struct WeaponLine {
+ std::string material;
+ std::string start;
+ std::string end;
+};
+
+struct ItemAttachment {
+ bool exists;
+ std::string ik_label;
+ std::string anim;
+ std::string anim_base;
+ ItemAttachment();
+};
+
+class Item : public AssetInfo {
+public:
+ Item( AssetManager* owner, uint32_t asset_id );
+ static AssetType GetType() { return ITEM_ASSET; }
+ static const char* GetTypeName() { return "ITEM_ASSET"; }
+ static bool AssetWarning() { return true; }
+
+ int sub_error;
+ int Load(const std::string &path, uint32_t load_flags);
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+ void Unload();
+ void Reload();
+ virtual void ReportLoad();
+ virtual void ReturnPaths(PathSet &path_set);
+
+ const std::string &GetIKAttach(AttachmentType type) const;
+ const std::string &GetAttachAnimPath(AttachmentType type);
+ const std::string &GetAttachAnimBasePath(AttachmentType type);
+ const std::string &GetObjPath();
+ bool HasAnimOverride( const std::string &anim );
+ const std::string &GetAnimOverride( const std::string &anim );
+ char GetAnimOverrideFlags( const std::string &anim );
+ bool HasAnimBlend( const std::string &anim );
+ const std::string &GetAnimBlend( const std::string &anim );
+ bool HasAttackOverride( const std::string &anim );
+ const std::string &GetAttackOverride( const std::string &anim );
+ vec3 GetPoint(const std::string &name);
+ size_t GetNumLines();
+ const vec3& GetLineStart(int which);
+ const vec3& GetLineEnd(int which);
+ const std::string& GetLineMaterial(int which);
+ float GetMass();
+ float GetRangeExtender();
+ ItemType GetItemType();
+ float GetRangeMultiplier();
+ bool HasAttachment(AttachmentType type);
+ int GetNumHands();
+ const std::string &GetLabel();
+ const std::string & GetContains() const;
+ const std::string& GetSoundModifier();
+ const std::list<std::string> & GetAttachmentList();
+ bool HasReactionOverride( const std::string &anim );
+ const std::string & GetReactionOverride( const std::string &anim );
+
+ virtual AssetLoaderBase* NewLoader();
+
+private:
+ static const int kMaxAttachments = 4;
+ ItemAttachment attachment[kMaxAttachments];
+ std::list<std::string> attachments;
+ std::string obj_path;
+ std::map<std::string, vec3> points;
+ std::vector<WeaponLine> lines;
+ std::map<std::string, std::string> anim_overrides;
+ std::map<std::string, std::string> reaction_overrides;
+ std::map<std::string, char> anim_override_flags;
+ std::map<std::string, std::string> anim_blend;
+ std::map<std::string, std::string> attack_overrides;
+ std::string contains;
+ std::string sound_modifier_;
+ std::string label;
+ ItemType type;
+ int hands;
+ float mass;
+ float range_extender;
+ float range_multiplier;
+};
+
+typedef AssetRef<Item> ItemRef;
diff --git a/Source/Asset/Asset/levelinfo.cpp b/Source/Asset/Asset/levelinfo.cpp
new file mode 100644
index 00000000..be452c36
--- /dev/null
+++ b/Source/Asset/Asset/levelinfo.cpp
@@ -0,0 +1,89 @@
+//-----------------------------------------------------------------------------
+// Name: levelinfo.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "levelinfo.h"
+
+#include <Asset/AssetLoader/fallbackassetloader.h>
+#include <Asset/Asset/filehash.h>
+
+#include <Main/engine.h>
+#include <Logging/logdata.h>
+
+LevelInfoAsset::LevelInfoAsset( AssetManager* owner, uint32_t asset_id ) : Asset(owner,asset_id) {
+
+}
+
+int LevelInfoAsset::Load( const std::string &_path, uint32_t load_flags ) {
+ path = _path;
+ Path p = FindFilePath(path);
+ if( p.isValid() ) {
+ std::string level_info_cache_path_string = GenerateParallelPath("Data/Levels", "Data/LevelInfo", "_linfo.xml", p );
+
+ Path level_info_cache_path = FindFilePath(level_info_cache_path_string);
+
+ if( level_info_cache_path.isValid() ) {
+ FileHashAssetRef level_hash = Engine::Instance()->GetAssetManager()->LoadSync<FileHashAsset>(path);
+ if( levelparser.Load(level_info_cache_path.GetAbsPathStr()) ) {
+ if( levelparser.hash == level_hash->hash.ToString() ) {
+ return kLoadOk;
+ }
+ }
+ }
+
+ if( levelparser.Load(p.GetAbsPathStr()) ) {
+ std::string savepath = AssemblePath(GetWritePath(p.GetModsource()),level_info_cache_path_string);
+ LOGI << "Saving a cached version of LevelInfoAsset to " << savepath << std::endl;
+ levelparser.Save(savepath);
+
+ return kLoadOk;
+ } else {
+ return kLoadErrorGeneralFileError;
+ }
+ } else {
+ return kLoadErrorMissingFile;
+ }
+}
+
+void LevelInfoAsset::ReportLoad() {
+
+}
+
+AssetLoaderBase* LevelInfoAsset::NewLoader() {
+ return new FallbackAssetLoader<LevelInfoAsset>();
+}
+
+const std::string LevelInfoAsset::GetLevelName() {
+ if( levelparser.name.empty() == true ) {
+ return SplitPathFileName(path).second;
+ } else {
+ return levelparser.name;
+ }
+}
+
+const std::string& LevelInfoAsset::GetLoadingScreenImage() {
+ return levelparser.loading_screen.image;
+}
+
+void LevelInfoAsset::Unload() {
+ levelparser.Clear();
+}
diff --git a/Source/Asset/Asset/levelinfo.h b/Source/Asset/Asset/levelinfo.h
new file mode 100644
index 00000000..8cfebcaa
--- /dev/null
+++ b/Source/Asset/Asset/levelinfo.h
@@ -0,0 +1,56 @@
+//-----------------------------------------------------------------------------
+// Name: levelinfo.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+
+#include <XML/Parsers/levelxmlparser.h>
+
+#include <vector>
+
+class LevelInfoAsset : public Asset
+{
+private:
+ LevelXMLParser levelparser;
+ std::string path;
+
+public:
+ LevelInfoAsset( AssetManager* owner, uint32_t asset_id );
+ static AssetType GetType() { return LEVEL_INFO_ASSET; }
+ static const char* GetTypeName() { return "LEVEL_INFO_ASSET"; }
+ const char* GetLoadErrorString() { return ""; }
+ const char* GetLoadErrorStringExtended() { return ""; }
+ static bool AssetWarning() { return false; }
+
+ int Load( const std::string &path, uint32_t load_flags );
+ virtual void ReportLoad();
+ virtual AssetLoaderBase* NewLoader();
+ void Unload();
+
+ const std::string GetLevelName();
+ const std::string& GetLoadingScreenImage();
+};
+
+typedef AssetRef<LevelInfoAsset> LevelInfoAssetRef;
diff --git a/Source/Asset/Asset/levelset.cpp b/Source/Asset/Asset/levelset.cpp
new file mode 100644
index 00000000..d3452b3e
--- /dev/null
+++ b/Source/Asset/Asset/levelset.cpp
@@ -0,0 +1,96 @@
+//-----------------------------------------------------------------------------
+// Name: levelset.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "levelset.h"
+
+#include <Asset/AssetLoader/fallbackassetloader.h>
+#include <XML/xml_helper.h>
+#include <Logging/logdata.h>
+
+#include <tinyxml.h>
+
+LevelSet::LevelSet( AssetManager* owner, uint32_t asset_id ) : AssetInfo( owner, asset_id ), sub_error(0) {
+}
+
+void LevelSet::clear() {
+ level_paths_.clear();
+}
+
+int LevelSet::Load( const std::string &path, uint32_t load_flags ) {
+ sub_error = 0;
+ TiXmlDocument doc;
+ if( LoadXMLRetryable(doc, path, "LevelSet") )
+ {
+ clear();
+
+ TiXmlHandle h_doc(&doc);
+ TiXmlHandle h_root = h_doc.FirstChildElement("LevelSet").FirstChildElement("Levels").FirstChildElement("Level");
+ TiXmlElement* field = h_root.ToElement();
+ for( ; field; field = field->NextSiblingElement()) {
+ std::string field_str(field->Value());
+ if(field_str == "Level"){
+ const char* path;
+ path = field->Attribute("path");
+ if(path){
+ level_paths_.push_back(path);
+ }
+ }
+ }
+ }
+ else
+ {
+ return kLoadErrorMissingFile;
+ }
+ return kLoadOk;
+}
+
+const char* LevelSet::GetLoadErrorString() {
+ switch(sub_error) {
+ case 0: return "";
+ default: return "Undefined error";
+ }
+}
+
+void LevelSet::Unload() {
+
+}
+
+void LevelSet::Reload() {
+ Load(path_,0x0);
+}
+
+void LevelSet::ReportLoad() {
+
+}
+
+void LevelSet::ReturnPaths( PathSet &path_set ) {
+ for(std::list<std::string>::iterator iter = level_paths_.begin();
+ iter != level_paths_.end(); ++iter)
+ {
+ path_set.insert("level "+(*iter));
+ }
+}
+
+AssetLoaderBase* LevelSet::NewLoader() {
+ return new FallbackAssetLoader<LevelSet>();
+}
diff --git a/Source/Asset/Asset/levelset.h b/Source/Asset/Asset/levelset.h
new file mode 100644
index 00000000..f0c7b51a
--- /dev/null
+++ b/Source/Asset/Asset/levelset.h
@@ -0,0 +1,58 @@
+//-----------------------------------------------------------------------------
+// Name: levelset.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+
+class LevelSet : public AssetInfo {
+public:
+ typedef std::list<std::string> LevelPaths;
+private:
+ LevelPaths level_paths_;
+public:
+ LevelSet(AssetManager* owner, uint32_t asset_id);
+ static AssetType GetType() { return LEVEL_SET_ASSET; }
+ static const char* GetTypeName() { return "LEVEL_SET_ASSET"; }
+ static bool AssetWarning() { return true; }
+
+ LevelPaths& level_paths(){return level_paths_;}
+
+ void ReturnPaths(PathSet &path_set);
+
+ int sub_error;
+ int Load(const std::string &path, uint32_t load_flags);
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+ void Unload();
+ void Reload();
+ virtual void ReportLoad();
+
+ void clear();
+
+ virtual AssetLoaderBase* NewLoader();
+};
+
+typedef AssetRef<LevelSet> LevelSetRef;
+
diff --git a/Source/Asset/Asset/lipsyncfile.cpp b/Source/Asset/Asset/lipsyncfile.cpp
new file mode 100644
index 00000000..5fde962f
--- /dev/null
+++ b/Source/Asset/Asset/lipsyncfile.cpp
@@ -0,0 +1,180 @@
+//-----------------------------------------------------------------------------
+// Name: lipsyncfile.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include <Asset/Asset/lipsyncfile.h>
+#include <Asset/AssetLoader/fallbackassetloader.h>
+
+#include <Internal/filesystem.h>
+#include <Internal/error.h>
+#include <Internal/timer.h>
+
+#include <Math/enginemath.h>
+#include <Compat/fileio.h>
+#include <Logging/logdata.h>
+#include <Main/engine.h>
+
+LipSyncFile::LipSyncFile(AssetManager* owner, uint32_t asset_id ) : Asset(owner,asset_id), sub_error(0) {
+
+}
+
+int LipSyncFile::Load( const std::string &path, uint32_t load_flags ) {
+ sub_error = 0;
+ char abs_path[kPathSize];
+ if(FindFilePath(path.c_str(), abs_path, kPathSize, kDataPaths|kModPaths) != 0) {
+ return kLoadErrorMissingFile;
+ }
+
+ std::ifstream file;
+ my_ifstream_open(file, abs_path);
+ if(file.fail()){
+ return kLoadErrorCouldNotOpen;
+ }
+
+ const std::map<std::string, int> &phn2vis =
+ Engine::Instance()->GetLipSyncSystem()->phn2vis[0];
+ std::map<std::string, int>::const_iterator iter;
+ LipSyncKey new_key;
+ std::string line;
+ while(!file.eof()){
+ // Extract lines with format:
+ // phn_vis start_time end_time num_keys phoneme weight...
+ file >> line;
+ if(line == "phn_vis"){
+ file >> new_key.time;
+ new_key.time -= 50.0f; //"Disney trick" to make shape before sound
+ //new_key.time *= 0.001f;
+ new_key.time /= 970.0f;
+ file.ignore(256,' ');
+ file.ignore(256,' ');
+ file >> line;
+ int num_keys = atoi(line.c_str());
+ new_key.keys.resize(num_keys);
+ for(int i = 0; i < num_keys; ++i){
+ file >> line;
+ iter = phn2vis.find(line);
+ if(iter == phn2vis.end()){
+ FatalError("Error",
+ "Could not find \"%d\" in phn2id", line.c_str());
+ }
+ new_key.keys[i].id = iter->second;
+ file >> new_key.keys[i].weight;
+ }
+ keys.push_back(new_key);
+ } else {
+ file.ignore(256, '\n'); // Skip this line
+ }
+ }
+ file.close();
+
+ time_bound[0] = keys[0].time;
+ time_bound[1] = keys[0].time;
+ for(unsigned i=1; i<keys.size(); ++i){
+ time_bound[0] = min(time_bound[0], keys[i].time);
+ time_bound[1] = max(time_bound[1], keys[i].time);
+ }
+ path_ = path;
+ return kLoadOk;
+}
+
+const char* LipSyncFile::GetLoadErrorString() {
+ return "";
+}
+
+void LipSyncFile::Unload() {
+
+}
+
+
+void LipSyncFile::Reload() {
+ Load(path_,0x0);
+}
+
+void LipSyncFile::GetWeights(float time,
+ int &marker,
+ std::vector<KeyWeight> &weights ) {
+ while(keys[marker].time < time){
+ ++marker;
+ }
+ weights = keys[marker].keys;
+}
+
+AssetLoaderBase* LipSyncFile::NewLoader() {
+ return new FallbackAssetLoader<LipSyncFile>();
+}
+
+void LipSyncFileReader::Update(float timestep) {
+ if(!valid()){
+ return;
+ }
+ time += timestep;
+ if(time > ls_ref->time_bound[1]){
+ ls_ref.clear();
+ }
+}
+
+void LipSyncFileReader::UpdateWeights() {
+ if(!valid()){
+ return;
+ }
+ ls_ref->GetWeights(time, marker, vis_weights);
+
+ std::map<std::string, float>::iterator iter;
+ for(iter = morph_weights.begin(); iter != morph_weights.end(); ++iter){
+ iter->second = 0.0f;
+ }
+
+ for(unsigned i=0; i<vis_weights.size(); ++i){
+ morph_weights[id2morph[vis_weights[i].id]] += vis_weights[i].weight;
+ }
+}
+
+bool LipSyncFileReader::valid() {
+ return ls_ref.valid();
+}
+
+void LipSyncFileReader::AttachTo( LipSyncFileRef& _ls_ref ) {
+ ls_ref = _ls_ref;
+ time = ls_ref->time_bound[0];
+ marker = 0;
+}
+
+void LipSyncFileReader::SetVisemeMorphs(const std::map<std::string, std::string> &phn2morph )
+{
+ std::string phn;
+ std::string morph;
+ std::map<std::string, int> phn2vis = Engine::Instance()->GetLipSyncSystem()->phn2vis[0];
+ std::map<std::string, std::string>::const_iterator iter;
+ for(iter = phn2morph.begin(); iter != phn2morph.end(); ++iter){
+ phn = iter->first;
+ morph = iter->second;
+ int id = phn2vis[phn];
+ id2morph[id] = morph;
+ morph_weights[morph] = 0.0f;
+ }
+}
+
+std::map<std::string, float>& LipSyncFileReader::GetMorphWeights() {
+ return morph_weights;
+}
+
diff --git a/Source/Asset/Asset/lipsyncfile.h b/Source/Asset/Asset/lipsyncfile.h
new file mode 100644
index 00000000..4145af84
--- /dev/null
+++ b/Source/Asset/Asset/lipsyncfile.h
@@ -0,0 +1,85 @@
+//-----------------------------------------------------------------------------
+// Name: lipsyncfile.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+
+#include <Math/quaternions.h>
+
+#include <vector>
+#include <string>
+#include <map>
+
+
+struct KeyWeight {
+ int id;
+ float weight;
+};
+
+struct LipSyncKey {
+ std::vector<KeyWeight> keys;
+ float time;
+};
+
+class LipSyncFile : public Asset {
+public:
+ LipSyncFile(AssetManager* owner, uint32_t asset_id);
+ static AssetType GetType() { return LIP_SYNC_FILE_ASSET; }
+ static const char* GetTypeName() { return "LIP_SYNC_FILE_ASSET"; }
+ static bool AssetWarning() { return true; }
+
+ std::vector<LipSyncKey> keys;
+ float time_bound[2];
+
+ int sub_error;
+ int Load(const std::string &path, uint32_t load_flags);
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+ void Unload();
+ void Reload();
+ void GetWeights(float time, int &marker, std::vector<KeyWeight> &weights );
+
+ virtual AssetLoaderBase* NewLoader();
+};
+
+typedef AssetRef<LipSyncFile> LipSyncFileRef;
+
+class ASContext;
+
+class LipSyncFileReader {
+ float time;
+ LipSyncFileRef ls_ref;
+ int marker;
+ std::vector<KeyWeight> vis_weights;
+ std::map<int, std::string> id2morph;
+ std::map<std::string, float> morph_weights;
+public:
+ void Update(float timestep);
+ bool valid();
+ void AttachTo(LipSyncFileRef& _ls_ref);
+ void UpdateWeights();
+ void SetVisemeMorphs( const std::map<std::string, std::string> &vis2morph );
+ std::map<std::string, float>& GetMorphWeights();
+};
diff --git a/Source/Asset/Asset/material.cpp b/Source/Asset/Asset/material.cpp
new file mode 100644
index 00000000..c9826a98
--- /dev/null
+++ b/Source/Asset/Asset/material.cpp
@@ -0,0 +1,246 @@
+//-----------------------------------------------------------------------------
+// Name: material.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "material.h"
+
+#include <Asset/AssetLoader/fallbackassetloader.h>
+#include <Asset/Asset/soundgroup.h>
+
+#include <Graphics/shaders.h>
+#include <XML/xml_helper.h>
+#include <Main/engine.h>
+
+#include <tinyxml.h>
+
+Material::Material(AssetManager* owner, uint32_t asset_id) : AssetInfo(owner, asset_id)
+ //context(NULL)
+{}
+
+Material::~Material()
+{
+// delete context;
+}
+
+
+int Material::Load( const std::string &path, uint32_t load_flags )
+{
+ //delete context;
+ //context = new ASContext();
+ //context->LoadScript(path);
+
+ //std::string xml_path = path.substr(0,path.size()-2)+"xml";
+ if(path != "Data/Materials/base.xml"){
+ //Materials::Instance()->ReturnRef("Data/Materials/base.xml");
+ base_ref = Engine::Instance()->GetAssetManager()->LoadSync<Material>("Data/Materials/base.xml");
+ }
+
+ TiXmlDocument doc;
+ LoadXMLRetryable(doc, path, "Material");
+ TiXmlHandle h_doc(&doc);
+ TiXmlHandle h_root = h_doc.FirstChildElement();
+ TiXmlElement* events = h_root.ToElement()->FirstChild("events")->ToElement();
+ while(events){
+ const char *mod_cstr = events->Attribute("mod");
+ std::string mod;
+ if(mod_cstr){
+ mod = mod_cstr;
+ }
+ TiXmlElement* event = events->FirstChildElement();
+ for( ; event; event = event->NextSiblingElement()) {
+ MaterialEvent me;
+ me.max_distance = 0.0f;
+ if(event->QueryFloatAttribute("max_distance", &me.max_distance) != TIXML_SUCCESS){
+ if(base_ref.valid()){
+ me.max_distance = base_ref->GetEvent(event->Value()).max_distance;
+ }
+ }
+ const char* sg_cstr = event->Attribute("soundgroup");
+ if(sg_cstr){
+ me.soundgroup = sg_cstr;
+ }
+ const char* c_str = event->Attribute("attached");
+ std::string attached_string;
+ if(c_str){
+ attached_string = c_str;
+ }
+ me.attached = (attached_string == "true");
+ event_map[mod][event->Value()] = me;
+ }
+ TiXmlNode *next = h_root.ToElement()->IterateChildren("events", events);
+ if(next){
+ events = next->ToElement();
+ } else {
+ events = NULL;
+ }
+ }
+
+ TiXmlHandle decals_handle = h_root.ToElement()->FirstChild("decals");
+ TiXmlElement* decals = decals_handle.ToElement();
+ if(decals){
+ TiXmlElement* decal = decals->FirstChildElement();
+ for( ; decal; decal = decal->NextSiblingElement()) {
+ MaterialDecal md;
+ md.color_path = decal->Attribute("color");
+ md.normal_path = decal->Attribute("normal");
+ md.shader = decal->Attribute("shader");
+ decal_map[decal->Value()] = md;
+ }
+ }
+
+ TiXmlHandle particles_handle = h_root.ToElement()->FirstChild("particles");
+ TiXmlElement* particles = particles_handle.ToElement();
+ if(particles){
+ TiXmlElement* particle = particles->FirstChildElement();
+ for( ; particle; particle = particle->NextSiblingElement()) {
+ MaterialParticle mp;
+ mp.particle_path = particle->Attribute("path");
+ particle_map[particle->Value()] = mp;
+ }
+ }
+
+ hardness = 1.0f;
+ friction = 1.0f;
+ sharp_penetration = 0.0f;
+ TiXmlHandle physics_handle = h_root.ToElement()->FirstChild("physics");
+ TiXmlElement* physics_el = physics_handle.ToElement();
+ if(physics_el){
+ physics_el->QueryFloatAttribute("hardness",&hardness);
+ physics_el->QueryFloatAttribute("friction",&friction);
+ physics_el->QueryFloatAttribute("sharp_penetration",&sharp_penetration);
+ //printf("Hardness of %s is %f\n",path.c_str(), hardness);
+ } else {
+ //printf("Hardness of %s not found, default is %f\n",path.c_str(), 1.0f);
+ }
+
+ return kLoadOk;
+}
+
+const char* Material::GetLoadErrorString() {
+ return "";
+}
+
+void Material::Unload() {
+
+}
+
+float Material::GetHardness() const {
+ return hardness;
+}
+
+void Material::Reload( )
+{
+ Load(path_,0x0);
+}
+
+void Material::ReportLoad()
+{
+
+}
+
+void Material::HandleEvent( const std::string &the_event, const vec3 &pos )
+{
+ // Make local copies so they can be passed to Angelscript without const
+ std::string event_string = the_event;
+ //vec3 event_pos = pos;
+
+ //Arglist args;
+ //args.AddObject(&event_string);
+ //args.AddObject(&event_pos);
+ //context->CallScriptFunction("void HandleEvent(string, vec3)", args);
+}
+
+
+const MaterialEvent& Material::GetEvent( const std::string &the_event, const std::string &mod )
+{
+ if(event_map[mod].find(the_event) != event_map[mod].end()){
+ return event_map[mod][the_event];
+ } else {
+ return event_map[""][the_event];
+ }
+}
+
+const MaterialEvent& Material::GetEvent( const std::string &the_event )
+{
+ return event_map[""][the_event];
+}
+
+const MaterialDecal& Material::GetDecal( const std::string &type )
+{
+ return decal_map[type];
+}
+
+const MaterialParticle& Material::GetParticle( const std::string &type )
+{
+ return particle_map[type];
+}
+
+float Material::GetFriction() const
+{
+ return friction;
+}
+
+float Material::GetSharpPenetration() const
+{
+ return sharp_penetration;
+}
+
+void Material::ReturnPaths( PathSet & path_set )
+{
+ path_set.insert("material "+path_);
+
+ for(std::map<std::string, std::map<std::string, MaterialEvent> >::const_iterator iter = event_map.begin();
+ iter != event_map.end(); ++iter)
+ {
+ const std::map<std::string, MaterialEvent> &inner_map = iter->second;
+ for(std::map<std::string, MaterialEvent>::const_iterator iter2 = inner_map.begin();
+ iter2 != inner_map.end(); ++iter2)
+ {
+ const MaterialEvent &me = iter2 ->second;
+ if(!me.soundgroup.empty()){
+ //SoundGroupInfoCollection::Instance()->ReturnRef(me.soundgroup)->ReturnPaths(path_set);
+ Engine::Instance()->GetAssetManager()->LoadSync<SoundGroupInfo>(me.soundgroup)->ReturnPaths(path_set);
+ }
+ }
+ }
+ for(std::map<std::string, MaterialDecal >::const_iterator iter = decal_map.begin();
+ iter != decal_map.end(); ++iter)
+ {
+ const MaterialDecal& md = iter->second;
+ path_set.insert("texture "+md.color_path);
+ path_set.insert("texture "+md.normal_path);
+ path_set.insert("shader "+GetShaderPath(md.shader, Shaders::Instance()->shader_dir_path, _vertex));
+ path_set.insert("shader "+GetShaderPath(md.shader, Shaders::Instance()->shader_dir_path, _fragment));
+ }
+ for(std::map<std::string, MaterialParticle >::const_iterator iter = particle_map.begin();
+ iter != particle_map.end(); ++iter)
+ {
+ const MaterialParticle& mp = iter->second;
+ if(!mp.particle_path.empty()){ //TODO: Why is this ever empty?
+ path_set.insert("particle "+mp.particle_path);
+ }
+ }
+}
+
+AssetLoaderBase* Material::NewLoader() {
+ return new FallbackAssetLoader<Material>();
+}
diff --git a/Source/Asset/Asset/material.h b/Source/Asset/Asset/material.h
new file mode 100644
index 00000000..3267ce2b
--- /dev/null
+++ b/Source/Asset/Asset/material.h
@@ -0,0 +1,87 @@
+//-----------------------------------------------------------------------------
+// Name: material.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assetinfobase.h>
+#include <Math/vec3.h>
+
+#include <map>
+
+struct MaterialEvent {
+ float max_distance;
+ std::string soundgroup;
+ bool attached;
+};
+
+struct MaterialDecal {
+ std::string color_path;
+ std::string normal_path;
+ std::string shader;
+};
+
+struct MaterialParticle {
+ std::string particle_path;
+};
+
+class AssetManager;
+
+class Material : public AssetInfo {
+ std::map<std::string, std::map<std::string, MaterialEvent> > event_map;
+ std::map<std::string, MaterialDecal> decal_map;
+ std::map<std::string, MaterialParticle> particle_map;
+
+ AssetRef<Material> base_ref;
+
+ float hardness;
+ float friction;
+ float sharp_penetration;
+ //ASContext *context;
+public:
+ Material(AssetManager *owner, uint32_t asset_id);
+ ~Material();
+
+ int Load(const std::string &path, uint32_t load_flags);
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+ void Unload();
+ void Reload( );
+ virtual void ReportLoad();
+ void HandleEvent(const std::string &the_event, const vec3 &pos);
+ const MaterialEvent& GetEvent( const std::string &the_event );
+ const MaterialEvent& GetEvent( const std::string &the_event, const std::string &mod );
+ const MaterialDecal& GetDecal( const std::string &type );
+ const MaterialParticle& GetParticle( const std::string &type );
+ float GetHardness() const;
+ float GetFriction() const;
+ float GetSharpPenetration() const;
+ void ReturnPaths( PathSet & path_set );
+
+ static AssetType GetType() { return MATERIAL_ASSET; }
+ static const char* GetTypeName() { return "MATERIAL_ASSET"; }
+ static bool AssetWarning() { return true; }
+
+ virtual AssetLoaderBase* NewLoader();
+};
+
+typedef AssetRef<Material> MaterialRef;
diff --git a/Source/Asset/Asset/objectfile.cpp b/Source/Asset/Asset/objectfile.cpp
new file mode 100644
index 00000000..b2a92c5f
--- /dev/null
+++ b/Source/Asset/Asset/objectfile.cpp
@@ -0,0 +1,312 @@
+//-----------------------------------------------------------------------------
+// Name: objectfile.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "objectfile.h"
+
+#include <Asset/Asset/character.h>
+#include <Asset/Asset/material.h>
+#include <Asset/AssetLoader/fallbackassetloader.h>
+
+#include <XML/xml_helper.h>
+#include <Internal/filesystem.h>
+#include <Graphics/shaders.h>
+#include <Scripting/scriptfile.h>
+#include <Logging/logdata.h>
+#include <Game/detailobjectlayer.h>
+#include <Main/engine.h>
+
+#include <tinyxml.h>
+
+#include <cmath>
+#include <map>
+#include <string>
+
+ObjectFile::ObjectFile(AssetManager* owner, uint32_t asset_id) : AssetInfo(owner,asset_id), sub_error(0) {
+
+}
+
+int ObjectFile::Load( const std::string &rel_path, uint32_t load_flags ) {
+ sub_error = 0;
+ TiXmlDocument doc;
+
+ char abs_path[kPathSize];
+ ModID modsource;
+ if(FindFilePath(rel_path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths | kAbsPath, true, NULL, &modsource) == -1){
+ return kLoadErrorMissingFile;
+ } else {
+ modsource_ = modsource;
+ doc = TiXmlDocument(abs_path);
+ }
+
+ doc.LoadFile();
+ if( doc.Error() ) {
+ return kLoadErrorCouldNotOpenXML;
+ }
+
+ if (!XmlHelper::getNodeValue(doc, "Object/Model", model_name)) {
+ sub_error = 1;
+ return kLoadErrorMissingSubFile;
+ }
+ if (!XmlHelper::getNodeValue(doc, "Object/ColorMap", color_map)) {
+ sub_error = 2;
+ return kLoadErrorMissingSubFile;
+ }
+ if (!XmlHelper::getNodeValue(doc, "Object/NormalMap", normal_map)) {
+ sub_error = 3;
+ return kLoadErrorMissingSubFile;
+ }
+ if (!XmlHelper::getNodeValue(doc, "Object/ShaderName", shader_name)) {
+ sub_error = 4;
+ return kLoadErrorMissingSubFile;
+ }
+ if (!XmlHelper::getNodeValue(doc, "Object/MaterialPath", material_path)) {
+ material_path = "Data/Materials/default.xml";
+ }
+ if (!XmlHelper::getNodeValue(doc, "Object/WeightMap", weight_map)) {
+ }
+ if (!XmlHelper::getNodeValue(doc, "Object/GroundOffset", ground_offset)) {
+ ground_offset = 0.0f;
+ }
+
+ TiXmlHandle hDoc(&doc);
+ TiXmlElement* palette_map_element = hDoc.FirstChildElement("Object").
+ FirstChildElement("PaletteMap").Element();
+ if (palette_map_element) {
+ palette_map_path = palette_map_element->GetText();
+ LoadAttribIntoString(palette_map_element, "label_red", palette_label[_pm_red]);
+ LoadAttribIntoString(palette_map_element, "label_green", palette_label[_pm_green]);
+ LoadAttribIntoString(palette_map_element, "label_blue", palette_label[_pm_blue]);
+ LoadAttribIntoString(palette_map_element, "label_alpha", palette_label[_pm_alpha]);
+ LoadAttribIntoString(palette_map_element, "label_other", palette_label[_pm_other]);
+ }
+
+ TiXmlElement* detail_map_element = hDoc.FirstChildElement("Object").
+ FirstChildElement("DetailMaps").
+ FirstChildElement("DetailMap").
+ Element();
+ while(detail_map_element) {
+ m_detail_color_maps.push_back(detail_map_element->Attribute("colorpath"));
+ m_detail_normal_maps.push_back(detail_map_element->Attribute("normalpath"));
+ const char* material = detail_map_element->Attribute("materialpath");
+ m_detail_map_scale.push_back(1.0f);
+ detail_map_element->QueryFloatAttribute("scale",&(m_detail_map_scale.back()));
+ if(material){
+ m_detail_materials.push_back(material);
+ } else {
+ m_detail_materials.push_back("Data/Materials/default.xml");
+ }
+ detail_map_element=detail_map_element->NextSiblingElement();
+ }
+
+ TiXmlElement* detail_object_element = hDoc.FirstChildElement("Object").
+ FirstChildElement("DetailObjects").
+ FirstChildElement("DetailObject").
+ Element();
+ while(detail_object_element) {
+ m_detail_object_layers.push_back(ReadDetailObjectLayerXML(detail_object_element));
+ detail_object_element=detail_object_element->NextSiblingElement();
+ }
+
+ transparent = false;
+ bush_collision = false;
+ no_collision = false;
+ terrain_fixed = false;
+ double_sided = false;
+ clamp_texture = false;
+ dynamic = false;
+
+ TiXmlElement* flags = hDoc.FirstChildElement("Object").
+ FirstChildElement("flags").Element();
+ if(flags){
+ const char* tf;
+ tf = flags->Attribute("transparent");
+ if(tf && (strcmp(tf, "true") == 0)){
+ transparent = true;
+ }
+ tf = flags->Attribute("no_collision");
+ if(tf && (strcmp(tf, "true") == 0)){
+ no_collision = true;
+ }
+ tf = flags->Attribute("clamp_texture");
+ if(tf && (strcmp(tf, "true") == 0)){
+ clamp_texture = true;
+ }
+ tf = flags->Attribute("bush_collision");
+ if(tf && (strcmp(tf, "true") == 0)){
+ bush_collision = true;
+ }
+ tf = flags->Attribute("terrain_fixed");
+ if(tf && strcmp(tf, "true") == 0){
+ terrain_fixed = true;
+ }
+ tf = flags->Attribute("dynamic");
+ if(tf && strcmp(tf, "true") == 0){
+ dynamic = true;
+ }
+ tf = flags->Attribute("double_sided");
+ if(tf && strcmp(tf, "true") == 0){
+ double_sided = true;
+ LOGI << rel_path << " is double sided!" << std::endl;
+ }
+ }
+
+ TiXmlElement* labelElem = hDoc.FirstChildElement("Object").
+ FirstChildElement("label").Element();
+ if(labelElem){
+ const char* label_cstr = labelElem->GetText();
+ if(label_cstr){
+ label = labelElem->GetText();
+ }
+ }
+
+ color_tint = vec3(1.0f);
+
+ TiXmlElement* ct = hDoc.FirstChildElement("Object").
+ FirstChildElement("ColorTint").Element();
+ if(ct){
+ ct->QueryFloatAttribute("r", &color_tint[0]);
+ ct->QueryFloatAttribute("g", &color_tint[1]);
+ ct->QueryFloatAttribute("b", &color_tint[2]);
+ }
+
+ TiXmlElement* ac = hDoc.FirstChildElement("Object").
+ FirstChildElement("avg_color").Element();
+ if(ac){
+ avg_color.resize(3);
+ avg_color_srgb.resize(3);
+ ac->QueryIntAttribute("r", &avg_color[0]);
+ ac->QueryIntAttribute("g", &avg_color[1]);
+ ac->QueryIntAttribute("b", &avg_color[2]);
+ avg_color_srgb[0] = (int)(pow(avg_color[0]/255.0f,2.2f)*255);
+ avg_color_srgb[1] = (int)(pow(avg_color[1]/255.0f,2.2f)*255);
+ avg_color_srgb[2] = (int)(pow(avg_color[2]/255.0f,2.2f)*255);
+ } else {
+ avg_color.resize(3, 200);
+ avg_color_srgb.resize(3, 150);
+ }
+
+ XmlHelper::getNodeValue(doc, "Object/TranslucencyMap", translucency_map);
+ XmlHelper::getNodeValue(doc, "Object/WindMap", wind_map);
+ XmlHelper::getNodeValue(doc, "Object/SharpnessMap", sharpness_map);
+
+ if(clamp_texture){
+ Textures::Instance()->setWrap(GL_CLAMP_TO_EDGE);
+ } else {
+ Textures::Instance()->setWrap(GL_REPEAT);
+ }
+ color_map_texture = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(color_map, PX_SRGB, 0x0);
+ if( color_map_texture.valid() == false ) {
+ return kLoadErrorMissingSubFile;
+ }
+
+ normal_map_texture = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(normal_map);
+ if( normal_map_texture.valid() == false ) {
+ return kLoadErrorMissingSubFile;
+ }
+
+ if( translucency_map.empty() == false ) {
+ translucency_map_texture = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(translucency_map);
+ if( translucency_map_texture.valid() == false ) {
+ return kLoadErrorMissingSubFile;
+ }
+ }
+
+ return kLoadOk;
+}
+
+const char* ObjectFile::GetLoadErrorString() {
+ switch(sub_error) {
+ case 0: return "";
+ case 1: return "Model name not found. Aborting object load.";
+ case 2: return "Color map not found. Aborting object load.";
+ case 3: return "Normal map not found. Aborting object load.";
+ case 4: return "Shader name not found. Aborting object load.";
+ default: return "Undefined error";
+ }
+}
+
+void ObjectFile::Unload() {
+
+}
+
+void ObjectFile::Reload() {
+ Load(path_,0x0);
+}
+
+void ObjectFile::ReportLoad() {
+
+}
+
+void ObjectFile::ReturnPaths( PathSet& path_set )
+{
+ path_set.insert("object "+path_);
+ if(!model_name.empty()){
+ path_set.insert("model "+model_name);
+ }
+ if(!color_map.empty()){
+ path_set.insert("texture "+color_map);
+ }
+ if(!normal_map.empty()){
+ path_set.insert("texture "+normal_map);
+ }
+ if(!translucency_map.empty()){
+ path_set.insert("texture "+translucency_map);
+ }
+ if(!shader_name.empty()){
+ path_set.insert("shader "+GetShaderPath(shader_name, Shaders::Instance()->shader_dir_path, _vertex));
+ path_set.insert("shader "+GetShaderPath(shader_name, Shaders::Instance()->shader_dir_path, _fragment));
+ }
+ if(!wind_map.empty()){
+ path_set.insert("image_sample "+wind_map);
+ }
+ if(!sharpness_map.empty()){
+ path_set.insert("image_sample "+sharpness_map);
+ }
+ if(!material_path.empty()){
+ //Materials::Instance()->ReturnRef(material_path)->ReturnPaths(path_set);
+ Engine::Instance()->GetAssetManager()->LoadSync<Material>(material_path)->ReturnPaths(path_set);
+ }
+ if(!weight_map.empty()){
+ path_set.insert("texture "+weight_map);
+ }
+ if(!palette_map_path.empty()){
+ path_set.insert("texture "+palette_map_path);
+ }
+ for(unsigned i=0; i<m_detail_color_maps.size(); ++i){
+ path_set.insert("texture "+m_detail_color_maps[i]);
+ }
+ for(unsigned i=0; i<m_detail_normal_maps.size(); ++i){
+ path_set.insert("texture "+m_detail_normal_maps[i]);
+ }
+ for(unsigned i=0; i<m_detail_materials.size(); ++i){
+ //Materials::Instance()->ReturnRef(m_detail_materials[i])->ReturnPaths(path_set);
+ Engine::Instance()->GetAssetManager()->LoadSync<Material>(m_detail_materials[i])->ReturnPaths(path_set);
+ }
+ for(unsigned i=0; i<m_detail_object_layers.size(); ++i){
+ m_detail_object_layers[i].ReturnPaths(path_set);
+ }
+}
+
+AssetLoaderBase* ObjectFile::NewLoader() {
+ return new FallbackAssetLoader<ObjectFile>();
+}
diff --git a/Source/Asset/Asset/objectfile.h b/Source/Asset/Asset/objectfile.h
new file mode 100644
index 00000000..94193732
--- /dev/null
+++ b/Source/Asset/Asset/objectfile.h
@@ -0,0 +1,96 @@
+//-----------------------------------------------------------------------------
+// Name: objectfile.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+#include <Asset/Asset/texture.h>
+
+#include <Math/vec3.h>
+#include <Graphics/palette.h>
+#include <Game/detailobjectlayer.h>
+
+#include <string>
+#include <vector>
+
+class AssetManager;
+
+class ObjectFile : public AssetInfo {
+public:
+ std::string model_name;
+ std::string color_map;
+ std::string normal_map;
+ std::string translucency_map;
+ std::string shader_name;
+ std::string wind_map;
+ std::string sharpness_map;
+ std::string label;
+ std::string material_path;
+ std::string weight_map;
+ std::string palette_map_path;
+ std::string palette_label[max_palette_elements];
+ std::vector<std::string> m_detail_color_maps;
+ std::vector<std::string> m_detail_normal_maps;
+ std::vector<std::string> m_detail_materials;
+ std::vector<float> m_detail_map_scale;
+ std::vector<DetailObjectLayer> m_detail_object_layers;
+ std::vector<int> avg_color;
+ std::vector<int> avg_color_srgb;
+ bool transparent;
+ bool double_sided;
+ bool terrain_fixed;
+ bool dynamic;
+ bool no_collision;
+ bool clamp_texture;
+ bool bush_collision;
+ float ground_offset;
+ vec3 color_tint;
+ ModID modsource_;
+
+ TextureAssetRef color_map_texture;
+ TextureAssetRef normal_map_texture;
+ TextureAssetRef translucency_map_texture;
+
+ ObjectFile(AssetManager* owner, uint32_t asset_id);
+
+ int sub_error;
+ int Load(const std::string &path, uint32_t load_flags);
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+ void Unload();
+
+ void Reload();
+ virtual void ReportLoad();
+
+ void ReturnPaths(PathSet& path_set);
+
+ static AssetType GetType() { return OBJECT_FILE_ASSET; }
+ static const char* GetTypeName() { return "OBJECT_FILE_ASSET"; }
+ static bool AssetWarning() { return true; }
+
+ virtual AssetLoaderBase* NewLoader();
+};
+
+typedef AssetRef<ObjectFile> ObjectFileRef;
diff --git a/Source/Asset/Asset/particletype.cpp b/Source/Asset/Asset/particletype.cpp
new file mode 100644
index 00000000..706db64b
--- /dev/null
+++ b/Source/Asset/Asset/particletype.cpp
@@ -0,0 +1,221 @@
+//-----------------------------------------------------------------------------
+// Name: particletype.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "particletype.h"
+
+#include <Graphics/particles.h>
+#include <Graphics/graphics.h>
+#include <Graphics/camera.h>
+#include <Graphics/textures.h>
+#include <Graphics/camera.h>
+#include <Graphics/shaders.h>
+
+#include <Internal/common.h>
+#include <Internal/timer.h>
+
+#include <Math/enginemath.h>
+#include <Math/vec3math.h>
+#include <Math/vec4math.h>
+
+#include <Physics/physics.h>
+#include <Physics/bulletworld.h>
+
+#include <Objects/movementobject.h>
+#include <Objects/decalobject.h>
+
+#include <Asset/AssetLoader/fallbackassetloader.h>
+#include <Asset/Asset/material.h>
+
+#include <Main/scenegraph.h>
+#include <Main/engine.h>
+
+#include <Online/online_datastructures.h>
+#include <XML/xml_helper.h>
+#include <Sound/sound.h>
+#include <Logging/logdata.h>
+
+#include "tinyxml.h"
+
+#include <assert.h>
+
+ParticleType::ParticleType( AssetManager* owner, uint32_t asset_id ) : Asset( owner, asset_id )
+{
+
+}
+
+TextureAssetRef LoadXMLTex(TiXmlElement* el, const char* label){
+ const char* str = el->Attribute(label);
+ if(str){
+ return Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(str);
+ } else {
+ return TextureAssetRef();
+ }
+}
+
+AnimationEffectRef LoadXMLAERef(TiXmlElement* el, const char* label){
+ const char* str = el->Attribute(label);
+ if(str){
+ //return AnimationEffects::Instance()->ReturnRef(str);
+ return Engine::Instance()->GetAssetManager()->LoadSync<AnimationEffect>(str);
+ } else {
+ return AnimationEffectRef();
+ }
+}
+
+int ParticleType::Load( const std::string &path, uint32_t load_flags ) {
+ sub_error = 0;
+ TiXmlDocument doc;
+
+ if(LoadXMLRetryable(doc, path, "Particle"))
+ {
+ clear();
+
+ TiXmlHandle h_doc(&doc);
+ TiXmlHandle h_root = h_doc.FirstChildElement();
+ TiXmlElement* field = h_root.ToElement()->FirstChildElement();
+ for( ; field; field = field->NextSiblingElement()) {
+ std::string field_str(field->Value());
+ if(field_str == "textures"){
+ ae_ref = LoadXMLAERef(field, "animation_effect");
+ color_map = LoadXMLTex(field, "color_map");
+ normal_map = LoadXMLTex(field, "normal_map");
+ const char* shader_str = field->Attribute("shader");
+ const char* soft_shader_str = field->Attribute("soft_shader");
+ if(soft_shader_str){
+ FormatString(shader_name, kMaxNameLen, "%s", soft_shader_str);
+ } else {
+ FormatString(shader_name, kMaxNameLen, "%s", shader_str);
+ }
+ } else if(field_str == "size"){
+ GetRange(field, "val", "min", "max", size_range[0], size_range[1]);
+ } else if(field_str == "rotation"){
+ GetRange(field, "val", "min", "max", rotation_range[0], rotation_range[1]);
+ } else if(field_str == "color"){
+ GetRange(field, "red", "red_min", "red_max",
+ color_range[0][0], color_range[1][0]);
+ GetRange(field, "green", "green_min", "green_max",
+ color_range[0][1], color_range[1][1]);
+ GetRange(field, "blue", "blue_min", "blue_max",
+ color_range[0][2], color_range[1][2]);
+ GetRange(field, "alpha", "alpha_min", "alpha_max",
+ color_range[0][3], color_range[1][3]);
+ } else if(field_str == "behavior"){
+ field->QueryFloatAttribute("inertia", &inertia);
+ field->QueryFloatAttribute("gravity", &gravity);
+ field->QueryFloatAttribute("wind", &wind);
+ field->QueryFloatAttribute("size_decay_rate", &size_decay_rate);
+ field->QueryFloatAttribute("opacity_decay_rate", &opacity_decay_rate);
+ opacity_ramp_time = 0.0f;
+ field->QueryFloatAttribute("opacity_ramp_time", &opacity_ramp_time);
+ } else if(field_str == "quadratic_expansion"){
+ quadratic_expansion = true;
+ field->QueryFloatAttribute("speed", &qe_speed);
+ } else if(field_str == "quadratic_dispersion"){
+ quadratic_dispersion = true;
+ field->QueryFloatAttribute("persistence", &qd_mult);
+ } else if(field_str == "stretch"){
+ const char* tf;
+ tf = field->Attribute("velocity_axis");
+ velocity_axis = (tf && strcmp(tf, "true")==0);
+ tf = field->Attribute("speed_stretch");
+ speed_stretch = (tf && strcmp(tf, "true")==0);
+ tf = field->Attribute("min_squash");
+ min_squash = (tf && strcmp(tf, "true")==0);
+ tf = field->Attribute("speed_mult");
+ speed_mult = 1.0f;
+ field->QueryFloatAttribute("speed_mult", &speed_mult);
+ } else if(field_str == "no_rotation"){
+ no_rotation = true;
+ } else if(field_str == "collision"){
+ collision = true;
+ const char* tf;
+ tf = field->Attribute("destroy");
+ collision_destroy = (tf && strcmp(tf, "true")==0);
+ tf = field->Attribute("character_collide");
+ character_collide = (tf && strcmp(tf, "true")==0);
+ tf = field->Attribute("character_add_blood");
+ character_add_blood = (tf && strcmp(tf, "true")==0);
+ const char* c_str = field->Attribute("decal");
+ if(c_str){
+ collision_decal = c_str;
+ }
+ c_str = field->Attribute("materialevent");
+ if(c_str){
+ collision_event = c_str;
+ }
+ field->QueryFloatAttribute("decal_size_mult", &collision_decal_size_mult);
+ }
+ }
+ return kLoadOk;
+ } else {
+ return kLoadErrorMissingFile;
+ }
+}
+
+const char* ParticleType::GetLoadErrorString() {
+ return "";
+}
+
+void ParticleType::Unload() {
+
+}
+
+void ParticleType::ReportLoad()
+{
+
+}
+
+void ParticleType::clear() {
+ size_decay_rate = 1.0f;
+ opacity_decay_rate = 1.0f;
+ quadratic_expansion = false;
+ qe_speed = 1.0f;
+ quadratic_dispersion = false;
+ no_rotation = false;
+ qd_mult = 1.0f;
+ gravity = 0.0f;
+ inertia = 1.0f;
+ wind = 0.0f;
+ velocity_axis = false;
+ speed_stretch = false;
+ min_squash = false;
+ speed_mult = 0.0f;
+ collision_decal_size_mult = 1.0f;
+ collision_decal.clear();
+ collision_event.clear();
+ collision_destroy = false;
+ collision = false;
+ character_collide = false;
+ character_add_blood = false;
+ rotation_range[0] = -360.0f;
+ rotation_range[1] = 360.0f;
+ opacity_ramp_time = 0.0f;
+}
+
+void ParticleType::Reload() {
+ Load(path_,0x0);
+}
+
+AssetLoaderBase* ParticleType::NewLoader() {
+ return new FallbackAssetLoader<ParticleType>();
+}
diff --git a/Source/Asset/Asset/particletype.h b/Source/Asset/Asset/particletype.h
new file mode 100644
index 00000000..cce438ec
--- /dev/null
+++ b/Source/Asset/Asset/particletype.h
@@ -0,0 +1,90 @@
+//-----------------------------------------------------------------------------
+// Name: particletype.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+#include <Math/vec4.h>
+
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+#include <Asset/Asset/animationeffect.h>
+
+#include <Graphics/textureref.h>
+
+#include <map>
+#include <vector>
+
+class ParticleType : public Asset {
+public:
+ ParticleType( AssetManager* owner, uint32_t asset_id );
+
+ static AssetType GetType() { return PARTICLE_TYPE_ASSET; }
+ static const char* GetTypeName() { return "PARTICLE_TYPE_ASSET"; }
+ static bool AssetWarning() { return true; }
+
+ static const int kMaxNameLen = 512;
+ char shader_name[kMaxNameLen];
+ TextureAssetRef color_map;
+ TextureAssetRef normal_map;
+ AnimationEffectRef ae_ref;
+ float size_range[2];
+ float rotation_range[2];
+ vec4 color_range[2];
+ float inertia;
+ float gravity;
+ float wind;
+ float size_decay_rate;
+ float opacity_decay_rate;
+ float opacity_ramp_time;
+ bool quadratic_expansion;
+ float qe_speed;
+ bool quadratic_dispersion;
+ float qd_mult;
+ bool velocity_axis;
+ bool speed_stretch;
+ float speed_mult;
+ bool min_squash;
+ bool no_rotation;
+ bool collision;
+ bool collision_destroy;
+ bool character_collide;
+ bool character_add_blood;
+ std::string collision_decal;
+ float collision_decal_size_mult;
+ std::string collision_event;
+
+ int sub_error;
+ int Load(const std::string &path, uint32_t load_flags);
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+ void Unload();
+ virtual void ReportLoad();
+ void Reload( );
+
+ void clear();
+
+ virtual AssetLoaderBase* NewLoader();
+};
+
+typedef AssetRef<ParticleType> ParticleTypeRef;
diff --git a/Source/Asset/Asset/reactions.cpp b/Source/Asset/Asset/reactions.cpp
new file mode 100644
index 00000000..b65a75a6
--- /dev/null
+++ b/Source/Asset/Asset/reactions.cpp
@@ -0,0 +1,114 @@
+//-----------------------------------------------------------------------------
+// Name: reactions.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "reactions.h"
+
+#include <Asset/AssetLoader/fallbackassetloader.h>
+#include <Asset/Asset/animation.h>
+
+#include <XML/xml_helper.h>
+#include <Logging/logdata.h>
+#include <Internal/error.h>
+
+#include <tinyxml.h>
+
+Reaction::Reaction( AssetManager* owner, uint32_t asset_id ) : AssetInfo( owner, asset_id ), sub_error(0) {
+}
+
+void Reaction::Reload() {
+ Load(path_,0x0);
+}
+
+void Reaction::ReportLoad() {
+
+}
+
+int Reaction::Load( const std::string &path, uint32_t load_flags ) {
+ sub_error = 0;
+ TiXmlDocument doc;
+
+ if( LoadXMLRetryable(doc, path, "Reaction") ) {
+ anim_paths.clear();
+ mirrored = 0;
+
+ TiXmlHandle h_doc(&doc);
+ TiXmlElement* root = h_doc.FirstChildElement().ToElement();
+ std::string label = root->Value();
+ if(label != "reaction"){
+ FatalError("Error", "Reaction xml has incorrect type: %s", label.c_str());
+ }
+ TiXmlElement* field = root->FirstChildElement();
+ for( ; field; field = field->NextSiblingElement()) {
+ std::string field_str(field->Value());
+ if(field_str == "reaction"){
+ anim_paths.push_back(field->GetText());
+ } else if(field_str == "flags"){
+ const char* tf = field->Attribute("mirrored");
+ if(tf && strcmp(tf, "true")==0){
+ mirrored = 1;
+ } else if(tf && strcmp(tf, "maybe")==0){
+ mirrored = 2;
+ }
+ }
+ }
+ } else {
+ return kLoadErrorMissingFile;
+ }
+
+ return kLoadOk;
+}
+
+const char* Reaction::GetLoadErrorString() {
+ switch(sub_error) {
+ case 0: return "";
+ default: return "Undefined error";
+ }
+}
+
+void Reaction::Unload() {
+
+}
+
+const std::string &Reaction::GetAnimPath( float severity )
+{
+ float choose_val = severity * 0.999f * anim_paths.size();
+ int choice = (int)choose_val;
+ return anim_paths[choice];
+}
+
+int Reaction::IsMirrored()
+{
+ return mirrored;
+}
+
+void Reaction::ReturnPaths( PathSet &path_set )
+{
+ path_set.insert("reaction "+path_);
+ for(unsigned i=0; i<anim_paths.size(); ++i){
+ ReturnAnimationAssetRef(anim_paths[i]);
+ }
+}
+
+AssetLoaderBase* Reaction::NewLoader() {
+ return new FallbackAssetLoader<Reaction>();
+}
diff --git a/Source/Asset/Asset/reactions.h b/Source/Asset/Asset/reactions.h
new file mode 100644
index 00000000..1cd67cbc
--- /dev/null
+++ b/Source/Asset/Asset/reactions.h
@@ -0,0 +1,56 @@
+//-----------------------------------------------------------------------------
+// Name: reactions.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+
+#include <vector>
+
+class Reaction : public AssetInfo {
+ std::vector<std::string> anim_paths;
+ int mirrored;
+
+public:
+ Reaction( AssetManager* owner, uint32_t asset_id );
+ static AssetType GetType() { return REACTION_ASSET; }
+ static const char* GetTypeName() { return "REACTION_ASSET"; }
+ static bool AssetWarning() { return true; }
+
+ int IsMirrored();
+ const std::string &GetAnimPath(float severity);
+ int sub_error;
+ int Load(const std::string &path, uint32_t load_flags);
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+ void Unload();
+
+ void Reload();
+ virtual void ReportLoad();
+ virtual void ReturnPaths(PathSet &path_set);
+
+ virtual AssetLoaderBase* NewLoader();
+};
+
+typedef AssetRef<Reaction> ReactionRef;
diff --git a/Source/Asset/Asset/skeletonasset.cpp b/Source/Asset/Asset/skeletonasset.cpp
new file mode 100644
index 00000000..c9b84e96
--- /dev/null
+++ b/Source/Asset/Asset/skeletonasset.cpp
@@ -0,0 +1,391 @@
+//-----------------------------------------------------------------------------
+// Name: skeletonasset.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "skeletonasset.h"
+
+#include <Compat/fileio.h>
+#include <Compat/filepath.h>
+
+#include <Graphics/models.h>
+#include <Graphics/skeleton.h>
+
+#include <Internal/checksum.h>
+#include <Internal/filesystem.h>
+#include <Internal/error.h>
+
+#include <Asset/AssetLoader/fallbackassetloader.h>
+#include <Physics/bulletworld.h>
+#include <Logging/logdata.h>
+#include <Math/vec3math.h>
+#include <Main/engine.h>
+
+#include <tinyxml.h>
+
+#ifdef min
+#undef min
+#endif
+
+#ifdef max
+#undef max
+#endif
+
+static const int _skeleton_version = 9;
+static const int _min_skeleton_version = 6;
+
+float CapsuleDistance (const vec3 &a, const vec3 &b, const vec3 &p) {
+ float length = distance(b,a);
+ vec3 dir = (b-a)/length;
+ float t = dot(p-a,dir);
+ if(t<0){
+ return distance(a,p);
+ }
+ if(t>length){
+ return distance(b,p);
+ }
+ return distance(a,p-t*dir);
+}
+
+int GetClosestBone( const SkeletonFileData &data, const vec3& point ) {
+ int closest = -1;
+ float closest_dist = 0.0f;
+ float dist;
+ for(unsigned i=0; i<data.bone_ends.size(); i+=2){
+ dist = CapsuleDistance(data.points[data.bone_ends[i]],
+ data.points[data.bone_ends[i+1]],
+ point);
+ if(closest == -1 || dist < closest_dist){
+ closest_dist = dist;
+ closest = i/2;
+ }
+ }
+ return closest;
+}
+
+
+int SkeletonAsset::Load( const std::string &rel_path, uint32_t load_flags ) {
+ char abs_path[kPathSize];
+ bool retry = true;
+ if (FindFilePath(rel_path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths) == -1) {
+ return kLoadErrorMissingFile;
+ }
+
+ TiXmlDocument doc(abs_path);
+ doc.LoadFile();
+ if( doc.Error() ) {
+ return kLoadErrorCouldNotOpenXML;
+ }
+
+ checksum_ = Checksum(abs_path);
+ TiXmlHandle h_doc(&doc);
+ TiXmlElement *rig = h_doc.FirstChildElement().ToElement();
+
+ if( rig ) {
+ std::string bone_path = rig->Attribute("bone_path");
+ FindFilePath(bone_path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths);
+ checksum_ += Checksum(abs_path);
+ std::string model_path = rig->Attribute("model_path");
+ FindFilePath(model_path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths);
+ checksum_ += Checksum(abs_path);
+ const char* mass_path_cstr = rig->Attribute("mass_path");
+ SkeletonAssetRef mass_path_ref;
+ if(mass_path_cstr){
+ //SkeletonAssets::Instance()->ReturnRef(mass_path_cstr);
+ mass_path_ref = Engine::Instance()->GetAssetManager()->LoadSync<SkeletonAsset>(mass_path_cstr);
+ checksum_ += mass_path_ref->checksum();
+ }
+
+ int model_id = Models::Instance()->loadModel(model_path, _MDL_CENTER);
+ const Model &model = Models::Instance()->GetModel(model_id);
+
+ FindFilePath(bone_path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths);
+ FILE *file = my_fopen(abs_path,"rb");
+ if(!file) {
+ sub_error = 1;
+ return kLoadErrorMissingSubFile;
+ }
+
+ int version = 5;
+
+ int temp_read = 0;
+ fread(&temp_read, sizeof(int), 1, file);
+
+ if(temp_read>=_min_skeleton_version){
+ version = temp_read;
+ fread(&temp_read, sizeof(int), 1, file);
+ }
+
+ data.rigging_stage = (RiggingStage)temp_read;
+
+ if(data.rigging_stage == _animate){
+ data.rigging_stage = _control_joints;
+ }
+
+ int num_points = 0;
+ fread(&num_points, sizeof(int), 1, file);
+ float point_center_float[3];
+ vec3 point_center;
+ for(int i=0; i<num_points; i++){
+ fread(point_center_float,sizeof(float),3,file);
+ point_center = vec3(point_center_float[0],
+ point_center_float[1],
+ point_center_float[2]);
+ data.points.push_back(point_center);
+ }
+
+ if(version >= 8){
+ data.point_parents.resize(data.points.size());
+ for(unsigned i=0; i<data.points.size(); i++){
+ fread(&data.point_parents[i], sizeof(int), 1, file);
+ }
+ }
+
+ int num_bones = 0;
+ fread(&num_bones, sizeof(int), 1, file);
+ int f_bone_ends[2] = {0,0};
+ for(int i=0; i<num_bones; i++){
+ fread(&f_bone_ends, sizeof(int), 2, file);
+ data.bone_ends.push_back(f_bone_ends[0]);
+ data.bone_ends.push_back(f_bone_ends[1]);
+ data.bone_mats.push_back(GetBoneMat(data.points[f_bone_ends[0]],
+ data.points[f_bone_ends[1]]));
+ }
+
+ if(version >= 8){
+ data.bone_parents.resize(num_bones);
+ for(int i=0; i<num_bones; i++){
+ fread(&data.bone_parents[i], sizeof(int), 1, file);
+ }
+ }
+
+ if(version>=6){
+ data.bone_mass.resize(num_bones);
+ for(int i=0; i<num_bones; i++){
+ fread(&data.bone_mass[i], sizeof(float), 1, file);
+ }
+ if(mass_path_ref.valid()){
+ int shared_bones = std::min(data.bone_mass.size(), mass_path_ref->GetData().bone_mass.size());
+ for(int i=0; i<shared_bones; ++i){
+ data.bone_mass[i] = mass_path_ref->GetData().bone_mass[i];
+ }
+ }
+ }
+
+ float com_float[3];
+ data.bone_com.resize(num_bones);
+ if(version>=7){
+ for(int i=0; i<num_bones; i++){
+ fread(&com_float, sizeof(float), 3, file);
+ data.bone_com[i] = vec3(com_float[0],
+ com_float[1],
+ com_float[2]);
+ }
+ } else {
+ for(int i=0; i<num_bones; i++){
+ data.bone_com[i] = (data.points[data.bone_ends[i*2+0]] +
+ data.points[data.bone_ends[i*2+1]]) *
+ 0.5f;
+ }
+ }
+
+ if(version>=9) {
+ mat4 mat;
+ for(int i=0; i<num_bones; i++){
+ fread(&mat.entries[0], sizeof(float), 16, file);
+ // data.bone_mats.push_back(mat);
+ }
+ }
+
+ if(data.rigging_stage == _control_joints){
+ int num_read_verts;
+ int precollapse_num_vertices = model.precollapse_num_vertices;
+ if(version>=11){
+ fread(&num_read_verts, sizeof(int), 1, file);
+ if(num_read_verts != precollapse_num_vertices) {
+ sub_error = 4;
+ return kLoadErrorCorruptFile;
+ }
+ }
+ int model_num_vertices = model.vertices.size()/3;
+ data.model_bone_weights.resize(model_num_vertices);
+ data.model_bone_ids.resize(model_num_vertices);
+
+ std::vector<vec4> temp_bone_weights(precollapse_num_vertices);
+ std::vector<vec4> temp_bone_ids(precollapse_num_vertices);
+ fread(&temp_bone_weights[0], sizeof(vec4), temp_bone_weights.size(), file);
+ fread(&temp_bone_ids[0], sizeof(vec4), temp_bone_ids.size(), file);
+ std::vector<vec4> temp_bone_weights2(model.precollapse_vert_reorder.size());
+ std::vector<vec4> temp_bone_ids2(model.precollapse_vert_reorder.size());
+ for(int i=0; i<model_num_vertices; ++i){
+ temp_bone_weights2[i] = temp_bone_weights[model.precollapse_vert_reorder[i]];
+ temp_bone_ids2[i] = temp_bone_ids[model.precollapse_vert_reorder[i]];
+ }
+ for(int i=0; i<model_num_vertices; ++i){
+ data.model_bone_weights[i] = temp_bone_weights2[model.optimize_vert_reorder[i]];
+ data.model_bone_ids[i] = temp_bone_ids2[model.optimize_vert_reorder[i]];
+ }
+ for(int i=0; i<model_num_vertices; ++i){
+ const vec4 &w = data.model_bone_weights[i];
+ if(w[0] + w[1] + w[2] + w[3] < 0.99f){
+ vec3 vert = vec3(model.vertices[i*3+0], model.vertices[i*3+1], model.vertices[i*3+2]);
+ int closest_bone = GetClosestBone(data, vert);
+ data.model_bone_weights[i][0] = 1.0f;
+ data.model_bone_ids[i][0] = (float)closest_bone;
+ }
+ }
+ data.old_model_center = model.old_center;
+
+ data.hier_parents.resize(num_bones);
+
+ int bone_id;
+ for(int i=0; i<num_bones; i++){
+ fread(&bone_id, sizeof(int), 1, file);
+ data.hier_parents[i] = bone_id;
+ }
+
+ int num_joints;
+ fread(&num_joints, sizeof(int), 1, file);
+ for(int i=0; i<num_joints; i++){
+ JointData joint;
+ fread(&joint.type, sizeof(int), 1, file);
+ if(joint.type == _amotor_joint){
+ fread(&joint.stop_angle, sizeof(float), 6, file);
+ joint.stop_angle[4] *= -1.0f;
+ joint.stop_angle[5] *= -1.0f;
+ std::swap(joint.stop_angle[4], joint.stop_angle[5]);
+ } else if(joint.type == _hinge_joint){
+ fread(&joint.stop_angle, sizeof(float), 2, file);
+ }
+ fread(&joint.bone_id, sizeof(int), 2, file);
+
+ if(joint.type == _hinge_joint) {
+ fread(&joint.axis,sizeof(vec3),1,file);
+ }
+ data.joints.push_back(joint);
+ }
+ }
+
+ if(version>=10) {
+ int num_ik_bones;
+ fread(&num_ik_bones, sizeof(int), 1, file);
+ for(int i=0; i<num_ik_bones; ++i){
+ SimpleIKBone bone;
+ fread(&bone.bone_id, sizeof(int), 1, file);
+ fread(&bone.chain_length, sizeof(int), 1, file);
+ int num_chars;
+ fread(&num_chars, sizeof(int), 1, file);
+ std::string the_string;
+ the_string.resize(num_chars);
+ fread(&the_string[0], sizeof(char), num_chars, file);
+ data.simple_ik_bones[the_string] = bone;
+ }
+ }
+
+ data.symmetry.resize(num_bones, -1);
+ bool missing_symmetry = false;
+ std::stringstream sstream;
+ sstream << " Asymmetrical bones: ";
+ for(int i=0; i<num_bones; ++i){
+ vec3 points[2];
+ points[0] = data.points[data.bone_ends[i*2+0]];
+ points[1] = data.points[data.bone_ends[i*2+1]];
+ for(int j=i; j<num_bones; ++j){
+ vec3 reverse[2];
+ reverse[0] = data.points[data.bone_ends[j*2+0]];
+ reverse[1] = data.points[data.bone_ends[j*2+1]];
+ reverse[0][0] *= -1.0f;
+ reverse[1][0] *= -1.0f;
+ if(distance_squared(points[0], reverse[0]) < 0.0001f &&
+ distance_squared(points[1], reverse[1]) < 0.0001f)
+ {
+ data.symmetry[i] = j;
+ data.symmetry[j] = i;
+ }
+ }
+ if(data.symmetry[i] == -1){
+ missing_symmetry = true;
+ sstream << "bone " << i << " at " << points[0] << ", " << points[1] << ", ";
+ }
+ }
+ if(missing_symmetry){
+ error_string = sstream.str();
+ error_string = error_string.substr(0, error_string.size() - 2);
+ sub_error = 3;
+ fclose(file);
+ return kLoadErrorCorruptFile;
+ }
+
+ fclose(file);
+ } else {
+ sub_error = 2;
+ return kLoadErrorIncompleteXML;
+ }
+
+ return kLoadOk;
+}
+
+const char* SkeletonAsset::GetLoadErrorString() {
+ switch(sub_error) {
+ case 0: return "";
+ case 1: return "Could not find bone_path file from attribute.";
+ case 2: return "Missing root Rig node in xml file.";
+ case 3: return "PHXBN skeleton is not symmetrical.";
+ case 4: return "Skeleton file and model file have different vertex counts.";
+ default: return "Undefined error.";
+ }
+}
+
+const char* SkeletonAsset::GetLoadErrorStringExtended() {
+ return error_string.c_str();
+}
+
+void SkeletonAsset::Unload() {
+
+}
+
+void SkeletonAsset::Reload() {
+
+}
+
+void SkeletonAsset::ReportLoad()
+{
+
+}
+
+SkeletonAsset::SkeletonAsset( AssetManager* owner, uint32_t asset_id ) : Asset(owner, asset_id), sub_error(0)
+{
+
+}
+
+SkeletonAsset::~SkeletonAsset()
+{
+
+}
+
+const SkeletonFileData& SkeletonAsset::GetData()
+{
+ return data;
+}
+
+AssetLoaderBase* SkeletonAsset::NewLoader() {
+ return new FallbackAssetLoader<SkeletonAsset>();
+}
diff --git a/Source/Asset/Asset/skeletonasset.h b/Source/Asset/Asset/skeletonasset.h
new file mode 100644
index 00000000..1824e1ab
--- /dev/null
+++ b/Source/Asset/Asset/skeletonasset.h
@@ -0,0 +1,109 @@
+//-----------------------------------------------------------------------------
+// Name: skeletonasset.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+#include <Math/mat4.h>
+
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+#include <Asset/assettypes.h>
+
+#include <Internal/integer.h>
+#include <Utility/flat_hash_map.hpp>
+
+#include <vector>
+#include <map>
+
+class AssetManager;
+
+enum {_hinge_joint = 0,
+ _amotor_joint = 1,
+ _fixed_joint = 2};
+
+struct JointData {
+ int type;
+ int bone_id[2];
+ float stop_angle[6];
+ vec3 axis;
+};
+
+struct SimpleIKBone {
+ int bone_id;
+ int chain_length;
+};
+
+enum RiggingStage { _nothing = -1,
+_create_bones = 0,
+_control_joints = 1,
+_animate = 2,
+_pose_weights = 3};
+
+struct SkeletonFileData {
+ std::vector<vec3> points; // Endpoints of bones
+ std::vector<int> bone_ends; // Indices into points array (2 per bone)
+ std::vector<int> hier_parents; // The parent bone id of each bone (including connector bones)
+ std::vector<int> point_parents; // The parent point id of each point
+ std::vector<int> bone_parents; // Same as hier_parents? (why?)
+ std::vector<float> bone_mass; // Mass of each bone
+ std::vector<vec3> bone_com; // The center of mass of each bone
+ std::vector<mat4> bone_mats; // The initial matrix for each bone
+ std::vector<JointData> joints; // Physics info for each joint
+ typedef ska::flat_hash_map<std::string, SimpleIKBone> IKBoneMap;
+ IKBoneMap simple_ik_bones; // labeled IK bone chains
+ std::vector<vec4> model_bone_weights; // For each vertex, the weight of the four attached bones
+ std::vector<vec4> model_bone_ids; // For each vertex, the id of the four attached bones
+ std::vector<int> symmetry;
+ vec3 old_model_center;
+ RiggingStage rigging_stage; // At which rigging stage was this skeleton saved (obsolete)
+};
+
+class SkeletonAsset : public Asset {
+ SkeletonFileData data;
+ unsigned short checksum_;
+ std::string error_string;
+public:
+ SkeletonAsset(AssetManager* owner, uint32_t asset_id);
+ ~SkeletonAsset();
+
+ const SkeletonFileData& GetData();
+ unsigned short checksum() {return checksum_;}
+
+ int sub_error;
+ int Load(const std::string &path, uint32_t load_flags);
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended();
+ void Unload();
+
+ void Reload( );
+ virtual void ReportLoad();
+ static AssetType GetType() { return SKELETON_ASSET; };
+ static const char* GetTypeName() { return "SKELETON_ASSET"; }
+ static bool AssetWarning() { return true; }
+
+ virtual AssetLoaderBase* NewLoader();
+};
+
+typedef AssetRef<SkeletonAsset> SkeletonAssetRef;
+
diff --git a/Source/Asset/Asset/songlistfile.cpp b/Source/Asset/Asset/songlistfile.cpp
new file mode 100644
index 00000000..6e7340ff
--- /dev/null
+++ b/Source/Asset/Asset/songlistfile.cpp
@@ -0,0 +1,91 @@
+//-----------------------------------------------------------------------------
+// Name: songlistfile.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "songlistfile.h"
+
+#include <Asset/AssetLoader/fallbackassetloader.h>
+#include <Asset/assetloaderrors.h>
+
+#include <XML/xml_helper.h>
+#include <Logging/logdata.h>
+
+#include <tinyxml.h>
+
+void SongListFile::Reload() {
+ Load(path_, 0x0);
+}
+
+int SongListFile::Load( const std::string &_path, uint32_t load_flags ) {
+ song_paths.clear();
+ path_ = "";
+
+ TiXmlDocument doc;
+
+ if(LoadXMLRetryable(doc, path_, "Song list")) {
+ path_ = _path;
+ if(doc.Error()) {
+ return kLoadErrorCorruptFile;
+ } else {
+ TiXmlHandle h_doc(&doc);
+ TiXmlHandle h_root = h_doc.FirstChildElement();
+ TiXmlElement* field = h_root.ToElement()->FirstChildElement();
+ for( ; field; field = field->NextSiblingElement()) {
+ std::string field_str(field->Value());
+ song_paths[field_str] = field->Attribute("path");
+ LOGI << "Loaded song " << field_str << " -> " << song_paths[field_str] << std::endl;
+ }
+ return kLoadOk;
+ }
+ } else {
+ LOGE << "Unable to load SongListFile " << _path << std::endl;
+ return kLoadErrorMissingFile;
+ }
+}
+
+const char* SongListFile::GetLoadErrorString() {
+ return "";
+}
+
+void SongListFile::Unload() {
+
+}
+
+const std::string & SongListFile::GetSongPath( const std::string song )
+{
+ std::map<std::string, std::string>::iterator iter;
+ iter = song_paths.find(song);
+ if(iter != song_paths.end()){
+ return iter->second;
+ } else {
+ return null_string;
+ }
+}
+
+const std::map<std::string, std::string> & SongListFile::GetSongPaths()
+{
+ return song_paths;
+}
+
+AssetLoaderBase* SongListFile::NewLoader() {
+ return new FallbackAssetLoader<SongListFile>();
+}
diff --git a/Source/Asset/Asset/songlistfile.h b/Source/Asset/Asset/songlistfile.h
new file mode 100644
index 00000000..059f3e65
--- /dev/null
+++ b/Source/Asset/Asset/songlistfile.h
@@ -0,0 +1,49 @@
+//-----------------------------------------------------------------------------
+// Name: songlistfile.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+
+#include <map>
+#include <string>
+
+class SongListFile : public Asset {
+ std::map<std::string, std::string> song_paths;
+ std::string null_string;
+
+public:
+ const std::string &GetSongPath(const std::string song);
+ const std::map<std::string, std::string> &GetSongPaths();
+ int Load(const std::string &path, uint32_t load_flags);
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+ void Unload();
+ void Reload();
+ virtual AssetLoaderBase* NewLoader();
+ static const char* GetTypeName() { return "SONG_LIST_ASSET"; }
+ static bool AssetWarning() { return true; }
+};
+
+typedef AssetRef<SongListFile> SongListFileRef;
diff --git a/Source/Asset/Asset/soundgroup.cpp b/Source/Asset/Asset/soundgroup.cpp
new file mode 100644
index 00000000..ffd2d47e
--- /dev/null
+++ b/Source/Asset/Asset/soundgroup.cpp
@@ -0,0 +1,113 @@
+//-----------------------------------------------------------------------------
+// Name: soundgroup.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "soundgroup.h"
+
+#include <Asset/AssetLoader/fallbackassetloader.h>
+#include <XML/xml_helper.h>
+#include <Internal/scoped_buffer.h>
+#include <Logging/logdata.h>
+#include <Main/engine.h>
+
+#include <tinyxml.h>
+#include <sstream>
+
+SoundGroup::SoundGroup( AssetManager* owner, uint32_t asset_id ) : Asset( owner, asset_id ), sub_error(0) {
+}
+
+void SoundGroup::Reload() {
+ Load(path_, 0x0);
+}
+
+int SoundGroup::Load( const std::string &path, uint32_t load_flags ) {
+ //SoundGroupInfoCollection::Instance()->ReturnRef(path);
+ sound_group_info = Engine::Instance()->GetAssetManager()->LoadSync<SoundGroupInfo>(path);
+
+ history_size = int(float(sound_group_info->GetNumVariants())/2.0f);
+ history.clear();
+ history.resize(history_size, -1);
+ history_index = 0;
+
+ LOGD.Format("Soundgroup: %s\n", path.c_str());
+ //LOGD.Format("Num variants: %d\n", num_variants);
+ //LOGD.Format("Delay: %f\n", delay);
+ //LOGD.Format("Volume: %f\n", volume);
+ return kLoadOk;
+}
+
+const char* SoundGroup::GetLoadErrorString() {
+ switch(sub_error) {
+ case 0: return "";
+ default: return "Undefined Errors";
+ }
+}
+
+void SoundGroup::Unload() {
+}
+
+std::string SoundGroup::GetSoundPath() {
+ bool bad_choice = true;
+ int choice;
+ while(bad_choice){
+ choice = rand()%(sound_group_info->GetNumVariants());
+ bad_choice = false;
+ for(int i=0; i<history_size; ++i){
+ if(choice == history[i]){
+ bad_choice = true;
+ break;
+ }
+ }
+ }
+
+ if(history_size){
+ history[history_index++] = choice;
+ history_index = history_index%history_size;
+ }
+
+ return sound_group_info->GetSoundPath(choice);
+}
+
+std::string SoundGroup::GetPath() const {
+ return path_;
+}
+
+int SoundGroup::GetNumVariants() const {
+ return sound_group_info->GetNumVariants();
+}
+
+float SoundGroup::GetVolume() const {
+ return sound_group_info->GetVolume();
+}
+
+float SoundGroup::GetDelay() const {
+ return sound_group_info->GetDelay();
+}
+
+float SoundGroup::GetMaxDistance() const {
+ return sound_group_info->GetMaxDistance();
+}
+
+AssetLoaderBase* SoundGroup::NewLoader() {
+ return new FallbackAssetLoader<SoundGroup>();
+}
+
diff --git a/Source/Asset/Asset/soundgroup.h b/Source/Asset/Asset/soundgroup.h
new file mode 100644
index 00000000..3eaebf13
--- /dev/null
+++ b/Source/Asset/Asset/soundgroup.h
@@ -0,0 +1,61 @@
+//-----------------------------------------------------------------------------
+// Name: soundgroup.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+#include <Asset/Asset/soundgroupinfo.h>
+
+#include <vector>
+
+class SoundGroup : public Asset {
+ SoundGroupInfoRef sound_group_info;
+
+ std::vector<int> history;
+ int history_index;
+ int history_size;
+
+public:
+ SoundGroup( AssetManager* owner, uint32_t asset_id );
+ static AssetType GetType() { return SOUND_GROUP_ASSET; }
+ static const char* GetTypeName() { return "SOUND_GROUP_ASSET"; }
+ static bool AssetWarning() { return true; }
+
+ int sub_error;
+ int Load( const std::string &path, uint32_t load_flags );
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+ void Unload();
+ virtual void ReportLoad() {}
+ void Reload( );
+ std::string GetPath() const;
+ int GetNumVariants() const;
+ float GetVolume() const;
+ float GetMaxDistance() const;
+ float GetDelay() const;
+ std::string GetSoundPath();
+ virtual AssetLoaderBase* NewLoader();
+};
+
+typedef AssetRef<SoundGroup> SoundGroupRef;
diff --git a/Source/Asset/Asset/soundgroupinfo.cpp b/Source/Asset/Asset/soundgroupinfo.cpp
new file mode 100644
index 00000000..967a532b
--- /dev/null
+++ b/Source/Asset/Asset/soundgroupinfo.cpp
@@ -0,0 +1,148 @@
+//-----------------------------------------------------------------------------
+// Name: soundgroupinfo.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "soundgroupinfo.h"
+
+#include <Internal/scoped_buffer.h>
+#include <Internal/integer.h>
+
+#include <XML/xml_helper.h>
+#include <Logging/logdata.h>
+#include <Asset/AssetLoader/fallbackassetloader.h>
+#include <Utility/strings.h>
+#include <Memory/allocation.h>
+
+#include <tinyxml.h>
+
+#include <sstream>
+
+SoundGroupInfo::SoundGroupInfo( AssetManager* owner, uint32_t asset_id ) : AssetInfo(owner, asset_id)
+{
+
+}
+
+bool SoundGroupInfo::ParseXML( const char* data )
+{
+ TiXmlDocument doc;
+ doc.Parse(data);
+ bool ret_value = false;
+
+ if(!doc.Error()) {
+ TiXmlHandle hDoc(&doc);
+ TiXmlHandle hRoot = hDoc.FirstChildElement();
+ TiXmlElement* root = hRoot.ToElement();
+ if( root ) {
+ if(root->QueryIntAttribute("variants", &num_variants) != TIXML_SUCCESS){
+ num_variants = 1;
+ LOGW << "Missing element \"variants\" in SoundGroupInfo asset: " << path_ << std::endl;
+ }
+
+ if(root->QueryFloatAttribute("delay", &delay) != TIXML_SUCCESS){
+ delay = 0.0f;
+ LOGW << "Missing element \"delay\" in SoundGroupInfo asset: " << path_ << std::endl;
+ }
+
+ if(root->QueryFloatAttribute("volume", &volume) != TIXML_SUCCESS){
+ volume = 1.0f;
+ LOGW << "Missing element \"volume\" in SoundGroupInfo asset: " << path_ << std::endl;
+ }
+
+ if(root->QueryFloatAttribute("max_distance", &max_distance) != TIXML_SUCCESS){
+ max_distance = 30.0f;
+ //LOGW << "Missing element \"max_distance\" in SoundGroupInfo asset: " << path_ << std::endl;
+ }
+
+ ret_value = true;
+ } else {
+ LOGE << "root node was null in SoundGroupInfo ParseXML" << std::endl;
+ }
+ } else {
+ LOGE << "Unable to parse soundgroupinfo from given data" << std::endl;
+ }
+
+ return ret_value;
+}
+
+int SoundGroupInfo::Load( const std::string &_path, uint32_t load_flags )
+{
+ path_ = _path;
+ if(path_.size() < 4){
+ return kLoadErrorMissingFile;
+ }
+ const char* suffix = &path_[path_.size()-4];
+ if(strmtch(suffix, ".xml")){
+ size_t size_out;
+ uint8_t *data = StackLoadText(path_.c_str(),&size_out);
+ if(data) {
+ if( ParseXML((const char*)data) ) {
+
+ } else {
+ LOGE << "Failed to parse xml contents from file: " << path_ << std::endl;
+ }
+ alloc.stack.Free(data);
+ } else {
+ LOGE << "LoadText on " << _path << " failed, will not parse" << std::endl;
+ return kLoadErrorMissingFile;
+ }
+ } else {
+ return kLoadErrorInvalidFileEnding;
+ }
+ return kLoadOk;
+}
+
+const char* SoundGroupInfo::GetLoadErrorString() {
+ return "";
+}
+
+void SoundGroupInfo::Unload() {
+
+}
+
+void SoundGroupInfo::Reload( )
+{
+
+}
+
+void SoundGroupInfo::ReportLoad()
+{
+
+}
+
+void SoundGroupInfo::ReturnPaths( PathSet &path_set )
+{
+ path_set.insert("soundgroup "+path_);
+ for(int i=0; i<num_variants; ++i){
+ path_set.insert("sound "+GetSoundPath(i));
+ }
+}
+
+std::string SoundGroupInfo::GetSoundPath ( int choice ) const
+{
+ std::ostringstream oss;
+ oss << path_.substr(0,path_.size()-4) << "_" << choice+1 << ".wav";
+ return oss.str();
+}
+
+AssetLoaderBase* SoundGroupInfo::NewLoader() {
+ return new FallbackAssetLoader<SoundGroupInfo>();
+}
diff --git a/Source/Asset/Asset/soundgroupinfo.h b/Source/Asset/Asset/soundgroupinfo.h
new file mode 100644
index 00000000..49bb9043
--- /dev/null
+++ b/Source/Asset/Asset/soundgroupinfo.h
@@ -0,0 +1,62 @@
+//-----------------------------------------------------------------------------
+// Name: soundgroupinfo.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+
+#include <vector>
+
+class SoundGroupInfo : public AssetInfo {
+ int num_variants;
+ float delay;
+ float volume;
+ float max_distance;
+
+public:
+ SoundGroupInfo( AssetManager* owner, uint32_t asset_id );
+
+ int Load( const std::string &path, uint32_t load_flags );
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+ void Unload();
+ void Reload( );
+ virtual void ReportLoad();
+
+ virtual void ReturnPaths(PathSet &path_set);
+ inline int GetNumVariants() const {return num_variants;}
+ inline float GetDelay() const {return delay;}
+ inline float GetVolume() const {return volume;}
+ inline float GetMaxDistance() const {return max_distance;}
+ std::string GetSoundPath( int choice ) const;
+ static AssetType GetType() { return SOUND_GROUP_INFO_ASSET; }
+ static const char* GetTypeName() { return "SOUND_GROUP_INFO_ASSET"; }
+ static bool AssetWarning() { return true; }
+
+ virtual AssetLoaderBase* NewLoader();
+private:
+ bool ParseXML( const char* data );
+};
+
+typedef AssetRef<SoundGroupInfo> SoundGroupInfoRef;
diff --git a/Source/Asset/Asset/spawnpointinfo.h b/Source/Asset/Asset/spawnpointinfo.h
new file mode 100644
index 00000000..ad32654f
--- /dev/null
+++ b/Source/Asset/Asset/spawnpointinfo.h
@@ -0,0 +1,37 @@
+//-----------------------------------------------------------------------------
+// Name: spawnpointinfo.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/mat4.h>
+#include <Math/quaternions.h>
+
+#include <Game/EntityDescription.h>
+#include <Asset/assets.h>
+#include <Scripting/scriptparams.h>
+
+struct SpawnPointInfo : public AssetInfo {
+ vec3 translation;
+ vec3 scale;
+ quaternion rotation;
+};
diff --git a/Source/Asset/Asset/syncedanimation.cpp b/Source/Asset/Asset/syncedanimation.cpp
new file mode 100644
index 00000000..902cae17
--- /dev/null
+++ b/Source/Asset/Asset/syncedanimation.cpp
@@ -0,0 +1,490 @@
+//-----------------------------------------------------------------------------
+// Name: syncedanimation.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "syncedanimation.h"
+
+#include <Internal/timer.h>
+#include <Internal/filesystem.h>
+#include <Internal/error.h>
+#include <Internal/profiler.h>
+
+#include <Graphics/animationclient.h>
+#include <Math/enginemath.h>
+#include <Physics/physics.h>
+#include <Logging/logdata.h>
+#include <Utility/assert.h>
+#include <Main/engine.h>
+#include <Asset/AssetLoader/fallbackassetloader.h>
+
+#include <tinyxml.h>
+
+#include <cmath>
+
+extern AnimationConfig animation_config;
+
+struct BlendCoordSorter {
+ bool operator()(const SyncedAnimation& a, const SyncedAnimation &b) {
+ return a.blend_coord < b.blend_coord;
+ }
+};
+
+void SyncedAnimationGroup::AddAnimation( AnimationAssetRef animation, float blend_coord , float ground_speed, float run_bounce, const std::string& time_coord_label ) {
+ // Check if animation has already been added
+ bool already_added = false;
+ std::vector<SyncedAnimation>::iterator iter;
+ for(iter = animations.begin(); iter != animations.end(); ++iter){
+ const SyncedAnimation& synced_anim = (*iter);
+ if(synced_anim.animation == animation){
+ already_added = true;
+ }
+ }
+
+ // If animation is not already there, add it!
+ if(!already_added){
+ animations.resize(animations.size()+1);
+ SyncedAnimation &new_anim = animations.back();
+ new_anim.animation = animation;
+ looping = (*animation).IsLooping();
+
+ //const AnimationAsset& base_anim = (*animation);
+ new_anim.blend_coord = blend_coord;
+ new_anim.ground_speed = ground_speed;
+ new_anim.run_bounce = run_bounce;
+ new_anim.time_coord_label = time_coord_label;
+
+ // Make sure animations are sorted by blend_coord
+ std::sort(animations.begin(),
+ animations.end(),
+ BlendCoordSorter());
+ }
+}
+
+PrevNext SyncedAnimationGroup::NearestAnimations(float blend_coord) const {
+ PrevNext results;
+
+ results.prev_index = -1;
+ for(unsigned i=0; i<animations.size(); i++){
+ if(blend_coord > animations[i].blend_coord){
+ results.prev_index = i;
+ }
+ }
+ results.next_index = min(results.prev_index+1,
+ (int)animations.size()-1);
+ results.prev_index = max(results.prev_index, 0);
+
+ return results;
+}
+
+unsigned SyncedAnimationGroup::NearestAnimation(float blend_coord) const{
+ int closest = -1;
+ float closest_distance;
+ for(unsigned i=0; i<animations.size(); i++){
+ if(closest == -1 || fabs(blend_coord - animations[i].blend_coord) < closest_distance){
+ closest = i;
+ closest_distance = fabs(blend_coord - animations[i].blend_coord);
+ }
+ }
+ return closest;
+}
+
+
+float SyncedAnimationGroup::GetInterpolationValue(const PrevNext &indices,
+ float blend_coord) const
+{
+ if(indices.prev_index == indices.next_index) {
+ return 0.0f;
+ }
+
+ float coord_a = animations[indices.prev_index].blend_coord;
+ float coord_b = animations[indices.next_index].blend_coord;
+
+ if(coord_a == coord_b) {
+ return 0.0f;
+ }
+
+ return (blend_coord - coord_a)/(coord_b-coord_a);
+}
+
+namespace {
+ void GetMatricesFromSyncedAnimation(const SyncedAnimation& sa,
+ float normalized_time,
+ AnimOutput &anim_output,
+ const AnimInput &anim_input)
+ {
+ PROFILER_ZONE(g_profiler_ctx, "GetMatricesFromSyncedAnimation");
+ if(!sa.time_coord_label.empty()){
+ const BlendMap& blendmap = anim_input.blendmap;
+ BlendMap::const_iterator iter = blendmap.find(sa.time_coord_label);
+ if(iter != blendmap.end()){
+ normalized_time = (*iter).second;
+ }
+ }
+ const AnimationAsset& animation = *sa.animation;
+ animation.GetMatrices(normalized_time, anim_output, anim_input);
+ if(sa.run_bounce != _no_ground_speed && !animation_config.kDisableModifiers){
+ float anim_speed = sa.animation->GetFrequency(anim_input.blendmap);
+
+ float curr_ground_speed = 0.5f;
+ {
+ BlendMap::const_iterator iter = anim_input.blendmap.find("ground_speed");
+ if(iter != anim_input.blendmap.end()){
+ curr_ground_speed = (*iter).second;
+ }
+ }
+ if(sa.ground_speed != _no_ground_speed){
+ anim_speed *= curr_ground_speed/sa.ground_speed;
+ }
+
+ anim_speed = max(anim_speed,2.0f);
+ float time = (1.0f / anim_speed) * 0.5f;
+ float initial_vel = -time*Physics::Instance()->gravity[1];
+ float anim_time = normalized_time;
+ if(anim_time > 0.5f){
+ anim_time -= 0.5f;
+ }
+ float bounce_mult = sa.run_bounce;
+ float t = anim_time/anim_speed;
+ anim_output.center_offset[1] += (initial_vel * t + t*t * Physics::Instance()->gravity[1])*bounce_mult;
+ }
+ }
+}
+
+void SyncedAnimationGroup::GetMatrices(int first,
+ int second,
+ float weight,
+ float normalized_time,
+ AnimOutput &anim_output,
+ const AnimInput &anim_input) const
+{
+ AnimOutput prev_anim_output;
+ GetMatricesFromSyncedAnimation(animations[first], normalized_time, prev_anim_output, anim_input);
+ AnimOutput next_anim_output;
+ GetMatricesFromSyncedAnimation(animations[second], normalized_time, next_anim_output, anim_input);
+ if(animation_config.kDisableAnimationMix){
+ weight = floorf(weight+0.5f);
+ }
+ std::string old_path;
+ if(prev_anim_output.old_path != next_anim_output.old_path){
+ if(prev_anim_output.old_path != "retargeted"){
+ Retarget(anim_input, prev_anim_output, prev_anim_output.old_path);
+ }
+ if(next_anim_output.old_path != "retargeted"){
+ Retarget(anim_input, next_anim_output, next_anim_output.old_path);
+ }
+ old_path = "retargeted";
+ } else {
+ old_path = prev_anim_output.old_path;
+ }
+ anim_output = mix(prev_anim_output, next_anim_output, weight);
+ anim_output.old_path = old_path;
+}
+
+void SyncedAnimationGroup::GetMatrices(float normalized_time,
+ AnimOutput &anim_output,
+ const AnimInput &anim_input,
+ float blend_coord) const {
+ LOG_ASSERT(!animations.empty());
+
+ PrevNext nearest = NearestAnimations(blend_coord);
+
+ if(nearest.prev_index == nearest.next_index){
+ if(!overshoot || animations.size() == 1){
+ GetMatricesFromSyncedAnimation(animations[nearest.prev_index], normalized_time, anim_output, anim_input);
+ } else {
+ if(nearest.prev_index == 0 ){
+ nearest.next_index = 1;
+ }
+ if(nearest.next_index == (int)animations.size()-1 ){
+ nearest.prev_index = animations.size()-2;
+ }
+ GetMatrices(nearest.prev_index,
+ nearest.next_index,
+ GetInterpolationValue(nearest, blend_coord),
+ normalized_time,
+ anim_output,
+ anim_input);
+ }
+ } else {
+ GetMatrices(nearest.prev_index,
+ nearest.next_index,
+ GetInterpolationValue(nearest, blend_coord),
+ normalized_time,
+ anim_output,
+ anim_input);
+ }
+ if(in_air){
+ anim_output.center_offset = vec3(0.0f);
+ }
+}
+
+void SyncedAnimationGroup::GetMatrices( float normalized_time,
+ AnimOutput &anim_output,
+ const AnimInput &anim_input) const
+{
+ float blend_coord = 0.0f;
+ BlendMap::const_iterator iter = anim_input.blendmap.find(coord_label);
+ if(iter != anim_input.blendmap.end()){
+ blend_coord = (*iter).second;
+ }
+ GetMatrices(normalized_time, anim_output, anim_input, blend_coord);
+}
+
+float SyncedAnimationGroup::GetFrequency(const BlendMap& blendmap ) const {
+ float blend_coord = 0.0f;
+ {
+ BlendMap::const_iterator iter = blendmap.find(coord_label);
+ if(iter != blendmap.end()){
+ blend_coord = (*iter).second;
+ }
+ }
+
+ float curr_ground_speed = 0.5f;
+ {
+ BlendMap::const_iterator iter = blendmap.find("ground_speed");
+ if(iter != blendmap.end()){
+ curr_ground_speed = (*iter).second;
+ }
+ }
+
+ PrevNext nearest = NearestAnimations(blend_coord);
+ float interp = GetInterpolationValue(nearest, blend_coord);
+
+ // Compare animation movement speed to real movement speed, and speed
+ // up or slow down animation as necessary.
+
+ const SyncedAnimation &prev_anim = animations[nearest.prev_index];
+ const SyncedAnimation &next_anim = animations[nearest.next_index];
+
+ float prev_ground_speed;
+ if(prev_anim.ground_speed == _no_ground_speed){
+ prev_ground_speed = (*prev_anim.animation).GetGroundSpeed(blendmap);
+ } else {
+ prev_ground_speed = prev_anim.ground_speed;
+ }
+ float next_ground_speed;
+ if(next_anim.ground_speed == _no_ground_speed){
+ next_ground_speed = (*next_anim.animation).GetGroundSpeed(blendmap);
+ } else {
+ next_ground_speed = next_anim.ground_speed;
+ }
+
+ float prev_freq_mult = curr_ground_speed / prev_ground_speed;
+ float next_freq_mult = curr_ground_speed / next_ground_speed;
+
+ if(prev_anim.ground_speed == _no_ground_speed){
+ prev_freq_mult = 1.0f;
+ }
+ if(prev_anim.ground_speed == _no_ground_speed){
+ next_freq_mult = 1.0f;
+ }
+
+ float prev_frequency = (*prev_anim.animation).GetFrequency(blendmap) * prev_freq_mult;
+ float next_frequency = (*next_anim.animation).GetFrequency(blendmap) * next_freq_mult;
+
+ return mix(prev_frequency, next_frequency, interp);
+}
+
+float SyncedAnimationGroup::GetPeriod(const BlendMap& blendmap) const {
+ return 1.0f/GetFrequency(blendmap);
+}
+
+int SyncedAnimationGroup::Load( const std::string &rel_path, uint32_t load_flags ) {
+ char abs_path[kPathSize];
+ if(FindFilePath(rel_path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths) == -1){
+ return kLoadErrorMissingFile;
+ }
+
+ TiXmlDocument doc(abs_path);
+ if (!doc.LoadFile()) {
+ return kLoadErrorCouldNotOpenXML;
+ }
+
+ clear();
+
+ TiXmlHandle hDoc(&doc);
+ TiXmlHandle hRoot = hDoc.FirstChildElement();
+ TiXmlElement* coord_label_element = hRoot.FirstChild("CoordLabel").Element();
+ coord_label = coord_label_element->GetText();
+
+ TiXmlElement* overshoot_element = hRoot.FirstChild("Overshoot").Element();
+ if(overshoot_element && !strcmp(overshoot_element->GetText(),"true")){
+ overshoot = true;
+ }
+ TiXmlElement* in_air_element = hRoot.FirstChild("InAir").Element();
+ if(in_air_element && !strcmp(in_air_element->GetText(),"true")){
+ in_air = true;
+ }
+
+ TiXmlElement* animation_element = hRoot.FirstChild("Animations").
+ FirstChild().Element();
+ while(animation_element != NULL){
+ std::string anim_path = animation_element->Attribute("path");
+ float weight_coord = 0.0f;
+ animation_element->QueryFloatAttribute("coord",&weight_coord);
+ std::string time_coord_label;
+ if(animation_element->Attribute("time_coord_label")){
+ time_coord_label = animation_element->Attribute("time_coord_label");
+ }
+ float ground_speed = 0.0f;
+ if(animation_element->QueryFloatAttribute("ground_speed",&ground_speed) == TIXML_NO_ATTRIBUTE){
+ ground_speed = _no_ground_speed;
+ }
+
+ float run_bounce = 0.0f;
+ if(animation_element->QueryFloatAttribute("run_bounce",&run_bounce) == TIXML_NO_ATTRIBUTE){
+ run_bounce = _no_ground_speed;
+ }
+
+ AnimationAssetRef new_ref;
+ if(extension(anim_path) == "xml"){
+ //SyncedAnimationGroups *SAGs = SyncedAnimationGroups::Instance();
+ SyncedAnimationGroupRef ref = Engine::Instance()->GetAssetManager()->LoadSync<SyncedAnimationGroup>(anim_path);
+ new_ref = AnimationAssetRef(&(*ref));
+ } else if(extension(anim_path) == "anm"){
+ //Animations *anims = Animations::Instance();
+ AnimationRef ref = Engine::Instance()->GetAssetManager()->LoadSync<Animation>(anim_path);
+ new_ref = AnimationAssetRef(&(*ref));
+ }
+
+ AddAnimation(new_ref, weight_coord, ground_speed, run_bounce, time_coord_label);
+
+ animation_element = animation_element->NextSiblingElement();
+ }
+ return kLoadOk;
+}
+
+const char* SyncedAnimationGroup::GetLoadErrorString() {
+ return "";
+}
+
+void SyncedAnimationGroup::Unload() {
+
+}
+
+void SyncedAnimationGroup::clear() {
+ animations.clear();
+ looping = false;
+ overshoot = false;
+ in_air = false;
+}
+
+bool SyncedAnimationGroup::IsLooping() const {
+ return looping;
+}
+
+SyncedAnimationGroup::SyncedAnimationGroup(AssetManager* owner, uint32_t asset_id) : AnimationAsset(owner,asset_id) {
+ clear();
+}
+
+void SyncedAnimationGroup::Reload() {
+ Load(path_,0x0);
+}
+
+void SyncedAnimationGroup::ReportLoad() {
+
+}
+
+float SyncedAnimationGroup::GetGroundSpeed( const BlendMap& blendmap ) const {
+ float curr_ground_speed = 0.5f;
+ {
+ BlendMap::const_iterator iter = blendmap.find("ground_speed");
+ if(iter != blendmap.end()){
+ curr_ground_speed = (*iter).second;
+ }
+ }
+ return curr_ground_speed;
+}
+
+std::vector<NormalizedAnimationEvent> SyncedAnimationGroup::GetEvents(int& anim_id, bool mirrored) const {
+ std::vector<NormalizedAnimationEvent> normalized_events;
+ for(unsigned i=0; i<animations.size(); i++){
+ std::vector<NormalizedAnimationEvent> new_events;
+ const AnimationAsset& animation = (*animations[i].animation);
+ new_events = animation.GetEvents(anim_id, mirrored);
+ normalized_events.insert(normalized_events.end(),
+ new_events.begin(),
+ new_events.end());
+ }
+
+ return normalized_events;
+}
+
+int SyncedAnimationGroup::GetActiveID( const BlendMap& blendmap, int &anim_id ) const {
+ float blend_coord = 0.0f;
+ BlendMap::const_iterator iter = blendmap.find(coord_label);
+ if(iter != blendmap.end()){
+ blend_coord = (*iter).second;
+ }
+ unsigned which_anim = NearestAnimation(blend_coord);
+
+ for(unsigned i=0; i<animations.size(); i++){
+ if(i==which_anim){
+ //LOGD << (*animations[i].animation).path << std::endl;
+ return (*animations[i].animation).GetActiveID(blendmap, anim_id);
+ } else {
+ (*animations[i].animation).GetActiveID(blendmap, anim_id);
+ }
+ }
+
+ return -1;
+}
+
+float SyncedAnimationGroup::AbsoluteTimeFromNormalized( float normalized_time ) const {
+ return animations[0].animation->AbsoluteTimeFromNormalized(normalized_time);
+}
+
+void SyncedAnimationGroup::ReturnPaths( PathSet& path_set ) {
+ path_set.insert("synced_animation "+path_);
+ for(unsigned i=0; i<animations.size(); ++i){
+ animations[i].animation->ReturnPaths(path_set);
+ }
+}
+
+AssetLoaderBase* SyncedAnimationGroup::NewLoader() {
+ return new FallbackAssetLoader<SyncedAnimationGroup>();
+}
+
+
+/* This old forced order disposal of assets will not be necessary anymore due to the new reference counting delete method in asset manager
+ * which will remove from the top, going down, removing remaining references.
+void SyncedAnimationGroups::Dispose() {
+ typedef std::list<SyncedAnimationGroup> SAGlist;
+ SAGlist &animations = assets_;
+ while(!animations.empty()){
+ bool deleted_something = false;
+ for(SAGlist::iterator iter = animations.begin(); iter != animations.end();){
+ SyncedAnimationGroup& group = *iter;
+ if(group.ref_count_ == 0){
+ iter = animations.erase(iter);
+ deleted_something = true;
+ } else {
+ ++iter;
+ }
+ }
+ if(!deleted_something) {
+ FatalError("Error", "Possible circular reference in Synced Animation Groups");
+ }
+ }
+ AssetContainer<SyncedAnimationGroup>::Dispose();
+}
+*/
diff --git a/Source/Asset/Asset/syncedanimation.h b/Source/Asset/Asset/syncedanimation.h
new file mode 100644
index 00000000..3ef9ded7
--- /dev/null
+++ b/Source/Asset/Asset/syncedanimation.h
@@ -0,0 +1,103 @@
+//-----------------------------------------------------------------------------
+// Name: syncedanimation.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/Asset/animation.h>
+#include <Asset/assettypes.h>
+#include <Internal/integer.h>
+
+class AssetManager;
+
+struct PrevNext {
+ int prev_index;
+ int next_index;
+};
+
+const float _no_ground_speed = -1.0f;
+
+struct SyncedAnimation {
+ AnimationAssetRef animation;
+ float blend_coord;
+ float ground_speed;
+ float run_bounce;
+ std::string time_coord_label;
+};
+
+class SyncedAnimationGroup: public AnimationAsset {
+ std::vector<SyncedAnimation> animations;
+ std::string coord_label;
+ bool looping;
+ bool overshoot;
+ bool in_air;
+
+ // Get the two animations closest to the blend_coord
+ PrevNext NearestAnimations(float blend_coord) const;
+
+ // Returns the weight of the next index. For example, returns
+ // 0 if blend_coord is exactly on the previous index, and
+ // returns 1 if it is exactly on the next index.
+ float GetInterpolationValue(const PrevNext &indices,
+ float blend_coord) const;
+ public:
+ SyncedAnimationGroup(AssetManager* owner, uint32_t asset_id);
+ void AddAnimation( AnimationAssetRef animation, float blend_coord , float ground_speed, float run_bounce, const std::string& time_coord_label );
+ void GetMatrices(float normalized_time,
+ AnimOutput &anim_output,
+ const AnimInput &anim_input) const;
+ void GetMatrices(float normalized_time,
+ AnimOutput &anim_output,
+ const AnimInput &anim_input,
+ float blend_coord) const;
+ void GetMatrices(int first,
+ int second,
+ float weight,
+ float normalized_time,
+ AnimOutput &anim_output,
+ const AnimInput &anim_input) const;
+ float GetFrequency(const BlendMap& blendmap) const;
+ float GetGroundSpeed(const BlendMap& blendmap) const;
+ float GetPeriod(const BlendMap& blendmap) const;
+ std::vector<NormalizedAnimationEvent> GetEvents(int &anim_id, bool mirrored) const;
+ int Load(const std::string &path, uint32_t load_flags);
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+ void Unload();
+ void Reload();
+ virtual void ReportLoad();
+ void clear();
+ int GetActiveID(const BlendMap& blendmap, int &anim_id) const;
+ bool IsLooping() const;
+ void CalcMirrored(bool cache, const std::string &skeleton_path);
+ unsigned NearestAnimation(float blend_coord) const;
+ float AbsoluteTimeFromNormalized( float normalized_time ) const;
+ void ReturnPaths(PathSet& path_set);
+
+ static AssetType GetType() { return SYNCED_ANIMATION_GROUP_ASSET; }
+ static const char* GetTypeName() { return "SYNCED_ANIMATION_GROUP_ASSET"; }
+ static bool AssetWarning() { return true; }
+
+ virtual AssetLoaderBase* NewLoader();
+};
+
+typedef AssetRef<SyncedAnimationGroup> SyncedAnimationGroupRef;
diff --git a/Source/Asset/Asset/texture.cpp b/Source/Asset/Asset/texture.cpp
new file mode 100644
index 00000000..f9889986
--- /dev/null
+++ b/Source/Asset/Asset/texture.cpp
@@ -0,0 +1,68 @@
+//-----------------------------------------------------------------------------
+// Name: texture.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "texture.h"
+
+#include <Asset/AssetLoader/fallbackassetloader.h>
+
+#include <Graphics/textureref.h>
+#include <Graphics/textures.h>
+
+TextureRef TextureAsset::GetTextureRef() {
+ return TextureRef(id);
+}
+
+TextureAsset::TextureAsset( AssetManager* owner, uint32_t asset_id ) : Asset(owner, asset_id) {
+ id = PhoenixTextures::INVALID_ID;
+}
+
+TextureAsset::~TextureAsset() {
+ Textures::Instance()->DecrementRefCount(id);
+}
+
+int TextureAsset::Load( const std::string &path, uint32_t load_flags ) {
+ id = Textures::Instance()->AllocateFreeSlot();
+ int load_texture_value = Textures::Instance()->loadTexture(path, id, load_flags);
+
+ if( load_texture_value != kLoadOk ) {
+ Textures::Instance()->DecrementRefCount(id);
+ id = PhoenixTextures::INVALID_ID;
+ }
+ return load_texture_value;
+}
+
+const char* TextureAsset::GetLoadErrorString() {
+ return "";
+}
+
+void TextureAsset::Unload() {
+
+}
+
+AssetLoaderBase* TextureAsset::NewLoader() {
+ return new FallbackAssetLoader<TextureAsset>();
+}
+
+unsigned int TextureAsset::GetTexID() {
+ return id;
+}
diff --git a/Source/Asset/Asset/texture.h b/Source/Asset/Asset/texture.h
new file mode 100644
index 00000000..f97af328
--- /dev/null
+++ b/Source/Asset/Asset/texture.h
@@ -0,0 +1,68 @@
+//-----------------------------------------------------------------------------
+// Name: texture.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+
+#include <Graphics/textureref.h>
+
+#include <vector>
+
+enum TextureAssetRefFlags{
+ PX_SRGB = (1<<0),
+ PX_NOMIPMAP = (1<<1),
+ PX_NOCONVERT = (1<<2),
+ PX_NOLIVEUPDATE = (1<<3),
+ PX_NOREDUCE = (1<<4)
+};
+
+class TextureAsset : public Asset
+{
+private:
+ friend class Textures;
+ unsigned int id;
+public:
+
+ TextureRef GetTextureRef();
+
+ TextureAsset( AssetManager* owner, uint32_t asset_id );
+ virtual ~TextureAsset();
+
+ static AssetType GetType() { return TEXTURE_ASSET; }
+ static const char* GetTypeName() { return "TEXTURE_ASSET"; }
+ static bool AssetWarning() { return true; }
+
+ int Load( const std::string &path, uint32_t load_flags );
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+ void Unload();
+
+ virtual void ReportLoad(){}
+ virtual AssetLoaderBase* NewLoader();
+
+ unsigned int GetTexID();
+};
+
+typedef AssetRef<TextureAsset> TextureAssetRef;
diff --git a/Source/Asset/Asset/texturedummy.cpp b/Source/Asset/Asset/texturedummy.cpp
new file mode 100644
index 00000000..d11db770
--- /dev/null
+++ b/Source/Asset/Asset/texturedummy.cpp
@@ -0,0 +1,33 @@
+//-----------------------------------------------------------------------------
+// Name: texturedummy.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "texturedummy.h"
+
+#include "Asset/AssetLoader/fallbackassetloader.h"
+
+TextureDummy::TextureDummy( AssetManager* owner, uint32_t asset_id ) : Asset(owner, asset_id) {
+}
+
+AssetLoaderBase* TextureDummy::NewLoader() {
+ return new FallbackAssetLoader<TextureDummy>();
+}
diff --git a/Source/Asset/Asset/texturedummy.h b/Source/Asset/Asset/texturedummy.h
new file mode 100644
index 00000000..60381f47
--- /dev/null
+++ b/Source/Asset/Asset/texturedummy.h
@@ -0,0 +1,48 @@
+//-----------------------------------------------------------------------------
+// Name: texturedummy.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+#include <Asset/assetloaderrors.h>
+
+#include <vector>
+
+class TextureDummy : public Asset
+{
+public:
+ TextureDummy( AssetManager* owner, uint32_t asset_id );
+ static AssetType GetType() { return TEXTURE_DUMMY_ASSET; }
+ static const char* GetTypeName() { return "TEXTURE_DUMMY_ASSET"; }
+ static bool AssetWarning() { return true; }
+
+ int Load( const std::string &path, uint32_t load_flags ) { return kLoadOk; };
+ const char* GetLoadErrorString() { return ""; }
+ const char* GetLoadErrorStringExtended() { return ""; }
+ void Unload() {};
+ virtual void ReportLoad(){}
+ virtual AssetLoaderBase* NewLoader();
+};
+
+typedef AssetRef<TextureDummy> TextureDummyRef;
diff --git a/Source/Asset/Asset/voicefile.cpp b/Source/Asset/Asset/voicefile.cpp
new file mode 100644
index 00000000..ce8b2c02
--- /dev/null
+++ b/Source/Asset/Asset/voicefile.cpp
@@ -0,0 +1,106 @@
+//-----------------------------------------------------------------------------
+// Name: voicefile.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "voicefile.h"
+
+#include <Asset/Asset/soundgroup.h>
+#include <Asset/AssetLoader/fallbackassetloader.h>
+
+#include <XML/xml_helper.h>
+#include <Logging/logdata.h>
+#include <Main/engine.h>
+
+#include <tinyxml.h>
+
+VoiceFile::VoiceFile(AssetManager* owner, uint32_t asset_id) : AssetInfo(owner,asset_id) {
+
+}
+
+void VoiceFile::Reload() {
+ Load(path_,0x0);
+}
+
+void VoiceFile::ReportLoad() {
+
+}
+
+int VoiceFile::Load( const std::string &path, uint32_t load_flags ) {
+ TiXmlDocument doc;
+ if( LoadXML(doc, path, "Voice") ) {
+ if( doc.Error() == false ) {
+ voice_paths.clear();
+ TiXmlHandle h_doc(&doc);
+ TiXmlHandle h_root = h_doc.FirstChildElement();
+ if( h_root.ToElement() ) {
+ TiXmlElement* field = h_root.ToElement()->FirstChildElement();
+ for( ; field; field = field->NextSiblingElement()) {
+ std::string field_str(field->Value());
+ voice_paths[field_str] = field->Attribute("path");
+ }
+ } else {
+ LOGE << "Error reading data out of XML state, missing root element in: " << path << std::endl;
+ return kLoadErrorCorruptFile;
+ }
+ } else {
+ LOGE << "Error parsing VoiceFile: " << path << ". Parser error: " << doc.ErrorDesc() << std::endl;
+ return kLoadErrorCouldNotOpenXML;
+ }
+ } else {
+ return kLoadErrorMissingFile;
+ }
+
+ return kLoadOk;
+}
+
+const char* VoiceFile::GetLoadErrorString() {
+ return "";
+}
+
+void VoiceFile::Unload() {
+
+}
+
+const std::string & VoiceFile::GetVoicePath( const std::string voice ) {
+ std::map<std::string, std::string>::iterator iter;
+ iter = voice_paths.find(voice);
+ if(iter != voice_paths.end()){
+ return iter->second;
+ } else {
+ return null_string;
+ }
+}
+
+void VoiceFile::ReturnPaths( PathSet &path_set )
+{
+ path_set.insert("voice "+path_);
+ for(std::map<std::string, std::string>::iterator iter = voice_paths.begin();
+ iter != voice_paths.end(); ++iter)
+ {
+ //SoundGroupInfoCollection::Instance()->ReturnRef(iter->second)->ReturnPaths(path_set);
+ Engine::Instance()->GetAssetManager()->LoadSync<SoundGroupInfo>(iter->second)->ReturnPaths(path_set);
+ }
+}
+
+AssetLoaderBase* VoiceFile::NewLoader() {
+ return new FallbackAssetLoader<VoiceFile>();
+}
diff --git a/Source/Asset/Asset/voicefile.h b/Source/Asset/Asset/voicefile.h
new file mode 100644
index 00000000..30089a46
--- /dev/null
+++ b/Source/Asset/Asset/voicefile.h
@@ -0,0 +1,55 @@
+//-----------------------------------------------------------------------------
+// Name: voicefile.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+
+#include <map>
+#include <string>
+
+class AssetManager;
+
+class VoiceFile : public AssetInfo {
+ std::map<std::string, std::string> voice_paths;
+ std::string null_string;
+
+public:
+ VoiceFile( AssetManager* owner, uint32_t asset_id );
+ const std::string &GetVoicePath(const std::string voice);
+ int Load(const std::string &path, uint32_t load_flags);
+ const char* GetLoadErrorString();
+ const char* GetLoadErrorStringExtended() { return ""; }
+ void Unload();
+ void Reload();
+ virtual void ReportLoad();
+ void ReturnPaths(PathSet &path_set);
+ static AssetType GetType() { return VOICE_FILE_ASSET; }
+ static const char* GetTypeName() { return "VOICE_FILE_ASSET"; }
+ static bool AssetWarning() { return true; }
+
+ virtual AssetLoaderBase* NewLoader();
+};
+
+typedef AssetRef<VoiceFile> VoiceFileRef;
diff --git a/Source/Asset/AssetLoader/fallbackassetloader.cpp b/Source/Asset/AssetLoader/fallbackassetloader.cpp
new file mode 100644
index 00000000..570125cf
--- /dev/null
+++ b/Source/Asset/AssetLoader/fallbackassetloader.cpp
@@ -0,0 +1,25 @@
+//-----------------------------------------------------------------------------
+// Name: fallbackassetloader.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "fallbackassetloader.h"
+
diff --git a/Source/Asset/AssetLoader/fallbackassetloader.h b/Source/Asset/AssetLoader/fallbackassetloader.h
new file mode 100644
index 00000000..93d45457
--- /dev/null
+++ b/Source/Asset/AssetLoader/fallbackassetloader.h
@@ -0,0 +1,114 @@
+//-----------------------------------------------------------------------------
+// Name: fallbackassetloader.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assetloaderbase.h>
+#include <Asset/assetloaderrors.h>
+
+#include <Utility/assert.h>
+
+template <typename TAsset>
+class FallbackAssetLoader : public AssetLoaderBase {
+private:
+ std::string path;
+ uint32_t load_flags;
+
+ TAsset* asset;
+ std::string error_message;
+ std::string error_message_extended;
+
+public:
+ FallbackAssetLoader() {
+
+ }
+
+ virtual ~FallbackAssetLoader() {
+
+ }
+
+ virtual void Initialize(const std::string& path, uint32_t load_flags, Asset* asset) {
+ this->load_flags = load_flags;
+ this->path = path;
+ this->asset = static_cast<TAsset*>(asset);
+ }
+
+ virtual const AssetLoaderStepType* GetAssetLoadSteps() const {
+ static const AssetLoaderStepType tl[] = { ASSET_LOADER_SYNC_JOB };
+ return tl;
+ }
+
+ virtual const int GetAssetLoadStepCount() const {
+ return 1;
+ }
+
+ virtual int DoLoadStep( const int step_id ) {
+ switch( step_id ) {
+ case 0:
+ asset->path_ = path;
+ int error_code = asset->Load(path,load_flags);
+ if( error_code == kLoadOk ) {
+ asset->ReportLoad();
+ } else {
+ error_message = asset->GetLoadErrorString();
+ error_message_extended = asset->GetLoadErrorStringExtended();
+ }
+ return error_code;
+ break;
+ }
+ return false;
+ }
+
+ virtual const char* GetLoadErrorString() {
+ return error_message.c_str();
+ }
+
+ virtual const char* GetLoadErrorStringExtended() {
+ return error_message_extended.c_str();
+ }
+
+ virtual const AssetLoaderStepType* GetAssetUnloadSteps() const {
+ static const AssetLoaderStepType tl[] = { ASSET_LOADER_SYNC_JOB };
+ return tl;
+ }
+
+ virtual const unsigned GetAssetUnloadStepCount() const {
+ return 1;
+ }
+
+ virtual bool DoUnloadStep( const int step_id ) {
+ switch( step_id )
+ {
+ case 0:
+ asset->path_ = path;
+ asset->Unload();
+ return true;
+ break;
+ }
+ return false;
+ }
+
+ virtual const char* GetTypeName() {
+ return TAsset::GetTypeName();
+ }
+};
diff --git a/Source/Asset/assetbase.cpp b/Source/Asset/assetbase.cpp
new file mode 100644
index 00000000..3623ab03
--- /dev/null
+++ b/Source/Asset/assetbase.cpp
@@ -0,0 +1,81 @@
+//-----------------------------------------------------------------------------
+// Name: assetbase.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "assetbase.h"
+
+#include <Asset/assetmanager.h>
+#include <Logging/logdata.h>
+
+#if defined(__GNUG__) && !defined(NDEBUG)
+#include <cxxabi.h>
+#endif
+
+#include <cstdlib>
+#include <cassert>
+
+Asset::Asset(AssetManager* owner, uint32_t asset_id):
+asset_id(asset_id),
+owner(owner) {
+
+}
+
+Asset::~Asset()
+{
+ /*
+ if( ref_count_ != 0 )
+ {
+ #if defined(__GNUG__) && !defined(NDEBUG)
+ int status;
+ char *realname;
+
+ // typeid
+ const std::type_info &ti = typeid(*this);
+
+ realname = abi::__cxa_demangle(ti.name(), 0, 0, &status);
+ LOGE << "For some reason the ref_count in asset \"" << realname << "\" isn't 0 it's: " << ref_count_ << std::endl;
+ OG_FREE(realname);
+ #else
+
+ LOGE << "For some reason the ref_count in asset \"" << GetName() << "\" isn't 0 it's: " << ref_count_ << std::endl;
+ #endif
+ }
+ */
+}
+
+void Asset::IncrementRefCount()
+{
+ if( owner ) {
+ owner->IncrementAsset(asset_id);
+ } else {
+ LOGE << "This asset was orphaned before it was destroyed, someone was holding a reference too long" << std::endl;
+ }
+}
+
+void Asset::DecrementRefCount()
+{
+ if( owner ) {
+ owner->DecrementAsset(asset_id);
+ } else {
+ LOGE << "This asset was orphaned before it was destroyed, someone was holding a reference too long" << std::endl;
+ }
+}
diff --git a/Source/Asset/assetbase.h b/Source/Asset/assetbase.h
new file mode 100644
index 00000000..a2730cba
--- /dev/null
+++ b/Source/Asset/assetbase.h
@@ -0,0 +1,86 @@
+//-----------------------------------------------------------------------------
+// Name: assetbase.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/integer.h>
+#include <Internal/filesystem.h>
+
+#include <Asset/assettypes.h>
+#include <Asset/assetloaderbase.h>
+
+#include <typeinfo>
+#include <string>
+
+template<class T>
+class AssetRef;
+
+class AssetManager;
+
+class Asset {
+private:
+ friend class AssetManager;
+
+public:
+ int32_t asset_id;
+ std::string path_;
+ AssetManager* owner;
+
+ virtual void ReportLoad() {}
+ virtual bool Valid() { return true; }
+ virtual const char* GetName() { return typeid(*this).name(); }
+
+ Asset(AssetManager* source, uint32_t asset_id);
+ virtual ~Asset();
+
+ void IncrementRefCount();
+ void DecrementRefCount();
+
+ //virtual static AssetType GetType() = 0;
+ //static AssetType GetAssetType() = 0 ;
+ virtual AssetLoaderBase* NewLoader() = 0;
+};
+
+///*
+// * An asset is an object which is read from disk or other data source and can be used within the game, like textures or models. This is what a datatype would implement.
+// */
+//class NAssetBase
+//{
+//
+//private:
+// friend class NAssetManager;
+//
+// /*
+// * Id for asset instance
+// */
+// uint32_t asset_id;
+// AssetType asset_type;
+// NAssetManager* owner;
+//
+// NAssetBase(NAssetManager* source, uint32_t asset_id, AssetType asset_type);
+// virtual ~NAssetBase();
+//
+// inline AssetType GetAssetType() {return asset_type;};
+// /* Returns a reference to the class which handles the loading and unloading of this particular asset */
+// /* Needs to be implemented on every variant of an asset, non virtual */
+//};
diff --git a/Source/Asset/assetdatabase.h b/Source/Asset/assetdatabase.h
new file mode 100644
index 00000000..d99b765c
--- /dev/null
+++ b/Source/Asset/assetdatabase.h
@@ -0,0 +1,31 @@
+//-----------------------------------------------------------------------------
+// Name: assetdatabase.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+/*
+ * This is the data referenced to by an NAsset
+ */
+class NAssetDataBase
+{
+
+};
diff --git a/Source/Asset/assetinfobase.cpp b/Source/Asset/assetinfobase.cpp
new file mode 100644
index 00000000..882d8722
--- /dev/null
+++ b/Source/Asset/assetinfobase.cpp
@@ -0,0 +1,28 @@
+//-----------------------------------------------------------------------------
+// Name: assetinfobase.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "assetinfobase.h"
+
+AssetInfo::AssetInfo( AssetManager* owner, uint32_t asset_id ) : Asset( owner, asset_id ) {
+
+}
diff --git a/Source/Asset/assetinfobase.h b/Source/Asset/assetinfobase.h
new file mode 100644
index 00000000..94fd2343
--- /dev/null
+++ b/Source/Asset/assetinfobase.h
@@ -0,0 +1,37 @@
+//-----------------------------------------------------------------------------
+// Name: assetinfobase.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/path_set.h>
+
+#include <Asset/assetbase.h>
+#include <Asset/assetmanager.h>
+
+class AssetInfo : public Asset {
+public:
+ AssetInfo( AssetManager* owner, uint32_t asset_id );
+ virtual void Print(){}
+ virtual void SetDefaults(){}
+ virtual void ReturnPaths(PathSet &path_set){}
+};
diff --git a/Source/Asset/assetloaderbase.cpp b/Source/Asset/assetloaderbase.cpp
new file mode 100644
index 00000000..36429cf4
--- /dev/null
+++ b/Source/Asset/assetloaderbase.cpp
@@ -0,0 +1,32 @@
+//-----------------------------------------------------------------------------
+// Name: assetloaderbase.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "assetloaderbase.h"
+
+AssetLoaderBase::AssetLoaderBase() {
+
+}
+
+AssetLoaderBase::~AssetLoaderBase() {
+
+}
diff --git a/Source/Asset/assetloaderbase.h b/Source/Asset/assetloaderbase.h
new file mode 100644
index 00000000..5b6dc2f9
--- /dev/null
+++ b/Source/Asset/assetloaderbase.h
@@ -0,0 +1,63 @@
+//-----------------------------------------------------------------------------
+// Name: assetloaderbase.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/filesystem.h>
+#include <Internal/integer.h>
+
+class AssetManager;
+class Asset;
+
+enum AssetLoaderStepType
+{
+ ASSET_LOADER_DISK_IO, //Perform job in IO thread
+ ASSET_LOADER_ASYNC_JOB, //Perform job in asynchronous work thread
+ ASSET_LOADER_SYNC_JOB //Perform job in main thread (like OpenGL calls)
+};
+
+/*
+ * An asset loader is responsible for loading, interpreting and potentially moving data for an asset.
+ */
+class AssetLoaderBase
+{
+public:
+ AssetLoaderBase();
+ virtual ~AssetLoaderBase();
+
+ virtual void Initialize(const std::string& path, uint32_t load_flags, Asset* asset) = 0;
+
+ virtual const AssetLoaderStepType* GetAssetLoadSteps() const = 0;
+ virtual const int GetAssetLoadStepCount() const = 0;
+
+ virtual int DoLoadStep( const int step_id ) = 0;
+
+ virtual const AssetLoaderStepType* GetAssetUnloadSteps() const = 0;
+ virtual const unsigned GetAssetUnloadStepCount() const = 0;
+
+ virtual bool DoUnloadStep( const int step_id ) = 0;
+
+ virtual const char* GetTypeName() = 0;
+ virtual const char* GetLoadErrorString() = 0;
+ virtual const char* GetLoadErrorStringExtended() = 0;
+};
diff --git a/Source/Asset/assetloaderrors.cpp b/Source/Asset/assetloaderrors.cpp
new file mode 100644
index 00000000..286f0605
--- /dev/null
+++ b/Source/Asset/assetloaderrors.cpp
@@ -0,0 +1,55 @@
+//-----------------------------------------------------------------------------
+// Name: assetloaderrors.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "assetloaderrors.h"
+
+const char* GetLoadErrorString(int& v) {
+ switch(v) {
+ case kLoadOk:
+ return "Load ok";
+ case kLoadErrorNoFile:
+ return "Path value is empty";
+ case kLoadErrorMissingFile:
+ return "File doesn't exist on disk";
+ case kLoadErrorCouldNotOpen:
+ return "Could not open file";
+ case kLoadErrorCouldNotRead:
+ return "Could not read from opened file";
+ case kLoadErrorCorruptFile:
+ return "File read has unexpected/corrupt data";
+ case kLoadErrorCouldNotOpenXML:
+ return "Could not open file as XML";
+ case kLoadErrorGeneralFileError:
+ return "File data error";
+ case kLoadErrorIncompleteXML:
+ return "Incomplete data, missing XML elements.";
+ case kLoadErrorIncompatibleFileVersion:
+ return "File appears to be of an unsupported format version.";
+ case kLoadErrorMissingSubFile:
+ return "Missing file that top file requires.";
+ case kLoadErrorInvalidFileEnding:
+ return "Loader demands that the file has a specific file-ending in the path";
+ default:
+ return "Unknown/Undefined error code";
+ }
+}
diff --git a/Source/Asset/assetloaderrors.h b/Source/Asset/assetloaderrors.h
new file mode 100644
index 00000000..b24c0f45
--- /dev/null
+++ b/Source/Asset/assetloaderrors.h
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------------
+// Name: assetloaderrors.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+const int kLoadOk = 0; //Load succesful
+const int kLoadErrorNoFile = 1; //No file specified
+const int kLoadErrorMissingFile = 2; //File given does not exist on disk
+const int kLoadErrorCouldNotOpen = 3; //fopen() failed
+const int kLoadErrorCouldNotRead = 4; //fread() failed
+const int kLoadErrorCorruptFile = 5; //Internal hash mismatch
+const int kLoadErrorCouldNotOpenXML = 6; //XML document could not be parsed/loaded
+const int kLoadErrorGeneralFileError = 7; // Error with loading data from file, unspecific
+const int kLoadErrorIncompleteXML = 8; //Missing expected values in file
+const int kLoadErrorIncompatibleFileVersion = 9; //File version mismatch from expectation or capability
+const int kLoadErrorMissingSubFile = 10;//File referenced to by the main file isn't accessible
+const int kLoadErrorInvalidFileEnding = 11;//Loader demands that the file has a specific file-ending in the path
+
+const char* GetLoadErrorString(int& v);
diff --git a/Source/Asset/assetmanager.cpp b/Source/Asset/assetmanager.cpp
new file mode 100644
index 00000000..5b2b0b4f
--- /dev/null
+++ b/Source/Asset/assetmanager.cpp
@@ -0,0 +1,541 @@
+//-----------------------------------------------------------------------------
+// Name: assetmanager.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "assetmanager.h"
+
+#include <Asset/Asset/actorfile.h>
+#include <Asset/Asset/ambientsounds.h>
+#include <Asset/Asset/animation.h>
+#include <Asset/Asset/animationeffect.h>
+#include <Asset/Asset/attachmentasset.h>
+#include <Asset/Asset/attacks.h>
+#include <Asset/Asset/averagecolorasset.h>
+#include <Asset/Asset/character.h>
+#include <Asset/Asset/decalfile.h>
+#include <Asset/Asset/fzx_file.h>
+#include <Asset/Asset/hotspotfile.h>
+#include <Asset/Asset/image_sampler.h>
+#include <Asset/Asset/item.h>
+#include <Asset/Asset/levelset.h>
+#include <Asset/Asset/lipsyncfile.h>
+#include <Asset/Asset/material.h>
+#include <Asset/Asset/objectfile.h>
+#include <Asset/Asset/particletype.h>
+#include <Asset/Asset/reactions.h>
+#include <Asset/Asset/skeletonasset.h>
+#include <Asset/Asset/songlistfile.h>
+#include <Asset/Asset/soundgroup.h>
+#include <Asset/Asset/soundgroupinfo.h>
+#include <Asset/Asset/spawnpointinfo.h>
+#include <Asset/Asset/syncedanimation.h>
+#include <Asset/Asset/texturedummy.h>
+#include <Asset/Asset/voicefile.h>
+#include <Asset/Asset/levelinfo.h>
+#include <Asset/Asset/filehash.h>
+#include <Asset/assetloaderrors.h>
+
+#include <Logging/logdata.h>
+#include <Utility/strings.h>
+
+#include <cstdlib>
+#include <stdint.h>
+#include <stdio.h>
+#include <set>
+#include <limits>
+
+#ifdef MEASURE_LOADS
+AssetLoadTime::AssetLoadTime(uint64_t time, uint32_t time_ms, const char* asset_type_name, AssetType asset_type ):
+time(time),
+time_ms(time_ms),
+asset_type_name(asset_type_name),
+asset_type(asset_type) {
+}
+#endif
+
+AssetManager::AssetManager():
+asset_id_counter(1),
+asset_list(NULL),
+asset_list_count(0),
+asset_list_size(0),
+load_id_counter(1),
+loading_instances(256,128),
+io_thread_manager(1),
+loading_thread_manager(1),
+load_warning_activated(false),
+asset_warning_count(0),
+asset_hold_count(0)
+{
+
+LOG_ASSERT((int)ASSET_TYPE_FINAL < MAX_ASSET_TYPE_COUNT);
+
+memset(asset_type_count,0,sizeof(int)*MAX_ASSET_TYPE_COUNT);
+
+}
+
+AssetManager::~AssetManager() {
+ free(asset_list);
+ asset_list = NULL;
+ asset_list_count = 0;
+ asset_list_size = 0;
+}
+
+void AssetManager::Initialize() {
+
+}
+
+bool AssetManager::UnloadUnreferenced( int limit, unsigned int required_attempts ) {
+ int removes = 0;
+ for( int i = (int)asset_list_count-1; i >= 0; i-- )
+ {
+ if( asset_list[i].count <= 0 )
+ {
+ if( asset_list[i].hold_load_mask == 0x0 )
+ {
+ if( asset_list[i].deallocate_attempts >= required_attempts )
+ {
+ //LOGI << "Unloading unreferenced " << asset_list[i].asset_type_name << std::endl;
+ Asset* asset = asset_list[i].asset;
+ AssetLoaderBase * loader = asset->NewLoader();
+ loader->Initialize(asset_list[i].rel_name,asset_list[i].load_flags, asset);
+ for( unsigned k = 0; k < loader->GetAssetUnloadStepCount(); k++ )
+ {
+ loader->DoUnloadStep(k);
+ }
+ delete loader;
+ DeallocateAsset( asset_list[i].id, i );
+ removes++;
+ }
+ else
+ {
+ asset_list[i].deallocate_attempts++;
+ }
+ }
+ }
+
+ if( removes >= limit && limit > 0 )
+ {
+ break;
+ }
+ }
+
+ return removes > 0;
+}
+
+uint32_t AssetManager::GetAsset( uint32_t asset_id ) {
+ for( unsigned i = 0; i < asset_list_count; i++ ) {
+ if( asset_list[i].id == asset_id ) {
+ return i;
+ }
+ }
+ return std::numeric_limits<uint32_t>::max();
+}
+
+void AssetManager::DeallocateAsset( uint32_t asset_id, uint32_t starting_id ) {
+ bool shifting = false;
+ for( unsigned int i = starting_id; i < asset_list_count; i++ ) {
+ if( shifting ) { //Moving all other assets one step
+ asset_list[i-1] = asset_list[i];
+ } else { //Searching
+ if( asset_list[i].id == asset_id ) {
+ if( asset_list[i].load_warning ) {
+ asset_warning_count--;
+ }
+ if(asset_list[i].hold_load_mask != 0x0 ) {
+ asset_hold_count--;
+ }
+
+ delete asset_list[i].asset;
+ asset_type_count[(int)asset_list[i].type]--;
+ shifting = true;
+ }
+ }
+ }
+
+ if( shifting ) { //We removed something
+ asset_list_count--;
+ }
+}
+
+void AssetManager::Dispose() {
+ DumpLoadWarningData("asset_manager_warnings.xml");
+#ifdef MEASURE_LOADS
+ DumpTimingData();
+#endif
+ LOGI << "Releasing hold on all preloaded assets" << std::endl;
+ ReleaseAssetHoldLoad(HOLD_LOAD_MASK_PRELOAD);
+ LOGI << "Disposing asset manager" << std::endl;
+ while(UnloadUnreferenced(0,0)){};
+
+ if( asset_list_count > 0 ) {
+ LOGW << "Following assets are still loaded after assetmanager is called for disposal" << std::endl;
+
+ for( unsigned int i = 0; i < asset_list_count; i++ ) {
+ LOGW << "References: " << asset_list[i].count << " path: " << asset_list[i].rel_name << std::endl;;
+ }
+
+ LOGW << "Marking them as orphans" << std::endl;
+ for( unsigned int i = 0; i < asset_list_count; i++ ) {
+ asset_list[i].asset->owner = NULL;
+ }
+ }
+}
+
+size_t GetNextAssetListSize( size_t current_size ) {
+ return current_size + 1024;
+}
+
+void AssetManager::ReallocAssetInstanceCounter( size_t new_size ) {
+ asset_list_size = new_size;
+ asset_list = static_cast<AssetInstanceCounter*>(realloc( asset_list, asset_list_size * sizeof(AssetInstanceCounter)));
+}
+
+void AssetManager::IncrementAsset( uint32_t asset_id ) {
+ for( size_t i = 0; i < asset_list_count; i++ ) {
+ if( asset_list[i].id == asset_id ) {
+ asset_list[i].deallocate_attempts = 0;
+ asset_list[i].count++;
+ return;
+ }
+ }
+ LOGW << "Unable to find asset of id for increment: " << asset_id << std::endl;
+}
+
+void AssetManager::DecrementAsset( uint32_t asset_id ) {
+ for( size_t i = 0; i < asset_list_count; i++ ) {
+ if( asset_list[i].id == asset_id ) {
+ asset_list[i].count--;
+ if( asset_list[i].count <= 0 ) {
+ //LOGI << "Asset count is " << asset_list[i].count << " " << asset_list[i].asset_type_name << " " << asset_list[i].asset->path_ << std::endl;
+ }
+ return;
+ }
+ }
+ LOGW << "Unable to find asset of id for decrement: " << asset_id << std::endl;
+}
+
+void AssetManager::LoadSync( AssetType type, const std::string& str, uint32_t load_flags, uint32_t hold_load_mask ) {
+ switch(type) {
+#ifndef OGDA
+ case SKELETON_ASSET:
+ LoadSync<SkeletonAsset>(str,load_flags,hold_load_mask);
+ break;
+ case SYNCED_ANIMATION_GROUP_ASSET:
+ LoadSync<SyncedAnimationGroup>(str,load_flags,hold_load_mask);
+ break;
+ case ANIMATION_ASSET:
+ LoadSync<Animation>(str,load_flags,hold_load_mask);
+ break;
+ case OBJECT_FILE_ASSET:
+ LoadSync<ObjectFile>(str,load_flags,hold_load_mask);
+ break;
+ case MATERIAL_ASSET:
+ LoadSync<Material>(str,load_flags,hold_load_mask);
+ break;
+ case ATTACK_ASSET:
+ LoadSync<Attack>(str,load_flags,hold_load_mask);
+ break;
+ case VOICE_FILE_ASSET:
+ LoadSync<VoiceFile>(str,load_flags,hold_load_mask);
+ break;
+ case ANIMATION_EFFECT_ASSET:
+ LoadSync<AnimationEffect>(str,load_flags,hold_load_mask);
+ break;
+ case SOUND_GROUP_INFO_ASSET:
+ LoadSync<SoundGroupInfo>(str,load_flags,hold_load_mask);
+ break;
+ case CHARACTER_ASSET:
+ LoadSync<Character>(str,load_flags,hold_load_mask);
+ break;
+ case REACTION_ASSET:
+ LoadSync<Reaction>(str,load_flags,hold_load_mask);
+ break;
+ case DECAL_FILE_ASSET:
+ LoadSync<DecalFile>(str,load_flags,hold_load_mask);
+ break;
+ case HOTSPOT_FILE_ASSET:
+ LoadSync<HotspotFile>(str,load_flags,hold_load_mask);
+ break;
+ case AMBIENT_SOUND_ASSET:
+ LoadSync<AmbientSound>(str,load_flags,hold_load_mask);
+ break;
+ case ACTOR_FILE_ASSET:
+ LoadSync<ActorFile>(str,load_flags,hold_load_mask);
+ break;
+ case ITEM_ASSET:
+ LoadSync<Item>(str,load_flags,hold_load_mask);
+ break;
+ case LEVEL_SET_ASSET:
+ LoadSync<LevelSet>(str,load_flags,hold_load_mask);
+ break;
+ case IMAGE_SAMPLER_ASSET:
+ LoadSync<ImageSampler>(str,load_flags,hold_load_mask);
+ break;
+ case SOUND_GROUP_ASSET:
+ LoadSync<SoundGroup>(str,load_flags,hold_load_mask);
+ break;
+ case PARTICLE_TYPE_ASSET:
+ LoadSync<ParticleType>(str,load_flags,hold_load_mask);
+ break;
+ case AVERAGE_COLOR_ASSET:
+ LoadSync<AverageColor>(str,load_flags,hold_load_mask);
+ break;
+ case FZX_ASSET:
+ LoadSync<FZXAsset>(str,load_flags,hold_load_mask);
+ break;
+ case ATTACHMENT_ASSET:
+ LoadSync<Attachment>(str,load_flags,hold_load_mask);
+ break;
+ case LIP_SYNC_FILE_ASSET:
+ LoadSync<LipSyncFile>(str,load_flags,hold_load_mask);
+ break;
+ case TEXTURE_DUMMY_ASSET:
+ LoadSync<TextureDummy>(str,load_flags,hold_load_mask);
+ break;
+ case TEXTURE_ASSET:
+ LoadSync<TextureAsset>(str,load_flags,hold_load_mask);
+ break;
+ case LEVEL_INFO_ASSET:
+ LoadSync<LevelInfoAsset>(str,load_flags,hold_load_mask);
+ break;
+ case FILE_HASH_ASSET:
+ LoadSync<FileHashAsset>(str,load_flags,hold_load_mask);
+ break;
+ case UNKNOWN:
+ LOGE << "Cannot load UNKNOWN asset type" << std::endl;
+ break;
+ case ASSET_TYPE_FINAL:
+ LOGE << "Cannot load ASSET_TYPE_FINAL, it's not an actual asset type, just a marker in the enum" << std::endl;
+ break;
+#else
+ default:
+ //LOGE << "Unahandled asset type " << type << " in LoadSync using AssetType value" << std::endl;
+ break;
+#endif
+ }
+}
+
+void AssetManager::Update() {
+ UnloadUnreferenced(1,60*20);
+
+ std::vector<uint32_t> remove_loading_instance_indexes;
+
+ //Pop all finished async jobs.
+ while( io_thread_manager.queue_out.Count() > 0 ) {
+ AssetLoaderThreadedInstance alti = io_thread_manager.queue_out.Pop();
+ for( unsigned i = 0; i < loading_instances.Count(); i++ ) {
+ if( alti.load_id == loading_instances[i].load_id ) {
+ loading_instances[i].in_queue = false;
+ loading_instances[i].load_step_result = alti.return_state;
+ }
+ }
+ }
+ while( loading_thread_manager.queue_out.Count() > 0 ) {
+ AssetLoaderThreadedInstance alti = loading_thread_manager.queue_out.Pop();
+ for( unsigned i = 0; i < loading_instances.Count(); i++ ) {
+ if( alti.load_id == loading_instances[i].load_id ) {
+ loading_instances[i].in_queue = false;
+ loading_instances[i].load_step_result = alti.return_state;
+ }
+ }
+ }
+
+ //Check for errors and update all loading instances
+ for( unsigned i = 0; i < loading_instances.Count(); i++ ) {
+ LoadingInstance& loading_instance = loading_instances[i];
+ if( loading_instance.in_queue == false ) {
+ if( loading_instance.load_step_result != kLoadOk ) {
+ int ret = DisplayFormatError( _ok_cancel_retry,
+ true,
+ "Asset Load Error",
+ "Error loading asset type: %s from path: %s.\n\nReason: %s, specifically: \"%s\"",
+ loading_instance.loader->GetTypeName(),
+ asset_list[GetAsset(loading_instance.asset_id)].rel_name,
+ GetLoadErrorString(loading_instance.load_step_result),
+ loading_instance.loader->GetLoadErrorString()
+ );
+
+ if(ret == _retry) {
+ loading_instance.next_step_id = 0;
+ loading_instance.load_step_result = kLoadOk;
+ } else {
+ loading_instance.next_step_id = loading_instance.loader->GetAssetLoadStepCount();
+ }
+ }
+
+ if( (int)loading_instance.next_step_id < loading_instance.loader->GetAssetLoadStepCount() ) {
+ AssetLoaderStepType asset_type = loading_instance.loader->GetAssetLoadSteps()[loading_instance.next_step_id];
+
+ if(asset_type == ASSET_LOADER_DISK_IO ) {
+ loading_instance.in_queue = true;
+ io_thread_manager.queue_in.Push(
+ AssetLoaderThreadedInstance(
+ loading_instance.loader,
+ loading_instance.load_id,
+ loading_instance.next_step_id
+ )
+ );
+ }
+ if(asset_type == ASSET_LOADER_ASYNC_JOB ) {
+ loading_instance.in_queue = true;
+ loading_thread_manager.queue_in.Push(
+ AssetLoaderThreadedInstance(
+ loading_instance.loader,
+ loading_instance.load_id,
+ loading_instance.next_step_id
+ )
+ );
+ }
+ if(asset_type == ASSET_LOADER_SYNC_JOB ) {
+ loading_instance.load_step_result = loading_instance.loader->DoLoadStep( loading_instance.next_step_id );
+ }
+ loading_instance.next_step_id++;
+ } else if( (int)loading_instance.next_step_id >= loading_instance.loader->GetAssetLoadStepCount() ) {
+ uint32_t asset_index = GetAsset(loading_instance.asset_id);
+ asset_list[asset_index].load_state = ASSET_LOADING_LOADED;
+
+ if( loading_instance.load_step_result == kLoadOk ) {
+ loading_instance.caller->AssetLoadedCallback( loading_instance.load_id, loading_instance.asset_ref );
+ } else {
+ loading_instance.caller->AssetFailedLoadCallback( loading_instance.load_id, loading_instance.asset_ref );
+ }
+
+ delete loading_instance.asset_ref;
+ loading_instance.asset_ref = NULL;
+
+ remove_loading_instance_indexes.push_back(i);
+ }
+ }
+ }
+
+ loading_instances.Deallocate(remove_loading_instance_indexes);
+}
+
+void AssetManager::ReleaseAssetHoldLoad( uint32_t hold_load_mask ) {
+ for( unsigned i = 0; i < asset_list_count; i++ ) {
+ if( asset_list[i].hold_load_mask != 0x0 && (asset_list[i].hold_load_mask & ~hold_load_mask) == 0x0) {
+ asset_hold_count--;
+ }
+ asset_list[i].hold_load_mask &= ~hold_load_mask;
+ }
+}
+
+void AssetManager::SetLoadWarningMode(bool val, const char* mode_name, const char* level_name) {
+ load_warning_activated = val;
+ strscpy(load_warning_mode_name,mode_name,32);
+ strscpy(load_warning_level_name,level_name,256);
+}
+
+void AssetManager::DumpLoadWarningData(const char* destination) {
+ LOGI << "Dumping asset manager warning data to " << destination << std::endl;
+ if( load_warning_instances.asset_warnings.size() > 0 ) {
+ std::string path = AssemblePath(GetWritePath(CoreGameModID),destination);
+ if(CheckFileAccess(path.c_str())) {
+ load_warning_instances.Load(path);
+ }
+ load_warning_instances.Save(path);
+ load_warning_instances.Clear();
+ }
+}
+
+size_t AssetManager::GetLoadedAssetCount() {
+ return asset_list_count;
+}
+
+int AssetManager::GetAssetTypeCount( AssetType asset ) {
+ return asset_type_count[(int)asset];
+}
+
+int AssetManager::GetAssetWarningCount() {
+ return asset_warning_count;
+}
+
+int AssetManager::GetAssetHoldCount() {
+ return asset_hold_count;
+}
+
+const char* AssetManager::GetAssetName(uint32_t index) {
+ if( index >= 0 && index < asset_list_count ) {
+ return asset_list[index].rel_name;
+ }
+ return "";
+}
+
+AssetType AssetManager::GetAssetType(uint32_t index) {
+ if( index >= 0 && index < asset_list_count ) {
+ return (AssetType)asset_list[index].type;
+ }
+ return (AssetType)0;
+}
+
+uint32_t AssetManager::GetAssetHoldFlags(uint32_t index) {
+ if( index >= 0 && index < asset_list_count ) {
+ return asset_list[index].hold_load_mask;
+ }
+ return (uint32_t)0;
+}
+
+uint16_t AssetManager::GetAssetRefCount(uint32_t index) {
+ if( index >= 0 && index < asset_list_count ) {
+ return asset_list[index].count;
+ }
+ return (uint16_t)0;
+}
+
+bool AssetManager::GetAssetLoadWarning(uint32_t index) {
+ if( index >= 0 && index < asset_list_count ) {
+ return asset_list[index].load_warning;
+ }
+ return false;
+}
+
+#ifdef MEASURE_LOADS
+void AssetManager::DumpTimingData()
+{
+ std::set<AssetType> types;
+
+ for( unsigned int i = 0; i < asset_load_times.size(); i++ ) {
+ std::cout << asset_load_times[i].time << " " << asset_load_times[i].time_ms << "ms " << asset_load_times[i].asset_type_name << std::endl;
+ types.insert(asset_load_times[i].asset_type);
+ }
+
+ std::cout << "Average data" << std::endl;
+
+ std::set<AssetType>::iterator typeit = types.begin();
+ for( ;typeit != types.end(); typeit++ ) {
+ int count=0;
+ uint64_t sum=0;
+ uint32_t sum_ms=0;
+ const char* name;
+
+ for( unsigned int i = 0; i < asset_load_times.size(); i++ ) {
+ if( asset_load_times[i].asset_type == *typeit ) {
+ sum += asset_load_times[i].time;
+ sum_ms += asset_load_times[i].time_ms;
+ count++;
+ name = asset_load_times[i].asset_type_name;
+ }
+ }
+ std::cout << sum/count << " " << (float)sum_ms/(float)count << "ms " << name << std::endl;
+ }
+}
+#endif
diff --git a/Source/Asset/assetmanager.h b/Source/Asset/assetmanager.h
new file mode 100644
index 00000000..b5390ecd
--- /dev/null
+++ b/Source/Asset/assetmanager.h
@@ -0,0 +1,408 @@
+//-----------------------------------------------------------------------------
+// Name: assetmanager.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assetref.h>
+#include <Asset/assetmanager.h>
+#include <Asset/assetloaderbase.h>
+#include <Asset/assettypes.h>
+#include <Asset/assetmanagerthreadhandler.h>
+#include <Asset/assetloaderrors.h>
+
+#include <Internal/common.h>
+#include <Internal/datemodified.h>
+#include <Internal/error.h>
+
+#include <Logging/logdata.h>
+#include <Utility/timing.h>
+#include <Threading/sdl_wrapper.h>
+#include <Utility/simple_vector.h>
+#include <XML/Parsers/assetloadwarningparser.h>
+
+#include <list>
+#include <string>
+#include <limits>
+
+#define HOLD_LOAD_MASK_PRELOAD (1UL >> 0)
+
+enum AssetLoadingState {
+ ASSET_LOADING_UNLOADED,
+ ASSET_LOADING_LOADING,
+ ASSET_LOADING_LOADED
+};
+
+struct AssetInstanceCounter {
+ uint32_t id;
+ uint32_t hold_load_mask;
+ uint32_t load_flags;
+ uint16_t count;
+ int64_t modified;
+ uint32_t deallocate_attempts;
+ AssetType type;
+ const char* asset_type_name;
+ Asset* asset;
+ AssetLoadingState load_state;
+ char rel_name[kPathSize];
+ bool load_warning;
+};
+
+class AssetLoadedCallbackInterface {
+public:
+ virtual void AssetLoadedCallback( uint32_t loadid, AssetRefBase* ref ) = 0;
+ virtual void AssetFailedLoadCallback( uint32_t loadid, AssetRefBase* ref ) = 0;
+};
+
+struct LoadingInstance {
+ uint32_t load_id;
+ uint32_t asset_id;
+ AssetLoaderBase* loader;
+ AssetLoadedCallbackInterface* caller;
+ bool in_queue;
+ uint32_t next_step_id;
+ int load_step_result;
+ AssetRefBase* asset_ref;
+};
+
+#ifdef MEASURE_LOADS
+struct AssetLoadTime {
+ AssetLoadTime(uint64_t time, uint32_t time_ms, const char* asset_type_name, AssetType asset_type );
+
+ uint64_t time_ms;
+ uint64_t time;
+ const char* asset_type_name;
+ AssetType asset_type;
+};
+#endif
+
+size_t GetNextAssetListSize( size_t current_size );
+
+/*
+ * Manager for all assets, keeps track of all assets.
+ */
+class AssetManager {
+private:
+#ifdef MEASURE_LOADS
+ std::vector<AssetLoadTime> asset_load_times;
+#endif
+ bool load_warning_activated;
+ char load_warning_mode_name[32];
+ char load_warning_level_name[256];
+ AssetLoadWarningParser load_warning_instances;
+
+ uint32_t asset_id_counter;
+
+ AssetInstanceCounter* asset_list;
+ size_t asset_list_count;
+ size_t asset_list_size;
+
+ void ReallocAssetInstanceCounter( size_t new_size );
+
+ uint32_t load_id_counter;
+
+ SimpleVector<LoadingInstance> loading_instances;
+
+ AssetManagerThreadHandler io_thread_manager;
+ AssetManagerThreadHandler loading_thread_manager;
+
+ static const int MAX_ASSET_TYPE_COUNT = 32;
+ int asset_type_count[MAX_ASSET_TYPE_COUNT];
+
+ int asset_warning_count;
+ int asset_hold_count;
+
+ template <typename TAsset>
+ uint32_t AllocateAsset( const std::string& rel_path, uint32_t load_flags ) {
+ for( uint32_t id = 0; id < asset_list_count; id++ ) {
+ if(asset_list[id].type == TAsset::GetType() && asset_list[id].load_flags == load_flags && strcmp(asset_list[id].rel_name, rel_path.c_str()) == 0) {
+ return id;
+ }
+ }
+
+ if( asset_list_count == asset_list_size ) {
+ ReallocAssetInstanceCounter(GetNextAssetListSize(asset_list_size));
+ } else if( asset_list_count > asset_list_size ) {
+ LOGF << "Asset list count is larger than asset list size, this should never be possible" << std::endl;
+ }
+
+ bool has_warning = false;
+ if( load_warning_activated && TAsset::AssetWarning() ) {
+ has_warning = true;
+ //LOGW << "Loading asset: \"" << rel_path << "\" " << TAsset::GetType() << " " << TAsset::GetTypeName() << " while in load warning mode. State: " << load_warning_mode_name << std::endl;
+ load_warning_instances.AddAssetWarning(AssetLoadWarningParser::AssetWarning(rel_path, load_flags, TAsset::GetTypeName(), load_warning_level_name));
+ }
+
+ uint32_t asset_id = asset_id_counter++;
+ TAsset *asset = new TAsset(this,asset_id);
+
+ asset_list[asset_list_count].asset = asset;
+ asset_list[asset_list_count].hold_load_mask = 0x0;
+ asset_list[asset_list_count].load_flags = load_flags;
+ asset_list[asset_list_count].type = TAsset::GetType();
+ asset_list[asset_list_count].asset_type_name = TAsset::GetTypeName();
+ asset_list[asset_list_count].id = asset_id;
+ asset_list[asset_list_count].count = 0;
+ asset_list[asset_list_count].load_state = ASSET_LOADING_UNLOADED;
+ asset_list[asset_list_count].load_warning = has_warning;
+ strncpy(asset_list[asset_list_count].rel_name, rel_path.c_str(), kPathSize);
+
+ asset_type_count[(int)TAsset::GetType()]++;
+
+ if( asset_list[asset_list_count].load_warning ) {
+ asset_warning_count++;
+ }
+
+ return (uint32_t) asset_list_count++;
+ }
+
+ uint32_t GetAsset( uint32_t asset_id );
+
+ void DeallocateAsset( uint32_t asset_id, uint32_t starting_id );
+
+public:
+ //Increment reference count to asset, indicating there are more valid references in existance.
+ void IncrementAsset( uint32_t asset_id );
+
+ //Decrement reference count to asset, marking it for deletion during update if it reaches zero.
+ void DecrementAsset( uint32_t asset_id );
+
+ void Initialize();
+
+ bool UnloadUnreferenced( int limit, unsigned int required_attempts );
+
+ template<typename TAsset>
+ void Reload() {
+ LOGI << "Reloading " << TAsset::GetTypeName() << std::endl;
+ int64_t new_modified;
+ for( size_t i = 0; i < asset_list_count; i++ ) {
+ if( asset_list[i].type == TAsset::GetType() ) {
+ TAsset* asset = static_cast<TAsset*>(asset_list[i].asset);
+ Path file_path = FindFilePath(asset->path_, kDataPaths | kModPaths | kAbsPath, false);
+ new_modified = GetDateModifiedInt64(file_path.GetFullPath());
+ if(asset_list[i].modified != new_modified) {
+ asset_list[i].modified = new_modified;
+ asset->Reload();
+ }
+ }
+ }
+ }
+
+ void Dispose();
+ AssetManager();
+ ~AssetManager();
+
+ /*
+ * Request that an asset is to be loaded
+ *
+ * returns the globally unique id of the asset loaded.
+ * The hold load mask is set to the asset loaded, if this mask is set
+ * to anything else than zero the asset manager will not unload the asset until the mask
+ * has been release by calling ReleaseHoldLoadMask(uint32_t) including all the bits set with the original call (or more)
+ * If the asset exists since before and the mask was set to something, the masks will be bitwise OR'd together.
+ */
+
+ template<typename TAsset>
+ uint32_t LoadAssetAsynchronized( const std::string& instr, uint32_t load_flags, AssetLoadedCallbackInterface* callback, uint32_t hold_load_mask ) {
+ std::string str = SanitizePath(instr);
+
+ uint32_t asset_index = AllocateAsset<TAsset>(str, load_flags);
+ uint32_t load_index = loading_instances.Allocate();
+
+ loading_instances[load_index].load_id = load_id_counter++;
+ loading_instances[load_index].asset_id = asset_list[asset_index].id;
+
+ if(asset_list[asset_index].hold_load_mask == 0x0 && hold_load_mask != 0x0 ) {
+ asset_hold_count++;
+ }
+
+ asset_list[asset_index].hold_load_mask |= hold_load_mask;
+
+ if( asset_list[asset_index].load_state == ASSET_LOADING_LOADED
+ || asset_list[asset_index].load_state == ASSET_LOADING_LOADING ) {
+ loading_instances[load_index].in_queue = false;
+ loading_instances[load_index].next_step_id = std::numeric_limits<uint32_t>::max(); //If we loaded, we did all the steps.
+ loading_instances[load_index].loader = NULL;
+ loading_instances[load_index].caller = callback;
+ loading_instances[load_index].asset_ref = new AssetRef<TAsset>(asset_list[asset_index].asset);
+ loading_instances[load_index].load_step_result = kLoadOk;
+
+ } else if( asset_list[asset_index].load_state == ASSET_LOADING_UNLOADED ) {
+ asset_list[asset_index].load_state = ASSET_LOADING_LOADING;
+ loading_instances[load_index].in_queue = false;
+ loading_instances[load_index].next_step_id = 0;
+ loading_instances[load_index].loader = asset_list[asset_index].asset->NewLoader();
+ loading_instances[load_index].caller = callback;
+ loading_instances[load_index].asset_ref = new AssetRef<TAsset>(asset_list[asset_index].asset);
+ loading_instances[load_index].load_step_result = kLoadOk;
+
+ loading_instances[load_index].loader->Initialize(str,load_flags,asset_list[asset_index].asset);
+ } else {
+ LOGE << "unknown load state" << std::endl;
+ }
+
+ return loading_instances[load_index].load_id;
+ }
+
+ template<typename TAsset>
+ uint32_t LoadAssetAsynchronized( const std::string& str, AssetLoadedCallbackInterface* callback ) {
+ return LoadAssetAsynchronized<TAsset>(str, callback, 0x0, 0x0);
+ }
+
+ //Load flags are specifically used by texture loading.
+ template<typename TAsset>
+ AssetRef<TAsset> LoadSync( const std::string& instr, uint32_t load_flags, uint32_t hold_load_mask ) {
+ std::string str = SanitizePath(instr);
+
+ uint32_t internal_id = AllocateAsset<TAsset>(str, load_flags);
+ bool failed_load = false;
+
+ if(asset_list[internal_id].hold_load_mask == 0x0 && hold_load_mask != 0x0 ) {
+ asset_hold_count++;
+ }
+
+ asset_list[internal_id].hold_load_mask |= hold_load_mask;
+
+ TAsset * asset = static_cast<TAsset*>(asset_list[internal_id].asset);
+
+ if( asset_list[internal_id].load_state == ASSET_LOADING_LOADING ) {
+ char buffer[kPathSize];
+ strcpy(buffer, asset_list[internal_id].rel_name);
+ LOGI << "Trying to synchronously load an asset being asynchronously loaded"
+ ", will wait until it's finished." << std::endl;
+ while( asset_list[internal_id].load_state != ASSET_LOADING_LOADED ) {
+ Update();
+ }
+
+ // This was discovered when a SyncedAnimationGroup (A) required another SyncedAnimationGroup (B) which in turn required A
+ if(strcmp(buffer, asset_list[internal_id].rel_name) != 0) {
+ LOGW << buffer << " internal_id changed while loading it" << std::endl;
+ LOGW << "There is a possible circular include, make sure " << buffer << " doesn't require something that in turn requires " << buffer << std::endl;
+ failed_load = true;
+ }
+ }
+
+ if( !failed_load && asset_list[internal_id].load_state == ASSET_LOADING_UNLOADED ) {
+ asset_list[internal_id].load_state = ASSET_LOADING_LOADING;
+#ifdef MEASURE_LOADS
+ uint64_t start_load = getCPUTSC();
+ uint32_t start_load_ms = SDL_TS_GetTicks();
+#endif
+ AssetLoaderBase* loader = asset->NewLoader();
+ loader->Initialize(str, load_flags, asset);
+
+ ErrorResponse ret = _retry;
+ while( ret == _retry ) {
+ ret = _continue;
+ for( int i = 0; i < loader->GetAssetLoadStepCount(); i++ ) {
+ int err = loader->DoLoadStep(i);
+ if( err != kLoadOk ) {
+ {
+ LOGE << "Error loading asset type: "
+ << loader->GetTypeName()
+ << " from path: "
+ << asset_list[internal_id].rel_name
+ << ". Reason: "
+ << GetLoadErrorString(err)
+ << ", specifically: \""
+ << loader->GetLoadErrorString()
+ << loader->GetLoadErrorStringExtended()
+ << "\""
+ << std::endl;
+ }
+
+ ret = DisplayFormatError( _ok_cancel_retry,
+ true,
+ "Asset Load Error",
+ "Error loading asset type: %s from path: %s.\n\nReason: %s, specifically: \"%s\", \nSee log output for more details (if available)",
+ loader->GetTypeName(),
+ asset_list[internal_id].rel_name,
+ GetLoadErrorString(err),
+ loader->GetLoadErrorString()
+
+ );
+
+ if( ret == _continue ) {
+ failed_load = true;
+ }
+ break;
+ }
+ }
+ }
+
+ delete loader;
+#ifdef MEASURE_LOADS
+ asset_load_times.push_back(AssetLoadTime(getCPUTSC()-start_load, SDL_TS_GetTicks()-start_load_ms, TAsset::GetTypeName(), TAsset::GetType() ));
+#endif
+ asset_list[internal_id].modified = GetDateModifiedInt64(str.c_str());
+ asset_list[internal_id].load_state = ASSET_LOADING_LOADED;
+ } else if( asset_list[internal_id].load_state == ASSET_LOADING_LOADED ) {
+
+ } else {
+ LOGE << "Unknown load state for asset" << std::endl;
+ }
+
+ if( failed_load ) {
+ return AssetRef<TAsset>();
+ } else {
+ return AssetRef<TAsset>(asset);
+ }
+ }
+
+ template<typename TAsset>
+ AssetRef<TAsset> LoadSync( const std::string& str ) {
+ return LoadSync<TAsset>( str, 0x0, 0x0 );
+ }
+
+ void LoadSync( AssetType type, const std::string& str, uint32_t load_flags, uint32_t hold_load_mask );
+
+ /*
+ * Main thread update step
+ */
+ void Update();
+
+ void ReleaseAssetHoldLoad( uint32_t hold_load_mask );
+
+ //Set if we want the asset-loading warning outputs.
+ //This is a tool for determining if and what assets
+ //are being loaded after the loading screen and during gameplay, which shouldn't
+ //happen.
+ void SetLoadWarningMode(bool val, const char* mode_name, const char* level_name);
+ //Clear and dump load warnings to write directory .xml
+ void DumpLoadWarningData(const char* destination);
+
+ size_t GetLoadedAssetCount();
+ const char* GetAssetName(uint32_t index);
+ AssetType GetAssetType(uint32_t index);
+ uint32_t GetAssetHoldFlags(uint32_t index);
+ uint16_t GetAssetRefCount(uint32_t index);
+ bool GetAssetLoadWarning(uint32_t index);
+
+ int GetAssetTypeCount( AssetType asset );
+
+ int GetAssetWarningCount();
+ int GetAssetHoldCount();
+#ifdef MEASURE_LOADS
+ void DumpTimingData();
+#endif
+};
+
diff --git a/Source/Asset/assetmanagerthreadhandler.cpp b/Source/Asset/assetmanagerthreadhandler.cpp
new file mode 100644
index 00000000..30216db5
--- /dev/null
+++ b/Source/Asset/assetmanagerthreadhandler.cpp
@@ -0,0 +1,128 @@
+//-----------------------------------------------------------------------------
+// Name: assetmanagerthreadhandler.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include "assetmanagerthreadhandler.h"
+
+#include <Memory/allocation.h>
+#include <Asset/assetloaderrors.h>
+
+#include <cstdlib>
+
+AssetLoaderThreadedInstance::AssetLoaderThreadedInstance( ) : loader(NULL), load_id(0), step(0), return_state(kLoadOk) {
+}
+
+AssetLoaderThreadedInstance::AssetLoaderThreadedInstance( AssetLoaderBase* loader, uint32_t load_id, int step ) : loader(loader), load_id(load_id), step(step), return_state(kLoadOk) {
+}
+
+AssetManagerThreadedInstanceQueue::AssetManagerThreadedInstanceQueue() : queue(NULL), queue_count(0), queue_size(0) {
+ accessmutex = new std::mutex();
+}
+
+AssetManagerThreadedInstanceQueue::~AssetManagerThreadedInstanceQueue() {
+ OG_FREE( queue );
+ queue = NULL;
+ queue_count = 0;
+ queue_size = 0;
+
+ delete accessmutex;
+ accessmutex = NULL;
+}
+
+AssetLoaderThreadedInstance AssetManagerThreadedInstanceQueue::Pop() {
+ AssetLoaderThreadedInstance instance;
+ accessmutex->lock();
+ if( queue_count > 0 )
+ {
+ instance = queue[0];
+ for( unsigned i = 1; i < queue_count; i++ )
+ {
+ queue[i-1] = queue[i];
+ }
+ queue_count -= 1;
+ }
+ accessmutex->unlock();
+
+ return instance;
+}
+
+void AssetManagerThreadedInstanceQueue::Push(const AssetLoaderThreadedInstance& input) {
+ accessmutex->lock();
+
+ if( queue_size >= queue_count )
+ {
+ queue_size += 32;
+ queue = static_cast<AssetLoaderThreadedInstance*>(realloc( queue, sizeof( AssetLoaderThreadedInstance ) * queue_size ));
+ }
+
+ queue[queue_count] = input;
+ queue_count += 1;
+
+ accessmutex->unlock();
+}
+
+size_t AssetManagerThreadedInstanceQueue::Count() {
+ size_t ret;
+ accessmutex->lock();
+ ret = queue_count;
+ accessmutex->unlock();
+ return ret;
+}
+
+AssetManagerThreadInstance::AssetManagerThreadInstance(AssetManagerThreadedInstanceQueue* queue_in, AssetManagerThreadedInstanceQueue* queue_out, bool* stop) : queue_in(queue_in), queue_out( queue_out ), stop(stop) {
+
+}
+
+void AssetManagerThreadHandler_Operate(void* userdata) {
+ AssetManagerThreadInstance* data = static_cast<AssetManagerThreadInstance*>(userdata);
+ while(*(data->stop) == false) {
+ if(data->queue_in->Count() > 0) {
+ AssetLoaderThreadedInstance instance = data->queue_in->Pop();
+
+ instance.return_state = instance.loader->DoLoadStep( instance.step );
+
+ data->queue_out->Push(instance);
+ } else {
+ std::this_thread::sleep_for(std::chrono::milliseconds(16));
+ }
+ }
+
+ delete data;
+}
+
+AssetManagerThreadHandler::AssetManagerThreadHandler(int thread_count) : stop(false) {
+ for( int i = 0; i < thread_count; i++ ) {
+ AssetManagerThreadInstance* threadInstance = new AssetManagerThreadInstance(&queue_in, &queue_out, &stop);
+ std::thread* thread = new std::thread(AssetManagerThreadHandler_Operate, threadInstance);
+ threads.push_back(thread);
+ }
+}
+
+AssetManagerThreadHandler::~AssetManagerThreadHandler() {
+ stop = true;
+
+ for( unsigned i = 0; i < threads.size(); i++ ) {
+ threads[i]->join();
+ threads[i] = NULL;
+ }
+}
diff --git a/Source/Asset/assetmanagerthreadhandler.h b/Source/Asset/assetmanagerthreadhandler.h
new file mode 100644
index 00000000..0a9cf66e
--- /dev/null
+++ b/Source/Asset/assetmanagerthreadhandler.h
@@ -0,0 +1,84 @@
+//-----------------------------------------------------------------------------
+// Name: assetmanagerthreadhandler.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/integer.h>
+#include <Asset/assetloaderbase.h>
+
+#include <thread>
+#include <mutex>
+#include <chrono>
+
+struct AssetLoaderThreadedInstance {
+ AssetLoaderThreadedInstance();
+ AssetLoaderThreadedInstance( AssetLoaderBase* loader, uint32_t load_id, int step );
+
+ AssetLoaderBase* loader;
+ uint32_t load_id;
+ int step;
+ int return_state;
+};
+
+class AssetManagerThreadedInstanceQueue {
+public:
+ AssetManagerThreadedInstanceQueue();
+ ~AssetManagerThreadedInstanceQueue();
+
+ AssetLoaderThreadedInstance Pop();
+ void Push(const AssetLoaderThreadedInstance& input);
+ size_t Count();
+
+private:
+ std::mutex* accessmutex;
+
+ AssetLoaderThreadedInstance *queue;
+ size_t queue_count;
+ size_t queue_size;
+};
+
+
+class AssetManagerThreadInstance {
+public:
+ bool* stop;
+ AssetManagerThreadedInstanceQueue* queue_in;
+ AssetManagerThreadedInstanceQueue* queue_out;
+
+ AssetManagerThreadInstance(AssetManagerThreadedInstanceQueue* queue_in, AssetManagerThreadedInstanceQueue* queue_out, bool* stop);
+};
+
+class AssetManagerThreadHandler {
+public:
+ AssetManagerThreadedInstanceQueue queue_in;
+ AssetManagerThreadedInstanceQueue queue_out;
+
+ AssetManagerThreadHandler(int thread_count);
+ ~AssetManagerThreadHandler();
+private:
+
+ std::vector<std::thread*> threads;
+
+ bool stop;
+};
+
+void AssetManagerThreadHandler_Operate(void* data);
diff --git a/Source/Asset/assetref.cpp b/Source/Asset/assetref.cpp
new file mode 100644
index 00000000..bcce286c
--- /dev/null
+++ b/Source/Asset/assetref.cpp
@@ -0,0 +1,32 @@
+//-----------------------------------------------------------------------------
+// Name: assetref.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "assetref.h"
+
+AssetRefBase::AssetRefBase() {
+
+}
+
+AssetRefBase::~AssetRefBase() {
+
+}
diff --git a/Source/Asset/assetref.h b/Source/Asset/assetref.h
new file mode 100644
index 00000000..d11a7775
--- /dev/null
+++ b/Source/Asset/assetref.h
@@ -0,0 +1,118 @@
+//-----------------------------------------------------------------------------
+// Name: assetref.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assetbase.h>
+#include <Asset/assettypes.h>
+
+class AssetRefBase {
+protected:
+ AssetRefBase();
+public:
+ virtual ~AssetRefBase();
+ virtual AssetType GetType() = 0;
+};
+
+template<class T>
+class AssetRef : public AssetRefBase {
+private:
+ T *asset_ptr_;
+
+public:
+ virtual AssetType GetType() {
+ return T::GetType();
+ };
+
+ void clear() {
+ if(asset_ptr_ != NULL){
+ asset_ptr_->DecrementRefCount();
+ asset_ptr_ = NULL;
+ }
+ }
+
+ bool valid() const {
+ return asset_ptr_ != NULL;
+ }
+
+ AssetRef() {
+ asset_ptr_ = NULL;
+ }
+
+ AssetRef(T* _asset_ptr) {
+ asset_ptr_ = _asset_ptr;
+ if(asset_ptr_ != NULL){
+ asset_ptr_->IncrementRefCount();
+ }
+ }
+
+ AssetRef(const AssetRef &other) {
+ asset_ptr_ = other.asset_ptr_;
+ if(asset_ptr_ != NULL){
+ asset_ptr_->IncrementRefCount();
+ }
+ }
+
+ virtual ~AssetRef() {
+ if(asset_ptr_ != NULL){
+ asset_ptr_->DecrementRefCount();
+ asset_ptr_ = NULL;
+ }
+ }
+
+ T& operator*(){
+ return *asset_ptr_;
+ }
+
+ const T& operator*() const{
+ return *asset_ptr_;
+ }
+
+ const AssetRef& operator=(const AssetRef& other) {
+ clear();
+ asset_ptr_ = other.asset_ptr_;
+ if(asset_ptr_){
+ asset_ptr_->IncrementRefCount();
+ }
+ return *this;
+ }
+
+ bool operator!=( const AssetRef& other ) const
+ {
+ return asset_ptr_ != other.asset_ptr_;
+ }
+
+ bool operator==( const AssetRef& other ) const
+ {
+ return asset_ptr_ == other.asset_ptr_;
+ }
+
+ bool operator<( const AssetRef& other ) const
+ {
+ return asset_ptr_ < other.asset_ptr_;
+ }
+
+ T* operator->() const {
+ return asset_ptr_;
+ }
+};
diff --git a/Source/Asset/assets.h b/Source/Asset/assets.h
new file mode 100644
index 00000000..0d11ddb6
--- /dev/null
+++ b/Source/Asset/assets.h
@@ -0,0 +1,32 @@
+//-----------------------------------------------------------------------------
+// Name: assets.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/path_set.h>
+#include <Internal/error.h>
+#include <Internal/datemodified.h>
+
+#include <list>
+#include <set>
+#include <string>
diff --git a/Source/Asset/assettypes.cpp b/Source/Asset/assettypes.cpp
new file mode 100644
index 00000000..1db51ed1
--- /dev/null
+++ b/Source/Asset/assettypes.cpp
@@ -0,0 +1,124 @@
+//-----------------------------------------------------------------------------
+// Name: assettypes.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "assettypes.h"
+
+#include <Utility/strings.h>
+
+const char* GetAssetTypeString( AssetType assettype ) {
+ switch(assettype) {
+ case UNKNOWN: return "UNKNOWN";
+ case SKELETON_ASSET: return "SKELETON_ASSET";
+ case SYNCED_ANIMATION_GROUP_ASSET: return "SYNCED_ANIMATION_GROUP_ASSET";
+ case ANIMATION_ASSET: return "ANIMATION_ASSET";
+ case OBJECT_FILE_ASSET: return "OBJECT_FILE_ASSET";
+ case MATERIAL_ASSET: return "MATERIAL_ASSET";
+ case ATTACK_ASSET: return "ATTACK_ASSET";
+ case VOICE_FILE_ASSET: return "VOICE_FILE_ASSET";
+ case ANIMATION_EFFECT_ASSET: return "ANIMATION_EFFECT_ASSET";
+ case SOUND_GROUP_INFO_ASSET: return "SOUND_GROUP_INFO_ASSET";
+ case CHARACTER_ASSET: return "CHARACTER_ASSET";
+ case REACTION_ASSET: return "REACTION_ASSET";
+ case DECAL_FILE_ASSET: return "DECAL_FILE_ASSET";
+ case HOTSPOT_FILE_ASSET: return "HOTSPOT_FILE_ASSET";
+ case AMBIENT_SOUND_ASSET: return "AMBIENT_SOUND_ASSET";
+ case ACTOR_FILE_ASSET: return "ACTOR_FILE_ASSET";
+ case ITEM_ASSET: return "ITEM_ASSET";
+ case LEVEL_SET_ASSET: return "LEVEL_SET_ASSET";
+ case IMAGE_SAMPLER_ASSET: return "IMAGE_SAMPLER_ASSET";
+ case SOUND_GROUP_ASSET: return "SOUND_GROUP_ASSET";
+ case PARTICLE_TYPE_ASSET: return "PARTICLE_TYPE_ASSET";
+ case AVERAGE_COLOR_ASSET: return "AVERAGE_COLOR_ASSET";
+ case FZX_ASSET: return "FZX_ASSET";
+ case ATTACHMENT_ASSET: return "ATTACHMENT_ASSET";
+ case LIP_SYNC_FILE_ASSET: return "LIP_SYNC_FILE_ASSET";
+ case TEXTURE_DUMMY_ASSET: return "TEXTURE_DUMMY_ASSET";
+ case TEXTURE_ASSET: return "TEXTURE_ASSET";
+ case LEVEL_INFO_ASSET: return "LEVEL_INFO_ASSET";
+ case FILE_HASH_ASSET: return "FILE_HASH_ASSET";
+ case ASSET_TYPE_FINAL: return "ASSET_TYPE_FINAL";
+ }
+ return "UNKNOWN";
+}
+
+AssetType GetAssetTypeValue( const char* assettype ) {
+ if(strmtch("UNKNOWN", assettype))
+ return UNKNOWN;
+ if(strmtch("SKELETON_ASSET", assettype))
+ return SKELETON_ASSET;
+ if(strmtch("SYNCED_ANIMATION_GROUP_ASSET", assettype))
+ return SYNCED_ANIMATION_GROUP_ASSET;
+ if(strmtch("ANIMATION_ASSET", assettype))
+ return ANIMATION_ASSET;
+ if(strmtch("OBJECT_FILE_ASSET", assettype))
+ return OBJECT_FILE_ASSET;
+ if(strmtch("MATERIAL_ASSET", assettype))
+ return MATERIAL_ASSET;
+ if(strmtch("ATTACK_ASSET", assettype))
+ return ATTACK_ASSET;
+ if(strmtch("VOICE_FILE_ASSET", assettype))
+ return VOICE_FILE_ASSET;
+ if(strmtch("ANIMATION_EFFECT_ASSET", assettype))
+ return ANIMATION_EFFECT_ASSET;
+ if(strmtch("SOUND_GROUP_INFO_ASSET", assettype))
+ return SOUND_GROUP_INFO_ASSET;
+ if(strmtch("CHARACTER_ASSET", assettype))
+ return CHARACTER_ASSET;
+ if(strmtch("REACTION_ASSET", assettype))
+ return REACTION_ASSET;
+ if(strmtch("DECAL_FILE_ASSET", assettype))
+ return DECAL_FILE_ASSET;
+ if(strmtch("HOTSPOT_FILE_ASSET", assettype))
+ return HOTSPOT_FILE_ASSET;
+ if(strmtch("AMBIENT_SOUND_ASSET", assettype))
+ return AMBIENT_SOUND_ASSET;
+ if(strmtch("ACTOR_FILE_ASSET", assettype))
+ return ACTOR_FILE_ASSET;
+ if(strmtch("ITEM_ASSET", assettype))
+ return ITEM_ASSET;
+ if(strmtch("LEVEL_SET_ASSET", assettype))
+ return LEVEL_SET_ASSET;
+ if(strmtch("IMAGE_SAMPLER_ASSET", assettype))
+ return IMAGE_SAMPLER_ASSET;
+ if(strmtch("SOUND_GROUP_ASSET", assettype))
+ return SOUND_GROUP_ASSET;
+ if(strmtch("PARTICLE_TYPE_ASSET", assettype))
+ return PARTICLE_TYPE_ASSET;
+ if(strmtch("AVERAGE_COLOR_ASSET", assettype))
+ return AVERAGE_COLOR_ASSET;
+ if(strmtch("FZX_ASSET", assettype))
+ return FZX_ASSET;
+ if(strmtch("ATTACHMENT_ASSET", assettype))
+ return ATTACHMENT_ASSET;
+ if(strmtch("LIP_SYNC_FILE_ASSET", assettype))
+ return LIP_SYNC_FILE_ASSET;
+ if(strmtch("TEXTURE_DUMMY_ASSET", assettype))
+ return TEXTURE_DUMMY_ASSET;
+ if(strmtch("TEXTURE_ASSET", assettype))
+ return TEXTURE_ASSET;
+ if(strmtch("LEVEL_INFO_ASSET", assettype))
+ return LEVEL_INFO_ASSET;
+ if(strmtch("FILE_HASH_ASSET", assettype))
+ return FILE_HASH_ASSET;
+ return UNKNOWN;
+}
diff --git a/Source/Asset/assettypes.h b/Source/Asset/assettypes.h
new file mode 100644
index 00000000..f7d8a1c2
--- /dev/null
+++ b/Source/Asset/assettypes.h
@@ -0,0 +1,60 @@
+//-----------------------------------------------------------------------------
+// Name: assettypes.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+enum AssetType {
+ UNKNOWN,
+ SKELETON_ASSET,
+ SYNCED_ANIMATION_GROUP_ASSET,
+ ANIMATION_ASSET,
+ OBJECT_FILE_ASSET,
+ MATERIAL_ASSET,
+ ATTACK_ASSET,
+ VOICE_FILE_ASSET,
+ ANIMATION_EFFECT_ASSET,
+ SOUND_GROUP_INFO_ASSET,
+ CHARACTER_ASSET,
+ REACTION_ASSET,
+ DECAL_FILE_ASSET,
+ HOTSPOT_FILE_ASSET,
+ AMBIENT_SOUND_ASSET,
+ ACTOR_FILE_ASSET,
+ ITEM_ASSET,
+ LEVEL_SET_ASSET,
+ IMAGE_SAMPLER_ASSET,
+ SOUND_GROUP_ASSET,
+ PARTICLE_TYPE_ASSET,
+ AVERAGE_COLOR_ASSET,
+ FZX_ASSET,
+ ATTACHMENT_ASSET,
+ LIP_SYNC_FILE_ASSET,
+ TEXTURE_DUMMY_ASSET,
+ TEXTURE_ASSET,
+ LEVEL_INFO_ASSET,
+ FILE_HASH_ASSET,
+ ASSET_TYPE_FINAL
+};
+
+const char* GetAssetTypeString( AssetType assettype );
+AssetType GetAssetTypeValue( const char* assettype );
diff --git a/Source/Compat/Linux/linux_compat.cpp b/Source/Compat/Linux/linux_compat.cpp
new file mode 100644
index 00000000..90886da5
--- /dev/null
+++ b/Source/Compat/Linux/linux_compat.cpp
@@ -0,0 +1,67 @@
+//-----------------------------------------------------------------------------
+// Name: linux_compat.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#if !PLATFORM_LINUX
+#error Do not compile this.
+#endif
+
+#include <Compat/compat.h>
+
+#include <sys/sendfile.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#if defined(PLATFORM_64)
+int os_copyfile( const char *source, const char *dest )
+{
+ int input, output;
+ if ((input = open(source, O_RDONLY)) == -1)
+ {
+ return -2;
+ }
+
+ if ((output = open(dest, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP )) == -1)
+ {
+ close(input);
+ return -3;
+ }
+
+ //Here we use kernel-space copying for performance reasons
+ //sendfile will work with non-socket output (i.e. regular file) on Linux 2.6.33+
+ off_t bytesCopied = 0;
+ struct stat fileinfo = {0};
+ fstat(input, &fileinfo);
+ int result = sendfile(output, input, &bytesCopied, fileinfo.st_size);
+
+ close(input);
+ close(output);
+
+ return result;
+}
+#else
+int os_copyfile( const char *source, const char *dest )
+{
+ return -1;
+}
+#endif
diff --git a/Source/Compat/Linux/linux_compat.h b/Source/Compat/Linux/linux_compat.h
new file mode 100644
index 00000000..4dc9c08f
--- /dev/null
+++ b/Source/Compat/Linux/linux_compat.h
@@ -0,0 +1,23 @@
+//-----------------------------------------------------------------------------
+// Name: linux_compat.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
diff --git a/Source/Compat/Linux/linux_hardware_info.cpp b/Source/Compat/Linux/linux_hardware_info.cpp
new file mode 100644
index 00000000..cf7c162a
--- /dev/null
+++ b/Source/Compat/Linux/linux_hardware_info.cpp
@@ -0,0 +1,27 @@
+//-----------------------------------------------------------------------------
+// Name: linux_hardware_info.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <Compat/hardware_info.h>
+
+unsigned GetDriverVersion(GLVendor vendor) {
+ return 0;
+}
diff --git a/Source/Compat/Linux/linux_time.cpp b/Source/Compat/Linux/linux_time.cpp
new file mode 100644
index 00000000..8b10f287
--- /dev/null
+++ b/Source/Compat/Linux/linux_time.cpp
@@ -0,0 +1,38 @@
+//-----------------------------------------------------------------------------
+// Name: linux_time.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <Compat/time.h>
+
+#include <sys/time.h>
+#include <stdlib.h>
+
+uint64_t GetPrecisionTime() {
+ struct timeval time;
+
+ gettimeofday(&time, NULL);
+ return time.tv_usec +
+ (time.tv_sec * 1000 * 1000);
+}
+
+uint64_t ToNanoseconds(uint64_t time){
+ return time * 1000;
+}
diff --git a/Source/Compat/Linux/os_dialogs_linux.cpp b/Source/Compat/Linux/os_dialogs_linux.cpp
new file mode 100644
index 00000000..f844da22
--- /dev/null
+++ b/Source/Compat/Linux/os_dialogs_linux.cpp
@@ -0,0 +1,95 @@
+//-----------------------------------------------------------------------------
+// Name: os_dialogs_linux.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <Compat/os_dialogs.h>
+#include <Logging/logdata.h>
+#include <UserInput/input.h>
+
+#include <SDL.h>
+
+
+ErrorResponse OSDisplayError(const char* title, const char* contents, ErrorType type)
+{
+ bool old_mouse = Input::Instance()->GetGrabMouse();
+ Input::Instance()->SetGrabMouse(false);
+ UIShowCursor(1);
+
+ SDL_MessageBoxButtonData buttons[] = {
+ { 0, 0, "OK"},
+ { 0, 1, "Cancel"},
+ { 0, 2, "Retry"},
+ };
+
+ int numButtons = 0;
+
+ switch (type) {
+ case _ok_cancel_retry:
+ ++numButtons;
+ case _ok_cancel:
+ ++numButtons;
+ case _ok:
+ ++numButtons;
+ break;
+ default:
+ LOGW << "Unhandled display error button type" << std::endl;
+ break;
+ }
+
+ std::string limited_contents = std::string(contents);
+
+ int newline_count = 0;
+ for(int i = 0; i < limited_contents.size(); i++) {
+ if(limited_contents[i] == '\n') {
+ newline_count++;
+ if(newline_count == 20) {
+ limited_contents = limited_contents.substr(0,i) + "\n[Contents Cut]";
+ }
+ }
+ }
+
+ SDL_MessageBoxData messageBox = {
+ SDL_MESSAGEBOX_ERROR,
+ SDL_GL_GetCurrentWindow(),
+ title,
+ limited_contents.c_str(),
+ numButtons,
+ buttons,
+ NULL
+ };
+
+ int choice = -1;
+ if (SDL_ShowMessageBox(&messageBox, &choice) < 0) {
+ LOGE << "Failed to show message dialog: " << SDL_GetError() << std::endl;
+ }
+
+ ErrorResponse ret = _continue;
+ if (choice == 2) {
+ ret = _retry;
+ } else if (choice == 1) {
+ ret = _er_exit;
+ } else {
+ }
+
+ Input::Instance()->SetGrabMouse(old_mouse);
+ UIShowCursor(0);
+ return ret;
+}
diff --git a/Source/Compat/Mac/mac_compat.h b/Source/Compat/Mac/mac_compat.h
new file mode 100644
index 00000000..57f4336b
--- /dev/null
+++ b/Source/Compat/Mac/mac_compat.h
@@ -0,0 +1,29 @@
+//-----------------------------------------------------------------------------
+// Name: mac_compat.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <string>
+
+namespace MacCompat {
+ std::string GetAppPath();
+}
diff --git a/Source/Compat/Mac/mac_compat.mm b/Source/Compat/Mac/mac_compat.mm
new file mode 100644
index 00000000..ca221670
--- /dev/null
+++ b/Source/Compat/Mac/mac_compat.mm
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------------
+// Name: mac_compat.mm
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "mac_compat.h"
+
+#include <Foundation/NSBundle.h>
+
+#ifndef OG_WORKER
+std::string MacCompat::GetAppPath() {
+ NSString *app_path = [[[[NSBundle mainBundle] resourcePath]
+ stringByDeletingLastPathComponent]
+ stringByDeletingLastPathComponent];
+ return std::string((char*)[app_path UTF8String]);
+}
+#endif
+
+int os_copyfile( const char *source, const char *dest )
+{
+ return -1;
+}
diff --git a/Source/Compat/Mac/mac_hardware_info.cpp b/Source/Compat/Mac/mac_hardware_info.cpp
new file mode 100644
index 00000000..64e9bd26
--- /dev/null
+++ b/Source/Compat/Mac/mac_hardware_info.cpp
@@ -0,0 +1,112 @@
+//-----------------------------------------------------------------------------
+// Name: mac_hardware_info.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <Compat/hardware_info.h>
+#include <Memory/allocation.h>
+
+#include <opengl.h>
+
+#include <string>
+
+unsigned GetDriverVersion(GLVendor vendor) {
+ //Mac driver versions are at the end of the GL_VERSION string, e.g.:
+ //"2.1 NVIDIA-1.6.6"
+ std::string driver_string = (const char*)glGetString(GL_VERSION);
+
+ //Remove everything before dash
+ unsigned final_dash_index = driver_string.rfind('-');
+ driver_string = driver_string.substr(final_dash_index+1);
+
+ //Remove dots
+ driver_string.erase(
+ std::remove(driver_string.begin(), driver_string.end(), '.'),
+ driver_string.end());
+
+ //Convert remaining string into unsigned int and return
+ unsigned driver_version = atoi(driver_string.c_str());
+ return driver_version;
+}
+
+#include <ApplicationServices/ApplicationServices.h>
+#include <IOKit/IOKitLib.h>
+
+int vramSize(long** vsArray)
+{
+ CGError err = CGDisplayNoErr;
+ unsigned int i = 0;
+ io_service_t *dspPorts = NULL;
+ CGDirectDisplayID *displays = NULL;
+ CGDisplayCount dspCount = 0;
+ CFTypeRef typeCode;
+
+ // How many active displays do we have?
+ err = CGGetActiveDisplayList(0, NULL, &dspCount);
+
+ // Allocate enough memory to hold all the display IDs we have
+ displays = (CGDirectDisplayID*)calloc((size_t)dspCount, sizeof(CGDirectDisplayID));
+ // Allocate enough memory for the number of displays we're asking about
+ *vsArray = (long int*)calloc((size_t)dspCount, sizeof(long));
+ // Allocate memory for our service ports
+ dspPorts = (io_service_t*)calloc((size_t)dspCount, sizeof(io_service_t));
+
+ // Get the list of active displays
+ err = CGGetActiveDisplayList(dspCount,
+ displays,
+ &dspCount);
+
+ // Now we iterate through them
+ for(i = 0; i < dspCount; i++)
+ {
+ // Get the service port for the display
+ dspPorts[i] = CGDisplayIOServicePort(displays[i]);
+ // Ask IOKit for the VRAM size property
+ typeCode = IORegistryEntryCreateCFProperty(dspPorts[i],
+ CFSTR(kIOFBMemorySizeKey),
+ kCFAllocatorDefault,
+ kNilOptions);
+
+ // Ensure we have valid data from IOKit
+ if(typeCode && CFGetTypeID(typeCode) == CFNumberGetTypeID())
+ {
+ // If so, convert the CFNumber into a plain unsigned long
+ CFNumberGetValue((const __CFNumber*)typeCode, kCFNumberSInt32Type, vsArray[i]);
+ if(typeCode)
+ CFRelease(typeCode);
+ }
+ }
+ OG_FREE(dspPorts);
+ // Return the total number of displays we found
+ return (int)dspCount;
+}
+
+
+long MACOS_GetVramSize( int displayNumber )
+{
+ long displaySize = -1;
+ long *vramArray;
+ int dispCount = vramSize(&vramArray);
+ if(displayNumber < dispCount && displayNumber >= 0) {
+ displaySize = vramArray[displayNumber];
+ }
+ OG_FREE(vramArray);
+ return displaySize;
+}
diff --git a/Source/Compat/Mac/mac_time.cpp b/Source/Compat/Mac/mac_time.cpp
new file mode 100644
index 00000000..6607b42a
--- /dev/null
+++ b/Source/Compat/Mac/mac_time.cpp
@@ -0,0 +1,35 @@
+//-----------------------------------------------------------------------------
+// Name: mac_time.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <Compat/time.h>
+
+#include <CoreServices/CoreServices.h>
+
+uint64_t GetPrecisionTime() {
+ AbsoluteTime upTime = UpTime();
+ return *(uint64_t*)&upTime;
+}
+
+uint64_t ToNanoseconds(uint64_t time){
+ Nanoseconds elapsedNano = AbsoluteToNanoseconds(*(AbsoluteTime*)&time );
+ return *(uint64_t*)&elapsedNano;
+}
diff --git a/Source/Compat/Mac/os_dialogs_mac.mm b/Source/Compat/Mac/os_dialogs_mac.mm
new file mode 100644
index 00000000..60cac390
--- /dev/null
+++ b/Source/Compat/Mac/os_dialogs_mac.mm
@@ -0,0 +1,66 @@
+//-----------------------------------------------------------------------------
+// Name: os_dialogs_mac.mm
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <Internal/error.h>
+#include <Internal/config.h>
+
+#include <UserInput/input.h>
+#include <Logging/logdata.h>
+
+#include <SDL.h>
+
+#import <AppKit/NSAlert.h>
+#import <AppKit/NSTextField.h>
+#import <AppKit/NSPanel.h>
+
+#include <map>
+#include <string>
+
+ErrorResponse OSDisplayError(const char* title, const char* contents, ErrorType type)
+{
+ bool old_mouse = Input::Instance()->GetGrabMouse();
+ Input::Instance()->SetGrabMouse(false);
+ UIShowCursor(1);
+
+ NSString *errStr = [NSString stringWithUTF8String:title];
+ NSString *messageStr = [NSString stringWithUTF8String:contents];
+
+ NSAlert *alert = [NSAlert alertWithMessageText:errStr
+ defaultButton:type == _ok_cancel_retry ? @"Continue" : @"Ok"
+ alternateButton:type == _ok ? nil : @"Cancel"
+ otherButton:type == _ok_cancel_retry ? @"Retry" : nil
+ informativeTextWithFormat:messageStr];
+
+ NSInteger button = [alert runModal];
+
+ if (button == NSAlertDefaultReturn) {
+ Input::Instance()->SetGrabMouse(old_mouse);
+ UIShowCursor(0);
+ return _continue;
+ } else if (button == NSAlertAlternateReturn) {
+ return _er_exit;
+ } else {
+ Input::Instance()->SetGrabMouse(old_mouse);
+ UIShowCursor(0);
+ return _retry;
+ }
+}
diff --git a/Source/Compat/Mac/os_file_dialogs_mac.h b/Source/Compat/Mac/os_file_dialogs_mac.h
new file mode 100644
index 00000000..9f4fc441
--- /dev/null
+++ b/Source/Compat/Mac/os_file_dialogs_mac.h
@@ -0,0 +1,32 @@
+//-----------------------------------------------------------------------------
+// Name: os_file_dialogs_mac.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/dialogues.h>
+
+namespace OsFileDialogsMac {
+
+Dialog::DialogErr OpenDialog(const char* filter_list, const char* default_path, char** output_path);
+Dialog::DialogErr SaveDialog(const char* filter_list, const char* default_path, char** output_path);
+
+}; // namespace OsFileDialogsMac
diff --git a/Source/Compat/Mac/os_file_dialogs_mac.mm b/Source/Compat/Mac/os_file_dialogs_mac.mm
new file mode 100644
index 00000000..d8ca3957
--- /dev/null
+++ b/Source/Compat/Mac/os_file_dialogs_mac.mm
@@ -0,0 +1,163 @@
+//-----------------------------------------------------------------------------
+// Name: os_file_dialogs_mac.mm
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <Internal/dialogues.h>
+
+#include <AppKit/AppKit.h>
+
+namespace OsFileDialogsMac {
+
+Dialog::DialogErr OpenDialog(const char* filter_list, const char* default_path, char** output_path) {
+ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+
+ NSWindow* key_window = [[NSApplication sharedApplication] keyWindow];
+ NSOpenPanel* dialog = [NSOpenPanel openPanel];
+ [dialog setAllowsMultipleSelection: NO];
+
+ if(filter_list && strlen(filter_list) != 0) {
+ NSMutableArray* allowed_extensions_list = [[NSMutableArray alloc] init];
+
+ char current_ext[256] = { 0 };
+ char* current_ext_char = &current_ext[0];
+ size_t filter_list_len = strlen(filter_list);
+
+ for(size_t i = 0; i < filter_list_len + 1; ++i) {
+ // TODO: Error check that we're not past the current_ext buffer length
+ *current_ext_char = filter_list[i];
+ ++current_ext_char;
+
+ if(filter_list[i] == '\0') {
+ NSString* current_ext_ns_string = [NSString stringWithUTF8String: current_ext];
+ [allowed_extensions_list addObject: current_ext_ns_string];
+
+ current_ext_char = &current_ext[0];
+ *current_ext_char = '\0';
+ }
+ }
+
+ NSArray* allowed_file_types = [NSArray arrayWithArray: allowed_extensions_list];
+ [allowed_extensions_list release];
+
+ if([allowed_file_types count] != 0) {
+ [dialog setAllowedFileTypes: allowed_file_types];
+ }
+ }
+
+ if(default_path && strlen(default_path) != 0) {
+ NSString* default_path_string = [NSString stringWithUTF8String: default_path];
+ NSURL* path_url = [NSURL fileURLWithPath: default_path_string isDirectory: YES];
+ [dialog setDirectoryURL: path_url];
+ }
+
+ Dialog::DialogErr result = Dialog::DialogErr::NO_SELECTION;
+
+ if([dialog runModal] == NSModalResponseOK) {
+ NSURL* path_url = [dialog URL];
+ const char* utf8_path = [[path_url path] UTF8String];
+
+ size_t len = strlen(utf8_path);
+ *output_path = (char*)malloc(len + 1);
+
+ if(!*output_path) {
+ [pool release];
+ [key_window makeKeyAndOrderFront: nil];
+ return Dialog::DialogErr::INTERNAL_BUFFER_TOO_SMALL;
+ }
+
+ memcpy(*output_path, utf8_path, len + 1);
+ result = Dialog::DialogErr::NO_ERR;
+ }
+
+ [pool release];
+ [key_window makeKeyAndOrderFront: nil];
+
+ return result;
+}
+
+Dialog::DialogErr SaveDialog(const char* filter_list, const char* default_path, char** output_path) {
+ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+
+ NSWindow* key_window = [[NSApplication sharedApplication] keyWindow];
+ NSSavePanel *dialog = [NSSavePanel savePanel];
+ [dialog setExtensionHidden: NO];
+
+ if(filter_list && strlen(filter_list) != 0) {
+ NSMutableArray* allowed_extensions_list = [[NSMutableArray alloc] init];
+
+ char current_ext[256] = { 0 };
+ char* current_ext_char = &current_ext[0];
+ size_t filter_list_len = strlen(filter_list);
+
+ for(size_t i = 0; i < filter_list_len + 1; ++i) {
+ // TODO: Error check that we're not past the current_ext buffer length
+ *current_ext_char = filter_list[i];
+ ++current_ext_char;
+
+ if(filter_list[i] == '\0') {
+ NSString* current_ext_ns_string = [NSString stringWithUTF8String: current_ext];
+ [allowed_extensions_list addObject: current_ext_ns_string];
+
+ current_ext_char = &current_ext[0];
+ *current_ext_char = '\0';
+ }
+ }
+
+ NSArray* allowed_file_types = [NSArray arrayWithArray: allowed_extensions_list];
+ [allowed_extensions_list release];
+
+ if([allowed_file_types count] != 0) {
+ [dialog setAllowedFileTypes: allowed_file_types];
+ }
+ }
+
+ if(default_path && strlen(default_path) != 0) {
+ NSString* default_path_string = [NSString stringWithUTF8String: default_path];
+ NSURL* path_url = [NSURL fileURLWithPath: default_path_string isDirectory: YES];
+ [dialog setDirectoryURL: path_url];
+ }
+
+ Dialog::DialogErr result = Dialog::DialogErr::NO_SELECTION;
+
+ if([dialog runModal] == NSModalResponseOK) {
+ NSURL* path_url = [dialog URL];
+ const char* utf8_path = [[path_url path] UTF8String];
+
+ size_t len = [path_url.path lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
+ *output_path = (char*)malloc(len + 1);
+
+ if(!*output_path) {
+ [pool release];
+ [key_window makeKeyAndOrderFront: nil];
+ return Dialog::DialogErr::INTERNAL_BUFFER_TOO_SMALL;
+ }
+
+ memcpy(*output_path, utf8_path, len + 1);
+ result = Dialog::DialogErr::NO_ERR;
+ }
+
+ [pool release];
+ [key_window makeKeyAndOrderFront: nil];
+
+ return result;
+}
+
+}; // namespace OsFileDialogsMac
diff --git a/Source/Compat/UNIX/unix_compat.cpp b/Source/Compat/UNIX/unix_compat.cpp
new file mode 100644
index 00000000..90e095bb
--- /dev/null
+++ b/Source/Compat/UNIX/unix_compat.cpp
@@ -0,0 +1,482 @@
+//-----------------------------------------------------------------------------
+// Name: unix_compat.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#if !PLATFORM_UNIX
+#error Do not compile this.
+#endif
+#include "unix_compat.h"
+
+#include <Compat/compat.h>
+#include <Compat/fileio.h>
+
+#include <Internal/common.h>
+#include <Internal/filesystem.h>
+
+#include <Memory/allocation.h>
+#include <Logging/logdata.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <unistd.h>
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <iostream>
+
+#if 1
+#define dprintf printf
+#else
+static inline void dprintf(const char *fmt, ...) {}
+#endif
+
+void WorkingDir( string *dir ) {
+ char cwd[kPathSize];
+ char* ret = getcwd(cwd, kPathSize);
+ if(ret != NULL) {
+ *dir = cwd;
+ }
+}
+
+std::string GetAbsPath( const char* rel ) {
+ char path[PATH_MAX];
+
+ if( realpath(rel, path) != NULL ) {
+ return std::string(path);
+ } else {
+ return std::string();
+ }
+}
+
+int initWriteDir(char* path, int buf_size) {
+ std::string write_dir;
+
+ #if PLATFORM_MACOSX
+ const char *home_env = getenv("HOME");
+ if (home_env == NULL){
+ home_env = "./";
+ }
+ write_dir += home_env;
+ write_dir += "/Library/Application Support/Overgrowth/";
+ #else
+ const char* env_path = getenv("XDG_DATA_HOME");
+ if(env_path){
+ write_dir += env_path;
+ } else {
+ env_path = getenv("HOME");
+ if(!env_path){
+ env_path = "./";
+ }
+ write_dir += env_path;
+ write_dir += "/.local/share";
+ }
+ write_dir += "/Overgrowth/";
+ #endif
+
+ FormatString(path, buf_size, "%s", write_dir.c_str());
+
+ return 0;
+}
+
+static char *findBinaryInPath(const char *bin, char *envr) {
+ size_t alloc_size = 0;
+ char *exe = NULL;
+ char *start = envr;
+ char *ptr;
+
+ do
+ {
+ size_t size;
+ ptr = strchr(start, ':'); // find next $PATH separator.
+ if (ptr)
+ *ptr = '\0';
+
+ size = strlen(start) + strlen(bin) + 2;
+ if (size > alloc_size)
+ {
+ char *x = (char *) realloc(exe, size);
+ if (x == NULL)
+ {
+ if (exe != NULL)
+ OG_FREE(exe);
+ return NULL;
+ }
+
+ alloc_size = size;
+ exe = x;
+ }
+
+ // build full binary path...
+ strcpy(exe, start);
+ if ((exe[0] == '\0') || (exe[strlen(exe) - 1] != '/'))
+ strcat(exe, "/");
+ strcat(exe, bin);
+
+ if (access(exe, X_OK) == 0) // Exists as executable? We're done.
+ {
+ strcpy(exe, start); // i'm lazy. piss off.
+ return(exe);
+ } /* if */
+
+ start = ptr + 1; // start points to beginning of next element.
+ } while (ptr != NULL);
+
+ if (exe != NULL)
+ OG_FREE(exe);
+
+ return(NULL); // doesn't exist in path.
+}
+
+void chdirToBasePath(const char *argv0) {
+ char *newdir = NULL;
+ const char *find = strchr(argv0, '/');
+ const char *envr = getenv("PATH");
+
+ if ((find == NULL) && (envr == NULL))
+ return;
+
+ if (find != NULL) { // Has a real path
+ newdir = (char *) OG_MALLOC(strlen(argv0)+1);
+ strcpy(newdir, argv0);
+ } else {
+ // If there isn't a path on argv0, then look through the $PATH for it.
+ char *envrcpy = new char[strlen(envr) + 1];
+ strcpy(envrcpy, envr);
+ newdir = findBinaryInPath(argv0, envrcpy);
+ delete[] envrcpy;
+ }
+
+ if (newdir)
+ {
+ char *ptr = strrchr(newdir, '/');
+ if (ptr)
+ *ptr = '\0';
+
+ char realbuf[PATH_MAX];
+ if (realpath(newdir, realbuf) == NULL)
+ strcpy(realbuf, newdir);
+
+ OG_FREE(newdir);
+
+ //dprintf("basepath '%s'\n", realbuf);
+ chdir(realbuf);
+ }
+}
+
+static void caseCorrectFilename(char *path) {
+ if (*path == '\0')
+ return; // this is the root directory ("/"), so just return.
+
+ if (access(path, F_OK) == 0)
+ return; // fast path: it definitely exists in acceptable case.
+
+ DIR *dir = NULL;
+ char *base = strrchr(path, '/');
+ if (base == path)
+ dir = opendir("/");
+ else if (base == NULL)
+ {
+ dir = opendir(".");
+ base = path-1;
+ }
+ else
+ {
+ *base = '\0';
+ dir = opendir(path);
+ *base = '/';
+ }
+ base++;
+
+ if (dir == NULL)
+ return; // basedir doesn't exist at all, handled elsewhere.
+
+ if (*base != '\0')
+ {
+ struct dirent *dent;
+ while ((dent = readdir(dir)) != NULL)
+ {
+ if (strcasecmp(base, dent->d_name) == 0) // found a match.
+ {
+ strcpy(base, dent->d_name); // make sure case is right.
+ break;
+ }
+ }
+ }
+
+ closedir(dir);
+}
+
+std::string caseCorrect(const std::string & path )
+{
+ char* buf = (char*)OG_MALLOC( (path.length()+1) * sizeof( char ) );
+
+ strncpy( buf, path.c_str(), path.length()+1 );
+
+ caseCorrect(buf);
+
+ std::string ret(buf);
+ OG_FREE(buf);
+ return ret;
+}
+
+// TODO: figure out long-term solution for case-sensitivity
+void caseCorrect(char* path) {
+ for (int i = 0; path[i]; i++)
+ {
+ if ((path[i] == '/') || (path[i] == '\\'))
+ {
+ path[i] = '\0';
+ caseCorrectFilename(path);
+ path[i] = '/';
+ }
+ }
+
+ // get last piece of the path.
+ caseCorrectFilename(path);
+}
+
+std::vector<std::string>& getSubdirectories( const char *basepath, std::vector<std::string>& mods ) {
+ DIR* moddir = opendir( basepath );
+ std::string path(basepath);
+
+ if( moddir )
+ {
+ struct dirent *entry;
+
+ while( (entry = readdir(moddir)) )
+ {
+ if( strcmp( entry->d_name, ".." ) != 0
+ && strcmp( entry->d_name, "." ) != 0 )
+ {
+ std::string fullPath;
+ if( path[path.length()-1] == '/' )
+ {
+ fullPath = path + std::string(entry->d_name);
+ }
+ else
+ {
+ fullPath = path + "/" + std::string(entry->d_name);
+ }
+
+ struct stat filestat;
+
+ lstat( fullPath.c_str(), &filestat );
+
+ if( S_ISDIR(filestat.st_mode) || S_ISLNK(filestat.st_mode) )
+ {
+ mods.push_back( fullPath );
+ }
+ }
+ }
+ closedir(moddir);
+ }
+ else
+ {
+
+ }
+
+ return mods;
+}
+
+
+std::vector<std::string>& getDeepManifest( const char *basepath, const char* prefix, std::vector<std::string>& files )
+{
+ std::string s_prefix(prefix);
+ DIR* moddir = opendir( basepath );
+ std::string path(basepath);
+
+ if( moddir )
+ {
+ struct dirent entrydata;
+ struct dirent *entry;
+
+ while( !readdir_r(moddir, &entrydata, &entry) && entry )
+ {
+ if( strcmp( entry->d_name, ".." ) != 0
+ && strcmp( entry->d_name, "." ) != 0 )
+ {
+ std::string fullPath;
+ std::string sub_prefix;
+ if( path[path.length()-1] == '/' )
+ {
+ fullPath = path + std::string(entry->d_name);
+ sub_prefix = s_prefix + std::string(entry->d_name);
+ }
+ else
+ {
+ fullPath = path + "/" + std::string(entry->d_name);
+
+ if( s_prefix.length() == 0 )
+ {
+ sub_prefix = std::string(entry->d_name);
+ }
+ else
+ {
+ sub_prefix = s_prefix + "/" + std::string(entry->d_name);
+ }
+ }
+
+ struct stat filestat;
+ //Using lstat because the file mode in dirent doesnt exist outside linux and bsd.
+ lstat( fullPath.c_str(), &filestat );
+
+ if( S_ISDIR( filestat.st_mode ) || S_ISLNK(filestat.st_mode) )
+ {
+ getDeepManifest( fullPath.c_str(), sub_prefix.c_str(), files );
+ }
+ else if( S_ISREG( filestat.st_mode ) )
+ {
+ files.push_back( sub_prefix );
+ }
+ }
+ }
+ closedir(moddir);
+ }
+ else
+ {
+ }
+
+ return files;
+}
+
+bool fileExist( const char *path )
+{
+ return access(path, F_OK) == 0;
+}
+
+bool fileReadable( const char* path )
+{
+ return access(path, R_OK) == 0;
+}
+
+bool isFile( const char* path )
+{
+ struct stat filestat;
+ //Using lstat because the file mode in dirent doesnt exist outside linux and bsd.
+ if( lstat( path, &filestat ) == 0 )
+ {
+
+ if( S_ISREG( filestat.st_mode ) )
+ {
+ return true;
+ }
+ }
+ else
+ {
+ LOGE << "Could not lstat " << path << std::endl;
+ return false;
+ }
+
+ return false;
+}
+
+bool areSame( const char *path1, const char *path2 )
+{
+ struct stat statbuf1;
+ struct stat statbuf2;
+
+ int status1 = stat( path1, &statbuf1 );
+ int status2 = stat( path2, &statbuf2 );
+
+ if( status1 == 0 && status2 == 0 )
+ {
+ //Check if same inode on same device
+ return (statbuf1.st_ino == statbuf2.st_ino && statbuf1.st_dev == statbuf2.st_dev);
+ }
+ else
+ {
+ LOGE << "Unable to stat " << path1 << " and/or " << path2 << std::endl;
+ return false;
+ }
+}
+
+std::string dumpIntoFile( const void* buf, size_t nbyte )
+{
+ char path[512];
+ snprintf(path,256,"%s/OGDAFILE.XXXXXX", P_tmpdir );
+
+ int fd = mkstemp(path);
+
+ if( fd == -1 )
+ {
+ LOGE << "Error creating a tmp file" << std::endl;
+ exit(1);
+ }
+ else
+ {
+ if( (ssize_t)nbyte != write( fd, buf, nbyte ) )
+ {
+ LOGE << "Didn't write as much as expected in dumpIntoFile" << std::endl;
+ }
+
+ close(fd);
+ }
+
+ return std::string(path);
+}
+
+bool checkFileAccess(const char* path){
+ return access(path, R_OK) != -1;
+}
+
+void createParentDirs(const char* abs_path) {
+ char build_path[kPathSize] = {'\0'};
+ for(int i=0; abs_path[i] != '\0'; ++i){
+ if(abs_path[i] == '/'){
+ if(!CheckFileAccess(build_path)){
+ mkdir(build_path, S_IRWXU);
+ }
+ }
+ build_path[i] = abs_path[i];
+ }
+}
+
+int os_movefile( const char *source, const char *dest )
+{
+ return rename(source, dest);
+}
+
+int os_deletefile( const char *path )
+{
+ return remove(path);
+}
+
+int os_createfile( const char *path )
+{
+ FILE* file = fopen(path, "w");
+ if(file) {
+ fclose(file);
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+int os_fileexists( const char *path )
+{
+ return access(path, F_OK);
+}
+
+// end of unix_compat.cpp ...
+
diff --git a/Source/Compat/UNIX/unix_compat.h b/Source/Compat/UNIX/unix_compat.h
new file mode 100644
index 00000000..627cb000
--- /dev/null
+++ b/Source/Compat/UNIX/unix_compat.h
@@ -0,0 +1,56 @@
+//-----------------------------------------------------------------------------
+// Name: unix_compat.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#if !PLATFORM_UNIX
+#error Do not compile this.
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <climits>
+#include <cassert>
+#include <cstdlib>
+#include <cstdio>
+#include <string>
+#include <vector>
+
+#ifndef PATH_MAX
+#define PATH_MAX 2048
+#endif
+
+extern "C" { void nonExistantFunctionToAlertYouToStubbedCode(void); }
+//#define STUBBED(txt) nonExistantFunctionToAlertYouToStubbedCode()
+#define STUBBED(txt) { static bool virgin = true; if (virgin) { virgin = false; fprintf(stderr, "STUBBED: %s at %s:%d\n", txt, __FILE__, __LINE__); } }
+#define MessageBox(hwnd, title, text, buttons) { STUBBED("MSGBOX"); fprintf(stderr, "MSGBOX: %s...%s (%s)\n", title, text, #buttons); }
+#define _open(x,y) open(x,y, S_IREAD | S_IWRITE)
+#define _O_RDONLY O_RDONLY
+#define _lseek(x,y,z) lseek(x,y,z)
+#define _close(x) close(x)
+
+#define _ASSERT(x) assert(x)
+
+// end of unix_compat.h ...
+
diff --git a/Source/Compat/UNIX/unix_filepath.cpp b/Source/Compat/UNIX/unix_filepath.cpp
new file mode 100644
index 00000000..ab66b591
--- /dev/null
+++ b/Source/Compat/UNIX/unix_filepath.cpp
@@ -0,0 +1,54 @@
+//-----------------------------------------------------------------------------
+// Name: unix_filepath.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#if !PLATFORM_UNIX
+#error Do not compile this.
+#endif
+
+#include <Compat/filepath.h>
+#include <Compat/compat.h>
+#include <Global/global_config.h>
+#include <Utility/strings.h>
+
+#include <unistd.h>
+#include <cstdlib>
+#include <string>
+
+std::string FindPath(std::string path) {
+ return path;
+}
+
+std::string AbsPathFromRel(std::string path){
+ const int max = 512;
+ char curr[max];
+ getcwd(curr, max);
+ return std::string(curr)+path;
+}
+
+void GetCaseCorrectPath(const char* input_path, char* correct_path) {
+ // Correct case using realpath() and cut off working directory
+ int num_dirs = CountCharsInString(input_path, '/');
+ char path[kPathSize];
+ realpath(input_path, path);
+ char *cut_path = &path[FindNthCharFromBack(path,'/',num_dirs+1)+1];
+ strcpy(correct_path, cut_path);
+}
diff --git a/Source/Compat/Win/os_dialogs_win.cpp b/Source/Compat/Win/os_dialogs_win.cpp
new file mode 100644
index 00000000..e5efcf94
--- /dev/null
+++ b/Source/Compat/Win/os_dialogs_win.cpp
@@ -0,0 +1,93 @@
+//-----------------------------------------------------------------------------
+// Name: os_dialogs_win.cpp
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: This is a simple wrapper for displaying error messages
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Compat/os_dialogs.h>
+#include <Logging/logdata.h>
+#include <UserInput/input.h>
+#include <Internal/config.h>
+
+#define NOMINMAX
+#include <windows.h>
+
+#include <map>
+#include <string>
+
+
+ErrorResponse OSDisplayError(const char* title, const char* contents, ErrorType type) {
+ bool old_mouse = Input::Instance()->GetGrabMouse();
+ Input::Instance()->SetGrabMouse(false);
+ UIShowCursor(1);
+
+ int msgboxID;
+ switch (type) {
+ case _ok_cancel_retry:
+ msgboxID = MessageBox(NULL,
+ contents,
+ title,
+ MB_CANCELTRYCONTINUE |
+ MB_DEFBUTTON2 |
+ MB_ICONHAND |
+ MB_TOPMOST |
+ MB_SETFOREGROUND);
+ break;
+ case _ok_cancel:
+ msgboxID = MessageBox(NULL,
+ contents,
+ title,
+ MB_OKCANCEL |
+ MB_DEFBUTTON1 |
+ MB_ICONHAND |
+ MB_TOPMOST |
+ MB_SETFOREGROUND);
+ break;
+ case _ok:
+ msgboxID = MessageBox(NULL,
+ contents,
+ title,
+ MB_DEFBUTTON1 |
+ MB_ICONHAND |
+ MB_TOPMOST |
+ MB_SETFOREGROUND);
+ break;
+ }
+ switch (msgboxID) {
+ case IDCANCEL:
+ return _er_exit;
+ break;
+ case IDTRYAGAIN:
+ case IDRETRY:
+ Input::Instance()->SetGrabMouse(old_mouse);
+ UIShowCursor(0);
+ return _retry;
+ break;
+ case IDCONTINUE:
+ case IDOK:
+ Input::Instance()->SetGrabMouse(old_mouse);
+ UIShowCursor(0);
+ return _continue;
+ break;
+ }
+ return _continue;
+}
diff --git a/Source/Compat/Win/win_compat.cpp b/Source/Compat/Win/win_compat.cpp
new file mode 100644
index 00000000..13c2a1c6
--- /dev/null
+++ b/Source/Compat/Win/win_compat.cpp
@@ -0,0 +1,631 @@
+//-----------------------------------------------------------------------------
+// Name: win_compat.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#if !PLATFORM_WINDOWS
+#error Do not compile this.
+#endif
+#include "win_compat.h"
+
+#include <Compat/compat.h>
+#include <Compat/fileio.h>
+
+#include <Internal/integer.h>
+#include <Internal/error.h>
+#include <Internal/filesystem.h>
+
+#include <Logging/logdata.h>
+#include <Memory/allocation.h>
+
+#include <utf8/utf8.h>
+
+#include <io.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#define NOMINMAX
+#include <Windows.h>
+#include <direct.h>
+#include <tchar.h>
+#include <strsafe.h>
+#include <conio.h>
+
+#include <cassert>
+#include <iostream>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+/// here's where things start getting ugly, replacements for stuff windows doesn't have.
+#include <ShlObj.h>
+
+// X_OK should be execute bit, but non exists in io.h? Windows doesn't have execute rights?
+#define X_OK 0x0
+#define F_OK 0x0
+#define W_OK 0x02
+#define R_OK 0x04
+#define RW_OK 0x06
+
+using std::string;
+using std::wstring;
+using std::basic_string;
+using std::vector;
+using std::endl;
+
+int UTF8Access(const string &path, int access_mode){
+ return _waccess(UTF16fromUTF8(path).c_str(), access_mode);
+}
+
+bool chdir(char *dir) {
+ return (SetCurrentDirectoryW(UTF16fromUTF8(dir).c_str()) == TRUE)?true:false;
+}
+
+#define S_IRWXU 0x0000000
+
+#if 1
+#define dprintf printf
+#else
+static inline void dprintf(const char *fmt, ...) {}
+#endif
+
+char* strtok_r(
+ char *str,
+ const char *delim,
+ char **nextp)
+{
+ char *ret;
+
+ if (str == NULL)
+ {
+ str = *nextp;
+ }
+
+ str += strspn(str, delim);
+
+ if (*str == '\0')
+ {
+ return NULL;
+ }
+
+ ret = str;
+
+ str += strcspn(str, delim);
+
+ if (*str)
+ {
+ *str++ = '\0';
+ }
+
+ *nextp = str;
+
+ return ret;
+}
+
+string GetAbsPath(const char* full_rel_path)
+{
+ wstring w_full_rel_path = UTF16fromUTF8(full_rel_path);
+ wchar_t w_full_path[PATH_MAX];
+ _wfullpath(w_full_path, w_full_rel_path.c_str(), PATH_MAX);
+ return UTF8fromUTF16(wstring(w_full_path));
+}
+
+// Get the Windows write directory
+// ("$VOL:/$USERNAME/Documents/Wolfire/Overgrowth")
+int initWriteDir(char* writedir, int kPathBufferSize) {
+ // Read documents path into UTF16 char buffer
+ wchar_t writedir_utf16_buf[MAX_PATH];
+ HRESULT result = SHGetFolderPathW(
+ NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT,
+ writedir_utf16_buf);
+ if(result != S_OK){
+ return 1;
+ }
+
+ /*wchar_t short_writedir_utf16_buf[MAX_PATH];
+ DWORD err = GetShortPathNameW(writedir_utf16_buf, short_writedir_utf16_buf, MAX_PATH);
+
+ if(err == 0) {
+ LPVOID lpMsgBuf;
+ LOGF << "Failed at retrieving short path for write dir" << endl;
+ DWORD dw = GetLastError();
+ FormatMessageA(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL,
+ dw,
+ MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
+ (LPTSTR) &lpMsgBuf,
+ 0, NULL );
+
+ DisplayError("Error init writedir", (const char*)lpMsgBuf, _ok, true);
+ LOGF << lpMsgBuf << endl;
+
+ LocalFree(lpMsgBuf);
+ return 4;
+ } else if( err > MAX_PATH ) {
+ LOGF << "Short path of write dir could not fit in buffer" << endl;
+ return 5;
+ }
+
+ if(!WideCharToMultiByte(CP_UTF8, 0, short_writedir_utf16_buf, -1,
+ writedir, kPathBufferSize, NULL, NULL))*/
+ if(!WideCharToMultiByte(CP_UTF8, 0, writedir_utf16_buf, -1,
+ writedir, kPathBufferSize, NULL, NULL))
+ {
+ return 2;
+ }
+ int len = strlen(writedir);
+ // Replace backslashes with forward slashes
+ for(int i=0; i<len; ++i){
+ if(writedir[i] == '\\'){
+ writedir[i] = '/';
+ }
+ }
+ // Append slash to the end if there isn't one already
+ if(writedir[len-1] != '/'){
+ if(len == kPathBufferSize){
+ return 3;
+ }
+ strcat(writedir, "/");
+ }
+ // Add program identifier
+ const char* to_append = "Wolfire/Overgrowth/";
+ if(len + (int)strlen(to_append) >= kPathBufferSize){
+ return 3;
+ }
+ strcat(writedir, to_append);
+ return 0;
+}
+
+static char *findBinaryInPath(const char *bin, char *envr) {
+ size_t alloc_size = 0;
+ char *exe = NULL;
+ char *start = envr;
+ char *ptr;
+ do {
+ size_t size;
+ ptr = strchr(start, ':'); // find next $PATH separator.
+ if (ptr)
+ *ptr = '\0';
+
+ size = strlen(start) + strlen(bin) + 2;
+ if (size > alloc_size) {
+ char *x = (char *) realloc(exe, size);
+ if (x == NULL) {
+ if (exe != NULL) {
+ free(exe);
+ }
+ return NULL;
+ }
+ alloc_size = size;
+ exe = x;
+ }
+ // build full binary path...
+ strcpy_s(exe, size, start);
+ if ((exe[0] == '\0') || (exe[strlen(exe) - 1] != '/'))
+ strcat_s(exe, size, "/");
+ strcat_s(exe, size, bin);
+ if (UTF8Access(exe, X_OK) == 0) { // Exists as executable? We're done.
+ strcpy_s(exe, size, start); // i'm lazy. piss off.
+ return(exe);
+ } /* if */
+ start = ptr + 1; // start points to beginning of next element.
+ } while (ptr != NULL);
+
+ if (exe != NULL) {
+ OG_FREE(exe);
+ }
+ return(NULL); // doesn't exist in path.
+}
+
+// Set home directory so file paths work properly when running from shortcuts
+void chdirToBasePath(char *argv0) {
+ basic_string<uint32_t> path_utf32;
+ {
+ wchar_t path_utf16_buf[kPathSize];
+ GetModuleFileNameW( NULL, path_utf16_buf, kPathSize);
+
+ // Convert path from UTF16 to UTF32 by way of UTF8
+ string path_utf8;
+ utf8::utf16to8(path_utf16_buf, &path_utf16_buf[wcsnlen(path_utf16_buf, kPathSize)-1], back_inserter(path_utf8));
+ utf8::utf8to32(path_utf8.begin(), path_utf8.end(), back_inserter(path_utf32));
+ }
+
+ // Eliminate everything after final '\\'
+ path_utf32 = path_utf32.substr(0, path_utf32.rfind('\\'));
+
+ wstring path_utf16;
+ {
+ // Convert path from UTF32 to UTF16 by way of UTF8
+ string path_utf8;
+ utf8::utf32to8(path_utf32.begin(), path_utf32.end(), back_inserter(path_utf8));
+ utf8::utf8to16(path_utf8.begin(), path_utf8.end(), back_inserter(path_utf16));
+ }
+
+ _wchdir(path_utf16.c_str());
+}
+
+void SetWorkingDir(const char* path) {
+ _wchdir(UTF16fromUTF8(path).c_str());
+}
+
+void caseCorrect(char* path) {
+ return;
+}
+
+string caseCorrect(const string & path ) {
+ return path;
+}
+
+static void caseCorrectFilename(char *path, bool inwritedir) {
+ if (*path == '\0') {
+ return; // this is the root directory ("/"), so just return.
+ }
+ if (UTF8Access(path, F_OK) == 0) {
+ return; // fast path: it definitely exists in acceptable case.
+ }
+ char *base = strrchr(path, '/');
+ if (base == path) {
+ } else if (base == NULL) {
+ base = path-1;
+ } else {
+ *base = '\0';
+ CreateDirectoryW(UTF16fromUTF8(path).c_str(), NULL);
+ *base = '/';
+ }
+ base++;
+}
+
+void ShortenWindowsPath(string &str) {
+ wstring path_utf16_buf;
+ wstring extra;
+ utf8::utf8to16(str.begin(), str.end(), back_inserter(path_utf16_buf));
+ struct _stat buf;
+ while(_wstat( &path_utf16_buf[0], &buf )!=0){
+ unsigned offset = path_utf16_buf.rfind('/');
+ if(offset == string::npos){
+ offset = path_utf16_buf.rfind('\\');
+ }
+ if(offset == string::npos){
+ return;
+ }
+ extra = path_utf16_buf.substr(offset, path_utf16_buf.size()-offset) + extra;
+ path_utf16_buf = path_utf16_buf.substr(0, offset);
+ }
+ wstring short_buf;
+ DWORD len = GetShortPathNameW(&path_utf16_buf[0], NULL, 0);
+
+ if(len == 0) {
+ LPVOID lpMsgBuf;
+
+ DWORD dw = GetLastError();
+ FormatMessageA(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL,
+ dw,
+ MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
+ (LPTSTR) &lpMsgBuf,
+ 0, NULL );
+
+ DisplayError("Error init writedir", (const char*)lpMsgBuf, _ok, true);
+
+ LocalFree(lpMsgBuf);
+ }
+ short_buf.resize(len);
+ len = GetShortPathNameW(&path_utf16_buf[0], &short_buf[0], len);
+ if(len == 0) {
+ LPVOID lpMsgBuf;
+
+ DWORD dw = GetLastError();
+ FormatMessageA(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL,
+ dw,
+ MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
+ (LPTSTR) &lpMsgBuf,
+ 0, NULL );
+
+ DisplayError("Error init writedir", (const char*)lpMsgBuf, _ok, true);
+
+ LocalFree(lpMsgBuf);
+ }
+ short_buf.resize(short_buf.size()-1);
+ short_buf += extra;
+ str.clear();
+ utf8::utf16to8(&short_buf[0], &short_buf[wcsnlen(&short_buf[0], kPathSize)], back_inserter(str));
+}
+
+void WorkingDir( string *dir ) {
+ wchar_t *buf = _wgetcwd(NULL, NULL);
+ string path_utf8;
+ utf8::utf16to8(buf, &buf[wcsnlen(buf, kPathSize)], back_inserter(path_utf8));
+ OG_FREE(buf);
+ *dir = path_utf8;
+}
+
+vector<string>& getSubdirectories( const char *basepath, vector<string>& mods )
+{
+ wstring path_utf16_buf;
+ string path(basepath);
+
+ path += "\\*";
+
+ utf8::utf8to16(path.begin(), path.end(), back_inserter(path_utf16_buf));
+
+ WIN32_FIND_DATAW ffd;
+ size_t length_of_arg;
+ HANDLE hFind = INVALID_HANDLE_VALUE;
+ DWORD dwError=0;
+
+ // Check that the input path plus 1 is not longer than MAX_PATH.
+ StringCchLengthW(path_utf16_buf.c_str(), MAX_PATH, &length_of_arg);
+
+ if (length_of_arg > (MAX_PATH-1))
+ {
+ LOGE << "Directory path is too long." << endl;
+ }
+ else
+ {
+ // Find the first file in the directory.
+ hFind = FindFirstFileW(path_utf16_buf.c_str(), &ffd);
+
+ if (hFind == INVALID_HANDLE_VALUE)
+ {
+ LOGE << "FindFirstFile failed " << GetLastError() << endl;
+ }
+ else
+ {
+ do
+ {
+ if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ {
+ wstring output_utf16( ffd.cFileName );
+ string output;
+ utf8::utf16to8(output_utf16.begin(), output_utf16.end(), back_inserter(output));
+ //printf("modpath:%s\n", output.c_str() );
+ if( output != string(".") && output != string("..") )
+ {
+ mods.push_back(string(basepath) + "/" + output);
+ }
+ }
+ }
+ while (FindNextFileW(hFind, &ffd) != 0);
+
+ dwError = GetLastError();
+ if (dwError != ERROR_NO_MORE_FILES)
+ {
+ LOGE << "Error gettings subdirs: " << dwError << endl;
+ }
+ FindClose(hFind);
+ }
+ }
+ return mods;
+}
+
+vector<string>& getDeepManifest( const char *basepath, const char* prefix, vector<string>& files )
+{
+ string s_prefix( prefix );
+ wstring path_utf16_buf;
+ string path(basepath);
+
+ path += "\\*";
+
+ utf8::utf8to16(path.begin(), path.end(), back_inserter(path_utf16_buf));
+
+ WIN32_FIND_DATAW ffd;
+ size_t length_of_arg;
+ HANDLE hFind = INVALID_HANDLE_VALUE;
+ DWORD dwError=0;
+
+ // Check that the input path plus 1 is not longer than MAX_PATH.
+ StringCchLengthW(path_utf16_buf.c_str(), MAX_PATH, &length_of_arg);
+
+ if (length_of_arg > (MAX_PATH-1))
+ {
+ LOGE << "Directory path is too long." << endl;
+ }
+ else
+ {
+ // Find the first file in the directory.
+ hFind = FindFirstFileW(path_utf16_buf.c_str(), &ffd);
+
+ if (hFind == INVALID_HANDLE_VALUE)
+ {
+ LOGE << "FindFirstFile failed " << GetLastError() << endl;
+ }
+ else
+ {
+ do
+ {
+
+ wstring output_utf16( ffd.cFileName );
+ string output;
+ utf8::utf16to8(output_utf16.begin(), output_utf16.end(), back_inserter(output));
+
+ string sub_prefix;
+
+ if( s_prefix.length() == 0 )
+ {
+ sub_prefix = output;
+ }
+ else
+ {
+ sub_prefix = s_prefix + "/" + output;
+ }
+
+ if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ {
+ //printf("modpath:%s\n", output.c_str() );
+ if( output != string(".") && output != string("..") )
+ {
+ string fullpath = string(basepath) + "/" + output;
+
+ getDeepManifest( fullpath.c_str(), sub_prefix.c_str(), files );
+ }
+ }
+ else
+ {
+ files.push_back( sub_prefix );
+ }
+ }
+ while (FindNextFileW(hFind, &ffd) != 0);
+
+ dwError = GetLastError();
+ if (dwError != ERROR_NO_MORE_FILES)
+ {
+ LOGE << "Error gettings subdirs: " << dwError << endl;
+ }
+ FindClose(hFind);
+ }
+ }
+ return files;
+}
+
+bool fileExist( const char *path )
+{
+ return UTF8Access(string(path), F_OK) == 0;
+}
+
+bool fileReadable( const char* path )
+{
+ return UTF8Access(string(path), R_OK) == 0;
+}
+
+int os_copyfile( const char *source, const char *dest )
+{
+ return CopyFileW(UTF16fromUTF8(source).c_str(), UTF16fromUTF8(dest).c_str(), FALSE) == 0;
+}
+
+int os_movefile( const char *source, const char *dest )
+{
+ return MoveFileExW(UTF16fromUTF8(source).c_str(), UTF16fromUTF8(dest).c_str(), MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) == 0;
+}
+
+int os_deletefile( const char *path )
+{
+ return _wremove(UTF16fromUTF8(path).c_str());
+}
+
+int os_createfile( const char *path )
+{
+ FILE* file = _wfopen(UTF16fromUTF8(path).c_str(), L"w");
+
+ if(file) {
+ fclose(file);
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+int os_fileexists( const char *path )
+{
+ return _waccess(UTF16fromUTF8(path).c_str(), F_OK);
+}
+
+string dumpIntoFile( const void* buf, size_t nbyte )
+{
+ LOGF << "dumpIntoFile PLACEHOLDER" << endl;
+ exit(1);
+}
+
+bool isFile( const char* path )
+{
+ struct _stat buf;
+ if(_wstat( UTF16fromUTF8(path).c_str(), &buf )==0){
+ if(_S_IFREG & buf.st_mode){
+ return true; // It is a regular file
+ } else {
+ return false; // It might be a directory or pipe
+ }
+ } else {
+ return false; //No such file
+ }
+}
+
+bool checkFileAccess(const char* path)
+{
+ return _waccess(UTF16fromUTF8(path).c_str(), 04) != -1;
+}
+
+void createParentDirs(const char* abs_path) {
+ char build_path[kPathSize] = {'\0'};
+ for(int i=0; abs_path[i] != '\0'; ++i){
+ if(abs_path[i] == '/' || abs_path[i] == '\\') {
+ if(!CheckFileAccess(build_path)){
+ CreateDirectoryW(UTF16fromUTF8(build_path).c_str(), NULL);
+ }
+ }
+ build_path[i] = abs_path[i];
+ }
+}
+
+bool areSame(const char* path1, const char* path2 )
+{
+ wstring w_path1 = UTF16fromUTF8(path1);
+ wstring w_path2 = UTF16fromUTF8(path2);
+
+ LPCWSTR szPath1 = w_path1.c_str();
+ LPCWSTR szPath2 = w_path2.c_str();
+
+ //Validate the input
+ _ASSERT(szPath1 != NULL);
+ _ASSERT(szPath2 != NULL);
+
+ //Get file handles
+ HANDLE handle1 = ::CreateFileW(szPath1, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ HANDLE handle2 = ::CreateFileW(szPath2, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+
+ bool bResult = false;
+
+ //if we could open both paths...
+ if (handle1 != INVALID_HANDLE_VALUE && handle2 != INVALID_HANDLE_VALUE)
+ {
+ BY_HANDLE_FILE_INFORMATION fileInfo1;
+ BY_HANDLE_FILE_INFORMATION fileInfo2;
+ if (::GetFileInformationByHandle(handle1, &fileInfo1) && ::GetFileInformationByHandle(handle2, &fileInfo2))
+ {
+ //the paths are the same if they refer to the same file (fileindex) on the same volume (volume serial number)
+ bResult = fileInfo1.dwVolumeSerialNumber == fileInfo2.dwVolumeSerialNumber &&
+ fileInfo1.nFileIndexHigh == fileInfo2.nFileIndexHigh &&
+ fileInfo1.nFileIndexLow == fileInfo2.nFileIndexLow;
+ }
+ }
+
+ //free the handles
+ if (handle1 != INVALID_HANDLE_VALUE )
+ {
+ ::CloseHandle(handle1);
+ }
+
+ if (handle2 != INVALID_HANDLE_VALUE )
+ {
+ ::CloseHandle(handle2);
+ }
+
+ //return the result
+ return bResult;
+}
diff --git a/Source/Compat/Win/win_compat.h b/Source/Compat/Win/win_compat.h
new file mode 100644
index 00000000..0139b11c
--- /dev/null
+++ b/Source/Compat/Win/win_compat.h
@@ -0,0 +1,52 @@
+//-----------------------------------------------------------------------------
+// Name: win_compat.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+#if !PLATFORM_WINDOWS
+#error Do not compile this.
+#endif
+
+#include <cstdio>
+#include <climits>
+#include <cstdlib>
+#include <string>
+#include <vector>
+
+#ifndef PATH_MAX
+#define PATH_MAX 2048
+#endif
+
+extern "C" { void nonExistantFunctionToAlertYouToStubbedCode(void); }
+#define STUBBED(txt) { static bool virgin = true; if (virgin) { virgin = false; fprintf(stderr, "STUBBED: %s at %s:%d\n", txt, __FILE__, __LINE__); } }
+
+char* strtok_r(char *str, const char *delim, char **nextp);
+
+static const char* win_compat_err_str[] = {
+ "No error",
+ "Problem getting documents path",
+ "Error converting utf16 string to utf8",
+ "Write dir is too long.",
+ "Error getting short-path version of write dir",
+ "Short path version of write dir could not fit in buffer"
+};
+
+void SetWorkingDir(const char* path);
diff --git a/Source/Compat/Win/win_filepath.cpp b/Source/Compat/Win/win_filepath.cpp
new file mode 100644
index 00000000..46c9c8b3
--- /dev/null
+++ b/Source/Compat/Win/win_filepath.cpp
@@ -0,0 +1,146 @@
+//-----------------------------------------------------------------------------
+// Name: win_filepath.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#if !PLATFORM_WINDOWS
+#error Do not compile this.
+#endif
+
+#include <Compat/compat.h>
+
+#include <Compat/fileio.h>
+#include <Internal/error.h>
+
+#define NOMINMAX
+#include <direct.h>
+#include <Windows.h>
+#include <tchar.h>
+#include <strsafe.h>
+
+#include <cstdio>
+#include <vector>
+#include <string>
+#include <cctype>
+
+using std::string;
+using std::vector;
+using std::equal;
+using std::toupper;
+
+static const int kPathBufferSize = 1024;
+
+static int getdir (string dir, vector<string> &files) {
+ WIN32_FIND_DATA ffd;
+ TCHAR szDir[MAX_PATH];
+ HANDLE hFind = INVALID_HANDLE_VALUE;
+ DWORD dwError=0;
+
+ // Prepare string for use with FindFile functions. First, copy the
+ // string to a buffer, then append '\*' to the directory name.
+
+ StringCchCopy(szDir, MAX_PATH, dir.c_str());
+ StringCchCat(szDir, MAX_PATH, TEXT("\\*"));
+
+ // Find the first file in the directory.
+
+ hFind = FindFirstFile(szDir, &ffd);
+
+ if (INVALID_HANDLE_VALUE == hFind)
+ {
+ DisplayError("Error","Getdir fail");
+ return dwError;
+ }
+
+ // List all the files in the directory with some info about them.
+
+ do {
+ files.push_back(ffd.cFileName);
+ } while (FindNextFile(hFind, &ffd) != 0);
+
+ dwError = GetLastError();
+ if (dwError != ERROR_NO_MORE_FILES)
+ {
+ DisplayError("Error","Getdir fail 2");
+ }
+
+ FindClose(hFind);
+ return dwError;
+}
+
+//Case insensitive equals compare
+bool iequals(string & str1, string &str2)
+{
+ return ((str1.size() == str2.size()) && equal(str1.begin(), str1.end(), str2.begin(), [](char & c1, char & c2) {
+ return (c1 == c2 || toupper(c1) == toupper(c2));
+ }));
+}
+
+string FindPath(string path) {
+ //"Data/Skeletons/basic-attached-guard-joints.phxbn"
+ string path_so_far = ".";
+ string next_dir;
+ size_t old_slash_pos = 0;
+ size_t slash_pos = 0;
+ bool case_problem = false;
+ bool found_match;
+ do {
+ // Get next path segment
+ slash_pos = path.find('/',old_slash_pos);
+ next_dir = path.substr(old_slash_pos,slash_pos-old_slash_pos);
+ old_slash_pos = slash_pos + 1;
+
+ // Compare to current directory
+ vector<string> files;
+ getdir(path_so_far, files);
+ found_match = false;
+ for(unsigned i=0; i<files.size(); i++){
+ if (iequals(files[i], next_dir)) {
+ if(files[i] != next_dir){
+ case_problem = true;
+ }
+ path_so_far += '/' + files[i];
+ found_match = true;
+ }
+ }
+ if(!found_match) {
+ return path;
+ }
+ } while (slash_pos != string::npos);
+
+ if(case_problem){
+ DisplayError("Warning",("Case discrepency:\nSearched for \n\"./"+ path + "\"\nFound \n\"" + path_so_far + "\"").c_str(), _ok_cancel, false);
+ }
+
+ return path_so_far;
+}
+
+string AbsPathFromRel(string path){
+ string wdir;
+ WorkingDir(&wdir);
+ return wdir + path;
+}
+
+void GetCaseCorrectPath(const char* input_path, char* correct_path) {
+ // Correct case by converting to short path and back to long
+ char short_path[kPathBufferSize];
+ GetShortPathName(input_path, short_path, kPathBufferSize);
+ GetLongPathName(short_path, correct_path, kPathBufferSize);
+}
diff --git a/Source/Compat/Win/win_hardware_info.cpp b/Source/Compat/Win/win_hardware_info.cpp
new file mode 100644
index 00000000..dce031a0
--- /dev/null
+++ b/Source/Compat/Win/win_hardware_info.cpp
@@ -0,0 +1,79 @@
+//-----------------------------------------------------------------------------
+// Name: win_hardware_info.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <Compat/hardware_info.h>
+
+#include <Logging/logdata.h>
+
+#include <nvapi.h>
+#include <opengl.h>
+
+#include <string>
+
+unsigned GetWindowsNvidiaDriverInfo(){
+ NvAPI_Status status = NvAPI_Initialize();
+ NvAPI_ShortString msg;
+ if (status != NVAPI_OK) {
+ NvAPI_GetErrorMessage (status, msg);
+ LOGE << "Cannot initialize NvAPI: " << msg << std::endl;
+ return 0;
+ }
+
+ NvDisplayHandle hDisp;
+ status = NvAPI_EnumNvidiaDisplayHandle (0, &hDisp);
+ if (status != NVAPI_OK) {
+ NvAPI_GetErrorMessage (status, msg);
+ LOGE << "Cannot initialize NvAPI: " << msg << std::endl;
+ return 0;
+ }
+
+ NV_DISPLAY_DRIVER_VERSION ver;
+ memset (&ver, 0, sizeof (NV_DISPLAY_DRIVER_VERSION));
+ ver.version = NV_DISPLAY_DRIVER_VERSION_VER;
+ status = NvAPI_GetDisplayDriverVersion (hDisp, &ver);
+ if (status != NVAPI_OK) {
+ NvAPI_GetErrorMessage (status, msg);
+ LOGE << "Cannot initialize NvAPI: " << msg << std::endl;
+ return 0;
+ }
+
+ return ver.drvVersion;
+}
+
+unsigned GetDriverVersion(GLVendor vendor) {
+ if(vendor == _nvidia){
+ return GetWindowsNvidiaDriverInfo();
+ } else if(vendor == _ati){
+ //ATI driver versions are the end of the GL_VERSION string, e.g.:
+ //2.1.8577 (8577 is the driver version)
+ std::string driver_string = (const char*)glGetString(GL_VERSION);
+
+ //Remove everything before the final dot
+ unsigned final_dot_index = driver_string.rfind('.');
+ driver_string = driver_string.substr(final_dot_index+1);
+
+ //What remains is the driver string. Return as unsigned int
+ return atoi(driver_string.c_str());
+ } else {
+ return 0;
+ }
+}
diff --git a/Source/Compat/Win/win_time.cpp b/Source/Compat/Win/win_time.cpp
new file mode 100644
index 00000000..6751fe7c
--- /dev/null
+++ b/Source/Compat/Win/win_time.cpp
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------------
+// Name: win_time.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <Compat/time.h>
+
+#define NOMINMAX
+#include <windows.h>
+
+uint64_t GetPrecisionTime() {
+ LARGE_INTEGER tick;
+ QueryPerformanceCounter(&tick);
+ return (uint64_t)tick.QuadPart;
+}
+
+uint64_t ToNanoseconds(uint64_t time){
+ LARGE_INTEGER ticksPerSecond;
+ QueryPerformanceFrequency(&ticksPerSecond);
+
+ time *= 1000000000;
+ time /= ticksPerSecond.QuadPart;
+
+ return time;
+}
diff --git a/Source/Compat/compat.h b/Source/Compat/compat.h
new file mode 100644
index 00000000..c76ce5f2
--- /dev/null
+++ b/Source/Compat/compat.h
@@ -0,0 +1,68 @@
+//-----------------------------------------------------------------------------
+// Name: compat.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <vector>
+#include <string>
+
+std::string GetAbsPath(const char* full_rel_path);
+int initWriteDir(char* writedir, int kPathBufferSize);
+void chdirToBasePath(char *argv0);
+void WorkingDir(std::string *dir);
+void ShortenWindowsPath(std::string &str);
+void caseCorrect(char* path);
+std::string caseCorrect(const std::string & path );
+std::vector<std::string>& getSubdirectories( const char *basepath, std::vector<std::string>& mods );
+std::vector<std::string>& getDeepManifest( const char *basepath, const char* prefix, std::vector<std::string>& files );
+bool fileExist( const char *path );
+bool fileReadable( const char *path );
+
+int os_copyfile( const char *source, const char *dest );
+int os_movefile( const char *source, const char *dest );
+int os_deletefile( const char *path );
+int os_createfile( const char *path );
+int os_fileexists( const char *path );
+
+std::string dumpIntoFile( const void* buf, size_t nbyte );
+
+bool isFile( const char* path );
+
+bool checkFileAccess(const char* path);
+void createParentDirs(const char* abs_path);
+bool areSame(const char* path1, const char* path2 );
+
+#if PLATFORM_UNIX == 1 //This is shared on mac and linux.
+#include <Compat/UNIX/unix_compat.h>
+#endif
+
+#if PLATFORM_LINUX == 1
+#include <Compat/Linux/linux_compat.h>
+#endif
+
+#if PLATFORM_MACOSX == 1
+#include <Compat/Mac/mac_compat.h>
+#endif
+
+#if PLATFORM_WINDOWS == 1
+#include <Compat/Win/win_compat.h>
+#endif
diff --git a/Source/Compat/fileio.cpp b/Source/Compat/fileio.cpp
new file mode 100644
index 00000000..5e6425c7
--- /dev/null
+++ b/Source/Compat/fileio.cpp
@@ -0,0 +1,109 @@
+//-----------------------------------------------------------------------------
+// Name: fileio.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "fileio.h"
+
+#include <Internal/filesystem.h>
+#include <Internal/casecorrectpath.h>
+
+#ifndef NO_ERR
+#include <Internal/error.h>
+#endif
+
+#ifdef _WIN32
+#define NOMINMAX
+#include <windows.h>
+#endif
+
+using std::string;
+using std::wstring;
+using std::ios_base;
+using std::fstream;
+using std::ifstream;
+using std::ofstream;
+
+#ifdef _WIN32
+wstring UTF16fromUTF8( const string& path_utf8 ) {
+ int size = MultiByteToWideChar(CP_UTF8, 0, path_utf8.c_str(), -1, NULL, 0);
+ wstring path_utf16;
+ path_utf16.resize(size);
+ MultiByteToWideChar(CP_UTF8, 0, path_utf8.c_str(), -1, &path_utf16[0], size);
+ return path_utf16;
+}
+
+string UTF8fromUTF16( const wstring& path_utf16) {
+ int size = WideCharToMultiByte(CP_UTF8, 0, path_utf16.c_str(), -1, NULL, 0, NULL, NULL );
+ string output;
+ output.resize(size);
+ WideCharToMultiByte(CP_UTF8, 0, path_utf16.c_str(), -1, &output[0], size, NULL, NULL );
+ return output;
+}
+#endif
+
+FILE* my_fopen( const char* abs_path, const char* mode ) {
+#ifdef _WIN32
+ FILE* file = _wfopen(UTF16fromUTF8(abs_path).c_str(), UTF16fromUTF8(mode).c_str());
+#else
+ FILE* file = fopen(abs_path, mode);
+#endif
+ if(!file){
+ // If writing, make sure the necessary directories exist
+ if(mode[0] == 'w'){
+ CreateParentDirs(abs_path);
+#ifdef _WIN32
+ file = _wfopen(UTF16fromUTF8(abs_path).c_str(), UTF16fromUTF8(mode).c_str());
+#else
+ file = fopen(abs_path, mode);
+#endif
+ } else {
+ return NULL;
+ }
+ }
+ if(!file){
+ return NULL;
+ }
+ return file;
+}
+
+void my_fstream_open( fstream &file, const string& path, ios_base::openmode mode) {
+#ifdef _WIN32
+ file.open(UTF16fromUTF8(path).c_str(), mode);
+#else
+ file.open(path.c_str(), mode);
+#endif
+}
+
+void my_ifstream_open( ifstream &file, const string& path, ios_base::openmode mode /*= ios_base::in*/ ) {
+#ifdef _WIN32
+ file.open(UTF16fromUTF8(path).c_str(), mode);
+#else
+ file.open(path.c_str(), mode);
+#endif
+}
+
+void my_ofstream_open( ofstream &file, const string& path, ios_base::openmode mode /*= ios_base::out*/ ) {
+#ifdef _WIN32
+ file.open(UTF16fromUTF8(path).c_str(), mode);
+#else
+ file.open(path.c_str(), mode);
+#endif
+}
diff --git a/Source/Compat/fileio.h b/Source/Compat/fileio.h
new file mode 100644
index 00000000..2860b91c
--- /dev/null
+++ b/Source/Compat/fileio.h
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------------
+// Name: fileio.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <stdio.h>
+#include <fstream>
+
+using std::wstring;
+using std::string;
+using std::fstream;
+using std::ifstream;
+using std::ofstream;
+using std::ios_base;
+
+#ifdef _WIN32
+wstring UTF16fromUTF8( const string& path_utf8 );
+string UTF8fromUTF16( const wstring& path_utf16);
+#endif
+FILE* my_fopen(const char* path, const char* mode);
+void my_fstream_open(fstream &file, const string& path, ios_base::openmode mode);
+void my_ifstream_open(ifstream &file, const string& path, ios_base::openmode mode = ios_base::in);
+void my_ofstream_open(ofstream &file, const string& path, ios_base::openmode mode = ios_base::out);
diff --git a/Source/Compat/filepath.h b/Source/Compat/filepath.h
new file mode 100644
index 00000000..aaa6aa44
--- /dev/null
+++ b/Source/Compat/filepath.h
@@ -0,0 +1,32 @@
+//-----------------------------------------------------------------------------
+// Name: filepath.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <string>
+
+using std::string;
+
+string FindPath(string path);
+string AbsPathFromRel(string path);
+
+void GetCaseCorrectPath(const char* input_path, char* correct_path);
diff --git a/Source/Compat/hardware_info.h b/Source/Compat/hardware_info.h
new file mode 100644
index 00000000..4a12836e
--- /dev/null
+++ b/Source/Compat/hardware_info.h
@@ -0,0 +1,27 @@
+//-----------------------------------------------------------------------------
+// Name: hardware_info.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+enum GLVendor {_unknown, _ati, _nvidia, _intel};
+
+unsigned GetDriverVersion(GLVendor vendor);
diff --git a/Source/Compat/os_dialogs.h b/Source/Compat/os_dialogs.h
new file mode 100644
index 00000000..84c28d78
--- /dev/null
+++ b/Source/Compat/os_dialogs.h
@@ -0,0 +1,41 @@
+//-----------------------------------------------------------------------------
+// Name: os_dialogs.h
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: This is a simple wrapper for displaying error messages
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Utility/compiler_macros.h>
+#include <Internal/error_response.h>
+
+#include <string>
+
+/**
+*Displays an error message.
+*@param title The title of the message box.
+*@param contents The contents of the message box.
+*@param type Which buttons are available
+*@param allow_repetition If false, skips error if exact content has already
+* been displayed
+*/
+ErrorResponse OSDisplayError(const char* title,
+ const char* contents,
+ ErrorType type = _ok_cancel);
diff --git a/Source/Compat/platformsetup.cpp b/Source/Compat/platformsetup.cpp
new file mode 100644
index 00000000..b74388a3
--- /dev/null
+++ b/Source/Compat/platformsetup.cpp
@@ -0,0 +1,184 @@
+//-----------------------------------------------------------------------------
+// Name: platformsetup.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "platformsetup.h"
+
+#include <Internal/filesystem.h>
+#include <Internal/common.h>
+#include <Internal/error.h>
+
+#include <Compat/filepath.h>
+#include <Logging/logdata.h>
+
+#ifdef _WIN32
+#define NOMINMAX
+#include <Windows.h>
+#include <csignal>
+static BOOL WINAPI ConsoleHandlerRoutine(DWORD dwCtrlType) {
+ switch(dwCtrlType){
+ case CTRL_CLOSE_EVENT:
+ case CTRL_LOGOFF_EVENT:
+ case CTRL_SHUTDOWN_EVENT:
+ raise(SIGTERM);
+ break;
+ }
+ return FALSE;
+}
+#endif
+
+#if PLATFORM_UNIX
+#include <cstring>
+#include <libgen.h>
+#endif
+
+// Set up working directory, file paths, Windows console
+void SetUpEnvironment(char* program_path, const char* overloaded_write_dir, const char* overloaded_working_dir) {
+ #if PLATFORM_UNIX
+ // Force numeric locale so numbers parse correctly
+ setenv("LC_NUMERIC", "en_US.utf8", 1);
+ if( strlen(overloaded_working_dir) > 0 ) {
+ chdir(overloaded_working_dir);
+ }
+ char* dirn = dirname( program_path );
+ AddPath(dirn, kDataPaths);
+ char cwd[kPathSize];
+ getcwd(cwd, kPathSize);
+ int len = strlen(cwd);
+ if(len < kPathSize - 1){
+ cwd[len] = '/';
+ cwd[len+1] = '\0';
+ }
+
+ if( false == areSame( cwd, dirn ) )
+ {
+ AddPath(cwd, kDataPaths);
+ }
+ else
+ {
+ LOGI << "Not adding " << cwd << " to FindPath list as it's the same as ./" << std::endl;
+ }
+ char write_dir[kPathSize];
+ initWriteDir(write_dir, kPathSize);
+
+ if( strcmp(overloaded_write_dir,"") != 0 )
+ {
+ AddPath(overloaded_write_dir, kWriteDir);
+ }
+ else
+ {
+ AddPath(write_dir, kWriteDir);
+ }
+
+ FreeImage_Initialise();
+ #elif defined(_WIN32)
+ std::string cwd;
+ if( strlen(overloaded_working_dir) > 0 ) {
+ SetWorkingDir(overloaded_working_dir);
+ WorkingDir(&cwd);
+ AddPath(ApplicationPathSeparators(cwd).c_str(), kDataPaths);
+ } else {
+ //TODO: Consider if we actually want to include the default working dir as a data path, it seems unecessary
+ WorkingDir(&cwd);
+ AddPath(ApplicationPathSeparators(cwd).c_str(), kDataPaths);
+ #ifdef _DEPLOY
+ char path[] = "";
+ chdirToBasePath(path); // Set working directory to .exe location (so shortcuts work)
+ WorkingDir(&cwd);
+ AddPath(ApplicationPathSeparators(cwd).c_str(), kDataPaths);
+ #endif
+ }
+ char write_path[kPathSize];
+ int err = initWriteDir(write_path, kPathSize);
+ if(err != 0){
+ FatalError("Error", win_compat_err_str[err]);
+ }
+
+ if( strcmp(overloaded_write_dir,"") != 0 )
+ {
+ AddPath(overloaded_write_dir, kWriteDir);
+ }
+ else
+ {
+ AddPath(write_path, kWriteDir);
+ }
+
+ { // Add working directory to data paths
+ wchar_t long_utf16_buf[kPathSize];
+ if(!_wgetcwd(long_utf16_buf, kPathSize)){
+ DisplayError("Error", "_wgetcwd failed in SetUpEnvironment");
+ }
+ char short_utf8_buf[kPathSize];
+ if(!WideCharToMultiByte(CP_UTF8, 0, long_utf16_buf, -1,
+ short_utf8_buf, kPathSize, NULL, NULL))
+ {
+ DisplayError("Error", "WideCharToMultiByte failed in SetUpEnvironment");
+ }
+ for(int i=0; i<kPathSize; ++i){
+ if(short_utf8_buf[i] == '\\'){
+ short_utf8_buf[i] = '/';
+ }
+ if(short_utf8_buf[i] == '\0' && i<kPathSize-1){
+ short_utf8_buf[i] = '/';
+ short_utf8_buf[i+1] = '\0';
+ break;
+ }
+ }
+ AddPath(short_utf8_buf, kDataPaths);
+ }
+ // Set up Windows console
+ #if OPEN_WIN32_CONSOLE
+ AllocConsole();
+ freopen("CONIN$","rb",stdin);
+ freopen("CONOUT$","wb",stdout);
+ freopen("CONOUT$","wb",stderr);
+ SetConsoleCtrlHandler(ConsoleHandlerRoutine, TRUE);
+ #endif
+ #endif
+
+ CreateParentDirs((std::string(GetWritePath(CoreGameModID).c_str())+"placeholder").c_str());
+
+ string working_dir;
+ WorkingDir(&working_dir);
+ working_dir += "/Auxiliary";
+
+ AddPath(working_dir.c_str(), kDataPaths);
+
+#ifdef AUX_DATA
+ AddPath(AUX_DATA, kDataPaths);
+#endif
+
+ if(CheckWritePermissions(write_path) != 0) {
+ #ifdef _WIN32
+ FatalError("Error", "Couldn't write to \"%s\", make sure write permissions are enabled for that folder. If you are using Windows 10, make sure Overgrowth is added to the Controlled Folder Access whitelist", write_path);
+ #else
+ FatalError("Error", "Couldn't write to \"%s\", make sure write permissions are enabled for that folder", write_path);
+ #endif
+ }
+}
+
+void DisposeEnvironment(){
+ #if PLATFORM_UNIX
+ FreeImage_DeInitialise();
+ #elif defined(_WIN32)
+ FreeConsole();
+ #endif
+}
diff --git a/Source/Compat/platformsetup.h b/Source/Compat/platformsetup.h
new file mode 100644
index 00000000..52bdd945
--- /dev/null
+++ b/Source/Compat/platformsetup.h
@@ -0,0 +1,43 @@
+//-----------------------------------------------------------------------------
+// Name: platformsetup.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#if !PLATFORM_UNIX
+ #define _WIN32 1
+#endif
+
+#include <Compat/compat.h>
+
+//Disable console
+#ifdef _WIN32
+ #pragma comment( linker, "/subsystem:\"windows\" \
+ /entry:\"mainCRTStartup\"" )
+#endif
+
+#if PLATFORM_UNIX
+ #include "FreeImage.h"
+#endif
+
+
+void SetUpEnvironment(char* program_path, const char* overloaded_write_dir, const char* overloaded_working_dir);
+void DisposeEnvironment();
diff --git a/Source/Compat/processpool.cpp b/Source/Compat/processpool.cpp
new file mode 100644
index 00000000..c18ee8d5
--- /dev/null
+++ b/Source/Compat/processpool.cpp
@@ -0,0 +1,725 @@
+//-----------------------------------------------------------------------------
+// Name: processpool.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "processpool.h"
+
+#include "Logging/logdata.h"
+
+#include <iostream>
+#include <sstream>
+#include <cstring>
+#include <cstdlib>
+#include <vector>
+#include <queue>
+
+#ifndef _WIN32
+#include <pthread.h>
+#endif
+
+using std::string;
+using std::wstring;
+using std::endl;
+using std::cout;
+using std::vector;
+using std::queue;
+using std::swap;
+
+namespace {
+const char *kWorkerProcessString = "ProcessPool::IAmAWorkerProcess";
+const char *kChildInitString = "ACK_INIT: Child process reporting for duty";
+}
+
+namespace {
+#ifdef _WIN32
+ void ErrorExit(const string &msg){
+ MessageBox(NULL, msg.c_str(), TEXT("Error"), MB_OK);
+ ExitProcess(1);
+ }
+#else
+ void ErrorExit(const string &msg){
+ LOGE << msg << endl;
+ exit(1);
+ }
+#endif
+}
+
+void ProcessPool::Resize( int _size ) {
+ int old_size = (int)processes_.size();
+ for(int i=_size; i<old_size; ++i){
+ delete processes_[i];
+ }
+ processes_.resize(_size, NULL);
+ for(int i=old_size; i<_size; ++i){
+ processes_[i] = new ProcessHandle(worker_path_, this);
+ }
+}
+
+int ProcessPool::GetIdleProcessIndex() {
+ int num_processes = (int)processes_.size();
+ for(int i=0; i<num_processes; ++i){
+ if(processes_[i]->idle()){
+ return i;
+ }
+ }
+ return -1;
+}
+
+ProcessPool::Error ProcessPool::ProcessFirstTaskInQueue() {
+ if(tasks_.empty()){
+ int num_processes = (int)processes_.size();
+ int num_idle = 0;
+ for(int i=0; i<num_processes; ++i){
+ if(processes_[i]->idle()){
+ ++num_idle;
+ }
+ }
+ if(num_idle == num_processes){
+ #ifdef _WIN32
+ SetEvent(idle_event_);
+ #else
+ if(pthread_mutex_lock(&idle_event_mutex_)){
+ ErrorExit("Error locking mutex");
+ }
+ idle_event_bool_ = true;
+ if(pthread_cond_signal(&idle_event_cond_)){
+ ErrorExit("Error signalling condition");
+ }
+ if(pthread_mutex_unlock(&idle_event_mutex_)){
+ ErrorExit("Error unlocking mutex");
+ }
+ #endif
+ }
+ return ProcessPool::NO_TASK_IN_QUEUE;
+ } else {
+#ifdef _WIN32
+ ResetEvent(idle_event_);
+#else
+ if(pthread_mutex_lock(&idle_event_mutex_)){
+ ErrorExit("Error locking mutex");
+ }
+ idle_event_bool_ = false;
+ if(pthread_mutex_unlock(&idle_event_mutex_)){
+ ErrorExit("Error unlocking mutex");
+ }
+#endif
+ }
+ int idle_process = GetIdleProcessIndex();
+ if(idle_process == -1){
+ return ProcessPool::NO_IDLE_PROCESS;
+ }
+ processes_[idle_process]->Process(tasks_.front());
+ tasks_.pop();
+ return ProcessPool::SUCCESS;
+}
+
+bool ProcessPool::AmIAWorkerProcess( int argc, char* argv[] ) {
+ return (argc >= 2 && strcmp(argv[1],kWorkerProcessString) == 0);
+}
+
+ProcessPool::ProcessPool( const string &worker_path, int size ):
+ worker_path_(worker_path)
+{
+ #ifdef _WIN32
+ mutex_ = CreateMutex(NULL, false, NULL);
+ idle_event_ = CreateEvent(NULL, true, true, NULL);
+ #else
+ pthread_mutex_init(&mutex_, NULL);
+ pthread_mutex_init(&idle_event_mutex_, NULL);
+ pthread_cond_init(&idle_event_cond_, NULL);
+ idle_event_bool_ = true;
+ #endif
+ Resize(size);
+}
+
+ProcessPool::~ProcessPool() {
+ Resize(0);
+ #ifdef _WIN32
+ CloseHandle(mutex_);
+ CloseHandle(idle_event_);
+ #else
+ pthread_mutex_destroy(&mutex_);
+ pthread_mutex_destroy(&idle_event_mutex_);
+ pthread_cond_destroy(&idle_event_cond_);
+ #endif
+}
+
+void ProcessPool::Schedule( const string& task ) {
+#ifdef _WIN32
+ WaitForSingleObject(mutex_,INFINITE);
+#else
+ if(pthread_mutex_lock(&mutex_)){
+ ErrorExit("Error locking mutex");
+ }
+#endif
+ tasks_.push(task);
+ ProcessFirstTaskInQueue();
+#ifdef _WIN32
+ if(!ReleaseMutex(mutex_)){
+ ErrorExit("Error releasing mutex");
+ }
+#else
+ if(pthread_mutex_unlock(&mutex_)){
+ ErrorExit("Error releasing mutex");
+ }
+#endif
+}
+
+ProcessHandle::ProcessHandle( const string &worker_path, ProcessPool* parent_process_pool )
+ : idle_(true), os_process_(worker_path), parent_process_pool_(parent_process_pool)
+{}
+
+bool ProcessHandle::ProcessMessageFromChild(const string& msg){
+ size_t divider = (int)msg.find(": ");
+ if(divider == string::npos){
+ ErrorExit("No divider in message from child: "+msg);
+ }
+ string type = msg.substr(0, divider);
+ string body = msg.substr(divider+2, msg.size()-(divider+2));
+ if(type == "PRINT"){
+ cout << body << endl;
+ }
+ if(type == "STATE"){
+ if(body == "IDLE"){
+ cout << "Task complete!" << endl;
+ return true;
+ }
+ }
+ return false;
+}
+
+void ProcessHandle::ProcessInBackground( ) {
+ while(!ProcessMessageFromChild(os_process_.WaitForChildMessage())){}
+ idle_ = true;
+ parent_process_pool_->NotifyTaskComplete();
+}
+
+namespace {
+#ifdef _WIN32
+ DWORD WINAPI ThreadFunc(LPVOID process_handle_ptr){
+ ((ProcessHandle*)process_handle_ptr)->ProcessInBackground();
+ return 0;
+ }
+#else
+ void* ThreadFunc(void* process_handle_ptr){
+ ((ProcessHandle*)process_handle_ptr)->ProcessInBackground();
+ return NULL;
+ }
+#endif
+}
+
+void ProcessHandle::Process( const string& task ) {
+ os_process_.SendMessageToChild("TASK: "+task);
+ idle_ = false;
+#ifdef _WIN32
+ HANDLE thread = CreateThread(NULL, 0, ThreadFunc, this, 0, NULL);
+ CloseHandle(thread);
+#else
+ pthread_t thread;
+ pthread_create(&thread, NULL, ThreadFunc, this);
+#endif
+}
+
+namespace {
+const int kPathBufferSize = 1024;
+const int kPipeBufSize = 256;
+#ifdef _WIN32
+wstring WStrFromCStr(const char* cstr){
+ int cstr_len = (int)strlen(cstr);
+ wstring wstr;
+ wstr.resize(cstr_len);
+ for(int i=0; i<cstr_len; ++i){
+ wstr[i] = cstr[i];
+ }
+ return wstr;
+}
+#endif
+
+#ifdef _WIN32
+void ReadMessageFromPipe(const HANDLE &pipe_handle, string *msg){
+ DWORD dwRead;
+ CHAR chBuf[kPipeBufSize];
+ BOOL bSuccess = FALSE;
+ string &message = *msg;
+ message.clear();
+
+ int offset = 0;
+ bool continue_reading = true;
+ while(continue_reading){
+ if(!ReadFile( pipe_handle, chBuf, 1, &dwRead, NULL)){
+ ExitProcess(1); // Other process was probably killed
+ }
+ if(chBuf[0] == '\0'){
+ continue_reading = false;
+ } else {
+ message += chBuf[0];
+ }
+ }
+}
+#else
+void ReadMessageFromPipe(int pipe_handle, string *msg){
+ int bytes_read;
+ char buf[1];
+ string &message = *msg;
+ message.clear();
+
+ bool continue_reading = true;
+ while(continue_reading){
+ bytes_read = read(pipe_handle, buf, 1);
+ if(bytes_read <= 0){
+ exit(1); // Other process was probably killed
+ }
+ if(buf[0] == '\0'){
+ continue_reading = false;
+ } else {
+ message += buf[0];
+ }
+ }
+}
+#endif
+}
+
+
+#ifdef _WIN32
+void WriteMessageToPipe(const HANDLE& pipe_handle, const string &msg){
+ int msg_length = (int)msg.length();
+ int total_sent = 0;
+ DWORD num_written;
+ while (total_sent < msg_length){
+ if(!WriteFile(pipe_handle, &msg[0], (DWORD)msg.length(), &num_written, NULL)){
+ ExitProcess(1); // Other process was probably killed
+ }
+ total_sent += num_written;
+ }
+ CHAR end_str[] = {'\0'};
+ if(!WriteFile(pipe_handle, end_str, 1, &num_written, NULL) || num_written != 1){
+ ExitProcess(1); // Other process was probably killed
+ }
+}
+#else
+void WriteMessageToPipe(int pipe_handle, const string &msg){
+ int msg_length = (int)msg.length();
+ int total_sent = 0;
+ int bytes_written;
+ while (total_sent < msg_length){
+ bytes_written = write(pipe_handle, &msg[0], (int)msg.length());
+ if(bytes_written<0){
+ exit(1); // Other process was probably killed
+ }
+ total_sent += bytes_written;
+ }
+ char end_str[] = {'\0'};
+ bytes_written = write(pipe_handle, end_str, 1);
+ if(bytes_written != 1){
+ exit(1); // Other process was probably killed
+ }
+}
+#endif
+
+string OSProcess::WaitForChildMessage() {
+ string msg;
+ ReadMessageFromPipe(read_pipe_, &msg);
+ return msg;
+}
+
+#ifdef _WIN32
+OSProcess::OSProcess(const string &worker_path) {
+ LOGI << "Creating OS Process" << endl;
+ wchar_t path_utf16_buf[kPathBufferSize];
+ GetModuleFileNameW( NULL, path_utf16_buf, kPathBufferSize);
+ int last_slash_pos = -1;
+ for(int i=0; i<kPathBufferSize; ++i){
+ if(path_utf16_buf[i] == '\\'){
+ path_utf16_buf[i] = '/';
+ last_slash_pos = i;
+ } else if(path_utf16_buf[i] == '\0'){
+ break;
+ }
+ }
+
+ if(!MultiByteToWideChar(CP_UTF8, 0, worker_path.c_str(), -1, &path_utf16_buf[last_slash_pos+1], kPathBufferSize-(last_slash_pos+1))){
+ ErrorExit("Error converting worker path utf8 string to utf16: " + worker_path);
+ }
+
+ // This uses UTF16 for the first arg and UTF8 for subsequent args
+ wstring param;
+ param.reserve(kPathBufferSize);
+ param += '\"';
+ param += path_utf16_buf;
+ param += '\"';
+ param += L" ";
+ param += WStrFromCStr(kWorkerProcessString);
+
+ STARTUPINFOW startup_info;
+ memset(&startup_info, 0, sizeof(startup_info));
+ memset(&process_info_, 0, sizeof(process_info_));
+ startup_info.cb = sizeof(startup_info);
+ startup_info.dwFlags |= STARTF_USESTDHANDLES;
+
+ SECURITY_ATTRIBUTES inheritable;
+ inheritable.nLength = sizeof(SECURITY_ATTRIBUTES);
+ inheritable.bInheritHandle = TRUE;
+ inheritable.lpSecurityDescriptor = NULL;
+
+ // Create pipes for communication with child
+ if(!CreatePipe(&read_pipe_, &startup_info.hStdOutput, &inheritable, 0)){
+ ErrorExit("Error creating first child process pipe");
+ }
+ if(!CreatePipe(&startup_info.hStdInput, &write_pipe_, &inheritable, 0)){
+ ErrorExit("Error creating second child process pipe");
+ }
+ startup_info.hStdError = GetStdHandle(STD_OUTPUT_HANDLE);
+
+ // Set parent end of pipes to not be inheritable
+ if(!SetHandleInformation(read_pipe_, HANDLE_FLAG_INHERIT, 0)){
+ ErrorExit("Error setting inherit properties of first child process pipe");
+ }
+ if(!SetHandleInformation(write_pipe_, HANDLE_FLAG_INHERIT, 0)){
+ ErrorExit("Error setting inherit properties of second child process pipe");
+ }
+
+ if(!CreateProcessW(path_utf16_buf,
+ &param[0],
+ NULL, NULL, true,
+ NORMAL_PRIORITY_CLASS,
+ NULL, NULL, &startup_info,
+ &process_info_))
+ {
+ ErrorExit("Could not open process: "+worker_path);
+ }
+
+ if(WaitForChildMessage() != kChildInitString){
+ ErrorExit("Invalid initial acknowledgement from child process");
+ }
+}
+
+OSProcess::~OSProcess() {
+ CloseHandle(process_info_.hThread);
+ TerminateProcess(process_info_.hProcess, 0);
+ CloseHandle(process_info_.hProcess);
+ CloseHandle(read_pipe_);
+ CloseHandle(write_pipe_);
+}
+#else
+#ifdef __APPLE__
+#include <mach-o/dyld.h>
+#endif
+OSProcess::OSProcess(const string& worker_path) {
+ LOGI << "Creating OS Process" << endl;
+
+ int pipe_in[2], pipe_out[2];
+ if(pipe(pipe_in) == -1){
+ ErrorExit("Error creating first pipe");
+ }
+ if(pipe(pipe_out) == -1){
+ ErrorExit("Error creating second pipe");
+ }
+
+#ifdef __APPLE__
+ string path;
+ {
+ char path_buf[1024];
+ uint32_t size = sizeof(path_buf);
+ if(_NSGetExecutablePath(path_buf, &size) == 0){
+ path = path_buf;
+ } else {
+ ErrorExit("Could not get application path");
+ }
+ }
+#else
+ string path;
+ char buf[1024];
+ ssize_t len = ::readlink("/proc/self/exe", buf, sizeof(buf)-1);
+ if (len != -1) {
+ buf[len] = '\0';
+ path = buf;
+ } else {
+ ErrorExit("Could not get application path");
+ }
+#endif
+ size_t last_slash = path.rfind('/');
+ if(last_slash == string::npos){
+ path.clear();
+ } else {
+ path = path.substr(0,last_slash+1);
+ }
+ path += worker_path;
+
+
+ int pid = fork();
+ if(pid == 0){
+ if(dup2(pipe_in[0], STDIN_FILENO) == -1){
+ ErrorExit("Error assigning child STDIN pipe");
+ }
+ if(dup2(pipe_out[1], STDOUT_FILENO) == -1){
+ ErrorExit("Error assigning child STDOUT pipe");
+ }
+ if(dup2(pipe_out[1], STDERR_FILENO) == -1){
+ ErrorExit("Error assigning child STDERR pipe");
+ }
+ if(close(pipe_in[0]) == -1){
+ ErrorExit("Error closing pipe a");
+ }
+ if(close(pipe_in[1]) == -1){
+ ErrorExit("Error closing pipe b");
+ }
+ if(close(pipe_out[0]) == -1){
+ ErrorExit("Error closing pipe c");
+ }
+ if(close(pipe_out[1]) == -1){
+ ErrorExit("Error closing pipe d");
+ }
+
+ vector<char*> args;
+ args.push_back((char*)path.c_str());
+ args.push_back((char*)kWorkerProcessString);
+ args.push_back(NULL);
+ if(execv(path.c_str(), &args[0]) == -1){
+ ErrorExit("Could not exec: "+path);
+ }
+ } else if(pid == -1){
+ ErrorExit("Problem with fork()");
+ }
+
+ if(close(pipe_in[0]) == -1){
+ ErrorExit("Error closing pipe e");
+ }
+ if(close(pipe_out[1]) == -1){
+ ErrorExit("Error closing pipe f");
+ }
+
+ process_info_ = pid;
+ read_pipe_ = pipe_out[0];
+ write_pipe_ = pipe_in[1];
+
+ if(WaitForChildMessage() != kChildInitString){
+ ErrorExit("Invalid initial acknowledgement from child process");
+ }
+}
+
+#include <signal.h>
+OSProcess::~OSProcess() {
+ kill(process_info_, SIGKILL);
+ if(close(read_pipe_) == -1){
+ ErrorExit("Error closing pipe e");
+ }
+ if(close(write_pipe_) == -1){
+ ErrorExit("Error closing pipe f");
+ }
+}
+#endif
+
+
+void OSProcess::SendMessageToChild( const string &msg ) {
+ WriteMessageToPipe(write_pipe_, msg);
+}
+
+namespace {
+
+void SendMessageToParent(const string &msg){
+#ifdef _WIN32
+ WriteMessageToPipe(GetStdHandle(STD_OUTPUT_HANDLE), msg);
+#else
+ WriteMessageToPipe(STDOUT_FILENO, msg);
+#endif
+}
+string WaitForParentMessage(){
+ string msg;
+#ifdef _WIN32
+ ReadMessageFromPipe(GetStdHandle(STD_INPUT_HANDLE), &msg);
+#else
+ ReadMessageFromPipe(STDIN_FILENO, &msg);
+#endif
+ return msg;
+}
+
+int ChildProcessMessage(const string& msg, const ProcessPool::JobMap &job_map){
+ size_t divider = (int)msg.find(": ");
+ if(divider == string::npos){
+ ErrorExit("No divider in message from parent: "+msg);
+ }
+ string type = msg.substr(0, divider);
+ string body = msg.substr(divider+2, msg.size()-(divider+2));
+ if(type == "TASK"){
+ SendMessageToParent("PRINT: Starting task \""+body+"\"");
+ size_t space = (int)body.find(' ');
+ string job;
+ string params;
+ if(space == string::npos){
+ job = body;
+ } else {
+ job = body.substr(0, space);
+ params = body.substr(space + 1, body.size()-(space+1));
+ }
+ ProcessPool::JobMap::const_iterator iter = job_map.find(job);
+ if(iter == job_map.end()){
+ ErrorExit("No job named: "+job);
+ }
+ ProcessPool::JobFunctionPtr func = iter->second;
+ vector<string> params_separated;
+ while(!params.empty()){
+ bool in_quotes = false;
+ int quote_start = -1;
+ int quote_end = -1;
+ size_t space = string::npos;
+ int num_params = (int)params.size();
+ for(int i=0; i<num_params; ++i){
+ if(params[i] == '\"'){
+ if(!in_quotes){
+ quote_start = i;
+ } else {
+ quote_end = i;
+ }
+ in_quotes = !in_quotes;
+ }
+ if(!in_quotes && params[i] == ' '){
+ space = i;
+ break;
+ }
+ }
+ if(quote_start != -1 && quote_end != -1){
+ params_separated.push_back(params.substr(quote_start+1, quote_end-quote_start-1));
+ } else if(space == string::npos){
+ params_separated.push_back(params);
+ } else {
+ params_separated.push_back(params.substr(0, space));
+ }
+ if(space == string::npos){
+ params.clear();
+ } else {
+ params = params.substr(space + 1, params.size()-(space+1));
+ }
+ }
+ int argc = (int)params_separated.size();
+ vector<const char*> argv;
+ for(int i=0; i<argc; ++i){
+ argv.push_back(params_separated[i].c_str());
+ }
+#ifdef _WIN32
+ HANDLE temp_pipe_read, temp_pipe_write;
+ if(!CreatePipe(&temp_pipe_read, &temp_pipe_write, NULL, 0)){
+ ErrorExit("Error creating temporary process pipe");
+ }
+ HANDLE parent_communicate = GetStdHandle(STD_OUTPUT_HANDLE);
+ if(!SetStdHandle(STD_OUTPUT_HANDLE, temp_pipe_write)){
+ ErrorExit("Error assigning STD_OUTPUT_HANDLE to temporary process pipe");
+ }
+ if(!SetStdHandle(STD_ERROR_HANDLE, temp_pipe_write)){
+ ErrorExit("Error assigning STD_ERROR_HANDLE to temporary process pipe");
+ }
+ int ret_val = func(argc, &argv[0]);
+ SetStdHandle(STD_OUTPUT_HANDLE, parent_communicate);
+ SetStdHandle(STD_ERROR_HANDLE, parent_communicate);
+ CloseHandle(temp_pipe_read);
+ CloseHandle(temp_pipe_write);
+#else
+ int pipe_old = dup(STDOUT_FILENO);
+ int pipe_temp[2];
+ if(pipe(pipe_temp) == -1){
+ ErrorExit("Error creating temp pipe");
+ }
+ if(dup2(pipe_temp[1], STDERR_FILENO) == -1){
+ ErrorExit("Error redirecting child STDERR pipe");
+ }
+ if(dup2(pipe_temp[1], STDOUT_FILENO) == -1){
+ ErrorExit("Error redirecting child STDOUT pipe");
+ }
+ int ret_val = func(argc, &argv[0]);
+ if(dup2(pipe_old, STDERR_FILENO) == -1){
+ ErrorExit("Error redirecting child STDERR pipe");
+ }
+ if(dup2(pipe_old, STDOUT_FILENO) == -1){
+ ErrorExit("Error redirecting child STDOUT pipe");
+ }
+ if(close(pipe_temp[0]) == -1){
+ ErrorExit("Error closing pipe a");
+ }
+ if(close(pipe_temp[1]) == -1){
+ ErrorExit("Error closing pipe b");
+ }
+ if(close(pipe_old) == -1){
+ ErrorExit("Error closing pipe c");
+ }
+#endif
+ SendMessageToParent("NULL: "); // TODO: Why does it stop working without this?
+ return ret_val;
+ }
+ return 0;
+}
+
+} // namespace ""
+
+int ProcessPool::WorkerProcessMain(const JobMap &job_map) {
+ SendMessageToParent(kChildInitString);
+ while(1){
+ string msg = WaitForParentMessage();
+ ChildProcessMessage(msg, job_map);
+ SendMessageToParent("STATE: IDLE");
+ }
+ return 0;
+}
+
+void ProcessPool::NotifyTaskComplete() {
+#ifdef _WIN32
+ WaitForSingleObject(mutex_,INFINITE);
+#else
+ if(pthread_mutex_lock(&mutex_)){
+ ErrorExit("Error locking mutex");
+ }
+#endif
+ ProcessFirstTaskInQueue();
+#ifdef _WIN32
+ if(!ReleaseMutex(mutex_)){
+ ErrorExit("Error releasing mutex");
+ }
+#else
+ if(pthread_mutex_unlock(&mutex_)){
+ ErrorExit("Error releasing mutex");
+ }
+#endif
+}
+
+void ProcessPool::WaitForTasksToComplete() {
+ #ifdef _WIN32
+ WaitForSingleObject(idle_event_, INFINITE);
+ #else
+ if(pthread_mutex_lock(&idle_event_mutex_)){
+ ErrorExit("Error locking mutex");
+ }
+ if(!idle_event_bool_){
+ pthread_cond_wait(&idle_event_cond_, &idle_event_mutex_);
+ }
+ if(pthread_mutex_unlock(&idle_event_mutex_)){
+ ErrorExit("Error locking mutex");
+ }
+
+
+ #endif
+}
+
+void ProcessPool::ClearQueuedTasks() {
+ queue<string> empty;
+ swap(tasks_, empty);
+}
+
+int ProcessPool::NumProcesses() {
+ return processes_.size();
+}
diff --git a/Source/Compat/processpool.h b/Source/Compat/processpool.h
new file mode 100644
index 00000000..1d040a2a
--- /dev/null
+++ b/Source/Compat/processpool.h
@@ -0,0 +1,138 @@
+//-----------------------------------------------------------------------------
+// Name: processpool.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Utility/disallow_copy_and_assign.h>
+
+#include <string>
+#include <vector>
+#include <queue>
+#include <map>
+
+using std::string;
+using std::map;
+using std::vector;
+using std::queue;
+
+#ifdef _WIN32
+#define NOMINMAX
+#include <windows.h>
+class OSProcess {
+ public:
+ OSProcess(const string &worker_path);
+ ~OSProcess();
+
+ string WaitForChildMessage();
+ void SendMessageToChild(const string &msg);
+ private:
+
+ HANDLE read_pipe_;
+ HANDLE write_pipe_;
+ PROCESS_INFORMATION process_info_;
+
+ DISALLOW_COPY_AND_ASSIGN(OSProcess);
+};
+#else
+#include <pthread.h>
+#include <unistd.h>
+class OSProcess {
+public:
+ OSProcess(const string &worker_path);
+ ~OSProcess();
+
+ string WaitForChildMessage();
+ void SendMessageToChild(const string &msg);
+private:
+ int read_pipe_;
+ int write_pipe_;
+ int process_info_;
+
+ DISALLOW_COPY_AND_ASSIGN(OSProcess);
+};
+#endif //_WIN32
+
+class ProcessPool;
+class ProcessHandle {
+public:
+ void Process(const string& task);
+ inline bool idle(){return idle_;}
+ void ProcessInBackground();
+
+ ProcessHandle(const string &worker_path, ProcessPool* parent_process_pool);
+
+private:
+ bool idle_;
+ OSProcess os_process_;
+ ProcessPool* parent_process_pool_;
+
+ bool ProcessMessageFromChild(const string& msg);
+ DISALLOW_COPY_AND_ASSIGN(ProcessHandle);
+};
+
+
+class ProcessPool {
+public:
+ // Change number of process handlers
+ void Resize(int _size);
+
+ typedef int (*JobFunctionPtr)(int argc, const char* argv[]);
+ typedef map<string, ProcessPool::JobFunctionPtr> JobMap;
+ enum Error{SUCCESS = 0,
+ NO_IDLE_PROCESS = -1,
+ NO_TASK_IN_QUEUE = -2};
+ static bool AmIAWorkerProcess( int argc, char* argv[] );
+ static int WorkerProcessMain(const JobMap &job_map);
+
+ void NotifyTaskComplete();
+ void Schedule(const string& task);
+ void WaitForTasksToComplete();
+ void ClearQueuedTasks();
+ int NumProcesses();
+
+ ProcessPool(const string &worker_path, int size=0);
+ ~ProcessPool();
+private:
+
+ // Attempts to start processing the first task in the queue in the first
+ // idle process. Can return SUCCESS, NO_IDLE_PROCESS or NO_TASK_IN_QUEUE.
+ ProcessPool::Error ProcessFirstTaskInQueue();
+
+ // Returns index of the first idle process in pool, or -1 if there
+ // are no idle processes
+ int GetIdleProcessIndex();
+
+ string worker_path_;
+ vector<ProcessHandle*> processes_;
+ queue<string> tasks_;
+#ifdef _WIN32
+ HANDLE mutex_;
+ HANDLE idle_event_;
+#else
+ pthread_mutex_t mutex_;
+ pthread_mutex_t idle_event_mutex_;
+ pthread_cond_t idle_event_cond_;
+ bool idle_event_bool_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(ProcessPool);
+};
diff --git a/Source/Compat/time.h b/Source/Compat/time.h
new file mode 100644
index 00000000..d877cb79
--- /dev/null
+++ b/Source/Compat/time.h
@@ -0,0 +1,28 @@
+//-----------------------------------------------------------------------------
+// Name: time.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/integer.h>
+
+uint64_t GetPrecisionTime();
+uint64_t ToNanoseconds(uint64_t time);
diff --git a/Source/Editors/actors_editor.cpp b/Source/Editors/actors_editor.cpp
new file mode 100644
index 00000000..cba260aa
--- /dev/null
+++ b/Source/Editors/actors_editor.cpp
@@ -0,0 +1,946 @@
+//-----------------------------------------------------------------------------
+// Name: actors_editor.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "actors_editor.h"
+
+#include <Objects/decalobject.h>
+#include <Objects/movementobject.h>
+#include <Objects/itemobject.h>
+#include <Objects/pathpointobject.h>
+#include <Objects/hotspot.h>
+#include <Objects/group.h>
+#include <Objects/prefab.h>
+#include <Objects/placeholderobject.h>
+#include <Objects/ambientsoundobject.h>
+#include <Objects/lightprobeobject.h>
+#include <Objects/lightvolume.h>
+#include <Objects/dynamiclightobject.h>
+#include <Objects/editorcameraobject.h>
+#include <Objects/cameraobject.h>
+#include <Objects/terrainobject.h>
+#include <Objects/envobject.h>
+#include <Objects/navmeshhintobject.h>
+#include <Objects/navmeshregionobject.h>
+#include <Objects/navmeshconnectionobject.h>
+#include <Objects/reflectioncaptureobject.h>
+
+#include <Graphics/models.h>
+#include <Graphics/camera.h>
+#include <Graphics/shaders.h>
+#include <Graphics/textures.h>
+#include <Graphics/palette.h>
+#include <Graphics/pxdebugdraw.h>
+
+#include <Internal/common.h>
+#include <Internal/datemodified.h>
+#include <Internal/memwrite.h>
+#include <Internal/profiler.h>
+#include <Internal/filesystem.h>
+#include <Internal/snprintf.h>
+#include <Internal/config.h>
+
+#include <XML/xml_helper.h>
+#include <XML/level_loader.h>
+
+#include <Online/online_datastructures.h>
+#include <Online/online.h>
+
+#include <UserInput/input.h>
+#include <UserInput/keycommands.h>
+
+#include <Math/vec4math.h>
+#include <GUI/gui.h>
+#include <Game/level.h>
+#include <Logging/logdata.h>
+#include <Game/EntityDescription.h>
+#include <Editors/map_editor.h>
+
+#include <tinyxml.h>
+
+#include <algorithm>
+#include <cstdarg>
+
+extern bool shadow_cache_dirty;
+
+void LoadGenericEntity( const Path& path, EntityDescriptionList &desc_list) {
+ TiXmlDocument doc(path.GetFullPath());
+ if (!doc.LoadFile()) {
+ DisplayError("Error",("Bad xml format in " + path.GetFullPathStr()).c_str());
+ return;
+ }
+ EntityType type = CheckGenericType(doc);
+ EntityDescription desc;
+ desc.AddEntityType(EDF_ENTITY_TYPE, type);
+ desc.AddString(EDF_FILE_PATH, path.GetOriginalPath());
+ desc_list.push_back(desc);
+}
+
+void LoadGenericEntity(const std::string& rel_path, EntityDescriptionList &desc_list) {
+ Path p = FindFilePath(rel_path.c_str(), kDataPaths | kModPaths | kAbsPath);
+ if( p.isValid() == false ) {
+ DisplayError("Error",("Invalid path " + p.GetFullPathStr()).c_str());
+ return;
+ }
+ LoadGenericEntity(p,desc_list);
+}
+
+void LoadDescriptionsFromXMLSection(TiXmlNode* parent, const std::string& section, EntityDescriptionList &desc_list){
+ TiXmlNode* pNode = parent->FirstChild(section.c_str());
+ if(!pNode){
+ return;
+ }
+ LoadEntityDescriptionListFromXML(desc_list, pNode->ToElement());
+}
+
+void LoadSavedPrefab(TiXmlNode* parent, EntityDescriptionList& desc_list) {
+ LoadDescriptionsFromXMLSection(parent, "Prefab", desc_list);
+}
+
+void LoadSavedEntitiesDesc(TiXmlNode* parent, EntityDescriptionList& desc_list) {
+ LoadDescriptionsFromXMLSection(parent, "ActorObjects", desc_list);
+ LoadDescriptionsFromXMLSection(parent, "EnvObjects", desc_list);
+ LoadDescriptionsFromXMLSection(parent, "Decals", desc_list);
+ LoadDescriptionsFromXMLSection(parent, "Hotspots", desc_list);
+ LoadDescriptionsFromXMLSection(parent, "Groups", desc_list);
+}
+
+bool ActorsEditor_AddEntity(SceneGraph* scenegraph, Object* o, std::map<int,int>* id_remap, bool group_addition, bool level_loading) {
+ if(o->GetType() == _env_object && !((EnvObject*)o)->ofr->dynamic){
+ shadow_cache_dirty = true;
+ }
+ std::vector<Object*> children;
+ o->GetChildren(&children);
+ for(size_t i=0, len=children.size(); i<len; ++i) {
+ int old_id = children[i]->GetID();
+ if( ActorsEditor_AddEntity(scenegraph, children[i], id_remap, group_addition, level_loading) == false ) {
+ LOGE << "Failed adding child entity, removing from self" << std::endl;
+ o->ChildLost(children[i]);
+ delete children[i];
+ children[i] = NULL;
+ } else {
+ if(old_id != children[i]->GetID() && id_remap == NULL) {
+ LOGW << "id_remap is NULL when adding entity with children" << std::endl;
+ continue;
+ }
+
+ if(id_remap != NULL)
+ id_remap->insert(std::pair<int,int>(old_id, children[i]->GetID()));
+ }
+ }
+
+ if(o->GetType() == _env_object){
+ ((EnvObject*)o)->added_to_physics_scene_ = false;
+ }
+
+ if(!level_loading || o->GetID() == -1){
+ scenegraph->AssignID(o);
+ }
+
+ if( scenegraph->addObject(o) ) {
+ if(!level_loading){
+ static const int kBufSize = 256;
+ char msg[kBufSize];
+ FormatString(msg, kBufSize, "added_object %d", o->GetID());
+ scenegraph->level->Message(msg);
+ }
+ return true;
+ } else {
+ LOGE << "Failed at adding entity" << std::endl;
+ for( unsigned i = 0; i < children.size(); i++ ) {
+ if( children[i] ) {
+ children[i]->SetParent(NULL);
+ }
+ }
+ return false;
+ }
+
+}
+
+static void GetFlattenedDescList(EntityDescriptionList *desc_list, std::vector<EntityDescription*> *flat_desc_list){
+ for(size_t desc_index=0, len=desc_list->size();
+ desc_index<len;
+ ++desc_index)
+ {
+ EntityDescription *desc = &desc_list->at(desc_index);
+ flat_desc_list->push_back(desc);
+ GetFlattenedDescList(&desc->children, flat_desc_list);
+ }
+}
+
+void LocalizeIDs(EntityDescriptionList *desc_list, bool keep_external_connections){
+ std::vector<EntityDescription*> flat_desc_list;
+ GetFlattenedDescList(desc_list, &flat_desc_list);
+ std::map<int,int> scene_ids;
+ typedef std::map<int,int>::iterator Iter;
+ for(size_t desc_index=0, len=flat_desc_list.size();
+ desc_index<len;
+ ++desc_index)
+ {
+ EntityDescription *desc = flat_desc_list[desc_index];
+ EntityDescriptionField *edf = desc->GetField(EDF_ID);
+ if(edf){
+ int id;
+ memread(&id, sizeof(int), 1, edf->data);
+ int new_id = (int) scene_ids.size();
+
+ scene_ids.insert(std::pair<int,int>(id, new_id));
+
+ edf->data.clear();
+ edf->WriteInt(new_id);
+ //scene_ids.push_back(id);
+ } else {
+ //scene_ids.push_back(-1);
+ LOGW << "Entity has no ID when trying to localize" << std::endl;
+ }
+ }
+ // TODO: do this in O(n) instead of O(n^2)
+ for(size_t desc_index=0, len=flat_desc_list.size();
+ desc_index<len;
+ ++desc_index)
+ {
+ EntityDescription *desc = flat_desc_list[desc_index];
+ EntityDescriptionField *edf = desc->GetField(EDF_CONNECTIONS);
+ if(edf) {
+ std::vector<int> connections;
+ std::vector<int> write_connections;
+ edf->ReadIntVec(&connections);
+ for(size_t connection_index=0, len=connections.size();
+ connection_index<len;
+ ++connection_index )
+ {
+ int id = connections[connection_index];
+ Iter iter = scene_ids.find(id);
+ if(iter != scene_ids.end()) {
+ id = iter->second;
+ write_connections.push_back(id);
+ }
+ else if(keep_external_connections)
+ write_connections.push_back(id);
+ }
+ edf->data.clear();
+ edf->WriteIntVec(write_connections);
+ }
+ edf = desc->GetField(EDF_ITEM_CONNECTIONS);
+ if(edf) {
+ std::vector<ItemConnectionData> connections;
+ std::vector<ItemConnectionData> write_connections;
+ edf->ReadItemConnectionDataVec(&connections);
+ for(size_t connection_index=0, len=connections.size();
+ connection_index<len;
+ ++connection_index )
+ {
+ ItemConnectionData item_connection_data = connections[connection_index];
+ Iter iter = scene_ids.find(item_connection_data.id);
+ if(iter != scene_ids.end()) {
+ item_connection_data.id = iter->second;
+ write_connections.push_back(item_connection_data);
+ }
+ else if(keep_external_connections)
+ write_connections.push_back(item_connection_data);
+ }
+ edf->data.clear();
+ edf->WriteItemConnectionDataVec(write_connections);
+ }
+ edf = desc->GetField(EDF_NAV_MESH_CONNECTIONS);
+ if(edf) {
+ std::vector<NavMeshConnectionData> connections;
+ std::vector<NavMeshConnectionData> write_connections;
+ edf->ReadNavMeshConnectionDataVec(&connections);
+ for(size_t connection_index=0, len=connections.size();
+ connection_index<len;
+ ++connection_index )
+ {
+ NavMeshConnectionData nav_mesh_connection_data = connections[connection_index];
+
+ Iter iter = scene_ids.find(nav_mesh_connection_data.other_object_id);
+ if(iter != scene_ids.end()) {
+ nav_mesh_connection_data.other_object_id = iter->second;
+ write_connections.push_back(nav_mesh_connection_data);
+ }
+ else if(keep_external_connections)
+ write_connections.push_back(nav_mesh_connection_data);
+ }
+ edf->data.clear();
+ edf->WriteNavMeshConnectionDataVec(write_connections);
+ }
+ edf = desc->GetField(EDF_CONNECTIONS_FROM);
+ if(edf) {
+ std::vector<int> connections;
+ std::vector<int> write_connections;
+ edf->ReadIntVec(&connections);
+ for(size_t connection_index=0, len=connections.size();
+ connection_index<len;
+ ++connection_index )
+ {
+ int id = connections[connection_index];
+ Iter iter = scene_ids.find(id);
+ if(iter != scene_ids.end()) {
+ id = iter->second;
+ write_connections.push_back(id);
+ }
+ else if(keep_external_connections) {
+ write_connections.push_back(id);
+ }
+ }
+ edf->data.clear();
+ edf->WriteIntVec(write_connections);
+ }
+ }
+}
+
+void ActorsEditor_CopySelectedEntities(SceneGraph* scenegraph, EntityDescriptionList *desc_list) {
+ desc_list->clear();
+ // Add non-grouped objects to copy list
+ for (size_t i=0, len = scenegraph->objects_.size(); i<len; ++i) {
+ Object* obj = scenegraph->objects_[i];
+ if (obj->Selected() && obj->permission_flags&Object::CAN_COPY) {
+ desc_list->resize(desc_list->size()+1);
+ obj->GetDesc(desc_list->back());
+ }
+ }
+}
+
+EntityType GetTypeFromDesc( const EntityDescription& desc ) {
+ EntityType type;
+ for (unsigned i = 0; i<desc.fields.size(); ++i){
+ const EntityDescriptionField& field = desc.fields[i];
+ if (field.type == EDF_ENTITY_TYPE){
+ memread(&type, sizeof(int), 1, field.data);
+ break;
+ }
+ }
+ return type;
+}
+
+Object * CreateObjectFromDesc( const EntityDescription& desc ) {
+ PROFILER_ZONE(g_profiler_ctx, "CreateObjectFromDesc");
+ EntityType type = GetTypeFromDesc(desc);
+
+ Object* obj = NULL;
+ switch(type){
+ case _movement_object:
+ obj = new MovementObject();
+ break;
+ case _camera_type:
+ obj = new EditorCameraObject();
+ ((CameraObject*)obj)->controlled = true;
+ ActiveCameras::Get()->SetCameraObject((CameraObject*)obj);
+ ActiveCameras::Get()->SetFlags(Camera::kEditorCamera);
+ break;
+ case _item_object:
+ obj = new ItemObject();
+ break;
+ case _ambient_sound_object:
+ obj = new AmbientSoundObject();
+ break;
+ case _path_point_object:
+ obj = new PathPointObject();
+ break;
+ case _env_object:
+ obj = new EnvObject();
+ break;
+ case _decal_object:
+ obj = new DecalObject();
+ break;
+ case _hotspot_object:
+ obj = new Hotspot();
+ break;
+ case _group:
+ obj = new Group();
+ break;
+ case _prefab:
+ obj = new Prefab();
+ break;
+ case _placeholder_object:
+ obj = new PlaceholderObject();
+ break;
+ case _light_probe_object:
+ obj = new LightProbeObject();
+ break;
+ case _reflection_capture_object:
+ obj = new ReflectionCaptureObject();
+ break;
+ case _light_volume_object:
+ obj = new LightVolumeObject();
+ break;
+ case _dynamic_light_object:
+ obj = new DynamicLightObject();
+ break;
+ case _navmesh_hint_object:
+ obj = new NavmeshHintObject();
+ break;
+ case _navmesh_connection_object:
+ obj = new NavmeshConnectionObject();
+ break;
+ case _navmesh_region_object:
+ obj = new NavmeshRegionObject();
+ break;
+ case _terrain_type:
+ case _spawn_point:
+ break;
+ default:
+ FatalError("Error", "ActorsEditor::CreateObjectFromDesc does not support this object type");
+ break;
+ }
+
+ if( obj ) {
+ if( false == obj->SetFromDesc(desc) ) {
+ delete obj;
+ obj = NULL;
+ }
+ }
+
+ return obj;
+}
+
+std::vector<Object*> ActorsEditor_AddEntitiesAtMouse(const Path& source, SceneGraph *scenegraph, const EntityDescriptionList& desc_list, LineSegment mouseray, bool in_new_prefab) {
+ if (desc_list.empty())
+ return std::vector<Object*>();
+
+ Collision c = scenegraph->lineCheckCollidable(mouseray.start, mouseray.end, NULL);
+
+ vec3 spawn_pos = mouseray.start + normalize(mouseray.end - mouseray.start) * 10.0f;
+ if (c.hit && c.hit_what != NULL) {
+ spawn_pos = c.hit_where;
+ }
+
+ return ActorsEditor_AddEntitiesAtPosition(source, scenegraph, desc_list, spawn_pos, in_new_prefab);
+}
+
+std::vector<Object*> ActorsEditor_AddEntitiesAtPosition(const Path& source, SceneGraph *scenegraph, const EntityDescriptionList& desc_list, vec3 spawn_pos, bool in_new_prefab, CommonObjectID hostid) {
+
+ Online* online = Online::Instance();
+ if (online->IsClient()) {
+ if (hostid == 0) {
+ std::string path;
+
+ // This is backwards, but when we paste, source will be "", which means that we rely on desc_list for the path
+ // we therefor have to figure out the path, and deserialize it like this.
+ bool found = false;
+ for (uint32_t i = 0; i < desc_list.size() && !found; i++) {
+ for (uint32_t j = 0; j < desc_list[i].fields.size() && !found; j++) {
+ if (desc_list[i].fields[j].type == EDF_FILE_PATH) {
+ desc_list[i].fields[j].ReadString(&path);
+ found = true;
+ }
+ }
+ }
+
+ online->Send<OnlineMessages::CreateEntity>(path, spawn_pos, 0);
+ return std::vector<Object*>();
+ }
+
+ }
+
+ if (desc_list.empty())
+ return std::vector<Object*>();
+
+ // Get bounding box of the centers of all entities
+ bool center_set = false;
+ vec3 desc_min, desc_max;
+ vec3 vec;
+ for(unsigned i=0; i<desc_list.size(); ++i){
+ const EntityDescriptionField* edf = desc_list[0].GetField(EDF_TRANSLATION);
+ if(edf){
+ memread(vec.entries, sizeof(float), 3, edf->data);
+ if(!center_set){
+ desc_min = vec;
+ desc_max = vec;
+ center_set = true;
+ } else {
+ for(unsigned j=0; j<3; ++j){
+ desc_min[j] = min(desc_min[j], vec[j]);
+ desc_max[j] = max(desc_max[j], vec[j]);
+ }
+ }
+ }
+ }
+
+ // Get midpoint of centers bounding box
+ vec3 copied_center = (desc_min + desc_max) * 0.5f;
+
+ std::map<int,int> id_remap;
+
+ std::vector<Object*> new_objects;
+ for (unsigned i = 0; i < desc_list.size(); i++) {
+ if (GetTypeFromDesc(desc_list[i]) == _decal_object) {
+ // Check we are not exceeding max decal limit
+ if ((scenegraph->decal_objects_.size() - scenegraph->dynamic_decals.size()) >= scenegraph->kMaxStaticDecals) {
+ DisplayError("Warning", "Static decal limit exceeded, cannot add new one!");
+ return new_objects;
+ }
+ }
+ Object* new_entity = CreateObjectFromDesc(desc_list[i]);
+ if( new_entity ) {
+ vec3 offset = (new_entity->GetTranslation() - copied_center);
+ vec3 pos = spawn_pos;
+ pos += offset;
+
+ if( new_entity->IsGroupDerived() ) {
+ Group* group = static_cast<Group*>(new_entity);
+ //generate the relative positioning data before moving group object.
+ group->InitRelMats();
+ }
+
+ const vec4 &dims = new_entity->box_.dims;
+ //The following compensation will instead occur on the full prefab, it's to offset up from the
+ //collision point.
+ if( in_new_prefab == false ) {
+ LOGI << "New entity box: " << dims << std::endl;
+ if (new_entity->GetType() == _movement_object) {
+ pos += vec3(0.0f,1.0f,0.0f);
+ }
+ if (new_entity->GetType() == _item_object) {
+ pos += vec3(0.0f,dims[1]*0.5f+0.1f,0.0f);
+ }
+ if (new_entity->GetType() == _env_object) {
+ pos[1] += dims[1]*((EnvObject*)new_entity)->ofr->ground_offset;
+ }
+ }
+ new_entity->SetTranslation(pos);
+ if( new_entity->IsGroupDerived() ) {
+ Group* group = static_cast<Group*>(new_entity);
+ group->PropagateTransformsDown(true);
+ }
+
+ int old_id = new_entity->GetID();
+ if( ActorsEditor_AddEntity(scenegraph, new_entity, &id_remap, false) ) { // note: this needs to be after placement, since some AddEntity functions may assume entity is already in place
+ new_objects.push_back(new_entity);
+ id_remap[old_id] = new_entity->GetID();
+
+
+
+ // Send of a package if host and in an active mp session
+ if (online->IsActive()) {
+ new_entity->created_on_the_fly = true;
+ if (online->IsHosting()) {
+ online->Send<OnlineMessages::CreateEntity>(new_entity->obj_file, spawn_pos, new_entity->GetID());
+ } else {
+ // this is hacky, checking if hostid is 0, but there is no other option atm.
+ // if it's zero, then this function was called with no id supplied, meaning there is no corresponding object
+ // on host, which means we are problably looking at a decal or something not gameplay related.
+ if (hostid != 0) {
+ online->RegisterHostObjectID(hostid, new_entity->GetID());
+ }
+ }
+ }
+ } else {
+ LOGE << "Failed at adding constructed entity" << std::endl;
+ delete new_entity;
+ }
+ } else {
+ LOGE << "Failed at constructing requested entity for spawn." << std::endl;
+ }
+ }
+
+ for (size_t i=0, len=new_objects.size(); i<len; ++i) {
+ new_objects[i]->RemapReferences(id_remap);
+ }
+
+ for (size_t i=0, len=new_objects.size(); i<len; ++i) {
+ new_objects[i]->ReceiveObjectMessage(OBJECT_MSG::FINALIZE_LOADED_CONNECTIONS);
+ }
+
+ if( in_new_prefab ) {
+ Prefab *prefab = new Prefab();
+ if( ActorsEditor_AddEntity(scenegraph, prefab) ) {
+ prefab->children.resize(new_objects.size());
+ prefab->prefab_path = source;
+ for(size_t i=0, len=new_objects.size(); i<len; ++i){
+ prefab->children[i].direct_ptr = new_objects[i];
+ }
+ for(size_t i=0, len=new_objects.size(); i<len; ++i){
+ prefab->children[i].direct_ptr->SetParent(prefab);
+ }
+ prefab->InitShape();
+ prefab->InitRelMats();
+
+ vec3 pos = prefab->GetTranslation();
+ pos += vec3(0.0f,prefab->box_.dims[1]*0.5f+0.1f,0.0f);
+ prefab->SetTranslation(pos);
+
+ prefab->PropagateTransformsDown(true);
+ } else {
+ LOGE << "Failed at adding a prefab to contain spawned group" << std::endl;
+ delete prefab;
+ }
+ new_objects.clear();
+ new_objects.push_back(prefab);
+ }
+ scenegraph->UpdatePhysicsTransforms();
+
+
+ return new_objects;
+}
+
+void ActorsEditor_AddEntitiesIntoPrefab(Object* obj,SceneGraph *scenegraph, const EntityDescriptionList& desc_list) {
+ if (desc_list.empty()) return;
+
+ Prefab *prefab = static_cast<Prefab*>(obj);
+
+ // Get bounding box of the centers of all entities
+ bool center_set = false;
+ vec3 desc_min, desc_max;
+ vec3 vec;
+ for(unsigned i=0; i<desc_list.size(); ++i){
+ const EntityDescriptionField* edf = desc_list[0].GetField(EDF_TRANSLATION);
+ if(edf){
+ memread(vec.entries, sizeof(float), 3, edf->data);
+ if(!center_set){
+ desc_min = vec;
+ desc_max = vec;
+ center_set = true;
+ } else {
+ for(unsigned j=0; j<3; ++j){
+ desc_min[j] = min(desc_min[j], vec[j]);
+ desc_max[j] = max(desc_max[j], vec[j]);
+ }
+ }
+ }
+ }
+
+ // Get midpoint of centers bounding box
+ vec3 copied_center = (desc_min + desc_max) * 0.5f;
+
+ std::map<int,int> id_remap;
+
+ std::vector<Object*> new_objects;
+ for (unsigned i = 0; i < desc_list.size(); i++) {
+ if (GetTypeFromDesc(desc_list[i]) == _decal_object) {
+ // Check we are not exceeding max decal limit
+ if ((scenegraph->decal_objects_.size() - scenegraph->dynamic_decals.size()) >= scenegraph->kMaxStaticDecals) {
+ DisplayError("Warning", "Static decal limit exceeded, cannot add new one!");
+ return;
+ }
+ }
+ Object* new_entity = CreateObjectFromDesc(desc_list[i]);
+
+ if( new_entity ) {
+ //Position offset from calculated relative origo.
+ vec3 pos = (new_entity->GetTranslation() - copied_center);
+
+ if( new_entity->IsGroupDerived() ) {
+ Group* group = static_cast<Group*>(new_entity);
+ //generate the relative positioning data before moving group object.
+ group->InitRelMats();
+ }
+
+ if( new_entity->IsGroupDerived() ) {
+ Group* group = static_cast<Group*>(new_entity);
+ group->PropagateTransformsDown(true);
+ }
+
+ int old_id = new_entity->GetID();
+ if( ActorsEditor_AddEntity(scenegraph, new_entity, &id_remap, false) ) { // note: this needs to be after placement, since some AddEntity functions may assume entity is already in place
+ //Set translation again in case intialization moved the object for some reason, this appears to happen for item_object
+ new_objects.push_back(new_entity);
+ id_remap[old_id] = new_entity->GetID();
+ new_entity->SetTranslation(pos);
+ } else {
+ LOGE << "Failed at adding new entity to scenegraph" << std::endl;
+ delete new_entity;
+ }
+ } else {
+ LOGE << "Failed and constructing entity for spawn" << std::endl;
+ }
+ }
+
+ for (size_t i=0, len=new_objects.size(); i<len; ++i) {
+ new_objects[i]->RemapReferences(id_remap);
+ }
+
+ for (size_t i=0, len=new_objects.size(); i<len; ++i) {
+ new_objects[i]->ReceiveObjectMessage(OBJECT_MSG::FINALIZE_LOADED_CONNECTIONS);
+ }
+
+ vec3 orig_trans = prefab->GetTranslation();
+ quaternion orig_rotation = prefab->GetRotation();
+ vec3 orig_scale_factor = vec3(1.0f);
+ if( prefab->original_scale_ != vec3(0.0f) ) {
+ orig_scale_factor = prefab->GetScale()/prefab->original_scale_;
+ }
+
+ prefab->SetTranslation(vec3(0.0f));
+ prefab->SetRotation(quaternion());
+ prefab->SetScale(vec3(1.0f));
+
+ for(size_t i=0, len=new_objects.size(); i<len; ++i){
+ Group::Child child;
+ child.direct_ptr = new_objects[i];
+ child.direct_ptr->SetParent(prefab);
+ prefab->children.push_back(child);
+ }
+
+ prefab->InitShape();
+
+ prefab->InitRelMats();
+
+ prefab->SetTranslation(orig_trans);
+ prefab->SetRotation(orig_rotation);
+ prefab->SetScale(prefab->GetScale()*orig_scale_factor);
+
+ prefab->PropagateTransformsDown(true);
+
+ scenegraph->UpdatePhysicsTransforms();
+}
+
+int ActorsEditor_LoadEntitiesFromFile(const std::string& rel_path, EntityDescriptionList &desc_list, std::string* file_type, Path* res_path) {
+ if(rel_path.empty()){
+ return -1;
+ }
+ *res_path = FindFilePath(rel_path.c_str(), kModPaths | kDataPaths | kAbsPath);
+
+ return ActorsEditor_LoadEntitiesFromFile(*res_path,desc_list,file_type);
+}
+
+int ActorsEditor_LoadEntitiesFromFile(const Path& path, EntityDescriptionList &desc_list, std::string* file_type)
+{
+ if(path.isValid() == false){
+ DisplayError("Error",("Could not find file " + path.GetFullPathStr()).c_str());
+ return -1;
+ }
+
+ TiXmlDocument doc(path.GetFullPath());
+ if (!doc.LoadFile()) {
+ DisplayError("Error",("Bad xml format in " + path.GetFullPathStr()).c_str());
+ return -1;
+ }
+
+ TiXmlHandle hDoc(&doc);
+ TiXmlDeclaration* decl = ((hDoc.FirstChild()).ToNode())->ToDeclaration();
+ if (strcmp(decl->Version(),"2.0") == 0) {
+ std::string type;
+ if (XmlHelper::getNodeValue(doc,"Type",type)) {
+ *file_type = std::string(type);
+ if (type == "generic") {
+ LoadGenericEntity(path, desc_list);
+ } else if (type == "saved") {
+ LoadSavedEntitiesDesc(&doc, desc_list);
+ } else if (type == "prefab") {
+ LoadSavedPrefab(&doc, desc_list);
+ } else {
+ DisplayError("Error",("Unhandled specification type in " + path.GetFullPathStr()).c_str(), _ok);
+ }
+ } else {
+ DisplayError("Error",("Bad XML in " + path.GetFullPathStr()).c_str());
+ }
+ } else if (strcmp(decl->Version(),"1.0") == 0) {
+ LoadGenericEntity(path,desc_list);
+ } else {
+ DisplayError("Error","Unhandled OG XML version");
+ }
+ return 0;
+
+}
+
+void ActorsEditor_UnlocalizeIDs(EntityDescriptionList *desc_list, SceneGraph *scenegraph){
+ std::vector<EntityDescription*> flat_desc_list;
+ GetFlattenedDescList(desc_list, &flat_desc_list);
+ std::map<int, int> scene_ids;
+ typedef std::map<int, int>::iterator Iter;
+ for(size_t desc_index=0, len=flat_desc_list.size();
+ desc_index<len;
+ ++desc_index)
+ {
+ EntityDescription *desc = flat_desc_list[desc_index];
+ EntityDescriptionField *edf = desc->GetField(EDF_ID);
+ if(edf){
+ int id;
+ memread(&id, sizeof(int), 1, edf->data);
+ int new_id = scenegraph->GetAndReserveID();
+
+ edf->data.clear();
+ edf->WriteInt(new_id);
+ scene_ids.insert(std::pair<int, int>(id, new_id));
+ }
+ }
+ for(size_t desc_index=0, len=flat_desc_list.size();
+ desc_index<len;
+ ++desc_index)
+ {
+ EntityDescription *desc = flat_desc_list[desc_index];
+ EntityDescriptionField *edf = desc->GetField(EDF_CONNECTIONS);
+ if(edf) {
+ std::vector<int> connections;
+ std::vector<int> write_connections;
+ edf->ReadIntVec(&connections);
+ for(size_t connection_index=0, len=connections.size();
+ connection_index<len;
+ ++connection_index )
+ {
+ int id = connections[connection_index];
+ Iter iter = scene_ids.find(id);
+ if(iter != scene_ids.end())
+ id = iter->second;
+ write_connections.push_back(id);
+ }
+ edf->data.clear();
+ edf->WriteIntVec(write_connections);
+ }
+ edf = desc->GetField(EDF_ITEM_CONNECTIONS);
+ if(edf) {
+ std::vector<ItemConnectionData> connections;
+ std::vector<ItemConnectionData> write_connections;
+ edf->ReadItemConnectionDataVec(&connections);
+ for(size_t connection_index=0, len=connections.size();
+ connection_index<len;
+ ++connection_index )
+ {
+ ItemConnectionData write_data = connections[connection_index];
+ Iter iter = scene_ids.find(write_data.id);
+ if(iter != scene_ids.end())
+ write_data.id = iter->second;
+ write_connections.push_back(write_data);
+ }
+ edf->data.clear();
+ edf->WriteItemConnectionDataVec(write_connections);
+ }
+ edf = desc->GetField(EDF_NAV_MESH_CONNECTIONS);
+ if(edf) {
+ std::vector<NavMeshConnectionData> connections;
+ std::vector<NavMeshConnectionData> write_connections;
+ edf->ReadNavMeshConnectionDataVec(&connections);
+ for(size_t connection_index=0, len=connections.size();
+ connection_index<len;
+ ++connection_index )
+ {
+ NavMeshConnectionData write_data = connections[connection_index];
+ Iter iter = scene_ids.find(write_data.other_object_id);
+ if(iter != scene_ids.end())
+ write_data.other_object_id = iter->second;
+ write_connections.push_back(write_data);
+ }
+ edf->data.clear();
+ edf->WriteNavMeshConnectionDataVec(write_connections);
+ }
+ edf = desc->GetField(EDF_CONNECTIONS_FROM);
+ if(edf) {
+ std::vector<int> connections;
+ std::vector<int> write_connections;
+ edf->ReadIntVec(&connections);
+ for(size_t connection_index=0, len=connections.size();
+ connection_index<len;
+ ++connection_index )
+ {
+ int id = connections[connection_index];
+ Iter iter = scene_ids.find(id);
+ if(iter != scene_ids.end())
+ id = iter->second;
+ write_connections.push_back(id);
+ }
+ edf->data.clear();
+ edf->WriteIntVec(write_connections);
+ }
+ }
+}
+
+TypeEnable::TypeEnable(const char* config_postfix):
+ unknown_types_enabled_(true),
+ config_postfix(config_postfix)
+{
+ SetFromConfig();
+}
+
+void TypeEnable::SetTypeEnabled( EntityType type, bool enabled ) {
+ type_enabled_[type] = enabled;
+ WriteTypeString(type);
+}
+
+void TypeEnable::SetAll( bool enabled ) {
+ unknown_types_enabled_ = enabled;
+ type_enabled_.clear();
+ WriteToConfig();
+}
+
+bool TypeEnable::IsTypeEnabled( EntityType type ) const {
+ TypeEnabledMap::const_iterator it = type_enabled_.find(type);
+ if(it == type_enabled_.end()){
+ return unknown_types_enabled_;
+ } else {
+ return it->second;
+ }
+}
+
+void TypeEnable::SetFromConfig() {
+ SetTypeEnabled(_camera_type, ReadTypeString(_camera_type));
+ SetTypeEnabled(_terrain_type, ReadTypeString(_terrain_type));
+ SetTypeEnabled(_env_object, ReadTypeString(_env_object));
+ SetTypeEnabled(_movement_object, ReadTypeString(_movement_object));
+ SetTypeEnabled(_spawn_point, ReadTypeString(_spawn_point));
+ SetTypeEnabled(_decal_object, ReadTypeString(_decal_object));
+ SetTypeEnabled(_hotspot_object, ReadTypeString(_hotspot_object));
+ SetTypeEnabled(_group, ReadTypeString(_group));
+ SetTypeEnabled(_rigged_object, ReadTypeString(_rigged_object));
+ SetTypeEnabled(_item_object, ReadTypeString(_item_object));
+ SetTypeEnabled(_path_point_object, ReadTypeString(_path_point_object));
+ SetTypeEnabled(_ambient_sound_object, ReadTypeString(_ambient_sound_object));
+ SetTypeEnabled(_placeholder_object, ReadTypeString(_placeholder_object));
+ SetTypeEnabled(_light_probe_object, ReadTypeString(_light_probe_object));
+ SetTypeEnabled(_dynamic_light_object, ReadTypeString(_dynamic_light_object));
+ SetTypeEnabled(_navmesh_hint_object, ReadTypeString(_navmesh_hint_object));
+ SetTypeEnabled(_navmesh_region_object, ReadTypeString(_navmesh_region_object));
+ SetTypeEnabled(_navmesh_connection_object, ReadTypeString(_navmesh_connection_object));
+ SetTypeEnabled(_reflection_capture_object, ReadTypeString(_reflection_capture_object));
+ SetTypeEnabled(_light_volume_object, ReadTypeString(_light_volume_object));
+ SetTypeEnabled(_prefab, ReadTypeString(_prefab));
+}
+
+void TypeEnable::WriteToConfig() {
+ WriteTypeString(_camera_type);
+ WriteTypeString(_terrain_type);
+ WriteTypeString(_env_object);
+ WriteTypeString(_movement_object);
+ WriteTypeString(_spawn_point);
+ WriteTypeString(_decal_object);
+ WriteTypeString(_hotspot_object);
+ WriteTypeString(_group);
+ WriteTypeString(_rigged_object);
+ WriteTypeString(_item_object);
+ WriteTypeString(_path_point_object);
+ WriteTypeString(_ambient_sound_object);
+ WriteTypeString(_placeholder_object);
+ WriteTypeString(_light_probe_object);
+ WriteTypeString(_dynamic_light_object);
+ WriteTypeString(_navmesh_hint_object);
+ WriteTypeString(_navmesh_region_object);
+ WriteTypeString(_navmesh_connection_object);
+ WriteTypeString(_reflection_capture_object);
+ WriteTypeString(_light_volume_object);
+ WriteTypeString(_prefab);
+}
+
+bool TypeEnable::ReadTypeString(EntityType type) {
+ char buffer[128];
+ FormatString(buffer, 128, "type_enabled_%s_%s", config_postfix, CStringFromEntityType(type));
+ if(config.HasKey(buffer)) {
+ return config[buffer].toBool();
+ } else {
+ return unknown_types_enabled_;
+ }
+}
+
+void TypeEnable::WriteTypeString(EntityType type) const {
+ char buffer[128];
+ FormatString(buffer, 128, "type_enabled_%s_%s", config_postfix, CStringFromEntityType(type));
+ config.GetRef(buffer) = IsTypeEnabled(type);
+}
diff --git a/Source/Editors/actors_editor.h b/Source/Editors/actors_editor.h
new file mode 100644
index 00000000..64a087bc
--- /dev/null
+++ b/Source/Editors/actors_editor.h
@@ -0,0 +1,49 @@
+//-----------------------------------------------------------------------------
+// Name: actors_editor.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/overgrowth_geometry.h>
+#include <Game/EntityDescription.h>
+#include <Online/online_datastructures.h>
+
+#include <string>
+
+class SceneGraph;
+class Object;
+
+bool ActorsEditor_AddEntity(SceneGraph *scenegraph, Object* o, std::map<int,int>* id_remap = NULL, bool group_addition = false, bool level_loading = false);
+void ActorsEditor_CopySelectedEntities(SceneGraph *scenegraph, EntityDescriptionList *desc_list);
+int ActorsEditor_LoadEntitiesFromFile(const std::string& filename, EntityDescriptionList &desc_list, std::string* file_type, Path* res_path);
+int ActorsEditor_LoadEntitiesFromFile(const Path& path, EntityDescriptionList &desc_list, std::string* file_type);
+std::vector<Object*> ActorsEditor_AddEntitiesAtMouse(const Path& source, SceneGraph *scenegraph, const EntityDescriptionList& desc_list, LineSegment mouseray, bool in_new_prefab);
+std::vector<Object*> ActorsEditor_AddEntitiesAtPosition(const Path& source, SceneGraph *scenegraph, const EntityDescriptionList& desc_list, vec3 position, bool in_new_prefab, CommonObjectID hostid = 0);
+
+void ActorsEditor_AddEntitiesIntoPrefab(Object* obj, SceneGraph *scenegraph, const EntityDescriptionList& desc_list);
+void ActorsEditor_UnlocalizeIDs(EntityDescriptionList *desc_list, SceneGraph *scenegraph);
+
+void ActorsEditor_AddEntitiesIntoPrefab(Object* obj, const EntityDescriptionList& desc_list);
+
+EntityType GetTypeFromDesc(const EntityDescription& desc);
+Object* CreateObjectFromDesc( const EntityDescription& desc );
+void LocalizeIDs(EntityDescriptionList *desc_list, bool keep_external_connections);
diff --git a/Source/Editors/editor_tools.cpp b/Source/Editors/editor_tools.cpp
new file mode 100644
index 00000000..b9dd4a71
--- /dev/null
+++ b/Source/Editors/editor_tools.cpp
@@ -0,0 +1,101 @@
+//-----------------------------------------------------------------------------
+// Name: editor_tools.cpp
+// Developer: Wolfire Games LLC
+// Author: Phillip Isola
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "editor_tools.h"
+
+#include <Graphics/graphics.h>
+#include <Graphics/shaders.h>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <Internal/profiler.h>
+
+BoxSelector::BoxSelector() :
+ acting(false), shader_id_(-1)
+{
+ gl_state_.blend = true;
+ gl_state_.cull_face = false;
+ gl_state_.depth_test = false;
+ gl_state_.depth_write = false;
+}
+
+void BoxSelector::Draw() {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "DrawBillboard()");
+ Graphics* graphics = Graphics::Instance();
+ Shaders *shaders = Shaders::Instance();
+ // Choose appearance parameters
+ const vec4 fill_color(0.1f,1.0f,0.4f,0.25f);
+ const vec4 edge_color(0.0f,0.0f,0.0f,1.0f);
+ const float kEdgeRadius = 1.0f;
+ // Set up projection matrix
+ mat4 proj_matrix;
+ proj_matrix.SetOrtho(0.0f, (float)graphics->window_dims[0], 0.0f, (float)graphics->window_dims[1], -100.0f, 100.0f);
+ // Set up vbo for green fill area
+ GLfloat fill_verts[8] = {points[0][0], points[0][1], points[1][0], points[0][1], points[1][0], points[1][1], points[0][0], points[1][1]};
+ fill_vbo_.Fill(kVBOFloat | kVBODynamic, sizeof(fill_verts), fill_verts);
+ if(!fill_index_vbo_.valid()){
+ static const GLuint index[] = {0,1,2, 0,2,3};
+ fill_index_vbo_.Fill(kVBOElement | kVBOStatic, sizeof(index), (void*)index);
+ }
+ // Set up vbo for black edge
+ const float e = (points[0][0]<points[1][0] && points[0][1]<points[1][1])?kEdgeRadius:-kEdgeRadius; // edge width
+ GLfloat edge_verts[32] = {
+ points[0][0] - e, points[0][1] - e, points[1][0] + e, points[0][1] - e, points[1][0] + e, points[0][1] + e, points[0][0] - e, points[0][1] + e,
+ points[1][0] - e, points[0][1] - e, points[1][0] + e, points[0][1] - e, points[1][0] + e, points[1][1] + e, points[1][0] - e, points[1][1] + e,
+ points[0][0] - e, points[1][1] - e, points[1][0] + e, points[1][1] - e, points[1][0] + e, points[1][1] + e, points[0][0] - e, points[1][1] + e,
+ points[0][0] - e, points[0][1] - e, points[0][0] + e, points[0][1] - e, points[0][0] + e, points[1][1] + e, points[0][0] - e, points[1][1] + e
+ };
+ edge_vbo_.Fill(kVBOFloat | kVBODynamic, sizeof(edge_verts), edge_verts);
+ if(!edge_index_vbo_.valid()){
+ static const GLuint index[] = { 0, 1, 2, 0, 2, 3,
+ 4, 5, 6, 4, 6, 7,
+ 8, 9,10, 8,10,11,
+ 12,13,14, 12,14,15};
+ edge_index_vbo_.Fill(kVBOElement | kVBOStatic, sizeof(index), (void*)index);
+ }
+ // Set GL and shader state
+ graphics->setGLState(gl_state_);
+ if(shader_id_ == -1){
+ shader_id_ = shaders->returnProgram("simple_2d #FLIPPED");
+ }
+ shaders->setProgram(shader_id_);
+ shaders->SetUniformMat4("mvp_mat", proj_matrix.entries);
+ int vert_attrib_id = shaders->returnShaderAttrib("vert_coord", shader_id_);
+ // Draw fill
+ shaders->SetUniformVec4("color", fill_color);
+ fill_vbo_.Bind();
+ fill_index_vbo_.Bind();
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ glVertexAttribPointer(vert_attrib_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0);
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ // Draw edge
+ shaders->SetUniformVec4("color", edge_color);
+ edge_vbo_.Bind();
+ edge_index_vbo_.Bind();
+ glVertexAttribPointer(vert_attrib_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0);
+ graphics->DrawElements(GL_TRIANGLES, 24, GL_UNSIGNED_INT, 0);
+ graphics->ResetVertexAttribArrays();
+}
+
diff --git a/Source/Editors/editor_tools.h b/Source/Editors/editor_tools.h
new file mode 100644
index 00000000..debfffa7
--- /dev/null
+++ b/Source/Editors/editor_tools.h
@@ -0,0 +1,54 @@
+//-----------------------------------------------------------------------------
+// Name: editor_tools.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec2.h>
+#include <Math/vec4.h>
+
+#include <Graphics/glstate.h>
+#include <Graphics/vbocontainer.h>
+
+#include <Editors/editor_types.h>
+
+class OmniTool {
+public:
+ EditorTypes::Tool t;
+};
+
+class BoxSelector {
+public:
+ BoxSelector();
+ void Draw();
+
+ bool acting;
+ vec2 points[2];
+
+private:
+ GLState gl_state_;
+ VBOContainer fill_vbo_;
+ VBOContainer edge_vbo_;
+ VBOContainer fill_index_vbo_;
+ VBOContainer edge_index_vbo_;
+ int shader_id_;
+};
diff --git a/Source/Editors/editor_types.h b/Source/Editors/editor_types.h
new file mode 100644
index 00000000..3733bf9c
--- /dev/null
+++ b/Source/Editors/editor_types.h
@@ -0,0 +1,53 @@
+//-----------------------------------------------------------------------------
+// Name: editor_types.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+namespace EditorTypes {
+ enum Tool {
+ ADD_ONCE,
+ TRANSLATE,
+ SCALE,
+ ROTATE,
+ OMNI,
+ NO_TOOL,
+ CONNECT,
+ DISCONNECT
+ };
+
+ inline const char* GetToolString( Tool e )
+ {
+ switch( e )
+ {
+ case ADD_ONCE: return "ADD_ONCE";
+ case TRANSLATE: return "TRANSLATE";
+ case SCALE: return "SCALE";
+ case ROTATE: return "ROTATE";
+ case OMNI: return "OMNI";
+ case NO_TOOL: return "NO_TOOL";
+ case CONNECT: return "CONNECT";
+ case DISCONNECT: return "DISCONNECT";
+ default: return "(unknown tool type)";
+ }
+ }
+}
diff --git a/Source/Editors/editor_utilities.cpp b/Source/Editors/editor_utilities.cpp
new file mode 100644
index 00000000..b6891866
--- /dev/null
+++ b/Source/Editors/editor_utilities.cpp
@@ -0,0 +1,94 @@
+//-----------------------------------------------------------------------------
+// Name: editor_utilities.cpp
+// Developer: Wolfire Games LLC
+// Author: Phillip Isola
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "editor_utilities.h"
+
+#include <Utility/assert.h>
+
+#include <SDL.h>
+
+#include <string>
+
+EditorEventTrigger::EditorEventTrigger(int delay_time) : m_delay_time(delay_time) {
+
+ m_active = false;
+ m_force_a_trigger = false;
+}
+
+void EditorEventTrigger::Start() {
+
+ m_active = true;
+ m_last_triggered_time = SDL_GetTicks();
+}
+
+void EditorEventTrigger::Stop() {
+
+ m_active = false;
+}
+
+bool EditorEventTrigger::IsActive() {
+
+ return m_active;
+}
+
+void EditorEventTrigger::ForceATrigger() {
+
+ m_force_a_trigger = true;
+}
+
+bool EditorEventTrigger::Check() {
+
+ if (!m_active) return false;
+ if (m_force_a_trigger) {
+ m_force_a_trigger = false;
+ return true;
+ }
+
+ int curr_time = SDL_GetTicks();
+ if (curr_time - m_last_triggered_time > m_delay_time) {
+ m_last_triggered_time = curr_time;
+ return true;
+ }
+ return false;
+}
+
+// returns a vec3 that is amount percent v2 and 1-amount percent v1
+vec3 Interpolate(const vec3& v1, const vec3& v2, float amount) {
+
+#ifdef _DEBUG
+ LOG_ASSERT(amount >= 0.0f && amount <= 1.0f);
+#endif
+
+ float amount_complement = 1.0f - amount;
+ return vec3(v2[0]*amount + v1[0]*amount_complement,
+ v2[1]*amount + v1[1]*amount_complement,
+ v2[2]*amount + v1[2]*amount_complement);
+}
+
+float ConvertToFirstCycle(float n, float cycle_size) {
+ n -=((int)(n/cycle_size))*cycle_size;
+ if (n < 0) n = cycle_size + n;
+ return n;
+}
+
diff --git a/Source/Editors/editor_utilities.h b/Source/Editors/editor_utilities.h
new file mode 100644
index 00000000..ec3f0d16
--- /dev/null
+++ b/Source/Editors/editor_utilities.h
@@ -0,0 +1,53 @@
+//-----------------------------------------------------------------------------
+// Name: editor_utilities.h
+// Developer: Wolfire Games LLC
+// Author: Phillip Isola
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#pragma once
+
+#include <Math/vec3.h>
+
+class EditorEventTrigger {
+public:
+ EditorEventTrigger(int delay_time = 0);
+
+ void Start();
+ void Stop();
+ bool IsActive();
+ void ForceATrigger();
+ bool Check();
+
+ inline int GetDelay() const { return m_delay_time; }
+ inline void SetDelay(int delay_time) { m_delay_time = delay_time; }
+
+private:
+ int m_last_triggered_time; // in milliseconds
+ int m_delay_time; //
+ bool m_force_a_trigger, m_active; // triggers can only be forced if the trigger is active
+};
+
+inline int GetSign(float n) { return n<0?-1:1; }
+
+vec3 Interpolate(const vec3& v1, const vec3& v2, float amount); // returns a vec3 that is amount percent v1 and 1-amount percent v2
+
+float ConvertToFirstCycle(float n, float cycle_size);
diff --git a/Source/Editors/entity_type.cpp b/Source/Editors/entity_type.cpp
new file mode 100644
index 00000000..2148ae91
--- /dev/null
+++ b/Source/Editors/entity_type.cpp
@@ -0,0 +1,53 @@
+//-----------------------------------------------------------------------------
+// Name: entity_type.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "entity_type.h"
+
+const char* CStringFromEntityType(EntityType type) {
+ switch(type){
+ case _any_type: return "_any_type"; break;
+ case _no_type: return "_no_type"; break;
+ case _camera_type: return "_camera_type"; break;
+ case _terrain_type: return "_terrain_type"; break;
+ case _env_object: return "_env_object"; break;
+ case _movement_object: return "_movement_object"; break;
+ case _spawn_point: return "_spawn_point"; break;
+ case _decal_object: return "_decal_object"; break;
+ case _hotspot_object: return "_hotspot_object"; break;
+ case _group: return "_editable_entity_group"; break;
+ case _prefab: return "_prefab"; break;
+ case _rigged_object: return "_rigged_object"; break;
+ case _item_object: return "_item_object"; break;
+ case _path_point_object: return "_path_point_object"; break;
+ case _ambient_sound_object: return "_ambient_sound_object"; break;
+ case _placeholder_object: return "_placeholder_object"; break;
+ case _light_probe_object: return "_light_probe_object"; break;
+ case _dynamic_light_object: return "_dynamic_light_object"; break;
+ case _navmesh_hint_object: return "_navmesh_hint_object"; break;
+ case _navmesh_region_object: return "_navmesh_region_object"; break;
+ case _navmesh_connection_object: return "_navmesh_connection_object"; break;
+ case _reflection_capture_object: return "_reflection_capture_object"; break;
+ case _light_volume_object: return "_light_volume_object"; break;
+ default: return "unknown type"; break;
+ }
+}
diff --git a/Source/Editors/entity_type.h b/Source/Editors/entity_type.h
new file mode 100644
index 00000000..4a7bc21b
--- /dev/null
+++ b/Source/Editors/entity_type.h
@@ -0,0 +1,52 @@
+//-----------------------------------------------------------------------------
+// Name: entity_type.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+// These values are used in TypeEnable::SetFromConfig and TypeEnable::SaveToConfig
+enum EntityType{_any_type = 0,
+ _no_type = 1,
+ _camera_type = 2,
+ _terrain_type = 11,
+ _env_object = 20,
+ _movement_object = 21,
+ _spawn_point = 23,
+ _decal_object = 24,
+ _hotspot_object = 26,
+ _group = 29,
+ _rigged_object = 30,
+ _item_object = 32,
+ _path_point_object = 33,
+ _ambient_sound_object = 34,
+ _placeholder_object = 35,
+ _light_probe_object = 36,
+ _dynamic_light_object = 37,
+ _navmesh_hint_object = 38,
+ _navmesh_region_object = 39,
+ _navmesh_connection_object = 40,
+ _reflection_capture_object = 41,
+ _light_volume_object = 42,
+ _prefab = 43
+};
+
+const char* CStringFromEntityType(EntityType type);
diff --git a/Source/Editors/map_editor.cpp b/Source/Editors/map_editor.cpp
new file mode 100644
index 00000000..ba852653
--- /dev/null
+++ b/Source/Editors/map_editor.cpp
@@ -0,0 +1,3855 @@
+//-----------------------------------------------------------------------------
+// Name: map_editor.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "map_editor.h"
+
+#include <Editors/sky_editor.h>
+#include <Editors/actors_editor.h>
+
+#include <Graphics/shaders.h>
+#include <Graphics/sky.h>
+#include <Graphics/camera.h>
+#include <Graphics/csg.h>
+#include <Graphics/graphics.h>
+#include <Graphics/Cursor.h>
+#include <Graphics/skeleton.h>
+#include <Graphics/models.h>
+#include <Graphics/pxdebugdraw.h>
+
+#include <Objects/group.h>
+#include <Objects/terrainobject.h>
+#include <Objects/riggedobject.h>
+#include <Objects/pathpointobject.h>
+#include <Objects/hotspot.h>
+#include <Objects/itemobject.h>
+#include <Objects/envobject.h>
+#include <Objects/decalobject.h>
+#include <Objects/lightvolume.h>
+#include <Objects/placeholderobject.h>
+#include <Objects/dynamiclightobject.h>
+#include <Objects/prefab.h>
+#include <Objects/terrainobject.h>
+#include <Objects/cameraobject.h>
+#include <Objects/movementobject.h>
+#include <Objects/envobject.h>
+#include <Objects/lightprobeobject.h>
+#include <Objects/reflectioncaptureobject.h>
+
+#include <Internal/dialogues.h>
+#include <Internal/datemodified.h>
+#include <Internal/memwrite.h>
+
+#include <Internal/levelxml.h>
+#include <Internal/comma_separated_list.h>
+#include <Internal/common.h>
+#include <Internal/config.h>
+#include <Internal/filesystem.h>
+#include <Internal/profiler.h>
+
+extern "C" {
+#include <Internal/snprintf.h>
+}
+
+#include <Physics/bulletcollision.h>
+#include <Physics/bulletworld.h>
+#include <Physics/bulletobject.h>
+
+#include <Math/vec3math.h>
+#include <Math/vec4math.h>
+
+#include <UserInput/input.h>
+#include <UserInput/keycommands.h>
+
+#include <GUI/gui.h>
+#include <GUI/dimgui/dimgui.h>
+
+#include <Online/online.h>
+#include <Online/online_datastructures.h>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <XML/level_loader.h>
+#include <Game/level.h>
+#include <Main/scenegraph.h>
+#include <Math/enginemath.h>
+#include <Utility/assert.h>
+#include <Asset/Asset/levelinfo.h>
+#include <Logging/logdata.h>
+
+#include <tinyxml.h>
+#include <SDL.h>
+
+#include <cmath>
+
+extern bool shadow_cache_dirty;
+extern bool g_no_reflection_capture;
+bool draw_group_and_prefab_boxes = true;
+bool always_draw_hotspot_connections = false;
+
+const char* new_empty_level_path = "Data/Levels/nothing.xml";
+
+using namespace PHOENIX_KEY_CONSTANTS;
+
+bool kLightProbe2pass = false;
+
+static const float kMapEditorMouseRayLength = 1000.0f; // From how far away can you select something?
+static const int kNumUpdatesBeforeSave = 5;
+static const int kNumUpdatesBeforeColorSave = 5;
+
+static void GetMouseRay( LineSegment* mouseray ) {
+ Camera *camera = ActiveCameras::Get();
+ mouseray->start = camera->GetPos();
+ mouseray->end = camera->GetPos() + normalize(camera->GetMouseRay()) * kMapEditorMouseRayLength;
+}
+
+class CollisionCompare {
+public:
+ CollisionCompare(const vec3 &point){
+ point_ = point;
+ }
+ bool operator()(const Collision &a, const Collision &b) {
+ return distance_squared(a.hit_where, point_) < distance_squared(b.hit_where, point_);
+ }
+private:
+ vec3 point_;
+};
+
+static void GetEditorLineCollisions(SceneGraph* scenegraph, const vec3& start, const vec3& end, std::vector<Collision>& collisions, const TypeEnable& type_enable) {
+ PROFILER_ZONE(g_profiler_ctx, "GetEditorLineCollisions");
+ scenegraph->LineCheckAll(start, end, &collisions);
+ if(collisions.empty()){
+ return;
+ }
+ std::sort(collisions.begin(), collisions.end(), CollisionCompare(start));
+
+ for(int i=0; i<(int)collisions.size();){
+ if(collisions[i].hit_what->GetType() == _group ||
+ collisions[i].hit_what->GetType() == _prefab ||
+ !(collisions[i].hit_what->permission_flags & Object::CAN_SELECT) ||
+ !type_enable.IsTypeEnabled(collisions[i].hit_what->GetType()))
+ {
+ collisions.erase(collisions.begin()+i);
+ } else if(collisions[i].hit_what->parent &&
+ (collisions[i].hit_what->parent->GetType() == _group ||
+ collisions[i].hit_what->parent->GetType() == _prefab))
+ {
+ int depth = 1;
+ while(collisions[i].hit_what->parent &&
+ (collisions[i].hit_what->parent->GetType() == _group ||
+ collisions[i].hit_what->parent->GetType() == _prefab)
+
+ ){
+ bool is_duplicate = false;
+ for(int j=0; j<i; ++j){ // Don't allow same group to be in list twice
+ if(collisions[i].hit_what->parent == collisions[j].hit_what){
+ is_duplicate = true;
+ break;
+ }
+ }
+ if(!is_duplicate){
+ ++depth;
+ collisions.insert(collisions.begin()+i, collisions[i]);
+ collisions[i].hit_what = collisions[i].hit_what->parent;
+ } else {
+ break;
+ }
+ }
+ i += depth;
+ } else {
+ ++i;
+ }
+ }
+
+}
+
+// Given a set of selected objects, what kind of connections are possible
+static Object::ConnectionType GetConnectionType(const std::vector<Object*> &selected) {
+ if(selected.empty()){
+ return Object::kCTNone;
+ }
+ bool movement_objects_only = true;
+ bool single_item_object = (selected.size()==1);
+ bool env_objects_only = true;
+ bool path_points_only = true;
+ bool navmesh_connection_objects_only = true;
+ bool single_placeholder_object = (selected.size()==1);
+ bool hotspots_only = true;
+ for(unsigned i=0; i<selected.size(); ++i) {
+ const Object* obj = selected[i];
+ EntityType type = obj->GetType();
+ if(type != _movement_object) {
+ movement_objects_only = false;
+ }
+ if(type != _item_object) {
+ single_item_object = false;
+ }
+ if(type != _env_object && type != _group && type != _prefab) {
+ env_objects_only = false;
+ }
+ if(type != _path_point_object) {
+ path_points_only = false;
+ }
+ if(type != _placeholder_object || !((PlaceholderObject*)obj)->connectable()) {
+ single_placeholder_object = false;
+ }
+ if(type != _navmesh_connection_object){
+ navmesh_connection_objects_only = false;
+ }
+ if(type != _hotspot_object) {
+ hotspots_only = false;
+ }
+ }
+ if(movement_objects_only){
+ return Object::kCTMovementObjects;
+ } else if(single_item_object){
+ return Object::kCTItemObjects;
+ } else if(env_objects_only){
+ return Object::kCTEnvObjectsAndGroups;
+ } else if(path_points_only){
+ return Object::kCTPathPoints;
+ } else if(single_placeholder_object){
+ return Object::kCTPlaceholderObjects;
+ } else if(navmesh_connection_objects_only) {
+ return Object::kCTNavmeshConnections;
+ } else if(hotspots_only) {
+ return Object::kCTHotspots;
+ } else {
+ return Object::kCTNone;
+ }
+}
+
+static void DrawObjInfo(Object* obj){
+ Engine::Instance()->gui.AddDebugText("selecteda", obj->obj_file, 10.0f);
+ const int kBufSize = 512;
+ char str[kBufSize];
+ FormatString(str, kBufSize, "Object ID: %d", obj->GetID());
+ Engine::Instance()->gui.AddDebugText("selectedb", str, 10.0f);
+ FormatString(str, kBufSize, "Object Type: %s", CStringFromEntityType(obj->GetType()));
+ Engine::Instance()->gui.AddDebugText("selectedc", str, 10.0f);
+}
+
+void MapEditor::RemoveObject(Object* o, SceneGraph* scenegraph, bool removed_by_socket) {
+ Online* online = Online::Instance();
+
+ if (online->IsActive()) {
+ if (online->IsHosting()) {
+ online->RemoveObject(o, o->GetID());
+ } else if (removed_by_socket == false) {
+ // This case is for the editor
+ // If we removed something, and it was not by the socket
+ // we want to inform the host.
+ // we will not remove this object until the host tells us to
+ online->RemoveObject(o, o->GetID());
+ return;
+ }
+ }
+
+ if(o->GetType() == _env_object && !((EnvObject*)o)->ofr->dynamic){
+ shadow_cache_dirty = true;
+ }
+ if(o->IsGroupDerived()) {
+ Group* group = static_cast<Group*>(o);
+ while(!group->children.empty()) {
+ RemoveObject(group->children.back().direct_ptr, scenegraph, true);
+ }
+ }
+ if(o->GetType() == _movement_object) {
+ RiggedObject* ro = static_cast<MovementObject*>(o)->rigged_object();
+ /*while(!ro->attached_items.items.empty()) {
+ RemoveObject(ro->attached_items.items.front().item.obj, scenegraph);
+ }*/
+ while(!ro->children.empty()) {
+ RemoveObject(ro->children.back().direct_ptr, scenegraph, true);
+ }
+ }
+ o->SetParent(NULL); // Disconnects from any parent objects
+ // Notify everyone that object is deleted
+ const int BUF_SIZE = 255;
+ char buf[BUF_SIZE];
+ snprintf(buf, BUF_SIZE, "notify_deleted %d", o->GetID());
+ scenegraph->level->Message(buf);
+ for (SceneGraph::object_list::iterator iter = scenegraph->objects_.begin();
+ iter != scenegraph->objects_.end();
+ ++iter)
+ {
+ Object* obj = *iter;
+ obj->NotifyDeleted(o);
+ }
+ scenegraph->UnlinkObject(o);
+ o->Dispose();
+ delete(o);
+}
+
+void LoadLevel(bool local) {
+ const int BUF_SIZE = kPathSize;
+ char buffer[BUF_SIZE];
+ char input_buffer[BUF_SIZE];
+
+ const char* start_dir = "Data/Levels";
+
+ if( local ) {
+ FormatString(input_buffer, BUF_SIZE, "%s/Data/Levels", GetWritePath(CoreGameModID).c_str());
+ start_dir = input_buffer;
+ }
+
+ Dialog::DialogErr err = Dialog::readFile("xml",1,start_dir, buffer, BUF_SIZE);
+
+ if( err != Dialog::NO_ERR ) {
+ LOGE << Dialog::DialogErrString( err ) << std::endl;
+ } else {
+ ApplicationPathSeparators(buffer);
+ Path level = FindFilePath(buffer, kAnyPath);
+ if(level.isValid()) {
+ level = FindShortestPath2(level.GetFullPath());
+ LevelInfoAssetRef levelinfo = Engine::Instance()->GetAssetManager()->LoadSync<LevelInfoAsset>(buffer);
+ if( levelinfo.valid() ) {
+ Engine::Instance()->QueueState(EngineState(levelinfo->GetName(), kEngineEditorLevelState, level));
+ } else {
+ LOGE << "Unable to load level info, will not queue level for load." << std::endl;
+ }
+ }
+ }
+}
+
+void MapEditor::RibbonItemClicked(const std::string& item, bool param) {
+ if(item == "prefab") {
+ int err = PrefabSelected();
+
+ if( err == 0 ) {
+ } else if( err == 1 ) {
+ ShowEditorMessage(3, "Selected object is already a prefab.");
+ } else if( err == 2 ) {
+ ShowEditorMessage(3, "Selected objects contain a prefab, prefabs can't contain prefabs.");
+ } else if( err == 3 ) {
+ ShowEditorMessage(3, "Selected group object has a parent, orphan it first.");
+ } else if( err == 4 ) {
+ ShowEditorMessage(3, "All selected objects already have a parent.");
+ } else if( err == 5 ) {
+ ShowEditorMessage(3, "Unknown error");
+ } else if( err == 6 ) {
+ ShowEditorMessage(3, "Error adding prefab to scenegraph");
+ }
+ }
+ else if(item == "ungroup") {
+ UngroupSelected();
+ }
+ else if(item == "sendinrabbot") {
+ SendInRabbot();
+ }
+ else if (item == "objecteditoractive") {
+ SetTypeEnabled(_env_object, param);
+ SetTypeEnabled(_group, param);
+ SetTypeEnabled(_prefab, param);
+ }
+ else if (item == "decaleditoractive") {
+ SetTypeEnabled(_decal_object, param);
+ }
+ else if (item == "hotspoteditoractive") {
+ gameplay_objects_enabled_ = param;
+
+ SetTypeEnabled(_hotspot_object, param);
+ SetTypeEnabled(_movement_object, param);
+ SetTypeEnabled(_path_point_object, param);
+ SetTypeEnabled(_item_object, param);
+ SetTypeEnabled(_navmesh_region_object, param);
+ SetTypeEnabled(_navmesh_hint_object, param);
+ SetTypeEnabled(_navmesh_connection_object, param);
+ SetTypeEnabled(_placeholder_object, param);
+ SetTypeVisible(_navmesh_region_object, param);
+ SetTypeVisible(_navmesh_hint_object, param);
+ SetTypeVisible(_navmesh_connection_object, param);
+ }
+ else if (item == "lighteditoractive") {
+ SetTypeEnabled(_dynamic_light_object, param);
+ SetTypeEnabled(_reflection_capture_object, param);
+ SetTypeEnabled(_light_volume_object, param);
+ }
+ else if (item == "isolate") {
+ //ToggleSelectedDecalIsolation();
+ }
+ else if (item == "exit") {
+ Input::Instance()->RequestQuit();
+ }
+ else if (item == "addprobes") {
+ AddLightProbes();
+ }
+ else if (item == "calculateao") {
+ BakeLightProbes(0);
+ }
+ else if (item == "calculate2pass") {
+ BakeLightProbes(1);
+ }
+ else if(item == "toggleviewnavmesh") {
+ SetViewNavMesh(param);
+ }
+ else if(item == "edit_selected_dialogue"){
+ scenegraph_->level->Message("edit_selected_dialogue");
+ }
+ else if(item == "load_dialogue"){
+ Input::Instance()->ignore_mouse_frame = true;
+ const int BUF_SIZE = 512;
+ char buffer[BUF_SIZE];
+ Dialog::DialogErr err = Dialog::readFile("txt",1,"Data/Dialogues", buffer, BUF_SIZE);
+
+ if( err != Dialog::NO_ERR )
+ {
+ LOGE << Dialog::DialogErrString( err ) << std::endl;
+ }
+ else
+ {
+ std::string shortened = FindShortestPath(std::string(buffer));
+ LoadDialogueFile(shortened.c_str());
+ }
+ }
+ else if (item == "create_empty_dialogue"){
+ LoadDialogueFile("empty");
+ }
+ else if(item == "level_script"){
+ Input::Instance()->ignore_mouse_frame = true;
+ const int BUF_SIZE = 512;
+ char buffer[BUF_SIZE];
+
+ // Display a file dialog to the user
+ Dialog::DialogErr err = Dialog::readFile("as",1,"Data/Scripts", buffer, BUF_SIZE);
+ if( err != Dialog::NO_ERR ){
+ LOGE << Dialog::DialogErrString( err ) << std::endl;
+ }
+ std::string scriptName = SplitPathFileName( buffer ).second;
+ scenegraph_->level->SetLevelSpecificScript( scriptName );
+ }
+ else if(item == "carve_against_terrain"){
+ CarveAgainstTerrain();
+ }
+ else if(item == "rebake_light_probes"){
+ LightProbeCollection& lpc = scenegraph_->light_probe_collection;
+ for(size_t light_probe_index=0, len=lpc.light_probes.size(); light_probe_index<len; ++light_probe_index){
+ LightProbe& light_probe = lpc.light_probes[light_probe_index];
+ for(int cube_face=0; cube_face<6; ++cube_face){
+ light_probe.ambient_cube_color[cube_face] = vec3(0.0f);
+ }
+ lpc.MoveProbe(light_probe.id, light_probe.pos); // Hackish way to force refresh of this probe
+ }
+ }
+ else if(item == "show_tet_mesh"){
+ LightProbeCollection& lpc = scenegraph_->light_probe_collection;
+ lpc.tet_mesh_viz_enabled = param;
+ if(!lpc.light_probes.empty()){ // Move probe to force tet mesh update
+ lpc.MoveProbe(lpc.light_probes[0].id, lpc.light_probes[0].pos);
+ }
+ }
+ else if(item == "show_probes"){
+ LightProbeCollection& lpc = scenegraph_->light_probe_collection;
+ lpc.show_probes = param;
+ SetTypeEnabled(_light_probe_object, param);
+ }
+ else if(item == "show_probes_through_walls"){
+ LightProbeCollection& lpc = scenegraph_->light_probe_collection;
+ lpc.show_probes_through_walls = param;
+ }
+ else if(item == "probe_lighting_enabled"){
+ LightProbeCollection& lpc = scenegraph_->light_probe_collection;
+ lpc.probe_lighting_enabled = param;
+ }
+}
+
+static void SendMessageToSelectedObjectsVAList(SceneGraph *scenegraph, OBJECT_MSG::Type type, va_list args){
+ for (SceneGraph::object_list::iterator iter = scenegraph->objects_.begin();
+ iter != scenegraph->objects_.end(); ++iter)
+ {
+ Object* obj = (*iter);
+ if (obj->Selected()) {
+ obj->ReceiveObjectMessageVAList(type, args);
+ }
+ }
+}
+
+static void SendMessageToSelectedObjects(SceneGraph *scenegraph, OBJECT_MSG::Type type, ...){
+ va_list args;
+ va_start(args, type);
+ SendMessageToSelectedObjectsVAList(scenegraph, type, args);
+ va_end(args);
+}
+
+static void BoxSelectEntities(SceneGraph *scenegraph,
+ const TypeEnable &type_enable,
+ const LineSegment& mouseray,
+ const vec4& p1, const vec4& p3, const vec4& r1, const vec4& r2,
+ const vec4& n1, const vec4& n2, const vec4& n3, const vec4& n4, bool holding_shift)
+{
+ Keyboard* keyboard = &(Input::Instance()->getKeyboard());
+
+ vec4 test_p, test_d;
+ Collision c;
+ for (size_t i=0, len=scenegraph->objects_.size(); i<len; ++i) {
+ Object* obj = scenegraph->objects_[i];
+ if(!obj->parent && obj->permission_flags & Object::CAN_SELECT && type_enable.IsTypeEnabled(obj->GetType())){
+ test_p = obj->GetTranslation();
+
+ test_d = test_p - p1;
+ if (dot(test_d,n1) < 0) continue;
+ if (dot(test_d,n4) < 0) continue;
+
+ test_d = test_p - p3;
+ if (dot(test_d,n2) < 0) continue;
+ if (dot(test_d,n3) < 0) continue;
+
+ if (keyboard->isKeycodeDown(SDLK_CTRL, KIMF_LEVEL_EDITOR_GENERAL)) {
+ // lineCheck against center and all eight corners of entity's bounding box; only miss if none of these points are hit
+ bool hit = false;
+ //vec3 rad_ws = obj->GetTransform() * (obj->box_.dims*0.5f);
+ c = scenegraph->lineCheckCollidable(mouseray.start, vec3(test_p[0], test_p[1], test_p[2]), NULL);
+ if (c.hit_what == obj) hit = true;
+ if (!hit) continue;
+ }
+
+ obj->Select(true);
+ }
+ }
+}
+
+
+MapEditor::MapEditor() :
+ state_(MapEditor::kIdle),
+ control_obj(NULL),
+ active_tool_(EditorTypes::OMNI),
+ save_countdown_(0),
+ sky_editor_(NULL),
+ scenegraph_(NULL),
+ last_saved_chunk_global_id(0),
+ create_as_prefab_(false),
+ type_enable_("edit"),
+ type_visible_("visible"),
+ gameplay_objects_enabled_(IsTypeEnabled(_hotspot_object)),
+ color_history_countdown_(-1),
+ terrain_preview_mode(false),
+ terrain_preview(NULL)
+{
+}
+
+MapEditor::~MapEditor() {
+ DeleteEditors();
+}
+
+void RibbonFlash(GUI* gui, std::string which) {
+}
+
+void MapEditor::SendInRabbot() {
+ LOG_ASSERT(state_ != MapEditor::kInGame);
+ LOGI << "Requesting player-controlled mode..." << std::endl;
+ scenegraph_->UpdatePhysicsTransforms();
+ state_ = MapEditor::kInGame;
+ LOGI << "Request successful..." << std::endl;
+}
+
+void MapEditor::StopRabbot(bool handle_gui) {
+ LOG_ASSERT(state_ == MapEditor::kInGame);
+ LOGI << "Requesting editor-controlled mode..." << std::endl;
+ state_ = MapEditor::kIdle;
+ LOGI << "Request successful..." << std::endl;
+}
+
+void MapEditor::DeleteEditors() {
+ delete sky_editor_;
+ sky_editor_ = NULL;
+ scenegraph_ = NULL;
+}
+
+void MapEditor::SetTool( EditorTypes::Tool tool )
+{
+ active_tool_ = tool;
+}
+
+void MapEditor::Initialize(SceneGraph* s) {
+ ClearUndoHistory();
+ DeleteEditors();
+ InitializeColorHistory();
+ scenegraph_ = s;
+ sky_editor_ = new SkyEditor(s);
+ sky_editor_->flare = scenegraph_->flares.MakeFlare(vec3(0.0f), 1.0f, true);
+ imui_context.Init();
+ control_editor_info.face_selected_ = -1;
+ control_editor_info.face_display_.facing = false;
+ control_editor_info.face_display_.plane = false;
+}
+
+void MapEditor::UpdateEnabledObjects() {
+ for (size_t i=0, len=scenegraph_->objects_.size(); i<len; ++i) {
+ Object* obj = scenegraph_->objects_[i];
+ obj->editor_visible = type_visible_.IsTypeEnabled(obj->GetType());
+ if(!type_enable_.IsTypeEnabled(obj->GetType())){
+ obj->Select(false);
+ }
+ }
+}
+
+void MapEditor::InitializeColorHistory() {
+ for(int i = 0; i < kColorHistoryLen; ++i) {
+ color_history_[i] = vec4(1.0f);
+ }
+}
+
+struct WeapConnectionResult {
+ AttachmentSlot slot;
+ MovementObject* mo;
+};
+
+void DrawWeaponConnectionUI(const SceneGraph* scenegraph, IMUIContext &imui_context, const ItemRef& item_ref, WeapConnectionResult* result) {
+ result->mo = NULL;
+ vec3 cam_facing = ActiveCameras::Get()->GetFacing();
+ vec3 cam_up = ActiveCameras::Get()->GetUpVector();
+ vec3 cam_right = cross(cam_facing, cam_up);
+ int ui_index = 0;
+ for(unsigned i=0; i<scenegraph->movement_objects_.size(); ++i){
+ MovementObject* mo = (MovementObject*)scenegraph->movement_objects_[i];
+ std::list<AttachmentSlot> list;
+ mo->rigged_object()->AvailableItemSlots(item_ref, &list);
+ for(std::list<AttachmentSlot>::iterator iter = list.begin(); iter != list.end(); ++iter){
+ AttachmentSlot& slot = *iter;
+ mat4 circle_transform;
+ circle_transform.SetColumn(0, cam_right * 0.1f);
+ circle_transform.SetColumn(1, cam_up * 0.1f);
+ circle_transform.SetColumn(2, cam_facing * 0.1f);
+ circle_transform.SetTranslationPart(slot.pos);
+ vec4 color = vec4(0.5f,0.5f,0.5f,0.5f);
+ vec3 screen_pos_tl = ActiveCameras::Get()->worldToScreen(circle_transform*vec3(-1,-1,0));
+ vec3 screen_pos_br = ActiveCameras::Get()->worldToScreen(circle_transform*vec3(1,1,0));
+ if(screen_pos_tl[2] < 1.0 && screen_pos_br[2] < 1.0){
+ vec2 top_left(screen_pos_tl[0], screen_pos_tl[1]);
+ vec2 bottom_right(screen_pos_br[0], screen_pos_br[1]);
+ IMUIContext::UIState ui_state;
+ if(imui_context.DoButton(ui_index++, top_left, bottom_right, ui_state)){
+ result->mo = mo;
+ result->slot = slot;
+ }
+ if(ui_state == IMUIContext::kActive){
+ color = vec4(0.0f,0.0f,0.0f,0.5f);
+ }
+ if(ui_state == IMUIContext::kHot){
+ color = vec4(1.0f,1.0f,1.0f,0.5f);
+ }
+ }
+ DebugDraw::Instance()->AddCircle(circle_transform, color, _delete_on_draw);
+ }
+ }
+}
+
+
+static vec3 TransformVec3(const mat4& transform, const vec3& vector) {
+ return {
+ transform.entries[0 + 0 * 4] * vector[0] + transform.entries[0 + 1 * 4] * vector[1] + transform.entries[0 + 2 * 4] * vector[2] + transform.entries[0 + 3 * 4],
+ transform.entries[1 + 0 * 4] * vector[0] + transform.entries[1 + 1 * 4] * vector[1] + transform.entries[1 + 2 * 4] * vector[2] + transform.entries[1 + 3 * 4],
+ transform.entries[2 + 0 * 4] * vector[0] + transform.entries[2 + 1 * 4] * vector[1] + transform.entries[2 + 2 * 4] * vector[2] + transform.entries[2 + 3 * 4],
+ };
+}
+
+static void DrawBox(const Box& box_, const Object* object_, bool highlight, const vec4& box_color) {
+ mat4 transform = object_->GetTransform();
+ // Translate
+ transform[0 + 3 * 4] += box_.center[0];
+ transform[1 + 3 * 4] += box_.center[1];
+ transform[2 + 3 * 4] += box_.center[2];
+ // Scale
+ transform[0 + 0 * 4] *= box_.dims[0];
+ transform[1 + 0 * 4] *= box_.dims[0];
+ transform[2 + 0 * 4] *= box_.dims[0];
+ transform[0 + 1 * 4] *= box_.dims[1];
+ transform[1 + 1 * 4] *= box_.dims[1];
+ transform[2 + 1 * 4] *= box_.dims[1];
+ transform[0 + 2 * 4] *= box_.dims[2];
+ transform[1 + 2 * 4] *= box_.dims[2];
+ transform[2 + 2 * 4] *= box_.dims[2];
+
+ vec4 draw_color = box_color;
+ draw_color[3] *= highlight ? 1.0f : 0.2f;
+
+ static const GLfloat vertices[] = {
+ -0.5f,-0.5f, 0.5f, 0.5f,-0.5f, 0.5f, 0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, // front (in coord system: +x to the right, +y up, -z into distance)
+ -0.5f,-0.5f,-0.5f, 0.5f,-0.5f,-0.5f, 0.5f, 0.5f,-0.5f, -0.5f, 0.5f,-0.5f, // back
+ };
+ static const GLubyte edges[] = {
+ 0,1, 1,2, 2,3, 3,0, 4,5, 5,6,
+ 6,7, 7,4, 4,0, 7,3, 5,1, 6,2
+ };
+
+ GLfloat transformed_vertices[3 * 8];
+ for(int i = 0; i < 8; ++i) {
+ vec3 temp = TransformVec3(transform, *(vec3*)&vertices[i * 3]);
+ memcpy(&transformed_vertices[i * 3], &temp, sizeof(vec3));
+ }
+
+ // Draw edges of box
+ for (int i = 0; i < 12; i++) {
+ int start_index = edges[i*2+0] * 3;
+ int end_index = edges[i*2+1] * 3;
+ vec3 start = *(vec3*)&transformed_vertices[start_index];
+ vec3 end = *(vec3*)&transformed_vertices[end_index];
+ DebugDraw::Instance()->AddLine(start, end, draw_color, _delete_on_draw);
+ }
+}
+
+static void DrawControlledFace(const Box& box_, const Object* object_, int face_selected_, FaceDisplay face_display_) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "DrawControlledFace");
+
+ mat4 translate_mat;
+ translate_mat.SetTranslation(box_.center);
+ mat4 model_mat = object_->GetTransform() * translate_mat;
+
+ static const GLfloat vertices[] = {
+ -0.5f,-0.5f, 0.5f, 0.5f,-0.5f, 0.5f, 0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, // front (in coord system: +x to the right, +y up, -z into distance)
+ -0.5f,-0.5f,-0.5f, 0.5f,-0.5f,-0.5f, 0.5f, 0.5f,-0.5f, -0.5f, 0.5f,-0.5f, // back
+ -0.5f,-0.5f,-0.5f, -0.5f,-0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f,-0.5f, // left
+ 0.5f,-0.5f, 0.5f, 0.5f,-0.5f,-0.5f, 0.5f, 0.5f,-0.5f, 0.5f, 0.5f, 0.5f, // right
+ -0.5f,-0.5f,-0.5f, 0.5f,-0.5f,-0.5f, 0.5f,-0.5f, 0.5f, -0.5f,-0.5f, 0.5f, // bottom
+ -0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f,-0.5f, -0.5f, 0.5f,-0.5f // top
+ };
+ static const GLuint faces[] = {
+ 0, 1, 2, 0, 2, 3, 5, 4, 7, 5, 7, 6, 8, 9,10, 8, 10, 11,
+ 12,13,14, 12,14,15, 16,17,18, 16,18,19, 20,21,22, 20,22,23
+ };
+
+ static VBOContainer vertices_vbo;
+ static VBOContainer faces_vbo;
+ if(!vertices_vbo.valid()){
+ vertices_vbo.Fill(kVBOStatic | kVBOFloat, sizeof(vertices), (void*)vertices);
+ faces_vbo.Fill(kVBOStatic | kVBOElement, sizeof(faces), (void*)faces);
+ }
+
+ Graphics* graphics = Graphics::Instance();
+
+ GLState gl_state;
+ gl_state.depth_test = true;
+ gl_state.blend = true;
+ gl_state.cull_face = false;
+ gl_state.depth_write = false;
+ graphics->setGLState(gl_state);
+ graphics->setDepthFunc(GL_LEQUAL);
+
+ mat4 scale_mat;
+ scale_mat.SetScale(box_.dims);
+ mat4 new_model_mat = model_mat * scale_mat;
+
+ // Draw selected face
+ if (face_selected_ != -1) {
+ Shaders* shaders = Shaders::Instance();
+ Camera* cam = ActiveCameras::Get();
+ int shader_id = shaders->returnProgram("face_stipple");
+ shaders->setProgram(shader_id);
+ mat4 mvp_mat = cam->GetProjMatrix() * cam->GetViewMatrix() * new_model_mat;
+ shaders->SetUniformMat4("mvp_mat", mvp_mat);
+ shaders->SetUniformVec4("color", vec4(0.1f, 1.0f, 0.4f, 1.0f));
+ int vert_coord_id = shaders->returnShaderAttrib("vert_coord", shader_id);
+ vertices_vbo.Bind();
+ faces_vbo.Bind();
+ graphics->EnableVertexAttribArray(vert_coord_id);
+ glVertexAttribPointer(vert_coord_id, 3, GL_FLOAT, false, 3*sizeof(GLfloat), 0);
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (const void*)(sizeof(GLuint) * (face_selected_*6)) );
+ graphics->ResetVertexAttribArrays();
+ // Draw direction line for selected face
+ if(face_display_.facing || face_display_.plane){
+ mat4 transform;
+ switch(face_selected_){
+ case 0:
+ transform = Mat4FromQuaternion(quaternion(vec4(0.0f, 1.0f, 0.0f, PI_f*-0.5f)));
+ break;
+ case 1:
+ transform = Mat4FromQuaternion(quaternion(vec4(0.0f, 1.0f, 0.0f, PI_f*0.5f)));
+ break;
+ case 2:
+ transform = Mat4FromQuaternion(quaternion(vec4(0.0f, 1.0f, 0.0f, PI_f)));
+ break;
+ case 3:
+ break;
+ case 4:
+ transform = Mat4FromQuaternion(quaternion(vec4(0.0f, 0.0f, 1.0f, PI_f*-0.5f)));
+ break;
+ case 5:
+ transform = Mat4FromQuaternion(quaternion(vec4(0.0f, 0.0f, 1.0f, PI_f*0.5f)));
+ break;
+ default:
+ break;
+ }
+
+ // Draw line if translating, scaling, or rotating on the plane normal
+ vec3 color(0.5f, 0.0f, 0.0f);
+ if(face_display_.facing){
+ vec3 points[2];
+ points[0] = new_model_mat * transform * vec3(0.5f, 0.0f, 0.0f);
+ points[1] = new_model_mat * transform * vec3(1.0f, 0.0f, 0.0f);
+ DebugDraw::Instance()->AddLine(points[0], points[1], vec4(color, 1.0f), vec4(color, 0.0f), _delete_on_draw);
+ }
+
+ // Draw grid if translating/scaling on a plane
+ if(face_display_.plane){
+ static const mat4 transform_test = Mat4FromQuaternion(quaternion(vec4(1.0f, 0.0f, 0.0f, PI_f*-0.5f)));
+ vec3 points[2];
+ mat4 mat[2];
+ mat[0] = new_model_mat * transform;
+ mat[1] = mat[0] * transform_test;
+ for(int i=0; i<3; ++i){
+ for(int j=0; j<2; ++j){
+ for(int k=0; k<2; ++k){
+ points[0] = mat[k] * vec3(0.5f, 0.0f, -0.5f+0.5f*(float)i);
+ points[1] = mat[k] * vec3(0.5f, 1.0f-(float)j*2.0f, -0.5f+0.5f*(float)i);
+ DebugDraw::Instance()->AddLine(points[0], points[1], vec4(color, 1.0f), vec4(color, 0.0f), _delete_on_draw);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+static void GetClosest(LineSegment mouseray, Object* obj, Object*& highlit_obj, float& highlit_obj_dist) {
+ vec3 point;
+ vec3 normal;
+ if(obj->lineCheck(mouseray.start, mouseray.end, &point, &normal) != -1) {
+ float dist_sq = dot(point - mouseray.start, point - mouseray.start);
+ if(dist_sq < highlit_obj_dist) {
+ highlit_obj = obj;
+ highlit_obj_dist = dist_sq;
+ }
+ }
+}
+
+void MapEditor::Draw() {
+ LineSegment mouseray;
+ GetMouseRay(&mouseray);
+
+ imui_context.UpdateControls();
+
+ if(!Graphics::Instance()->media_mode()){
+ if(active_tool_ != EditorTypes::CONNECT && active_tool_ != EditorTypes::DISCONNECT) {
+ for (size_t i=0, len=scenegraph_->objects_.size(); i<len; ++i) {
+ Object *obj = scenegraph_->objects_[i];
+ if(obj->editor_visible){
+ const EntityType& type = obj->GetType();
+
+ if(IsTypeEnabled(type) && (
+ (
+ ((type != _group && type != _prefab) || draw_group_and_prefab_boxes) &&
+ type != _env_object &&
+ type != _decal_object &&
+ type != _light_probe_object &&
+ type != _dynamic_light_object &&
+ type != _navmesh_hint_object) ||
+ obj->Selected())) {
+ // Draw box for selected objects or objects that always have a visible box.
+ DrawBox(obj->box_, obj, obj->Selected(), obj->box_color);
+ }
+ }
+ if(always_draw_hotspot_connections) {
+ vec3 start = obj->GetTranslation();
+ for(size_t j = 0; j < obj->connected_to.size(); ++j) {
+ vec3 end = scenegraph_->GetObjectFromID(obj->connected_to[j])->GetTranslation();
+ DebugDraw::Instance()->AddLine(start, end, vec4(0.0f,1.0f,0.0f,0.8f), _delete_on_draw);
+ }
+ }
+ }
+ }
+ if(state_ == MapEditor::kTransformDrag && control_obj){
+ DrawControlledFace(control_obj->box_, control_obj, control_editor_info.face_selected_, control_editor_info.face_display_);
+ }
+ }
+ sky_editor_->Draw();
+ if (sky_editor_->m_lighting_changed) {
+ scenegraph_->SendMessageToAllObjects(OBJECT_MSG::LIGHTING_CHANGED);
+ sky_editor_->m_lighting_changed = false;
+ }
+
+ if(box_selector_.acting){
+ box_selector_.Draw();
+ }
+
+ // Handle attachments
+ ReturnSelected(&selected);
+ if(selected.size() == 1 && selected[0]->GetType() == _item_object && (active_tool_ == EditorTypes::CONNECT || active_tool_ == EditorTypes::DISCONNECT)) {
+ // Attach weapons to character attachment points
+ DebugDraw::Instance()->AddLine(selected[0]->GetTranslation(), mouseray.end, vec4(vec3(0.5f),0.5f), _delete_on_draw, _DD_XRAY);
+ WeapConnectionResult wcr;
+ Object* weap_obj = selected[0];
+ DrawWeaponConnectionUI(scenegraph_, imui_context, ((ItemObject*)weap_obj)->item_ref(), &wcr);
+ if(wcr.mo) {
+ if(active_tool_ == EditorTypes::CONNECT) {
+ wcr.mo->AttachItemToSlotEditor(weap_obj->GetID(), wcr.slot.type, wcr.slot.mirrored, wcr.slot.attachment_ref);
+ } else {
+ weap_obj->Disconnect(*wcr.mo);
+ }
+ QueueSaveHistoryState();
+ }
+ }
+ box_objects.clear();
+ box_objects.reserve(64);
+ if(active_tool_ == EditorTypes::CONNECT || active_tool_ == EditorTypes::DISCONNECT) {
+ Object* highlit_obj = NULL;
+ Object::ConnectionType connection_type = GetConnectionType(selected);
+
+ if(active_tool_ == EditorTypes::CONNECT) {
+ float highlit_obj_dist = FLT_MAX;
+
+ if(connection_type != Object::kCTNone) {
+ for(size_t i = 0; i < scenegraph_->objects_.size(); ++i) {
+ Object* obj = scenegraph_->objects_[i];
+ if(IsTypeEnabled(obj->GetType())) {
+ bool add_object = obj->Selected();
+ if(!obj->Selected()) {
+ for(size_t j = 0; j < selected.size(); ++j) {
+ if(selected[j]->GetType() == _hotspot_object) {
+ Hotspot* selected_obj = (Hotspot*)selected[j];
+ if(selected_obj->AcceptConnectionsTo(*obj)) {
+ if(obj->GetType() == _hotspot_object) {
+ if(((Hotspot*)obj)->AcceptConnectionsFromImplemented()) {
+ add_object = obj->AcceptConnectionsFrom(connection_type, *selected_obj);
+ if(!add_object) {
+ add_object = false;
+ break;
+ }
+ } else {
+ add_object = true;
+ }
+ } else {
+ add_object = true;
+ }
+ } else {
+ add_object = false;
+ break;
+ }
+ } else {
+ add_object = obj->AcceptConnectionsFrom(connection_type, *selected[j]);
+ if(!add_object) {
+ add_object = false;
+ break;
+ }
+ }
+ }
+ }
+
+ if(add_object) {
+ vec3 point;
+ vec3 normal;
+ if(obj->lineCheck(mouseray.start, mouseray.end, &point, &normal) != -1) {
+ float dist_sq = dot(point - mouseray.start, point - mouseray.start);
+ if(dist_sq < highlit_obj_dist) {
+ highlit_obj = obj;
+ highlit_obj_dist = dist_sq;
+ }
+ }
+ box_objects.push_back(obj);
+ }
+ }
+ }
+ }
+ } else {
+ float highlit_obj_dist = FLT_MAX;
+ std::vector<int> connected_ids;
+ connected_ids.reserve(64);
+ for(size_t i = 0; i < selected.size(); ++i) {
+ Object* selected_obj = selected[i];
+ box_objects.push_back(selected_obj);
+ connected_ids.clear();
+ selected[i]->GetConnectionIDs(&connected_ids);
+ for(size_t j = 0; j < connected_ids.size(); ++j) {
+ Object* obj = scenegraph_->GetObjectFromID(connected_ids[j]);
+ GetClosest(mouseray, obj, highlit_obj, highlit_obj_dist);
+ box_objects.push_back(obj);
+ }
+ for(size_t j = 0; j < selected_obj->connected_to.size(); ++j) {
+ Object* obj = scenegraph_->GetObjectFromID(selected_obj->connected_to[j]);
+ GetClosest(mouseray, obj, highlit_obj, highlit_obj_dist);
+ box_objects.push_back(obj);
+ }
+ for(size_t j = 0; j < selected_obj->connected_from.size(); ++j) {
+ Object* obj = scenegraph_->GetObjectFromID(selected_obj->connected_from[j]);
+ GetClosest(mouseray, obj, highlit_obj, highlit_obj_dist);
+ box_objects.push_back(obj);
+ }
+ }
+ }
+
+ if(connection_type == Object::kCTEnvObjectsAndGroups) {
+ // Attach static objects to character bones
+ static const uint32_t MAX_SELECTED_OBJECTS = 32;
+ if(selected.size() <= MAX_SELECTED_OBJECTS){
+ Object* selected_objects[MAX_SELECTED_OBJECTS];
+ for(size_t i=0, len=selected.size(); i<len; ++i){
+ DebugDraw::Instance()->AddLine(selected[i]->GetTranslation(), mouseray.end, vec4(vec3(0.5f),0.5f), _delete_on_draw, _DD_XRAY);
+ selected_objects[i] = selected[i];
+ }
+ std::vector<Object*> &movement_objects = scenegraph_->movement_objects_;
+ for(size_t i=0, len=movement_objects.size(); i<len; ++i){
+ MovementObject* mo = (MovementObject*)movement_objects[i];
+ if(mo->rigged_object()->DrawBoneConnectUI(selected_objects, (int) selected.size(), imui_context, active_tool_, mo->GetID())){
+ QueueSaveHistoryState();
+ }
+ }
+ }
+ }
+
+ for(size_t i = 0; i < selected.size(); ++i) {
+ vec3 start = selected[i]->GetTranslation();
+ for(size_t j = 0; j < selected[i]->connected_to.size(); ++j) {
+ vec3 end = scenegraph_->GetObjectFromID(selected[i]->connected_to[j])->GetTranslation();
+ DebugDraw::Instance()->AddLine(start, end, vec4(0.0f,1.0f,0.0f,0.8f), _delete_on_draw);
+ DebugDraw::Instance()->AddLine(start, end, vec4(0.0f,1.0f,0.0f,0.3f), _delete_on_draw, _DD_XRAY);
+ }
+ for(size_t j = 0; j < selected[i]->connected_from.size(); ++j) {
+ vec3 end = scenegraph_->GetObjectFromID(selected[i]->connected_from[j])->GetTranslation();
+ DebugDraw::Instance()->AddLine(start, end, vec4(1.0f,0.0f,0.0f,0.8f), _delete_on_draw);
+ DebugDraw::Instance()->AddLine(start, end, vec4(1.0f,0.0f,0.0f,0.3f), _delete_on_draw, _DD_XRAY);
+ }
+ }
+
+ for(size_t i = 0; i < box_objects.size(); ++i) {
+ DrawBox(box_objects[i]->box_, box_objects[i], box_objects[i] == highlit_obj || box_objects[i]->Selected(), box_objects[i]->box_color);
+ }
+ } else {
+ imui_context.ClearHot();
+ }
+
+
+ if(terrain_preview_mode) {
+ int width = Graphics::Instance()->window_dims[0];
+ int height = Graphics::Instance()->window_dims[1];
+ ImGui::SetNextWindowSize(ImVec2(256.0f, 54.0f));
+ ImGui::SetNextWindowPos(ImVec2(width * 0.5f - 128.0f, height - 64.0f));
+ ImGui::Begin("##terrainpreview", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove);
+ ImGui::Text("%s", "Terrain preview mode");
+ if(ImGui::Button("Use new terrain")) {
+ if(terrain_preview) {
+ TerrainInfo terrain_info = terrain_preview->terrain_info();
+ terrain_info.minimal = false;
+
+ Object* terrain_object_ptr = scenegraph_->terrain_object_;
+ if(terrain_object_ptr) {
+ scenegraph_->UnlinkObject(terrain_object_ptr);
+ delete terrain_object_ptr;
+ }
+ scenegraph_->UnlinkObject(terrain_preview);
+ delete terrain_preview;
+ terrain_preview = NULL;
+
+ TerrainObject* new_terrain = new TerrainObject(terrain_info);
+ new_terrain->SetID(0);
+ scenegraph_->addObject(new_terrain);
+ scenegraph_->terrain_object_ = new_terrain;
+ new_terrain->PreparePhysicsMesh();
+ scenegraph_->sky->QueueLoadResources();
+ scenegraph_->sky->BakeFirstPass();
+ }
+ // Nothing to do if heightmap hasn't changed
+
+ ExitTerrainPreviewMode();
+ }
+ ImGui::SameLine();
+ if(ImGui::Button("Keep old terrain")) {
+ if(terrain_preview) {
+ scenegraph_->UnlinkObject(terrain_preview);
+ delete terrain_preview;
+ terrain_preview = NULL;
+ } else {
+ TerrainObject* terrain = scenegraph_->terrain_object_;
+ terrain->SetTerrainColorTexture(real_terrain_info.colormap.c_str());
+ terrain->SetTerrainWeightTexture(real_terrain_info.weightmap.c_str());
+ terrain->SetTerrainDetailTextures(real_terrain_info.detail_map_info);
+ }
+
+ ExitTerrainPreviewMode();
+ }
+ ImGui::End();
+ }
+}
+
+static Collision lineCheckSelected(SceneGraph::object_list &objects, const vec3 &start, const vec3 &end, vec3 *point) {
+ Collision new_collision;
+ new_collision.hit=false;
+ new_collision.hit_what = NULL;
+ vec3 new_end = end;
+ vec3 normal;
+ int collided=-1;
+ int collide=-1;
+ for(size_t i=0, len=objects.size(); i<len; ++i){
+ Object* obj = objects[i];
+ if (obj->Selected()) {
+ collide = obj->lineCheck(start,new_end,point,&normal);
+ if(collide!=-1) {
+ new_end = *point;
+ collided=collide;
+ new_collision.hit=true;
+ new_collision.hit_normal = normal;
+ new_collision.hit_what = obj;
+ new_collision.hit_how = collided;
+ new_collision.hit_where = new_end;
+ }
+ }
+ }
+ return new_collision;
+}
+
+static Collision lineCheckActiveSelected(SceneGraph* scenegraph, const vec3 &start, const vec3 &end, vec3 *point, vec3 *normal) {
+ vec3 new_end = end;
+ Collision tmp, ret;
+ vec3 tmp_point;
+ vec3 *point_ptr;
+ if(point){
+ point_ptr = point;
+ } else {
+ point_ptr = &tmp_point;
+ }
+ // lineCheck aginst the terrain
+ if (scenegraph != NULL && scenegraph->terrain_object_ != NULL) {
+ vec3 normal;
+ if (scenegraph->terrain_object_->lineCheck(start, end, point_ptr, &normal)!=-1) {
+ new_end = *point_ptr;
+ new_end += 0.01f * normalize(end - start); // so that decals on surface of terrain are hit (To do: Why is this necessary? Isn't it also done in DecalEditor::lineCheckDisplay2D?)
+ }
+ }
+ tmp = lineCheckSelected(scenegraph->objects_, start, new_end, point_ptr);
+ if (tmp.hit) {
+ new_end = tmp.hit_where;
+ ret = tmp;
+ }
+ return ret;
+}
+
+static EditorTypes::Tool OmniGetTool(const Collision& c, int permission_flags, const Object* object_, const Box& box_) {
+ const Keyboard& keyboard = Input::Instance()->getKeyboard();
+ if(KeyCommand::CheckDown(keyboard, KeyCommand::kForceTranslate, KIMF_LEVEL_EDITOR_GENERAL) && permission_flags&Object::CAN_TRANSLATE){
+ return EditorTypes::TRANSLATE;
+ } else if(KeyCommand::CheckDown(keyboard, KeyCommand::kForceScale, KIMF_LEVEL_EDITOR_GENERAL) && permission_flags&Object::CAN_SCALE){
+ return EditorTypes::SCALE;
+ } else if(KeyCommand::CheckDown(keyboard, KeyCommand::kForceRotate, KIMF_LEVEL_EDITOR_GENERAL) && permission_flags&Object::CAN_ROTATE){
+ return EditorTypes::ROTATE;
+ }
+
+ vec3 point = c.hit_where;
+ vec3 normal = c.hit_normal;
+
+ // to object space
+ mat4 inv = invert(object_->GetTransform());
+ point = inv*point;
+ normal = normalize(inv.GetRotatedvec3(normal));
+
+ int which_face = box_.GetHitFaceIndex(normal, point);
+ if (which_face == -1) {
+ return EditorTypes::NO_TOOL;
+ }
+ float point_dist = 0;
+ int closest_point = box_.GetNearestPointIndex(point, point_dist);
+ point_dist = length( (point - box_.GetPoint(closest_point)) / box_.dims);
+
+ static const float dimensions_shrink = 0.75f;
+ if( permission_flags&Object::CAN_TRANSLATE &&
+ ( ( !( permission_flags&Object::CAN_ROTATE ) && !( permission_flags&Object::CAN_SCALE ) ) ||
+ box_.IsInFace( point, which_face, dimensions_shrink ) ) )
+ {
+ return EditorTypes::TRANSLATE;
+ } else if( permission_flags&Object::CAN_SCALE &&
+ ( !(permission_flags&Object::CAN_ROTATE) || point_dist < 0.25f ) )
+ {
+ return EditorTypes::SCALE;
+ } else if (permission_flags&Object::CAN_ROTATE) {
+ return EditorTypes::ROTATE;
+ }
+ return EditorTypes::NO_TOOL;
+}
+
+static void SetBit(uint32_t* bitfield, int mask, bool val) {
+ if(val) {
+ *bitfield |= mask;
+ } else {
+ *bitfield &= ~mask;
+ }
+}
+
+void MapEditor::Update(GameCursor* cursor) {
+ if(!Engine::Instance()->menu_paused) {
+ // Update editor mouseray
+ LineSegment mouseray;
+ GetMouseRay(&mouseray);
+
+ if (state_ == MapEditor::kIdle) { // Update active tool if no action is occuring
+ Collision mouseray_collision_selected = lineCheckActiveSelected(scenegraph_, mouseray.start, mouseray.end, NULL, NULL);
+ omni_tool_tool_ = EditorTypes::NO_TOOL;
+ bool hit_something = mouseray_collision_selected.hit;
+ if (hit_something) {;
+ Object* hit_what = mouseray_collision_selected.hit_what;
+ if(hit_what){
+ omni_tool_tool_ = OmniGetTool(mouseray_collision_selected, hit_what->permission_flags, hit_what, hit_what->box_);
+ }
+ }
+ }
+ // Handle sky editor
+ if (state_ == MapEditor::kSkyDrag || !lineCheckActiveSelected(scenegraph_, mouseray.start, mouseray.end, NULL, NULL).hit) {
+ vec3 mouseray_dir = normalize(mouseray.end - mouseray.start);
+ float dst_from_sun = dot(mouseray_dir,sky_editor_->sun_dir_);
+ if (dst_from_sun <= 1.0f && dst_from_sun >= 0.0f) {
+ float angle_from_sun = acosf(dst_from_sun);
+
+ // Check if we've double-clicked on the sun
+ if (!sky_editor_->HandleSelect(angle_from_sun)) {
+ // Get relevant tool based on mouse position
+ if (!sky_editor_->m_sun_translating && !sky_editor_->m_sun_scaling && !sky_editor_->m_sun_rotating){
+ sky_editor_->m_tool = sky_editor_->OmniGetTool(angle_from_sun, mouseray);
+ }
+ // Handle the activated tool
+ if (!sky_editor_->m_sun_scaling && !sky_editor_->m_sun_rotating && sky_editor_->m_tool == EditorTypes::TRANSLATE) sky_editor_->HandleSunTranslate(angle_from_sun);
+ if (!sky_editor_->m_sun_translating && !sky_editor_->m_sun_rotating && sky_editor_->m_tool == EditorTypes::SCALE) sky_editor_->HandleSunScale(angle_from_sun);
+ if (!sky_editor_->m_sun_translating && !sky_editor_->m_sun_scaling && sky_editor_->m_tool == EditorTypes::ROTATE) sky_editor_->HandleSunRotate(angle_from_sun, mouseray_dir, cursor);
+ }
+ }
+ }
+ if (!CheckForSelections( mouseray )) {
+ if(state_ == MapEditor::kIdle){
+ HandleShortcuts( mouseray );
+ }
+ UpdateTools(mouseray, cursor);
+ }
+ // Update cursor
+ if(state_ == MapEditor::kIdle){
+ UpdateCursor(mouseray, cursor);
+ }
+ }
+
+ if(color_history_countdown_) {
+ color_history_countdown_--;
+ if(color_history_countdown_ == 0) {
+ int found_index = -1;
+ for(int i = 0; i < kColorHistoryLen; ++i) {
+ bool closeEnough = true;
+
+ for(int j = 0; j < 4; ++j) {
+ if(std::fabs(color_history_candidate_[j] - color_history_[i][j]) > 0.001f) {
+ closeEnough = false;
+ break;
+ }
+ }
+
+ if(closeEnough){
+ found_index = i;
+ break;
+ }
+ }
+
+ if(found_index == -1){
+ for(int i = kColorHistoryLen - 1; i > 0; --i)
+ color_history_[i] = color_history_[i - 1];
+ } else {
+ for(int i = found_index; i > 0; --i) {
+ color_history_[i] = color_history_[i - 1];
+ }
+ }
+
+ color_history_[0] = color_history_candidate_;
+ }
+ }
+
+ if(save_countdown_){
+ save_countdown_--;
+ if(save_countdown_ == 0){
+ // Add a new state to the state history
+ ++state_history_.current_state;
+ state_history_.num_states = state_history_.current_state+1;
+ int state_id = state_history_.current_state;
+
+ { // Erase all chunks past current state
+ std::list<SavedChunk> &chunks = state_history_.chunks;
+ for(std::list<SavedChunk>::iterator iter = chunks.begin();
+ iter != chunks.end();)
+ {
+ SavedChunk& chunk = (*iter);
+ if(chunk.state_id >= state_history_.current_state){
+ iter = chunks.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+ }
+ Graphics *graphics = Graphics::Instance();
+ if(!graphics->nav_mesh_out_of_date){
+ graphics->nav_mesh_out_of_date_chunk = state_id;
+ }
+ LOGI << "Saving history state " << state_id << "..." << std::endl;
+ LOGI << "-----------------------" << std::endl;
+ // Get iterator to last saved chunk before current state save
+ std::list<SavedChunk> &chunks = state_history_.chunks;
+ std::list<SavedChunk>::iterator last_chunk = chunks.end();
+ if(last_chunk!=chunks.begin()){
+ last_chunk--;
+ }
+ scenegraph_->level->SaveHistoryState(chunks, state_id);
+ std::vector<Object*> entities;
+ for(size_t i=0, len=scenegraph_->objects_.size(); i<len; ++i){
+ Object* obj = scenegraph_->objects_[i];
+ if(!obj->parent && !obj->exclude_from_undo){
+ entities.push_back(obj);
+ }
+ }
+ for (unsigned i=0; i<entities.size(); ++i) {
+ entities[i]->SaveHistoryState(chunks, state_id);
+ }
+ sky_editor_->SaveHistoryState(chunks, state_id);
+ // Find out how many chunks have been changed
+ int num_chunks_changed = 0;
+ int num_chunks_checked = 0;
+ std::list<SavedChunk>::iterator chunk_iter = last_chunk;
+ if(chunk_iter!=chunks.end()){
+ chunk_iter++;
+ }
+ for(; chunk_iter!=chunks.end(); ++chunk_iter){
+ num_chunks_changed++;
+ }
+ // No chunks changed? Don't bother saving this revision
+ if(num_chunks_changed == 0 && state_history_.num_states > 2){
+ Undo();
+ state_history_.num_states--;
+ LOGI << "None of " << num_chunks_checked << " chunks changed... undoing." << std::endl;
+ } else {
+ LOGI << "Some of " << num_chunks_checked << " chunks changed." << std::endl;
+ }
+ save_countdown_ = 0;
+
+ //If the last saved chunk global is invalid we try and fix it,
+ //This is mainly due to startup, where we don't have a state to point to until the first
+ //state is saved.
+ if( last_saved_chunk_global_id == 0 )
+ {
+ SetLastSaveOnCurrentUndoChunk();
+ }
+ }
+ }
+}
+
+void MapEditor::ShowEditorMessage( int type, const std::string& message) {
+ DisplayError("Editor Error", message.c_str(), _ok);
+}
+
+void MapEditor::CPSetColor(const vec3 &color){
+ SendMessageToSelectedObjects(scenegraph_, OBJECT_MSG::SET_COLOR, &color);
+}
+
+void MapEditor::UpdateCursor( const LineSegment& mouseray, GameCursor* cursor ) {
+ Collision mouseray_collision_selected =
+ lineCheckActiveSelected(scenegraph_, mouseray.start, mouseray.end, NULL, NULL);
+ EditorTypes::Tool tool_cursor;
+ switch(active_tool_){
+ case EditorTypes::OMNI:
+ tool_cursor = omni_tool_tool_;
+ break;
+ case EditorTypes::CONNECT:
+ tool_cursor = EditorTypes::CONNECT;
+ break;
+ case EditorTypes::DISCONNECT:
+ tool_cursor = EditorTypes::DISCONNECT;
+ break;
+ case EditorTypes::ADD_ONCE:
+ tool_cursor = EditorTypes::ADD_ONCE;
+ break;
+ default:
+ if (mouseray_collision_selected.hit){
+ tool_cursor = active_tool_;
+ } else {
+ if(!sky_editor_->m_sun_translating && !sky_editor_->m_sun_scaling && !sky_editor_->m_sun_rotating){
+ sky_editor_->UpdateCursor(cursor);
+ }
+ return;
+ //tool_cursor = EditorTypes::NO_TOOL;
+ }
+ }
+ switch(tool_cursor) {
+ case EditorTypes::CONNECT:
+ cursor->SetCursor(LINK_CURSOR);
+ break;
+ case EditorTypes::DISCONNECT:
+ cursor->SetCursor(UNLINK_CURSOR);
+ break;
+ case EditorTypes::TRANSLATE:
+ cursor->SetCursor(TRANSLATE_CURSOR);
+ break;
+ case EditorTypes::SCALE:
+ cursor->SetCursor(SCALE_CURSOR);
+ break;
+ case EditorTypes::ROTATE:
+ cursor->SetCursor(ROTATE_CURSOR);
+ break;
+ case EditorTypes::ADD_ONCE:
+ cursor->SetCursor(ADD_CURSOR);
+ break;
+ default:
+ cursor->SetCursor(DEFAULT_CURSOR);
+ break;
+ }
+}
+
+void MapEditor::CopySelected() {
+ RibbonFlash(gui, "copy");
+ ActorsEditor_CopySelectedEntities(scenegraph_, &copy_desc_list_);
+}
+
+void MapEditor::Paste( const LineSegment& mouseray ) {
+ RibbonFlash(gui, "paste");
+ EntityDescriptionList fixed_copy_list = copy_desc_list_;
+ ActorsEditor_UnlocalizeIDs(&fixed_copy_list, scenegraph_);
+ ActorsEditor_AddEntitiesAtMouse(Path(), scenegraph_, fixed_copy_list, mouseray, false);
+ QueueSaveHistoryState();
+}
+
+static bool HasSelectedDeletableParent(Object* obj) {
+ if(obj->parent){
+ if(obj->parent->Selected()){
+ return (obj->parent->permission_flags&Object::CAN_DELETE) != 0;
+ } else {
+ return HasSelectedDeletableParent(obj->parent);
+ }
+ } else {
+ return false;
+ }
+}
+
+void MapEditor::DeleteSelected() {
+ RibbonFlash(gui, "delete");
+ std::vector<Object*> objects_to_delete;
+ for(int i=(int)scenegraph_->objects_.size()-1; i>=0; --i){
+ Object* obj = scenegraph_->objects_[i];
+ if (!HasSelectedDeletableParent(obj)) {
+ if(obj->Selected() && obj->permission_flags&Object::CAN_DELETE)
+ objects_to_delete.push_back(obj);
+ }
+ }
+ for(size_t i=0, len=objects_to_delete.size(); i<len; ++i){
+ RemoveObject(objects_to_delete[i], scenegraph_);
+ }
+ QueueSaveHistoryState();
+}
+
+void MapEditor::CutSelected() {
+ RibbonFlash(gui, "cut");
+ CopySelected();
+ DeleteSelected();
+}
+
+void MapEditor::GroupSelected() {
+ RibbonFlash(gui, "group");
+ ReturnSelected(&selected);
+ if(selected.size() > 1){
+ std::vector<int> child_ids;
+ for(size_t i=0, len=selected.size(); i<len; ++i){
+ if(!selected[i]->parent){
+ child_ids.push_back(selected[i]->GetID());
+ }
+ }
+ if(child_ids.size() > 1){
+ Group *group = new Group();
+ if( ActorsEditor_AddEntity(scenegraph_, group) ) {
+ group->children.resize(child_ids.size());
+ for(size_t i=0, len=child_ids.size(); i<len; ++i){
+ group->children[i].direct_ptr = scenegraph_->GetObjectFromID(child_ids[i]);
+ }
+ for(size_t i=0, len=child_ids.size(); i<len; ++i){
+ group->children[i].direct_ptr->SetParent(group);
+ }
+ group->InitShape();
+ group->InitRelMats();
+ DeselectAll(scenegraph_);
+ group->Select(true);
+ QueueSaveHistoryState();
+ } else {
+ LOGE << "Failed at adding new Group to scenegraph" << std::endl;
+ delete group;
+ }
+ }
+ }
+}
+
+bool MapEditor::ContainsPrefabsRecursively( std::vector<Object*> objects ) {
+ bool res = false;
+ for( unsigned i = 0; i < objects.size(); i++ ) {
+ if( objects[i]->IsGroupDerived() ) {
+ Group *g = static_cast<Group*>(objects[i]);
+ std::vector<Object*> kids;
+ for( unsigned k = 0; k < g->children.size(); k++ ) {
+ kids.push_back(g->children[k].direct_ptr);
+ }
+ res = res || ContainsPrefabsRecursively(kids);
+
+ if( objects[i]->GetType() == _prefab ) {
+ res = true;
+ }
+ }
+ }
+ return res;
+}
+
+int MapEditor::PrefabSelected() {
+ RibbonFlash(gui, "prefab");
+ ReturnSelected(&selected);
+
+ if(selected.size() == 1 && selected[0]->GetType() == _prefab) {
+ return 1;
+ } else if( ContainsPrefabsRecursively(selected) ) {
+ return 2;
+ } else if( selected.size() == 1 && selected[0]->GetType() == _group) {
+ Group* f_group = static_cast<Group*>(selected[0]);
+ if( f_group->HasParent() ) {
+ return 3;
+ } else {
+ Prefab* prefab = new Prefab();
+ if( ActorsEditor_AddEntity(scenegraph_, prefab) ) {
+ DeselectAll(scenegraph_);
+
+ size_t c_count = f_group->children.size();
+ prefab->children.resize(c_count);
+ for(unsigned i=0; i < c_count; i++) {
+ prefab->children[i].direct_ptr = f_group->children[i].direct_ptr;
+ }
+ for(unsigned i=0; i < c_count; i++) {
+ prefab->children[i].direct_ptr->SetParent(prefab);
+ }
+
+ f_group->children.clear();
+ scenegraph_->UnlinkObject(f_group);
+ delete f_group;
+
+ prefab->InitShape();
+ prefab->InitRelMats();
+ prefab->Select(true);
+ QueueSaveHistoryState();
+ return 0;
+ } else {
+ LOGE << "Failed at adding prefab to scenegraph" << std::endl;
+ delete prefab;
+ return 6;
+ }
+ }
+ } else if(selected.size() > 0) {
+ std::vector<int> child_ids;
+ for(size_t i=0, len=selected.size(); i<len; ++i){
+ if(!selected[i]->parent){
+ child_ids.push_back(selected[i]->GetID());
+ }
+ }
+ if(child_ids.size() > 0){
+ Prefab *prefab = new Prefab();
+ if( ActorsEditor_AddEntity(scenegraph_, prefab) ) {
+ prefab->children.resize(child_ids.size());
+ for(size_t i=0, len=child_ids.size(); i<len; ++i){
+ prefab->children[i].direct_ptr = scenegraph_->GetObjectFromID(child_ids[i]);
+ }
+ for(size_t i=0, len=child_ids.size(); i<len; ++i){
+ prefab->children[i].direct_ptr->SetParent(prefab);
+ }
+ prefab->InitShape();
+ prefab->InitRelMats();
+ DeselectAll(scenegraph_);
+ prefab->Select(true);
+ QueueSaveHistoryState();
+ return 0;
+ } else {
+ LOGE << "Failed at adding prefab to scenegraph" << std::endl;
+ delete prefab;
+ return 6;
+ }
+ } else {
+ return 4;
+ }
+ } else {
+ return 5;
+ }
+}
+
+void MapEditor::UngroupSelected() {
+ RibbonFlash(gui, "ungroup");
+ ReturnSelected(&selected);
+ for(size_t i=0, len=selected.size(); i<len; ++i){
+ if(selected[i]->GetType() == _group || selected[i]->GetType() == _prefab){
+ selected[i]->SetParent(NULL); // Disconnects from any parent objects
+ // Notify everyone that object is deleted
+ const int BUF_SIZE = 255;
+ char buf[BUF_SIZE];
+ snprintf(buf, BUF_SIZE, "notify_deleted %d", selected[i]->GetID());
+ scenegraph_->level->Message(buf);
+ for (SceneGraph::object_list::iterator iter = scenegraph_->objects_.begin();
+ iter != scenegraph_->objects_.end();
+ ++iter)
+ {
+ Object* obj = *iter;
+ obj->NotifyDeleted(selected[i]);
+ }
+ scenegraph_->UnlinkObject(selected[i]);
+ selected[i]->Dispose();
+ delete(selected[i]);
+ }
+ }
+ QueueSaveHistoryState();
+}
+
+bool MapEditor::IsSomethingSelected() {
+ for (size_t i=0, len=scenegraph_->objects_.size(); i<len; ++i) {
+ if (scenegraph_->objects_[i]->Selected()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool MapEditor::IsOneObjectSelected() {
+ ReturnSelected(&selected);
+ if(selected.size()!=1) {
+ return false;
+ }
+ if(selected[0]->GetType() != _env_object) {
+ return false;
+ }
+ return true;
+}
+
+void MapEditor::QueueSaveHistoryState() {
+ save_countdown_ = kNumUpdatesBeforeSave;
+}
+
+void MapEditor::ApplyScriptParams(const ScriptParamMap& spm, int id){
+ if(id == LEVEL_PARAM_ID){
+ if( !testScriptParamsEqual( spm, scenegraph_->level->script_params().GetParameterMap() ) ) {
+ SceneGraph::ApplyScriptParams(scenegraph_, spm);
+ QueueSaveHistoryState();
+ }
+ } else {
+ Object* obj = scenegraph_->GetObjectFromID(id);
+ if(obj){
+ ScriptParamMap new_spm = spm;
+ ScriptParamMap::iterator iter;
+
+ if( !testScriptParamsEqual(new_spm, obj->GetScriptParamMap() ) ) {
+ obj->SetScriptParams(new_spm);
+ QueueSaveHistoryState();
+ }
+ }
+ }
+
+}
+
+OGPalette* MapEditor::GetPalette(int id){
+ Object* obj = scenegraph_->GetObjectFromID(id);
+ if(!obj) {
+ DisplayError("Error", "Could not get palette of object because it doesn't exist.");
+ return NULL;
+ }
+ return obj->GetPalette();
+}
+
+struct CollisionSortable {
+ int id;
+ float dist;
+};
+
+bool CollisionSortableCompare(const CollisionSortable &a, const CollisionSortable &b) {
+ return a.dist < b.dist;
+}
+
+void MapEditor::HandleShortcuts( const LineSegment &mouseray ) {
+ Online* online = Online::Instance();
+
+ const Keyboard& keyboard = Input::Instance()->getKeyboard();
+ if ( KeyCommand::CheckPressed(keyboard, KeyCommand::kEditScriptParams, KIMF_LEVEL_EDITOR_GENERAL) ) {
+ show_selected = !show_selected;
+ }
+
+ if ( KeyCommand::CheckPressed(keyboard, KeyCommand::kSearchScenegraph, KIMF_LEVEL_EDITOR_GENERAL) ) {
+ select_scenegraph_search = true;
+ show_scenegraph = true;
+ }
+
+ if ( KeyCommand::CheckPressed(keyboard, KeyCommand::kScenegraph, KIMF_LEVEL_EDITOR_GENERAL) ) {
+ show_scenegraph = !show_scenegraph;
+ }
+
+ if ( KeyCommand::CheckPressed(keyboard, KeyCommand::kEditColor, KIMF_LEVEL_EDITOR_GENERAL) ) {
+ //Color picking.
+ show_color_picker = !show_color_picker;
+ }
+
+ if ( KeyCommand::CheckPressed(keyboard, KeyCommand::kTogglePlayer, KIMF_LEVEL_EDITOR_GENERAL) ) {
+ ReturnSelected(&selected);
+ bool one_character_selected = false;
+ if(selected.size() == 1 && selected[0]->GetType() == _movement_object){
+ one_character_selected = true;
+ }
+ if(one_character_selected){
+ MovementObject* mo = (MovementObject*)(selected[0]);
+ mo->is_player = !mo->is_player;
+ QueueSaveHistoryState();
+
+ if (online->IsHosting()) {
+ if (!mo->is_player) {
+ online->RemoveFreeAvatarId(mo->GetID());
+ }
+ else {
+ online->AddFreeAvatarId(mo->GetID());
+ }
+ }
+
+ }
+ }
+ // Handle switching editor type
+ if ( KeyCommand::CheckPressed(keyboard, KeyCommand::kToggleObjectEditing, KIMF_LEVEL_EDITOR_GENERAL) ) {
+ SetTypeEnabled(_env_object, !IsTypeEnabled(_env_object));
+ }
+ if ( KeyCommand::CheckPressed(keyboard, KeyCommand::kToggleDecalEditing, KIMF_LEVEL_EDITOR_GENERAL) ) {
+ SetTypeEnabled(_decal_object, !IsTypeEnabled(_decal_object));
+ }
+ if ( KeyCommand::CheckPressed(keyboard, KeyCommand::kToggleHotspotEditing, KIMF_LEVEL_EDITOR_GENERAL) ) {
+ SetTypeEnabled(_hotspot_object, !IsTypeEnabled(_hotspot_object));
+ }
+ // Handle saves
+ // This bool is needed because the new default keybind for "save level as"
+ // is the old "save selected items", so to keep backward/forward compatibility
+ // it needs to be able to have the same keybind, but override the old behaviour
+ bool save_triggered = false;
+ if ( KeyCommand::CheckPressed(keyboard, KeyCommand::kSaveLevelAs, KIMF_LEVEL_EDITOR_GENERAL) ) {
+ SaveLevel(LevelLoader::kSaveAs);
+ save_triggered = true;
+ }
+ if ( KeyCommand::CheckPressed(keyboard, KeyCommand::kSaveLevel, KIMF_LEVEL_EDITOR_GENERAL) ) {
+ SaveLevel(LevelLoader::kSaveInPlace);
+ }
+ if ( !save_triggered && KeyCommand::CheckPressed(keyboard, KeyCommand::kSaveSelectedItems, KIMF_LEVEL_EDITOR_GENERAL) ) {
+ KeyCommand::CheckPressed(keyboard, KeyCommand::kSaveLevelAs, KIMF_LEVEL_EDITOR_GENERAL);
+ SaveSelected();
+ }
+ // handle undo/redo
+ if ( KeyCommand::CheckPressed(keyboard, KeyCommand::kRedo, KIMF_LEVEL_EDITOR_GENERAL) ) {
+ Redo();
+ }
+ if ( KeyCommand::CheckPressed(keyboard, KeyCommand::kUndo, KIMF_LEVEL_EDITOR_GENERAL) ) {
+ Undo();
+ }
+ // handle copy/paste
+ if ( KeyCommand::CheckPressed(keyboard, KeyCommand::kCopy, KIMF_LEVEL_EDITOR_GENERAL) ) {
+ CopySelected();
+ }
+ if ( KeyCommand::CheckPressed(keyboard, KeyCommand::kCut, KIMF_LEVEL_EDITOR_GENERAL) ) {
+ CutSelected();
+ }
+ if ( KeyCommand::CheckPressed(keyboard, KeyCommand::kPaste, KIMF_LEVEL_EDITOR_GENERAL) ) {
+ Paste(mouseray);
+ }
+ // handle delete
+ if (keyboard.wasScancodePressed(SDL_SCANCODE_DELETE, KIMF_LEVEL_EDITOR_GENERAL) || keyboard.wasScancodePressed(SDL_SCANCODE_BACKSPACE, KIMF_LEVEL_EDITOR_GENERAL)) {
+ DeleteSelected();
+ }
+ if ( KeyCommand::CheckPressed(keyboard, KeyCommand::kEnableImposter, KIMF_LEVEL_EDITOR_GENERAL) ) {
+ SetImposter(true);
+ }
+ if ( KeyCommand::CheckPressed(keyboard, KeyCommand::kDisableImposter, KIMF_LEVEL_EDITOR_GENERAL) ) {
+ SetImposter(false);
+ }
+ const Mouse &mouse = Input::Instance()->getMouse();
+ const bool kEnableControlClickAdd = false;
+ if(kEnableControlClickAdd){
+ if (mouse.mouse_down_[Mouse::LEFT] == 1 && keyboard.isKeycodeDown(command_SDLK_key, KIMF_LEVEL_EDITOR_GENERAL) && !IsViewingNavMesh()) {
+ Camera *ci = ActiveCameras::Get();
+ vec3 start = ci->GetPos();
+ vec3 end = ci->GetPos()+ci->GetMouseRay()*kMapEditorMouseRayLength;
+ Collision c = scenegraph_->lineCheckCollidable(start, end);
+ if(c.hit){
+ //Object* ppo = new ReflectionCaptureObject();
+ Object* ppo = new LightVolumeObject();
+ if( ActorsEditor_AddEntity(scenegraph_, ppo) ) {
+ ppo->SetTranslation(c.hit_where + c.hit_normal*0.7f);
+ QueueSaveHistoryState();
+ } else {
+ LOGE << "Failed at adding LightVolumeObject to scenegraph" << std::endl;
+ delete ppo;
+ }
+ }
+ }
+ }
+ // When holding ALT, see if should activate Connect/Disconnect tool
+ bool connect = KeyCommand::CheckDown(keyboard, KeyCommand::kConnect, KIMF_LEVEL_EDITOR_GENERAL);
+ bool disconnect = KeyCommand::CheckDown(keyboard, KeyCommand::kDisconnect, KIMF_LEVEL_EDITOR_GENERAL);
+ if(active_tool_ != EditorTypes::CONNECT && (connect || disconnect) && !selected.empty()) {
+ ReturnSelected(&selected);
+ Object::ConnectionType connection_type = GetConnectionType(selected);
+ if(connection_type != Object::kCTNone) { // Disable connection if mouse is over object, so we can still alt-drag clone
+ Collision mouseray_collision_selected = lineCheckActiveSelected(scenegraph_, mouseray.start, mouseray.end, NULL, NULL);
+ for(size_t i=0, len=selected.size(); i<len; ++i){
+ if(mouseray_collision_selected.hit_what == selected[i]) {
+ connection_type = Object::kCTNone;
+ }
+ }
+ }
+ if(connection_type != Object::kCTNone) {
+ if(connect) {
+ SetTool(EditorTypes::CONNECT);
+ } else if(disconnect) {
+ SetTool(EditorTypes::DISCONNECT);
+ }
+ }
+ }
+ // End connect/disconnect if alt is released
+ if((active_tool_ == EditorTypes::CONNECT || active_tool_ == EditorTypes::DISCONNECT) && !connect && !disconnect) {
+ SetTool( EditorTypes::OMNI );
+ }
+ if(active_tool_ == EditorTypes::CONNECT && disconnect) {
+ SetTool( EditorTypes::DISCONNECT );
+ }
+ if(active_tool_ == EditorTypes::DISCONNECT && !disconnect){
+ SetTool( EditorTypes::CONNECT );
+ }
+ if((active_tool_ == EditorTypes::CONNECT || active_tool_ == EditorTypes::DISCONNECT) && mouse.mouse_down_[Mouse::LEFT] == 1) {
+ Camera *ci = ActiveCameras::Get();
+ vec3 start = ci->GetPos();
+ vec3 end = ci->GetPos()+ci->GetMouseRay()*kMapEditorMouseRayLength;
+
+ std::vector<Collision> collisions;
+ scenegraph_->LineCheckAll(start, end, &collisions);
+ std::vector<CollisionSortable> collision_sortable;
+ collision_sortable.resize(collisions.size());
+ for(int i=0, len=(int)collisions.size(); i<len; ++i) {
+ collision_sortable[i].dist = distance_squared(start, collisions[i].hit_where);
+ collision_sortable[i].id = i;
+ }
+ std::sort(collision_sortable.begin(), collision_sortable.end(), CollisionSortableCompare);
+
+ bool something_happened = false;
+ for(size_t i=0, len=collisions.size(); i<len && !something_happened; ++i) {
+ Collision c = collisions[collision_sortable[i].id];
+ if(c.hit) {
+ Object* hit_obj = c.hit_what;
+ if(std::find(box_objects.begin(), box_objects.end(), hit_obj) != box_objects.end()) {
+ ReturnSelected(&selected);
+ for(unsigned i=0; i<selected.size(); ++i) {
+ if(active_tool_ == EditorTypes::CONNECT) {
+ Object* obj = selected[i];
+ //if(obj->ConnectTo(*hit_obj)){
+ // something_happened = true;
+ //}
+ } else if(active_tool_ == EditorTypes::DISCONNECT) {
+ Object* obj = selected[i];
+ Object* hit_obj = c.hit_what;
+ //if(obj->Disconnect(*hit_obj)){
+ //something_happened = true;
+ // }
+ }
+ }
+ if(something_happened) {
+ DeselectAll(scenegraph_);
+ if(c.hit_what->permission_flags & Object::CAN_SELECT){
+ c.hit_what->Select(true);
+ }
+ QueueSaveHistoryState();
+ } else if(c.hit_what->GetType() == _env_object || c.hit_what->GetType() == _terrain_type){
+ something_happened = true;
+ }
+ }
+ }
+ }
+ }
+ // Handle grouping
+ if(KeyCommand::CheckPressed(keyboard, KeyCommand::kUngroup, KIMF_LEVEL_EDITOR_GENERAL)) {
+ UngroupSelected();
+ }
+ else if(KeyCommand::CheckPressed(keyboard, KeyCommand::kGroup, KIMF_LEVEL_EDITOR_GENERAL)) {
+ GroupSelected();
+ }
+ if (KeyCommand::CheckPressed(keyboard, KeyCommand::kKillSelected, KIMF_LEVEL_EDITOR_GENERAL)) {
+ /*ReturnSelected(&selected);
+ bool one_character_selected = false;
+ if (selected.size() == 1 && selected[0]->GetType() == _movement_object){
+ one_character_selected = true;
+ }
+ if (one_character_selected){
+ MovementObject* mo = (MovementObject*)(selected[0]);
+ mo->ToggleDead();
+ QueueSaveHistoryState();
+ }*/
+ }
+
+ if(KeyCommand::CheckPressed(keyboard, KeyCommand::kFrameSelectedForce, KIMF_LEVEL_EDITOR_GENERAL)) {
+ CameraObject *co = ActiveCameras::Instance()->Get()->getCameraObject();
+ if( co ) {
+ co->FrameSelection(false);
+ }
+ } else if(KeyCommand::CheckPressed(keyboard, KeyCommand::kFrameSelected, KIMF_LEVEL_EDITOR_GENERAL)) {
+ CameraObject *co = ActiveCameras::Instance()->Get()->getCameraObject();
+ if( co ) {
+ co->FrameSelection(true);
+ }
+ }
+}
+
+static bool MouseWasClickedThisTimestep(Mouse* mouse) {
+ for (int i = 0; i < Mouse::NUM_BUTTONS; i++) {
+ if (mouse->mouse_down_[i] == Mouse::CLICKED) return true;
+ }
+ return false;
+}
+
+int MapEditor::ReplaceObjects( const std::vector<Object*>& objects, const std::string& replacement_path ) {
+ if(LoadEntitiesFromFile(replacement_path) != 0)
+ return -1;
+
+ ActorsEditor_UnlocalizeIDs(&add_desc_list_, scenegraph_);
+
+ for(size_t i = 0; i < objects.size(); i++) {
+ std::vector<Object*> new_objects = ActorsEditor_AddEntitiesAtPosition(add_desc_list_source_, scenegraph_, add_desc_list_, objects[i]->GetTranslation(), create_as_prefab_);
+
+ if(!new_objects.empty()){
+ vec3 scale = objects[i]->GetScale();
+ vec3 dims = objects[i]->box_.dims;
+ vec3 target_dims = dims * scale;
+ if(create_as_prefab_) {
+ Prefab* prefab = static_cast<Prefab*>(new_objects[0]);
+ vec3 prefab_scale = target_dims / prefab->original_scale_;
+
+ prefab->SetScale(prefab->original_scale_ * prefab_scale);
+ prefab->SetRotation(objects[i]->GetRotation());
+ prefab->SetTranslation(objects[i]->GetTranslation());
+ prefab->PropagateTransformsDown(true);
+ } else {
+ for(size_t j = 0; j < new_objects.size(); j++) {
+ vec3 new_dims = new_objects[j]->box_.dims;
+ vec3 target_scale = target_dims / new_dims;
+
+ new_objects[j]->SetScale(target_scale);
+ new_objects[j]->SetRotation(objects[i]->GetRotation());
+ new_objects[j]->SetTranslation(objects[i]->GetTranslation());
+ }
+ }
+ }
+
+ RemoveObject(objects[i], scenegraph_, true);
+ }
+
+ QueueSaveHistoryState();
+ return 0;
+}
+
+void MapEditor::UpdateTools( const LineSegment& mouseray, GameCursor* cursor ) {
+ EditorTypes::Tool t;
+ if (active_tool_ == EditorTypes::OMNI) {
+ t = omni_tool_tool_;
+ } else {
+ t = active_tool_;
+ }
+ Collision mouseray_collision_selected = lineCheckActiveSelected(scenegraph_, mouseray.start, mouseray.end, NULL, NULL);
+ switch(t) {
+ case EditorTypes::ADD_ONCE: {
+ Mouse* mouse = &(Input::Instance()->getMouse());
+ if (mouse->mouse_down_[Mouse::LEFT] == Mouse::CLICKED) {
+ ActorsEditor_UnlocalizeIDs(&add_desc_list_, scenegraph_);
+ ActorsEditor_AddEntitiesAtMouse(add_desc_list_source_, scenegraph_, add_desc_list_, mouseray, create_as_prefab_);
+ QueueSaveHistoryState();
+ SetTool( EditorTypes::OMNI );
+ }
+ break; }
+ case EditorTypes::TRANSLATE:
+ case EditorTypes::SCALE:
+ case EditorTypes::ROTATE:
+ UpdateTransformTool(scenegraph_, t, mouseray, mouseray_collision_selected, cursor);
+ break;
+ default:
+ //mjh - what is a reasonable thing to do here?
+ break;
+ }
+}
+
+bool MapEditor::HandleScrollSelect(const vec3 &start, const vec3 &end) {
+ bool something_happened = false;
+
+ // Check scroll direction
+ int scrolled = 0;
+ if (Input::Instance()->getMouse().wheel_delta_y_ < 0) {
+ scrolled = -1;
+ } else if (Input::Instance()->getMouse().wheel_delta_y_ > 0) {
+ scrolled = 1;
+ }
+ if(scrolled == 0){
+ return false;
+ }
+
+ // Get sorted list of collisions with mouse ray
+ std::vector<Collision> collisions;
+ GetEditorLineCollisions(scenegraph_, start, end, collisions, type_enable_);
+ if(collisions.empty()){
+ return false;
+ }
+ std::sort(collisions.begin(), collisions.end(), CollisionCompare(start));
+
+ for(int i=0; i<(int)collisions.size();){
+ if(collisions[i].hit_what->GetType() == _group || collisions[i].hit_what->GetType() == _prefab){
+ collisions.erase(collisions.begin()+i);
+ } else if(collisions[i].hit_what->parent &&
+ (collisions[i].hit_what->parent->GetType() == _group ||
+ collisions[i].hit_what->parent->GetType() == _prefab)
+ ){
+ int depth = 1;
+ while(collisions[i].hit_what->parent &&
+ (collisions[i].hit_what->parent->GetType() == _group ||
+ collisions[i].hit_what->parent->GetType() == _prefab )
+
+ ){
+ bool is_duplicate = false;
+ for(int j=0; j<i; ++j){ // Don't allow same group to be in list twice
+ if(collisions[i].hit_what->parent == collisions[j].hit_what){
+ is_duplicate = true;
+ break;
+ }
+ }
+ if(!is_duplicate){
+ ++depth;
+ collisions.insert(collisions.begin()+i, collisions[i]);
+ collisions[i].hit_what = collisions[i].hit_what->parent;
+ } else {
+ break;
+ }
+ }
+ i += depth;
+ } else {
+ ++i;
+ }
+ }
+
+ // Get selected item in collision list
+ int selected_item = -1;
+ for(int i=0, len=(int)collisions.size(); i < len; ++i){
+ Object* obj = collisions[i].hit_what;
+ if(obj->Selected()){
+ selected_item = i;
+ break;
+ }
+ }
+
+ // Move selection to next selectable item in list according to scroll direction
+ if(selected_item != -1){
+ int next_selectable = -1;
+ for(int i=selected_item+scrolled, len=(int)collisions.size(); i>=0 && i<len; i+=scrolled){
+ Object* obj = collisions[i].hit_what;
+ if(obj->permission_flags&Object::CAN_SELECT){
+ next_selectable = i;
+ break;
+ }
+ }
+ if(next_selectable != -1){
+ DeselectAll(scenegraph_);
+ collisions[next_selectable].hit_what->Select(true);
+ DrawObjInfo(collisions[next_selectable].hit_what);
+ }
+ }
+
+ return something_happened;
+}
+
+static Collision GetSelectableInLineSegment(SceneGraph *scenegraph, const LineSegment& l, const TypeEnable &type_enable){
+ std::vector<Collision> collisions;
+ GetEditorLineCollisions(scenegraph, l.start, l.end, collisions, type_enable);
+ if(!collisions.empty()){
+ return collisions[0];
+ } else {
+ return Collision();
+ }
+}
+
+void CalculateGroupString(Object* obj, std::string& str) {
+ if(obj->GetType() == _group || obj->GetType() == _prefab){
+ Group* group = (Group*)obj;
+ for(size_t i=0, len=group->children.size(); i<len; ++i){
+ CalculateGroupString(group->children[i].direct_ptr, str);
+ }
+ } else {
+ str = str + obj->obj_file;
+ }
+}
+
+void MapEditor::SelectAll(){
+ for (size_t i=0, len=scenegraph_->objects_.size(); i<len; ++i) {
+ Object* obj = scenegraph_->objects_[i];
+ EntityType type = obj->GetType();
+ if(type_enable_.IsTypeEnabled(type) && obj->permission_flags & Object::CAN_SELECT && !obj->parent){
+ obj->Select(true);
+ }
+ }
+}
+
+void MapEditor::ReloadAllPrefabs() {
+ std::vector<Object*> prefabs;
+ int current_depth = 1;
+ bool active_depth = true;
+
+ while( active_depth ) {
+ active_depth = false;
+ prefabs.clear();
+ for (size_t i=0, len=scenegraph_->objects_.size(); i<len; ++i) {
+ Object* obj = scenegraph_->objects_[i];
+ if( obj->GetGroupDepth() == current_depth ) {
+ active_depth = true;
+ if(obj->GetType() == _prefab){
+ prefabs.push_back(obj);
+ }
+ }
+ }
+
+ for(unsigned i = 0; i < prefabs.size(); i++) {
+ ReloadPrefab(prefabs[i],GetSceneGraph());
+ }
+ current_depth++;
+ }
+}
+
+void MapEditor::ReloadPrefabs(const Path& path) {
+ std::vector<Object*> prefabs;
+ for (size_t i=0, len=scenegraph_->objects_.size(); i<len; ++i) {
+ Object* obj = scenegraph_->objects_[i];
+ if(obj->GetType() == _prefab){
+ Prefab* prefab = static_cast<Prefab*>(obj);
+ if(strmtch(prefab->prefab_path.GetFullPath(),path.GetFullPath())) {
+ prefabs.push_back(obj);
+ }
+ }
+ }
+
+ for(unsigned i = 0; i < prefabs.size(); i++) {
+ ReloadPrefab(prefabs[i],GetSceneGraph());
+ }
+}
+
+bool MapEditor::ReloadPrefab(Object *obj, SceneGraph* scenegraph) {
+ if(obj->GetType() != _prefab) {
+ return false;
+ }
+
+ Prefab* prefab = static_cast<Prefab*>(obj);
+
+ if( prefab->prefab_locked == false ) {
+ return false;
+ }
+
+ if( prefab->prefab_path.isValid() == false ) {
+ return false;
+ }
+
+ if( FileExists(prefab->prefab_path) == false ) {
+ return false;
+ }
+ std::vector<Object*> ret_children;
+
+ prefab->GetBottomUpCompleteChildren(&ret_children);
+
+ for( unsigned i = 0; i < ret_children.size(); i++) {
+ RemoveObject(ret_children[i], scenegraph, true);
+ }
+
+ EntityDescriptionList desc_list;
+ std::string file_type;
+ ActorsEditor_LoadEntitiesFromFile(prefab->prefab_path, desc_list, &file_type);
+
+ if( file_type != "prefab" ) {
+ return false;
+ }
+
+ ActorsEditor_AddEntitiesIntoPrefab(prefab, scenegraph, desc_list);
+
+ return true;
+}
+
+bool MapEditor::CheckForSelections( const LineSegment& mouseray ) {
+ bool something_happened = false;
+ const Keyboard& keyboard = Input::Instance()->getKeyboard();
+ Mouse* mouse = &(Input::Instance()->getMouse());
+ if (!box_selector_.acting && state_ == MapEditor::kIdle) {
+ if (mouse->mouse_down_[Mouse::LEFT] && (mouse->mouse_down_[Mouse::RIGHT] || KeyCommand::CheckDown(keyboard, KeyCommand::kBoxSelect, KIMF_LEVEL_EDITOR_GENERAL))) {
+ // Start box select
+ box_selector_.acting = true;
+ state_ = MapEditor::kBoxSelectDrag;
+ if (ActiveCameras::Get()->m_camera_object != NULL) {
+ ActiveCameras::Get()->m_camera_object->IgnoreMouseInput(true);
+ }
+ Mouse* mouse = &(Input::Instance()->getMouse());
+ box_selector_.points[0][0] = (float)mouse->pos_[0];
+ box_selector_.points[0][1] = (float)(Graphics::Instance()->window_dims[1] - mouse->pos_[1]);
+ box_selector_.points[1][0] = box_selector_.points[0][0];
+ box_selector_.points[1][1] = box_selector_.points[0][1];
+ something_happened = true;
+ }
+ } else if (box_selector_.acting) {
+ if (mouse->mouse_down_[Mouse::LEFT] && (mouse->mouse_down_[Mouse::RIGHT] || KeyCommand::CheckDown(keyboard, KeyCommand::kBoxSelect, KIMF_LEVEL_EDITOR_GENERAL))) {
+ // Update box select
+ Mouse* mouse = &(Input::Instance()->getMouse());
+ box_selector_.points[1][0] = (float)mouse->pos_[0];
+ box_selector_.points[1][1] = (float)(Graphics::Instance()->window_dims[1] - mouse->pos_[1]);
+ something_happened = true;
+ }
+ else {
+ // End box select
+ const Keyboard& keyboard = Input::Instance()->getKeyboard();
+ bool holding_shift = KeyCommand::CheckDown(keyboard, KeyCommand::kAddToSelection, KIMF_LEVEL_EDITOR_GENERAL);
+ if(!holding_shift) {
+ DeselectAll(scenegraph_);
+ }
+ // Make the selections
+ Camera* cam = ActiveCameras::Get();
+ if(box_selector_.points[0][0] > box_selector_.points[1][0]){
+ std::swap(box_selector_.points[0][0],box_selector_.points[1][0]);
+ }
+ if(box_selector_.points[0][1] < box_selector_.points[1][1]){
+ std::swap(box_selector_.points[0][1],box_selector_.points[1][1]);
+ }
+ vec4 p1 = cam->UnProjectPixel((int)box_selector_.points[0][0], (int)box_selector_.points[0][1]);
+ vec4 p2 = cam->UnProjectPixel((int)box_selector_.points[1][0], (int)box_selector_.points[0][1]);
+ vec4 p3 = cam->UnProjectPixel((int)box_selector_.points[1][0], (int)box_selector_.points[1][1]);
+ vec4 p4 = cam->UnProjectPixel((int)box_selector_.points[0][0], (int)box_selector_.points[1][1]);
+ vec4 d1 = p2-p1;
+ vec4 d2 = p3-p2;
+ vec4 d3 = p4-p3;
+ vec4 d4 = p1-p4;
+ if(length(d1) != 0 && length(d2) != 0 && length(d3) != 0 && length(d4) != 0) {
+ vec4 r1 = cam->GetRayThroughPixel((int)box_selector_.points[0][0], (int)box_selector_.points[0][1]);
+ vec4 r2 = cam->GetRayThroughPixel((int)box_selector_.points[1][0], (int)box_selector_.points[1][1]);
+ vec4 n1 = cross(r1,d1);
+ vec4 n2 = cross(r2,d2);
+ vec4 n3 = cross(r2,d3);
+ vec4 n4 = cross(r1,d4);
+ BoxSelectEntities(scenegraph_, type_enable_, mouseray, p1, p3, r1, r2, n1, n2, n3, n4, holding_shift);
+ }
+ if (ActiveCameras::Get()->m_camera_object != NULL) {
+ ActiveCameras::Get()->m_camera_object->IgnoreMouseInput(false);
+ }
+ box_selector_.acting = false;
+ state_ = MapEditor::kIdle;
+ something_happened = true;
+ }
+ }
+ if(KeyCommand::CheckPressed(keyboard, KeyCommand::kDeselectAll, KIMF_LEVEL_EDITOR_GENERAL)) {
+ DeselectAll(scenegraph_);
+ something_happened = true;
+ }
+ if(KeyCommand::CheckPressed(keyboard, KeyCommand::kSelectSimilar, KIMF_LEVEL_EDITOR_GENERAL)) {
+ std::vector<std::string> selected_string;
+ for (size_t i=0, len=scenegraph_->objects_.size(); i<len; ++i) {
+ Object* obj = scenegraph_->objects_[i];
+ if(obj->GetType() == _group || obj->GetType() == _prefab){
+ CalculateGroupString(obj, obj->obj_file);
+ }
+ }
+ for (size_t i=0, len=scenegraph_->objects_.size(); i<len; ++i) {
+ Object* obj = scenegraph_->objects_[i];
+ if(obj->Selected()){
+ selected_string.push_back(obj->obj_file);
+ }
+ }
+ for (size_t i=0, len=scenegraph_->objects_.size(); i<len; ++i) {
+ Object* obj = scenegraph_->objects_[i];
+ if(!obj->Selected() && obj->permission_flags & Object::CAN_SELECT){
+ const std::string& to_string = obj->obj_file;
+ for (size_t j = 0; j < selected_string.size(); j++) {
+ if (to_string == selected_string[j] ){
+ obj->Select(true);
+ break;
+ }
+ }
+ }
+ }
+ something_happened = true;
+ }
+ if(KeyCommand::CheckPressed(keyboard, KeyCommand::kSelectAll, KIMF_LEVEL_EDITOR_GENERAL)) {
+ SelectAll();
+ something_happened = true;
+ }
+ // handle double-click select
+ if(mouse->mouse_double_click_[Mouse::LEFT] && KeyCommand::CheckDown(keyboard, KeyCommand::kAddToSelection, KIMF_LEVEL_EDITOR_GENERAL)) {
+ Collision c = GetSelectableInLineSegment(scenegraph_, mouseray, type_enable_);
+ if (c.hit){
+ c.hit_what->Select(!c.hit_what->Selected());
+ if(c.hit_what->Selected()){
+ DrawObjInfo(c.hit_what);
+ }
+ }
+ something_happened = true;
+ } else if(mouse->mouse_double_click_[Mouse::LEFT]) {
+ DeselectAll(scenegraph_);
+ Collision c = GetSelectableInLineSegment(scenegraph_, mouseray, type_enable_);
+ if (c.hit) {
+ c.hit_what->Select(true);
+ DrawObjInfo(c.hit_what);
+ }
+ something_happened = true;
+ }
+ // handle group scroll select
+ something_happened = something_happened || HandleScrollSelect(mouseray.start, mouseray.end);
+ return something_happened;
+}
+
+void MapEditor::DeselectAll(SceneGraph *scenegraph) {
+ for(size_t i=0, len=scenegraph->objects_.size(); i<len; ++i) {
+ scenegraph->objects_[i]->Select(false);
+ }
+}
+
+static float matrix_distance_squared(const mat4 &a, const mat4 &b){
+ float total = 0.0f;
+ for(unsigned i=0; i<16; i++){
+ total += square(a.entries[i] - b.entries[i]);
+ }
+ return total;
+}
+
+static float ClosestMatch(const mat4 &matrix, const std::vector<mat4> &matrices) {
+ float closest_distance = 99999.0f;
+ int closest = -1;
+ float test_distance;
+ for(unsigned i=0; i<matrices.size(); ++i){
+ test_distance = matrix_distance_squared(matrix, matrices[i]);
+ if(closest==-1 || test_distance < closest_distance) {
+ closest_distance = test_distance;
+ closest = i;
+ }
+ }
+ return closest_distance;
+}
+
+bool IDCompare(const Object *a, const Object *b) {
+ return a->GetID() < b->GetID();
+}
+
+void MapEditor::SaveEntities(TiXmlNode* root) {
+ sky_editor_->SaveSky(root);
+
+ TiXmlElement* objects = new TiXmlElement("ActorObjects");
+ root->LinkEndChild(objects);
+ std::vector<mat4> matrices;
+ const float _difference_threshold = 0.000001f;
+ SceneGraph::object_list ordered_objects = scenegraph_->objects_;
+ std::sort(ordered_objects.begin(), ordered_objects.end(), IDCompare);
+ for (SceneGraph::object_list::iterator iter = ordered_objects.begin();
+ iter != ordered_objects.end();
+ ++iter)
+ {
+ Object* obj = *iter;
+ // Only save object if it has no parent and is not an exact duplicate of another object at same transform
+ if(!obj->parent && !obj->exclude_from_save) {
+ if( ClosestMatch(obj->GetTransform(), matrices) <= _difference_threshold )
+ {
+ LOGW << "The saved " << obj << " is placed exactly on top of another object, is this intentional? I'm still saving the object." << std::endl;
+ }
+
+ obj->SaveToXML(objects);
+ matrices.push_back(obj->GetTransform());
+ LOGD << "Saved object " << obj << " to level xml file" << std::endl;
+ }
+ else
+ {
+ if( obj->exclude_from_save )
+ {
+ LOGW << "Skipping save of object " << obj << " because it's explicitly excluded from saving." << std::endl;
+ }
+ else
+ {
+ LOGW << "Skipping save of objects " << obj << " into level because it has a parent object." << std::endl;
+ }
+ }
+
+ if(obj->GetType() == _reflection_capture_object){
+ char save_path[kPathSize];
+ if(scenegraph_->level_path_.source != kAbsPath) {
+ if(config["allow_game_dir_save"].toBool()) {
+ FormatString(save_path, kPathSize, "%s_refl_cap_%d", scenegraph_->level_path_.GetOriginalPath(), obj->GetID());
+ } else {
+ FormatString(save_path, kPathSize, "%s%s_refl_cap_%d", GetWritePath(scenegraph_->level_path_.mod_source).c_str(), scenegraph_->level_path_.GetOriginalPath(), obj->GetID());
+ }
+ } else {
+ FormatString(save_path, kPathSize, "%s_refl_cap_%d", scenegraph_->level_path_.GetOriginalPath(), obj->GetID());
+ }
+ CreateParentDirs(save_path);
+ ReflectionCaptureObject* rco = (ReflectionCaptureObject*)obj;
+ if(rco->cube_map_ref.valid()){
+ Textures::SaveCubeMapMipmapsHDR(rco->cube_map_ref, save_path, Textures::Instance()->getWidth(rco->cube_map_ref));
+ }
+ }
+ }
+}
+
+void MapEditor::SetUpSky(const SkyInfo &si) {
+ sky_editor_->ApplySkyInfo(si);
+}
+
+int MapEditor::LoadEntitiesFromFile(const std::string& filename) {
+ add_desc_list_.clear();
+ add_desc_list_source_ = Path();
+
+ std::string file_type;
+ Path source;
+ if(ActorsEditor_LoadEntitiesFromFile(filename, add_desc_list_, &file_type, &source) == -1){
+ return -1;
+ }
+ add_desc_list_source_ = source;
+
+ if(file_type == "prefab") {
+ create_as_prefab_ = true;
+ } else {
+ create_as_prefab_ = false;
+ }
+
+ //Files saved with new LocalizeIDs function will have IDs in the range [0, <itemcount>)
+ //But old files are [X, Y], so this is needed
+ LocalizeIDs(&add_desc_list_, false);
+ //for(EntityDescriptionList::iterator iter=add_desc_list_.begin(); iter!=add_desc_list_.end(); ++iter){
+ // EntityDescription* desc = &(*iter);
+ // ClearDescID(desc);
+ //}
+ return 0;
+}
+
+void MapEditor::SavePrefab(bool do_overwrite) {
+ char buf[kPathSize];
+ std::vector<Object*> selected;
+
+ scenegraph_->ReturnSelected(&selected);
+ if( selected.size() > 1 ) {
+ int err = PrefabSelected();
+ if( err == 0 ) {
+ SavePrefab(false);
+ } else if( err == 1 ) {
+ ShowEditorMessage(3, "Selected object is already a prefab.");
+ } else if( err == 2 ) {
+ ShowEditorMessage(3, "Selected objects contain a prefab, prefabs can't contain prefabs.");
+ } else if( err == 3 ) {
+ ShowEditorMessage(3, "Selected group object has a parent, orphan it first.");
+ } else if( err == 4 ) {
+ ShowEditorMessage(3, "All selected objects already have a parent.");
+ } else if( err == 5 ) {
+ ShowEditorMessage(3, "Unknown error");
+ } else if( err == 6 ) {
+ ShowEditorMessage(3, "Error adding prefab to scenegraph");
+ }
+ //Always return, because we re-call if we do ok_
+ return;
+ }
+
+ if( selected.size() == 0 ) {
+ ShowEditorMessage(3, "Nothing selected");
+ return;
+ }
+
+ Object* object = selected[0];
+
+ if( object->GetType() != _prefab ) {
+ int err = PrefabSelected();
+ if( err == 0 ) {
+ SavePrefab(false);
+ } else if( err == 1 ) {
+ ShowEditorMessage(3, "Selected object is already a prefab."); // will not happen given context
+ } else if( err == 2 ) {
+ ShowEditorMessage(3, "Selected objects contain a prefab, prefabs can't contain prefabs.");
+ } else if( err == 3 ) {
+ ShowEditorMessage(3, "Selected group object has a parent, orphan it first.");
+ } else if( err == 4 ) {
+ ShowEditorMessage(3, "All selected objects already have a valid parent.");
+ } else if( err == 5 ) {
+ ShowEditorMessage(3, "Unknown error");
+ } else if( err == 6 ) {
+ ShowEditorMessage(3, "Error adding prefab to scenegraph");
+ }
+ return;
+ }
+ Prefab* group = static_cast<Prefab*>(object);
+
+ if(group->GetRotation() != quaternion()) {
+ ShowEditorMessage(3, "Can't save prefab that's rotated, this is a current limitation to prevent confusion. Go into Selected window and reset rotation to enable saving.");
+ return;
+ }
+
+ Dialog::DialogErr err = Dialog::NO_ERR;
+ if( FileExists(group->prefab_path) && do_overwrite ) {
+ strscpy(buf,group->prefab_path.GetFullPath(),kPathSize);
+ } else {
+ err = Dialog::writeFile("xml",1,"Data/Objects",buf,kPathSize);
+ }
+ if(!err){
+ TiXmlDocument doc(buf);
+ // write xml declaration header
+ TiXmlDeclaration* decl = new TiXmlDeclaration( "2.0", "", "" );
+ doc.LinkEndChild( decl );
+ // write file type
+ TiXmlElement* version = new TiXmlElement("Type");
+ doc.LinkEndChild(version);
+ version->LinkEndChild(new TiXmlText("prefab"));
+ // write out entities
+ TiXmlElement* parent = new TiXmlElement("Prefab");
+ doc.LinkEndChild(parent);
+
+ EntityDescriptionList descriptions;
+ for( unsigned i = 0; i < group->children.size(); i++) {
+ const ScriptParamMap &script_param_map = group->children[i].direct_ptr->GetScriptParams()->GetParameterMap();
+ const ScriptParamMap::const_iterator it = script_param_map.find("No Save");
+ if(it != script_param_map.end()){
+ const ScriptParam &param = it->second;
+ if(param.GetInt() == 1){
+ continue;
+ }
+ }
+
+ EntityDescription desc;
+ group->children[i].direct_ptr->GetDesc(desc);
+ descriptions.push_back(desc);
+ }
+ LocalizeIDs(&descriptions, false);
+
+ for( unsigned i = 0; i < descriptions.size(); i++ ) {
+ descriptions[i].SaveToXML(parent);
+ //group->children[i].direct_ptr->SaveToXML(parent);
+ }
+ doc.SaveFile(buf);
+
+ group->prefab_path = FindShortestPath2(buf);
+ group->original_scale_ = group->GetScale(); //Reset original_scale_, as the original_scale_ is used to figure out relative scale to prefab link.
+ group->prefab_locked = true;
+
+ ReloadPrefabs(group->prefab_path);
+ }
+}
+
+void MapEditor::SaveSelected() {
+ const int BUF_SIZE = 512;
+ char buf[BUF_SIZE];
+ Dialog::DialogErr err = Dialog::writeFile("xml",1,"Data/Objects",buf,BUF_SIZE);
+ if(!err){
+ TiXmlDocument doc(buf);
+ // write xml declaration header
+ TiXmlDeclaration* decl = new TiXmlDeclaration( "2.0", "", "" );
+ doc.LinkEndChild( decl );
+ // write file type
+ TiXmlElement* version = new TiXmlElement("Type");
+ doc.LinkEndChild(version);
+ version->LinkEndChild(new TiXmlText("saved"));
+ // write out entities
+ TiXmlElement* parent = new TiXmlElement("ActorObjects");
+ doc.LinkEndChild(parent);
+ for(size_t i=0, len=scenegraph_->objects_.size(); i<len; ++i) {
+ Object* obj = scenegraph_->objects_[i];
+ if (obj->Selected()) {
+ obj->SaveToXML(parent);
+ }
+ }
+ doc.SaveFile(buf);
+ }
+}
+
+bool MapEditor::CanUndo() {
+ if (state_ != MapEditor::kIdle || state_history_.current_state <= state_history_.start_state || Online::Instance()->IsActive())
+ return false;
+ return true;
+}
+
+namespace {
+ typedef std::map<int, SavedChunk*> Time_ChunkMap;
+ typedef std::map<int, Time_ChunkMap> ID_Time_ChunkMap;
+ typedef std::map<ChunkType::ChunkType, ID_Time_ChunkMap> Type_ID_Time_ChunkMap;
+
+ /* Disabled because unused.
+ *
+ void GetObjectsInState(const Type_ID_Time_ChunkMap &save_states, int state, std::vector<SavedChunk*> &vec){
+ for(Type_ID_Time_ChunkMap::const_iterator iter = save_states.begin();
+ iter != save_states.end(); ++iter)
+ {
+ const ID_Time_ChunkMap &id_time = iter->second;
+ for(ID_Time_ChunkMap::const_iterator iter = id_time.begin();
+ iter != id_time.end(); ++iter)
+ {
+ const Time_ChunkMap &time_chunk = iter->second;
+ Time_ChunkMap::const_iterator iter2 = time_chunk.find(state);
+ if(iter2 != time_chunk.end()){
+ vec.push_back(iter2->second);
+ }
+ }
+ }
+ }
+ */
+}
+
+void MapEditor::BringAllToCurrentState(int old_state){
+ // Create map for easy saved-chunk lookup
+ Type_ID_Time_ChunkMap save_states;
+ // Loop through saved chunks to populate lookup map
+ for(std::list<SavedChunk>::iterator iter = state_history_.chunks.begin(); iter != state_history_.chunks.end(); ++iter) {
+ SavedChunk& chunk = (*iter);
+ save_states[chunk.type][chunk.obj_id][chunk.state_id] = &chunk;
+ }
+
+ // Remove all objects with editors
+ std::vector<int> selected_ids;
+ int index = 0;
+ while(index < (int)scenegraph_->objects_.size()){
+ Object* obj = scenegraph_->objects_[index];
+ if(obj->Selected()){
+ selected_ids.push_back(obj->GetID());
+ }
+ if(!obj->exclude_from_undo){
+ RemoveObject(scenegraph_->objects_[index], scenegraph_, true);
+ } else {
+ ++index;
+ }
+ }
+ // Loop through each chunk and set each object to the most appropriate saved chunk
+ for(Type_ID_Time_ChunkMap::iterator iter = save_states.begin(); iter != save_states.end(); ++iter) {
+ const ChunkType::ChunkType &type = iter->first;
+ ID_Time_ChunkMap &submap = iter->second;
+ for(ID_Time_ChunkMap::iterator iter = submap.begin(); iter != submap.end(); ++iter) {
+ SavedChunk* the_chunk = NULL;
+ Time_ChunkMap &timeline = iter->second;
+ // Find the last entry that is before the current time
+ for(Time_ChunkMap::iterator iter2 = timeline.begin(); iter2 != timeline.end(); ++iter2) {
+ const int &time = iter2->first;
+ SavedChunk *chunk = iter2->second;
+ if(time == state_history_.current_state) {
+ the_chunk = chunk;
+ }
+ }
+ // Apply that entry to the appropriate object or editor
+ if(the_chunk) {
+ switch(type){
+ case ChunkType::SKY_EDITOR:
+ if(sky_editor_->ReadChunk(*the_chunk)){
+ scenegraph_->sky->LightingChanged(scenegraph_->terrain_object_ != NULL);
+ }
+ break;
+ case ChunkType::LEVEL:
+ scenegraph_->level->ReadChunk(*the_chunk);
+ break;
+ default:
+ Object* o = CreateObjectFromDesc(the_chunk->desc);
+ if(o){
+ if( ActorsEditor_AddEntity(scenegraph_, o, NULL, false, true) ) {
+
+ } else {
+ LOGE << "Failed at adding object to scenegraph" << std::endl;
+ }
+ } else {
+ LOGE << "Failed at constructing object" << std::endl;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ for(size_t i=0, len=scenegraph_->objects_.size(); i<len; ++i){
+ Object* obj = scenegraph_->objects_[i];
+ static const int kBufSize = 256;
+ char msg[kBufSize];
+ FormatString(msg, kBufSize, "added_object %d", obj->GetID());
+ scenegraph_->level->Message(msg);
+ }
+
+ if( g_no_reflection_capture == false ) {
+ scenegraph_->LoadReflectionCaptureCubemaps();
+ }
+
+ scenegraph_->SendMessageToAllObjects(OBJECT_MSG::FINALIZE_LOADED_CONNECTIONS);
+
+ // Restore selection
+ for(size_t i=0, len=selected_ids.size(); i<len; ++i){
+ Object* obj = scenegraph_->GetObjectFromID(selected_ids[i]);
+ if(obj){
+ obj->Select(true);
+ }
+ }
+}
+
+void MapEditor::Undo() {
+ if(terrain_preview_mode) {
+ DisplayMessage("Cannot undo", "It is not possible to undo while previewing terrain");
+ return;
+ }
+ RibbonFlash(gui, "undo");
+ if(!CanUndo()) {
+ return;
+ }
+
+ int old_state = state_history_.current_state;
+ state_history_.current_state--;
+ BringAllToCurrentState(old_state);
+ scenegraph_->UpdatePhysicsTransforms();
+ Graphics *graphics = Graphics::Instance();
+ if(state_history_.current_state <= graphics->nav_mesh_out_of_date_chunk){
+ graphics->nav_mesh_out_of_date = false;
+ }
+}
+
+bool MapEditor::CanRedo() {
+ if (state_ != MapEditor::kIdle || state_history_.current_state >= state_history_.num_states-1 || Online::Instance()->IsActive())
+ return false;
+ return true;
+}
+
+void MapEditor::Redo() {
+ if(terrain_preview_mode) {
+ DisplayMessage("Cannot redo", "It is not possible to redo while previewing terrain");
+ return;
+ }
+ RibbonFlash(gui, "redo");
+ if(CanRedo()) {
+
+ int old_state = state_history_.current_state;
+ state_history_.current_state++;
+ BringAllToCurrentState(old_state);
+ scenegraph_->UpdatePhysicsTransforms();
+ }
+}
+
+void MapEditor::SetViewNavMesh( bool enabled ) {
+ scenegraph_->SetNavMeshVisible(enabled);
+}
+
+void MapEditor::SetViewCollisionNavMesh(bool enabled){
+ scenegraph_->SetCollisionNavMeshVisible(enabled);
+}
+
+bool MapEditor::IsViewingNavMesh() {
+ return scenegraph_->IsNavMeshVisible();
+}
+
+bool MapEditor::IsViewingCollisionNavMesh() {
+ return scenegraph_->IsCollisionNavMeshVisible();
+}
+
+
+void MapEditor::AddLightProbes() {
+ std::vector<Object*> selected;
+ ReturnSelected(&selected);
+ if (selected.size() == 1 && selected[0]->GetType() == _placeholder_object) {
+ // Add probes to selected area
+ vec3 scale = selected[0]->box_.dims * selected[0]->GetScale() * 0.5f;
+ vec3 translation = selected[0]->GetTranslation();
+ quaternion rotation = selected[0]->GetRotation();
+ PlaceLightProbes(scenegraph_, translation, rotation, scale);
+ } else {
+ // TODO: Remove existing probes
+ /*
+ if (!scenegraph_->light_probe_collection.light_probes.empty()) {
+ for (int i = 0, len = scenegraph_->objects_.size(); i < len; ++i) {
+ if (dynamic_cast<LightProbeObject*>(scenegraph_->objects_[i]) != NULL) {
+ RemoveObject(scenegraph_->objects_[i], scenegraph_);
+ }
+ }
+ }
+ */
+ // Cover entire level with probes
+ vec3 minval = vec3(FLT_MAX, FLT_MAX, FLT_MAX);
+ vec3 maxval = vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
+ for (size_t i = 0, len = scenegraph_->visible_objects_.size(); i < len; ++i) {
+ vec3 objtrans = scenegraph_->visible_objects_[i]->GetTranslation();
+ maxval.x() = max(maxval.x(), objtrans.x());
+ minval.x() = min(minval.x(), objtrans.x());
+ maxval.y() = max(maxval.y(), objtrans.y());
+ minval.y() = min(minval.y(), objtrans.y());
+ maxval.z() = max(maxval.z(), objtrans.z());
+ minval.z() = min(minval.z(), objtrans.z());
+ }
+
+ vec3 translation = (maxval + minval) / 2.0f;
+ quaternion rotation = quaternion();
+ vec3 scale = 0.6f * (maxval - minval); // Some margin on edges
+ PlaceLightProbes(scenegraph_, translation, rotation, scale);
+ }
+}
+
+void MapEditor::BakeLightProbes(int pass) {
+ if (!scenegraph_->light_probe_collection.light_probes.empty()) {
+ LightProbeCollection& light_probes = scenegraph_->light_probe_collection;
+ for (size_t i = 0, len = light_probes.light_probes.size(); i < len; ++i){
+ LightProbeUpdateEntry entry;
+ entry.id = light_probes.light_probes[i].id;
+ entry.pass = pass;
+ light_probes.to_process.push(entry);
+ }
+ if (pass == 1) {
+ kLightProbe2pass = true;
+ }
+ }
+}
+
+void MapEditor::ToggleImposter() {
+ SendMessageToSelectedObjects(scenegraph_, OBJECT_MSG::TOGGLE_IMPOSTER);
+}
+
+void MapEditor::SetImposter(bool set) {
+ ReturnSelected(&selected);
+ for(unsigned i=0; i<selected.size(); ++i){
+ selected[i]->SetImposter(set);
+ }
+}
+
+Object* MapEditor::AddEntityFromDesc( SceneGraph* scenegraph, const EntityDescription& desc, bool level_loading ) {
+ if(desc.fields[0].type != EDF_ENTITY_TYPE){
+ DisplayError("Error", "No type info given to MapEditor::AddEntityFromDesc");
+ return NULL;
+ }
+ Object* o = CreateObjectFromDesc(desc);
+ if(o){
+ if( ActorsEditor_AddEntity(scenegraph, o, NULL, false, level_loading) ) {
+ return o;
+ } else {
+ LOGE << "Failed adding entity from desc to scenegraph" << std::endl;
+ delete o;
+ return NULL;
+ }
+ } else {
+ return NULL;
+ }
+}
+
+void MapEditor::ApplyPalette( const OGPalette &palette, int edited_id ) {
+ Object* obj = scenegraph_->GetObjectFromID(edited_id);
+ if(!obj || obj->GetType() != _movement_object){
+ return;
+ }
+ obj->ApplyPalette(palette);
+}
+
+void MapEditor::ReceiveMessage( const std::string &msg ) {
+ if(msg == "DisplaySettings"){
+ } else if(msg == "carve_against_terrain"){
+ }
+}
+
+void MapEditor::LoadDialogueFile(const std::string &path) {
+ if(state_ == MapEditor::kIdle && LoadEntitiesFromFile("Data/Objects/placeholder/empty_placeholder.xml")==0){
+ ScriptParams sp;
+ sp.ASAddString("Dialogue", path); // "Dialogue" is used in dimgui.cpp, change it there as well if changed here
+ add_desc_list_[0].AddScriptParams(EDF_SCRIPT_PARAMS, sp.GetParameterMap());
+ SetTool(EditorTypes::ADD_ONCE);
+ }
+}
+
+//Check if we've performed an action that warrants a new save (meaning action that effects the undo stack).
+bool MapEditor::WasLastSaveOnCurrentUndoChunk()
+{
+ std::list<SavedChunk>::iterator chunkit = state_history_.GetCurrentChunk();
+
+ if( chunkit != state_history_.chunks.end() )
+ {
+ return chunkit->GetGlobalID() == last_saved_chunk_global_id;
+ }
+ else
+ {
+ //We assume that no chunks means we haven't opened the editor because that will create a base undo object.
+ return true;
+ }
+}
+
+void MapEditor::SetLastSaveOnCurrentUndoChunk()
+{
+ LOGI << "Reseting undo block value" << std::endl;
+ //Remember what undo position we last saved in
+ std::list<SavedChunk>::iterator chunkit = state_history_.GetCurrentChunk();
+ if( chunkit != state_history_.chunks.end() ) {
+ last_saved_chunk_global_id = chunkit->GetGlobalID();
+ LOGI << "Reseting undo block value to " << chunkit->GetGlobalID() << std::endl;
+ } else {
+ last_saved_chunk_global_id = 0; //Counter starts as 1, so 0 will always mismatch.
+ }
+}
+
+void MapEditor::SaveLevel(LevelLoader::SaveLevelType type) {
+ if(terrain_preview_mode) {
+ DisplayMessage("Cannot save level", "It is not possible to save while previewing terrain");
+ return;
+ }
+
+ scenegraph_->level->Message("save_selected_dialogue");
+
+ LevelLoader::SaveLevel(*scenegraph_, type);
+
+ SetLastSaveOnCurrentUndoChunk();
+
+ scenegraph_->level->setMetaDataClean();
+
+ if(scenegraph_->level_path_.isValid() && scenegraph_->level_has_been_previously_saved_) {
+ Engine::Instance()->AddLevelPathToRecentLevels(scenegraph_->level_path_);
+ }
+}
+
+int MapEditor::CreateObject( const std::string& path) {
+ int id = -1;
+ EntityDescriptionList desc_list;
+ std::string file_type;
+ Path source;
+ ActorsEditor_LoadEntitiesFromFile(path, desc_list, &file_type,&source);
+ for(unsigned i=0; i<desc_list.size(); ++i){
+ Object* obj = AddEntityFromDesc(scenegraph_, desc_list[i], false);
+ if(obj){
+ id = obj->GetID();
+ } else {
+ LOGE << "Failed at creating part of object " << path << std::endl;
+ }
+ }
+ QueueSaveHistoryState();
+ return id;
+}
+
+void MapEditor::ReturnSelected(std::vector<Object*> *selected_objects) {
+ selected_objects->clear();
+ for (size_t i=0, len=scenegraph_->objects_.size(); i<len; ++i) {
+ if (scenegraph_->objects_[i]->Selected()) {
+ selected_objects->push_back(scenegraph_->objects_[i]);
+ }
+ }
+}
+
+void MapEditor::DeleteID(int val) {
+ LOGD << "Deleting id: " << val << std::endl;
+ Object* obj = scenegraph_->GetObjectFromID(val);
+ Camera* active_camera = ActiveCameras::Get();
+ if(obj && obj->GetType() == _camera_type) {
+ LOGE << "Tried deleting camera object" << std::endl;
+ } else if(obj) {
+ RemoveObject(obj, scenegraph_, true);
+ }
+}
+
+bool MapEditor::IsTypeEnabled( EntityType type ) {
+ return type_enable_.IsTypeEnabled(type);
+}
+
+void MapEditor::SetTypeEnabled( EntityType type, bool enabled ) {
+ type_enable_.SetTypeEnabled(type, enabled);
+ for (size_t i=0, len=scenegraph_->objects_.size(); i<len; ++i) {
+ Object* obj = scenegraph_->objects_[i];
+ if(!type_enable_.IsTypeEnabled(obj->GetType())){
+ obj->Select(false);
+ }
+ }
+}
+
+bool MapEditor::IsTypeVisible( EntityType type ) {
+ return type_visible_.IsTypeEnabled(type);
+}
+
+void MapEditor::SetTypeVisible( EntityType type, bool visible ) {
+ type_visible_.SetTypeEnabled(type, visible);
+ for (size_t i=0, len=scenegraph_->objects_.size(); i<len; ++i) {
+ Object* obj = scenegraph_->objects_[i];
+ obj->editor_visible = type_visible_.IsTypeEnabled(obj->GetType());
+ }
+}
+
+int MapEditor::DuplicateObject( const Object* obj ) {
+ EntityDescription desc;
+ obj->GetDesc(desc);
+ ClearDescID(&desc);
+ Object* new_obj = AddEntityFromDesc(scenegraph_, desc, false);
+ int id = -1;
+ if(new_obj){
+ id = new_obj->GetID();
+ } else {
+ LOGE << "Failed at creating a duplicate" << std::endl;
+ }
+ QueueSaveHistoryState();
+ return id;
+}
+
+Object* MapEditor::GetSelectedCameraObject() {
+ Object* selected = NULL;
+ bool multiple = false;
+ // Check for any camera preview objects
+ for(SceneGraph::object_list::iterator iter = scenegraph_->objects_.begin();
+ iter != scenegraph_->objects_.end();
+ ++iter)
+ {
+ Object* obj = *iter;
+ if(obj->GetType() == _placeholder_object){
+ PlaceholderObject* po = (PlaceholderObject*)obj;
+ if(po->GetSpecialType() == PlaceholderObject::kCamPreview){
+ if(selected == NULL){
+ selected = (Object*)po;
+ } else {
+ multiple = true;
+ }
+ }
+ }
+ }
+ // If multiple camera preview objects are found, then check for any that are selected
+ if(multiple){
+ for(SceneGraph::object_list::iterator iter = scenegraph_->objects_.begin();
+ iter != scenegraph_->objects_.end();
+ ++iter)
+ {
+ Object* obj = *iter;
+ if(obj->GetType() == _placeholder_object){
+ PlaceholderObject* po = (PlaceholderObject*)obj;
+ if(po->GetSpecialType() == PlaceholderObject::kCamPreview && po->Selected()){
+ selected = (Object*)po;
+ }
+ }
+ }
+ }
+ return selected;
+}
+
+void MapEditor::ClearUndoHistory() {
+ state_history_.clear();
+ QueueSaveHistoryState(); // Save state to provide a base state to undo to
+}
+
+void MapEditor::UpdateGPUSkinning() {
+ scenegraph_->SendMessageToAllObjects(OBJECT_MSG::UPDATE_GPU_SKINNING);
+}
+
+void MapEditor::CarveAgainstTerrain() {
+ for(std::vector<EnvObject*>::iterator iter = scenegraph_->visible_static_meshes_.begin(); iter != scenegraph_->visible_static_meshes_.end(); ++iter){
+ EnvObject* eo = *iter;
+ if(eo->Selected()){
+ CSGResultCombined results;
+ if(CollideObjects(*scenegraph_->bullet_world_,
+ *scenegraph_->terrain_object_->GetModel(),
+ scenegraph_->terrain_object_->GetTransform(),
+ *eo->GetModel(),
+ eo->GetTransform(),
+ &results))
+ {
+ Model* model = &Models::Instance()->GetModel(eo->model_id_);
+ float old_model_texel_density = model->texel_density;
+ CSGModelInfo model_info;
+ AddCSGResult(results.result[1][1], &model_info, *model, false);
+ AddCSGResult(results.result[0][0], &model_info, *scenegraph_->terrain_object_->GetModel(), true);
+ // Transform from world space back to model space
+ mat4 inv_transform = invert(eo->GetTransform());
+ for(size_t i=0, len=model_info.verts.size(); i<len; i+=3){
+ MultiplyMat4Vec3D(inv_transform, &model_info.verts[i]);
+ }
+ if(!CheckShapeValid(model_info.faces, model_info.verts)){
+ LOGE << "Model is invalid" << std::endl;
+ } else {
+ eo->model_id_ = ModelFromCSGModelInfo(model_info);
+ model = &Models::Instance()->GetModel(eo->model_id_);
+ model->texel_density = old_model_texel_density;
+ eo->SetCSGModified();
+ }
+ }
+ }
+ }
+}
+
+void MapEditor::ExecuteSaveLevelChanges() {
+ if(terrain_preview_mode) {
+ DisplayMessage("Mesh preview will not be saved", "Terrain preview mode is enabled, the previewed terrain will not be saved and the old one will be restored");
+ if(terrain_preview) {
+ scenegraph_->UnlinkObject(terrain_preview);
+ delete terrain_preview;
+ terrain_preview = NULL;
+ } else {
+ TerrainObject* terrain = scenegraph_->terrain_object_;
+ terrain->SetTerrainColorTexture(real_terrain_info.colormap.c_str());
+ terrain->SetTerrainWeightTexture(real_terrain_info.weightmap.c_str());
+ terrain->SetTerrainDetailTextures(real_terrain_info.detail_map_info);
+ }
+ ExitTerrainPreviewMode();
+ }
+
+ SaveLevel(LevelLoader::kSaveInPlace);
+}
+
+static void GetCamBasis(Basis *basis) {
+ Camera* cam = ActiveCameras::Get();
+ basis->up = cam->GetUpVector();
+ basis->facing = cam->GetFacing();
+ basis->right = normalize(cross(basis->facing, basis->up));
+}
+
+static ToolMode DetermineToolMode(EditorTypes::Tool type) {
+ const Mouse& mouse = Input::Instance()->getMouse();
+ const Keyboard& keyboard = Input::Instance()->getKeyboard();
+
+ switch(type){
+ case EditorTypes::ROTATE:
+ if (mouse.mouse_down_[Mouse::LEFT]) {
+ return ROTATE_SPHERE;
+ } else {
+ return ROTATE_CIRCLE;
+ }
+ case EditorTypes::TRANSLATE:
+ if (mouse.mouse_down_[Mouse::LEFT]) {
+ return TRANSLATE_CAMERA_PLANE;
+ } else if (KeyCommand::CheckDown(keyboard, KeyCommand::kNormalTransform, KIMF_LEVEL_EDITOR_GENERAL)) {
+ return TRANSLATE_FACE_NORMAL;
+ } else {
+ return TRANSLATE_FACE_PLANE;
+ }
+ case EditorTypes::SCALE:
+ if (mouse.mouse_down_[Mouse::LEFT]) {
+ return SCALE_WHOLE;
+ } else if (KeyCommand::CheckDown(keyboard, KeyCommand::kNormalTransform, KIMF_LEVEL_EDITOR_GENERAL)) {
+ return SCALE_NORMAL;
+ } else {
+ return SCALE_PLANE;
+ }
+ default:
+ LOG_ASSERT(false);
+ return TRANSLATE_CAMERA_PLANE;
+ }
+}
+
+
+static bool GetTranslationBasis(vec3 clicked_point, vec3 clicked_normal,
+ Basis* basis, const Object* object_, const Box& box_, ToolMode tool_mode)
+{
+ mat4 obj_transform = object_->GetTransform();
+ quaternion obj_rot = object_->GetRotation();
+ int box_face_index = box_.GetHitFaceIndex(invert(obj_rot) * clicked_normal,
+ invert(obj_transform) * clicked_point);
+ if (box_face_index != -1){
+ switch(tool_mode){
+ case TRANSLATE_FACE_PLANE:
+ basis->up = obj_rot * Box::GetPlaneTangent(box_face_index);
+ basis->facing = obj_rot * Box::GetPlaneNormal(box_face_index);
+ break;
+ case TRANSLATE_FACE_NORMAL:
+ basis->up = obj_rot * Box::GetPlaneTangent(box_face_index);
+ basis->facing = obj_rot * Box::GetPlaneNormal(box_face_index);
+ break;
+ default:
+ return false; // Tool is incorrect
+ }
+ basis->right = obj_rot * Box::GetPlaneBitangent(box_face_index);
+ return true;
+ } else {
+ return false; // Didn't hit any box faces
+ }
+}
+
+// Returns false if no basis selected
+static bool GetScaleBasis(vec3 clicked_point, vec3 clicked_normal,
+ Basis* basis, vec3* p, const Object* object_, const Box& box_, ToolMode tool_mode)
+{
+ mat4 obj_transform = object_->GetTransform();
+ quaternion obj_rot = object_->GetRotation();
+
+ int i = box_.GetHitFaceIndex(invert(obj_rot) * clicked_normal, invert(obj_transform) * clicked_point);
+ if (i == -1) {
+ return false;
+ }
+ basis->up = obj_rot * Box::GetPlaneTangent(i);
+ basis->facing = obj_rot * Box::GetPlaneNormal(i);
+ basis->right = obj_rot * Box::GetPlaneBitangent(i);
+ if (tool_mode == SCALE_NORMAL) {
+ *p = clicked_point;
+ } else {
+ *p = obj_transform * box_.GetPlanePoint(i);
+ }
+ return true;
+}
+
+static bool GetRotationBasis(vec3 clicked_point, vec3 clicked_normal,
+ Basis* basis, vec3* p, vec3* around,
+ const Object* object_, const Box& box_)
+{
+ mat4 obj_transform = object_->GetTransform();
+ quaternion obj_rot = object_->GetRotation();
+ int i = box_.GetHitFaceIndex(invert(obj_rot) * clicked_normal, invert(obj_transform) * clicked_point);
+ if (i == -1) {
+ return false;
+ }
+ basis->up = obj_rot * Box::GetPlaneTangent(i);
+ basis->facing = obj_rot * Box::GetPlaneNormal(i);
+ basis->right = obj_rot * Box::GetPlaneBitangent(i);
+ *around = Box::GetPlaneNormal(i);
+ *p = obj_transform*box_.GetPlanePoint(i);
+ return true;
+}
+
+static vec3 GetTranslation(const LineSegment& mouseray, bool snapping_enabled, const vec3& cam_moved, const ControlEditorInfo* control_editor_info, Tool* tool_, Object* object_) {
+ static const float TRANSLATION_SNAP_INCR = 0.50f; // percent of dimension in direction of movement
+ vec3 new_point;
+ vec3 old_point;
+
+ switch(tool_->mode){
+ case TRANSLATE_FACE_NORMAL:
+ RayLineClosestPoint(mouseray.start,
+ normalize(mouseray.end - mouseray.start),
+ control_editor_info->clicked_point_,
+ control_editor_info->basis_.facing,
+ &new_point);
+ RayLineClosestPoint(control_editor_info->start_mouseray_.start,
+ normalize(control_editor_info->start_mouseray_.end - control_editor_info->start_mouseray_.start),
+ control_editor_info->clicked_point_,
+ control_editor_info->basis_.facing,
+ &old_point);
+ break;
+ case TRANSLATE_FACE_PLANE:
+ case TRANSLATE_CAMERA_PLANE: {
+ vec3 dir = normalize(mouseray.end - mouseray.start);
+ float to_dist = RayPlaneIntersection(mouseray.start, dir,
+ control_editor_info->clicked_point_,
+ control_editor_info->basis_.facing);
+ if ( to_dist > 0.0f ) {
+ new_point = to_dist * dir;
+ new_point += mouseray.start;
+ } else {
+ return vec3(0.0f);
+ }
+ dir = normalize(control_editor_info->start_mouseray_.end -
+ control_editor_info->start_mouseray_.start);
+ to_dist = RayPlaneIntersection(control_editor_info->start_mouseray_.start,
+ dir,
+ control_editor_info->clicked_point_,
+ control_editor_info->basis_.facing);
+ if ( to_dist > 0.0f ) {
+ old_point = to_dist * dir;
+ old_point += control_editor_info->start_mouseray_.start;
+ } else {
+ return vec3(0.0f);
+ }
+ }
+ break;
+ default:
+ LOGW << "Unhandled tool_->mode value: " << GetToolModeString(tool_->mode) << std::endl;
+ break;
+ }
+
+ vec3 delta_translation = new_point - old_point;
+ if (snapping_enabled) {
+ quaternion obj_rotate = object_->GetRotation();
+ delta_translation = invert( object_->GetRotation() ) * delta_translation;
+ for(int i=0; i<3; ++i){
+ delta_translation[i] = floorf(delta_translation[i]/TRANSLATION_SNAP_INCR+0.5f) * TRANSLATION_SNAP_INCR;
+ }
+ delta_translation = obj_rotate * delta_translation;
+ }
+ return delta_translation;
+}
+
+static vec3 GetScale(const LineSegment& mouseray, vec3* trans, bool snapping_enabled, const vec3& cam_moved,
+ const ControlEditorInfo* control_editor_info, Tool* tool_, Object* object_, const Box& box_)
+{
+ static const float SCALE_SNAP_INCR = 1.50f; // incr whenever new size = 1.25 * old size
+ if(trans){
+ *trans = vec3(0.0f);
+ }
+ vec3 new_clicked_point;
+ vec3 old_clicked_point;
+ vec3 original_click_dir = normalize(control_editor_info->start_mouseray_.end - control_editor_info->start_mouseray_.start);
+ if (tool_->mode == SCALE_NORMAL) {
+ vec3 mouseray_dir = normalize(mouseray.end - mouseray.start);
+ RayLineClosestPoint(mouseray.start, mouseray_dir, tool_->center, -control_editor_info->basis_.facing, &new_clicked_point);
+ RayLineClosestPoint(control_editor_info->start_cam_pos, original_click_dir, tool_->center, -control_editor_info->basis_.facing, &old_clicked_point);
+ } else if (tool_->mode == SCALE_PLANE) {
+ vec3 mouseray_dir = normalize(mouseray.end - mouseray.start);
+ float to_dist = RayPlaneIntersection(mouseray.start, mouseray_dir, tool_->center, -control_editor_info->basis_.facing);
+ if (to_dist > 0.0f) {
+ new_clicked_point = mouseray.start + to_dist * mouseray_dir;
+ } else {
+ LOG_ASSERT(false);
+ return vec3(1.0f);
+ }
+ to_dist = RayPlaneIntersection(control_editor_info->start_cam_pos, original_click_dir, tool_->center, -control_editor_info->basis_.facing);
+ if (to_dist > 0.0f) {
+ old_clicked_point = control_editor_info->start_cam_pos + to_dist * original_click_dir;
+ } else {
+ LOG_ASSERT(false);
+ return vec3(1.0f);
+ }
+ }
+
+ vec3 initial_rel_point;
+ vec3 scale;
+ if (tool_->mode == SCALE_WHOLE) {
+ Camera* cam = ActiveCameras::Get();
+ vec3 cam_facing = cam->GetFacing();
+ vec3 center = tool_->center;
+ vec3 mouseray_dir = normalize(mouseray.end-mouseray.start);
+ float t = RayPlaneIntersection(mouseray.start, mouseray_dir, center, cam_facing);
+ vec3 curr_mouse_on_plane = mouseray.start + mouseray_dir * t;
+ float t3 = RayPlaneIntersection(control_editor_info->start_cam_pos, original_click_dir, center, cam_facing);
+ vec3 orig_mouse_on_plane = control_editor_info->start_cam_pos + original_click_dir * t3;
+ float length_real_old = length(orig_mouse_on_plane - center);
+ float length_new = length(curr_mouse_on_plane - center);
+
+ vec3 cam_up = cam->GetUpVector();
+ vec3 cam_right = cross(cam_facing, cam_up);
+ mat4 old_scale;
+ old_scale.SetUniformScale(length_real_old);
+ mat4 new_scale;
+ new_scale.SetUniformScale(length_new);
+ mat4 circle_transform;
+ circle_transform.SetColumn(0, cam_right);
+ circle_transform.SetColumn(1, cam_up);
+ circle_transform.SetColumn(2, cam_facing);
+ circle_transform.SetTranslationPart(center + cam_moved);
+ DebugDraw::Instance()->AddCircle(circle_transform * old_scale, vec4(0.0f,0.0f,0.0f,0.5f), _delete_on_update, _DD_XRAY);
+ DebugDraw::Instance()->AddCircle(circle_transform * new_scale, vec4(1.0f,1.0f,1.0f,0.5f), _delete_on_update, _DD_XRAY);
+ if (length_real_old > 0 && length_new > 0) {
+ scale = length_new / length_real_old;
+ }
+ } else {
+ initial_rel_point = object_->GetRotation() *
+ (-1.0f * object_->start_transform.scale *
+ box_.GetPoint(tool_->control_vertex) );
+ initial_rel_point += object_->start_transform.translation;
+ vec3 new_clicked_point_local = invert(object_->GetRotation()) * (new_clicked_point - initial_rel_point);
+ vec3 old_clicked_point_local = invert(object_->GetRotation()) * (old_clicked_point - initial_rel_point);
+
+ for(int i=0; i<3; ++i){
+ if(old_clicked_point_local[i] == 0.0f){
+ LOG_ASSERT(false);
+ return vec3(1.0f);
+ }
+ scale[i] = new_clicked_point_local[i] / old_clicked_point_local[i];
+ }
+ }
+
+ if (snapping_enabled) {
+ float log_SCALE_SNAP_INCR = logf(SCALE_SNAP_INCR);
+ for(int i=0; i<3; ++i){
+ bool neg = (scale[i] < 0.0f);
+ scale[i] = floorf((logf(fabsf(scale[i]))/log_SCALE_SNAP_INCR)+0.5f);
+ scale[i] = (float)pow(SCALE_SNAP_INCR, scale[i]);
+ if(neg){
+ scale[i] *= -1.0f;
+ }
+ }
+ }
+
+ if(trans && tool_->mode != SCALE_WHOLE) {
+ vec3 new_initial_rel_point = object_->GetRotation() *
+ (-1.0f * (object_->start_transform.scale * scale) * box_.GetPoint(tool_->control_vertex) );
+ new_initial_rel_point += object_->start_transform.translation;
+ *trans = initial_rel_point - new_initial_rel_point;
+ }
+
+ return scale;
+}
+
+static quaternion GetRotation(const LineSegment& mouseray, bool snapping_enabled, const vec3& cam_moved, const ControlEditorInfo* control_editor_info, Tool* tool_, Object* object_, const Box& box_, GameCursor* cursor) {
+ static const float ANGLE_SNAP_INCR = 30;
+
+ vec3 around = tool_->around;
+ float angle = 0.0f;
+
+ vec3 dir = normalize(mouseray.end - mouseray.start);
+
+ vec3 a, b, p, n;
+ vec3 new_point;
+
+ quaternion rotation;
+
+ if (tool_->mode == ROTATE_SPHERE) {
+ int* mouse_pos = Input::Instance()->getMouse().pos_;
+ Camera* cam = ActiveCameras::Get();
+
+ float sensitivity = 0.1f;
+ vec3 temp_radius = box_.dims*0.5f;
+ temp_radius *= object_->GetScale();
+ sensitivity *= distance(tool_->center, cam->GetPos()) / length(temp_radius);
+
+ angle = (float)(mouse_pos[0] - tool_->old_mousex);
+ angle *= sensitivity;
+ tool_->old_mousex = mouse_pos[0];
+ vec4 x_rot(cam->GetUpVector(),angle/180.0f*PI_f);
+ mat4 mat;
+ quaternion x_rot_quat(x_rot);
+
+ angle = (float)(mouse_pos[1] - tool_->old_mousey);
+ angle *= sensitivity;
+ angle *= -1.0f;
+ tool_->old_mousey = mouse_pos[1];
+ vec4 y_rot(cross(cam->GetUpVector(), cam->GetFacing()),angle/180.0f*PI_f);
+ quaternion y_rot_quat(y_rot);
+ tool_->trackball_accumulate = x_rot_quat * y_rot_quat * tool_->trackball_accumulate;
+ rotation = tool_->trackball_accumulate;
+ }
+ if ( tool_->mode == ROTATE_CIRCLE ) {
+ n = -control_editor_info->basis_.facing;
+ p = tool_->center;
+
+ float to_dist = RayPlaneIntersection(mouseray.start, dir, p, n);
+ if (to_dist < 0) {
+ return quaternion();
+ }
+ new_point = mouseray.start + to_dist * dir;
+
+ to_dist = RayPlaneIntersection(control_editor_info->start_mouseray_.start, normalize(control_editor_info->start_mouseray_.end - control_editor_info->start_mouseray_.start), p, n);
+ if (to_dist < 0) {
+ return quaternion();
+ }
+ vec3 old_point = control_editor_info->start_mouseray_.start + to_dist * normalize(control_editor_info->start_mouseray_.end - control_editor_info->start_mouseray_.start);
+
+ a = normalize(new_point - p);
+ b = normalize(old_point - p);
+
+ float a_dot_b = dot(a,b);
+ if (a_dot_b < -1 || a_dot_b > 1 || a_dot_b == 0 ) {
+ return quaternion();
+ }
+ vec3 a_cross_b = cross(a,b);
+ angle = atan2f(dot(n,a_cross_b), a_dot_b);
+
+ around = control_editor_info->basis_.facing;
+
+ if (snapping_enabled) {
+ const float ANGLE_SNAP_INCR_rad = ANGLE_SNAP_INCR*deg2radf;
+ angle = floorf((angle / ANGLE_SNAP_INCR_rad) + 0.5f) * ANGLE_SNAP_INCR_rad;
+ }
+
+ // handle rotation cursor
+ if (tool_->mode == ROTATE_CIRCLE) {
+ cursor->SetCursor(ROTATE_CIRCLE_CURSOR);
+ vec3 screen_p1 = ActiveCameras::Get()->ProjectPoint(tool_->center[0], tool_->center[1], tool_->center[2]);
+ vec3 screen_p2 = ActiveCameras::Get()->ProjectPoint(new_point[0], new_point[1], new_point[2]);
+ vec3 screen_l = screen_p2 - screen_p1;
+ float cursor_rotation;
+ if (screen_l[1] == 0)
+ if (screen_l[0] < 0) cursor_rotation = -90.0f;
+ else cursor_rotation = 90.0f;
+ else {
+ float slope = screen_l[1]/screen_l[0];
+ if (screen_l[0] < 0.0f) {
+ cursor_rotation = atanf(slope)*(float)(rad2deg)+90.0f;
+ } else {
+ cursor_rotation = atanf(slope)*(float)(rad2deg)-90.0f;
+ }
+ }
+ cursor->SetRotation(cursor_rotation);
+ }
+ DebugDraw::Instance()->AddLine(p+cam_moved, mouseray.end+cam_moved, vec4(0,0,0,0.5f), _delete_on_update, _DD_XRAY);
+ rotation = quaternion(vec4(around, angle));
+ }
+
+ // Handle snaps
+ if (snapping_enabled) {
+ if (fabsf(angle) >= ANGLE_SNAP_INCR) {
+ angle = GetSign(angle)*ANGLE_SNAP_INCR;
+ } else {
+ angle = 0;
+ }
+ }
+ return rotation;
+}
+
+static void StartMouseTransformation(EditorTypes::Tool type, Object* obj, ControlEditorInfo* control_editor_info, const Collision& c, const LineSegment &mouseray, GameCursor* cursor) {
+ control_editor_info->start_mouseray_ = mouseray;
+ control_editor_info->start_cam_pos = ActiveCameras::Get()->GetPos();
+ control_editor_info->clicked_point_ = c.hit_where;
+
+ // turn off camera controls
+ if (ActiveCameras::Get()->m_camera_object != NULL) {
+ ActiveCameras::Get()->m_camera_object->IgnoreMouseInput(true);
+ }
+ ToolMode mode = DetermineToolMode(type);
+
+ mat4 obj_transform = obj->GetTransform();
+ quaternion obj_rot = obj->GetRotation();
+ int box_face_index = obj->box_.GetHitFaceIndex(invert(obj_rot) * c.hit_normal,
+ invert(obj_transform) * control_editor_info->clicked_point_);
+ int which_face_clicked = box_face_index;
+
+ switch(mode){
+ case TRANSLATE_FACE_PLANE:
+ case SCALE_PLANE:
+ control_editor_info->face_display_.plane = true;
+ control_editor_info->face_selected_ = which_face_clicked;
+ break;
+ case TRANSLATE_FACE_NORMAL:
+ case SCALE_NORMAL:
+ case ROTATE_CIRCLE:
+ control_editor_info->face_display_.facing = true;
+ control_editor_info->face_selected_ = which_face_clicked;
+ break;
+ default:
+ LOGW << "Unhandled mode: " << GetToolModeString(mode) << std::endl;
+ break;
+ }
+ control_editor_info->tool_.mode = mode;
+ switch(type){
+ case EditorTypes::ROTATE: {
+ if (control_editor_info->tool_.mode == ROTATE_CIRCLE) {
+ vec3 rt_center = control_editor_info->tool_.center;
+ vec3 rt_around = control_editor_info->tool_.around;
+ GetRotationBasis(control_editor_info->clicked_point_,
+ c.hit_normal, &control_editor_info->basis_, &rt_center, &rt_around,
+ obj, obj->box_);
+ control_editor_info->tool_.center = rt_center;
+ control_editor_info->tool_.around = rt_around;
+ } else {
+ control_editor_info->tool_.center = obj->GetTranslation();
+ }
+
+ control_editor_info->tool_.old_mousex = Input::Instance()->getMouse().pos_[0];
+ control_editor_info->tool_.old_mousey = Input::Instance()->getMouse().pos_[1];
+ control_editor_info->tool_.trackball_accumulate = quaternion();
+
+ GetRotation(mouseray, false, vec3(0.0f), control_editor_info, &control_editor_info->tool_, obj, obj->box_, cursor);
+ } break;
+ case EditorTypes::TRANSLATE: {
+ if (control_editor_info->tool_.mode == TRANSLATE_CAMERA_PLANE) {
+ GetCamBasis(&control_editor_info->basis_);
+ } else {
+ if (!GetTranslationBasis(
+ control_editor_info->clicked_point_,
+ c.hit_normal,
+ &control_editor_info->basis_,
+ obj, obj->box_, control_editor_info->tool_.mode))
+ {
+ FatalError("Error", "Bad translation mode.");
+ }
+ }
+ GetTranslation(mouseray, false, vec3(0.0f), control_editor_info, &control_editor_info->tool_, obj);
+ } break;
+ case EditorTypes::SCALE: {
+ vec3 scale_tool_center = control_editor_info->tool_.center;
+ if (mode == SCALE_WHOLE ||
+ !GetScaleBasis(control_editor_info->clicked_point_,
+ c.hit_normal, &control_editor_info->basis_, &scale_tool_center,
+ obj, obj->box_,
+ control_editor_info->tool_.mode) )
+ {
+ control_editor_info->tool_.center = obj->GetTranslation();
+ } else {
+ control_editor_info->tool_.center = scale_tool_center;
+ }
+
+ // Back to object space
+ mat4 inv = invert(obj->GetTransform());
+ vec3 point = inv*control_editor_info->clicked_point_;
+ float point_dist = 0;
+ control_editor_info->tool_.control_vertex = obj->box_.GetNearestPointIndex(point, point_dist);
+
+ GetScale(mouseray, NULL, false, vec3(0.0f), control_editor_info, &control_editor_info->tool_, obj, obj->box_);
+ } break;
+ default:
+ LOG_ASSERT(false);
+ }
+}
+
+static bool GetMouseTransformation(
+ EditorTypes::Tool type, Object* obj,
+ ControlEditorInfo* control_editor_info, bool snapping_enabled,
+ const LineSegment &mouseray, SeparatedTransform* curr_transform,
+ GameCursor* cursor)
+{
+ vec3 cam_moved = ActiveCameras::Get()->GetPos() - control_editor_info->start_cam_pos;
+ LineSegment cam_corrected_mouseray;
+ cam_corrected_mouseray.start = mouseray.start - cam_moved;
+ cam_corrected_mouseray.end = mouseray.end - cam_moved;
+ curr_transform->translation = vec3(0.0f);
+ curr_transform->scale = vec3(1.0f);
+ curr_transform->rotation = quaternion();
+ switch(type){
+ case EditorTypes::TRANSLATE:
+ curr_transform->translation = GetTranslation(cam_corrected_mouseray, snapping_enabled, cam_moved, control_editor_info, &control_editor_info->tool_, obj);
+ curr_transform->translation += cam_moved;
+ return (curr_transform->translation != obj->GetTranslation());
+ case EditorTypes::ROTATE:
+ curr_transform->rotation = GetRotation(cam_corrected_mouseray, snapping_enabled, cam_moved, control_editor_info, &control_editor_info->tool_, obj, obj->box_, cursor);
+ curr_transform->translation += cam_moved;
+ return (curr_transform->rotation != obj->GetRotation());
+ case EditorTypes::SCALE:
+ curr_transform->scale = GetScale(cam_corrected_mouseray, &curr_transform->translation, snapping_enabled, cam_moved, control_editor_info, &control_editor_info->tool_, obj, obj->box_);
+ curr_transform->translation += cam_moved;
+ return (curr_transform->scale != obj->GetScale());
+ default:
+ LOGW << "Unknown EditorTypes::Tool " << EditorTypes::GetToolString(type) << std::endl;
+ break;
+
+ }
+ return false;
+}
+
+static void EndTransformation(SceneGraph *scenegraph, EditorTypes::Tool type, Object* control_obj, ControlEditorInfo* control_editor_info, GameCursor* cursor) {
+ for(SceneGraph::object_list::iterator iter = scenegraph->objects_.begin();
+ iter != scenegraph->objects_.end();
+ ++iter)
+ {
+ Object* obj = *iter;
+ if (obj->Selected()) {
+ obj->HandleTransformationOccurred();
+ }
+ }
+ if (control_obj != NULL) {
+ // turn on camera controls
+ if (ActiveCameras::Get()->m_camera_object != NULL) {
+ ActiveCameras::Get()->m_camera_object->IgnoreMouseInput(false);
+ }
+ if (control_editor_info->tool_.mode == ROTATE_CIRCLE) {
+ cursor->SetRotation(0);
+ }
+ control_obj = NULL;
+ }
+}
+
+void MapEditor::UpdateTransformTool(SceneGraph *scenegraph, EditorTypes::Tool type, const LineSegment &mouseray, const Collision& c, GameCursor* cursor) {
+ PROFILER_ZONE(g_profiler_ctx, "UpdateTransformTool()");
+
+ Mouse* mouse = &(Input::Instance()->getMouse());
+ const Keyboard& keyboard = Input::Instance()->getKeyboard();
+
+ bool input_happened = false;
+ bool transformation_happened = false;
+
+ if ((mouse->mouse_down_[Mouse::LEFT] || mouse->mouse_down_[Mouse::RIGHT]) && !mouse->mouse_double_click_[Mouse::LEFT]){
+ if (state_ == MapEditor::kIdle) {
+ if (MouseWasClickedThisTimestep(mouse) && c.hit && c.hit_what && c.hit_what->Selected()) {
+ // We are just starting to drag
+ control_obj = c.hit_what;
+ input_happened = true;
+ StartMouseTransformation(type, control_obj, &control_editor_info, c, mouseray, cursor);
+ }
+ } else {
+ // We are continuing to drag
+ input_happened = true;
+ bool snapping_enabled = KeyCommand::CheckDown(keyboard, KeyCommand::kSnapTransform, KIMF_LEVEL_EDITOR_GENERAL);
+ if(control_obj) {
+ transformation_happened = GetMouseTransformation(type, control_obj, &control_editor_info, snapping_enabled, mouseray, &curr_tool_transform, cursor);
+ }
+ }
+ }
+
+ // Apply the transformation
+ if (input_happened && state_ == MapEditor::kIdle) {
+ PROFILER_ZONE(g_profiler_ctx, "Starting transform");
+ if(KeyCommand::CheckDown(keyboard, KeyCommand::kCloneTransform, KIMF_LEVEL_EDITOR_GENERAL)){
+ EntityDescriptionList copy_desc_list;
+ ActorsEditor_CopySelectedEntities(scenegraph, &copy_desc_list);
+ ActorsEditor_UnlocalizeIDs(&copy_desc_list, scenegraph);
+ MapEditor::DeselectAll(scenegraph);
+ std::vector<Object*> new_objects;
+ for (size_t i=0, len=copy_desc_list.size(); i<len; ++i) {
+ Object* new_entity = CreateObjectFromDesc(copy_desc_list[i]);
+ if( new_entity ) {
+ new_objects.push_back(new_entity);
+ if(new_entity->permission_flags & Object::CAN_SELECT){
+ new_entity->Select(true);
+ }
+ if( ActorsEditor_AddEntity(scenegraph, new_entity, NULL, false) ) {
+
+ } else {
+ LOGE << "Failed to add entity to scenegraph while cloning" << std::endl;
+ delete new_entity;
+ }
+ } else {
+ LOGE << "Failed to entity" << std::endl;
+ }
+ }
+ for (size_t i=0, len=new_objects.size(); i<len; ++i) {
+ new_objects[i]->ReceiveObjectMessage(OBJECT_MSG::FINALIZE_LOADED_CONNECTIONS);
+ }
+ }
+ state_ = MapEditor::kTransformDrag;
+ for (SceneGraph::object_list::iterator iter = scenegraph->objects_.begin();
+ iter != scenegraph->objects_.end();
+ ++iter)
+ {
+ Object* obj = *iter;
+ obj->start_transform.translation = obj->GetTranslation();
+ obj->start_transform.rotation = obj->GetRotation();
+ obj->start_transform.scale = obj->GetScale();
+ }
+ } else if (state_ == MapEditor::kTransformDrag && !input_happened) { // end
+ EndTransformation(scenegraph, type, control_obj, &control_editor_info, cursor);
+ control_editor_info.face_selected_ = -1;
+ control_editor_info.face_display_.facing = false;
+ control_editor_info.face_display_.plane = false;
+ state_ = MapEditor::kIdle;
+ QueueSaveHistoryState();
+ scenegraph_->UpdatePhysicsTransforms();
+ } else if (state_ == MapEditor::kTransformDrag && transformation_happened) { // continue
+ std::vector<int> moved_objects;
+ PROFILER_ZONE(g_profiler_ctx, "Updating transform");
+ for (SceneGraph::object_list::iterator iter = scenegraph->objects_.begin();
+ iter != scenegraph->objects_.end();
+ ++iter)
+ {
+ Object* obj = *iter;
+ if (obj->Selected()) {
+ obj->SetTranslation(obj->start_transform.translation + curr_tool_transform.translation);
+ obj->SetRotation(curr_tool_transform.rotation * obj->start_transform.rotation);
+ obj->SetScale(curr_tool_transform.scale * obj->start_transform.scale);
+ obj->HandleTransformationOccurred();
+
+ moved_objects.push_back(obj->GetID());
+ }
+ }
+ if(!moved_objects.empty()) {
+ PROFILER_ZONE(g_profiler_ctx, "Send moved_objects message");
+
+ std::ostringstream oss;
+ oss << "moved_objects";
+
+ for(size_t i=0, len=moved_objects.size(); i<len; ++i){
+ oss << " " << moved_objects[i];
+ }
+
+ scenegraph_->level->Message(oss.str());
+ }
+ }
+ return;
+}
+
+bool IsBeingMoved(const MapEditor* map_editor, const Object* object) {
+ if(object->parent && IsBeingMoved(map_editor, object->parent)){
+ return true;
+ } else {
+ if(map_editor->state_ == MapEditor::kTransformDrag && map_editor->control_obj == object){
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
+
+void MapEditor::AddColorToHistory(vec4 color) {
+ color_history_candidate_ = color;
+ color_history_countdown_ = kNumUpdatesBeforeColorSave;
+}
+
+void MapEditor::PreviewTerrainHeightmap(const char* path) {
+ PreviewTerrain(path, NULL, NULL);
+}
+
+void MapEditor::PreviewTerrainColormap(const char* path) {
+ PreviewTerrain(NULL, path, NULL);
+}
+
+void MapEditor::PreviewTerrainWeightmap(const char* path) {
+ PreviewTerrain(NULL, NULL, path);
+}
+
+void MapEditor::PreviewTerrainDetailmap(int index, const char* color_path, const char* normal_path, const char* material_path) {
+ EnterTerrainPreviewMode();
+
+ assert(index >= 0 && index < 4);
+ if(terrain_preview) {
+ std::vector<DetailMapInfo> detail_map_info = terrain_preview->terrain_info().detail_map_info;
+ detail_map_info[index].colorpath = color_path;
+ detail_map_info[index].normalpath = normal_path;
+ detail_map_info[index].materialpath = material_path;
+
+ terrain_preview->SetTerrainDetailTextures(detail_map_info);
+ } else if(scenegraph_->terrain_object_) {
+ std::vector<DetailMapInfo> detail_map_info = scenegraph_->terrain_object_->terrain_info().detail_map_info;
+ detail_map_info[index].colorpath = color_path;
+ detail_map_info[index].normalpath = normal_path;
+ detail_map_info[index].materialpath = material_path;
+
+ scenegraph_->terrain_object_->SetTerrainDetailTextures(detail_map_info);
+ }
+}
+
+void MapEditor::PreviewTerrain(const char* heightmap_path, const char* colormap_path, const char* weightmap_path) {
+ EnterTerrainPreviewMode();
+
+ if(heightmap_path) {
+ const char* blank_normal_path = Textures::Instance()->GetBlankNormalTextureAssetRef()->path_.c_str();
+ TerrainInfo terrain_info;
+ bool keep_data = true;
+ TerrainObject* current_terrain = scenegraph_->terrain_object_;
+ if(terrain_preview) {
+ // Copy data from old and delete since a new one will be created
+ terrain_info = terrain_preview->terrain_info();
+ scenegraph_->UnlinkObject(terrain_preview);
+ delete terrain_preview;
+ } else if(current_terrain) {
+ // Copy data from "real" terrain
+ terrain_info = current_terrain->terrain_info();
+ current_terrain->preview_mode = true;
+ } else {
+ keep_data = false;
+ terrain_info.SetDefaults();
+ DetailMapInfo blank_detail;
+ blank_detail.colorpath = "Data/Textures/Terrain/default_d.png";
+ blank_detail.normalpath = blank_normal_path;
+ blank_detail.materialpath = "Data/Materials/default.xml";
+ for(int i = 0; i < 4; ++i)
+ terrain_info.detail_map_info.push_back(blank_detail);
+ }
+ terrain_info.minimal = true;
+
+ if(heightmap_path && strlen(heightmap_path)) {
+ terrain_info.heightmap = heightmap_path;
+ } else {
+ terrain_info.heightmap = "Data/Textures/Terrain/default_hm.png";
+ }
+ if(colormap_path && strlen(colormap_path)) {
+ terrain_info.colormap = colormap_path;
+ } else if(!terrain_preview && !keep_data) {
+ terrain_info.colormap = "Data/Textures/Terrain/default_c.png";
+ }
+ if(weightmap_path && strlen(weightmap_path)) {
+ terrain_info.weightmap = weightmap_path;
+ } else if(!terrain_preview && !keep_data) {
+ terrain_info.weightmap = "Data/Textures/Terrain/default_w.png";
+ }
+
+ terrain_preview = new TerrainObject(terrain_info);
+ scenegraph_->addObject(terrain_preview);
+ } else {
+ TerrainObject* terrain = scenegraph_->terrain_object_;
+ if(terrain_preview)
+ terrain = terrain_preview;
+
+ if(colormap_path) {
+ terrain->SetTerrainColorTexture(colormap_path);
+ }
+ if(weightmap_path) {
+ terrain->SetTerrainWeightTexture(weightmap_path);
+ }
+ }
+}
+
+bool MapEditor::GameplayObjectsEnabled() const
+{
+ return gameplay_objects_enabled_;
+}
+
+const TerrainInfo* MapEditor::GetPreviewTerrainInfo() const
+{
+ return (terrain_preview ? &terrain_preview->terrain_info() : NULL);
+}
+
+bool MapEditor::CreateObjectFromHost(const std::string& path, const vec3& pos, CommonObjectID host_id)
+{
+ int retVal = LoadEntitiesFromFile(path);
+ ActorsEditor_AddEntitiesAtPosition(add_desc_list_source_, scenegraph_, add_desc_list_, pos, create_as_prefab_, host_id);
+
+ return retVal == 0;
+}
+
+void MapEditor::EnterTerrainPreviewMode()
+{
+ if(!terrain_preview_mode) {
+ TerrainObject* current_terrain = scenegraph_->terrain_object_;
+ if(current_terrain) {
+ // Keep these so they can be restored
+ real_terrain_info = current_terrain->terrain_info();
+ }
+
+ terrain_preview_mode = true;
+ }
+}
+
+void MapEditor::ExitTerrainPreviewMode()
+{
+ if(terrain_preview_mode) {
+ if(scenegraph_->terrain_object_)
+ scenegraph_->terrain_object_->preview_mode = false;
+ terrain_preview_mode = false;
+ }
+}
diff --git a/Source/Editors/map_editor.h b/Source/Editors/map_editor.h
new file mode 100644
index 00000000..203449f7
--- /dev/null
+++ b/Source/Editors/map_editor.h
@@ -0,0 +1,333 @@
+//-----------------------------------------------------------------------------
+// Name: map_editor.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Editors/editor_types.h>
+#include <Editors/editor_utilities.h>
+#include <Editors/editor_tools.h>
+#include <Editors/save_state.h>
+
+#include <Internal/collisiondetection.h>
+#include <Internal/levelxml.h>
+
+#include <Game/EntityDescription.h>
+#include <Online/online_datastructures.h>
+#include <GUI/IMUI/imui.h>
+#include <Objects/object.h>
+#include <XML/level_loader.h>
+#include <Math/overgrowth_geometry.h>
+#include <UserInput/keyboard.h>
+
+#include <queue>
+#include <set>
+
+extern bool draw_group_and_prefab_boxes;
+extern bool always_draw_hotspot_connections;
+
+namespace PHOENIX_KEY_CONSTANTS {
+ #ifdef PLATFORM_MACOSX
+ const int command_key = Keyboard::GUI;
+ const int command_SDLK_key = SDLK_GUI;
+ #else
+ const int command_key = Keyboard::CTRL;
+ const int command_SDLK_key = SDLK_CTRL;
+ #endif
+}
+
+class SceneGraph;
+class EnvObject;
+class TiXmlDocument;
+class TiXmlElement;
+class Group;
+class ActorsEditor;
+class SkyEditor;
+class Hotspot;
+class DecalObject;
+class GUI;
+class GameCursor;
+class TiXmlNode;
+class TerrainObject;
+
+class TypeEnable {
+public:
+ TypeEnable(const char* config_postfix);
+ bool IsTypeEnabled(EntityType type) const;
+ void SetTypeEnabled(EntityType type, bool enabled);
+ void SetAll(bool enabled);
+ void SetFromConfig();
+ void WriteToConfig();
+private:
+ typedef std::map<EntityType, bool> TypeEnabledMap;
+ TypeEnabledMap type_enabled_;
+ bool unknown_types_enabled_;
+ const char* config_postfix;
+ bool ReadTypeString(EntityType type);
+ void WriteTypeString(EntityType type) const;
+};
+
+struct FaceDisplay {
+ bool facing, plane;
+};
+
+enum ToolMode {
+ SCALE_WHOLE,
+ SCALE_PLANE,
+ SCALE_NORMAL,
+ TRANSLATE_CAMERA_PLANE,
+ TRANSLATE_FACE_PLANE,
+ TRANSLATE_FACE_NORMAL,
+ ROTATE_SPHERE,
+ ROTATE_CIRCLE
+};
+
+inline const char* GetToolModeString( ToolMode m )
+{
+ switch( m)
+ {
+ case SCALE_WHOLE: return "SCALE_WHOLE";
+ case SCALE_PLANE: return "SCALE_PLANE";
+ case SCALE_NORMAL: return "SCALE_NORMAL";
+ case TRANSLATE_CAMERA_PLANE: return "TRANSLATE_CAMERA_PLANE";
+ case TRANSLATE_FACE_PLANE: return "TRANSLATE_FACE_PLANE";
+ case TRANSLATE_FACE_NORMAL: return "TRANSLATE_FACE_NORMAL";
+ case ROTATE_SPHERE: return "ROTATE_SPHERE";
+ case ROTATE_CIRCLE: return "ROTATE_CIRCLE";
+ default: return "(unknown tool mode string value)";
+ }
+}
+
+struct Tool {
+ ToolMode mode;
+
+ int control_vertex;
+
+ quaternion trackball_accumulate;
+ vec3 center;
+ vec3 around;
+ int old_mousex;
+ int old_mousey;
+};
+
+struct Basis {
+ vec3 up;
+ vec3 facing;
+ vec3 right;
+};
+
+struct ControlEditorInfo {
+ vec3 start_cam_pos;
+ LineSegment start_mouseray_;
+ vec3 clicked_point_;
+ Basis basis_;
+ int face_selected_;
+ FaceDisplay face_display_;
+
+ Tool tool_;
+};
+
+// The MapEditor class contains all of the editors.
+class MapEditor {
+public:
+ MapEditor();
+ ~MapEditor();
+
+ enum State {
+ kInGame,
+ kIdle,
+ kTransformDrag,
+ kBoxSelectDrag,
+ kSkyDrag
+ };
+
+ State state_;
+ GUI* gui;
+
+
+ void Initialize(SceneGraph* s);
+ void UpdateEnabledObjects();
+
+ void Draw();
+ void Update(GameCursor* cursor);
+
+ void ShowEditorMessage( int type, const std::string& message );
+
+ void Undo();
+ void Redo();
+ void CopySelected();
+ void Paste( const LineSegment& mouseray );
+ void DeleteSelected();
+ void DeleteID(int val);
+ void CutSelected();
+ void SavePrefab(bool do_resave);
+ void SaveSelected();
+ void GroupSelected();
+ bool ContainsPrefabsRecursively( std::vector<Object*> objects );
+ int PrefabSelected();
+ void UngroupSelected();
+ bool IsSomethingSelected();
+ bool IsOneObjectSelected();
+
+ void SendInRabbot();
+ void StopRabbot(bool handle_gui = true);
+
+ void SaveEntities(TiXmlNode* root);
+
+ void QueueSaveHistoryState();
+
+ bool CanUndo();
+ bool CanRedo();
+ void SetViewNavMesh( bool enabled );
+ void SetViewCollisionNavMesh(bool enabled);
+ bool IsViewingNavMesh();
+ bool IsViewingCollisionNavMesh();
+ void ToggleImposter();
+ void SetImposter(bool set);
+ void CPSetColor(const vec3 &color);
+ static Object* AddEntityFromDesc( SceneGraph *scenegraph, const EntityDescription& desc, bool level_loading );
+ void ApplyScriptParams(const ScriptParamMap& spm, int id);
+ void ApplyPalette( const OGPalette &palette, int edited_id );
+ OGPalette* GetPalette(int id);
+ void ReceiveMessage( const std::string &msg );
+ void SetUpSky(const SkyInfo &si);
+ void RemoveObject(Object* o, SceneGraph* scenegraph, bool removed_by_socket = false);
+
+ bool WasLastSaveOnCurrentUndoChunk();
+ void SetLastSaveOnCurrentUndoChunk();
+ void SaveLevel(LevelLoader::SaveLevelType type);
+
+ int CreateObject( const std::string& path);
+ int DuplicateObject( const Object* obj );
+ int ReplaceObjects( const std::vector<Object*>& objects, const std::string& replacement_path );
+ bool IsTypeEnabled( EntityType type );
+ void SetTypeEnabled( EntityType type, bool enabled );
+ bool IsTypeVisible ( EntityType type );
+ void SetTypeVisible( EntityType type, bool enabled );
+ Object* GetSelectedCameraObject();
+ static void DeselectAll(SceneGraph *scenegraph);
+ void ClearUndoHistory();
+ void LoadDialogueFile(const std::string &path);
+ void UpdateGPUSkinning();
+ void CarveAgainstTerrain();
+ void ExecuteSaveLevelChanges();
+ void AddLightProbes();
+ void BakeLightProbes(int pass);
+ int LoadEntitiesFromFile(const std::string& filename);
+ void ReturnSelected(std::vector<Object*> *selected_objects);
+ void RibbonItemClicked(const std::string& item, bool param);
+ void SelectAll();
+ const StateHistory& state_history() {return state_history_;}
+
+ void ReloadAllPrefabs();
+ void ReloadPrefabs(const Path& path);
+ bool ReloadPrefab(Object *obj, SceneGraph* scenegraph);
+
+ SceneGraph* GetSceneGraph() { return scenegraph_; }
+
+ Object* control_obj; // editor for curr object controlling the transformations
+ ControlEditorInfo control_editor_info;
+ EditorTypes::Tool active_tool_;
+ SkyEditor* sky_editor_;
+
+ vec4 GetColorHistoryIndex(int index) {return color_history_[index];}
+
+ static const int kColorHistoryLen = 20;
+ void AddColorToHistory(vec4 color);
+
+ void PreviewTerrain(const char* heightmap_path, const char* colormap_path, const char* weightmap_path);
+ void PreviewTerrainDetailmap(int index, const char* color_path, const char* normal_path, const char* material_path);
+ void PreviewTerrainHeightmap(const char* path);
+ void PreviewTerrainColormap(const char* path);
+ void PreviewTerrainWeightmap(const char* path);
+
+ bool GameplayObjectsEnabled() const;
+
+ const TerrainInfo* GetPreviewTerrainInfo() const;
+ bool GetTerrainPreviewMode() const { return terrain_preview_mode; }
+ bool CreateObjectFromHost(const std::string& path, const vec3& pos, CommonObjectID host_id);
+private:
+ void UpdateTransformTool(SceneGraph *scenegraph, EditorTypes::Tool type, const LineSegment &mouseray, const Collision& c, GameCursor* cursor);
+ TypeEnable type_enable_;
+ TypeEnable type_visible_;
+ vec4 color_history_candidate_;
+
+ bool gameplay_objects_enabled_; // The setting is called "gameplay objects" in-game, but the enum value is actually hotspots...
+
+ StateHistory state_history_;
+ EntityDescriptionList add_desc_list_;
+ Path add_desc_list_source_;
+ bool create_as_prefab_;
+ SeparatedTransform curr_tool_transform;
+
+ enum {
+ LEVEL_PARAM_ID = -1
+ };
+
+ void InitializeColorHistory();
+ void SaveColorToHistory();
+ int color_history_countdown_;
+ vec4 color_history_[kColorHistoryLen];
+
+ void UpdateCursor( const LineSegment& mouseray, GameCursor* cursor );
+ // internal helper functions
+ void DeleteEditors();
+
+ void SetTool( EditorTypes::Tool tool );
+
+ // Control and tool handlers
+ void HandleShortcuts( const LineSegment& mouseray );
+ void UpdateTools( const LineSegment& mouseray, GameCursor* cursor );
+ bool CheckForSelections();
+ bool CheckForSelections( const LineSegment& mouseray );
+ IMUIContext imui_context;
+
+ void BringAllToCurrentState(int old_state);
+ bool HandleScrollSelect(const vec3 &start, const vec3 &end);
+ int save_countdown_;
+
+ SceneGraph* scenegraph_;
+
+ EntityDescriptionList copy_desc_list_;
+
+ // Tool structures
+ BoxSelector box_selector_;
+ EditorTypes::Tool omni_tool_tool_;
+
+ std::vector<Object*> selected; // This is only a member to avoid per-frame memory allocs
+ std::vector<Object*> box_objects;
+
+ //Value indicating what point in the undo stack we last saved.
+ unsigned last_saved_chunk_global_id;
+
+ void EnterTerrainPreviewMode();
+ void ExitTerrainPreviewMode();
+
+ bool terrain_preview_mode;
+ TerrainInfo real_terrain_info;
+ TerrainObject* terrain_preview;
+};
+
+void LoadLevel(bool local);
+bool IsBeingMoved(const MapEditor* map_editor, const Object* object);
+// Don't move whole code chunk from engine.cpp yet
+extern void PlaceLightProbes(SceneGraph* scenegraph, vec3 translation, quaternion rotation, vec3 scale);
diff --git a/Source/Editors/mem_read_entity_description.cpp b/Source/Editors/mem_read_entity_description.cpp
new file mode 100644
index 00000000..ced4605c
--- /dev/null
+++ b/Source/Editors/mem_read_entity_description.cpp
@@ -0,0 +1,61 @@
+//-----------------------------------------------------------------------------
+// Name: mem_read_entity_description.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "mem_read_entity_description.h"
+
+#include <Game/EntityDescription.h>
+#include <Internal/memwrite.h>
+
+void MemReadItemConnectionData(ItemConnectionData &icd, const std::vector<char> &mem_stream, int &index) {
+ memread(&icd.id, sizeof(icd.id), 1, mem_stream, index);
+ memread(&icd.mirrored, sizeof(icd.mirrored), 1, mem_stream, index);
+ memread(&icd.attachment_type, sizeof(icd.attachment_type), 1, mem_stream, index);
+ int32_t str_size;
+ memread(&str_size, sizeof(str_size), 1, mem_stream, index);
+ icd.attachment_str.resize(str_size);
+ memread(&icd.attachment_str[0], str_size, 1, mem_stream, index);
+}
+
+void MemWriteItemConnectionData(const ItemConnectionData &icd, std::vector<char> &mem_stream) {
+ memwrite(&icd.id, sizeof(icd.id), 1, mem_stream);
+ memwrite(&icd.mirrored, sizeof(icd.mirrored), 1, mem_stream);
+ memwrite(&icd.attachment_type, sizeof(icd.attachment_type), 1, mem_stream);
+ int32_t str_size = icd.attachment_str.size();
+ memwrite(&str_size, sizeof(str_size), 1, mem_stream);
+ memwrite(&icd.attachment_str[0], str_size, 1, mem_stream);
+}
+
+void MemReadNavMeshConnectionData(NavMeshConnectionData &nmcd, const std::vector<char> &mem_stream, int &index)
+{
+ memread(&nmcd.other_object_id, sizeof(nmcd.other_object_id), 1, mem_stream, index);
+ memread(&nmcd.offmesh_connection_id, sizeof(nmcd.offmesh_connection_id), 1, mem_stream, index);
+ memread(&nmcd.poly_area, sizeof(nmcd.poly_area), 1, mem_stream, index);
+}
+
+void MemWriteNavMeshConnectionData(const NavMeshConnectionData &nmcd, std::vector<char> &mem_stream)
+{
+ memwrite(&nmcd.other_object_id, sizeof(nmcd.other_object_id), 1, mem_stream);
+ memwrite(&nmcd.offmesh_connection_id, sizeof(nmcd.offmesh_connection_id), 1, mem_stream);
+ memwrite(&nmcd.poly_area, sizeof(nmcd.poly_area), 1, mem_stream);
+}
+
diff --git a/Source/Editors/mem_read_entity_description.h b/Source/Editors/mem_read_entity_description.h
new file mode 100644
index 00000000..bf741e7d
--- /dev/null
+++ b/Source/Editors/mem_read_entity_description.h
@@ -0,0 +1,36 @@
+//-----------------------------------------------------------------------------
+// Name: mem_read_entity_description.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/integer.h>
+#include <Game/EntityDescription.h>
+
+#include <vector>
+
+struct ItemConnectionData;
+
+void MemReadItemConnectionData(ItemConnectionData &icd, const std::vector<char> &mem_stream, int &index);
+void MemWriteItemConnectionData(const ItemConnectionData &icd, std::vector<char> &mem_stream);
+void MemReadNavMeshConnectionData(NavMeshConnectionData &nmcs, const std::vector<char> &mem_stream, int &index);
+void MemWriteNavMeshConnectionData(const NavMeshConnectionData &nmcd, std::vector<char> &mem_stream);
diff --git a/Source/Editors/object_sanity_state.cpp b/Source/Editors/object_sanity_state.cpp
new file mode 100644
index 00000000..6747790b
--- /dev/null
+++ b/Source/Editors/object_sanity_state.cpp
@@ -0,0 +1,120 @@
+//-----------------------------------------------------------------------------
+// Name: object_sanity_state.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "object_sanity_state.h"
+
+#include <Internal/common.h>
+
+ObjectSanityState::ObjectSanityState() : id(-1), type(_no_type), state_flags(~0UL) {
+}
+
+ObjectSanityState::ObjectSanityState(EntityType type, int32_t id, uint32_t state_flags) : type(type), id(id), state_flags(state_flags) {
+}
+
+bool ObjectSanityState::Valid() {
+ return id >= 0;
+}
+
+bool ObjectSanityState::Ok() {
+ return state_flags == 0;
+}
+
+int32_t ObjectSanityState::GetID() {
+ return id;
+}
+
+uint32_t ObjectSanityState::GetStateFlags() {
+ return state_flags;
+}
+
+uint32_t ObjectSanityState::GetErrorCount() {
+ int count = 0;
+ for( int i = 0; i < 32; i++ ) {
+ if( state_flags & (1UL << i) ) {
+ count++;
+ }
+ }
+ return count;
+}
+
+void ObjectSanityState::GetError(uint32_t errindex, char* outbuf, uint32_t size) {
+ int index_count = -1;
+ uint32_t curr_flag = 0;
+ for( int i = 0; i < 32; i++ ) {
+ if( state_flags & (1UL << i) ) {
+ index_count++;
+ if( index_count == errindex ) {
+ curr_flag = (1UL << i);
+ }
+ }
+ }
+
+ if( curr_flag == 0 ) {
+ FormatString(outbuf, size, "Out of bounds error index %d\n", errindex);
+ } else if( type == _placeholder_object ) {
+ if( curr_flag == kObjectSanity_PO_UnsetConnectID ) {
+ FormatString(outbuf, size, "Connect ID is unset (no character tied to dialogue?)");
+ } else {
+ FormatString(outbuf, size, "Unknown error flag for _placeholder_object %x\n", curr_flag);
+ }
+ } else if( type == _group ) {
+ switch( curr_flag ) {
+ case kObjectSanity_G_NullChild:
+ FormatString(outbuf, size, "There is a child object which is a NULL pointer");
+ break;
+ case kObjectSanity_G_ChildHasSanityIssue:
+ FormatString(outbuf, size, "Some object in the group fails a sanity check");
+ break;
+ case kObjectSanity_G_Empty:
+ FormatString(outbuf, size, "Group is empty");
+ break;
+ case kObjectSanity_G_ChildHasExternalConnection:
+ FormatString(outbuf, size, "There is a connection from inside the group to the outside world");
+ break;
+ default:
+ FormatString(outbuf, size, "Unknown error flag for _group %x\n", curr_flag);
+ break;
+ }
+ } else if( type == _prefab ) {
+ switch( curr_flag ) {
+ case kObjectSanity_G_NullChild:
+ FormatString(outbuf, size, "There is a child object which is a NULL pointer");
+ break;
+ case kObjectSanity_G_ChildHasSanityIssue:
+ FormatString(outbuf, size, "Some object in the prefab fails a sanity check");
+ break;
+ case kObjectSanity_G_Empty:
+ FormatString(outbuf, size, "Prefab is empty");
+ break;
+ case kObjectSanity_G_ChildHasExternalConnection:
+ FormatString(outbuf, size, "There is a connection from inside the prefab to the outside world");
+ break;
+ default:
+ FormatString(outbuf, size, "Unknown error flag for _prefab %x\n", curr_flag);
+ break;
+ }
+ } else if( curr_flag ) {
+ FormatString(outbuf, size, "Unknown error flag %x\n", curr_flag);
+ }
+}
+
diff --git a/Source/Editors/object_sanity_state.h b/Source/Editors/object_sanity_state.h
new file mode 100644
index 00000000..f130b947
--- /dev/null
+++ b/Source/Editors/object_sanity_state.h
@@ -0,0 +1,54 @@
+//-----------------------------------------------------------------------------
+// Name: object_sanity_state.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Editors/entity_type.h>
+#include <Internal/integer.h>
+
+static const int kObjectSanity_PO_UnsetConnectID = (1UL<<0);
+
+static const int kObjectSanity_G_NullChild = (1UL<<0);
+static const int kObjectSanity_G_ChildHasSanityIssue = (1UL<<1);
+static const int kObjectSanity_G_Empty = (1UL<<2);
+static const int kObjectSanity_G_ChildHasExternalConnection = (1UL<<3);
+
+class ObjectSanityState {
+public:
+ EntityType type;
+private:
+ int32_t id;
+ uint32_t state_flags;
+
+public:
+ ObjectSanityState();
+ ObjectSanityState(EntityType type, int32_t id, uint32_t state_flags);
+ bool Valid();
+ bool Ok();
+
+ int32_t GetID();
+ uint32_t GetStateFlags();
+
+ uint32_t GetErrorCount();
+ void GetError(uint32_t errindex, char* outbuf, uint32_t size);
+};
diff --git a/Source/Editors/save_state.cpp b/Source/Editors/save_state.cpp
new file mode 100644
index 00000000..73701e65
--- /dev/null
+++ b/Source/Editors/save_state.cpp
@@ -0,0 +1,61 @@
+//-----------------------------------------------------------------------------
+// Name: save_state.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "save_state.h"
+
+unsigned SavedChunk::global_chunk_id_counter = 1;
+
+SavedChunk::SavedChunk() : global_chunk_id(global_chunk_id_counter++)
+{
+
+}
+
+unsigned SavedChunk::GetGlobalID()
+{
+ return global_chunk_id;
+}
+
+void AddChunkToHistory(std::list<SavedChunk> &chunk_list, int state_id, SavedChunk &saved_chunk){
+ saved_chunk.state_id = state_id;
+ chunk_list.push_back(saved_chunk);
+}
+
+void StateHistory::clear() {
+ chunks.clear();
+ current_state = -1;
+ start_state = 0;
+ num_states = 0;;
+}
+
+std::list<SavedChunk>::iterator StateHistory::GetCurrentChunk()
+{
+ std::list<SavedChunk>::iterator chunkit = chunks.begin();
+ for( ; chunkit != chunks.end(); chunkit++ )
+ {
+ if( chunkit->state_id == current_state )
+ {
+ return chunkit;
+ }
+ }
+ return chunks.end();
+}
diff --git a/Source/Editors/save_state.h b/Source/Editors/save_state.h
new file mode 100644
index 00000000..9256ba2c
--- /dev/null
+++ b/Source/Editors/save_state.h
@@ -0,0 +1,69 @@
+//-----------------------------------------------------------------------------
+// Name: save_state.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Game/EntityDescription.h>
+
+#include <list>
+#include <vector>
+
+namespace ChunkType {
+ enum ChunkType {
+ LEVEL,
+ SKY_EDITOR,
+ OBJECT,
+ GROUP
+ };
+}
+
+struct SavedChunk {
+private:
+ static unsigned global_chunk_id_counter;
+ unsigned global_chunk_id;
+public:
+ SavedChunk();
+ int state_id;
+ ChunkType::ChunkType type;
+ int obj_id;
+ EntityDescription desc;
+ unsigned GetGlobalID();
+};
+
+// StateHistory stores all of the saved state chunks, as well as information
+// about where we are in the undo/redo timeline
+class StateHistory {
+public:
+ std::list<SavedChunk> chunks;
+ int current_state;
+ int start_state;
+ int num_states;
+
+ void clear();
+
+ StateHistory(){clear();}
+
+ std::list<SavedChunk>::iterator GetCurrentChunk();
+};
+
+void AddChunkToHistory(std::list<SavedChunk> &chunk_list, int state_id, SavedChunk &saved_chunk);
diff --git a/Source/Editors/sky_editor.cpp b/Source/Editors/sky_editor.cpp
new file mode 100644
index 00000000..276b3a0b
--- /dev/null
+++ b/Source/Editors/sky_editor.cpp
@@ -0,0 +1,564 @@
+//-----------------------------------------------------------------------------
+// Name: sky_editor.cpp
+// Developer: Wolfire Games LLC
+// Author: Phillip Isola
+// Description: Editor for the sky
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "sky_editor.h"
+
+#include <Editors/map_editor.h>
+#include <Editors/editor_utilities.h>
+
+#include <Graphics/sky.h>
+#include <Graphics/flares.h>
+#include <Graphics/camera.h>
+#include <Graphics/camera.h>
+#include <Graphics/graphics.h>
+#include <Graphics/shaders.h>
+#include <Graphics/Cursor.h>
+
+#include <Math/vec3math.h>
+#include <Math/vec4math.h>
+
+#include <Internal/datemodified.h>
+#include <Internal/memwrite.h>
+
+#include <UserInput/input.h>
+#include <Objects/cameraobject.h>
+#include <Main/scenegraph.h>
+#include <XML/xml_helper.h>
+
+#include <tinyxml.h>
+
+#include <cmath>
+
+static const float _initial_sun_angular_rad = PI_f/30.0f; // must not be zero
+static const float _sun_brightness_constant = 1.0f/((float)pow(tanf(_initial_sun_angular_rad),2.0f));
+static const float _min_sun_angular_rad = PI_f/100.0f;
+static const float _max_sun_angular_rad = PI_f/10.0f;
+static const float _color_orb_scale = tanf(_initial_sun_angular_rad)*0.1f;
+
+static const float _min_sun_brightness = tanf(_min_sun_angular_rad) * tanf(_min_sun_angular_rad) * _sun_brightness_constant;
+static const float _max_sun_brightness = tanf(_max_sun_angular_rad) * tanf(_max_sun_angular_rad) * _sun_brightness_constant;
+
+SkyEditor::SkyEditor(SceneGraph* s) : sun_dir_(1.0f, 0.0f, 0.0f),
+ translation_offset_(0.0f, 0.0f, 0.0f, 0.0f),
+ rotation_zero_(1.0f, 0.0f, 0.0f),
+ scenegraph_(s)
+{
+ m_sun_selected = false;
+
+ m_tool = EditorTypes::NO_TOOL;
+
+ m_sun_angular_rad = _initial_sun_angular_rad;
+ CalcBrightnessFromAngularRad();
+
+ m_sun_color_angle = 0.0f;
+
+ m_sun_translating = false;
+ m_sun_scaling = false;
+ m_sun_rotating = false;
+
+ m_lighting_changed = false;
+
+ m_gl_state.depth_test = false;
+ m_gl_state.cull_face = false;
+ m_gl_state.blend = true;
+ m_gl_state.depth_write = false;
+}
+
+void SkyEditor::Draw() {
+ if (m_sun_selected) {
+ Camera* cam = ActiveCameras::Get();
+ Graphics* graphics = Graphics::Instance();
+ Shaders* shaders = Shaders::Instance();
+ // align with sun ray
+ vec3 origin_dir(0,0,1);
+ float angle = GetAngleBetween(sun_dir_, origin_dir);
+ vec3 around = cross(origin_dir, sun_dir_);
+ quaternion quat_a(vec4(normalize(around), angle * PI_f / 180.0f));
+
+ // align with rotation around z so up y is maintained as (0,1,0)
+ vec3 curr_up(cam->GetUpVector());
+ curr_up = normalize(invert(quat_a)*curr_up);
+ vec3 sun_up_vector(0.0f, 1.0f, 0.0f);
+ curr_up[2] = sun_up_vector[2]; // forces rotation around z only (any rotation not around z would violate this equality
+ angle = GetAngleBetween(sun_up_vector, curr_up);
+ around = cross(curr_up, sun_up_vector);
+ quaternion quat_b(vec4(normalize(around), -angle * PI_f / 180.0f));
+ m_viewing_transform = Mat4FromQuaternion(quat_a * quat_b);
+
+ vec3 camera_pos(cam->GetPos());
+ m_viewing_transform.AddTranslation(camera_pos);
+
+ graphics->setGLState(m_gl_state);
+ graphics->setDepthFunc(GL_LEQUAL);
+
+ mat4 mvp = cam->GetProjMatrix() * cam->GetViewMatrix() * m_viewing_transform;
+
+ vec3 rgb;
+ float scale = tanf(m_sun_angular_rad);
+
+ mat4 circle_scale;
+ circle_scale.SetScale(vec3(scale, scale, 1.0f));
+ mat4 circle_mvp = mvp * circle_scale;
+
+ // Draw circle
+ int shader_id = shaders->returnProgram("3d_color #COLOR_ATTRIB #NO_VELOCITY_BUF");
+ shaders->setProgram(shader_id);
+ shaders->SetUniformMat4("mvp", circle_mvp);
+ GLfloat data[37*7];
+ int index = 0;
+ for(int i=0; i <= 36; i++) {
+ float angle = (float)i*(float)PI/18.0f;
+ if (m_sun_rotating) {
+ rgb = CalcColorFromAngle((float)rad2deg*angle);
+ data[index+0] = rgb[0];
+ data[index+1] = rgb[1];
+ data[index+2] = rgb[2];
+ data[index+3] = 1.0f;
+ } else {
+ data[index+0] = 0.4f;
+ data[index+1] = 0.4f;
+ data[index+2] = 0.4f;
+ data[index+3] = 0.3f;
+ }
+ data[index+4] = cosf(angle);
+ data[index+5] = sinf(angle);
+ data[index+6] = 1.0f;
+ index += 7;
+ }
+ static VBOContainer data_vbo;
+ data_vbo.Fill(kVBODynamic | kVBOFloat, sizeof(data), data);
+ data_vbo.Bind();
+ int vert_attrib_id = shaders->returnShaderAttrib("vert_attrib", shader_id);
+ int color_attrib_id = shaders->returnShaderAttrib("color_attrib", shader_id);
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ graphics->EnableVertexAttribArray(color_attrib_id);
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, 7*sizeof(GLfloat), (const void*)(sizeof(GLfloat) * 4));
+ glVertexAttribPointer(color_attrib_id, 4, GL_FLOAT, false, 7*sizeof(GLfloat), 0);
+ glDrawArrays(GL_LINE_STRIP, 0, 37);
+ graphics->ResetVertexAttribArrays();
+
+ // Draw handle
+ {
+ shader_id = shaders->returnProgram("3d_color #COLOR_UNIFORM #NO_VELOCITY_BUF");
+ shaders->setProgram(shader_id);
+ mat4 translate_mat;
+ translate_mat.SetTranslation(vec3(cosf((float)deg2rad*m_sun_color_angle)*scale, sinf((float)deg2rad*m_sun_color_angle)*scale, 0.0f));
+ mat4 handle_mvp = mvp * translate_mat;
+ shaders->SetUniformMat4("mvp", handle_mvp);
+ rgb = CalcColorFromAngle(m_sun_color_angle);
+ shaders->SetUniformVec4("color_uniform", vec4(rgb, 1.0f));
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+ float data[30];
+ data[0] = 0.0f;
+ data[1] = 0.0f;
+ data[2] = 1.0f;
+ int index = 3;
+ for (float angle=(float)PI*2; angle >= 0; angle-=(float)PI/4.0f) {
+ data[index+0] = cosf(angle)*_color_orb_scale;
+ data[index+1] = sinf(angle)*_color_orb_scale;
+ data[index+2] = 1.0f;
+ index += 3;
+ }
+ data_vbo.Fill(kVBODynamic | kVBOFloat, sizeof(data), data);
+ data_vbo.Bind();
+ int vert_attrib_id = shaders->returnShaderAttrib("vert_attrib", shader_id);
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, 3*sizeof(GLfloat), 0);
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 10);
+ shaders->SetUniformVec4("color_uniform", vec4(vec3(0.4f), 0.3f));
+ glDrawArrays(GL_LINE_STRIP, 1, 9);
+ graphics->ResetVertexAttribArrays();
+ }
+ }
+}
+
+void SkyEditor::UpdateCursor(GameCursor* cursor) {
+ switch(m_tool) {
+ case EditorTypes::TRANSLATE:
+ cursor->SetCursor(TRANSLATE_CURSOR);
+ break;
+ case EditorTypes::SCALE:
+ cursor->SetCursor(SCALE_CURSOR);
+ break;
+ case EditorTypes::ROTATE:
+ cursor->SetCursor(ROTATE_CURSOR);
+ break;
+ default:
+ cursor->SetCursor(DEFAULT_CURSOR);
+ break;
+ }
+}
+
+bool SkyEditor::MouseOverColorOrb(const LineSegment& mouseray) {
+ float scale = tanf(m_sun_angular_rad);
+ vec3 mouseray_dir = normalize(mouseray.end - mouseray.start);
+
+ vec3 p = m_viewing_transform*vec3(cosf((float)deg2rad*m_sun_color_angle)*scale, sinf((float)deg2rad*m_sun_color_angle)*scale, 1.0f);
+ vec3 n = -sun_dir_;
+ float r = _color_orb_scale;
+
+ float to_dist = RayPlaneIntersection(mouseray.start, mouseray_dir, p, n);
+ if (to_dist < 0) return false;
+ vec3 hit_point = mouseray.start + to_dist * mouseray_dir;
+
+ if (length(hit_point - p) < r) return true;
+ else return false;
+}
+
+EditorTypes::Tool SkyEditor::OmniGetTool(float angle_from_sun,
+ const LineSegment& mouseray)
+{
+ if (!m_sun_selected) {
+ return EditorTypes::NO_TOOL;
+ } else if (MouseOverColorOrb(mouseray)) {
+ return EditorTypes::ROTATE;
+ } else if (angle_from_sun <= m_sun_angular_rad*0.8f) {
+ return EditorTypes::TRANSLATE;
+ } else if (angle_from_sun <= m_sun_angular_rad*1.4f) {
+ return EditorTypes::SCALE;
+ } else {
+ return EditorTypes::NO_TOOL;
+ }
+}
+
+bool SkyEditor::HandleSelect(float angle_from_sun) {
+ Mouse* mouse = &(Input::Instance()->getMouse());
+ if (mouse->mouse_double_click_[Mouse::LEFT]) {
+ if (angle_from_sun < PI/50) {
+ m_sun_selected = true;
+ return true;
+ }
+ else {
+ m_sun_selected = false;
+ return true;
+ }
+ }
+ return false;
+}
+
+extern bool shadow_cache_dirty;
+extern bool shadow_cache_dirty_sun_moved;
+
+void SkyEditor::HandleSunTranslate(float angle_from_sun) {
+ Mouse* mouse = &(Input::Instance()->getMouse());
+
+ if (!m_sun_translating// && angle_from_sun <= m_sun_angular_rad
+ && mouse->mouse_down_[Mouse::LEFT] == Mouse::CLICKED && m_sun_selected) { // start
+ HandleTransformationStarted();
+ m_sun_translating = true;
+
+ vec4 sun_center = m_viewing_transform*vec4(0.0f, 0.0f, 1.0f, 1.0f);
+ vec3 ray_through_sun_center = normalize(sun_center.xyz() - ActiveCameras::Get()->GetPos());
+
+ translation_offset_ = vec4(ray_through_sun_center - ActiveCameras::Get()->GetMouseRay(), 0.0f);
+ translation_offset_ = invert(m_viewing_transform)*translation_offset_;
+ }
+ else if (m_sun_translating) {
+ if (mouse->mouse_down_[Mouse::LEFT]) { // continue
+ PlaceSun(ActiveCameras::Get()->GetMouseRay() + (m_viewing_transform*translation_offset_).xyz());
+ }
+ else { // stop
+ PlaceSun(ActiveCameras::Get()->GetMouseRay() + (m_viewing_transform*translation_offset_).xyz());
+ m_sun_translating = false;
+
+ HandleTransformationStopped();
+ }
+ }
+
+ shadow_cache_dirty = true;
+ shadow_cache_dirty_sun_moved = true;
+}
+
+void SkyEditor::HandleSunScale(float angle_from_sun) {
+
+ Mouse* mouse = &(Input::Instance()->getMouse());
+
+ if (!m_sun_scaling// && angle_from_sun > m_sun_angular_rad && angle_from_sun < m_sun_angular_rad*1.2f
+ && mouse->mouse_down_[Mouse::LEFT] == Mouse::CLICKED && m_sun_selected) { // start
+ HandleTransformationStarted();
+ m_sun_scaling = true;
+ scale_angle_zero_ = angle_from_sun;
+
+ if (ActiveCameras::Get()->m_camera_object != NULL) {
+ ActiveCameras::Get()->m_camera_object->IgnoreMouseInput(true);
+ }
+ }
+ else if (m_sun_scaling) {
+ if (mouse->mouse_down_[Mouse::LEFT]) { // continue
+ if (scale_angle_zero_ != 0) {
+ float factor = angle_from_sun / scale_angle_zero_;
+ ScaleSun(factor);
+ scale_angle_zero_ = angle_from_sun;
+ }
+ }
+ else { // stop
+ if (scale_angle_zero_ != 0) {
+ float factor = angle_from_sun / scale_angle_zero_;
+ ScaleSun(factor);
+ scale_angle_zero_ = angle_from_sun;
+ }
+ m_sun_scaling = false;
+
+ if (ActiveCameras::Get()->m_camera_object != NULL) {
+ ActiveCameras::Get()->m_camera_object->IgnoreMouseInput(false);
+ }
+
+ HandleTransformationStopped();
+ }
+ }
+}
+
+static float GetSignedAngleBetween(const vec3& n, const vec3& v1, const vec3& v2) {
+ const vec3 a = normalize(v1);
+ const vec3 b = normalize(v2);
+ float d = dot(a,b);
+ d = clamp(d, -1.0f, 1.0f);
+ if (d != 0.0f ) {
+ vec4 c = cross(a,b);
+ return rad2degf*atan2f(dot(normalize(n),c), d);
+ } else {
+ return 0.0f;
+ }
+}
+
+
+void SkyEditor::HandleSunRotate(float angle_from_sun, const vec3& mouseray, GameCursor* cursor) {
+ Mouse* mouse = &(Input::Instance()->getMouse());
+ if (!m_sun_rotating// && angle_from_sun > m_sun_angular_rad && angle_from_sun < m_sun_angular_rad*1.2f
+ && mouse->mouse_down_[Mouse::LEFT] == Mouse::CLICKED && m_sun_selected) { // start
+ HandleTransformationStarted();
+ m_sun_rotating = true;
+
+ vec3 dir = normalize(mouseray - sun_dir_);
+ cursor->SetCursor(ROTATE_CIRCLE_CURSOR);
+ cursor->SetRotation(90-m_sun_color_angle);
+ rotation_zero_ = dir;
+
+ if (ActiveCameras::Get()->m_camera_object != NULL) {
+ ActiveCameras::Get()->m_camera_object->IgnoreMouseInput(true);
+ }
+ } else if (m_sun_rotating) {
+ if (mouse->mouse_down_[Mouse::LEFT]) { // continue
+ vec3 dir = normalize(mouseray - sun_dir_);
+ float angle = -GetSignedAngleBetween(sun_dir_, dir, rotation_zero_);
+ RotateSun(angle);
+ cursor->SetRotation(90-m_sun_color_angle);
+ rotation_zero_ = dir;
+ } else { // stop
+ vec3 dir = normalize(mouseray - sun_dir_);
+ float angle = -GetSignedAngleBetween(sun_dir_, dir, rotation_zero_);
+ RotateSun(angle);
+ cursor->SetRotation(0);
+ cursor->SetCursor(ROTATE_CIRCLE_CURSOR);
+ rotation_zero_ = dir;
+
+ m_sun_rotating = false;
+
+ if (ActiveCameras::Get()->m_camera_object != NULL) {
+ ActiveCameras::Get()->m_camera_object->IgnoreMouseInput(false);
+ }
+
+ HandleTransformationStopped();
+ }
+ }
+}
+
+void SkyEditor::HandleTransformationStarted() {
+ scenegraph_->map_editor->state_ = MapEditor::kSkyDrag;
+}
+
+void SkyEditor::HandleTransformationStopped() {
+ scenegraph_->map_editor->state_ = MapEditor::kIdle;
+ scenegraph_->sky->LightingChanged(scenegraph_->terrain_object_ != NULL);
+ scenegraph_->map_editor->QueueSaveHistoryState();
+}
+
+void SkyEditor::SaveSky(TiXmlNode* root) {
+ TiXmlElement * sky = new TiXmlElement("Sky");
+ root->LinkEndChild(sky);
+ TiXmlElement* sky_el;
+ sky_el = new TiXmlElement("DomeTexture");
+ sky_el->LinkEndChild( new TiXmlText(scenegraph_->sky->dome_texture_name.c_str()) );
+ sky->LinkEndChild(sky_el);
+
+ std::stringstream num1;
+ std::string num_string;
+
+ num1 << m_sun_angular_rad;
+ sky_el = new TiXmlElement("SunAngularRad");
+ num_string = num1.str();
+ sky_el->LinkEndChild( new TiXmlText(num_string.c_str()) );
+ sky->LinkEndChild(sky_el);
+
+ std::stringstream num2;
+ num2 << ConvertToFirstCycle(m_sun_color_angle, 360.0f);
+ sky_el = new TiXmlElement("SunColorAngle");
+ num_string = num2.str();
+ sky_el->LinkEndChild( new TiXmlText(num_string.c_str()) );
+ sky->LinkEndChild(sky_el);
+
+ sky_el = new TiXmlElement("RayToSun");
+ sky_el->SetDoubleAttribute("r0", sun_dir_[0]);
+ sky_el->SetDoubleAttribute("r1", sun_dir_[1]);
+ sky_el->SetDoubleAttribute("r2", sun_dir_[2]);
+ sky->LinkEndChild(sky_el);
+}
+
+
+// Transforms
+
+void SkyEditor::PlaceSun(const vec3& dir) {
+ sun_dir_ = normalize(dir);
+ scenegraph_->primary_light.pos = sun_dir_;
+ flare->position = sun_dir_;
+ m_lighting_changed = true;
+}
+
+void SkyEditor::TranslateSun(const vec3& trans) {
+ sun_dir_ = normalize(sun_dir_ + trans);
+ scenegraph_->primary_light.pos = sun_dir_;
+ flare->position = sun_dir_;
+ m_lighting_changed = true;
+}
+
+void SkyEditor::ScaleSun(float factor) {
+ float new_angular_rad = m_sun_angular_rad * factor;
+ new_angular_rad = clamp(new_angular_rad,
+ _min_sun_angular_rad,
+ _max_sun_angular_rad);
+ m_sun_angular_rad = new_angular_rad;
+ // scale brightness to be proportional to sun's surface area
+ CalcBrightnessFromAngularRad();
+ flare->diffuse = m_sun_brightness;
+ scenegraph_->primary_light.intensity = 1.0f;
+ if(m_sun_brightness>_no_glare_threshold){
+ float range = _max_sun_brightness-_no_glare_threshold;
+ float intensity = max(0.0f,1.0f-(m_sun_brightness-_no_glare_threshold)/range);
+ scenegraph_->primary_light.intensity = intensity;
+ }
+ if(m_sun_brightness<_sharp_glare_threshold){
+ float range = _sharp_glare_threshold-_min_sun_brightness;
+ float intensity = max(0.0f,1.0f+(_sharp_glare_threshold-m_sun_brightness)*2.0f/range);
+ scenegraph_->primary_light.intensity = intensity;
+ }
+ //printf("brightness = %g\n", m_sun_brightness);
+}
+
+void SkyEditor::RotateSun(float angle) {
+ //RotateHueHSV(m_sun_color_angle, angle);
+ //vec4 rgb = HSVtoRGB(m_sun_color_angle,0.25f,1.0f);
+ m_sun_color_angle += angle;
+ vec3 rgb = CalcColorFromAngle(m_sun_color_angle);
+ scenegraph_->primary_light.color = rgb;
+ flare->color = rgb;
+}
+
+void SkyEditor::CalcBrightnessFromAngularRad() {
+ float size = tan(m_sun_angular_rad);
+ m_sun_brightness = size*size*_sun_brightness_constant;
+}
+
+// white -> yellow -> orange -> red -> violet -> blue-white
+static const vec3 WHITE(1.0f, 1.0f, 1.0f);
+static const vec3 YELLOW(252.0f/255.0f, 1.0f, 178.0f/255.0f);
+static const vec3 ORANGE(1.0f, 223.0f/255.0f, 178.0f/255.0f);
+static const vec3 RED(1.0f, 178.0f/255.0f, 178.0f/255.0f);
+static const vec3 VIOLET(1.0f, 178.0f/255.0f, 232.0f/255.0f);
+static const vec3 BLUE_WHITE(225.0f/255.0f, 248.0f/255.0f, 1.0f);
+
+vec3 SkyEditor::CalcColorFromAngle(float angle) {
+ angle = ConvertToFirstCycle(angle, 360.0f);
+ if (angle < 60) {
+ return Interpolate(WHITE, YELLOW, angle/60.0f);
+ } else if (angle < 120) {
+ return Interpolate(YELLOW, ORANGE, (angle-60.0f)/60.0f);
+ } else if (angle < 180) {
+ return Interpolate(ORANGE, RED, (angle-120.0f)/60.0f);
+ } else if (angle < 240) {
+ return Interpolate(RED, VIOLET, (angle-180.0f)/60.0f);
+ } else if (angle < 300) {
+ return Interpolate(VIOLET, BLUE_WHITE, (angle-240.0f)/60.0f);
+ } else {
+ return Interpolate(BLUE_WHITE, WHITE, (angle-300.0f)/60.0f);
+ }
+}
+
+void SkyEditor::SaveHistoryState( std::list<SavedChunk> &chunk_list, int state_id ) {
+ SavedChunk saved_chunk;
+ saved_chunk.obj_id = 0;
+ saved_chunk.type = ChunkType::SKY_EDITOR;
+
+ saved_chunk.desc.AddFloat(EDF_SUN_RADIUS, m_sun_angular_rad);
+ saved_chunk.desc.AddFloat(EDF_SUN_COLOR_ANGLE, m_sun_color_angle);
+ saved_chunk.desc.AddVec3(EDF_SUN_DIRECTION, sun_dir_);
+
+ AddChunkToHistory(chunk_list, state_id, saved_chunk);
+}
+
+bool SkyEditor::ReadChunk( SavedChunk &the_chunk ) {
+ bool something_changed = false;
+ EntityDescription &desc = the_chunk.desc;
+ for(unsigned i=0; i<desc.fields.size(); ++i){
+ EntityDescriptionField& field = desc.fields[i];
+ switch(field.type){
+ case EDF_SUN_RADIUS:{
+ float old_m_sun_angular_rad = m_sun_angular_rad;
+ memread(&m_sun_angular_rad, sizeof(float), 1, field.data);
+ if(m_sun_angular_rad != old_m_sun_angular_rad){
+ something_changed = true;
+ }
+ break;}
+ case EDF_SUN_COLOR_ANGLE:{
+ float old_m_sun_color_angle = m_sun_color_angle;
+ memread(&m_sun_color_angle, sizeof(float), 1, field.data);
+ if(m_sun_color_angle != old_m_sun_color_angle){
+ something_changed = true;
+ }
+ break;}
+ case EDF_SUN_DIRECTION: {
+ vec3 old_m_sun_ray = sun_dir_;
+ memread(&sun_dir_.entries, sizeof(float), 3, field.data);
+ if(sun_dir_ != old_m_sun_ray){
+ something_changed = true;
+ }
+ break;}
+ }
+ }
+ if(something_changed){
+ PlaceSun(sun_dir_);
+ ScaleSun(1.0f);
+ RotateSun(0.0f);
+ }
+ return something_changed;
+}
+
+void SkyEditor::ApplySkyInfo( const SkyInfo &si ) {
+ scenegraph_->sky->dome_texture_name = si.dome_texture_path;
+ PlaceSun(si.ray_to_sun);
+ m_sun_angular_rad = si.sun_angular_rad;
+ ScaleSun(1.0f);
+ RotateSun(si.sun_color_angle);
+ scenegraph_->sky->level_name = scenegraph_->level_name_;
+ m_lighting_changed = false;
+}
diff --git a/Source/Editors/sky_editor.h b/Source/Editors/sky_editor.h
new file mode 100644
index 00000000..9b20f551
--- /dev/null
+++ b/Source/Editors/sky_editor.h
@@ -0,0 +1,136 @@
+//-----------------------------------------------------------------------------
+// Name: sky_editor.h
+// Developer: Wolfire Games LLC
+// Author: Phillip Isola
+// Description: Editor for the sky
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Editors/editor_types.h>
+#include <Editors/save_state.h>
+
+#include <Graphics/glstate.h>
+#include <Internal/levelxml.h>
+#include <Math/mat4.h>
+
+struct LineSegment;
+class SceneGraph;
+class TiXmlDocument;
+class TiXmlNode;
+struct Flare;
+class GameCursor;
+
+/// The SkyEditor class allows the player to drag, tint, and resize the sun.
+class SkyEditor {
+public:
+ /**
+ * Initialize a new SkyEditor.
+ * @param s A pointer to the active scenegraph.
+ */
+ SkyEditor(SceneGraph* s);
+
+ /**
+ * Draw the controls for the sky editor.
+ * Namely, the sun radius circle and the color picker circle.
+ */
+ void Draw();
+
+ /**
+ * Set the cursor to reflect the value of m_tool.
+ */
+ void UpdateCursor(GameCursor* cursor);
+
+ /**
+ * Load the sky data from the level xml file.
+ * @param filename The path to the level xml file.
+ */
+ void ApplySkyInfo( const SkyInfo &si );
+
+ /**
+ * Save the sky data to a level xml file.
+ * @param doc A reference to the level xml file.
+ */
+ void SaveSky(TiXmlNode* root);
+
+ void SaveHistoryState( std::list<SavedChunk> &chunk_list, int state_id );
+ bool ReadChunk( SavedChunk &saved_chunk );
+
+ // Control and tool handlers
+ EditorTypes::Tool OmniGetTool(float angle_from_sun, const LineSegment& mouseray);
+ bool HandleSelect(float angle_from_sun); // returns true is something happened
+ void HandleSunTranslate(float angle_from_sun);
+ void HandleSunScale(float angle_from_sun);
+ void HandleSunRotate(float angle_from_sun, const vec3& mouseray, GameCursor* cursor);
+ void HandleTransformationStarted();
+ void HandleTransformationStopped();
+
+ // Transformations
+ void PlaceSun(const vec3& dir);
+ void TranslateSun(const vec3& trans);
+ void ScaleSun(float factor);
+ void RotateSun(float angle);
+
+ bool m_sun_selected; /// Whether the sun is selected or not.
+
+ EditorTypes::Tool m_tool; /// Which tool is active.
+
+ Flare* flare;
+ vec3 sun_dir_; /// ray >>to<< sun.
+
+ vec4 translation_offset_; /// Used to drag the sun from points other than the center.
+ float scale_angle_zero_; /// The original value of m_sun_angular_rad when scaling begins.
+ vec3 rotation_zero_; /// The original rotation value when rotation begins.
+
+ float m_sun_angular_rad; /// Angle between the center of the sun and a point on the ring.
+ float m_sun_brightness; /// The 'brightness' (size) of the sun.
+ float m_sun_color_angle; /// Angle of the sun on the color wheel (in degrees).
+
+ bool m_sun_translating; /// Currently dragging sun to translate.
+ bool m_sun_scaling; /// Currently dragging scale in and out.
+ bool m_sun_rotating; /// Currently dragging on color wheel.
+
+ GLState m_gl_state; /// GLState for drawing the editor lines
+
+ mat4 m_viewing_transform; /// Transformation from world space to editor space.
+
+ SceneGraph* scenegraph_; /// Pointer to relevant scenegraph.
+
+ bool m_lighting_changed; /// Whether the sun has changed or not.
+
+ /**
+ * Takes a mouseray and returns whether it intersects the color wheel orb.
+ * @param mouseray The mouse ray vector.
+ * @return True if the mouseray intersects the orb.
+ */
+ bool MouseOverColorOrb(const LineSegment& mouseray);
+
+ /**
+ * Calculates m_sun_brightness from m_sun_angular_rad.
+ */
+ void CalcBrightnessFromAngularRad();
+
+ /**
+ * Calculates the sun color from an angle on the color wheel.
+ * @param angle The angle on the color wheel.
+ * @return A vec3 representing the color.
+ */
+ vec3 CalcColorFromAngle(float angle); // angle in degrees
+};
diff --git a/Source/GUI/IMUI/im_behaviors.cpp b/Source/GUI/IMUI/im_behaviors.cpp
new file mode 100644
index 00000000..e020ee0d
--- /dev/null
+++ b/Source/GUI/IMUI/im_behaviors.cpp
@@ -0,0 +1,754 @@
+//-----------------------------------------------------------------------------
+// Name: im_behaviors.cpp
+// Developer: Wolfire Games LLC
+// Description: A collection of useful behaviors for the UI tools
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "im_behaviors.h"
+
+/*************************************
+*****************
+*******
+*
+* update behaviors
+*
+*******/
+
+/*******
+*
+* Tweens
+*
+*/
+
+/**
+* Fades the element in over a given time
+**/
+
+IMFadeIn::IMFadeIn( uint64_t time, IMTweenType _tweener ) :
+ elapsed( 0 )
+{
+ tweenType = _tweener;
+ tweener = getTweenInstance( tweenType );
+ targetTime = time;
+ tweener->compute( 0, 0.1f, 0.5, float( targetTime ) );
+}
+
+
+bool IMFadeIn::initialize( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+
+ originalAlpha = element->getAlpha();
+ element->setEffectAlpha( 0.0f );
+ return true;
+
+}
+
+bool IMFadeIn::update( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+
+ float newValue = tweener->compute( float(elapsed), 0.0f, originalAlpha, float( targetTime ) );
+
+ element->setEffectAlpha(newValue );
+
+ elapsed += delta;
+
+ if( elapsed >= targetTime ) {
+ // We're done so inform the element to remove this tween
+ element->clearEffectAlpha();
+ return false;
+ }
+ else {
+ // keep going
+ return true;
+ }
+
+}
+
+void IMFadeIn::cleanUp( IMElement* element ) {
+ element->clearEffectAlpha();
+}
+
+/**
+* Uses rendering displacement to 'move in' the element from a given offset
+**/
+IMMoveIn::IMMoveIn( uint64_t time, vec2 _offset, IMTweenType _tweener ) :
+ elapsed(0),
+ targetTime(time),
+ tweenType(_tweener),
+ offset(_offset)
+{
+ tweener = getTweenInstance(tweenType);
+}
+
+bool IMMoveIn::initialize( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ element->setDisplacement( offset );
+ return true;
+}
+
+bool IMMoveIn::update( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+
+ elapsed += delta;
+
+ float tweenValue = tweener->compute( (float) elapsed, 0.0f, 1.0f, (float) targetTime );
+
+ vec2 newDisplacement = vec2( mix( offset.x(), 0.0f, tweenValue ),
+ mix( offset.y(), 0.0f, tweenValue ) );
+
+ element->setDisplacement( newDisplacement );
+
+ if( elapsed >= targetTime ) {
+ // We're done so inform the element to remove this tween
+ element->setDisplacement( vec2(0, 0) );
+ return false;
+ }
+ else {
+ // keep going
+ return true;
+ }
+}
+
+
+/**
+* Transition text by fading in and fading out
+**/
+// NOTE: Target must be a text element
+/**
+* Fades the element in over a given time
+**/
+
+IMChangeTextFadeOutIn::IMChangeTextFadeOutIn( uint64_t time, std::string const& _targetText, IMTweenType _tweenerOut, IMTweenType _tweenerIn ) :
+ elapsed(0)
+{
+ tweenerOutType = _tweenerOut;
+ tweenerInType = _tweenerIn;
+ tweenerOut = getTweenInstance( tweenerOutType );
+ tweenerIn = getTweenInstance( tweenerInType );
+ targetTime = time;
+ targetText = _targetText;
+ fadeAwayDone = false;
+}
+
+bool IMChangeTextFadeOutIn::initialize( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+
+ originalAlpha = element->getAlpha();
+ return true;
+
+}
+
+bool IMChangeTextFadeOutIn::update( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+
+ if( !fadeAwayDone ) {
+ float newValue = tweenerOut->compute( float(elapsed), originalAlpha, 0.0f, float( targetTime ) );
+
+ element->setEffectAlpha(newValue );
+
+ elapsed += delta;
+
+ if( elapsed >= targetTime ) {
+ // We're done so change the text
+ IMText* textElement = static_cast<IMText*>( element );
+
+ textElement->setText( targetText );
+ textElement->setEffectAlpha( 0.0f );
+ elapsed = 0;
+
+ fadeAwayDone = true;
+
+ return true;
+ }
+ else {
+ // keep going
+ return true;
+ }
+ }
+ else {
+ float newValue = tweenerIn->compute( float(elapsed), 0.0f, originalAlpha, float( targetTime ) );
+
+ element->setEffectAlpha(newValue );
+
+ elapsed += delta;
+
+ if( elapsed >= targetTime ) {
+ // We're done so inform the element to remove this tween
+ element->clearEffectAlpha();
+ return false;
+ }
+ else {
+ // keep going
+ return true;
+ }
+ }
+}
+
+
+/**
+* Transition image by fading in and fading out
+**/
+// NOTE: Target must be a image element
+
+
+IMChangeImageFadeOutIn::IMChangeImageFadeOutIn( uint64_t time, std::string const& _targetImage, IMTweenType _tweenerOut, IMTweenType _tweenerIn ) :
+elapsed(0)
+{
+ tweenerOutType = _tweenerOut;
+ tweenerInType = _tweenerIn;
+ tweenerOut = getTweenInstance( tweenerOutType );
+ tweenerIn = getTweenInstance( tweenerInType );
+ targetTime = time;
+ targetImage = _targetImage;
+ fadeAwayDone = false;
+}
+
+bool IMChangeImageFadeOutIn::initialize( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+
+ originalAlpha = element->getAlpha();
+ return true;
+
+}
+
+bool IMChangeImageFadeOutIn::update( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+
+ if( !fadeAwayDone ) {
+ float newValue = tweenerOut->compute( float(elapsed), originalAlpha, 0.0f, float( targetTime ) );
+
+ element->setEffectAlpha(newValue );
+
+ elapsed += delta;
+
+ if( elapsed >= targetTime ) {
+ // We're done so change the image
+ IMImage* imageElement = static_cast<IMImage*>( element );
+
+ // store the old x value so we can rescale
+ float oldX = imageElement->getSizeX();
+
+ imageElement->setImageFile( targetImage );
+ imageElement->scaleToSizeX( oldX );
+ imageElement->setEffectAlpha( 0.0f );
+ elapsed = 0;
+
+ fadeAwayDone = true;
+
+ return true;
+ }
+ else {
+ // keep going
+ return true;
+ }
+ }
+ else {
+ float newValue = tweenerIn->compute( float(elapsed), 0.0f, originalAlpha, float( targetTime ) );
+
+ element->setEffectAlpha(newValue );
+
+ elapsed += delta;
+
+ if( elapsed >= targetTime ) {
+ // We're done so inform the element to remove this tween
+ element->clearEffectAlpha();
+ return false;
+ }
+ else {
+ // keep going
+ return true;
+ }
+ }
+
+}
+
+/*******
+*
+* Continuous update behaviors
+*
+*/
+
+/**
+* Continually pulse the alpha of this element
+**/
+
+IMPulseAlpha::IMPulseAlpha( float lower, float upper, float _speed ) {
+ speed = _speed;
+ midPoint = (lower + upper )/2;
+ difference = (lower - upper )/2;
+}
+
+bool IMPulseAlpha::initialize( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ return true;
+}
+
+float IMPulseAlpha::computeTransition( float base, float range ) {
+ return base + (float) sin( float( elapsedTime ) / (1000.0 / speed ) * (2.0 * PI) ) * range;
+}
+
+bool IMPulseAlpha::update( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ float currentAlpha = computeTransition( midPoint, difference );
+ element->setEffectAlpha( currentAlpha );
+ elapsedTime += delta;
+
+ return true;
+
+}
+
+/**
+* Continually pulse the alpha of this element's border
+**/
+
+
+IMPulseBorderAlpha::IMPulseBorderAlpha( float lower, float upper, float _speed ) {
+ speed = _speed;
+ midPoint = (lower + upper )/2;
+ difference = (lower - upper )/2;
+}
+
+bool IMPulseBorderAlpha::initialize( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ return true;
+}
+
+float IMPulseBorderAlpha::computeTransition( float base, float range ) {
+ return base + (float) sin( float( elapsedTime ) / (1000.0 / speed ) * (2.0 * PI) ) * range;
+}
+
+bool IMPulseBorderAlpha::update( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ float currentAlpha = computeTransition( midPoint, difference );
+ element->setBorderAlpha( currentAlpha );
+ elapsedTime += delta;
+
+ return true;
+
+}
+
+
+/*************************************
+*****************
+*******
+*
+* mouse over behaviors
+*
+*******/
+
+/**
+* Scales when the mouse is over
+**/
+
+IMMouseOverScale::IMMouseOverScale( uint64_t time, float _offset, IMTweenType _tweener ) :
+ elapsed(0),
+ targetTime(time),
+ tweenType(_tweener)
+{
+ tweener = getTweenInstance(tweenType);
+ offset = _offset;
+}
+
+void IMMouseOverScale::onStart( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ IMImage* imageElement = static_cast<IMImage*>( element );
+ // store the old x value so we can rescale
+ oldX = imageElement->getSizeX();
+ oldY = imageElement->getSizeY();
+ elapsed = 0;
+}
+
+float IMMouseOverScale::computeTransition( float base, float range ) {
+ return base + (float)sin( (float(elapsedTime) / (1000.0 * speed )) * (2.0 * PI) ) * range;
+}
+
+void IMMouseOverScale::onContinue( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ elapsed += delta;
+ if(elapsed > targetTime) {
+ elapsed = targetTime;
+ }
+
+ float tweenValue = tweener->compute( (float)elapsed, 0.0f, offset, (float)targetTime );
+
+ IMImage* imageElement = static_cast<IMImage*>( element );
+
+ /*if( elapsed >= targetTime ) {
+ // We're done so inform the element to remove this tween
+ }
+ else {*/
+ // keep going
+ //imageElement->scaleToSizeX( oldX + tweenValue );
+ imageElement->setSizeX( oldX + tweenValue );
+ imageElement->setSizeY( oldY + tweenValue );
+ float offset = (tweenValue / 2.0f) * -1.0f;
+ element->setDisplacement(vec2( offset, offset ));
+ //}
+}
+
+bool IMMouseOverScale::onFinish( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ IMImage* imageElement = static_cast<IMImage*>( element );
+ //imageElement->scaleToSizeX( oldX );
+ imageElement->setSizeX( oldX );
+ imageElement->setSizeY( oldY );
+ element->setDisplacement( vec2(0, 0) );
+ return true;
+}
+
+/**
+* Moves when the mouse is over
+**/
+
+IMMouseOverMove::IMMouseOverMove( uint64_t time, vec2 _offset, IMTweenType _tweener ) :
+ elapsed(0),
+ targetTime(time),
+ tweenType(_tweener),
+ offset(_offset)
+{
+ tweener = getTweenInstance(tweenType);
+}
+
+void IMMouseOverMove::onStart( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ element->setDisplacement( vec2(0, 0) );
+ elapsed = 0;
+}
+
+float IMMouseOverMove::computeTransition( float base, float range ) {
+ return base + (float) sin( (float(elapsedTime) / (1000.0 * speed )) * (2.0 * PI) ) * range;
+}
+
+void IMMouseOverMove::onContinue( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ elapsed += delta;
+
+ float tweenValue = tweener->compute( (float)elapsed, 0.0f, 1.0f, (float)targetTime );
+
+ vec2 newDisplacement = vec2( mix( 0.0f, offset.x(), tweenValue ),
+ mix( 0.0f, offset.y(), tweenValue ) );
+
+ element->setDisplacement( newDisplacement );
+
+ if( elapsed >= targetTime ) {
+ // We're done so inform the element to remove this tween
+ element->setDisplacement( offset );
+ }
+}
+
+bool IMMouseOverMove::onFinish( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ element->setDisplacement( vec2(0, 0) );
+ return true;
+}
+
+/**
+* Shows the border when the mouse is over
+**/
+
+
+IMMouseOverShowBorder::IMMouseOverShowBorder() {
+}
+
+void IMMouseOverShowBorder::onStart( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ originalBorderState = element->border;
+ element->showBorder();
+}
+
+void IMMouseOverShowBorder::onContinue( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+}
+
+bool IMMouseOverShowBorder::onFinish( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ element->showBorder( originalBorderState );
+ return true;
+}
+
+
+/**
+* Causes the element to pulse between two given colors when the mouse is hovering
+**/
+IMMouseOverPulseColor::IMMouseOverPulseColor( vec4 first, vec4 second, float _speed ) {
+
+ speed = _speed;
+
+ midPoints = vec4( (first.x() + second.x()) /2.0f,
+ (first.y() + second.y()) /2.0f,
+ (first.z() + second.z()) /2.0f,
+ (first.a() + second.a()) /2.0f );
+
+ differences = vec4( (first.x() - second.x())/2.0f,
+ (first.y() - second.y())/2.0f,
+ (first.z() - second.z())/2.0f,
+ (first.a() - second.a())/2.0f );
+
+}
+
+void IMMouseOverPulseColor::onStart( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ originalColor = element->getColor();
+ elapsedTime = 0;
+}
+
+float IMMouseOverPulseColor::computeTransition( float base, float range ) {
+ return base + (float) sin( (float(elapsedTime) / (1000.0 * speed )) * (2.0 * PI) ) * range;
+}
+
+void IMMouseOverPulseColor::onContinue( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ vec4 currentColor = vec4( computeTransition( midPoints.x(), differences.x() ),
+ computeTransition( midPoints.y(), differences.y() ),
+ computeTransition( midPoints.z(), differences.z() ),
+ computeTransition( midPoints.a(), differences.a() ) );
+
+ element->setEffectColor( currentColor );
+ elapsedTime += delta;
+}
+
+bool IMMouseOverPulseColor::onFinish( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ element->clearColorEffect();
+ return true;
+}
+
+
+/**
+* Causes the element's border to pulse between two given colors when the mouse is hovering
+**/
+
+IMMouseOverPulseBorder::IMMouseOverPulseBorder( vec4 first, vec4 second, float _speed ) {
+
+ speed = _speed;
+
+ midPoints = vec4( first.x() + second.x() /2,
+ first.y() + second.y() /2,
+ first.z() + second.z() /2,
+ first.a() + second.a() /2 );
+
+ differences = vec4( (first.x() - second.x())/2,
+ (first.y() - second.y())/2,
+ (first.z() - second.z())/2,
+ (first.a() - second.a())/2 );
+
+}
+
+void IMMouseOverPulseBorder::onStart( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ originalColor = element->getBorderColor();
+ elapsedTime = 0;
+}
+
+float IMMouseOverPulseBorder::computeTransition( float base, float range ) {
+ return base + (float) sin( float( elapsedTime ) / (1000.0 / speed ) * (2.0 * PI) ) * range;
+}
+
+void IMMouseOverPulseBorder::onContinue( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ vec4 currentColor = vec4( computeTransition( midPoints.x(), differences.x() ),
+ computeTransition( midPoints.y(), differences.y() ),
+ computeTransition( midPoints.z(), differences.z() ),
+ computeTransition( midPoints.a(), differences.a() ) );
+
+ element->setBorderColor( currentColor );
+
+ elapsedTime += delta;
+}
+
+bool IMMouseOverPulseBorder::onFinish( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ element->setBorderColor( originalColor );
+ return true;
+}
+
+/**
+* Just pulse the border alpha
+**/
+
+
+IMMouseOverPulseBorderAlpha::IMMouseOverPulseBorderAlpha( float lower, float upper, float _speed ) {
+
+ speed = _speed;
+ midPoint = (lower + upper )/2;
+ difference = (lower - upper )/2;
+
+}
+
+void IMMouseOverPulseBorderAlpha::onStart( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ originalAlpha = element->getBorderAlpha();
+ elapsedTime = 0;
+}
+
+float IMMouseOverPulseBorderAlpha::computeTransition( float base, float range ) {
+ return base + (float) sin( float( elapsedTime ) / (1000.0 / speed ) * (2.0 * PI) ) * range;
+}
+
+void IMMouseOverPulseBorderAlpha::onContinue( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ float currentAlpha = computeTransition( midPoint, difference );
+ element->setBorderAlpha( currentAlpha );
+ elapsedTime += delta;
+}
+
+bool IMMouseOverPulseBorderAlpha::onFinish( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ element->setBorderAlpha( originalAlpha );
+ return true;
+}
+
+/**
+* Fades the element in from 0.0 to 1.0f alpha
+**/
+IMMouseOverFadeIn::IMMouseOverFadeIn( uint64_t time, IMTweenType _tweener, float targetAlpha/*= 1.0f*/ ) :
+ elapsed( 0 ),
+ targetAlpha( targetAlpha )
+{
+ tweenType = _tweener;
+ tweener = getTweenInstance( tweenType );
+ targetTime = time;
+ tweener->compute(0.0f, 0.0f, 1.0f, float( targetTime ) );
+}
+
+void IMMouseOverFadeIn::onStart( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ elapsed = 0;
+}
+
+void IMMouseOverFadeIn::onContinue( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ if(elapsed > targetTime) {
+ elapsed = targetTime;
+ }
+ float newValue = tweener->compute( float(elapsed), 0.0f, targetAlpha, float( targetTime ) );
+ element->setEffectAlpha(newValue );
+ elapsed += delta;
+}
+
+bool IMMouseOverFadeIn::onFinish( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ element->clearColorEffect();
+ return true;
+}
+
+/**
+* Sends a specific message on the different mouse over states
+**/
+
+IMFixedMessageOnMouseOver::IMFixedMessageOnMouseOver( IMMessage* _enterMessage,
+ IMMessage* _overMessage,
+ IMMessage* _leaveMessage ) {
+
+ enterMessage = _enterMessage;
+ overMessage = _overMessage;
+ leaveMessage = _leaveMessage;
+
+}
+
+void IMFixedMessageOnMouseOver::onStart( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ if( enterMessage != NULL ) {
+ enterMessage->AddRef();
+ element->sendMessage( enterMessage );
+ }
+}
+
+void IMFixedMessageOnMouseOver::onContinue( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ if( overMessage != NULL ) {
+ overMessage->AddRef();
+ element->sendMessage( overMessage );
+ }
+}
+
+bool IMFixedMessageOnMouseOver::onFinish( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ if( leaveMessage != NULL ) {
+ leaveMessage->AddRef();
+ element->sendMessage( leaveMessage );
+ }
+ return true;
+}
+
+
+/*************************************
+*****************
+*******
+*
+* mouse click behaviors
+*
+*******/
+
+/**
+* Sends a specific message on click (mouse *up*)
+**/
+
+
+/*******************************************************************************************/
+/**
+ * @brief Various constructors
+ *
+ */
+IMFixedMessageOnClick::IMFixedMessageOnClick( std::string const& messageName ) {
+ theMessage = new IMMessage(messageName);
+}
+
+IMFixedMessageOnClick::IMFixedMessageOnClick( std::string const& messageName, int param ) {
+ theMessage = new IMMessage(messageName, param);
+}
+
+IMFixedMessageOnClick::IMFixedMessageOnClick( std::string const& messageName, std::string param ) {
+ theMessage = new IMMessage(messageName, param);
+}
+
+IMFixedMessageOnClick::IMFixedMessageOnClick( std::string const& messageName, float param ) {
+ theMessage = new IMMessage(messageName, param);
+}
+
+IMFixedMessageOnClick::IMFixedMessageOnClick( IMMessage* message ) {
+ theMessage = message;
+}
+
+bool IMFixedMessageOnClick::onUp( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ if( theMessage ) {
+ theMessage->AddRef();
+ element->sendMessage( theMessage );
+ }
+ return true;
+}
+
+IMMouseOverSound::IMMouseOverSound(std::string const& audioFile) {
+ this->audioFile = audioFile;
+}
+
+IMMouseOverSound::~IMMouseOverSound() {
+}
+
+void IMMouseOverSound::onStart(IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate) {
+ SoundGroupRef sgr = Engine::Instance()->GetAssetManager()->LoadSync<SoundGroup>(audioFile);
+ SoundGroupPlayInfo sgpi = SoundGroupPlayInfo(*sgr, vec3(0.0f));
+ sgpi.flags = sgpi.flags | SoundFlags::kRelative;
+
+ int audioHandle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->PlayGroup(audioHandle, sgpi);
+}
+
+void IMMouseOverSound::onContinue(IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate) {
+
+}
+
+bool IMMouseOverSound::onFinish(IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate) {
+ return true;
+}
+
+IMSoundOnClick::IMSoundOnClick(std::string const& audioFile) {
+ this->audioFile = audioFile;
+}
+
+bool IMSoundOnClick::onUp(IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate) {
+ SoundGroupRef sgr = Engine::Instance()->GetAssetManager()->LoadSync<SoundGroup>(audioFile);
+ SoundGroupPlayInfo sgpi = SoundGroupPlayInfo(*sgr, vec3(0.0f));
+ sgpi.flags = sgpi.flags | SoundFlags::kRelative;
+
+ int audioHandle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->PlayGroup(audioHandle, sgpi);
+ return true;
+}
+
+IMFuncOnClick::IMFuncOnClick(asIScriptFunction * func) {
+ func->AddRef();
+ this->func = func;
+}
+
+bool IMFuncOnClick::onUp(IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate) {
+ asIScriptContext* ctx = func->GetEngine()->CreateContext();
+ ctx->Prepare(func);
+ int r = ctx->Execute();
+ if(r != asEXECUTION_FINISHED) {
+ LOGE << "There was an issue executing an IMFuncOnClick function! Error code: " << r << std::endl;
+ }
+ ctx->Release();
+ return true;
+}
diff --git a/Source/GUI/IMUI/im_behaviors.h b/Source/GUI/IMUI/im_behaviors.h
new file mode 100644
index 00000000..9d3bf0fa
--- /dev/null
+++ b/Source/GUI/IMUI/im_behaviors.h
@@ -0,0 +1,740 @@
+//-----------------------------------------------------------------------------
+// Name: im_behaviors.cpp
+// Developer: Wolfire Games LLC
+// Description: A collection of useful behaviors for the UI tools
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+#include "im_behaviors.h"
+
+#include <GUI/IMUI/imgui.h>
+#include <GUI/IMUI/im_support.h>
+#include <GUI/IMUI/im_element.h>
+#include <GUI/IMUI/im_text.h>
+#include <GUI/IMUI/im_image.h>
+#include <GUI/IMUI/im_tween.h>
+
+#include <Sound/sound.h>
+#include <Main/engine.h>
+
+
+/*************************************
+ *****************
+ *******
+ *
+ * update behaviors
+ *
+ *******/
+
+ /*******
+ *
+ * Tweens
+ *
+ */
+
+/**
+ * Fades the element in over a given time
+ **/
+struct IMFadeIn : public IMUpdateBehavior {
+
+ IMTween* tweener;
+ IMTweenType tweenType;
+ uint64_t elapsed;
+ uint64_t targetTime;
+ float originalAlpha;
+
+ IMFadeIn( uint64_t time, IMTweenType _tweener );
+
+ static IMFadeIn* ASFactory( uint64_t time, IMTweenType _tweener ) {
+ return new IMFadeIn(time, _tweener);
+ }
+
+ bool initialize( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+ bool update( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+ void cleanUp( IMElement* element );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Create a copy of this object (respecting inheritance)
+ *
+ */
+ virtual IMUpdateBehavior* clone() {
+ IMFadeIn* c = new IMFadeIn(targetTime, tweenType);
+ c->originalAlpha = originalAlpha;
+ return c;
+ }
+
+};
+
+/**
+ * Uses rendering displacement to 'move in' the element from a given offset
+ **/
+struct IMMoveIn : public IMUpdateBehavior {
+
+ IMTween* tweener;
+ IMTweenType tweenType;
+ uint64_t elapsed;
+ uint64_t targetTime;
+ vec2 offset;
+
+ IMMoveIn( uint64_t time, vec2 _offset, IMTweenType _tweener );
+
+ static IMMoveIn* ASFactory( uint64_t _time, vec2 _offset, IMTweenType _tweener ) {
+ return new IMMoveIn( _time, _offset, _tweener);
+ }
+
+ bool initialize( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+ bool update( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Create a copy of this object (respecting inheritance)
+ *
+ */
+ virtual IMUpdateBehavior* clone() {
+ IMMoveIn* c = new IMMoveIn(targetTime, offset, tweenType);
+ return c;
+ }
+
+};
+
+/**
+ * Transition text by fading in and fading out
+ **/
+// NOTE: Target must be a text element
+/**
+ * Fades the element in over a given time
+ **/
+struct IMChangeTextFadeOutIn : public IMUpdateBehavior {
+
+ IMTween* tweenerOut;
+ IMTween* tweenerIn;
+
+ IMTweenType tweenerOutType;
+ IMTweenType tweenerInType;
+
+ uint64_t elapsed;
+ uint64_t targetTime;
+ float originalAlpha;
+ std::string targetText;
+ bool fadeAwayDone;
+
+ IMChangeTextFadeOutIn( uint64_t time, std::string const& _targetText, IMTweenType _tweenerOut, IMTweenType _tweenerIn );
+
+ static IMChangeTextFadeOutIn* ASFactory( uint64_t time, std::string const& _targetText, IMTweenType _tweenerOut, IMTweenType _tweenerIn ) {
+ return new IMChangeTextFadeOutIn(time, _targetText, _tweenerOut, _tweenerIn);
+ }
+
+ bool initialize( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+ bool update( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Create a copy of this object (respecting inheritance)
+ *
+ */
+ virtual IMUpdateBehavior* clone() {
+ IMChangeTextFadeOutIn* c = new IMChangeTextFadeOutIn(targetTime, targetText, tweenerOutType, tweenerInType);
+ return c;
+ }
+
+};
+
+/**
+ * Transition image by fading in and fading out
+ **/
+// NOTE: Target must be a image element
+
+struct IMChangeImageFadeOutIn : public IMUpdateBehavior {
+
+ IMTween* tweenerOut;
+ IMTween* tweenerIn;
+ IMTweenType tweenerOutType;
+ IMTweenType tweenerInType;
+
+ uint64_t elapsed;
+ uint64_t targetTime;
+ float originalAlpha;
+ std::string targetImage;
+ bool fadeAwayDone;
+
+ IMChangeImageFadeOutIn( uint64_t time, std::string const& _targetImage, IMTweenType _tweenerOut, IMTweenType _tweenerIn );
+
+ static IMChangeImageFadeOutIn* ASFactory( uint64_t time, std::string const& _targetImage, IMTweenType _tweenerOut, IMTweenType _tweenerIn ) {
+ return new IMChangeImageFadeOutIn(time, _targetImage, _tweenerOut, _tweenerIn);
+ }
+
+ bool initialize( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+ bool update( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Create a copy of this object (respecting inheritance)
+ *
+ */
+ virtual IMUpdateBehavior* clone() {
+ IMChangeImageFadeOutIn* c = new IMChangeImageFadeOutIn(targetTime, targetImage, tweenerOutType, tweenerInType);
+ return c;
+ }
+
+
+};
+
+/*******
+ *
+ * Continuous update behaviors
+ *
+ */
+
+/**
+ * Continually pulse the alpha of this element
+ **/
+
+struct IMPulseAlpha : public IMUpdateBehavior {
+ float midPoint;
+ float difference;
+ float speed;
+ uint64_t elapsedTime;
+
+ IMPulseAlpha( float lower, float upper, float _speed );
+
+ static IMPulseAlpha* ASFactory( float lower, float upper, float _speed ) {
+ return new IMPulseAlpha(lower, upper, _speed);
+ }
+
+ bool initialize( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+ float computeTransition( float base, float range );
+ bool update( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Create a copy of this object (respecting inheritance)
+ *
+ */
+ virtual IMUpdateBehavior* clone() {
+ IMPulseAlpha* c = new IMPulseAlpha(0, 0, 0);
+ c->midPoint = midPoint;
+ c->difference = difference;
+ c->speed = speed;
+ return c;
+ }
+
+};
+
+
+/**
+ * Continually pulse the alpha of this element's border
+ **/
+
+struct IMPulseBorderAlpha : public IMUpdateBehavior {
+ float midPoint;
+ float difference;
+ float speed;
+ uint64_t elapsedTime;
+
+ IMPulseBorderAlpha( float lower, float upper, float _speed );
+
+ static IMPulseBorderAlpha* ASFactory( float lower, float upper, float _speed ) {
+ return new IMPulseBorderAlpha(lower, upper, _speed);
+ }
+
+ bool initialize( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+ float computeTransition( float base, float range );
+ bool update( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Create a copy of this object (respecting inheritance)
+ *
+ */
+ virtual IMUpdateBehavior* clone() {
+ IMPulseBorderAlpha* c = new IMPulseBorderAlpha(0, 0, 0);
+ c->midPoint = midPoint;
+ c->difference = difference;
+ c->speed = speed;
+ return c;
+ }
+
+};
+
+
+/*************************************
+ *****************
+ *******
+ *
+ * mouse over behaviors
+ *
+ *******/
+
+ /**
+ * Scales when the mouse is over
+ **/
+
+ struct IMMouseOverScale : public IMMouseOverBehavior {
+
+ vec4 midPoints;
+ vec4 differences;
+ vec4 originalColor;
+ uint64_t elapsedTime;
+ float speed;
+ IMTween* tweener;
+ IMTweenType tweenType;
+ uint64_t elapsed;
+ uint64_t targetTime;
+ float offset;
+ float oldX;
+ float oldY;
+
+ IMMouseOverScale( uint64_t time, float _offset, IMTweenType _tweener );
+
+ static IMMouseOverScale* ASFactory( uint64_t _time, float _offset, IMTweenType _tweener ) {
+ return new IMMouseOverScale( _time, _offset, _tweener );
+ }
+
+ void onStart( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+ float computeTransition( float base, float range );
+ void onContinue( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+ bool onFinish( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Create a copy of this object (respecting inheritance)
+ *
+ */
+ virtual IMMouseOverBehavior* clone() {
+ IMMouseOverScale* c = new IMMouseOverScale(targetTime, offset, tweenType);
+ c->midPoints = midPoints;
+ c->differences = differences;
+ c->speed = speed;
+ return c;
+ }
+
+ };
+
+ /**
+ * Moves when the mouse is over
+ **/
+
+ struct IMMouseOverMove : public IMMouseOverBehavior {
+
+ vec4 midPoints;
+ vec4 differences;
+ vec4 originalColor;
+ uint64_t elapsedTime;
+ float speed;
+ IMTween* tweener;
+ IMTweenType tweenType;
+ uint64_t elapsed;
+ uint64_t targetTime;
+ vec2 offset;
+
+ IMMouseOverMove( uint64_t time, vec2 _offset, IMTweenType _tweener );
+
+ static IMMouseOverMove* ASFactory( uint64_t _time, vec2 _offset, IMTweenType _tweener ) {
+ return new IMMouseOverMove( _time, _offset, _tweener );
+ }
+
+ void onStart( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+ float computeTransition( float base, float range );
+ void onContinue( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+ bool onFinish( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Create a copy of this object (respecting inheritance)
+ *
+ */
+ virtual IMMouseOverBehavior* clone() {
+ IMMouseOverMove* c = new IMMouseOverMove(targetTime, offset, tweenType);
+ c->midPoints = midPoints;
+ c->differences = differences;
+ c->speed = speed;
+ return c;
+ }
+
+ };
+
+/**
+ * Shows the border when the mouse is over
+ **/
+
+struct IMMouseOverShowBorder : public IMMouseOverBehavior {
+
+ bool originalBorderState;
+
+ IMMouseOverShowBorder();
+
+ static IMMouseOverShowBorder* ASFactory() {
+ return new IMMouseOverShowBorder();
+ }
+
+ void onStart( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+ void onContinue( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+ bool onFinish( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Create a copy of this object (respecting inheritance)
+ *
+ */
+ virtual IMMouseOverBehavior* clone() {
+ IMMouseOverShowBorder* c = new IMMouseOverShowBorder;
+ return c;
+ }
+
+};
+
+
+ /**
+ * Causes the element to pulse between two given colors when the mouse is hovering
+ **/
+ struct IMMouseOverPulseColor : public IMMouseOverBehavior {
+
+ vec4 midPoints;
+ vec4 differences;
+ vec4 originalColor;
+ uint64_t elapsedTime;
+ float speed;
+
+ IMMouseOverPulseColor( vec4 first, vec4 second, float _speed );
+
+ static IMMouseOverPulseColor* ASFactory( vec4 first, vec4 second, float _speed ) {
+ return new IMMouseOverPulseColor( first, second, _speed );
+ }
+
+ void onStart( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+ float computeTransition( float base, float range );
+ void onContinue( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+ bool onFinish( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Create a copy of this object (respecting inheritance)
+ *
+ */
+ virtual IMMouseOverBehavior* clone() {
+ IMMouseOverPulseColor* c = new IMMouseOverPulseColor(0,0,0);
+ c->midPoints = midPoints;
+ c->differences = differences;
+ c->speed = speed;
+ return c;
+ }
+
+};
+
+ /**
+ * Causes the element's border to pulse between two given colors when the mouse is hovering
+ **/
+struct IMMouseOverPulseBorder : public IMMouseOverBehavior {
+
+ vec4 midPoints;
+ vec4 differences;
+ vec4 originalColor;
+ uint64_t elapsedTime;
+ float speed;
+
+ IMMouseOverPulseBorder( vec4 first, vec4 second, float _speed );
+
+ static IMMouseOverPulseBorder* ASFactory( vec4 first, vec4 second, float _speed ) {
+ return new IMMouseOverPulseBorder( first, second, _speed );
+ }
+
+ void onStart( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+ float computeTransition( float base, float range );
+ void onContinue( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+ bool onFinish( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+
+
+ /*******************************************************************************************/
+ /**
+ * @brief Create a copy of this object (respecting inheritance)
+ *
+ */
+ virtual IMMouseOverBehavior* clone() {
+ IMMouseOverPulseBorder* c = new IMMouseOverPulseBorder(0,0,0);
+ c->midPoints = midPoints;
+ c->differences = differences;
+ c->speed = speed;
+ return c;
+ }
+};
+
+/**
+ * Just pulse the border alpha
+ **/
+
+struct IMMouseOverPulseBorderAlpha : public IMMouseOverBehavior {
+
+ float midPoint;
+ float difference;
+ float originalAlpha;
+ float speed;
+ uint64_t elapsedTime;
+
+ IMMouseOverPulseBorderAlpha( float lower, float upper, float _speed );
+
+ static IMMouseOverPulseBorderAlpha* ASFactory( float lower, float upper, float _speed ) {
+ return new IMMouseOverPulseBorderAlpha( lower, upper, _speed );
+ }
+
+ void onStart( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+ float computeTransition( float base, float range );
+ void onContinue( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+ bool onFinish( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Create a copy of this object (respecting inheritance)
+ *
+ */
+ virtual IMMouseOverBehavior* clone() {
+ IMMouseOverPulseBorderAlpha* c = new IMMouseOverPulseBorderAlpha(0,0,0);
+ c->midPoint = midPoint;
+ c->difference = difference;
+ c->speed = speed;
+ return c;
+ }
+
+};
+
+/**
+ * Sends a specific message on the different mouse over states
+ **/
+struct IMFixedMessageOnMouseOver : public IMMouseOverBehavior {
+
+ IMMessage* enterMessage;
+ IMMessage* overMessage;
+ IMMessage* leaveMessage;
+
+ IMFixedMessageOnMouseOver( IMMessage* _enterMessage,
+ IMMessage* _overMessage,
+ IMMessage* _leaveMessage );
+
+ static IMFixedMessageOnMouseOver* ASFactory( IMMessage* _enterMessage,
+ IMMessage* _overMessage,
+ IMMessage* _leaveMessage ) {
+ return new IMFixedMessageOnMouseOver( _enterMessage, _overMessage, _leaveMessage );
+ }
+
+ void onStart( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+ void onContinue( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+ bool onFinish( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Create a copy of this object (respecting inheritance)
+ *
+ */
+ virtual IMMouseOverBehavior* clone() {
+
+ IMMessage* eM = NULL;
+ IMMessage* oM = NULL;
+ IMMessage* lM = NULL;
+
+ if( enterMessage != NULL ) {
+ eM = new IMMessage( *enterMessage );
+ }
+ if( overMessage != NULL ) {
+ oM = new IMMessage( *overMessage );
+ }
+ if( enterMessage != NULL ) {
+ lM = new IMMessage( *enterMessage );
+ }
+
+ IMFixedMessageOnMouseOver* c = new IMFixedMessageOnMouseOver(eM, oM, lM);
+ return c;
+ }
+
+ virtual ~IMFixedMessageOnMouseOver() {
+ if( enterMessage != NULL ) {
+ enterMessage->Release();
+ }
+ if( overMessage != NULL ) {
+ overMessage->Release();
+ }
+ if( leaveMessage != NULL ) {
+ leaveMessage->Release();
+ }
+ }
+
+};
+
+ /**
+ * Makes the element fade in from 0.0 to 1.0 alpha
+ **/
+ struct IMMouseOverFadeIn : public IMMouseOverBehavior {
+
+ IMTween* tweener;
+ IMTweenType tweenType;
+ uint64_t elapsed;
+ uint64_t targetTime;
+ float targetAlpha;
+
+ IMMouseOverFadeIn( uint64_t time, IMTweenType _tweener, float targetAlpha = 1.0f );
+
+ static IMMouseOverFadeIn* ASFactory( uint64_t time, IMTweenType _tweener, float targetAlpha ) {
+ return new IMMouseOverFadeIn(time, _tweener, targetAlpha);
+ }
+
+ void onStart( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+ float computeTransition( float base, float range );
+ void onContinue( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+ bool onFinish( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Create a copy of this object (respecting inheritance)
+ *
+ */
+ virtual IMMouseOverBehavior* clone() {
+ IMMouseOverFadeIn* c = new IMMouseOverFadeIn(targetTime, tweenType);
+ return c;
+ }
+};
+
+/*************************************
+ *****************
+ *******
+ *
+ * mouse click behaviors
+ *
+ *******/
+
+/**
+ * Sends a specific message on click (mouse *up*)
+ **/
+struct IMFixedMessageOnClick : public IMMouseClickBehavior {
+
+ IMMessage* theMessage;
+
+ /*******************************************************************************************/
+ /**
+ * *brief Various constructors
+ *
+ */
+ IMFixedMessageOnClick( std::string const& messageName );
+ IMFixedMessageOnClick( std::string const& messageName, int param );
+ IMFixedMessageOnClick( std::string const& messageName, std::string param );
+ IMFixedMessageOnClick( std::string const& messageName, float param );
+ IMFixedMessageOnClick( IMMessage* message );
+
+ static IMFixedMessageOnClick* ASFactory( std::string const& messageName ) {
+ return new IMFixedMessageOnClick( messageName );
+ }
+
+ static IMFixedMessageOnClick* ASFactory_int( std::string const& messageName, int param ) {
+ return new IMFixedMessageOnClick( messageName, param );
+ }
+
+ static IMFixedMessageOnClick* ASFactory_string( std::string const& messageName, std::string const& param ) {
+ return new IMFixedMessageOnClick( messageName, param );
+ }
+
+ static IMFixedMessageOnClick* ASFactory_float( std::string const& messageName, float param ) {
+ return new IMFixedMessageOnClick( messageName, param );
+ }
+
+ static IMFixedMessageOnClick* ASFactoryMessage( IMMessage* message) {
+ return new IMFixedMessageOnClick( message );
+ }
+
+ bool onUp( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Create a copy of this object (respecting inheritance)
+ *
+ */
+ virtual IMMouseClickBehavior* clone() {
+ IMFixedMessageOnClick* c = new IMFixedMessageOnClick( theMessage );
+ return c;
+ }
+
+ virtual ~IMFixedMessageOnClick() {
+ if( theMessage != NULL ) {
+ theMessage->Release();
+ }
+ }
+
+};
+
+struct IMMouseOverSound : public IMMouseOverBehavior {
+
+ std::string audioFile = "";
+
+ IMMouseOverSound(std::string const& audioFile);
+ virtual ~IMMouseOverSound();
+
+ static IMMouseOverSound* ASFactory(std::string const& audioFile) {
+ return new IMMouseOverSound(audioFile);
+ }
+
+ void onStart( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+ void onContinue( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+ bool onFinish( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Create a copy of this object (respecting inheritance)
+ *
+ */
+ virtual IMMouseOverBehavior* clone() {
+ IMMouseOverSound* c = new IMMouseOverSound(audioFile);
+ return c;
+ }
+
+};
+
+struct IMSoundOnClick : public IMMouseClickBehavior {
+ std::string audioFile;
+
+ IMSoundOnClick(std::string const& audioFile);
+
+ static IMSoundOnClick* ASFactory(std::string const& audioFile) {
+ return new IMSoundOnClick(audioFile);
+ }
+
+ bool onUp(IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate);
+
+ virtual IMMouseClickBehavior* clone() {
+ IMSoundOnClick* c = new IMSoundOnClick(audioFile);
+ return c;
+ }
+};
+
+struct IMFuncOnClick : public IMMouseClickBehavior {
+ asIScriptFunction * func = 0;
+
+ IMFuncOnClick(asIScriptFunction * func);
+ virtual ~IMFuncOnClick() {
+ func->Release();
+ }
+
+ static IMFuncOnClick* ASFactory(asIScriptFunction * func) {
+ return new IMFuncOnClick(func);
+ }
+
+ bool onUp(IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate);
+
+ virtual IMMouseClickBehavior* clone() {
+ IMFuncOnClick* c = new IMFuncOnClick(func);
+ return c;
+ }
+};
diff --git a/Source/GUI/IMUI/im_container.cpp b/Source/GUI/IMUI/im_container.cpp
new file mode 100644
index 00000000..8c1adcba
--- /dev/null
+++ b/Source/GUI/IMUI/im_container.cpp
@@ -0,0 +1,916 @@
+//-----------------------------------------------------------------------------
+// Name: im_container.cpp
+// Developer: Wolfire Games LLC
+// Description: Most basic container, fixed size that simply serves to
+// contain a single element (and supports a number of
+// 'floating' elements)
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "im_container.h"
+
+#include <GUI/IMUI/imgui.h>
+
+
+/*******************************************************************************************/
+/**
+ * @brief Constructor
+ *
+ * @param _defaultSize
+ *
+ */
+
+SizePolicy::SizePolicy() :
+ errorOnOverflow(true),
+ defaultSize(UNDEFINEDSIZE),
+ maxSize(UNDEFINEDSIZE),
+ expansionPolicy(ContainerExpansionStatic)
+{
+}
+
+SizePolicy::SizePolicy( float _defaultSize ) :
+ errorOnOverflow(true),
+ expansionPolicy(ContainerExpansionStatic)
+{
+ defaultSize = _defaultSize;
+ maxSize = _defaultSize;
+}
+
+SizePolicy SizePolicy::expand( float _maxSize ) {
+ expansionPolicy = ContainerExpansionExpand;
+ maxSize = _maxSize;
+ return *this;
+}
+
+SizePolicy SizePolicy::inheritMax() {
+ expansionPolicy = ContainerExpansionInheritMax;
+ return *this;
+}
+
+SizePolicy SizePolicy::staticMax() {
+ expansionPolicy = ContainerExpansionStatic;
+ return *this;
+}
+
+SizePolicy SizePolicy::overflowClip( bool shouldClip ) {
+ errorOnOverflow = !shouldClip;
+ return *this;
+}
+
+AHFloatingElement::AHFloatingElement() :
+ element(NULL),
+ centerVal( std::make_pair(false, false) )
+{}
+
+void AHFloatingElement::setElement( IMElement* _element ) {
+ if( element != NULL ) {
+ element->Release();
+ }
+
+ if( _element != NULL ) {
+ _element->AddRef();
+ }
+
+ element = _element;
+}
+
+AHFloatingElement::~AHFloatingElement() {
+ if( element != NULL ) {
+ element->Release();
+ }
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Constructor
+ *
+ * @param name Element name
+ * @param size Size of this container
+ *
+ */
+IMContainer::IMContainer( std::string const& name, SizePolicy sizeX, SizePolicy sizeY ) :
+ IMElement( name ),
+ contents(NULL),
+ contentsXAlignment(CACenter),
+ contentsYAlignment(CACenter),
+ backgroundImage(NULL),
+ sizePolicyX(sizeX),
+ sizePolicyY(sizeY)
+{
+ IMrefCountTracker.addRefCountObject( getElementTypeName() );
+ setSize( vec2( sizePolicyX.defaultSize, sizePolicyY.defaultSize ) );
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Constructor
+ *
+ * @param size Size of this container
+ *
+ */
+IMContainer::IMContainer( SizePolicy sizeX, SizePolicy sizeY ) :
+ contents(NULL),
+ contentsXAlignment(CACenter),
+ contentsYAlignment(CACenter),
+ backgroundImage(NULL),
+ sizePolicyX(sizeX),
+ sizePolicyY(sizeY)
+{
+ IMrefCountTracker.addRefCountObject( getElementTypeName() );
+ setSize( vec2( sizePolicyX.defaultSize, sizePolicyY.defaultSize ) );
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Set’s this element’s parent (and does nessesary logic)
+ *
+ * @param _parent New parent
+ *
+ */
+void IMContainer::setOwnerParent( IMGUI* _owner, IMElement* _parent ) {
+
+ owner = _owner;
+ parent = _parent;
+
+ // Simply pass this on to the children
+ if( contents != NULL ) {
+ contents->setOwnerParent( _owner, this );
+ }
+
+ for( FEMap::iterator it = floatingContents.begin();
+ it != floatingContents.end();
+ ++it ) {
+ it->second->getElement()->setOwnerParent(_owner, this);
+ }
+
+}
+
+
+/*******************************************************************************************/
+/**
+ * @brief Set’s the size policy objects for this container
+ *
+ * @param sizeX x dimension policy
+ * @param sizeY y dimension policy
+ *
+ */
+void IMContainer::setSizePolicy( SizePolicy sizeX, SizePolicy sizeY ) {
+
+ sizePolicyX = sizeX;
+ sizePolicyY = sizeY;
+
+ setSize( vec2( sizePolicyX.defaultSize, sizePolicyY.defaultSize ) );
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the name of the type of this element — for autonaming and debugging
+ *
+ * @returns name of the element type as a string
+ *
+ */
+std::string IMContainer::getElementTypeName() {
+ return "Container";
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the size of the region (not including padding)
+ *
+ * @param _size 2d size vector (-1 element implies undefined - or use UNDEFINEDSIZE)
+ *
+ */
+void IMContainer::setSize( const vec2 _size ) {
+
+ //resize the container
+ IMElement::setSize( _size );
+
+ // Now reposition our contents
+ positionByAlignment();
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the x dimension of a region
+ *
+ * @param x x dimension size (-1 implies undefined - or use UNDEFINEDSIZE)
+ *
+ */
+void IMContainer::setSizeX( const float x ) {
+
+ //resize the container
+ IMElement::setSize( vec2( x, size.y() ) );
+
+ // Now reposition our contents
+ positionByAlignment();
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the y dimension of a region
+ *
+ * @param y y dimension size (-1 implies undefined - or use UNDEFINEDSIZE)
+ * @param resetBoundarySize Should we reset the boundary if it's too small?
+ *
+ */
+void IMContainer::setSizeY( const float y ) {
+
+ //resize the container
+ IMElement::setSize( vec2( size.x(), y ) );
+
+ // Now reposition our contents
+ positionByAlignment();
+}
+
+
+/*******************************************************************************************/
+/**
+ * @brief Clear the contents of this container, leaving everything else the same
+ *
+ */
+void IMContainer::clear() {
+ // clear the floating contents
+ //
+ FEMap deletemap = floatingContents;
+
+ floatingContents.clear();
+
+ for( FEMap::iterator it = deletemap.begin();
+ it != deletemap.end();
+ ++it ) {
+ delete it->second;
+ }
+
+ // clear the contained element
+ if( contents != NULL ) {
+ contents->Release();
+ }
+
+ contents = NULL;
+ contentsXAlignment = CACenter;
+ contentsYAlignment = CACenter;
+ contentsOffset = vec2(0,0);
+
+ if( backgroundImage != NULL ) {
+ backgroundImage->Release();
+ }
+
+ // clear the background
+ backgroundImage = NULL;
+
+ setSize(getDefaultSize());
+ setDisplacement();
+
+ onRelayout();
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Updates the element
+ *
+ * @param delta Number of millisecond elapsed since last update
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param guistate The state of the GUI at this update
+ *
+ */
+void IMContainer::update( uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+
+ bool mouseOverState = guistate.inheritedMouseOver;
+ bool mouseDownState = guistate.inheritedMouseDown;
+ IMUIContext::ButtonState currentMouseState = guistate.inheritedMouseState;
+
+ // Do whatever the superclass wants
+ IMElement::update( delta, drawOffset, guistate );
+
+ // Simply pass this on to the children
+ if( contents != NULL ) {
+ contents->update( delta, drawOffset + drawDisplacement + contentsOffset, guistate);
+ }
+
+ for( FEMap::iterator it = floatingContents.begin();
+ it != floatingContents.end();
+ ++it ) {
+ AHFloatingElement* floatingElement = it->second;
+ floatingElement->getElement()->update( delta, drawOffset + drawDisplacement + floatingElement->relativePos, guistate );
+
+ if(floatingContents.empty()) {
+ break;
+ }
+ }
+
+ guistate.inheritedMouseOver = mouseOverState;
+ guistate.inheritedMouseDown = mouseDownState;
+ guistate.inheritedMouseState = currentMouseState;
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Rather counter-intuitively, this draws this object on the screen
+ *
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param clipPos pixel location of upper lefthand corner of clipping region
+ * @param clipSize size of clipping region
+ *
+ */
+void IMContainer::render( vec2 drawOffset, vec2 clipPos, vec2 clipSize ) {
+
+ // See if we need to adjust clip for this container
+ vec2 currentClipPos;
+ vec2 currentClipSize;
+
+ if( getSizeX() == UNDEFINEDSIZE || getSizeY() == UNDEFINEDSIZE ) {
+ currentClipPos = clipPos;
+ currentClipSize = clipSize;
+ }
+ else {
+ currentClipPos = drawOffset + drawDisplacement;
+ currentClipSize = getSize();
+ }
+
+ // Do whatever the superclass wants
+ IMElement::render( drawOffset, currentClipPos, currentClipSize );
+
+ // Render the background, if any
+ if( backgroundImage != NULL ) {
+ backgroundImage->render( drawOffset + drawDisplacement, currentClipPos, currentClipSize );
+ }
+
+ // Render the contents
+ if( contents != NULL ) {
+ contents->render( drawOffset + drawDisplacement + contentsOffset, currentClipPos, currentClipSize );
+ }
+
+ // Render the floating elements
+ for( FEMap::iterator it = floatingContents.begin();
+ it != floatingContents.end();
+ ++it ) {
+ AHFloatingElement* floatingElement = it->second;
+ floatingElement->getElement()->render( drawOffset + drawDisplacement + floatingElement->relativePos, currentClipPos, currentClipSize );
+ }
+
+}
+
+
+/*******************************************************************************************/
+/**
+ * @brief When a resize, move, etc has happened do whatever is necessary
+ *
+ */
+ void IMContainer::doRelayout() {
+
+ // Invoke the elements relayout
+ IMElement::doRelayout();
+
+ // Make sure background image has kept up with any resizing
+ if( backgroundImage != NULL ) {
+ backgroundImage->setSize( getSize() );
+ }
+
+ // Make sure background image has kept up with any resizing
+ if( contents != NULL ) {
+ contents->doRelayout();
+ }
+
+ // Now trigger the children
+ for( FEMap::iterator it = floatingContents.begin();
+ it != floatingContents.end();
+ ++it ) {
+
+ AHFloatingElement* felement = it->second;
+
+
+ // see if we need to center this element
+ if( felement->centering().first &&
+ getSizeX() != UNDEFINEDSIZE &&
+ felement->getElement()->getSizeX() != UNDEFINEDSIZE ) {
+
+ float center = getSizeX()/2.0f;
+ felement->relativePos.x() = center - felement->getElement()->getSizeX()/2.0f;
+
+ }
+
+ if( felement->centering().second &&
+ getSizeY() != UNDEFINEDSIZE &&
+ felement->getElement()->getSizeY() != UNDEFINEDSIZE ) {
+
+ float center = getSizeY()/2.0f;
+ felement->relativePos.y() = center - felement->getElement()->getSizeY()/2.0f;
+ }
+
+ felement->getElement()->doRelayout();
+ }
+
+ // now make sure everything is fine with our container
+ checkRegion();
+
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Rederive the regions for the various orientation containers - for internal use
+ *
+ */
+void IMContainer::checkRegion() {
+
+ if( contents == NULL ) {
+ // we have nothing to do
+ return;
+ }
+
+ // first reposition our element
+ positionByAlignment();
+
+ // now see if we exceed the dimensions of container in any direction
+ vec2 UL = contentsOffset;
+ vec2 LR = contentsOffset + contents->getSize();
+
+ // see if we're over in the x dimension
+ if( UL.x() != UNDEFINEDSIZE && ( UL.x() < 0 || LR.x() > getSizeX() ) ) {
+
+ float neededSize = contents->getSizeX();
+ float maxSize = UNDEFINEDSIZE;
+
+ if( sizePolicyX.expansionPolicy == ContainerExpansionInheritMax ) {
+ if( parent != NULL ) {
+ maxSize = parent->getSizeX();
+ }
+ else {
+ maxSize = sizePolicyX.maxSize;
+ }
+ }
+
+ // if we have no max, just treat it as the size we need
+ if( maxSize == UNDEFINEDSIZE ) {
+ maxSize = contents->getSizeX();
+ }
+
+ float newSize = min( neededSize, maxSize );
+ if( newSize != getSizeX() ) {
+
+ setSizeX( newSize );
+
+ // see if we need to (and should) through an overflow error
+ if( getSizeX() > maxSize && sizePolicyX.errorOnOverflow ) {
+ onError("Container overflow for object " + contents->name );
+ }
+ }
+
+ }
+
+ // see if we're over in the y dimension
+ if( UL.y() != UNDEFINEDSIZE && ( UL.y() < 0 || LR.y() > getSizeY() ) ) {
+
+ float neededSize = contents->getSizeY();
+ float maxSize = UNDEFINEDSIZE;
+
+ if( sizePolicyY.expansionPolicy == ContainerExpansionInheritMax ) {
+ if( parent != NULL ) {
+ maxSize = parent->getSizeY();
+ }
+ else {
+ maxSize = sizePolicyY.maxSize;
+ }
+ }
+
+ // if we have no max, just treat it as the size we need
+ if( maxSize == UNDEFINEDSIZE ) {
+ maxSize = contents->getSizeY();
+ }
+
+ float newSize = min( neededSize, maxSize );
+
+ if( newSize != getSizeY() ) {
+
+ setSizeY( newSize );
+
+ // see if we need to (and should) through an overflow error
+ if( getSizeY() > maxSize && sizePolicyX.errorOnOverflow ) {
+ onError("Container overflow for object " + contents->name );
+ }
+ }
+ }
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Do whatever is necessary when the resolution changes
+ *
+ */
+ void IMContainer::doScreenResize() {
+
+ // Invoke the superclass's method
+ IMElement::doScreenResize();
+
+ // Now trigger the children
+ if( contents != NULL ) {
+ contents->doScreenResize();
+ }
+
+ for( FEMap::iterator it = floatingContents.begin();
+ it != floatingContents.end();
+ ++it ) {
+ it->second->getElement()->doScreenResize();
+ }
+
+ }
+
+
+/*******************************************************************************************/
+/**
+ * @brief Adds an arbitrarily placed element to the container
+ *
+ * @param newElement Element to add
+ * @param name Name of the element (this *will* rename the element )
+ * @param position Position relative to the upper right of this container (GUI Space) - if
+ * a parameter is undefined, it will center on that axis
+ * @param z z ordering for the new element (relative to this container -- optional )
+ *
+ */
+void IMContainer::addFloatingElement( IMElement* element, std::string const& _name, vec2 position, int z ) {
+
+ std::pair<bool, bool> centering;
+
+ // see if we've got real coordinates and if not, make some
+ if( position.x() == UNDEFINEDSIZE ) {
+ centering.first = true;
+ position.x() = 0.0f;
+ }
+ else {
+ centering.first = false;
+ }
+
+ if( position.y() == UNDEFINEDSIZE ) {
+ centering.second = true;
+ position.y() = 0.0f;
+ }
+ else {
+ centering.second = false;
+ }
+
+ // Make sure the element has the name
+ element->setName( _name );
+
+ AHFloatingElement* floatingElement = new AHFloatingElement();
+
+ floatingElement->relativePos = position;
+ floatingElement->setElement( element );
+ floatingElement->centering() = centering;
+
+ // See if we're replacing an element
+ FEMap::iterator it = floatingContents.find(_name);
+ if( it != floatingContents.end() ) {
+ delete it->second;
+ }
+
+ floatingContents[ _name ] = floatingElement;
+
+ // Link to this element/owning GUI
+ element->setOwnerParent( owner, this );
+
+ // Make sure it's in front of us
+ if( z < 1 ) {
+ element->setZOrdering( getZOrdering() + element->getZOrdering() );
+ }
+ else {
+ element->setZOrdering( getZOrdering() + z );
+ }
+
+ // Signal that something new has changed
+ onRelayout();
+
+ // release the ref we were given for this method
+ element->Release();
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the contained element
+ *
+ * @param element, element to add
+ *
+ */
+void IMContainer::setElement( IMElement* element ) {
+ if(contents) {
+ contents->Release();
+ }
+ contents = element;
+ contents->setOwnerParent( owner, this );
+ positionByAlignment();
+ onRelayout();
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Adjust the offset position according to the alignments - used internally
+ *
+ */
+void IMContainer::positionByAlignment() {
+
+ if( contents == NULL ) {
+ contentsOffset = vec2(0,0);
+ return;
+ }
+
+ // compute the horizontal position
+ if( contentsXAlignment == CALeft ) {
+ contentsOffset.x() = 0;
+ }
+ else if( contentsXAlignment == CARight ) {
+ contentsOffset.x() = getSizeX() - contents->getSizeX();
+ }
+ else {
+ contentsOffset.x() = (getSizeX() / 2)-(contents->getSizeX()/2);
+ }
+
+ // figure out the vertical position
+ if( contentsYAlignment == CATop ) {
+ contentsOffset.y() = 0.0;
+ }
+ else if( contentsYAlignment == CABottom ) {
+ contentsOffset.y() = getSizeY() - contents->getSizeY();
+ }
+ else {
+ contentsOffset.y() = (getSizeY() / 2)-(contents->getSizeY()/2);
+ }
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Set’s the alignment of the contained object
+ *
+ * @param xAlignment horizontal alignment
+ * @param yAlignment vertical alignment
+ *
+ */
+void IMContainer::setAlignment( ContainerAlignment xAlignment, ContainerAlignment yAlignment ) {
+ contentsXAlignment = xAlignment;
+ contentsYAlignment = yAlignment;
+
+ positionByAlignment();
+}
+
+
+/*******************************************************************************************/
+/**
+ * @brief Removes an element
+ *
+ * @param name Name of the element to remove
+ *
+ * @returns the element if it's there, NULL otherwise
+ *
+ */
+ IMElement* IMContainer::removeElement( std::string const& name ) {
+
+ // make sure it exists
+ FEMap::iterator it = floatingContents.find(name);
+ if( it != floatingContents.end() ) {
+
+ AHFloatingElement* floatingElement = it->second;
+ IMElement* element = floatingElement->getElement();
+
+ // since we're returning it
+ element->AddRef();
+
+ // get rid of the storage
+ floatingContents.erase(it);
+ delete floatingElement;
+
+ // Signal that something new has changed
+ onRelayout();
+
+ return element;
+ }
+ else {
+ // If not, let the caller know
+ return NULL;
+ }
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Moves and element to a new position
+ *
+ * @param name Name of the element to move
+ * @param newPos new position of the element
+ *
+ */
+void IMContainer::moveElement( std::string const& name, vec2 newPos ) {
+ // make sure it exists
+ FEMap::iterator it = floatingContents.find(name);
+ if( it != floatingContents.end() ) {
+ AHFloatingElement* floatingElement = it->second;
+ floatingElement->relativePos = newPos;
+ }
+ else {
+ // If not, throw an error
+ IMDisplayError("GUI Error", "Unable to find element " + name + " to move");
+ }
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Moves and element to a new position relative to its old one
+ *
+ * @param name Name of the element to move
+ * @param posChange change in element position
+ *
+ */
+void IMContainer::moveElementRelative( std::string const& name, vec2 posChange ) {
+ // make sure it exists
+ FEMap::iterator it = floatingContents.find(name);
+ if( it != floatingContents.end() ) {
+ AHFloatingElement* floatingElement = it->second;
+ floatingElement->relativePos = floatingElement->relativePos + posChange;
+ }
+ else {
+ // If not, throw an error
+ IMDisplayError("GUI Error", "Unable to find element " + name + " to move");
+ }
+}
+
+vec2 IMContainer::getElementPosition( std::string const& name ) {
+ // make sure it exists
+ FEMap::iterator it = floatingContents.find(name);
+ if( it != floatingContents.end() ) {
+ AHFloatingElement* floatingElement = it->second;
+ return floatingElement->relativePos;
+ }
+ else {
+ // If not, throw an error
+ IMDisplayError("GUI Error", "Unable to find element " + name + " to move");
+ return vec2( UNDEFINEDSIZE, UNDEFINEDSIZE );
+ }
+}
+
+
+/*******************************************************************************************/
+/**
+ * @brief Find an element by name — called internally
+ *
+ *
+ */
+IMElement* IMContainer::findElement( std::string const& elementName ) {
+ // Check if this is the droid we're looking for
+ if( name == elementName ) {
+ AddRef();
+ return this;
+ }
+ else {
+ // If not, look at the children
+ FEMap::iterator it = floatingContents.find(name);
+ if( it != floatingContents.end() ) {
+ AHFloatingElement* floatingElement = it->second;
+ IMElement* element = floatingElement->getElement();
+ element->AddRef();
+
+ return element;
+ }
+ else {
+ // If not, let the caller know
+ return NULL;
+ }
+ }
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Set a backgound image for this container
+ *
+ * @param fileName file name for the image (empty string to clear)
+ * @param color 4 component color vector
+ *
+ */
+void IMContainer::setBackgroundImage( std::string const& fileName, vec4 color ) {
+ // See if we already have a background
+ if( backgroundImage != NULL ) {
+ backgroundImage->Release();
+ }
+
+ if( fileName == "" ) {
+ backgroundImage = NULL;
+ return;
+ }
+
+ if( getSizeX() == UNDEFINEDSIZE || getSizeY() == UNDEFINEDSIZE ) {
+ IMDisplayError("GUI Error", "Cannot add a background image to a container with an undefined dimension" );
+ }
+
+ backgroundImage = new IMImage( fileName );
+
+ // Load the image
+ backgroundImage->setImageFile( fileName );
+
+ // Set the size to be the same as this container
+ backgroundImage->setSize( getSize() );
+
+ // Set the color
+ backgroundImage->setColor( color );
+
+ // Set the appropriate rendering order
+ backgroundImage->setZOrdering( getZOrdering() );
+
+}
+
+void IMContainer::setPauseBehaviors( bool pause ) {
+ IMElement::setPauseBehaviors( pause );
+ if( contents != NULL ) {
+ contents->setPauseBehaviors( pause );
+ }
+
+ for( FEMap::iterator it = floatingContents.begin();
+ it != floatingContents.end();
+ ++it ) {
+ AHFloatingElement* floatingElement = it->second;
+ floatingElement->getElement()->setPauseBehaviors( pause );
+ }
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Remove all referenced object without releaseing references
+ *
+ */
+void IMContainer::clense() {
+
+ IMElement::clense();
+
+ floatingContents.clear();
+
+ contents = NULL;
+ backgroundImage = NULL;
+
+}
+
+std::vector<IMElement*> IMContainer::getFloatingContents() {
+ std::vector<IMElement*> elements;
+ elements.reserve(floatingContents.size());
+ for(FEMap::iterator iter = floatingContents.begin(); iter != floatingContents.end(); ++iter) {
+ IMElement* element = iter->second->getElement();
+ //element->AddRef();
+ elements.push_back(element);
+ }
+ return elements;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Destructor
+ *
+ */
+IMContainer::~IMContainer() {
+ IMrefCountTracker.removeRefCountObject( getElementTypeName() );
+ clear();
+}
+
+
+void IMContainer::DestroyedIMElement( IMElement* element ) {
+ if( element == contents ) {
+ contents = NULL;
+ }
+
+ if( element == backgroundImage ) {
+ backgroundImage = NULL;
+ }
+
+ FEMap::iterator feit;
+
+ for( feit = floatingContents.begin(); feit != floatingContents.end(); feit++ ) {
+ if( feit->second->getElement() == element ) {
+ floatingContents.erase(feit);
+ feit = floatingContents.begin();
+ }
+ }
+
+ IMElement::DestroyedIMElement(element);
+}
+
+void IMContainer::DestroyedIMGUI( IMGUI* imgui ) {
+
+ IMElement::DestroyedIMGUI(imgui);
+}
+
+
diff --git a/Source/GUI/IMUI/im_container.h b/Source/GUI/IMUI/im_container.h
new file mode 100644
index 00000000..3543b80a
--- /dev/null
+++ b/Source/GUI/IMUI/im_container.h
@@ -0,0 +1,412 @@
+//-----------------------------------------------------------------------------
+// Name: im_container.h
+// Developer: Wolfire Games LLC
+// Description: Most basic container, fixed size that simply serves to
+// contain a single element (and supports a number of
+// 'floating' elements)
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <GUI/IMUI/im_support.h>
+#include <GUI/IMUI/im_element.h>
+#include <GUI/IMUI/im_image.h>
+
+#include <map>
+
+// How does this subdivision grow when the internal size gets bigger
+enum ExpansionPolicy {
+ ContainerExpansionStatic, // Stay at the given size
+ ContainerExpansionExpand, // Expand with the contents (if optional )
+ ContainerExpansionInheritMax // Max out at the size of the owning container
+};
+
+
+/*******************************************************************************************/
+/**
+ * @brief Base class for the sizing/resizing policy for a subdivider
+ *
+ */
+struct SizePolicy {
+ bool errorOnOverflow; // should an error be thrown if the container is overfull
+ float defaultSize; // what is in the initial(default) size
+ float maxSize; // what is the biggest this container can get
+ ExpansionPolicy expansionPolicy; // see
+
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor
+ *
+ * @param _defaultSize
+ *
+ */
+ SizePolicy();
+ SizePolicy( float _defaultSize );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Copy constructor
+ *
+ */
+ SizePolicy( const SizePolicy& other ) {
+ errorOnOverflow = other.errorOnOverflow;
+ defaultSize = other.defaultSize;
+ maxSize = other.maxSize;
+ expansionPolicy = other.expansionPolicy;
+ }
+
+
+ SizePolicy expand( float _maxSize = UNDEFINEDSIZE );
+ SizePolicy inheritMax();
+ SizePolicy staticMax();
+ SizePolicy overflowClip( bool shouldClip );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor
+ *
+ */
+ virtual ~SizePolicy() {}
+
+};
+
+static void SizePolicy_ASfactory_noparams( SizePolicy *self ) {
+ new(self) SizePolicy();
+}
+
+static void SizePolicy_ASfactory_params( SizePolicy *self, float _defaultSize ) {
+ new(self) SizePolicy( _defaultSize );
+}
+
+static void SizePolicy_ASdestructor( SizePolicy *self) {
+ self->~SizePolicy();
+}
+
+static void SizePolicy_ASCopyConstructor( SizePolicy *self, const SizePolicy& other ) {
+ new(self) SizePolicy( other );
+}
+
+static const SizePolicy& SizePolicy_ASAssign( SizePolicy *self, const SizePolicy& other) {
+ return (*self) = other;
+}
+
+
+
+/*******************************************************************************************/
+/**
+ * @brief Hold the information for the contained element
+ *
+ */
+struct AHFloatingElement {
+private:
+ IMElement* element; // Handle to the element itself
+ std::pair<bool, bool> centerVal; // Is x, y centered in the container?
+
+public:
+ AHFloatingElement();
+ void setElement( IMElement* _element = NULL );
+ IMElement* getElement() { return element; }
+ std::pair<bool, bool>& centering() { return centerVal; }
+ vec2 relativePos; // Where is this element offset from the upper/right of this container
+ virtual ~AHFloatingElement();
+};
+
+typedef std::map<std::string, AHFloatingElement*> FEMap;
+
+/*******************************************************************************************/
+/**
+ * @brief Basic container class, holds other elements
+ *
+ */
+class IMContainer : public IMElement {
+
+public:
+
+ FEMap floatingContents; // floating elements in this container
+ IMElement* contents; // what's contained in this container
+ vec2 contentsOffset;
+ ContainerAlignment contentsXAlignment; // horizontal alignment of element
+ ContainerAlignment contentsYAlignment; // vertical alignment of element
+ IMImage* backgroundImage; // image to be put on the background
+
+ SizePolicy sizePolicyX; // how should this container grow in the x direction
+ SizePolicy sizePolicyY; // how should this container grow in the y direction
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor
+ *
+ * @param name Element name
+ * @param size Size of this container
+ *
+ */
+ IMContainer( std::string const& name, SizePolicy sizeX = SizePolicy(), SizePolicy sizeY = SizePolicy() );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor
+ *
+ * @param size Size of this container
+ *
+ */
+ IMContainer( SizePolicy sizeX = SizePolicy(), SizePolicy sizeY = SizePolicy() );
+
+ /*******
+ *
+ * Angelscript factory
+ *
+ */
+ static IMContainer* ASFactory_named( std::string const& name, SizePolicy sizeX = SizePolicy(), SizePolicy sizeY = SizePolicy() ) {
+ return new IMContainer( name, sizeX, sizeY );
+ }
+
+ static IMContainer* ASFactory_unnamed( SizePolicy sizeX = SizePolicy(), SizePolicy sizeY = SizePolicy() ) {
+ return new IMContainer( sizeX, sizeY );
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Set’s this element’s parent (and does nessesary logic)
+ *
+ * @param _parent New parent
+ *
+ */
+ void setOwnerParent( IMGUI* _owner, IMElement* _parent );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Set’s the size policy objects for this container
+ *
+ * @param sizeX x dimension policy
+ * @param sizeY y dimension policy
+ *
+ */
+ void setSizePolicy( SizePolicy sizeX, SizePolicy sizeY );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the name of the type of this element — for autonaming and debugging
+ *
+ * @returns name of the element type as a string
+ *
+ */
+ std::string getElementTypeName();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the size of the region (not including padding)
+ *
+ * @param _size 2d size vector (-1 element implies undefined - or use UNDEFINEDSIZE)
+ *
+ */
+ void setSize( const vec2 _size );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the x dimension of a region
+ *
+ * @param x x dimension size (-1 implies undefined - or use UNDEFINEDSIZE)
+ *
+ */
+ void setSizeX( const float x );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the y dimension of a region
+ *
+ * @param y y dimension size (-1 implies undefined - or use UNDEFINEDSIZE)
+ * @param resetBoundarySize Should we reset the boundary if it's too small?
+ *
+ */
+ void setSizeY( const float y );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Clear the contents of this container, leaving everything else the same
+ *
+ */
+ void clear();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Updates the element
+ *
+ * @param delta Number of millisecond elapsed since last update
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param guistate The state of the GUI at this update
+ *
+ */
+ void update( uint64_t delta, vec2 drawOffset, GUIState& guistate );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Rather counter-intuitively, this draws this object on the screen
+ *
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param clipPos pixel location of upper lefthand corner of clipping region
+ * @param clipSize size of clipping region
+ *
+ */
+ void render( vec2 drawOffset, vec2 clipPos, vec2 clipSize );
+
+ /*******************************************************************************************/
+ /**
+ * @brief When a resize, move, etc has happened do whatever is necessary
+ *
+ */
+ void doRelayout();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Rederive the regions for the various orientation containers - for internal use
+ *
+ */
+ void checkRegion();
+
+
+ /*******************************************************************************************/
+ /**
+ * @brief Do whatever is necessary when the resolution changes
+ *
+ */
+ void doScreenResize();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Adds an arbitrarily placed element to the container
+ *
+ * @param newElement Element to add
+ * @param name Name of the element (this *will* rename the element )
+ * @param position Position relative to the upper right of this container (GUI Space) - if
+ * a parameter is undefined, it will center on that axis
+ * @param z z ordering for the new element (relative to this container -- optional )
+ *
+ */
+ void addFloatingElement( IMElement* element, std::string const& name, vec2 position, int z = -1 );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the contained element
+ *
+ * @param element, element to add
+ *
+ */
+ void setElement( IMElement* element );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Adjust the offset position according to the alignments - used internally
+ *
+ */
+ void positionByAlignment();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Set’s the alignment of the contained object
+ *
+ * @param xAlignment horizontal alignment
+ * @param yAlignment vertical alignment
+ *
+ */
+ void setAlignment( ContainerAlignment xAlignment, ContainerAlignment yAlignment );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Removes an element
+ *
+ * @param name Name of the element to remove
+ *
+ * @returns the element if it's there, NULL otherwise
+ *
+ */
+ IMElement* removeElement( std::string const& name );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Moves and element to a new position
+ *
+ * @param name Name of the element to move
+ * @param newPos new position of the element
+ *
+ */
+ void moveElement( std::string const& name, vec2 newPos );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Moves and element to a new position relative to its old one
+ *
+ * @param name Name of the element to move
+ * @param posChange change in element position
+ *
+ */
+ void moveElementRelative( std::string const& name, vec2 posChange );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Get floating element position by name
+ *
+ * @param name Name of the element to move
+ *
+ * @returns position of the element (UNDEFINEDSIZE, UNDEFINEDSIZE) if not found (and an error)
+ *
+ */
+ vec2 getElementPosition( std::string const& name );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Find an element by name — called internally
+ *
+ *
+ */
+ IMElement* findElement( std::string const& elementName );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Set a backgound image for this container
+ *
+ * @param fileName file name for the image (empty string to clear)
+ *
+ */
+ void setBackgroundImage( std::string const& fileName, vec4 color = vec4(1.0) );
+
+ virtual void setPauseBehaviors( bool pause );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Remove all referenced object without releaseing references
+ *
+ */
+ virtual void clense();
+
+ IMElement* getContents() { if(contents) contents->AddRef(); return contents; }
+ std::vector<IMElement*> getFloatingContents();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Destructor
+ *
+ */
+ virtual ~IMContainer();
+
+ virtual void DestroyedIMElement( IMElement* element );
+ virtual void DestroyedIMGUI( IMGUI* imgui );
+};
+
+
diff --git a/Source/GUI/IMUI/im_divider.cpp b/Source/GUI/IMUI/im_divider.cpp
new file mode 100644
index 00000000..280eca4b
--- /dev/null
+++ b/Source/GUI/IMUI/im_divider.cpp
@@ -0,0 +1,656 @@
+//-----------------------------------------------------------------------------
+// Name: im_divider.cpp
+// Developer: Wolfire Games LLC
+// Description: Specialized container element class for creating adhoc GUIs
+// as part of the UI tools
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "im_divider.h"
+
+#include <sstream>
+
+/*******************************************************************************************/
+/**
+ * @brief Constructor
+ *
+ * @param name IMElement name
+ * @param _orientation The orientation of the container
+ *
+ */
+IMDivider::IMDivider( std::string const& name, DividerOrientation _orientation ) :
+ IMElement(name),
+ contentsNum(0),
+ contentsXAlignment(CACenter),
+ contentsYAlignment(CACenter),
+ orientation(DOVertical) // vertical by default
+{
+ IMrefCountTracker.addRefCountObject( getElementTypeName() );
+ orientation = _orientation;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Constructor
+ *
+ * @param name IMElement name
+ * @param _orientation The orientation of the container
+ *
+ */
+IMDivider::IMDivider( DividerOrientation _orientation ) :
+ IMElement(),
+ contentsNum(0),
+ contentsXAlignment(CACenter),
+ contentsYAlignment(CACenter),
+ orientation(DOVertical) // vertical by default
+{
+ IMrefCountTracker.addRefCountObject( getElementTypeName() );
+ orientation = _orientation;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the name of the type of this element — for autonaming and debugging
+ *
+ * @returns name of the element type as a string
+ *
+ */
+std::string IMDivider::getElementTypeName() {
+ return "Divider";
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Set’s this element’s parent (and does nessesary logic)
+ *
+ * @param _parent New parent
+ *
+ */
+void IMDivider::setOwnerParent( IMGUI* _owner, IMElement* _parent ) {
+ owner = _owner;
+ parent = _parent;
+
+ // Simply pass this on to the children
+ for( unsigned int i = 0; i < containers.size(); i++ ) {
+ containers[i]->setOwnerParent( owner, this );
+ }
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Set’s the alignment of the contained object
+ *
+ * @param xAlignment horizontal alignment
+ * @param yAlignment vertical alignment
+ * @param reposition should we go through and reposition existing objects
+ *
+ */
+void IMDivider::setAlignment( ContainerAlignment xAlignment, ContainerAlignment yAlignment, bool reposition ) {
+
+ contentsXAlignment = xAlignment;
+ contentsYAlignment = yAlignment;
+
+ if( reposition ) {
+ for( unsigned int i = 0; i < containers.size(); i++ ) {
+ containers[i]->setAlignment( xAlignment, yAlignment );
+ }
+ }
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Clear the contents of this divider, leaving everything else the same
+ *
+ */
+void IMDivider::clear() {
+ std::vector<IMContainer*> containers_copy = containers;
+ for(std::vector<IMContainer*>::iterator iter = containers_copy.begin();
+ iter != containers_copy.end();
+ ++iter) {
+ (*iter)->Release();
+ }
+ containers.resize(0);
+ onRelayout();
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Updates the element
+ *
+ * @param delta Number of millisecond elapsed since last update
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param guistate The state of the GUI at this update
+ *
+ */
+void IMDivider::update( uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+
+ bool mouseOverState = guistate.inheritedMouseOver;
+ bool mouseDownState = guistate.inheritedMouseDown;
+ IMUIContext::ButtonState currentMouseState = guistate.inheritedMouseState;
+
+ // Do whatever the superclass wants
+ IMElement::update( delta, drawOffset, guistate );
+
+ vec2 currentDrawOffset = drawOffset + drawDisplacement;
+
+ // Simply pass this on to the children
+ for( unsigned int i = 0; i < containers.size(); i++ ) {
+
+ containers[i]->update( delta, currentDrawOffset, guistate );
+
+ if( orientation == DOVertical ) {
+ currentDrawOffset.y() += containers[i]->getSizeY();
+ }
+ else {
+ currentDrawOffset.x() += containers[i]->getSizeX();
+ }
+ }
+
+ guistate.inheritedMouseOver = mouseOverState;
+ guistate.inheritedMouseDown = mouseDownState;
+ guistate.inheritedMouseState = currentMouseState;
+
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Draw this object on the screen
+ *
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param clipPos pixel location of upper lefthand corner of clipping region
+ * @param clipSize size of clipping region
+ *
+ */
+void IMDivider::render( vec2 drawOffset, vec2 clipPos, vec2 clipSize ) {
+
+ // See if we need to adjust clip for this container
+ vec2 currentClipPos = drawOffset + drawDisplacement;
+ vec2 currentClipSize;
+
+ if( getSizeX() == UNDEFINEDSIZE || getSizeY() == UNDEFINEDSIZE ) {
+ currentClipSize = clipSize;
+ }
+ else {
+ currentClipSize = getSize();
+ }
+
+ // See if the superclass wants to do anything
+ IMElement::render( drawOffset, currentClipPos, currentClipSize );
+
+ // Simply pass this on to the children
+ vec2 currentDrawOffset = drawOffset + drawDisplacement;
+ for( unsigned int i = 0; i < containers.size(); i++ ) {
+
+ containers[i]->render( currentDrawOffset, currentClipPos, currentClipSize );
+
+ if( orientation == DOVertical ) {
+
+ if( containers[i]->getSizeY() > 0 ) {
+ currentDrawOffset.y() += containers[i]->getSizeY();
+ }
+ }
+ else {
+
+ if( containers[i]->getSizeX() > 0 ) {
+ currentDrawOffset.x() += containers[i]->getSizeX();
+ }
+ }
+ }
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Rederive the regions for the various orientation containers - for internal use
+ *
+ */
+void IMDivider::checkRegions() {
+
+ // keep track of the dynamic spacers so we can resize them when we're done
+ std::vector<IMSpacer*> dynamicSpacers;
+ float totalSize = 0;
+
+ if( orientation == DOVertical ) {
+
+ // First calculate the height
+ for( unsigned int i = 0; i < containers.size(); i++ ) {
+
+ // see if this element is a dynamic spacer
+ // first check if there is an element in this container
+ if( containers[i]->contents != NULL ) {
+ // now see if we can cast it
+ if( IMSpacer* spacer = dynamic_cast<IMSpacer*>( containers[i]->contents ) ) {
+ if( spacer != NULL && spacer->isStatic == false ) {
+ // queue this up
+ dynamicSpacers.push_back( spacer );
+ // skip the rest of the loop so we don't add this to the total
+ continue;
+ }
+ }
+ }
+
+ // add to our total
+ totalSize += containers[i]->getSizeY();
+
+ }
+
+ float addedSpace = 0;
+ // see if we've got dynamic spacers and spare space
+ if( dynamicSpacers.size() > 0 && totalSize < parent->getSizeY() ) {
+ float spacerSize = (parent->getSizeY() - totalSize )/((float) dynamicSpacers.size() );
+ for( unsigned int i = 0; i < dynamicSpacers.size(); i++ ) {
+ dynamicSpacers[i]->setSizeY( spacerSize );
+ addedSpace += spacerSize;
+ }
+ }
+
+ totalSize += addedSpace;
+
+ setSizeY( totalSize );
+
+ // Now just fit the divider to the widest element
+ float maxSize = UNDEFINEDSIZE;
+ for( unsigned int i = 0; i < containers.size(); i++ ) {
+ if( containers[i]->getSizeX() > maxSize ) {
+ maxSize = containers[i]->getSizeX();
+ }
+ }
+
+ // if we've grown then resize the container and its elements
+ for( unsigned int i = 0; i < containers.size(); i++ ) {
+ if( containers[i]->getSizeX() != maxSize ) {
+ containers[i]->setSizeX( maxSize );
+ }
+ }
+
+ // finally, set our own dimensions
+ setSizeX( maxSize );
+
+
+ }
+ else {
+
+ // First calculate the height
+ for( unsigned int i = 0; i < containers.size(); i++ ) {
+
+ // see if this element is a dynamic spacer
+ // first check if there is an element in this container
+ if( containers[i]->contents != NULL ) {
+ // now see if we can cast it
+ IMSpacer* spacer = dynamic_cast<IMSpacer*>( containers[i]->contents );
+
+ if( spacer != NULL && spacer->isStatic == false ) {
+ // queue this up
+ dynamicSpacers.push_back( spacer );
+ // skip the rest of the loop so we don't add this to the total
+ continue;
+ }
+ }
+
+ // add to our total
+ totalSize += containers[i]->getSizeX();
+
+ }
+
+ float addedSpace = 0;
+ // see if we've got dynamic spacers and spare space
+ if( dynamicSpacers.size() > 0 && totalSize < parent->getSizeX() ) {
+ float spacerSize = (parent->getSizeX() - totalSize )/((float)dynamicSpacers.size() );
+ for( unsigned int i = 0; i < dynamicSpacers.size(); i++ ) {
+ dynamicSpacers[i]->setSizeX( spacerSize );
+ addedSpace += spacerSize;
+ }
+ }
+
+ totalSize += addedSpace;
+
+ setSizeX( totalSize );
+
+ // Now just fit the divider to the widest element
+ float maxSize = UNDEFINEDSIZE;
+ for( unsigned int i = 0; i < containers.size(); i++ ) {
+ if( containers[i]->getSizeY() > maxSize ) {
+ maxSize = containers[i]->getSizeY();
+ }
+ }
+
+ // if we've grown then resize the container and its elements
+ for( unsigned int i = 0; i < containers.size(); i++ ) {
+ if( containers[i]->getSizeY() != maxSize ) {
+ containers[i]->setSizeY( maxSize );
+ }
+ }
+
+ // finally, set our own dimensions
+ setSizeY( maxSize );
+
+ }
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief When a resize, move, etc has happened do whatever is necessary
+ *
+ */
+void IMDivider::doRelayout() {
+
+ // Invoke the parents relayout
+ IMElement::doRelayout();
+
+ // First pass this down to the children
+ for( unsigned int i = 0; i < containers.size(); i++ ) {
+ containers[i]->doRelayout();
+ }
+
+ checkRegions();
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Do whatever is necessary when the resolution changes
+ *
+ */
+void IMDivider::doScreenResize() {
+
+ // Invoke the superclass's method
+ IMElement::doScreenResize();
+
+ // Pass this down to the children
+ for( unsigned int i = 0; i < containers.size(); i++ ) {
+ containers[i]->doScreenResize();
+ }
+
+ onRelayout();
+
+}
+
+
+/*******************************************************************************************/
+/**
+ * @brief Convenience function to add a spacer element to this divider
+ *
+ * @param size Size of the element in terms of GUI space pixels
+ *
+ * @returns the space object created, just in case you need it
+ *
+ */
+IMSpacer* IMDivider::appendSpacer( float _size ) {
+
+ // Create a new spacer object
+ IMSpacer* newSpacer = new IMSpacer( orientation, _size );
+
+ // Add this to the divider (with a referene)
+ newSpacer->AddRef();
+
+ IMContainer* newContainer = append( newSpacer );
+ newContainer->Release();
+
+ // return a reference to this object in case the
+ // user needs to reference it (get the name, etc)
+ return newSpacer;
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Convenience function to add a dynamic spacer element to this divider
+ * A dynamic spacer will eat up any extra space in the divider
+ *
+ * @param size Size of the element in terms of GUI space pixels
+ *
+ * @returns the space object created, just in case you need it
+ *
+ */
+IMSpacer* IMDivider::appendDynamicSpacer() {
+ // Create a new spacer object
+ IMSpacer* newSpacer = new IMSpacer( orientation );
+
+ // Add this to the divider
+ append( newSpacer );
+
+ // return a reference to this object in case the
+ // user needs to reference it (get the name, etc)
+ return newSpacer;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Get the number of containers in this divider
+ *
+ * @returns count of the containers
+ *
+ */
+unsigned int IMDivider::getContainerCount() {
+ return containers.size();
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Fetch the container at the given index
+ *
+ *
+ * @param i index of the container
+ *
+ */
+IMContainer* IMDivider::getContainerAt( unsigned int i ) {
+ if( i >= containers.size() ) {
+ return NULL;
+ }
+ else {
+ containers[i]->AddRef();
+ return containers[i];
+ }
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the container of a named element
+ *
+ * @param _name Name of the element
+ *
+ * @returns container of the element (NULL if none)
+ *
+ */
+IMContainer* IMDivider::getContainerOf( std::string const& _name ) {
+ for( unsigned int i = 0; i < containers.size(); i++ ) {
+
+ if( containers[i]->contents != NULL && containers[i]->contents->getName() == _name ) {
+ return containers[i];
+ }
+ }
+ return NULL;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Adds an element to the divider
+ *
+ * @param newElement IMElement to add
+ * @param direction Portion of the divider to add to (default top/left)
+ *
+ */
+IMContainer* IMDivider::append( IMElement* newElement, float containerSize ) {
+
+ // Link to this element/owning GUI
+ newElement->setOwnerParent( owner, this );
+
+ // Make sure it's in front of us
+ newElement->setZOrdering( getZOrdering() + 1 );
+
+ // Make sure the element has a name
+ if( newElement->getName() == "" ) {
+ // build a new name
+ contentsNum++;
+
+ std::ostringstream oss;
+ oss << name << "_container_" << contentsNum;
+ newElement->name = oss.str();
+ }
+
+ // Make a new container for it
+ IMContainer* newContainer;
+
+ if( orientation == DOVertical ) {
+ newContainer = new IMContainer( SizePolicy( UNDEFINEDSIZE ).expand().overflowClip(true),
+ SizePolicy( containerSize ).expand().overflowClip(true) );
+ }
+ else {
+ newContainer = new IMContainer( SizePolicy( containerSize ).expand().overflowClip(true),
+ SizePolicy( UNDEFINEDSIZE ).expand().overflowClip(true) );
+ }
+
+ // Add a reference as setElement is expecting it
+ newElement->AddRef();
+ newContainer->setElement( newElement );
+ newContainer->setZOrdering( getZOrdering() );
+
+ // Link to this element/owning GUI
+ newContainer->setOwnerParent( owner, this );
+
+ vec2 newSize;
+
+ if( orientation == DOHorizontal ) {
+ // If we're not given a size, fit to the element size
+ if( containerSize == UNDEFINEDSIZE ) {
+ newSize.x() = newElement->getSizeX();
+ }
+ else {
+ newSize.x() = containerSize;
+ }
+
+ // Base the height on our current one
+ newSize.y() = getSizeY();
+ }
+ else {
+ // If we're not given a size, fit to the element size
+ if( containerSize == UNDEFINEDSIZE ) {
+ newSize.y() = newElement->getSizeY();
+ }
+ else {
+ newSize.y() = containerSize;
+ }
+
+ // Base the width on our current one
+ newSize.x() = getSizeX();
+ }
+
+ newContainer->setSize( newSize );
+ newContainer->setAlignment( contentsXAlignment, contentsYAlignment );
+
+ // Make sure we keep a reference to this
+ newContainer->AddRef();
+
+ containers.push_back( newContainer );
+
+ // Signal that something new has changed
+ onRelayout();
+
+ // get rid of the reference we were given
+ newElement->Release();
+
+ return newContainer;
+
+}
+
+
+/*******************************************************************************************/
+/**
+ * @brief Find an element by name — called internally
+ *
+ *
+ */
+IMElement* IMDivider::findElement( std::string const& elementName ) {
+
+ // Check if this is the droid we're looking for
+ if( name == elementName ) {
+ // Up our reference count
+ AddRef();
+ return this;
+ }
+ else {
+ // If not, pass the request onto the children
+
+ for( unsigned int i = 0; i < containers.size(); i++ ) {
+
+ IMElement* results = containers[i]->findElement( elementName );
+
+ if( results != NULL ) {
+ return results;
+ }
+ }
+
+ // if we've got this, far we don't have it and so report
+ return NULL;
+ }
+}
+
+void IMDivider::setPauseBehaviors( bool pause ) {
+ IMElement::setPauseBehaviors( pause );
+ for( unsigned int i = 0; i < containers.size(); i++ ) {
+ containers[i]->setPauseBehaviors( pause );
+ }
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Remove all referenced object without releaseing references
+ *
+ */
+void IMDivider::clense() {
+ IMElement::clense();
+ containers.clear();
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Destructor
+ *
+ */
+IMDivider::~IMDivider() {
+ IMrefCountTracker.removeRefCountObject( getElementTypeName() );
+
+ std::vector<IMContainer*> deletelist = containers;
+ containers.clear();
+
+ for( std::vector<IMContainer*>::iterator it = deletelist.begin();
+ it != deletelist.end();
+ ++it ) {
+ (*it)->Release();
+ }
+}
+
+void IMDivider::DestroyedIMElement( IMElement* element ) {
+ for( int i = containers.size()-1; i >= 0; i-- ) {
+ if( containers[i] == element ) {
+ containers.erase(containers.begin()+i);
+ }
+ }
+
+ IMElement::DestroyedIMElement(element);
+}
+
+void IMDivider::DestroyedIMGUI( IMGUI* imgui ) {
+
+ IMElement::DestroyedIMGUI(imgui);
+}
diff --git a/Source/GUI/IMUI/im_divider.h b/Source/GUI/IMUI/im_divider.h
new file mode 100644
index 00000000..b4c97ac8
--- /dev/null
+++ b/Source/GUI/IMUI/im_divider.h
@@ -0,0 +1,255 @@
+//-----------------------------------------------------------------------------
+// Name: im_divider.h
+// Developer: Wolfire Games LLC
+// Description: Specialized container element class for creating adhoc GUIs
+// as part of the UI tools
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <GUI/IMUI/im_support.h>
+#include <GUI/IMUI/im_element.h>
+#include <GUI/IMUI/im_container.h>
+#include <GUI/IMUI/im_spacer.h>
+
+/*******************************************************************************************/
+/**
+ * @brief Basic container class, holds other elements
+ *
+ */
+class IMDivider : public IMElement {
+
+public:
+
+ std::vector<IMContainer*> containers; // Whats in this divider
+ DividerOrientation orientation; // vertical by default
+ int contentsNum; // just a counter for making unique names
+
+ ContainerAlignment contentsXAlignment; // horizontal alignment of new elements
+ ContainerAlignment contentsYAlignment; // vertical alignment of new elements
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor
+ *
+ * @param name IMElement name
+ * @param _orientation The orientation of the container
+ *
+ */
+ IMDivider( std::string const& name, DividerOrientation _orientation = DOVertical );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor
+ *
+ * @param name IMElement name
+ * @param _orientation The orientation of the container
+ *
+ */
+ IMDivider( DividerOrientation _orientation = DOVertical );
+
+ /*******
+ *
+ * Angelscript factory
+ *
+ */
+ static IMDivider* ASFactory_named( std::string const& name, DividerOrientation _orientation = DOVertical ) {
+ return new IMDivider( name, _orientation );
+ }
+
+ static IMDivider* ASFactory_unnamed( DividerOrientation _orientation = DOVertical ) {
+ return new IMDivider( _orientation );
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the name of the type of this element — for autonaming and debugging
+ *
+ * @returns name of the element type as a string
+ *
+ */
+ std::string getElementTypeName();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Set’s this element’s parent (and does nessesary logic)
+ *
+ * @param _parent New parent
+ *
+ */
+ void setOwnerParent( IMGUI* _owner, IMElement* _parent );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Set’s the alignment of the contained object
+ *
+ * @param xAlignment horizontal alignment
+ * @param yAlignment vertical alignment
+ * @param reposition should we go through and reposition existing objects
+ *
+ */
+ void setAlignment( ContainerAlignment xAlignment, ContainerAlignment yAlignment, bool reposition = true );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Clear the contents of this divider, leaving everything else the same
+ *
+ */
+ void clear();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Updates the element
+ *
+ * @param delta Number of millisecond elapsed since last update
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param guistate The state of the GUI at this update
+ *
+ */
+ void update( uint64_t delta, vec2 drawOffset, GUIState& guistate );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Rather counter-intuitively, this draws this object on the screen
+ *
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param clipPos pixel location of upper lefthand corner of clipping region
+ * @param clipSize size of clipping region
+ *
+ */
+ void render( vec2 drawOffset, vec2 clipPos, vec2 clipSize );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Rederive the regions for the various orientation containers - for internal use
+ *
+ */
+ void checkRegions();
+
+ /*******************************************************************************************/
+ /**
+ * @brief When a resize, move, etc has happened do whatever is necessary
+ *
+ */
+ void doRelayout();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Do whatever is necessary when the resolution changes
+ *
+ */
+ void doScreenResize();
+
+
+ /*******************************************************************************************/
+ /**
+ * @brief Convenience function to add a spacer element to this divider
+ *
+ * @param size Size of the element in terms of GUI space pixels
+ *
+ * @returns the space object created, just in case you need it
+ *
+ */
+ IMSpacer* appendSpacer( float _size );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Convenience function to add a dynamic spacer element to this divider
+ * A dynamic spacer will eat up any extra space in the divider
+ *
+ * @param size Size of the element in terms of GUI space pixels
+ *
+ * @returns the space object created, just in case you need it
+ *
+ */
+ IMSpacer* appendDynamicSpacer();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Get the number of containers in this divider
+ *
+ * @returns count of the containers
+ *
+ */
+ unsigned int getContainerCount();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Fetch the container at the given index
+ *
+ *
+ * @param i index of the container
+ *
+ */
+ IMContainer* getContainerAt( unsigned int i );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the container of a named element
+ *
+ * @param _name Name of the element
+ *
+ * @returns container of the element (NULL if none)
+ *
+ */
+ IMContainer* getContainerOf( std::string const& _name );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Adds an element to the divider
+ *
+ * @param newElement IMElement to add
+ * @param direction Portion of the divider to add to (default top/left)
+ *
+ */
+ IMContainer* append( IMElement* newElement, float containerSize = UNDEFINEDSIZE );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Find an element by name — called internally
+ *
+ *
+ */
+ IMElement* findElement( std::string const& elementName );
+
+ void setPauseBehaviors( bool pause );
+
+ DividerOrientation getOrientation() const { return orientation; }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Remove all referenced object without releaseing references
+ *
+ */
+ virtual void clense();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Destructor
+ *
+ */
+ virtual ~IMDivider();
+
+
+ virtual void DestroyedIMElement( IMElement* element );
+ virtual void DestroyedIMGUI( IMGUI* imgui );
+};
+
+
diff --git a/Source/GUI/IMUI/im_element.cpp b/Source/GUI/IMUI/im_element.cpp
new file mode 100644
index 00000000..864dc1cf
--- /dev/null
+++ b/Source/GUI/IMUI/im_element.cpp
@@ -0,0 +1,1737 @@
+//-----------------------------------------------------------------------------
+// Name: im_element.cpp
+// Developer: Wolfire Games LLC
+// Description: Base class for all AdHoc Gui elements
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "im_element.h"
+
+#include <GUI/IMUI/imgui.h>
+
+#include <algorithm>
+#include <map>
+#include <cmath>
+
+/*******************************************************************************************/
+/**
+ * @brief Constructor
+ *
+ * @param _name Name for this object (incumbent on the programmer to make sure they're unique)
+ *
+ */
+IMElement::IMElement( std::string const& _name ) :
+ size( UNDEFINEDSIZE,UNDEFINEDSIZE ),
+ defaultSize( UNDEFINEDSIZE,UNDEFINEDSIZE ),
+ drawDisplacement( 0.0f, 0.0f ),
+ zOrdering(1),
+ parent(NULL),
+ owner(NULL),
+ show(true),
+ shouldClip(true),
+ color(1.0,1.0,1.0,1.0),
+ effectColor(1.0,1.0,1.0,1.0),
+ isColorEffected(false),
+ border(false),
+ borderSize(1),
+ borderColor(1.0,1.0,1.0,1.0),
+ mouseOver(false),
+ mouse_clicking(false),
+ mouseDownForChildren(false),
+ mouseOverForChildren(false),
+ refCount(1),
+ numBehaviors(0),
+ pauseBehaviors(false),
+ scriptMouseOver(false)
+{
+ name = _name;
+ setPadding( 0.0, 0.0, 0.0, 0.0 );
+
+ imevents.RegisterListener(this);
+}
+
+std::vector<IMElement*> IMElement::deletion_schedule;
+
+/*******
+ *
+ * Angelscript memory management boilerplate
+ *
+ */
+void IMElement::AddRef()
+{
+ // Increase the reference counter
+ refCount++;
+}
+
+void IMElement::Release()
+{
+ refCount--;
+ // Decrease ref count and delete if it reaches 0
+ if( refCount == 0 ) {
+ for(IMElement* element : IMElement::deletion_schedule) {
+ if(element == this) {
+ return;
+ }
+ }
+ IMElement::deletion_schedule.push_back(this);
+ } else if( refCount < 0 ) {
+ LOGE << "Negative refcount" << std::endl;
+ }
+}
+
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the name of the type of this element — for autonaming and debugging
+ *
+ * @returns name of the element type as a string
+ *
+ */
+std::string IMElement::getElementTypeName() {
+ return "Element";
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Set’s this element’s parent (and does nessesary logic)
+ *
+ * @param _parent New parent
+ *
+ */
+void IMElement::setOwnerParent( IMGUI* _owner, IMElement* _parent ) {
+ owner = _owner;
+ parent = _parent;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Set the color for the element
+ *
+ * @param _color 4 component vector for the color
+ *
+ */
+void IMElement::setColor( vec4 _color ) {
+ color = _color;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the current color
+ *
+ * If the color is effected, it'll return the effected color
+ *
+ * @returns 4 component vector of the color
+ *
+ */
+ vec4 IMElement::getColor() {
+ if(isColorEffected ) {
+ return effectColor;
+ }
+ else {
+ return color;
+ }
+ }
+
+ /*******************************************************************************************/
+/**
+ * @brief Gets the current color -- ignoring the effect color
+ *
+ * @returns 4 component vector of the color
+ *
+ */
+ vec4 IMElement::getBaseColor() {
+ return color;
+ }
+
+
+/*******************************************************************************************/
+/**
+ * @brief Set the effect color for the element
+ *
+ * @param _color 4 component vector for the color
+ *
+ */
+void IMElement::setEffectColor( vec4 _color ) {
+ isColorEffected = true;
+ effectColor = _color;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the effect current color
+ *
+ * @returns 4 component vector of the color
+ *
+ */
+ vec4 IMElement::getEffectColor() {
+ if( !isColorEffected ) {
+ return color;
+ }
+ return effectColor;
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Clears any effect color (reseting to the base)
+ *
+ */
+ void IMElement::clearColorEffect() {
+ isColorEffected = false;
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the red value
+ *
+ * @param value Color value
+ *
+ */
+ void IMElement::setR( float value ) {
+ color.x() = value;
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the red value
+ *
+ * @returns Color value
+ *
+ */
+ float IMElement::getR() {
+ return color.x();
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the green value
+ *
+ * @param value Color value
+ *
+ */
+ void IMElement::setG( float value ) {
+ color.y() = value;
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the green value
+ *
+ * @returns Color value
+ *
+ */
+ float IMElement::getG() {
+ return color.y();
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the blue value
+ *
+ * @param value Color value
+ *
+ */
+ void IMElement::setB( float value ) {
+ color.z() = value;
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the blue value
+ *
+ * @returns Color value
+ *
+ */
+ float IMElement::getB() {
+ return color.y();
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the alpha value
+ *
+ * @param value Color value
+ *
+ */
+ void IMElement::setAlpha( float value ) {
+ color.a() = value;
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the alpha value
+ *
+ * @returns Color value
+ *
+ */
+ float IMElement::getAlpha() {
+ return color.a();
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the effect red value
+ *
+ * @param value Color value
+ *
+ */
+ void IMElement::setEffectR( float value ) {
+ if( !isColorEffected ) {
+ effectColor = color;
+ }
+ isColorEffected = true;
+ effectColor.x() = value;
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the effect red value
+ *
+ * @returns Color value
+ *
+ */
+float IMElement::getEffectR() {
+ if( !isColorEffected ) {
+ return color.x();
+ }
+ else {
+ return effectColor.x();
+ }
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Clear effect red value
+ *
+ */
+void IMElement::clearEffectR() {
+ isColorEffected = false;
+ effectColor.x() = color.x();
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the effect green value
+ *
+ * @param value Color value
+ *
+ */
+ void IMElement::setEffectG( float value ) {
+ if( !isColorEffected ) {
+ effectColor = color;
+ }
+ isColorEffected = true;
+ effectColor.y() = value;
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the effect green value
+ *
+ * @returns Color value
+ *
+ */
+ float IMElement::getEffectG() {
+ if( !isColorEffected ) {
+ return color.y();
+ }
+ else {
+ return effectColor.y();
+ }
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Clear effect green value
+ *
+ */
+void IMElement::clearEffectG() {
+ isColorEffected = false;
+ effectColor.y() = color.y();
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the blue value
+ *
+ * @param value Color value
+ *
+ */
+ void IMElement::setEffectB( float value ) {
+ if( !isColorEffected ) {
+ effectColor = color;
+ }
+ isColorEffected = true;
+ effectColor.z() = value;
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the blue value
+ *
+ * @returns Color value
+ *
+ */
+ float IMElement::getEffectB() {
+ if( !isColorEffected ) {
+ return color.z();
+ }
+ else {
+ return effectColor.z();
+ }
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Clear effect blue value
+ *
+ */
+void IMElement::clearEffectB() {
+ isColorEffected = false;
+ effectColor.z() = color.z();
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the alpha value
+ *
+ * @param value Color value
+ *
+ */
+ void IMElement::setEffectAlpha( float value ) {
+ if( !isColorEffected ) {
+ effectColor = color;
+ }
+ isColorEffected = true;
+ effectColor.a() = value;
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the alpha value
+ *
+ * @returns Color value
+ *
+ */
+ float IMElement::getEffectAlpha() {
+ if( !isColorEffected ) {
+ return color.z();
+ }
+ else {
+ return effectColor.z();
+ }
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Clear effect alpha value
+ *
+ */
+void IMElement::clearEffectAlpha() {
+ isColorEffected = false;
+ effectColor = color;
+}
+
+
+/*******************************************************************************************/
+/**
+ * @brief Should this element have a border
+ *
+ * @param _border Show this border or not
+ *
+ */
+ void IMElement::showBorder( bool _border ) {
+ border = _border;
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the border thickness
+ *
+ * @param thickness Thickness of the border in GUI space pixels
+ *
+ */
+ void IMElement::setBorderSize( float _borderSize ) {
+ borderSize = _borderSize;
+ }
+
+
+
+/*******************************************************************************************/
+/**
+ * @brief Set the color for the border
+ *
+ * @param _color 4 component vector for the color
+ *
+ */
+void IMElement::setBorderColor( vec4 _color ) {
+ borderColor = _color;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the current border color
+ *
+ * @returns 4 component vector of the color
+ *
+ */
+ vec4 IMElement::getBorderColor() {
+ return borderColor;
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the border red value
+ *
+ * @param value Color value
+ *
+ */
+ void IMElement::setBorderR( float value ) {
+ borderColor.x() = value;
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the border red value
+ *
+ * @returns Color value
+ *
+ */
+ float IMElement::getBorderR() {
+ return borderColor.x();
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the border green value
+ *
+ * @param value Color value
+ *
+ */
+ void IMElement::setBorderG( float value ) {
+ borderColor.y() = value;
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the border green value
+ *
+ * @returns Color value
+ *
+ */
+ float IMElement::getBorderG() {
+ return borderColor.y();
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the border blue value
+ *
+ * @param value Color value
+ *
+ */
+ void IMElement::setBorderB( float value ) {
+ borderColor.z() = value;
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the border blue value
+ *
+ * @returns Color value
+ *
+ */
+ float IMElement::getBorderB() {
+ return borderColor.y();
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the border alpha value
+ *
+ * @param value Color value
+ *
+ */
+ void IMElement::setBorderAlpha( float value ) {
+ borderColor.a() = value;
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the border alpha value
+ *
+ * @returns Color value
+ *
+ */
+ float IMElement::getBorderAlpha() {
+ return borderColor.a();
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the z ordering (order of drawing, higher is drawing on top of lower)
+ *
+ * @param z new Z ordering value (expected to be greater then 0 and the parent container)
+ *
+ */
+ void IMElement::setZOrdering( int z ) {
+ zOrdering = z;
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the z ordering (order of drawing - higher is drawing on top of lower)
+ *
+ * @returns current Z ordering value
+ *
+ */
+ int IMElement::getZOrdering() {
+ return zOrdering;
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Set the z ordering of this element to be higher than the given element
+ *
+ * @param element Element to be below this one
+ *
+ */
+ void IMElement::renderAbove( IMElement* element ) {
+ setZOrdering( element->getZOrdering() + 1 );
+ element->Release();
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Set the z ordering of this element to be lower than the given element
+ *
+ * (note that if the element parameter has a z value within 1 of the parent container
+ * this element will be assigned to the same value, which may not look nice )
+ *
+ * @param element Element to be below this one
+ *
+ */
+ void IMElement::renderBelow( IMElement* element ) {
+
+ int minZ;
+ // See if we're a root container
+ if( parent != NULL ) {
+ // not a root
+ minZ = parent->getZOrdering();
+ }
+ else {
+ // we're a root
+ minZ = 1;
+ }
+
+ // set the z ordering, respecting the derived minimum
+ setZOrdering( max( minZ, element->getZOrdering() - 1 ) );
+
+ element->Release();
+
+ }
+
+
+/*******************************************************************************************/
+/**
+ * @brief Show or hide this element
+ *
+ * @param _show Show this element or not
+ *
+ */
+ void IMElement::setVisible( bool _show ) {
+ show = _show;
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Should this element be including in the container clipping?
+ *
+ * @param _clip Clip this element or not
+ *
+ */
+ void IMElement::setClip( bool _clip ) {
+ shouldClip = _clip;
+ }
+
+
+/*******************************************************************************************/
+/**
+ * @brief Draw this object on the screen
+ *
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param clipPos pixel location of upper lefthand corner of clipping region
+ * @param clipSize size of clipping region
+ *
+ */
+void IMElement::render( vec2 drawOffset, vec2 currentClipPos, vec2 currentClipSize ) {
+
+ // see if we're visible
+ if( !show ) return;
+
+ // See if we're supposed to draw a border
+ if( border ) {
+
+ vec2 borderCornerUL = drawOffset + drawDisplacement + vec2(2,2) + vec2( paddingL, paddingU );
+ vec2 borderCornerLR = drawOffset + drawDisplacement + size - vec2(1,1) + vec2( paddingR, paddingD );
+
+ vec2 screenCornerUL = screenMetrics.GUIToScreen( borderCornerUL );
+ vec2 screenCornerLR = screenMetrics.GUIToScreen( borderCornerLR );
+
+ // figure out the thickness in screen pixels (minimum 1)
+ float thickness = max( borderSize * screenMetrics.GUItoScreenXScale, 1.0f );
+
+ // top
+ owner->drawBox( screenCornerUL,
+ vec2( screenCornerLR.x() - screenCornerUL.x(), thickness ),
+ borderColor,
+ zOrdering + 1,
+ shouldClip,
+ currentClipPos,
+ currentClipSize );
+
+ // bottom
+ owner->drawBox( vec2( screenCornerUL.x(), screenCornerLR.y() - thickness ),
+ vec2( screenCornerLR.x() - screenCornerUL.x(), thickness ),
+ borderColor,
+ zOrdering + 1,
+ shouldClip,
+ currentClipPos,
+ currentClipSize );
+
+ // left
+ owner->drawBox( vec2( screenCornerUL.x(), screenCornerUL.y() + thickness ),
+ vec2( thickness, screenCornerLR.y() - screenCornerUL.y() - (2 * thickness) ),
+ borderColor,
+ zOrdering + 1,
+ shouldClip,
+ currentClipPos,
+ currentClipSize );
+ // right
+ owner->drawBox( vec2( screenCornerLR.x() - thickness, screenCornerUL.y() + thickness ),
+ vec2( thickness, screenCornerLR.y() - screenCornerUL.y() - (2 * thickness) ),
+ borderColor,
+ zOrdering + 1,
+ shouldClip,
+ currentClipPos,
+ currentClipSize );
+
+ }
+}
+
+vec2 IMElement::getScreenPosition( ){
+
+ vec2 GUIRenderPos = lastDrawOffset + vec2( paddingL, paddingU ) + drawDisplacement;
+
+ vec2 screenRenderPos = screenMetrics.GUIToScreen( GUIRenderPos );
+
+ return vec2( screenRenderPos.x(), screenRenderPos.y() );
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Checks to see if a point is inside this element
+ *
+ * @param drawOffset The upper left hand corner of where the boundary is drawn
+ * @param point point in question
+ *
+ * @returns true if inside, false otherwise
+ *
+ */
+bool IMElement::pointInElement( vec2 drawOffset, vec2 point ) {
+
+ vec2 UL = drawOffset + drawDisplacement;
+ vec2 LR = UL + size;
+
+ if( UL.x() <= point.x() && UL.y() <= point.y() &&
+ LR.x() > point.x() && LR.y() > point.y() ) {
+ return true;
+ }
+ else {
+ return false;
+ }
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Add an update behavior
+ *
+ * @param behavior Handle to behavior in question
+ * @param behaviorName name to identify the behavior
+ *
+ */
+void IMElement::addUpdateBehavior( IMUpdateBehavior* behavior, std::string const& behaviorName ) {
+
+ std::string bName = behaviorName;
+
+ // if they haven't given us a name, generate one
+ if( bName == "" ) {
+ std::ostringstream oss;
+ oss << "behavior" << numBehaviors;
+ bName = oss.str();
+ }
+ else if( updateBehaviors.find( bName) != updateBehaviors.end() ) {
+ removeUpdateBehavior( bName );
+ }
+
+ // add a reference as we're storing it
+ behavior->AddRef();
+
+ updateBehaviors[bName] = behavior;
+
+ // get rid of the reference we were given
+ behavior->Release();
+
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Removes a named update behavior
+ *
+ * @param behaviorName name to identify the behavior
+ *
+ * @returns true if there was a behavior to remove, false otherwise
+ *
+ */
+ bool IMElement::removeUpdateBehavior( std::string const& behaviorName ) {
+ // see if there is already a behavior with this name
+ UBMap::iterator it = updateBehaviors.find(behaviorName);
+ if( it != updateBehaviors.end() ) {
+ // if so clean it up if its been initialized
+ IMUpdateBehavior* updater = it->second;
+
+ if( updater->initialized ) {
+ updater->cleanUp( this );
+ }
+
+ // and remove it, deref it
+ updater->Release();
+ updateBehaviors.erase( it );
+
+ return true;
+ }
+ else {
+ // Let the caller know
+ return false;
+ }
+
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Indicates if a behavior exists, can be used to see if its finished.
+ *
+ * @param behaviorName name to identify the behavior
+ *
+ * @returns true if there was a behavior
+ *
+ */
+bool IMElement::hasUpdateBehavior(std::string const& behaviorName)
+{
+
+ UBMap::iterator it = updateBehaviors.find(behaviorName);
+ if( it != updateBehaviors.end() ) {
+ return true;
+ }
+ else {
+ // Let the caller know
+ return false;
+ }
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Clear update behaviors
+ *
+ */
+ void IMElement::clearUpdateBehaviors() {
+
+ // iterate through all the behaviors and clean them up
+ for( UBMap::iterator it = updateBehaviors.begin();
+ it != updateBehaviors.end();
+ ++it ) {
+ IMUpdateBehavior* updater = it->second;
+ updater->cleanUp( this );
+ updater->Release();
+ }
+
+ updateBehaviors.clear();
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Add a mouse over behavior
+ *
+ * @param behavior Handle to behavior in question
+ * @param behaviorName name to identify the behavior
+ *
+ */
+ void IMElement::addMouseOverBehavior( IMMouseOverBehavior* behavior, std::string const& behaviorName ) {
+
+ std::string bName = behaviorName;
+
+ // if they haven't given us a name, generate one
+ if( bName == "" ) {
+
+ std::ostringstream oss;
+ oss << "behavior" << numBehaviors;
+ bName = oss.str();
+ numBehaviors++;
+
+ }
+ else if( mouseOverBehaviors.find( bName) != mouseOverBehaviors.end() ) {
+ // Remove old behavior with this name (if any)
+ removeMouseOverBehavior( bName );
+ }
+
+ // add a reference as we're storing it
+ behavior->AddRef();
+
+ mouseOverBehaviors[bName] = behavior;
+
+ // get rid of the reference we were given
+ behavior->Release();
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Removes a named update behavior
+ *
+ * @param behaviorName name to identify the behavior
+ *
+ * @returns true if there was a behavior to remove, false otherwise
+ *
+ */
+ bool IMElement::removeMouseOverBehavior( std::string const& behaviorName ) {
+ // see if there is already a behavior with this name
+ MOBMap::iterator it = mouseOverBehaviors.find(behaviorName);
+ if( it != mouseOverBehaviors.end() ) {
+
+ // if so clean it up
+ IMMouseOverBehavior* updater = it->second;
+ updater->cleanUp( this );
+
+ // and remove/deref it
+ updater->Release();
+ mouseOverBehaviors.erase( it );
+
+ return true;
+ }
+ else {
+ // Let the caller know
+ return false;
+ }
+
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Clear mouse over behaviors
+ *
+ */
+ void IMElement::clearMouseOverBehaviors() {
+
+ // iterate through all the behaviors and clean them up
+ for( MOBMap::iterator it = mouseOverBehaviors.begin();
+ it != mouseOverBehaviors.end();
+ ++it ) {
+ IMMouseOverBehavior* updater = it->second;
+ updater->Release();
+ updater->cleanUp( this );
+ }
+
+ mouseOverBehaviors.clear();
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Add a click behavior
+ *
+ * @param behavior Handle to behavior in question
+ * @param behaviorName name to identify the behavior
+ *
+ */
+void IMElement::addLeftMouseClickBehavior( IMMouseClickBehavior* behavior, std::string const& behaviorName ) {
+ std::string bName = behaviorName;
+
+ // if they haven't given us a name, generate one
+ if( bName == "" ) {
+ std::ostringstream oss;
+ oss << "behavior" << numBehaviors;
+ bName = oss.str();
+ numBehaviors++;
+ }
+ else if( leftMouseClickBehaviors.find( bName) != leftMouseClickBehaviors.end() ) {
+ removeLeftMouseClickBehavior( bName );
+ }
+
+ // add a reference as we're storing it
+ behavior->AddRef();
+
+ leftMouseClickBehaviors[bName] = behavior;
+
+ // get rid of the reference we were given
+ behavior->Release();
+}
+
+
+/*******************************************************************************************/
+/**
+ * @brief Removes a named click behavior
+ *
+ * @param behaviorName name to identify the behavior
+ *
+ * @returns true if there was a behavior to remove, false otherwise
+ *
+ */
+ bool IMElement::removeLeftMouseClickBehavior( std::string const& behaviorName ) {
+
+ // see if there is already a behavior with this name
+ MCBMap::iterator it = leftMouseClickBehaviors.find(behaviorName);
+ if( it != leftMouseClickBehaviors.end() ) {
+
+ // if so clean it up
+ IMMouseClickBehavior* updater = it->second;
+ updater->cleanUp( this );
+
+ // and remove/deref it
+ updater->Release();
+ leftMouseClickBehaviors.erase( it );
+
+ return true;
+ }
+ else {
+ // Let the caller know
+ return false;
+ }
+
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Clear mouse over behaviors
+ *
+ */
+void IMElement::clearLeftMouseClickBehaviors() {
+
+ // iterate through all the behaviors and clean them up
+ for( MCBMap::iterator it = leftMouseClickBehaviors.begin();
+ it != leftMouseClickBehaviors.end();
+ ++it ) {
+ IMMouseClickBehavior* updater = it->second;
+ updater->cleanUp( this );
+ updater->Release();
+ }
+
+ leftMouseClickBehaviors.clear();
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Updates the element
+ *
+ * @param delta Number of millisecond elapsed since last update
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param guistate The state of the GUI at this update
+ *
+ */
+void IMElement::update( uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ // Update behaviors
+ if(!pauseBehaviors)
+ {
+ std::vector<std::string> remove_list;
+
+ for( UBMap::iterator it = updateBehaviors.begin();
+ it != updateBehaviors.end();
+ ++it ) {
+
+ IMUpdateBehavior* updater = it->second;
+
+ // See if this behavior has been initialized
+ if( !updater->initialized ) {
+
+ if( !updater->initialize( this, delta, drawOffset, guistate ) ) {
+ // If the behavior has indicated it should not begin remove it
+ remove_list.push_back(it->first);
+ continue;
+ }
+ else {
+ updater->initialized = true;
+ }
+ }
+
+ if( !updater->update( this, delta, drawOffset, guistate ) ) {
+ // If the behavior has indicated it is done
+ remove_list.push_back(it->first);
+ }
+ }
+
+ std::vector<std::string>::iterator rmit;
+ for( rmit = remove_list.begin(); rmit != remove_list.end(); rmit++) {
+ removeUpdateBehavior(*rmit);
+ }
+ lastDrawOffset = drawOffset;
+ }
+
+ // Now do mouse behaviors
+
+ // Mouse overs
+
+ if( (scriptMouseOver || (pointInElement( drawOffset, guistate.mousePosition ) || guistate.inheritedMouseOver)) && !pauseBehaviors) {
+ if( mouseOverForChildren ) {
+ guistate.inheritedMouseOver = true;
+ }
+
+ if( !mouseOver ) {
+ mouseOver = true;
+
+ // Update behaviors
+ for( MOBMap::iterator it = mouseOverBehaviors.begin();
+ it != mouseOverBehaviors.end();
+ ++it ) {
+
+ IMMouseOverBehavior* behavior = it->second;
+ behavior->onStart( this, delta, drawOffset, guistate );
+ }
+ }
+ else {
+
+ // Update behaviors
+ for( MOBMap::iterator it = mouseOverBehaviors.begin();
+ it != mouseOverBehaviors.end();
+ ++it ) {
+
+ IMMouseOverBehavior* behavior = it->second;
+ behavior->onContinue( this, delta, drawOffset, guistate );
+ }
+
+ }
+ }
+ else {
+ // See if this is an 'exit'
+ if( mouseOver )
+ {
+ std::vector<std::string> remove_list;
+ for( MOBMap::iterator it = mouseOverBehaviors.begin();
+ it != mouseOverBehaviors.end();
+ ++it ) {
+ IMMouseOverBehavior* behavior = it->second;
+
+ if( !behavior->onFinish( this, delta, drawOffset, guistate ) ) {
+ // If the behavior has indicated it is done
+ remove_list.push_back(it->first);
+
+ }
+ }
+ mouseOver = false;
+ std::vector<std::string>::iterator rm_it;
+ for( rm_it = remove_list.begin(); rm_it != remove_list.end(); rm_it++ ) {
+ removeMouseOverBehavior( *rm_it );
+ }
+
+ }
+ }
+
+ if(mouse_clicking && guistate.leftMouseState == IMUIContext::kMouseStillUp){
+ mouse_clicking = false;
+ }
+
+ // Mouse click status
+ if( !pauseBehaviors && (!guistate.clickHandled && ( pointInElement( drawOffset, guistate.mousePosition ) || guistate.inheritedMouseDown )) ) {
+
+ IMUIContext::ButtonState effectiveState = guistate.leftMouseState;
+
+ // See if we have an override from our parent
+ if( guistate.inheritedMouseDown ) {
+ effectiveState = guistate.inheritedMouseState;
+ }
+
+ switch( effectiveState ) {
+
+ case IMUIContext::kMouseDown: {
+
+ if( mouseDownForChildren ) {
+ guistate.inheritedMouseDown = true;
+ }
+
+ mouse_clicking = true;
+
+ std::vector<std::string> remove_list;
+ // Update behaviors
+ for( MCBMap::iterator it = leftMouseClickBehaviors.begin();
+ it != leftMouseClickBehaviors.end();
+ ++it ) {
+
+ guistate.clickHandled = true;
+
+ IMMouseClickBehavior* behavior = it->second;
+ if( !behavior->onDown( this, delta, drawOffset, guistate ) ) {
+ // If the behavior has indicated it is done
+ remove_list.push_back(it->first);
+ }
+ }
+
+ std::vector<std::string>::iterator rm_it;
+ for( rm_it = remove_list.begin(); rm_it != remove_list.end(); rm_it++ ) {
+ removeLeftMouseClickBehavior( *rm_it );
+ }
+
+ }
+ break;
+
+ case IMUIContext::kMouseStillDown: {
+
+ if( mouseDownForChildren ) {
+ guistate.inheritedMouseDown = true;
+ }
+
+ std::vector<std::string> remove_list;
+ for( MCBMap::iterator it = leftMouseClickBehaviors.begin();
+ it != leftMouseClickBehaviors.end();
+ ++it ) {
+
+ guistate.clickHandled = true;
+
+ IMMouseClickBehavior* behavior = it->second;
+
+ if( !behavior->onStillDown( this, delta, drawOffset, guistate ) ) {
+ // If the behavior has indicated it is done
+ remove_list.push_back(it->first);
+
+ }
+ }
+
+ std::vector<std::string>::iterator rm_it;
+ for( rm_it = remove_list.begin(); rm_it != remove_list.end(); rm_it++ ){
+ removeLeftMouseClickBehavior( *rm_it );
+ }
+
+ }
+ break;
+
+ case IMUIContext::kMouseUp: {
+
+ if(mouse_clicking){
+ for( MCBMap::iterator it = leftMouseClickBehaviors.begin();
+ it != leftMouseClickBehaviors.end();
+ ++it ) {
+
+ guistate.clickHandled = true;
+
+ IMMouseClickBehavior* behavior = it->second;
+
+ if( !behavior->onUp( this, delta, drawOffset, guistate ) ) {
+ // If the behavior has indicated it is done
+ removeLeftMouseClickBehavior( it->first );
+ }
+
+ }
+ }
+ mouse_clicking = false;
+
+ // Consider this no longer hovering
+ mouseOver = false;
+ {
+ std::vector<std::string> remove_list;
+ for( MOBMap::iterator it = mouseOverBehaviors.begin();
+ it != mouseOverBehaviors.end();
+ ++it ) {
+
+ guistate.clickHandled = true;
+
+ IMMouseOverBehavior* behavior = it->second;
+ if( !behavior->onFinish( this, delta, drawOffset, guistate ) ) {
+ // If the behavior has indicated it is done
+ remove_list.push_back( it->first );
+
+ }
+ }
+
+ std::vector<std::string>::iterator rm_it;
+ for( rm_it = remove_list.begin(); rm_it != remove_list.end(); rm_it++ ) {
+ removeMouseOverBehavior( *rm_it );
+ }
+ }
+ }
+ break;
+
+ case IMUIContext::kMouseStillUp:
+ default:
+
+ break;
+
+ }
+
+ }
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief When this element is resized, moved, etc propagate this signal upwards
+ *
+ */
+ void IMElement::onRelayout() {
+
+ if( owner != NULL ) {
+ owner->onRelayout();
+ }
+
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief When this element has an error, propagate it upwards
+ *
+ * @param newError Error message
+ *
+ */
+void IMElement::onError( std::string const& newError ) {
+
+ if( owner != NULL ) {
+ owner->reportError( newError );
+ }
+
+ }
+
+
+/*******************************************************************************************/
+/**
+ * @brief When a resize, move, etc has happened do whatever is necessary
+ *
+ */
+ void IMElement::doRelayout() {
+ // Nothing to do in the base class
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Do whatever is necessary when the resolution changes
+ *
+ */
+ void IMElement::doScreenResize() {
+ // Nothing to do in the base class
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Set the name of this element
+ *
+ * @param _name New name (incumbent on the programmer to make sure they're unique)
+ *
+ */
+void IMElement::setName( std::string const& _name ) {
+
+ name = _name;
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the name of this element
+ *
+ * @returns name of this element
+ *
+ */
+std::string IMElement::getName() {
+ return name;
+}
+
+
+/*******************************************************************************************/
+/**
+ * @brief Set the padding for each direction on the element
+ *
+ * UNDEFINEDSIZE will cause no change
+ *
+ * @param U (minimum) Padding between the element and the upper boundary
+ * @param D (minimum) Padding between the element and the lower boundary
+ * @param L (minimum) Padding between the element and the left boundary
+ * @param R (minimum) Padding between the element and the right boundary
+ *
+ */
+void IMElement::setPadding( float U, float D, float L, float R) {
+
+ if( U != UNDEFINEDSIZE ) { paddingU = U; }
+ if( D != UNDEFINEDSIZE ) { paddingD = D; }
+ if( L != UNDEFINEDSIZE ) { paddingL = L; }
+ if( R != UNDEFINEDSIZE ) { paddingR = R; }
+
+ onRelayout();
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Set the padding above the element
+ *
+ * @param paddingSize The number of pixels (in GUI space) for the padding
+ *
+ */
+void IMElement::setPaddingU( float paddingSize ) {
+ paddingU = paddingSize;
+ onRelayout();
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Set the padding below the element
+ *
+ * @param paddingSize The number of pixels (in GUI space) for the padding
+ *
+ */
+void IMElement::setPaddingD( float paddingSize ) {
+ paddingD = paddingSize;
+ onRelayout();
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Set the padding to the left of the element
+ *
+ * @param paddingSize The number of pixels (in GUI space) for the padding
+ *
+ */
+void IMElement::setPaddingL( float paddingSize ) {
+ paddingL = paddingSize;
+ onRelayout();
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Set the padding to the right of the element
+ *
+ * @param paddingSize The number of pixels (in GUI space) for the padding
+ *
+ */
+void IMElement::setPaddingR( float paddingSize ) {
+ paddingR = paddingSize;
+ onRelayout();
+}
+
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the drawing displacement (mostly used for tweening)
+ *
+ * @param newDisplacement newValues for the displacement
+ *
+ */
+ void IMElement::setDisplacement( vec2 newDisplacement ) {
+ drawDisplacement = newDisplacement;
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the drawing displacement x component (mostly used for tweening)
+ *
+ * @param newDisplacement newValues for the displacement
+ *
+ */
+ void IMElement::setDisplacementX( float newDisplacement ) {
+ drawDisplacement.x() = newDisplacement;
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the drawing displacement y component (mostly used for tweening)
+ *
+ * @param newDisplacement newValues for the displacement
+ *
+ */
+ void IMElement::setDisplacementY( float newDisplacement ) {
+ drawDisplacement.y() = newDisplacement;
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the drawing displacement (mostly used for tweening)
+ *
+ * @returns Displacement vector
+ *
+ */
+ vec2 IMElement::getDisplacement( vec2 newDisplacement ) {
+ return drawDisplacement;
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the drawing displacement x component (mostly used for tweening)
+ *
+ * @returns Displacement value
+ *
+ */
+ float IMElement::getDisplacementX() {
+ return drawDisplacement.x();
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the drawing displacement y component (mostly used for tweening)
+ *
+ * @returns Displacement value
+ *
+ */
+ float IMElement::getDisplacementY() {
+ return drawDisplacement.y();
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the default size
+ *
+ * @param newDefault the new default size
+ *
+ */
+void IMElement::setDefaultSize( vec2 newDefault ) {
+
+ defaultSize = newDefault;
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Retrieves the default size
+ *
+ * @returns 2d integer vector of the default size
+ *
+ */
+ vec2 IMElement::getDefaultSize() {
+ return defaultSize;
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief For container type classes - resize event, called internally
+ *
+ */
+ void IMElement::onChildResize( IMElement* child ) {
+ // nothing to do in the base class
+ child->Release();
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief For container type classes - resize event, called internally
+ *
+ */
+
+ void IMElement::onParentResize() {
+ // nothing to do in the base class
+ }
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the size of the region (not including padding)
+ *
+ * @param _size 2d size vector (-1 element implies undefined - or use UNDEFINEDSIZE)
+ *
+ */
+void IMElement::setSize( const vec2 _size ) {
+
+ vec2 oldSize = size;
+
+ size = _size;
+
+ size.x() += paddingL + paddingR;
+ size.y() += paddingU + paddingD;
+
+ // make sure the size has actually changed
+ if( size.x() != oldSize.x() || size.y() != oldSize.y() ) {
+ // Signal that something has changed
+ onRelayout();
+ }
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the x dimension of a region
+ *
+ * @param x x dimension size (-1 implies undefined - or use UNDEFINEDSIZE)
+ *
+ */
+void IMElement::setSizeX( const float x ) {
+ setSize( vec2( x, size.y() ) );
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the y dimension of a region
+ *
+ * @param y y dimension size (-1 implies undefined - or use UNDEFINEDSIZE)
+ *
+ */
+void IMElement::setSizeY( const float y ) {
+ setSize( vec2( size.x(), y ) );
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the size vector
+ *
+ * @returns The size vector
+ *
+ */
+vec2 IMElement::getSize() {
+ return size;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the size x component
+ *
+ * @returns The x size
+ *
+ */
+float IMElement::getSizeX() {
+ return size.x();
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the size y component
+ *
+ * @returns The y size
+ *
+ */
+float IMElement::getSizeY() {
+ return size.y();
+}
+
+
+/*******************************************************************************************/
+/**
+ * @brief Sends a message to the owning GUI
+ *
+ * @param theMessage the message
+ *
+ */
+ void IMElement::sendMessage( IMMessage* theMessage ) {
+ if( owner != NULL ) {
+ // receiveMessage releases the message for us
+ owner->receiveMessage( theMessage );
+ } else {
+ theMessage->Release();
+ }
+ }
+
+
+/*******************************************************************************************/
+/**
+ * @brief Finds an element by a given name
+ *
+ * @param elementName the name of the element
+ *
+ * @returns handle to the element (NULL if not found)
+ *
+ */
+IMElement* IMElement::findElement( std::string const& elementName ) {
+ // Check if this is the droid we're looking for
+ if( name == elementName ) {
+ AddRef();
+ return this;
+ }
+ else {
+ return NULL;
+ }
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Remove all referenced object without releaseing references
+ *
+ */
+void IMElement::clense() {
+ owner = NULL;
+ parent = NULL;
+
+ updateBehaviors.clear();
+ mouseOverBehaviors.clear();
+ leftMouseClickBehaviors.clear();
+}
+
+void IMElement::setPauseBehaviors( bool pause ) {
+ pauseBehaviors = pause;
+}
+
+bool IMElement::isMouseOver() {
+ return mouseOver;
+}
+
+void IMElement::setScriptMouseOver(bool mouseOver) {
+ scriptMouseOver = mouseOver;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Destructor
+ *
+ */
+IMElement::~IMElement() {
+ LOG_ASSERT(refCount == 0);
+ for( UBMap::iterator it = updateBehaviors.begin();
+ it != updateBehaviors.end();
+ ++it ) {
+ it->second->Release();
+ }
+ updateBehaviors.clear();
+
+ for( MOBMap::iterator it = mouseOverBehaviors.begin();
+ it != mouseOverBehaviors.end();
+ ++it ) {
+ it->second->Release();
+ }
+ mouseOverBehaviors.clear();
+
+ for( MCBMap::iterator it = leftMouseClickBehaviors.begin();
+ it != leftMouseClickBehaviors.end();
+ ++it ) {
+ it->second->Release();
+ }
+ leftMouseClickBehaviors.clear();
+
+ imevents.DeRegisterListener(this);
+ imevents.TriggerDestroyed(this);
+}
+
+void IMElement::DestroyQueuedIMElements() {
+ while (!IMElement::deletion_schedule.empty()) {
+ IMElement* element = IMElement::deletion_schedule[0];
+ if(element != nullptr) {
+ delete element;
+ element = nullptr;
+ }
+
+ IMElement::deletion_schedule.erase(IMElement::deletion_schedule.begin());
+ }
+}
+
+void IMElement::DestroyedIMElement( IMElement* element ) {
+ if( element == parent ) {
+ parent = NULL;
+ }
+}
+
+void IMElement::DestroyedIMGUI( IMGUI* imgui) {
+ if( owner == imgui ) {
+ owner = NULL;
+ }
+}
diff --git a/Source/GUI/IMUI/im_element.h b/Source/GUI/IMUI/im_element.h
new file mode 100644
index 00000000..5b7889cc
--- /dev/null
+++ b/Source/GUI/IMUI/im_element.h
@@ -0,0 +1,1460 @@
+//-----------------------------------------------------------------------------
+// Name: im_element.h
+// Developer: Wolfire Games LLC
+// Description: Base class for all AdHoc Gui elements
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <GUI/IMUI/im_support.h>
+#include <GUI/IMUI/imui_state.h>
+#include <GUI/IMUI/im_message.h>
+#include <GUI/IMUI/im_events.h>
+
+#include <Scripting/angelscript/asmodule.h>
+
+#include <map>
+#include <string>
+
+
+class IMElement; // Forward declaration
+
+/*******************************************************************************************/
+/**
+ * @brief Attachable behavior base class - called on update
+ *
+ */
+struct IMUpdateBehavior {
+
+ bool initialized; // Has this update been run once?
+ int refCount; // for AS reference counting
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor
+ *
+ * @param _name Name for this object (incumbent on the programmer to make sure they're unique)
+ *
+ */
+ IMUpdateBehavior() :
+ initialized(false),
+ refCount(1)
+ {
+ IMrefCountTracker.addRefCountObject("UpdateBehavior");
+ }
+
+ /*******
+ *
+ * Angelscript factory
+ *
+ */
+ static IMUpdateBehavior* ASFactory() {
+ return new IMUpdateBehavior();
+ }
+
+
+ /*******
+ *
+ * Angelscript memory management boilerplate
+ *
+ */
+ void AddRef()
+ {
+ // Increase the reference counter
+ refCount++;
+ }
+
+ void Release()
+ {
+ // Decrease ref count and delete if it reaches 0
+ if( --refCount == 0 ) {
+ delete this;
+ }
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Called before the first update
+ *
+ * @param element The element attached to this behavior
+ * @param delta Number of millisecond elapsed since last update
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param guistate The state of the GUI at this update
+ *
+ * @returns true if this behavior should continue next update, false otherwise
+ *
+ */
+ virtual bool initialize( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ return true;
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Called on update
+ *
+ * @param element The element attached to this behavior
+ * @param delta Number of millisecond elapsed since last update
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param guistate The state of the GUI at this update
+ *
+ * @returns true if this behavior should continue next update, false otherwise
+ *
+ */
+ virtual bool update( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ return true;
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Called when the behavior ceases, whether by its own indicate or externally
+ *
+ * @param element The element attached to this behavior
+ *
+ */
+ virtual void cleanUp( IMElement* element ) {
+
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Create a copy of this object (respecting inheritance)
+ *
+ */
+ virtual IMUpdateBehavior* clone() {
+ IMUpdateBehavior* c = new IMUpdateBehavior;
+ return c;
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Destructor
+ *
+ */
+ virtual ~IMUpdateBehavior() {
+ IMrefCountTracker.removeRefCountObject("UpdateBehavior");
+ }
+
+};
+
+/*******************************************************************************************/
+/**
+ * @brief Attachable behavior base class - called on mouse over
+ *
+ */
+struct IMMouseOverBehavior {
+
+ int refCount; // for AS reference counting
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor
+ *
+ */
+ IMMouseOverBehavior() :
+ refCount(1)
+ {
+ IMrefCountTracker.addRefCountObject("MouseOverBehavior");
+ }
+
+
+ /*******
+ *
+ * Angelscript factory
+ *
+ */
+ static IMMouseOverBehavior* ASFactory() {
+ return new IMMouseOverBehavior();
+ }
+
+ /*******
+ *
+ * Angelscript memory management boilerplate
+ *
+ */
+ void AddRef()
+ {
+ // Increase the reference counter
+ refCount++;
+ }
+
+ void Release()
+ {
+ // Decrease ref count and delete if it reaches 0
+ if( --refCount == 0 ) {
+ delete this;
+ }
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Called when the mouse enters the element
+ *
+ * @param element The element attached to this behavior
+ * @param delta Number of millisecond elapsed since last update
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param guistate The state of the GUI at this update
+ *
+ */
+ virtual void onStart( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Called when the mouse is still over the element
+ *
+ * @param element The element attached to this behavior
+ * @param delta Number of millisecond elapsed since last update
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param guistate The state of the GUI at this update
+ *
+ */
+ virtual void onContinue( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Called when the mouse leaves the element
+ *
+ * @param element The element attached to this behavior
+ * @param delta Number of millisecond elapsed since last update
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param guistate The state of the GUI at this update
+ *
+ * @return true if this behavior should be retained, false otherwise
+ *
+ */
+ virtual bool onFinish( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ return true;
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Called when the behavior ceases, whether by its own indicate or externally
+ *
+ * @param element The element attached to this behavior
+ *
+ */
+ virtual void cleanUp( IMElement* element ) {
+
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Create a copy of this object (respecting inheritance)
+ *
+ */
+ virtual IMMouseOverBehavior* clone() {
+ IMMouseOverBehavior* c = new IMMouseOverBehavior;
+ return c;
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Destructor
+ *
+ */
+ virtual ~IMMouseOverBehavior() {
+ IMrefCountTracker.removeRefCountObject("MouseOverBehavior");
+ }
+
+
+};
+
+/*******************************************************************************************/
+/**
+ * @brief Attachable behavior base class - called on mouse down
+ *
+ */
+struct IMMouseClickBehavior {
+
+ int refCount; // for AS reference counting
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor
+ *
+ */
+ IMMouseClickBehavior() :
+ refCount(1)
+ {
+ IMrefCountTracker.addRefCountObject("MouseClickBehavior");
+ }
+
+ /*******
+ *
+ * Angelscript factory
+ *
+ */
+ static IMUpdateBehavior* ASFactory() {
+ return new IMUpdateBehavior();
+ }
+
+ /*******
+ *
+ * Angelscript memory management boilerplate
+ *
+ */
+ void AddRef() {
+ // Increase the reference counter
+ refCount++;
+ }
+
+ void Release() {
+ // Decrease ref count and delete if it reaches 0
+ if( --refCount == 0 ) {
+ delete this;
+ }
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Called when the mouse button is pressed on element
+ *
+ * @param element The element attached to this behavior
+ * @param delta Number of millisecond elapsed since last update
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param guistate The state of the GUI at this update
+ *
+ * @return true if this behavior should be retained, false otherwise
+ *
+ */
+ virtual bool onDown( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ return true;
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Called when the mouse button continues to be pressed on an element
+ *
+ * @param element The element attached to this behavior
+ * @param delta Number of millisecond elapsed since last update
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param guistate The state of the GUI at this update
+ *
+ * @return true if this behavior should be retained, false otherwise
+ *
+ */
+ virtual bool onStillDown( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ return true;
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Called when the mouse button is released on element
+ *
+ * @param element The element attached to this behavior
+ * @param delta Number of millisecond elapsed since last update
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param guistate The state of the GUI at this update
+ *
+ * @return true if this behavior should be retained, false otherwise
+ *
+ */
+ virtual bool onUp( IMElement* element, uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ return true;
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Called when the behavior ceases, whether by its own indicate or externally
+ *
+ * @param element The element attached to this behavior
+ *
+ */
+ virtual void cleanUp( IMElement* element ) {
+
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Create a copy of this object (respecting inheritance)
+ *
+ */
+ virtual IMMouseClickBehavior* clone() {
+ IMMouseClickBehavior* c = new IMMouseClickBehavior;
+ return c;
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Destructor
+ *
+ */
+ virtual ~IMMouseClickBehavior()
+ {
+ IMrefCountTracker.removeRefCountObject("MouseClickBehavior");
+ }
+
+};
+
+struct ControllerItem {
+ IMMessage* message;
+ IMMessage* messageOnSelect;
+ IMMessage* messageLeft;
+ IMMessage* messageRight;
+ IMMessage* messageUp;
+ IMMessage* messageDown;
+ bool execute_on_select;
+ bool skip_show_border;
+
+ int refCount;
+
+ ControllerItem()
+ : refCount(1)
+ , message(NULL)
+ , messageOnSelect(NULL)
+ , messageLeft(NULL)
+ , messageRight(NULL)
+ , messageUp(NULL)
+ , messageDown(NULL)
+ , skip_show_border(false)
+ , execute_on_select(false)
+ {
+ IMrefCountTracker.addRefCountObject("CItem");
+ }
+
+ ~ControllerItem()
+ {
+ IMrefCountTracker.removeRefCountObject("CItem");
+ if(message) {
+ message->Release();
+ }
+ if(messageOnSelect) {
+ messageOnSelect->Release();
+ }
+ if(messageLeft) {
+ messageLeft->Release();
+ }
+ if(messageRight) {
+ messageRight->Release();
+ }
+ if(messageUp) {
+ messageUp->Release();
+ }
+ if(messageDown) {
+ messageDown->Release();
+ }
+ }
+
+ static ControllerItem* ASFactory() {
+ return new ControllerItem();
+ }
+
+ void AddRef()
+ {
+ refCount++;
+ }
+
+ void Release()
+ {
+ if(--refCount == 0) {
+ delete this;
+ }
+ }
+
+ void setMessage(IMMessage* message)
+ {
+ if(this->message) {
+ this->message->Release();
+ }
+ this->message = message;
+ }
+ void setMessageOnSelect(IMMessage* message)
+ {
+ if(this->messageOnSelect) {
+ this->messageOnSelect->Release();
+ }
+ this->messageOnSelect = message;
+ }
+ void setMessages(IMMessage* message, IMMessage* messageOnSelect, IMMessage* messageLeft, IMMessage* messageRight, IMMessage* messageUp, IMMessage* messageDown)
+ {
+ if(message) {
+ if(this->message) {
+ this->message->Release();
+ }
+ this->message = message;
+ }
+ if(messageOnSelect) {
+ if(this->messageOnSelect) {
+ this->messageOnSelect->Release();
+ }
+ this->messageOnSelect = messageOnSelect;
+ }
+ if(messageLeft) {
+ if(this->messageLeft) {
+ this->messageLeft->Release();
+ }
+ this->messageLeft = messageLeft;
+ }
+ if(messageRight) {
+ if(this->messageRight) {
+ this->messageRight->Release();
+ }
+ this->messageRight = messageRight;
+ }
+ if(messageUp) {
+ if(this->messageUp) {
+ this->messageUp->Release();
+ }
+ this->messageUp = messageUp;
+ }
+ if(messageDown) {
+ if(this->messageDown) {
+ this->messageDown->Release();
+ }
+ this->messageDown = messageDown;
+ }
+ }
+
+ IMMessage* getMessage() { if(message) message->AddRef(); return message; }
+ IMMessage* getMessageOnSelect() { if(messageOnSelect) messageOnSelect->AddRef(); return messageOnSelect; }
+ IMMessage* getMessageLeft() { if(messageLeft) messageLeft->AddRef(); return messageLeft; }
+ IMMessage* getMessageRight() { if(messageRight) messageRight->AddRef(); return messageRight; }
+ IMMessage* getMessageUp() { if(messageUp) messageUp->AddRef(); return messageUp; }
+ IMMessage* getMessageDown() { if(messageDown) messageDown->AddRef(); return messageDown; }
+
+ bool isActive() { return message || messageOnSelect || messageLeft || messageRight || messageUp || messageDown; }
+};
+
+class IMGUI; // Forward declaration
+
+class IMElement : public IMEventListener {
+
+public:
+ vec2 size; // dimensions of the actual region (GUI space)
+ vec2 defaultSize; // What size (if any) should this element become once 'reset'
+ vec2 drawDisplacement; // Is this element being drawn somewhere other than where it 'lives' (mostly for tweening)
+ vec2 lastDrawOffset;
+ float paddingU; // (minimum) Padding between the element and the upper boundary
+ float paddingD; // (minimum) Padding between the element and the lower boundary
+ float paddingL; // (minimum) Padding between the element and the left boundary
+ float paddingR; // (minimum) Padding between the element and the right boundary
+
+ ControllerItem controllerItem;
+
+ int zOrdering; // At what point in the rendering process does this get drawn in
+
+ std::string name; // name to refer to this object by -- incumbent on the programmer to make sure they're unique
+
+ IMElement* parent; // NULL if 'root'
+ IMGUI* owner; // what GUI owns this element
+
+ int numBehaviors; // Counter for unique behavior names
+
+ typedef std::map<std::string, IMUpdateBehavior*> UBMap;
+ typedef std::map<std::string, IMMouseOverBehavior*> MOBMap;
+ typedef std::map<std::string, IMMouseClickBehavior*> MCBMap;
+
+ UBMap updateBehaviors; // update behaviors
+ MOBMap mouseOverBehaviors; // mouse over behaviors
+ MCBMap leftMouseClickBehaviors; // mouse up behaviors
+
+ bool show; // should this element be rendered?
+ bool shouldClip; // should this element be included in container clipping?
+ vec4 color; // if this element is colored, what color is it? -- other elements may define further colors
+ vec4 effectColor; // if the color is temp
+ bool isColorEffected; // is there a temporary color change?
+ bool border; // should this element have a border?
+ float borderSize; // how thick is this border (in GUI space pixels)
+ vec4 borderColor; // color for the border
+
+ bool mouseOver; // has mouse been over this element
+ bool mouse_clicking;
+
+ bool mouseDownForChildren; // Should this pass on mouse down to all its children
+ bool mouseOverForChildren; // Should this pass on mouse over to all its children
+
+ int refCount; // for AS reference counting
+ static std::vector<IMElement*> deletion_schedule;
+
+ bool pauseBehaviors; // Don't update any behaviors (mouse exit will still be called to avoid breakage)
+ bool scriptMouseOver;
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor
+ *
+ * @param _name Name for this object (incumbent on the programmer to make sure they're unique)
+ *
+ */
+ IMElement( std::string const& _name = "" );
+
+ /*******
+ *
+ * Angelscript memory management boilerplate
+ *
+ */
+ void AddRef();
+ void Release();
+
+ int getRefCount() { return refCount; }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the name of the type of this element — for autonaming and debugging
+ *
+ * @returns name of the element type as a string
+ *
+ */
+ virtual std::string getElementTypeName();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Set’s this element’s parent (and does nessesary logic)
+ *
+ * @param _parent New parent
+ *
+ */
+ virtual void setOwnerParent( IMGUI* _owner, IMElement* _parent );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Set the color for the element
+ *
+ * @param _color 4 component vector for the color
+ *
+ */
+ virtual void setColor( vec4 _color );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the current color
+ *
+ * If the color is effected, it'll return the effected color
+ *
+ * @returns 4 component vector of the color
+ *
+ */
+ virtual vec4 getColor();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the current color -- ignoring the effect color
+ *
+ * @returns 4 component vector of the color
+ *
+ */
+ virtual vec4 getBaseColor();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Set the effect color for the element
+ *
+ * @param _color 4 component vector for the color
+ *
+ */
+ virtual void setEffectColor( vec4 _color );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the effect current color
+ *
+ * @returns 4 component vector of the color
+ *
+ */
+ virtual vec4 getEffectColor();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Clears any effect color (reseting to the base)
+ *
+ */
+ virtual void clearColorEffect();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the red value
+ *
+ * @param value Color value
+ *
+ */
+ virtual void setR( float value );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the red value
+ *
+ * @returns Color value
+ *
+ */
+ virtual float getR();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the green value
+ *
+ * @param value Color value
+ *
+ */
+ virtual void setG( float value );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the green value
+ *
+ * @returns Color value
+ *
+ */
+ virtual float getG();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the blue value
+ *
+ * @param value Color value
+ *
+ */
+ virtual void setB( float value );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the blue value
+ *
+ * @returns Color value
+ *
+ */
+ virtual float getB();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the alpha value
+ *
+ * @param value Color value
+ *
+ */
+ virtual void setAlpha( float value );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the alpha value
+ *
+ * @returns Color value
+ *
+ */
+ virtual float getAlpha();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the effect red value
+ *
+ * @param value Color value
+ *
+ */
+ virtual void setEffectR( float value );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the effect red value
+ *
+ * @returns Color value
+ *
+ */
+ virtual float getEffectR();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Clear effect red value
+ *
+ */
+ virtual void clearEffectR();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the effect green value
+ *
+ * @param value Color value
+ *
+ */
+ virtual void setEffectG( float value );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the effect green value
+ *
+ * @returns Color value
+ *
+ */
+ virtual float getEffectG();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Clear effect green value
+ *
+ */
+ virtual void clearEffectG();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the blue value
+ *
+ * @param value Color value
+ *
+ */
+ virtual void setEffectB( float value );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the blue value
+ *
+ * @returns Color value
+ *
+ */
+ virtual float getEffectB();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Clear effect blue value
+ *
+ */
+ virtual void clearEffectB();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the alpha value
+ *
+ * @param value Color value
+ *
+ */
+ virtual void setEffectAlpha( float value );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the alpha value
+ *
+ * @returns Color value
+ *
+ */
+ virtual float getEffectAlpha();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Clear effect alpha value
+ *
+ */
+ virtual void clearEffectAlpha();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Should this element have a border
+ *
+ * @param _border Show this border or not
+ *
+ */
+ virtual void showBorder( bool _border = true );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the border thickness
+ *
+ * @param thickness Thickness of the border in GUI space pixels
+ *
+ */
+ virtual void setBorderSize( float _borderSize );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Set the color for the border
+ *
+ * @param _color 4 component vector for the color
+ *
+ */
+ virtual void setBorderColor( vec4 _color );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the current border color
+ *
+ * @returns 4 component vector of the color
+ *
+ */
+ virtual vec4 getBorderColor();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the border red value
+ *
+ * @param value Color value
+ *
+ */
+ virtual void setBorderR( float value );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the border red value
+ *
+ * @returns Color value
+ *
+ */
+ virtual float getBorderR();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the border green value
+ *
+ * @param value Color value
+ *
+ */
+ virtual void setBorderG( float value );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the border green value
+ *
+ * @returns Color value
+ *
+ */
+ virtual float getBorderG();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the border blue value
+ *
+ * @param value Color value
+ *
+ */
+ virtual void setBorderB( float value );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the border blue value
+ *
+ * @returns Color value
+ *
+ */
+ virtual float getBorderB();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the border alpha value
+ *
+ * @param value Color value
+ *
+ */
+ virtual void setBorderAlpha( float value );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the border alpha value
+ *
+ * @returns Color value
+ *
+ */
+ virtual float getBorderAlpha();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the z ordering (order of drawing, higher is drawing on top of lower)
+ *
+ * @param z new Z ordering value (expected to be greater then 0 and the parent container)
+ *
+ */
+ virtual void setZOrdering( int z );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the z ordering (order of drawing - higher is drawing on top of lower)
+ *
+ * @returns current Z ordering value
+ *
+ */
+ virtual int getZOrdering();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Set the z ordering of this element to be higher than the given element
+ *
+ * @param element Element to be below this one
+ *
+ */
+ virtual void renderAbove( IMElement* element );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Set the z ordering of this element to be lower than the given element
+ *
+ * (note that if the element parameter has a z value within 1 of the parent container
+ * this element will be assigned to the same value, which may not look nice )
+ *
+ * @param element Element to be below this one
+ *
+ */
+ virtual void renderBelow( IMElement* element );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Show or hide this element
+ *
+ * @param _show Show this element or not
+ *
+ */
+ virtual void setVisible( bool _show );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Should this element be including in the container clipping?
+ *
+ * @param _clip Clip this element or not
+ *
+ */
+ virtual void setClip( bool _clip );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Rather counter-intuitively, this draws this object on the screen
+ *
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param clipPos pixel location of upper lefthand corner of clipping region
+ * @param clipSize size of clipping region
+ *
+ */
+ virtual void render( vec2 drawOffset, vec2 currentClipPos, vec2 currentClipSize );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Get the element position on screen.
+ *
+ */
+ virtual vec2 getScreenPosition();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Checks to see if a point is inside this element
+ *
+ * @param drawOffset The upper left hand corner of where the boundary is drawn
+ * @param point point in question
+ *
+ * @returns true if inside, false otherwise
+ *
+ */
+ virtual bool pointInElement( vec2 drawOffset, vec2 point );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Add an update behavior
+ *
+ * @param behavior Handle to behavior in question
+ * @param behaviorName name to identify the behavior
+ *
+ */
+ virtual void addUpdateBehavior( IMUpdateBehavior* behavior, std::string const& behaviorName = "" );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Removes a named update behavior
+ *
+ * @param behaviorName name to identify the behavior
+ *
+ * @returns true if there was a behavior to remove, false otherwise
+ *
+ */
+ virtual bool removeUpdateBehavior( std::string const& behaviorName );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Indicates if a behavior exists, can be used to see if its finished.
+ *
+ * @param behaviorName name to identify the behavior
+ *
+ * @returns true if there was a behavior
+ *
+ */
+ virtual bool hasUpdateBehavior(std::string const& behaviorName);
+
+ /*******************************************************************************************/
+ /**
+ * @brief Clear update behaviors
+ *
+ */
+ virtual void clearUpdateBehaviors();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Add a mouse over behavior
+ *
+ * @param behavior Handle to behavior in question
+ * @param behaviorName name to identify the behavior
+ *
+ */
+ virtual void addMouseOverBehavior( IMMouseOverBehavior* behavior, std::string const& behaviorName = "" );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Removes a named update behavior
+ *
+ * @param behaviorName name to identify the behavior
+ *
+ * @returns true if there was a behavior to remove, false otherwise
+ *
+ */
+ virtual bool removeMouseOverBehavior( std::string const& behaviorName );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Clear mouse over behaviors
+ *
+ */
+ virtual void clearMouseOverBehaviors();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Add a click behavior
+ *
+ * @param behavior Handle to behavior in question
+ * @param behaviorName name to identify the behavior
+ *
+ */
+ virtual void addLeftMouseClickBehavior( IMMouseClickBehavior* behavior, std::string const& behaviorName = "" );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Removes a named click behavior
+ *
+ * @param behaviorName name to identify the behavior
+ *
+ * @returns true if there was a behavior to remove, false otherwise
+ *
+ */
+ virtual bool removeLeftMouseClickBehavior( std::string const& behaviorName );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Clear mouse over behaviors
+ *
+ */
+ virtual void clearLeftMouseClickBehaviors();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Should mouse down apply to this elements children?
+ *
+ * @param send true if should (default)
+ *
+ */
+ void sendMouseDownToChildren( bool send = true ) {
+ mouseDownForChildren = send;
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Should mouse over apply to this elements children?
+ *
+ * @param true if should (default)
+ *
+ */
+ void sendMouseOverToChildren( bool send = true ) {
+ mouseOverForChildren = send;
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Updates the element
+ *
+ * @param delta Number of millisecond elapsed since last update
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param guistate The state of the GUI at this update
+ *
+ */
+ virtual void update( uint64_t delta, vec2 drawOffset, GUIState& guistate );
+
+ /*******************************************************************************************/
+ /**
+ * @brief When this element is resized, moved, etc propagate this signal upwards
+ *
+ */
+ virtual void onRelayout();
+
+ /*******************************************************************************************/
+ /**
+ * @brief When this element has an error, propagate it upwards
+ *
+ * @param newError Error message
+ *
+ */
+ virtual void onError( std::string const& newError );
+
+ /*******************************************************************************************/
+ /**
+ * @brief When a resize, move, etc has happened do whatever is necessary
+ *
+ */
+ virtual void doRelayout();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Do whatever is necessary when the resolution changes
+ *
+ */
+ virtual void doScreenResize();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Set the name of this element
+ *
+ * @param _name New name (incumbent on the programmer to make sure they're unique)
+ *
+ */
+ virtual void setName( std::string const& _name );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the name of this element
+ *
+ * @returns name of this element
+ *
+ */
+ virtual std::string getName();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Set the padding for each direction on the element
+ *
+ * UNDEFINEDSIZE will cause no change
+ *
+ * @param U (minimum) Padding between the element and the upper boundary
+ * @param D (minimum) Padding between the element and the lower boundary
+ * @param L (minimum) Padding between the element and the left boundary
+ * @param R (minimum) Padding between the element and the right boundary
+ *
+ */
+ virtual void setPadding( float U, float D, float L, float R);
+
+ /*******************************************************************************************/
+ /**
+ * @brief Set the padding above the element
+ *
+ * @param paddingSize The number of pixels (in GUI space) for the padding
+ *
+ */
+ virtual void setPaddingU( float paddingSize );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Set the padding below the element
+ *
+ * @param paddingSize The number of pixels (in GUI space) for the padding
+ *
+ */
+ virtual void setPaddingD( float paddingSize );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Set the padding to the left of the element
+ *
+ * @param paddingSize The number of pixels (in GUI space) for the padding
+ *
+ */
+ virtual void setPaddingL( float paddingSize );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Set the padding to the right of the element
+ *
+ * @param paddingSize The number of pixels (in GUI space) for the padding
+ *
+ */
+ virtual void setPaddingR( float paddingSize );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the drawing displacement (mostly used for tweening)
+ *
+ * @param newDisplacement newValues for the displacement
+ *
+ */
+ virtual void setDisplacement( vec2 newDisplacement = vec2(0,0) );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the drawing displacement x component (mostly used for tweening)
+ *
+ * @param newDisplacement newValues for the displacement
+ *
+ */
+ virtual void setDisplacementX( float newDisplacement = 0 );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the drawing displacement y component (mostly used for tweening)
+ *
+ * @param newDisplacement newValues for the displacement
+ *
+ */
+ virtual void setDisplacementY( float newDisplacement = 0 );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the drawing displacement (mostly used for tweening)
+ *
+ * @returns Displacement vector
+ *
+ */
+ virtual vec2 getDisplacement( vec2 newDisplacement = vec2(0,0) );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the drawing displacement x component (mostly used for tweening)
+ *
+ * @returns Displacement value
+ *
+ */
+ virtual float getDisplacementX();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the drawing displacement y component (mostly used for tweening)
+ *
+ * @returns Displacement value
+ *
+ */
+ virtual float getDisplacementY();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the default size
+ *
+ * @param newDefault the new default size
+ *
+ */
+ virtual void setDefaultSize( vec2 newDefault );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Retrieves the default size
+ *
+ * @returns 2d integer vector of the default size
+ *
+ */
+ virtual vec2 getDefaultSize();
+
+ /*******************************************************************************************/
+ /**
+ * @brief For container type classes - resize event, called internally
+ *
+ */
+ virtual void onChildResize( IMElement* child );
+
+ /*******************************************************************************************/
+ /**
+ * @brief For container type classes - resize event, called internally
+ *
+ */
+ virtual void onParentResize();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the size of the region (not including padding)
+ *
+ * @param _size 2d size vector (-1 element implies undefined - or use UNDEFINEDSIZE)
+ *
+ */
+ virtual void setSize( const vec2 _size );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the x dimension of a region
+ *
+ * @param x x dimension size (-1 implies undefined - or use UNDEFINEDSIZE)
+ *
+ */
+ virtual void setSizeX( const float x );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the y dimension of a region
+ *
+ * @param y y dimension size (-1 implies undefined - or use UNDEFINEDSIZE)
+ *
+ */
+ virtual void setSizeY( const float y );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the size vector
+ *
+ * @returns The size vector
+ *
+ */
+ virtual vec2 getSize();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the size x component
+ *
+ * @returns The x size
+ *
+ */
+ virtual float getSizeX();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the size y component
+ *
+ * @returns The y size
+ *
+ */
+ virtual float getSizeY();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sends a message to the owning GUI
+ *
+ * @param theMessage the message
+ *
+ */
+ virtual void sendMessage( IMMessage* theMessage );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Finds an element by a given name
+ *
+ * @param elementName the name of the element
+ *
+ * @returns handle to the element (NULL if not found)
+ *
+ */
+ virtual IMElement* findElement( std::string const& elementName );
+
+ IMElement* getParent () const { if(parent) parent->AddRef(); return parent; }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Remove all referenced object without releaseing references
+ *
+ */
+ virtual void clense();
+
+ virtual void setPauseBehaviors( bool pause );
+
+ bool isMouseOver();
+ void setScriptMouseOver(bool mouseOver);
+
+ /*******************************************************************************************/
+ /**
+ * @brief Destructor
+ *
+ */
+ virtual ~IMElement();
+
+ static void DestroyQueuedIMElements();
+ virtual void DestroyedIMElement( IMElement* element );
+ virtual void DestroyedIMGUI( IMGUI* imgui );
+};
diff --git a/Source/GUI/IMUI/im_events.cpp b/Source/GUI/IMUI/im_events.cpp
new file mode 100644
index 00000000..41d65898
--- /dev/null
+++ b/Source/GUI/IMUI/im_events.cpp
@@ -0,0 +1,53 @@
+//-----------------------------------------------------------------------------
+// Name: im_events.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "im_events.h"
+
+#include <GUI/IMUI/imgui.h>
+#include <GUI/IMUI/im_element.h>
+
+IMEvents imevents;
+
+void IMEvents::RegisterListener( IMEventListener *listener ) {
+ listeners.push_back(listener);
+}
+
+void IMEvents::DeRegisterListener( IMEventListener *listener ) {
+ for( int i = listeners.size()-1; i >= 0; i-- ) {
+ if( listeners[i] == listener ) {
+ listeners.erase(listeners.begin()+i);
+ }
+ }
+}
+
+void IMEvents::TriggerDestroyed(IMElement* elem) {
+ for( unsigned i = 0; i < listeners.size(); i++ ) {
+ listeners[i]->DestroyedIMElement(elem);
+ }
+}
+
+void IMEvents::TriggerDestroyed(IMGUI* imgui) {
+ for( unsigned i = 0; i < listeners.size(); i++ ) {
+ listeners[i]->DestroyedIMGUI(imgui);
+ }
+}
diff --git a/Source/GUI/IMUI/im_events.h b/Source/GUI/IMUI/im_events.h
new file mode 100644
index 00000000..bd07e7f5
--- /dev/null
+++ b/Source/GUI/IMUI/im_events.h
@@ -0,0 +1,48 @@
+//-----------------------------------------------------------------------------
+// Name: im_events.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <vector>
+
+class IMGUI;
+class IMElement;
+
+class IMEventListener {
+public:
+ virtual void DestroyedIMElement( IMElement* element ) = 0;
+ virtual void DestroyedIMGUI( IMGUI* IMGUI ) = 0;
+};
+
+class IMEvents {
+private:
+ std::vector<IMEventListener*> listeners;
+public:
+ void RegisterListener( IMEventListener *eventlistener );
+ void DeRegisterListener( IMEventListener *eventlistener );
+
+ void TriggerDestroyed(IMElement* elem);
+ void TriggerDestroyed(IMGUI* imgui);
+};
+
+extern IMEvents imevents;
diff --git a/Source/GUI/IMUI/im_image.cpp b/Source/GUI/IMUI/im_image.cpp
new file mode 100644
index 00000000..c9d71dcc
--- /dev/null
+++ b/Source/GUI/IMUI/im_image.cpp
@@ -0,0 +1,345 @@
+//-----------------------------------------------------------------------------
+// Name: im_image.cpp
+// Developer: Wolfire Games LLC
+// Description: Image element class for creating adhoc GUIs as part of
+// the UI tools
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "im_image.h"
+
+#include <GUI/IMUI/imgui.h>
+#include <Math/vec2math.h>
+
+
+/*******************************************************************************************/
+/**
+ * @brief Constructor
+ *
+ */
+IMImage::IMImage() :
+ rotation( 0.0 ),
+ skip_aspect_fitting(false),
+ center(false)
+
+{
+ IMrefCountTracker.addRefCountObject( getElementTypeName() );
+ setColor( vec4( 1.0, 1.0, 1.0, 1.0 ) );
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Copy constructor
+ *
+ */
+IMImage::IMImage( IMImage const& other ) {
+
+ IMrefCountTracker.addRefCountObject( getElementTypeName() );
+
+ imageFileName = other.imageFileName;
+ originalImageSize = other.originalImageSize;
+ rotation = other.rotation;
+ skip_aspect_fitting = other.skip_aspect_fitting;
+ center = other.center;
+ textureOffset = other.textureOffset;
+ textureSize = other.textureSize;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Constructor
+ *
+ * @param imageName Filename for the image
+ *
+ */
+IMImage::IMImage(std::string const& imageName) :
+ IMElement( imageName ),
+ rotation( 0.0 ),
+ skip_aspect_fitting(false),
+ center(false)
+{
+ IMrefCountTracker.addRefCountObject( getElementTypeName() );
+
+ setImageFile( imageName );
+ setColor( vec4( 1.0, 1.0, 1.0, 1.0 ) );
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Constructor
+ *
+ * @param imageName Path for the image
+ *
+ */
+IMImage::IMImage(std::string const& imageName, bool abs_path) :
+ IMElement( imageName ),
+ rotation( 0.0 ),
+ skip_aspect_fitting(false),
+ center(false)
+{
+ IMrefCountTracker.addRefCountObject( getElementTypeName() );
+
+ if( abs_path ) {
+ setImageFileAbs(imageName);
+ } else {
+ setImageFile(imageName);
+ }
+ setColor( vec4( 1.0, 1.0, 1.0, 1.0 ) );
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the name of the type of this element — for autonaming and debugging
+ *
+ * @returns name of the element type as a string
+ *
+ */
+std::string IMImage::getElementTypeName() {
+ return "Image";
+}
+
+void IMImage::setSkipAspectFitting(bool val) {
+ skip_aspect_fitting = val;
+}
+
+void IMImage::setCenter(bool val) {
+ center = val;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the source for the image
+ *
+ * @param _fileName
+ *
+ */
+void IMImage::setImageFile( std::string const& _fileName ) {
+
+ if( _fileName == "" )
+ {
+ return;
+ }
+
+ imageFileName = "Data/" + _fileName;
+ if( !imuiImage.loadImage( imageFileName ) ) {
+ IMDisplayError("Error", std::string("Unable to locate image " + imageFileName) );
+ return;
+ }
+
+ //Get the size
+ originalImageSize = vec2( int( imuiImage.getTextureWidth() ),
+ int( imuiImage.getTextureHeight() ) );
+
+ setSize( originalImageSize );
+
+}
+
+void IMImage::setImageFileAbs( std::string const& _fileName ) {
+
+ if( _fileName == "" )
+ {
+ return;
+ }
+
+ imageFileName = _fileName;
+ if( !imuiImage.loadImage( imageFileName ) ) {
+ IMDisplayError("Error", std::string("Unable to locate image " + imageFileName) );
+ return;
+ }
+
+ //Get the size
+ originalImageSize = vec2( int( imuiImage.getTextureWidth() ),
+ int( imuiImage.getTextureHeight() ) );
+
+ setSize( originalImageSize );
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Rescales the image to a specified width
+ *
+ * @param newSize new x size
+ *
+ */
+void IMImage::scaleToSizeX( float newSize ) {
+ float newYSize = (originalImageSize.y()/originalImageSize.x()) * newSize;
+ setSize( vec2(newSize, newYSize) );
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Rescales the image to a specified height
+ *
+ * @param newSize new y size
+ *
+ */
+void IMImage::scaleToSizeY( float newSize ) {
+ float newXSize = (originalImageSize.x()/originalImageSize.y()) * newSize;
+ setSize( vec2(newXSize, newSize) );
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Rather counter-intuitively, this draws this object on the screen
+ *
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param clipPos pixel location of upper lefthand corner of clipping region
+ * @param clipSize size of clipping region
+ *
+ */
+void IMImage::render( vec2 drawOffset, vec2 currentClipPos, vec2 currentClipSize ) {
+
+ Graphics* graphics = Graphics::Instance();
+
+ // Make sure we have an an image and we're supposed draw it
+ if( imageFileName != "" && show ) {
+
+ vec2 GUIRenderPos = vec2(0,0);
+
+ imuiImage.skip_aspect_fitting = skip_aspect_fitting;
+
+ if( center ) {
+ float diff_x = (float)graphics->window_dims[0] - getSizeX();
+ float diff_y = (float)graphics->window_dims[1] - getSizeY();
+ GUIRenderPos = vec2(diff_x/2.0f,diff_y/2.0f);
+ } else {
+ GUIRenderPos = drawOffset + drawDisplacement;
+ }
+
+ if( skip_aspect_fitting ) {
+ imuiImage.setRenderSize( vec2( getSizeX(), getSizeY() ) );
+
+ imuiImage.setPosition( vec3( GUIRenderPos.x(), GUIRenderPos.y(), (float)getZOrdering() ) );
+ } else {
+ vec2 screenRenderPos = screenMetrics.GUIToScreen( GUIRenderPos );
+
+ imuiImage.setRenderSize( vec2( (float(getSizeX())*screenMetrics.GUItoScreenXScale),
+ (float(getSizeY())*screenMetrics.GUItoScreenYScale) ) );
+
+ imuiImage.setPosition( vec3( screenRenderPos.x(), screenRenderPos.y(), (float)getZOrdering() ) );
+ }
+
+ if( isColorEffected ) {
+ imuiImage.setColor( effectColor );
+ }
+ else {
+ imuiImage.setColor( color );
+ }
+
+ imuiImage.setRotation( rotation );
+
+ if( textureSize.x() != 0 && textureSize.y() != 0 ) {
+ imuiImage.setRenderOffset( vec2( textureOffset.x(), textureOffset.y() ),
+ vec2( textureSize.x(), textureSize.y() ) );
+ }
+
+ // Add clipping (if we need it)
+ if( shouldClip && currentClipSize.x() != UNDEFINEDSIZE && currentClipSize.y() != UNDEFINEDSIZE ) {
+
+ vec2 adjustedClipPos = screenMetrics.GUIToScreen( currentClipPos );
+ imuiImage.setClipping( vec2(adjustedClipPos.x(),
+ adjustedClipPos.y()),
+ vec2(currentClipSize.x() * screenMetrics.GUItoScreenXScale,
+ currentClipSize.y() * screenMetrics.GUItoScreenYScale) );
+ }
+
+ if( owner != NULL ) {
+ owner->IMGUI_IMUIContext->queueImage( imuiImage );
+ }
+
+ }
+
+ // Call the superclass to make sure any element specific rendering is done
+ IMElement::render( drawOffset, currentClipPos, currentClipSize );
+
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Updates the element
+ *
+ * @param delta Number of millisecond elapsed since last update
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param guistate The state of the GUI at this update
+ *
+ */
+void IMImage::update( uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ IMElement::update( delta, drawOffset, guistate );
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the rotation for this image
+ *
+ * @param Rotation (in degrees)
+ *
+ */
+void IMImage::setRotation( float _rotation ) {
+ rotation = _rotation;
+ imuiImage.setRotation( rotation );
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the rotation for this image
+ *
+ * @returns current rotation (in degrees)
+ *
+ */
+float IMImage::getRotation() {
+ return rotation;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Render only part of this image
+ *
+ * (all coordinates are based on the original size of the image)
+ *
+ * @param offset position of the upper lefthand coordinate to source the rendering from
+ * @param size size of the rectangle to use for the rendering
+ *
+ */
+void IMImage::setImageOffset( vec2 offset, vec2 size ) {
+ textureOffset = offset;
+ textureSize = size;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Remove all referenced object without releaseing references
+ *
+ */
+void IMImage::clense() {
+
+ IMElement::clense();
+
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Destructor
+ *
+ */
+IMImage::~IMImage() {
+ IMrefCountTracker.removeRefCountObject( getElementTypeName() );
+}
diff --git a/Source/GUI/IMUI/im_image.h b/Source/GUI/IMUI/im_image.h
new file mode 100644
index 00000000..4bebc88a
--- /dev/null
+++ b/Source/GUI/IMUI/im_image.h
@@ -0,0 +1,200 @@
+//-----------------------------------------------------------------------------
+// Name: im_image.cpp
+// Developer: Wolfire Games LLC
+// Description: Image element class for creating adhoc GUIs as part of
+// the UI tools
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <GUI/IMUI/im_support.h>
+#include <GUI/IMUI/im_element.h>
+
+#include <Internal/filesystem.h>
+
+#include <string>
+/*******************************************************************************************/
+/**
+ * @brief Any styled text element
+ *
+ */
+class IMImage : public IMElement
+{
+
+public:
+ bool skip_aspect_fitting;
+ bool center;
+
+ std::string imageFileName; //Filename for the image
+ IMUIImage imuiImage; //Engine image description
+
+ vec2 originalImageSize;//What is the base size of the image before transformation
+
+ float rotation; // how much is this image rotated
+
+ vec2 textureOffset; // Where to start drawing the texture from
+ vec2 textureSize; // How much of the image to use
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor
+ *
+ */
+ IMImage();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Copy constructor
+ *
+ */
+ IMImage( IMImage const& other );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor
+ *
+ * @param imageName Filename for the image
+ *
+ */
+ IMImage(std::string const& imageName);
+
+ IMImage(std::string const& imageName, bool abs_path);
+
+ /*******
+ *
+ * Angelscript factory
+ *
+ */
+ static IMImage* ASFactory( std::string const& imageName ) {
+ return new IMImage( imageName );
+ }
+
+ static IMImage* ASFactoryPath( Path& path ) {
+ return new IMImage( path.GetAbsPathStr(), true );
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the name of the type of this element — for autonaming and debugging
+ *
+ * @returns name of the element type as a string
+ *
+ */
+ std::string getElementTypeName();
+
+ void setSkipAspectFitting(bool val);
+ void setCenter(bool val);
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the source for the image
+ *
+ * @param _fileName
+ *
+ */
+ void setImageFile( std::string const& _fileName );
+ void setImageFileAbs( std::string const& _fileName );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Rescales the image to a specified width
+ *
+ * @param newSize new x size
+ *
+ */
+ void scaleToSizeX( float newSize );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Rescales the image to a specified height
+ *
+ * @param newSize new y size
+ *
+ */
+ void scaleToSizeY( float newSize );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Rather counter-intuitively, this draws this object on the screen
+ *
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param clipPos pixel location of upper lefthand corner of clipping region
+ * @param clipSize size of clipping region
+ *
+ */
+ void render( vec2 drawOffset, vec2 currentClipPos, vec2 currentClipSize );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Updates the element
+ *
+ * @param delta Number of millisecond elapsed since last update
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param guistate The state of the GUI at this update
+ *
+ */
+ void update( uint64_t delta, vec2 drawOffset, GUIState& guistate );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the rotation for this image
+ *
+ * @param Rotation (in degrees)
+ *
+ */
+ void setRotation( float _rotation );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the rotation for this image
+ *
+ * @returns current rotation (in degrees)
+ *
+ */
+ float getRotation();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Render only part of this image
+ *
+ * (all coordinates are based on the original size of the image)
+ *
+ * @param offset position of the upper lefthand coordinate to source the rendering from
+ * @param size size of the rectangle to use for the rendering
+ *
+ */
+ void setImageOffset( vec2 offset, vec2 size );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Remove all referenced object without releaseing references
+ *
+ */
+ virtual void clense();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Destructor
+ *
+ */
+ virtual ~IMImage();
+
+};
+
diff --git a/Source/GUI/IMUI/im_message.h b/Source/GUI/IMUI/im_message.h
new file mode 100644
index 00000000..ec326e87
--- /dev/null
+++ b/Source/GUI/IMUI/im_message.h
@@ -0,0 +1,190 @@
+//-----------------------------------------------------------------------------
+// Name: im_message.h
+// Developer: Wolfire Games LLC
+// Description: Internal helper class for communication in adhoc GUIs as
+// part of the UI tools
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <GUI/IMUI/im_support.h>
+
+class IMElement; // Forward declaration
+
+/*******************************************************************************************/
+/**
+ * @brief A container for messages and its parameters
+ *
+ */
+struct IMMessage
+{
+
+ std::string name;
+ std::vector<std::string> stringParams;
+ std::vector<int> intParams;
+ std::vector<float> floatParams;
+
+ int refCount; // for AS reference counting
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor
+ *
+ */
+ IMMessage(std::string const& _name = "INVALID" ) :
+ refCount(1)
+ {
+ IMrefCountTracker.addRefCountObject("Message");
+
+ name = _name;
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Copy constructor
+ *
+ */
+ IMMessage( IMMessage const& other ) :
+ refCount(1)
+ {
+ IMrefCountTracker.addRefCountObject("Message");
+
+ name = other.name;
+ stringParams = other.stringParams;
+ intParams = other.intParams;
+ floatParams = other.floatParams;
+ }
+
+ // A few shortcuts for quickly filling in messages
+ // given that the majority of messages will have 0 or 1 parameters
+ IMMessage(std::string const& _name, int param ) :
+ refCount(1)
+ {
+ IMrefCountTracker.addRefCountObject("Message");
+
+ name = _name;
+ intParams.push_back( param );
+ }
+
+ IMMessage(std::string const& _name, float param ) :
+ refCount(1)
+ {
+ IMrefCountTracker.addRefCountObject("Message");
+
+ name = _name;
+ floatParams.push_back( param );
+ }
+
+ IMMessage(std::string const& _name, std::string const& param ) :
+ refCount(1)
+ {
+ IMrefCountTracker.addRefCountObject("Message");
+
+ name = _name;
+ stringParams.push_back( param );
+ }
+
+ void AddRef()
+ {
+ // Increase the reference counter
+ refCount++;
+ }
+
+ void Release()
+ {
+ // Decrease ref count and delete if it reaches 0
+ if( --refCount == 0 ) {
+ delete this;
+ }
+ }
+
+ static IMMessage* ASFactory( std::string const& name ) {
+ return new IMMessage(name);
+ }
+
+ static IMMessage* ASFactory_int( std::string const& name, int param ) {
+ return new IMMessage(name, param);
+ }
+
+ static IMMessage* ASFactory_float( std::string const& name, float param ) {
+ return new IMMessage(name, param);
+ }
+
+ static IMMessage* ASFactory_string( std::string const& name, std::string const& param ) {
+ return new IMMessage(name, param);
+ }
+
+ int numInts() {
+ return (int) intParams.size();
+ }
+
+ int numFloats() {
+ return (int) floatParams.size();
+ }
+
+ int numStrings() {
+ return (int) stringParams.size();
+ }
+
+ void addInt( int param ) {
+ intParams.push_back( param );
+ }
+
+ void addFloat( float param ) {
+ floatParams.push_back( param );
+ }
+
+ void addString( std::string const& param ) {
+ stringParams.push_back( param );
+ }
+
+ int getInt( int index ) {
+ if( index < (int)intParams.size() ) {
+ return intParams[ index ];
+ }
+ else {
+ return 0;
+ }
+ }
+
+ float getFloat( int index ) {
+ if( index < (int)floatParams.size() ) {
+ return floatParams[ index ];
+ }
+ else {
+ return 0.0;
+ }
+ }
+
+ std::string getString( int index ) {
+ if( index < (int)stringParams.size() ) {
+ return stringParams[ index ];
+ }
+ else {
+ return "";
+ }
+ }
+
+ ~IMMessage() {
+ IMrefCountTracker.removeRefCountObject("Message");
+ }
+};
+
+
diff --git a/Source/GUI/IMUI/im_selection_list.cpp b/Source/GUI/IMUI/im_selection_list.cpp
new file mode 100644
index 00000000..dbd2f5f3
--- /dev/null
+++ b/Source/GUI/IMUI/im_selection_list.cpp
@@ -0,0 +1,165 @@
+//-----------------------------------------------------------------------------
+// Name: im_selection_list.cpp
+// Developer: Wolfire Games LLC
+// Description: Divider types for making selection lists
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "im_selection_list.h"
+
+/*******************************************************************************************/
+/**
+ * @brief Constructor
+ *
+ *
+ */
+IMTextSelectionList::IMTextSelectionList( std::string const& name,
+ FontSetup _font,
+ float _betweenSpace,
+ IMMouseOverBehavior* _mouseOver ) :
+ IMDivider( name ),
+ itemBehavior(NULL),
+ itemXAlignment(CACenter),
+ itemYAlignment(CACenter)
+{
+
+ IMrefCountTracker.addRefCountObject( getElementTypeName() );
+
+ // let our superclass set us up
+ font = _font;
+ betweenSpace = _betweenSpace;
+ mouseOver = _mouseOver;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Set’s the alignment of the list objects
+ *
+ * @param xAlignment horizontal alignment
+ * @param yAlignment vertical alignment
+ *
+ */
+void IMTextSelectionList::setAlignment( ContainerAlignment xAlignment, ContainerAlignment yAlignment ) {
+
+ itemXAlignment = xAlignment;
+ itemYAlignment = yAlignment;
+
+}
+
+
+/*******************************************************************************************/
+/**
+ * @brief Add an update behavior to each subsequent item
+ *
+ * @param behavior Handle to behavior in question
+ *
+ */
+void IMTextSelectionList::setItemUpdateBehaviour( IMUpdateBehavior* behavior ) {
+ if( itemBehavior != NULL ) {
+ itemBehavior->Release();
+ }
+
+ itemBehavior = behavior->clone();
+
+ // release the reference we were given
+ behavior->Release();
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Add a new entry to this list
+ *
+ * @param name GUI name for this text
+ * @param text Text to display
+ * @param name Text to send as a message when this item is selected
+ *
+ */
+void IMTextSelectionList::addEntry( std::string const& name, std::string const& text, std::string const& message ) {
+ std::string empty = "";
+ addEntryParam(name, text, message, empty);
+}
+
+void IMTextSelectionList::addEntryParam( std::string const& name, std::string const& text, std::string const& message, std::string const& param ) {
+ // build the new object
+ IMText* newText = new IMText( text, font );
+ newText->setName( name );
+ IMMouseClickBehavior* clickBehavior = NULL;
+ if( param == "" ) {
+ clickBehavior = new IMFixedMessageOnClick(message);
+ } else {
+ clickBehavior = new IMFixedMessageOnClick(message, param);
+ }
+
+ clickBehavior->AddRef();
+ newText->addLeftMouseClickBehavior( clickBehavior );
+ clickBehavior->Release();
+
+ if( mouseOver != NULL ) {
+ // make a new copy of the mouseover behaviour
+ IMMouseOverBehavior* newBehavior = mouseOver->clone();
+
+ newBehavior->AddRef();
+ newText->addMouseOverBehavior(newBehavior, "");
+ newBehavior->Release();
+ }
+
+ if( itemBehavior != NULL ) {
+
+ IMUpdateBehavior* newBehavior = itemBehavior->clone();
+ newBehavior->AddRef();
+ newText->addUpdateBehavior(newBehavior, "");
+ newBehavior->Release();
+
+ }
+
+ // if we have been given space and we have at least on element, add the space
+ if( (betweenSpace > 0) && (getContainerCount() > 0) ) {
+ IMSpacer* newSpacer = appendSpacer( betweenSpace );
+ newSpacer->Release();
+ }
+
+ // .. and add it to the list
+ IMContainer* newContainer = append( newText );
+ newContainer->Release();
+
+
+ // finally, remove the reference as we're done with it (and append will have addrefed it)
+ //newText->Release();
+
+}
+
+
+
+/*******************************************************************************************/
+/**
+ * @brief Destructor
+ *
+ */
+IMTextSelectionList::~IMTextSelectionList() {
+
+ IMrefCountTracker.removeRefCountObject( getElementTypeName() );
+
+ if( mouseOver != NULL ) {
+ mouseOver->Release();
+ }
+
+ if( itemBehavior != NULL ) {
+ itemBehavior->Release();
+ }
+}
diff --git a/Source/GUI/IMUI/im_selection_list.h b/Source/GUI/IMUI/im_selection_list.h
new file mode 100644
index 00000000..533e4b62
--- /dev/null
+++ b/Source/GUI/IMUI/im_selection_list.h
@@ -0,0 +1,126 @@
+//-----------------------------------------------------------------------------
+// Name: im_selection_list.h
+// Developer: Wolfire Games LLC
+// Description: Divider types for making selection lists
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <GUI/IMUI/im_support.h>
+#include <GUI/IMUI/im_element.h>
+#include <GUI/IMUI/im_text.h>
+#include <GUI/IMUI/im_divider.h>
+#include <GUI/IMUI/im_behaviors.h>
+
+/*******************************************************************************************/
+/**
+ * @brief A selection list of text elements
+ *
+ *
+ */
+class IMTextSelectionList : public IMDivider {
+
+ IMUpdateBehavior* itemBehavior;
+
+public:
+
+ FontSetup font; // what font to use for this selection list
+ float betweenSpace; // how much space to put between items?
+ IMMouseOverBehavior* mouseOver; // what behaviour for mousing over?
+ ContainerAlignment itemXAlignment; // list item x alignment
+ ContainerAlignment itemYAlignment; // list item y alignment
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor
+ *
+ *
+ */
+ IMTextSelectionList( std::string const& name,
+ FontSetup _font,
+ float _betweenSpace,
+ IMMouseOverBehavior* _mouseOver = NULL );
+
+ IMTextSelectionList() {}
+
+ /*******
+ *
+ * Angelscript factory
+ *
+ */
+ static IMTextSelectionList* ASFactory( std::string const& _name,
+ FontSetup _fontSetup,
+ float _betweenSpace,
+ IMMouseOverBehavior* _mouseOver = NULL ) {
+ return new IMTextSelectionList( _name, _fontSetup, _betweenSpace, _mouseOver );
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Set’s the alignment of the list objects
+ *
+ * @param xAlignment horizontal alignment
+ * @param yAlignment vertical alignment
+ *
+ */
+ void setAlignment( ContainerAlignment xAlignment, ContainerAlignment yAlignment );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the name of the type of this element — for autonaming and debugging
+ *
+ * @returns name of the element type as a string
+ *
+ */
+ virtual std::string getElementTypeName() {
+ return "TextSelectionList";
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Add an update behavior to each subsequent item
+ *
+ * @param behavior Handle to behavior in question
+ *
+ */
+ void setItemUpdateBehaviour( IMUpdateBehavior* behavior );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Add a new entry to this list
+ *
+ * @param name GUI name for this text
+ * @param text Text to display
+ * @param name Text to send as a message when this item is selected
+ *
+ */
+ void addEntry( std::string const& name, std::string const& text, std::string const& message );
+ void addEntryParam( std::string const& name, std::string const& text, std::string const& message, std::string const& param );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Destructor
+ *
+ */
+ virtual ~IMTextSelectionList();
+
+};
+
+
diff --git a/Source/GUI/IMUI/im_spacer.cpp b/Source/GUI/IMUI/im_spacer.cpp
new file mode 100644
index 00000000..4f48d71a
--- /dev/null
+++ b/Source/GUI/IMUI/im_spacer.cpp
@@ -0,0 +1,97 @@
+//-----------------------------------------------------------------------------
+// Name: im_spacer.cpp
+// Developer: Wolfire Games LLC
+// Description: Blank space element class for creating adhoc GUIs as part
+// of the UI tools. You probably don't want to create these
+// yourself, instead use the method in Divider
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "im_spacer.h"
+
+/*******************************************************************************************/
+/**
+ * @brief Constructor - static divider
+ *
+ */
+IMSpacer::IMSpacer( DividerOrientation _orientation, float size ) {
+
+ IMrefCountTracker.addRefCountObject( getElementTypeName() );
+
+ orientation = _orientation;
+
+ if( orientation == DOVertical ) {
+ setSize( vec2(UNDEFINEDSIZE, size) );
+ }
+ else {
+ setSize( vec2(size, UNDEFINEDSIZE ) );
+ }
+
+ isStatic = true;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Constructor - expanding divider
+ *
+ */
+IMSpacer::IMSpacer( DividerOrientation _orientation ) {
+
+ IMrefCountTracker.addRefCountObject( getElementTypeName() );
+
+ // Start with 0 size, we'll
+ if( orientation == DOVertical ) {
+ setSize( vec2(UNDEFINEDSIZE, 0.0f) );
+ }
+ else {
+ setSize( vec2(0.0f, UNDEFINEDSIZE) );
+ }
+
+ orientation = _orientation;
+
+ isStatic = false;
+}
+
+IMSpacer::IMSpacer() {
+ IMrefCountTracker.addRefCountObject( getElementTypeName() );
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the name of the type of this element — for autonaming and debugging
+ *
+ * @returns name of the element type as a string
+ *
+ */
+std::string IMSpacer::getElementTypeName() {
+ return "Spacer";
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Destructor
+ *
+ */
+IMSpacer::~IMSpacer() {
+ IMrefCountTracker.removeRefCountObject( getElementTypeName() );
+}
+
+
+
+
diff --git a/Source/GUI/IMUI/im_spacer.h b/Source/GUI/IMUI/im_spacer.h
new file mode 100644
index 00000000..52ae1fec
--- /dev/null
+++ b/Source/GUI/IMUI/im_spacer.h
@@ -0,0 +1,89 @@
+//-----------------------------------------------------------------------------
+// Name: im_spacer.h
+// Developer: Wolfire Games LLC
+// Description: Blank space element class for creating adhoc GUIs as part
+// of the UI tools. You probably don't want to create these
+// yourself, instead use the method in Divider
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <GUI/IMUI/im_support.h>
+#include <GUI/IMUI/im_element.h>
+
+/*******************************************************************************************/
+/**
+ * @brief Blank space
+ *
+ */
+class IMSpacer : public IMElement
+{
+
+public:
+
+ DividerOrientation orientation; // what direction is the host divider
+ bool isStatic; // should this grow and shrink by the needs of its container?
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor - static divider
+ *
+ */
+ IMSpacer( DividerOrientation _orientation, float size );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor - expanding divider
+ *
+ */
+ IMSpacer( DividerOrientation _orientation );
+ IMSpacer();
+
+ /*******
+ *
+ * Angelscript factory
+ *
+ */
+ static IMSpacer* ASFactory_static( DividerOrientation _orientation, float size ) {
+ return new IMSpacer( _orientation, size );
+ }
+
+ static IMSpacer* ASFactory_dynamic( DividerOrientation _orientation ) {
+ return new IMSpacer( _orientation );
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the name of the type of this element — for autonaming and debugging
+ *
+ * @returns name of the element type as a string
+ *
+ */
+ std::string getElementTypeName();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Destructor
+ *
+ */
+ virtual ~IMSpacer();
+};
+
+
diff --git a/Source/GUI/IMUI/im_support.cpp b/Source/GUI/IMUI/im_support.cpp
new file mode 100644
index 00000000..46975d5a
--- /dev/null
+++ b/Source/GUI/IMUI/im_support.cpp
@@ -0,0 +1,270 @@
+//-----------------------------------------------------------------------------
+// Name: im_support.cpp
+// Developer: Wolfire Games LLC
+// Description: A set of support function and defines for putting together
+// `ad hoc'/overlay GUIs
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "im_support.h"
+
+const float UNDEFINEDSIZE = -1.0;
+const int UNDEFINEDSIZEI = -1;
+
+/*******************************************************************************************/
+/**
+ * @brief Helper class to derive and contain the scaling factors and offsets between GUI space
+ * and screen space
+ *
+ */
+
+/*******************************************************************************************/
+/**
+ * @brief Constructor, for constructing
+ *
+ */
+ScreenMetrics::ScreenMetrics() :
+ mainSize( 2560, 1440 ),
+ fourThree( 2560, 1920 ),
+ GUISpace( 2560, 1440 ),
+ renderOffset(0,0)
+{
+ computeFactors();
+}
+
+float ScreenMetrics::getScreenWidth() {
+ return (float)Graphics::Instance()->window_dims[0];
+}
+
+float ScreenMetrics::getScreenHeight() {
+ return (float)Graphics::Instance()->window_dims[1];
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the current screen dimensions
+ *
+ * @returns 2d vector of the screen dimenions
+ *
+ */
+vec2 ScreenMetrics::getMetrics() {
+ computeFactors();
+
+ return screenSize;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Checks to see if the resolution has changed and if so rederive the values
+ *
+ * @returns true if the resolution has changed, false otherwise
+ *
+ */
+bool ScreenMetrics::checkMetrics( vec2& metrics ) {
+
+ if( screenSize.x() != getScreenWidth() || screenSize.y() != getScreenHeight() ) {
+ computeFactors();
+ }
+
+ if( screenSize.x() != metrics.x() || screenSize.y() != metrics.y() ) {
+ metrics = screenSize;
+ return true;
+ }
+ else {
+ return false;
+ }
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Compute various values this class is responsible for
+ *
+ */
+void ScreenMetrics::computeFactors() {
+ const float max_aspect_ratio = 16.0f/9.0f;
+ float aspect_ratio = getScreenWidth()/getScreenHeight();
+
+ if( aspect_ratio > max_aspect_ratio ) {
+ aspect_ratio = max_aspect_ratio;
+ }
+
+ GUISpace.x() = 2560;
+ GUISpace.y() = 2560 * (1.0f/aspect_ratio);
+
+ screenSize = vec2( getScreenHeight()*aspect_ratio, getScreenHeight() );
+
+ GUItoScreenXScale = ( screenSize.x() / GUISpace.x() );
+ GUItoScreenYScale = ( screenSize.y() / GUISpace.y() );
+
+ renderOffset = vec2((getScreenWidth() - getScreenHeight()*aspect_ratio)/2.0f, 0.0f);
+}
+
+vec2 ScreenMetrics::GUIToScreen( const vec2 pos ) {
+ return vec2( (pos.x() * GUItoScreenXScale ),
+ (pos.y() * GUItoScreenYScale ) );
+}
+
+ScreenMetrics screenMetrics; // Dimension and translation information for the screen
+
+
+// Scooping this from the Angelscript source to maintain compatibilty and avoid having to expose it
+int64_t parseInt(const std::string val, unsigned int base) {
+ // Only accept base 10 and 16
+ if( base != 10 && base != 16 )
+ {
+ return 0;
+ }
+
+ const char *end = &val[0];
+
+ // Determine the sign
+ bool sign = false;
+ if( *end == '-' )
+ {
+ sign = true;
+ end++;
+ }
+ else if( *end == '+' )
+ end++;
+
+ int64_t res = 0;
+ if( base == 10 )
+ {
+ while( *end >= '0' && *end <= '9' )
+ {
+ res *= 10;
+ res += *end++ - '0';
+ }
+ }
+ else if( base == 16 )
+ {
+ while( (*end >= '0' && *end <= '9') ||
+ (*end >= 'a' && *end <= 'f') ||
+ (*end >= 'A' && *end <= 'F') )
+ {
+ res *= 16;
+ if( *end >= '0' && *end <= '9' )
+ res += *end++ - '0';
+ else if( *end >= 'a' && *end <= 'f' )
+ res += *end++ - 'a' + 10;
+ else if( *end >= 'A' && *end <= 'F' )
+ res += *end++ - 'A' + 10;
+ }
+ }
+
+ if( sign )
+ res = -res;
+
+ return res;
+}
+
+
+vec4 HexColor(std::string const& hex)
+{
+
+ if( hex.substr(0,1) == "#" && hex.length() == 7 )
+ {
+ float c1 = (float)parseInt(hex.substr(1,2), 16);
+ float c2 = (float)parseInt(hex.substr(3,2), 16);
+ float c3 = (float)parseInt(hex.substr(5,2), 16);
+
+ return vec4( c1/255.0f,c2/255.0f,c3/255.0f,1.0f);
+ }
+ if( hex.substr(0,1) == "#" && hex.length() == 4 )
+ {
+ float c1 = (float)parseInt(hex.substr(1,1), 16);
+ float c2 = (float)parseInt(hex.substr(2,1), 16);
+ float c3 = (float)parseInt(hex.substr(3,1), 16);
+
+ return vec4( c1/16.0f,c2/16.0f,c3/16.0f,1.0f);
+ }
+ else
+ {
+ return vec4(1.0f);
+ }
+
+}
+
+void IMDisplayError( std::string const& errorTitle, std::string const& errorMessage )
+{
+ DisplayError( errorTitle.c_str(), errorMessage.c_str(), _ok);
+}
+
+void IMDisplayError( std::string const& errorMessage ) {
+ IMDisplayError("GUI Error", errorMessage.c_str());
+}
+
+
+
+// Register a new ref counted object
+void IMReferenceCountTracker::addRefCountObject( std::string const& className ) {
+ totalRefCountObjs++;
+
+ std::map<std::string, int>::iterator findIt = typeCounts.find( className );
+
+ if( findIt == typeCounts.end() ) {
+ typeCounts[ className ] = 1;
+ }
+ else {
+ typeCounts[ className ] += 1;
+ }
+
+}
+
+// Register the destruction of a ref counted object
+void IMReferenceCountTracker::removeRefCountObject( std::string const& className ) {
+ totalRefCountObjs--;
+
+ std::map<std::string, int>::iterator findIt = typeCounts.find( className );
+
+ if( findIt == typeCounts.end() ) {
+ LOGI << "removeRefCountObject with an object never registered" << std::endl;
+ }
+ else {
+ typeCounts[ className ] -= 1;
+ }
+
+}
+
+// Display a message if there are still live objects
+void IMReferenceCountTracker::logSanityCheck() {
+ if( totalRefCountObjs == 0 ) {
+ LOGI << "No live IMGUI elements" << std::endl;
+ }
+ else {
+ LOGI << "Error: There still " << totalRefCountObjs << " LIVE IMGUI elements" << std::endl;
+
+ for( std::map<std::string, int>::iterator it = typeCounts.begin();
+ it != typeCounts.end();
+ ++it ) {
+
+ if( it->second != 0 ) {
+ LOGI << it->first << ": " << it->second << std::endl;
+ }
+ }
+
+ }
+}
+
+IMReferenceCountTracker::~IMReferenceCountTracker() {
+ logSanityCheck();
+}
+
+IMReferenceCountTracker IMrefCountTracker;
diff --git a/Source/GUI/IMUI/im_support.h b/Source/GUI/IMUI/im_support.h
new file mode 100644
index 00000000..154c328f
--- /dev/null
+++ b/Source/GUI/IMUI/im_support.h
@@ -0,0 +1,160 @@
+//-----------------------------------------------------------------------------
+// Name: im_support.h
+// Developer: Wolfire Games LLC
+// Description: A set of support function and defines for putting together
+// `ad hoc'/overlay GUIs
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec2.h>
+#include <Math/vec4.h>
+
+#include <Internal/error.h>
+
+#include <GUI/IMUI/imui.h>
+
+#include <Graphics/graphics.h>
+
+#include <string>
+#include <map>
+
+
+// All coordinates are specified in terms of a space 2560 x 1440
+// (at the moment -- this assumes 16:9 ratio -- other ratios are a TODO)
+// when rendering to the screen the projection is done automatically
+// coordinates start at the top left of the screen
+
+extern const float UNDEFINEDSIZE;
+extern const int UNDEFINEDSIZEI;
+
+/*******************************************************************************************/
+/**
+ * @brief Helper class to derive and contain the scaling factors and offsets between GUI space
+ * and screen space
+ *
+ */
+struct ScreenMetrics {
+
+ vec2 mainSize;
+ vec2 fourThree;
+ vec2 GUISpace;
+
+ vec2 renderOffset; // for extreme resolutions where we have to adjust
+
+ float GUItoScreenXScale; // Scaling factor between screen width and GUI space width
+ float GUItoScreenYScale; // Scaling factor between screen height and GUI space height
+ vec2 screenSize; // what physical screen resolution are these values based on
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor, for constructing
+ *
+ */
+ ScreenMetrics();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the current screen dimensions
+ *
+ * @returns 2d vector of the screen dimenions
+ *
+ */
+ vec2 getMetrics();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Checks to see if the resolution has changed
+ *
+ * @param metrics 2d vector to compare against
+ *
+ * @returns true if the resolution has changed, false otherwise
+ *
+ */
+ bool checkMetrics( vec2& metrics );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Computer various values this class is responsible for
+ *
+ */
+ void computeFactors();
+
+ vec2 GUIToScreen( const vec2 pos );
+
+ float getScreenWidth();
+ float getScreenHeight();
+
+};
+
+extern ScreenMetrics screenMetrics; // Dimension and translation information for the screen
+
+/*******************************************************************************************/
+/**
+ * @brief Enums for various options
+ *
+ */
+enum DividerOrientation {
+ DOVertical, // self explanatory
+ DOHorizontal // also self explanatory
+};
+
+// When the boundary of an element is bigger than itself, how should it align itself
+enum ContainerAlignment {
+ CATop = 0,
+ CALeft = 0,
+ CACenter = 1,
+ CARight = 2,
+ CABottom = 2
+};
+
+vec4 HexColor(std::string const& hex);
+
+
+// Wrap the internal error function
+void IMDisplayError( std::string const& errorTitle, std::string const& errorMessage );
+void IMDisplayError( std::string const& errorMessage );
+
+// For sanity checking the reference counting for the GUI subsystem
+class IMReferenceCountTracker {
+
+ int totalRefCountObjs;
+ std::map<std::string, int> typeCounts;
+
+public:
+
+ IMReferenceCountTracker() :
+ totalRefCountObjs(0)
+ {
+ }
+
+ // Register a new ref counted object
+ void addRefCountObject( std::string const& className );
+ // Register the destruction of a ref counted object
+ void removeRefCountObject( std::string const& className );
+
+ // Display a message if there are still live objects
+ void logSanityCheck();
+
+ virtual ~IMReferenceCountTracker();
+
+};
+
+extern IMReferenceCountTracker IMrefCountTracker;
diff --git a/Source/GUI/IMUI/im_text.cpp b/Source/GUI/IMUI/im_text.cpp
new file mode 100644
index 00000000..3ecef3c6
--- /dev/null
+++ b/Source/GUI/IMUI/im_text.cpp
@@ -0,0 +1,361 @@
+//-----------------------------------------------------------------------------
+// Name: im_text.cpp
+// Developer: Wolfire Games LLC
+// Description: Text element class for creating adhoc GUIs as part of the
+// UI tools
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "im_text.h"
+
+#include <GUI/IMUI/imgui.h>
+
+/*******************************************************************************************/
+/**
+ * @brief Constructor
+ *
+ */
+IMText::IMText() :
+ fontObjDirty(false),
+ fontObjInit(false)
+{
+ IMrefCountTracker.addRefCountObject( getElementTypeName() );
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Constructor
+ *
+ * @param _name Name for this object (incumbent on the programmer to make sure they're unique)
+ *
+ */
+IMText::IMText(std::string const& _name) :
+ IMElement(_name)
+{
+ IMrefCountTracker.addRefCountObject( getElementTypeName() );
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the name of the type of this element — for autonaming and debugging
+ *
+ * @returns name of the element type as a string
+ *
+ */
+std::string IMText::getElementTypeName() {
+ return "Text";
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Constructor
+ *
+ * @param _text String for the text
+ * @param _fontName name of the font (assumed to be in Data/Fonts)
+ * @param _fontSize height of the font
+ * @param _color vec4 color rgba
+ *
+ */
+IMText::IMText(std::string const& _text, std::string const& _fontName, int _fontSize, vec4 _color ) {
+
+ IMrefCountTracker.addRefCountObject( getElementTypeName() );
+
+ setText( _text );
+ setFontByName( _fontName, _fontSize );
+ setColor( _color );
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Constructor
+ *
+ * @param _text String for the text
+ * @param _fontSetup Font parameter structure
+ *
+ */
+IMText::IMText( std::string const& _text, FontSetup _fontSetup )
+{
+ IMrefCountTracker.addRefCountObject( getElementTypeName() );
+
+ setText( _text );
+ setFont( _fontSetup );
+}
+
+
+/*******************************************************************************************/
+/**
+ * @brief Copy constructor
+ *
+ */
+IMText::IMText( IMText const& other ) {
+
+ IMrefCountTracker.addRefCountObject( getElementTypeName() );
+
+ fontObjDirty = other.fontObjDirty;
+ fontObjInit = other.fontObjInit;
+ fontSetup = other.fontSetup;
+ text = other.text;
+ screenFontSize = other.screenFontSize;
+ screenSize = other.screenSize;
+}
+
+
+/*******************************************************************************************/
+/**
+ * @brief Derives the various metrics for this text element
+ *
+ */
+void IMText::updateEngineTextObject() {
+
+ // only bother if we have text, an owner and we haven't done it yet
+ if( text != "" && owner != NULL && fontObjDirty ) {
+
+ // update the actual screen resize
+ screenFontSize = (int) (screenMetrics.GUItoScreenYScale * float(fontSetup.size));
+
+ // get a new text object
+ int flags = 0;
+ if(fontSetup.fontName == "edosz"){
+ flags = TextAtlas::kSmallLowercase;
+ }
+ imuiText = owner->IMGUI_IMUIContext->makeText( std::string("Data/Fonts/" + fontSetup.fontName + ".ttf"),
+ screenFontSize, flags );
+ fontObjInit = true;
+
+ // update its attributes
+ imuiText.setText( text );
+
+ if( fontSetup.shadowed ) {
+ imuiText.setRenderFlags( TextAtlasRenderer::kTextShadow );
+ }
+
+ imuiText.setRotation( fontSetup.rotation );
+
+ // ask the engine for the text dimensions
+ vec2 boundingBox = imuiText.getBoundingBoxDimensions();
+
+ screenSize.x() = boundingBox.x() + 0.5f;
+ screenSize.y() = boundingBox.y() + 0.5f;
+
+ vec2 newSize = vec2( (screenSize.x() / screenMetrics.GUItoScreenXScale) + 0.5f,
+ (screenSize.y() / screenMetrics.GUItoScreenYScale) + 0.5f );
+
+ setSize( newSize );
+
+ fontObjDirty = false;
+ onRelayout();
+
+ }
+
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief When a resize, move, etc has happened do whatever is necessary
+ *
+ */
+void IMText::doRelayout() {
+
+ if( fontObjDirty ) {
+ updateEngineTextObject();
+ }
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Do whatever is necessary when the resolution changes
+ *
+ */
+void IMText::doScreenResize() {
+ screenFontSize = int(screenMetrics.GUItoScreenYScale * float(fontSetup.size));
+ fontObjDirty = true;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the font attributes
+ *
+ * @param _fontSetup font description object
+ *
+ */
+void IMText::setFont( FontSetup _fontSetup ) {
+ fontSetup = _fontSetup;
+ setColor( fontSetup.color );
+ fontObjDirty = true;
+}
+
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the font attributes
+ *
+ * @param _fontName name of the font (assumed to be in Data/Fonts)
+ * @param _fontSize height of the font
+ *
+ */
+void IMText::setFontByName( std::string const& _fontName, int _fontSize ) {
+ fontSetup.fontName = _fontName;
+ fontSetup.size = _fontSize;
+ fontObjDirty = true;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the actual text
+ *
+ * @param _text String for the text
+ *
+ */
+void IMText::setText( std::string const& _text ) {
+ text = _text;
+ fontObjDirty = true;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the current text
+ *
+ * @returns String for the text
+ *
+ */
+std::string IMText::getText() {
+ return text;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the text to be shadowed
+ *
+ * @param shadow true (default) if the text should have a shadow, false otherwise
+ *
+ */
+void IMText::setShadowed( bool shouldShadow ) {
+ fontSetup.shadowed = shouldShadow;
+ fontObjDirty = true;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the rotation for this image
+ *
+ * @param Rotation (in degrees)
+ *
+ */
+void IMText::setRotation( float _rotation ) {
+ fontSetup.rotation = _rotation;
+ fontObjDirty = true;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the rotation for this text
+ *
+ * @returns current rotation (in degrees)
+ *
+ */
+float IMText::getRotation() {
+ return fontSetup.rotation;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Updates the element
+ *
+ * @param delta Number of millisecond elapsed since last update
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param guistate The state of the GUI at this update
+ *
+ */
+void IMText::update( uint64_t delta, vec2 drawOffset, GUIState& guistate ) {
+ IMElement::update( delta, drawOffset, guistate );
+
+ // update the text object, if any
+ updateEngineTextObject();
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Rather counter-intuitively, this draws this object on the screen
+ *
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param clipPos pixel location of upper lefthand corner of clipping region
+ * @param clipSize size of clipping region
+ *
+ */
+void IMText::render( vec2 drawOffset, vec2 currentClipPos, vec2 currentClipSize ) {
+
+ // Make sure we're supposed draw (and have something to draw)
+ if( show && text != "" && fontObjInit ) {
+
+ vec2 GUIRenderPos = drawOffset + vec2( paddingL, paddingU ) + drawDisplacement;
+
+ vec2 screenRenderPos = screenMetrics.GUIToScreen( GUIRenderPos );
+
+ imuiText.setPosition( vec3( screenRenderPos.x(), screenRenderPos.y(), (float) getZOrdering() ) );
+
+ if( isColorEffected ) {
+ imuiText.setColor( effectColor );
+ }
+ else {
+ imuiText.setColor( color );
+ }
+
+ if( shouldClip && currentClipSize.x() != UNDEFINEDSIZE && currentClipSize.y() != UNDEFINEDSIZE ) {
+
+ vec2 adjustedClipPos = screenMetrics.GUIToScreen( currentClipPos + drawDisplacement );
+
+ vec2 screenClipPos( vec2(adjustedClipPos.x(),
+ adjustedClipPos.y()) );
+ vec2 screenClipSize(vec2((currentClipSize.x() * screenMetrics.GUItoScreenXScale),
+ (currentClipSize.y() * screenMetrics.GUItoScreenYScale) ) );
+
+ imuiText.setClipping( screenClipPos, screenClipSize );
+
+ }
+
+ if( fontSetup.shadowed ) {
+ imuiText.setRenderFlags( TextAtlasRenderer::kTextShadow );
+ }
+
+ if( owner != NULL ) {
+ owner->IMGUI_IMUIContext->queueText( imuiText );
+ }
+
+ }
+
+ // Call the superclass to make sure any element specific rendering is done
+ IMElement::render( drawOffset, currentClipPos, currentClipSize );
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Remove all referenced object without releaseing references
+ *
+ */
+void IMText::clense() {
+
+}
+
+IMText::~IMText() {
+ IMrefCountTracker.removeRefCountObject( getElementTypeName() );
+}
diff --git a/Source/GUI/IMUI/im_text.h b/Source/GUI/IMUI/im_text.h
new file mode 100644
index 00000000..eb24ef98
--- /dev/null
+++ b/Source/GUI/IMUI/im_text.h
@@ -0,0 +1,287 @@
+//-----------------------------------------------------------------------------
+// Name: im_text.h
+// Developer: Wolfire Games LLC
+// Description: Text element class for creating adhoc GUIs as part of the
+// UI tools
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <GUI/IMUI/im_support.h>
+#include <GUI/IMUI/im_element.h>
+
+struct FontSetup
+{
+ std::string fontName; // name for the font
+ int size; // size in GUI pixels
+ vec4 color; // color for the font
+ float rotation; // Rotation for the text
+ bool shadowed; // should this text have a shadow?
+
+ FontSetup() :
+ rotation(0.0),
+ shadowed(false)
+ {
+ fontName = "OpenSans-Regular";
+ size = 60;
+ color = HexColor("#fff");
+
+ }
+
+ FontSetup( std::string const& _name, int _size, vec4 _color, bool _shadowed = false ) :
+ rotation(0.0)
+ {
+ fontName = _name;
+ size = _size;
+ color = _color;
+ shadowed = _shadowed;
+ }
+
+ ~FontSetup() {}
+};
+
+
+static void FontSetup_ASconstructor_default(FontSetup *self) {
+ new(self) FontSetup();
+}
+
+static void FontSetup_ASconstructor_params(FontSetup *self, std::string const& _name, int32_t _size, vec4 _color, bool _shadowed = false ) {
+ new(self) FontSetup( _name, _size, _color, _shadowed );
+}
+
+static void FontSetup_AScopy(FontSetup *self, const FontSetup& other ) {
+ new(self) FontSetup( other );
+}
+
+static void FontSetup_ASdestructor(FontSetup *self ) {
+ self->~FontSetup();
+}
+
+static const FontSetup& FontSetup_ASassign(FontSetup *self, const FontSetup& other) {
+ return (*self) = other;
+}
+
+
+
+
+/*******************************************************************************************/
+/**
+ * @brief Any styled text element
+ *
+ */
+class IMText : public IMElement
+{
+public:
+ IMUIText imuiText; // Engine object for the text
+ bool fontObjDirty; // Does the engine object need updating
+ bool fontObjInit; // Has the font object been initialized, at least once
+ FontSetup fontSetup;// Attributes for the current font
+ std::string text; // Actual text to render
+ int screenFontSize; // Height of the text (screen space)
+ vec2 screenSize; // Bit of a hack as we need the screen height for getting the right metrics
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor
+ *
+ */
+ IMText();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Copy constructor
+ *
+ */
+ IMText( IMText const& other );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor
+ *
+ * @param _name Name for this object (incumbent on the programmer to make sure they're unique)
+ *
+ */
+ IMText(std::string const& _name);
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor
+ *
+ * @param _text String for the text
+ * @param _fontName name of the font (assumed to be in Data/Fonts)
+ * @param _fontSize height of the font
+ * @param _color vec4 color rgba
+ *
+ */
+ IMText(std::string const& _text, std::string const& _fontName, int _fontSize, vec4 _color = vec4(1.0f) );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor
+ *
+ * @param _text String for the text
+ * @param _fontSetup Font parameter structure
+ *
+ */
+ IMText( std::string const& _text, FontSetup _fontSetup );
+
+ /*******
+ *
+ * Angelscript factory
+ *
+ */
+ static IMText* ASFactory_named( std::string const& name ) {
+ return new IMText( name );
+ }
+
+ static IMText* ASFactory_unnamed( std::string const& _text, std::string const& _fontName, int _fontSize, vec4 _color = vec4(1.0f) ) {
+ return new IMText( _text, _fontName, _fontSize, _color );
+ }
+
+ static IMText* ASFactory_fontsetup( std::string const& _text, FontSetup _fontSetup ) {
+ return new IMText( _text, _fontSetup );
+ }
+
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the name of the type of this element — for autonaming and debugging
+ *
+ * @returns name of the element type as a string
+ *
+ */
+ std::string getElementTypeName();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Derives the various metrics for this text element
+ *
+ */
+ void updateEngineTextObject();
+
+ /*******************************************************************************************/
+ /**
+ * @brief When a resize, move, etc has happened do whatever is necessary
+ *
+ */
+ void doRelayout();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Do whatever is necessary when the resolution changes
+ *
+ */
+ void doScreenResize();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the font attributes
+ *
+ * @param _fontSetup font description object
+ *
+ */
+ void setFont( FontSetup _fontSetup );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the font attributes
+ *
+ * @param _fontName name of the font (assumed to be in Data/Fonts)
+ * @param _fontSize height of the font
+ *
+ */
+ void setFontByName( std::string const& _fontName, int _fontSize );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the actual text
+ *
+ * @param _text String for the text
+ *
+ */
+ void setText( std::string const& _text );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the current text
+ *
+ * @returns String for the text
+ *
+ */
+ std::string getText();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the text to be shadowed
+ *
+ * @param shadow true (default) if the text should have a shadow, false otherwise
+ *
+ */
+ void setShadowed( bool shouldShadow = true );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the rotation for this image
+ *
+ * @param Rotation (in degrees)
+ *
+ */
+ void setRotation( float _rotation );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the rotation for this text
+ *
+ * @returns current rotation (in degrees)
+ *
+ */
+ float getRotation();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Updates the element
+ *
+ * @param delta Number of millisecond elapsed since last update
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param guistate The state of the GUI at this update
+ *
+ */
+ void update( uint64_t delta, vec2 drawOffset, GUIState& guistate );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Rather counter-intuitively, this draws this object on the screen
+ *
+ * @param drawOffset Absolute offset from the upper lefthand corner (GUI space)
+ * @param clipPos pixel location of upper lefthand corner of clipping region
+ * @param clipSize size of clipping region
+ *
+ */
+ void render( vec2 drawOffset, vec2 currentClipPos, vec2 currentClipSize );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Remove all referenced object without releaseing references
+ *
+ */
+ virtual void clense();
+
+ virtual ~IMText();
+};
diff --git a/Source/GUI/IMUI/im_tween.cpp b/Source/GUI/IMUI/im_tween.cpp
new file mode 100644
index 00000000..e4718dde
--- /dev/null
+++ b/Source/GUI/IMUI/im_tween.cpp
@@ -0,0 +1,246 @@
+//-----------------------------------------------------------------------------
+// Name: im_tween.cpp
+// Developer: Wolfire Games LLC
+// Description: A collection of tweening functions
+// License: Read below
+//-----------------------------------------------------------------------------
+/*******
+ *
+ * Based on tween.lua by Enrique García Cota (https://github.com/kikito/tween.lua)
+ *
+ * Original license (for this file):
+ *
+ * MIT LICENSE
+ *
+ * Copyright (c) 2014 Enrique García Cota, Yuichi Tateno, Emmanuel Oga
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+#include "im_tween.h"
+
+#include <cmath>
+
+// MJB Note: this is a really ugly kludge, but it's the least
+// work to fix anoter weird angelscript error
+LinearTween linearTween_inst;
+InQuadTween inQuadTween_inst;
+OutQuadTween outQuadTween_inst;
+InOutQuadTween inOutQuadTween_inst;
+OutInQuadTween outInQuadTween_inst;
+InCubicTween inCubicTween_inst;
+OutCubicTween outCubicTween_inst;
+InOutCubicTween inOutCubicTween_inst;
+OutInCubicTween outInCubicTween_inst;
+InQuartTween inQuartTween_inst;
+OutQuartTween outQuartTween_inst;
+InOutQuartTween inOutQuartTween_inst;
+OutInQuartTween outInQuartTween_inst;
+InQuintTween inQuintTween_inst;
+OutQuintTween outQuintTween_inst;
+InOutQuintTween inOutQuintTween_inst;
+OutInQuintTween outInQuintTween_inst;
+InSineTween inSineTween_inst;
+OutSineTween outSineTween_inst;
+InOutSineTween inOutSineTween_inst;
+OutInSineTween outInSineTween_inst;
+InExpoTween inExpoTween_inst;
+OutExpoTween outExpoTween_inst;
+InOutExpoTween inOutExpoTween_inst;
+OutInExpoTween outInExpoTween_inst;
+InCircTween inCircTween_inst;
+OutCircTween outCircTween_inst;
+InOutCircTween inOutCircTween_inst;
+OutInCircTween outInCircTween_inst;
+OutBounceTween outBounceTween_inst;
+InBounceTween inBounceTween_inst;
+InOutBounceTween inOutBounceTween_inst;
+OutInBounceTween outInBounceTween_inst;
+
+IMTween* getTweenInstance( IMTweenType tweenType ) {
+ switch(tweenType) {
+ case linearTween: {
+ return (IMTween*) &linearTween_inst;
+ }
+ break;
+
+ case inQuadTween: {
+ return (IMTween*) &inQuadTween_inst;
+ }
+ break;
+
+ case outQuadTween: {
+ return (IMTween*) &outQuadTween_inst;
+ }
+ break;
+
+ case inOutQuadTween: {
+ return (IMTween*) &inOutQuadTween_inst;
+ }
+ break;
+
+ case outInQuadTween: {
+ return (IMTween*) &outInQuadTween_inst;
+ }
+ break;
+
+ case inCubicTween: {
+ return (IMTween*) &inCubicTween_inst;
+ }
+ break;
+
+ case outCubicTween: {
+ return (IMTween*) &outCubicTween_inst;
+ }
+ break;
+
+ case inOutCubicTween: {
+ return (IMTween*) &inOutCubicTween_inst;
+ }
+ break;
+
+ case outInCubicTween: {
+ return (IMTween*) &outInCubicTween_inst;
+ }
+ break;
+
+ case inQuartTween: {
+ return (IMTween*) &inQuartTween_inst;
+ }
+ break;
+
+ case outQuartTween: {
+ return (IMTween*) &outQuartTween_inst;
+ }
+ break;
+
+ case inOutQuartTween: {
+ return (IMTween*) &inOutQuartTween_inst;
+ }
+ break;
+
+ case outInQuartTween: {
+ return (IMTween*) &outInQuartTween_inst;
+ }
+ break;
+
+ case inQuintTween: {
+ return (IMTween*) &inQuintTween_inst;
+ }
+ break;
+
+ case outQuintTween: {
+ return (IMTween*) &outQuintTween_inst;
+ }
+ break;
+
+ case inOutQuintTween: {
+ return (IMTween*) &inOutQuintTween_inst;
+ }
+ break;
+
+ case outInQuintTween: {
+ return (IMTween*) &outInQuintTween_inst;
+ }
+ break;
+
+ case inSineTween: {
+ return (IMTween*) &inSineTween_inst;
+ }
+ break;
+
+ case outSineTween: {
+ return (IMTween*) &outSineTween_inst;
+ }
+ break;
+
+ case inOutSineTween: {
+ return (IMTween*) &inOutSineTween_inst;
+ }
+ break;
+
+ case outInSineTween: {
+ return (IMTween*) &outInSineTween_inst;
+ }
+ break;
+
+ case inExpoTween: {
+ return (IMTween*) &inExpoTween_inst;
+ }
+ break;
+
+ case outExpoTween: {
+ return (IMTween*) &outExpoTween_inst;
+ }
+ break;
+
+ case inOutExpoTween: {
+ return (IMTween*) &inOutExpoTween_inst;
+ }
+ break;
+
+ case outInExpoTween: {
+ return (IMTween*) &outInExpoTween_inst;
+ }
+ break;
+
+ case inCircTween: {
+ return (IMTween*) &inCircTween_inst;
+ }
+ break;
+
+ case outCircTween: {
+ return (IMTween*) &outCircTween_inst;
+ }
+ break;
+
+ case inOutCircTween: {
+ return (IMTween*) &inOutCircTween_inst;
+ }
+ break;
+
+ case outInCircTween: {
+ return (IMTween*) &outInCircTween_inst;
+ }
+ break;
+
+ case outBounceTween: {
+ return (IMTween*) &outBounceTween_inst;
+ }
+ break;
+
+ case inBounceTween: {
+ return (IMTween*) &inBounceTween_inst;
+ }
+ break;
+
+ case inOutBounceTween: {
+ return (IMTween*) &inOutBounceTween_inst;
+ }
+ break;
+
+ case outInBounceTween: {
+ return (IMTween*) &outInBounceTween_inst;
+ }
+ break;
+
+ }
+ return NULL;
+}
diff --git a/Source/GUI/IMUI/im_tween.h b/Source/GUI/IMUI/im_tween.h
new file mode 100644
index 00000000..7327b54f
--- /dev/null
+++ b/Source/GUI/IMUI/im_tween.h
@@ -0,0 +1,482 @@
+//-----------------------------------------------------------------------------
+// Name: im_tween.h
+// Developer: Wolfire Games LLC
+// Description: A collection of tweening functions
+// License: Read below
+//-----------------------------------------------------------------------------
+/*******
+ *
+ * Based on tween.lua by Enrique García Cota (https://github.com/kikito/tween.lua)
+ *
+ * Original license (for this file):
+ *
+ * MIT LICENSE
+ *
+ * Copyright (c) 2014 Enrique García Cota, Yuichi Tateno, Emmanuel Oga
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+#pragma once
+
+#include <Math/enginemath.h>
+#include <cmath>
+
+using std::pow;
+
+// For all functions:
+// time t running time. How much time has passed *right now*
+// begin b starting property value
+// change c ending - beginning
+// duration d how much time has to pass for the tweening to complete
+
+/*******************************************************************************************/
+/**
+ * @brief Base struct for all tweens
+ *
+ */
+
+struct IMTween {
+
+ int refCount;
+
+ virtual float compute( float t, float b, float c, float d ) = 0;
+
+ // dummy functions as we're not ref counting these
+ void AddRef() {
+ }
+ void Release() {
+ }
+};
+
+/**
+ * Linear
+ **/
+
+struct LinearTween : public IMTween {
+ virtual float compute( float t, float b, float c, float d ) {
+ return c * t / d + b;
+ }
+};
+
+
+/**
+ * Quad
+ **/
+
+struct InQuadTween : public IMTween {
+ virtual float compute( float t, float b, float c, float d ) {
+ return c * pow(t / d, 2) + b;
+ }
+};
+
+struct OutQuadTween : public IMTween {
+ virtual float compute( float t, float b, float c, float d ) {
+ t = t / d;
+ return -c * t * (t - 2) + b;
+ }
+};
+
+
+struct InOutQuadTween : public IMTween {
+ virtual float compute( float t, float b, float c, float d ) {
+ t = t / d * 2;
+ if ( t < 1 ) {
+ return c / 2 * pow(t, 2) + b;
+ }
+ return -c / 2 * ((t - 1) * (t - 3) - 1) + b;
+ }
+};
+
+struct OutInQuadTween : public IMTween {
+ InQuadTween inQuad;
+ OutQuadTween outQuad;
+
+ virtual float compute( float t, float b, float c, float d ) {
+ if ( t < d / 2 ) {
+ return outQuad.compute(t * 2, b, c / 2, d);
+ }
+ return inQuad.compute((t * 2) - d, b + c / 2, c / 2, d);
+ }
+};
+
+
+/**
+ * Cubic
+ **/
+
+struct InCubicTween : public IMTween {
+ virtual float compute( float t, float b, float c, float d ) {
+ return c * pow(t / d, 3) + b;
+ }
+};
+
+struct OutCubicTween : public IMTween {
+ virtual float compute( float t, float b, float c, float d ) {
+ return c * (pow(t / d - 1, 3) + 1) + b;
+ }
+};
+
+struct InOutCubicTween : public IMTween {
+ virtual float compute( float t, float b, float c, float d ) {
+ t = t / d * 2;
+ if ( t < 1 ) {
+ return c / 2 * t * t * t + b;
+ }
+ t = t - 2;
+ return c / 2 * (t * t * t + 2) + b;
+ }
+};
+
+
+struct OutInCubicTween : public IMTween {
+ OutCubicTween outCubic;
+ InCubicTween inCubic;
+
+ virtual float compute( float t, float b, float c, float d ) {
+ if( t < d / 2 ) {
+ return outCubic.compute(t * 2, b, c / 2, d);
+ }
+ return inCubic.compute((t * 2) - d, b + c / 2, c / 2, d);
+ }
+};
+
+
+/**
+ * Quart
+ **/
+
+struct InQuartTween : public IMTween {
+ virtual float compute( float t, float b, float c, float d ) {
+ return c * pow(t / d, 4) + b;
+ }
+};
+
+struct OutQuartTween : public IMTween {
+ virtual float compute( float t, float b, float c, float d ) {
+ return -c * (pow(t / d - 1, 4) - 1) + b;
+ }
+};
+
+struct InOutQuartTween : public IMTween {
+ virtual float compute( float t, float b, float c, float d ) {
+ t = t / d * 2;
+ if (t < 1 ) {
+ return c / 2 * pow(t, 4) + b;
+ }
+ return -c / 2 * (pow(t - 2, 4) - 2) + b;
+ }
+};
+
+struct OutInQuartTween : public IMTween {
+ OutQuartTween outQuart;
+ InQuartTween inQuart;
+
+ virtual float compute( float t, float b, float c, float d ) {
+ if (t < d / 2 ) {
+ return outQuart.compute(t * 2, b, c / 2, d);
+ }
+ return inQuart.compute((t * 2) - d, b + c / 2, c / 2, d);
+ }
+};
+
+
+/**
+ * Quint
+ **/
+
+
+struct InQuintTween : public IMTween {
+ virtual float compute( float t, float b, float c, float d ) {
+ return c * pow(t / d, 5) + b;
+ }
+};
+
+struct OutQuintTween : public IMTween {
+ virtual float compute( float t, float b, float c, float d ) {
+ return c * (pow(t / d - 1, 5) + 1) + b;
+ }
+};
+
+
+struct InOutQuintTween : public IMTween {
+ virtual float compute( float t, float b, float c, float d ) {
+ t = t / d * 2;
+
+ if (t < 1) {
+ return c / 2 * pow(t, 5) + b;
+ }
+
+ return c / 2 * (pow(t - 2, 5) + 2) + b;
+ }
+};
+
+
+struct OutInQuintTween : public IMTween {
+ InQuintTween inQuint;
+ OutQuintTween outQuint;
+
+ virtual float compute( float t, float b, float c, float d ) {
+ if ( t < d / 2 ) {
+ return outQuint.compute(t * 2, b, c / 2, d);
+ }
+ return inQuint.compute((t * 2) - d, b + c / 2, c / 2, d);
+ }
+};
+
+
+/**
+ * Sine
+ **/
+
+struct InSineTween : public IMTween {
+ virtual float compute( float t, float b, float c, float d ) {
+ return -c * cos(t / d * (PI_f / 2)) + c + b;
+ }
+};
+
+
+struct OutSineTween : public IMTween {
+ virtual float compute( float t, float b, float c, float d ) {
+ return c * sin(t / d * (PI_f / 2)) + b;
+ }
+};
+
+
+struct InOutSineTween : public IMTween {
+ virtual float compute( float t, float b, float c, float d ) {
+ return -c / 2 * (cos(PI_f * t / d) - 1) + b;
+ }
+};
+
+struct OutInSineTween : public IMTween {
+ InSineTween inSine;
+ OutSineTween outSine;
+
+ virtual float compute( float t, float b, float c, float d ) {
+ if( t < d / 2 ) {
+ return outSine.compute(t * 2, b, c / 2, d);
+ }
+ return inSine.compute((t * 2) -d, b + c / 2, c / 2, d);
+ }
+};
+
+
+
+/**
+ * Expo
+ **/
+
+struct InExpoTween : public IMTween {
+ virtual float compute( float t, float b, float c, float d ) {
+ if( t == 0 ){
+ return b;
+ }
+ return (float) (c * pow(2, 10 * (t / d - 1)) + b - c * 0.001);
+ }
+};
+
+struct OutExpoTween : public IMTween {
+ virtual float compute( float t, float b, float c, float d ) {
+ if( t == d ){
+ return b + c;
+ }
+
+ return (float) (c * 1.001 * (-pow(2, -10 * t / d) + 1) + b);
+ }
+};
+
+struct InOutExpoTween : public IMTween {
+ virtual float compute( float t, float b, float c, float d ) {
+ if( t == 0 ){
+ return b;
+ }
+
+ if( t == d ){
+ return b + c;
+ }
+
+ t = t / d * 2;
+
+ if( t < 1 ){
+ return (float) (c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005);
+ }
+
+ return (float) (c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b);
+ }
+};
+
+struct OutInExpoTween : public IMTween {
+ InExpoTween inExpo;
+ OutExpoTween outExpo;
+
+ virtual float compute( float t, float b, float c, float d ) {
+ if( t < d / 2 ){
+ return outExpo.compute(t * 2, b, c / 2, d);
+ }
+
+ return inExpo.compute((t * 2) - d, b + c / 2, c / 2, d);
+ }
+};
+
+
+/**
+ * Circ
+ **/
+
+
+struct InCircTween : public IMTween {
+ virtual float compute( float t, float b, float c, float d ) {
+ return(-c * (sqrt(1 - pow(t / d, 2)) - 1) + b);
+ }
+};
+
+
+struct OutCircTween : public IMTween {
+ virtual float compute( float t, float b, float c, float d ) {
+ return(c * sqrt(1 - pow(t / d - 1, 2)) + b);
+ }
+};
+
+struct InOutCircTween : public IMTween {
+ virtual float compute( float t, float b, float c, float d ) {
+ t = t / d * 2;
+ if( t < 1 ){
+ return -c / 2 * (sqrt(1 - t * t) - 1) + b;
+ }
+ t = t - 2;
+ return c / 2 * (sqrt(1 - t * t) + 1) + b;
+ }
+};
+
+struct OutInCircTween : public IMTween {
+ InCircTween inCirc;
+ OutCircTween outCirc;
+
+ virtual float compute( float t, float b, float c, float d ) {
+ if( t < d / 2 ){
+ return outCirc.compute(t * 2, b, c / 2, d);
+ }
+ return inCirc.compute((t * 2) - d, b + c / 2, c / 2, d);
+ }
+};
+
+
+
+/**
+ * Bounce
+ **/
+
+struct OutBounceTween : public IMTween {
+ virtual float compute( float t, float b, float c, float d ) {
+ t = t / d;
+ if( t < 1 / 2.75f ){
+ return c * (7.5625f * t * t) + b;
+ }
+
+ if( t < 2 / 2.75f ) {
+ t = t - (1.5f / 2.75f);
+ return c * (7.5625f * t * t + 0.75f) + b;
+ }
+ else if( t < 2.5f / 2.75f ){
+ t = t - (2.25f / 2.75f);
+ return c * (7.5625f * t * t + 0.9375f) + b;
+ }
+ t = t - (2.625f / 2.75f);
+ return c * (7.5625f * t * t + 0.984375f) + b;
+ }
+};
+
+struct InBounceTween : public IMTween {
+ OutBounceTween outBounce;
+
+ virtual float compute( float t, float b, float c, float d ) {
+ return c - outBounce.compute(d - t, 0, c, d) + b;
+ }
+};
+
+struct InOutBounceTween : public IMTween {
+ OutBounceTween outBounce;
+ InBounceTween inBounce;
+
+ virtual float compute( float t, float b, float c, float d ) {
+ if( t < d / 2 ) {
+ return inBounce.compute(t * 2, 0, c, d) * 0.5f + b;
+ }
+ return outBounce.compute(t * 2 - d, 0, c, d) * 0.5f + c * 0.5f + b;
+ }
+};
+
+struct OutInBounceTween : public IMTween {
+ InBounceTween inBounce;
+ OutBounceTween outBounce;
+
+ virtual float compute( float t, float b, float c, float d ) {
+ if( t < d / 2 ) {
+ return outBounce.compute(t * 2, b, c / 2, d);
+ }
+ return inBounce.compute((t * 2) - d, b + c / 2, c / 2, d);
+ }
+};
+
+
+/*******
+ *
+ * Enum type for enumerating the tweens
+ *
+ */
+enum IMTweenType {
+ linearTween,
+ inQuadTween,
+ outQuadTween,
+ inOutQuadTween,
+ outInQuadTween,
+ inCubicTween,
+ outCubicTween,
+ inOutCubicTween,
+ outInCubicTween,
+ inQuartTween,
+ outQuartTween,
+ inOutQuartTween,
+ outInQuartTween,
+ inQuintTween,
+ outQuintTween,
+ inOutQuintTween,
+ outInQuintTween,
+ inSineTween,
+ outSineTween,
+ inOutSineTween,
+ outInSineTween,
+ inExpoTween,
+ outExpoTween,
+ inOutExpoTween,
+ outInExpoTween,
+ inCircTween,
+ outCircTween,
+ inOutCircTween,
+ outInCircTween,
+ outBounceTween,
+ inBounceTween,
+ inOutBounceTween,
+ outInBounceTween
+};
+
+// Turn an instance into a pointer to a tween object
+IMTween* getTweenInstance( IMTweenType tweenType );
diff --git a/Source/GUI/IMUI/imgui.cpp b/Source/GUI/IMUI/imgui.cpp
new file mode 100644
index 00000000..fc3d25e8
--- /dev/null
+++ b/Source/GUI/IMUI/imgui.cpp
@@ -0,0 +1,1190 @@
+//-----------------------------------------------------------------------------
+// Name: imgui.cpp
+// Developer: Wolfire Games LLC
+// Description: Main class for creating adhoc GUIs as part of the UI tools
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "imgui.h"
+
+#include <Internal/timer.h>
+
+#include <Graphics/hudimage.h>
+
+#include <map>
+#include <vector>
+#include <string>
+
+/*******************************************************************************************/
+/**
+ * @brief Constructor
+ *
+ * @param mainOrientation The orientation of the container
+ *
+ */
+int guicount = 0;
+
+IMGUI::IMGUI() :
+ elementCount(1),
+ header(),
+ footer(),
+ main(),
+ mainOffset(0.0),
+ footerOffset(0.0),
+ headerHeight(0.0),
+ footerHeight(0.0),
+ mainHeight(0.0),
+ headerPanelGap(0.0),
+ footerPanelGap(0.0),
+ mainPanelGap(0.0),
+ pendingFGLayers(1),
+ pendingBGLayers(1),
+ pendingHeaderHeight(0.0f),
+ pendingFooterHeight(0.0f),
+ refCount(1)
+{
+
+ IMrefCountTracker.addRefCountObject( "IMGUI" );
+ guicount++;
+
+ guiid = guicount;
+
+ screenSize = screenMetrics.getMetrics();
+
+ lastUpdateTime = 0;
+
+ needsRelayout = false;
+ showGuides = false;
+
+ IMGUI_IMUIContext = new IMUIContext;
+ IMGUI_IMUIContext->Init();
+
+ imevents.RegisterListener(this);
+}
+/*
+IMGUI::IMGUI( IMGUI const& other ) {
+
+ IMrefCountTracker.addRefCountObject( "IMGUI" );
+
+ lastUpdateTime = other.lastUpdateTime;
+ elementCount = other.elementCount;
+ needsRelayout = other.needsRelayout;
+ reportedErrors = other.reportedErrors;
+ showGuides = other.showGuides;
+ mousePosition = other.mousePosition;
+ leftMouseState = other.leftMouseState;
+ messageQueue = other.messageQueue;
+ backgrounds = other.backgrounds;
+ foregrounds = other.foregrounds;
+ header = other.header;
+ footer = other.footer;
+ main = other.main;
+ mainOffset = other.mainOffset;
+ footerOffset = other.footerOffset;
+ headerHeight = other.headerHeight;
+ footerHeight = other.footerHeight;
+ mainHeight = other.mainHeight;
+ headerPanelWidths = other.headerPanelWidths;
+ footerPanelWidths = other.footerPanelWidths;
+ mainPanelWidths = other.mainPanelWidths;
+ headerPanelGap = other.headerPanelGap;
+ footerPanelGap = other.footerPanelGap;
+ mainPanelGap = other.mainPanelGap;
+ pendingFGLayers = other.pendingFGLayers;
+ pendingBGLayers = other.pendingBGLayers;
+ pendingHeaderHeight = other.pendingHeaderHeight;
+ pendingFooterHeight = other.pendingFooterHeight;
+ pendingHeaderPanels = other.pendingHeaderPanels;
+ pendingMainPanels = other.pendingMainPanels;
+ pendingFooterPanels = other.pendingFooterPanels;
+ screenSize = other.screenSize;
+ refCount = other.refCount;
+
+ IMGUI_IMUIContext = new IMUIContext;
+ IMGUI_IMUIContext->Init();
+
+ imevents.RegisterListener(this);
+}
+*/
+
+void IMGUI::AddRef()
+{
+ // Increase the reference counter
+ refCount++;
+}
+
+void IMGUI::Release()
+{
+ // Decrease ref count and delete if it reaches 0
+ if( --refCount == 0 ) {
+ delete this;
+ }
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Set the number and initializes the background layers
+ *
+ * NOTE: This will destroy any existing layers
+ *
+ * Background layers are rendered highest index value first
+ *
+ * @param numLayers number of layers required
+ *
+ */
+void IMGUI::setBackgroundLayers( unsigned int numLayers ) {
+ pendingBGLayers = numLayers;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Set the number and initializes the background layers
+ *
+ * NOTE: This will destroy any existing layers
+ *
+ * Background layers are rendered highest index value first
+ *
+ * @param numLayers number of layers required
+ *
+ */
+void IMGUI::setForegroundLayers( unsigned int numLayers ) {
+ pendingFGLayers = numLayers;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Set the size of the header (0 for none)
+ *
+ * NOTE: This will take effect when setup is called next
+ *
+ * @param _headerSize size in GUI space
+ *
+ */
+void IMGUI::setHeaderHeight( float _headerSize ) {
+ pendingHeaderHeight = _headerSize;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Set the size of the footer (0 for none)
+ *
+ * NOTE: This will take effect when setup is called next
+ *
+ * @param _footerSize size in GUI space
+ *
+ */
+void IMGUI::setFooterHeight( float _footerSize ) {
+ pendingFooterHeight = _footerSize;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the sizes of the various header panels
+ *
+ * If not specified (specified means non-zero) there will be one panel, full width
+ * If one is specified, it will be centered
+ * If two are specified, they will be left and right justified
+ * Three will be left, center, right justified
+ *
+ */
+void IMGUI::setHeaderPanels( float first, float second, float third ) {
+
+ if( first > 0.0 ) {
+ pendingHeaderPanels.push_back( first );
+ }
+
+ if( second > 0.0 ) {
+ pendingHeaderPanels.push_back( second );
+ }
+
+ if( third > 0.0 ) {
+ pendingHeaderPanels.push_back( third );
+ }
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the sizes of the various main panels
+ *
+ * If not specified (specified means non-zero) there will be one panel, full width
+ * If one is specified, it will be centered
+ * If two are specified, they will be left and right justified
+ * Three will be left, center, right justified
+ *
+ */
+void IMGUI::setMainPanels( float first, float second, float third ) {
+
+ if( first > 0.0 ) {
+ pendingMainPanels.push_back( first );
+ }
+
+ if( second > 0.0 ) {
+ pendingMainPanels.push_back( second );
+ }
+
+ if( third > 0.0 ) {
+ pendingMainPanels.push_back( third );
+ }
+
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Sets the sizes of the various footer panels
+ *
+ * If not specified (specified means non-zero) there will be one panel, full width
+ * If one is specified, it will be centered
+ * If two are specified, they will be left and right justified
+ * Three will be left, center, right justified
+ *
+ */
+void IMGUI::setFooterPanels( float first, float second, float third ) {
+
+ if( first > 0.0 ) {
+ pendingFooterPanels.push_back( first );
+ }
+
+ if( second > 0.0 ) {
+ pendingFooterPanels.push_back( second );
+ }
+
+ if( third > 0.0 ) {
+ pendingFooterPanels.push_back( first );
+ }
+
+}
+
+
+/*******************************************************************************************/
+/**
+ * @brief Sets up main layer with header, footer and primary panel (clears any existing)
+ *
+ * @param _headerSize y size of the header (0 for none)
+ * @param _footerSize y size of the footer (0 for none)
+ *
+ */
+void IMGUI::setup() {
+
+ for( std::vector<IMContainer*>::iterator it = header.begin();
+ it != header.end();
+ ++it ) {
+
+ (*it)->Release();
+ }
+
+ for( std::vector<IMContainer*>::iterator it = footer.begin();
+ it != footer.end();
+ ++it ) {
+
+ (*it)->Release();
+ }
+
+ for( std::vector<IMContainer*>::iterator it = main.begin();
+ it != main.end();
+ ++it ) {
+
+ (*it)->Release();
+ }
+
+ header.clear();
+ footer.clear();
+ main.clear();
+
+ // Make sure we at least some values
+ if( pendingMainPanels.size() == 0 ) {
+ pendingMainPanels.push_back( screenMetrics.mainSize.x() );
+ }
+
+ if( pendingHeaderPanels.size() == 0 ) {
+ pendingHeaderPanels.push_back( screenMetrics.mainSize.x() );
+ }
+
+ if( pendingFooterPanels.size() == 0 ) {
+ pendingFooterPanels.push_back( screenMetrics.mainSize.x() );
+ }
+
+ // Sanity check
+ if( pendingHeaderHeight + pendingFooterHeight > screenMetrics.mainSize.y() ) {
+ IMDisplayError("GUI Error", "Header/footer too large");
+ }
+
+ const std::string numbering[] = { std::string("0"), std::string("1"), std::string("2") };
+
+ // Now build the panels
+ if( pendingHeaderHeight > 0 ) {
+ headerHeight = pendingHeaderHeight;
+
+ float totalWidth = 0;
+
+ // Go through the list of pending panels, create them and total their widths
+ int panelCount = 0;
+ for( std::vector<float>::iterator it = pendingHeaderPanels.begin();
+ it != pendingHeaderPanels.end();
+ ++it ) {
+
+ IMContainer* panel = new IMContainer( "header" + numbering[panelCount],
+ SizePolicy(*it),
+ SizePolicy(headerHeight) );
+ panel->setOwnerParent( this, NULL );
+ header.push_back( panel );
+
+ totalWidth += *it;
+
+ }
+
+ // Sanity check
+ if( totalWidth > screenMetrics.mainSize.x() ) {
+ IMDisplayError("GUI Error", "Header panels too wide");
+ }
+
+ // determine the gap size
+ if( header.size() == 1 || header.size() == 3 ) {
+ headerPanelGap = ( screenMetrics.mainSize.x() - totalWidth )/2;
+ }
+ else {
+ headerPanelGap = ( screenMetrics.mainSize.x() - totalWidth );
+ }
+
+ }
+
+ if( pendingFooterHeight > 0 ) {
+
+ footerHeight = pendingFooterHeight;
+
+ float totalWidth = 0;
+
+ // Go through the list of pending panels, create them and total their widths
+ int panelCount = 0;
+ for( std::vector<float>::iterator it = pendingFooterPanels.begin();
+ it != pendingFooterPanels.end();
+ ++it ) {
+
+ IMContainer* panel = new IMContainer( "footer" + numbering[panelCount],
+ SizePolicy(*it),
+ SizePolicy(footerHeight) );
+ panel->setOwnerParent( this, NULL );
+
+ footer.push_back( panel );
+
+ totalWidth += *it;
+
+ }
+
+ // Sanity check
+ if( totalWidth > screenMetrics.mainSize.x() ) {
+ IMDisplayError("GUI Error", "Footer panels too wide");
+ }
+
+ // determine the gap size
+ if( footer.size() == 1 || main.size() == 3 ) {
+ footerPanelGap = ( screenMetrics.mainSize.x() - totalWidth )/2;
+ }
+ else {
+ footerPanelGap = ( screenMetrics.mainSize.x() - totalWidth );
+ }
+
+ }
+
+ mainHeight = screenMetrics.mainSize.y() - (headerHeight + footerHeight);
+
+ {
+
+ float totalWidth = 0;
+
+ // Go through the list of pending panels, create them and total their widths
+ int panelCount = 0;
+ for( std::vector<float>::iterator it = pendingMainPanels.begin();
+ it != pendingMainPanels.end();
+ ++it ) {
+
+ IMContainer* panel = new IMContainer( "main" + numbering[panelCount],
+ SizePolicy(*it),
+ SizePolicy(mainHeight) );
+ panel->setOwnerParent( this, NULL );
+
+ main.push_back( panel );
+
+ totalWidth += *it;
+
+ }
+
+ // Sanity check
+ if( totalWidth > screenMetrics.mainSize.x() ) {
+ IMDisplayError("GUI Error", "Main panels too wide");
+ }
+
+ // determine the gap size for rendering
+ if( main.size() == 1 || main.size() == 3 ) {
+ mainPanelGap = ( screenMetrics.mainSize.x() - totalWidth )/2;
+ }
+ else {
+ mainPanelGap = ( screenMetrics.mainSize.x() - totalWidth );
+ }
+
+ }
+
+ // setup background layers
+ for( unsigned int i = 0; i < pendingFGLayers; ++i ) {
+ IMContainer* newContainer = new IMContainer( SizePolicy(screenMetrics.GUISpace.x()),
+ SizePolicy(screenMetrics.GUISpace.y()) );
+
+ newContainer->setOwnerParent( this, NULL );
+
+ foregrounds.push_back( newContainer );
+ }
+
+ for( unsigned int i = 0; i < pendingBGLayers; ++i ) {
+ IMContainer* newContainer = new IMContainer( SizePolicy(screenMetrics.GUISpace.x()),
+ SizePolicy(screenMetrics.GUISpace.y()) );
+
+ newContainer->setOwnerParent( this, NULL );
+
+ backgrounds.push_back( newContainer );
+ }
+
+ derivePanelOffsets();
+
+ doRelayout();
+
+ pendingFGLayers = 1;
+ pendingBGLayers = 1;
+ pendingHeaderHeight = 0.0f;
+ pendingFooterHeight = 0.0f;
+ pendingHeaderPanels.clear();
+ pendingFooterPanels.clear();
+ pendingMainPanels.clear();
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Figure out where we’re going to render the main panels - used internally
+ *
+ */
+void IMGUI::derivePanelOffsets() {
+
+ float headerEnd = -1;
+
+ if( header.size() != 0 ) {
+ headerEnd = headerHeight - 1;
+ }
+
+ footerOffset = screenMetrics.GUISpace.y();
+
+ if( footer.size() != 0 ) {
+ footerOffset = screenMetrics.GUISpace.y() - footerHeight;
+ }
+
+ if( main.size() != 0 ) {
+
+ // see if we have any space
+ if( headerHeight + footerHeight + mainHeight < screenMetrics.GUISpace.y() ) {
+ // first center the main panel
+ mainOffset = (screenMetrics.GUISpace.y()/2.0f) - (mainHeight/2.0f);
+
+ // now check if we're going over our bounds
+ if( mainOffset <= headerEnd ) {
+ mainOffset += (headerEnd - mainOffset) + 1;
+ }
+
+ float mainEnd = mainOffset + mainHeight -1;
+
+ if ( mainEnd >= footerOffset ) {
+ // we can do these two checks independently as we know there's enough room for all three
+ mainOffset -= ( mainEnd - footerOffset ) + 1;
+ }
+ }
+ else {
+ mainOffset = headerEnd + 1;
+ }
+ }
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Clear the GUI - you probably want resetMainLayer though
+ *
+ * @param mainOrientation The orientation of the container
+ *
+ */
+void IMGUI::clear() {
+
+ std::vector<IMContainer*> containerList;
+
+ containerList.insert(containerList.end(), backgrounds.begin(), backgrounds.end() );
+ containerList.insert(containerList.end(), foregrounds.begin(), foregrounds.end() );
+ containerList.insert(containerList.end(), header.begin(), header.end() );
+ containerList.insert(containerList.end(), footer.begin(), footer.end() );
+ containerList.insert(containerList.end(), main.begin(), main.end() );
+
+ backgrounds.clear();
+ foregrounds.clear();
+ header.clear();
+ footer.clear();
+ main.clear();
+
+ for( int i = int(containerList.size())-1; i >= 0; --i ) {
+ containerList[i]->Release();
+ }
+
+ for( int i = 0; i < int(messageQueue.size()); ++i ) {
+ messageQueue[i]->Release();
+ }
+ messageQueue.clear();
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Turns on (or off) the visual guides
+ *
+ * @param setting True to turn on guides, false otherwise
+ *
+ */
+void IMGUI::setGuides( bool setting ) {
+ showGuides = setting;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief When this element is resized, moved, etc propagate this signal upwards
+ *
+ */
+void IMGUI::onRelayout() {
+
+ // Record this
+ needsRelayout = true;
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Records all the errors reported by child elements
+ *
+ * @param newError Newly reported error string
+ *
+ */
+void IMGUI::reportError( std::string const& newError ) {
+ reportedErrors += newError + "\n";
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Receives a message - used internally
+ *
+ * Remember to increase the message's ref count if used internally!
+ *
+ * @param message The message in question
+ *
+ */
+void IMGUI::receiveMessage( IMMessage* message ) {
+ IMMessage* messageCopy = new IMMessage( *message );
+ messageQueue.push_back( messageCopy );
+ message->Release();
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the size of the waiting message queue
+ *
+ * @returns size of the queue (as an unsigned integer)
+ *
+ */
+unsigned int IMGUI::getMessageQueueSize() {
+ return (unsigned int)messageQueue.size();
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Gets the next available message in the queue (NULL if none)
+ *
+ * @returns A copy of the message (NULL if none)
+ *
+ */
+IMMessage* IMGUI::getNextMessage() {
+ // Again, I'm aware this isn't very efficient, but we're only dealing with at most
+ // a half dozen of these a frame
+ IMMessage* message = *messageQueue.begin();
+ messageQueue.erase( messageQueue.begin() );
+
+ return message;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Retrieves a reference to a specified background layer
+ *
+ * @param layerNum index of the background layer (starting at 0)
+ *
+ */
+IMContainer* IMGUI::getBackgroundLayer( unsigned int layerNum ) {
+ if( layerNum >= backgrounds.size() ) {
+ IMDisplayError("GUI Error", "Unknown background layer ");
+ return NULL;
+ }
+ IMContainer* container = backgrounds[ layerNum ];
+ container->AddRef();
+ return container;
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Retrieves a reference to a specified foreground layer
+ *
+ * @param layerNum index of the foreground layer (starting at 0)
+ *
+ */
+IMContainer* IMGUI::getForegroundLayer( unsigned int layerNum ) {
+ if( layerNum >= foregrounds.size() ) {
+ IMDisplayError("GUI Error", "Unknown foreground layer ");
+ return NULL;
+ }
+
+ IMContainer* container = foregrounds[ layerNum ];
+ container->AddRef();
+ return container;
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief If a relayout is requested, perform it
+ *
+ */
+void IMGUI::doRelayout() {
+
+ int relayoutCount = 0;
+ while( needsRelayout ) {
+ relayoutCount++;
+
+ // clear the error string
+ reportedErrors = "";
+
+ needsRelayout = false;
+ for( int layer = int(backgrounds.size())-1; layer >= 0; --layer ) {
+ backgrounds[ layer ]->doRelayout();
+ }
+
+ for( std::vector<IMContainer*>::iterator it = header.begin();
+ it != header.end();
+ ++it ) {
+ (*it)->doRelayout();
+ }
+
+ for( std::vector<IMContainer*>::iterator it = footer.begin();
+ it != footer.end();
+ ++it ) {
+ (*it)->doRelayout();
+ }
+
+ for( std::vector<IMContainer*>::iterator it = main.begin();
+ it != main.end();
+ ++it ) {
+ (*it)->doRelayout();
+ }
+
+ for( unsigned int layer = 0; layer < foregrounds.size(); ++layer ) {
+ foregrounds[ layer ]->doRelayout();
+ }
+
+ // MJB Note: I'm aware that redoing the layout for the whole structure
+ // is rather inefficient, but given that this is targeted at most a few
+ // dozen elements, this should not be a problem -- can easily be optimized
+ // if so
+ }
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Updates the gui
+ *
+ */
+extern Timer ui_timer;
+void IMGUI::update() {
+
+ // Do relayout as necessary
+ doRelayout();
+
+ // If we haven't updated yet, set the time
+ if( lastUpdateTime == 0 ) {
+ lastUpdateTime = uint64_t( ui_timer.game_time * 1000 );
+ }
+
+ // Calculate the delta time
+ uint64_t delta = uint64_t( ui_timer.game_time * 1000 ) - lastUpdateTime;
+
+ // Update the time
+ lastUpdateTime = uint64_t( ui_timer.game_time * 1000 );
+
+ // Get the input from the engine
+ IMGUI_IMUIContext->UpdateControls();
+
+ vec2 engineMousePos = IMGUI_IMUIContext->getMousePosition();
+
+ // Translate to GUISpace
+ mousePosition.x() = (engineMousePos.x() - screenMetrics.renderOffset.x() ) / screenMetrics.GUItoScreenXScale;
+ mousePosition.y() = ( screenMetrics.getScreenHeight() - engineMousePos.y() + screenMetrics.renderOffset.y() ) / screenMetrics.GUItoScreenYScale;
+ leftMouseState = IMGUI_IMUIContext->getLeftMouseState();
+
+ // Fill in our GUI structure
+ guistate.mousePosition = mousePosition;
+ guistate.leftMouseState = leftMouseState;
+ guistate.inheritedMouseDown = false;
+ guistate.inheritedMouseOver = false;
+ guistate.clickHandled = false;
+
+ // Do relayout as necessary
+ doRelayout();
+
+ vec2 drawOffset(0,0);
+ // update the backgrounds
+ for( int layer = int(backgrounds.size())-1; layer >= 0; --layer ) {
+ backgrounds[ layer ]->update( delta, drawOffset, guistate );
+ }
+
+ if( header.size() == 1 ) {
+ drawOffset.x() = headerPanelGap;
+ header[0]->update( delta, drawOffset, guistate );
+ }
+ else if( header.size() == 2 ) {
+ header[0]->update( delta, drawOffset, guistate );
+ drawOffset.x() = header[0]->getSizeX() + headerPanelGap;
+ header[1]->update( delta, drawOffset, guistate );
+ }
+ else if( header.size() == 3 ) {
+ header[0]->update( delta, drawOffset, guistate );
+ drawOffset.x() = header[0]->getSizeX() + headerPanelGap;
+ header[1]->update( delta, drawOffset, guistate );
+ drawOffset.x() = header[1]->getSizeX() + headerPanelGap;
+ header[2]->update( delta, drawOffset, guistate );
+ }
+
+ drawOffset.x() = 0;
+ drawOffset.y() = mainOffset;
+ if( main.size() == 1 ) {
+ drawOffset.x() = mainPanelGap;
+ main[0]->update( delta, drawOffset, guistate );
+ }
+ else if( main.size() == 2 ) {
+ main[0]->update( delta, drawOffset, guistate );
+ drawOffset.x() = main[0]->getSizeX() + mainPanelGap;
+ main[1]->update( delta, drawOffset, guistate );
+ }
+ else if( main.size() == 3 ) {
+ main[0]->update( delta, drawOffset, guistate );
+ drawOffset.x() = main[0]->getSizeX() + mainPanelGap;
+ main[1]->update( delta, drawOffset, guistate );
+ drawOffset.x() = main[1]->getSizeX() + mainPanelGap;
+ main[2]->update( delta, drawOffset, guistate );
+ }
+
+ drawOffset.x() = 0;
+ drawOffset.y() = footerOffset;
+ if( footer.size() == 1 ) {
+ drawOffset.x() = footerPanelGap;
+ footer[0]->update( delta, drawOffset, guistate );
+ }
+ else if( footer.size() == 2 ) {
+ footer[0]->update( delta, drawOffset, guistate );
+ drawOffset.x() = footer[0]->getSizeX() + footerPanelGap;
+ footer[1]->update( delta, drawOffset, guistate );
+ }
+ else if( footer.size() == 3 ) {
+ footer[0]->update( delta, drawOffset, guistate );
+ drawOffset.x() = footer[0]->getSizeX() + footerPanelGap;
+ footer[1]->update( delta, drawOffset, guistate );
+ drawOffset.x() = footer[1]->getSizeX() + footerPanelGap;
+ footer[2]->update( delta, drawOffset, guistate );
+ }
+
+ drawOffset = vec2(0,0);
+ // update the backgrounds
+ for( int layer = int(foregrounds.size())-1; layer >= 0; --layer ) {
+ foregrounds[ layer ]->update( delta, drawOffset, guistate );
+ }
+
+ // See if this triggered any relayout events
+ doRelayout();
+
+ // If we've got here and still have an error, report it
+ if( reportedErrors != "" ) {
+ IMDisplayError("GUI Error", reportedErrors);
+ }
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Fired when the screen resizes
+ *
+ */
+void IMGUI::doScreenResize() {
+
+ screenMetrics.checkMetrics( screenSize );
+
+ // All the font sizes will likely have changed
+ IMGUI_IMUIContext->clearTextAtlases();
+
+ // Move our panels around (if necessary)
+ derivePanelOffsets();
+
+ // We need to inform the elements
+ for( int layer = int(backgrounds.size())-1; layer >= 0; --layer ) {
+ backgrounds[ layer ]->setSizePolicy( SizePolicy(screenMetrics.GUISpace.x()),
+ SizePolicy(screenMetrics.GUISpace.y()) );
+
+ backgrounds[ layer ]->setSize( vec2( screenMetrics.GUISpace.x(),
+ screenMetrics.GUISpace.y() ) );
+
+ backgrounds[ layer ]->doScreenResize();
+ }
+
+ for( std::vector<IMContainer*>::iterator it = header.begin();
+ it != header.end();
+ ++it ) {
+ (*it)->doScreenResize();
+ }
+
+ for( std::vector<IMContainer*>::iterator it = footer.begin();
+ it != footer.end();
+ ++it ) {
+ (*it)->doScreenResize();
+ }
+
+ for( std::vector<IMContainer*>::iterator it = main.begin();
+ it != main.end();
+ ++it ) {
+ (*it)->doScreenResize();
+ }
+
+ for( unsigned int layer = 0; layer < foregrounds.size(); ++layer ) {
+ // reset the size
+ foregrounds[ layer ]->setSizePolicy( SizePolicy(screenMetrics.GUISpace.x()),
+ SizePolicy(screenMetrics.GUISpace.y()) );
+
+
+ foregrounds[ layer ]->doScreenResize();
+ }
+
+ doRelayout();
+}
+
+
+/*******************************************************************************************/
+/**
+ * @brief Render the GUI
+ *
+ */
+void IMGUI::render() {
+
+ vec2 origin(0,0);
+ // render the backgrounds
+ for( int layer = int(backgrounds.size())-1; layer >= 0; --layer ) {
+ backgrounds[ layer ]->render( origin, origin, vec2( UNDEFINEDSIZE, UNDEFINEDSIZE ) );
+ IMGUI_IMUIContext->render();
+ }
+
+ // render the main content
+ vec2 drawOffset(0,0);
+ if( header.size() == 1 ) {
+ drawOffset.x() = headerPanelGap;
+ header[0]->render( drawOffset, drawOffset, vec2( UNDEFINEDSIZE, UNDEFINEDSIZE ) );
+ }
+ else if( header.size() == 2 ) {
+ header[0]->render( drawOffset, drawOffset, vec2( UNDEFINEDSIZE, UNDEFINEDSIZE ) );
+ drawOffset.x() = header[0]->getSizeX() + headerPanelGap;
+ header[1]->render( drawOffset, drawOffset, vec2( UNDEFINEDSIZE, UNDEFINEDSIZE ) );
+ }
+ else if( header.size() == 3 ) {
+ header[0]->render( drawOffset, drawOffset, vec2( UNDEFINEDSIZE, UNDEFINEDSIZE ) );
+ drawOffset.x() = header[0]->getSizeX() + headerPanelGap;
+ header[1]->render( drawOffset, drawOffset, vec2( UNDEFINEDSIZE, UNDEFINEDSIZE ) );
+ drawOffset.x() = header[1]->getSizeX() + headerPanelGap;
+ header[2]->render( drawOffset, drawOffset, vec2( UNDEFINEDSIZE, UNDEFINEDSIZE ) );
+ }
+
+ drawOffset.x() = 0;
+ drawOffset.y() = mainOffset;
+
+ if( main.size() == 1 ) {
+ drawOffset.x() = mainPanelGap;
+ main[0]->render( drawOffset, drawOffset, vec2( UNDEFINEDSIZE, UNDEFINEDSIZE ) );
+ }
+ else if( main.size() == 2 ) {
+ main[0]->render( drawOffset, drawOffset, vec2( UNDEFINEDSIZE, UNDEFINEDSIZE ) );
+ drawOffset.x() = main[0]->getSizeX() + mainPanelGap;
+ main[1]->render( drawOffset, drawOffset, vec2( UNDEFINEDSIZE, UNDEFINEDSIZE ) );
+ }
+ else if( main.size() == 3 ) {
+ main[0]->render( drawOffset, drawOffset, vec2( UNDEFINEDSIZE, UNDEFINEDSIZE ) );
+ drawOffset.x() = main[0]->getSizeX() + mainPanelGap;
+ main[1]->render( drawOffset, drawOffset, vec2( UNDEFINEDSIZE, UNDEFINEDSIZE ) );
+ drawOffset.x() = main[1]->getSizeX() + mainPanelGap;
+ main[2]->render( drawOffset, drawOffset, vec2( UNDEFINEDSIZE, UNDEFINEDSIZE ) );
+ }
+
+ drawOffset.x() = 0;
+ drawOffset.y() = footerOffset;
+ if( footer.size() == 1 ) {
+ drawOffset.x() = footerPanelGap;
+ footer[0]->render( drawOffset, drawOffset, vec2( UNDEFINEDSIZE, UNDEFINEDSIZE ) );
+ }
+ else if( footer.size() == 2 ) {
+ footer[0]->render( drawOffset, drawOffset, vec2( UNDEFINEDSIZE, UNDEFINEDSIZE ) );
+ drawOffset.x() = footer[0]->getSizeX() + footerPanelGap;
+ footer[1]->render( drawOffset, drawOffset, vec2( UNDEFINEDSIZE, UNDEFINEDSIZE ) );
+ }
+ else if( footer.size() == 3 ) {
+ footer[0]->render( drawOffset, drawOffset, vec2( UNDEFINEDSIZE, UNDEFINEDSIZE ) );
+ drawOffset.x() = footer[0]->getSizeX() + footerPanelGap;
+ footer[1]->render( drawOffset, drawOffset, vec2( UNDEFINEDSIZE, UNDEFINEDSIZE ) );
+ drawOffset.x() = footer[1]->getSizeX() + footerPanelGap;
+ footer[2]->render( drawOffset, drawOffset, vec2( UNDEFINEDSIZE, UNDEFINEDSIZE ) );
+ }
+
+ IMGUI_IMUIContext->render();
+
+ // render the foregrounds
+ for( unsigned int layer = 0; layer < foregrounds.size(); ++layer ) {
+ foregrounds[ layer ]->render( origin, origin, vec2( UNDEFINEDSIZE, UNDEFINEDSIZE ) );
+ IMGUI_IMUIContext->render();
+ }
+
+
+ // TODO
+// if( showGuides ) {
+//
+// for( int i = 2; i <= 16; i *= 2 ) {
+//
+// // draw the vertical lines
+// float increment = screenMetrics.getScreenWidth() / (float)i;
+// float xpos = increment;
+//
+// while( xpos < screenMetrics.getScreenWidth() ) {
+//
+// HUDImage* newimage = hud.AddImage();
+// newimage->SetImageFromPath("Data/Textures/ui/whiteblock.tga");
+//
+// vec2 imagepos( xpos-1, 0.0f );
+//
+// newimage->scale = 1;
+// newimage->scale.x *= 2;
+// newimage->scale.y *= screenMetrics.getScreenHeight();
+//
+// newimage->position.x = imagepos.x;
+// newimage->position.y() = screenMetrics.getScreenHeight() - imagepos.y() - (newimage.GetWidth() * newimage.scale.y() );
+// newimage->position.z() = -100.0;// 0.1f;
+//
+// newimage->color = vec4( 0.0, 0.2, 0.5 + (0.5/i), 0.25 );
+//
+// xpos += increment;
+// }
+//
+// increment = GetScreenHeight() / i;
+// int ypos = increment;
+//
+// while( ypos < GetScreenHeight() ) {
+//
+// HUDImage @newimage = hud.AddImage();
+// newimage.SetImageFromPath("Data/Textures/ui/whiteblock.tga");
+//
+// vec2 imagepos( 0, ypos -1 );
+//
+// newimage.scale = 1;
+// newimage.scale.x *= GetScreenWidth();
+// newimage.scale.y *= 2;
+//
+// newimage.position.x = imagepos.x;
+// newimage.position.y = GetScreenHeight() - imagepos.y - (newimage.GetWidth() * newimage.scale.y );
+// newimage.position.z = -100.0;// 0.1f;
+//
+// newimage.color = vec4( 0.0, 0.2, 0.5 + (0.5/i), 0.25 );
+//
+// ypos += increment;
+// }
+// }
+// }
+//
+// hud.Draw();
+ IMGUI_IMUIContext->render();
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Draw a box (in *screen* coordinates) -- used internally
+ *
+ */
+void IMGUI::drawBox( vec2 boxPos, vec2 boxSize, vec4 boxColor, int zOrder, bool shouldClip,
+ vec2 currentClipPos,
+ vec2 currentClipSize ) {
+
+ IMUIImage boxImage("Data/Textures/ui/whiteblock.tga");
+
+ boxImage.setPosition( vec3( boxPos.x(), boxPos.y(), float(zOrder) ) );
+ boxImage.setColor( boxColor );
+ boxImage.setRenderSize( vec2( boxSize.x(), boxSize.y() ) );
+
+ if( shouldClip && currentClipSize.x() != UNDEFINEDSIZE && currentClipSize.y() != UNDEFINEDSIZE ) {
+
+ vec2 screenClipPos = screenMetrics.GUIToScreen( currentClipPos );
+
+ vec2 screenClipSize( (currentClipSize.x()*screenMetrics.GUItoScreenXScale) + 0.5f,
+ (currentClipSize.y()*screenMetrics.GUItoScreenYScale) + 0.5f );
+
+ boxImage.setClipping( vec2( screenClipPos.x(), float(screenClipPos.y()) ),
+ vec2( screenClipSize.x(), screenClipSize.y() ) );
+ }
+
+ IMGUI_IMUIContext->queueImage( boxImage );
+
+}
+
+
+/*******************************************************************************************/
+/**
+ * @brief Finds an element in the gui by a given name
+ *
+ * @param elementName the name of the element
+ *
+ * @returns handle to the element (NULL if not found)
+ *
+ */
+IMElement* IMGUI::findElement( std::string const& elementName ) {
+
+
+ for( std::vector<IMContainer*>::iterator it = header.begin();
+ it != header.end();
+ ++it ) {
+ IMElement* foundElement = (*it)->findElement( elementName );
+
+ if( foundElement != NULL ) {
+ foundElement->AddRef();
+ return foundElement;
+ }
+ }
+
+ for( std::vector<IMContainer*>::iterator it = footer.begin();
+ it != footer.end();
+ ++it ) {
+ IMElement* foundElement = (*it)->findElement( elementName );
+
+ if( foundElement != NULL ) {
+ foundElement->AddRef();
+ return foundElement;
+ }
+ }
+
+ for( std::vector<IMContainer*>::iterator it = main.begin();
+ it != main.end();
+ ++it ) {
+ IMElement* foundElement = (*it)->findElement( elementName );
+
+ if( foundElement != NULL ) {
+ foundElement->AddRef();
+ return foundElement;
+ }
+ }
+
+ return NULL;
+
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Gets a unique name for assigning to unnamed elements (used internally)
+ *
+ * @returns Unique name as string
+ *
+ */
+std::string IMGUI::getUniqueName( std::string const& type ) {
+ elementCount += 1;
+
+ std::ostringstream oss;
+ oss << type << elementCount;
+
+ return oss.str();
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Remove all referenced object without releaseing references
+ *
+ */
+void IMGUI::clense() {
+
+ backgrounds.clear();
+ foregrounds.clear();
+ header.clear();
+ footer.clear();
+ main.clear();
+}
+
+/*******************************************************************************************/
+/**
+ * @brief Destructor
+ *
+ */
+IMGUI::~IMGUI() {
+
+ IMrefCountTracker.removeRefCountObject( "IMGUI" );
+ clear();
+ delete IMGUI_IMUIContext;
+
+ imevents.DeRegisterListener(this);
+ imevents.TriggerDestroyed(this);
+}
+
+void IMGUI::DestroyedIMElement( IMElement* element ) {
+ for( int i = backgrounds.size()-1; i >= 0; i-- ) {
+ if( backgrounds[i] == element ) {
+ backgrounds.erase(backgrounds.begin()+i);
+ }
+ }
+
+ for( int i = foregrounds.size()-1; i >= 0; i-- ) {
+ if( foregrounds[i] == element ) {
+ foregrounds.erase(foregrounds.begin()+i);
+ }
+ }
+
+ for( int i = header.size()-1; i >= 0; i-- ) {
+ if( header[i] == element ) {
+ header.erase(header.begin()+i);
+ }
+ }
+
+ for( int i = footer.size()-1; i >= 0; i-- ) {
+ if( footer[i] == element ) {
+ LOGI << "Removing footer " << element << std::endl;
+ footer.erase(footer.begin()+i);
+ }
+ }
+
+ for( int i = main.size()-1; i >= 0; i-- ) {
+ if( main[i] == element ) {
+ LOGI << "Removing main " << element << std::endl;
+ main.erase(main.begin()+i);
+ }
+ }
+}
+
+void IMGUI::DestroyedIMGUI( IMGUI* IMGUI ) {
+
+}
diff --git a/Source/GUI/IMUI/imgui.h b/Source/GUI/IMUI/imgui.h
new file mode 100644
index 00000000..a078cf1c
--- /dev/null
+++ b/Source/GUI/IMUI/imgui.h
@@ -0,0 +1,415 @@
+//-----------------------------------------------------------------------------
+// Name: imgui.h
+// Developer: Wolfire Games LLC
+// Description: Main class for creating adhoc GUIs as part of the UI tools
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <GUI/IMUI/imui.h>
+#include <GUI/IMUI/im_element.h>
+#include <GUI/IMUI/im_message.h>
+#include <GUI/IMUI/im_support.h>
+#include <GUI/IMUI/im_container.h>
+#include <GUI/IMUI/im_events.h>
+
+/*******************************************************************************************/
+/**
+ * @brief One thing to rule them all
+ *
+ */
+class IMGUI : public IMEventListener {
+
+
+ uint64_t lastUpdateTime; // When was this last updated (ms)
+ unsigned int elementCount; // Counter for naming unnamed elements
+ bool needsRelayout; // has a relayout signal been fired?
+ std::string reportedErrors; // have any errors been reported?
+ bool showGuides; // overlay some extra lines to aid layout
+
+ vec2 mousePosition; // Where is the mouse?
+ IMUIContext::ButtonState leftMouseState; // What's the state of the left mouse button?
+ std::vector<IMMessage*> messageQueue; // Messages waiting to process
+ std::vector<IMContainer*> backgrounds; // Containers for background layers, if any
+ std::vector<IMContainer*> foregrounds; // Containers for foreground layers, if any
+
+ std::vector<IMContainer*> header; // header panels
+ std::vector<IMContainer*> footer; // footer panels
+ std::vector<IMContainer*> main; // main panels
+
+ float mainOffset; // y offset for the main panel
+ float footerOffset; // y offset for the footer panel
+
+ float headerHeight; // size of the header (0 if none)
+ float footerHeight; // size of the footer (0 if none)
+ float mainHeight; // size of the main panel (0 if none)
+
+ std::vector<float> headerPanelWidths; // Width of the header panels
+ std::vector<float> footerPanelWidths; // Width of the header panels
+ std::vector<float> mainPanelWidths; // Width of the header panels
+
+ float headerPanelGap; // How much space between header panels
+ float footerPanelGap; // How much space between footer panels
+ float mainPanelGap; // How much space between main panels
+
+ unsigned int pendingFGLayers; // Foreground layers to create on 'setup'
+ unsigned int pendingBGLayers; // Background layers to create on 'setup'
+ float pendingHeaderHeight; // Size of header to create on 'setup'
+ float pendingFooterHeight; // Size of footer to create on 'setup'
+
+ std::vector<float> pendingHeaderPanels; // Sizes of header panels
+ std::vector<float> pendingMainPanels; // Sizes of main panels
+ std::vector<float> pendingFooterPanels; // Sizes of footer panels
+
+ vec2 screenSize; // for detecting changes
+
+ int refCount; // for AS reference counting
+
+
+
+
+ /*******************************************************************************************/
+ /**
+ * @brief If a relayout is requested, perform it
+ *
+ */
+ void doRelayout();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Figure out where we’re going to render the main panels - used internally
+ *
+ */
+ void derivePanelOffsets();
+
+private:
+ IMGUI( IMGUI const& other );
+
+public:
+
+ int guiid;
+
+ GUIState guistate; // current state of the GUI, updated at 'update'
+
+ IMUIContext* IMGUI_IMUIContext; // UI features from the engine
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor
+ *
+ * @param mainOrientation The orientation of the container
+ *
+ */
+ IMGUI();
+
+
+ /*******
+ *
+ * Angelscript memory management boilerplate
+ *
+ */
+ void AddRef();
+ void Release();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Set the number of background layers
+ *
+ * NOTE: This will take effect when setup is called next
+ *
+ * Background layers are rendered highest index value first
+ *
+ * @param numLayers number of layers required
+ *
+ */
+ void setBackgroundLayers( unsigned int numLayers );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Set the number of foreground layers
+ *
+ * NOTE: This will take effect when setup is called next
+ *
+ * Foreground layers are rendered lowest index value first
+ *
+ * @param numLayers number of layers required
+ *
+ */
+ void setForegroundLayers( unsigned int numLayers );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Set the size of the header (0 for none)
+ *
+ * NOTE: This will take effect when setup is called next
+ *
+ * @param _headerSize size in GUI space
+ *
+ */
+ void setHeaderHeight( float _headerSize );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Set the size of the footer (0 for none)
+ *
+ * NOTE: This will take effect when setup is called next
+ *
+ * @param _footerSize size in GUI space
+ *
+ */
+ void setFooterHeight( float _footerSize );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the sizes of the various header panels
+ *
+ * If not specified (specified means non-zero) there will be one panel, full width
+ * If one is specified, it will be centered
+ * If two are specified, they will be left and right justified
+ * Three will be left, center, right justified
+ *
+ */
+ void setHeaderPanels( float first = 0, float second = 0, float third = 0 );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the sizes of the various main panels
+ *
+ * If not specified (specified means non-zero) there will be one panel, full width
+ * If one is specified, it will be centered
+ * If two are specified, they will be left and right justified
+ * Three will be left, center, right justified
+ *
+ */
+ void setMainPanels( float first = 0, float second = 0, float third = 0 );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the sizes of the various footer panels
+ *
+ * If not specified (specified means non-zero) there will be one panel, full width
+ * If one is specified, it will be centered
+ * If two are specified, they will be left and right justified
+ * Three will be left, center, right justified
+ *
+ */
+ void setFooterPanels( float first = 0, float second = 0, float third = 0 );
+
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets up the GUI components: header, footer, main panel and foreground/background
+ *
+ */
+ void setup();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Clear the GUI - you probably want resetMainLayer though
+ *
+ * @param mainOrientation The orientation of the container
+ *
+ */
+ void clear();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Turns on (or off) the visual guides
+ *
+ * @param setting True to turn on guides, false otherwise
+ *
+ */
+ void setGuides( bool setting );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Records all the errors reported by child elements
+ *
+ * @param newError Newly reported error string
+ *
+ */
+ void reportError( std::string const& newError );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Receives a message - used internally
+ *
+ * @param message The message in question
+ *
+ */
+ void receiveMessage( IMMessage* message );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the size of the waiting message queue
+ *
+ * @returns size of the queue (as an unsigned integer)
+ *
+ */
+ unsigned int getMessageQueueSize();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the next available message in the queue (NULL if none)
+ *
+ * @returns A copy of the message (NULL if none)
+ *
+ */
+ IMMessage* getNextMessage();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Retrieves a reference to the main panel
+ *
+ */
+ IMContainer* getMain(unsigned int panel = 0) {
+ if( panel < main.size() ) {
+ main[panel]->AddRef();
+ return main[panel];
+ }
+ else {
+ return NULL;
+ }
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Retrieves a reference to the footer panel
+ *
+ */
+ IMContainer* getFooter(unsigned int panel = 0) {
+ if( panel < footer.size() ) {
+ footer[panel]->AddRef();
+ return footer[panel];
+ }
+ else {
+ return NULL;
+ }
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Retrieves a reference to the header panel
+ *
+ */
+ IMContainer* getHeader(unsigned int panel = 0) {
+ if( panel < header.size() ) {
+ header[panel]->AddRef();
+ return header[panel];
+ }
+ else {
+ return NULL;
+ }
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Retrieves a reference to a specified background layer
+ *
+ * @param layerNum index of the background layer (starting at 0)
+ *
+ */
+ IMContainer* getBackgroundLayer( unsigned int layerNum = 0 );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Retrieves a reference to a specified foreground layer
+ *
+ * @param layerNum index of the foreground layer (starting at 0)
+ *
+ */
+ IMContainer* getForegroundLayer( unsigned int layerNum = 0 );
+
+ /*******************************************************************************************/
+ /**
+ * @brief When this element is resized, moved, etc propagate this signal upwards
+ *
+ */
+ void onRelayout();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Updates the gui
+ *
+ */
+ void update();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Fired when the screen resizes
+ *
+ */
+ void doScreenResize();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Render the GUI
+ *
+ */
+ void render();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Draw a box (in *screen* coordinates) -- used internally
+ *
+ */
+ void drawBox( vec2 boxPos, vec2 boxSize, vec4 boxColor, int zOrder, bool shouldClip = false,
+ vec2 currentClipPos = vec2(UNDEFINEDSIZE, UNDEFINEDSIZE),
+ vec2 currentClipSize = vec2(UNDEFINEDSIZE, UNDEFINEDSIZE) );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Finds an element in the gui by a given name
+ *
+ * @param elementName the name of the element
+ *
+ * @returns handle to the element (NULL if not found)
+ *
+ */
+ IMElement* findElement( std::string const& elementName );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets a unique name for assigning to unnamed elements (used internally)
+ *
+ * @returns Unique name as string
+ *
+ */
+ std::string getUniqueName( std::string const& type = "Unkowntype");
+
+ /*******************************************************************************************/
+ /**
+ * @brief Remove all referenced object without releaseing references
+ *
+ */
+ virtual void clense();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Destructor
+ *
+ */
+ virtual ~IMGUI();
+
+ virtual void DestroyedIMElement( IMElement* element );
+ virtual void DestroyedIMGUI( IMGUI* IMGUI );
+};
+
+
diff --git a/Source/GUI/IMUI/imui.cpp b/Source/GUI/IMUI/imui.cpp
new file mode 100644
index 00000000..4c82d753
--- /dev/null
+++ b/Source/GUI/IMUI/imui.cpp
@@ -0,0 +1,917 @@
+//-----------------------------------------------------------------------------
+// Name: imui.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include <Graphics/graphics.h>
+#include <Graphics/shaders.h>
+#include <Graphics/vbocontainer.h>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <UserInput/input.h>
+#include <GUI/IMUI/imui.h>
+#include <Wrappers/glm.h>
+#include <Internal/profiler.h>
+#include <Main/engine.h>
+
+#include <utf8/utf8.h>
+#include <SDL.h>
+
+#include <cmath>
+#include <algorithm>
+
+namespace {
+ GLState gl_state;
+}
+
+/**
+ * IMUIRenderable
+ **/
+
+void IMUIRenderable::setPosition( vec3 newPos ) {
+ pos = newPos;
+}
+
+void IMUIRenderable::setColor( vec4 newColor ) {
+ color = newColor;
+}
+
+void IMUIRenderable::setClipping( vec2 offset, vec2 size ) {
+ enableClip = true;
+ clipPosition = offset;
+ clipSize = size;
+}
+
+void IMUIRenderable::disableClipping() {
+ enableClip = false;
+}
+
+void IMUIRenderable::setRotation( float newRotation ) {
+ rotation = newRotation;
+}
+
+/**
+ * IMUIImage
+ **/
+
+IMUIImage::IMUIImage( std::string filename ) :
+ IMUIRenderable( IMIURimage )
+{
+ loadImage( filename );
+}
+
+bool IMUIImage::loadImage( std::string filename )
+{
+
+ textureRef = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(filename, PX_NOMIPMAP | PX_NOREDUCE | PX_NOCONVERT, 0x0);
+
+ // If this was loaded then we can populate some attributes
+ if( textureRef.valid() ) {
+
+ // Record the original texture dimensions
+ textureSize[ 0 ] = (float)(Textures::Instance()->getReducedWidth(textureRef));
+
+ textureSize[ 1 ] = (float)(Textures::Instance()->getReducedHeight(textureRef));
+
+ // Assume that we're going to render the whole thing unless otherwise told
+ textureOffset = vec2( 0.0 );
+ textureSourceSize = textureSize;
+
+ // No scaling by default
+ renderSize = textureSize;
+
+ return true;
+ }
+ else {
+ // We're not good, tell the caller
+ return false;
+ }
+}
+
+bool IMUIImage::isValid() const {
+ return textureRef.valid();
+}
+
+float IMUIImage::getTextureWidth() const {
+ return textureSize[ 0 ];
+}
+
+float IMUIImage::getTextureHeight() const {
+ return textureSize[ 1 ];
+}
+
+void IMUIImage::setRenderOffset( vec2 offset, vec2 size ) {
+
+ // Make sure we have a valid texture
+ if( !isValid() ) return;
+
+ // If the offset is greater than the texture size, let's wrap around
+ textureOffset = vec2( (float)fmod( offset[0], textureSize[0] ),
+ (float)fmod( offset[1], textureSize[1] ) );
+
+ // Make sure the requested size doesn't go over the size of the texture
+ textureSourceSize = vec2( min( size[0], textureSize[0] - textureOffset[0] ),
+ min( size[1], textureSize[1] - textureOffset[1] ) );
+
+}
+
+void IMUIImage::setRenderSize( vec2 size ) {
+ renderSize = size;
+}
+
+/**
+ * IMUIText
+ **/
+
+void IMUIText::setText( std::string& _text ) {
+
+ text = _text;
+
+ // Make sure we've been properly initialized with a font
+ if( owner != NULL ) {
+ owner->deriveFontDimensions( *this );
+ }
+ else {
+ dimensions = vec2(0.0);
+ boundingBox = vec2(0.0);
+ }
+
+}
+
+void IMUIText::setRotation( float newRotation )
+{
+ rotation = newRotation;
+
+ // Make sure we've been properly initialized with a font
+ if( owner != NULL && text != "" ) {
+ owner->deriveFontDimensions( *this );
+ }
+ else {
+ dimensions = vec2(0.0);
+ boundingBox = vec2(0.0);
+ }
+
+}
+
+IMUIContext::IMUIContext() :
+ image_index_vbo(new VBOContainer()),
+ image_data_vbo(new VBORingContainer(sizeof(GLfloat) * 4 * 4,kVBOFloat | kVBODynamic)),
+ character_data_vbo(new VBORingContainer(sizeof(GLfloat) * kMaxCharacters * 4 * kFloatsPerVert, kVBOFloat | kVBODynamic )),
+ character_index_vbo(new VBORingContainer(sizeof(GLuint) * kMaxCharacters * 6, kVBOElement | kVBODynamic))
+{
+
+}
+
+void IMUIContext::Init() {
+ hot_ = -1;
+ active_ = -1;
+ lmb_state = kMouseStillUp;
+ debug_viz_enabled_ = false;
+
+ gl_state.blend = true;
+ gl_state.cull_face = false;
+ gl_state.depth_test = false;
+ gl_state.depth_write = false;
+}
+
+bool IMUIContext::DoButton( int id, vec2 top_left, vec2 bottom_right, UIState& ui_state) {
+ bool mouse_over = false;
+ if(mouse_pos[0] > top_left[0] && mouse_pos[0] < bottom_right[0] &&
+ mouse_pos[1] > top_left[1] && mouse_pos[1] < bottom_right[1])
+ {
+ mouse_over = true;
+ }
+
+ if(debug_viz_enabled_){
+ debug_viz_squares.resize(debug_viz_squares.size() + 1);
+ debug_viz_squares.back().top_left = top_left;
+ debug_viz_squares.back().bottom_right = bottom_right;
+ debug_viz_squares.back().ui_state = ui_state;
+ }
+
+ return DoButtonMouseOver(id, mouse_over, ui_state);
+}
+
+bool IMUIContext::DoButtonMouseOver( int id, bool mouse_over, UIState& ui_state){
+ bool result = false;
+ if(active_ == id && lmb_state == kMouseUp){
+ if(hot_ == id){
+ result = true;
+ }
+ active_ = -1;
+ } else if(hot_ == id && lmb_state == kMouseDown){
+ active_ = id;
+ }
+ if(mouse_over) {
+ if(active_ == -1 && hot_ == -1){
+ hot_ = id;
+ }
+ } else if(hot_ == id){
+ hot_ = -1;
+ }
+ if(active_ == id){
+ ui_state = kActive;
+ } else if(hot_ == id){
+ ui_state = kHot;
+ } else {
+ ui_state = kNothing;
+ }
+ return result;
+}
+
+void IMUIContext::ClearHot() {
+ hot_ = -1;
+}
+
+void IMUIContext::UpdateControls() {
+ Input* user_input = Input::Instance();
+ const Mouse& mouse = user_input->getMouse();
+ mouse_pos[0] = (float)mouse.pos_[0];
+ mouse_pos[1] = (float)(Graphics::Instance()->window_dims[1] - mouse.pos_[1]);
+ switch(lmb_state){
+ case kMouseUp:
+ lmb_state = kMouseStillUp;
+ //printf("Mouse: Still up\n");
+ case kMouseStillUp:
+ if(mouse.mouse_down_[Mouse::LEFT]){
+ lmb_state = kMouseDown;
+ //printf("Mouse: Down\n");
+ }
+ break;
+ case kMouseDown:
+ lmb_state = kMouseStillDown;
+ //printf("Mouse: Still down\n");
+ case kMouseStillDown:
+ if(!mouse.mouse_down_[Mouse::LEFT]){
+ lmb_state = kMouseUp;
+ //printf("Mouse: up\n");
+ }
+ break;
+ }
+ if(debug_viz_enabled_){
+ Draw();
+ }
+}
+
+
+void IMUIContext::Draw() {
+ Graphics* gi = Graphics::Instance();
+ gi->setGLState(gl_state);
+ Shaders::Instance()->noProgram();
+
+ glMatrixMode(GL_PROJECTION);
+ glPushMatrix();
+ glLoadIdentity();
+ glOrtho(0, gi->window_dims[0],
+ 0, gi->window_dims[1],
+ -100,100);
+ glMatrixMode(GL_MODELVIEW);
+ glPushMatrix();
+ glLoadIdentity();
+ glPushAttrib(GL_POLYGON_BIT);
+
+ for(unsigned i=0; i<debug_viz_squares.size(); ++i){
+ const DebugVizSquare &square = debug_viz_squares[i];
+ switch(square.ui_state){
+ case kActive:
+ glColor4f(0.1f,0.1f,0.1f,0.5f); break;
+ case kNothing:
+ glColor4f(0.5f,0.5f,0.5f,0.5f); break;
+ case kHot:
+ glColor4f(0.8f,0.8f,0.8f,0.5f); break;
+ }
+ int border_offset_y = 2;
+ int border_offset_x = 2;
+ int x1 = (int)square.top_left[0];
+ int x2 = (int)square.bottom_right[0];
+ int y1 = (int)square.top_left[1];
+ int y2 = (int)square.bottom_right[1];
+ if (y2 > y1) border_offset_y *= -1;
+ if (x2 < x1) border_offset_x *= -1;
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+ //TODO: set stipple
+ glBegin(GL_QUADS);
+ glVertex3f((GLfloat)(x1+border_offset_x),(GLfloat)(y1-border_offset_y),0);
+ glVertex3f((GLfloat)(x2-border_offset_x),(GLfloat)(y1-border_offset_y),0);
+ glVertex3f((GLfloat)(x2-border_offset_x),(GLfloat)(y2+border_offset_y),0);
+ glVertex3f((GLfloat)(x1+border_offset_x),(GLfloat)(y2+border_offset_y),0);
+ glEnd();
+ glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+ Graphics::Instance()->SetLineWidth(2);
+ glColor4f(0,0,0,1);
+ glBegin(GL_QUADS);
+ glVertex3f((GLfloat)x1,(GLfloat)y1,0);
+ glVertex3f((GLfloat)x2,(GLfloat)y1,0);
+ glVertex3f((GLfloat)x2,(GLfloat)y2,0);
+ glVertex3f((GLfloat)x1,(GLfloat)y2,0);
+ glEnd();
+ }
+ glPopAttrib();
+ glPopMatrix();
+ glMatrixMode(GL_PROJECTION);
+ glPopMatrix();
+ glMatrixMode(GL_MODELVIEW);
+ debug_viz_squares.clear();
+}
+
+void IMUIContext::SetDebugVizEnabled( bool enabled ) {
+ debug_viz_enabled_ = enabled;
+}
+
+vec2 IMUIContext::getMousePosition() {
+ return mouse_pos;
+}
+
+IMUIContext::ButtonState IMUIContext::getLeftMouseState() {
+ return lmb_state;
+}
+
+IMUIContext::~IMUIContext() {
+ // Free up our resources
+ clearTextAtlases();
+}
+
+void IMUIContext::queueImage( IMUIImage& newImage ) {
+
+ IMUIImage* queuedImage = new IMUIImage( newImage );
+ renderQueue.push_back( queuedImage );
+
+}
+
+IMUIText IMUIContext::makeText( std::string const& fontName, int size, int fontFlags, int renderFlags ) {
+
+ IMUIText newText;
+
+ newText.fontName = fontName;
+ newText.size = size;
+ newText.fontFlags = fontFlags;
+ newText.renderFlags = renderFlags;
+
+ newText.owner = this;
+
+ return newText;
+
+}
+
+void IMUIContext::deriveFontDimensions( IMUIText& text ) {
+
+ // Make sure we have a meaningful text object
+ if( text.owner==NULL || text.text == "" || text.fontName == "" ) {
+ // If not make sure that the dimensions are sensible
+ text.dimensions = vec2(0.0);
+ text.boundingBox = vec2(0.0);
+ return;
+ }
+
+ TextAtlas* atlas = getTextAtlas( text.fontName, text.size, text.fontFlags);
+
+ if( atlas == NULL ) {
+ std::string errorMessage = "Unable to find font " + text.fontName;
+ text.dimensions = vec2(0.0);
+ text.boundingBox = vec2(0.0);
+ DisplayError("Error", errorMessage.c_str() );
+ return;
+ }
+
+ // Now that we're sure everything is ok, we can finally figure out how big our text is
+ vec2 dimensions( 0, atlas->pixel_height );
+
+ // Get our singleton
+ FontRenderer* font_renderer = FontRenderer::Instance();
+
+ int last_c = -1;
+ int length = text.text.length();
+ int currentLineX = 0;
+
+ std::vector<uint32_t> utf32_string;
+ try {
+ utf8::utf8to32(text.text.begin(), text.text.end(), std::back_inserter(utf32_string));
+ } catch( const utf8::not_enough_room& ner ) {
+ LOGE << "Got utf8 exception \"" << ner.what() << "\" this might indicate invalid utf-8 data" << std::endl;
+ }
+
+ for(unsigned i=0; i<utf32_string.size(); ++i){
+ uint32_t c = utf32_string[i];
+ if(c == '\n') {
+
+ if( currentLineX > dimensions[ 0 ] ) {
+ dimensions[ 0 ] = (float)currentLineX;
+ }
+
+ dimensions[1] += atlas->pixel_height;
+
+ // Reset our x position
+ currentLineX = 0;
+ last_c = -1;
+
+ // Go onto the next character
+ continue;
+ }
+
+ std::vector<CharacterInfo>::iterator character;
+ for( character = atlas->alphabet.begin();
+ character != atlas->alphabet.end();
+ character++ ) {
+ if( character->codepoint == c )
+ break;
+ }
+
+ // Make sure it's a character we can render
+ if(character != atlas->alphabet.end()){
+
+ // Put in the approriate ammount of space
+ int kerning_x = 0;
+ if(last_c != -1) {
+ FT_Vector vec;
+ font_renderer->GetKerning(0, last_c, c, &vec);
+ kerning_x = vec.x / 64;
+ }
+
+ currentLineX += kerning_x;
+
+ currentLineX += character->advance_x;
+
+ last_c = c;
+
+ }
+
+ }
+
+ if( currentLineX > dimensions[ 0 ] ) {
+ dimensions[ 0 ] = (float)currentLineX;
+ }
+
+ text.dimensions = dimensions;
+
+ // Now we compute the dimensions under a given rotation
+ if( text.rotation == 0 ) {
+ text.boundingBox = dimensions;
+ }
+ else {
+
+ float halfX = text.dimensions[0] / 2.0f;
+ float halfY = text.dimensions[1] / 2.0f;
+
+ // Define four coordinates
+ vec2 UL( -halfX, halfY );
+ vec2 UR( halfX, halfY );
+ vec2 LL( -halfX, -halfY );
+ vec2 LR( halfX, -halfY );
+
+ // Get the rotation in radians
+ float rotation = text.rotation * ( PI_f/180 );
+
+ vec2 ULP( (UL[ 0 ] * cosf( rotation )) - (UL[1] * sinf( rotation )),
+ (UL[ 0 ] * sinf( rotation )) + (UL[1] * cosf( rotation )) );
+
+ vec2 URP( (UR[ 0 ] * cosf( rotation )) - (UR[1] * sinf( rotation )),
+ (UR[ 0 ] * sinf( rotation )) + (UR[1] * cosf( rotation )) );
+
+ vec2 LLP( (LL[ 0 ] * cosf( rotation )) - (LL[1] * sinf( rotation )),
+ (LL[ 0 ] * sinf( rotation )) + (LL[1] * cosf( rotation )) );
+
+ vec2 LRP( (LR[ 0 ] * cosf( rotation )) - (LR[1] * sinf( rotation )),
+ (LR[ 0 ] * sinf( rotation )) + (LR[1] * cosf( rotation )) );
+
+ // Now find the extremal points
+ float maxX = max( max( ULP[0], URP[0] ), max( LLP[0], LRP[0] ) );
+ float minX = min( min( ULP[0], URP[0] ), min( LLP[0], LRP[0] ) );
+ float maxY = max( max( ULP[1], URP[1] ), max( LLP[1], LRP[1] ) );
+ float minY = min( min( ULP[1], URP[1] ), min( LLP[1], LRP[1] ) );
+
+ // Finally write out our bouding values
+ text.boundingBox = vec2( fabsf( maxX ) + fabsf( minX ), fabsf( maxY ) + fabsf( minY ) );
+
+ }
+
+}
+
+void IMUIContext::queueText( IMUIText& newText ) {
+
+ IMUIText* queuedText = new IMUIText( newText );
+ renderQueue.push_back( queuedText );
+}
+
+void IMUIContext::clearTextAtlases() {
+ for(int i = 0; i < atlases.num_atlases; ++i){
+ atlases.cached[i].atlas.Dispose();
+ }
+ // Reset the cached atlas counter
+ atlases.num_atlases = 0;
+}
+
+class RenderableZCompare {
+public:
+ bool operator()(const IMUIRenderable* a, const IMUIRenderable* b) {
+ return a->pos[2] > b->pos[2];
+ }
+};
+
+struct HUDImageGLState {
+ GLState gl_state;
+ HUDImageGLState() {
+ gl_state.blend = true;
+ gl_state.blend_src = GL_SRC_ALPHA;
+ gl_state.blend_dst = GL_ONE_MINUS_SRC_ALPHA;
+ gl_state.cull_face = false;
+ gl_state.depth_test = false;
+ gl_state.depth_write = false;
+ }
+};
+
+static const HUDImageGLState hud_gl_state;
+
+void IMUIContext::render() {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "IMUIContext::render");
+
+ Graphics* graphics = Graphics::Instance();
+ Shaders* shaders = Shaders::Instance();
+ Textures* textures = Textures::Instance();
+
+ // Sort renderables so we can draw them in order of depth
+ std::sort(renderQueue.begin(), renderQueue.end(), RenderableZCompare());
+
+ int shader_id = shaders->returnProgram("simple_2d #TEXTURE #FLIPPED");
+ shaders->setProgram(shader_id);
+ int vert_coord_id = shaders->returnShaderAttrib("vert_coord", shader_id);
+ int tex_coord_id = shaders->returnShaderAttrib("tex_coord", shader_id);
+ int mvp_mat_id = shaders->returnShaderVariable("mvp_mat", shader_id);
+ int color_id = shaders->returnShaderVariable("color", shader_id);
+ graphics->setGLState(hud_gl_state.gl_state);
+ glm::mat4 proj_mat;
+
+ const float winWidth = (float)graphics->window_dims[0];
+ const float winHeight = (float)graphics->window_dims[1];
+
+ const float max_aspect_ratio = 16.0f/9.0f;
+ float aspect_ratio = winWidth/winHeight;
+ float xOffset = 0.0f;
+
+
+ while( renderQueue.size() > 0 ) {
+ IMUIRenderable* renderable = renderQueue[renderQueue.size()-1];
+ renderQueue.resize(renderQueue.size()-1);
+
+ xOffset = 0.0f;
+ if( renderable->skip_aspect_fitting == false) {
+ if( aspect_ratio > max_aspect_ratio ) {
+ xOffset = (winWidth - winHeight*max_aspect_ratio)/2.0f;
+ }
+ }
+
+ proj_mat = glm::translate(glm::ortho(0.0f, winWidth, 0.0f, winHeight), glm::vec3(xOffset,0.0f,0.0f));
+
+ if( renderable->enableClip ) {
+
+ glScissor( (GLint) (renderable->clipPosition[0] + xOffset),
+ (GLint) (winHeight - (renderable->clipPosition[1] + renderable->clipSize[1] )),
+ (GLsizei) (renderable->clipSize[0]),
+ (GLsizei) (renderable->clipSize[1]) );
+
+ glEnable(GL_SCISSOR_TEST);
+
+ }
+
+ switch( renderable->type ) {
+ case IMIURimage: {
+
+ // Cast this to an image
+ IMUIImage* image = (IMUIImage*)renderable;
+
+ // If we don't have a valid texture or we're completely transparent, skip this
+ if( !image->isValid() || image->color[3] <= 0.0f ){
+ break;
+ }
+
+ // Get our texture dimensions
+ float width = image->textureSize[ 0 ];
+ float height = image->textureSize[ 1 ];
+
+ // Image need to be scaled up to compensate for texture reduction
+ width = (float)textures->getReducedWidth(image->textureRef);
+ height = (float)textures->getReducedHeight(image->textureRef);
+
+ // Image use non-premultiplied alpha
+ graphics->SetBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ // Bind the texture
+ textures->bindTexture(image->textureRef);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.0f);
+
+ // Setup our texture parameters
+ glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
+ glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
+
+ // Inform the shader of the color
+ glUniform4fv(color_id, 1, (GLfloat*)&(image->color));
+
+ // Now we get to build some triangles and attach some texture coordinates
+ const vec2 to = vec2( image->textureOffset[0]/width,
+ (height - (image->textureOffset[1] + image->textureSourceSize[1]))/height );
+ const vec2 tss = vec2( image->textureSourceSize[0]/width,
+ image->textureSourceSize[1]/height );
+ const vec2 rs = image->renderSize;
+
+ GLfloat data[] = {
+ to[0], to[1], 0.0, 0.0,
+ to[0]+tss[0], to[1], 1.0, 0.0,
+ to[0]+tss[0], to[1]+tss[1], 1.0, 1.0,
+ to[0], to[1]+tss[1], 0.0, 1.0
+ };
+
+ static const GLuint indices[] = {0, 1, 2, 0, 2, 3};
+
+ // Set up the model matrix
+ glm::mat4 model_mat;
+ model_mat = glm::translate(model_mat,
+ glm::vec3(image->pos[0],
+ winHeight - (image->pos[1] + rs[1] ),
+ 0.0f));
+
+ model_mat = glm::translate(model_mat, glm::vec3(0.5f * image->renderSize[0],
+ 0.5f * image->renderSize[1],
+ 0.0f));
+
+ model_mat = glm::rotate(model_mat, image->rotation, glm::vec3(0.0f, 0.0f, 1.0f));
+ model_mat = glm::translate(model_mat, glm::vec3(-0.5f * image->renderSize[0],
+ -0.5f * image->renderSize[1],
+ 0.0f));
+
+ model_mat = glm::scale(model_mat, glm::vec3(image->renderSize[0],
+ image->renderSize[1],
+ 1.0f));
+
+ glm::mat4 mvp_mat = proj_mat * model_mat;
+
+ glUniformMatrix4fv(mvp_mat_id, 1, false, (GLfloat*)&mvp_mat);
+ graphics->SetClientActiveTexture(0);
+ graphics->EnableVertexAttribArray(vert_coord_id);
+ graphics->EnableVertexAttribArray(tex_coord_id);
+
+ if(!image_index_vbo->valid()) {
+ image_index_vbo->Fill(kVBOElement | kVBOStatic, sizeof(indices), (void*)indices);
+ }
+
+ image_data_vbo->Fill(sizeof(data), (void*)data);
+ image_data_vbo->Bind();
+ image_index_vbo->Bind();
+
+ glVertexAttribPointer(vert_coord_id, 2, GL_FLOAT, false, 4*sizeof(GLfloat), (const void*)(image_data_vbo->offset()+2*sizeof(GLfloat)));
+ glVertexAttribPointer(tex_coord_id, 2, GL_FLOAT, false, 4*sizeof(GLfloat), (const void*)(image_data_vbo->offset()));
+
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (const void*)image_index_vbo->offset());
+
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, 0.0f);
+
+ }
+ break;
+
+ case IMIURtext: {
+
+ // Storage for character mapping in the texture atlas
+ unsigned num_characters = 0;
+ unsigned indices[kMaxCharacters * 6];
+ float verts[kMaxCharacters * 4 * kFloatsPerVert]; //2v2t
+
+ // Cast this to a text object
+ IMUIText* text = (IMUIText*)renderable;
+
+ // Make sure there's anything to do
+ if( text->owner==NULL || text->text == "" || text->fontName == "" ) {
+ return;
+ }
+
+ // Make sure we have our text atlas
+ TextAtlas* atlas = getTextAtlas( text->fontName, text->size, text->fontFlags);
+
+ if( atlas == NULL ) {
+ std::string errorMessage = "Unable to find font " + text->fontName;
+ DisplayError("Error", errorMessage.c_str() );
+ return;
+ }
+
+ // Now that we're sure everything is ok, we can finally figure out how big our text is
+ vec2 dimensions( 0, atlas->pixel_height );
+
+ // Get our singleton
+ FontRenderer* font_renderer = FontRenderer::Instance();
+
+ int last_c = -1;
+ int length = text->text.length();
+
+
+ int line_height = font_renderer->GetFontInfo(0, FontRenderer::INFO_HEIGHT) / 64;
+ int ascender_height = font_renderer->GetFontInfo(0, FontRenderer::INFO_ASCENDER) / 64;
+
+ float ascender_size = ((float)abs(ascender_height)/(float)abs(line_height)) * text->size;
+
+ // Since we have the size already caclulated we can center text on the origin
+ // (for rotational purposes)
+ float startX = (float)(-1.0 * text->dimensions[0]/2.0);
+ vec2 currentPos( startX, (text->dimensions[1])/2.0f - ascender_size );
+
+
+ std::vector<uint32_t> utf32_string;
+
+ try {
+ utf8::utf8to32(text->text.begin(), text->text.end(), std::back_inserter(utf32_string));
+ } catch( const utf8::not_enough_room& ner ) {
+ LOGE << "Got utf8 exception \"" << ner.what() << "\" this might indicate invalid utf-8 data" << std::endl;
+ }
+
+ for(unsigned i=0; i < utf32_string.size() && i < kMaxCharacters; ++i){
+
+ if(num_characters < kMaxCharacters){
+ uint32_t c = utf32_string[i];
+
+ if(c == '\n'){
+ currentPos[1] += atlas->pixel_height;
+ currentPos[0] = startX;
+ }
+
+
+ std::vector<CharacterInfo>::iterator character;
+ for( character = atlas->alphabet.begin();
+ character != atlas->alphabet.end();
+ character++ ) {
+ if( character->codepoint == c )
+ break;
+ }
+
+ if(character != atlas->alphabet.end()) {
+ int kerning_x = 0;
+ if(last_c != -1){
+ FT_Vector vec;
+ font_renderer->GetKerning(0, last_c, c, &vec);
+ kerning_x = vec.x / 64;
+ }
+ currentPos[0] += kerning_x;
+
+ int vert_index = num_characters * 4 * TextAtlasRenderer::kFloatsPerVert;
+ verts[vert_index++] = (float)currentPos[0] + character->bearing[0];
+ verts[vert_index++] = (float)currentPos[1] + character->bearing[1];
+ verts[vert_index++] = (character->pos[0]) / (float)atlas->atlas_dims[0];
+ verts[vert_index++] = 1.0f - ((character->pos[1]) / (float)atlas->atlas_dims[1]);
+
+ verts[vert_index++] = (float)currentPos[0] + character->width + character->bearing[0];
+ verts[vert_index++] = (float)currentPos[1] + character->bearing[1];
+ verts[vert_index++] = (character->pos[0] + character->width) / (float)atlas->atlas_dims[0];
+ verts[vert_index++] = 1.0f - ((character->pos[1]) / (float)atlas->atlas_dims[1]);
+
+ verts[vert_index++] = (float)currentPos[0] + character->width + character->bearing[0];
+ verts[vert_index++] = (float)currentPos[1] - ( character->height - character->bearing[1] );
+ verts[vert_index++] = (character->pos[0] + character->width) / (float)atlas->atlas_dims[0];
+ verts[vert_index++] = 1.0f - ((character->pos[1] + character->height) / (float)atlas->atlas_dims[1]);
+
+ verts[vert_index++] = (float)currentPos[0] + character->bearing[0];
+ verts[vert_index++] = (float)currentPos[1] - ( character->height - character->bearing[1] );
+ verts[vert_index++] = (character->pos[0]) / (float)atlas->atlas_dims[0];
+ verts[vert_index++] = 1.0f - ((character->pos[1] + character->height) / (float)atlas->atlas_dims[1]);
+
+ unsigned indices_index = num_characters * 6;
+ unsigned vert_start = num_characters * 4;
+ indices[indices_index++] = vert_start + 0;
+ indices[indices_index++] = vert_start + 1;
+ indices[indices_index++] = vert_start + 2;
+ indices[indices_index++] = vert_start + 0;
+ indices[indices_index++] = vert_start + 2;
+ indices[indices_index++] = vert_start + 3;
+
+ ++num_characters;
+ currentPos[0] += character->advance_x;
+ last_c = c;
+ }
+ }
+ }
+
+ character_data_vbo->Fill(sizeof(verts)*num_characters/kMaxCharacters, verts);
+ character_index_vbo->Fill(sizeof(indices)*num_characters/kMaxCharacters, indices);
+
+ character_data_vbo->Bind();
+ character_index_vbo->Bind();
+
+ graphics->EnableVertexAttribArray(vert_coord_id);
+ graphics->EnableVertexAttribArray(tex_coord_id);
+
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, atlas->tex);
+ glVertexAttribPointer(vert_coord_id, 2, GL_FLOAT, false, 4*sizeof(GLfloat), (const void*)(character_data_vbo->offset()));
+ glVertexAttribPointer(tex_coord_id, 2, GL_FLOAT, false, 4*sizeof(GLfloat), (const void*)(character_data_vbo->offset()+sizeof(GL_FLOAT)*2));
+
+ int num_indices = sizeof(indices)*num_characters/kMaxCharacters/sizeof(float);
+
+
+
+ // Set up the model matrix
+ glm::mat4 model_mat;
+ model_mat = glm::translate(model_mat,
+ glm::vec3(text->pos[0] + 0.5f * text->boundingBox[0],
+ winHeight - (text->pos[1] + (0.5 * text->boundingBox[1] ) ),
+ 0.0f));
+
+ model_mat = glm::rotate(model_mat, text->rotation, glm::vec3(0.0f, 0.0f, 1.0f));
+
+ glm::mat4 mvp_mat = proj_mat * model_mat;
+
+ if( text->renderFlags & TextAtlasRenderer::kTextShadow ){
+ glm::mat4 offset_model_mat;
+ offset_model_mat = glm::translate(offset_model_mat,
+ glm::vec3(text->pos[0] + 0.5f * text->boundingBox[0] + 2.0,
+ winHeight - (text->pos[1] + (0.5 * text->boundingBox[1] ) + 2.0 ),
+ 0.0f));
+ glm::mat4 mat = proj_mat * offset_model_mat;
+ glUniformMatrix4fv(mvp_mat_id, 1, false, (GLfloat*)&mat);
+ glUniform4f(color_id, 0.0f, 0.0f, 0.0f, text->color.a());
+ graphics->DrawElements(GL_TRIANGLES, num_indices, GL_UNSIGNED_INT, (const void*)character_index_vbo->offset());
+ }
+
+ glUniformMatrix4fv(mvp_mat_id, 1, false, (GLfloat*)&mvp_mat);
+ glUniform4fv(color_id, 1, &(text->color[0]));
+ graphics->DrawElements(GL_TRIANGLES, num_indices, GL_UNSIGNED_INT, (const void*)character_index_vbo->offset());
+
+ graphics->BindElementVBO(0);
+ graphics->BindArrayVBO(0);
+ graphics->ResetVertexAttribArrays();
+ }
+ break;
+
+ case IMUIRinvalid:
+ default:
+ LOGE << "Unknown renderable type: " << renderable->type << std::endl;
+
+ }
+
+ if( renderable->enableClip ) {
+ glDisable(GL_SCISSOR_TEST);
+ }
+
+ // Clean up the copy
+ delete renderable;
+ }
+ graphics->ResetVertexAttribArrays();
+
+ renderQueue.clear();
+}
+
+TextAtlas* IMUIContext::getTextAtlas( std::string fontName, int size, int flags ) {
+ TextAtlas* atlas = NULL;
+
+ if(fontName.length() >= CachedTextAtlas::kPathSize){
+ DisplayError("Error", "Font path is too long");
+ } else {
+ int found = -1;
+ // See if we've chached this font atlas
+ for(int i=0; i<atlases.num_atlases; ++i){
+ const CachedTextAtlas& cached = atlases.cached[i];
+ if(strcmp(cached.path, fontName.c_str()) == 0 &&
+ size == cached.pixel_height &&
+ flags == cached.flags)
+ {
+ found = i;
+ }
+ }
+ if(found == -1){
+ // If we don't have this font cached, try to load it
+ if(atlases.num_atlases >= CachedTextAtlases::kMaxAtlases){
+ DisplayError("Error", "Too many cached text atlases");
+ } else {
+ found = atlases.num_atlases++;
+ CachedTextAtlas* new_atlas = &atlases.cached[found];
+ strncpy(new_atlas->path, fontName.c_str(), CachedTextAtlas::kPathSize);
+ new_atlas->pixel_height = size;
+ new_atlas->flags = flags;
+ new_atlas->atlas.Create(fontName.c_str(), size,
+ FontRenderer::Instance(), flags);
+ }
+ }
+
+ // See if, one way or another, we have a texture atlas
+ if(found != -1){
+ atlas = &atlases.cached[found].atlas;
+ }
+ }
+
+ return atlas;
+
+}
diff --git a/Source/GUI/IMUI/imui.h b/Source/GUI/IMUI/imui.h
new file mode 100644
index 00000000..e046e7ee
--- /dev/null
+++ b/Source/GUI/IMUI/imui.h
@@ -0,0 +1,496 @@
+//-----------------------------------------------------------------------------
+// Name: imui.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Logging/logdata.h>
+#include <Logging/loghandler.h>
+#include <Logging/consolehandler.h>
+
+#include <Math/vec2.h>
+#include <Math/vec3.h>
+#include <Math/vec4.h>
+
+#include <Graphics/textureref.h>
+#include <Graphics/shaders.h>
+#include <Graphics/graphics.h>
+#include <Graphics/text.h>
+#include <Graphics/vboringcontainer.h>
+
+#include <vector>
+#include <string>
+#include <ostream>
+
+enum IMUIRenderableType {
+ IMUIRinvalid = 0,
+ IMIURimage = 1,
+ IMIURtext = 2
+};
+
+struct IMUIRenderable {
+ bool skip_aspect_fitting; /**Ignore constraints used for ensuring UI sanity **/
+
+ vec3 pos; /**< Position (including z ordering) */
+ vec4 color; /**< Color for this element */
+
+ float rotation; /**< Rotation for the element ( in degrees ) */
+ bool enableClip; /**< Should we clip while rendering? */
+ vec2 clipPosition; /**< offset from the upper left corner of the screen to clip */
+ vec2 clipSize; /**< width and height of the clip */
+
+ IMUIRenderableType type; /**< Type for quick and dirty RTTI */
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor
+ *
+ */
+ IMUIRenderable( IMUIRenderableType _type = IMUIRinvalid ) :
+ pos( 0.0 ),
+ color( 1.0 ),
+ rotation(0),
+ enableClip( false ),
+ type(_type),
+ skip_aspect_fitting(false)
+ {}
+
+ /*******************************************************************************************/
+ /**
+ * @brief Copy constructor
+ *
+ */
+ IMUIRenderable( const IMUIRenderable& other ) {
+
+ pos = other.pos;
+ color = other.color;
+
+ rotation = other.rotation;
+ enableClip = other.enableClip;
+ clipPosition = other.clipPosition;
+ clipSize = clipSize = other.clipSize;
+
+ type = other.type;
+
+ skip_aspect_fitting = other.skip_aspect_fitting;
+ }
+
+ virtual ~IMUIRenderable(){};
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the position (z will be used for drawing order, no perspective)
+ *
+ * @param newPos new position for the renderable
+ *
+ */
+ void setPosition( vec3 newPos );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the color
+ *
+ * @param newColor new color for the renderable
+ *
+ */
+ void setColor( vec4 newColor );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the rotation
+ *
+ * @param newRotation new rotation for the renderable
+ *
+ */
+ void setRotation( float newRotation );
+
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the clipping for rendering this object
+ *
+ * @param offset position in pixels (from upper left) for the clipping box
+ * @param size width and height of the clipping box
+ *
+ */
+ void setClipping( vec2 offset, vec2 size );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Disables clipping for this renderable
+ *
+ */
+ void disableClipping();
+
+};
+
+struct IMUIImage : public IMUIRenderable {
+ TextureAssetRef textureRef; /**< Texture associated with this image (if any) */
+
+ vec2 renderSize; /**< what size in screen pixels should we be rendering at */
+ vec2 textureSize; /**< what size is the texture as loaded */
+ vec2 textureOffset; /**< where do we start rendering in the texture */
+ vec2 textureSourceSize; /**< How much of the texture are we rendering? */
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor
+ *
+ */
+ IMUIImage() :
+ IMUIRenderable( IMIURimage )
+ {}
+
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor
+ *
+ *
+ * @param filename Takes the filename to populate this iamge
+ */
+
+ IMUIImage( std::string filename );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Copy constructor
+ *
+ */
+ IMUIImage( const IMUIImage& other ) :
+ IMUIRenderable( (IMUIRenderable)other ),
+ textureRef( other.textureRef ),
+ renderSize( other.renderSize ),
+ textureSize( other.textureSize ),
+ textureOffset( other.textureOffset ),
+ textureSourceSize( other.textureSourceSize )
+ {
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Destructor
+ *
+ */
+ ~IMUIImage() {
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Set the texture from a file
+ *
+ * @param filename Where to find the texture
+ *
+ * @returns true if the file was loaded, false otherwise
+ *
+ */
+ bool loadImage( std::string filename );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Determines if this image is valid for display
+ *
+ * @returns true if valid, false otherwise
+ *
+ */
+ bool isValid() const;
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the width of the attached texture
+ *
+ * @returns The width (as float)
+ *
+ */
+ float getTextureWidth() const;
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the height of the attached texture
+ *
+ * @returns The height (as float)
+ *
+ */
+ float getTextureHeight() const;
+
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the offset of where to start drawing the image
+ *
+ * @param offset Offset from the upperlefthand
+ * @param size How big an area are we going to render (clamps to make sure the
+ *
+ */
+ void setRenderOffset( vec2 offset, vec2 size );
+
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the size to render on the screen
+ *
+ * @param size new render dimensions
+ *
+ */
+ void setRenderSize( vec2 size );
+
+};
+
+
+class IMUIContext;
+
+struct IMUIText : public IMUIRenderable {
+
+ std::string text; /**< actual text for this object */
+
+ IMUIContext* owner; /**< what context created this object */
+
+ // font information (filled by IMUIContext)
+ int size; /**< size of font, in pixels */
+ std::string fontName; /**< fontName (including full path) */
+ int fontFlags; /**< font options flags */
+ int renderFlags; /**< render options flags */
+
+ vec2 dimensions; /**< dimensions of the text */
+ vec2 boundingBox; /**< dimensions of the text under rotation (if any) */
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor
+ *
+ */
+ IMUIText() :
+ IMUIRenderable( IMIURtext ),
+ text( "" ),
+ owner( NULL ),
+ size( 0 ),
+ fontName(""),
+ fontFlags( 0 ),
+ renderFlags( 0 ),
+ dimensions( 0.0 ),
+ boundingBox( 0.0 )
+ {}
+
+ /*******************************************************************************************/
+ /**
+ * @brief Copy constructor
+ *
+ */
+ IMUIText( const IMUIText & other ) :
+ IMUIRenderable( (IMUIRenderable)other ),
+ text( other.text ),
+ owner( other.owner ),
+ size( other.size ),
+ fontName( other.fontName ),
+ fontFlags( other.fontFlags ),
+ renderFlags( other.renderFlags ),
+ dimensions( other.dimensions ),
+ boundingBox( other.boundingBox )
+ {
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Destructor
+ *
+ */
+ ~IMUIText() {
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the hight and width of the bounding box around the text
+ *
+ * @returns Size of the text as a 2d vector
+ *
+ */
+ vec2 getDimensions() const { return dimensions; }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the hight and width of the bounding box around the text (under rotation, if any)
+ *
+ * @returns Size of the text as a 2d vector
+ *
+ */
+ vec2 getBoundingBoxDimensions() const { return boundingBox; }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the text for this instance
+ *
+ * @param text Text for this instance
+ *
+ */
+ void setText( std::string& _text );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the rotation
+ *
+ * @param newRotation new rotation for the renderable
+ *
+ */
+ void setRotation( float newRotation );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Sets the render flags for this text
+ *
+ * (currently only kTextShadow )
+ *
+ * @param newFlags flags for rendering
+ *
+ */
+ void setRenderFlags( int newFlags ) {
+ renderFlags = newFlags;
+ }
+
+};
+
+class IMUIContext {
+public:
+
+ enum UIState{kNothing, kHot, kActive};
+ enum ButtonState{kMouseUp, kMouseDown, kMouseStillUp, kMouseStillDown};
+
+ void UpdateControls();
+ bool DoButton(int id, vec2 top_left, vec2 bottom_right, UIState &ui_state);//, UIState* ui_state = NULL);
+ bool DoButtonMouseOver( int id, bool mouse_over, UIState& ui_state);
+ void ClearHot();
+ void Init();
+ void SetDebugVizEnabled(bool enabled);
+ vec2 getMousePosition();
+ ButtonState getLeftMouseState();
+
+ // Text and image support
+ std::vector<IMUIRenderable*> renderQueue; /**< queue for objects to render this frame */
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor
+ *
+ */
+ IMUIContext();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Destructor
+ *
+ */
+ ~IMUIContext();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Queue an image for rendering
+ *
+ * @param newImage pointer to an image description
+ *
+ */
+ void queueImage( IMUIImage& newImage );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Create a new text object with given font characteristics
+ *
+ * @param fontName filename for this font (including path)
+ * @param size size in pixels
+ * @param flags style flags
+ *
+ */
+ IMUIText makeText( std::string const& fontName, int size, int fontFlags, int renderFlags = 0 );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Adds the dimensions to a given text object
+ *
+ * @param text Text object (reference)
+ *
+ */
+ void deriveFontDimensions( IMUIText& text );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Queue some text for rendering
+ *
+ * @param newImage
+ *
+ */
+ void queueText( IMUIText& newText );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Clear all the cached texts
+ *
+ */
+ void clearTextAtlases();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Render all queued elements and clear the render queue
+ *
+ */
+ void render();
+
+
+private:
+ static const unsigned kFloatsPerVert = 4;
+ static const unsigned kMaxCharacters = 1024;
+
+ RC_VBORingContainer image_data_vbo;
+ RC_VBOContainer image_index_vbo;
+
+ RC_VBORingContainer character_data_vbo;
+ RC_VBORingContainer character_index_vbo;
+
+ CachedTextAtlases atlases; /**< text atlases owned by this instance */
+
+ struct DebugVizSquare {
+ UIState ui_state;
+ vec2 top_left, bottom_right;
+ };
+
+ bool debug_viz_enabled_;
+ int hot_;
+ int active_;
+ vec2 mouse_pos;
+
+ ButtonState lmb_state;
+ std::vector<DebugVizSquare> debug_viz_squares;
+ std::vector<IMUIRenderableType*> renderables;
+
+ void Draw();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Get the atlas for the specified font (loaded if not already cached)
+ *
+ * @param fontName filename for this font (including path)
+ * @param size size in pixels
+ * @param flags style flags
+ *
+ * @returns Pointer to the atlas structure, null if not found
+ *
+ */
+ TextAtlas* getTextAtlas( std::string fontName, int size, int flags );
+
+};
diff --git a/Source/GUI/IMUI/imui_script.cpp b/Source/GUI/IMUI/imui_script.cpp
new file mode 100644
index 00000000..7ca15e02
--- /dev/null
+++ b/Source/GUI/IMUI/imui_script.cpp
@@ -0,0 +1,1119 @@
+//-----------------------------------------------------------------------------
+// Name: imui_script.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "imui_script.h"
+
+#include <GUI/IMUI/imgui.h>
+#include <GUI/IMUI/imui.h>
+#include <GUI/IMUI/im_container.h>
+#include <GUI/IMUI/im_divider.h>
+#include <GUI/IMUI/im_element.h>
+#include <GUI/IMUI/im_image.h>
+#include <GUI/IMUI/im_message.h>
+#include <GUI/IMUI/im_selection_list.h>
+#include <GUI/IMUI/im_spacer.h>
+#include <GUI/IMUI/im_text.h>
+#include <GUI/IMUI/im_behaviors.h>
+#include <GUI/IMUI/imui_state.h>
+#include <GUI/IMUI/im_support.h>
+#include <GUI/IMUI/im_tween.h>
+
+#include <Scripting/angelscript/ascontext.h>
+#include <Scripting/angelscript/add_on/scriptarray/scriptarray.h>
+
+static void IMUIContextDefaultConstructor(void *self) {
+ new(self) IMUIContext();
+}
+
+static void IMUIContextCopyConstructor(IMUIImage *self, const IMUIContext& other ) {
+ new(self) IMUIContext( other );
+}
+
+static const IMUIImage& IMUIContextAssign(IMUIImage *self, const IMUIImage& other) {
+ return (*self) = other;
+}
+
+static void IMUIContextDestructor(IMUIContext *self ) {
+ self->~IMUIContext();
+}
+
+
+static void IMUIImageDefaultConstructor(IMUIImage *self) {
+ new(self) IMUIImage();
+}
+
+static void IMUIImageFileNameConstructor(IMUIImage *self, std::string filename) {
+ new(self) IMUIImage( filename );
+}
+
+static void IMUIImageCopyConstructor(IMUIImage *self, const IMUIImage& other ) {
+ new(self) IMUIImage( other );
+}
+
+static const IMUIImage& IMUIImageAssign(IMUIImage *self, const IMUIImage& other) {
+ return (*self) = other;
+}
+
+static void IMUIImageDestructor(IMUIImage *self ) {
+ self->~IMUIImage();
+}
+
+static void IMUITextDefaultConstructor(IMUIText *self) {
+ new(self) IMUIText();
+}
+
+static void IMUITextCopyConstructor(IMUIText *self, const IMUIText& other ) {
+ new(self) IMUIText( other );
+}
+
+static const IMUIText& IMUITextAssign(IMUIText *self, const IMUIText& other) {
+ return (*self) = other;
+}
+
+static void IMUITextDestructor(IMUIText *self ) {
+ self->~IMUIText();
+}
+
+
+float ASUNDEFINEDSIZE = UNDEFINEDSIZE;
+int ASUNDEFINEDSIZEI = UNDEFINEDSIZEI;
+
+
+// Example opCast behaviour
+template<class A, class B>
+B* refCast(A* a)
+{
+ // If the handle already is a null handle, then just return the null handle
+ if( !a ) return 0;
+ // Now try to dynamically cast the pointer to the wanted type
+ B* b = dynamic_cast<B*>(a);
+ if( b != 0 )
+ {
+ // Since the cast was made, we need to increase the ref counter for the returned handle
+ b->AddRef();
+ }
+
+ return b;
+}
+
+static CScriptArray* AS_GetFloatingContents(IMContainer* element) {
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<IMElement@>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+
+ std::vector<IMElement*> vals = element->getFloatingContents();
+
+ array->Reserve(vals.size());
+
+ for( unsigned i = 0; i < vals.size(); i++ ) {
+ array->InsertLast((void*)&vals[i]);
+ }
+
+ return array;
+}
+
+static CScriptArray* AS_GetContainers(IMDivider* element) {
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<IMContainer@>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+
+ unsigned int count = element->getContainerCount();
+ array->Reserve(count);
+
+ for( unsigned i = 0; i < count; i++ ) {
+ IMContainer* container = element->getContainerAt(i);
+ container->Release();
+ array->InsertLast((void*)&container);
+ }
+
+ return array;
+}
+
+template<class T>
+void registerASIMElement( ASContext *ctx, std::string const& className ) {
+
+ ctx->RegisterObjectType(className.c_str(), 0, asOBJ_REF );
+
+ ctx->RegisterObjectProperty(className.c_str(), "CItem controllerItem", asOFFSET(T, controllerItem));
+
+ ctx->RegisterObjectBehaviour(className.c_str(), asBEHAVE_ADDREF, "void f()", asMETHOD(T,AddRef), asCALL_THISCALL);
+ ctx->RegisterObjectBehaviour(className.c_str(), asBEHAVE_RELEASE, "void f()", asMETHOD(T,Release), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod(className.c_str(), "string getElementTypeName()", asMETHOD(T, getElementTypeName), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setColor( vec4 _color )", asMETHOD(T, setColor), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "vec4 getColor()", asMETHOD(T, getColor), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "vec4 getBaseColor()", asMETHOD(T, getBaseColor), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setEffectColor( vec4 _color )", asMETHOD(T, setEffectColor), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "vec4 getEffectColor()", asMETHOD(T, getEffectColor), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void clearColorEffect()", asMETHOD(T, clearColorEffect), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setR( float value )", asMETHOD(T, setR), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "float getR()", asMETHOD(T, getR), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setG( float value )", asMETHOD(T, setG), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "float getG()", asMETHOD(T, getG), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setB( float value )", asMETHOD(T, setB), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "float getB()", asMETHOD(T, getB), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setAlpha( float value )", asMETHOD(T, setAlpha), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "float getAlpha()", asMETHOD(T, getAlpha), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setEffectR( float value )", asMETHOD(T, setEffectR), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "float getEffectR()", asMETHOD(T, getEffectR), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void clearEffectR()", asMETHOD(T, clearEffectR), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setEffectG( float value )", asMETHOD(T, setEffectG), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "float getEffectG()", asMETHOD(T, getEffectG), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void clearEffectG()", asMETHOD(T, clearEffectG), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setEffectB( float value )", asMETHOD(T, setEffectB), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "float getEffectB()", asMETHOD(T, getEffectB), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void clearEffectB()", asMETHOD(T, clearEffectB), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setEffectAlpha( float value )", asMETHOD(T, setEffectAlpha), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "float getEffectAlpha()", asMETHOD(T, getEffectAlpha), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void clearEffectAlpha()", asMETHOD(T, clearEffectAlpha), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void showBorder( bool _border = true )", asMETHOD(T, showBorder), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setBorderSize( float _borderSize )", asMETHOD(T, setBorderSize), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setBorderColor( vec4 _color )", asMETHOD(T, setBorderColor), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "vec4 getBorderColor()", asMETHOD(T, getBorderColor), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setBorderR( float value )", asMETHOD(T, setBorderR), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "float getBorderR()", asMETHOD(T, getBorderR), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setBorderG( float value )", asMETHOD(T, setBorderG), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "float getBorderG()", asMETHOD(T, getBorderG), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setBorderB( float value )", asMETHOD(T, setBorderB), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "float getBorderB()", asMETHOD(T, getBorderB), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setBorderAlpha( float value )", asMETHOD(T, setBorderAlpha), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "float getBorderAlpha()", asMETHOD(T, getBorderAlpha), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setZOrdering( int z )", asMETHOD(T, setZOrdering), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "int getZOrdering()", asMETHOD(T, getZOrdering), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void renderAbove( IMElement@ element )", asMETHOD(T, renderAbove), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void renderBelow( IMElement@ element )", asMETHOD(T, renderBelow), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setVisible( bool _show )", asMETHOD(T, setVisible), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setClip( bool _clip )", asMETHOD(T, setClip), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "vec2 getScreenPosition()", asMETHOD(T, getScreenPosition), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void addUpdateBehavior( IMUpdateBehavior@ behavior, const string &in behaviorName )", asMETHOD(T, addUpdateBehavior), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "bool removeUpdateBehavior( const string &in behaviorName )", asMETHOD(T, removeUpdateBehavior), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "bool hasUpdateBehavior(const string &in behaviorName)", asMETHOD(T, hasUpdateBehavior), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void clearUpdateBehaviors()", asMETHOD(T, clearUpdateBehaviors), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void addMouseOverBehavior( IMMouseOverBehavior@ behavior, const string &in behaviorName )", asMETHOD(T, addMouseOverBehavior), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "bool removeMouseOverBehavior( const string &in behaviorName )", asMETHOD(T, removeMouseOverBehavior), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void clearMouseOverBehaviors()", asMETHOD(T, clearMouseOverBehaviors), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void addLeftMouseClickBehavior( IMMouseClickBehavior@ behavior, const string &in behaviorName )", asMETHOD(T, addLeftMouseClickBehavior), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "bool removeLeftMouseClickBehavior( const string &in behaviorName )", asMETHOD(T, removeLeftMouseClickBehavior), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void clearLeftMouseClickBehaviors()", asMETHOD(T, clearLeftMouseClickBehaviors), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod(className.c_str(), "void sendMouseDownToChildren( bool send = true )", asMETHOD(T, sendMouseDownToChildren), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod(className.c_str(), "void sendMouseOverToChildren( bool send = true )", asMETHOD(T, sendMouseOverToChildren), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod(className.c_str(), "void setPauseBehaviors( bool pause )", asMETHOD(T, setPauseBehaviors), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "bool isMouseOver()", asMETHOD(T, isMouseOver), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod(className.c_str(), "void setName( const string &in _name )", asMETHOD(T, setName), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "string getName()", asMETHOD(T, getName), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setPadding( float U, float D, float L, float R)", asMETHOD(T, setPadding), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setPaddingU( float paddingSize )", asMETHOD(T, setPaddingU), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setPaddingD( float paddingSize )", asMETHOD(T, setPaddingD), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setPaddingL( float paddingSize )", asMETHOD(T, setPaddingL), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setPaddingR( float paddingSize )", asMETHOD(T, setPaddingR), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setDisplacement( vec2 newDisplacement = vec2(0,0) )", asMETHOD(T, setDisplacement), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setDisplacementX( float newDisplacement = 0 )", asMETHOD(T, setDisplacementX), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setDisplacementY( float newDisplacement = 0 )", asMETHOD(T, setDisplacementY), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "vec2 getDisplacement( vec2 newDisplacement = vec2(0,0) )", asMETHOD(T, getDisplacement), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "float getDisplacementX()", asMETHOD(T, getDisplacementX), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "float getDisplacementY()", asMETHOD(T, getDisplacementY), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setDefaultSize( vec2 newDefault )", asMETHOD(T, setDefaultSize), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "vec2 getDefaultSize()", asMETHOD(T, getDefaultSize), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setSize( const vec2 _size )", asMETHOD(T, setSize), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setSizeX( const float x )", asMETHOD(T, setSizeX), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void setSizeY( const float y )", asMETHOD(T, setSizeY), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "vec2 getSize()", asMETHOD(T, getSize), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "float getSizeX()", asMETHOD(T, getSizeX), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "float getSizeY()", asMETHOD(T, getSizeY), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "void sendMessage( IMMessage@ theMessage )", asMETHOD(T, sendMessage), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "IMElement@ findElement( const string &in elementName )", asMETHOD(T, findElement), asCALL_THISCALL);
+ ctx->RegisterObjectMethod(className.c_str(), "IMElement@ getParent()", asMETHOD(T, getParent), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod(className.c_str(), "void setMouseOver(bool hovered)", asMETHOD(T, setScriptMouseOver), asCALL_THISCALL);
+
+ std::string opCastStr = className + "@ opCast()";
+
+ // Expose the inheritance
+ ctx->RegisterObjectMethod("IMElement", opCastStr.c_str(), asFUNCTION((refCast<IMElement,T>)), asCALL_CDECL_OBJLAST);
+ ctx->RegisterObjectMethod(className.c_str(), "IMElement@ opImplCast()", asFUNCTION((refCast<T,IMElement>)), asCALL_CDECL_OBJLAST);
+
+}
+
+static int context_instance_id_counter = 1;
+static std::map<int,IMUIContext*> context_instance;
+
+extern "C" {
+
+ static int AS_CreateIMUIContext() {
+ int id = context_instance_id_counter++;
+ IMUIContext* instance = new IMUIContext();
+ context_instance[id] = instance;
+ return id;
+ }
+
+ static IMUIContext* AS_GetIMUIContext(int id) {
+ std::map<int,IMUIContext*>::iterator it = context_instance.find(id);
+ if( it != context_instance.end() ) {
+ return it->second;
+ } else {
+ return NULL;
+ }
+ }
+
+ static void AS_DisposeIMUIContext(int id) {
+ std::map<int,IMUIContext*>::iterator it = context_instance.find(id);
+ if( it != context_instance.end() ) {
+ delete it->second;
+ context_instance.erase(it);
+ }
+ }
+ static void AS_Context_queueImage( IMUIContext* t, IMUIImage* img ) {
+ t->queueImage(*img);
+ }
+
+ static void AS_Context_queueText( IMUIContext* t, IMUIText* txt ) {
+ t->queueText(*txt);
+ }
+
+ static IMGUI* AS_CreateIMGUI() {
+ return new IMGUI();
+ }
+}
+
+void AttachIMUI( ASContext *ctx ) {
+ ctx->RegisterEnum("UIState");
+ ctx->RegisterEnumValue("UIState", "kNothing", IMUIContext::kNothing);
+ ctx->RegisterEnumValue("UIState", "kHot", IMUIContext::kHot);
+ ctx->RegisterEnumValue("UIState", "kActive", IMUIContext::kActive);
+
+ ctx->RegisterEnum("UIMouseState");
+ ctx->RegisterEnumValue("UIMouseState", "kMouseUp", IMUIContext::kMouseUp);
+ ctx->RegisterEnumValue("UIMouseState", "kMouseDown", IMUIContext::kMouseDown);
+ ctx->RegisterEnumValue("UIMouseState", "kMouseStillUp", IMUIContext::kMouseStillUp);
+ ctx->RegisterEnumValue("UIMouseState", "kMouseStillDown", IMUIContext::kMouseStillDown);
+
+ ctx->RegisterEnum("TextFlags");
+ ctx->RegisterEnumValue("TextFlags", "kTextShadow", TextAtlasRenderer::kTextShadow );
+
+ /**
+ * IMUIImage
+ **/
+
+ ctx->RegisterObjectType("IMUIImage", sizeof(IMUIImage), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK );
+
+ // from IMUIRenderable
+
+ ctx->RegisterObjectMethod("IMUIImage", "void setPosition( vec3 newPos )", asMETHOD(IMUIImage, setPosition), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMUIImage", "void setColor( vec4 newColor )", asMETHOD(IMUIImage, setColor), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMUIImage", "void setRotation( float newRotation )", asMETHOD(IMUIImage, setRotation), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMUIImage", "void setClipping( vec2 offset, vec2 size )", asMETHOD(IMUIImage, setClipping), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMUIImage", "void disableClipping()", asMETHOD(IMUIImage, disableClipping), asCALL_THISCALL);
+
+ // from IMUIImage
+ ctx->RegisterObjectBehaviour("IMUIImage", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(IMUIImageDefaultConstructor), asCALL_CDECL_OBJFIRST );
+
+ ctx->RegisterObjectBehaviour("IMUIImage", asBEHAVE_CONSTRUCT, "void f( string filename )", asFUNCTION(IMUIImageFileNameConstructor), asCALL_CDECL_OBJFIRST );
+
+ ctx->RegisterObjectBehaviour("IMUIImage", asBEHAVE_CONSTRUCT, "void f( const IMUIImage &in other )", asFUNCTION(IMUIImageCopyConstructor), asCALL_CDECL_OBJFIRST );
+
+ ctx->RegisterObjectMethod("IMUIImage", "IMUIImage& opAssign(const IMUIImage &in other)", asFUNCTION(IMUIImageAssign), asCALL_CDECL_OBJFIRST);
+
+ ctx->RegisterObjectBehaviour("IMUIImage", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(IMUIImageDestructor), asCALL_CDECL_OBJFIRST );
+
+ ctx->RegisterObjectMethod("IMUIImage", "bool loadImage( string filename )", asMETHOD(IMUIImage, loadImage), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMUIImage", "bool isValid() const", asMETHOD(IMUIImage, isValid), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMUIImage", "float getTextureWidth() const", asMETHOD(IMUIImage, getTextureWidth), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMUIImage", "float getTextureHeight() const", asMETHOD(IMUIImage, getTextureHeight), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMUIImage", "void setRenderOffset( vec2 offset, vec2 size )", asMETHOD(IMUIImage, setRenderOffset), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMUIImage", "void setRenderSize( vec2 size )", asMETHOD(IMUIImage, setRenderSize), asCALL_THISCALL);
+
+ /**
+ * IMUIText
+ **/
+
+ ctx->RegisterObjectType("IMUIText", sizeof(IMUIText), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK );
+
+ // from IMUIRenderable
+
+ ctx->RegisterObjectMethod("IMUIText", "void setPosition( vec3 newPos )", asMETHOD(IMUIText, setPosition), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMUIText", "void setColor( vec4 newColor )", asMETHOD(IMUIText, setColor), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMUIText", "void setRotation( float newRotation )", asMETHOD(IMUIText, setRotation), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMUIText", "void setClipping( vec2 offset, vec2 size )", asMETHOD(IMUIText, setClipping), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMUIText", "void disableClipping()", asMETHOD(IMUIText, disableClipping), asCALL_THISCALL);
+
+ // from IMUIText
+ ctx->RegisterObjectBehaviour("IMUIText", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(IMUITextDefaultConstructor), asCALL_CDECL_OBJFIRST );
+
+ ctx->RegisterObjectBehaviour("IMUIText", asBEHAVE_CONSTRUCT, "void f( const IMUIText &in other )", asFUNCTION(IMUITextCopyConstructor), asCALL_CDECL_OBJFIRST );
+
+ ctx->RegisterObjectMethod("IMUIText", "IMUIText& opAssign(const IMUIText &in other)", asFUNCTION(IMUITextAssign), asCALL_CDECL_OBJFIRST);
+
+ ctx->RegisterObjectBehaviour("IMUIText", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(IMUITextDestructor), asCALL_CDECL_OBJFIRST );
+
+ ctx->RegisterObjectMethod("IMUIText", "vec2 getDimensions() const", asMETHOD(IMUIText, getDimensions), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMUIText", "vec2 getBoundingBoxDimensions() const", asMETHOD(IMUIText, getBoundingBoxDimensions), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMUIText", "void setText( const string &in _text )", asMETHOD(IMUIText, setText), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMUIText", "void setRenderFlags( int newFlags )", asMETHOD(IMUIText, setRenderFlags), asCALL_THISCALL);
+
+ /**
+ * IMUIContext
+ **/
+
+ //ctx->RegisterObjectType("IMUIContext", sizeof(IMUIContext), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK );
+ ctx->RegisterObjectType("IMUIContext", 0, asOBJ_REF | asOBJ_NOCOUNT );
+
+ ctx->RegisterGlobalFunction(
+ "int CreateIMUIContext()",
+ asFUNCTION(AS_CreateIMUIContext),
+ asCALL_CDECL
+ );
+
+ ctx->RegisterGlobalFunction(
+ "IMUIContext@ GetIMUIContext(int id)",
+ asFUNCTION(AS_GetIMUIContext),
+ asCALL_CDECL
+ );
+
+ ctx->RegisterGlobalFunction(
+ "void DisposeIMUIContext(int id)",
+ asFUNCTION(AS_DisposeIMUIContext),
+ asCALL_CDECL
+ );
+
+ ctx->RegisterObjectMethod(
+ "IMUIContext",
+ "void UpdateControls()",
+ asMETHOD(IMUIContext, UpdateControls),
+ asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod(
+ "IMUIContext",
+ "bool DoButton(int, vec2, vec2, UIState &out)",
+ asMETHOD(IMUIContext, DoButton),
+ asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod(
+ "IMUIContext",
+ "void Init()",
+ asMETHOD(IMUIContext, Init),
+ asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod(
+ "IMUIContext",
+ "vec2 getMousePosition()",
+ asMETHOD(IMUIContext, getMousePosition),
+ asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod(
+ "IMUIContext",
+ "UIMouseState getLeftMouseState()",
+ asMETHOD(IMUIContext, getLeftMouseState),
+ asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod(
+ "IMUIContext",
+ "void queueImage( IMUIImage &in newImage )",
+ asMETHOD(IMUIContext, queueImage),
+ asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod(
+ "IMUIContext",
+ "void queueText( IMUIText &in newText )",
+ asMETHOD(IMUIContext, queueText),
+ asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod(
+ "IMUIContext",
+ "IMUIText makeText( string &in fontName, int size, int fontFlags, int renderFlags = 0 )",
+ asMETHOD(IMUIContext, makeText),
+ asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod(
+ "IMUIContext",
+ "void render()",
+ asMETHOD(IMUIContext, render),
+ asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod(
+ "IMUIContext",
+ "void clearTextAtlases()",
+ asMETHOD(IMUIContext, clearTextAtlases),
+ asCALL_THISCALL);
+
+ /*******
+ *
+ * IMGUI elements
+ *
+ */
+
+ /*******
+ *
+ * Constants
+ *
+ */
+
+ ctx->RegisterGlobalProperty("const float UNDEFINEDSIZE", &ASUNDEFINEDSIZE);
+ ctx->RegisterGlobalProperty("const int UNDEFINEDSIZEI", &ASUNDEFINEDSIZEI);
+
+ ctx->RegisterEnum("DividerOrientation");
+ ctx->RegisterEnumValue("DividerOrientation", "DOVertical", DOVertical);
+ ctx->RegisterEnumValue("DividerOrientation", "DOHorizontal", DOHorizontal);
+
+ ctx->RegisterEnum("ContainerAlignment");
+ ctx->RegisterEnumValue("ContainerAlignment", "CATop", CATop);
+ ctx->RegisterEnumValue("ContainerAlignment", "CALeft", CALeft);
+ ctx->RegisterEnumValue("ContainerAlignment", "CACenter", CACenter);
+ ctx->RegisterEnumValue("ContainerAlignment", "CARight", CARight);
+ ctx->RegisterEnumValue("ContainerAlignment", "CABottom", CABottom);
+
+ ctx->RegisterEnum("ExpansionPolicy");
+ ctx->RegisterEnumValue("ExpansionPolicy", "ContainerExpansionStatic", ContainerExpansionStatic);
+ ctx->RegisterEnumValue("ExpansionPolicy", "ContainerExpansionExpand", ContainerExpansionExpand);
+ ctx->RegisterEnumValue("ExpansionPolicy", "ContainerExpansionInheritMax", ContainerExpansionInheritMax);
+
+ /*******
+ *
+ * Messages
+ *
+ */
+
+ ctx->RegisterObjectType("IMMessage", 0, asOBJ_REF );
+
+ ctx->RegisterObjectBehaviour("IMMessage", asBEHAVE_ADDREF, "void f()", asMETHOD(IMMessage,AddRef), asCALL_THISCALL);
+ ctx->RegisterObjectBehaviour("IMMessage", asBEHAVE_RELEASE, "void f()", asMETHOD(IMMessage,Release), asCALL_THISCALL);
+
+ ctx->RegisterObjectProperty("IMMessage",
+ "string name",
+ asOFFSET(IMMessage, name));
+
+
+ ctx->RegisterObjectBehaviour("IMMessage", asBEHAVE_FACTORY, "IMMessage@ f( const string &in name)", asFUNCTION(IMMessage::ASFactory), asCALL_CDECL);
+
+ ctx->RegisterObjectBehaviour("IMMessage", asBEHAVE_FACTORY, "IMMessage@ f( const string &in name, int param )", asFUNCTION(IMMessage::ASFactory_int), asCALL_CDECL);
+
+ ctx->RegisterObjectBehaviour("IMMessage", asBEHAVE_FACTORY, "IMMessage@ f( const string &in name, float param )", asFUNCTION(IMMessage::ASFactory_float), asCALL_CDECL);
+
+ ctx->RegisterObjectBehaviour("IMMessage", asBEHAVE_FACTORY, "IMMessage@ f( const string &in name, const string &in param )", asFUNCTION(IMMessage::ASFactory_string), asCALL_CDECL);
+
+ ctx->RegisterObjectMethod("IMMessage", "int numInts()", asMETHOD(IMMessage, numInts), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMMessage", "int numFloats()", asMETHOD(IMMessage, numFloats), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMMessage", "int numStrings()", asMETHOD(IMMessage, numStrings), asCALL_THISCALL);
+
+
+ ctx->RegisterObjectMethod("IMMessage", "void addInt( int param )", asMETHOD(IMMessage, addInt), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMMessage", "void addFloat( float param )", asMETHOD(IMMessage, addFloat), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMMessage", "void addString( const string &in param )", asMETHOD(IMMessage, addString), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMMessage", "int getInt( int index ) ", asMETHOD(IMMessage, getInt), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMMessage", "float getFloat( int index ) ", asMETHOD(IMMessage, getFloat), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMMessage", "string getString( int index ) ", asMETHOD(IMMessage, getString), asCALL_THISCALL);
+
+
+ /*******
+ *
+ * Controller items "CItem"
+ *
+ */
+ ctx->RegisterObjectType("CItem", 0, asOBJ_REF );
+ ctx->RegisterObjectBehaviour("CItem", asBEHAVE_FACTORY, "CItem@ f()", asFUNCTION(ControllerItem::ASFactory), asCALL_CDECL);
+ ctx->RegisterObjectBehaviour("CItem", asBEHAVE_ADDREF, "void f()", asMETHOD(ControllerItem,AddRef), asCALL_THISCALL);
+ ctx->RegisterObjectBehaviour("CItem", asBEHAVE_RELEASE, "void f()", asMETHOD(ControllerItem,Release), asCALL_THISCALL);
+
+ ctx->RegisterObjectProperty("CItem", "bool execute_on_select", asOFFSET(ControllerItem, execute_on_select));
+ ctx->RegisterObjectProperty("CItem", "bool skip_show_border", asOFFSET(ControllerItem, skip_show_border));
+
+ ctx->RegisterObjectMethod("CItem", "void setMessage(IMMessage@ message)", asMETHOD(ControllerItem, setMessage), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("CItem", "void setMessageOnSelect(IMMessage@ message)", asMETHOD(ControllerItem, setMessageOnSelect), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("CItem", "void setMessages(IMMessage@ message, IMMessage@ message_on_select, IMMessage@ message_left, IMMessage@ message_right, IMMessage@ message_up, IMMessage@ message_down)", asMETHOD(ControllerItem, setMessages), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("CItem", "IMMessage@ getMessage()", asMETHOD(ControllerItem, getMessage), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("CItem", "IMMessage@ getMessageOnSelect()", asMETHOD(ControllerItem, getMessageOnSelect), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("CItem", "IMMessage@ getMessageLeft()", asMETHOD(ControllerItem, getMessageLeft), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("CItem", "IMMessage@ getMessageRight()", asMETHOD(ControllerItem, getMessageRight), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("CItem", "IMMessage@ getMessageUp()", asMETHOD(ControllerItem, getMessageUp), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("CItem", "IMMessage@ getMessageDown()", asMETHOD(ControllerItem, getMessageDown), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("CItem", "bool isActive()", asMETHOD(ControllerItem, isActive), asCALL_THISCALL);
+
+ /*******
+ *
+ * GUIState
+ *
+ */
+
+// struct GUIState
+// {
+// vec2 mousePosition;
+// IMUIContext::ButtonState leftMouseState;
+// };
+
+ ctx->RegisterObjectType("GUIState", 0, asOBJ_REF | asOBJ_NOCOUNT );
+ ctx->RegisterObjectProperty("GUIState",
+ "vec2 mousePosition",
+ asOFFSET(GUIState, mousePosition));
+ ctx->RegisterObjectProperty("GUIState",
+ "UIMouseState leftMouseState",
+ asOFFSET(GUIState, leftMouseState));
+
+ /*******
+ *
+ * Font setup
+ *
+ */
+
+ ctx->RegisterGlobalFunction("vec4 HexColor(const string &in hex)", asFUNCTION(HexColor), asCALL_CDECL);
+
+ ctx->RegisterObjectType("FontSetup", sizeof(FontSetup), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK );
+
+ ctx->RegisterObjectBehaviour("FontSetup", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(FontSetup_ASconstructor_default), asCALL_CDECL_OBJFIRST );
+
+ ctx->RegisterObjectBehaviour("FontSetup", asBEHAVE_CONSTRUCT, "void f( const string &in _name, int32 _size, vec4 _color, bool _shadowed = false )", asFUNCTION(FontSetup_ASconstructor_params), asCALL_CDECL_OBJFIRST );
+
+ ctx->RegisterObjectBehaviour("FontSetup", asBEHAVE_CONSTRUCT, "void f( const FontSetup &in other )", asFUNCTION(FontSetup_AScopy), asCALL_CDECL_OBJFIRST );
+
+ ctx->RegisterObjectMethod("FontSetup", "FontSetup& opAssign(const FontSetup &in other)", asFUNCTION(FontSetup_ASassign), asCALL_CDECL_OBJFIRST);
+
+ ctx->RegisterObjectBehaviour("FontSetup", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(FontSetup_ASdestructor), asCALL_CDECL_OBJFIRST );
+
+ ctx->RegisterObjectProperty("FontSetup",
+ "string fontName",
+ asOFFSET(FontSetup, fontName));
+
+ ctx->RegisterObjectProperty("FontSetup",
+ "int size",
+ asOFFSET(FontSetup, size));
+
+ ctx->RegisterObjectProperty("FontSetup",
+ "vec4 color",
+ asOFFSET(FontSetup, color));
+
+ ctx->RegisterObjectProperty("FontSetup",
+ "float rotation",
+ asOFFSET(FontSetup, rotation));
+
+ ctx->RegisterObjectProperty("FontSetup",
+ "bool shadowed",
+ asOFFSET(FontSetup, shadowed));
+
+ /*******
+ *
+ * SizePolicy
+ *
+ */
+ ctx->RegisterObjectType("SizePolicy", sizeof(SizePolicy), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK );
+
+ ctx->RegisterObjectBehaviour("SizePolicy", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(SizePolicy_ASfactory_noparams), asCALL_CDECL_OBJFIRST );
+
+ ctx->RegisterObjectBehaviour("SizePolicy", asBEHAVE_CONSTRUCT, "void f( float _defaultSize )", asFUNCTION(SizePolicy_ASfactory_params), asCALL_CDECL_OBJFIRST );
+
+ ctx->RegisterObjectBehaviour("SizePolicy", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(SizePolicy_ASdestructor), asCALL_CDECL_OBJFIRST );
+
+ ctx->RegisterObjectBehaviour("SizePolicy", asBEHAVE_CONSTRUCT, "void f( const SizePolicy &in other )", asFUNCTION(SizePolicy_ASCopyConstructor), asCALL_CDECL_OBJFIRST );
+
+ ctx->RegisterObjectMethod("SizePolicy", "IMUIText& opAssign(const SizePolicy &in other)", asFUNCTION(SizePolicy_ASAssign), asCALL_CDECL_OBJFIRST);
+
+ ctx->RegisterObjectMethod("SizePolicy", "SizePolicy expand( float _maxSize = UNDEFINEDSIZE )", asMETHOD(SizePolicy, expand), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("SizePolicy", "SizePolicy inheritMax()", asMETHOD(SizePolicy, inheritMax), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("SizePolicy", "SizePolicy staticMax()", asMETHOD(SizePolicy, staticMax), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("SizePolicy", "SizePolicy overflowClip( bool shouldClip )", asMETHOD(SizePolicy, overflowClip), asCALL_THISCALL);
+
+ /*******
+ *
+ * Tweens
+ *
+ */
+
+ ctx->RegisterEnum("IMTweenType");
+ ctx->RegisterEnumValue("IMTweenType", "linearTween", linearTween);
+ ctx->RegisterEnumValue("IMTweenType", "inQuadTween", inQuadTween);
+ ctx->RegisterEnumValue("IMTweenType", "outQuadTween", outQuadTween);
+ ctx->RegisterEnumValue("IMTweenType", "inOutQuadTween", inOutQuadTween);
+ ctx->RegisterEnumValue("IMTweenType", "outInQuadTween", outInQuadTween);
+ ctx->RegisterEnumValue("IMTweenType", "inCubicTween", inCubicTween);
+ ctx->RegisterEnumValue("IMTweenType", "outCubicTween", outCubicTween);
+ ctx->RegisterEnumValue("IMTweenType", "inOutCubicTween", inOutCubicTween);
+ ctx->RegisterEnumValue("IMTweenType", "outInCubicTween", outInCubicTween);
+ ctx->RegisterEnumValue("IMTweenType", "inQuartTween", inQuartTween);
+ ctx->RegisterEnumValue("IMTweenType", "outQuartTween", outQuartTween);
+ ctx->RegisterEnumValue("IMTweenType", "inOutQuartTween", inOutQuartTween);
+ ctx->RegisterEnumValue("IMTweenType", "outInQuartTween", outInQuartTween);
+ ctx->RegisterEnumValue("IMTweenType", "inQuintTween", inQuintTween);
+ ctx->RegisterEnumValue("IMTweenType", "outQuintTween", outQuintTween);
+ ctx->RegisterEnumValue("IMTweenType", "inOutQuintTween", inOutQuintTween);
+ ctx->RegisterEnumValue("IMTweenType", "outInQuintTween", outInQuintTween);
+ ctx->RegisterEnumValue("IMTweenType", "inSineTween", inSineTween);
+ ctx->RegisterEnumValue("IMTweenType", "outSineTween", outSineTween);
+ ctx->RegisterEnumValue("IMTweenType", "inOutSineTween", inOutSineTween);
+ ctx->RegisterEnumValue("IMTweenType", "outInSineTween", outInSineTween);
+ ctx->RegisterEnumValue("IMTweenType", "inExpoTween", inExpoTween);
+ ctx->RegisterEnumValue("IMTweenType", "outExpoTween", outExpoTween);
+ ctx->RegisterEnumValue("IMTweenType", "inOutExpoTween", inOutExpoTween);
+ ctx->RegisterEnumValue("IMTweenType", "outInExpoTween", outInExpoTween);
+ ctx->RegisterEnumValue("IMTweenType", "inCircTween", inCircTween);
+ ctx->RegisterEnumValue("IMTweenType", "outCircTween", outCircTween);
+ ctx->RegisterEnumValue("IMTweenType", "inOutCircTween", inOutCircTween);
+ ctx->RegisterEnumValue("IMTweenType", "outInCircTween", outInCircTween);
+ ctx->RegisterEnumValue("IMTweenType", "outBounceTween", outBounceTween);
+ ctx->RegisterEnumValue("IMTweenType", "inBounceTween", inBounceTween);
+ ctx->RegisterEnumValue("IMTweenType", "inOutBounceTween", inOutBounceTween);
+ ctx->RegisterEnumValue("IMTweenType", "outInBounceTween", outInBounceTween);
+
+ /**
+ * Behavior base clases
+ **/
+
+ ctx->RegisterObjectType("IMUpdateBehavior", 0, asOBJ_REF );
+ ctx->RegisterObjectBehaviour("IMUpdateBehavior", asBEHAVE_FACTORY, "IMUpdateBehavior@ f()", asFUNCTION(IMUpdateBehavior::ASFactory), asCALL_CDECL);
+ ctx->RegisterObjectBehaviour("IMUpdateBehavior", asBEHAVE_ADDREF, "void f()", asMETHOD(IMUpdateBehavior,AddRef), asCALL_THISCALL);
+ ctx->RegisterObjectBehaviour("IMUpdateBehavior", asBEHAVE_RELEASE, "void f()", asMETHOD(IMUpdateBehavior,Release), asCALL_THISCALL);
+
+
+ ctx->RegisterObjectType("IMMouseOverBehavior", 0, asOBJ_REF );
+ ctx->RegisterObjectBehaviour("IMMouseOverBehavior", asBEHAVE_FACTORY, "IMMouseOverBehavior@ f()", asFUNCTION(IMMouseOverBehavior::ASFactory), asCALL_CDECL);
+ ctx->RegisterObjectBehaviour("IMMouseOverBehavior", asBEHAVE_ADDREF, "void f()", asMETHOD(IMMouseOverBehavior,AddRef), asCALL_THISCALL);
+ ctx->RegisterObjectBehaviour("IMMouseOverBehavior", asBEHAVE_RELEASE, "void f()", asMETHOD(IMMouseOverBehavior,Release), asCALL_THISCALL);
+
+ ctx->RegisterObjectType("IMMouseClickBehavior", 0, asOBJ_REF );
+ ctx->RegisterObjectBehaviour("IMMouseClickBehavior", asBEHAVE_FACTORY, "IMMouseClickBehavior@ f()", asFUNCTION(IMMouseClickBehavior::ASFactory), asCALL_CDECL);
+ ctx->RegisterObjectBehaviour("IMMouseClickBehavior", asBEHAVE_ADDREF, "void f()", asMETHOD(IMMouseClickBehavior,AddRef), asCALL_THISCALL);
+ ctx->RegisterObjectBehaviour("IMMouseClickBehavior", asBEHAVE_RELEASE, "void f()", asMETHOD(IMMouseClickBehavior,Release), asCALL_THISCALL);
+
+ /**
+ * Behaviors clases
+ **/
+
+ //// IMUpdateBehavior
+
+ ctx->RegisterObjectType("IMFadeIn", 0, asOBJ_REF );
+ ctx->RegisterObjectBehaviour("IMFadeIn", asBEHAVE_FACTORY, "IMFadeIn@ f(uint64 time, IMTweenType _tweener)", asFUNCTION(IMFadeIn::ASFactory), asCALL_CDECL);
+
+ ctx->RegisterObjectMethod("IMUpdateBehavior", "IMFadeIn@ opCast()", asFUNCTION((refCast<IMUpdateBehavior,IMFadeIn>)), asCALL_CDECL_OBJLAST);
+ ctx->RegisterObjectMethod("IMFadeIn", "IMUpdateBehavior@ opImplCast()", asFUNCTION((refCast<IMFadeIn,IMUpdateBehavior>)), asCALL_CDECL_OBJLAST);
+
+ ctx->RegisterObjectBehaviour("IMFadeIn", asBEHAVE_ADDREF, "void f()", asMETHOD(IMFadeIn,AddRef), asCALL_THISCALL);
+ ctx->RegisterObjectBehaviour("IMFadeIn", asBEHAVE_RELEASE, "void f()", asMETHOD(IMFadeIn,Release), asCALL_THISCALL);
+
+
+ ctx->RegisterObjectType("IMMoveIn", 0, asOBJ_REF );
+ ctx->RegisterObjectBehaviour("IMMoveIn", asBEHAVE_FACTORY, "IMMoveIn@ f(uint64 time, vec2 offset, IMTweenType _tweener)", asFUNCTION(IMMoveIn::ASFactory), asCALL_CDECL);
+
+ ctx->RegisterObjectMethod("IMUpdateBehavior", "IMMoveIn@ opCast()", asFUNCTION((refCast<IMUpdateBehavior,IMMoveIn>)), asCALL_CDECL_OBJLAST);
+ ctx->RegisterObjectMethod("IMMoveIn", "IMUpdateBehavior@ opImplCast()", asFUNCTION((refCast<IMMoveIn,IMUpdateBehavior>)), asCALL_CDECL_OBJLAST);
+
+ ctx->RegisterObjectBehaviour("IMMoveIn", asBEHAVE_ADDREF, "void f()", asMETHOD(IMMoveIn,AddRef), asCALL_THISCALL);
+ ctx->RegisterObjectBehaviour("IMMoveIn", asBEHAVE_RELEASE, "void f()", asMETHOD(IMMoveIn,Release), asCALL_THISCALL);
+
+
+
+ ctx->RegisterObjectType("IMChangeTextFadeOutIn", 0, asOBJ_REF );
+ ctx->RegisterObjectBehaviour("IMChangeTextFadeOutIn", asBEHAVE_FACTORY, "IMChangeTextFadeOutIn@ f(uint64 time, const string &in _targetText, IMTweenType _tweenerOut, IMTweenType _tweenerIn)", asFUNCTION(IMChangeTextFadeOutIn::ASFactory), asCALL_CDECL);
+
+ ctx->RegisterObjectMethod("IMUpdateBehavior", "IMChangeTextFadeOutIn@ opCast()", asFUNCTION((refCast<IMUpdateBehavior,IMChangeTextFadeOutIn>)), asCALL_CDECL_OBJLAST);
+ ctx->RegisterObjectMethod("IMChangeTextFadeOutIn", "IMUpdateBehavior@ opImplCast()", asFUNCTION((refCast<IMChangeTextFadeOutIn,IMUpdateBehavior>)), asCALL_CDECL_OBJLAST);
+
+ ctx->RegisterObjectBehaviour("IMChangeTextFadeOutIn", asBEHAVE_ADDREF, "void f()", asMETHOD(IMChangeTextFadeOutIn,AddRef), asCALL_THISCALL);
+ ctx->RegisterObjectBehaviour("IMChangeTextFadeOutIn", asBEHAVE_RELEASE, "void f()", asMETHOD(IMChangeTextFadeOutIn,Release), asCALL_THISCALL);
+
+
+
+ ctx->RegisterObjectType("IMChangeImageFadeOutIn", 0, asOBJ_REF );
+ ctx->RegisterObjectBehaviour("IMChangeImageFadeOutIn", asBEHAVE_FACTORY, "IMChangeImageFadeOutIn@ f(uint64 time, const string &in _targetImage, IMTweenType _tweenerOut, IMTweenType _tweenerIn)", asFUNCTION(IMChangeImageFadeOutIn::ASFactory), asCALL_CDECL);
+
+ ctx->RegisterObjectMethod("IMUpdateBehavior", "IMChangeImageFadeOutIn@ opCast()", asFUNCTION((refCast<IMUpdateBehavior,IMChangeImageFadeOutIn>)), asCALL_CDECL_OBJLAST);
+ ctx->RegisterObjectMethod("IMChangeImageFadeOutIn", "IMUpdateBehavior@ opImplCast()", asFUNCTION((refCast<IMChangeImageFadeOutIn,IMUpdateBehavior>)), asCALL_CDECL_OBJLAST);
+
+ ctx->RegisterObjectBehaviour("IMChangeImageFadeOutIn", asBEHAVE_ADDREF, "void f()", asMETHOD(IMChangeImageFadeOutIn,AddRef), asCALL_THISCALL);
+ ctx->RegisterObjectBehaviour("IMChangeImageFadeOutIn", asBEHAVE_RELEASE, "void f()", asMETHOD(IMChangeImageFadeOutIn,Release), asCALL_THISCALL);
+
+
+
+ ctx->RegisterObjectType("IMPulseAlpha", 0, asOBJ_REF );
+ ctx->RegisterObjectBehaviour("IMPulseAlpha", asBEHAVE_FACTORY, "IMPulseAlpha@ f(float lower, float upper, float _speed)", asFUNCTION(IMPulseAlpha::ASFactory), asCALL_CDECL);
+
+ ctx->RegisterObjectMethod("IMUpdateBehavior", "IMPulseAlpha@ opCast()", asFUNCTION((refCast<IMUpdateBehavior,IMPulseAlpha>)), asCALL_CDECL_OBJLAST);
+ ctx->RegisterObjectMethod("IMPulseAlpha", "IMUpdateBehavior@ opImplCast()", asFUNCTION((refCast<IMPulseAlpha,IMUpdateBehavior>)), asCALL_CDECL_OBJLAST);
+
+ ctx->RegisterObjectBehaviour("IMPulseAlpha", asBEHAVE_ADDREF, "void f()", asMETHOD(IMPulseAlpha,AddRef), asCALL_THISCALL);
+ ctx->RegisterObjectBehaviour("IMPulseAlpha", asBEHAVE_RELEASE, "void f()", asMETHOD(IMPulseAlpha,Release), asCALL_THISCALL);
+
+
+ ctx->RegisterObjectType("IMPulseBorderAlpha", 0, asOBJ_REF );
+ ctx->RegisterObjectBehaviour("IMPulseBorderAlpha", asBEHAVE_FACTORY, "IMPulseBorderAlpha@ f(float lower, float upper, float _speed)", asFUNCTION(IMPulseBorderAlpha::ASFactory), asCALL_CDECL);
+
+ ctx->RegisterObjectMethod("IMUpdateBehavior", "IMPulseBorderAlpha@ opCast()", asFUNCTION((refCast<IMUpdateBehavior,IMPulseBorderAlpha>)), asCALL_CDECL_OBJLAST);
+ ctx->RegisterObjectMethod("IMPulseBorderAlpha", "IMUpdateBehavior@ opImplCast()", asFUNCTION((refCast<IMPulseBorderAlpha,IMUpdateBehavior>)), asCALL_CDECL_OBJLAST);
+
+ ctx->RegisterObjectBehaviour("IMPulseBorderAlpha", asBEHAVE_ADDREF, "void f()", asMETHOD(IMPulseAlpha,AddRef), asCALL_THISCALL);
+ ctx->RegisterObjectBehaviour("IMPulseBorderAlpha", asBEHAVE_RELEASE, "void f()", asMETHOD(IMPulseAlpha,Release), asCALL_THISCALL);
+
+
+
+ //// IMMouseOverBehavior
+ ctx->RegisterObjectType("IMMouseOverScale", 0, asOBJ_REF );
+ ctx->RegisterObjectBehaviour("IMMouseOverScale", asBEHAVE_FACTORY, "IMMouseOverScale@ f(uint64 time, float offset, IMTweenType _tweener)", asFUNCTION(IMMouseOverScale::ASFactory), asCALL_CDECL);
+
+ ctx->RegisterObjectMethod("IMMouseOverBehavior", "IMMouseOverScale@ opCast()", asFUNCTION((refCast<IMMouseOverBehavior,IMMouseOverScale>)), asCALL_CDECL_OBJLAST);
+ ctx->RegisterObjectMethod("IMMouseOverScale", "IMMouseOverBehavior@ opImplCast()", asFUNCTION((refCast<IMMouseOverScale,IMMouseOverBehavior>)), asCALL_CDECL_OBJLAST);
+
+ ctx->RegisterObjectBehaviour("IMMouseOverScale", asBEHAVE_ADDREF, "void f()", asMETHOD(IMMouseOverScale,AddRef), asCALL_THISCALL);
+ ctx->RegisterObjectBehaviour("IMMouseOverScale", asBEHAVE_RELEASE, "void f()", asMETHOD(IMMouseOverScale,Release), asCALL_THISCALL);
+
+
+
+ ctx->RegisterObjectType("IMMouseOverMove", 0, asOBJ_REF );
+ ctx->RegisterObjectBehaviour("IMMouseOverMove", asBEHAVE_FACTORY, "IMMouseOverMove@ f(uint64 time, vec2 offset, IMTweenType _tweener)", asFUNCTION(IMMouseOverMove::ASFactory), asCALL_CDECL);
+
+ ctx->RegisterObjectMethod("IMMouseOverBehavior", "IMMouseOverMove@ opCast()", asFUNCTION((refCast<IMMouseOverBehavior,IMMouseOverMove>)), asCALL_CDECL_OBJLAST);
+ ctx->RegisterObjectMethod("IMMouseOverMove", "IMMouseOverBehavior@ opImplCast()", asFUNCTION((refCast<IMMouseOverMove,IMMouseOverBehavior>)), asCALL_CDECL_OBJLAST);
+
+ ctx->RegisterObjectBehaviour("IMMouseOverMove", asBEHAVE_ADDREF, "void f()", asMETHOD(IMMouseOverMove,AddRef), asCALL_THISCALL);
+ ctx->RegisterObjectBehaviour("IMMouseOverMove", asBEHAVE_RELEASE, "void f()", asMETHOD(IMMouseOverMove,Release), asCALL_THISCALL);
+
+
+
+ ctx->RegisterObjectType("IMMouseOverShowBorder", 0, asOBJ_REF );
+ ctx->RegisterObjectBehaviour("IMMouseOverShowBorder", asBEHAVE_FACTORY, "IMMouseOverShowBorder@ f()", asFUNCTION(IMMouseOverShowBorder::ASFactory), asCALL_CDECL);
+
+ ctx->RegisterObjectMethod("IMMouseOverBehavior", "IMMouseOverShowBorder@ opCast()", asFUNCTION((refCast<IMMouseOverBehavior,IMMouseOverShowBorder>)), asCALL_CDECL_OBJLAST);
+ ctx->RegisterObjectMethod("IMMouseOverShowBorder", "IMMouseOverBehavior@ opImplCast()", asFUNCTION((refCast<IMMouseOverShowBorder,IMMouseOverBehavior>)), asCALL_CDECL_OBJLAST);
+
+ ctx->RegisterObjectBehaviour("IMMouseOverShowBorder", asBEHAVE_ADDREF, "void f()", asMETHOD(IMMouseOverShowBorder,AddRef), asCALL_THISCALL);
+ ctx->RegisterObjectBehaviour("IMMouseOverShowBorder", asBEHAVE_RELEASE, "void f()", asMETHOD(IMMouseOverShowBorder,Release), asCALL_THISCALL);
+
+
+
+ ctx->RegisterObjectType("IMMouseOverPulseColor", 0, asOBJ_REF );
+ ctx->RegisterObjectBehaviour("IMMouseOverPulseColor", asBEHAVE_FACTORY, "IMMouseOverPulseColor@ f(vec4 first, vec4 second, float _speed)", asFUNCTION(IMMouseOverPulseColor::ASFactory), asCALL_CDECL);
+
+ ctx->RegisterObjectMethod("IMMouseOverBehavior", "IMMouseOverPulseColor@ opCast()", asFUNCTION((refCast<IMMouseOverBehavior,IMMouseOverPulseColor>)), asCALL_CDECL_OBJLAST);
+ ctx->RegisterObjectMethod("IMMouseOverPulseColor", "IMMouseOverBehavior@ opImplCast()", asFUNCTION((refCast<IMMouseOverPulseColor,IMMouseOverBehavior>)), asCALL_CDECL_OBJLAST);
+
+ ctx->RegisterObjectBehaviour("IMMouseOverPulseColor", asBEHAVE_ADDREF, "void f()", asMETHOD(IMMouseOverPulseColor,AddRef), asCALL_THISCALL);
+ ctx->RegisterObjectBehaviour("IMMouseOverPulseColor", asBEHAVE_RELEASE, "void f()", asMETHOD(IMMouseOverPulseColor,Release), asCALL_THISCALL);
+
+
+ ctx->RegisterObjectType("IMMouseOverPulseBorder", 0, asOBJ_REF );
+ ctx->RegisterObjectBehaviour("IMMouseOverPulseBorder", asBEHAVE_FACTORY, "IMMouseOverPulseBorder@ f(vec4 first, vec4 second, float _speed)", asFUNCTION(IMMouseOverPulseBorder::ASFactory), asCALL_CDECL);
+
+ ctx->RegisterObjectMethod("IMMouseOverBehavior", "IMMouseOverPulseBorder@ opCast()", asFUNCTION((refCast<IMMouseOverBehavior,IMMouseOverPulseBorder>)), asCALL_CDECL_OBJLAST);
+ ctx->RegisterObjectMethod("IMMouseOverPulseBorder", "IMMouseOverBehavior@ opImplCast()", asFUNCTION((refCast<IMMouseOverPulseBorder,IMMouseOverBehavior>)), asCALL_CDECL_OBJLAST);
+
+ ctx->RegisterObjectBehaviour("IMMouseOverPulseBorder", asBEHAVE_ADDREF, "void f()", asMETHOD(IMMouseOverPulseBorder,AddRef), asCALL_THISCALL);
+ ctx->RegisterObjectBehaviour("IMMouseOverPulseBorder", asBEHAVE_RELEASE, "void f()", asMETHOD(IMMouseOverPulseBorder,Release), asCALL_THISCALL);
+
+
+
+ ctx->RegisterObjectType("IMMouseOverPulseBorderAlpha", 0, asOBJ_REF );
+ ctx->RegisterObjectBehaviour("IMMouseOverPulseBorderAlpha", asBEHAVE_FACTORY, "IMMouseOverPulseBorderAlpha@ f(vec4 first, vec4 second, float _speed)", asFUNCTION(IMMouseOverPulseBorderAlpha::ASFactory), asCALL_CDECL);
+
+ ctx->RegisterObjectMethod("IMMouseOverBehavior", "IMMouseOverPulseBorderAlpha@ opCast()", asFUNCTION((refCast<IMMouseOverBehavior,IMMouseOverPulseBorderAlpha>)), asCALL_CDECL_OBJLAST);
+ ctx->RegisterObjectMethod("IMMouseOverPulseBorderAlpha", "IMMouseOverBehavior@ opImplCast()", asFUNCTION((refCast<IMMouseOverPulseBorderAlpha,IMMouseOverBehavior>)), asCALL_CDECL_OBJLAST);
+
+ ctx->RegisterObjectBehaviour("IMMouseOverPulseBorderAlpha", asBEHAVE_ADDREF, "void f()", asMETHOD(IMMouseOverPulseBorderAlpha,AddRef), asCALL_THISCALL);
+ ctx->RegisterObjectBehaviour("IMMouseOverPulseBorderAlpha", asBEHAVE_RELEASE, "void f()", asMETHOD(IMMouseOverPulseBorderAlpha,Release), asCALL_THISCALL);
+
+
+
+ ctx->RegisterObjectType("IMFixedMessageOnMouseOver", 0, asOBJ_REF );
+ ctx->RegisterObjectBehaviour("IMFixedMessageOnMouseOver", asBEHAVE_FACTORY, "IMFixedMessageOnMouseOver@ f(IMMessage@ _enterMessage, IMMessage@ _overMessage, IMMessage@ _leaveMessage)", asFUNCTION(IMFixedMessageOnMouseOver::ASFactory), asCALL_CDECL);
+
+ ctx->RegisterObjectMethod("IMMouseOverBehavior", "IMFixedMessageOnMouseOver@ opCast()", asFUNCTION((refCast<IMMouseOverBehavior,IMMouseOverShowBorder>)), asCALL_CDECL_OBJLAST);
+ ctx->RegisterObjectMethod("IMFixedMessageOnMouseOver", "IMMouseOverBehavior@ opImplCast()", asFUNCTION((refCast<IMFixedMessageOnMouseOver,IMMouseOverBehavior>)), asCALL_CDECL_OBJLAST);
+
+ ctx->RegisterObjectBehaviour("IMFixedMessageOnMouseOver", asBEHAVE_ADDREF, "void f()", asMETHOD(IMFixedMessageOnMouseOver,AddRef), asCALL_THISCALL);
+ ctx->RegisterObjectBehaviour("IMFixedMessageOnMouseOver", asBEHAVE_RELEASE, "void f()", asMETHOD(IMFixedMessageOnMouseOver,Release), asCALL_THISCALL);
+
+
+ ctx->RegisterObjectType("IMMouseOverFadeIn", 0, asOBJ_REF );
+ ctx->RegisterObjectBehaviour("IMMouseOverFadeIn", asBEHAVE_FACTORY, "IMMouseOverFadeIn@ f(uint64 time, IMTweenType _tweener, float targetAlpha = 1.0f)", asFUNCTION(IMMouseOverFadeIn::ASFactory), asCALL_CDECL);
+
+ ctx->RegisterObjectMethod("IMMouseOverBehavior", "IMMouseOverFadeIn@ opCast()", asFUNCTION((refCast<IMMouseOverBehavior,IMMouseOverFadeIn>)), asCALL_CDECL_OBJLAST);
+ ctx->RegisterObjectMethod("IMMouseOverFadeIn", "IMMouseOverBehavior@ opImplCast()", asFUNCTION((refCast<IMMouseOverFadeIn,IMMouseOverBehavior>)), asCALL_CDECL_OBJLAST);
+
+ ctx->RegisterObjectBehaviour("IMMouseOverFadeIn", asBEHAVE_ADDREF, "void f()", asMETHOD(IMMouseOverFadeIn,AddRef), asCALL_THISCALL);
+ ctx->RegisterObjectBehaviour("IMMouseOverFadeIn", asBEHAVE_RELEASE, "void f()", asMETHOD(IMMouseOverFadeIn,Release), asCALL_THISCALL);
+
+
+ ctx->RegisterObjectType("IMMouseOverSound", 0, asOBJ_REF);
+ ctx->RegisterObjectBehaviour("IMMouseOverSound", asBEHAVE_FACTORY, "IMMouseOverSound@ f(const string &in audioFile)", asFUNCTION(IMMouseOverSound::ASFactory), asCALL_CDECL);
+
+ ctx->RegisterObjectMethod("IMMouseOverBehavior", "IMMouseOverSound@ opCast()", asFUNCTION((refCast<IMMouseOverBehavior,IMMouseOverSound>)), asCALL_CDECL_OBJLAST);
+ ctx->RegisterObjectMethod("IMMouseOverSound", "IMMouseOverBehavior@ opImplCast()", asFUNCTION((refCast<IMMouseOverSound,IMMouseOverBehavior>)), asCALL_CDECL_OBJLAST);
+
+ ctx->RegisterObjectBehaviour("IMMouseOverSound", asBEHAVE_ADDREF, "void f()", asMETHOD(IMMouseOverSound,AddRef), asCALL_THISCALL);
+ ctx->RegisterObjectBehaviour("IMMouseOverSound", asBEHAVE_RELEASE, "void f()", asMETHOD(IMMouseOverSound,Release), asCALL_THISCALL);
+
+
+ //// IMMouseClickBehavior
+
+ ctx->RegisterObjectType("IMFixedMessageOnClick", 0, asOBJ_REF );
+ ctx->RegisterObjectBehaviour("IMFixedMessageOnClick", asBEHAVE_FACTORY, "IMFixedMessageOnClick@ f(IMMessage@ _enterMessage, IMMessage@ _overMessage, IMMessage@ _leaveMessage)", asFUNCTION(IMFixedMessageOnClick::ASFactory), asCALL_CDECL);
+
+ ctx->RegisterObjectBehaviour("IMFixedMessageOnClick", asBEHAVE_FACTORY, "IMFixedMessageOnClick@ f(const string &in messageName)", asFUNCTION(IMFixedMessageOnClick::ASFactory), asCALL_CDECL);
+
+ ctx->RegisterObjectBehaviour("IMFixedMessageOnClick", asBEHAVE_FACTORY, "IMFixedMessageOnClick@ f(const string &in messageName, int param)", asFUNCTION(IMFixedMessageOnClick::ASFactory_int), asCALL_CDECL);
+
+ ctx->RegisterObjectBehaviour("IMFixedMessageOnClick", asBEHAVE_FACTORY, "IMFixedMessageOnClick@ f(const string &in messageName, const string &in param)", asFUNCTION(IMFixedMessageOnClick::ASFactory_string), asCALL_CDECL);
+
+ ctx->RegisterObjectBehaviour("IMFixedMessageOnClick", asBEHAVE_FACTORY, "IMFixedMessageOnClick@ f(const string &in messageName, float param)", asFUNCTION(IMFixedMessageOnClick::ASFactory_float), asCALL_CDECL);
+
+ ctx->RegisterObjectBehaviour("IMFixedMessageOnClick", asBEHAVE_FACTORY, "IMFixedMessageOnClick@ f(IMMessage@ message)", asFUNCTION(IMFixedMessageOnClick::ASFactoryMessage), asCALL_CDECL);
+
+ ctx->RegisterObjectMethod("IMMouseClickBehavior", "IMFixedMessageOnClick@ opCast()", asFUNCTION((refCast<IMMouseClickBehavior,IMFixedMessageOnClick>)), asCALL_CDECL_OBJLAST);
+ ctx->RegisterObjectMethod("IMFixedMessageOnClick", "IMMouseClickBehavior@ opImplCast()", asFUNCTION((refCast<IMFixedMessageOnClick,IMMouseClickBehavior>)), asCALL_CDECL_OBJLAST);
+
+ ctx->RegisterObjectBehaviour("IMFixedMessageOnClick", asBEHAVE_ADDREF, "void f()", asMETHOD(IMFixedMessageOnClick,AddRef), asCALL_THISCALL);
+ ctx->RegisterObjectBehaviour("IMFixedMessageOnClick", asBEHAVE_RELEASE, "void f()", asMETHOD(IMFixedMessageOnClick,Release), asCALL_THISCALL);
+
+
+ ctx->RegisterObjectType("IMSoundOnClick", 0, asOBJ_REF);
+ ctx->RegisterObjectBehaviour("IMSoundOnClick", asBEHAVE_FACTORY, "IMSoundOnClick@ f(const string &in audioFile)", asFUNCTION(IMSoundOnClick::ASFactory), asCALL_CDECL);
+
+ ctx->RegisterObjectMethod("IMMouseClickBehavior", "IMSoundOnClick@ opCast()", asFUNCTION((refCast<IMMouseClickBehavior,IMSoundOnClick>)), asCALL_CDECL_OBJLAST);
+ ctx->RegisterObjectMethod("IMSoundOnClick", "IMMouseClickBehavior@ opImplCast()", asFUNCTION((refCast<IMSoundOnClick,IMMouseClickBehavior>)), asCALL_CDECL_OBJLAST);
+
+ ctx->RegisterObjectBehaviour("IMSoundOnClick", asBEHAVE_ADDREF, "void f()", asMETHOD(IMSoundOnClick,AddRef), asCALL_THISCALL);
+ ctx->RegisterObjectBehaviour("IMSoundOnClick", asBEHAVE_RELEASE, "void f()", asMETHOD(IMSoundOnClick,Release), asCALL_THISCALL);
+
+ ctx->RegisterFuncdef("void OnClickCallback()");
+
+ ctx->RegisterObjectType("IMFuncOnClick", 0, asOBJ_REF);
+ ctx->RegisterObjectBehaviour("IMFuncOnClick", asBEHAVE_FACTORY, "IMFuncOnClick@ f(OnClickCallback@ func)", asFUNCTION(IMFuncOnClick::ASFactory), asCALL_CDECL);
+
+ ctx->RegisterObjectMethod("IMMouseClickBehavior", "IMFuncOnClick@ opCast()", asFUNCTION((refCast<IMMouseClickBehavior,IMFuncOnClick>)), asCALL_CDECL_OBJLAST);
+ ctx->RegisterObjectMethod("IMFuncOnClick", "IMMouseClickBehavior@ opImplCast()", asFUNCTION((refCast<IMFuncOnClick,IMMouseClickBehavior>)), asCALL_CDECL_OBJLAST);
+
+ ctx->RegisterObjectBehaviour("IMFuncOnClick", asBEHAVE_ADDREF, "void f()", asMETHOD(IMFuncOnClick,AddRef), asCALL_THISCALL);
+ ctx->RegisterObjectBehaviour("IMFuncOnClick", asBEHAVE_RELEASE, "void f()", asMETHOD(IMFuncOnClick,Release), asCALL_THISCALL);
+
+
+ /*******
+ *
+ * GUI elements
+ *
+ */
+
+ registerASIMElement<IMElement>( ctx, "IMElement" );
+
+
+ registerASIMElement<IMSpacer>( ctx, "IMSpacer" );
+
+ ctx->RegisterObjectBehaviour("IMSpacer", asBEHAVE_FACTORY, "IMSpacer@ f( DividerOrientation _orientation, float size)", asFUNCTION(IMSpacer::ASFactory_static), asCALL_CDECL);
+
+ ctx->RegisterObjectBehaviour("IMSpacer", asBEHAVE_FACTORY, "IMSpacer@ f(DividerOrientation _orientation )", asFUNCTION(IMSpacer::ASFactory_dynamic), asCALL_CDECL);
+
+ registerASIMElement<IMContainer>( ctx, "IMContainer" );
+
+ ctx->RegisterObjectBehaviour("IMContainer", asBEHAVE_FACTORY, "IMContainer@ f(const string &in name, SizePolicy sizeX = SizePolicy(), SizePolicy sizeY = SizePolicy())", asFUNCTION(IMContainer::ASFactory_named), asCALL_CDECL);
+
+ ctx->RegisterObjectBehaviour("IMContainer", asBEHAVE_FACTORY, "IMContainer@ f( SizePolicy sizeX = SizePolicy(), SizePolicy sizeY = SizePolicy())", asFUNCTION(IMContainer::ASFactory_unnamed), asCALL_CDECL);
+
+ ctx->RegisterObjectMethod("IMContainer", "void setSizePolicy( SizePolicy sizeX, SizePolicy sizeY )", asMETHOD(IMContainer, setSizePolicy), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMContainer", "void clear()", asMETHOD(IMContainer, clear), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMContainer", "void addFloatingElement( IMElement@ element, const string &in name, vec2 position, int z = -1 )", asMETHOD(IMContainer, addFloatingElement), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMContainer", "void setElement( IMElement@ element )", asMETHOD(IMContainer, setElement), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMContainer", "void setAlignment( ContainerAlignment xAlignment, ContainerAlignment yAlignment )", asMETHOD(IMContainer, setAlignment), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMContainer", "IMElement@ removeElement( const string &in name )", asMETHOD(IMContainer, removeElement), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMContainer", "void moveElement( const string &in name, vec2 newPos )", asMETHOD(IMContainer, moveElement), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMContainer", "void moveElementRelative( const string &in name, vec2 posChange )", asMETHOD(IMContainer, moveElementRelative), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMContainer", "vec2 getElementPosition( const string &in name )", asMETHOD(IMContainer, getElementPosition), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMContainer", "void setBackgroundImage( const string &in fileName, vec4 color )", asMETHOD(IMContainer, setBackgroundImage), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMContainer", "IMElement@ getContents( )", asMETHOD(IMContainer, getContents), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMContainer", "array<IMElement@>@ getFloatingContents( )", asFUNCTION(AS_GetFloatingContents), asCALL_CDECL_OBJFIRST);
+
+ registerASIMElement<IMDivider>( ctx, "IMDivider" );
+
+ ctx->RegisterObjectBehaviour("IMDivider", asBEHAVE_FACTORY, "IMDivider@ f(const string &in name, DividerOrientation _orientation = DOVertical)", asFUNCTION(IMDivider::ASFactory_named), asCALL_CDECL);
+
+ ctx->RegisterObjectBehaviour("IMDivider", asBEHAVE_FACTORY, "IMDivider@ f( DividerOrientation _orientation = DOVertical)", asFUNCTION(IMDivider::ASFactory_unnamed), asCALL_CDECL);
+
+ ctx->RegisterObjectMethod("IMDivider", "void setAlignment( ContainerAlignment xAlignment, ContainerAlignment yAlignment, bool reposition = true )", asMETHOD(IMDivider, setAlignment), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMDivider", "void clear()", asMETHOD(IMDivider, clear), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMDivider", "IMSpacer@ appendSpacer( float _size )", asMETHOD(IMDivider, appendSpacer), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMDivider", "IMSpacer@ appendDynamicSpacer()", asMETHOD(IMDivider, appendDynamicSpacer), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMDivider", "uint getContainerCount()", asMETHOD(IMDivider, getContainerCount), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMDivider", "IMContainer@ getContainerAt( uint i )", asMETHOD(IMDivider, getContainerAt), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMDivider", "IMContainer@ getContainerOf( const string &in _name )", asMETHOD(IMDivider, getContainerOf), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMDivider", "array<IMContainer@>@ getContainers()", asFUNCTION(AS_GetContainers), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("IMDivider", "IMContainer@ append( IMElement@ newElement, float containerSize = UNDEFINEDSIZE )", asMETHOD(IMDivider, append), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMDivider", "DividerOrientation getOrientation()", asMETHOD(IMDivider, getOrientation), asCALL_THISCALL);
+
+ registerASIMElement<IMDivider>( ctx, "IMTextSelectionList" );
+
+ ctx->RegisterObjectBehaviour("IMTextSelectionList", asBEHAVE_FACTORY, "IMTextSelectionList@ f(const string &in _name, FontSetup _fontSetup, float _betweenSpace, IMMouseOverBehavior@ _mouseOver = null )", asFUNCTION(IMTextSelectionList::ASFactory), asCALL_CDECL);
+
+ ctx->RegisterObjectMethod("IMTextSelectionList", "void setAlignment( ContainerAlignment xAlignment, ContainerAlignment yAlignment )", asMETHOD(IMTextSelectionList, setAlignment), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMTextSelectionList", "void setItemUpdateBehaviour( IMUpdateBehavior@ behavior )", asMETHOD(IMTextSelectionList, setItemUpdateBehaviour), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMTextSelectionList", "void addEntry( const string &in name, const string &in text, const string &in message )", asMETHOD(IMTextSelectionList, addEntry), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMTextSelectionList", "void addEntry( const string &in name, const string &in text, const string &in message, const string &in param )", asMETHOD(IMTextSelectionList, addEntryParam), asCALL_THISCALL);
+
+ registerASIMElement<IMImage>( ctx, "IMImage" );
+
+ ctx->RegisterObjectBehaviour("IMImage", asBEHAVE_FACTORY, "IMImage@ f(const string &in name, DividerOrientation _orientation = DOVertical)", asFUNCTION(IMImage::ASFactory), asCALL_CDECL);
+
+ ctx->RegisterObjectMethod("IMImage", "void setSkipAspectFitting(bool val)", asMETHOD(IMImage, setSkipAspectFitting), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMImage", "void setCenter(bool val)", asMETHOD(IMImage, setCenter), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMImage", "void setImageFile( const string &in _fileName )", asMETHOD(IMImage, setImageFile), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMImage", "void scaleToSizeX( float newSize )", asMETHOD(IMImage, scaleToSizeX), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMImage", "void scaleToSizeY( float newSize )", asMETHOD(IMImage, scaleToSizeY), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMImage", "void setRotation( float _rotation )", asMETHOD(IMImage, setRotation), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMImage", "float getRotation()", asMETHOD(IMImage, getRotation), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMImage", "void setImageOffset( vec2 offset, vec2 size )", asMETHOD(IMImage, setImageOffset), asCALL_THISCALL);
+
+ registerASIMElement<IMText>( ctx, "IMText" );
+
+ ctx->RegisterObjectBehaviour("IMText", asBEHAVE_FACTORY, "IMText@ f(const string &in name)", asFUNCTION(IMText::ASFactory_named), asCALL_CDECL);
+
+ ctx->RegisterObjectBehaviour("IMText", asBEHAVE_FACTORY, "IMText@ f(const string &in _text, const string &in _fontName, int _fontSize, vec4 _color = vec4(1.0f))", asFUNCTION(IMText::ASFactory_unnamed), asCALL_CDECL);
+
+ ctx->RegisterObjectBehaviour("IMText", asBEHAVE_FACTORY, "IMText@ f(const string &in _text, FontSetup _fontSetup)", asFUNCTION(IMText::ASFactory_fontsetup), asCALL_CDECL);
+
+
+ ctx->RegisterObjectMethod("IMText", "void setFont( FontSetup _fontSetup )", asMETHOD(IMText, setFont), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMText", "void setFontByName( const string &in _fontName, int _fontSize )", asMETHOD(IMText, setFontByName), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMText", "void setText( const string &in _text )", asMETHOD(IMText, setText), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMText", "string getText()", asMETHOD(IMText, getText), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMText", "void setShadowed( bool shouldShadow = true )", asMETHOD(IMText, setShadowed), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMText", "void setRotation( float _rotation )", asMETHOD(IMText, setRotation), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMText", "float getRotation()", asMETHOD(IMText, getRotation), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMText", "void updateEngineTextObject()", asMETHOD(IMText, updateEngineTextObject), asCALL_THISCALL);
+
+ /*******
+ *
+ * Screen metrics
+ *
+ */
+ ctx->RegisterObjectType("ScreenMetrics", 0, asOBJ_REF | asOBJ_NOHANDLE);
+
+ ctx->RegisterObjectProperty("ScreenMetrics",
+ "vec2 GUISpace",
+ asOFFSET(ScreenMetrics, GUISpace));
+
+ ctx->RegisterObjectProperty("ScreenMetrics",
+ "vec2 screenSize",
+ asOFFSET(ScreenMetrics, screenSize));
+
+ ctx->RegisterObjectProperty("ScreenMetrics",
+ "float GUItoScreenXScale",
+ asOFFSET(ScreenMetrics, GUItoScreenXScale));
+
+ ctx->RegisterObjectProperty("ScreenMetrics",
+ "float GUItoScreenYScale",
+ asOFFSET(ScreenMetrics, GUItoScreenYScale));
+
+ ctx->RegisterObjectMethod("ScreenMetrics","vec2 getMetrics()",asMETHOD(ScreenMetrics, getMetrics), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("ScreenMetrics","bool checkMetrics( vec2 &in metrics )",asMETHOD(ScreenMetrics, checkMetrics), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("ScreenMetrics","void computeFactors()",asMETHOD(ScreenMetrics, computeFactors), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("ScreenMetrics","vec2 GUIToScreen( const vec2 pos )",asMETHOD(ScreenMetrics, GUIToScreen), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("ScreenMetrics","float getScreenWidth()",asMETHOD(ScreenMetrics, getScreenWidth), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("ScreenMetrics","float getScreenHeight()",asMETHOD(ScreenMetrics, getScreenHeight), asCALL_THISCALL);
+
+ ctx->RegisterGlobalProperty("ScreenMetrics screenMetrics", &screenMetrics);
+
+
+ /*******************************************************************************************/
+ /**
+ * @brief Main GUI class
+ *
+ */
+
+ ctx->RegisterObjectType("IMGUI", 0, asOBJ_REF );
+
+ /* We don't want implicitly created IMGUI instances, because it will result in unexpected behaviour for users.
+ ctx->RegisterObjectBehaviour("IMGUI",
+ asBEHAVE_FACTORY,
+ "IMGUI@ f()",
+ asFUNCTION(IMGUI::ASFactory),
+ asCALL_CDECL);
+ */
+
+
+ ctx->RegisterObjectBehaviour("IMGUI", asBEHAVE_ADDREF, "void f()", asMETHOD(IMGUI,AddRef), asCALL_THISCALL);
+ ctx->RegisterObjectBehaviour("IMGUI", asBEHAVE_RELEASE, "void f()", asMETHOD(IMGUI,Release), asCALL_THISCALL);
+
+ ctx->RegisterObjectProperty("IMGUI",
+ "GUIState guistate",
+ asOFFSET(IMGUI, guistate));
+
+ ctx->RegisterObjectMethod("IMGUI", "void setBackgroundLayers( uint numLayers )", asMETHOD(IMGUI, setBackgroundLayers), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMGUI", "void setForegroundLayers( uint numLayers )", asMETHOD(IMGUI, setForegroundLayers), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMGUI", "void setHeaderHeight( float _headerSize )", asMETHOD(IMGUI, setHeaderHeight), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMGUI", "void setFooterHeight( float _footerSize )", asMETHOD(IMGUI, setFooterHeight), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMGUI", "void setHeaderPanels( float first = 0, float second = 0, float third = 0 )", asMETHOD(IMGUI, setHeaderPanels), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMGUI", "void setMainPanels( float first = 0, float second = 0, float third = 0 )", asMETHOD(IMGUI, setMainPanels), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMGUI", "void setFooterPanels( float first = 0, float second = 0, float third = 0 )", asMETHOD(IMGUI, setFooterPanels), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMGUI", "void setup()", asMETHOD(IMGUI, setup), asCALL_THISCALL);
+// ctx->RegisterObjectMethod("IMGUI", "void derivePanelOffsets()", asMETHOD(IMGUI, derivePanelOffsets), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMGUI", "void clear()", asMETHOD(IMGUI, clear), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMGUI", "void setGuides( bool setting )", asMETHOD(IMGUI, setGuides), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMGUI", "void reportError( const string &in newError )", asMETHOD(IMGUI, reportError), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMGUI", "void receiveMessage( IMMessage@ message )", asMETHOD(IMGUI, receiveMessage), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMGUI", "uint getMessageQueueSize()", asMETHOD(IMGUI, getMessageQueueSize), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMGUI", "IMMessage@ getNextMessage()", asMETHOD(IMGUI, getNextMessage), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMGUI", "IMContainer@ getMain( uint panel = 0 )", asMETHOD(IMGUI, getMain), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMGUI", "IMContainer@ getFooter( uint panel = 0 )", asMETHOD(IMGUI, getFooter), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMGUI", "IMContainer@ getHeader( uint panel = 0 )", asMETHOD(IMGUI, getHeader), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMGUI", "IMContainer@ getBackgroundLayer( uint layerNum = 0 )", asMETHOD(IMGUI, getBackgroundLayer), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMGUI", "IMContainer@ getForegroundLayer( uint layerNum = 0 )", asMETHOD(IMGUI, getForegroundLayer), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("IMGUI", "void onRelayout()", asMETHOD(IMGUI, onRelayout), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMGUI", "void update()", asMETHOD(IMGUI, update), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMGUI", "void doScreenResize()", asMETHOD(IMGUI, doScreenResize), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMGUI", "void render()", asMETHOD(IMGUI, render), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMGUI", "IMElement@ findElement( const string &in elementName )", asMETHOD(IMGUI, findElement), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMGUI", "string getUniqueName( const string &in type = \"Unkowntype\")", asMETHOD(IMGUI, getUniqueName), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("IMGUI", "void drawBox( vec2 boxPos, vec2 boxSize, vec4 boxColor, int zOrder, bool shouldClip = false, vec2 currentClipPos = vec2(UNDEFINEDSIZE, UNDEFINEDSIZE), vec2 currentClipSize = vec2(UNDEFINEDSIZE, UNDEFINEDSIZE) )", asMETHOD(IMGUI, drawBox), asCALL_THISCALL);
+
+
+ ctx->RegisterGlobalFunction("IMGUI@ CreateIMGUI()", asFUNCTION(AS_CreateIMGUI), asCALL_CDECL );
+
+ ctx->DocsCloseBrace();
+}
diff --git a/Source/GUI/IMUI/imui_script.h b/Source/GUI/IMUI/imui_script.h
new file mode 100644
index 00000000..5d5236e0
--- /dev/null
+++ b/Source/GUI/IMUI/imui_script.h
@@ -0,0 +1,29 @@
+//-----------------------------------------------------------------------------
+// Name: imui_script.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+class ASContext;
+class IMUIContext;
+
+void AttachIMUI( ASContext *ctx );
diff --git a/Source/GUI/IMUI/imui_state.h b/Source/GUI/IMUI/imui_state.h
new file mode 100644
index 00000000..a366c100
--- /dev/null
+++ b/Source/GUI/IMUI/imui_state.h
@@ -0,0 +1,49 @@
+//-----------------------------------------------------------------------------
+// Name: imui_state.h
+// Developer: Wolfire Games LLC
+// Description: Internal helper class for creating adhoc GUIs as part of the UI tools
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <GUI/IMUI/im_support.h>
+
+/*******************************************************************************************/
+/**
+ * @brief The current state of the GUI passed during update
+ *
+ */
+struct GUIState
+{
+ vec2 mousePosition;
+ IMUIContext::ButtonState leftMouseState;
+ bool inheritedMouseOver;
+ bool inheritedMouseDown;
+ IMUIContext::ButtonState inheritedMouseState;
+ bool clickHandled;
+
+ GUIState() :
+ inheritedMouseOver(false),
+ inheritedMouseDown(false),
+ clickHandled(false)
+ {}
+
+};
+
diff --git a/Source/GUI/dimgui/chat_window.cpp b/Source/GUI/dimgui/chat_window.cpp
new file mode 100644
index 00000000..aca2da8e
--- /dev/null
+++ b/Source/GUI/dimgui/chat_window.cpp
@@ -0,0 +1,23 @@
+//-----------------------------------------------------------------------------
+// Name: chat_window.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
diff --git a/Source/GUI/dimgui/chat_window.h b/Source/GUI/dimgui/chat_window.h
new file mode 100644
index 00000000..88fea7fd
--- /dev/null
+++ b/Source/GUI/dimgui/chat_window.h
@@ -0,0 +1,23 @@
+//-----------------------------------------------------------------------------
+// Name: chat_window.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
diff --git a/Source/GUI/dimgui/dimgui.cpp b/Source/GUI/dimgui/dimgui.cpp
new file mode 100644
index 00000000..726877e2
--- /dev/null
+++ b/Source/GUI/dimgui/dimgui.cpp
@@ -0,0 +1,5329 @@
+//-----------------------------------------------------------------------------
+// Name: dimgui.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "dimgui.h"
+
+#include <Internal/spawneritem.h>
+#include <Internal/timer.h>
+#include <Internal/config.h>
+#include <Internal/dialogues.h>
+#include <Internal/profiler.h>
+#include <Internal/stopwatch.h>
+
+#include <Objects/object.h>
+#include <Objects/itemobject.h>
+#include <Objects/prefab.h>
+#include <Objects/envobject.h>
+#include <Objects/movementobject.h>
+#include <Objects/cameraobject.h>
+#include <Objects/decalobject.h>
+#include <Objects/dynamiclightobject.h>
+#include <Objects/placeholderobject.h>
+#include <Objects/terrainobject.h>
+#include <Objects/hotspot.h>
+
+#include <Math/enginemath.h>
+#include <Math/vec3.h>
+#include <Math/vec3math.h>
+#include <Math/vec4.h>
+#include <Math/vec4math.h>
+
+#include <Editors/map_editor.h>
+#include <Editors/sky_editor.h>
+
+#include <Utility/imgui_macros.h>
+#include <Utility/stacktrace.h>
+
+#include <Graphics/sky.h>
+#include <Graphics/pxdebugdraw.h>
+#include <Graphics/model.h>
+#include <Graphics/models.h>
+#include <Graphics/simplify.hpp>
+#include <Graphics/halfedge.h>
+
+#include <GUI/gui.h>
+#include <GUI/dimgui/dimgui_graph.h>
+#include <GUI/dimgui/imgui_impl_sdl_gl3.h>
+#include <GUI/dimgui/settings_screen.h>
+#include <GUI/dimgui/modmenu.h>
+
+#include <Logging/ramhandler.h>
+#include <Steam/steamworks.h>
+#include <Version/version.h>
+#include <Scripting/angelscript/asdebugger.h>
+#include <Online/online.h>
+#include <Main/engine.h>
+#include <UserInput/keyTranslator.h>
+#include <Game/level.h>
+#include <Asset/Asset/levelinfo.h>
+#include <UserInput/keycommands.h>
+
+// disable Windows.h defining min and max as macros. Windows.h is pulled in by imgui.cpp
+#define NOMINMAX
+//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+#include <imgui_internal.h>
+#include <imgui.h>
+#include <imgui_color_picker.h>
+#include <implot.h>
+
+#include <SDL.h>
+
+#include <array>
+#include <climits>
+
+#define IMGUI_RED ImVec4(0.9f, 0.1f, 0.1f, 1.0f)
+#define IMGUI_ORANGE ImVec4(0.7f, 0.4f, 0.1f, 1.0f)
+#define IMGUI_GREEN ImVec4(0.1f, 0.9f, 0.1f, 1.0f)
+#define IMGUI_WHITE ImVec4(1.0f, 1.0f, 1.0f, 1.0f)
+
+static inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x * rhs, lhs.y * rhs); }
+static inline ImVec2 operator/(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x / rhs, lhs.y / rhs); }
+static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x + rhs.x, lhs.y + rhs.y); }
+static inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x - rhs.x, lhs.y - rhs.y); }
+static inline ImVec2 operator*(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); }
+static inline ImVec2 operator/(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x / rhs.x, lhs.y / rhs.y); }
+static inline ImVec2& operator*=(ImVec2& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; return lhs; }
+static inline ImVec2& operator/=(ImVec2& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; return lhs; }
+static inline ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; return lhs; }
+static inline ImVec2& operator-=(ImVec2& lhs, const ImVec2& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; return lhs; }
+static inline ImVec2& operator*=(ImVec2& lhs, const ImVec2& rhs) { lhs.x *= rhs.x; lhs.y *= rhs.y; return lhs; }
+static inline ImVec2& operator/=(ImVec2& lhs, const ImVec2& rhs) { lhs.x /= rhs.x; lhs.y /= rhs.y; return lhs; }
+static inline ImVec4 operator+(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w); }
+static inline ImVec4 operator-(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w); }
+static inline ImVec4 operator*(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); }
+
+char imgui_ini_path[kPathSize];
+
+typedef std::vector<SpawnerItem> SpawnerTab;
+typedef std::map<std::string, SpawnerTab> SpawnerTabMap;
+SpawnerTabMap spawner_tabs;
+
+ImGuiTextFilter scenegraph_filter;
+ImGuiTextFilter spawner_global_filter;
+typedef std::map<std::string, ImGuiTextFilter> SpawnerTabFilterMap;
+SpawnerTabFilterMap spawner_tab_filters;
+
+static AssetType asset_detail_list_type;
+static std::vector<std::string> asset_detail_list;
+static std::vector<uint32_t> asset_detail_list_load_flags;
+static std::vector<uint16_t> asset_detail_list_ref_count;
+static std::vector<bool> asset_detail_list_load_warning;
+static const char* asset_detail_title = "";
+
+bool show_test_window = false;
+bool show_scenegraph = false;
+bool select_scenegraph_search = false;
+bool select_scenegraph_level_params = false;
+bool search_children_scenegraph = false;
+bool show_flat_scenegraph = false;
+bool show_named_only_scenegraph = false;
+bool show_selected = false;
+bool select_object_script_params = false;
+bool show_paintbrush = false;
+bool show_color_picker = false;
+bool show_mod_menu = false;
+bool show_mod_menu_previous = false;
+bool show_performance = false;
+bool show_mp_debug = false;
+bool show_mp_settings = false;
+bool show_log = false;
+bool show_warnings = false;
+bool show_state = false;
+bool show_events = false;
+bool show_save = false;
+bool show_sound = false;
+bool show_mp_info = true;
+bool show_input_debug = false;
+bool show_chat = true;
+bool show_build_watermark = true;
+bool show_pose = false;
+bool show_script = false;
+bool show_select_script = false;
+
+std::string preview_script_name = "";
+
+float imgui_scale = 1.0f;
+
+std::map<std::string, ImGUIGraph<256>> graphs;
+
+//ImGUIGraph<256> host_walltime_offset_shift;
+//ImGUIGraph<128> interpolation_step;
+//ImGUIGraph<128> rigged_interpolation_time;
+//ImGUIGraph<1000> interpolation_speed;
+//ImGUIGraph<1000> playerVelocity;
+//ImGUIGraph<1000> delta_time;
+//ImGUIGraph<256> pendingBonesHistory;
+//ImGUIGraph<256> pendingPosHistory;
+//ImGUIGraph<256> frametimeHistory;
+//ImGUIGraph<256> deltabetweenframes;
+//ImGUIGraph<256> root_bone_x;
+//ImGUIGraph<256> root_bone_y;
+//ImGUIGraph<256> root_bone_z;
+//ImGUIGraph<256> last_frame_delta;
+
+extern std::string script_dir_path;
+
+extern bool asdebugger_enabled;
+extern bool asprofiler_enabled;
+extern bool g_make_invisible_visible;
+extern bool g_level_shadows;
+extern bool g_simple_shadows;
+bool show_asdebugger_contexts = false;
+bool show_asprofiler = false;
+bool break_on_script_change = false;
+
+bool show_load_item_search = false;
+static bool run_parse_spawner_flag;
+
+extern Timer game_timer;
+float GraphTime = 0.f;
+
+extern RamHandler ram_handler;
+
+static int select_start_index = -1;
+
+
+static bool show_graphics_debug_disable_menu = false;
+
+extern bool g_debug_runtime_disable_blood_surface_pre_draw;
+extern bool g_debug_runtime_disable_debug_draw;
+extern bool g_debug_runtime_disable_debug_ribbon_draw;
+extern bool g_debug_runtime_disable_decal_object_draw;
+extern bool g_debug_runtime_disable_decal_object_pre_draw_frame;
+extern bool g_debug_runtime_disable_detail_object_surface_draw;
+extern bool g_debug_runtime_disable_detail_object_surface_pre_draw;
+extern bool g_debug_runtime_disable_dynamic_light_object_draw;
+extern bool g_debug_runtime_disable_env_object_draw;
+extern bool g_debug_runtime_disable_env_object_draw_depth_map;
+extern bool g_debug_runtime_disable_env_object_draw_detail_object_instances;
+extern bool g_debug_runtime_disable_env_object_draw_instances;
+extern bool g_debug_runtime_disable_env_object_draw_instances_transparent;
+extern bool g_debug_runtime_disable_env_object_pre_draw_camera;
+extern bool g_debug_runtime_disable_flares_draw;
+extern bool g_debug_runtime_disable_gpu_particle_field_draw;
+extern bool g_debug_runtime_disable_group_pre_draw_camera;
+extern bool g_debug_runtime_disable_group_pre_draw_frame;
+extern bool g_debug_runtime_disable_hotspot_draw;
+extern bool g_debug_runtime_disable_hotspot_pre_draw_frame;
+extern bool g_debug_runtime_disable_item_object_draw;
+extern bool g_debug_runtime_disable_item_object_draw_depth_map;
+extern bool g_debug_runtime_disable_item_object_pre_draw_frame;
+extern bool g_debug_runtime_disable_morph_target_pre_draw_camera;
+extern bool g_debug_runtime_disable_movement_object_draw;
+extern bool g_debug_runtime_disable_movement_object_draw_depth_map;
+extern bool g_debug_runtime_disable_movement_object_pre_draw_camera;
+extern bool g_debug_runtime_disable_movement_object_pre_draw_frame;
+extern bool g_debug_runtime_disable_navmesh_connection_object_draw;
+extern bool g_debug_runtime_disable_navmesh_hint_object_draw;
+extern bool g_debug_runtime_disable_particle_draw;
+extern bool g_debug_runtime_disable_particle_system_draw;
+extern bool g_debug_runtime_disable_pathpoint_object_draw;
+extern bool g_debug_runtime_disable_placeholder_object_draw;
+extern bool g_debug_runtime_disable_reflection_capture_object_draw;
+extern bool g_debug_runtime_disable_rigged_object_draw;
+extern bool g_debug_runtime_disable_rigged_object_pre_draw_camera;
+extern bool g_debug_runtime_disable_rigged_object_pre_draw_frame;
+extern bool g_debug_runtime_disable_scene_graph_draw;
+extern bool g_debug_runtime_disable_scene_graph_draw_depth_map;
+extern bool g_debug_runtime_disable_scene_graph_prepare_lights_and_decals;
+extern bool g_debug_runtime_disable_sky_draw;
+extern bool g_debug_runtime_disable_terrain_object_draw_depth_map;
+extern bool g_debug_runtime_disable_terrain_object_draw_terrain;
+extern bool g_debug_runtime_disable_terrain_object_pre_draw_camera;
+
+
+#ifdef _WIN32
+#include <shellapi.h>
+static void open_url(const char* url) {
+#if ENABLE_STEAMWORKS
+ if( Steamworks::Instance()->IsConnected() ) {
+ Steamworks::Instance()->OpenWebPage(url);
+ return;
+ }
+#endif
+
+ ShellExecute(GetActiveWindow(),
+ "open", url, NULL, NULL, SW_SHOWNORMAL);
+}
+#elif defined(PLATFORM_MACOSX)
+#include <ApplicationServices/ApplicationServices.h>
+static void open_url (const char* url) {
+#if ENABLE_STEAMWORKS
+ if( Steamworks::Instance()->IsConnected() ) {
+ Steamworks::Instance()->OpenWebPage(url);
+ return;
+ }
+#endif
+ CFURLRef url_ref = CFURLCreateWithBytes(NULL, (UInt8*)url, strlen(url), kCFStringEncodingASCII, NULL);
+ LSOpenCFURLRef(url_ref, NULL);
+ CFRelease(url_ref);
+}
+#else
+static void open_url(const char* url){
+#if ENABLE_STEAMWORKS
+ if( Steamworks::Instance()->IsConnected() ) {
+ Steamworks::Instance()->OpenWebPage(url);
+ return;
+ }
+#endif
+ DisplayError("Error", "Browser open is not yet implemented on this OS");
+}
+#endif
+
+
+static vec4 GetObjColor(Object* obj){
+ vec4 color(1.0f);
+ switch(obj->GetType()){
+ case _env_object:
+ color = vec4(0.8f, 0.8f, 1.0f, 1.0f); break;
+ case _movement_object:
+ color = vec4(1.0f, 0.8f, 0.8f, 1.0f); break;
+ case _hotspot_object:
+ color = vec4(1.0f, 1.0f, 0.8f, 1.0f); break;
+ case _decal_object:
+ color = vec4(0.8f, 1.0f, 0.8f, 1.0f); break;
+ default:
+ color = vec4(0.9f, 0.9f, 0.9f, 1.0f); break;
+ }
+ if(obj->exclude_from_save){
+ color[3] *= 0.5f;
+ }
+ return color;
+}
+
+static int ParseDetails(const char* str, float* results) {
+ //example str: "min:0,max:2,step:0.001,text_mult:100"
+ //parse that into results = {0.0f, 2.0f, 0.001f, 100.0f}
+ //return 0 on success, -1 on failure
+ int last_break = -1;
+ int last_colon = -1;
+ for(int index=0;; ++index){
+ if(str[index] == ':'){
+ last_colon = index;
+ }
+ if(str[index] == ' ' && index == last_break+1){
+ last_break = index;
+ }
+ if(str[index] == ',' || str[index] == '\0'){
+ if(last_colon <= last_break){
+ return -1;
+ }
+ const char* token = &str[last_break+1];
+ int token_len = last_colon - last_break - 1;
+ float val = (float) atof(&str[last_colon+1]);
+ switch(token_len){
+ case 3:
+ if(strncmp(token, "min", 3) == 0){
+ results[0] = val;
+ } else if(strncmp(token, "max", 3) == 0){
+ results[1] = val;
+ } else {
+ return -1;
+ }
+ break;
+ case 4:
+ if(strncmp(token, "step", 4) == 0){
+ results[2] = val;
+ } else {
+ return -1;
+ }
+ break;
+ case 9:
+ if(strncmp(token, "text_mult", 9) == 0){
+ results[3] = val;
+ } else {
+ return -1;
+ }
+ break;
+ default:
+ return -1;
+ }
+ last_break = index;
+ }
+ if(str[index] == '\0'){
+ break;
+ }
+ }
+ for(int i=0; i<4; ++i){
+ if(results[i] == FLT_MAX){
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static void DrawColorPicker(Object** selected, unsigned selected_count, SceneGraph* scenegraph) {
+ MapEditor* me = scenegraph->map_editor;
+ bool color_changed = false;
+ vec4 color;
+
+ ImGui::Text("Color history");
+ ImGui::BeginGroup();
+ for(int i = 0; i < MapEditor::kColorHistoryLen; ++i) {
+ vec4 col = me->GetColorHistoryIndex(i);
+ ImVec4 imCol(col.x(), col.y(), col.z(), col.w());
+ ImGui::PushID(i);
+ if(ImGui::ColorButton("##histcolbutt", imCol, false)) {
+ color.x() = imCol.x;
+ color.y() = imCol.y;
+ color.z() = imCol.z;
+ color_changed = true;
+ }
+ ImGui::PopID();
+
+ if(i == 0 || (i + 1) % 10 != 0)
+ ImGui::SameLine(0, -1);
+ }
+ ImGui::EndGroup();
+
+ // Show color picker if no objects are selected
+ bool only_movement_objects_selected = (selected_count != 0);
+ for(unsigned selected_i=0; selected_i<selected_count; ++selected_i){
+ if(selected[selected_i]->GetType() != _movement_object) {
+ only_movement_objects_selected = false;
+ break;
+ }
+ }
+
+ const size_t MAX_PALETTES = 24;
+ const char* labeled_colors[MAX_PALETTES];
+ unsigned labeled_color_count = 0;
+
+ if(only_movement_objects_selected) {
+ static int palette_index = 0;
+ int max_palette_index = 0;
+ for(unsigned selected_i=0; selected_i<selected_count; ++selected_i){
+ MovementObject* mo = (MovementObject*)selected[selected_i];
+ OGPalette* palette = mo->GetPalette();
+ for(size_t i=0, len=palette->size(); i<len; ++i){
+ LabeledColor* labeled_color = &palette->at(i);
+ bool found = false;
+ for(unsigned palette_i = 0; palette_i < labeled_color_count; palette_i++) {
+ if(strcmp(labeled_colors[palette_i], labeled_color->label.c_str()) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ if(!found) {
+ if(labeled_color_count < MAX_PALETTES) {
+ labeled_colors[labeled_color_count] = labeled_color->label.c_str();
+ labeled_color_count++;
+ }
+ }
+ }
+ }
+
+ if (labeled_color_count != 0) {
+ palette_index = min(palette_index, (int)labeled_color_count - 1);
+ char dropdown_text[512];
+ size_t dropdown_text_length = 0;
+ for (unsigned i = 0; i < labeled_color_count; ++i) {
+ strcpy(&dropdown_text[dropdown_text_length], labeled_colors[i]);
+ dropdown_text_length += strlen(labeled_colors[i]) + 1;
+ }
+ dropdown_text[dropdown_text_length] = '\0';
+ ImGui::Combo("Palette", &palette_index, dropdown_text);
+
+ if (!color_changed) {
+ for (unsigned selected_i = 0; selected_i < selected_count; ++selected_i) {
+ MovementObject* mo = (MovementObject*)selected[selected_i];
+ OGPalette* palette = mo->GetPalette();
+ for (size_t i = 0, len = palette->size(); i < len; ++i) {
+ LabeledColor* labeled_color = &palette->at(i);
+ bool found = false;
+ if (strcmp(labeled_colors[palette_index], labeled_color->label.c_str()) == 0) {
+ color = labeled_color->color;
+ goto breakselectedloop;
+ }
+ }
+ }
+ }
+
+ breakselectedloop:
+ if(ColorPicker(&color[0], NULL, false, false) || color_changed){
+ if(!color_changed)
+ me->AddColorToHistory(color);
+
+ for(unsigned selected_i=0; selected_i<selected_count; ++selected_i){
+ MovementObject* mo = (MovementObject*)selected[selected_i];
+ OGPalette* palette = mo->GetPalette();
+ bool found = false;
+ for(size_t i=0, len=palette->size(); i<len; ++i){
+ LabeledColor* labeled_color = &palette->at(i);
+ if(strcmp(labeled_colors[palette_index], labeled_color->label.c_str()) == 0) {
+ labeled_color->color = color.xyz();
+ mo->ApplyPalette(*palette);
+ break;
+ }
+ }
+ }
+ }
+ }
+ } else {
+ float overbright(0.0f);
+
+ if(!color_changed) {
+ for(unsigned i=0; i<selected_count; ++i){
+ if(selected[i]->GetType() == _env_object){
+ color = ((EnvObject*)selected[i])->GetColorTint();
+ overbright = ((EnvObject*)selected[i])->GetOverbright();
+ break;
+ } else if(selected[i]->GetType() == _item_object){
+ color = ((ItemObject*)selected[i])->GetColorTint();
+ overbright = ((ItemObject*)selected[i])->GetOverbright();
+ break;
+ } else if(selected[i]->GetType() == _decal_object){
+ color = ((DecalObject*)selected[i])->color_tint_component_.tint_;
+ overbright = ((DecalObject*)selected[i])->color_tint_component_.overbright_;
+ break;
+ } else if(selected[i]->GetType() == _dynamic_light_object ) {
+ color = ((DynamicLightObject*)selected[i])->GetTint();
+ overbright = ((DynamicLightObject*)selected[i])->GetOverbright();
+ }
+ }
+ }
+
+ if(ColorPicker(&color[0], &overbright, false, true) || color_changed) {
+ if(!color_changed)
+ me->AddColorToHistory(color);
+
+ for(size_t i = 0, len = scenegraph->objects_.size(); i < len; ++i) {
+ Object* obj = scenegraph->objects_[i];
+ if(obj->Selected()){
+ if(obj->GetType() == _env_object) {
+ EnvObject* eo = (EnvObject*)obj;
+ eo->ReceiveObjectMessage(OBJECT_MSG::SET_COLOR, &color);
+ eo->ReceiveObjectMessage(OBJECT_MSG::SET_OVERBRIGHT, &overbright);
+ }
+ if(obj->GetType() == _item_object) {
+ ItemObject* io = (ItemObject*)obj;
+ io->ReceiveObjectMessage(OBJECT_MSG::SET_COLOR, &color);
+ io->ReceiveObjectMessage(OBJECT_MSG::SET_OVERBRIGHT, &overbright);
+ }
+ if(obj->GetType() == _decal_object) {
+ DecalObject* decalo = (DecalObject*)obj;
+ decalo->ReceiveObjectMessage(OBJECT_MSG::SET_COLOR, &color);
+ decalo->ReceiveObjectMessage(OBJECT_MSG::SET_OVERBRIGHT, &overbright);
+ }
+ if(obj->GetType() == _dynamic_light_object) {
+ DynamicLightObject* dlo = (DynamicLightObject*)obj;
+ dlo->ReceiveObjectMessage(OBJECT_MSG::SET_COLOR, &color);
+ dlo->ReceiveObjectMessage(OBJECT_MSG::SET_OVERBRIGHT, &overbright);
+ }
+ }
+ }
+ scenegraph->map_editor->QueueSaveHistoryState();
+ }
+ }
+}
+
+static void DrawAudioDebug() {
+ std::vector<SoundSourceInfo> sound_sources = Engine::Instance()->GetSound()->GetCurrentSoundSources();
+ if(ImGui::TreeNode("Sounds", "Sounds: %d", (int)sound_sources.size())){
+ for( unsigned i = 0; i < sound_sources.size(); i++ ) {
+ SoundSourceInfo &ss = sound_sources[i];
+ ImGui::Text("%s :%f %f %f", ss.name, ss.pos[0], ss.pos[1], ss.pos[2]);
+ }
+ ImGui::TreePop();
+ }
+
+ if( ImGui::TreeNode( "Handle Count", "Handle Count: %d", (int)Engine::Instance()->GetSound()->GetSoundHandleCount() ) ) {
+ std::map<wrapper_sound_handle,real_sound_handle> handles = Engine::Instance()->GetSound()->GetAllHandles();
+ std::map<wrapper_sound_handle,real_sound_handle>::iterator hit = handles.begin();
+
+ for(; hit != handles.end(); hit++) {
+ ImGui::Text("%s %s: %s",Engine::Instance()->GetSound()->GetID(hit->first).c_str(), Engine::Instance()->GetSound()->GetName(hit->first).c_str(), Engine::Instance()->GetSound()->GetType(hit->first).c_str());
+ }
+ ImGui::TreePop();
+ }
+
+ ImGui::Text( "Sound Instances: %d", (int)Engine::Instance()->GetSound()->GetSoundInstanceCount() );
+
+ ImGui::Text("Current Song: %s", Engine::Instance()->GetSound()->GetSongName().c_str() );
+ ImGui::Text("Current Song Type: %s", Engine::Instance()->GetSound()->GetSongType().c_str() );
+
+ if( strmtch( Engine::Instance()->GetSound()->GetSongType().c_str(), "layered" ) ) {
+ std::vector<std::string> layers = Engine::Instance()->GetSound()->GetLayerNames();
+ if(ImGui::TreeNode("Layers", "Layers: %d", (int)layers.size())) {
+
+ static float v = 0.0f;
+ static std::string selected_layer_name = "";
+
+ for( unsigned i = 0; i < layers.size(); i++ ) {
+ float layer_gain = Engine::Instance()->GetSound()->GetLayerGain(layers[i]);
+ ImGui::Text("%s %f", layers[i].c_str(), layer_gain );
+
+ if( ImGui::IsItemClicked() ) {
+ if( selected_layer_name == layers[i] ) {
+ selected_layer_name = "";
+ } else {
+ selected_layer_name = layers[i];
+ v = layer_gain;
+ }
+ }
+
+ if( selected_layer_name == layers[i] ) {
+ if( ImGui::DragFloat(layers[i].c_str(),&v, 0.01f, 0.0f, 1.0f) ) {
+ LOGI << "Settings layer gain" << std::endl;
+ Engine::Instance()->GetSound()->SetLayerGain(layers[i],v);
+ }
+ }
+ }
+
+ ImGui::TreePop();
+ }
+ }
+
+ ImGui::Text("Next Song: %s", Engine::Instance()->GetSound()->GetNextSongName().c_str() );
+ ImGui::Text("Next Song Type: %s", Engine::Instance()->GetSound()->GetNextSongType().c_str() );
+}
+
+static void DrawDebugText(GUI* gui) {
+ uint32_t curr_ticks = SDL_TS_GetTicks();
+ for(GUI::DebugTextMap::iterator iter = gui->debug_text.begin();
+ iter != gui->debug_text.end();)
+ {
+ GUI::DebugTextEntry* debug_text_entry = &(iter->second);
+ if(debug_text_entry->delete_time >= curr_ticks){
+ ImGui::Text(debug_text_entry->text.c_str());
+ ++iter;
+ } else {
+ gui->debug_text.erase(iter++);
+ }
+ }
+}
+
+static void DrawDebugText(GUI* gui, const std::string& text) {
+ GUI::DebugTextMap::iterator iter = gui->debug_text.find(text);
+ if(iter != gui->debug_text.end()) {
+ ImGui::Text(iter->second.text.c_str());
+ }
+}
+
+// Return true if anything was changed
+static bool DrawScriptParamsEditor(ScriptParams* params) {
+ Online* online = Online::Instance();
+ const int kBufSize = 256;
+ char buf[kBufSize];
+ bool changed = false;
+ bool rename = false;
+ bool to_delete = false;
+ char rename_buf[2][kBufSize];
+ const ScriptParamMap& spm = params->GetParameterMap();
+ ImGui::Columns(3);
+ int id = 0;
+ for(ScriptParamMap::const_iterator iter = spm.begin(); iter != spm.end(); ++iter){
+ const ScriptParam& sp = iter->second;
+ const ScriptParamParts::Editor& editor = sp.editor();
+ FormatString(buf, kBufSize, "%s", iter->first.c_str());
+ ImGui::PushItemWidth(-1); // Don't make space for invisible labels
+ ImGui::PushID(id++);
+ if(ImGui::InputText("###inputnamething", buf, kBufSize, ImGuiInputTextFlags_EnterReturnsTrue)){
+ FormatString(rename_buf[0], kBufSize, "%s", iter->first.c_str());
+ FormatString(rename_buf[1], kBufSize, "%s", buf);
+ rename = true;
+ }
+ ImGui::PopID();
+ ImGui::PopItemWidth();
+ ImGui::NextColumn();
+ /*
+ UNDEFINED,
+ TEXTFIELD,
+ DISPLAY_SLIDER,
+ COLOR_PICKER,
+ CHECKBOX,
+ DROPDOWN_MENU,
+ MULTI_SELECT,
+ CUSTOM_WINDOW_LAUNCHER*/
+
+ ImGui::PushItemWidth(-1); // Don't make space for invisible labels
+ ImGui::PushID(id++);
+ switch(editor.type()){
+ case ScriptParamEditorType::CHECKBOX:{
+ bool checked = (sp.GetInt()==1);
+ if(ImGui::Checkbox("", &checked)){
+ params->ASSetInt(iter->first, checked?1:0);
+ changed = true;
+ }
+ break;}
+ case ScriptParamEditorType::COLOR_PICKER:{
+ vec3 color = ColorFromString(sp.GetString().c_str());
+ if(ImGui::ColorEdit3("", &color[0])){
+ FormatString(buf, kBufSize, "%d, %d, %d", (int)(color[0]*255), (int)(color[1]*255), (int)(color[2]*255));
+ params->ASSetString(iter->first, buf);
+ changed = true;
+ }
+ break;}
+ case ScriptParamEditorType::DISPLAY_SLIDER:{
+ const char* details = editor.GetDetails().c_str();
+ float details_flt[4] = {FLT_MAX, FLT_MAX, 1.0f, 1.0f};
+ if(ParseDetails(details, details_flt) == 0){
+ bool is_float = false;
+ if(sp.GetFloat() != (int)sp.GetFloat() ||
+ details_flt[0] != (int)details_flt[0] ||
+ details_flt[1] != (int)details_flt[1] ||
+ details_flt[2] != (int)details_flt[2] ||
+ details_flt[3] != (int)details_flt[3])
+ {
+ is_float = true;
+ }
+ if(is_float){
+ float val = sp.GetFloat() * details_flt[3];
+ if(ImGui::SliderFloat("", &val, details_flt[0] * details_flt[3], details_flt[1] * details_flt[3])){
+ params->ASSetFloat(iter->first, val / details_flt[3]);
+ changed = true;
+ }
+ } else {
+ int val = (int) (sp.GetInt() * details_flt[3]);
+ if(ImGui::SliderInt("", &val, (int) (details_flt[0] * details_flt[3]), (int) (details_flt[1] * details_flt[3]))){
+ params->ASSetInt(iter->first, (int) (val / details_flt[3]));
+ changed = true;
+ }
+ }
+ }
+ break;}
+ case ScriptParamEditorType::TEXTFIELD:{
+ if(sp.IsString()){
+ //Due to some problem in ImGUI this value can't grow during runtime. Limits input.
+ const size_t kLargerBufSize = 1024 * 16;
+ if( sp.GetString().size() < (kBufSize - 1) ) {
+ FormatString(buf, kBufSize, "%s", sp.GetString().c_str());
+ if(ImGui::InputText("##smallertextfield", buf, kBufSize)){
+ params->ASSetString(iter->first, buf);
+ changed = true;
+ }
+ } else if( sp.GetString().size() < kLargerBufSize ) {
+ char* larger_buf = (char*)alloc.stack.Alloc(kLargerBufSize);
+ FormatString(larger_buf, kLargerBufSize, "%s", sp.GetString().c_str());
+ if(ImGui::InputTextMultiline("##largertextfield", larger_buf, kLargerBufSize)){
+ params->ASSetString(iter->first, larger_buf);
+ changed = true;
+ }
+ alloc.stack.Free(larger_buf);
+ } else {
+ ImGui::TextWrapped("String is too large to display");
+ }
+ } else if(sp.IsInt()){
+ int val = sp.GetInt();
+ if(ImGui::InputInt("", &val)){
+ params->ASSetInt(iter->first, val);
+ changed = true;
+ }
+ } else if(sp.IsFloat()){
+ int val = (int) sp.GetFloat();
+ if(ImGui::InputInt("", &val)){
+ params->ASSetFloat(iter->first, (float) val);
+ changed = true;
+ }
+ }
+ break;}
+ default:
+ ImGui::LabelText("", sp.AsString().c_str());
+ }
+ ImGui::PopID();
+ ImGui::PopItemWidth();
+ ImGui::NextColumn();
+ ImGui::PushID(id++);
+ if(ImGui::Button("x")){
+ to_delete = true;
+ FormatString(rename_buf[0], kBufSize, "%s", iter->first.c_str());
+ }
+ ImGui::PopID();
+ ImGui::NextColumn();
+ }
+ ImGui::Columns(1);
+ if(ImGui::Button("New parameter")){
+ ScriptParamMap spm = params->GetParameterMap();
+ ScriptParam sp;
+ sp.SetString("");
+ params->InsertNewScriptParam("Untitled parameter",sp);
+ changed = true;
+ }
+ if(rename){
+ ScriptParamMap spm = params->GetParameterMap();
+ ScriptParamMap::iterator iter = spm.find(std::string(rename_buf[0]));
+
+ if(iter != spm.end()){
+ ScriptParam sp = iter->second;
+ spm.erase(iter);
+ spm[rename_buf[1]] = sp;
+ params->SetParameterMap(spm);
+ changed = true;
+ if (online->IsActive()) {
+ online->Send<OnlineMessages::SPRenameMessage>(params->GetObjectID(), std::string(rename_buf[1]), std::string(rename_buf[0]));
+ }
+ }
+ }
+ if(to_delete){
+ ScriptParamMap spm = params->GetParameterMap();
+ ScriptParamMap::iterator iter = spm.find(std::string(rename_buf[0]));
+ if(iter != spm.end()){
+ spm.erase(iter);
+ params->SetParameterMap(spm);
+ changed = true;
+ if (online->IsActive()) {
+ online->Send<OnlineMessages::SPRemoveMessage>(params->GetObjectID(), std::string(rename_buf[0]));
+ }
+ }
+ }
+ return changed;
+}
+
+#include <Objects/itemobjectscriptreader.h>
+#include <Objects/pathpointobject.h>
+#include <Objects/navmeshconnectionobject.h>
+
+void GetDisplayName(Object* obj, std::vector<char>& buffer) {
+ char temp[512];
+ temp[0] = '\0';
+ if(!obj->GetName().empty()) {
+ sprintf(temp, "%d, ", obj->GetID());
+ }
+ obj->GetDisplayName(temp + strlen(temp), 512);
+ size_t length = strlen(temp) + 1;
+ size_t old_size = buffer.size();
+ buffer.resize(old_size + length);
+ memcpy(buffer.data() + old_size, temp, length);
+}
+
+static void PrintConnections(MovementObject* mov,
+ const char* name,
+ const std::list<ItemObjectScriptReader>& list,
+ AttachmentType type,
+ bool mirrored,
+ bool show_connected,
+ bool only_named)
+{
+ if(ImGui::TreeNodeEx(name)) {
+ if (ImGui::BeginListBox("Connected")) {
+ for (std::list<ItemObjectScriptReader>::const_iterator it = list.begin(); it != list.end(); ++it) {
+ if (it->attachment_type == type && it->attachment_mirror == mirrored) {
+ Object* obj = (*it).obj;
+ char buffer[512];
+ obj->GetDisplayName(buffer, 512);
+ if (ImGui::Selectable(buffer, false)) {
+ mov->Disconnect(*obj);
+ }
+ }
+ }
+ ImGui::EndListBox();
+ }
+
+ if (ImGui::BeginListBox("Available")) {
+ for (size_t i = 0; i < mov->scenegraph_->item_objects_.size(); ++i) {
+ ItemObject* obj = (ItemObject*)mov->scenegraph_->item_objects_[i];
+ if ((!show_connected && obj->HeldByWhom() != -1)
+ || (only_named && obj->GetName().empty()))
+ {
+ continue;
+ }
+
+ bool found = false;
+ for (std::list<ItemObjectScriptReader>::const_iterator it = list.begin(); it != list.end(); ++it) {
+ if (obj->GetID() == (*it)->GetID() && it->attachment_type == type && it->attachment_mirror == mirrored) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ const ItemRef& ref = obj->item_ref();
+ if (ref->HasAttachment(type)) {
+ int held_by_id = obj->HeldByWhom();
+ Object* held_by = NULL;
+ if (held_by_id != -1)
+ held_by = obj->scenegraph_->GetObjectFromID(held_by_id);
+
+ char buffer[512];
+ obj->GetDisplayName(buffer, 512);
+
+ size_t length = strlen(buffer);
+ if (held_by != NULL) {
+ if (held_by->GetName().empty()) {
+ length += sprintf(buffer + length, " held by %d", held_by->GetID());
+ }
+ else {
+ length += sprintf(buffer + length, " held by %d (%s)", held_by->GetID(), held_by->GetName().c_str());
+ }
+ }
+
+ if (ImGui::Selectable(buffer, false)) {
+ AttachmentRef attachment_ref;
+ mov->AttachItemToSlotEditor(obj->GetID(), type, mirrored, attachment_ref);
+ }
+ }
+ }
+ }
+ ImGui::EndListBox();
+ }
+ ImGui::TreePop();
+ }
+}
+
+static void DrawLaunchCustomGuiButton(Object* obj, bool& are_script_params_read_only, int button_instance_id = -1) {
+ if(button_instance_id != -1) {
+ ImGui::PushID(button_instance_id);
+ }
+
+ if(obj->GetType() == _hotspot_object) {
+ Hotspot* hotspot = (Hotspot*)obj;
+
+ if(hotspot->HasCustomGUI() && ImGui::Button("Open custom editor")) {
+ hotspot->LaunchCustomGUI();
+ }
+
+ are_script_params_read_only = hotspot->ObjectInspectorReadOnly();
+ } else if(obj->GetType() == _placeholder_object) {
+ PlaceholderObject* placeholder = (PlaceholderObject*)obj;
+
+ if(placeholder->GetScriptParams()->HasParam("Dialogue")) {
+ if(ImGui::Button("Launch dialogue editor")) {
+ char buffer[64];
+ sprintf(buffer, "edit_dialogue_id%i", placeholder->GetID());
+ placeholder->scenegraph_->level->Message(buffer);
+ }
+
+ are_script_params_read_only = true;
+ }
+ }
+
+ if(button_instance_id != -1) {
+ ImGui::PopID();
+ }
+}
+
+static void DrawObjectInfoFlat(Object* obj) {
+ ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing());
+ obj->DrawImGuiEditor();
+ ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing());
+
+ bool are_script_params_read_only = false;
+ DrawLaunchCustomGuiButton(obj, are_script_params_read_only);
+
+ bool temp_enabled = obj->enabled_;
+ if(ImGui::Checkbox("Enabled", &temp_enabled)){
+ obj->SetEnabled(temp_enabled);
+ }
+
+ const size_t name_len = 64;
+ char name[name_len];
+ strscpy(name,obj->GetName().c_str(),name_len);
+ if(ImGui::InputText("Name", name, name_len)) {
+ obj->SetName(std::string(name));
+ obj->scenegraph_->map_editor->QueueSaveHistoryState();
+ }
+
+ if( obj->GetType() == _prefab ) {
+ Prefab *prefab = static_cast<Prefab*>(obj);
+
+ bool prefab_locked;
+ prefab_locked = prefab->prefab_locked;
+
+ if(ImGui::Checkbox("Prefab Locked", &prefab_locked)) {
+ prefab->prefab_locked = prefab_locked;
+ prefab->scenegraph_->map_editor->QueueSaveHistoryState();
+ }
+
+ ImGui::Text("Prefab Source: %s", prefab->prefab_path.GetOriginalPath());
+ }
+
+ bool changed = false;
+
+ vec3 translation = obj->GetTranslation();
+ if(ImGui::DragFloat3("Translation", &translation[0], 0.01f) || ImGui::IsItemActive() ){
+ obj->SetTranslation(translation);
+ changed = true;
+ }
+
+ vec3 scale = obj->GetScale();
+ if(ImGui::DragFloat3("Scale", &scale[0], 0.01f) || ImGui::IsItemActive()){
+ obj->SetScale(scale);
+ changed = true;
+ }
+
+ vec3 euler_angles = obj->GetRotationEuler();
+ euler_angles *= 180.0f / (float) M_PI;
+
+ if(ImGui::DragFloat3("Rotation", &euler_angles[0], 1.0f, 0.0f, 0.0f, "%.0f") || ImGui::IsItemActive()){
+ obj->SetRotationEuler(euler_angles * (float) M_PI / 180.0f);
+ changed = true;
+ }
+
+ if( ImGui::Button("Reset Rotation") ) {
+ obj->SetRotation(quaternion());
+ changed = true;
+ }
+
+ if(changed){
+ obj->scenegraph_->UpdatePhysicsTransforms();
+ obj->scenegraph_->map_editor->QueueSaveHistoryState();
+ }
+
+ SceneGraph* scenegraph = obj->scenegraph_;
+ ImGui::Separator();
+ bool show_connections = false;
+ switch(obj->GetType()) {
+ case _movement_object:{
+ static bool only_named = false;
+ ImGui::Text("Connected objects");
+ ImGui::Checkbox("Show only named items in available list", &only_named);
+ static bool show_connected = true;
+ ImGui::Checkbox("Show items with connections in available list", &show_connected);
+ MovementObject* mov = (MovementObject*)obj;
+ std::vector<char> text;
+ text.reserve(1024);
+ text.resize(strlen("Nothing") + 1, '\0');
+ strcpy(text.data(), "Nothing");
+ int item_index = 0;
+ int path_point_count = 0;
+ for(size_t i = 0; i < scenegraph->path_points_.size(); ++i) {
+ Object* obj = scenegraph->path_points_[i];
+ if(!(only_named && obj->GetName().empty())) {
+ GetDisplayName(obj, text);
+ if(obj->GetID() == mov->connected_pathpoint_id) {
+ item_index = path_point_count + 1;
+ }
+ path_point_count++;
+ }
+ }
+ int movement_object_count = 0;
+ for(size_t i = 0; i < scenegraph->movement_objects_.size(); ++i) {
+ Object* obj = scenegraph->movement_objects_[i];
+ if(!(only_named && obj->GetName().empty()) && mov->GetID() != obj->GetID()) {
+ GetDisplayName(obj, text);
+ if(obj->GetID() == mov->connected_pathpoint_id) {
+ item_index = path_point_count + movement_object_count + 1;
+ }
+ movement_object_count++;
+ }
+ }
+ text.push_back('\0');
+ if(ImGui::Combo("Path object", &item_index, text.data())) {
+ if(item_index == 0 && mov->connected_pathpoint_id != -1) {
+ mov->Disconnect(*scenegraph->GetObjectFromID(mov->connected_pathpoint_id));
+ } else {
+ size_t offset = 0;
+ for(int i = 0; i < item_index; ++i) {
+ offset += strlen(text.data() + offset) + 1;
+ }
+ int id = atoi(text.data() + offset);
+ mov->ConnectTo(*scenegraph->GetObjectFromID(id));
+ }
+ }
+
+ std::list<ItemObjectScriptReader> list = mov->item_connections;
+ PrintConnections(mov, "Grip", list, _at_grip, false, show_connected, only_named);
+ PrintConnections(mov, "Mirrored grip", list, _at_grip, true, show_connected, only_named);
+ PrintConnections(mov, "Sheathe", list, _at_sheathe, false, show_connected, only_named);
+ PrintConnections(mov, "Mirrored sheathe", list, _at_sheathe, true, show_connected, only_named);
+
+ if(ImGui::TreeNode("Connected objects")) {
+ show_connections = true;
+ RiggedObject* rig = mov->rigged_object();
+ if(rig && !rig->children.empty()) {
+ if (ImGui::BeginListBox("Attached objects")) {
+ for (size_t i = 0; i < rig->children.size(); ++i) {
+ Object* ptr = rig->children[i].direct_ptr;
+ char buffer[512];
+ ptr->GetDisplayName(buffer, 512);
+ if (ImGui::Selectable(buffer, false)) {
+ ptr->SetParent(NULL);
+ }
+ }
+ ImGui::EndListBox();
+ }
+ }
+ }
+ break;}
+ case _item_object:{
+ ItemObject* item = (ItemObject*)obj;
+ Object* held_by = NULL;
+ if(item->HeldByWhom() != -1) {
+ held_by = scenegraph->GetObjectFromID(item->HeldByWhom());
+ if(ImGui::Button("X")) {
+ item->Disconnect(*held_by);
+ } else {
+ ImGui::SameLine();
+ if(held_by->GetName().empty()) {
+ ImGui::Text("Held by %d", held_by->GetID());
+ } else {
+ ImGui::Text("Held by %d (%s)", held_by->GetID(), held_by->GetName().c_str());
+ }
+ }
+ } else {
+ ImGui::Text("Not held by anyone");
+ }
+ if(ImGui::TreeNode("Hotspots connected to this")) {
+ show_connections = true;
+ }
+ break;}
+ case _path_point_object:{
+ PathPointObject* path_obj = (PathPointObject*)obj;
+ if (ImGui::BeginListBox("Connected")) {
+ for (size_t i = 0; i < scenegraph->movement_objects_.size(); ++i) {
+ MovementObject* mov_obj = (MovementObject*)scenegraph->movement_objects_[i];
+ if (mov_obj->connected_pathpoint_id == path_obj->GetID()) {
+ char buffer[512];
+ mov_obj->GetDisplayName(buffer, 512);
+ if (ImGui::Selectable(buffer, false)) {
+ mov_obj->Disconnect(*path_obj);
+ }
+ }
+ }
+ for (size_t i = 0; i < path_obj->connection_ids.size(); i++) {
+ Object* obj = scenegraph->GetObjectFromID(path_obj->connection_ids[i]);
+ char buffer[512];
+ obj->GetDisplayName(buffer, 512);
+ if (ImGui::Selectable(buffer, false)) {
+ path_obj->Disconnect(*obj);
+ }
+ }
+ ImGui::EndListBox();
+ }
+ if (ImGui::BeginListBox("Available characters")) {
+ for (size_t i = 0; i < scenegraph->movement_objects_.size(); ++i) {
+ MovementObject* obj = (MovementObject*)scenegraph->movement_objects_[i];
+ if (obj->connected_pathpoint_id != path_obj->GetID()) {
+ char buffer[512];
+ obj->GetDisplayName(buffer, 512);
+ if (ImGui::Selectable(buffer, false)) {
+ obj->ConnectTo(*path_obj);
+ }
+ }
+ }
+ ImGui::EndListBox();
+ }
+ if(ImGui::TreeNode("Hotspots connected to this")) {
+ show_connections = true;
+ }
+ break;}
+ case _navmesh_connection_object:{
+ static bool only_named = false;
+ ImGui::Text("Connected objects");
+ ImGui::Checkbox("Show only named items in available list", &only_named);
+ static bool show_connected = true;
+ ImGui::Checkbox("Show items with connections in available list", &show_connected);
+ NavmeshConnectionObject* nav_con = (NavmeshConnectionObject*)obj;
+ if (ImGui::BeginListBox("Connected")) {
+ for (size_t i = 0; i < nav_con->connections.size(); i++) {
+ Object* obj = scenegraph->GetObjectFromID(nav_con->connections[i].other_object_id);
+ char buffer[512];
+ obj->GetDisplayName(buffer, 512);
+ if (ImGui::Selectable(buffer, false)) {
+ nav_con->Disconnect(*obj);
+ }
+ }
+ ImGui::EndListBox();
+ }
+ if (ImGui::BeginListBox("Available")) {
+ for (size_t i = 0; i < scenegraph->navmesh_connections_.size(); ++i) {
+ NavmeshConnectionObject* obj = (NavmeshConnectionObject*)scenegraph->navmesh_connections_[i];
+ if (obj->GetID() == nav_con->GetID()
+ || (!show_connected && !obj->connections.empty())) {
+ continue;
+ }
+ bool connected = false;
+ for (size_t j = 0; j < nav_con->connections.size(); j++) {
+ if (obj->GetID() == nav_con->connections[j].other_object_id) {
+ connected = true;
+ break;
+ }
+ }
+ if (!connected && !(only_named && obj->GetName().empty())) {
+ char buffer[512];
+ obj->GetDisplayName(buffer, 512);
+ if (ImGui::Selectable(buffer, false)) {
+ nav_con->ConnectTo(*obj);
+ }
+ }
+ }
+ ImGui::EndListBox();
+ }
+ if(ImGui::TreeNode("Hotspots connected to this")) {
+ show_connections = true;
+ }
+ break;}
+ case _placeholder_object:{
+ PlaceholderObject* place_obj = (PlaceholderObject*)obj;
+ int item_index = 0;
+ std::vector<char> text;
+ text.reserve(1024);
+ text.resize(strlen("Nothing") + 1, '\0');
+ strcpy(text.data(), "Nothing");
+ for(size_t i = 0; i < scenegraph->movement_objects_.size(); ++i) {
+ MovementObject* mov_obj = (MovementObject*)scenegraph->movement_objects_[i];
+ GetDisplayName(mov_obj, text);
+ if(mov_obj->GetID() == place_obj->GetConnectID()) {
+ item_index = (int)i + 1;
+ }
+ }
+ text.push_back('\0');
+ if(ImGui::Combo("##setcon", &item_index, text.data())) {
+ if(item_index == 0 && place_obj->GetConnectID() != -1) {
+ place_obj->Disconnect(*scenegraph->GetObjectFromID(place_obj->GetConnectID()));
+ } else {
+ size_t offset = 0;
+ for(int i = 0; i < item_index; ++i) {
+ offset += strlen(text.data() + offset) + 1;
+ }
+ int id = atoi(text.data() + offset);
+ place_obj->ConnectTo(*scenegraph->GetObjectFromID(id));
+ }
+ }
+ if(ImGui::TreeNode("Hotspots connected to this")) {
+ show_connections = true;
+ }
+ break;}
+ case _hotspot_object:{
+ if(ImGui::TreeNode("Connected objects")) {
+ show_connections = true;
+ }
+ break;}
+ default:
+ if(ImGui::TreeNode("Hotspots connected to this")) {
+ show_connections = true;
+ }
+ break;
+ }
+
+ if(show_connections) {
+ if (!obj->connected_to.empty()) {
+ if (ImGui::BeginListBox("Hotspots")) {
+ for (size_t i = 0; i < obj->connected_to.size(); ++i) {
+ Object* ptr = scenegraph->GetObjectFromID(obj->connected_to[i]);
+ char buffer[512];
+ ptr->GetDisplayName(buffer, 512);
+ if (ImGui::Selectable(buffer)) {
+ obj->Disconnect(*ptr);
+ }
+ }
+ ImGui::EndListBox();
+ }
+ }
+ ImGui::TreePop();
+ }
+
+ ImGui::Separator();
+ DrawColorPicker(&obj, 1, obj->scenegraph_);
+ if(obj->GetType() == _env_object){
+ EnvObject* eo = (EnvObject*)obj;
+ ImGui::Separator();
+ ImGui::Checkbox("no_navmesh", &eo->no_navmesh);
+ }
+
+ ImGui::Separator();
+ if(obj->GetType() == _movement_object) {
+ MovementObject* mov_obj = (MovementObject*)obj;
+ ImGui::Text("NPC control script: %s", mov_obj->GetCurrentControlScript().c_str());
+ ImGui::SameLine();
+ if (ImGui::Button("Preview script")) {
+ show_script = preview_script_name == mov_obj->GetCurrentControlScript() ? !show_script : true;
+ preview_script_name = mov_obj->GetCurrentControlScript();
+ }
+
+ if(ImGui::Button("Set NPC script...")) {
+ const int BUF_SIZE = 512;
+ char buffer[BUF_SIZE];
+ Dialog::DialogErr err = Dialog::readFile("as",1,"Data/Scripts", buffer, BUF_SIZE);
+ if( err != Dialog::NO_ERR ){
+ LOGE << Dialog::DialogErrString( err ) << std::endl;
+ } else {
+ std::string script_name = SplitPathFileName( buffer ).second;
+ mov_obj->object_npc_script_path = script_name;
+ mov_obj->ChangeControlScript(script_name);
+ }
+ }
+ if(!mov_obj->object_npc_script_path.empty()) {
+ ImGui::SameLine();
+ if(ImGui::Button("Remove control script")) {
+ mov_obj->object_npc_script_path.clear();
+ mov_obj->ChangeControlScript(mov_obj->scenegraph_->level->GetNPCScript(mov_obj));
+ }
+ }
+ if(mov_obj->is_player) {
+ ImGui::Text("PC control script: %s", mov_obj->scenegraph_->level->GetPCScript(mov_obj).c_str());
+ ImGui::SameLine();
+ if (ImGui::Button("Preview script##1")) {
+ show_script = preview_script_name == mov_obj->scenegraph_->level->GetPCScript(mov_obj) ? !show_script : true;
+ preview_script_name = mov_obj->scenegraph_->level->GetPCScript(mov_obj);
+ }
+
+ if(ImGui::Button("Set PC script...")) {
+ const int BUF_SIZE = 512;
+ char buffer[BUF_SIZE];
+ Dialog::DialogErr err = Dialog::readFile("as",1,"Data/Scripts", buffer, BUF_SIZE);
+ if( err != Dialog::NO_ERR ){
+ LOGE << Dialog::DialogErrString( err ) << std::endl;
+ } else {
+ std::string script_name = SplitPathFileName( buffer ).second;
+ mov_obj->object_pc_script_path = script_name;
+ }
+ }
+ if(!mov_obj->object_pc_script_path.empty()) {
+ ImGui::SameLine();
+ if(ImGui::Button("Remove control script")) {
+ mov_obj->object_pc_script_path.clear();
+ }
+ }
+ }
+ }
+ ImGui::Text("Script Params");
+
+ const int kSecondLaunchCustomGuiInstanceId = 1;
+ DrawLaunchCustomGuiButton(obj, are_script_params_read_only, kSecondLaunchCustomGuiInstanceId);
+
+ ImGui::BeginDisabled(are_script_params_read_only);
+ if(DrawScriptParamsEditor(obj->GetScriptParams())){
+ obj->scenegraph_->map_editor->QueueSaveHistoryState();
+ obj->UpdateScriptParams();
+ }
+ ImGui::EndDisabled();
+
+ ImGui::Separator();
+ ImGui::Text("Debug");
+ ImGui::Text("Selectable: %s", obj->selectable_ ? "true" : "false");
+ if( obj->parent ) {
+ ImGui::Text("Parent: %d", obj->parent->GetID());
+ } else {
+ ImGui::Text("No Parent");
+ }
+}
+
+static void DrawObjectInfo(Object* obj, bool force_expand_script_params){
+ ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing());
+ obj->DrawImGuiEditor();
+ ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing());
+
+ bool are_script_params_read_only = false;
+ DrawLaunchCustomGuiButton(obj, are_script_params_read_only);
+
+ bool temp_enabled = obj->enabled_;
+ if(ImGui::Checkbox("Enabled", &temp_enabled)){
+ obj->SetEnabled(temp_enabled);
+ }
+
+ if(ImGui::TreeNode("Name")){
+ const size_t name_len = 64;
+ char name[name_len];
+ strscpy(name,obj->GetName().c_str(),name_len);
+ if(ImGui::InputText("Name", name, name_len)) {
+ obj->SetName(std::string(name));
+ obj->scenegraph_->map_editor->QueueSaveHistoryState();
+ }
+
+ if( obj->GetType() == _prefab ) {
+ Prefab *prefab = static_cast<Prefab*>(obj);
+
+ bool prefab_locked;
+ prefab_locked = prefab->prefab_locked;
+
+ if(ImGui::Checkbox("Prefab Locked", &prefab_locked)) {
+ prefab->prefab_locked = prefab_locked;
+ prefab->scenegraph_->map_editor->QueueSaveHistoryState();
+ }
+
+ ImGui::Text("Prefab Source: %s", prefab->prefab_path.GetOriginalPath());
+ }
+
+ ImGui::TreePop();
+ }
+
+ if(ImGui::TreeNode("Transform")){
+ bool changed = false;
+
+ vec3 translation = obj->GetTranslation();
+ if(ImGui::DragFloat3("Translation", &translation[0], 0.01f) || ImGui::IsItemActive() ){
+ obj->SetTranslation(translation);
+ changed = true;
+ }
+
+ vec3 scale = obj->GetScale();
+ if(ImGui::DragFloat3("Scale", &scale[0], 0.01f) || ImGui::IsItemActive()){
+ obj->SetScale(scale);
+ changed = true;
+ }
+
+ vec3 euler_angles = obj->GetRotationEuler();
+ euler_angles *= 180.0f / (float) M_PI;
+
+ if(ImGui::DragFloat3("Rotation", &euler_angles[0], 1.0f, 0.0f, 0.0f, "%.0f") || ImGui::IsItemActive()){
+ obj->SetRotationEuler(euler_angles * (float) M_PI / 180.0f);
+ changed = true;
+ }
+
+ if( ImGui::Button("Reset Rotation") ) {
+ obj->SetRotation(quaternion());
+ changed = true;
+ }
+
+ if(changed){
+ obj->scenegraph_->UpdatePhysicsTransforms();
+ obj->scenegraph_->map_editor->QueueSaveHistoryState();
+ }
+
+ ImGui::TreePop();
+ } // Transform
+
+ if(obj->GetType() == _env_object){
+ EnvObject* eo = (EnvObject*)obj;
+ vec3 color = eo->GetColorTint();
+ if(ImGui::TreeNode("Tint")){
+ if(ColorPicker(&color[0],NULL,false,false)){
+ eo->ReceiveObjectMessage(OBJECT_MSG::SET_COLOR, &color);
+ obj->scenegraph_->map_editor->QueueSaveHistoryState();
+ }
+ ImGui::TreePop();
+ }
+ if(ImGui::TreeNode("Flags")) {
+ ImGui::Checkbox("no_navmesh", &eo->no_navmesh);
+ ImGui::TreePop();
+ }
+ }
+
+ if(obj->GetType() == _movement_object){
+ MovementObject* mo = (MovementObject*)obj;
+ if(ImGui::TreeNode("Color Palette")){
+ DrawColorPicker(&obj, 1, obj->scenegraph_);
+ ImGui::TreePop();
+ }
+ }
+
+ if (force_expand_script_params) {
+ ImGui::SetNextItemOpen(true);
+ }
+
+ if(ImGui::TreeNode("Script Params")){
+ DrawLaunchCustomGuiButton(obj, are_script_params_read_only);
+
+ ImGui::BeginDisabled(are_script_params_read_only);
+ if(DrawScriptParamsEditor(obj->GetScriptParams())) {
+ obj->scenegraph_->map_editor->QueueSaveHistoryState();
+ obj->UpdateScriptParams();
+ }
+ ImGui::EndDisabled();
+
+ ImGui::TreePop();
+ }
+
+ if(ImGui::TreeNode("Debug")){
+ ImGui::Text("Selectable: %s", obj->selectable_ ? "true" : "false");
+ if( obj->parent ) {
+ ImGui::Text("Parent: %d", obj->parent->GetID());
+ } else {
+ ImGui::Text("No Parent");
+ }
+
+ ImGui::TreePop();
+ }
+}
+
+static bool IsCharacterSelceted(SpawnerItem* item) {
+ bool return_value = false;
+ if (ImGui::MenuItem(item->display_name.c_str())) {
+ return_value = true;
+ }
+
+
+ if (ImGui::IsItemHovered()) {
+ std::string thumbnail_full_path = item->thumbnail_path;
+
+ if (thumbnail_full_path != Engine::Instance()->current_spawner_thumbnail) {
+ if (FileExists(thumbnail_full_path, kDataPaths | kModPaths)) {
+ Engine::Instance()->spawner_thumbnail = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(thumbnail_full_path, PX_NOMIPMAP | PX_NOREDUCE | PX_NOCONVERT, 0x0);
+ }
+ else {
+ Engine::Instance()->spawner_thumbnail.clear();
+ }
+ Engine::Instance()->current_spawner_thumbnail = thumbnail_full_path;
+ }
+
+ if (Engine::Instance()->spawner_thumbnail.valid()) {
+ ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0.5, 0.5, 1.0, 0.5));
+ ImGui::BeginTooltip();
+ ImGui::PushTextWrapPos(450.0f);
+
+ Textures::Instance()->EnsureInVRAM(Engine::Instance()->spawner_thumbnail);
+ ImGui::Image((ImTextureID)Textures::Instance()->returnTexture(Engine::Instance()->spawner_thumbnail), ImVec2(128, 128), ImVec2(0, 0), ImVec2(1, 1));
+
+ ImGui::PopTextWrapPos();
+ ImGui::EndTooltip();
+ ImGui::PopStyleColor();
+ }
+ }
+ return return_value;
+}
+
+static bool AddSpawnerItem(SpawnerItem* item, SceneGraph* scenegraph) {
+ bool return_value = false;
+ if (ImGui::MenuItem(item->display_name.c_str())) {
+ if (scenegraph->map_editor->state_ == MapEditor::kIdle
+ && scenegraph->map_editor->LoadEntitiesFromFile(item->path)==0) {
+ scenegraph->level->PushSpawnerItemRecent(*item);
+ scenegraph->map_editor->active_tool_ = EditorTypes::ADD_ONCE;
+ return_value = true;
+ }
+ }
+ if (ImGui::IsItemHovered()) {
+ std::string thumbnail_full_path = item->thumbnail_path;
+
+ if( thumbnail_full_path != Engine::Instance()->current_spawner_thumbnail ) {
+ if(FileExists(thumbnail_full_path, kDataPaths | kModPaths)){
+ Engine::Instance()->spawner_thumbnail = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(thumbnail_full_path, PX_NOMIPMAP | PX_NOREDUCE | PX_NOCONVERT, 0x0);
+ } else {
+ Engine::Instance()->spawner_thumbnail.clear();
+ }
+ Engine::Instance()->current_spawner_thumbnail = thumbnail_full_path;
+ }
+
+ if( Engine::Instance()->spawner_thumbnail.valid() ) {
+ ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0.5, 0.5, 1.0, 0.5));
+ ImGui::BeginTooltip();
+ ImGui::PushTextWrapPos(450.0f);
+
+ Textures::Instance()->EnsureInVRAM(Engine::Instance()->spawner_thumbnail);
+ ImGui::Image((ImTextureID)Textures::Instance()->returnTexture(Engine::Instance()->spawner_thumbnail), ImVec2(128,128), ImVec2(0,0), ImVec2(1,1));
+
+ ImGui::PopTextWrapPos();
+ ImGui::EndTooltip();
+ ImGui::PopStyleColor();
+ }
+ }
+ return return_value;
+}
+
+static void LevelMenuItem(ModInstance::Campaign& campaign, ModInstance::Level& level) {
+ const char* level_name = level.title;
+ const char* level_path = level.path;
+ const char* level_id = level.id;
+
+ if(ImGui::MenuItem(level_name, NULL, false, level_name[0] != '*')) {
+ Engine::Instance()->ScriptableUICallback("set_campaign " + campaign.id.str());
+ Engine::Instance()->ScriptableUICallback("load_campaign_level " + std::string(level_id));
+ }
+ if (ImGui::IsItemHovered()) {
+ ImGui::BeginTooltip();
+ ImGui::PushTextWrapPos(450.0f);
+ ImGui::Text("Path: %s", level_path);
+ ImGui::PopTextWrapPos();
+ ImGui::EndTooltip();
+ }
+
+}
+
+static void OpenLevelMenu() {
+ if (ImGui::BeginMenu("Open")) {
+
+ ModLoading& mod_loading = ModLoading::Instance();
+
+ std::vector<ModInstance::Campaign> campaigns = mod_loading.GetCampaigns();
+
+ for (ModInstance::Campaign& campaign : campaigns) {
+ if (ImGui::BeginMenu(campaign.id.c_str())) {
+ for (ModInstance::Level& level : campaign.levels) {
+ LevelMenuItem(campaign, level);
+ }
+
+ ImGui::EndMenu();
+ }
+ }
+
+
+ ImGui::EndMenu();
+ }
+}
+
+static void OpenRecentMenu() {
+ if (ImGui::BeginMenu("Open Recent")) {
+ for(int i=0; i<kMaxLevelHistory; ++i){
+ const int kBufSize = 128;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "level_history%d", i+1);
+ if(config.HasKey(buf) && config[buf].str() != ""){
+ if(ImGui::MenuItem(config[buf].str().c_str())){
+ if(FileExists(config[buf].str(),kAnyPath)) {
+ LevelInfoAssetRef levelinfo = Engine::Instance()->GetAssetManager()->LoadSync<LevelInfoAsset>(config[buf].str());
+ Path p = FindFilePath(config[buf].str(),kAnyPath);
+ Engine::Instance()->QueueState(EngineState(levelinfo->GetName(),kEngineEditorLevelState, p));
+ } else {
+ LOGE << "Level missing: " << config[buf].str() << std::endl;
+ }
+ }
+ }
+ }
+ ImGui::EndMenu();
+ }
+}
+
+static bool TreeScenegraphElementVisible(Object* obj, Object* root, char* buf) {
+ const int kBufSize = 512;
+ if((show_named_only_scenegraph == false || obj->GetName().empty() == false) && obj->selectable_) {
+ if( obj->parent == root ) {
+ obj->GetDisplayName(buf, kBufSize);
+ if (scenegraph_filter.IsActive() && !scenegraph_filter.PassFilter( buf )){
+ if(search_children_scenegraph && obj->IsGroupDerived()) {
+ char child_buf[kBufSize];
+ Group* group = static_cast<Group*>(obj);
+ for(size_t i = 0; i < group->children.size(); i++) {
+ group->children[i].direct_ptr->GetDisplayName(child_buf, kBufSize);
+ if(scenegraph_filter.PassFilter( child_buf )) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+}
+static void DrawTreeScenegraphFor( SceneGraph* scenegraph, GUI* gui, Object* root );
+
+static void DrawTreeScenegraphElement( SceneGraph* scenegraph, GUI* gui, Object* parent, Object* obj, int index, bool force ) {
+ static bool select_elements;
+
+ const int kBufSize = 512;
+ char buf[kBufSize];
+ if(!force) {
+ if(!TreeScenegraphElementVisible(obj, parent, buf))
+ return;
+ } else {
+ obj->GetDisplayName(buf, kBufSize);
+ }
+
+ ImGuiTreeNodeFlags node_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
+ if(obj->Selected()) {
+ node_flags |= ImGuiTreeNodeFlags_Selected;
+ }
+ if(obj->GetType() != _group && obj->GetType() != _prefab) {
+ node_flags |= ImGuiTreeNodeFlags_Leaf;
+ }
+ ImGui::PushID(obj->GetID());
+ vec4 color = GetObjColor(obj);
+ ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(color[0], color[1], color[2], color[3]));
+ bool node_open = ImGui::TreeNodeEx("", node_flags, "%s", buf);
+ ImGui::PopStyleColor();
+ ImGui::PopID();
+ if (ImGui::IsItemClicked()) {
+ obj->Select(!obj->Selected());
+
+ const Keyboard& keyboard = Input::Instance()->getKeyboard();
+
+ if(ImGui::IsKeyDown(ImGui::GetKeyIndex(ImGuiKey_LShift)) || ImGui::IsKeyDown(ImGui::GetKeyIndex(ImGuiKey_RShift))) {
+ if(select_start_index == -1) {
+ select_start_index = index;
+ select_elements = obj->Selected();
+ } else {
+ char temp_buf[kBufSize];
+
+ int low_index = std::min(select_start_index, index);
+ int high_index = std::max(select_start_index, index);
+
+ for(int j=0, len=(int)scenegraph->objects_.size(); j<len; ++j) {
+ Object* temp_obj = scenegraph->objects_[j];
+ if(TreeScenegraphElementVisible(temp_obj, parent, temp_buf)) {
+ if(j >= low_index && j <= high_index) {
+ if(temp_obj->Selected() != select_elements)
+ temp_obj->Select(select_elements);
+ }
+ }
+ }
+ }
+ } else {
+ select_start_index = index;
+ select_elements = obj->Selected();
+ }
+ }
+ if (node_open) {
+ if ((node_flags & ImGuiTreeNodeFlags_Leaf) == 0) {
+ DrawTreeScenegraphFor(scenegraph, gui, obj);
+ }
+ ImGui::TreePop();
+ }
+}
+
+static void DrawTreeScenegraphFor( SceneGraph* scenegraph, GUI* gui, Object* root ) {
+ if(root && root->IsGroupDerived()) {
+ Group* group = static_cast<Group*>(root);
+ for(int i=0, len=(int)group->children.size(); i<len; ++i){
+ DrawTreeScenegraphElement(scenegraph, gui, root, group->children[i].direct_ptr, i, true);
+ }
+ } else {
+ for(int i=0, len=(int)scenegraph->objects_.size(); i<len; ++i){
+ DrawTreeScenegraphElement(scenegraph, gui, root, scenegraph->objects_[i], i, false);
+ }
+ }
+}
+
+static void FindFaces( const Model &model, const WOLFIRE_SIMPLIFY::SimplifyModel& processed_model, const std::vector<HalfEdge>& half_edges, std::vector<int>& half_edge_face, std::vector<int>& face_vert_equivalent ) {
+ std::vector<VertSortable> face_vert_sortable;
+ face_vert_sortable.resize(model.vertices.size()/3);
+ for(int vert_index=0, vert_id=0, len=(int)model.vertices.size(); vert_index<len; ++vert_id, vert_index += 3){
+ face_vert_sortable[vert_id].id = vert_id;
+ memcpy(&face_vert_sortable[vert_id].vert, &model.vertices[vert_index], sizeof(vec3));
+ }
+ std::vector<VertSortable> processed_vert_sortable;
+ processed_vert_sortable.resize(processed_model.vertices.size()/3);
+ for(int vert_index=0, vert_id=0, len=(int)processed_model.vertices.size(); vert_index<len; ++vert_id, vert_index += 3){
+ processed_vert_sortable[vert_id].id = vert_id;
+ memcpy(&processed_vert_sortable[vert_id].vert, &processed_model.vertices[vert_index], sizeof(vec3));
+ }
+ std::sort(face_vert_sortable.begin(), face_vert_sortable.end(), SortVertSortable);
+ std::sort(processed_vert_sortable.begin(), processed_vert_sortable.end(), SortVertSortable);
+ face_vert_equivalent.resize(face_vert_sortable.size(), -1);
+ unsigned processed_vert_index=0;
+ for(size_t i=0, len=face_vert_equivalent.size(); i<len;){
+ if(face_vert_sortable[i].vert == processed_vert_sortable[processed_vert_index].vert){
+ face_vert_equivalent[face_vert_sortable[i].id] = processed_vert_sortable[processed_vert_index].id;
+ ++i;
+ } else {
+ ++processed_vert_index;
+ }
+ if(processed_vert_index >= processed_vert_sortable.size()){
+ break;
+ }
+ }
+
+ std::vector<EdgeSortable> face_edge_sortable;
+ face_edge_sortable.resize(model.faces.size());
+ for(int face_index=0, face_id=0, len=(int)model.faces.size(); face_index<len; ++face_id, face_index += 3){
+ for(int i=0; i<3; ++i){
+ face_edge_sortable[face_index+i].id = face_id;
+ face_edge_sortable[face_index+i].verts[0] = face_vert_equivalent[model.faces[face_index+i]];
+ face_edge_sortable[face_index+i].verts[1] = face_vert_equivalent[model.faces[face_index+((i+1)%3)]];
+ }
+ }
+ std::vector<EdgeSortable> half_edge_sortable;
+ half_edge_sortable.resize(half_edges.size());
+ for(int half_edge_id=0, len=(int)half_edges.size(); half_edge_id<len; ++half_edge_id){
+ half_edge_sortable[half_edge_id].id = half_edge_id;
+ for(int i=0; i<2; ++i){
+ half_edge_sortable[half_edge_id].verts[i] = half_edges[half_edge_id].vert[i];
+ }
+ }
+ std::sort(face_edge_sortable.begin(), face_edge_sortable.end(), SortEdgeSortable);
+ std::sort(half_edge_sortable.begin(), half_edge_sortable.end(), SortEdgeSortable);
+ half_edge_face.resize(half_edges.size(), -1);
+ unsigned face_index=0;
+ for(size_t i=0, len=half_edges.size(); i<len;){
+ if(half_edge_sortable[i].verts[0] == face_edge_sortable[face_index].verts[0]){
+ if(half_edge_sortable[i].verts[1] == face_edge_sortable[face_index].verts[1]){
+ half_edge_face[half_edge_sortable[i].id] = face_edge_sortable[face_index].id;
+ ++i;
+ } else {
+ ++face_index;
+ }
+ } else {
+ ++face_index;
+ }
+ if(face_index >= face_edge_sortable.size()){
+ break;
+ }
+ }
+}
+
+static int LogWindowCallback(ImGuiInputTextCallbackData *data) {
+ uint32_t* selection_out = static_cast<uint32_t*>(data->UserData);
+
+ selection_out[0] = data->SelectionStart;
+ selection_out[1] = data->SelectionEnd;
+ return 0;
+}
+
+static void ParseSpawner() {
+ spawner_tabs.clear();
+
+ const std::vector<ModInstance*>& mods = ModLoading::Instance().GetMods();
+
+ for( unsigned i = 0; i < mods.size(); i++ ) {
+ if( mods[i]->IsActive() ) {
+ for( unsigned u = 0; u < mods[i]->items.size(); u++ ) {
+ const ModInstance::Item& item = mods[i]->items[u];
+
+ SpawnerTab* spawner_tab_ptr = &spawner_tabs[std::string(item.category)];
+ spawner_tab_ptr->resize(spawner_tab_ptr->size()+1);
+
+ SpawnerItem* new_item = &spawner_tab_ptr->back();
+
+ new_item->mod_source_title = mods[i]->name;
+ new_item->display_name = item.title;
+ new_item->path = item.path;
+ new_item->thumbnail_path = item.thumbnail;
+
+ spawner_tab_filters.insert(make_pair(std::string(item.category), ImGuiTextFilter()));
+ }
+ }
+ }
+}
+
+void InitImGui() {
+ ImGui::CreateContext();
+ ImPlot::CreateContext();
+ ImGui_ImplSdlGL3_Init(Graphics::Instance()->sdl_window_);
+ FormatString(imgui_ini_path, kPathSize, "%simgui.ini", GetWritePath(CoreGameModID).c_str());
+
+ ImGui::GetIO().IniFilename = imgui_ini_path;
+
+ run_parse_spawner_flag = true;
+ InitializeModMenu();
+ imgui_scale = 1.0f + config.GetRef("imgui_scale").toNumber<float>() / 4.0f;
+}
+
+void UpdateImGui() {
+ if( run_parse_spawner_flag ) {
+ ParseSpawner();
+ run_parse_spawner_flag = false;
+ }
+}
+
+void ReloadImGui() {
+ run_parse_spawner_flag = true;
+}
+
+void DisposeImGui() {
+ ImGui_ImplSdlGL3_Shutdown();
+}
+
+void ProcessEventImGui(SDL_Event* event) {
+ ImGui_ImplSdlGL3_ProcessEvent(event);
+}
+
+void DrawImGuiCameraPreview(Engine* engine, SceneGraph* scenegraph_ ,Graphics* graphics ) {
+ Object* cam_object = scenegraph_->map_editor->GetSelectedCameraObject();
+ if(cam_object && (!graphics->queued_screenshot || cam_object->Selected()) && ActiveCameras::Instance()->Get()->GetFlags() != Camera::kPreviewCamera){
+ ImGui::SetNextWindowSizeConstraints(ImVec2(320.0f, 240.0f), ImVec2(FLT_MAX, FLT_MAX));
+ if (ImGui::Begin("Camera Preview", NULL, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoFocusOnAppearing)){
+ PushGPUProfileRange("Draw in-game camera");
+ bool media_mode = graphics->media_mode();
+ graphics->SetMediaMode(true);
+
+ ((PlaceholderObject*)cam_object)->SetVisible(false);
+ cam_object->editor_visible = false;
+ float top_left[2], bottom_right[2];
+ {
+ ImVec2 pos = ImGui::GetWindowPos();
+ ImVec2 window_content_min = ImGui::GetWindowContentRegionMin();
+ ImVec2 window_content_max = ImGui::GetWindowContentRegionMax();
+ top_left[0] = pos.x + window_content_min.x;
+ top_left[1] = pos.y + window_content_min.y;
+ bottom_right[0] = pos.x+window_content_max.x;
+ bottom_right[1] = pos.y+window_content_max.y;
+ }
+ if(bottom_right[0] > top_left[0] && bottom_right[1] > top_left[1]) {
+ {
+
+ ImVec2 size(bottom_right[0] - top_left[0], bottom_right[1] - top_left[1]);
+ ImVec2 uv0(top_left[0] / graphics->render_dims[0], 1.0f - top_left[1] / graphics->render_dims[1]);
+ ImVec2 uv1(bottom_right[0] / graphics->render_dims[0], 1.0f - bottom_right[1] / graphics->render_dims[1]);
+ ImGui::Image((ImTextureID)Textures::Instance()->returnTexture(Graphics::Instance()->post_effects.temp_screen_tex), size, uv0, uv1);
+ }
+
+ top_left[0] /= graphics->window_dims[0];
+ top_left[1] /= graphics->window_dims[1];
+ top_left[1] = 1.0f - top_left[1];
+ bottom_right[0] /= graphics->window_dims[0];
+ bottom_right[1] /= graphics->window_dims[1];
+ bottom_right[1] = 1.0f - bottom_right[1];
+ std::swap(top_left[1], bottom_right[1]);
+
+ if(graphics->queued_screenshot && cam_object->Selected()) {
+ top_left[0] = 0.0f;
+ top_left[1] = 0.0f;
+ bottom_right[0] = 1.0f;
+ bottom_right[1] = 1.0f;
+ }
+
+ engine->active_screen_start[0] = clamp(top_left[0], 0.0f, 1.0f);
+ engine->active_screen_start[1] = clamp(top_left[1], 0.0f, 1.0f);
+ engine->active_screen_end[0] = clamp(bottom_right[0], 0.0f, 1.0f);
+ engine->active_screen_end[1] = clamp(bottom_right[1], 0.0f, 1.0f);
+
+ graphics->startDraw(vec2(top_left[0], top_left[1]),
+ vec2(bottom_right[0], bottom_right[1]),
+ Graphics::kWindow);
+ ActiveCameras::Set(2);
+ ActiveCameras::Get()->SetFlags(Camera::kPreviewCamera);
+
+ // Apply camera object settings to camera
+ Camera* camera = ActiveCameras::Instance()->Get();
+
+ //static Camera prev_camera = *camera;
+ //LOGI << "First camera diff" << std::endl;
+ //camera->PrintDifferences(prev_camera);
+ //prev_camera = *camera;
+
+ // Set camera position
+ camera->SetPos(cam_object->GetTranslation());
+ // Set camera euler angles from rotation matrix
+ const mat4 &rot = Mat4FromQuaternion(cam_object->GetRotation());
+ vec3 front = rot * vec3(0,0,1);
+ camera->SetYRotation(atan2f(front[0], front[2])*180.0f/PI_f);
+ camera->SetXRotation(asinf(front[1])*-180.0f/PI_f);
+ vec3 up = rot * vec3(0,1,0);
+ vec3 expected_right = normalize(cross(front, vec3(0,1,0)));
+ vec3 expected_up = normalize(cross(expected_right, front));
+ camera->SetZRotation(atan2f(dot(up,expected_right), dot(up, expected_up))*180.0f/PI_f);
+ // Set camera zoom from scale
+ const float zoom_sensitivity = 3.5f;
+ camera->SetFOV(min(150.0f, 90.0f / max(0.001f,(1.0f+(cam_object->GetScale()[0]-1.0f)*zoom_sensitivity))));
+ camera->SetDistance(0.0f);
+
+ scenegraph_->level->Message("request_preview_dof");
+
+ // Draw view
+ engine->DrawScene(Engine::kViewport, Engine::kFinal, SceneGraph::kStaticAndDynamic);
+
+ if(!graphics->queued_screenshot){
+ camera->DrawSafeZone();
+ }
+
+ graphics->bindFramebuffer(graphics->post_effects.post_framebuffer);
+ graphics->framebufferColorTexture2D(Graphics::Instance()->post_effects.temp_screen_tex);
+ graphics->bindFramebuffer(0);
+ CHECK_FBO_ERROR();
+ CHECK_GL_ERROR();
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, graphics->post_effects.post_framebuffer);
+ glReadBuffer(GL_BACK);
+ glDrawBuffer(GL_COLOR_ATTACHMENT0);
+ glBlitFramebuffer(0, 0, graphics->window_dims[0], graphics->window_dims[1], 0, 0, graphics->render_dims[0], graphics->render_dims[1], GL_COLOR_BUFFER_BIT, GL_NEAREST);
+ glBindFramebuffer(GL_FRAMEBUFFER, graphics->curr_framebuffer);
+ CHECK_FBO_ERROR();
+ CHECK_GL_ERROR();
+
+ ((PlaceholderObject*)cam_object)->SetVisible(true);
+ cam_object->editor_visible = true;
+ graphics->SetMediaMode(media_mode);
+ PopGPUProfileRange();
+ }
+ }
+ ImGui::End();
+ }
+}
+
+bool DrawBrowsePath(const char* label, const char* current_path, const char* extensions, int extension_count, char* out_buffer, int out_buffer_size, const char* start_dir = NULL) {
+ bool change = false;
+ ImGui::PushID(label);
+ char path_buffer[kPathSize];
+ if(ImGui::Button("Browse...")) {
+ Dialog::DialogErr err = Dialog::readFile(extensions, extension_count, start_dir ? start_dir : "Data", path_buffer, kPathSize);
+ if( err != Dialog::NO_ERR ){
+ LOGE << Dialog::DialogErrString( err ) << std::endl;
+ } else {
+ change = true;
+ }
+ } else {
+ int copy = strscpy(path_buffer, current_path, kPathSize);
+ if(copy != 0 && copy != SOURCE_IS_NULL) {
+ LOGE << "Path is too long for buffer. Contact developers and attach log file\n" << GenerateStacktrace() << std::endl;
+ LOGE << "Path is " << current_path << std::endl;
+ }
+ assert(copy == 0 || copy == SOURCE_IS_NULL);
+ }
+ ImGui::SameLine();
+ if(ImGui::InputText(label, path_buffer, kPathSize, ImGuiInputTextFlags_EnterReturnsTrue)) {
+ change = true;
+ }
+ if(change) {
+ const char* data = strstr(path_buffer, "Data/");
+ if(!data) {
+ DisplayError("Error", "Path must include 'Data' folder");
+ change = false;
+ } else {
+ if(!FindFilePath(data, kDataPaths | kModPaths, false).isValid()) {
+ DisplayError("Error", "Path must be inside Overgrowth data directory");
+ change = false;
+ } else {
+ int copy = strscpy(out_buffer, data, out_buffer_size);
+ if(copy != 0 && copy != SOURCE_IS_NULL) {
+ LOGE << "Path is too long for buffer. Contact developers and attach log file\n" << GenerateStacktrace() << std::endl;
+ LOGE << "Path is " << current_path << std::endl;
+ }
+ assert(copy == 0 || copy == SOURCE_IS_NULL);
+ }
+ }
+ }
+ ImGui::PopID();
+ return change;
+}
+
+static void ResizeCallback(ImGuiSizeCallbackData* data) {
+ static float last_x = 0.0f;
+ static float last_y = 0.0f;
+
+ float max_height = std::min(data->CurrentSize.y, Graphics::Instance()->window_dims[1] - 29.0f);
+
+ float increase_x = (data->CurrentSize.x - last_x) * 0.1f;
+ float increase_y = (max_height - last_y) * 0.1f;
+
+ if(increase_x < 1.0f && increase_x > -1.0f) {
+ increase_x = data->CurrentSize.x - last_x;
+ }
+ if(increase_y < 1.0f && increase_y > -1.0f) {
+ increase_y = max_height - last_y;
+ }
+
+ data->DesiredSize = ImVec2(last_x + increase_x, last_y + increase_y);
+
+ last_x = data->DesiredSize.x;
+ last_y = data->DesiredSize.y;
+}
+
+void DrawImGui(Graphics* graphics, SceneGraph* scenegraph, GUI* gui, AssetManager* assetmanager, Engine* engine, bool cursor_visible) {
+ Online* online = Online::Instance();
+
+ ImGui::GetIO().FontGlobalScale = imgui_scale;
+ ImGui_ImplSdlGL3_NewFrame(graphics->sdl_window_, Input::Instance()->GetGrabMouse());
+ ImGui::GetIO().MouseDrawCursor = cursor_visible;
+
+ if(engine->check_save_level_changes_dialog_is_showing) {
+ bool dialog_finished = false;
+ bool execute_save = false;
+ bool continue_action = false;
+
+ if( false == scenegraph->map_editor->WasLastSaveOnCurrentUndoChunk() || scenegraph->level->isMetaDataDirty() ){
+ ImGui::OpenPopup("Check Save Changes");
+
+ ImVec2 center(ImGui::GetIO().DisplaySize.x * 0.5f, ImGui::GetIO().DisplaySize.y * 0.5f);
+ ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
+
+ if (ImGui::BeginPopupModal("Check Save Changes", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize)) {
+ ImGui::Text("Save changes to the level before closing it?");
+ ImGui::Separator();
+
+ if(ImGui::Button("Yes", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); continue_action = true; execute_save = true; dialog_finished = true; }
+ ImGui::SetItemDefaultFocus();
+
+ ImGui::SameLine();
+ if(ImGui::Button("No", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); continue_action = true; execute_save = false; dialog_finished = true; }
+
+ ImGui::SameLine();
+ if(ImGui::Button("Cancel", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); dialog_finished = true; }
+
+ if(ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Escape))) { ImGui::CloseCurrentPopup(); dialog_finished = true; }
+
+ ImGui::EndPopup();
+ } else {
+ dialog_finished = true;
+ }
+
+ } else {
+ // Continue on immediately without showing dialog
+ continue_action = true;
+ dialog_finished = true;
+ }
+
+ if(dialog_finished) {
+ if(execute_save) {
+ scenegraph->map_editor->ExecuteSaveLevelChanges();
+ }
+
+ engine->check_save_level_changes_dialog_is_finished = true;
+ engine->check_save_level_changes_dialog_is_showing = false;
+ engine->check_save_level_changes_last_result = continue_action;
+ }
+
+ return;
+ }
+
+ const bool kDisplayKeys = false;
+ if(kDisplayKeys){
+ {
+ const int kBufSize = 256;
+ char buf[kBufSize] = {'\0'};
+ FormatString(buf, kBufSize, "Keys(ImGui): ");
+ char temp[kBufSize];
+ for(int i=0; i<512; ++i){
+ if(ImGui::IsKeyDown(i)){
+ int val = i;
+ if(val > SDLK_z){
+ val = val + SDLK_CAPSLOCK - 1 - SDLK_z;
+ }
+ FormatString(temp, kBufSize, "%s%s ", buf, SDLKeycodeToString(val));
+ FormatString(buf, kBufSize, "%s", temp);
+ }
+ }
+ gui->AddDebugText("keys_imgui", buf, 0.5f);
+ }
+
+ {
+ const int kBufSize = 256;
+ char buf[kBufSize] = {'\0'};
+ char temp[kBufSize];
+ FormatString(buf, kBufSize, "Keys(game): ");
+ Keyboard::KeyStatusMap* keys = &Input::Instance()->getKeyboard().keys;
+ for(Keyboard::KeyStatusMap::iterator iter = keys->begin(); iter != keys->end(); ++iter){
+ if(iter->second.down){
+ FormatString(temp, kBufSize, "%s%s ", buf, SDLKeycodeToString(iter->first));
+ FormatString(buf, kBufSize, "%s", temp);
+ }
+ }
+ gui->AddDebugText("keys_game", buf, 0.5f);
+ }
+ }
+
+ if(!scenegraph || scenegraph->map_editor->state_ != MapEditor::kInGame){
+ if (ImGui::BeginMainMenuBar())
+ {
+ if(!scenegraph){
+ if (ImGui::BeginMenu("File"))
+ {
+ if (ImGui::MenuItem("New Level", KeyCommand::GetDisplayText(KeyCommand::kNewLevel))) {
+ Engine::NewLevel();
+ }
+ if (ImGui::MenuItem("Open Level...", KeyCommand::GetDisplayText(KeyCommand::kOpenLevel))) {
+ LoadLevel(false);
+ }
+ if(ImGui::MenuItem("Open Local Level...", "")) {
+ LoadLevel(true);
+ }
+ OpenLevelMenu();
+ OpenRecentMenu();
+ ImGui::Separator();
+ if (ImGui::MenuItem("Quit", KeyCommand::GetDisplayText(KeyCommand::kQuit))) {
+ Input::Instance()->RequestQuit();
+ }
+ ImGui::EndMenu();
+ }
+ if (ImGui::BeginMenu("Settings")) {
+ DrawSettingsImGui(scenegraph, IMGST_MENU);
+ ImGui::EndMenu();
+ }
+ }
+ if(scenegraph){
+ MapEditor* me = scenegraph->map_editor;
+
+ // TODO: only enable items that are relevant to what we can do now
+ /*
+ if(gui->ribbon_gui_){
+ // Update ribbon items
+ uint32_t* ribbon_flags = &gui->ribbon_gui_->buttons_enabled[0];
+ SetBit(ribbon_flags, RibbonGUI::decal_editor_active, IsTypeEnabled(_decal_object));
+ SetBit(ribbon_flags, RibbonGUI::hotspots_editor_active, IsTypeEnabled(_hotspot_object));
+ SetBit(ribbon_flags, RibbonGUI::objects_editor_active, IsTypeEnabled(_env_object));
+ SetBit(ribbon_flags, RibbonGUI::lights_editor_active, IsTypeEnabled(_dynamic_light_object));
+ SetBit(ribbon_flags, RibbonGUI::something_selected, IsSomethingSelected());
+ SetBit(ribbon_flags, RibbonGUI::one_object_selected, IsOneObjectSelected());
+ SetBit(ribbon_flags, RibbonGUI::can_undo, CanUndo());
+ SetBit(ribbon_flags, RibbonGUI::can_redo, CanRedo());
+ SetBit(ribbon_flags, RibbonGUI::show_probes, scenegraph_->light_probe_collection.show_probes);
+ SetBit(ribbon_flags, RibbonGUI::show_probes_through_walls, scenegraph_->light_probe_collection.show_probes_through_walls);
+ SetBit(ribbon_flags, RibbonGUI::probe_lighting_enabled, scenegraph_->light_probe_collection.probe_lighting_enabled);
+ SetBit(ribbon_flags, RibbonGUI::show_tet_mesh, scenegraph_->light_probe_collection.tet_mesh_viz_enabled);
+
+ uint32_t* ribbon_toggle = &gui->ribbon_gui_->buttons_toggled[0];
+ SetBit(ribbon_toggle, RibbonGUI::view_nav_mesh, IsViewingNavMesh());
+ SetBit(ribbon_toggle, RibbonGUI::view_collision_nav_mesh, IsViewingCollisionNavMesh());
+ SetBit(ribbon_toggle, RibbonGUI::view_nav_mesh_hints, type_enable_.IsTypeEnabled( _navmesh_hint_object ));
+ SetBit(ribbon_toggle, RibbonGUI::view_nav_mesh_region, type_enable_.IsTypeEnabled( _navmesh_region_object ));
+ SetBit(ribbon_toggle, RibbonGUI::view_nav_mesh_jump_nodes, type_enable_.IsTypeEnabled( _navmesh_connection_object ));
+ }
+ */
+ if (ImGui::BeginMenu("File"))
+ {
+ if (ImGui::MenuItem("New Level", KeyCommand::GetDisplayText(KeyCommand::kNewLevel))) {
+ Engine::NewLevel();
+ }
+ if (ImGui::MenuItem("Open Level...", KeyCommand::GetDisplayText(KeyCommand::kOpenLevel))) {
+ LoadLevel(false);
+ }
+ if (ImGui::MenuItem("Open Local Level...")) {
+ LoadLevel(true);
+ }
+ OpenLevelMenu();
+ OpenRecentMenu();
+ if (ImGui::MenuItem("Save", KeyCommand::GetDisplayText(KeyCommand::kSaveLevel), false, !me->GetTerrainPreviewMode())) {
+ me->SaveLevel(LevelLoader::kSaveInPlace);
+ }
+ if (ImGui::MenuItem("Save As..", KeyCommand::GetDisplayText(KeyCommand::kSaveLevelAs), false, !me->GetTerrainPreviewMode())) {
+ me->SaveLevel(LevelLoader::kSaveAs);
+ }
+
+ ImGui::Separator();
+ if (ImGui::MenuItem("Back to main menu")) {
+ if(scenegraph){
+ Engine::Instance()->ScriptableUICallback("back_to_main_menu");
+ }
+ }
+ if (ImGui::MenuItem("Quit", KeyCommand::GetDisplayText(KeyCommand::kQuit))) {
+ Input::Instance()->RequestQuit();
+ }
+ ImGui::EndMenu();
+ }
+
+ if (ImGui::BeginMenu("Edit"))
+ {
+ if (ImGui::MenuItem("Undo", KeyCommand::GetDisplayText(KeyCommand::kUndo), false, !me->GetTerrainPreviewMode())) {
+ me->Undo();
+ }
+ if (ImGui::MenuItem("Redo", KeyCommand::GetDisplayText(KeyCommand::kRedo), false, !me->GetTerrainPreviewMode())) {
+ me->Redo();
+ }
+ ImGui::Separator();
+ if (ImGui::MenuItem("Cut", KeyCommand::GetDisplayText(KeyCommand::kCut))) {
+ me->CutSelected();
+ }
+ if (ImGui::MenuItem("Copy", KeyCommand::GetDisplayText(KeyCommand::kCopy))) {
+ me->CopySelected();
+ }
+ if (ImGui::MenuItem("Paste", KeyCommand::GetDisplayText(KeyCommand::kPaste))) {
+ me->RibbonItemClicked("paste", true);
+ }
+ ImGui::Separator();
+
+ if (ImGui::MenuItem("Select All", KeyCommand::GetDisplayText(KeyCommand::kSelectAll))) {
+ me->SelectAll();
+ }
+ if (ImGui_TooltipMenuItem("Deselect All", KeyCommand::GetDisplayText(KeyCommand::kDeselectAll), "Deselect everything in the scenegraph")) {
+ me->DeselectAll(scenegraph);
+ }
+ ImGui::Separator();
+
+ if(ImGui_TooltipMenuItem("Reload All Prefabs", NULL, "Reload all prefabs from their source file, assuming they are locked")) {
+ me->ReloadAllPrefabs();
+ }
+ ImGui::Separator();
+
+ if(ImGui::MenuItem("Set Level Params...")) {
+ show_scenegraph = true;
+ select_scenegraph_level_params = true;
+ }
+ if(ImGui::MenuItem("Set Level Script...")){
+ me->RibbonItemClicked("level_script", true);
+ }
+ if(ImGui::MenuItem("Set Sky Texture...")){
+ Input::Instance()->ignore_mouse_frame = true;
+ const int BUF_SIZE = 512;
+ char buffer[BUF_SIZE];
+
+ // Display a file dialog to the user
+ Dialog::DialogErr err = Dialog::readFile("tga\0png\0jpg\0dds", 4,"Data/Textures/skies/", buffer, BUF_SIZE);
+ if( err != Dialog::NO_ERR ){
+ LOGE << Dialog::DialogErrString( err ) << std::endl;
+ } else {
+ const char* find_str[2] = {"data/", "DATA\\"};
+ int found_match = -1;
+ for(size_t i=0, len=strlen(buffer); i<len-5; ++i){
+ bool match = true;
+ for(size_t j=0; j<5; ++j){
+ if(buffer[i+j] != find_str[0][j] &&
+ buffer[i+j] != find_str[1][j] )
+ {
+ match = false;
+ break;
+ }
+ }
+ if(match){
+ found_match = (int)i;
+ break;
+ }
+ }
+ if(found_match != -1){
+ scenegraph->sky->dome_texture_name = &buffer[found_match];
+ scenegraph->sky->LightingChanged(scenegraph->terrain_object_ != NULL);
+ } else {
+ DisplayError("Error", "Path must include 'Data' folder");
+ }
+ }
+ }
+ if(ImGui::MenuItem("Set Player Control Script...")){
+ const int BUF_SIZE = 512;
+ char buffer[BUF_SIZE];
+ Dialog::DialogErr err = Dialog::readFile("as",1,"Data/Scripts", buffer, BUF_SIZE);
+ if( err != Dialog::NO_ERR ){
+ LOGE << Dialog::DialogErrString( err ) << std::endl;
+ } else {
+ std::string scriptName = SplitPathFileName( buffer ).second;
+ scenegraph->level->SetPCScript( scriptName );
+ }
+ }
+ if(ImGui::BeginMenu("Player control script")) {
+ std::string pc_script = scenegraph->level->GetPCScript(NULL);
+ if(pc_script == Level::DEFAULT_PLAYER_SCRIPT) {
+ ImGui::Text("%s (default)", pc_script.c_str());
+ } else {
+ ImGui::Text("%s (custom)", pc_script.c_str());
+ }
+ if(ImGui::MenuItem("Change script...")){
+ const int BUF_SIZE = 512;
+ char buffer[BUF_SIZE];
+ Dialog::DialogErr err = Dialog::readFile("as",1,"Data/Scripts", buffer, BUF_SIZE);
+ if( err != Dialog::NO_ERR ){
+ LOGE << Dialog::DialogErrString( err ) << std::endl;
+ } else {
+ std::string script_name = SplitPathFileName( buffer ).second;
+ for(size_t i = 0; i < scenegraph->movement_objects_.size(); ++i) {
+ MovementObject* obj = (MovementObject*)scenegraph->movement_objects_[i];
+ std::string old_script = scenegraph->level->GetPCScript(obj);
+ if(obj->object_pc_script_path.empty() && obj->GetCurrentControlScript() == old_script) {
+ obj->ChangeControlScript( script_name );
+ }
+ }
+ if(script_name != Level::DEFAULT_PLAYER_SCRIPT)
+ scenegraph->level->SetPCScript( script_name );
+ else
+ scenegraph->level->ClearPCScript();
+ }
+ }
+ if(ImGui::MenuItem("Clear script")) {
+ for(size_t i = 0; i < scenegraph->movement_objects_.size(); ++i) {
+ MovementObject* obj = (MovementObject*)scenegraph->movement_objects_[i];
+ std::string old_script = scenegraph->level->GetPCScript(obj);
+ if(obj->object_pc_script_path.empty() && obj->GetCurrentControlScript() == old_script) {
+ obj->ChangeControlScript( obj->GetActorScript().empty() ? Level::DEFAULT_PLAYER_SCRIPT : obj->GetActorScript() );
+ }
+ }
+ scenegraph->level->ClearPCScript();
+ }
+ ImGui::EndMenu();
+ }
+ if(ImGui::BeginMenu("Enemy control script")) {
+ std::string npc_script = scenegraph->level->GetNPCScript(NULL);
+ if(npc_script == Level::DEFAULT_ENEMY_SCRIPT) {
+ ImGui::Text("%s (default)", npc_script.c_str());
+ } else {
+ ImGui::Text("%s (custom)", npc_script.c_str());
+ }
+ if(ImGui::MenuItem("Change script...")){
+ const int BUF_SIZE = 512;
+ char buffer[BUF_SIZE];
+ Dialog::DialogErr err = Dialog::readFile("as",1,"Data/Scripts", buffer, BUF_SIZE);
+ if( err != Dialog::NO_ERR ){
+ LOGE << Dialog::DialogErrString( err ) << std::endl;
+ } else {
+ std::string script_name = SplitPathFileName( buffer ).second;
+ for(size_t i = 0; i < scenegraph->movement_objects_.size(); ++i) {
+ MovementObject* obj = (MovementObject*)scenegraph->movement_objects_[i];
+ std::string old_script = scenegraph->level->GetNPCScript(obj);
+ if(obj->object_npc_script_path.empty() && obj->GetCurrentControlScript() == old_script) {
+ obj->ChangeControlScript( script_name );
+ }
+ }
+ if(script_name != Level::DEFAULT_ENEMY_SCRIPT)
+ scenegraph->level->SetNPCScript( script_name );
+ else
+ scenegraph->level->ClearNPCScript();
+ }
+ }
+ if(ImGui::MenuItem("Clear script")) {
+ for(size_t i = 0; i < scenegraph->movement_objects_.size(); ++i) {
+ MovementObject* obj = (MovementObject*)scenegraph->movement_objects_[i];
+ std::string old_script = scenegraph->level->GetNPCScript(obj);
+ if(obj->object_npc_script_path.empty() && obj->GetCurrentControlScript() == old_script) {
+ obj->ChangeControlScript( obj->GetActorScript().empty() ? Level::DEFAULT_ENEMY_SCRIPT : obj->GetActorScript() );
+ }
+ }
+ scenegraph->level->ClearNPCScript();
+ }
+ ImGui::EndMenu();
+ }
+ ImGui::Separator();
+ bool temp = me->IsTypeEnabled(_env_object);
+ if(ImGui::Checkbox("Edit static meshes", &temp)){
+ me->RibbonItemClicked("objecteditoractive", temp);
+ }
+ temp = me->IsTypeEnabled(_decal_object);
+ if(ImGui::Checkbox("Edit decals", &temp)){
+ me->RibbonItemClicked("decaleditoractive", temp);
+ }
+ temp = me->IsTypeEnabled(_hotspot_object);
+ if(ImGui::Checkbox("Edit gameplay objects", &temp)){
+ me->RibbonItemClicked("hotspoteditoractive", temp);
+ }
+ temp = me->IsTypeEnabled(_dynamic_light_object);
+ if(ImGui::Checkbox("Edit lighting", &temp)){
+ me->RibbonItemClicked("lighteditoractive", temp);
+ }
+ ImGui::Separator();
+ if (ImGui::MenuItem("Play level", "8")) {
+ me->RibbonItemClicked("sendinrabbot", true);
+ }
+ if (ImGui::MenuItem("Media mode")) {
+ Graphics::Instance()->SetMediaMode(true);
+ }
+ ImGui::EndMenu();
+ }
+
+ if( ImGui::BeginMenu("View") ) {
+ bool temp = scenegraph->IsNavMeshVisible();
+ if(ImGui::Checkbox("Nav mesh", &temp)){
+ scenegraph->SetNavMeshVisible(temp);
+ }
+ temp = scenegraph->IsCollisionNavMeshVisible();
+ if(ImGui::Checkbox("Nav mesh collision", &temp)){
+ scenegraph->SetCollisionNavMeshVisible(temp);
+ }
+ ImGui::Checkbox("Collision paint visualization", &g_draw_collision);
+
+ ImGui::Separator();
+
+ ImGui::BeginDisabled(!me->GameplayObjectsEnabled());
+ temp = me->IsTypeEnabled(_navmesh_hint_object);
+ if(ImGui::Checkbox("Nav hints", &temp)){
+ me->SetTypeEnabled(_navmesh_hint_object, temp);
+ me->SetTypeVisible(_navmesh_hint_object, temp);
+ }
+
+ temp = me->IsTypeEnabled(_navmesh_region_object);
+ if(ImGui::Checkbox("Nav regions", &temp)){
+ me->SetTypeEnabled(_navmesh_region_object, temp);
+ me->SetTypeVisible(_navmesh_region_object, temp);
+ }
+
+ temp = me->IsTypeEnabled(_navmesh_connection_object);
+ if(ImGui::Checkbox("Jump nodes", &temp)){
+ me->SetTypeEnabled(_navmesh_connection_object, temp);
+ me->SetTypeVisible(_navmesh_connection_object, temp);
+ }
+ ImGui::EndDisabled();
+
+ ImGui::Separator();
+ ImGui::Checkbox("Invisible objects", &g_make_invisible_visible);
+ ImGui::Checkbox("Draw boxes around groups and prefabs", &draw_group_and_prefab_boxes);
+ ImGui::Checkbox("Always show hotspot connections", &always_draw_hotspot_connections);
+
+ ImGui::EndMenu();
+ }
+
+ std::vector<Object*> selected;
+ scenegraph->ReturnSelected(&selected);
+
+ if (ImGui::BeginMenu("Selected", selected.size() > 0)) {
+ std::vector<Object*> selected;
+ scenegraph->ReturnSelected(&selected);
+ bool any_selected_object_has_custom_gui = false;
+ bool selected_items_changed = false;
+
+ for(unsigned selected_i = 0; selected_i < selected.size(); ++selected_i) {
+ if(selected[selected_i]->GetType() == _hotspot_object) {
+ Hotspot* hotspot = (Hotspot*)selected[selected_i];
+
+ if(hotspot->HasCustomGUI()) {
+ any_selected_object_has_custom_gui = true;
+ break;
+ }
+ } else if(selected[selected_i]->GetType() == _placeholder_object) {
+ PlaceholderObject* placeholder = (PlaceholderObject*)selected[selected_i];
+
+ if(placeholder->GetScriptParams()->HasParam("Dialogue")) {
+ any_selected_object_has_custom_gui = true;
+ break;
+ }
+ }
+ }
+
+ if(!any_selected_object_has_custom_gui) {
+ ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
+ ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
+ }
+
+ if(ImGui::MenuItem("Open Custom Editor", KeyCommand::GetDisplayText(KeyCommand::kOpenCustomEditor))) {
+ LOGI << "Launch custom editor(s)" << std::endl;
+ scenegraph->level->Message("edit_selected_dialogue");
+
+ for(unsigned selected_i = 0; selected_i < selected.size(); ++selected_i) {
+ if(selected[selected_i]->GetType() == _hotspot_object) {
+ Hotspot* hotspot = (Hotspot*)selected[selected_i];
+
+ if(hotspot->HasCustomGUI()) {
+ hotspot->LaunchCustomGUI();
+ }
+ }
+ }
+ }
+
+ if(!any_selected_object_has_custom_gui) {
+ ImGui::PopItemFlag();
+ ImGui::PopStyleVar();
+ }
+
+ if(ImGui::MenuItem("Go To Selected", KeyCommand::GetDisplayText(KeyCommand::kFrameSelected))) {
+ CameraObject *co = ActiveCameras::Instance()->Get()->getCameraObject();
+ if( co ) {
+ co->FrameSelection(true);
+ }
+ }
+
+ ImGui::Separator();
+ if (ImGui::MenuItem("Delete", "Backspace")) {
+ me->DeleteSelected();
+ selected_items_changed = true;
+ }
+ if(ImGui::MenuItem("Replace mesh...")) {
+ Input::Instance()->ignore_mouse_frame = true;
+ const int BUF_SIZE = 512;
+ char buf[BUF_SIZE];
+ Dialog::DialogErr err = Dialog::readFile("xml",1,"Data/Objects",buf,BUF_SIZE);
+ if(!err){
+ //Un-absolute the file and make it relative to the "best" candidate for relative reference.
+ std::string shortened = FindShortestPath(std::string(buf));
+ scenegraph->map_editor->ReplaceObjects(selected, shortened);
+ }
+ selected_items_changed = true;
+ }
+
+ ImGui::Separator();
+ if (ImGui::MenuItem("Group", KeyCommand::GetDisplayText(KeyCommand::kGroup))) {
+ me->GroupSelected();
+ selected_items_changed = true;
+ }
+
+ if (ImGui::MenuItem("Ungroup", KeyCommand::GetDisplayText(KeyCommand::kUngroup))) {
+ me->RibbonItemClicked("ungroup", true);
+ selected_items_changed = true;
+ }
+
+ // TODO: Reenable?
+ /*ImGui::Separator();
+ if(ImGui::MenuItem("Set Selection Script Params...")) {
+ show_selected = true;
+ select_object_script_params = true;
+ }*/
+
+ ImGui::Separator();
+ if(ImGui_TooltipMenuItem("Save Selection...", KeyCommand::GetDisplayText(KeyCommand::kSaveSelectedItems), "Save selection in to loadable file, not a prefab")){
+ me->SaveSelected();
+ }
+
+ if(ImGui_TooltipMenuItem("Prefab Save", NULL, "Save selection as a prefab object")) {
+ me->SavePrefab(true);
+ selected_items_changed = true;
+ }
+
+ if(ImGui_TooltipMenuItem("Prefab Save As...", NULL,"Save selection as a prefab object in an explicit path")) {
+ me->SavePrefab(false);
+ selected_items_changed = true;
+ }
+
+ ImGui::Separator();
+
+ if(selected_items_changed) {
+ selected.clear();
+ scenegraph->ReturnSelected(&selected);
+ }
+
+ bool any_movement_objects_selected = false;
+ for(unsigned selected_i = 0; selected_i < selected.size(); ++selected_i) {
+ if(selected[selected_i]->GetType() == _movement_object) {
+ any_movement_objects_selected = true;
+ break;
+ }
+ }
+
+ if(!any_movement_objects_selected) {
+ ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
+ ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
+ }
+
+ if(ImGui_TooltipMenuItem("Make Character(s) Corpse", KeyCommand::GetDisplayText(KeyCommand::kMakeSelectedCharacterSavedCorpse), "Save selected characters' current corpse positions (and knock them out, if awake)")) {
+ scenegraph->level->Message("make_selected_character_saved_corpse");
+ }
+
+ if(ImGui_TooltipMenuItem("Revive Character Corpse(s)", KeyCommand::GetDisplayText(KeyCommand::kReviveSelectedCharacterAndUnsaveCorpse), "Wipe selected characters' saved corpse positions (and revive them, if knocked out)")) {
+ scenegraph->level->Message("revive_selected_character_and_unsave_corpse");
+ }
+
+ if(!any_movement_objects_selected) {
+ ImGui::PopItemFlag();
+ ImGui::PopStyleVar();
+ }
+
+ ImGui::EndMenu();
+ }
+
+ if (ImGui::BeginMenu("Load", "")) {
+ if (ImGui::MenuItem("Load item...", "")) {
+ Input::Instance()->ignore_mouse_frame = true;
+ const int BUF_SIZE = 512;
+ char buf[BUF_SIZE];
+ Dialog::DialogErr err = Dialog::readFile("xml",1,"Data/Objects",buf,BUF_SIZE);
+ if(!err){
+ //Un-absolute the file and make it relative to the "best" candidate for relative reference.
+ std::string shortened = FindShortestPath(std::string(buf));
+ if(scenegraph->map_editor->state_ == MapEditor::kIdle && scenegraph->map_editor->LoadEntitiesFromFile(shortened)==0){
+ scenegraph->level->PushSpawnerItemRecent(SpawnerItem( "Load Item", SplitPathFileName(shortened).second, shortened, "empty_placeholder.png"));
+ scenegraph->map_editor->active_tool_ = EditorTypes::ADD_ONCE;
+ }
+ }
+ }
+ if( ImGui::BeginMenu("Open Recent...") ) {
+ std::vector<SpawnerItem> spawner_items = scenegraph->level->GetRecentlyCreatedItems();
+
+ for (auto it = spawner_items.rbegin(); it != spawner_items.rend(); it++) {
+ AddSpawnerItem( &(*it), scenegraph);
+ }
+
+ ImGui::EndMenu();
+ }
+ ImGui::Separator();
+
+ /*if (ImGui::IsWindowHovered()) {
+ ImGui::SetKeyboardFocusHere();
+ }*/
+
+ // Not calling filter.Draw() so we can pass InputText flags
+ if (ImGui::InputText("", spawner_global_filter.InputBuf, IM_ARRAYSIZE(spawner_global_filter.InputBuf), ImGuiInputTextFlags_AutoSelectAll)) {
+ spawner_global_filter.Build();
+ }
+
+ if (spawner_global_filter.IsActive()) {
+ for (SpawnerTabMap::iterator tab_iter = spawner_tabs.begin(); tab_iter != spawner_tabs.end(); ++tab_iter) {
+ SpawnerTab* tab = &tab_iter->second;
+
+ for (SpawnerTab::iterator item_iter = tab->begin(); item_iter != tab->end(); ++item_iter) {
+ std::string item_display_name = tab_iter->first + " -> " + item_iter->display_name;
+
+ if (spawner_global_filter.PassFilter( item_iter->mod_source_title.c_str() ) || spawner_global_filter.PassFilter(item_display_name.c_str())) {
+ AddSpawnerItem(&(*item_iter), scenegraph);
+ }
+ }
+ }
+ } else {
+ for (SpawnerTabMap::iterator tab_iter = spawner_tabs.begin(); tab_iter != spawner_tabs.end(); ++tab_iter) {
+ if (ImGui::BeginMenu(tab_iter->first.c_str())) {
+ SpawnerTab* tab = &tab_iter->second;
+ ImGuiTextFilter& current_tab_filter = spawner_tab_filters[tab_iter->first];
+
+ //if (ImGui::IsWindowHovered()) {
+ // ImGui::SetKeyboardFocusHere();
+ //}
+
+ // Not calling filter.Draw() so we can pass InputText flags
+ if (ImGui::InputText("", current_tab_filter.InputBuf, IM_ARRAYSIZE(current_tab_filter.InputBuf), ImGuiInputTextFlags_AutoSelectAll)) {
+ current_tab_filter.Build();
+ }
+
+ for (SpawnerTab::iterator item_iter = tab->begin(); item_iter != tab->end(); ++item_iter) {
+ if (current_tab_filter.PassFilter(item_iter->mod_source_title.c_str()) || current_tab_filter.PassFilter(item_iter->display_name.c_str())) {
+ AddSpawnerItem(&(*item_iter), scenegraph);
+ }
+ }
+ ImGui::EndMenu();
+ }
+ }
+ }
+ ImGui::EndMenu();
+ }
+
+ if (ImGui::BeginMenu("Settings")) {
+ DrawSettingsImGui(scenegraph, IMGST_MENU);
+ ImGui::EndMenu();
+ }
+
+ /*if (ImGui::BeginMenu("Lighting")) {
+ if(ImGui::MenuItem("Calculate GI first pass")){
+ me->RibbonItemClicked("rebake_light_probes", true);
+ }
+ if(ImGui::MenuItem("Calculate GI second pass")){
+ me->RibbonItemClicked("calculate2pass", true);
+ }
+ LightProbeCollection* lpc = &scenegraph->light_probe_collection;
+ if(ImGui::Checkbox("Probe lighting enabled", &lpc->probe_lighting_enabled)){
+ }
+ if(ImGui::Checkbox("Show probes through walls", &lpc->show_probes_through_walls)){
+ }
+ if(ImGui::Checkbox("Show tet mesh", &lpc->tet_mesh_viz_enabled)){
+ me->RibbonItemClicked("show_tet_mesh", lpc->tet_mesh_viz_enabled);
+ }
+ if(ImGui::Checkbox("Show probes", &lpc->show_probes)){
+ me->RibbonItemClicked("show_probes", lpc->show_probes);
+ }
+ ImGui::EndMenu();
+ }*/
+ if (ImGui::BeginMenu("Nav Mesh", !scenegraph->map_editor->GetTerrainPreviewMode())) {
+ NavMeshParameters *nmp = &scenegraph->level->nav_mesh_parameters_;
+ ImGui::Checkbox("Automatically generate navmesh for this level", &nmp->generate);
+
+ ImGui::Separator();
+
+ if(ImGui::MenuItem("Create")){
+ scenegraph->CreateNavMesh();
+ }
+ if(ImGui::MenuItem("Save")){
+ scenegraph->SaveNavMesh();
+ }
+ if(ImGui::MenuItem("Load")){
+ scenegraph->LoadNavMesh();
+ }
+
+ ImGui::Separator();
+ ImGui::Text("%s", "Nav Mesh Parameters");
+
+ if (ImGui::IsItemHovered()) {
+ ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0.5, 0.5, 1.0, 0.5));
+ ImGui::BeginTooltip();
+ ImGui::PushTextWrapPos(450.0f);
+ ImGui::Text("%s", "Don't change these parameters if the defaults work well for you. Changing these values will have an impact on how long generation take, and potential navigational ability of characters. If the initial coverage of the navmesh isn't good for you, i suggest you start off with changing the cell size and height to a lower value.");
+ ImGui::PopTextWrapPos();
+ ImGui::EndTooltip();
+ ImGui::PopStyleColor(1);
+ }
+
+
+ if(ImGui::SliderFloat("Cell Size", &nmp->m_cellSize, 0.1f, 0.5f, "%.2f")) {
+ Graphics::Instance()->nav_mesh_out_of_date = true;
+ }
+ if(ImGui::SliderFloat("Cell Height", &nmp->m_cellHeight, 0.1f, 0.5f, "%.2f")) {
+ Graphics::Instance()->nav_mesh_out_of_date = true;
+ }
+ if(ImGui::SliderFloat("Agent Height", &nmp->m_agentHeight, 1.5f, 2.0f, "%.2f")) {
+ Graphics::Instance()->nav_mesh_out_of_date = true;
+ }
+ if(ImGui::SliderFloat("Agent Radius", &nmp->m_agentRadius, 0.2f, 1.0f, "%.2f")) {
+ Graphics::Instance()->nav_mesh_out_of_date = true;
+ }
+ if(ImGui::SliderFloat("Agent Max Climb", &nmp->m_agentMaxClimb, 1.0f, 2.0f, "%.2f")) {
+ Graphics::Instance()->nav_mesh_out_of_date = true;
+ }
+ if(ImGui::SliderFloat("Agent Max Slope", &nmp->m_agentMaxSlope, 45.0f, 70.0f, "%.2f")) {
+ Graphics::Instance()->nav_mesh_out_of_date = true;
+ }
+
+ ImGui::EndMenu();
+ }
+ if (ImGui::BeginMenu("Dialogue")) {
+ if(ImGui::MenuItem("Edit selected")){
+ me->RibbonItemClicked("edit_selected_dialogue", true);
+ }
+ /*
+ if(ImGui::MenuItem("Load pose")){
+ me->RibbonItemClicked("load_dialogue_pose", true);
+ }
+ */
+ if(ImGui::MenuItem("Load dialogue")){
+ me->RibbonItemClicked("load_dialogue", true);
+ }
+ if(ImGui::MenuItem("New dialogue")){
+ me->RibbonItemClicked("create_empty_dialogue", true);
+ }
+ ImGui::EndMenu();
+ }
+ }
+ if (ImGui::BeginMenu("Windows")) {
+ if(scenegraph) {
+ if(ImGui::MenuItem("Scenegraph", KeyCommand::GetDisplayText(KeyCommand::kScenegraph), &show_scenegraph)) {
+ }
+ if(ImGui::MenuItem("Selected", KeyCommand::GetDisplayText(KeyCommand::kEditScriptParams), &show_selected)) {
+ }
+ if(ImGui::MenuItem("ColorPicker", KeyCommand::GetDisplayText(KeyCommand::kEditColor), &show_color_picker)) {
+ }
+ if(ImGui::MenuItem("Collision Paint", "", &show_paintbrush)) {
+ }
+ } else {
+ if(ImGui::MenuItem("Mods", NULL, &show_mod_menu)){
+ }
+ }
+ if (ImGui::MenuItem("Multiplayer debug", "", &show_mp_debug)) {
+ }
+ if (ImGui::MenuItem("Multiplayer settings", "", &show_mp_settings)) {
+ }
+ if(ImGui::MenuItem("Performance", "", &show_performance)) {
+ }
+ if(ImGui::MenuItem("Log", "", &show_log)) {
+ }
+ if(ImGui::MenuItem("Warnings", "", &show_warnings)) {
+ }
+ if(ImGui::MenuItem("Save", "", &show_save)) {
+ }
+ if(ImGui::MenuItem("State", "", &show_state)) {
+ }
+ if(ImGui::MenuItem("Sound", "", &show_sound)) {
+ }
+ if (ImGui::MenuItem("Input", "", &show_input_debug)) {
+ }
+ bool val = config["debug_draw_window"].toNumber<bool>();
+ if(ImGui::MenuItem("Debug Window", "", &val)) {
+ config.GetRef("debug_draw_window") = val;
+ }
+ if (ImGui::MenuItem("View scripts", "", &show_select_script)) {
+ }
+ if (ImGui::MenuItem("Test pose", "", &show_pose)) {
+ }
+ if(ImGui::MenuItem("ImGUI Demo", "", &show_test_window)) {
+ }
+ ImGui::EndMenu();
+ }
+ if (ImGui::BeginMenu("Debug")) {
+ if(ImGui::MenuItem("Events", "", &show_events)) {
+ }
+ if(asdebugger_enabled && ImGui::MenuItem("AS debugger contexts", "", &show_asdebugger_contexts)) {
+ }
+ if(asprofiler_enabled && ImGui::MenuItem("AS profiler", "", &show_asprofiler)) {
+ }
+ if(ImGui::MenuItem("Graphics Debug Disable", "", &show_graphics_debug_disable_menu)) {
+ }
+ ImGui::EndMenu();
+ }
+
+ if (ImGui::BeginMenu("Help")) {
+ if (ImGui::BeginMenu("Web Links")) {
+ if (ImGui::MenuItem("Contact us...")) {
+ open_url("http://www.wolfire.com/contact");
+ }
+ if (ImGui::MenuItem("Overgrowth wiki...")) {
+ open_url("http://wiki.wolfire.com/index.php/Portal:Overgrowth");
+ }
+ if (ImGui::MenuItem("Wolfire discussion board...")) {
+ open_url("http://forums.wolfire.com/");
+ }
+ ImGui::EndMenu();
+ }
+ if (ImGui::BeginMenu("Hardware Info"))
+ {
+ static bool first = true;
+ static std::string output;
+ if( first ) {
+ PrintGPU(output,true);
+ first = false;
+ }
+
+ ImGui::Text(output.c_str());
+
+ ImGui::EndMenu();
+ }
+ if (ImGui::MenuItem("Open Write Directory...", NULL, false, false)) {}
+ ImGui::Separator();
+ if (ImGui::BeginMenu("Credits...")) {
+ ImGui::Text("Kylie Allen\nMicah J Best\nJimmy Chi\n\
+Max Danielsson\nJosh Goheen\nJohn Graham\nPhillip Isola\nTuro Lamminen\n\
+Tapio Liukkonen\nRyan Mapa\nBrendan Mauro\nGyrth McMulin\nMerlyn Morgan-Graham\n\
+Tuomas Narvainen\nJillian Ogle\n\
+Lukas Orsvarn\nConstance Paige\nWes Platt\nTim Pratt\nAnton Riehl\nDavid Rosen\nJeffrey Rosen\nAubrey Serr\nCarl Söyseth\n\
+Mark Stockton\nMikko Tarmia");
+ ImGui::EndMenu();
+ }
+
+ if (ImGui::BeginMenu("About...")) {
+ ImGui::Text("%s",GetPlatform());
+ ImGui::Text("%s",GetArch());
+ ImGui::Text("%s",GetBuildVersion());
+ ImGui::Text("%s",GetBuildTimestamp());
+ ImGui::EndMenu();
+ }
+ ImGui::EndMenu();
+ }
+ if(scenegraph && scenegraph->level) {
+ bool draw_menu = false;
+ const std::vector<Level::HookedASContext>& contexts = scenegraph->level->as_contexts_;
+ for(std::vector<Level::HookedASContext>::iterator iter = scenegraph->level->as_contexts_.begin(); iter != scenegraph->level->as_contexts_.end(); ++iter) {
+ if(iter->context_name == "mod_level_hook") {
+ if(iter->ctx->HasFunction(iter->as_funcs.menu)) {
+ draw_menu = true;
+ break;
+ }
+ }
+ }
+ if(draw_menu && ImGui::BeginMenu("Mods")) {
+ const std::vector<Level::HookedASContext>& contexts = scenegraph->level->as_contexts_;
+ for(std::vector<Level::HookedASContext>::iterator iter = scenegraph->level->as_contexts_.begin(); iter != scenegraph->level->as_contexts_.end(); ++iter) {
+ if(iter->context_name == "mod_level_hook") {
+ iter->ctx->CallScriptFunction(iter->as_funcs.menu);
+ }
+ }
+ ImGui::EndMenu();
+ }
+ }
+ ImGui::EndMainMenuBar();
+ }
+
+ if(show_load_item_search) {
+ ImGui::OpenPopup("loaditemsearch");
+ show_load_item_search = false;
+ }
+
+
+ ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x * 0.5f, 19.0f), ImGuiCond_Always, ImVec2(0.5f, 0.0f));
+ ImGui::SetNextWindowSizeConstraints(ImVec2(-1.0f, -1.0f), ImVec2(-1.0f, -1.0f), ResizeCallback);
+ //ImGui::SetNextWindowSizeConstraints(ImVec2(-1.0f, -1.0f), ImVec2(-1.0f, -1.0f), ResizeCallback);
+ // This stupid counter has to be here, because otherwise the SetKeyboardFocusHere
+ // call does nothing the first time the popup is opened if it isn't called
+ // like 30 times
+ static int counter = 0;
+ if(ImGui::BeginPopup("loaditemsearch")) {
+ static int available_numbers = 0;
+
+ int pressed_number = -1;
+ for(int i = SDLK_1; i < SDLK_9; ++i) {
+ Keyboard& keyboard = Input::Instance()->getKeyboard();
+ if(ImGui::GetIO().KeyCtrl && ImGui::IsKeyPressed(i)) {
+ pressed_number = i - SDLK_1 + 1;
+ break;
+ }
+ }
+ if(ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Enter))) {
+ pressed_number = 1;
+ }
+
+ if(pressed_number >= available_numbers) {
+ pressed_number = -1;
+ }
+
+ if(pressed_number == -1) {
+ ImGui::Text("Press CTRL + number to spawn items");
+ if(counter < 60) {
+ ImGui::SetKeyboardFocusHere(0);
+ }
+ if (ImGui::InputText("Search for item", spawner_global_filter.InputBuf, IM_ARRAYSIZE(spawner_global_filter.InputBuf), ImGuiInputTextFlags_AutoSelectAll)) {
+ spawner_global_filter.Build();
+ }
+ }
+ bool item_clicked = false;
+ if(pressed_number != -1) {
+ int items = 0;
+ for (SpawnerTabMap::iterator tab_iter = spawner_tabs.begin(); !item_clicked && tab_iter != spawner_tabs.end(); ++tab_iter) {
+ SpawnerTab* tab = &tab_iter->second;
+
+ for (SpawnerTab::iterator item_iter = tab->begin(); item_iter != tab->end(); ++item_iter) {
+ std::string item_display_name = tab_iter->first + " -> " + item_iter->display_name;
+
+ if (spawner_global_filter.PassFilter( item_iter->mod_source_title.c_str() ) || spawner_global_filter.PassFilter(item_display_name.c_str())) {
+ if (pressed_number == items + 1 && scenegraph->map_editor->state_ == MapEditor::kIdle && scenegraph->map_editor->LoadEntitiesFromFile(item_iter->path)==0) {
+ scenegraph->level->PushSpawnerItemRecent(*item_iter);
+ scenegraph->map_editor->active_tool_ = EditorTypes::ADD_ONCE;
+ item_clicked = true;
+ break;
+ }
+ items++;
+ }
+ }
+ }
+ } else {
+ int items = 0;
+ if (spawner_global_filter.IsActive()) {
+ for (SpawnerTabMap::iterator tab_iter = spawner_tabs.begin(); tab_iter != spawner_tabs.end(); ++tab_iter) {
+ SpawnerTab* tab = &tab_iter->second;
+
+ for (SpawnerTab::iterator item_iter = tab->begin(); item_iter != tab->end(); ++item_iter) {
+ std::string item_display_name = tab_iter->first + " -> " + item_iter->display_name;
+
+ if (spawner_global_filter.PassFilter( item_iter->mod_source_title.c_str() ) || spawner_global_filter.PassFilter(item_display_name.c_str())) {
+ if(items < 9) {
+ char buffer[32];
+ sprintf(buffer, "%i", items + 1);
+ ImGui::Text("%s", buffer);
+ ImGui::SameLine(0.0f, 16.0f);
+ if(AddSpawnerItem(&(*item_iter), scenegraph)) {
+ item_clicked = true;
+ }
+ } else {
+ if(AddSpawnerItem(&(*item_iter), scenegraph)) {
+ item_clicked = true;
+ }
+ }
+ items++;
+ }
+ }
+ }
+ }
+ available_numbers = items + 1;
+ }
+ if(item_clicked) {
+ ImGui::CloseCurrentPopup();
+ }
+ ImGui::EndPopup();
+ counter++;
+ } else {
+ counter = 0;
+ }
+
+ if (scenegraph && show_scenegraph) {
+ ImGui::SetNextWindowSize(ImVec2(640.0f, 480.0f), ImGuiCond_FirstUseEver);
+ ImGui::Begin("Scenegraph", &show_scenegraph);
+ if (select_scenegraph_level_params) {
+ ImGui::SetNextItemOpen(true);
+ // Flag cleared farther down, after Script Params is expanded as well
+ }
+ if (ImGui::TreeNode("Level")) {
+ char input_text_buf[kPathSize];
+ strscpy(input_text_buf, scenegraph->level->loading_screen_.image.c_str(), kPathSize);
+ static char last_checked_screenshot_path[kPathSize];
+ static int last_checked_screenshot_path_color = 0;
+
+ if( strmtch( last_checked_screenshot_path, input_text_buf ) == false ) {
+ if( FileExists( input_text_buf, kModPaths | kDataPaths) ) {
+ last_checked_screenshot_path_color = 1;
+ } else {
+ last_checked_screenshot_path_color = 2;
+ }
+ strscpy(last_checked_screenshot_path, input_text_buf, kPathSize);
+ }
+
+ if( last_checked_screenshot_path_color == 1 ) {
+ ImGui::PushStyleColor(ImGuiCol_Text, IMGUI_GREEN);
+ } else {
+ ImGui::PushStyleColor(ImGuiCol_Text, IMGUI_RED);
+ }
+
+ if(ImGui::InputText("Loading Screen Image", input_text_buf, kPathSize)) {
+ scenegraph->level->loading_screen_.image = input_text_buf;
+ }
+
+ ImGui::PopStyleColor();
+
+ vec3 pos = scenegraph->primary_light.pos;
+ if(ImGui::DragFloat3("Sun Position", &pos[0], 0.1f, -1.0f, 1.0f)){
+ scenegraph->map_editor->sky_editor_->PlaceSun(normalize(pos));
+ shadow_cache_dirty = true;
+ shadow_cache_dirty_sun_moved = true;
+ }
+ float color = scenegraph->map_editor->sky_editor_->m_sun_color_angle;
+ if(ImGui::DragFloat("Sun Color", &color, 1.0f)){
+ scenegraph->map_editor->sky_editor_->m_sun_color_angle = 0.0f;
+ scenegraph->map_editor->sky_editor_->RotateSun(color);
+ }
+ float sun_intensity = scenegraph->map_editor->sky_editor_->m_sun_angular_rad;
+ if(ImGui::DragFloat("Sun Intensity", &sun_intensity, 0.001f, 0.0f, 0.314f)){
+ scenegraph->map_editor->sky_editor_->m_sun_angular_rad = sun_intensity;
+ scenegraph->map_editor->sky_editor_->ScaleSun(1.0f);
+ }
+ if (select_scenegraph_level_params) {
+ ImGui::SetScrollHereY();
+ ImGui::SetNextItemOpen(true);
+ select_scenegraph_level_params = false;
+ }
+
+ if(ImGui::Checkbox("Enable dynamic shadows for this level", &g_level_shadows)) {
+ Graphics::Instance()->setSimpleShadows(g_simple_shadows);
+ scenegraph->level->setMetaDataDirty();
+ }
+ if(g_simple_shadows) {
+ ImGui::Text("Note: disable \"Simple shadows\" in the settings menu to see dynamic shadows");
+ }
+
+ /*ImGui::Separator();
+ ImGui::Text("%s", "Terrain");
+ const TerrainInfo* terrain_info = NULL;
+ if(scenegraph->map_editor->GetPreviewTerrainInfo()) {
+ terrain_info = scenegraph->map_editor->GetPreviewTerrainInfo();
+ } else {
+ for(size_t i = 0; i < scenegraph->terrain_objects_.size(); ++i) {
+ TerrainObject* terrain_object = scenegraph->terrain_objects_[i];
+ terrain_info = &(terrain_object->terrain_info());
+ break;
+ }
+ }
+
+ char buffer[kPathSize];
+ const char* path = "";
+ if(terrain_info) {
+ path = terrain_info->heightmap.c_str();
+ if(!terrain_info->heightmap.empty()) {
+ strcpy(buffer, terrain_info->heightmap.c_str());
+ *strrchr(buffer, '/') = '\0';
+ } else {
+ strcpy(buffer, "Data/Textures/Terrain");
+ }
+ if(DrawBrowsePath("Heightmap", path, "png\0dds\0tga", 3, buffer, kPathSize, buffer)) {
+ scenegraph->map_editor->PreviewTerrainHeightmap(buffer);
+ }
+ path = terrain_info->colormap.c_str();
+ if(!terrain_info->colormap.empty()) {
+ strcpy(buffer, terrain_info->colormap.c_str());
+ *strrchr(buffer, '/') = '\0';
+ } else {
+ strcpy(buffer, "Data/Textures/Terrain");
+ }
+ if(DrawBrowsePath("Colormap", path, "png\0dds\0tga", 3, buffer, kPathSize, buffer)) {
+ scenegraph->map_editor->PreviewTerrainColormap(buffer);
+ }
+ path = terrain_info->weightmap.c_str();
+ if(!terrain_info->weightmap.empty()) {
+ strcpy(buffer, terrain_info->weightmap.c_str());
+ *strrchr(buffer, '/') = '\0';
+ } else {
+ strcpy(buffer, "Data/Textures/Terrain");
+ }
+ if(DrawBrowsePath("Weightmap", path, "png\0dds\0tga", 3, buffer, kPathSize, buffer)) {
+ scenegraph->map_editor->PreviewTerrainWeightmap(buffer);
+ }
+ for(int i = 0; i < (int)terrain_info->detail_map_info.size(); ++i) {
+ if(ImGui::TreeNode(&terrain_info->detail_map_info[i], "Detail map %d", i)) {
+ const DetailMapInfo& info = terrain_info->detail_map_info[i];
+ path = info.colorpath.c_str();
+ if(!info.colorpath.empty()) {
+ strcpy(buffer, info.colorpath.c_str());
+ *strrchr(buffer, '/') = '\0';
+ } else {
+ strcpy(buffer, "Data/Textures/Terrain/DetailTextures/");
+ }
+ if(DrawBrowsePath("Colormap", path, "png\0dds\0tga", 3, buffer, kPathSize, buffer)) {
+ scenegraph->map_editor->PreviewTerrainDetailmap(i, buffer, info.normalpath.c_str(), info.materialpath.c_str());
+ }
+ path = info.normalpath.c_str();
+ if(!info.normalpath.empty()) {
+ strcpy(buffer, info.normalpath.c_str());
+ *strrchr(buffer, '/') = '\0';
+ } else {
+ strcpy(buffer, "Data/Textures/Terrain/DetailTextures/");
+ }
+ if(DrawBrowsePath("Normal", path, "png\0dds\0tga", 3, buffer, kPathSize, buffer)) {
+ scenegraph->map_editor->PreviewTerrainDetailmap(i, info.colorpath.c_str(), buffer, info.materialpath.c_str());
+ }
+ path = info.materialpath.c_str();
+ if(!info.materialpath.empty()) {
+ strcpy(buffer, info.materialpath.c_str());
+ *strrchr(buffer, '/') = '\0';
+ } else {
+ strcpy(buffer, "Data/Objects/");
+ }
+ if(DrawBrowsePath("Material", path, "png\0dds\0tga", 3, buffer, kPathSize, buffer)) {
+ scenegraph->map_editor->PreviewTerrainDetailmap(i, info.colorpath.c_str(), info.materialpath.c_str(), buffer);
+ }
+ ImGui::TreePop();
+ }
+ }
+ } else {
+ if(DrawBrowsePath("Heightmap", path, "png\0dds\0tga", 3, buffer, kPathSize, "Data/Textures/Terrain")) {
+ scenegraph->map_editor->PreviewTerrainHeightmap(buffer);
+ }
+ }*/
+
+ ImGui::Separator();
+ if(ImGui::TreeNode("Script Params")){
+ ScriptParamMap spm = scenegraph->level->script_params().GetParameterMap();
+ {
+ ScriptParam& sp = spm["Sky Rotation"];
+ sp.SetFloat(scenegraph->sky->sky_rotation);
+ sp.editor().SetDisplaySlider("min:-360,max:360,step:1,text_mult:1");
+ }
+ {
+ ScriptParam& sp = spm["Level Boundaries"];
+ sp.SetInt(scenegraph->level->script_params().ASGetInt("Level Boundaries"));
+ sp.editor().SetCheckbox();
+ }
+ {
+ ScriptParam& sp = spm["Shared Camera"];
+ sp.SetInt(scenegraph->level->script_params().ASGetInt("Shared Camera"));
+ sp.editor().SetCheckbox();
+ }
+ {
+ ScriptParam& sp = spm["HDR White point"];
+ sp.SetFloat(Graphics::Instance()->hdr_white_point);
+ sp.editor().SetDisplaySlider("min:0,max:2,step:0.001,text_mult:100");
+ }
+ {
+ ScriptParam& sp = spm["HDR Black point"];
+ sp.SetFloat(Graphics::Instance()->hdr_black_point);
+ sp.editor().SetDisplaySlider("min:0,max:2,step:0.001,text_mult:100");
+ }
+ {
+ ScriptParam& sp = spm["HDR Bloom multiplier"];
+ sp.SetFloat(Graphics::Instance()->hdr_bloom_mult);
+ sp.editor().SetDisplaySlider("min:0,max:5,step:0.001,text_mult:100");
+ }
+ {
+ ScriptParam& sp = spm["Saturation"];
+ sp.SetFloat(scenegraph->level->script_params().ASGetFloat("Saturation"));
+ sp.editor().SetDisplaySlider("min:0,max:2,step:0.001,text_mult:100");
+ }
+ {
+ ScriptParam& sp = spm["Fog amount"];
+ sp.SetFloat(scenegraph->fog_amount);
+ sp.editor().SetDisplaySlider("min:0,max:5s,step:0.1,text_mult:10");
+ }
+ {
+ scenegraph->level->script_params().ASAddString("Sky Tint", "255, 255, 255");
+ spm["Sky Tint"].SetString(scenegraph->level->script_params().GetStringVal("Sky Tint"));
+ spm["Sky Tint"].editor().SetColorPicker();
+ }
+ {
+ scenegraph->level->script_params().ASAddFloat("Sky Brightness", 1.0f);
+ spm["Sky Brightness"].SetFloat(scenegraph->level->script_params().ASGetFloat("Sky Brightness"));
+ spm["Sky Brightness"].editor().SetDisplaySlider("min:0,max:5,step:0.01,text_mult:100");
+ }
+ scenegraph->level->script_params().SetParameterMap(spm);
+ if(DrawScriptParamsEditor(&scenegraph->level->script_params())){
+ SceneGraph::ApplyScriptParams(scenegraph, scenegraph->level->script_params().GetParameterMap());
+ scenegraph->map_editor->QueueSaveHistoryState();
+ }
+ ImGui::TreePop();
+ }
+ ImGui::TreePop();
+ }
+ ImGui::Separator();
+ if (select_scenegraph_search) {
+ ImGui::SetKeyboardFocusHere();
+ select_scenegraph_search = false;
+ }
+ if (ImGui::InputText("", scenegraph_filter.InputBuf, IM_ARRAYSIZE(scenegraph_filter.InputBuf), ImGuiInputTextFlags_AutoSelectAll)) {
+ scenegraph_filter.Build();
+ select_start_index = -1;
+ }
+ ImGui::Checkbox("Flat", &show_flat_scenegraph);
+ ImGui::Checkbox("Search children", &search_children_scenegraph);
+ ImGui::Checkbox("Named Only", &show_named_only_scenegraph);
+ if( show_flat_scenegraph ) {
+ for(size_t i=0, len=scenegraph->objects_.size(); i<len; ++i){
+ const int kBufSize = 512;
+ char buf[kBufSize];
+ Object* obj = scenegraph->objects_[i];
+
+ if((show_named_only_scenegraph == false || obj->GetName().empty() == false) && obj->selectable_) {
+ obj->GetDisplayName(buf, kBufSize);
+ if (scenegraph_filter.IsActive() && !scenegraph_filter.PassFilter( buf )){
+ bool pass = false;
+ if(search_children_scenegraph) {
+ if(obj->IsGroupDerived()) {
+ char child_buf[kBufSize];
+ Group* group = static_cast<Group*>(obj);
+ for(size_t i = 0; i < group->children.size(); i++) {
+ group->children[i].direct_ptr->GetDisplayName(child_buf, kBufSize);
+ if(scenegraph_filter.PassFilter( child_buf )) {
+ pass = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if(!pass)
+ continue;
+ }
+
+ ImGuiTreeNodeFlags node_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
+
+ if(obj->Selected()){
+ node_flags |= ImGuiTreeNodeFlags_Selected;
+ }
+ ImGui::PushID(obj->GetID());
+ vec4 color = GetObjColor(obj);
+ ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(color[0], color[1], color[2], color[3]));
+ bool node_open = ImGui::TreeNodeEx("", node_flags, "%s", buf);
+ ImGui::PopStyleColor(1);
+ ImGui::PopID();
+ if (ImGui::IsItemClicked()) {
+ obj->Select(!obj->Selected());
+ }
+ if (node_open){
+ DrawObjectInfo(obj,false);
+ ImGui::TreePop();
+ }
+ }
+ }
+ } else {
+ DrawTreeScenegraphFor(scenegraph, gui, NULL);
+ }
+ ImGui::End();
+ }
+ if (scenegraph && show_selected) {
+ ImGui::SetNextWindowSize(ImVec2(640.0f, 480.0f), ImGuiCond_FirstUseEver);
+ ImGui::Begin("Selected", &show_selected);
+
+ std::vector<Object*> selected;
+ scenegraph->ReturnSelected(&selected);
+
+ ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 0.5f));
+ ImGui::Text(selected.size() == 1 ? "%u object selected" : "%u objects selected", selected.size());
+ ImGui::PopStyleColor(1);
+
+ for(size_t i=0, len=selected.size(); i<len; ++i){
+ const int kBufSize = 512;
+ char buf[kBufSize];
+ Object* obj = selected[i];
+ obj->GetDisplayName(buf, kBufSize);
+ if(len > 1) {
+ ImGui::PushID(obj->GetID());
+
+ vec4 color = GetObjColor(obj);
+ ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(color[0], color[1], color[2], color[3]));
+ bool node_open = ImGui::TreeNode("", "%s", buf);
+ ImGui::PopStyleColor(1);
+
+ ImGui::SameLine();
+ ImGui::SmallButton("copy");
+ if(ImGui::BeginPopupContextItem("copy object info", 0)) {
+ ImGui::Text("Copy to clipboard");
+ ImGui::Separator();
+ if(ImGui::Button("copy object id")) {
+ char copy_buf[kBufSize];
+ FormatString(copy_buf, kBufSize, "%i", obj->GetID());
+ ImGui::SetClipboardText(copy_buf);
+ ImGui::CloseCurrentPopup();
+ }
+ if(ImGui::Button("copy object path")) {
+ char copy_buf[kBufSize];
+ FormatString(copy_buf, kBufSize, "%s", obj->obj_file.c_str());
+ ImGui::SetClipboardText(copy_buf);
+ ImGui::CloseCurrentPopup();
+ }
+ if(ImGui::Button("copy all")) {
+ ImGui::SetClipboardText(buf);
+ ImGui::CloseCurrentPopup();
+ }
+ ImGui::EndPopup();
+ }
+
+ ImGui::PopID();
+
+ if (node_open) {
+ DrawObjectInfoFlat(obj);
+ ImGui::TreePop();
+ }
+ } else {
+ vec4 color = GetObjColor(obj);
+ ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(color[0], color[1], color[2], color[3]));
+ ImGui::Text("%s", buf);
+ ImGui::PopStyleColor(1);
+
+ ImGui::SameLine();
+ ImGui::SmallButton("copy");
+ if(ImGui::BeginPopupContextItem("copy object info", 0)) {
+ ImGui::Text("Copy to clipboard");
+ ImGui::Separator();
+ if(ImGui::Button("copy object id")) {
+ char copy_buf[kBufSize];
+ FormatString(copy_buf, kBufSize, "%i", obj->GetID());
+ ImGui::SetClipboardText(copy_buf);
+ ImGui::CloseCurrentPopup();
+ }
+ if(ImGui::Button("copy object path")) {
+ char copy_buf[kBufSize];
+ FormatString(copy_buf, kBufSize, "%s", obj->obj_file.c_str());
+ ImGui::SetClipboardText(copy_buf);
+ ImGui::CloseCurrentPopup();
+ }
+ if(ImGui::Button("copy all")) {
+ ImGui::SetClipboardText(buf);
+ ImGui::CloseCurrentPopup();
+ }
+ ImGui::EndPopup();
+ }
+
+ DrawObjectInfoFlat(obj);
+ }
+ }
+ select_object_script_params = false;
+ ImGui::End();
+ }
+
+ if (scenegraph && show_paintbrush) {
+ ImGui::Begin("Collision", &show_paintbrush, ImGuiWindowFlags_AlwaysAutoResize);
+ static int paint_type = 0;
+ ImGui::RadioButton("Per-object", &paint_type, 0);
+ ImGui::SameLine();
+ ImGui::RadioButton("Per-triangle", &paint_type, 1);
+
+ static int spread = 1;
+ static float spread_threshold = 0.0;
+ if(paint_type == 1){
+ ImGui::RadioButton("Don't spread", &spread, 0);
+ ImGui::RadioButton("Spread across flat surfaces", &spread, 1);
+ ImGui::RadioButton("Spread across similar normals", &spread, 2);
+
+ if(spread == 2){
+ ImGui::DragFloat("Range", &spread_threshold, 0.01f, -1.0f, 1.0f);
+ }
+ }
+
+ if(ImGui::Button("Save")){
+ SaveCollisionNormals(scenegraph);
+ }
+ ImGui::SameLine();
+ if(ImGui::Button("Load")){
+ LoadCollisionNormals(scenegraph);
+ }
+
+ ImGui::Separator();
+ static int paintbrush_type = 0;
+ ImGui::Text("Floor:");
+ ImGui::RadioButton("Walkable", &paintbrush_type, 1);
+ ImGui::SameLine();
+ ImGui::RadioButton("Balance", &paintbrush_type, 2);
+ ImGui::SameLine();
+ ImGui::RadioButton("Slide", &paintbrush_type, 3);
+ ImGui::Text("Wall:");
+ ImGui::RadioButton("Wallrun", &paintbrush_type, 4);
+ ImGui::SameLine();
+ ImGui::RadioButton("No wallrun", &paintbrush_type, 5);
+ ImGui::SameLine();
+ ImGui::RadioButton("Ledge", &paintbrush_type, 6);
+ ImGui::SameLine();
+ ImGui::RadioButton("Ragdoll", &paintbrush_type, 10);
+ ImGui::SameLine();
+ ImGui::RadioButton("Ragdoll-death", &paintbrush_type, 11);
+ ImGui::Text("Ceiling:");
+ ImGui::RadioButton("Ceiling", &paintbrush_type, 7);
+ ImGui::Text("Unlabeled:");
+ ImGui::RadioButton("Clear", &paintbrush_type, 8);
+ ImGui::SameLine();
+ ImGui::RadioButton("No climb", &paintbrush_type, 9);
+ if(paintbrush_type == 9) {
+ if(ImGui::Button("Apply 'no climb' to all unlabeled surfaces")){
+ for(size_t obj_index=0, len=scenegraph->collide_objects_.size(); obj_index<len; ++obj_index){
+ Object* obj = scenegraph->collide_objects_[obj_index];
+ if(obj->GetType() == _env_object){
+ EnvObject* eo = (EnvObject*)obj;
+ for(size_t i=0, len=eo->normal_override_custom.size(); i<len; ++i){
+ if(eo->normal_override_custom[i] == vec4(0.0f)){
+ eo->normal_override_custom[i] = vec4(0.0f,9.5f,0.0f,1.0f);
+ }
+ }
+ eo->normal_override_buffer_dirty = true;
+ }
+ }
+ }
+ }
+ const bool kEnableBrushSize = false;
+ static float brush_size = 10.0f;
+ if(kEnableBrushSize){
+ ImGui::DragFloat("Size", &brush_size);
+ if(brush_size < 1.0f){
+ brush_size = 1.0f;
+ }
+ Camera* cam = ActiveCameras::Get();
+ vec3 cam_facing = cam->GetFacing();
+ vec3 cam_up = cam->GetUpVector();
+ vec3 cam_right = cross(cam_facing, cam_up);
+ mat4 old_scale;
+ old_scale.SetUniformScale(brush_size);
+ mat4 circle_transform;
+ circle_transform.SetColumn(0, cam_right);
+ circle_transform.SetColumn(1, cam_up);
+ circle_transform.SetColumn(2, cam_facing);
+ circle_transform.SetTranslationPart(cam->GetPos() + normalize(cam->GetMouseRay())*100.0f);
+ DebugDraw::Instance()->AddCircle(circle_transform * old_scale, vec4(0.0f,0.0f,0.0f,0.5f), _delete_on_draw, _DD_XRAY);
+ // Paintbrush tag (wall, floor, ceiling)
+ // Paintbrush toggle by-asset or by-triangle
+ // Paintbrush size
+ }
+
+
+ ImGui::End();
+
+ PROFILER_ZONE(g_profiler_ctx, "Normal override paint");
+ Collision mouseray_collision_selected;
+ /*
+ std::vector<Collision> collisions;
+ GetEditorLineCollisions(scenegraph_, mouseray.start, mouseray.end, collisions, type_enable_);
+ for(int i=0, len=collisions.size(); i<len; ++i){
+ if(collisions[i].hit_what && collisions[i].hit_what->GetType()==_env_object){
+ EnvObject* eo = (EnvObject*)collisions[i].hit_what;
+ if(eo->GetCollisionModelID() != -1 && !eo->ofr->no_collision){
+ mouseray_collision_selected = collisions[i];
+ break;
+ }
+ }
+ }*/
+
+ bool actually_painting = (Input::Instance()->getKeyboard().isScancodeDown(SDL_SCANCODE_B, KIMF_LEVEL_EDITOR_GENERAL));
+ bool actually_edge_painting = (Input::Instance()->getKeyboard().isScancodeDown(SDL_SCANCODE_V, KIMF_LEVEL_EDITOR_GENERAL));
+
+ SimpleRayTriResultCallback cb;
+ int num_samples = 1;
+ if(paint_type == 1 && kEnableBrushSize){
+ num_samples = 20;
+ }
+ for(int i=0; i<num_samples; ++i){
+ float rand_amount = 0.0f;
+ if(paint_type == 1 && kEnableBrushSize){
+ rand_amount = brush_size * 0.01f;
+ }
+ vec3 jitter = vec3(RangedRandomFloat(-rand_amount,rand_amount), RangedRandomFloat(-rand_amount,rand_amount), RangedRandomFloat(-rand_amount,rand_amount));
+ vec3 dir = ActiveCameras::Get()->GetMouseRay();
+ cb.m_collisionObject = NULL;
+ float dist = 10.0f;
+ while(!cb.m_collisionObject && dist < 1000.0f){
+ scenegraph->bullet_world_->CheckRayTriCollisionInfo(ActiveCameras::Get()->GetPos(), ActiveCameras::Get()->GetPos() + normalize(dir+jitter)*dist, cb, true);
+ dist *= 2.0f;
+ }
+ if(cb.hasHit() && cb.m_object){
+ mouseray_collision_selected.hit_what = cb.m_object->owner_object;
+ mouseray_collision_selected.hit_where = cb.m_hit_pos;
+ mouseray_collision_selected.hit_how = cb.m_tri;
+ } else {
+ mouseray_collision_selected.hit_what = NULL;
+ }
+
+ if(mouseray_collision_selected.hit_what){
+ if(mouseray_collision_selected.hit_what->GetType() == _env_object){
+ EnvObject* eo = (EnvObject*)mouseray_collision_selected.hit_what;
+ vec4 norm;
+ if(paintbrush_type != 8) {
+ norm = vec4(0,paintbrush_type+0.5f,0,1.0f);
+ } else {
+ norm = vec4(0.0f);
+ }
+ if(paint_type == 0){
+ if(actually_painting){
+ for(size_t i=0, len=eo->normal_override_custom.size(); i<len; ++i){
+ eo->normal_override_custom[i] = norm;
+ }
+ eo->normal_override_buffer_dirty = true;
+ }
+ } else {
+ int model_id = eo->GetCollisionModelID();
+ if(model_id != -1){
+ Model* model = &Models::Instance()->GetModel(model_id);
+ WOLFIRE_SIMPLIFY::SimplifyModel processed_model;
+ std::vector<HalfEdge> half_edges;
+ WOLFIRE_SIMPLIFY::Process(*model, processed_model, half_edges, true);
+ std::vector<int> half_edge_faces;
+ std::vector<int> face_vert_equivalent;
+ FindFaces(*model, processed_model, half_edges, half_edge_faces, face_vert_equivalent);
+ vec3 verts[3];
+ int face_index = mouseray_collision_selected.hit_how * 3;
+ int vert_translate[3] = {-1};
+ for(size_t index=0, len=processed_model.vertices.size(); index<len; index+=3){
+ for(int vert_id=0; vert_id<3; ++vert_id){
+ int vert_index = model->faces[face_index+vert_id]*3;
+ if(processed_model.vertices[index+0] == model->vertices[vert_index+0] &&
+ processed_model.vertices[index+1] == model->vertices[vert_index+1] &&
+ processed_model.vertices[index+2] == model->vertices[vert_index+2])
+ {
+ vert_translate[vert_id] = (int)index/3;
+ }
+ }
+ }
+ for(int vert_id=0; vert_id<3; ++vert_id){
+ int vert_index = model->faces[face_index+vert_id]*3;
+ memcpy(&verts[vert_id], &model->vertices[vert_index], sizeof(vec3));
+ verts[vert_id] = eo->GetTransform() * verts[vert_id];
+ }
+ std::vector<int> checked;
+ checked.resize(half_edges.size(), 0);
+ std::queue<HalfEdge*> to_check;
+ for(size_t index=0, len=half_edges.size(); index<len; ++index){
+ if(half_edges[index].vert[0] == vert_translate[0] &&
+ half_edges[index].vert[1] == vert_translate[1])
+ {
+ to_check.push(&half_edges[index]);
+ }
+ }
+ vec3 start_norm;
+ bool norm_set = false;
+ while(!to_check.empty()){
+ HalfEdge* curr = to_check.front();
+ to_check.pop();
+ if(checked[curr->id] == 1){
+ continue;
+ }
+
+ vec3 verts[3];
+ for(int i=0; i<3; ++i){
+ int vert_index = processed_model.old_vert_id[curr->vert[0]]*3;
+ memcpy(&verts[i], &model->vertices[vert_index], sizeof(vec3));
+ curr = curr->next;
+ }
+
+ vec3 curr_norm = normalize(cross(verts[1]-verts[0], verts[2]-verts[0]));
+ if(!norm_set){
+ norm_set = true;
+ start_norm = curr_norm;
+
+ float shortest_dist = FLT_MAX;
+ int closest_edge = 0;
+ for(int edge=0; edge<3; ++edge){
+ vec3 perp_dir = normalize(cross(dir, eo->GetTransform()*verts[(edge+1)%3] - eo->GetTransform()*verts[edge]));
+ float dist = fabs(dot(perp_dir, eo->GetTransform()*verts[edge]) - dot(perp_dir, mouseray_collision_selected.hit_where));
+ if(dist < shortest_dist){
+ shortest_dist = dist;
+ closest_edge = edge;
+ }
+ }
+ for(int edge=0; edge<3; ++edge){
+ vec4 color = vec4(1.0);
+ if(edge == closest_edge){
+ color = vec4(0,1,0,1);
+ if(actually_edge_painting){
+ DebugDraw::Instance()->AddLine(eo->GetTransform()*verts[edge], eo->GetTransform()*verts[(edge+1)%3], color, _fade, _DD_XRAY);
+ }
+ }
+ DebugDraw::Instance()->AddLine(eo->GetTransform()*verts[edge], eo->GetTransform()*verts[(edge+1)%3], color, _delete_on_draw, _DD_XRAY);
+ }
+ }
+ bool can_spread = false;
+ if(spread == 1){
+ can_spread = distance(start_norm, curr_norm) < 0.1f;
+ } else if(spread == 2){
+ can_spread = dot(start_norm, curr_norm) > spread_threshold;
+ }
+ if(actually_painting && half_edge_faces[curr->id] != -1 && (spread == 0 || can_spread)){
+ if((int)eo->normal_override_custom.size() > half_edge_faces[curr->id]){
+ eo->normal_override_custom[half_edge_faces[curr->id]] = norm;
+ }
+ eo->normal_override_buffer_dirty = true;
+ }
+ if(can_spread){
+ for(int i=0; i<3; ++i){
+ //DebugDraw::Instance()->AddLine(eo->GetTransform()*verts[i], eo->GetTransform()*verts[(i+1)%3], vec4(1.0), _delete_on_draw, _DD_XRAY);
+ checked[curr->id] = 1;
+ if(curr->twin && checked[curr->twin->id] == 0){
+ to_check.push(curr->twin);
+ }
+ curr = curr->next;
+ }
+ }
+ }
+ eo->ledge_lines.clear();
+ for(size_t index=0, len=half_edges.size(); index<len; ++index){
+ HalfEdge* curr = &half_edges[index];
+ if(curr->twin) {
+ if( (int)eo->normal_override_custom.size() > half_edge_faces[curr->id] ) {
+ int curr_color = (int)(eo->normal_override_custom[half_edge_faces[curr->id]][1]);
+ int neighbor_color = (int)(eo->normal_override_custom[half_edge_faces[curr->twin->id]][1]);
+ if(curr_color == 6 && neighbor_color < 4){
+ for(int j=0; j<2; ++j){
+ int vert_index = processed_model.old_vert_id[curr->vert[j]]*3;
+ eo->ledge_lines.push_back(vert_index);
+ }
+ }
+ }
+ }
+ }
+ for(size_t index=1, len=eo->ledge_lines.size(); index<len; index+=2){
+ DebugDraw::Instance()->AddLine(eo->GetTransform() * *((vec3*)&model->vertices[eo->ledge_lines[index-1]]),
+ eo->GetTransform() * *((vec3*)&model->vertices[eo->ledge_lines[index]]),
+ vec4(1.0),
+ vec4(1.0),
+ _fade,
+ _DD_XRAY);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if( show_mod_menu ) {
+ show_mod_menu_previous = true;
+ DrawModMenu(engine);
+ }
+
+ if(show_mod_menu_previous && !show_mod_menu) {
+ show_mod_menu_previous = false;
+ CleanupModMenu();
+ }
+
+ if (scenegraph && show_color_picker) {
+ std::vector<Object*> selected;
+ scenegraph->ReturnSelected(&selected);
+
+ ImGui::SetNextWindowSize(ImVec2(0.0f, 0.0f), ImGuiCond_FirstUseEver);
+ ImGui::Begin("Color picker", &show_color_picker, ImGuiWindowFlags_NoResize);
+
+ DrawColorPicker(selected.data(), (int)selected.size(), scenegraph);
+
+ ImGui::End();
+ }
+ if(!gui->debug_text.empty())
+ {
+ if(config["debug_draw_window"].toBool()) {
+ ImGui::SetNextWindowPos(ImVec2(10,30));
+ ImGui::Begin("##debug_text_overlay", NULL, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoInputs);
+ DrawDebugText(gui);
+ ImGui::End();
+ } else if(config["fps_label"].toBool()) {
+ ImGui::SetNextWindowPos(ImVec2(10,30));
+ ImGui::Begin("##debug_text_overlay", NULL, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoInputs);
+ DrawDebugText(gui, "_framerate");
+ DrawDebugText(gui, "_frametime");
+ ImGui::End();
+ }
+ }
+ if (show_test_window) {
+ ImGui::SetNextWindowPos(ImVec2(650, 20), ImGuiCond_FirstUseEver);
+ ImGui::ShowDemoWindow(&show_test_window);
+ }
+ } else if(scenegraph && scenegraph->map_editor->state_ == MapEditor::kInGame && !gui->debug_text.empty()){
+ if(engine->current_engine_state_.type == kEngineEditorLevelState && config["debug_draw_window"].toBool()) {
+ ImGui::SetNextWindowPos(ImVec2(10,10));
+
+ ImGui::Begin("##debug_text_overlay", NULL, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoInputs);
+ DrawDebugText(gui);
+ ImGui::End();
+ } else if(config["fps_label"].toBool()) {
+ ImGui::SetNextWindowPos(ImVec2(10,30));
+ ImGui::Begin("##debug_text_overlay", NULL, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoInputs);
+ DrawDebugText(gui, "_framerate");
+ DrawDebugText(gui, "_frametime");
+ ImGui::End();
+ }
+ }
+
+ if (show_mp_settings) {
+ ImGui::SetNextWindowPos(ImVec2(0.f, 200.f));
+ ImGui::Begin("Multiplayer Settings", &show_mp_info,
+ ImGuiWindowFlags_NoTitleBar |
+ ImGuiWindowFlags_NoResize |
+ ImGuiWindowFlags_NoMove |
+ ImGuiWindowFlags_NoSavedSettings |
+ ImGuiWindowFlags_AlwaysAutoResize);
+
+ bool host_allow_editor = online->GetHostAllowsClientEditor();
+ ImGui::Checkbox("Allow clients using the editor", &host_allow_editor);
+
+ online->SetIfHostAllowsClientEditor(host_allow_editor);
+
+ if (spawner_tabs.find("Character") != spawner_tabs.end()) {
+ SpawnerTab * character_tab = &spawner_tabs["Character"];
+ std::string current_hot_join_char = online->GetDefaultHotJoinCharacter();
+ ImGui::Text(("Hot join character: " + current_hot_join_char).c_str());
+ if (ImGui::BeginMenu("Select hot join character")) {
+ for (uint32_t i = 0; i < character_tab->size(); i++) {
+ SpawnerItem * item = &((*character_tab)[i]); // no way that the asm is pretty here
+
+ if (IsCharacterSelceted(item)) {
+ online->SetDefaultHotJoinCharacter(item->path);
+ }
+ }
+ ImGui::EndMenu();
+ }
+ }
+
+ ImGui::End();
+ }
+
+ if (show_build_watermark && GetBuildID() != -1) {
+ const ImVec4 transparent = ImVec4(0.0f, 0.0f, 0.0f, 0.0f);
+ ImGui::PushStyleColor(ImGuiCol_WindowBg, transparent);
+ ImGui::PushStyleColor(ImGuiCol_Border, transparent);
+ ImGui::SetNextWindowPos(ImVec2(0, Graphics::Instance()->render_dims[1] - ImGui::GetTextLineHeight() * 2), ImGuiCond_Always);
+ ImGui::SetNextWindowSize(ImVec2(400, ImGui::GetTextLineHeight()), ImGuiCond_Always);
+ ImGui::Begin("VersionWatermark", (bool*) 1, ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs);
+ ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 1.0f), std::string("BUILD ID: " + std::to_string(GetBuildID()) + ", IN_DEV").c_str());
+ ImGui::End();
+ ImGui::PopStyleColor(2);
+ }
+
+ static bool write_to_chat = false;
+ if (online->IsActive()) {
+ Input* input = Input::Instance();
+ const Keyboard& keyboard = input->getKeyboard();
+ int window_x = Graphics::Instance()->render_dims[0];
+ int window_y = Graphics::Instance()->render_dims[1];
+
+ ImGuiWindowFlags w_flags = 0;
+
+
+ static bool enter_is_down_this_frame = false;
+ static bool enter_was_down_last_frame = false;
+
+ enter_was_down_last_frame = enter_is_down_this_frame;
+ enter_is_down_this_frame = keyboard.isKeycodeDown(SDLK_RETURN, KIMF_ANY);
+
+ static bool wait_for_enter_release = false;
+ static float time_from_host_start = 0;
+ static bool host_started_level_last_frame = false;
+
+ if (host_started_level_last_frame != online->host_started_level) { //check if host started the level this frame
+ wait_for_enter_release = true;
+ host_started_level_last_frame = online->host_started_level;
+ time_from_host_start = game_timer.game_time;
+ write_to_chat = false; // close the chat when moving from lobby into game
+ }
+
+ if (wait_for_enter_release) {
+ //can not open chat by pressing enter for the first half second when going from loading screen to game, except from if enter is released
+ if (game_timer.game_time - time_from_host_start > 0.5f) {
+ wait_for_enter_release = false;
+ } else if (enter_was_down_last_frame && !enter_is_down_this_frame) {
+ wait_for_enter_release = false;
+ }
+ }
+
+ bool player_pressed_enter = enter_is_down_this_frame && !enter_was_down_last_frame && !wait_for_enter_release;
+
+ bool open_chat = false;
+ if (!write_to_chat && player_pressed_enter) {
+ write_to_chat = true;
+ open_chat = true;
+ } else if (write_to_chat && player_pressed_enter) {
+ write_to_chat = false;
+ }
+
+
+ if (show_chat) {
+ SceneGraph* scene_graph = Engine::Instance()->GetSceneGraph();
+ if (scene_graph) {
+
+ //This will prevent the chat from open when i a dialogue.
+ ObjectID player_object_id = online->GetOwnPlayerState().object_id;
+ if (player_object_id != -1) {
+ Object* player_object = scene_graph->GetObjectFromID(player_object_id);
+ if (player_object && player_object->GetType() == _movement_object) {
+ MovementObject* player_mo = static_cast<MovementObject*>(player_object);
+ //if player is in dialogue set write_to_chat to false;
+ write_to_chat &= !*reinterpret_cast<bool*>(player_mo->as_context->module.GetVarPtrCache("dialogue_control"));
+ }
+ }
+ }
+
+ const auto& chat_messages = online->GetChatMessages();
+ constexpr size_t line_count = 5;
+ constexpr float chat_width = 600;
+ constexpr float chat_alpha = 0.1f;
+ ImVec2 chat_write_pos = ImVec2(10, window_y - ImGui::GetTextLineHeight() * 2 - 10);
+ ImVec2 chat_text_pos = chat_write_pos - ImVec2(0, ImGui::GetTextLineHeight() * line_count * 2);
+
+ if (chat_messages.size() > 0) {
+
+ ImGui::SetNextWindowBgAlpha(chat_alpha);
+ ImGui::SetNextWindowPos(chat_text_pos, ImGuiCond_Always);
+ ImGui::SetNextWindowSize(ImVec2(chat_width, ImGui::GetTextLineHeight() * line_count * 2), ImGuiCond_Always);
+ ImGui::Begin("Online Chat", &show_chat, ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs);
+
+ // Pad lines so messages are always bottom aligned
+ for (size_t i = 0; i < line_count + 1; i++) {
+ ImGui::Text("");
+ }
+
+ // Display actual chat messages
+ ImGui::PushTextWrapPos();
+ for(size_t i = 0; i < chat_messages.size(); i++) {
+ ImGui::Text(chat_messages[i].message.c_str());
+ }
+ ImGui::PopTextWrapPos();
+
+ // Smooth scroll to the bottom of the chat window
+ if (ImGui::GetScrollY() < ImGui::GetScrollMaxY()) {
+ ImGui::SetScrollY(ImGui::GetScrollY() + (ImGui::GetScrollMaxY() - ImGui::GetScrollY() + 10.0f) * ImGui::GetIO().DeltaTime * 10);
+ }
+
+
+ ImGui::End();
+
+ online->RemoveOldChatMessages(10.0f); // todo: should be moved to mp.update once thats a thing
+ }
+ if (write_to_chat) {
+
+ ImGui::SetNextWindowBgAlpha(chat_alpha);
+ ImGui::SetNextWindowPos(chat_write_pos, ImGuiCond_Always);
+ ImGui::SetNextWindowSize(ImVec2(chat_width, ImGui::GetTextLineHeight()), ImGuiCond_Always);
+ ImGui::Begin("Online Chat Write", &show_chat, ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs);
+
+ static char input_buffer[512]; //player will be able to write a message with 511 chars
+ if (open_chat) {
+ ImGui::SetKeyboardFocusHere();
+ memset(input_buffer, '\0', sizeof(input_buffer));
+ }
+ if (write_to_chat) {
+ if (ImGui::InputText("", input_buffer, sizeof(input_buffer), ImGuiInputTextFlags_EnterReturnsTrue)) {
+ write_to_chat = false;
+
+ if (input_buffer[0] != '\0') {
+ online->BroadcastChatMessage(input_buffer);
+ }
+ }
+ }
+ ImGui::End();
+ }
+ }
+ } else {
+ write_to_chat = false;
+ }
+
+ if (show_mp_debug && online->IsActive()) {
+ ImGui::SetNextWindowSize(ImVec2(640.0f, 480.0f), ImGuiCond_FirstUseEver);
+ ImGui::Begin("Multiplayer status", &show_mp_debug, ImGuiWindowFlags_NoSavedSettings);
+ std::vector<ObjectID> myAvatarID = online->GetLocalAvatarIDs();
+ float step = online->GetNoDataInterpStepOverRide();
+ ImGui::SliderFloat("Interp no data override", &step, 0, 1.0);
+ online->SetNoDataInterpStepOverRide(step);
+
+ if(ImGui::Button("Send Test Message")) {
+ online->Send<OnlineMessages::TestMessage>("Test message from debug menu");
+ }
+
+ if(online->IsClient()) {
+ ImGui::SliderFloat("Time Shift Coefficient", &TimeInterpolator::offset_shift_coefficient_factor, 0.0f, 20.0f, "%.3f", 0);
+ ImGui::SliderInt("Target Window Size", &TimeInterpolator::target_window_size, 3, 16);
+ }
+
+#if ENABLE_STEAMWORKS || ENABLE_GAMENETWORKINGSOCKETS
+ ISteamNetworkingUtils* utils = SteamNetworkingUtils();
+ ISteamNetworkingSockets * isns = SteamNetworkingSockets();
+ if (utils && isns) {
+ SteamNetworkingConfigValue_t config[4];
+
+ static int32_t outPing = 0;
+ static int32_t inPing = 0;
+
+ static float packetLossIn = 0;
+ static float packetOutIn = 0;
+
+ ImGui::Text(std::string("Outgoing Messages: " + std::to_string(online->OutgoingMessageCount())).c_str());
+ ImGui::Text(std::string("Incoming Messages: " + std::to_string(online->IncomingMessageCount())).c_str());
+
+ if (ImGui::TreeNode("Link Quality Testing")) {
+ ImGui::SliderInt("Ping out in ms", &outPing, 0, 1000);
+ ImGui::SliderInt("Ping in ms", &inPing, 0, 1000);
+
+ ImGui::SliderFloat("Packet loss percentage OUT", &packetOutIn, 0, 100);
+ ImGui::SliderFloat("Packet loss percentage IN", &packetLossIn, 0, 100);
+ ImGui::TreePop();
+ }
+
+ config[0].SetInt32(ESteamNetworkingConfigValue::k_ESteamNetworkingConfig_FakePacketLag_Send, outPing);
+ config[1].SetInt32(ESteamNetworkingConfigValue::k_ESteamNetworkingConfig_FakePacketLag_Recv, inPing);
+ config[2].SetFloat(ESteamNetworkingConfigValue::k_ESteamNetworkingConfig_FakePacketLoss_Recv, packetLossIn);
+ config[3].SetFloat(ESteamNetworkingConfigValue::k_ESteamNetworkingConfig_FakePacketLoss_Send, packetOutIn);
+
+ utils->SetConfigValueStruct(config[0], ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Global, 0);
+ utils->SetConfigValueStruct(config[1], ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Global, 0);
+ utils->SetConfigValueStruct(config[2], ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Global, 0);
+ utils->SetConfigValueStruct(config[3], ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Global, 0);
+ intptr_t packageSize = 0;
+ ESteamNetworkingConfigDataType type;
+ int mtuDatasize = 0;
+ int mtuPacketsize = 0;
+ size_t size = 4;
+ int outgoingbuffersize = 0;
+ int sentRateMax = -1;
+ int sentRateMin = -1;
+
+ utils->GetConfigValue(k_ESteamNetworkingConfig_MTU_PacketSize, ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Global, 0, &type, &mtuDatasize, &size);
+ utils->GetConfigValue(k_ESteamNetworkingConfig_MTU_DataSize, ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Global, 0, &type, &mtuPacketsize, &size);
+ utils->GetConfigValue(k_ESteamNetworkingConfig_SendRateMax, ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Global, 0, &type, &sentRateMax, &size);
+ utils->GetConfigValue(k_ESteamNetworkingConfig_SendRateMin, ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Global, 0, &type, &sentRateMin, &size);
+ utils->GetConfigValue(k_ESteamNetworkingConfig_SendBufferSize, ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Global, 0, &type, &outgoingbuffersize, &size);
+
+ if (ImGui::TreeNode("Steam Network Config")) {
+ ImGui::SliderInt("MTU packet size", &mtuPacketsize, 1, 5000);
+
+ ImGui::SliderInt("Internal buffer size", &outgoingbuffersize, 1, 4096 * 60 * 10);
+ ImGui::TreePop();
+ }
+
+ SteamNetworkingConfigValue_t config_1[4];
+
+ config_1[0].SetInt32(ESteamNetworkingConfigValue::k_ESteamNetworkingConfig_MTU_PacketSize, mtuPacketsize + 100);
+
+ config_1[1].SetInt32(ESteamNetworkingConfigValue::k_ESteamNetworkingConfig_SendBufferSize, outgoingbuffersize);
+
+ config_1[2].SetInt32(ESteamNetworkingConfigValue::k_ESteamNetworkingConfig_SendRateMin, 1024 * 1024 * 9);
+
+ config_1[3].SetInt32(ESteamNetworkingConfigValue::k_ESteamNetworkingConfig_SendRateMax, 1024 * 1024 * 10);
+
+
+ utils->SetConfigValueStruct(config_1[0], ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Global, 0);
+ utils->SetConfigValueStruct(config_1[1], ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Global, 0);
+
+ utils->SetConfigValueStruct(config_1[2], ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Global, 0);
+ utils->SetConfigValueStruct(config_1[3], ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Global, 0);
+
+ }
+#endif
+ if (ImGui::TreeNode("Data config")) {
+ static int tickperiod = 30;
+ ImGui::SliderInt("Compression level", &online->compression_level, 0, 100);
+ ImGui::SliderInt("Tickperiod in ms", &tickperiod, 1, 200);
+ online->SetTickperiod(tickperiod);
+ ImGui::TreePop();
+ }
+
+ static uint8_t current_frame = 0;
+
+ std::vector<Peer> peers = online->GetPeers();
+ static ImGuiTableFlags flags = ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV |
+ ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable;
+
+ uint32_t count = 0;
+ float bytesOut = 0.f;
+ float bytesIn = 0.f;
+ if (ImGui::BeginTable("##table", 6, flags, ImVec2(-1, 0))) {
+
+ ImGui::TableSetupColumn("Player", ImGuiTableColumnFlags_WidthStretch);
+ ImGui::TableSetupColumn("Message Ping", ImGuiTableColumnFlags_WidthFixed, 100.0f);
+ ImGui::TableSetupColumn("Ping", ImGuiTableColumnFlags_WidthFixed, 75.0f);
+
+ ImGui::TableSetupColumn("Bytes out", ImGuiTableColumnFlags_WidthFixed, 75.0f);
+ ImGui::TableSetupColumn("Bytes in", ImGuiTableColumnFlags_WidthFixed, 75.0f);
+ ImGui::TableSetupColumn("Packet loss", ImGuiTableColumnFlags_WidthFixed, 75.0f);
+ ImGui::TableHeadersRow();
+ ImPlot::PushColormap(ImPlotColormap_Cool);
+ for (auto& it : peers) {
+ ConnectionStatus c_status;
+ online->GetConnectionStatus(it, &c_status);
+ ImGui::TableNextRow();
+ static ImGuiTableFlags flags = ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV |
+ ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable;
+ ImGui::TableSetColumnIndex(0);
+ ImGui::Text("%d", count++);
+
+ ImGui::TableSetColumnIndex(1);
+ ImGui::Text("%f", it.current_ping_delta);
+
+ ImGui::TableSetColumnIndex(2);
+ ImGui::Text("%d", c_status.ms_ping);
+
+ ImGui::TableSetColumnIndex(3);
+ ImGui::Text("%f", c_status.in_bytes_per_sec);
+
+
+ ImGui::TableSetColumnIndex(4);
+ ImGui::Text(" %f", c_status.out_bytes_per_sec);
+
+
+ ImGui::TableSetColumnIndex(5);
+ ImGui::Text("%f", 1.0f - c_status.connection_quality_remote);
+ bytesIn += c_status.in_bytes_per_sec;
+ bytesOut += c_status.out_bytes_per_sec;
+ }
+ ImPlot::PopColormap();
+ ImGui::EndTable();
+ }
+
+
+#ifdef NETWORK_DEBUG_DATA
+ static ScrollingBuffer<ImVec2> sdata1, sdata2, sdata3, sdata4, sdata5;
+
+ GraphTime += ImGui::GetIO().DeltaTime;
+
+
+
+ static float history = 10.0f;
+ static ImPlotAxisFlags flags1 = 0; ImPlotAxisFlags_NoTickLabels;
+ if (ImGui::TreeNode("Data graphs")) {
+ ImGui::SliderFloat("History", &history, 1, 30, "%.1f s");
+
+ sdata1.AddPoint(ImVec2(GraphTime, (bytesOut) / 1000.f));
+ sdata2.AddPoint(ImVec2(GraphTime, (bytesIn) / 1000.f));
+ sdata3.AddPoint(ImVec2(GraphTime, (bytesIn + bytesOut) / 1000.f));
+
+
+ if (ImPlot::BeginPlot("##BytesRealTime", ImVec2(-1, 150))) {
+ ImPlot::SetupAxes(NULL, NULL, flags1, flags1);
+ ImPlot::SetupAxisLimits(ImAxis_X1, GraphTime - history, GraphTime, ImGuiCond_Always);
+ ImPlot::SetupAxisLimits(ImAxis_Y1, 0, 1000);
+ ImPlot::SetNextFillStyle(IMPLOT_AUTO_COL, 0.5f);
+
+ ImPlot::PlotLine("Total traffic bytes", &sdata3.points[0].x, &sdata3.points[0].y, (int)sdata3.points.size(), sdata3.offset, 2 * sizeof(float));
+ ImPlot::PlotLine("Total traffic bytes out", &sdata1.points[0].x, &sdata1.points[0].y, (int)sdata1.points.size(), sdata1.offset, 2 * sizeof(float));
+ ImPlot::PlotLine("Total traffic bytes in", &sdata2.points[0].x, &sdata2.points[0].y, (int)sdata2.points.size(), sdata2.offset, 2 * sizeof(float));
+
+ ImPlot::EndPlot();
+ }
+
+ ImGui::TreePop();
+ }
+#endif
+
+ if(ImGui::TreeNode("Player debug info")) {
+ for(const auto& player_state : online->GetPlayerStates()) {
+ ImGui::Text("Playername: %d", player_state.playername.c_str());
+ ImGui::Text("Object ID: %d (%d)", player_state.object_id, online->GetOriginalID(player_state.object_id));
+ ImGui::Text("Ping: %d", player_state.ping);
+ ImGui::Text("Controller ID: %d", player_state.controller_id);
+ ImGui::Text("Camera ID: %d", player_state.camera_id);
+ ImGui::Separator();
+ }
+ ImGui::TreePop();
+ }
+
+ if(ImGui::TreeNode("Peer debug info")) {
+ auto peers = online->GetPeers();
+ for(auto peer : peers) {
+ ImGui::Text("Connection ID: %d", peer.conn_id);
+ ImGui::Text("Peer ID: %d", peer.peer_id);
+ ImGui::Separator();
+ }
+ ImGui::TreePop();
+ }
+
+ if(ImGui::TreeNode("Player Characters")) {
+ if(scenegraph != nullptr) {
+ int num_avatars;
+ int avatar_ids[256];
+
+ scenegraph->GetPlayerCharacterIDs(&num_avatars, avatar_ids, 256);
+
+ for(int i = 0; i < num_avatars; i++) {
+ MovementObject* avatar = static_cast<MovementObject*>(scenegraph->GetObjectFromID(engine->avatar_ids[i]));
+
+ ImGui::Text("ID %d", avatar->GetID());
+ ImGui::Text("Name %s", avatar->GetName().c_str());
+ ImGui::Text("Controller %d", avatar->controller_id);
+ ImGui::Text("Controlled %s", avatar->controlled ? "true" : "false");
+ ImGui::Text("Camera ID %d", avatar->camera_id);
+ ImGui::Separator();
+ }
+ ImGui::TreePop();
+ }
+ }
+
+ if(ImGui::TreeNode("NPC Characters")) {
+ if(scenegraph != nullptr) {
+ int num_avatars;
+ int avatar_ids[256];
+
+ scenegraph->GetNPCCharacterIDs(&num_avatars, avatar_ids, 256);
+
+ for(int i = 0; i < num_avatars; i++) {
+ MovementObject* avatar = static_cast<MovementObject*>(scenegraph->GetObjectFromID(engine->avatar_ids[i]));
+
+ ImGui::Text("ID %d", avatar->GetID());
+ ImGui::Text("Name %s", avatar->GetName().c_str());
+ ImGui::Text("Controller %d", avatar->controller_id);
+ ImGui::Text("Controlled %s", avatar->controlled ? "true" : "false");
+ ImGui::Separator();
+ }
+ ImGui::TreePop();
+ }
+ }
+
+ if(ImGui::TreeNode("Character Graphs")) {
+ if(scenegraph != nullptr) {
+ int num_avatars;
+ int avatar_ids[256];
+
+ scenegraph->GetCharacterIDs(&num_avatars, avatar_ids, 256);
+
+ for(int i = 0; i < num_avatars; i++) {
+ float pending_bones = 0.0f;
+
+ std::stringstream ss;
+ ss << avatar_ids[i];
+ std::string id_prefix = ss.str();
+
+ MovementObject * movobj = (MovementObject*)(scenegraph->GetObjectFromID(avatar_ids[i]));
+ RiggedObject * rigged = movobj->rigged_object();
+
+ ImGui::PushID(avatar_ids[i]);
+ if(ImGui::TreeNode("MO #%s", id_prefix.c_str())) {
+ if (movobj != nullptr) {
+ pending_bones = movobj->network_time_interpolator.full_interpolation;
+
+ graphs[id_prefix + "_host_walltime_diff"].Push(movobj->last_walltime_diff);
+ graphs[id_prefix + "_host_walltime_offset_shift"].Push(movobj->network_time_interpolator.host_walltime_offset_shift_vel);
+
+ graphs[id_prefix + "_root_bone_x"].Push(rigged->GetDisplayBonePosition(0).x());
+ graphs[id_prefix + "_root_bone_y"].Push(rigged->GetDisplayBonePosition(0).y());
+ graphs[id_prefix + "_root_bone_z"].Push(rigged->GetDisplayBonePosition(0).z());
+
+ graphs[id_prefix + "_rigged_interpolation_time"].Push(0);
+ graphs[id_prefix + "_mov_interpolation_step"].Push(movobj->network_time_interpolator.interpolation_step);
+
+ graphs[id_prefix + "_player_velocity"].Push(length(movobj->velocity));
+ graphs[id_prefix + "_delta_between_frames"].Push(0);
+
+ graphs[id_prefix + "_pending_bones_history"].Push(movobj->network_time_interpolator.full_interpolation);
+
+
+ ImGui::Checkbox("Update Camera Pos", &rigged->update_camera_pos);
+ ImGui::Checkbox("Disable Linear Interpolation", &movobj->disable_network_bone_interpolation);
+ }
+
+ graphs[id_prefix + "_host_walltime_diff"].Draw("Host Walltime Diff");
+ graphs[id_prefix + "_host_walltime_offset_shift"].Draw("Host Walltime Offset Shift");
+ graphs[id_prefix + "_rig_interpolation_step"].Draw("Rig Interpolation Step");
+ graphs[id_prefix + "_mov_interpolation_step"].Draw("MO Interpolation Step");
+
+ graphs[id_prefix + "_root_bone_x"].Draw("Root Bone X");
+ graphs[id_prefix + "_root_bone_y"].Draw("Root Bone Y");
+ graphs[id_prefix + "_root_bone_z"].Draw("Root Bone Z");
+
+ graphs[id_prefix + "_rigged_interpolation_time"].Draw("Rigged Object Interpolation Time");
+
+ graphs[id_prefix + "_player_velocity"].Draw("Player Velocity History");
+ graphs[id_prefix + "_delta_between_frames"].Draw("Delta Between Frame History");
+
+ graphs[id_prefix + "_pending_bones_history"].Draw("Nr Pending Bones History");
+
+ ImGui::TreePop();
+ }
+ ImGui::PopID();
+ }
+ }
+ ImGui::TreePop();
+ }
+
+ ImGui::Separator();
+ DrawDebugText(gui, "_framerate");
+ DrawDebugText(gui, "_frametime");
+
+ ImGui::End();
+ }
+
+
+
+ if(show_state) {
+ ImGui::SetNextWindowSize(ImVec2(500.0f, 200.0f), ImGuiCond_FirstUseEver);
+ ImGui::Begin("State", &show_state);
+
+ ImGui::Text("Engine State Stack");
+
+ ImGui::Text( "0 %s: %s (%s)",
+ CStrEngineStateType(engine->current_engine_state_.type),
+ engine->current_engine_state_.path.GetOriginalPath(),
+ engine->current_engine_state_.id.c_str()
+ );
+
+ for( unsigned i = 0; i < engine->state_history.size(); i++ ) {
+ ImGui::Text( "%u %s: %s (%s)",
+ i+1,
+ CStrEngineStateType(engine->state_history[i].type),
+ engine->state_history[i].path.GetOriginalPath(),
+ engine->state_history[i].id.c_str()
+ );
+ }
+
+ ImGui::Separator();
+
+ ScriptableCampaign *sc = engine->GetCurrentCampaign();
+ if( sc ) {
+ ImGui::Text("Current Campaign ID: %s", sc->GetCampaignID().c_str());
+ } else {
+ ImGui::Text("No Active Campaign");
+ }
+
+ std::string current_level_id = engine->GetCurrentLevelID();
+ if(current_level_id.empty() == false) {
+ ImGui::Text("Currently Loaded Level: %s", current_level_id.c_str());
+ } else {
+ ImGui::Text("No Level Currently Loaded");
+ }
+
+ ImGui::End();
+ }
+
+ if (show_save) {
+ ImGui::SetNextWindowSize(ImVec2(350.0f, 150.0f), ImGuiCond_FirstUseEver);
+ ImGui::Begin("Save", &show_save);
+ std::set<std::string> campaign_ids;
+ for( size_t i = 0; i < engine->save_file_.GetSaveCount(); i++ ) {
+ SavedLevel& level = engine->save_file_.GetSaveIndex(i);
+
+ campaign_ids.insert(level.campaign_id_);
+ }
+
+ std::set<std::string>::iterator campaign_id_it = campaign_ids.begin();
+ for(;campaign_id_it != campaign_ids.end(); campaign_id_it++) {
+ const char* node_title = "{empty}";
+ if( *campaign_id_it != "" ) {
+ node_title = (*campaign_id_it).c_str();
+ }
+
+ char save_t_id[256];
+ FormatString(save_t_id, 256, "save_cat_%s", node_title);
+
+ if( ImGui::TreeNode(save_t_id, "%s", node_title ) ) {
+ for( size_t i = 0; i < engine->save_file_.GetSaveCount(); i++ ) {
+ SavedLevel& level = engine->save_file_.GetSaveIndex(i);
+ if( *campaign_id_it == level.campaign_id_ ) {
+ FormatString(save_t_id, 256, "save_inst_%s_%s_%s",level.campaign_id_.c_str(), level.save_category_.c_str(), level.save_name_.c_str() );
+ if( ImGui::TreeNode(save_t_id, "%s %s", level.save_category_.c_str(), level.save_name_.c_str() ) ) {
+
+ const DataMap& data_map = level.GetDataMap();
+
+ if( data_map.size() > 0 ) {
+ if( ImGui::TreeNode("DataMap") ) {
+
+ DataMap::const_iterator data_map_it = data_map.begin();
+
+ for(; data_map_it != data_map.end(); data_map_it++ ) {
+ ImGui::Text("[%s]:%s", data_map_it->first.c_str(), data_map_it->second.c_str());
+ }
+ ImGui::TreePop();
+ }
+ } else {
+ ImGui::Text("%s", "No DataMap Data");
+ }
+
+ const ArrayDataMap& array_data_map = level.GetArrayDataMap();
+
+ if( array_data_map.size() > 0 ) {
+ if(ImGui::TreeNode("ArrayDataMap")) {
+ ArrayDataMap::const_iterator array_data_map_it = array_data_map.begin();
+ for(; array_data_map_it != array_data_map.end(); array_data_map_it++) {
+ if(ImGui::TreeNode(array_data_map_it->first.c_str())) {
+ std::vector<std::string>::const_iterator array_data_it = array_data_map_it->second.begin();
+ int index = 0;
+ for(; array_data_it != array_data_map_it->second.end(); array_data_it++) {
+ ImGui::Text("%d: %s", index, array_data_it->c_str());
+ index++;
+ }
+
+ ImGui::TreePop();
+ }
+ }
+
+ ImGui::TreePop();
+ }
+ } else {
+ ImGui::Text("%s", "No ArrayDataMap Data");
+ }
+
+ ImGui::TreePop();
+ }
+ }
+ }
+
+ ImGui::TreePop();
+ }
+ }
+
+ ImGui::End();
+ }
+
+ if(show_events) {
+ ImGui::SetNextWindowSize(ImVec2(300.0f, 146.0f), ImGuiCond_FirstUseEver);
+ ImGui::Begin("Events", &show_events);
+ static fixed_string<256> event_buf;
+ ImGui::InputText("Message", event_buf.ptr(), event_buf.max_size());
+
+ if(ImGui::Button("Send As Global Event")){
+ std::string msg(event_buf.str());
+ if( scenegraph ) {
+ scenegraph->SendScriptMessageToAllObjects(msg);
+ scenegraph->level->Message(msg);
+ }
+ ScriptableCampaign *sc = Engine::Instance()->GetCurrentCampaign();
+ if( sc ) {
+ sc->ReceiveMessage(msg);
+ }
+ }
+ if(ImGui::Button("Send As Campaign Event")){
+ std::string msg(event_buf.str());
+ ScriptableCampaign *sc = Engine::Instance()->GetCurrentCampaign();
+ if( sc ) {
+ sc->ReceiveMessage(msg);
+ }
+ }
+ if(ImGui::Button("Send As Level Event")){
+ std::string msg(event_buf.str());
+ if( scenegraph ) {
+ scenegraph->level->Message(msg);
+ }
+ }
+
+ if(ImGui::Button("Send as ScriptableUICallback")) {
+ std::string msg(event_buf.str());
+ Engine::Instance()->ScriptableUICallback(msg);
+ }
+
+ ImGui::End();
+ }
+
+ if (show_performance) {
+ ImGui::SetNextWindowSize(ImVec2(640.0f, 500.0f), ImGuiCond_FirstUseEver);
+ ImGui::Begin("Performance", &show_performance);
+ static PrecisionStopwatch frame_time_stopwatch;
+ const int kNumElements = 40;
+ static float frame_times[kNumElements] = {0.0f};
+ static bool paused = true;
+ ImGui::Checkbox("Pause", &paused);
+ static float max_time = 0.0f;
+ if(!paused){
+ max_time = 0.0f;
+ for(int i=0, len=kNumElements-1; i<len; ++i){
+ frame_times[i] = frame_times[i+1];
+ }
+ frame_times[kNumElements-1] = (float)(frame_time_stopwatch.StopAndReportNanoseconds() / 1000000.0);
+ for(int i=0, len=kNumElements; i<len; ++i){
+ max_time = max(max_time, frame_times[i]);
+ }
+ } else {
+ frame_time_stopwatch.StopAndReportNanoseconds();
+ }
+ ImGui::PlotHistogram("Frame Times", frame_times, kNumElements, 0, NULL, 0.0f, 100.0f, ImVec2(0,80));
+ ImGui::Text("Max time: %3.2f ms (%3.0f fps)", max_time, 1000.0f/(max_time));
+
+ ImGui::Separator();
+
+#if MONITOR_MEMORY
+ ImGui::Text("malloc Calls This Frame: %d", GetAndResetMallocAllocationCount() );
+ ImGui::Text("malloc Current Allocations: %d", GetCurrentNumberOfMallocAllocations() );
+
+ for( int i = 0; i < OG_MALLOC_TYPE_COUNT; i++ ) {
+ uint64_t mem_cb = GetCurrentTotalMallocAllocation(i);
+ if( mem_cb >= 1024 ) {
+ ImGui::Text("malloc Memory Use (%s): %u KiB\n", OgMallocTypeString(i), (unsigned)(mem_cb / 1024) );
+ } else {
+ ImGui::Text("malloc Memory Use (%s): %u B\n", OgMallocTypeString(i), (unsigned)mem_cb );
+ }
+ }
+#else
+ ImGui::TextWrapped( "Compile project with -DMONITOR_MEMORY to have detailed memory use information on malloc and new" );
+#endif
+ ImGui::Separator();
+
+ ImGui::Text("Stack Frame Allocs: %d", alloc.stack.GetAndResetAllocationCount());
+ ImGui::Text("Stack Current Allocs: %d", alloc.stack.GetCurrentAllocations());
+ if( alloc.stack.AllocatedAmountMemory() >= 1024 ) {
+ ImGui::Text("Stack Alloc Use: %u KiB", (unsigned)(alloc.stack.AllocatedAmountMemory() / 1024) );
+ } else {
+ ImGui::Text("Stack Alloc Use: %u B", (unsigned)(alloc.stack.AllocatedAmountMemory()) );
+ }
+
+ ImGui::Separator();
+ ImGui::Text("Total Active Assets: %d", assetmanager->GetLoadedAssetCount());
+
+ if( ImGui::IsItemClicked() ) {
+ asset_detail_list.clear();
+ asset_detail_list_load_flags.clear();
+ asset_detail_list_ref_count.clear();
+ asset_detail_list_load_warning.clear();
+
+ asset_detail_list_type = (AssetType)0;
+ asset_detail_title = "Complete Asset List";
+ for( size_t k = 0; k < assetmanager->GetLoadedAssetCount(); k++ ) {
+ asset_detail_list.push_back(assetmanager->GetAssetName(k));
+ asset_detail_list_load_flags.push_back(assetmanager->GetAssetHoldFlags(k));
+ asset_detail_list_ref_count.push_back(assetmanager->GetAssetRefCount(k));
+ asset_detail_list_load_warning.push_back(assetmanager->GetAssetLoadWarning(k));
+ }
+ }
+
+ ImGui::Text("Asset Load Warnings: %d", assetmanager->GetAssetWarningCount());
+
+ if( ImGui::IsItemClicked() ) {
+ asset_detail_list.clear();
+ asset_detail_list_load_flags.clear();
+ asset_detail_list_ref_count.clear();
+ asset_detail_list_load_warning.clear();
+
+ asset_detail_list_type = (AssetType)0;
+ asset_detail_title = "Asset Load Warning List";
+ for( size_t k = 0; k < assetmanager->GetLoadedAssetCount(); k++ ) {
+ if(assetmanager->GetAssetLoadWarning(k)) {
+ asset_detail_list.push_back(assetmanager->GetAssetName(k));
+ asset_detail_list_load_flags.push_back(assetmanager->GetAssetHoldFlags(k));
+ asset_detail_list_ref_count.push_back(assetmanager->GetAssetRefCount(k));
+ asset_detail_list_load_warning.push_back(assetmanager->GetAssetLoadWarning(k));
+ }
+ }
+ }
+
+ ImGui::Text("Assets held (Likely Preloaded): %d", assetmanager->GetAssetHoldCount());
+ if( ImGui::IsItemClicked() ) {
+ asset_detail_list.clear();
+ asset_detail_list_load_flags.clear();
+ asset_detail_list_ref_count.clear();
+ asset_detail_list_load_warning.clear();
+
+ asset_detail_list_type = (AssetType)0;
+ asset_detail_title = "Assets held";
+ for( size_t k = 0; k < assetmanager->GetLoadedAssetCount(); k++ ) {
+ if(assetmanager->GetAssetHoldFlags(k)) {
+ asset_detail_list.push_back(assetmanager->GetAssetName(k));
+ asset_detail_list_load_flags.push_back(assetmanager->GetAssetHoldFlags(k));
+ asset_detail_list_ref_count.push_back(assetmanager->GetAssetRefCount(k));
+ asset_detail_list_load_warning.push_back(assetmanager->GetAssetLoadWarning(k));
+ }
+ }
+ }
+
+ int tot = 0;
+ for( int i = 1; i < (int)ASSET_TYPE_FINAL; i++ ) {
+ int c = assetmanager->GetAssetTypeCount((AssetType)i);
+ ImGui::Text("Active %s: %d", GetAssetTypeString((AssetType)i), c);
+ tot += c;
+
+ if( ImGui::IsItemClicked() ) {
+ asset_detail_list.clear();
+ asset_detail_list_load_flags.clear();
+ asset_detail_list_ref_count.clear();
+ asset_detail_list_load_warning.clear();
+ asset_detail_list_type = (AssetType)i;
+ asset_detail_title = "Dump insight to loaded AssetType";
+ for( size_t k = 0; k < assetmanager->GetLoadedAssetCount(); k++ ) {
+ if(assetmanager->GetAssetType(k) == (AssetType)i) {
+ asset_detail_list.push_back(assetmanager->GetAssetName(k));
+ asset_detail_list_load_flags.push_back(assetmanager->GetAssetHoldFlags(k));
+ asset_detail_list_ref_count.push_back(assetmanager->GetAssetRefCount(k));
+ asset_detail_list_load_warning.push_back(assetmanager->GetAssetLoadWarning(k));
+ }
+ }
+
+ }
+ }
+
+ ImGui::Text("Total Sanity Check: %d", tot);
+
+ if( asset_detail_list.size() > 0 ) {
+ ImGui::Separator();
+ ImGui::Text("%s: %s", asset_detail_title, GetAssetTypeString(asset_detail_list_type));
+ if( ImGui::IsItemClicked() ) {
+ asset_detail_list.clear();
+ asset_detail_list_load_flags.clear();
+ asset_detail_list_ref_count.clear();
+ asset_detail_list_load_warning.clear();
+ }
+ for( unsigned int i = 0; i < asset_detail_list.size(); i++ ) {
+ const char* second = "";
+ if( asset_detail_list_load_flags[i] & HOLD_LOAD_MASK_PRELOAD ) {
+ ImGui::PushStyleColor(ImGuiCol_Text, IMGUI_GREEN);
+ second = " (Held by preloader)";
+ } else if( asset_detail_list_ref_count[i] == 0 ) {
+ ImGui::PushStyleColor(ImGuiCol_Text, IMGUI_RED);
+ } else if( asset_detail_list_load_warning[i] ) {
+ ImGui::PushStyleColor(ImGuiCol_Text, IMGUI_ORANGE);
+ second = " (Post Loadscreen Warning)";
+ } else {
+ ImGui::PushStyleColor(ImGuiCol_Text, IMGUI_WHITE);
+ }
+ ImGui::Text("(refc: %d) %s%s", (int)asset_detail_list_ref_count[i], asset_detail_list[i].c_str(),second);
+ ImGui::PopStyleColor();
+ }
+ }
+
+ ImGui::Separator();
+
+ Textures::Instance()->DrawImGuiDebug();
+
+ ImGui::Separator();
+
+ DrawAudioDebug();
+
+ ImGui::End();
+ }
+
+ if( show_log ) {
+ static bool show_log_scrollbar = false;
+ static bool show_log_spam = false;
+ static bool show_log_debug = false;
+ static bool show_log_info = false;
+ static bool show_log_warning = true;
+ static bool show_log_error = true;
+ static bool do_lock = false;
+ static bool do_pause = false;
+ static uint8_t *edit_log_field = NULL;
+ static uint32_t edit_log_field_size;
+ static uint32_t edit_log_selection[2] = {0,0};
+
+ bool do_pop_style = false;
+
+ int window_x = Graphics::Instance()->render_dims[0];
+ int window_y = Graphics::Instance()->render_dims[1];
+
+ ImGuiWindowFlags w_flags = 0;
+
+ if(do_lock) {
+ ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f,1.0f,1.0f,0.05f));
+ do_pop_style = true;
+ ImGui::SetNextWindowPos(ImVec2(10, window_y - ImGui::GetTextLineHeight() * 20 - 10), ImGuiCond_Always);
+ ImGui::SetNextWindowSize(ImVec2(window_x * 0.8f, ImGui::GetTextLineHeight() * 20), ImGuiCond_Always);
+ w_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs;
+ } else {
+ ImGui::SetNextWindowSize(ImVec2(700,500), ImGuiCond_FirstUseEver);
+ }
+
+ ImGui::Begin("Log", &show_log, w_flags);
+
+ ImGui::BeginChild("settings", ImVec2(0,20));
+ if( do_pause == false ) {
+ ImGui::Checkbox("Show Scrollbar", &show_log_scrollbar);
+ ImGui::SameLine();
+ ImGui::Checkbox("Info", &show_log_info);
+ ImGui::SameLine();
+ ImGui::Checkbox("Warnings", &show_log_warning);
+ ImGui::SameLine();
+ ImGui::Checkbox("Errors", &show_log_error);
+ ImGui::SameLine();
+ ImGui::Checkbox("Debug", &show_log_debug);
+ ImGui::SameLine();
+ ImGui::Checkbox("Spam", &show_log_spam);
+ ImGui::SameLine();
+ ImGui::Checkbox("Lock", &do_lock);
+ ImGui::SameLine();
+ }
+ if(ImGui::Checkbox("Pause", &do_pause)) {
+ if( do_pause ) {
+ ram_handler.Lock();
+ uint32_t count = ram_handler.GetCount();
+ uint32_t current_pos = 0;
+ edit_log_field_size = 0;
+ if(edit_log_field) {
+ OG_FREE(edit_log_field);
+ edit_log_field = NULL;
+ }
+ for( uint32_t stage = 0; stage < 2; stage++ ) {
+ if( stage == 1 ) {
+ edit_log_field = (uint8_t*)OG_MALLOC(edit_log_field_size+1);
+ edit_log_field[edit_log_field_size] = '\0';
+ }
+ for( uint32_t i = 0; i < count; i++ ) {
+ RamHandlerLogRow rhlr = ram_handler.GetLog(i);
+
+ const char* level_string = "";
+ bool skip = false;
+ bool pushed_style = false;
+
+ switch( rhlr.type )
+ {
+ case LogSystem::spam:
+ level_string = "s";
+ skip = !show_log_spam;
+ break;
+ case LogSystem::debug:
+ level_string = "d";
+ skip = !show_log_debug;
+ break;
+ case LogSystem::info:
+ level_string = "i";
+ skip = !show_log_info;
+ break;
+ case LogSystem::warning:
+ level_string = "w";
+ skip = !show_log_warning;
+ break;
+ case LogSystem::error:
+ level_string = "e";
+ skip = !show_log_error;
+ break;
+ case LogSystem::fatal:
+ level_string = "f";
+ break;
+ default:
+ level_string = "u";
+ break;
+ }
+
+ if( skip == false ) {
+ if( stage == 0 ) {
+ edit_log_field_size += snprintf(NULL, 0, "[%s][%2s][%-17s]:%-4u: %s", level_string, rhlr.prefix, rhlr.filename, rhlr.row, rhlr.data);
+ } else {
+ current_pos += snprintf((char*)(edit_log_field + current_pos), edit_log_field_size - current_pos, "[%s][%2s][%-17s]:%-4u: %s", level_string, rhlr.prefix, rhlr.filename, rhlr.row, rhlr.data);
+ assert(current_pos < edit_log_field_size);
+ }
+ }
+ }
+ }
+ ram_handler.Unlock();
+ } else {
+ if(edit_log_field) {
+ OG_FREE(edit_log_field);
+ edit_log_field = NULL;
+ }
+ }
+ }
+
+ size_t selection_begin;
+ size_t selection_length;
+ if( edit_log_selection[1] > edit_log_selection[0] ) {
+ selection_length = edit_log_selection[1] - edit_log_selection[0];
+ selection_begin = edit_log_selection[0];
+ } else {
+ selection_length = edit_log_selection[0] - edit_log_selection[1];
+ selection_begin = edit_log_selection[1];
+ }
+
+ if(do_pause) {
+ ImGui::SameLine();
+ if(ImGui::Button("Copy Selection")) {
+ if( selection_length > 0 ) {
+ uint8_t* selection = (uint8_t*)OG_MALLOC(selection_length + 1);
+ if( selection_begin + selection_length < edit_log_field_size ) {
+ memcpy(selection, edit_log_field+selection_begin, selection_length);
+ selection[selection_length] = '\0';
+ SDL_SetClipboardText((char*)selection);
+ }
+ OG_FREE(selection);
+ } else {
+ uint8_t* selection = (uint8_t*)OG_MALLOC(edit_log_field_size);
+ memcpy(selection, edit_log_field, edit_log_field_size);
+ selection[edit_log_field_size-1] = '\0';
+ SDL_SetClipboardText((char*)selection);
+ OG_FREE(selection);
+ }
+ }
+ }
+ ImGui::EndChild();
+
+ w_flags = 0;
+
+ if( show_log_scrollbar == false ) {
+ w_flags |= ImGuiWindowFlags_NoScrollbar;
+ }
+
+ ImGui::BeginChild("Log body", ImVec2(0,0), !do_lock, w_flags);
+ if( do_pause ) {
+ ImGui::InputTextMultiline("log_edit", (char*)edit_log_field, edit_log_field_size, ImVec2(-1,-1), ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CallbackAlways, LogWindowCallback, &edit_log_selection );
+ } else {
+ ram_handler.Lock();
+ uint32_t count = ram_handler.GetCount();
+ for( uint32_t i = 0; i < count; i++ ) {
+ RamHandlerLogRow rhlr = ram_handler.GetLog(i);
+
+ const char* level_string = "";
+ bool skip = false;
+ bool pushed_style = false;
+
+ ImVec4 text_color = ImGui::GetStyle().Colors[ImGuiCol_Text];
+ switch( rhlr.type )
+ {
+ case LogSystem::spam:
+ level_string = "s";
+ skip = !show_log_spam;
+ break;
+ case LogSystem::debug:
+ level_string = "d";
+ skip = !show_log_debug;
+ break;
+ case LogSystem::info:
+ level_string = "i";
+ skip = !show_log_info;
+ text_color = ImVec4(144.0f/255.0f,192.0f/255.0f,34.0f/255.0f,1.0f);
+ break;
+ case LogSystem::warning:
+ level_string = "w";
+ skip = !show_log_warning;
+ text_color = ImVec4(229.0f/255.0f,220.0f/255.0f,89.0f/255.0f,1.0f);
+ break;
+ case LogSystem::error:
+ level_string = "e";
+ skip = !show_log_error;
+ text_color = ImVec4(1.0f,107.0f/255.0f,104.0f/255.0f,1.0f);
+ break;
+ case LogSystem::fatal:
+ level_string = "f";
+ text_color = ImVec4(1.0f,107.0f/255.0f,104.0f/255.0f,1.0f);
+ break;
+ default:
+ level_string = "u";
+ break;
+ }
+
+ if( skip == false ) {
+ ImGui::PushStyleColor(ImGuiCol_Text, text_color);
+ ImGui::Text("[%s][%2s][%-17s]:%-4u: %s", level_string, rhlr.prefix, rhlr.filename, rhlr.row, rhlr.data);
+ ImGui::PopStyleColor();
+ }
+ }
+ ram_handler.Unlock();
+ }
+
+ if( show_log_scrollbar == false ) {
+ ImGui::SetScrollY(ImGui::GetScrollMaxY());
+ }
+
+ ImGui::EndChild();
+
+ ImGui::End();
+
+ if(do_pop_style) {
+ ImGui::PopStyleColor();
+ }
+ }
+
+ if( show_warnings ) {
+ ImGuiWindowFlags w_flags = 0;
+ ImGui::Begin("Warnings", &show_warnings, w_flags);
+
+ if( scenegraph ) {
+ const int kBufSize = 1024;
+ char* buf_sanityerror = (char*)alloc.stack.Alloc(kBufSize);
+ char* buf_dispname = (char*)alloc.stack.Alloc(kBufSize);
+ for( unsigned i = 0; i < SceneGraph::kMaxWarnings; i++ ) {
+ ObjectSanityState& sanity = scenegraph->sanity_list[i];
+ if(sanity.Valid() && sanity.Ok() == false) {
+ Object* obj = scenegraph->GetObjectFromID(sanity.GetID());
+ if(obj) {
+ obj->GetDisplayName(buf_dispname, kBufSize);
+
+ ImGuiTreeNodeFlags node_flags = 0UL; //ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
+ if(obj->Selected()) {
+ node_flags |= ImGuiTreeNodeFlags_Selected;
+ }
+
+ node_flags |= ImGuiTreeNodeFlags_Leaf;
+
+ ImGui::PushID(obj->GetID());
+ vec4 color = GetObjColor(obj);
+ ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(color[0], color[1], color[2], color[3]));
+
+ for( unsigned k = 0; k < sanity.GetErrorCount(); k++ ) {
+ sanity.GetError(k,buf_sanityerror,kBufSize);
+ bool node_open = ImGui::TreeNodeEx("", node_flags, "%d (%s): %s", sanity.GetID(), buf_dispname, buf_sanityerror);
+
+ if( node_open ) {
+ ImGui::TreePop();
+ }
+ }
+
+ ImGui::PopStyleColor(1);
+ ImGui::PopID();
+ if (ImGui::IsItemClicked()) {
+ scenegraph->UnselectAll();
+ obj->Select(true);
+ }
+ }
+ }
+ }
+ alloc.stack.Free(buf_dispname);
+ alloc.stack.Free(buf_sanityerror);
+ }
+ ImGui::End();
+ }
+
+ static ASContext* active_asdebugger_context = NULL;
+ if( show_asdebugger_contexts ) {
+ ImGui::SetNextWindowSize(ImVec2(640.0f, 480.0f), ImGuiCond_FirstUseEver);
+ ImGui::Begin("AS debugger contexts", &show_asdebugger_contexts);
+
+ static char debug_buffer[256] = "somefile.as:123";
+ ImGui::InputText("##Breakpoint", debug_buffer, 256);
+ ImGui::SameLine();
+ if(ImGui::Button("Set breakpoint")) {
+ for(size_t i = 0; i < strlen(debug_buffer); ++i) {
+ if(debug_buffer[i] == ':') {
+ debug_buffer[i] = '\0';
+ int line = atoi(&debug_buffer[i + 1]);
+ ASDebugger::AddGlobalBreakpoint(debug_buffer, line);
+ }
+ }
+ }
+
+ ImGui::Text("File breakpoints");
+ const std::vector<std::pair<std::string, int> > global_breakpoints = ASDebugger::GetGlobalBreakpoints();
+ for(size_t i = 0; i < global_breakpoints.size(); ++i) {
+ char buffer[256];
+ sprintf(buffer, "%s:%d", global_breakpoints[i].first.c_str(), global_breakpoints[i].second);
+ if(ImGui::Button(buffer)) {
+ ASDebugger::RemoveGlobalBreakpoint(global_breakpoints[i].first.c_str(), global_breakpoints[i].second);
+ }
+ }
+
+ ImGui::Separator();
+ ImGui::Text("Active contexts");
+ const std::vector<ASContext*>& active_contexts = ASDebugger::GetActiveContexts();
+
+ for(size_t i = 0; i < active_contexts.size(); ++i) {
+ ImGui::PushID((int)i);
+ if(ImGui::Button(active_contexts[i]->current_script.GetOriginalPath())) {
+ active_asdebugger_context = active_contexts[i];
+ }
+ ImGui::PopID();
+ }
+
+ ImGui::End();
+ }
+
+ if( active_asdebugger_context != NULL ) {
+ const std::vector<ASContext*>& active_contexts = ASDebugger::GetActiveContexts();
+ // Contexts may disappear whenever
+ if(std::find(active_contexts.begin(), active_contexts.end(), active_asdebugger_context) == active_contexts.end()) {
+ active_asdebugger_context = NULL;
+ } else {
+ bool is_open = true;
+ ImGui::SetNextWindowSize(ImVec2(1024.0f, 768.0f), ImGuiCond_FirstUseEver);
+ ImGui::Begin("AS debugger preview", &is_open);
+
+ const ScriptFile* script = ScriptFileUtil::GetScriptFile(active_asdebugger_context->current_script);
+ const char* file_name = script->file_path.GetFullPath();
+ size_t last_slash = 0;
+ for(size_t i = 0; i < std::strlen(file_name); ++i) {
+ if(file_name[i] == '\\' || file_name[i] == '/')
+ last_slash = i;
+ }
+ file_name += last_slash + 1;
+
+ ImGui::Text("%s", file_name);
+
+ const std::string& script_source = script->unexpanded_contents;
+ int start_index = 0;
+ int end_index = 0;
+
+ ImGui::BeginChild("ASSourceMain", ImVec2(0,0), false, 0);
+ ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
+ ImGui::Columns(2);
+
+ int line_nr = 1;
+
+ const int MAX_LINE_LENGTH = 255;
+ char line[MAX_LINE_LENGTH + 1];
+
+ const std::vector<int>* breakpoints = active_asdebugger_context->dbg.GetBreakpoints(file_name);
+
+ ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.5f));
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
+ while(end_index != (int)script_source.size()) {
+ end_index = (int)std::min(script_source.find('\n', start_index), script_source.size());
+
+ int copy_length = std::min(MAX_LINE_LENGTH, end_index - start_index);
+ std::memcpy(line, script_source.c_str() + start_index, copy_length);
+ line[copy_length] = '\0';
+ start_index = end_index + 1;
+
+ bool is_breakpoint = false;
+ if(breakpoints != NULL) {
+ for(size_t i = 0; i < breakpoints->size(); ++i) {
+ if(breakpoints->at(i) == line_nr) {
+ is_breakpoint = true;
+ break;
+ }
+ }
+ }
+
+ if(is_breakpoint)
+ ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.0f, 0.0f, 0.5f));
+
+ ImGui::Text("%d", line_nr);
+ ImGui::NextColumn();
+ ImGui::PushID(line_nr);
+ ImGui::SetColumnOffset(-1, 36);
+ if(ImGui::ButtonEx(line, ImVec2(ImGui::GetWindowWidth() - 36, 0), ImGuiButtonFlags_AlignTextBaseLine)) {
+ active_asdebugger_context->dbg.ToggleBreakpoint(file_name, line_nr);
+ }
+ ImGui::NextColumn();
+ ImGui::PopID();
+ if(is_breakpoint)
+ ImGui::PopStyleColor();
+
+ ++line_nr;
+ }
+ ImGui::PopStyleVar(2);
+
+ ImGui::PopStyleColor();
+ ImGui::EndChild();
+ ImGui::End();
+
+ if(!is_open)
+ active_asdebugger_context = NULL;
+ }
+ }
+
+ if( show_asprofiler ) {
+ ImGui::SetNextWindowSize(ImVec2(640.0f, 480.0f), ImGuiCond_FirstUseEver);
+ ImGui::Begin("AS profiler", &show_asprofiler);
+
+ ImGui::Separator();
+ ImGui::Text("Active contexts");
+ const std::vector<ASContext*>& active_contexts = ASProfiler::GetActiveContexts();
+
+ for(size_t i = 0; i < active_contexts.size(); ++i) {
+ ImGui::PushID((int)i);
+ if(ImGui::Checkbox(active_contexts[i]->current_script.GetOriginalPath(), &active_contexts[i]->profiler.enabled)) {
+ }
+ ImGui::PopID();
+ }
+
+ ImGui::Separator();
+ ImGui::Text("Times in microseconds");
+ ImGui::Text("Showing history over %.2f seconds", ASProfiler::GetMaxWindowSize() * ASProfiler::GetMaxTimeHistorySize() / 120.0f);
+ ImGui::Text("Each time point is calculated by taking the max time over %d frames", ASProfiler::GetMaxWindowSize());
+ ImGui::Text("Times may appear to be lagging because not all functions are called each frame");
+
+ for(size_t i = 0; i < active_contexts.size(); ++i) {
+ if(active_contexts[i]->profiler.enabled)
+ active_contexts[i]->profiler.Draw();
+ }
+
+ ImGui::End();
+ }
+
+ if( show_graphics_debug_disable_menu ) {
+
+ ImGui::SetNextWindowSize(ImVec2(640.0f, 480.0f), ImGuiCond_FirstUseEver);
+ ImGui::Begin("Graphics Debug Disable", &show_graphics_debug_disable_menu);
+
+ ImGui::Text("Warning: Messing with these will likely crash your game.");
+ ImGui::Text("These settings are not saved across runs.");
+
+ if (ImGui::Button("Enable All")) {
+ g_debug_runtime_disable_blood_surface_pre_draw = false;
+ g_debug_runtime_disable_debug_draw = false;
+ g_debug_runtime_disable_debug_ribbon_draw = false;
+ g_debug_runtime_disable_decal_object_draw = false;
+ g_debug_runtime_disable_decal_object_pre_draw_frame = false;
+ g_debug_runtime_disable_detail_object_surface_draw = false;
+ g_debug_runtime_disable_detail_object_surface_pre_draw = false;
+ g_debug_runtime_disable_dynamic_light_object_draw = false;
+ g_debug_runtime_disable_env_object_draw = false;
+ g_debug_runtime_disable_env_object_draw_depth_map = false;
+ g_debug_runtime_disable_env_object_draw_detail_object_instances = false;
+ g_debug_runtime_disable_env_object_draw_instances = false;
+ g_debug_runtime_disable_env_object_draw_instances_transparent = false;
+ g_debug_runtime_disable_env_object_pre_draw_camera = false;
+ g_debug_runtime_disable_flares_draw = false;
+ g_debug_runtime_disable_gpu_particle_field_draw = false;
+ g_debug_runtime_disable_group_pre_draw_camera = false;
+ g_debug_runtime_disable_group_pre_draw_frame = false;
+ g_debug_runtime_disable_hotspot_draw = false;
+ g_debug_runtime_disable_hotspot_pre_draw_frame = false;
+ g_debug_runtime_disable_item_object_draw = false;
+ g_debug_runtime_disable_item_object_draw_depth_map = false;
+ g_debug_runtime_disable_item_object_pre_draw_frame = false;
+ g_debug_runtime_disable_morph_target_pre_draw_camera = false;
+ g_debug_runtime_disable_movement_object_draw = false;
+ g_debug_runtime_disable_movement_object_draw_depth_map = false;
+ g_debug_runtime_disable_movement_object_pre_draw_camera = false;
+ g_debug_runtime_disable_movement_object_pre_draw_frame = false;
+ g_debug_runtime_disable_navmesh_connection_object_draw = false;
+ g_debug_runtime_disable_navmesh_hint_object_draw = false;
+ g_debug_runtime_disable_particle_draw = false;
+ g_debug_runtime_disable_particle_system_draw = false;
+ g_debug_runtime_disable_pathpoint_object_draw = false;
+ g_debug_runtime_disable_placeholder_object_draw = false;
+ g_debug_runtime_disable_reflection_capture_object_draw = false;
+ g_debug_runtime_disable_rigged_object_draw = false;
+ g_debug_runtime_disable_rigged_object_pre_draw_camera = false;
+ g_debug_runtime_disable_rigged_object_pre_draw_frame = false;
+ g_debug_runtime_disable_scene_graph_draw = false;
+ g_debug_runtime_disable_scene_graph_draw_depth_map = false;
+ g_debug_runtime_disable_scene_graph_prepare_lights_and_decals = false;
+ g_debug_runtime_disable_sky_draw = false;
+ g_debug_runtime_disable_terrain_object_draw_depth_map = false;
+ g_debug_runtime_disable_terrain_object_draw_terrain = false;
+ g_debug_runtime_disable_terrain_object_pre_draw_camera = false;
+ }
+
+ if (ImGui::Button("Disable Drawing Objects")) {
+ g_debug_runtime_disable_debug_draw = true;
+ g_debug_runtime_disable_debug_ribbon_draw = true;
+ g_debug_runtime_disable_decal_object_draw = true;
+ g_debug_runtime_disable_detail_object_surface_draw = true;
+ g_debug_runtime_disable_dynamic_light_object_draw = true;
+ g_debug_runtime_disable_env_object_draw = true;
+ g_debug_runtime_disable_env_object_draw_depth_map = true;
+ g_debug_runtime_disable_env_object_draw_detail_object_instances = true;
+ g_debug_runtime_disable_env_object_draw_instances = true;
+ g_debug_runtime_disable_env_object_draw_instances_transparent = true;
+ g_debug_runtime_disable_flares_draw = true;
+ g_debug_runtime_disable_gpu_particle_field_draw = true;
+ g_debug_runtime_disable_hotspot_draw = true;
+ g_debug_runtime_disable_item_object_draw = true;
+ g_debug_runtime_disable_item_object_draw_depth_map = true;
+ g_debug_runtime_disable_movement_object_draw = true;
+ g_debug_runtime_disable_movement_object_draw_depth_map = true;
+ g_debug_runtime_disable_navmesh_connection_object_draw = true;
+ g_debug_runtime_disable_navmesh_hint_object_draw = true;
+ g_debug_runtime_disable_particle_draw = true;
+ g_debug_runtime_disable_particle_system_draw = true;
+ g_debug_runtime_disable_pathpoint_object_draw = true;
+ g_debug_runtime_disable_placeholder_object_draw = true;
+ g_debug_runtime_disable_reflection_capture_object_draw = true;
+ g_debug_runtime_disable_rigged_object_draw = true;
+ g_debug_runtime_disable_sky_draw = true;
+ g_debug_runtime_disable_terrain_object_draw_depth_map = true;
+ g_debug_runtime_disable_terrain_object_draw_terrain = true;
+ }
+
+ if (ImGui::Button("Disable All")) {
+ g_debug_runtime_disable_blood_surface_pre_draw = true;
+ g_debug_runtime_disable_debug_draw = true;
+ g_debug_runtime_disable_debug_ribbon_draw = true;
+ g_debug_runtime_disable_decal_object_draw = true;
+ g_debug_runtime_disable_decal_object_pre_draw_frame = true;
+ g_debug_runtime_disable_detail_object_surface_draw = true;
+ g_debug_runtime_disable_detail_object_surface_pre_draw = true;
+ g_debug_runtime_disable_dynamic_light_object_draw = true;
+ g_debug_runtime_disable_env_object_draw = true;
+ g_debug_runtime_disable_env_object_draw_depth_map = true;
+ g_debug_runtime_disable_env_object_draw_detail_object_instances = true;
+ g_debug_runtime_disable_env_object_draw_instances = true;
+ g_debug_runtime_disable_env_object_draw_instances_transparent = true;
+ g_debug_runtime_disable_env_object_pre_draw_camera = true;
+ g_debug_runtime_disable_flares_draw = true;
+ g_debug_runtime_disable_gpu_particle_field_draw = true;
+ g_debug_runtime_disable_group_pre_draw_camera = true;
+ g_debug_runtime_disable_group_pre_draw_frame = true;
+ g_debug_runtime_disable_hotspot_draw = true;
+ g_debug_runtime_disable_hotspot_pre_draw_frame = true;
+ g_debug_runtime_disable_item_object_draw = true;
+ g_debug_runtime_disable_item_object_draw_depth_map = true;
+ g_debug_runtime_disable_item_object_pre_draw_frame = true;
+ g_debug_runtime_disable_morph_target_pre_draw_camera = true;
+ g_debug_runtime_disable_movement_object_draw = true;
+ g_debug_runtime_disable_movement_object_draw_depth_map = true;
+ g_debug_runtime_disable_movement_object_pre_draw_camera = true;
+ g_debug_runtime_disable_movement_object_pre_draw_frame = true;
+ g_debug_runtime_disable_navmesh_connection_object_draw = true;
+ g_debug_runtime_disable_navmesh_hint_object_draw = true;
+ g_debug_runtime_disable_particle_draw = true;
+ g_debug_runtime_disable_particle_system_draw = true;
+ g_debug_runtime_disable_pathpoint_object_draw = true;
+ g_debug_runtime_disable_placeholder_object_draw = true;
+ g_debug_runtime_disable_reflection_capture_object_draw = true;
+ g_debug_runtime_disable_rigged_object_draw = true;
+ g_debug_runtime_disable_rigged_object_pre_draw_camera = true;
+ g_debug_runtime_disable_rigged_object_pre_draw_frame = true;
+ g_debug_runtime_disable_scene_graph_draw = true;
+ g_debug_runtime_disable_scene_graph_draw_depth_map = true;
+ g_debug_runtime_disable_scene_graph_prepare_lights_and_decals = true;
+ g_debug_runtime_disable_sky_draw = true;
+ g_debug_runtime_disable_terrain_object_draw_depth_map = true;
+ g_debug_runtime_disable_terrain_object_draw_terrain = true;
+ g_debug_runtime_disable_terrain_object_pre_draw_camera = true;
+ }
+
+ ImGui::TextColored(ImVec4(1, 1, 0, 1), "Individual options");
+ ImGui::BeginChild("Scrolling");
+
+ ImGui::Checkbox("Disable Scene Graph Draw", &g_debug_runtime_disable_scene_graph_draw);
+ ImGui::Checkbox("Disable Scene Graph Draw Depth Map", &g_debug_runtime_disable_scene_graph_draw_depth_map);
+ ImGui::Checkbox("Disable Scene Graph Prepare Lights And Decals", &g_debug_runtime_disable_scene_graph_prepare_lights_and_decals);
+
+ ImGui::Separator();
+
+ ImGui::Checkbox("Disable Debug Draw", &g_debug_runtime_disable_debug_draw);
+ ImGui::Checkbox("Disable Debug Ribbon Draw", &g_debug_runtime_disable_debug_ribbon_draw);
+ ImGui::Checkbox("Disable Decal Object Draw", &g_debug_runtime_disable_decal_object_draw);
+ ImGui::Checkbox("Disable Detail Object Surface Draw", &g_debug_runtime_disable_detail_object_surface_draw);
+ ImGui::Checkbox("Disable Dynamic Light Object Draw", &g_debug_runtime_disable_dynamic_light_object_draw);
+ ImGui::Checkbox("Disable Env Object Draw", &g_debug_runtime_disable_env_object_draw);
+ ImGui::Checkbox("Disable Env Object Draw Depth Map", &g_debug_runtime_disable_env_object_draw_depth_map);
+ ImGui::Checkbox("Disable Env Object Draw Detail Object Instances", &g_debug_runtime_disable_env_object_draw_detail_object_instances);
+ ImGui::Checkbox("Disable Env Object Draw Instances", &g_debug_runtime_disable_env_object_draw_instances);
+ ImGui::Checkbox("Disable Env Object Draw Instances Transparent", &g_debug_runtime_disable_env_object_draw_instances_transparent);
+ ImGui::Checkbox("Disable Flares Draw", &g_debug_runtime_disable_flares_draw);
+ ImGui::Checkbox("Disable Gpu Particle Field Draw", &g_debug_runtime_disable_gpu_particle_field_draw);
+ ImGui::Checkbox("Disable Hotspot Draw", &g_debug_runtime_disable_hotspot_draw);
+ ImGui::Checkbox("Disable Item Object Draw", &g_debug_runtime_disable_item_object_draw);
+ ImGui::Checkbox("Disable Item Object Draw Depth Map", &g_debug_runtime_disable_item_object_draw_depth_map);
+ ImGui::Checkbox("Disable Movement Object Draw", &g_debug_runtime_disable_movement_object_draw);
+ ImGui::Checkbox("Disable Movement Object Draw Depth Map", &g_debug_runtime_disable_movement_object_draw_depth_map);
+ ImGui::Checkbox("Disable Navmesh Connection Object Draw", &g_debug_runtime_disable_navmesh_connection_object_draw);
+ ImGui::Checkbox("Disable Navmesh Hint Object Draw", &g_debug_runtime_disable_navmesh_hint_object_draw);
+ ImGui::Checkbox("Disable Particle Draw", &g_debug_runtime_disable_particle_draw);
+ ImGui::Checkbox("Disable Particle System Draw", &g_debug_runtime_disable_particle_system_draw);
+ ImGui::Checkbox("Disable Pathpoint Object Draw", &g_debug_runtime_disable_pathpoint_object_draw);
+ ImGui::Checkbox("Disable Placeholder Object Draw", &g_debug_runtime_disable_placeholder_object_draw);
+ ImGui::Checkbox("Disable Reflection Capture Object Draw", &g_debug_runtime_disable_reflection_capture_object_draw);
+ ImGui::Checkbox("Disable Rigged Object Draw", &g_debug_runtime_disable_rigged_object_draw);
+ ImGui::Checkbox("Disable Sky Draw", &g_debug_runtime_disable_sky_draw);
+ ImGui::Checkbox("Disable Terrain Object Draw Depth Map", &g_debug_runtime_disable_terrain_object_draw_depth_map);
+ ImGui::Checkbox("Disable Terrain Object Draw Terrain", &g_debug_runtime_disable_terrain_object_draw_terrain);
+
+ ImGui::Separator();
+
+ ImGui::Checkbox("Disable Blood Surface Pre-Draw", &g_debug_runtime_disable_blood_surface_pre_draw);
+ ImGui::Checkbox("Disable Decal Object Pre-Draw Frame", &g_debug_runtime_disable_decal_object_pre_draw_frame);
+ ImGui::Checkbox("Disable Detail Object Surface Pre-Draw", &g_debug_runtime_disable_detail_object_surface_pre_draw);
+ ImGui::Checkbox("Disable Env Object Pre-Draw Camera", &g_debug_runtime_disable_env_object_pre_draw_camera);
+ ImGui::Checkbox("Disable Group Pre-Draw Camera", &g_debug_runtime_disable_group_pre_draw_camera);
+ ImGui::Checkbox("Disable Group Pre-Draw Frame", &g_debug_runtime_disable_group_pre_draw_frame);
+ ImGui::Checkbox("Disable Hotspot Pre-Draw Frame", &g_debug_runtime_disable_hotspot_pre_draw_frame);
+ ImGui::Checkbox("Disable Item Object Pre-Draw Frame", &g_debug_runtime_disable_item_object_pre_draw_frame);
+ ImGui::Checkbox("Disable Morph Target Pre-Draw Camera", &g_debug_runtime_disable_morph_target_pre_draw_camera);
+ ImGui::Checkbox("Disable Movement Object Pre-Draw Camera", &g_debug_runtime_disable_movement_object_pre_draw_camera);
+ ImGui::Checkbox("Disable Movement Object Pre-Draw Frame", &g_debug_runtime_disable_movement_object_pre_draw_frame);
+ ImGui::Checkbox("Disable Rigged Object Pre-Draw Camera", &g_debug_runtime_disable_rigged_object_pre_draw_camera);
+ ImGui::Checkbox("Disable Rigged Object Pre-Draw Frame", &g_debug_runtime_disable_rigged_object_pre_draw_frame);
+ ImGui::Checkbox("Disable Terrain Object Pre-Draw Camera", &g_debug_runtime_disable_terrain_object_pre_draw_camera);
+
+ ImGui::EndChild();
+
+ ImGui::End();
+ }
+
+ if( show_sound ) {
+ ImGui::SetNextWindowSize(ImVec2(400.0f, 300.0f), ImGuiCond_FirstUseEver);
+ ImGui::Begin("Sound", &show_sound);
+
+ ImGui::End();
+ }
+
+ if (show_input_debug) {
+ ImGui::SetNextWindowSize(ImVec2(400.0f, 300.0f), ImGuiCond_FirstUseEver);
+ ImGui::Begin("Input Debug", &show_input_debug);
+
+ Input::JoystickMap& open_joysticks = Input::Instance()->GetOpenJoysticks();
+
+
+ std::vector<MovementObject*> controlled_movementobjects;
+ std::vector<MovementObject*> controllable_movement_objects;
+
+ if (scenegraph != nullptr) {
+ controlled_movementobjects = scenegraph->GetControlledMovementObjects();
+ controllable_movement_objects = scenegraph->GetControllableMovementObjects();
+ }
+
+ auto RenderMovementObject = [&](MovementObject* mo) {
+ ImGui::Text("ID %d", mo->GetID());
+ ImGui::Text("Name: %s", mo->name.c_str());
+ ImGui::Text("Obj File: %s", mo->obj_file.c_str());
+ ImGui::Text("Actor File: %s", mo->actor_file_ref->character.c_str());
+ ImGui::Text("Controlled: %s", mo->controlled ? "true" : "false");
+ };
+
+ auto RenderControlledMovementObject = [&](int controller_id) -> void {
+ bool found = false;
+
+ ImGui::PushID(controller_id);
+ ImGui::BeginGroup();
+ ImGui::Text("Avatar");
+ ImGui::Indent(20.0f);
+ for (MovementObject* mo : controlled_movementobjects) {
+ if (mo->controller_id == controller_id) {
+ RenderMovementObject(mo);
+ found = true;
+ }
+ }
+
+ if(found == false) {
+ ImGui::Text("No Avatar Assigned");
+ }
+
+ ImGui::Unindent(20.0f);
+ ImGui::EndGroup();
+ ImGui::PopID();
+ };
+
+ ImGui::Text("Input Sources");
+ ImGui::Separator();
+
+ ImGui::Indent(20.0f);
+ ImGui::Text("Keyboard");
+ ImGui::Text("player_input: 0");
+ RenderControlledMovementObject(0);
+ ImGui::Separator();
+
+ int joystick_subid = 0;
+ ImGui::PushID("joystick_inputs");
+ for (auto joystick : open_joysticks) {
+ ImGui::PushID(joystick_subid);
+ SDL_Joystick* sdl_joystick = joystick.second->sdl_joystick;
+ ImGui::Text("Gamepad #%d: \"%s\"", joystick.first, SDL_JoystickName(sdl_joystick));
+ ImGui::Text("player_input: %d", joystick.second->player_input);
+ //ImGui::BeginCombo("Change Avatar");
+ //for(MovementObject* mo : controllable_movement_objects) {
+ // //ImGui::Selectable
+ //}
+ //ImGui::EndCombo();
+ RenderControlledMovementObject(joystick.second->player_input);
+ ImGui::Separator();
+ ImGui::PopID();
+ }
+ ImGui::PopID();
+ ImGui::Unindent(20.0f);
+
+ if (online->IsActive()) {
+ ImGui::PushID("virtual_sources");
+ ImGui::Text("Virtual Sources");
+
+ ImGui::Indent(20.0f);
+
+ int mp_subid = 0;
+ for (auto& it : online->online_session->player_states) {
+ if (it.second.controller_id != -1) {
+ ImGui::PushID(mp_subid++);
+ ImGui::Text("player_state: %d", it.first);
+ ImGui::Text("player_input: %d", it.second.controller_id);
+
+ RenderControlledMovementObject(it.second.controller_id);
+ ImGui::Separator();
+ ImGui::PopID();
+ }
+ }
+ ImGui::PopID();
+ ImGui::Unindent(20.0f);
+ }
+
+ ImGui::Text("Player Movement Objects");
+
+ ImGui::Indent(20.0f);
+ for(MovementObject* mo : controllable_movement_objects) {
+ RenderMovementObject(mo);
+ ImGui::Separator();
+ }
+
+ ImGui::Unindent(20.0f);
+ ImGui::End();
+ }
+
+ if (show_pose) {
+ std::string animationPath = "";
+ std::vector<ModInstance*> all_mods = ModLoading::Instance().GetAllMods();
+
+ ImGui::Begin("Test pose", &show_pose);
+ for (const auto& m : all_mods) {
+ for (const auto& p : m->poses) {
+ if (ImGui::Button(p.name)) {
+ animationPath = p.path;
+ }
+ }
+ }
+ ImGui::End();
+ SceneGraph* scene_graph = Engine::Instance()->GetSceneGraph();
+ if (scene_graph) {
+ std::vector<MovementObject*> movement_objects = scene_graph->GetControllableMovementObjects();
+ if (!movement_objects.empty() && !animationPath.empty()) {
+ MovementObject* player1 = movement_objects.front();
+ for (int i = 1; i < movement_objects.size(); i++) {
+ if (movement_objects[i]->controller_id < player1->controller_id) {
+ player1 = movement_objects[i];
+ }
+ }
+ player1->StartPoseAnimation(animationPath);
+ }
+ }
+ }
+
+ if (show_select_script) {
+
+ ImGui::Begin("Select script", &show_select_script);
+
+ std::vector<std::string> script_paths;
+ std::string script_path;
+ for (int i = 0; i < vanilla_data_paths.num_paths; i++) {
+ script_path = vanilla_data_paths.paths[i] + script_dir_path;
+ if (FindFilePath(script_path, kAnyPath, false).isValid()) {
+ script_paths.push_back(std::move(script_path));
+ }
+ }
+ for (int i = 0; i < mod_paths.num_paths; i++) {
+ script_path = mod_paths.paths[i] + script_dir_path;
+ if (FindFilePath(script_path, kAnyPath, false).isValid()) {
+ script_paths.push_back(std::move(script_path));
+ }
+ }
+
+ for (const auto& p : script_paths) {
+ std::vector<std::string> script_names;
+ GenerateManifest(p.c_str(), script_names);
+ for (const auto& n : script_names) {
+ if (ImGui::MenuItem(n.c_str())) {
+ show_script = true;
+ preview_script_name = n;
+ }
+ }
+ }
+ ImGui::End();
+ }
+
+ if (show_script && !preview_script_name.empty()) {
+ static std::string prev_script_path = "";
+ static std::vector<unsigned char> content;
+
+ Path path = FindFilePath(script_dir_path + preview_script_name, kDataPaths | kModPaths, false);
+ if (path.isValid()) {
+ if (prev_script_path != path.GetFullPathStr()) {
+ prev_script_path = path.GetFullPathStr();
+ content = readFile((prev_script_path.c_str()));
+ content.push_back('\0');
+ }
+ ImGui::Begin(preview_script_name.c_str(), &show_script, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar);
+ ImGui::TextEx(reinterpret_cast<const char*>(content.data()));
+ ImGui::End();
+ } else {
+ show_script = false;
+ }
+ }
+}
+
+void RenderImGui() {
+ ImGui::Render();
+}
+
+bool WantMouseImGui() {
+ return ImGui::GetIO().WantCaptureMouse;
+}
+
+bool WantKeyboardImGui() {
+ return ImGui::GetIO().WantCaptureKeyboard;
+}
diff --git a/Source/GUI/dimgui/dimgui.h b/Source/GUI/dimgui/dimgui.h
new file mode 100644
index 00000000..bed326aa
--- /dev/null
+++ b/Source/GUI/dimgui/dimgui.h
@@ -0,0 +1,65 @@
+//-----------------------------------------------------------------------------
+// Name: dimgui.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Main/engine.h>
+#include <Main/scenegraph.h>
+
+#include <SDL.h>
+
+class ImGuiTextEditCallbackData;
+
+extern bool show_test_window;
+extern bool show_debug_text;
+extern bool show_scenegraph;
+extern bool select_scenegraph_search;
+extern bool show_selected;
+extern bool show_color_picker;
+extern bool show_mod_menu;
+extern bool show_sound;
+extern bool show_performance;
+extern bool show_log;
+extern bool show_state;
+extern bool show_save;
+extern bool show_warnings;
+extern bool show_asdebugger_contexts;
+extern bool show_asprofiler;
+extern bool show_mp_debug;
+extern bool show_mp_info;
+extern bool show_mp_settings;
+extern bool show_input_debug;
+extern bool break_on_script_change;
+extern bool show_load_item_search;
+
+void InitImGui();
+void ReloadImGui();
+void DisposeImGui();
+
+void UpdateImGui();
+void ProcessEventImGui(SDL_Event* event);
+void DrawImGuiCameraPreview(Engine* engine, SceneGraph* scenegraph,Graphics* graphics);
+void DrawImGui(Graphics* graphics, SceneGraph* scenegraph, GUI* gui, AssetManager* assetmanager, Engine* engine, bool cursor_visible);
+bool WantMouseImGui();
+bool WantKeyboardImGui();
+void RenderImGui();
diff --git a/Source/GUI/dimgui/dimgui_graph.cpp b/Source/GUI/dimgui/dimgui_graph.cpp
new file mode 100644
index 00000000..8c4a3691
--- /dev/null
+++ b/Source/GUI/dimgui/dimgui_graph.cpp
@@ -0,0 +1,23 @@
+//-----------------------------------------------------------------------------
+// Name: dimgui_graph.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
diff --git a/Source/GUI/dimgui/dimgui_graph.h b/Source/GUI/dimgui/dimgui_graph.h
new file mode 100644
index 00000000..b66aabfb
--- /dev/null
+++ b/Source/GUI/dimgui/dimgui_graph.h
@@ -0,0 +1,78 @@
+//-----------------------------------------------------------------------------
+// Name: dimgui_graph.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <imgui.h>
+
+template<unsigned int d_elems>
+class ImGUIGraph {
+ int offset;
+ int last_min_adj;
+ int last_max_adj;
+ float minV, maxV;
+ std::array<float, d_elems> elements;
+
+public:
+ ImGUIGraph() {
+ offset = 0;
+ minV = 0.0f;
+ maxV = 1.0f;
+ for(int i = 0; i < elements.size(); i++) {
+ elements[i] = 0.0f;
+ }
+ }
+
+ void Push(float value) {
+ elements[offset] = value;
+
+ if(value > maxV) {
+ maxV = value;
+ last_max_adj = 0;
+ }
+
+ if(value < minV) {
+ minV = value;
+ last_min_adj = 0;
+ }
+
+ //Only shrink if the gap between the scales is more than 1.0f
+ if(abs(maxV - minV) > 0.5f) {
+ if(last_min_adj > d_elems) {
+ minV = mix(minV, maxV, 0.99f);
+ }
+ if(last_max_adj > d_elems) {
+ maxV = mix(maxV, minV, 0.99f);
+ }
+ }
+
+ offset++;
+ if(offset >= d_elems) {
+ offset = 0;
+ }
+ }
+
+ void Draw(const char* title) {
+ ImGui::PlotLines(title, (float*)elements.data(), d_elems, offset, "", minV, maxV, ImVec2(300, 200));
+ }
+};
diff --git a/Source/GUI/dimgui/dimgui_impl.cpp b/Source/GUI/dimgui/dimgui_impl.cpp
new file mode 100644
index 00000000..8f47d4b0
--- /dev/null
+++ b/Source/GUI/dimgui/dimgui_impl.cpp
@@ -0,0 +1,33 @@
+//-----------------------------------------------------------------------------
+// Name: dimgui_impl.cpp
+// Developer: Wolfire Games LLC
+// Description: This is the implementation file for Dear IMGUI, this is where
+// all the code in the project gets compiled for use in the
+// project.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <imgui.cpp>
+#include <imgui_demo.cpp>
+#include <imgui_draw.cpp>
+#include <imgui_widgets.cpp>
+#include <imgui_tables.cpp>
+#include <imgui_color_picker.cpp>
+#include <implot.cpp>
+#include <implot_items.cpp>
diff --git a/Source/GUI/dimgui/editor.cpp b/Source/GUI/dimgui/editor.cpp
new file mode 100644
index 00000000..f2f17de0
--- /dev/null
+++ b/Source/GUI/dimgui/editor.cpp
@@ -0,0 +1,24 @@
+//-----------------------------------------------------------------------------
+// Name: editor.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "editor.h"
diff --git a/Source/GUI/dimgui/editor.h b/Source/GUI/dimgui/editor.h
new file mode 100644
index 00000000..62f75b31
--- /dev/null
+++ b/Source/GUI/dimgui/editor.h
@@ -0,0 +1,24 @@
+//-----------------------------------------------------------------------------
+// Name: editor.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
diff --git a/Source/GUI/dimgui/imgui_impl_sdl_gl3.cpp b/Source/GUI/dimgui/imgui_impl_sdl_gl3.cpp
new file mode 100644
index 00000000..11f155c3
--- /dev/null
+++ b/Source/GUI/dimgui/imgui_impl_sdl_gl3.cpp
@@ -0,0 +1,452 @@
+//-----------------------------------------------------------------------------
+// Name: imgui_impl_sdl_gl3.cpp
+// Developer: Wolfire Games LLC
+// Description: This is the OpenGL 3 rendering implementation from the Dear IMGUI
+// projects with some minor modifications for use in Overgrowth.
+// License: MIT, see below
+//-----------------------------------------------------------------------------
+//
+// The MIT License (MIT)
+//
+// Copyright(c) 2014 - 2022 Omar Cornut
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files(the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions :
+//
+// The above copyright noticeand this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//-----------------------------------------------------------------------------
+// ImGui SDL2 binding with OpenGL3
+// In this binding, ImTextureID is used to store an OpenGL 'GLuint' texture identifier. Read the FAQ about ImTextureID in imgui.cpp.
+
+// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this.
+// If you use this binding you'll need to call 4 functions: ImGui_ImplXXXX_Init(), ImGui_ImplXXXX_NewFrame(), ImGui::Render() and ImGui_ImplXXXX_Shutdown().
+// If you are new to ImGui, see examples/README.txt and documentation at the top of imgui.cpp.
+// https://github.com/ocornut/imgui
+
+#include "imgui.h"
+#include "imgui_impl_sdl_gl3.h"
+
+
+#include <imgui-1.85/implot.h>
+#include <imgui-1.85/implot_internal.h>
+
+#include <SDL.h>
+#include <SDL_syswm.h>
+#include <Graphics/graphics.h>
+
+// Data
+static double g_Time = 0.0f;
+static bool g_MousePressed[3] = { false, false, false };
+static float g_MouseWheel = 0.0f;
+static GLuint g_FontTexture = 0;
+static int g_ShaderHandle = 0, g_VertHandle = 0, g_FragHandle = 0;
+static int g_AttribLocationTex = 0, g_AttribLocationProjMtx = 0;
+static int g_AttribLocationPosition = 0, g_AttribLocationUV = 0, g_AttribLocationColor = 0;
+static unsigned int g_VboHandle = 0, g_VaoHandle = 0, g_ElementsHandle = 0;
+
+// This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structure)
+// If text or lines are blurry when integrating ImGui in your engine:
+// - in your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f)
+void ImGui_ImplSdlGL3_RenderDrawLists(ImDrawData* draw_data)
+{
+ // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
+ ImGuiIO& io = ImGui::GetIO();
+ int fb_width = (int)(io.DisplaySize.x * io.DisplayFramebufferScale.x);
+ int fb_height = (int)(io.DisplaySize.y * io.DisplayFramebufferScale.y);
+ if (fb_width == 0 || fb_height == 0)
+ return;
+ draw_data->ScaleClipRects(io.DisplayFramebufferScale);
+
+ // Backup GL state
+ GLint last_program; glGetIntegerv(GL_CURRENT_PROGRAM, &last_program);
+ GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
+ GLint last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, &last_active_texture);
+ GLint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer);
+ GLint last_element_array_buffer; glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &last_element_array_buffer);
+ GLint last_vertex_array; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array);
+ GLint last_blend_src; glGetIntegerv(GL_BLEND_SRC, &last_blend_src);
+ GLint last_blend_dst; glGetIntegerv(GL_BLEND_DST, &last_blend_dst);
+ GLint last_blend_equation_rgb; glGetIntegerv(GL_BLEND_EQUATION_RGB, &last_blend_equation_rgb);
+ GLint last_blend_equation_alpha; glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &last_blend_equation_alpha);
+ GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport);
+ GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box);
+ GLboolean last_enable_blend = glIsEnabled(GL_BLEND);
+ GLboolean last_enable_cull_face = glIsEnabled(GL_CULL_FACE);
+ GLboolean last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST);
+ GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST);
+
+ // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled
+ glEnable(GL_BLEND);
+ glBlendEquation(GL_FUNC_ADD);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glDisable(GL_CULL_FACE);
+ glDisable(GL_DEPTH_TEST);
+ glEnable(GL_SCISSOR_TEST);
+ glActiveTexture(GL_TEXTURE0);
+
+ // Setup orthographic projection matrix
+ glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height);
+ const float ortho_projection[4][4] =
+ {
+ { 2.0f/io.DisplaySize.x, 0.0f, 0.0f, 0.0f },
+ { 0.0f, 2.0f/-io.DisplaySize.y, 0.0f, 0.0f },
+ { 0.0f, 0.0f, -1.0f, 0.0f },
+ {-1.0f, 1.0f, 0.0f, 1.0f },
+ };
+ glUseProgram(g_ShaderHandle);
+ glUniform1i(g_AttribLocationTex, 0);
+ glUniformMatrix4fv(g_AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]);
+ glBindVertexArray(g_VaoHandle);
+
+ for (int n = 0; n < draw_data->CmdListsCount; n++)
+ {
+ const ImDrawList* cmd_list = draw_data->CmdLists[n];
+ const ImDrawIdx* idx_buffer_offset = 0;
+
+ glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle);
+ glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)cmd_list->VtxBuffer.Size * sizeof(ImDrawVert), (GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW);
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_ElementsHandle);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx), (GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW);
+
+ for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
+ {
+ const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
+ if (pcmd->UserCallback)
+ {
+ pcmd->UserCallback(cmd_list, pcmd);
+ }
+ else
+ {
+ glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId);
+ glScissor((int)pcmd->ClipRect.x, (int)(fb_height - pcmd->ClipRect.w), (int)(pcmd->ClipRect.z - pcmd->ClipRect.x), (int)(pcmd->ClipRect.w - pcmd->ClipRect.y));
+ glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer_offset);
+ }
+ idx_buffer_offset += pcmd->ElemCount;
+ }
+ }
+
+ // Restore modified GL state
+ glUseProgram(last_program);
+ glActiveTexture(last_active_texture);
+ glBindTexture(GL_TEXTURE_2D, last_texture);
+ glBindVertexArray(last_vertex_array);
+ glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, last_element_array_buffer);
+ glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha);
+ glBlendFunc(last_blend_src, last_blend_dst);
+ if (last_enable_blend) glEnable(GL_BLEND); else glDisable(GL_BLEND);
+ if (last_enable_cull_face) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE);
+ if (last_enable_depth_test) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST);
+ if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST);
+ glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]);
+ glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]);
+}
+
+static const char* ImGui_ImplSdlGL3_GetClipboardText(void*)
+{
+ return SDL_GetClipboardText();
+}
+
+static void ImGui_ImplSdlGL3_SetClipboardText(void*, const char* text)
+{
+ SDL_SetClipboardText(text);
+}
+
+int KeyRemap(int key){
+ if(key > 127){
+ return SDLK_z + 1 + key - SDLK_CAPSLOCK;
+ } else {
+ return key;
+ }
+}
+
+bool ImGui_ImplSdlGL3_ProcessEvent(SDL_Event* event)
+{
+ ImGuiIO& io = ImGui::GetIO();
+ switch (event->type)
+ {
+ case SDL_MOUSEWHEEL:
+ {
+ if (event->wheel.y > 0)
+ g_MouseWheel = 1;
+ if (event->wheel.y < 0)
+ g_MouseWheel = -1;
+ return true;
+ }
+ case SDL_MOUSEBUTTONDOWN:
+ {
+ if (event->button.button == SDL_BUTTON_LEFT) g_MousePressed[0] = true;
+ if (event->button.button == SDL_BUTTON_RIGHT) g_MousePressed[1] = true;
+ if (event->button.button == SDL_BUTTON_MIDDLE) g_MousePressed[2] = true;
+ return true;
+ }
+ case SDL_TEXTINPUT:
+ {
+ io.AddInputCharactersUTF8(event->text.text);
+ return true;
+ }
+ case SDL_KEYDOWN:
+ case SDL_KEYUP:
+ {
+ int key = KeyRemap(event->key.keysym.sym);
+ if( key >= 0 && key < 512 ) {
+ io.KeysDown[key] = (event->type == SDL_KEYDOWN);
+ }
+ io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0);
+ io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0);
+ io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0);
+ io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0);
+
+ return true;
+ }
+ }
+ return false;
+}
+
+void ImGui_ImplSdlGL3_CreateFontsTexture()
+{
+ // Build texture atlas
+ ImGuiIO& io = ImGui::GetIO();
+ unsigned char* pixels;
+ int width, height;
+ io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bits for OpenGL3 demo because it is more likely to be compatible with user's existing shader.
+
+ // Upload texture to graphics system
+ GLint last_texture;
+ glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
+ glGenTextures(1, &g_FontTexture);
+ glBindTexture(GL_TEXTURE_2D, g_FontTexture);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
+
+ // Store our identifier
+ io.Fonts->TexID = (void *)(intptr_t)g_FontTexture;
+
+ // Restore state
+ glBindTexture(GL_TEXTURE_2D, last_texture);
+}
+
+bool ImGui_ImplSdlGL3_CreateDeviceObjects()
+{
+ // Backup GL state
+ GLint last_texture, last_array_buffer, last_vertex_array;
+ glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
+ glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer);
+ glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array);
+
+ const GLchar *vertex_shader =
+ "#version 330\n"
+ "uniform mat4 ProjMtx;\n"
+ "in vec2 Position;\n"
+ "in vec2 UV;\n"
+ "in vec4 Color;\n"
+ "out vec2 Frag_UV;\n"
+ "out vec4 Frag_Color;\n"
+ "void main()\n"
+ "{\n"
+ " Frag_UV = UV;\n"
+ " Frag_Color = Color;\n"
+ " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
+ "}\n";
+
+ const GLchar* fragment_shader =
+ "#version 330\n"
+ "uniform sampler2D Texture;\n"
+ "in vec2 Frag_UV;\n"
+ "in vec4 Frag_Color;\n"
+ "out vec4 Out_Color;\n"
+ "void main()\n"
+ "{\n"
+ " Out_Color = Frag_Color * texture( Texture, Frag_UV.st);\n"
+ "}\n";
+
+ g_ShaderHandle = glCreateProgram();
+ g_VertHandle = glCreateShader(GL_VERTEX_SHADER);
+ g_FragHandle = glCreateShader(GL_FRAGMENT_SHADER);
+ glShaderSource(g_VertHandle, 1, &vertex_shader, 0);
+ glShaderSource(g_FragHandle, 1, &fragment_shader, 0);
+ glCompileShader(g_VertHandle);
+ glCompileShader(g_FragHandle);
+ glAttachShader(g_ShaderHandle, g_VertHandle);
+ glAttachShader(g_ShaderHandle, g_FragHandle);
+ glLinkProgram(g_ShaderHandle);
+
+ g_AttribLocationTex = glGetUniformLocation(g_ShaderHandle, "Texture");
+ g_AttribLocationProjMtx = glGetUniformLocation(g_ShaderHandle, "ProjMtx");
+ g_AttribLocationPosition = glGetAttribLocation(g_ShaderHandle, "Position");
+ g_AttribLocationUV = glGetAttribLocation(g_ShaderHandle, "UV");
+ g_AttribLocationColor = glGetAttribLocation(g_ShaderHandle, "Color");
+
+ glGenBuffers(1, &g_VboHandle);
+ glGenBuffers(1, &g_ElementsHandle);
+
+ glGenVertexArrays(1, &g_VaoHandle);
+ glBindVertexArray(g_VaoHandle);
+ glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle);
+ glEnableVertexAttribArray(g_AttribLocationPosition);
+ glEnableVertexAttribArray(g_AttribLocationUV);
+ glEnableVertexAttribArray(g_AttribLocationColor);
+
+#define OFFSETOF(TYPE, ELEMENT) ((size_t)&(((TYPE *)0)->ELEMENT))
+ glVertexAttribPointer(g_AttribLocationPosition, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)OFFSETOF(ImDrawVert, pos));
+ glVertexAttribPointer(g_AttribLocationUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)OFFSETOF(ImDrawVert, uv));
+ glVertexAttribPointer(g_AttribLocationColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)OFFSETOF(ImDrawVert, col));
+#undef OFFSETOF
+
+ ImGui_ImplSdlGL3_CreateFontsTexture();
+
+ // Restore modified GL state
+ glBindTexture(GL_TEXTURE_2D, last_texture);
+ glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer);
+ glBindVertexArray(last_vertex_array);
+
+ return true;
+}
+
+void ImGui_ImplSdlGL3_InvalidateDeviceObjects()
+{
+ if (g_VaoHandle) glDeleteVertexArrays(1, &g_VaoHandle);
+ if (g_VboHandle) glDeleteBuffers(1, &g_VboHandle);
+ if (g_ElementsHandle) glDeleteBuffers(1, &g_ElementsHandle);
+ g_VaoHandle = g_VboHandle = g_ElementsHandle = 0;
+
+ if (g_ShaderHandle && g_VertHandle) glDetachShader(g_ShaderHandle, g_VertHandle);
+ if (g_VertHandle) glDeleteShader(g_VertHandle);
+ g_VertHandle = 0;
+
+ if (g_ShaderHandle && g_FragHandle) glDetachShader(g_ShaderHandle, g_FragHandle);
+ if (g_FragHandle) glDeleteShader(g_FragHandle);
+ g_FragHandle = 0;
+
+ if (g_ShaderHandle) glDeleteProgram(g_ShaderHandle);
+ g_ShaderHandle = 0;
+
+ if (g_FontTexture)
+ {
+ glDeleteTextures(1, &g_FontTexture);
+ ImGui::GetIO().Fonts->TexID = 0;
+ g_FontTexture = 0;
+ }
+}
+
+bool ImGui_ImplSdlGL3_Init(SDL_Window* window)
+{
+ ImGuiIO& io = ImGui::GetIO();
+ io.KeyMap[ImGuiKey_Tab] = KeyRemap(SDLK_TAB); // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array.
+ io.KeyMap[ImGuiKey_LeftArrow] = KeyRemap(SDLK_LEFT);
+ io.KeyMap[ImGuiKey_RightArrow] = KeyRemap(SDLK_RIGHT);
+ io.KeyMap[ImGuiKey_UpArrow] = KeyRemap(SDLK_UP);
+ io.KeyMap[ImGuiKey_DownArrow] = KeyRemap(SDLK_DOWN);
+ io.KeyMap[ImGuiKey_PageUp] = KeyRemap(SDLK_PAGEUP);
+ io.KeyMap[ImGuiKey_PageDown] = KeyRemap(SDLK_PAGEDOWN);
+ io.KeyMap[ImGuiKey_Home] = KeyRemap(SDLK_HOME);
+ io.KeyMap[ImGuiKey_End] = KeyRemap(SDLK_END);
+ io.KeyMap[ImGuiKey_Delete] = KeyRemap(SDLK_DELETE);
+ io.KeyMap[ImGuiKey_Backspace] = KeyRemap(SDLK_BACKSPACE);
+ io.KeyMap[ImGuiKey_Enter] = KeyRemap(SDLK_RETURN);
+ io.KeyMap[ImGuiKey_Escape] = KeyRemap(SDLK_ESCAPE);
+ io.KeyMap[ImGuiKey_A] = KeyRemap(SDLK_a);
+ io.KeyMap[ImGuiKey_C] = KeyRemap(SDLK_c);
+ io.KeyMap[ImGuiKey_V] = KeyRemap(SDLK_v);
+ io.KeyMap[ImGuiKey_X] = KeyRemap(SDLK_x);
+ io.KeyMap[ImGuiKey_Y] = KeyRemap(SDLK_y);
+ io.KeyMap[ImGuiKey_Z] = KeyRemap(SDLK_z);
+ io.KeyMap[ImGuiKey_LShift] = KeyRemap(SDLK_LSHIFT);
+ io.KeyMap[ImGuiKey_RShift] = KeyRemap(SDLK_RSHIFT);
+ io.KeyMap[ImGuiKey_NumEnter] = KeyRemap(SDLK_KP_ENTER);
+
+ //io.RenderDrawListsFn = ImGui_ImplSdlGL3_RenderDrawLists; // Alternatively you can set this to NULL and call ImGui::GetDrawData() after ImGui::Render() to get the same ImDrawData pointer.
+ io.SetClipboardTextFn = ImGui_ImplSdlGL3_SetClipboardText;
+ io.GetClipboardTextFn = ImGui_ImplSdlGL3_GetClipboardText;
+ io.ClipboardUserData = NULL;
+
+#ifdef _WIN32
+ SDL_SysWMinfo wmInfo;
+ SDL_VERSION(&wmInfo.version);
+ SDL_GetWindowWMInfo(window, &wmInfo);
+ io.ImeWindowHandle = wmInfo.info.win.window;
+#else
+ (void)window;
+#endif
+
+ return true;
+}
+
+void ImGui_ImplSdlGL3_Shutdown()
+{
+ ImGui_ImplSdlGL3_InvalidateDeviceObjects();
+ ImPlot::DestroyContext();
+ ImGui::DestroyContext();
+}
+
+void ImGui_ImplSdlGL3_NewFrame(SDL_Window* window, bool ignore_mouse) {
+ if (!g_FontTexture)
+ ImGui_ImplSdlGL3_CreateDeviceObjects();
+
+ ImGuiIO& io = ImGui::GetIO();
+
+ // Setup display size (every frame to accommodate for window resizing)
+ int w, h;
+ int display_w, display_h;
+ SDL_GetWindowSize(window, &w, &h);
+ SDL_GL_GetDrawableSize(window, &display_w, &display_h);
+ io.DisplaySize = ImVec2((float)w, (float)h);
+ io.DisplayFramebufferScale = ImVec2(w > 0 ? ((float)display_w / w) : 0, h > 0 ? ((float)display_h / h) : 0);
+
+ // Setup time step
+ uint32_t time = SDL_GetTicks();
+ double current_time = time / 1000.0;
+ io.DeltaTime = g_Time > 0.0 ? (float)(current_time - g_Time) : (float)(1.0f / 60.0f);
+ g_Time = current_time;
+
+
+ if( ignore_mouse ) {
+ io.MouseDown[0] = false;
+ io.MouseDown[1] = false;
+ io.MouseDown[2] = false;
+ g_MousePressed[0] = g_MousePressed[1] = g_MousePressed[2] = false;
+
+ io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
+ io.MouseWheel = g_MouseWheel;
+ g_MouseWheel = 0.0f;
+ } else {
+ // Setup inputs
+ // (we already got mouse wheel, keyboard keys & characters from SDL_PollEvent())
+ int mx, my;
+ uint32_t mouseMask = SDL_GetMouseState(&mx, &my);
+ if (SDL_GetWindowFlags(window) & SDL_WINDOW_MOUSE_FOCUS)
+ io.MousePos = ImVec2((float)mx, (float)my); // Mouse position, in pixels (set to -1,-1 if no mouse / on another screen, etc.)
+ else
+ io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
+
+ io.MouseDown[0] = g_MousePressed[0] || (mouseMask & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0; // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame.
+ io.MouseDown[1] = g_MousePressed[1] || (mouseMask & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0;
+ io.MouseDown[2] = g_MousePressed[2] || (mouseMask & SDL_BUTTON(SDL_BUTTON_MIDDLE)) != 0;
+ g_MousePressed[0] = g_MousePressed[1] = g_MousePressed[2] = false;
+
+ io.MouseWheel = g_MouseWheel;
+ g_MouseWheel = 0.0f;
+ }
+
+ // Hide OS mouse cursor if ImGui is drawing it
+ //SDL_ShowCursor(io.MouseDrawCursor ? 0 : 1);
+
+ // Start the frame
+ ImGui::NewFrame();
+}
diff --git a/Source/GUI/dimgui/imgui_impl_sdl_gl3.h b/Source/GUI/dimgui/imgui_impl_sdl_gl3.h
new file mode 100644
index 00000000..07c214a7
--- /dev/null
+++ b/Source/GUI/dimgui/imgui_impl_sdl_gl3.h
@@ -0,0 +1,50 @@
+//-----------------------------------------------------------------------------
+// Name: imgui_impl_sdl_gl3.h
+// Developer: Wolfire Games LLC
+// Description: This is the OpenGL 3 rendering implementation from the Dear IMGUI
+// projects with some minor modifications for use in Overgrowth.
+// License: MIT, see below
+//-----------------------------------------------------------------------------
+//
+// The MIT License (MIT)
+//
+// Copyright(c) 2014 - 2022 Omar Cornut
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files(the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions :
+//
+// The above copyright noticeand this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//-----------------------------------------------------------------------------
+// ImGui SDL2 binding with OpenGL3
+// In this binding, ImTextureID is used to store an OpenGL 'GLuint' texture identifier. Read the FAQ about ImTextureID in imgui.cpp.
+
+// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this.
+// If you use this binding you'll need to call 4 functions: ImGui_ImplXXXX_Init(), ImGui_ImplXXXX_NewFrame(), ImGui::Render() and ImGui_ImplXXXX_Shutdown().
+// If you are new to ImGui, see examples/README.txt and documentation at the top of imgui.cpp.
+// https://github.com/ocornut/imgui
+
+struct SDL_Window;
+typedef union SDL_Event SDL_Event;
+
+IMGUI_API bool ImGui_ImplSdlGL3_Init(SDL_Window* window);
+IMGUI_API void ImGui_ImplSdlGL3_Shutdown();
+IMGUI_API void ImGui_ImplSdlGL3_NewFrame(SDL_Window* window, bool ignore_mouse);
+IMGUI_API bool ImGui_ImplSdlGL3_ProcessEvent(SDL_Event* event);
+
+// Use if you want to reset your rendering device without losing ImGui state.
+IMGUI_API void ImGui_ImplSdlGL3_InvalidateDeviceObjects();
+IMGUI_API bool ImGui_ImplSdlGL3_CreateDeviceObjects();
+IMGUI_API void ImGui_ImplSdlGL3_RenderDrawLists(ImDrawData* draw_data);
diff --git a/Source/GUI/dimgui/modmenu.cpp b/Source/GUI/dimgui/modmenu.cpp
new file mode 100644
index 00000000..2525ed60
--- /dev/null
+++ b/Source/GUI/dimgui/modmenu.cpp
@@ -0,0 +1,1203 @@
+//-----------------------------------------------------------------------------
+// Name: modmenu.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "modmenu.h"
+
+
+#include <Internal/modloading.h>
+#include <Internal/common.h>
+#include <Internal/modid.h>
+#include <Internal/config.h>
+
+#include <Steam/steamworks.h>
+#include <Steam/ugc_item.h>
+
+#include <Main/engine.h>
+#include <Graphics/textures.h>
+#include <Utility/flat_hash_map.hpp>
+
+#include <imgui.h>
+#include <imgui_internal.h>
+
+extern bool show_mod_menu;
+
+static ModID mod_menu_selected_sid = -1;
+static ModID upload_to_sid = -1;
+static ModID update_to_sid = -1;
+
+static bool show_upload_to_steamworks = false;
+static bool show_update_to_steamworks = false;
+static bool show_manifest = false;
+
+static void SetDrawUploadToSteamworks( ModID _upload_to_sid ) {
+ upload_to_sid = _upload_to_sid;
+}
+
+static int update_to_currently_selected_visbility = 2;
+
+static void SetDrawUpdateToSteamworks( ModID _update_to_sid ) {
+ update_to_sid = _update_to_sid;
+
+#if ENABLE_STEAMWORKS
+ SteamworksUGC* ugc = Steamworks::Instance()->GetUGC();
+ ModInstance *target_modi = ModLoading::Instance().GetMod(update_to_sid);
+
+ if( target_modi ) {
+ SteamworksUGCItem *ugc_item = target_modi->GetUGCItem();
+ if( ugc_item ) {
+ for( int i = 0; i < update_to_visibilty_options_count; i++ ) {
+ if( mod_visibility_map[i] == ugc_item->visibility ) {
+ update_to_currently_selected_visbility = i;
+ }
+ }
+ }
+ }
+#endif
+}
+
+static std::vector<std::pair<const char*,std::vector<ModInstance*> > > GetCategorizedMods() {
+
+ std::vector<std::pair<const char*,std::vector<ModInstance*> > > categorized_mods;
+
+ categorized_mods.push_back( std::pair<const char*, std::vector<ModInstance*> >( "Local Mods", std::vector<ModInstance*>() ) );
+ categorized_mods.push_back( std::pair<const char*, std::vector<ModInstance*> >( "Subscribed Steam Workshop Mods", std::vector<ModInstance*>()) );
+ categorized_mods.push_back( std::pair<const char*, std::vector<ModInstance*> >( "Unsubscribed Steam Workshop Mods", std::vector<ModInstance*>() ) );
+ categorized_mods.push_back( std::pair<const char*, std::vector<ModInstance*> >( "My Steam Workshop Mods", std::vector<ModInstance*>() ) );
+ categorized_mods.push_back( std::pair<const char*, std::vector<ModInstance*> >( "My Favorites", std::vector<ModInstance*>() ) );
+ categorized_mods.push_back( std::pair<const char*, std::vector<ModInstance*> >( "Core Packages", std::vector<ModInstance*>() ) );
+
+ const std::vector<ModInstance*> mods = ModLoading::Instance().GetAllMods();
+
+ for( uint32_t i = 0; i < mods.size() ; i++ ) {
+ if( mods[i]->IsCore() ) {
+ categorized_mods[5].second.push_back( mods[i] );
+ } else if( mods[i]->modsource == ModSourceLocalModFolder ) {
+ categorized_mods[0].second.push_back( mods[i] );
+ } else if( mods[i]->modsource == ModSourceSteamworks ) {
+ if( mods[i]->IsOwnedByCurrentUser() ) {
+ categorized_mods[3].second.push_back( mods[i] );
+ } else {
+ if( mods[i]->IsFavorite() ) {
+ categorized_mods[4].second.push_back( mods[i] );
+ } else {
+ if( mods[i]->IsSubscribed() ) {
+ categorized_mods[1].second.push_back( mods[i] );
+ } else {
+ categorized_mods[2].second.push_back( mods[i] );
+ }
+ }
+ }
+ }
+ }
+ return categorized_mods;
+}
+
+typedef std::vector<std::pair<const char*, std::vector<ModInstance*> > > CategorizedModsList;
+static CategorizedModsList categorized_mods_cache_;
+static int categorized_mods_iterations_until_next_check = 0;
+const int kIterationsPerModListCheck = 30;
+
+static CategorizedModsList& GetCachedCategorizedMods() {
+ --categorized_mods_iterations_until_next_check;
+ if (categorized_mods_iterations_until_next_check <= 0) {
+ categorized_mods_iterations_until_next_check = (int) (kIterationsPerModListCheck + ((float)rand() * (kIterationsPerModListCheck - 1)) / RAND_MAX);
+ categorized_mods_cache_ = GetCategorizedMods();
+ }
+ return categorized_mods_cache_;
+}
+
+typedef ska::flat_hash_map<ModInstance*, bool> ModCanActivateCheckCacheMap;
+static ModCanActivateCheckCacheMap mod_can_activate_check_cache_map_;
+
+static bool CanActivateModInstance(ModInstance* mod) {
+ bool can_activate;
+ auto cached_can_activate_check_iter = mod_can_activate_check_cache_map_.find(mod);
+
+ if(cached_can_activate_check_iter == mod_can_activate_check_cache_map_.end()) {
+ can_activate = mod->CanActivate();
+ mod_can_activate_check_cache_map_.insert(cached_can_activate_check_iter, ModCanActivateCheckCacheMap::value_type(mod, can_activate));
+ } else {
+ // TODO: If mod gets activated outside of this menu, need to clear this cache
+ can_activate = cached_can_activate_check_iter->second;
+ }
+
+ return can_activate;
+}
+
+static void DrawSimpleModMenu( ) {
+
+ CategorizedModsList& categmods = GetCachedCategorizedMods();
+ CategorizedModsList::iterator modcatit = categmods.begin();
+
+ for(; modcatit != categmods.end(); modcatit++ ) {
+ const std::vector<ModInstance*> mods = modcatit->second;
+ if( mods.size() > 0 && ImGui::TreeNodeEx(modcatit->first,ImGuiTreeNodeFlags_DefaultOpen) ) {
+ for(uint32_t i = 0; i < mods.size(); ++i) {
+ ModInstance* mod = mods[i];
+ bool active = mod->IsActive();
+ ModID sid = mod->GetSid();
+ ImGui::PushID(sid.id);
+ if(CanActivateModInstance(mod)){
+ if(ImGui::Checkbox(mod->name, &active)){
+ ModLoading::Instance().GetMod(mod->GetSid())->Activate(active);
+ mod_can_activate_check_cache_map_.clear();
+ }
+ } else {
+ ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_TextDisabled]);
+ ImGui::Checkbox(mod->name, &active);
+ ImGui::PopStyleColor(1);
+ }
+ ImGui::PopID();
+ if (ImGui::IsItemHovered()) {
+ ImGui::BeginTooltip();
+ ImGui::PushTextWrapPos(450.0f);
+ const int kBufSize = 512;
+ char buf[kBufSize];
+
+ if( mod->GetValidityErrors().size() > 0 ) {
+ FormatString(
+ buf, kBufSize,
+ "%s %s\nWarnings:\n%s",
+ mod->name.c_str(), mod->version.c_str(), mod->GetValidityErrors().c_str());
+ } else {
+ FormatString(
+ buf, kBufSize,
+ "%s %s\n",
+ mod->name.c_str(), mod->version.c_str());
+ }
+ ImGui::TextUnformatted(buf);
+ ImGui::PopTextWrapPos();
+ ImGui::EndTooltip();
+ }
+ }
+ ImGui::TreePop();
+ }
+ }
+}
+
+static void ImGuiYesNo(bool v) {
+ ImGui::Text("%s", v ? "Yes" : "No");
+}
+
+static void ImguiDrawParameter(ModInstance::Parameter* p, const char* parent_type, int parent_index) {
+ const char* name = p->name.c_str();
+ const char* type = p->type.c_str();
+ const char* value = p->value.c_str();
+ ImGui::PushID(parent_index);
+
+ if(strmtch(type, "array") || strmtch(type, "table")) {
+ if( strmtch( parent_type, "table" )) {
+ if(ImGui::TreeNode("p", "[\"%s\"]", name)) {
+ for(uint32_t i = 0; i < p->parameters.size(); i++) {
+ ImguiDrawParameter(&p->parameters[i],type,i);
+ }
+ ImGui::TreePop();
+ }
+ } else if( strmtch( parent_type, "array" ) ) {
+ if(ImGui::TreeNode("p", "[%d]", parent_index)) {
+ for(uint32_t i = 0; i < p->parameters.size(); i++) {
+ ImguiDrawParameter(&p->parameters[i],type,i);
+ }
+ ImGui::TreePop();
+ }
+ } else {
+ if(ImGui::TreeNode("p", "Parameter")) {
+ for(uint32_t i = 0; i < p->parameters.size(); i++) {
+ ImguiDrawParameter(&p->parameters[i],type,i);
+ }
+ ImGui::TreePop();
+ }
+ }
+ } else {
+ if( strmtch( parent_type, "table" )) {
+ ImGui::BulletText("[\"%s\"]: \"%s\"", name, value);
+ } else if( strmtch( parent_type, "array" ) ) {
+ ImGui::BulletText("[%d]: \"%s\"", parent_index, value);
+ } else {
+ ImGui::BulletText("\"%s\"", value);
+ }
+ }
+
+ ImGui::PopID();
+}
+
+static void DrawAdvancedModMenu(Engine* engine) {
+ const std::vector<ModInstance*>& mods = ModLoading::Instance().GetMods();
+
+ //Automatic size
+ ImGui::BeginChild("Sub1", ImVec2(300,0), false, ImGuiWindowFlags_HorizontalScrollbar & ImGuiWindowFlags_AlwaysAutoResize );
+
+ ImGui::Columns(1);
+
+#if ENABLE_STEAMWORKS
+ if( Steamworks::Instance()->UserNeedsToAcceptWorkshopAgreement() ) {
+ ImGui::TextWrapped("%s", "Note: You need to go to steampowered.com and accept the Workshop Agreement before you're able to fully publish mods.");
+ ImGui::Separator();
+ }
+#endif
+
+ CategorizedModsList& categmods = GetCachedCategorizedMods();
+ CategorizedModsList::iterator modcatit = categmods.begin();
+
+ for(; modcatit != categmods.end(); modcatit++ ) {
+ const std::vector<ModInstance*> mods = modcatit->second;
+ if( mods.size() > 0 && ImGui::TreeNodeEx(modcatit->first,ImGuiTreeNodeFlags_DefaultOpen) ) {
+ for(uint32_t i = 0; i < mods.size(); ++i) {
+ ModInstance* mod = mods[i];
+ bool active = mod->IsActive();
+ ModID sid = mod->GetSid();
+ ImGui::PushID(sid.id);
+ if(CanActivateModInstance(mod)){
+ if( mod->IsActive() ) {
+ ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.0f,1.0f,0.0f,1.0f));
+ }
+
+ if (ImGui::Selectable(mod->name, mod_menu_selected_sid == sid, ImGuiSelectableFlags_SpanAllColumns)) {
+ mod_menu_selected_sid = sid;
+ }
+
+ if( mod->IsActive() ) {
+ ImGui::PopStyleColor(1);
+ }
+ /*
+ if(ImGui::Checkbox(mod->name.c_str(), &active)){
+ ModLoading::Instance().GetMod(mod->GetSid())->Activate(active);
+ mod_can_activate_check_cache_map_.clear();
+ }
+ */
+ } else {
+ if( mod->IsInstalled() ) {
+ ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_TextDisabled]);
+ } else {
+ ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.3f, 0.1f, 1.0f));
+ }
+
+ if (ImGui::Selectable(mod->name, mod_menu_selected_sid == sid, ImGuiSelectableFlags_SpanAllColumns)) {
+ mod_menu_selected_sid = sid;
+ }
+ ImGui::PopStyleColor(1);
+ /*
+ ImGui::Checkbox(mod->name.c_str(), &active);
+ //ImGui::MenuItem(mod->name.c_str(), NULL, false, false);
+ */
+ }
+ ImGui::PopID();
+ if (ImGui::IsItemHovered()) {
+ ImGui::BeginTooltip();
+ ImGui::PushTextWrapPos(450.0f);
+ const int kBufSize = 512;
+ char buf[kBufSize];
+ if( mod->GetValidityErrors().size() > 0 ) {
+ FormatString(
+ buf, kBufSize,
+ "%s %s\nWarnings:\n%s",
+ mod->name.c_str(), mod->version.c_str(),
+ mod->GetValidityErrors().c_str());
+ } else {
+ FormatString(
+ buf, kBufSize,
+ "%s %s\n",
+ mod->name.c_str(), mod->version.c_str());
+ }
+ ImGui::TextUnformatted(buf);
+ ImGui::PopTextWrapPos();
+ ImGui::EndTooltip();
+ }
+ }
+ ImGui::TreePop();
+ }
+ }
+
+ ImGui::EndChild();
+
+ ImGui::SameLine();
+
+ ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 5.0f);
+ ImGui::BeginChild("Sub2", ImVec2(0,0), true,ImGuiWindowFlags_HorizontalScrollbar & ImGuiWindowFlags_AlwaysAutoResize);
+
+ ModInstance *modi = ModLoading::Instance().GetMod(mod_menu_selected_sid);
+
+ if( modi != NULL ) {
+ Path thumbnail_path = FindFilePath(AssemblePath(modi->path.c_str(), modi->thumbnail), kAbsPath, false);
+ if(strlen(modi->thumbnail) > 0 && thumbnail_path.isValid()) {
+ Engine::Instance()->spawner_thumbnail = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(thumbnail_path.GetFullPath(), PX_NOMIPMAP | PX_NOREDUCE | PX_NOCONVERT, 0x0);
+ Textures::Instance()->EnsureInVRAM(Engine::Instance()->spawner_thumbnail->GetTextureRef());
+ ImGui::Image((ImTextureID)(uintptr_t)Textures::Instance()->returnTexture(Engine::Instance()->spawner_thumbnail), ImVec2(400,225), ImVec2(0,0), ImVec2(1,1));
+ ImGui::Separator();
+ }
+
+ ImGui::Columns(2);
+ ImGui::Text( "ID:" ); ImGui::NextColumn();
+ ImGui::Text( "%s", modi->id.c_str() ); ImGui::NextColumn();
+ if( modi->modsource == ModSourceSteamworks ) {
+#if ENABLE_STEAMWORKS
+ SteamworksUGCItem* item = modi->GetUGCItem();
+ if( item ) {
+ ImGui::Text( "Steamworks ID:"); ImGui::NextColumn();
+ ImGui::TextWrapped( "%s", item->GetSteamworksIDString().c_str() ); ImGui::NextColumn();
+ }
+#endif
+ }
+ ImGui::Text( "Version:" ); ImGui::NextColumn();
+ ImGui::Text( "%s", modi->version.c_str() ); ImGui::NextColumn();
+ ImGui::Text( "Type:" ); ImGui::NextColumn();
+ ImGui::Text( "%s", modi->GetModsourceString() ); ImGui::NextColumn();
+ ImGui::Text( "Name:" ); ImGui::NextColumn();
+ ImGui::Text( "%s", modi->name.c_str() ); ImGui::NextColumn();
+ ImGui::Text( "Category:" ); ImGui::NextColumn();
+ ImGui::Text("%s", modi->category.c_str() ); ImGui::NextColumn();
+ ImGui::Text( "Author:" ); ImGui::NextColumn();
+ ImGui::Text( "%s", modi->author.c_str() ); ImGui::NextColumn();
+ ImGui::Text( "Tags:" ) ; ImGui::NextColumn();
+ ImGui::TextWrapped( "%s", modi->GetTagsListString().c_str() ); ImGui::NextColumn();
+
+ if( modi->modsource == ModSourceSteamworks ) {
+#if ENABLE_STEAMWORKS
+ SteamworksUGCItem* item = modi->GetUGCItem();
+ if( item ) {
+ ImGui::Text( "Visibility" ) ; ImGui::NextColumn();
+ int visibility_index = 0;
+ for( int i = 0; i < update_to_visibilty_options_count; i++ ) {
+ if( mod_visibility_map[i] == item->visibility ) {
+ visibility_index = i;
+ }
+ }
+ ImGui::Text( "%s", mod_visibility_options[visibility_index] );
+ ImGui::NextColumn();
+ }
+#endif
+ }
+
+ ImGui::Separator();
+
+ ImGui::Text( "Valid:" ); ImGui::NextColumn();
+ ImGuiYesNo(modi->IsValid()); ImGui::NextColumn();
+
+ ImGui::Text( "Activated:" ); ImGui::NextColumn();
+ ImGuiYesNo( modi->IsActive() ); ImGui::NextColumn();
+
+ if( modi->modsource == ModSourceSteamworks ) {
+#if ENABLE_STEAMWORKS
+ ImGui::Text( "Personal Favorite:" ); ImGui::NextColumn();
+ ImGuiYesNo( modi->IsFavorite() ); ImGui::NextColumn();
+
+ ImGui::Text( "Personal Vote:" ); ImGui::NextColumn();
+ const char* vote = "";
+
+ switch(modi->GetUserVote()) {
+ case ModInstance::k_VoteUnknown: vote = "Unknown"; break;
+ case ModInstance::k_VoteNone: vote = "None"; break;
+ case ModInstance::k_VoteUp: vote = "Up"; break;
+ case ModInstance::k_VoteDown: vote = "Down"; break;
+ }
+ ImGui::Text("%s", vote);
+ ImGui::NextColumn();
+
+ SteamworksUGCItem* item = modi->GetUGCItem();
+ if( item ) {
+ ImGui::Separator();
+
+ ImGui::Text( "Subscribed:" ); ImGui::NextColumn();
+ ImGuiYesNo(item->IsSubscribed()); ImGui::NextColumn();
+
+
+ ImGui::Text( "Installed:" ); ImGui::NextColumn();
+ ImGuiYesNo( item->IsInstalled() ); ImGui::NextColumn();
+
+ ImGui::Text( "Downloading:" ); ImGui::NextColumn();
+ ImGuiYesNo( item->IsDownloading() ); ImGui::NextColumn();
+
+ ImGui::Text( "Download Pending:" ); ImGui::NextColumn();
+ ImGuiYesNo( item->IsDownloadPending() ); ImGui::NextColumn();
+
+ ImGui::Text( "Needs Update:" ); ImGui::NextColumn();
+ ImGuiYesNo( item->NeedsUpdate() ); ImGui::NextColumn();
+
+ ImGui::Text( "Steam Error:" ); ImGui::NextColumn();
+ ImGui::TextWrapped( "%s", item->GetLastResultError() ); ImGui::NextColumn();
+ }
+#else
+ ImGui::Text("Game wasn't compiled with Steamworks Support.");
+#endif
+ }
+ ImGui::Separator();
+
+ ImGui::Text( "Path:" ); ImGui::NextColumn();
+ ImGui::TextWrapped( "%s", modi->path.c_str() ); ImGui::NextColumn();
+
+ ImGui::Separator();
+
+ std::vector<std::string> validity_errors = modi->GetValidityErrorsArr();
+ ImGui::Text( "Errors:" );
+ for( unsigned i = 0; i < validity_errors.size(); i++ ) {
+ ImGui::NextColumn();
+ ImGui::Text( "%s", validity_errors[i].c_str() );
+ ImGui::NextColumn();
+ }
+ ImGui::Separator();
+ ImGui::Text( "Mod Dependencies:" );
+ for( unsigned i = 0; i < modi->mod_dependencies.size(); i++ ) {
+ ImGui::NextColumn();
+ ImGui::Text( "%s", modi->mod_dependencies[i].id.c_str() );
+ ImGui::NextColumn();
+ }
+ ImGui::Separator();
+ ImGui::Text( "Supported Version:" );
+ for( unsigned i = 0; i < modi->supported_versions.size(); i++ ) {
+ ImGui::NextColumn();
+ ImGui::Text( "%s", modi->supported_versions[i].c_str() );
+ ImGui::NextColumn();
+ }
+ ImGui::Separator();
+
+ ImGui::Text("User Control");
+ ImGui::NextColumn();
+
+ if( modi->CanActivate() ) {
+ if( modi->IsActive() ) {
+ if( ImGui::Button("Deactivate") ) {
+ modi->Activate(false);
+ mod_can_activate_check_cache_map_.clear();
+ }
+ } else {
+ if( ImGui::Button("Activate") ) {
+ modi->Activate(true);
+ mod_can_activate_check_cache_map_.clear();
+ }
+ }
+ }
+#if ENABLE_STEAMWORKS
+ SteamworksUGC* ugc = Steamworks::Instance()->GetUGC();
+ if( ugc ) {
+ if( modi->modsource == ModSourceSteamworks ) {
+ if( ImGui::Button("Open Workshop Page") ) {
+ ModID id = modi->GetSid();
+ Steamworks::Instance()->OpenWebPageToMod(id);
+ }
+
+ if( ImGui::Button("Open Author Page") ) {
+ ModID id = modi->GetSid();
+ Steamworks::Instance()->OpenWebPageToModAuthor(id);
+ }
+
+ if( modi->IsSubscribed() ) {
+ if( ImGui::Button("Unsubscribe") ) {
+ modi->RequestUnsubscribe();
+ }
+ } else {
+ if( ImGui::Button("Subscribe") ) {
+ modi->RequestSubscribe();
+ }
+ }
+
+ if( modi->IsFavorite() ) {
+ if( ImGui::Button("Remove Favorite") ) {
+ modi->RequestFavoriteSet(false);
+ }
+ } else {
+ if( ImGui::Button("Make Favorite") ) {
+ modi->RequestFavoriteSet(true);
+ }
+ }
+ if( modi->GetUserVote() != ModInstance::k_VoteUp ) {
+ if( ImGui::Button("Vote Up") ) {
+ modi->RequestVoteSet(true);
+ }
+ }
+ if( modi->GetUserVote() != ModInstance::k_VoteDown ) {
+ if( ImGui::Button("Vote Down") ) {
+ modi->RequestVoteSet(false);
+ }
+ }
+ }
+ }
+#endif
+ ImGui::NextColumn();
+
+ ImGui::Separator();
+
+ ImGui::Text("Owner Control");
+ ImGui::NextColumn();
+
+ if( ImGui::Button( "Manifest..." ) ) {
+ show_manifest = true;
+ }
+
+ if( modi->IsActive()
+ && (!modi->levels.empty() || !modi->campaigns.empty())
+ && ImGui::Button("Generate cache") ) {
+ engine->GenerateLevelCache(modi);
+ }
+
+#if ENABLE_STEAMWORKS
+ if( ugc ) {
+ if( modi->modsource == ModSourceLocalModFolder && modi->IsCore() == false) {
+ if( ImGui::Button("Upload To Steamworks...") ) {
+ show_upload_to_steamworks = true;
+ SetDrawUploadToSteamworks( mod_menu_selected_sid );
+ }
+ }
+
+ if( modi->modsource == ModSourceSteamworks) {
+ if( modi->IsOwnedByCurrentUser() ) {
+ if( ImGui::Button("Upload Update...") ) {
+ show_update_to_steamworks = true;
+ SetDrawUpdateToSteamworks( mod_menu_selected_sid );
+ }
+ } else {
+ ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_TextDisabled]);
+ ImGui::Button("Upload Update...");
+ ImGui::PopStyleColor(1);
+ }
+ }
+ }
+#endif
+
+ ImGui::NextColumn();
+
+ ImGui::Separator();
+
+ ImGui::Text( "Items:" );
+ for( unsigned i = 0; i < modi->items.size(); i++ ) {
+ ImGui::NextColumn();
+ ImGui::Text("%s", modi->items[i].title.c_str());
+ ImGui::NextColumn();
+ }
+ ImGui::Separator();
+
+ ImGui::Text( "Levels:" );
+ for( unsigned i = 0; i < modi->levels.size(); i++ ) {
+ ImGui::NextColumn();
+ ImGui::Text("%s", modi->levels[i].title.c_str());
+ ImGui::NextColumn();
+ }
+ ImGui::Separator();
+
+ ImGui::Text( "Campaigns:" );
+ for( unsigned i = 0; i < modi->campaigns.size(); ++i) {
+ ImGui::NextColumn();
+ ImGui::Text("%s", modi->campaigns[i].title.c_str());
+ ImGui::NextColumn();
+ }
+
+ ImGui::Separator();
+
+ for( unsigned i = 0; i < modi->campaigns.size(); i++ ) {
+ ImGui::Text("%s levels:", modi->campaigns[i].title.c_str());
+
+ for( unsigned j = 0; j < modi->campaigns[i].levels.size(); j++ ) {
+ ImGui::NextColumn();
+ ImGui::Text("%s", modi->campaigns[i].levels[j].title.c_str());
+ ImGui::NextColumn();
+ }
+
+ if(i != modi->campaigns.size()-1){
+ ImGui::Separator();
+ }
+ }
+ }
+
+ ImGui::Columns(1);
+
+ if( modi != NULL ) {
+ ImGui::Separator();
+ ImGui::Text("Parameter Data");
+ ImGui::Separator();
+
+ for( unsigned i = 0; i < modi->levels.size(); i++ ) {
+ if(ImGui::TreeNode(modi->levels[i].title.c_str(),"%s level", modi->levels[i].title.c_str())) {
+ ImguiDrawParameter(&modi->levels[i].parameter,"",0);
+ ImGui::TreePop();
+ }
+ }
+
+ for(unsigned i = 0; i < modi->campaigns.size(); ++i) {
+ ImGui::PushID("campaign_params");
+ if(ImGui::TreeNode(modi->campaigns[i].id.c_str(), "%s campaign", modi->campaigns[i].title.c_str())) {
+ ImguiDrawParameter(&modi->campaigns[i].parameter,"",0);
+ for( unsigned j = 0; j < modi->campaigns[i].levels.size(); j++ ) {
+ if(ImGui::TreeNode(modi->campaigns[i].levels[j].title.c_str(),"%s level", modi->campaigns[i].levels[j].title.c_str())){
+ ImguiDrawParameter(&modi->campaigns[i].levels[j].parameter,"",0);
+ ImGui::TreePop();
+ }
+ }
+
+ ImGui::TreePop();
+ }
+ ImGui::PopID();
+ }
+ }
+ ImGui::EndChild();
+ ImGui::PopStyleVar();
+}
+
+static void DrawUploadToSteamworks() {
+ UGCID upload_ugcid;
+
+ ImGui::SetNextWindowSize(ImVec2(1024.0f, 768.0f), ImGuiCond_FirstUseEver);
+ ImGui::Begin("Steamworks New Upload", &show_upload_to_steamworks);
+
+#if ENABLE_STEAMWORKS
+ SteamworksUGC* ugc = Steamworks::Instance()->GetUGC();
+ ModInstance *modi = ModLoading::Instance().GetMod(upload_to_sid);
+
+ if( ugc && modi ) {
+ ImGui::Columns(2);
+ ImGui::Text( "ID:" ); ImGui::NextColumn();
+ ImGui::Text( "%s", modi->id.c_str() ); ImGui::NextColumn();
+ ImGui::Text( "Version:" ); ImGui::NextColumn();
+ ImGui::Text( "%s", modi->version.c_str() ); ImGui::NextColumn();
+ ImGui::Text( "Name:" ); ImGui::NextColumn();
+ ImGui::Text( "%s", modi->name.c_str() ); ImGui::NextColumn();
+ ImGui::Text( "Category:" ); ImGui::NextColumn();
+ ImGui::Text( "%s", modi->category.c_str() ); ImGui::NextColumn();
+ ImGui::Text( "Author:" ); ImGui::NextColumn();
+ ImGui::Text( "%s", modi->author.c_str() ); ImGui::NextColumn();
+ ImGui::Text( "Tags:" ) ; ImGui::NextColumn();
+ ImGui::TextWrapped( "%s", modi->GetTagsListString().c_str() ); ImGui::NextColumn();
+
+ ImGui::Columns(1);
+ ImGui::Separator();
+
+ static char change_desc[k_cchPublishedDocumentChangeDescriptionMax] = "Initial upload";
+
+ ImGui::Text( "Update Message" );
+ ImGui::InputTextMultiline("##source", change_desc, IM_ARRAYSIZE(change_desc), ImVec2(-1.0f, ImGui::GetTextLineHeight() * 12), ImGuiInputTextFlags_AllowTabInput );
+
+ uint32_t upload_status = modi->GetUploadValidity();
+
+ if( upload_status == ModInstance::k_UploadValidityOk ) {
+ ImGui::Separator();
+
+ ImGui::Columns(2);
+
+ std::vector<SteamworksUGCItem*>::iterator uploaded_item = ugc->GetItem(upload_ugcid);
+ if( uploaded_item != ugc->GetItemEnd() ) {
+ ImGui::Text("Upload Status:"); ImGui::NextColumn();
+ ImGui::Text("%s", (*uploaded_item)->UpdateStatusString()); ImGui::NextColumn();
+ ImGui::Text("Progress:"); ImGui::NextColumn();
+ ImGui::ProgressBar((*uploaded_item)->ItemUploadProgress()); ImGui::NextColumn();
+ ImGui::Text( "Steam Error:" ); ImGui::NextColumn();
+ ImGui::TextWrapped( "%s", (*uploaded_item)->GetLastResultError() ); ImGui::NextColumn();
+ } else {
+ ImGui::Text("Upload Status:"); ImGui::NextColumn();
+ ImGui::Text("Waiting"); ImGui::NextColumn();
+ ImGui::Text("Progress:"); ImGui::NextColumn();
+ ImGui::ProgressBar(0.0f); ImGui::NextColumn();
+ ImGui::Text( "Steam Error:" ); ImGui::NextColumn();
+ ImGui::TextWrapped( "%s", "" ); ImGui::NextColumn();
+ }
+
+ ImGui::Columns(1);
+ ImGui::Separator();
+
+ ImGui::TextWrapped("Before uploading, ensure you have the rights to publish this mod. Abuse and copyright infringement is likely to result in a ban from steam workshop. In some instances local and international law might apply.");
+
+ if( ModLoading::Instance().GetSteamModsMatchingID( modi->id.str() ).size() > 0 ) {
+ ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f,0,0,1.0f));
+ ImGui::TextWrapped( "There is already an instance of this id, are you sure you don't wish to update an existing mod rather than upload a new one?" );
+ ImGui::PopStyleColor(1);
+ }
+
+ static bool verify_upload = false;
+ ImGui::Checkbox( "I have fully read all the above and agree", &verify_upload );
+
+ if( verify_upload ) {
+ if( ImGui::Button("Upload") ) {
+ if( verify_upload ) {
+ verify_upload = false;
+ upload_ugcid = ugc->TryUploadMod(upload_to_sid);
+
+ if( upload_ugcid.Valid() == false ) {
+ LOGE << "Upload failed" << std::endl;
+ }
+ }
+ }
+ }
+ } else {
+ ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1,0,0,1));
+ ImGui::TextWrapped("%s", "The following errors prevents you from uploading your mod.");
+ ImGui::PopStyleColor();
+
+ if( upload_status & ModInstance::k_UploadValidityInvalidPreviewImage ) {
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "A referenced <PreviewImage> file is invalid in the source mod.");
+ }
+
+ if( upload_status & ModInstance::k_UploadValidityMissingPreview ) {
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "The source mod is missing a valid <PreviewImage> file, atleast one is required for steamworks upload.");
+ }
+
+ if( upload_status & ModInstance::k_UploadValidityInvalidModFolder ) {
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "The mod source path to folder is invalid.");
+ }
+
+ if( upload_status & ModInstance::k_UploadValidityBrokenXml ) {
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "The mod.xml file is malformed.");
+ }
+
+ if( upload_status & ModInstance::k_UploadValidityInvalidID ) {
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "The id field is invalid.");
+ }
+
+ if( upload_status & ModInstance::k_UploadValidityInvalidVersion ) {
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "The version field is invalid.");
+ }
+
+ if( upload_status & ModInstance::k_UploadValidityMissingReadRights ) {
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "Missing read rights in the mod folder.");
+ }
+
+ if( upload_status & ModInstance::k_UploadValidityMissingXml ) {
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "Missing mod.xml file");
+ }
+
+ if( upload_status & ModInstance::k_UploadValidityInvalidThumbnail ) {
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "Missing a valid <Thumbnail> file");
+ }
+
+ if( upload_status & ModInstance::k_UploadValidityOversizedPreviewImage ) {
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "<PreviewImage> file size exceeds 1MB limit");
+ }
+
+ if( upload_status & ModInstance::k_UploadValidityGenericValidityError ) {
+ ImGui::Separator();
+ ImGui::TextWrapped(
+ "%s",
+ ModInstance::GenerateValidityErrors(modi->GetValidity() & ModInstance::kValidityUploadSteamworksBlockingMask).c_str()
+ );
+
+ std::vector<std::string> invalid_item_paths = modi->GetInvalidItemPaths();
+
+ if( invalid_item_paths.size() > 0 ) {
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "The following <Item> paths are invalid");
+
+ for( unsigned int i = 0; i < invalid_item_paths.size(); i++ ) {
+ ImGui::TextWrapped("%s", invalid_item_paths[i].c_str());
+ }
+ }
+ }
+ }
+ }
+#else
+ ImGui::Text("Game was not compiled with Steamworks Support.");
+#endif
+ ImGui::End();
+}
+
+static void DrawUpdateToSteamworks() {
+ ModInstance *target_modi = ModLoading::Instance().GetMod(update_to_sid);
+ ModInstance *source_modi = NULL;
+
+ static int currently_selected_source = 0;
+
+ ImGui::SetNextWindowSize(ImVec2(1024.0f, 768.0f), ImGuiCond_FirstUseEver);
+ ImGui::Begin("Steamworks Mod Update Upload", &show_update_to_steamworks);
+#if ENABLE_STEAMWORKS
+ SteamworksUGC* ugc = Steamworks::Instance()->GetUGC();
+ if( ugc && target_modi ) {
+ SteamworksUGCItem* uploaded_ugc_item = target_modi->GetUGCItem();
+ if( uploaded_ugc_item ) {
+
+ std::vector<ModInstance*> possible_sources;
+
+ if( strlen(target_modi->id) == 0 ) {
+ possible_sources = ModLoading::Instance().GetLocalMods();
+ } else {
+ possible_sources = ModLoading::Instance().GetLocalModsMatchingID(target_modi->id.str());
+ }
+
+ if((unsigned)currently_selected_source > possible_sources.size() ) {
+ currently_selected_source = possible_sources.size();
+ }
+
+ std::vector<const char*> dropdown_names;
+
+ for( unsigned i = 0; i < possible_sources.size(); i++ ) {
+ dropdown_names.push_back( possible_sources[i]->name );
+ }
+
+ if( possible_sources.size() > 0 && currently_selected_source < (int)possible_sources.size() ) {
+ source_modi = possible_sources[currently_selected_source];
+ }
+
+ if( source_modi ) {
+ uploaded_ugc_item->SetIntendedUpdateModSource(source_modi->GetSid());
+ } else {
+ uploaded_ugc_item->SetIntendedUpdateModSource(ModID());
+ }
+
+ uint32_t source_upload_status = 0ULL;
+ if( source_modi ) {
+ source_upload_status = source_modi->GetUploadValidity();
+ }
+
+ uint32_t target_upload_status = uploaded_ugc_item->VerifyForUpload();
+
+
+ ImGui::Columns(1);
+
+ ImGui::Combo("Local Source Mod", &currently_selected_source, &dropdown_names[0], dropdown_names.size(), 10 );
+
+ ImGui::Separator();
+
+ ImGui::Text( "Source Mod" );
+
+ ImGui::Separator();
+
+ ImGui::Columns(2);
+
+ if( source_modi ) {
+ ImGui::Text( "ID:" ); ImGui::NextColumn();
+ ImGui::Text( "%s", source_modi->id.c_str() ); ImGui::NextColumn();
+ ImGui::Text( "Version:" ); ImGui::NextColumn();
+ if(target_upload_status & SteamworksUGCItem::k_UploadVerifyUnchangedVersion ) {
+ ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1,0,0,1));
+ }
+ ImGui::Text( "%s", source_modi->version.c_str() ); ImGui::NextColumn();
+ if( target_upload_status & SteamworksUGCItem::k_UploadVerifyUnchangedVersion ) {
+ ImGui::PopStyleColor();
+ }
+ ImGui::Text( "Name:" ); ImGui::NextColumn();
+ ImGui::Text( "%s", source_modi->name.c_str() ); ImGui::NextColumn();
+ ImGui::Text( "Category:" ); ImGui::NextColumn();
+ ImGui::Text( "%s", source_modi->category.c_str() ); ImGui::NextColumn();
+ ImGui::Text( "Author:" ); ImGui::NextColumn();
+ ImGui::Text( "%s", source_modi->author.c_str() ); ImGui::NextColumn();
+ ImGui::Text( "Tags:" ) ; ImGui::NextColumn();
+ ImGui::TextWrapped( "%s", source_modi->GetTagsListString().c_str() ); ImGui::NextColumn();
+ ImGui::Text( "Visibility" ) ; ImGui::NextColumn();
+ ImGui::Combo( "", &update_to_currently_selected_visbility, mod_visibility_options, 3, 3);
+ } else {
+ ImGui::Text( "ID:" ); ImGui::NextColumn();
+ ImGui::NextColumn();
+ ImGui::Text( "Version:" ); ImGui::NextColumn();
+ ImGui::NextColumn();
+ ImGui::Text( "Name:" ); ImGui::NextColumn();
+ ImGui::NextColumn();
+ ImGui::Text( "Category:" ); ImGui::NextColumn();
+ ImGui::NextColumn();
+ ImGui::Text( "Author:" ); ImGui::NextColumn();
+ ImGui::NextColumn();
+ ImGui::Text( "Tags:" ) ; ImGui::NextColumn();
+ ImGui::NextColumn();
+ ImGui::Text( "Visibility" ) ; ImGui::NextColumn();
+ ImGui::NextColumn();
+ }
+
+ ImGui::Columns(1);
+
+ ImGui::Separator();
+
+ ImGui::Text( "Destination Mod" );
+
+ ImGui::Separator();
+
+ ImGui::Columns(2);
+
+ ImGui::Text( "ID:" ); ImGui::NextColumn();
+ ImGui::Text( "%s", target_modi->id.c_str() ); ImGui::NextColumn();
+ ImGui::Text( "Version:" ); ImGui::NextColumn();
+ ImGui::Text( "%s", target_modi->version.c_str() ); ImGui::NextColumn();
+ ImGui::Text( "Name:" ); ImGui::NextColumn();
+ ImGui::Text( "%s", target_modi->name.c_str() ); ImGui::NextColumn();
+ ImGui::Text( "Category:" ); ImGui::NextColumn();
+ ImGui::Text( "%s", target_modi->category.c_str() ); ImGui::NextColumn();
+ ImGui::Text( "Author:" ); ImGui::NextColumn();
+ ImGui::Text( "%s", target_modi->author.c_str() ); ImGui::NextColumn();
+ ImGui::Text( "Tags:" ) ; ImGui::NextColumn();
+ ImGui::TextWrapped( "%s", target_modi->GetTagsListString().c_str() ); ImGui::NextColumn();
+ ImGui::Text( "Visibility" ) ; ImGui::NextColumn();
+
+
+ int visibility_index = 0;
+ for( int i = 0; i < update_to_visibilty_options_count; i++ ) {
+ if( mod_visibility_map[i] == uploaded_ugc_item->visibility ) {
+ visibility_index = i;
+ }
+ }
+ ImGui::Text( "%s", mod_visibility_options[visibility_index] );
+ ImGui::NextColumn();
+
+ ImGui::Columns(1);
+
+ ImGui::Separator();
+
+ static char change_desc[k_cchPublishedDocumentChangeDescriptionMax] = "Generic update";
+
+ ImGui::Text( "Update Message" );
+ ImGui::InputTextMultiline("##source", change_desc, IM_ARRAYSIZE(change_desc), ImVec2(-1.0f, ImGui::GetTextLineHeight() * 12), ImGuiInputTextFlags_AllowTabInput );
+
+ /*
+ static bool only_metadata = false;
+ ImGui::Checkbox( "Only upload meta information", &only_metadata );
+
+ if( ImGui::IsItemHovered() ) {
+ ImGui::BeginTooltip();
+ ImGui::Text("%s", "Limit upload only to mod meta information, including visibility.");
+ ImGui::Text("%s", "This excludes Data folder and mod.xml file.");
+ ImGui::EndTooltip();
+ }
+ */
+
+ ImGui::Separator();
+
+ ImGui::Columns(2);
+
+ ImGui::Text("Upload Status:"); ImGui::NextColumn();
+ ImGui::Text("%s", uploaded_ugc_item->UpdateStatusString()); ImGui::NextColumn();
+ ImGui::Text("Progress:"); ImGui::NextColumn();
+ ImGui::ProgressBar(uploaded_ugc_item->ItemUploadProgress()); ImGui::NextColumn();
+ ImGui::Text("Upload Error:"); ImGui::NextColumn();
+ ImGui::TextWrapped("%s", uploaded_ugc_item->GetLastResultError() ); ImGui::NextColumn();
+
+ if( source_modi ) {
+ if( uploaded_ugc_item->GetUpdateStatus() == k_EItemUpdateStatusInvalid ) {
+ if( target_upload_status == SteamworksUGCItem::k_UploadVerifyOk
+ && source_upload_status == ModInstance::k_UploadValidityOk ) {
+
+ ImGui::Columns(1);
+ ImGui::Separator();
+
+ ImGui::TextWrapped("Before uploading, ensure you have the rights to publish this mod. Abuse and copyright infringement is likely to result in a ban from steam workshop. In some instances local and international law might apply.");
+
+ static bool verify_upload = false;
+ ImGui::Checkbox( "I have fully read all the above and agree", &verify_upload );
+
+ if( verify_upload ) {
+ if( ImGui::Button("Upload") ) {
+ if( verify_upload ) {
+ verify_upload = false;
+ target_modi->RequestUpdate( source_modi->GetSid(), change_desc, static_cast<ModVisibilityOptions>(update_to_currently_selected_visbility));
+ }
+ }
+ }
+ } else {
+ ImGui::Columns(1);
+ ImGui::Separator();
+
+ ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1,0,0,1));
+ ImGui::TextWrapped("%s", "The following errors prevents you from updating your mod.");
+ ImGui::PopStyleColor();
+
+ if( source_upload_status & ModInstance::k_UploadValidityInvalidPreviewImage ) {
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "A referenced <PreviewImage> file is invalid in the source mod.");
+ }
+
+ if( source_upload_status & ModInstance::k_UploadValidityMissingPreview ) {
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "The source mod is missing a valid <PreviewImage> file, atleast one is required for steamworks upload.");
+ }
+
+ if( source_upload_status & ModInstance::k_UploadValidityInvalidModFolder ) {
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "The mod source path to folder is invalid.");
+ }
+
+ if( source_upload_status & ModInstance::k_UploadValidityBrokenXml ) {
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "The mod.xml file is malformed.");
+ }
+ if( source_upload_status & ModInstance::k_UploadValidityInvalidID ) {
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "The id field is invalid.");
+ }
+
+ if( source_upload_status & ModInstance::k_UploadValidityInvalidVersion ) {
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "The version field is invalid.");
+ }
+
+ if( source_upload_status & ModInstance::k_UploadValidityMissingReadRights ) {
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "Missing read rights in the mod folder.");
+ }
+
+ if( source_upload_status & ModInstance::k_UploadValidityGenericValidityError ) {
+ ImGui::Separator();
+ ImGui::TextWrapped(
+ "%s",
+ ModInstance::GenerateValidityErrors(source_modi->GetValidity() & ModInstance::kValidityUploadSteamworksBlockingMask).c_str()
+ );
+ }
+
+ if( source_upload_status & ModInstance::k_UploadValidityMissingXml ) {
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "Missing mod.xml file");
+ }
+
+ if( source_upload_status & ModInstance::k_UploadValidityInvalidThumbnail ) {
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "Missing a valid <Thumbnail> file");
+ }
+
+ if( source_upload_status & ModInstance::k_UploadValidityOversizedPreviewImage ) {
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "<PreviewImage> file size exceeds 1MB limit");
+ }
+
+ std::vector<std::string> invalid_item_paths = source_modi->GetInvalidItemPaths();
+
+ if( invalid_item_paths.size() > 0 ) {
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "The following <Item> paths are invalid");
+
+ for( unsigned int i = 0; i < invalid_item_paths.size(); i++ ) {
+ ImGui::TextWrapped("%s", invalid_item_paths[i].c_str());
+ }
+ }
+
+ if( target_upload_status & SteamworksUGCItem::k_UploadVerifyUnchangedVersion ) {
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "The version is the same in source and target");
+ }
+
+ if( target_upload_status & SteamworksUGCItem::k_UploadVerifyMissingModSource ) {
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "No valid mod source selected");
+ }
+
+ if( target_upload_status & SteamworksUGCItem::k_UploadVerifyInvalidPreviewImage ) {
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "Preview image path is invalid");
+ }
+ }
+ } else {
+ ImGui::Columns(1);
+ ImGui::Separator();
+ ImGui::TextWrapped("%s", "An upload is currently underway...");
+ }
+ }
+ }
+ }
+#else
+ ImGui::Text("Game was not compiled with Steamworks support.");
+#endif
+ ImGui::End();
+}
+
+static void ModActivationChanged(const ModInstance* mod);
+class ImGuiModMenuLoadingCallback : public ModLoadingCallback {
+public:
+ virtual void ModActivationChange(const ModInstance* mod) {
+ ModActivationChanged(mod);
+ }
+};
+static ImGuiModMenuLoadingCallback imgui_mod_loading_callback_;
+
+static bool advanced_menu = false;
+void InitializeModMenu() {
+ advanced_menu = config["advanced_mod_menu"].toBool();
+ ModLoading::Instance().RegisterCallback(&imgui_mod_loading_callback_);
+}
+
+void DrawModMenu(Engine* engine) {
+
+ ImGui::SetNextWindowSize(ImVec2(1024.0f, 768.0f), ImGuiCond_FirstUseEver);
+ ImGui::Begin("Mods", &show_mod_menu,ImGuiWindowFlags_MenuBar);
+
+
+ if (ImGui::BeginMenuBar())
+ {
+ if (ImGui::BeginMenu("Menu")) {
+#if ENABLE_STEAMWORKS
+ if( Steamworks::Instance()->UserCanAccessWorkshop() ) {
+ if( ImGui::MenuItem("Steam Workshop", NULL, false, Steamworks::Instance()->IsConnected() ) ) {
+ Steamworks::Instance()->OpenWebPageToWorkshop();
+ }
+ }
+#endif
+
+ if( ImGui::MenuItem("Advanced", NULL, &advanced_menu) ){
+ config.GetRef("advanced_mod_menu") = advanced_menu;
+ }
+ ImGui::EndMenu();
+ }
+ ImGui::EndMenuBar();
+ }
+
+ if( advanced_menu ) {
+ DrawAdvancedModMenu(engine);
+ } else {
+ DrawSimpleModMenu();
+ }
+
+ ImGui::End();
+
+#if ENABLE_STEAMWORKS
+ SteamworksUGC* ugc = Steamworks::Instance()->GetUGC();
+ if( ugc ) {
+ ModInstance *modi = ModLoading::Instance().GetMod(upload_to_sid);
+ if( modi ) {
+ if( modi->modsource == ModSourceLocalModFolder ) {
+ if( show_upload_to_steamworks ) {
+ DrawUploadToSteamworks();
+ }
+ }
+ }
+
+ modi = ModLoading::Instance().GetMod(update_to_sid);
+ if( modi ) {
+ if( modi->modsource == ModSourceSteamworks ) {
+ if( show_update_to_steamworks ) {
+ DrawUpdateToSteamworks();
+ }
+ }
+ }
+ }
+#endif
+
+ if( show_manifest ) {
+ ModInstance *modi = ModLoading::Instance().GetMod(mod_menu_selected_sid);
+ ImGui::SetNextWindowSize(ImVec2(1024.0f, 768.0f), ImGuiCond_FirstUseEver);
+ ImGui::Begin("Manifest", &show_manifest);
+ if( modi ) {
+ for( unsigned i = 0; i < modi->manifest.size(); i++ ) {
+ ImGui::Text( "%s", modi->manifest[i].c_str() );
+ }
+ }
+ ImGui::End();
+ }
+}
+
+void CleanupModMenu() {
+ mod_can_activate_check_cache_map_.clear();
+ categorized_mods_cache_.clear();
+ categorized_mods_iterations_until_next_check = 0;
+}
+
+static void ModActivationChanged(const ModInstance* mod) {
+ // TODO: Can mods get added or removed during runtime?
+ mod_can_activate_check_cache_map_.clear();
+}
diff --git a/Source/GUI/dimgui/modmenu.h b/Source/GUI/dimgui/modmenu.h
new file mode 100644
index 00000000..a75cf164
--- /dev/null
+++ b/Source/GUI/dimgui/modmenu.h
@@ -0,0 +1,30 @@
+//-----------------------------------------------------------------------------
+// Name: modmenu.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+class Engine;
+
+void InitializeModMenu();
+void DrawModMenu(Engine* engine);
+void CleanupModMenu();
diff --git a/Source/GUI/dimgui/settings_screen.cpp b/Source/GUI/dimgui/settings_screen.cpp
new file mode 100644
index 00000000..91bce3e4
--- /dev/null
+++ b/Source/GUI/dimgui/settings_screen.cpp
@@ -0,0 +1,1258 @@
+//-----------------------------------------------------------------------------
+// Name: settings_screen.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "settings_screen.h"
+
+#include <Graphics/graphics.h>
+#include <Graphics/textures.h>
+#include <Graphics/sky.h>
+#include <Graphics/shaders.h>
+#include <Graphics/camera.h>
+#include <Graphics/pxdebugdraw.h>
+
+#include <Internal/config.h>
+#include <Internal/timer.h>
+#include <Internal/filesystem.h>
+#include <Internal/common.h>
+
+#include <Asset/Asset/animation.h>
+#include <Asset/Asset/levelinfo.h>
+
+#include <Main/scenegraph.h>
+#include <Main/engine.h>
+
+#include <Scripting/angelscript/ascrashdump.h>
+#include <Scripting/angelscript/ascontext.h>
+
+#include <Logging/logdata.h>
+#include <UserInput/input.h>
+#include <Editors/map_editor.h>
+#include <Sound/sound.h>
+#include <Math/vec3math.h>
+#include <UserInput/keyTranslator.h>
+#include <Compat/fileio.h>
+
+#include <SDL_keycode.h>
+
+#include <imgui.h>
+#include <imgui_internal.h>
+
+#include <sstream>
+
+extern float imgui_scale;
+
+extern Config config;
+extern AnimationConfig animation_config;
+extern bool g_simple_shadows;
+extern bool g_level_shadows;
+extern bool g_simple_water;
+extern bool g_disable_fog;
+extern bool g_draw_vr;
+extern bool g_albedo_only;
+extern bool g_no_reflection_capture;
+extern bool g_no_detailmaps;
+extern bool g_no_decals;
+extern bool g_no_decal_elements;
+extern bool g_single_pass_shadow_cascade;
+extern bool g_perform_occlusion_query;
+extern bool g_gamma_correct_final_output;
+extern bool g_attrib_envobj_intancing_support;
+extern bool g_attrib_envobj_intancing_enabled;
+extern bool g_ubo_batch_multiplier_force_1x;
+extern Timer game_timer;
+
+const vec3 kRedBloodColor(0.4f,0.0f,0.0f);
+const vec3 kGreenBloodColor(0.0f,0.4f,0.0f);
+const vec3 kCyanBloodColor(0.0f,0.4f,0.4f);
+const vec3 kBlackBloodColor(0.1f,0.1f,0.1f);
+
+static void UpdateFullscreenMode(FullscreenMode::Mode val) {
+ config.GetRef("fullscreen") = static_cast<int>(val);
+ Graphics::Instance()->SetFullscreen(static_cast<FullscreenMode::Mode>(config["fullscreen"].toNumber<int>()));
+}
+
+static void UpdateMultisampleSetting(int val){
+ config.GetRef("multisample") = val;
+ Graphics::Instance()->SetFSAA(config["multisample"].toNumber<int>());
+}
+
+static void UpdateAnisotropy(int val){
+ config.GetRef("anisotropy") = val;
+ if(config["anisotropy"].toNumber<int>() != Graphics::Instance()->config_.anisotropy()){
+ Graphics::Instance()->SetAnisotropy((float)config["anisotropy"].toNumber<int>());
+ Textures::Instance()->ApplyAnisotropy();
+ }
+}
+
+static void UpdateSimpleFog(bool val){
+ config.GetRef("simple_fog") = val;
+ Graphics::Instance()->SetSimpleFog(val);
+}
+
+static void UpdateDepthOfField(bool val){
+ config.GetRef("depth_of_field") = val;
+ Graphics::Instance()->SetDepthOfField(val);
+}
+
+static void UpdateDepthOfFieldReduced(bool val){
+ config.GetRef("depth_of_field_reduced") = val;
+ Graphics::Instance()->SetDepthOfFieldReduced(val);
+}
+
+static void UpdateDetailObjects(bool val){
+ config.GetRef("detail_objects") = val;
+ Graphics::Instance()->SetDetailObjects(val);
+}
+
+static void UpdateDetailObjectDecals(bool val){
+ config.GetRef("detail_object_decals") = val;
+ Graphics::Instance()->SetDetailObjectDecals(val);
+}
+
+static void UpdateDetailObjectLowres(bool val){
+ config.GetRef("detail_object_lowres") = val;
+ Graphics::Instance()->SetDetailObjectLowres(val);
+}
+
+static void UpdateDetailObjectShadows(bool val){
+ config.GetRef("detail_object_disable_shadows") = !val;
+ Graphics::Instance()->SetDetailObjectShadows(val);
+}
+
+static void UpdateDetailObjectsReduced(bool val){
+ config.GetRef("detail_objects_reduced") = val;
+ Graphics::Instance()->SetDetailObjectsReduced(val);
+}
+
+static void UpdateSimpleShadows(bool val){
+ config.GetRef("simple_shadows") = val;
+ Graphics::Instance()->setSimpleShadows(val);
+}
+
+static void UpdateSimpleWater(bool val) {
+ config.GetRef("simple_water") = val;
+ Graphics::Instance()->setSimpleWater(config["simple_water"].toNumber<bool>());
+}
+
+static void UpdateParticleFieldSimple(bool val){
+ config.GetRef("particle_field_simple") = val;
+ Graphics::Instance()->SetParticleFieldSimple(val);
+}
+
+static void UpdateAttribEnvObjInstancing(bool val){
+ config.GetRef("attrib_envobj_instancing") = val;
+ Graphics::Instance()->setAttribEnvObjInstancing(val);
+}
+
+static void UpdateTetMeshLighting(SceneGraph* scenegraph, bool val){
+ if(config["tet_mesh_lighting"] != val){
+ config.GetRef("tet_mesh_lighting") = val;
+ }
+ if(scenegraph){
+ scenegraph->light_probe_collection.probe_lighting_enabled = val;
+ }
+}
+
+static void UpdateLightVolumes(SceneGraph* scenegraph, bool val){
+ if(config["light_volume_lighting"] != val){
+ config.GetRef("light_volume_lighting") = val;
+ }
+ if(scenegraph){
+ scenegraph->light_probe_collection.light_volume_enabled = val;
+ }
+}
+
+static void UpdateMotionBlur(float val) {
+ config.GetRef("motion_blur_amount") = val;
+ Graphics::Instance()->config_.motion_blur_amount_ = val;
+}
+
+static void UpdateNoReflectionCapture(bool val ) {
+ LOGI << "Setting no reflection capture to " << val << std::endl;
+ g_no_reflection_capture = val;
+ config.GetRef("no_reflection_capture") = val;
+}
+
+static void AddResolution(std::vector<Resolution> &resolutions, int w, int h){
+ if(w < 640 || h < 480){
+ return;
+ }
+ // Check if resolution is already there
+ int num_res = resolutions.size();
+ for(int i=0; i<num_res; ++i){
+ if(resolutions[i].w == w && resolutions[i].h == h){
+ return;
+ }
+ }
+ // Add resolution
+ resolutions.push_back(Resolution(w,h));
+}
+
+static void UpdateEnableLayeredSoundtrackLimiter(bool val) {
+ Engine::Instance()->GetSound()->EnableLayeredSoundtrackLimiter(val);
+ config.GetRef("use_soundtrack_limiter") = val;
+}
+
+void AddResolutionDropdownImGui() {
+ std::vector<Resolution> resolutions = config.GetPossibleResolutions();
+ // Add current resolution
+ const int *render_dims = Graphics::Instance()->render_dims;
+ AddResolution(resolutions, render_dims[0], render_dims[1]);
+ // Sort resolutions
+ std::sort(resolutions.begin(), resolutions.end(), ResolutionCompare());
+ // Add dropdown list
+ static int item = 0;
+ for(int i=0, len=resolutions.size(); i<len; ++i){
+ if(render_dims[0] == resolutions[i].w && render_dims[1] == resolutions[i].h){
+ item = i+1;
+ }
+ }
+ const int kMaxResolutions = 30;
+ const int kMaxResolutionLen = 30;
+ const int kMaxStrLen = kMaxResolutions*kMaxResolutionLen;
+ char resolution_items[kMaxStrLen];
+ char temp[kMaxResolutionLen];
+ int num_resolutions = min((int)resolutions.size(), kMaxResolutions);
+ int index = 0;
+ FormatString(temp, kMaxResolutionLen, "Custom");
+ for(int j=0,len=strlen(temp); j<len; ++j){
+ resolution_items[index] = temp[j];
+ ++index;
+ }
+ resolution_items[index] = '\0';
+ ++index;
+ for(int i=0; i<num_resolutions; ++i){
+ FormatString(temp, kMaxResolutionLen, "%dx%d", resolutions[i].w, resolutions[i].h);
+ for(int j=0,len=strlen(temp); j<len; ++j){
+ resolution_items[index] = temp[j];
+ ++index;
+ }
+ resolution_items[index] = '\0';
+ ++index;
+ }
+ resolution_items[index] = '\0';
+ ++index;
+ static int custom_res[2];
+ int set_new_res[] = {-1,-1};
+ if(ImGui::Combo("Resolution", &item, resolution_items)){
+ if(item == 0){
+ ImGui::OpenPopup("custom_resolution");
+ custom_res[0] = render_dims[0];
+ custom_res[1] = render_dims[1];
+ } else {
+ set_new_res[0] = resolutions[item-1].w;
+ set_new_res[1] = resolutions[item-1].h;
+ }
+ }
+ if (ImGui::BeginPopup("custom_resolution")) {
+ ImGui::Text("Set custom resolution:");
+ ImGui::InputInt("Width", &custom_res[0], 0);
+ ImGui::InputInt("Height", &custom_res[1], 0);
+ if(ImGui::Button("Apply")){
+ set_new_res[0] = custom_res[0];
+ set_new_res[1] = custom_res[1];
+ ImGui::CloseCurrentPopup();
+ }
+ ImGui::EndPopup();
+ }
+ if(set_new_res[0] != -1){
+ set_new_res[0] = max(set_new_res[0], 640);
+ set_new_res[1] = max(set_new_res[1], 480);
+ config.GetRef("screenwidth") = set_new_res[0];
+ config.GetRef("screenheight") = set_new_res[1];
+ Graphics::Instance()->SetResolution(set_new_res[0], set_new_res[1], false);
+ }
+}
+
+static void SetGameSpeed(float val, bool hard) {
+ Engine::Instance()->SetGameSpeed(val, hard);
+}
+
+void DrawSettingsImGui(SceneGraph* scenegraph, ImGuiSettingsType type){
+ Graphics* graphics = Graphics::Instance();
+ static Config* global_settings = Config::GetPresets();
+
+ if (ImGui::BeginMenu("Graphics")) {
+ bool checkbox_val;
+ int dropdown_item;
+
+ // Determine if we match any of the global_settings presets
+ dropdown_item = 0;
+ for(int i=0; i<3; ++i){
+ Config::Map& map = global_settings[i].map_;
+ for(Config::Map::iterator iter = map.begin(); iter != map.end(); ++iter ){
+ if(config.GetRef(iter->first) != iter->second.data){
+ dropdown_item = i + 1;
+ break;
+ }
+ }
+ if(dropdown_item == i){
+ break;
+ }
+ }
+ if(dropdown_item == 3){
+ dropdown_item = 0;
+ } else {
+ ++dropdown_item;
+ }
+
+ if(ImGui::Combo("Overall", &dropdown_item, "Custom\0Low\0Medium\0High\0\0")){
+ if(dropdown_item != 0){
+ int curr_global_setting = dropdown_item-1;
+ Config::Map& map = global_settings[curr_global_setting].map_;
+ for(Config::Map::iterator iter = map.begin(); iter != map.end(); ++iter ){
+ config.GetRef(iter->first) = iter->second.data;
+ }
+ UpdateMultisampleSetting(config.GetRef("multisample").toNumber<int>());
+ UpdateAnisotropy(config.GetRef("anisotropy").toNumber<int>());
+ UpdateSimpleFog(config.GetRef("simple_fog").toNumber<bool>());
+ UpdateDepthOfField(config.GetRef("depth_of_field").toNumber<bool>());
+ UpdateDepthOfFieldReduced(config.GetRef("depth_of_field_reduced").toNumber<bool>());
+ UpdateDetailObjects(config.GetRef("detail_objects").toNumber<bool>());
+ UpdateDetailObjectDecals(config.GetRef("detail_object_decals").toNumber<bool>());
+ UpdateDetailObjectLowres(config.GetRef("detail_object_lowres").toNumber<bool>());
+ UpdateDetailObjectShadows(!config.GetRef("detail_object_disable_shadows").toNumber<bool>());
+ UpdateSimpleShadows(config.GetRef("simple_shadows").toNumber<bool>());
+ UpdateSimpleWater(config.GetRef("simple_water").toNumber<bool>());
+ UpdateAttribEnvObjInstancing(config.GetRef("attrib_envobj_instancing").toNumber<bool>());
+ UpdateTetMeshLighting(scenegraph, config.GetRef("tet_mesh_lighting").toNumber<bool>());
+ UpdateMotionBlur(config.GetRef("motion_blur_amount").toNumber<float>());
+ UpdateNoReflectionCapture(config.GetRef("no_reflection_capture").toBool());
+ }
+ }
+
+ AddResolutionDropdownImGui();
+
+ dropdown_item = static_cast<int>(graphics->config_.full_screen());
+ const char* fullscreen_choices[] = {
+ "Windowed",
+ "Fullscreen",
+ "Windowed borderless",
+ "Fullscreen borderless"
+ };
+ if (ImGui::Combo("Fullscreen mode", &dropdown_item, fullscreen_choices, IM_ARRAYSIZE(fullscreen_choices))){
+ UpdateFullscreenMode(static_cast<FullscreenMode::Mode>(dropdown_item));
+ }
+
+ switch(graphics->config_.FSAA_samples()) {
+ case 2: dropdown_item=1; break;
+ case 4: dropdown_item=2; break;
+ case 8: dropdown_item=3; break;
+ default: dropdown_item=0; break;
+ }
+ const char* aa_choices[] = {
+ "None",
+ "2x",
+ "4x",
+ "8x"
+ };
+ if(ImGui::Combo("Anti-aliasing", &dropdown_item, aa_choices, IM_ARRAYSIZE(aa_choices))){
+ switch(dropdown_item){
+ case 0:
+ UpdateMultisampleSetting(1); break;
+ case 1:
+ UpdateMultisampleSetting(2); break;
+ case 2:
+ UpdateMultisampleSetting(4); break;
+ case 3:
+ UpdateMultisampleSetting(8); break;
+ }
+ }
+
+ switch(config["anisotropy"].toNumber<int>()) {
+ case 2: dropdown_item=1; break;
+ case 4: dropdown_item=2; break;
+ case 8: dropdown_item=3; break;
+ default: dropdown_item=0; break;
+ }
+ if(ImGui::Combo("Anisotropy", &dropdown_item, aa_choices, IM_ARRAYSIZE(aa_choices))){
+ switch(dropdown_item){
+ case 0:
+ UpdateAnisotropy(1); break;
+ case 1:
+ UpdateAnisotropy(2); break;
+ case 2:
+ UpdateAnisotropy(4); break;
+ case 3:
+ UpdateAnisotropy(8); break;
+ }
+ }
+
+ dropdown_item = config.GetRef("texture_reduce").toNumber<int>();
+ const char* texture_detail_choices[] = {
+ "Full",
+ "1/2",
+ "1/4",
+ "1/8"
+ };
+ if(ImGui::Combo("Texture Detail (requires restart)", &dropdown_item, texture_detail_choices, IM_ARRAYSIZE(texture_detail_choices))){
+ config.GetRef("texture_reduce") = dropdown_item;
+ }
+
+
+ dropdown_item = config.GetRef("imgui_scale").toNumber<int>();
+ const char* imgui_scale_choices[] = {
+ "100%",
+ "125%",
+ "150%",
+ "175%",
+ "200%",
+ "225%",
+ "250%",
+ "275%",
+ "300%"
+ };
+ if (ImGui::Combo("Debug Menu Scale", &dropdown_item, imgui_scale_choices, IM_ARRAYSIZE(imgui_scale_choices))) {
+ config.GetRef("imgui_scale") = dropdown_item;
+ imgui_scale = 1.0f + static_cast<float>(dropdown_item) / 4.0f;
+ }
+
+
+ checkbox_val = graphics->config_.vSync();
+ if(ImGui::Checkbox("VSync", &checkbox_val)){
+ config.GetRef("vsync") = checkbox_val;
+ graphics->SetVsync(checkbox_val);
+ }
+
+ checkbox_val = graphics->config_.limit_fps_in_game();
+ if(ImGui::Checkbox("Limit FPS In Game", &checkbox_val)){
+ config.GetRef("limit_fps_in_game") = checkbox_val;
+ graphics->SetLimitFpsInGame(checkbox_val);
+ }
+
+ {
+ int max_fps_val = graphics->config_.max_frame_rate();
+ if(ImGui::DragInt("Max FPS", &max_fps_val, 5.0f, 15, 300)){
+ if(max_fps_val < 15){
+ max_fps_val = 15;
+ }
+ if(max_fps_val > 500){
+ max_fps_val = 500;
+ }
+ config.GetRef("max_frame_rate") = max_fps_val;
+ graphics->SetMaxFrameRate(max_fps_val);
+ }
+ }
+
+ checkbox_val = g_simple_shadows;
+ if(ImGui::Checkbox("Simple shadows", &checkbox_val)){
+ UpdateSimpleShadows(checkbox_val);
+ }
+
+ checkbox_val = config["simple_fog"].toNumber<bool>();
+ if(ImGui::Checkbox("Simple fog", &checkbox_val)){
+ UpdateSimpleFog(checkbox_val);
+ }
+
+ checkbox_val = config["simple_water"].toNumber<bool>();
+ if(ImGui::Checkbox("Simple Water", &checkbox_val)){
+ UpdateSimpleWater(checkbox_val);
+ }
+
+ bool depth_of_field = config["depth_of_field"].toNumber<bool>();
+ if(ImGui::Checkbox("Depth of field", &depth_of_field)){
+ UpdateDepthOfField(depth_of_field);
+ }
+
+ ImGui::BeginDisabled(!depth_of_field);
+ checkbox_val = config["depth_of_field_reduced"].toNumber<bool>();
+ if(ImGui::Checkbox("Depth of field reduce amount", &checkbox_val)){
+ UpdateDepthOfFieldReduced(checkbox_val);
+ }
+ ImGui::EndDisabled();
+
+ ImGui::BeginDisabled(!g_attrib_envobj_intancing_support);
+ checkbox_val = g_attrib_envobj_intancing_enabled;
+ if(ImGui::Checkbox("Vertex Attrib Instancing", &checkbox_val)){
+ UpdateAttribEnvObjInstancing(checkbox_val);
+ }
+ ImGui::EndDisabled();
+
+ checkbox_val = config.GetRef("tet_mesh_lighting").toNumber<bool>();
+ if(ImGui::Checkbox("Use tet mesh lighting", &checkbox_val)){
+ UpdateTetMeshLighting(scenegraph, checkbox_val);
+ }
+
+ checkbox_val = config["light_volume_lighting"].toNumber<bool>();
+ if(ImGui::Checkbox("Use ambient light volumes", &checkbox_val)){
+ UpdateLightVolumes(scenegraph, checkbox_val);
+ }
+
+ bool detail_objects = graphics->config_.detail_objects();
+ if(ImGui::Checkbox("Detail objects", &detail_objects)){
+ UpdateDetailObjects(detail_objects);
+ }
+
+ ImGui::BeginDisabled(!detail_objects);
+ checkbox_val = config["detail_objects_reduced"].toNumber<bool>();
+ if(ImGui::Checkbox("Detail objects reduce count", &checkbox_val)) {
+ UpdateDetailObjectsReduced(checkbox_val);
+ }
+
+ checkbox_val = config["detail_object_decals"].toNumber<bool>();
+ if(ImGui::Checkbox("Detail object decals", &checkbox_val)) {
+ UpdateDetailObjectDecals(checkbox_val);
+ }
+
+ checkbox_val = config["detail_object_lowres"].toNumber<bool>();
+ if(ImGui::Checkbox("Lowres detail objects (needs restart)", &checkbox_val)) {
+ UpdateDetailObjectLowres(checkbox_val);
+ }
+
+ checkbox_val = !config["detail_object_disable_shadows"].toNumber<bool>();
+ if(ImGui::Checkbox("Detail object shadows", &checkbox_val)) {
+ UpdateDetailObjectShadows(checkbox_val);
+ }
+ ImGui::EndDisabled();
+
+ checkbox_val = config.GetRef("particle_field").toNumber<bool>();
+ if(ImGui::Checkbox("GPU particle field", &checkbox_val)){
+ config.GetRef("particle_field") = checkbox_val;
+ }
+
+ checkbox_val = config.GetRef("particle_field_simple").toNumber<bool>();
+ if(ImGui::Checkbox("GPU particle field (simplify)", &checkbox_val)){
+ config.GetRef("particle_field_simple") = checkbox_val;
+ }
+
+ checkbox_val = config.GetRef("custom_level_shaders").toNumber<bool>();
+ if(ImGui::Checkbox("Custom level shaders", &checkbox_val)){
+ config.GetRef("custom_level_shaders") = checkbox_val;
+ }
+
+ checkbox_val = config["no_reflection_capture"].toNumber<bool>();
+ if(ImGui::Checkbox("No reflection capture", &checkbox_val)){
+ UpdateNoReflectionCapture(checkbox_val);
+ }
+
+ int slider_val = (int)( graphics->config_.motion_blur_amount_ * 100.0f + 0.5f );
+ if(ImGui::SliderInt("Motion blur", &slider_val, 0, 100, "%.0f%%")){
+ UpdateMotionBlur(slider_val * 0.01f);
+ }
+
+ {
+ int slider_val = (int)( config.GetRef("brightness").toNumber<float>() * 100.0f + 0.5f );
+ if(ImGui::SliderInt("Brightness", &slider_val, 10, 200, "%.0f%%")){
+ config.GetRef("brightness") = slider_val * 0.01f;
+ }
+ }
+
+ ImGui::EndMenu();
+ }
+ if (ImGui::BeginMenu("Audio")) {
+ int slider_val = (int)(config.GetRef("music_volume").toNumber<float>() * 100.0f + 0.5f);
+ if(ImGui::SliderInt("Music Volume", &slider_val, 0, 100, "%.0f%%")){
+ float val = slider_val / 100.0f;
+ config.GetRef("music_volume") = val;
+ Engine::Instance()->GetSound()->SetMusicVolume(val);
+ }
+
+ slider_val = (int)(config.GetRef("master_volume").toNumber<float>() * 100.0f + 0.5f);
+ if(ImGui::SliderInt("Master Volume", &slider_val, 0, 100, "%.0f%%")){
+ float val = slider_val / 100.0f;
+ config.GetRef("master_volume") = val;
+ Engine::Instance()->GetSound()->SetMasterVolume(val);
+ }
+
+ static int device_selection = -1;
+ std::vector<std::string> available_devices = Engine::Instance()->GetSound()->GetAvailableDevices();
+ if( device_selection == -1 ) {
+ for( unsigned i = 0; i < available_devices.size(); i++ ) {
+ if( strmtch( available_devices[i], config["preferred_audio_device"].str() ) ) {
+ device_selection = i;
+ }
+ }
+ }
+ std::string used_device = Engine::Instance()->GetSound()->GetUsedDevice();
+ const char* devices[16];
+ int audio_device_count = 0;
+ for( unsigned i = 0; i < 16 && i < available_devices.size(); i++ ) {
+ devices[i] = available_devices[i].c_str();
+ if( device_selection == -1 ) {
+ if( strmtch(used_device, available_devices[i]) ) {
+ device_selection = i;
+ }
+ }
+ audio_device_count++;
+ }
+ if( device_selection == -1 ) {
+ device_selection = 0;
+ }
+ ImGui::Text("Used Device: %s", used_device.c_str());
+ ImGui::Text("You will need to restart to apply new audio device usage.");
+ if(ImGui::Combo("Preferred Audio Device", &device_selection, devices, audio_device_count)) {
+ if( device_selection < audio_device_count ) {
+ LOGI << "Setting new preferred audio device: " << devices[device_selection] << std::endl;
+ config.GetRef("preferred_audio_device") = devices[device_selection];
+ }
+ }
+ ImGui::EndMenu();
+ }
+ if (ImGui::BeginMenu("Game")) {
+ int dropdown_val;
+ dropdown_val = 2 - config.GetRef("blood").toNumber<int>();
+ if(ImGui::Combo("Blood amount", &dropdown_val, "Full\0No dripping\0None\0\0")){
+ int val = 2 -dropdown_val;
+ config.GetRef("blood") = val;
+ Graphics::Instance()->config_.SetBlood(val);
+ }
+
+ dropdown_val = 0;
+ const float kBloodColorThreshold = 0.01f;
+ vec3 blood_color = GraphicsConfig::BloodColorFromString(config["blood_color"].str());
+ if(distance_squared(blood_color, kGreenBloodColor) < kBloodColorThreshold){
+ dropdown_val = 1;
+ } else if(distance_squared(blood_color, kCyanBloodColor) < kBloodColorThreshold){
+ dropdown_val = 2;
+ } else if(distance_squared(blood_color, kBlackBloodColor) < kBloodColorThreshold){
+ dropdown_val = 3;
+ }
+ if(ImGui::Combo("Blood color", &dropdown_val, "Red\0Green\0Cyan\0Black\0\0")){
+ vec3 col;
+ switch(dropdown_val){
+ case 0: col = kRedBloodColor; break;
+ case 1: col = kGreenBloodColor; break;
+ case 2: col = kCyanBloodColor; break;
+ case 3: col = kBlackBloodColor; break;
+ }
+ std::ostringstream oss;
+ oss << col[0] << " " << col[1] << " " << col[2];
+ config.GetRef("blood_color") = oss.str();
+ Graphics::Instance()->config_.SetBloodColor(col);
+ }
+
+ bool checkbox_val = config["split_screen"].toNumber<bool>();
+ if(ImGui::Checkbox("Split screen", &checkbox_val)){
+ config.GetRef("split_screen") = checkbox_val;
+ Graphics::Instance()->config_.SetSplitScreen(checkbox_val);
+ }
+
+ bool tutorials = config["tutorials"].toBool();
+ if(ImGui::Checkbox("Tutorials", &tutorials)) {
+ config.GetRef("tutorials") = tutorials;
+ }
+
+ checkbox_val = config["auto_ledge_grab"].toNumber<bool>();
+ if(ImGui::Checkbox("Automatic ledge grab", &checkbox_val)){
+ config.GetRef("auto_ledge_grab") = checkbox_val;
+ }
+
+ std::vector<std::string> diff_presets = config.GetDifficultyPresets();
+ const char* custom_fallback = "Custom";
+ std::vector<const char*> diff_presets_c;
+
+ std::string diff_current = config.GetClosestDifficulty();
+
+ int dropdown_item = -1;
+
+ for( size_t i = 0; i < diff_presets.size(); i++ ) {
+ if( diff_presets[i] == diff_current ) {
+ dropdown_item = i;
+ }
+ diff_presets_c.push_back(diff_presets[i].c_str());
+ }
+
+ if(dropdown_item == -1) {
+ diff_presets_c.push_back(custom_fallback);
+ dropdown_item = diff_presets_c.size()-1;
+ }
+
+ if(ImGui::Combo("Difficulty Preset", &dropdown_item, &diff_presets_c[0], diff_presets.size())){
+ config.SetDifficultyPreset(diff_presets[dropdown_item]);
+ config.ReloadDynamicSettings();
+ }
+
+ float global_time_scale_mult = config["global_time_scale_mult"].toNumber<float>();
+ if(ImGui::SliderFloat("Game Speed", &global_time_scale_mult, 0.5f, 1.0f)){
+ SetGameSpeed(global_time_scale_mult, true);
+ }
+
+ float game_difficulty = config["game_difficulty"].toNumber<float>();
+ if(ImGui::SliderFloat("Game Difficulty", &game_difficulty, 0.0f, 1.0f)){
+ config.GetRef("game_difficulty") = game_difficulty;
+ }
+
+ ImGui::EndMenu();
+ }
+ if (ImGui::BeginMenu("Controls")) {
+ int slider_val;
+ slider_val = (int)(config["mouse_sensitivity"].toNumber<float>()*40.0f + 0.5f);
+ if(ImGui::SliderInt("Mouse sensitivity", &slider_val, 0, 100, "%.0f%%")){
+ float val = slider_val / 40.0f;
+ config.GetRef("mouse_sensitivity") = val;
+ Input::Instance()->SetMouseSensitivity(val);
+ }
+
+ bool checkbox_val;
+ checkbox_val = config["invert_x_mouse_look"].toNumber<bool>();
+ if(ImGui::Checkbox("Invert mouse X", &checkbox_val)){
+ config.GetRef("invert_x_mouse_look") = checkbox_val;
+ Input::Instance()->SetInvertXMouseLook(checkbox_val);
+ }
+
+ checkbox_val = config["invert_y_mouse_look"].toNumber<bool>();
+ if(ImGui::Checkbox("Invert mouse Y", &checkbox_val)){
+ config.GetRef("invert_y_mouse_look") = checkbox_val;
+ Input::Instance()->SetInvertYMouseLook(checkbox_val);
+ }
+
+ checkbox_val = config["use_raw_input"].toNumber<bool>();
+ if(ImGui::Checkbox("Raw mouse input", &checkbox_val)){
+ config.GetRef("use_raw_input") = checkbox_val;
+ Input::Instance()->UseRawInput(checkbox_val);
+ }
+
+ checkbox_val = config["auto_camera"].toNumber<bool>();
+ if(ImGui::Checkbox("Automatic camera", &checkbox_val)){
+ config.GetRef("auto_camera") = checkbox_val;
+ int num_cameras = ActiveCameras::NumCameras();
+ int curr_id = ActiveCameras::GetID();
+ for(int i=0; i<num_cameras; ++i){
+ ActiveCameras::Set(i);
+ ActiveCameras::Get()->SetAutoCamera(checkbox_val);
+ }
+ ActiveCameras::Set(curr_id);
+ }
+
+ static int key_selected = -1;
+ const char* commands[] = {
+ "Forwards",
+ "key[up]",
+ "Backwards",
+ "key[down]",
+ "Left",
+ "key[left]",
+ "Right",
+ "key[right]",
+ "Jump",
+ "key[jump]",
+ "Crouch",
+ "key[crouch]",
+ "Slow motion",
+ "key[slow]",
+ "Equip/sheathe item",
+ "key[item]",
+ "Throw/pick-up item",
+ "key[drop]",
+ "\0"
+ };
+
+ ImGui::Columns(2);
+ int id=0;
+ for(int i=0; commands[i][0] != '\0'; i+=2){
+ ImGui::PushItemWidth(-1);
+ if(i == key_selected){
+ ImGui::Text("Enter input");
+ } else {
+ ImGui::PushID(id);
+ if(ImGui::Button(config[commands[i+1]].str().c_str())){
+ key_selected = i;
+ }
+ ImGui::PopID();
+ ++id;
+ }
+ ImGui::PopItemWidth();
+ ImGui::NextColumn();
+ ImGui::PushID(id);
+ ImGui::Text("%s", commands[i]);
+ ImGui::PopID();
+ ++id;
+ ImGui::NextColumn();
+ }
+ if(key_selected != -1){
+ ImGui::CaptureKeyboardFromApp();
+ ImGui::CaptureMouseFromApp();
+ for(int i=0; i<512; ++i){
+ if(ImGui::IsKeyDown(i)){
+ int val = i;
+ if(val > SDLK_z){
+ val = val + SDLK_CAPSLOCK - 1 - SDLK_z;
+ }
+ config.GetRef(commands[key_selected+1]) = SDLKeycodeToString((SDL_Keycode)val);
+ Input::Instance()->SetFromConfig(config);
+ key_selected = -1;
+ }
+ }
+ }
+ ImGui::Columns(1);
+ ImGui::EndMenu();
+ }
+
+ if (ImGui::BeginMenu("Camera")) {
+ int slider_val;
+ slider_val = static_cast<int>(config["chase_camera_fov"].toNumber<float>());
+
+ if (slider_val == 0) {
+ slider_val = 90;
+ }
+
+ if (ImGui::SliderInt("Chase Camera FOV", &slider_val, 1, 180, "%.0f degrees")) {
+ config.GetRef("chase_camera_fov") = static_cast<float>(slider_val);
+ }
+
+ slider_val = static_cast<int>(config["editor_camera_fov"].toNumber<float>());
+
+ if (slider_val == 0) {
+ slider_val = 90;
+ }
+
+ if (ImGui::SliderInt("Editor Camera FOV", &slider_val, 1, 180, "%.0f degrees")) {
+ config.GetRef("editor_camera_fov") = static_cast<float>(slider_val);
+ }
+
+ ImGui::Text("+/- key to change current FOV");
+ ImGui::Text("0 to reset to 90");
+
+ ImGui::EndMenu();
+ }
+
+ if (ImGui::BeginMenu("Debug")) {
+ bool checkbox_val;
+
+ checkbox_val = config["debug_keys"].toNumber<bool>();
+ if(ImGui::Checkbox("Debug keys", &checkbox_val)){
+ config.GetRef("debug_keys") = checkbox_val;
+ Input::Instance()->debug_keys = checkbox_val;
+ }
+
+ checkbox_val = config["skip_loading_pause"].toNumber<bool>();
+ if(ImGui::Checkbox("Skip Loading Pause 'press any key'", &checkbox_val)){
+ config.GetRef("skip_loading_pause") = checkbox_val;
+ }
+
+ ImGui::Checkbox("Dynamic Character Cubemap", &Graphics::Instance()->config_.dynamic_character_cubemap_);
+
+ checkbox_val = config["decal_normals"].toNumber<bool>();
+ if(ImGui::Checkbox("Decal normal maps", &checkbox_val)){
+ config.GetRef("decal_normals") = checkbox_val;
+ }
+
+ checkbox_val = config["visible_raycasts"].toNumber<bool>();
+ if(ImGui::Checkbox("Visible Raycasts", &checkbox_val)){
+ config.GetRef("visible_raycasts") = checkbox_val;
+ }
+
+ checkbox_val = config["visible_sound_spheres"].toNumber<bool>();
+ if(ImGui::Checkbox("Visible Sound Spheres", &checkbox_val)){
+ config.GetRef("visible_sound_spheres") = checkbox_val;
+ DebugDrawAux::Instance()->visible_sound_spheres = checkbox_val;
+ }
+
+ checkbox_val = config["sound_label"].toNumber<bool>();
+ if(ImGui::Checkbox("Sound Display", &checkbox_val)){
+ config.GetRef("sound_label") = checkbox_val;
+ }
+
+ checkbox_val = config["fps_label"].toNumber<bool>();
+ if(ImGui::Checkbox("FPS Display", &checkbox_val)){
+ config.GetRef("fps_label") = checkbox_val;
+ }
+
+ checkbox_val = config["editor_mode"].toNumber<bool>();
+ if(ImGui::Checkbox("Start Level In Editor", &checkbox_val)){
+ config.GetRef("editor_mode") = checkbox_val;
+ }
+
+ checkbox_val = config["main_menu"].toNumber<bool>();
+ if(ImGui::Checkbox("Start In Main Menu", &checkbox_val)){
+ config.GetRef("main_menu") = checkbox_val;
+ }
+
+ const int kBufSize = 256;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "%s", config["debug_load_level"].str().c_str());
+ if(ImGui::InputText("Level to load", buf, kBufSize)){
+ config.GetRef("debug_load_level") = buf;
+ }
+
+ checkbox_val = config["seamless_cubemaps"].toNumber<bool>();
+ if(ImGui::Checkbox("Seamless Cubemaps", &checkbox_val)){
+ config.GetRef("seamless_cubemaps") = checkbox_val;
+ Graphics::Instance()->SetSeamlessCubemaps(checkbox_val);
+ }
+
+ /*
+ checkbox_val = config["background_process_pool"].toNumber<bool>();
+ if(ImGui::Checkbox("Background Process Pool", &checkbox_val)){
+ config.GetRef("background_process_pool") = checkbox_val;
+ Textures::Instance()->SetProcessPoolsEnabled(checkbox_val);
+ }
+ */
+
+ if (ImGui::BeginMenu("Animation")) {
+ ImGui::Checkbox("Disable IK", &animation_config.kDisableIK);
+ ImGui::Checkbox("Disable Modifiers", &animation_config.kDisableModifiers);
+ ImGui::Checkbox("Disable Soft Animation", &animation_config.kDisableSoftAnimation);
+ ImGui::Checkbox("Disable Interpolation", &animation_config.kDisableInterpolation);
+ ImGui::Checkbox("Disable Animation Layers", &animation_config.kDisableAnimationLayers);
+ ImGui::Checkbox("Disable Animation Mix", &animation_config.kDisableAnimationMix);
+ ImGui::Checkbox("Disable Animation Transition", &animation_config.kDisableAnimationTransition);
+ ImGui::Checkbox("Disable Physics Interpolation", &animation_config.kDisablePhysicsInterpolation);
+ ImGui::Checkbox("Force Idle Anim", &animation_config.kForceIdleAnim);
+ ImGui::EndMenu();
+ }
+
+ if (ImGui::BeginMenu("Movement")) {
+ checkbox_val = config["debug_draw_collision_spheres"].toNumber<bool>();
+ if(ImGui::Checkbox("Show Collision Spheres", &checkbox_val)){
+ config.GetRef("debug_draw_collision_spheres") = checkbox_val;
+ }
+
+ checkbox_val = config["debug_draw_combat_debug"].toNumber<bool>();
+ if(ImGui::Checkbox("Show Combat State", &checkbox_val)){
+ config.GetRef("debug_draw_combat_debug") = checkbox_val;
+ }
+
+ checkbox_val = config["debug_draw_vis_lines"].toNumber<bool>();
+ if(ImGui::Checkbox("Draw Vis Lines", &checkbox_val)){
+ config.GetRef("debug_draw_vis_lines") = checkbox_val;
+ }
+
+ checkbox_val = config["debug_draw_bones"].toNumber<bool>();
+ if(ImGui::Checkbox("Draw Bones", &checkbox_val)){
+ config.GetRef("debug_draw_bones") = checkbox_val;
+ }
+
+ ImGui::EndMenu();
+ }
+
+ if (ImGui::BeginMenu("AI")) {
+ checkbox_val = config["debug_show_ai_state"].toNumber<bool>();
+ if(ImGui::Checkbox("Show AI State", &checkbox_val)){
+ config.GetRef("debug_show_ai_state") = checkbox_val;
+ }
+
+ checkbox_val = config["debug_draw_stealth_debug"].toNumber<bool>();
+ if(ImGui::Checkbox("Show AI Awareness State", &checkbox_val)){
+ config.GetRef("debug_draw_stealth_debug") = checkbox_val;
+ }
+
+ checkbox_val = config["debug_show_ai_path"].toNumber<bool>();
+ if(ImGui::Checkbox("Show AI Path", &checkbox_val)){
+ config.GetRef("debug_show_ai_path") = checkbox_val;
+ }
+
+ checkbox_val = config["debug_show_ai_jump"].toNumber<bool>();
+ if(ImGui::Checkbox("Show AI Jump", &checkbox_val)){
+ config.GetRef("debug_show_ai_jump") = checkbox_val;
+ }
+
+ checkbox_val = config["debug_show_ai_investigate"].toNumber<bool>();
+ if(ImGui::Checkbox("Show AI Investigate", &checkbox_val)){
+ config.GetRef("debug_show_ai_investigate") = checkbox_val;
+ }
+
+ checkbox_val = config["debug_mouse_path_test"].toNumber<bool>();
+ if(ImGui::Checkbox("AI Mouse Path Testing", &checkbox_val)){
+ config.GetRef("debug_mouse_path_test") = checkbox_val;
+ }
+
+ ImGui::EndMenu();
+ }
+
+ if (ImGui::BeginMenu("Graphics")) {
+ checkbox_val = config["albedo_only"].toNumber<bool>();
+ if(ImGui::Checkbox("Albedo Only", &checkbox_val)){
+ config.GetRef("albedo_only") = checkbox_val;
+ g_albedo_only = checkbox_val;
+ }
+
+ checkbox_val = config["no_decals"].toNumber<bool>();
+ if(ImGui::Checkbox("No Light/Decal system", &checkbox_val)){
+ config.GetRef("no_decals") = checkbox_val;
+ g_no_decals = checkbox_val;
+ }
+
+ checkbox_val = config["no_decal_elements"].toNumber<bool>();
+ if(ImGui::Checkbox("No Lights or Decals", &checkbox_val)){
+ config.GetRef("no_decal_elements") = checkbox_val;
+ g_no_decal_elements = checkbox_val;
+ }
+
+ checkbox_val = config["occlusion_query"].toNumber<bool>();
+ if(ImGui::Checkbox("Occlusion Queries", &checkbox_val)){
+ config.GetRef("occlusion_query") = checkbox_val;
+ g_perform_occlusion_query = checkbox_val;
+ }
+
+ checkbox_val = config["gamma_correct_final_output"].toNumber<bool>();
+ if(ImGui::Checkbox("Gamma Correct Final Output", &checkbox_val)){
+ config.GetRef("gamma_correct_final_output") = checkbox_val;
+ g_gamma_correct_final_output = checkbox_val;
+ }
+
+ if (GLEW_VERSION_4_0) {
+ checkbox_val = config["single_pass_shadow_cascade"].toNumber<bool>();
+ if(ImGui::Checkbox("Single-Pass Shadow Cascade", &checkbox_val)){
+ config.GetRef("single_pass_shadow_cascade") = checkbox_val;
+ g_single_pass_shadow_cascade = checkbox_val;
+ }
+ }
+
+ checkbox_val = config["no_detailmaps"].toNumber<bool>();
+ if(ImGui::Checkbox("Disable detail maps", &checkbox_val)){
+ config.GetRef("no_detailmaps") = checkbox_val;
+ g_no_detailmaps = checkbox_val;
+ }
+
+ checkbox_val = g_ubo_batch_multiplier_force_1x;
+ if(ImGui::Checkbox("Disable UBO Batch Multiplier", &checkbox_val)){
+ // Not saved to config (at least for now)
+ g_ubo_batch_multiplier_force_1x = checkbox_val;
+ }
+
+ checkbox_val = config["volume_shadows"].toNumber<bool>();
+ if(ImGui::Checkbox("Volume Shadows", &checkbox_val)){
+ config.GetRef("volume_shadows") = checkbox_val;
+ }
+
+ checkbox_val = config["ssao"].toNumber<bool>();
+ if(ImGui::Checkbox("SSAO", &checkbox_val)){
+ config.GetRef("ssao") = checkbox_val;
+ }
+
+ checkbox_val = config["disable_fog"].toNumber<bool>();
+ if(ImGui::Checkbox("Disable Fog", &checkbox_val)){
+ config.GetRef("disable_fog") = checkbox_val;
+ g_disable_fog = config["disable_fog"].toNumber<bool>();
+ }
+
+ checkbox_val = graphics->config_.gpu_skinning();
+ if(ImGui::Checkbox("GPU Skinning", &checkbox_val)){
+ config.GetRef("gpu_skinning") = checkbox_val;
+ graphics->config_.SetGPUSkinning(checkbox_val);
+ if( scenegraph ) {
+ scenegraph->map_editor->UpdateGPUSkinning();
+ }
+ }
+
+ checkbox_val = config["opengl_callback_errors"].toNumber<bool>();
+ if(ImGui::Checkbox("OpenGL Callbacks Errors", &checkbox_val)){
+ config.GetRef("opengl_callback_errors") = checkbox_val;
+ }
+
+ checkbox_val = config["opengl_callback_error_dialog"].toNumber<bool>();
+ if(ImGui::Checkbox("Dialog on OpenGL error", &checkbox_val)){
+ config.GetRef("opengl_callback_error_dialog") = checkbox_val;
+ }
+
+ ImGui::EndMenu();
+ }
+
+ if (ImGui::BeginMenu("Sound")) {
+ checkbox_val = config["use_soundtrack_limiter"].toBool();
+ if(ImGui::Checkbox("Use Soundtrack Limiter", &checkbox_val)) {
+ UpdateEnableLayeredSoundtrackLimiter(checkbox_val);
+ }
+
+ ImGui::EndMenu();
+ }
+
+ if (ImGui::BeginMenu("Game")) {
+ checkbox_val = config["block_cheating_progress"].toBool();
+ if(ImGui::Checkbox("Block Cheating Progress", &checkbox_val)) {
+ config.GetRef("block_cheating_progress") = checkbox_val;
+ }
+
+ ImGui::EndMenu();
+ }
+
+ checkbox_val = config["allow_game_dir_save"].toNumber<bool>();
+ if(ImGui::Checkbox("Allow Game Directory Save", &checkbox_val)){
+ config.GetRef("allow_game_dir_save") = checkbox_val;
+ }
+
+ checkbox_val = config["no_auto_nav_mesh"].toNumber<bool>();
+ if(ImGui::Checkbox("Disable Auto Navmesh Regen", &checkbox_val)){
+ config.GetRef("no_auto_nav_mesh") = checkbox_val;
+ }
+
+ checkbox_val = config["no_texture_convert"].toNumber<bool>();
+ if(ImGui::Checkbox("Disable Auto Texture Conversion", &checkbox_val)){
+ config.GetRef("no_texture_convert") = checkbox_val;
+ }
+
+ checkbox_val = config.HasKey("list_include_files_when_logging_mod_errors")
+ ? config["list_include_files_when_logging_mod_errors"].toNumber<bool>()
+ : true;
+ if(ImGui::Checkbox("List Include Files When Logging Mod Errors", &checkbox_val)){
+ config.GetRef("list_include_files_when_logging_mod_errors") = checkbox_val;
+ }
+
+ ImGui::Separator();
+
+ if(ImGui::Button("Clear local write cache (dangerous)")){
+ ClearCache(false);
+ }
+
+ if(ImGui::Button("List files that would be cleared from cache")){
+ ClearCache(true);
+ }
+
+ ImGui::Separator();
+
+ if(ImGui::Button("Show test error message")){
+ DisplayError("Error test title", "Error test body");
+ }
+
+ if( ImGui::Button("Dump state" ) ) {
+ SceneGraph* s = Engine::Instance()->GetSceneGraph();
+ if(s) {
+ s->DumpState();
+ }
+ DumpAngelscriptStates();
+ LogSystem::Flush();
+ }
+
+ ImGui::Separator();
+
+ if (ImGui::BeginMenu("Campaign Progress (Dangerous)")) {
+ if(ImGui::Button("Unlock all campaign levels")){
+ std::vector<ModInstance::Campaign> campaigns = ModLoading::Instance().GetCampaigns();
+ for( size_t i = 0; i < campaigns.size(); i++ ) {
+ ModInstance::Campaign camp = campaigns[i];
+ SavedLevel& s = Engine::Instance()->save_file_.GetSave(camp.id.str(),"linear_campaign","");
+ for( size_t k = 0; k < camp.levels.size(); k++ ) {
+ s.SetArrayValue("unlocked_levels",k,camp.levels[k].id.str());
+ }
+ }
+ Engine::Instance()->save_file_.QueueWriteInPlace();
+ }
+
+ if(ImGui::BeginMenu("Unlock level in campaign")) {
+ std::vector<ModInstance::Campaign> campaigns = ModLoading::Instance().GetCampaigns();
+ for( size_t i = 0; i < campaigns.size(); i++ ) {
+ ModInstance::Campaign camp = campaigns[i];
+ if(ImGui::BeginMenu(camp.title.c_str())) {
+ for( size_t k = 0; k < camp.levels.size(); k++ ) {
+ if(ImGui::Button(camp.levels[k].title.c_str())) {
+ SavedLevel& s = Engine::Instance()->save_file_.GetSave(camp.id.str(),"linear_campaign","");
+ s.AppendArrayValueIfUnique("unlocked_levels",camp.levels[k].id.str());
+ Engine::Instance()->save_file_.QueueWriteInPlace();
+ }
+ }
+ ImGui::EndMenu();
+ }
+ }
+ ImGui::EndMenu();
+ }
+
+ if(ImGui::Button("Clear all campaign progress")){
+ std::vector<ModInstance::Campaign> campaigns = ModLoading::Instance().GetCampaigns();
+ for( size_t i = 0; i < campaigns.size(); i++ ) {
+ ModInstance::Campaign camp = campaigns[i];
+ SavedLevel& s = Engine::Instance()->save_file_.GetSave(camp.id.str(),"linear_campaign","");
+ s.ClearData();
+ }
+ Engine::Instance()->save_file_.QueueWriteInPlace();
+ }
+
+ std::vector<std::string> difficulties = config.GetDifficultyPresets();
+
+ if(ImGui::BeginMenu("Finish All Levels On Difficulty")) {
+ for( size_t k = 0; k < difficulties.size(); k++ ) {
+ if( ImGui::Button(difficulties[k].c_str())) {
+ std::vector<ModInstance::Campaign> campaigns = ModLoading::Instance().GetCampaigns();
+ for( size_t i = 0; i < campaigns.size(); i++ ) {
+ ModInstance::Campaign camp = campaigns[i];
+ for( size_t j = 0; j < camp.levels.size(); j++ ) {
+ SavedLevel& s = Engine::Instance()->save_file_.GetSave(camp.id.str(),"linear_campaign",camp.levels[j].id.str());
+ if( false == s.ArrayContainsValue("finished_difficulties",difficulties[k]) ) {
+ s.AppendArrayValue("finished_difficulties",difficulties[k]);
+ }
+ }
+ }
+ Engine::Instance()->save_file_.QueueWriteInPlace();
+ }
+ }
+ ImGui::EndMenu();
+ }
+
+ if(ImGui::BeginMenu("Finish Level On Difficulty")) {
+ std::vector<ModInstance::Campaign> campaigns = ModLoading::Instance().GetCampaigns();
+ for( size_t i = 0; i < campaigns.size(); i++ ) {
+ ModInstance::Campaign camp = campaigns[i];
+ if(ImGui::BeginMenu(camp.title)) {
+ for( size_t j = 0; j < camp.levels.size(); j++ ) {
+ ModInstance::Level level = camp.levels[j];
+ if(ImGui::BeginMenu(level.title)) {
+ for( size_t k = 0; k < difficulties.size(); k++ ) {
+ if( ImGui::Button(difficulties[k].c_str())) {
+ SavedLevel& s = Engine::Instance()->save_file_.GetSave(camp.id.str(),"linear_campaign",level.id.str());
+ if( false == s.ArrayContainsValue("finished_difficulties",difficulties[k]) ) {
+ s.AppendArrayValue("finished_difficulties",difficulties[k]);
+ }
+ Engine::Instance()->save_file_.QueueWriteInPlace();
+ }
+ }
+ ImGui::EndMenu();
+ }
+ }
+ ImGui::EndMenu();
+ }
+ }
+ ImGui::EndMenu();
+ }
+
+ if(ImGui::Button("Clear All Linear Level Data")) {
+ std::vector<ModInstance::Campaign> campaigns = ModLoading::Instance().GetCampaigns();
+ for( size_t i = 0; i < campaigns.size(); i++ ) {
+ ModInstance::Campaign camp = campaigns[i];
+ for( size_t j = 0; j < camp.levels.size(); j++ ) {
+ SavedLevel& s = Engine::Instance()->save_file_.GetSave(camp.id.str(),"linear_campaign",camp.levels[j].id.str());
+ s.ClearData();
+ }
+ }
+ Engine::Instance()->save_file_.QueueWriteInPlace();
+ }
+
+ if(ImGui::BeginMenu("Clear Save Data For Level")) {
+ std::vector<ModInstance::Campaign> campaigns = ModLoading::Instance().GetCampaigns();
+ for( size_t i = 0; i < campaigns.size(); i++ ) {
+ ModInstance::Campaign camp = campaigns[i];
+ if(ImGui::BeginMenu(camp.title)) {
+ for( size_t j = 0; j < camp.levels.size(); j++ ) {
+ ModInstance::Level level = camp.levels[j];
+ if(ImGui::Button(level.title)) {
+ SavedLevel& s = Engine::Instance()->save_file_.GetSave(camp.id.str(),"linear_campaign",level.id.str());
+ s.ClearData();
+ Engine::Instance()->save_file_.QueueWriteInPlace();
+ }
+ }
+ ImGui::EndMenu();
+ }
+ }
+ ImGui::EndMenu();
+ }
+
+ ImGui::EndMenu();
+ }
+
+ checkbox_val = config.GetRef("player_invincible").toNumber<bool>();
+ if(ImGui::Checkbox("Make player invincible", &checkbox_val)) {
+ config.GetRef("player_invincible") = checkbox_val;
+ }
+
+ ImGui::EndMenu();
+ }
+}
diff --git a/Source/GUI/dimgui/settings_screen.h b/Source/GUI/dimgui/settings_screen.h
new file mode 100644
index 00000000..d2d3a4d4
--- /dev/null
+++ b/Source/GUI/dimgui/settings_screen.h
@@ -0,0 +1,30 @@
+//-----------------------------------------------------------------------------
+// Name: settings_screen.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+class ASContext;
+class SceneGraph;
+enum ImGuiSettingsType {IMGST_MENU, IMGST_WINDOW};
+void DrawSettingsImGui(SceneGraph* scenegraph, ImGuiSettingsType type);
+void AttachSettingsScreen(ASContext* ctx);
diff --git a/Source/GUI/gui.cpp b/Source/GUI/gui.cpp
new file mode 100644
index 00000000..39790630
--- /dev/null
+++ b/Source/GUI/gui.cpp
@@ -0,0 +1,35 @@
+//-----------------------------------------------------------------------------
+// Name: gui.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "gui.h"
+
+#include <Internal/timer.h>
+#include <Threading/sdl_wrapper.h>
+
+extern Timer game_timer;
+
+void GUI::AddDebugText(const std::string &key, const std::string &text, float lifetime) {
+ GUI::DebugTextEntry* debug_text_entry = &(debug_text[key]);
+ debug_text_entry->text = text;
+ debug_text_entry->delete_time = SDL_TS_GetTicks() + (uint32_t)(1000*lifetime);
+}
diff --git a/Source/GUI/gui.h b/Source/GUI/gui.h
new file mode 100644
index 00000000..26a1313e
--- /dev/null
+++ b/Source/GUI/gui.h
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------------
+// Name: gui.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/integer.h>
+
+#include <vector>
+#include <map>
+#include <string>
+
+class GUI {
+public:
+ struct DebugTextEntry {
+ std::string text;
+ uint32_t delete_time;
+ };
+ typedef std::map<std::string, DebugTextEntry> DebugTextMap;
+ DebugTextMap debug_text;
+
+ void AddDebugText(const std::string &key, const std::string &text, float lifetime);
+};
diff --git a/Source/GUI/scriptable_ui.cpp b/Source/GUI/scriptable_ui.cpp
new file mode 100644
index 00000000..af08df2b
--- /dev/null
+++ b/Source/GUI/scriptable_ui.cpp
@@ -0,0 +1,200 @@
+//-----------------------------------------------------------------------------
+// Name: scriptable_ui.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "scriptable_ui.h"
+
+#include <GUI/IMUI/imui.h>
+#include <GUI/IMUI/imui_script.h>
+
+#include <Scripting/angelscript/asfuncs.h>
+
+#include <Graphics/hudimage.h>
+#include <Graphics/text.h>
+
+#include <Game/levelset_script.h>
+#include <Game/savefile.h>
+#include <Game/savefile_script.h>
+
+#include <Internal/levelxml_script.h>
+#include <Internal/filesystem.h>
+#include <Internal/common.h>
+#include <Internal/profiler.h>
+#include <Internal/locale.h>
+
+#include <Utility/assert.h>
+#include <Main/engine.h>
+
+#include <cassert>
+
+namespace {
+void AttachScriptableUI(ASContext *as_context, ScriptableUI *sui) {
+ as_context->RegisterObjectType("ScriptableUI", 0, asOBJ_REF | asOBJ_NOHANDLE);
+ as_context->RegisterObjectMethod("ScriptableUI", "void SendCallback(const string &in)", asMETHOD(ScriptableUI, SendCallback), asCALL_THISCALL);
+ as_context->DocsCloseBrace();
+ as_context->RegisterGlobalProperty("ScriptableUI this_ui", sui);
+}
+} //Anonymous namespace
+
+void ScriptableUI::Initialize(const Path& script_path, const ASData& as_data, bool debug_break) {
+ mod_activation_change_ = false;
+ LOG_ASSERT(!as_context && !hud_images);
+ // Construct all the components
+ as_context = new ASContext("scriptable_ui", as_data);
+ if(debug_break)
+ as_context->dbg.Break();
+ hud_images = new HUDImages();
+ // Attach necessary script functionality
+ AttachTextCanvasTextureToASContext(as_context);
+ AttachStringConvert(as_context);
+ AttachIMUI(as_context);
+ AttachScreenWidth(as_context);
+ hud_images->AttachToContext(as_context);
+ AttachLevelSet(as_context);
+ AttachLevelXML(as_context);
+ AttachUIQueries(as_context);
+ AttachTokenIterator(as_context);
+ AttachScriptableUI(as_context, this);
+ AttachSimpleFile(as_context);
+ AttachLocale(as_context);
+ AttachIMGUI(as_context);
+ AttachIMGUIModding(as_context);
+ AttachInterlevelData(as_context);
+ AttachEngine(as_context);
+ AttachOnline(as_context);
+
+ AttachSaveFile( as_context, &Engine::Instance()->save_file_ );
+
+ as_funcs.initialize = as_context->RegisterExpectedFunction("void Initialize()",true);
+ as_funcs.can_go_back = as_context->RegisterExpectedFunction("bool CanGoBack()",true);
+ as_funcs.dispose = as_context->RegisterExpectedFunction("void Dispose()",true);
+ as_funcs.draw_gui = as_context->RegisterExpectedFunction("void DrawGUI()",true);
+ as_funcs.update = as_context->RegisterExpectedFunction("void Update()",true);
+
+ as_funcs.mod_activation_reload = as_context->RegisterExpectedFunction("void ModActivationReload()",false);
+ as_funcs.resize = as_context->RegisterExpectedFunction("void Resize()",false);
+ as_funcs.script_reloaded = as_context->RegisterExpectedFunction("void ScriptReloaded()",false);
+
+ as_funcs.queue_basic_popup = as_context->RegisterExpectedFunction("void QueueBasicPopup(string title, string body)",false);
+
+ // Get screen dimensions so we can detect when things change
+ currentWindowDims[0] = Graphics::Instance()->window_dims[0];
+ currentWindowDims[1] = Graphics::Instance()->window_dims[1];
+
+ PROFILER_ENTER(g_profiler_ctx, "Exporting docs");
+ char path[kPathSize];
+ FormatString(path, kPathSize, "%sasscriptable_ui_docs.h", GetWritePath(CoreGameModID).c_str());
+ as_context->ExportDocs(path);
+ PROFILER_LEAVE(g_profiler_ctx);
+
+
+ // Load script and run init function
+ as_context->LoadScript(script_path);
+ as_context->CallScriptFunction(as_funcs.initialize);
+
+ ModLoading::Instance().RegisterCallback(this);
+}
+
+bool ScriptableUI::CanGoBack()
+{
+ ASArg ret;
+ asBYTE retvalue;
+ ret.type = _as_bool;
+ ret.data = &retvalue;
+ ASArglist args;
+
+ as_context->CallScriptFunction(as_funcs.can_go_back, &args, &ret);
+
+ return retvalue;
+}
+
+void ScriptableUI::Dispose() {
+ ModLoading::Instance().DeRegisterCallback(this);
+
+ as_context->CallScriptFunction(as_funcs.dispose);
+
+ delete as_context;
+ delete hud_images;
+}
+
+void ScriptableUI::Draw() {
+ as_context->CallScriptFunction(as_funcs.draw_gui);
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "hud_images->Draw()");
+ hud_images->Draw();
+ }
+}
+
+void ScriptableUI::Update() {
+ if( mod_activation_change_ ) {
+ LOGI << "ModActivation: ModActivationReload() called" << std::endl;
+ as_context->CallScriptFunction(as_funcs.mod_activation_reload);
+ mod_activation_change_ = false;
+ }
+
+ // See if the window size has changed
+ if( currentWindowDims[0] != Graphics::Instance()->window_dims[0] ||
+ currentWindowDims[1] != Graphics::Instance()->window_dims[1] ) {
+ currentWindowDims[0] = Graphics::Instance()->window_dims[0];
+ currentWindowDims[1] = Graphics::Instance()->window_dims[1];
+ as_context->CallScriptFunction(as_funcs.resize);
+ }
+
+ as_context->CallScriptFunction(as_funcs.update);
+}
+
+void ScriptableUI::QueueBasicPopup(const std::string& title, const std::string& body) {
+ if(as_context->HasFunction(as_funcs.queue_basic_popup)) {
+ ASArglist args;
+ args.AddString(&const_cast<std::string&>(title));
+ args.AddString(&const_cast<std::string&>(body));
+ as_context->CallScriptFunction(as_funcs.queue_basic_popup, &args);
+ } else {
+ LOGW << "Attempted to send popup to scriptable UI, but ASContext has not specified void QueueBasicPopup(string title, string body)! This is usually included in menu_common.as" << std::endl;
+ LOGW << "Failed to queue popup: \"" << title << "\" \"" << body << "\"" << std::endl;
+ }
+}
+
+void ScriptableUI::SendCallback( const std::string& message ) {
+ LOG_ASSERT(notification_callback_ && callback_instance_);
+ notification_callback_(callback_instance_, message);
+}
+
+void ScriptableUI::Reload(bool force) {
+ if( as_context && as_context->Reload() ) {
+ as_context->CallScriptFunction(as_funcs.script_reloaded);
+ } else if(force) {
+ as_context->CallScriptFunction(as_funcs.script_reloaded);
+ }
+}
+
+void ScriptableUI::ScheduleDelete() {
+ to_delete_ = true;
+}
+
+bool ScriptableUI::IsDeleteScheduled() {
+ return to_delete_;
+}
+
+void ScriptableUI::ModActivationChange( const ModInstance* mod ) {
+ mod_activation_change_ = true;
+}
diff --git a/Source/GUI/scriptable_ui.h b/Source/GUI/scriptable_ui.h
new file mode 100644
index 00000000..b692879c
--- /dev/null
+++ b/Source/GUI/scriptable_ui.h
@@ -0,0 +1,92 @@
+//-----------------------------------------------------------------------------
+// Name: scriptable_ui.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/modloading.h>
+#include <Internal/path.h>
+
+#include <Utility/disallow_copy_and_assign.h>
+#include <Scripting/angelscript/ascontext.h>
+
+#include <string>
+#include <memory>
+
+class ASContext;
+struct HUDImages;
+struct ASData;
+class SaveFile;
+
+class ScriptableUI : public ModLoadingCallback {
+public:
+ typedef void (*NotificationCallback)(void*, const std::string&);
+private:
+ // These are pointers to minimize header includes
+ HUDImages *hud_images;
+ void Dispose();
+ void* callback_instance_;
+ bool to_delete_;
+ bool mod_activation_change_;
+ NotificationCallback notification_callback_;
+
+ int currentWindowDims[2];
+
+ struct {
+ ASFunctionHandle initialize;
+ ASFunctionHandle can_go_back;
+ ASFunctionHandle dispose;
+ ASFunctionHandle draw_gui;
+ ASFunctionHandle update;
+
+ ASFunctionHandle mod_activation_reload;
+ ASFunctionHandle resize;
+ ASFunctionHandle script_reloaded;
+
+ ASFunctionHandle queue_basic_popup;
+ } as_funcs;
+public:
+ ASContext *as_context;
+ ScriptableUI(void *callback_instance, NotificationCallback notification_callback)
+ :as_context(NULL), hud_images(NULL),
+ callback_instance_(callback_instance), to_delete_(false),
+ notification_callback_(notification_callback)
+ {}
+ ~ScriptableUI() {
+ Dispose();
+ };
+
+ bool CanGoBack();
+ void ScheduleDelete();
+ bool IsDeleteScheduled();
+ void Initialize(const Path& script_path, const ASData& data, bool debug_break = false);
+ void Update();
+ void Draw();
+ void QueueBasicPopup(const std::string& title, const std::string& body);
+ void SendCallback(const std::string& message);
+
+ void Reload(bool force=false);
+
+ virtual void ModActivationChange( const ModInstance* mod );
+
+ DISALLOW_COPY_AND_ASSIGN(ScriptableUI);
+};
diff --git a/Source/GUI/widgetframework.cpp b/Source/GUI/widgetframework.cpp
new file mode 100644
index 00000000..02715b10
--- /dev/null
+++ b/Source/GUI/widgetframework.cpp
@@ -0,0 +1,56 @@
+//-----------------------------------------------------------------------------
+// Name: widgetframework.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "widgetframework.h"
+
+#include <Logging/logdata.h>
+
+#ifdef WIN32
+#define snprintf sprintf_s
+#endif
+
+NativeLoadingText native_loading_text;
+
+void AddLoadingText( const std::string &text ) {
+ native_loading_text.AddLine(text.c_str());
+ LOGI << text.c_str() << std::endl;
+}
+
+void NativeLoadingText::AddLine(const char* msg) {
+ buf_mutex.lock();
+ // Shift all lines up
+ for(int i=kMaxLines*kMaxCharPerLine-1, index=(kMaxLines-1)*kMaxCharPerLine-1;
+ i>=kMaxCharPerLine;)
+ {
+ buf[i--] = buf[index--];
+ }
+ // Add new line
+ snprintf(buf, kMaxCharPerLine, "%s", msg);
+ buf_mutex.unlock();
+}
+
+void NativeLoadingText::Clear() {
+ buf_mutex.lock();
+ memset(buf, 0, sizeof(buf));
+ buf_mutex.unlock();
+};
diff --git a/Source/GUI/widgetframework.h b/Source/GUI/widgetframework.h
new file mode 100644
index 00000000..2b1bda97
--- /dev/null
+++ b/Source/GUI/widgetframework.h
@@ -0,0 +1,43 @@
+//-----------------------------------------------------------------------------
+// Name: widgetframework.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <string>
+#include <cstring>
+#include <mutex>
+
+struct NativeLoadingText {
+ std::mutex buf_mutex;
+ static const int kMaxLines = 20;
+ static const int kMaxCharPerLine = 256;
+ char buf[kMaxLines * kMaxCharPerLine];
+ NativeLoadingText() {
+ Clear();
+ }
+
+ void AddLine(const char* msg);
+ void Clear();
+};
+
+void AddLoadingText(const std::string &text);
diff --git a/Source/Game/EntityDescription.cpp b/Source/Game/EntityDescription.cpp
new file mode 100644
index 00000000..ac038e32
--- /dev/null
+++ b/Source/Game/EntityDescription.cpp
@@ -0,0 +1,1374 @@
+//-----------------------------------------------------------------------------
+// Name: EntityDescription.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "EntityDescription.h"
+
+#include <Asset/Asset/objectfile.h>
+#include <Asset/Asset/decalfile.h>
+#include <Asset/Asset/hotspotfile.h>
+#include <Asset/Asset/actorfile.h>
+#include <Asset/Asset/ambientsounds.h>
+#include <Asset/Asset/item.h>
+
+#include <Objects/envobjectattach.h>
+#include <Objects/object.h>
+
+#include <Internal/memwrite.h>
+#include <Internal/error.h>
+
+#include <Editors/editor_types.h>
+#include <Editors/mem_read_entity_description.h>
+
+#include <Main/engine.h>
+#include <Compat/filepath.h>
+#include <Game/color_tint_component.h>
+#include <Math/mat4.h>
+#include <Graphics/lightprobecollection.hpp>
+#include <Logging/logdata.h>
+
+#include <tinyxml.h>
+
+#include <sstream>
+#include <cmath>
+
+void EntityDescription::AddPath(EntityDescriptionFieldType type, const Path& val) {
+ AddType(type);
+ fields.back().WritePath(val);
+}
+
+void EntityDescription::AddVec3( EntityDescriptionFieldType type, const vec3& val ){
+ AddType(type);
+ memwrite(val.entries, sizeof(float), 3, fields.back().data);
+}
+
+void EntityDescription::AddInt( EntityDescriptionFieldType type, const int& val ){
+ AddType(type);
+ fields.back().WriteInt(val);
+}
+
+void EntityDescription::AddMat4( EntityDescriptionFieldType type, const mat4& val ){
+ AddType(type);
+ memwrite(val.entries, sizeof(float), 16, fields.back().data);
+}
+
+void EntityDescription::AddQuaternion( EntityDescriptionFieldType type, const quaternion& val ){
+ AddType(type);
+ memwrite(val.entries, sizeof(float), 4, fields.back().data);
+}
+
+void EntityDescription::AddEntityType( EntityDescriptionFieldType type, const EntityType& val ){
+ AddType(type);
+ int the_type = val;
+ memwrite(&the_type, sizeof(int), 1, fields.back().data);
+}
+
+void EntityDescription::AddIntVec( EntityDescriptionFieldType type, const std::vector<int>& val ){
+ AddType(type);
+ fields.back().WriteIntVec(val);
+}
+
+void EntityDescription::AddString( EntityDescriptionFieldType type, const std::string& val ){
+ AddType(type);
+ fields.back().WriteString(val);
+}
+
+const EntityDescriptionField* EntityDescription::GetField( EntityDescriptionFieldType type ) const {
+ for(unsigned i=0; i<fields.size(); ++i){
+ if(fields[i].type == type){
+ return &fields[i];
+ }
+ }
+ return NULL;
+}
+
+EntityDescriptionField* EntityDescription::GetField( EntityDescriptionFieldType type ) {
+ for(unsigned i=0; i<fields.size(); ++i){
+ if(fields[i].type == type){
+ return &fields[i];
+ }
+ }
+ return NULL;
+}
+
+EntityDescriptionField* EntityDescription::GetEditableField( EntityDescriptionFieldType type ){
+ for(unsigned i=0; i<fields.size(); ++i){
+ if(fields[i].type == type){
+ return &fields[i];
+ }
+ }
+ return NULL;
+}
+
+void EntityDescription::AddScriptParams( EntityDescriptionFieldType type, const ScriptParamMap& val ){
+ AddType(type);
+ WriteScriptParamsToRAM(val, fields.back().data);
+}
+
+void EntityDescription::AddPalette( EntityDescriptionFieldType type, const OGPalette& val ){
+ AddType(type);
+ WritePaletteToRAM(val, fields.back().data);
+}
+
+void EntityDescription::ReturnPaths( PathSet& path_set ){
+ std::string str;
+ EntityType type = _no_type;
+ for(unsigned i=0; i<fields.size(); ++i){
+ EntityDescriptionField &field = fields[i];
+ switch(field.type){
+ case EDF_FILE_PATH:
+ field.ReadString(&str);
+ if(!str.empty()){
+ if(path_set.find("object "+str) != path_set.end()){
+ return;
+ }
+ path_set.insert("object "+str);
+ }
+ break;
+ case EDF_ENTITY_TYPE:
+ field.ReadInt((int*)&type);
+ break;
+ }
+ }
+
+ switch(type){
+ case _env_object:
+ //ObjectFiles::Instance()->ReturnRef(str)->ReturnPaths(path_set);
+ Engine::Instance()->GetAssetManager()->LoadSync<ObjectFile>(str)->ReturnPaths(path_set);
+ break;
+ case _decal_object:
+ //DecalFiles::Instance()->ReturnRef(str)->ReturnPaths(path_set);
+ Engine::Instance()->GetAssetManager()->LoadSync<DecalFile>(str)->ReturnPaths(path_set);
+ break;
+ case _hotspot_object:
+ //HotspotFiles::Instance()->ReturnRef(str)->ReturnPaths(path_set);
+ Engine::Instance()->GetAssetManager()->LoadSync<HotspotFile>(str)->ReturnPaths(path_set);
+ break;
+ case _ambient_sound_object:
+ //AmbientSounds::Instance()->ReturnRef(str)->ReturnPaths(path_set);
+ Engine::Instance()->GetAssetManager()->LoadSync<AmbientSound>(str)->ReturnPaths(path_set);
+ break;
+ case _movement_object:
+ //ActorFiles::Instance()->ReturnRef(str)->ReturnPaths(path_set);
+ Engine::Instance()->GetAssetManager()->LoadSync<ActorFile>(str)->ReturnPaths(path_set);
+ break;
+ case _item_object:
+ //Items::Instance()->ReturnRef(str)->ReturnPaths(path_set);
+ Engine::Instance()->GetAssetManager()->LoadSync<Item>(str)->ReturnPaths(path_set);
+ break;
+ case _group:{
+ for(EntityDescriptionList::iterator iter = children.begin(); iter != children.end(); ++iter){
+ EntityDescription& child = (*iter);
+ child.ReturnPaths(path_set);
+ }
+ break;}
+ case _prefab:{
+ for(EntityDescriptionList::iterator iter = children.begin(); iter != children.end(); ++iter){
+ EntityDescription& child = (*iter);
+ child.ReturnPaths(path_set);
+ }
+ break;}
+ case _camera_type:
+ case _path_point_object:
+ case _placeholder_object:
+ case _light_probe_object:
+ case _reflection_capture_object:
+ case _light_volume_object:
+ case _dynamic_light_object:
+ case _navmesh_hint_object:
+ case _navmesh_region_object:
+ case _navmesh_connection_object:
+ break;
+ default:
+ DisplayError("Error", "Unhandled type passed to EntityDescription::ReturnPaths");
+ break;
+ }
+}
+
+void EntityDescription::AddItemConnectionVec( EntityDescriptionFieldType type, const std::vector<ItemConnectionData>& val ) {
+ AddType(type);
+ fields.back().WriteItemConnectionDataVec(val);
+}
+
+void EntityDescription::AddNavMeshConnnectionVec(EntityDescriptionFieldType type, const std::vector<NavMeshConnectionData>& val) {
+ AddType(type);
+ fields.back().WriteNavMeshConnectionDataVec(val);
+}
+
+void EntityDescription::AddType( EntityDescriptionFieldType type ) {
+ fields.resize(fields.size()+1);
+ fields.back().type = type;
+}
+
+void EntityDescription::SaveToXML( TiXmlElement* parent ) const {
+ TiXmlElement* object = new TiXmlElement("Unknown Type");
+ parent->LinkEndChild(object);
+ for(unsigned i=0; i<fields.size(); ++i){
+ const EntityDescriptionField &field = fields[i];
+ switch(field.type){
+ case EDF_ID: {
+ int id;
+ field.ReadInt(&id);
+ object->SetAttribute("id", id);
+ break;}
+ case EDF_VERSION: {
+ int version;
+ field.ReadInt(&version);
+ object->SetAttribute("version", version);
+ break;}
+ case EDF_ENTITY_TYPE: {
+ int type;
+ field.ReadInt(&type);
+ std::string type_str;
+ switch(type){
+ case _hotspot_object:
+ type_str = "Hotspot";
+ break;
+ case _env_object:
+ type_str = "EnvObject";
+ break;
+ case _decal_object:
+ type_str = "Decal";
+ break;
+ case _movement_object:
+ type_str = "ActorObject";
+ break;
+ case _item_object:
+ type_str = "ItemObject";
+ break;
+ case _group:
+ type_str = "Group";
+ break;
+ case _ambient_sound_object:
+ type_str = "AmbientSoundObject";
+ break;
+ case _path_point_object:
+ type_str = "PathPointObject";
+ break;
+ case _placeholder_object:
+ type_str = "PlaceholderObject";
+ break;
+ case _light_probe_object:
+ type_str = "LightProbeObject";
+ break;
+ case _reflection_capture_object:
+ type_str = "ReflectionCaptureObject";
+ break;
+ case _dynamic_light_object:
+ type_str = "DynamicLightObject";
+ break;
+ case _navmesh_hint_object:
+ type_str = "NavmeshHintObject";
+ break;
+ case _navmesh_connection_object:
+ type_str = "NavmeshConnectionObject";
+ break;
+ case _navmesh_region_object:
+ type_str = "NavmeshRegionObject";
+ break;
+ case _camera_type:
+ type_str = "CameraObject";
+ break;
+ case _light_volume_object:
+ type_str = "LightVolumeObject";
+ break;
+ case _prefab:
+ type_str = "Prefab";
+ break;
+ default:
+ FatalError("Error", "Unknown EntityType in EntityDescription");
+ break;
+ }
+ object->SetValue(type_str.c_str());
+ break;}
+ case EDF_NAME: {
+ std::string name;
+ field.ReadString(&name);
+ if(name.empty() == false) {
+ object->SetAttribute("name",name.c_str());
+ }
+ break;}
+ case EDF_FILE_PATH: {
+ std::string type_file;
+ field.ReadString(&type_file);
+ object->SetAttribute("type_file", type_file.c_str());
+ break;}
+ case EDF_TRANSLATION: {
+ vec3 translation;
+ memread(translation.entries, sizeof(float), 3, field.data);
+ object->SetDoubleAttribute("t0", translation[0]);
+ object->SetDoubleAttribute("t1", translation[1]);
+ object->SetDoubleAttribute("t2", translation[2]);
+ break;}
+ case EDF_SCALE: {
+ vec3 scale;
+ memread(scale.entries, sizeof(float), 3, field.data);
+ object->SetDoubleAttribute("s0", scale[0]);
+ object->SetDoubleAttribute("s1", scale[1]);
+ object->SetDoubleAttribute("s2", scale[2]);
+ break;}
+ case EDF_ROTATION: {
+ quaternion q;
+ memread(q.entries, sizeof(float), 4, field.data);
+ object->SetDoubleAttribute("q0", q.entries[0]);
+ object->SetDoubleAttribute("q1", q.entries[1]);
+ object->SetDoubleAttribute("q2", q.entries[2]);
+ object->SetDoubleAttribute("q3", q.entries[3]);
+ break;}
+ case EDF_ROTATION_EULER: {
+ vec3 rot;
+ memread(rot.entries, sizeof(float), 3, field.data);
+ object->SetDoubleAttribute("euler_x", rot.entries[0]);
+ object->SetDoubleAttribute("euler_y", rot.entries[1]);
+ object->SetDoubleAttribute("euler_z", rot.entries[2]);
+ break;}
+ case EDF_SCRIPT_PARAMS: {
+ ScriptParamMap spm;
+ ReadScriptParametersFromRAM(spm, field.data);
+ TiXmlElement* params = new TiXmlElement("parameters");
+ object->LinkEndChild(params);
+ WriteScriptParamsToXML(spm, params);
+ break;}
+ case EDF_COLOR: {
+ vec3 color;
+ memread(color.entries, sizeof(float), 3, field.data);
+ object->SetDoubleAttribute("color_r", color[0]);
+ object->SetDoubleAttribute("color_g", color[1]);
+ object->SetDoubleAttribute("color_b", color[2]);
+ break;}
+ case EDF_OVERBRIGHT: {
+ float overbright;
+ memread(&overbright, sizeof(float), 1, field.data);
+ object->SetDoubleAttribute("overbright", overbright);
+ break;}
+ case EDF_PALETTE: {
+ OGPalette palette;
+ ReadPaletteFromRAM(palette, field.data);
+ TiXmlElement* palette_el = new TiXmlElement("Palette");
+ object->LinkEndChild(palette_el);
+ WritePaletteToXML(palette, palette_el);
+ break;}
+ case EDF_CONNECTIONS: {
+ // Read connections from entity description
+ std::vector<int> connections;
+ field.ReadIntVec(&connections);
+ // Write connections to XML
+ TiXmlElement* connections_el = new TiXmlElement("Connections");
+ object->LinkEndChild(connections_el);
+ for(size_t i=0, len=connections.size(); i<len; ++i){
+ TiXmlElement* connection = new TiXmlElement("Connection");
+ connection->SetAttribute("id", connections[i]);
+ connections_el->LinkEndChild(connection);
+ }
+ break;}
+ case EDF_CONNECTIONS_FROM: {
+ // Read connections from entity description
+ std::vector<int> connections;
+ field.ReadIntVec(&connections);
+ // Write connections to XML
+ TiXmlElement* connections_el = new TiXmlElement("ConnectionsFrom");
+ object->LinkEndChild(connections_el);
+ for(size_t i=0, len=connections.size(); i<len; ++i){
+ TiXmlElement* connection = new TiXmlElement("Connection");
+ connection->SetAttribute("id", connections[i]);
+ connections_el->LinkEndChild(connection);
+ }
+ break;}
+ case EDF_ITEM_CONNECTIONS: {
+ std::vector<ItemConnectionData> item_connections;
+ field.ReadItemConnectionDataVec(&item_connections);
+ // Write item connections to XML
+ TiXmlElement* item_connections_el = new TiXmlElement("ItemConnections");
+ object->LinkEndChild(item_connections_el);
+ for(size_t i=0, len=item_connections.size(); i<len; ++i) {
+ ItemConnectionData& item_connection = item_connections[i];
+ TiXmlElement* item_connection_el = new TiXmlElement("ItemConnection");
+ item_connection_el->SetAttribute("id", item_connection.id);
+ item_connection_el->SetAttribute("type", item_connection.attachment_type);
+ item_connection_el->SetAttribute("mirrored", item_connection.mirrored);
+ item_connection_el->SetAttribute("attachment", item_connection.attachment_str.c_str());
+ item_connections_el->LinkEndChild(item_connection_el);
+ }
+ break;}
+ case EDF_IS_PLAYER: {
+ int is_player;
+ memread(&is_player, sizeof(int), 1, field.data);
+ object->SetAttribute("is_player", is_player != 0);
+ break;}
+ case EDF_USING_IMPOSTER: {
+ int using_imposter;
+ memread(&using_imposter, sizeof(int), 1, field.data);
+ object->SetAttribute("uses_imposter", using_imposter != 0);
+ break;}
+ case EDF_DIMENSIONS: {
+ vec3 dims;
+ memread(dims.entries, sizeof(float), 3, field.data);
+ object->SetDoubleAttribute("d0", dims[0]);
+ object->SetDoubleAttribute("d1", dims[1]);
+ object->SetDoubleAttribute("d2", dims[2]);
+ break;}
+ case EDF_DIRECTION:{
+ vec3 direction;
+ memread(direction.entries, sizeof(float), 3, field.data);
+ object->SetDoubleAttribute("p0", direction[0]);
+ object->SetDoubleAttribute("p1", direction[1]);
+ object->SetDoubleAttribute("p2", direction[2]);
+ break;}
+ case EDF_DECAL_ISOLATION_ID:{
+ int isolation_id;
+ field.ReadInt(&isolation_id);
+ object->SetAttribute("i", isolation_id!=-1);
+ if(isolation_id != -1) {
+ object->SetAttribute("io", isolation_id);
+ }
+ break;}
+ case EDF_DISPLAY_MODE:{
+ int display_mode;
+ memread(&display_mode, sizeof(int), 1, field.data);
+ object->SetAttribute("p_mode", display_mode);
+ break;}
+ case EDF_NEGATIVE_LIGHT_PROBE:{
+ int val;
+ memread(&val, sizeof(int), 1, field.data);
+ object->SetAttribute("negative_light_probe", val);
+ break;}
+ case EDF_GI_COEFFICIENTS:{
+ // There are a fixed amount of coefficients
+ // Use attributes instead of child elements
+
+ float data[kLightProbeNumCoeffs];
+ memcpy(data, &field.data[0], sizeof(data));
+ char elemName[] = "gicoeff00";
+ for (unsigned int i = 0; i < kLightProbeNumCoeffs; i++) {
+ elemName[7] = '0' + (i / 10);
+ elemName[8] = '0' + (i % 10);
+ object->SetDoubleAttribute(elemName, data[i]);
+ }
+ break;}
+ case EDF_SPECIAL_TYPE:{
+ int special_type;
+ memread(&special_type, sizeof(int), 1, field.data);
+ object->SetAttribute("special_type", special_type);
+ break;}
+ case EDF_DECAL_MISS_LIST:{
+ // Read miss list from entity description
+ int miss_list_size;
+ int index=0;
+ memread(&miss_list_size, sizeof(int), 1, field.data, index);
+ std::vector<int> miss_list;
+ miss_list.resize(miss_list_size);
+ if(miss_list_size){
+ memread(&miss_list[0], sizeof(int), miss_list_size, field.data, index);
+ }
+ // Write miss list to XML
+ for(int i=0; i<miss_list_size; ++i){
+ std::ostringstream oss;
+ oss << "o" << i;
+ object->SetDoubleAttribute(oss.str().c_str(), miss_list[i]);
+ }
+ break;}
+ case EDF_ENV_OBJECT_ATTACH:{
+ std::vector<AttachedEnvObject> aeo_vec;
+ Deserialize(field.data, aeo_vec);
+ TiXmlElement* env_object_attachments = new TiXmlElement("EnvObjectAttachments");
+ object->LinkEndChild(env_object_attachments);
+ for(unsigned i=0; i<aeo_vec.size(); ++i){
+ TiXmlElement* connection = new TiXmlElement("EnvObjectAttachment");
+ env_object_attachments->LinkEndChild(connection);
+ const AttachedEnvObject& aeo = aeo_vec[i];
+ for(unsigned j=0; j<kMaxBoneConnects; ++j){
+ TiXmlElement* bone_connect_el = new TiXmlElement("BoneConnect");
+ connection->LinkEndChild(bone_connect_el);
+ const BoneConnect &bone_connect = aeo.bone_connects[j];
+ bone_connect_el->SetAttribute("bone_id", bone_connect.bone_id);
+ bone_connect_el->SetAttribute("num_connections", bone_connect.num_connections);
+ int index;
+ char name[4];
+ for (int c = 0; c < 4; c++) {
+ for (int r = 0; r < 4; r++) {
+ name[0] = 'm';
+ index = r+c*4;
+ if(index<10){
+ name[1] = '0'+index;
+ name[2] = '\0';
+ } else {
+ name[1] = '1';
+ name[2] = '0'+index%10;
+ name[3] = '\0';
+ }
+ bone_connect_el->SetDoubleAttribute(name, (double)bone_connect.rel_mat(r,c));
+ }
+ }
+ }
+ }
+
+ break;}
+ case EDF_NAV_MESH_CONNECTIONS: {
+ std::vector<NavMeshConnectionData> navmesh_connections;
+ field.ReadNavMeshConnectionDataVec(&navmesh_connections);
+
+ TiXmlElement* navmesh_connections_el = new TiXmlElement("NavMeshConnections");
+ object->LinkEndChild(navmesh_connections_el);
+ for(size_t i=0, len=navmesh_connections.size(); i<len; ++i) {
+ NavMeshConnectionData& navmesh_connection = navmesh_connections[i];
+ TiXmlElement* navmesh_connection_el = new TiXmlElement("NavMeshConnection");
+ navmesh_connection_el->SetAttribute("other_object_id", navmesh_connection.other_object_id);
+ navmesh_connection_el->SetAttribute("offmesh_connection_id", navmesh_connection.offmesh_connection_id);
+ navmesh_connection_el->SetAttribute("poly_area", navmesh_connection.poly_area);
+ navmesh_connections_el->LinkEndChild(navmesh_connection_el);
+ }
+ break;}
+ case EDF_NO_NAVMESH: {
+ bool val;
+ field.ReadBool(&val);
+ if(val) {
+ object->SetAttribute("no_navmesh","true");
+ }
+ break;}
+ case EDF_PREFAB_LOCKED: {
+ bool val;
+ field.ReadBool(&val);
+ object->SetAttribute("prefab_locked", val ? "true" : "false");
+ break;}
+ case EDF_PREFAB_PATH: {
+ std::string val;
+ field.ReadString(&val);
+ object->SetAttribute("prefab_path", val.c_str());
+ break;}
+ case EDF_ORIGINAL_SCALE: {
+ vec3 scale;
+ memread(scale.entries, sizeof(float), 3, field.data);
+ object->SetDoubleAttribute("os0", scale[0]);
+ object->SetDoubleAttribute("os1", scale[1]);
+ object->SetDoubleAttribute("os2", scale[2]);
+ break;}
+ case EDF_NPC_SCRIPT_PATH: {
+ std::string path;
+ field.ReadString(&path);
+ object->SetAttribute("npc_script_path", path.c_str());
+ break;}
+ case EDF_PC_SCRIPT_PATH: {
+ std::string path;
+ field.ReadString(&path);
+ object->SetAttribute("pc_script_path", path.c_str());
+ break;}
+ }
+ }
+
+ for(EntityDescriptionList::const_iterator iter = children.begin();
+ iter != children.end(); ++iter)
+ {
+ const EntityDescription& child = (*iter);
+ child.SaveToXML(object);
+ }
+}
+
+void EntityDescription::AddFloat( EntityDescriptionFieldType type, const float& val ) {
+ AddType(type);
+ memwrite(&val, sizeof(float), 1, fields.back().data);
+}
+
+void EntityDescription::AddData(EntityDescriptionFieldType type, const std::vector<char>& val) {
+ AddType(type);
+ memwrite(&val[0], val.size(), 1, fields.back().data);
+}
+
+void EntityDescription::AddBool(EntityDescriptionFieldType type, const bool& val) {
+ AddType(type);
+ char in = val ? 1 : 0;
+ memwrite(&in, sizeof(char), 1, fields.back().data);
+}
+
+void GetTSREinfo(const TiXmlElement* pElement, vec3 &translation, vec3 &scale, quaternion &rotation, vec3 &rotation_euler) {
+ GetTSRinfo(pElement, translation, scale, rotation);
+
+ rotation_euler = vec3();
+ double dval;
+ if(pElement->QueryDoubleAttribute("euler_x",&dval)==TIXML_SUCCESS) rotation_euler[0] = (float)dval;
+ if(pElement->QueryDoubleAttribute("euler_y",&dval)==TIXML_SUCCESS) rotation_euler[1] = (float)dval;
+ if(pElement->QueryDoubleAttribute("euler_z",&dval)==TIXML_SUCCESS) rotation_euler[2] = (float)dval;
+}
+
+void GetTSRinfo(const TiXmlElement* pElement, vec3 &translation, vec3 &scale, quaternion &rotation) {
+ double dval;
+
+ char name[4];
+ for (int i = 0; i < 3; i++) {
+ name[0] = 't';
+ name[1] = '0'+i;
+ name[2] = '\0';
+ if (pElement->QueryDoubleAttribute(name,&dval)==TIXML_SUCCESS) {
+ translation[i] = (float)dval;
+ } else {
+ translation[i] = 0;
+ }
+ }
+
+ for (int i = 0; i < 3; i++) {
+ name[0] = 's';
+ name[1] = '0'+i;
+ name[2] = '\0';
+ if (pElement->QueryDoubleAttribute(name,&dval)==TIXML_SUCCESS) scale[i] = (float)dval;
+ else scale[i] = 0;
+ }
+
+ int index;
+ int quat_elements = 0;
+
+ for (int i = 0; i < 4; i++) {
+ name[0] = 'q';
+ name[1] = '0' + i;
+ name[2] = '\0';
+ if (pElement->QueryDoubleAttribute(name,&dval)==TIXML_SUCCESS) {
+ rotation.entries[i] = (float)dval;
+ ++quat_elements;
+ }
+ }
+
+ if (quat_elements == 3) {
+ // reconstruct the last element
+ float temp = 1.0f - (rotation.entries[0] * rotation.entries[0] + rotation.entries[1] * rotation.entries[1] + rotation.entries[2] * rotation.entries[2]);
+ if (temp < 0.0f) {
+ rotation.entries[3] = 0.0f;
+ LOGE << "Invalid quaternion in level xml file" << std::endl;
+ } else {
+ rotation.entries[3] = sqrtf(temp);
+ }
+ rotation = QNormalize(rotation);
+ } else if(quat_elements < 3) {
+ // no quaternion, old format
+ // reconstruct from matrix
+ mat4 temp;
+ for (int c = 0; c < 4; c++) {
+ for (int r = 0; r < 4; r++) {
+ name[0] = 'r';
+ index = r+c*4;
+ if(index<10){
+ name[1] = '0'+index;
+ name[2] = '\0';
+ } else {
+ name[1] = '1';
+ name[2] = '0'+index%10;
+ name[3] = '\0';
+ }
+ if (pElement->QueryDoubleAttribute(name,&dval)==TIXML_SUCCESS) temp(r,c) = (float)dval;
+ else temp(r,c) = 0;
+ }
+ }
+ rotation = QuaternionFromMat4(temp);
+ }
+}
+
+void GetTSRIinfo( EntityDescription &desc, const TiXmlElement* pElement ){
+ int ival;
+ if (pElement->QueryIntAttribute("id",&ival)!=TIXML_SUCCESS) {
+ ival = -1;
+ }
+
+ desc.AddInt(EDF_ID, ival);
+
+ vec3 translation;
+ vec3 scale;
+ quaternion rotation;
+ vec3 rotation_euler;
+
+ GetTSREinfo(pElement, translation, scale, rotation, rotation_euler);
+
+ desc.AddVec3(EDF_TRANSLATION, translation);
+ desc.AddVec3(EDF_SCALE, scale);
+ desc.AddQuaternion(EDF_ROTATION, rotation);
+ desc.AddVec3(EDF_ROTATION_EULER, rotation_euler);
+
+ ScriptParamMap spm;
+ const TiXmlElement* params = pElement->FirstChildElement("parameters");
+ if(params){
+ ReadScriptParametersFromXML(spm, params);
+ }
+ desc.AddScriptParams(EDF_SCRIPT_PARAMS, spm);
+}
+
+void LoadEnvDescriptionFromXML( EntityDescription& desc, const TiXmlElement* el){
+ desc.fields.reserve(6);
+ desc.AddEntityType(EDF_ENTITY_TYPE, _env_object);
+ const char* name = el->Attribute("name");
+ if( name ) {
+ desc.AddString(EDF_NAME, name);
+ }
+
+ std::string type_file = SanitizePath(el->Attribute("type_file"));
+ desc.AddString(EDF_FILE_PATH, type_file);
+
+ const char* no_navmesh = el->Attribute("no_navmesh");
+ if(no_navmesh) {
+ desc.AddBool(EDF_NO_NAVMESH, saysTrue(no_navmesh) == 1);
+ } else {
+ desc.AddBool(EDF_NO_NAVMESH, false);
+ }
+
+ ColorTintComponent::LoadDescriptionFromXML(desc, el);
+
+ GetTSRIinfo(desc, el);
+}
+
+void LoadDecalDescriptionFromXML( EntityDescription& desc, const TiXmlElement* el){
+ desc.AddEntityType(EDF_ENTITY_TYPE, _decal_object);
+ const char* ename = el->Attribute("name");
+ if( ename ) {
+ desc.AddString(EDF_NAME, ename);
+ }
+
+ std::string type_file = SanitizePath(el->Attribute("type_file"));
+ desc.AddString(EDF_FILE_PATH, type_file);
+
+ vec3 projected_dir;
+ double dval;
+ char name[4];
+ for (int i = 0; i < 3; i++) {
+ name[0] = 'p';
+ name[1] = '0'+i;
+ name[2] = '\0';
+ if (el->QueryDoubleAttribute(name,&dval)==TIXML_SUCCESS) projected_dir[i] = (float)dval;
+ else projected_dir[i] = 0;
+ }
+ desc.AddVec3(EDF_DIRECTION, projected_dir);
+
+ ColorTintComponent::LoadDescriptionFromXML(desc, el);
+
+ int display_mode = 0;
+ int pmode;
+ if (el->QueryIntAttribute("p_mode",&pmode)==TIXML_SUCCESS) {
+ display_mode = pmode;
+ }
+ desc.AddInt(EDF_DISPLAY_MODE, display_mode);
+
+ GetTSRIinfo(desc, el);
+
+ // load object miss list
+ std::vector<int> miss_list;
+ int miss_list_val;
+ std::string curr_num;
+ while(1) {
+ std::stringstream num;
+ curr_num = num.str();
+ if (el->QueryIntAttribute(("o" + curr_num).c_str(), &miss_list_val) == TIXML_SUCCESS) {
+ miss_list.push_back(miss_list_val);
+ }
+ else break;
+ }
+
+ desc.AddIntVec(EDF_DECAL_MISS_LIST, miss_list);
+
+ int isolation_id = -1;
+ int isolation_state = 0;
+ if (el->QueryIntAttribute("i", &isolation_state) == TIXML_SUCCESS) {
+ if (isolation_state) {
+ int read_isolation_id;
+ if (el->QueryIntAttribute("io", &read_isolation_id) == TIXML_SUCCESS) {
+ isolation_id = read_isolation_id;
+ }
+ }
+ }
+ desc.AddInt(EDF_DECAL_ISOLATION_ID, isolation_id);
+}
+
+
+void LoadGroupDescriptionFromXML( EntityDescription& desc, const TiXmlElement* el ) {
+ desc.AddEntityType(EDF_ENTITY_TYPE, _group);
+ const char* ename = el->Attribute("name");
+ if( ename ) {
+ desc.AddString(EDF_NAME, ename);
+ }
+ GetTSRIinfo(desc, el);
+
+ int ival;
+ int using_imposter = 0;
+ if (el->QueryIntAttribute("uses_imposter",&ival)==TIXML_SUCCESS) {
+ using_imposter = ival!=0;
+ }
+ desc.AddInt(EDF_USING_IMPOSTER, using_imposter);
+
+ if (el->QueryIntAttribute("version",&ival)==TIXML_SUCCESS) {
+ desc.AddInt(EDF_VERSION, ival);
+ }
+
+ vec3 dims;
+ char name[3];
+ double dval;
+ for (int i = 0; i < 3; i++) {
+ name[0] = 'd';
+ name[1] = '0'+i;
+ name[2] = '\0';
+ if (el->QueryDoubleAttribute(name,&dval)==TIXML_SUCCESS){
+ dims[i] = (float)dval;
+ } else {
+ dims[i] = 1;
+ }
+ }
+ desc.AddVec3(EDF_DIMENSIONS, dims);
+
+ vec3 color;
+ for (int i = 0; i < 3; i++) {
+ name[0] = 'c';
+ name[1] = '0'+i;
+ name[2] = '\0';
+ if (el->QueryDoubleAttribute(name,&dval)==TIXML_SUCCESS){
+ color[i] = (float)dval;
+ } else {
+ color[i] = 0;
+ }
+ }
+ desc.AddVec3(EDF_COLOR, color);
+
+ LoadEntityDescriptionListFromXML(desc.children, el);
+}
+
+void LoadPrefabDescriptionFromXML( EntityDescription& desc, const TiXmlElement* el ) {
+ desc.AddEntityType(EDF_ENTITY_TYPE, _prefab);
+ desc.AddBool(EDF_PREFAB_LOCKED,SAYS_TRUE == saysTrue(el->Attribute("prefab_locked")));
+ desc.AddString(EDF_PREFAB_PATH, nullAsEmpty(el->Attribute("prefab_path")));
+
+ const char* ename = el->Attribute("name");
+ if( ename ) {
+ desc.AddString(EDF_NAME, ename);
+ }
+ GetTSRIinfo(desc, el);
+
+ double os0,os1,os2;
+ bool success = (el->QueryDoubleAttribute("os0", &os0) == TIXML_SUCCESS)
+ && (el->QueryDoubleAttribute("os1", &os1) == TIXML_SUCCESS)
+ && (el->QueryDoubleAttribute("os2", &os2) == TIXML_SUCCESS);
+ if( success ) {
+ vec3 original_scale((float)os0, (float)os1, (float)os2);
+ desc.AddVec3(EDF_ORIGINAL_SCALE,original_scale);
+ }
+
+ int ival;
+ int using_imposter = 0;
+ if (el->QueryIntAttribute("uses_imposter",&ival)==TIXML_SUCCESS) {
+ using_imposter = ival!=0;
+ }
+ desc.AddInt(EDF_USING_IMPOSTER, using_imposter);
+
+ if (el->QueryIntAttribute("version",&ival)==TIXML_SUCCESS) {
+ desc.AddInt(EDF_VERSION, ival);
+ }
+
+ vec3 dims;
+ char name[3];
+ double dval;
+ for (int i = 0; i < 3; i++) {
+ name[0] = 'd';
+ name[1] = '0'+i;
+ name[2] = '\0';
+ if (el->QueryDoubleAttribute(name,&dval)==TIXML_SUCCESS){
+ dims[i] = (float)dval;
+ } else {
+ dims[i] = 1;
+ }
+ }
+ desc.AddVec3(EDF_DIMENSIONS, dims);
+
+ vec3 color;
+ for (int i = 0; i < 3; i++) {
+ name[0] = 'c';
+ name[1] = '0'+i;
+ name[2] = '\0';
+ if (el->QueryDoubleAttribute(name,&dval)==TIXML_SUCCESS){
+ color[i] = (float)dval;
+ } else {
+ color[i] = 0;
+ }
+ }
+ desc.AddVec3(EDF_COLOR, color);
+
+ LoadEntityDescriptionListFromXML(desc.children, el);
+}
+
+void LoadActorFromXML( EntityDescription& desc, const TiXmlElement* el, EntityType type) {
+ const char* type_file_raw = el->Attribute("type_file");
+ std::string type_file;
+ if(type_file_raw && strlen(type_file_raw) > 0){
+ type_file = SanitizePath(type_file_raw);
+ }
+
+ desc.AddEntityType(EDF_ENTITY_TYPE, type);
+ const char* name = el->Attribute("name");
+ if( name ) {
+ desc.AddString(EDF_NAME, name);
+ }
+ desc.AddString(EDF_FILE_PATH, type_file);
+
+ if(type == _movement_object){
+ std::string script_path;
+ if(el->QueryStringAttribute("npc_script_path", &script_path) == TIXML_SUCCESS) {
+ desc.AddString(EDF_NPC_SCRIPT_PATH, script_path);
+ }
+ if(el->QueryStringAttribute("pc_script_path", &script_path) == TIXML_SUCCESS) {
+ desc.AddString(EDF_PC_SCRIPT_PATH, script_path);
+ }
+
+ int the_id = -1;
+ const TiXmlElement* connections = el->FirstChildElement("Connections");
+ if(connections){
+ const TiXmlElement* connection = connections->FirstChildElement("Connection");
+ int id;
+ while(connection){
+ if(connection->QueryIntAttribute("id", &id)==TIXML_SUCCESS){
+ the_id = id;
+ }
+ connection = connection->NextSiblingElement();
+ }
+ }
+ std::vector<int> connection_vec;
+ connection_vec.push_back(the_id);
+ desc.AddIntVec(EDF_CONNECTIONS, connection_vec);
+ int is_player = 0;
+ el->QueryIntAttribute("is_player", &is_player);
+ desc.AddInt(EDF_IS_PLAYER, is_player);
+
+ std::vector<AttachedEnvObject> aeo_vec;
+ const TiXmlElement* env_object_attachments = el->FirstChildElement("EnvObjectAttachments");
+ if(env_object_attachments){
+ const TiXmlElement* connection = env_object_attachments->FirstChildElement("EnvObjectAttachment");
+ while(connection){
+ AttachedEnvObject aeo;
+ if(connection->QueryIntAttribute("obj_id", &aeo.legacy_obj_id) == TIXML_NO_ATTRIBUTE){
+ aeo.legacy_obj_id = -1;
+ }
+ const TiXmlElement* bone_connect_el = connection->FirstChildElement("BoneConnect");
+ unsigned num = 0;
+ while(bone_connect_el){
+ BoneConnect bone_connect;
+ bone_connect_el->QueryIntAttribute("bone_id", &bone_connect.bone_id);
+ bone_connect_el->QueryIntAttribute("num_connections", &bone_connect.num_connections);
+ int index;
+ double dval;
+ char name[4];
+ for (int c = 0; c < 4; c++) {
+ for (int r = 0; r < 4; r++) {
+ name[0] = 'm';
+ index = r+c*4;
+ if(index<10){
+ name[1] = '0'+index;
+ name[2] = '\0';
+ } else {
+ name[1] = '1';
+ name[2] = '0'+index%10;
+ name[3] = '\0';
+ }
+ if (bone_connect_el->QueryDoubleAttribute(name,&dval)==TIXML_SUCCESS) bone_connect.rel_mat(r,c) = (float)dval;
+ else bone_connect.rel_mat(r,c) = 0;
+ }
+ }
+ if(num<kMaxBoneConnects){
+ aeo.bone_connects[num] = bone_connect;
+ }
+ ++num;
+ bone_connect_el = bone_connect_el->NextSiblingElement();
+ }
+ aeo_vec.push_back(aeo);
+ connection = connection->NextSiblingElement();
+ }
+ }
+ if(!aeo_vec.empty()){
+ std::vector<char> aeo_data;
+ Serialize(aeo_vec, aeo_data);
+ desc.AddData(EDF_ENV_OBJECT_ATTACH, aeo_data);
+ }
+
+ const TiXmlElement* item_connections = el->FirstChildElement("ItemConnections");
+ if(item_connections){
+ std::vector<ItemConnectionData> item_connection_vec;
+ const TiXmlElement* item_connection = item_connections->FirstChildElement("ItemConnection");
+ while(item_connection){
+ ItemConnectionData new_item_connection;
+ item_connection->QueryIntAttribute("id", &new_item_connection.id);
+ item_connection->QueryIntAttribute("type", &new_item_connection.attachment_type);
+ item_connection->QueryIntAttribute("mirrored", &new_item_connection.mirrored);
+ new_item_connection.attachment_str = item_connection->Attribute("attachment");
+ item_connection_vec.push_back(new_item_connection);
+ item_connection = item_connection->NextSiblingElement();
+ }
+ desc.AddItemConnectionVec(EDF_ITEM_CONNECTIONS, item_connection_vec);
+ }
+
+ OGPalette palette;
+ const TiXmlElement* palette_el = el->FirstChildElement("Palette");
+ if(palette_el){
+ ReadPaletteFromXML(palette, palette_el);
+ }
+ desc.AddPalette(EDF_PALETTE, palette);
+ LoadEntityDescriptionListFromXML(desc.children, el);
+
+ } else if( type == _navmesh_connection_object ) {
+ const TiXmlElement* navmesh_connections = el->FirstChildElement("NavMeshConnections");
+ if(navmesh_connections){
+ std::vector<NavMeshConnectionData> navmesh_connection_vec;
+ const TiXmlElement* navmesh_connection = navmesh_connections->FirstChildElement("NavMeshConnection");
+ while(navmesh_connection){
+ NavMeshConnectionData new_navmesh_connection;
+ navmesh_connection->QueryIntAttribute("other_object_id", &new_navmesh_connection.other_object_id);
+ navmesh_connection->QueryIntAttribute("offmesh_connection_id", &new_navmesh_connection.offmesh_connection_id);
+ int poly_area;
+ navmesh_connection->QueryIntAttribute("poly_area", &poly_area);
+ new_navmesh_connection.poly_area = (SamplePolyAreas)poly_area;
+ navmesh_connection_vec.push_back( new_navmesh_connection );
+ navmesh_connection = navmesh_connection->NextSiblingElement();
+ }
+ desc.AddNavMeshConnnectionVec(EDF_NAV_MESH_CONNECTIONS, navmesh_connection_vec);
+ }
+ } else if(type == _path_point_object ){
+ const TiXmlElement* connections = el->FirstChildElement("Connections");
+ if(connections){
+ const TiXmlElement* connection = connections->FirstChildElement("Connection");
+ std::vector<int> ids;
+ int id;
+ while(connection){
+ if(connection->QueryIntAttribute("id", &id)==TIXML_SUCCESS){
+ ids.push_back(id);
+ }
+ connection = connection->NextSiblingElement();
+ }
+ desc.AddIntVec(EDF_CONNECTIONS, ids);
+ }
+ } else if(type == _item_object){
+ ColorTintComponent::LoadDescriptionFromXML(desc, el);
+ } else if(type == _reflection_capture_object){
+ const size_t data_size = kLightProbeNumCoeffs * sizeof(float);
+ std::vector<char> data(data_size);
+ char elemName[] = "gicoeff00";
+ for (unsigned int i = 0; i < kLightProbeNumCoeffs; i++) {
+ elemName[7] = '0' + (i / 10);
+ elemName[8] = '0' + (i % 10);
+ float fval = 0.0f;
+ el->QueryFloatAttribute(elemName, &fval);
+ memcpy(&data[i * sizeof(float)], &fval, sizeof(float));
+ }
+ desc.AddData(EDF_GI_COEFFICIENTS, data);
+ } else if(type == _light_probe_object){
+ int val = 0;
+ el->QueryIntAttribute("negative_light_probe", &val);
+ desc.AddInt(EDF_NEGATIVE_LIGHT_PROBE, val);
+ if (!val) {
+ const size_t data_size = kLightProbeNumCoeffs * sizeof(float);
+ std::vector<char> data(data_size);
+ char elemName[] = "gicoeff00";
+ for (unsigned int i = 0; i < kLightProbeNumCoeffs; i++) {
+ elemName[7] = '0' + (i / 10);
+ elemName[8] = '0' + (i % 10);
+ float fval = 0.0f;
+ el->QueryFloatAttribute(elemName, &fval);
+ memcpy(&data[i * sizeof(float)], &fval, sizeof(float));
+ }
+
+ desc.AddData(EDF_GI_COEFFICIENTS, data);
+ }
+ } else if(type == _dynamic_light_object) {
+ vec3 color;
+ char name[] = "color_x";
+ const char rgb[] = "rgb";
+ double dval = 0.0f;
+ for (int i = 0; i < 3; i++) {
+ name[6] = rgb[i];
+ if (el->QueryDoubleAttribute(name,&dval)==TIXML_SUCCESS){
+ color[i] = (float)dval;
+ } else {
+ color[i] = 0;
+ }
+ }
+ desc.AddVec3(EDF_COLOR, color);
+ double overbright = 1.0;
+ el->QueryDoubleAttribute("overbright", &overbright);
+ desc.AddFloat(EDF_OVERBRIGHT, (float) overbright);
+ } else if(type == _placeholder_object){
+ int the_id = -1;
+ const TiXmlElement* connections = el->FirstChildElement("Connections");
+ if(connections){
+ const TiXmlElement* connection = connections->FirstChildElement("Connection");
+ int id;
+ while(connection){
+ if(connection->QueryIntAttribute("id", &id)==TIXML_SUCCESS){
+ the_id = id;
+ }
+ connection = connection->NextSiblingElement();
+ }
+ }
+ std::vector<int> connection_vec;
+ connection_vec.push_back(the_id);
+ desc.AddIntVec(EDF_CONNECTIONS, connection_vec);
+ int special_type = 0;
+ el->QueryIntAttribute("special_type",&special_type);
+ desc.AddInt(EDF_SPECIAL_TYPE, special_type);
+ }
+
+ GetTSRIinfo(desc, el);
+}
+
+void LoadHotspotFromXML( EntityDescription& desc, const TiXmlElement* el ) {
+ desc.AddEntityType(EDF_ENTITY_TYPE, _hotspot_object);
+ const char* name = el->Attribute("name");
+ if( name ) {
+ desc.AddString(EDF_NAME, name);
+ }
+ std::string type_file = SanitizePath(el->Attribute("type_file"));
+ desc.AddString(EDF_FILE_PATH, type_file);
+ GetTSRIinfo(desc, el);
+
+ std::vector<int> connection_ids;
+ const TiXmlElement* connections = el->FirstChildElement("Connections");
+ if(connections) {
+ const TiXmlElement* connection = connections->FirstChildElement("Connection");
+
+ int id;
+ while(connection) {
+ if(connection->QueryIntAttribute("id", &id) == TIXML_SUCCESS) {
+ connection_ids.push_back(id);
+ }
+ connection = connection->NextSiblingElement();
+ }
+ }
+ desc.AddIntVec(EDF_CONNECTIONS, connection_ids);
+
+ connection_ids.resize(0);
+ connections = el->FirstChildElement("ConnectionsFrom");
+ if(connections) {
+ const TiXmlElement* connection = connections->FirstChildElement("Connection");
+
+ int id;
+ while(connection) {
+ if(connection->QueryIntAttribute("id", &id) == TIXML_SUCCESS) {
+ connection_ids.push_back(id);
+ }
+ connection = connection->NextSiblingElement();
+ }
+ }
+ desc.AddIntVec(EDF_CONNECTIONS_FROM, connection_ids);
+}
+
+void LoadEntityDescriptionFromXML( EntityDescription& desc, const TiXmlElement* el ) {
+ const char* type = el->Value();
+ switch(type[0]){
+ case 'A':
+ switch(type[1]){
+ case 'c':
+ if(strcmp(type,"ActorObject") == 0){ LoadActorFromXML(desc, el, _movement_object); }
+ break;
+ case 'm':
+ if(strcmp(type,"AmbientSoundObject") == 0){ LoadActorFromXML(desc, el, _ambient_sound_object); }
+ break;
+ }
+ break;
+ case 'C':
+ if(strcmp(type,"CameraObject") == 0){ LoadActorFromXML(desc, el, _camera_type); }
+ break;
+ case 'D':
+ switch (type[1]){
+ case 'e':
+ if (strcmp(type, "Decal") == 0){ LoadDecalDescriptionFromXML(desc, el); }
+ break;
+ case 'y':
+ if (strcmp(type, "DynamicLightObject") == 0){ LoadActorFromXML(desc, el, _dynamic_light_object); }
+ break;
+ }
+ break;
+ case 'E':
+ if(strcmp(type,"EnvObject") == 0){ LoadEnvDescriptionFromXML(desc, el); }
+ break;
+ case 'G':
+ if(strcmp(type,"Group") == 0){ LoadGroupDescriptionFromXML(desc, el); }
+ break;
+ case 'H':
+ if(strcmp(type,"Hotspot") == 0){ LoadHotspotFromXML(desc, el); }
+ break;
+ case 'I':
+ if(strcmp(type,"ItemObject") == 0){ LoadActorFromXML(desc, el, _item_object); }
+ break;
+ case 'L':
+ switch (type[5]){
+ case 'P':
+ if(strcmp(type,"LightProbeObject") == 0){ LoadActorFromXML(desc, el, _light_probe_object); }
+ break;
+ case 'V':
+ if(strcmp(type,"LightVolumeObject") == 0){ LoadActorFromXML(desc, el, _light_volume_object); }
+ break;
+ }
+ break;
+ case 'P':
+ switch(type[1]){
+ case 'a':
+ if(strcmp(type,"PathPointObject") == 0){ LoadActorFromXML(desc, el, _path_point_object); }
+ break;
+ case 'l':
+ if(strcmp(type,"PlaceholderObject") == 0){ LoadActorFromXML(desc, el, _placeholder_object); }
+ break;
+ case 'r':
+ if(strcmp(type,"Prefab") == 0){ LoadPrefabDescriptionFromXML(desc, el); }
+ break;
+ }
+ break;
+ case 'N':
+ if(strcmp(type,"NavmeshHintObject") == 0 ){ LoadActorFromXML(desc, el, _navmesh_hint_object); }
+ if(strcmp(type,"NavmeshConnectionObject") == 0 ){ LoadActorFromXML(desc, el, _navmesh_connection_object); }
+ if(strcmp(type,"NavmeshRegionObject") == 0 ){ LoadActorFromXML(desc, el, _navmesh_region_object); }
+ break;
+ case 'R':
+ if(strcmp(type,"ReflectionCaptureObject") == 0){ LoadActorFromXML(desc, el, _reflection_capture_object); }
+ break;
+ }
+}
+
+void LoadEntityDescriptionListFromXML( EntityDescriptionList& desc_list, const TiXmlElement* el ) {
+ if(!el){
+ FatalError("Error", "Cannot load entity description list from null pointer");
+ }
+ const TiXmlElement* child = el->FirstChildElement();
+ while(child){
+ const char* val = child->Value();
+ if(strcmp(val, "parameters") != 0) {
+ EntityDescription desc;
+ LoadEntityDescriptionFromXML(desc, child);
+ if(!desc.fields.empty()){
+ desc_list.push_back(desc);
+ }
+ }
+ child = child->NextSiblingElement();
+ }
+}
+
+void ClearDescID(EntityDescription *desc) {
+ EntityDescriptionField* edf = desc->GetEditableField(EDF_ID);
+ if(edf){
+ edf->data.clear();
+ int null_id = -1;
+ memwrite(&null_id, sizeof(int), 1, edf->data);
+ }
+ EntityDescriptionList &children = desc->children;
+ for(EntityDescriptionList::iterator it = children.begin();
+ it != children.end(); ++it)
+ {
+ ClearDescID(&(*it));
+ }
+}
+
+void EntityDescriptionField::ReadInt(int* id) const {
+ memread(id, sizeof(int), 1, data);
+}
+
+void EntityDescriptionField::WriteInt(int id) {
+ memwrite(&id, sizeof(int), 1, data);
+}
+
+void EntityDescriptionField::ReadBool(bool* value) const {
+ char id;
+ memread(&id, sizeof(char), 1, data);
+ *value = (id != 0);
+}
+
+void EntityDescriptionField::WriteBool(bool value) {
+ char id = value ? 1 : 0;
+ memwrite(&id, sizeof(char), 1, data);
+}
+
+void EntityDescriptionField::ReadIntVec(std::vector<int>* vec) const {
+ int index=0;
+ int size;
+ memread(&size, sizeof(int), 1, data, index);
+ vec->resize(size);
+ if(size){
+ memread(&vec->at(0), sizeof(int), size, data, index);
+ }
+}
+
+void EntityDescriptionField::WriteIntVec(const std::vector<int>& val) {
+ int val_size = (int)val.size();
+ memwrite(&val_size, sizeof(int), 1, data);
+ if(val_size > 0){
+ memwrite(&val[0], sizeof(int), val_size, data);
+ }
+}
+
+void EntityDescriptionField::ReadItemConnectionDataVec(std::vector<ItemConnectionData>* item_connections) const {
+ int index=0;
+ int num_connections;
+ memread(&num_connections, sizeof(int), 1, data, index);
+ if(num_connections){
+ item_connections->resize(num_connections);
+ for(int i=0; i<num_connections; ++i){
+ MemReadItemConnectionData(item_connections->at(i), data, index);
+ }
+ }
+}
+
+void EntityDescriptionField::WriteItemConnectionDataVec(const std::vector<ItemConnectionData> &val) {
+ int val_size = (int)val.size();
+ memwrite(&val_size, sizeof(int), 1, data);
+ for(int i=0; i<val_size; ++i){
+ const ItemConnectionData &icd = val[i];
+ MemWriteItemConnectionData(icd, data);
+ }
+}
+
+void EntityDescriptionField::ReadNavMeshConnectionDataVec(std::vector<NavMeshConnectionData>* navmesh_connections) const
+{
+ int index=0;
+ int32_t num_connections;
+ memread(&num_connections, sizeof(int32_t), 1, data, index);
+ if(num_connections){
+ navmesh_connections->resize(num_connections);
+ for(int i=0; i<num_connections; ++i){
+ MemReadNavMeshConnectionData(navmesh_connections->at(i), data, index);
+ }
+ }
+}
+
+void EntityDescriptionField::WriteNavMeshConnectionDataVec(const std::vector<NavMeshConnectionData> &val)
+{
+ int32_t val_size = (int32_t)val.size();
+ memwrite(&val_size, sizeof(int32_t), 1, data);
+ for( int i=0; i<val_size; ++i)
+ {
+ const NavMeshConnectionData &nmcd = val[i];
+ MemWriteNavMeshConnectionData(nmcd,data);
+ }
+}
+
+void EntityDescriptionField::ReadString(std::string* str) const {
+ str->assign(data.begin(), data.end());
+}
+
+void EntityDescriptionField::WriteString(const std::string& str) {
+ data.assign(str.begin(), str.end());
+}
+
+void EntityDescriptionField::ReadPath( Path* path ) const {
+ std::string str;
+ ReadString(&str);
+ if( str.empty() ) {
+ *path = Path();
+ } else {
+ *path = FindFilePath(str);
+ }
+}
+
+void EntityDescriptionField::WritePath( const Path& val ) {
+ WriteString(std::string(val.original));
+}
diff --git a/Source/Game/EntityDescription.h b/Source/Game/EntityDescription.h
new file mode 100644
index 00000000..533da95d
--- /dev/null
+++ b/Source/Game/EntityDescription.h
@@ -0,0 +1,175 @@
+//-----------------------------------------------------------------------------
+// Name: EntityDescription.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/Asset/attachmentasset.h>
+#include <Asset/assetinfobase.h>
+
+#include <Editors/entity_type.h>
+#include <Scripting/scriptparams.h>
+#include <Graphics/palette.h>
+#include <Game/attachment_type.h>
+#include <AI/sample.h>
+
+#include <vector>
+
+class vec3;
+class mat4;
+class quaternion;
+
+enum EntityDescriptionFieldType {
+ EDF_ENTITY_TYPE,
+ EDF_NAME,
+ EDF_TRANSLATION,
+ EDF_SCALE,
+ EDF_ROTATION,
+ EDF_VERSION,
+ EDF_COLOR,
+ EDF_OVERBRIGHT,
+ EDF_ID,
+ EDF_FILE_PATH,
+ EDF_BILLBOARD_PATH,
+ EDF_SPECIAL_TYPE,
+ EDF_CONNECTIONS,
+ EDF_ITEM_CONNECTIONS,
+ EDF_DIRECTION,
+ EDF_DECAL_MISS_LIST,
+ EDF_DECAL_ISOLATION_ID,
+ EDF_DISPLAY_MODE,
+ EDF_DIMENSIONS,
+ EDF_USING_IMPOSTER,
+ EDF_IS_PLAYER,
+ EDF_SCRIPT_PARAMS,
+ EDF_PALETTE,
+ EDF_SKY_ROTATION,
+ EDF_SUN_RADIUS,
+ EDF_SUN_COLOR_ANGLE,
+ EDF_SUN_DIRECTION,
+ EDF_ENV_OBJECT_ATTACH,
+ EDF_NEGATIVE_LIGHT_PROBE,
+ EDF_GI_COEFFICIENTS,
+ EDF_NAV_MESH_CONNECTIONS,
+ EDF_NO_NAVMESH,
+ EDF_PREFAB_LOCKED,
+ EDF_PREFAB_PATH,
+ EDF_ORIGINAL_SCALE,
+ EDF_ROTATION_EULER,
+ EDF_HOTSPOT_CONNECTED_TO, // This is never saved to disk, only used internally
+ EDF_HOTSPOT_CONNECTED_FROM, // This is never saved to disk, only used internally
+ EDF_NPC_SCRIPT_PATH,
+ EDF_PC_SCRIPT_PATH,
+ // EDF_CONNECTIONS means connections to other objects, EDF_CONNECTIONS_FROM
+ // means objects connected to this object (i.e. coming from another other)
+ EDF_CONNECTIONS_FROM
+};
+
+struct ItemConnection {
+ int id;
+ AttachmentType attachment_type;
+ AttachmentRef attachment_ref;
+ bool mirrored;
+};
+
+struct ItemConnectionData {
+ int32_t id;
+ int32_t attachment_type;
+ std::string attachment_str;
+ int32_t mirrored;
+};
+
+struct NavMeshConnectionData {
+ int32_t other_object_id;
+ int32_t offmesh_connection_id;
+ SamplePolyAreas poly_area;
+};
+
+struct EntityDescriptionField {
+ int type;
+ std::vector<char> data;
+ void ReadBool(bool* val) const;
+ void WriteBool(bool val);
+ void ReadInt(int* id) const;
+ void WriteInt(int id);
+ void ReadIntVec(std::vector<int>* vec) const;
+ void WriteIntVec(const std::vector<int>& val);
+ void ReadItemConnectionDataVec(std::vector<ItemConnectionData>* item_connections) const;
+ void WriteItemConnectionDataVec(const std::vector<ItemConnectionData> &val);
+ void ReadNavMeshConnectionDataVec(std::vector<NavMeshConnectionData>* navmesh_connections) const;
+ void WriteNavMeshConnectionDataVec(const std::vector<NavMeshConnectionData> &val);
+ void ReadString( std::string* type_file ) const;
+ void WriteString(const std::string& str);
+ void ReadPath( Path* path ) const;
+ void WritePath( const Path& val );
+};
+
+struct EntityDescription;
+typedef std::vector<EntityDescription> EntityDescriptionList;
+
+struct EntityDescription {
+ EntityDescriptionList children;
+ std::vector<EntityDescriptionField> fields;
+
+ void AddPath(EntityDescriptionFieldType type, const Path& val);
+ void AddVec3(EntityDescriptionFieldType type, const vec3& val);
+ void AddInt(EntityDescriptionFieldType type, const int& val);
+ void AddFloat(EntityDescriptionFieldType type, const float& val);
+ void AddMat4(EntityDescriptionFieldType type, const mat4& val);
+ void AddQuaternion(EntityDescriptionFieldType type, const quaternion& val);
+ void AddEntityType(EntityDescriptionFieldType type, const EntityType& val);
+ void AddIntVec(EntityDescriptionFieldType type, const std::vector<int>& val);
+ void AddItemConnectionVec(EntityDescriptionFieldType type, const std::vector<ItemConnectionData>& val);
+ void AddNavMeshConnnectionVec(EntityDescriptionFieldType type, const std::vector<NavMeshConnectionData>& val);
+ void AddScriptParams(EntityDescriptionFieldType type, const ScriptParamMap& val);
+ void AddPalette(EntityDescriptionFieldType type, const OGPalette& val);
+ void AddString(EntityDescriptionFieldType type, const std::string& val);
+ void AddData(EntityDescriptionFieldType type, const std::vector<char> &val);
+ void AddBool(EntityDescriptionFieldType type, const bool& val);
+ const EntityDescriptionField* GetField(EntityDescriptionFieldType type) const;
+ EntityDescriptionField* GetField(EntityDescriptionFieldType type);
+ EntityDescriptionField* GetEditableField( EntityDescriptionFieldType type );
+ void ReturnPaths(PathSet& path_set);
+ void SaveToXML( TiXmlElement* object ) const;
+
+ private:
+ void AddType(EntityDescriptionFieldType type);
+};
+
+inline bool operator==(const EntityDescription& a, const EntityDescription& b){
+ return a.children == b.children && a.fields == b.fields;
+}
+
+inline bool operator!=(const EntityDescription& a, const EntityDescription& b){
+ return !(a == b);
+}
+
+inline bool operator==(const EntityDescriptionField& a, const EntityDescriptionField& b){
+ return a.type == b.type && a.data == b.data;
+}
+
+void GetTSRIinfo( EntityDescription &desc, const TiXmlElement* pElement );
+void LoadEntityDescriptionFromXML(EntityDescription& ed, const TiXmlElement* el);
+void LoadEntityDescriptionListFromXML( EntityDescriptionList& desc_list, const TiXmlElement* el );
+void GetTSREinfo( const TiXmlElement* pElement, vec3 &translation, vec3 &scale, quaternion &rotation, vec3 &rotation_euler );
+void GetTSRinfo( const TiXmlElement* pElement, vec3 &translation, vec3 &scale, quaternion &rotation );
+void ClearDescID(EntityDescription *desc);
diff --git a/Source/Game/attachment_type.h b/Source/Game/attachment_type.h
new file mode 100644
index 00000000..b45d1e22
--- /dev/null
+++ b/Source/Game/attachment_type.h
@@ -0,0 +1,26 @@
+//-----------------------------------------------------------------------------
+// Name: attachment_type.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+enum AttachmentType {_at_grip, _at_sheathe, _at_attachment, _at_unspecified};
diff --git a/Source/Game/attackscript.cpp b/Source/Game/attackscript.cpp
new file mode 100644
index 00000000..b5d7d6d0
--- /dev/null
+++ b/Source/Game/attackscript.cpp
@@ -0,0 +1,280 @@
+//-----------------------------------------------------------------------------
+// Name: attackscript.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "attackscript.h"
+
+#include <Math/enginemath.h>
+#include <Math/vec3math.h>
+
+#include <Scripting/angelscript/ascontext.h>
+#include <Main/engine.h>
+
+#include <stack>
+#include <sstream>
+
+extern std::stack<ASContext*> active_context_stack;
+
+static const std::string attack_getter_empty_path = "";
+static const std::string attack_getter_empty_material_event = "";
+
+bool AttackScriptGetter::Load( std::string _path ) {
+ if(_path.empty()){
+ return false;
+ }
+ path_ = _path;
+ if(_path[_path.size()-2] == ' ' &&
+ _path[_path.size()-1] == 'm')
+ {
+ mirror_ = true;
+ _path.resize(_path.size()-2);
+ } else {
+ mirror_ = false;
+ }
+ //Attacks::Instance()->ReturnRef(_path);
+ attack_ref_ = Engine::Instance()->GetAssetManager()->LoadSync<Attack>(_path);
+ return true;
+}
+
+std::string AttackScriptGetter::GetUnblockedAnimPath() {
+ return attack_ref_.valid() ? attack_ref_->unblocked_anim_path : attack_getter_empty_path;
+}
+
+std::string AttackScriptGetter::GetBlockedAnimPath() {
+ return attack_ref_.valid() ? attack_ref_->blocked_anim_path : attack_getter_empty_path;
+}
+
+std::string AttackScriptGetter::GetThrowAnimPath() {
+ return attack_ref_.valid() ? attack_ref_->throw_anim_path : attack_getter_empty_path;
+}
+
+std::string AttackScriptGetter::GetThrownAnimPath() {
+ return attack_ref_.valid() ? attack_ref_->thrown_anim_path : attack_getter_empty_path;
+}
+
+std::string AttackScriptGetter::GetSpecial() {
+ return attack_ref_.valid() ? attack_ref_->special : attack_getter_empty_path;
+}
+
+int AttackScriptGetter::GetHeight() {
+ return attack_ref_.valid() ? attack_ref_->height : AttackHeight::_medium;
+}
+
+int AttackScriptGetter::GetDirection() {
+ return attack_ref_.valid() ? attack_ref_->direction : AttackDirection::_front;
+}
+
+int AttackScriptGetter::GetSwapStance() {
+ return (int)(attack_ref_.valid() ? attack_ref_->swap_stance : false);
+}
+
+int AttackScriptGetter::GetSwapStanceBlocked() {
+ return (int)(attack_ref_.valid() ? attack_ref_->swap_stance_blocked : false);
+}
+
+int AttackScriptGetter::GetUnblockable() {
+ return (int)(attack_ref_.valid() ? attack_ref_->unblockable : false);
+}
+
+std::string AttackScriptGetter::GetMaterialEvent() {
+ return attack_ref_.valid() ? attack_ref_->materialevent : attack_getter_empty_material_event;
+}
+
+std::string AttackScriptGetter::GetReactionPath() {
+ if(attack_ref_.valid()) {
+ if(attack_ref_->direction == _front || !mirror_) {
+ return attack_ref_->reaction;
+ } else {
+ return attack_ref_->reaction + " m";
+ }
+ } else {
+ return attack_getter_empty_path;
+ }
+}
+
+vec3 AttackScriptGetter::GetImpactDir() {
+ if(attack_ref_.valid()) {
+ if(!mirror_){
+ return attack_ref_->impact_dir;
+ } else {
+ return attack_ref_->impact_dir*vec3(-1.0f,1.0f,1.0f);
+ }
+ } else {
+ return vec3();
+ }
+}
+
+float AttackScriptGetter::GetBlockDamage() {
+ return attack_ref_.valid() ? RangedRandomFloat(attack_ref_->block_damage[0], attack_ref_->block_damage[1]) : 0.0f;
+}
+
+float AttackScriptGetter::GetSharpDamage() {
+ return attack_ref_.valid() ? RangedRandomFloat(attack_ref_->sharp_damage[0], attack_ref_->sharp_damage[1]) : 0.0f;
+}
+
+float AttackScriptGetter::GetDamage() {
+ return attack_ref_.valid() ? RangedRandomFloat(attack_ref_->damage[0], attack_ref_->damage[1]) : 0.0f;
+}
+
+float AttackScriptGetter::GetForce() {
+ return attack_ref_.valid() ? RangedRandomFloat(attack_ref_->force[0], attack_ref_->force[1]) : 0.0f;
+}
+
+std::string AttackScriptGetter::GetPath() {
+ return path_;
+}
+
+AttackScriptGetter *AttackScriptGetter_Factory() {
+ return new AttackScriptGetter();
+}
+
+static void ASLoad(AttackScriptGetter *asg, const std::string &path){
+ if(!asg->Load(path)){
+ std::string callstack = active_context_stack.top()->GetCallstack();
+ FatalError("Error", "Attempting to load attack with empty path\n Called from:\n%s", callstack.c_str());
+ }
+}
+
+void AttachAttackScriptGetter( ASContext *as_context ) {
+ as_context->RegisterObjectType("AttackScriptGetter", 0, asOBJ_REF, "Used to query information from an attack xml file");
+ as_context->RegisterObjectBehaviour("AttackScriptGetter", asBEHAVE_FACTORY, "AttackScriptGetter@ f()", asFUNCTION(AttackScriptGetter_Factory), asCALL_CDECL);
+ as_context->RegisterObjectBehaviour("AttackScriptGetter", asBEHAVE_ADDREF, "void AttackScriptGetter()", asMETHOD(AttackScriptGetter,AddRef), asCALL_THISCALL);
+ as_context->RegisterObjectBehaviour("AttackScriptGetter", asBEHAVE_RELEASE, "void AttackScriptGetter()", asMETHOD(AttackScriptGetter,ReleaseRef), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "void Load(const string &in path)", asFUNCTION(ASLoad), asCALL_CDECL_OBJFIRST, "Load an attack xml file into the attack script getter (e.g. \"Data/Attacks/knifeslash.xml\")");
+ as_context->RegisterObjectMethod("AttackScriptGetter", "string GetPath()", asMETHOD(AttackScriptGetter, GetPath), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "string GetSpecial()", asMETHOD(AttackScriptGetter, GetSpecial), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "string GetUnblockedAnimPath()", asMETHOD(AttackScriptGetter, GetUnblockedAnimPath), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "string GetBlockedAnimPath()", asMETHOD(AttackScriptGetter, GetBlockedAnimPath), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "string GetThrowAnimPath()", asMETHOD(AttackScriptGetter, GetThrowAnimPath), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "string GetThrownAnimPath()", asMETHOD(AttackScriptGetter, GetThrownAnimPath), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "string GetThrownCounterAnimPath()", asMETHOD(AttackScriptGetter, GetThrownCounterAnimPath), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "int IsThrow()", asMETHOD(AttackScriptGetter, IsThrow), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "int GetHeight()", asMETHOD(AttackScriptGetter, GetHeight), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "vec3 GetCutPlane()", asMETHOD(AttackScriptGetter, GetCutPlane), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "int GetCutPlaneType()", asMETHOD(AttackScriptGetter, GetCutPlaneType), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "bool HasCutPlane()", asMETHOD(AttackScriptGetter, HasCutPlane), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "vec3 GetStabDir()", asMETHOD(AttackScriptGetter, GetStabDir), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "int GetStabDirType()", asMETHOD(AttackScriptGetter, GetStabDirType), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "bool HasStabDir()", asMETHOD(AttackScriptGetter, HasStabDir), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "int GetDirection()", asMETHOD(AttackScriptGetter, GetDirection), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "int GetSwapStance()", asMETHOD(AttackScriptGetter, GetSwapStance), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "int GetSwapStanceBlocked()", asMETHOD(AttackScriptGetter, GetSwapStanceBlocked), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "int GetUnblockable()", asMETHOD(AttackScriptGetter, GetUnblockable), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "int GetFleshUnblockable()", asMETHOD(AttackScriptGetter, GetFleshUnblockable), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "int GetAsLayer()", asMETHOD(AttackScriptGetter, GetAsLayer), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "string GetAlternate()", asMETHOD(AttackScriptGetter, GetAlternate), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "string GetReactionPath()", asMETHOD(AttackScriptGetter, GetReactionPath), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "string GetMaterialEvent()", asMETHOD(AttackScriptGetter, GetMaterialEvent), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "vec3 GetImpactDir()", asMETHOD(AttackScriptGetter, GetImpactDir), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "float GetBlockDamage()", asMETHOD(AttackScriptGetter, GetBlockDamage), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "float GetSharpDamage()", asMETHOD(AttackScriptGetter, GetSharpDamage), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "float GetDamage()", asMETHOD(AttackScriptGetter, GetDamage), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "float GetForce()", asMETHOD(AttackScriptGetter, GetForce), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "int GetMirrored()", asMETHOD(AttackScriptGetter, GetMirrored), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("AttackScriptGetter", "int GetMobile()", asMETHOD(AttackScriptGetter, GetMobile), asCALL_THISCALL);
+ as_context->DocsCloseBrace();
+
+ static const int const_high = _high;
+ static const int const_medium = _medium;
+ static const int const_low = _low;
+ static const int const_right = _right;
+ static const int const_front = _front;
+ static const int const_left = _left;
+
+ as_context->RegisterGlobalProperty("const int _high", (void*)&const_high);
+ as_context->RegisterGlobalProperty("const int _medium", (void*)&const_medium);
+ as_context->RegisterGlobalProperty("const int _low", (void*)&const_low);
+ as_context->RegisterGlobalProperty("const int _right", (void*)&const_right);
+ as_context->RegisterGlobalProperty("const int _front", (void*)&const_front);
+ as_context->RegisterGlobalProperty("const int _left", (void*)&const_left);
+}
+
+int AttackScriptGetter::GetMirrored() {
+ return (int)mirror_;
+}
+
+int AttackScriptGetter::GetMobile() {
+ return (int)(attack_ref_.valid() ? attack_ref_->mobile : false);
+}
+
+int AttackScriptGetter::IsThrow() {
+ if(!attack_ref_.valid()){
+ return 0;
+ }
+ return (int)(!(attack_ref_->throw_anim_path.empty() &&
+ attack_ref_->thrown_anim_path.empty()));
+}
+
+vec3 AttackScriptGetter::GetCutPlane() {
+ return attack_ref_.valid() ? attack_ref_->cut_plane : vec3();
+}
+
+int AttackScriptGetter::GetCutPlaneType() {
+ return attack_ref_.valid() ? attack_ref_->cut_plane_type : CutPlaneType::_heavy;
+}
+
+bool AttackScriptGetter::HasCutPlane() {
+ return attack_ref_.valid() ? attack_ref_->has_cut_plane : false;
+}
+
+vec3 AttackScriptGetter::GetStabDir() {
+ return attack_ref_.valid() ? attack_ref_->stab_dir : vec3();
+}
+
+int AttackScriptGetter::GetStabDirType() {
+ return attack_ref_.valid() ? attack_ref_->stab_type : CutPlaneType::_heavy;
+}
+
+bool AttackScriptGetter::HasStabDir() {
+ return attack_ref_.valid() ? attack_ref_->has_stab_dir : false;
+}
+
+int AttackScriptGetter::GetFleshUnblockable() {
+ return (int)(attack_ref_.valid() ? attack_ref_->flesh_unblockable : false);
+}
+
+int AttackScriptGetter::GetAsLayer() {
+ return (int)(attack_ref_.valid() ? attack_ref_->as_layer : false);
+}
+
+std::string AttackScriptGetter::GetAlternate() {
+ return attack_ref_.valid() ? attack_ref_->alternate : attack_getter_empty_path;
+}
+
+std::string AttackScriptGetter::GetThrownCounterAnimPath() {
+ return attack_ref_.valid() ? attack_ref_->thrown_counter_anim_path : attack_getter_empty_path;
+}
+
+AttackScriptGetter::AttackScriptGetter():
+ ref_count(1)
+{}
+
+void AttackScriptGetter::AddRef() {
+ ++ref_count;
+}
+
+void AttackScriptGetter::ReleaseRef() {
+ --ref_count;
+ if(ref_count == 0){
+ delete this;
+ }
+}
diff --git a/Source/Game/attackscript.h b/Source/Game/attackscript.h
new file mode 100644
index 00000000..5eb416bc
--- /dev/null
+++ b/Source/Game/attackscript.h
@@ -0,0 +1,78 @@
+//-----------------------------------------------------------------------------
+// Name: attackscript.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/Asset/attacks.h>
+
+class ASContext;
+
+class AttackScriptGetter {
+public:
+ AttackScriptGetter();
+
+ AttackRef attack_ref_;
+ bool mirror_;
+ std::string path_;
+ int ref_count;
+
+ std::string GetUnblockedAnimPath();
+ std::string GetBlockedAnimPath();
+ int GetHeight();
+ int GetDirection();
+ int GetSwapStance();
+ int GetMirrored();
+ std::string GetReactionPath();
+ std::string GetMaterialEvent();
+ vec3 GetImpactDir();
+ float GetBlockDamage();
+ float GetSharpDamage();
+ float GetDamage();
+ float GetForce();
+ std::string GetPath();
+ std::string GetSpecial();
+ int GetUnblockable();
+ int GetFleshUnblockable();
+ int GetMobile();
+ int GetSwapStanceBlocked();
+ std::string GetThrowAnimPath();
+ std::string GetThrownAnimPath();
+ std::string GetThrownCounterAnimPath();
+ int IsThrow();
+ vec3 GetCutPlane();
+ bool HasCutPlane();
+ int GetCutPlaneType();
+ vec3 GetStabDir();
+ int GetStabDirType();
+ bool HasStabDir();
+ int GetAsLayer();
+ std::string GetAlternate();
+
+ void AddRef();
+ void ReleaseRef();
+ const AttackRef& attack_ref() const {return attack_ref_;}
+ bool mirror() const {return mirror_;}
+ bool Load( std::string path );
+};
+
+void AttachAttackScriptGetter( ASContext *as_context );
diff --git a/Source/Game/avatar_control_manager.cpp b/Source/Game/avatar_control_manager.cpp
new file mode 100644
index 00000000..b2f7ac21
--- /dev/null
+++ b/Source/Game/avatar_control_manager.cpp
@@ -0,0 +1,227 @@
+//-----------------------------------------------------------------------------
+// Name: avatar_control_manager.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "avatar_control_manager.h"
+
+#include <Main/engine.h>
+#include <Main/scenegraph.h>
+
+#include <Graphics/camera.h>
+#include <Editors/map_editor.h>
+#include <Objects/cameraobject.h>
+#include <Game/level.h>
+#include <Online/online.h>
+
+std::vector<Possession> AvatarControlManager::GeneratePossessionList() {
+ Engine* engine = Engine::Instance();
+ Online* online = Online::Instance();
+ SceneGraph* scenegraph_ = Engine::Instance()->GetSceneGraph();
+
+ std::vector<Possession> possessions;
+
+ int local_player_index = 0;
+ if(online->IsActive()) {
+ for(auto& player_state : online->GetPlayerStates()) {
+ if (player_state.object_id == -1) {
+ continue;
+ }
+
+ bool is_local = online->IsLocalAvatar(player_state.object_id);
+ int camera_id = -1;
+ int controller_id = -1;
+
+ if(is_local) {
+ camera_id = 0;
+ controller_id = local_player_index;
+ local_player_index++;
+ } else {
+ //Virtual controller_id, used for syncing input from remote source
+ camera_id = player_state.camera_id;
+ controller_id = player_state.controller_id;
+ }
+
+ MovementObject* mo = nullptr;
+ if(scenegraph_ != nullptr ) {
+ Object* ob = scenegraph_->GetObjectFromID(player_state.object_id);
+
+ if(ob != nullptr && ob->GetType() == EntityType::_movement_object) {
+ mo = static_cast<MovementObject*>(ob);
+ }
+ }
+
+ if(mo != nullptr && camera_id != -1 && controller_id != -1) {
+ possessions.push_back(Possession(
+ player_state.object_id,
+ controller_id,
+ camera_id,
+ is_local
+ ));
+ }
+ }
+ }
+
+ std::vector<ObjectID> unpossessed_avatars = GetUnpossessedAvatars();
+
+ for(int i = 0; i < unpossessed_avatars.size(); i++) {
+ int camera_id = 0;
+
+ if(Engine::Instance()->GetSplitScreen()) {
+ camera_id = i;
+ }
+
+ possessions.push_back(Possession(unpossessed_avatars[i], local_player_index, camera_id, !Online::Instance()->IsActive() || Online::Instance()->IsHosting()));
+ local_player_index++;
+ }
+
+ return possessions;
+}
+
+std::vector<ObjectID> AvatarControlManager::GetUnpossessedAvatars() {
+ Online* online = Online::Instance();
+ SceneGraph* scenegraph_ = Engine::Instance()->GetSceneGraph();
+
+ std::vector<ObjectID> unpossessed_avatars;
+
+ int avatar_ids[Engine::kMaxAvatars];
+ int num_avatars;
+ if(scenegraph_ != nullptr) {
+ scenegraph_->GetPlayerCharacterIDs(&num_avatars, avatar_ids, Engine::kMaxAvatars);
+
+ for(int i = 0; i < num_avatars; i++) {
+ bool found = online->IsAvatarPossessed(avatar_ids[i]);
+
+ if(found == false) {
+ unpossessed_avatars.push_back(avatar_ids[i]);
+ }
+ }
+ }
+
+ return unpossessed_avatars;
+}
+
+void AvatarControlManager::Update() {
+ Engine* engine = Engine::Instance();
+ Online* online = Online::Instance();
+ SceneGraph* scenegraph_ = Engine::Instance()->GetSceneGraph();
+
+ scenegraph_->GetPlayerCharacterIDs(&engine->num_avatars, engine->avatar_ids, Engine::kMaxAvatars);
+
+ if (scenegraph_->map_editor->state_ != MapEditor::kInGame) { // We probably shouldn't do this in mp at all, not even our own controlable char as host
+ for(int i=0; i < engine->num_avatars; ++i){
+ Object* obj = scenegraph_->GetObjectFromID(engine->avatar_ids[i]);
+ if(obj->GetType() == _movement_object){
+ MovementObject* avatar = (MovementObject*)obj;
+
+ //If we're in a multiplayer game, don't switch other players characters to NPC mode, only do it to ours.
+ if (online->IsHosting() == false || online->IsLocalAvatar(engine->avatar_ids[i])) {
+ avatar->ChangeControlScript(scenegraph_->level->GetNPCScript(avatar));
+ avatar->controlled = false;
+ avatar->camera_id = 0;
+ }
+ }
+ }
+ Camera& active_camera = *ActiveCameras::Get();
+ CameraObject& camera_object = *active_camera.getCameraObject();
+ if(!camera_object.controlled) {
+ camera_object.controlled = true;
+ camera_object.SetTranslation(active_camera.GetPos());
+ }
+ } else {
+ //If there is no controllable avatars in the scene, create one at the camera position and use it.
+ if (engine->num_avatars == 0) {
+ int id = scenegraph_->map_editor->CreateObject("Data/Objects/IGF_Characters/IGF_TurnerActor.xml");
+ MovementObject* mo = static_cast<MovementObject*>(scenegraph_->GetObjectFromID(id));
+ mo->SetTranslation(ActiveCameras::Get()->getCameraObject()->GetTranslation());
+ mo->is_player = true;
+ scenegraph_->GetPlayerCharacterIDs(&engine->num_avatars, engine->avatar_ids, Engine::kMaxAvatars);
+ }
+
+ //If we're running in multiplayer, disable multiple local avatars for now.
+ //This might change in the future if we want to support local split-screen, then
+ //this has to be called with the number of local players on _this_ client/host,
+ //because this function sets up the bindings between controllers and the indexes of controller
+ //input from 0 to 3.
+ if(online->IsActive()) {
+ Input::Instance()->SetUpForXPlayers(1);
+ } else {
+ Input::Instance()->SetUpForXPlayers(engine->num_avatars);
+ }
+
+ bool focused_character_set = false;
+ /*
+ for (uint32_t i = 0; i < num_npc_avatars; i++) {
+
+ MovementObject* avatar = static_cast<MovementObject*>(scenegraph_->GetObjectFromID(avatar_ids[i]));
+ if (Engine::Instance()->multiplayer.IsActive() && !Engine::Instance()->multiplayer.IsHosting()) {
+ avatar->ChangeControlScript(scenegraph_->level->GetNPCMPScript());
+ }
+ }
+ */
+
+ std::vector<Possession> possessions = GeneratePossessionList();
+
+ for(Possession possession : possessions) {
+ MovementObject* avatar = static_cast<MovementObject*>(scenegraph_->GetObjectFromID(possession.avatar));
+
+ if (avatar != nullptr) {
+ avatar->ChangeControlScript(scenegraph_->level->GetPCScript(avatar));
+
+ avatar->controlled = true;
+ avatar->controller_id = possession.controller_id;
+ avatar->remote = !possession.is_local;
+
+ if (possession.is_local) {
+ //Changing the focused value should only occur if we're playing in split-screen, as it's the only
+ //situation where it's relevant to dynamically shift the audio source, and the underlying reason why
+ //we have this concept in the engine in the first place.
+ if (engine->GetSplitScreen()) {
+ //TODO:
+ //Getting the knocked_out value here from the angel script env is pretty rough.
+ //We should reconsider this approach. /Max
+ if (!focused_character_set && avatar->ASGetIntVar("knocked_out") == MovementObject::_awake) {
+ avatar->focused_character = true;
+ focused_character_set = true;
+ }
+ else {
+ avatar->focused_character = false;
+ }
+ }
+ else {
+ if (!avatar->focused_character && !focused_character_set && !avatar->remote && Online::Instance()->IsHosting()) {
+ Online::Instance()->PossessAvatar(Online::Instance()->online_session->local_player_id, avatar->GetID());
+ }
+
+ //Set the first player controlled avatar as the focused one, this works
+ //in single player because we only expect there to be one controllable
+ //avatar.
+ avatar->focused_character = !focused_character_set;
+ focused_character_set = true;
+ }
+ }
+
+ avatar->camera_id = possession.camera_id;
+ }
+ }
+ ActiveCameras::Get()->getCameraObject()->controlled = false;
+ }
+}
diff --git a/Source/Game/avatar_control_manager.h b/Source/Game/avatar_control_manager.h
new file mode 100644
index 00000000..cb6606c2
--- /dev/null
+++ b/Source/Game/avatar_control_manager.h
@@ -0,0 +1,57 @@
+//-----------------------------------------------------------------------------
+// Name: avatar_control_manager.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Objects/object.h>
+
+#include <vector>
+#include <utility>
+
+class Possession {
+public:
+ ObjectID avatar;
+ int controller_id;
+ int camera_id;
+ bool is_local;
+
+ Possession(ObjectID avatar, int controller_id, int camera_id, bool is_local) : avatar(avatar), controller_id(controller_id), camera_id(camera_id), is_local(is_local) {};
+};
+
+
+//The AvatarControlManager dynamically changes player avatars control states base on requested possesions and engine state.
+//This is done because the player characters can get AI possesion when switching into an editor state, or other non-game states.
+//But needs to correctly revert to the possesor on demand, both local and online.
+class AvatarControlManager {
+private:
+ //std::vector<std::pair<ObjectID, int>> possesions;
+
+ //Get a list of what controller input controlsw what player avatar.
+ std::vector<Possession> GeneratePossessionList();
+
+ std::vector<ObjectID> GetUnpossessedAvatars();
+
+public:
+ //void DefinePlayerPossesion(ObjectID avatar, int controller_id);
+ void Update();
+};
diff --git a/Source/Game/characterscript.cpp b/Source/Game/characterscript.cpp
new file mode 100644
index 00000000..bb713cf9
--- /dev/null
+++ b/Source/Game/characterscript.cpp
@@ -0,0 +1,168 @@
+//-----------------------------------------------------------------------------
+// Name: characterscript.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "characterscript.h"
+
+#include <Scripting/angelscript/ascontext.h>
+#include <Objects/itemobject.h>
+#include <Main/engine.h>
+
+bool CharacterScriptGetter::Load( const std::string& _path ) {
+ //Characters::Instance()->ReturnRef(_path);
+ character_ref = Engine::Instance()->GetAssetManager()->LoadSync<Character>(_path);
+ if( character_ref.valid() ) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+std::string CharacterScriptGetter::GetObjPath( ) {
+ return character_ref->GetObjPath();
+}
+
+std::string CharacterScriptGetter::GetSkeletonPath( ) {
+ return character_ref->GetSkeletonPath();
+}
+
+std::string CharacterScriptGetter::GetAnimPath( const std::string& action ) {
+ for(unsigned i=0; i<items.size(); ++i){
+ if(items[i]->HasAnimOverride(action)){
+ return items[i]->GetAnimOverride(action);
+ }
+ }
+ CharAnimOverrideMap::iterator iter = char_anim_overrides_.find(action);
+ if(iter == char_anim_overrides_.end()){
+ return character_ref->GetAnimPath(action);
+ } else {
+ return iter->second;
+ }
+}
+
+const std::string & CharacterScriptGetter::GetTag(const std::string &key) {
+ return character_ref->GetTag(key);
+}
+
+
+char CharacterScriptGetter::GetAnimFlags( const std::string& action ) {
+ for(unsigned i=0; i<items.size(); ++i){
+ if(items[i]->HasAnimOverride(action)){
+ return items[i]->GetAnimOverrideFlags(action);
+ }
+ }
+ return 0;
+}
+
+std::string CharacterScriptGetter::GetAttackPath( const std::string& action ) {
+ for(unsigned i=0; i<items.size(); ++i){
+ if(items[i]->HasAttackOverride(action)){
+ return items[i]->GetAttackOverride(action);
+ }
+ }
+ return character_ref->GetAttackPath(action);
+}
+
+int CharacterScriptGetter::OnSameTeam( const std::string& char_path ) {
+ //CharacterRef other = Characters::Instance()->ReturnRef(char_path);
+ CharacterRef other = Engine::Instance()->GetAssetManager()->LoadSync<Character>(char_path);
+ const std::set<std::string> &team_set = character_ref->GetTeamSet();
+ for(std::set<std::string>::const_iterator iter = team_set.begin();
+ iter != team_set.end();
+ ++iter)
+ {
+ if(other->IsOnTeam(*iter)){
+ return 1;
+ }
+ }
+ return 0;
+}
+
+void CharacterScriptGetter::GetTeamString(std::string &str) {
+ str.clear();
+ const std::set<std::string> &team_set = character_ref->GetTeamSet();
+ for(std::set<std::string>::const_iterator iter = team_set.begin();
+ iter != team_set.end();
+ ++iter)
+ {
+ if(iter != team_set.begin()){
+ str += ", ";
+ }
+ str += (*iter);
+ }
+}
+
+void CharacterScriptGetter::AttachToScript( ASContext *as_context, const std::string& as_name ) {
+ as_context->RegisterObjectType("CharacterScriptGetter", 0, asOBJ_REF | asOBJ_NOHANDLE, "Can load a character xml and provide access to its data");
+ as_context->RegisterObjectMethod("CharacterScriptGetter", "bool Load(const string &in)", asMETHOD(CharacterScriptGetter, Load), asCALL_THISCALL, "Load a character xml file (e.g. \"Data/Characters/guard.xml\")");
+ as_context->RegisterObjectMethod("CharacterScriptGetter", "string GetObjPath()", asMETHOD(CharacterScriptGetter, GetObjPath), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("CharacterScriptGetter", "string GetSkeletonPath()", asMETHOD(CharacterScriptGetter, GetSkeletonPath), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("CharacterScriptGetter", "string GetAnimPath(const string &in anim_label)", asMETHOD(CharacterScriptGetter, GetAnimPath), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("CharacterScriptGetter", "const string& GetTag(const string &in key)", asMETHOD(CharacterScriptGetter, GetTag), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("CharacterScriptGetter", "string GetAttackPath(const string &in attack_label)", asMETHOD(CharacterScriptGetter, GetAttackPath), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("CharacterScriptGetter", "void GetTeamString(string &out team_string)", asMETHOD(CharacterScriptGetter, GetTeamString), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("CharacterScriptGetter", "float GetHearing()", asMETHOD(CharacterScriptGetter, GetHearing), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("CharacterScriptGetter", "const string& GetChannel(int which_channel)", asMETHOD(CharacterScriptGetter, GetChannel), asCALL_THISCALL, "Get type of given color channel (e.g. \"fur\", \"cloth\")");
+ as_context->RegisterObjectMethod("CharacterScriptGetter", "bool GetMorphMetaPoints(const string &in label, vec3 &out start, vec3 &out end)", asMETHOD(CharacterScriptGetter, GetMorphMetaPoints), asCALL_THISCALL);
+ as_context->DocsCloseBrace();
+ as_context->RegisterGlobalProperty(("CharacterScriptGetter "+as_name).c_str(), this);
+}
+
+const std::string &CharacterScriptGetter::GetChannel(int which){
+ return character_ref->GetChannel(which);
+}
+
+void CharacterScriptGetter::AttachExtraToScript( ASContext *as_context, const std::string& as_name ) {
+ as_context->RegisterGlobalProperty(("CharacterScriptGetter "+as_name).c_str(), this);
+}
+
+void CharacterScriptGetter::ItemsChanged( const std::vector<ItemRef> &_items ) {
+ items = _items;
+}
+
+const std::string CharacterScriptGetter::GetClothingPath() {
+ return character_ref->GetClothingPath();
+}
+
+const std::string& CharacterScriptGetter::GetSoundMod() {
+ return character_ref->GetSoundMod();
+}
+
+float CharacterScriptGetter::GetHearing() {
+ return character_ref->GetHearing();
+}
+
+void CharacterScriptGetter::OverrideCharAnim( const std::string &label, const std::string &new_path ) {
+ char_anim_overrides_[label] = new_path;
+}
+
+float CharacterScriptGetter::GetDefaultScale() {
+ return character_ref->GetDefaultScale();
+}
+
+float CharacterScriptGetter::GetModelScale() {
+ return character_ref->GetModelScale();
+}
+
+bool CharacterScriptGetter::GetMorphMetaPoints(const std::string& label, vec3 &start, vec3 &end) {
+ return character_ref->GetMorphMeta(label, start, end);
+}
diff --git a/Source/Game/characterscript.h b/Source/Game/characterscript.h
new file mode 100644
index 00000000..3c9912e2
--- /dev/null
+++ b/Source/Game/characterscript.h
@@ -0,0 +1,60 @@
+//-----------------------------------------------------------------------------
+// Name: characterscript.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/Asset/character.h>
+#include <Asset/Asset/item.h>
+
+#include <vector>
+
+class ASContext;
+
+class CharacterScriptGetter {
+ CharacterRef character_ref;
+ std::vector<ItemRef> items;
+ typedef std::map<std::string, std::string> CharAnimOverrideMap;
+ CharAnimOverrideMap char_anim_overrides_;
+
+public:
+ void OverrideCharAnim(const std::string &label, const std::string &new_path);
+ void AttachToScript( ASContext *as_context, const std::string& as_name );
+ void AttachExtraToScript( ASContext *as_context, const std::string& as_name );
+ void ItemsChanged( const std::vector<ItemRef> &_items );
+ const std::string GetClothingPath();
+ const std::string& GetSoundMod();
+ bool Load( const std::string& path );
+ std::string GetAttackPath( const std::string& action );
+ std::string GetAnimPath( const std::string& action );
+ std::string GetSkeletonPath( );
+ std::string GetObjPath( );
+ bool GetMorphMetaPoints(const std::string& label, vec3 &start, vec3 &end);
+ float GetDefaultScale();
+ float GetModelScale();
+ int OnSameTeam( const std::string& character_path );
+ float GetHearing();
+ char GetAnimFlags( const std::string& path );
+ void GetTeamString(std::string &str);
+ const std::string &GetChannel(int which);
+ const std::string &GetTag( const std::string &key );
+};
diff --git a/Source/Game/color_tint_component.cpp b/Source/Game/color_tint_component.cpp
new file mode 100644
index 00000000..fdc24574
--- /dev/null
+++ b/Source/Game/color_tint_component.cpp
@@ -0,0 +1,98 @@
+//-----------------------------------------------------------------------------
+// Name: color_tint_component.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include <Game/color_tint_component.h>
+#include <Game/EntityDescription.h>
+
+#include <Internal/memwrite.h>
+#include <Editors/save_state.h>
+
+#include <tinyxml.h>
+
+void ColorTintComponent::LoadDescriptionFromXML( EntityDescription& desc, const TiXmlElement* el ) {
+ double dval;
+ vec3 color_tint(1.0f);
+ float overbright = 0.0f;
+ if(el->QueryDoubleAttribute("color_r",&dval)==TIXML_SUCCESS) color_tint[0] = (float)dval;
+ if(el->QueryDoubleAttribute("color_g",&dval)==TIXML_SUCCESS) color_tint[1] = (float)dval;
+ if(el->QueryDoubleAttribute("color_b",&dval)==TIXML_SUCCESS) color_tint[2] = (float)dval;
+ if(el->QueryDoubleAttribute("overbright",&dval)==TIXML_SUCCESS) overbright = (float)dval;
+
+ float max_val = 0.0f;
+ for(int i=0; i<3; ++i){
+ if(color_tint[i] > max_val){
+ max_val = color_tint[i];
+ }
+ }
+
+ if(max_val > 1.0f){
+ for(int i=0; i<3; ++i){
+ color_tint[i] /= max_val;
+ }
+ overbright += (max_val - 1.0f)/0.3f;
+ }
+
+ desc.AddVec3(EDF_COLOR, color_tint);
+ desc.AddFloat(EDF_OVERBRIGHT, overbright);
+}
+
+void ColorTintComponent::SetFromDescription( const EntityDescription& desc ) {
+ for(unsigned i=0; i<desc.fields.size(); ++i){
+ const EntityDescriptionField& field = desc.fields[i];
+ switch(field.type){
+ case EDF_COLOR:
+ memread(tint_.entries, sizeof(float), 3, field.data);
+ break;
+ case EDF_OVERBRIGHT:
+ memread(&overbright_, sizeof(float), 1, field.data);
+ break;
+ }
+ }
+}
+
+void ColorTintComponent::SaveToXML( TiXmlElement* el ) {
+ el->SetDoubleAttribute("color_r", tint_[0]);
+ el->SetDoubleAttribute("color_g", tint_[1]);
+ el->SetDoubleAttribute("color_b", tint_[2]);
+ el->SetDoubleAttribute("overbright", overbright_);
+}
+
+void ColorTintComponent::AddToDescription( EntityDescription& desc ) const {
+ desc.AddVec3(EDF_COLOR, tint_);
+ desc.AddFloat(EDF_OVERBRIGHT, overbright_);
+}
+
+void ColorTintComponent::ReceiveObjectMessageVAList( OBJECT_MSG::Type type, va_list args ) {
+ switch(type){
+ case OBJECT_MSG::SET_COLOR:
+ tint_ = *va_arg(args, vec3*);
+ break;
+ case OBJECT_MSG::SET_OVERBRIGHT:
+ overbright_ = *va_arg(args, float*);
+ break;
+ default:
+ break;
+ }
+}
+
diff --git a/Source/Game/color_tint_component.h b/Source/Game/color_tint_component.h
new file mode 100644
index 00000000..0c967905
--- /dev/null
+++ b/Source/Game/color_tint_component.h
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------------
+// Name: color_tint_component.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Game/component.h>
+#include <Math/vec3.h>
+
+class ColorTintComponent : public Phoenix::Component {
+public:
+ vec3 tint_;
+ float overbright_;
+ ColorTintComponent():tint_(1.0f), overbright_(0.0f) {}
+ static void LoadDescriptionFromXML(EntityDescription& desc, const TiXmlElement* el);
+ virtual void SetFromDescription(const EntityDescription& desc);
+ virtual void AddToDescription(EntityDescription& desc) const;
+ virtual void SaveToXML(TiXmlElement* el);
+ virtual void ReceiveObjectMessageVAList(OBJECT_MSG::Type type, va_list args);
+};
diff --git a/Source/Game/component.h b/Source/Game/component.h
new file mode 100644
index 00000000..aba69a90
--- /dev/null
+++ b/Source/Game/component.h
@@ -0,0 +1,55 @@
+//-----------------------------------------------------------------------------
+// Name: component.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Objects/object_msg.h>
+
+#include <cstdarg>
+
+struct EntityDescription;
+class TiXmlElement;
+
+namespace Phoenix {
+
+class Component {
+public:
+ enum ComponentType {
+ COLOR_TINT
+ };
+ ComponentType type() {return type_;}
+ virtual void SetFromDescription(const EntityDescription& desc)=0;
+ virtual void SaveToXML(TiXmlElement* el)=0;
+ virtual void ReceiveObjectMessage(OBJECT_MSG::Type msg_type, ...) {
+ va_list args;
+ va_start(args, msg_type);
+ ReceiveObjectMessageVAList(msg_type, args);
+ va_end(args);
+ }
+ virtual void ReceiveObjectMessageVAList(OBJECT_MSG::Type msg_type, va_list args) = 0;
+ virtual ~Component() {}
+private:
+ ComponentType type_;
+};
+
+}
diff --git a/Source/Game/connection_closed_reason.h b/Source/Game/connection_closed_reason.h
new file mode 100644
index 00000000..61b69fde
--- /dev/null
+++ b/Source/Game/connection_closed_reason.h
@@ -0,0 +1,75 @@
+//-----------------------------------------------------------------------------
+// Name: connection_closed_reason.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Logging/logdata.h>
+
+#include <string>
+
+/// Important: Steam expects "usual" connection ends to be in 1000 - 1999
+/// while "unusual" connection ends need to be in 2000 - 2999!
+/// Failure to keep this range will always result in error code 1999 to be returned instead of the error code specified
+/// TODO: in the future, wrap these somehow so we can still use them even if we're using a different underlying networking system.
+enum class ConnectionClosedReason {
+ UNSPECIFIED = 1000,
+ DISCONNECTED, // Connection closed as part of natural disconnections
+
+ BAD_REQUEST = 2000, // Client has sent a malformed package
+ LOBBY_FULL, // Max capacity reached, connection denied
+ LOBBY_PRIVATE, // Lobby is private, client was not invited
+ CLIENT_BANNED, // Server has banned this client from joining
+ HOST_STOPPED_HOSTING, // Server has stopped hosting
+ MISSING_FILES, // Joining client is missing files
+ EDITOR_FORBIDDEN, // Client tried to send editor instructions
+ CLIENT_OUTDATED, // The client is on a lower version than the host
+ SERVER_OUTDATED, // The host runs a lower version than the client
+ MOD_MISMATCH, // Host and Client have different mods enabled
+};
+
+class ConnectionClosedReasonUtil {
+public:
+ static std::string GetErrorMessage(ConnectionClosedReason reason) {
+ switch (reason) {
+ case ConnectionClosedReason::UNSPECIFIED: return "No error specified!";
+ case ConnectionClosedReason::BAD_REQUEST: return "Host has received a malformed request and has closed the connection with you!";
+ case ConnectionClosedReason::LOBBY_FULL: return "This lobby is full!";
+ case ConnectionClosedReason::LOBBY_PRIVATE: return "This lobby is private, ask for an invite!";
+ case ConnectionClosedReason::CLIENT_BANNED: return "You've been banned from this lobby";
+ case ConnectionClosedReason::HOST_STOPPED_HOSTING: return "The host has stopped hosting!";
+ case ConnectionClosedReason::MISSING_FILES: return "You are missing files, make sure to have the right mods enabled!";
+ case ConnectionClosedReason::EDITOR_FORBIDDEN: return "You are not allowed to use the editor in this lobby!";
+ case ConnectionClosedReason::CLIENT_OUTDATED: return "Your game version is too old, upgrade your game! Unable to join!";
+ case ConnectionClosedReason::SERVER_OUTDATED: return "The host is playing on an older version of the game! Unable to join!";
+ case ConnectionClosedReason::MOD_MISMATCH: return "You are not using the same mods the host is! The host has received a full list of expected mods!";
+
+ default:
+ LOGE << "Unspecified error code in ConnectionClosedReason::GetErrorMessage: " + std::to_string(static_cast<int>(reason)) << std::endl;
+ return "";
+ }
+ }
+
+ static bool IsUnusualReason(ConnectionClosedReason reason) {
+ return static_cast<int>(reason) >= static_cast<int>(ConnectionClosedReason::BAD_REQUEST);
+ }
+};
diff --git a/Source/Game/detailobjectlayer.cpp b/Source/Game/detailobjectlayer.cpp
new file mode 100644
index 00000000..9c3f9629
--- /dev/null
+++ b/Source/Game/detailobjectlayer.cpp
@@ -0,0 +1,102 @@
+//-----------------------------------------------------------------------------
+// Name: detailobjectlayer.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "detailobjectlayer.h"
+
+#include <Asset/Asset/character.h>
+#include <Asset/Asset/material.h>
+
+#include <XML/xml_helper.h>
+#include <Internal/filesystem.h>
+#include <Graphics/shaders.h>
+#include <Scripting/scriptfile.h>
+#include <Logging/logdata.h>
+
+#include <tinyxml.h>
+
+#include <map>
+#include <string>
+#include <cmath>
+
+DetailObjectLayer ReadDetailObjectLayerXML( const TiXmlElement* detail_object_element) {
+ DetailObjectLayer detail_object_layer;
+ detail_object_layer.obj_path = SanitizePath(detail_object_element->Attribute("obj_path"));
+ {
+ const char *str = detail_object_element->Attribute("weight_path");
+ if(str){
+ detail_object_layer.weight_path = SanitizePath(detail_object_element->Attribute("weight_path"));
+ }
+ }
+ detail_object_element->QueryFloatAttribute("density", &detail_object_layer.density);
+ detail_object_element->QueryFloatAttribute("normal_conform", &detail_object_layer.normal_conform);
+ detail_object_element->QueryFloatAttribute("min_embed", &detail_object_layer.min_embed);
+ detail_object_element->QueryFloatAttribute("max_embed", &detail_object_layer.max_embed);
+ detail_object_element->QueryFloatAttribute("min_scale", &detail_object_layer.min_scale);
+ detail_object_element->QueryFloatAttribute("max_scale", &detail_object_layer.max_scale);
+ detail_object_element->QueryFloatAttribute("view_distance", &detail_object_layer.view_dist);
+ detail_object_element->QueryFloatAttribute("overbright", &detail_object_layer.overbright);
+ detail_object_element->QueryFloatAttribute("jitter_degrees", &detail_object_layer.jitter_degrees);
+ detail_object_element->QueryFloatAttribute("tint_weight", &detail_object_layer.tint_weight);
+ {
+ const char *chr = detail_object_element->Attribute("collision_type");
+ if(chr){
+ std::string str(chr);
+ if(str == "static") {
+ detail_object_layer.collision_type = _static;
+ } else if(str == "plant") {
+ detail_object_layer.collision_type = _plant;
+ }
+ }
+ }
+ return detail_object_layer;
+}
+
+bool DetailObjectLayer::operator==( const DetailObjectLayer& other ) const {
+ return obj_path == other.obj_path && weight_path == other.weight_path &&
+ density == other.density && min_embed == other.min_embed &&
+ max_embed == other.max_embed && min_scale == other.min_scale &&
+ max_scale == other.max_scale && view_dist == other.view_dist &&
+ normal_conform == other.normal_conform &&
+ overbright == other.overbright &&
+ jitter_degrees == other.jitter_degrees;
+}
+
+DetailObjectLayer::DetailObjectLayer():
+ density(1.0f),
+ normal_conform(0.0f),
+ min_embed(0.0f),
+ max_embed(0.0f),
+ min_scale(1.0f),
+ max_scale(1.0f),
+ view_dist(30.0f),
+ jitter_degrees(0.0f),
+ overbright(0.0f),
+ tint_weight(1.0f),
+ collision_type(_none)
+{
+}
+
+void DetailObjectLayer::ReturnPaths( PathSet& path_set ) {
+ path_set.insert("object "+obj_path);
+ path_set.insert("image_sample "+weight_path);
+}
diff --git a/Source/Game/detailobjectlayer.h b/Source/Game/detailobjectlayer.h
new file mode 100644
index 00000000..a435beed
--- /dev/null
+++ b/Source/Game/detailobjectlayer.h
@@ -0,0 +1,56 @@
+//-----------------------------------------------------------------------------
+// Name: detailobjectlayer.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+
+#include <Math/vec3.h>
+#include <Graphics/palette.h>
+
+#include <string>
+#include <vector>
+
+enum CollisionType {_none, _static, _plant};
+
+struct DetailObjectLayer {
+ DetailObjectLayer();
+ std::string obj_path;
+ std::string weight_path;
+ float density;
+ float normal_conform;
+ float min_embed;
+ float max_embed;
+ float min_scale;
+ float max_scale;
+ float view_dist;
+ float jitter_degrees;
+ float overbright;
+ float tint_weight;
+ CollisionType collision_type;
+ bool operator==( const DetailObjectLayer& other ) const;
+ void ReturnPaths(PathSet& path_set);
+};
+
+DetailObjectLayer ReadDetailObjectLayerXML( const TiXmlElement* detail_object_element);
diff --git a/Source/Game/hardcoded_assets.h b/Source/Game/hardcoded_assets.h
new file mode 100644
index 00000000..5a201d4f
--- /dev/null
+++ b/Source/Game/hardcoded_assets.h
@@ -0,0 +1,45 @@
+//-----------------------------------------------------------------------------
+// Name: hardcoded_assets.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+namespace HardcodedPaths {
+
+enum {
+ kRetargetFile,
+ kLightProbe,
+ kMonoFontPath,
+ kDynamicFontPath,
+ kPointLight,
+ kNumAssets
+};
+
+static const char* paths[kNumAssets] = {
+ "Data/Animations/retarget.xml",
+ "Data/Models/engine/lightprobe.obj",
+ "Data/Fonts/Inconsolata.otf",
+ "Data/Fonts/OpenSans-Regular.ttf",
+ "Data/Models/engine/light_point.obj",
+};
+
+} // namespace "HardcodedPaths"
diff --git a/Source/Game/level.cpp b/Source/Game/level.cpp
new file mode 100644
index 00000000..72aacc43
--- /dev/null
+++ b/Source/Game/level.cpp
@@ -0,0 +1,999 @@
+//-----------------------------------------------------------------------------
+// Name: level.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "level.h"
+
+#include <Scripting/angelscript/ascontext.h>
+#include <Scripting/angelscript/asfuncs.h>
+#include <Scripting/angelscript/add_on/scriptarray/scriptarray.h>
+#include <Scripting/scriptfile.h>
+#include <Scripting/angelscript/ascollisions.h>
+
+#include <Game/characterscript.h>
+#include <Game/savefile.h>
+#include <Game/savefile_script.h>
+
+#include <Objects/hotspot.h>
+#include <Objects/movementobject.h>
+#include <Objects/placeholderobject.h>
+
+#include <Main/scenegraph.h>
+#include <Main/engine.h>
+
+#include <Internal/comma_separated_list.h>
+#include <Internal/common.h>
+#include <Internal/filesystem.h>
+#include <Internal/levelxml.h>
+#include <Internal/profiler.h>
+#include <Internal/modloading.h>
+#include <Internal/locale.h>
+
+#include <Graphics/pxdebugdraw.h>
+#include <GUI/IMUI/imui_script.h>
+#include <Logging/logdata.h>
+#include <Online/online.h>
+
+extern std::string script_dir_path;
+
+const char* Level::DEFAULT_ENEMY_SCRIPT = "enemycontrol.as";
+const char* Level::DEFAULT_PLAYER_SCRIPT = "playercontrol.as";
+
+Level::Level() :
+ metaDataDirty( false )
+{}
+
+Level::~Level() {
+ Dispose();
+}
+
+void Level::Dispose() {
+ Message("dispose_level");
+
+ for( uint32_t i = 0; i < as_contexts_.size(); i++ ) {
+ delete as_contexts_[i].ctx;
+ as_contexts_[i].ctx = NULL;
+ }
+ as_contexts_.clear();
+
+ Engine::Instance()->GetASNetwork()->DeRegisterASNetworkCallback(this);
+
+ old_col_map_.clear();
+ for(int i=0; i<kMaxTextElements; ++i){
+ text_elements[i].in_use = false;
+ text_elements[i].text_canvas_texture.Reset();
+ }
+}
+
+Path Level::FindScript(const std::string& path) {
+ Path out_path = FindFilePath(path, kDataPaths | kModPaths, false);
+ if(out_path.isValid() == false){
+ out_path = FindFilePath(script_dir_path + path, kDataPaths | kModPaths, false);
+ }
+
+ return out_path;
+}
+
+void Level::StartDialogue(const std::string& dialogue) const {
+ ASArglist args;
+ args.AddObject((void*)&dialogue);
+ for (unsigned i = 0; i < as_contexts_.size(); i++) {
+ if (as_contexts_[i].ctx->HasFunction(as_contexts_[i].as_funcs.start_dialogue)) {
+ as_contexts_[i].ctx->CallScriptFunction(as_contexts_[i].as_funcs.start_dialogue);
+ }
+ }
+}
+
+void Level::RegisterMPCallbacks() const {
+ for (unsigned i = 0; i < as_contexts_.size(); i++) {
+ if (as_contexts_[i].ctx->HasFunction(as_contexts_[i].as_funcs.register_mp_callbacks)) {
+ as_contexts_[i].ctx->CallScriptFunction(as_contexts_[i].as_funcs.register_mp_callbacks);
+ }
+ }
+}
+
+void Level::Initialize(SceneGraph *scenegraph, GUI* gui) {
+ Dispose();
+
+ this->scenegraph = scenegraph;
+
+ Engine::Instance()->GetASNetwork()->RegisterASNetworkCallback(this);
+
+ Path script_path = FindFilePath(script_dir_path+"level.as", kDataPaths|kModPaths);
+
+ HookedASContext hasc;
+ hasc.path = script_path;
+ hasc.context_name = "main_level_script";
+ as_contexts_.push_back(hasc);
+
+ if(!level_specific_script_.empty()){
+ Path level_specific_script_path = FindScript(level_specific_script_);
+ if(level_specific_script_path.isValid() == false) {
+ DisplayError("Couldn't find file", "Couldn't find player script file, default will be used", ErrorType::_ok);
+ LOGE << "Could not find file specified in <Script> in level: " << level_specific_script_ << std::endl;
+ }
+
+ if( level_specific_script_path.isValid() ) {
+ HookedASContext hasc;
+ hasc.path = level_specific_script_path;
+ hasc.context_name = "level_specific";
+ as_contexts_.push_back(hasc);
+ }
+ }
+
+ if(!pc_script_.empty()){
+ Path pc_script_path = FindScript(pc_script_);
+ while(!pc_script_path.isValid() && DisplayError("Couldn't find file", "Couldn't find player script file, default will be used instead", ErrorType::_ok_cancel_retry) == ErrorResponse::_retry) {
+ pc_script_path = FindScript(pc_script_);
+ LOGE << "Could not find file specified in <PCScript> in level: " << pc_script_ << std::endl;
+ }
+
+ if(!pc_script_path.isValid())
+ pc_script_ = "";
+ }
+
+ if(!npc_script_.empty()){
+ Path npc_script_path = FindScript(npc_script_);
+ while(!npc_script_path.isValid() && DisplayError("Couldn't find file", "Couldn't find enemy script file, default will be used instead", ErrorType::_ok_cancel_retry) == ErrorResponse::_retry) {
+ npc_script_path = FindScript(npc_script_);
+ LOGE << "Could not find file specified in <NPCScript> in level: " << npc_script_ << std::endl;
+ }
+
+ if(!npc_script_path.isValid())
+ npc_script_ = "";
+ }
+
+ if(!npc_script_.empty()) {
+ for(size_t i = 0; i < scenegraph->movement_objects_.size(); ++i) {
+ MovementObject* obj = (MovementObject*)scenegraph->movement_objects_[i];
+ if(obj->GetNPCObjectScript().empty()) {
+ obj->ChangeControlScript(npc_script_);
+ }
+ }
+ }
+
+ std::vector<std::string> mod_level_hooks = ModLoading::Instance().ActiveLevelHooks();
+ std::vector<std::string>::iterator modlhit = mod_level_hooks.begin();
+ for(; modlhit != mod_level_hooks.end(); modlhit++) {
+ HookedASContext hasc;
+ Path level_path = FindFilePath(modlhit->c_str(), kDataPaths | kModPaths);
+ if(level_path.isValid()){
+ hasc.path = level_path;
+ hasc.context_name = "mod_level_hook";
+ as_contexts_.push_back(hasc);
+ } else {
+ LOGE << "Unable to find level script file: " << *modlhit << std::endl;
+ }
+ }
+
+ ASData as_data;
+ as_data.scenegraph = scenegraph;
+ as_data.gui = gui;
+
+ as_collisions.reset(new ASCollisions(scenegraph));
+
+ for(unsigned i = 0; i < as_contexts_.size(); i++) {
+ ASContext* ctx = new ASContext(as_contexts_[i].context_name.c_str(),as_data);
+
+ AttachASNetwork(ctx);
+ AttachUIQueries(ctx);
+ AttachActiveCamera(ctx);
+ AttachScreenWidth(ctx);
+ AttachPhysics(ctx);
+ AttachEngine(ctx);
+ AttachScenegraph(ctx, scenegraph);
+ AttachMessages(ctx);
+ AttachStringConvert(ctx);
+ AttachTextCanvasTextureToASContext(ctx);
+ AttachLevel(ctx);
+ AttachInterlevelData(ctx);
+ AttachIMUI(ctx);
+ AttachPlaceholderObject(ctx);
+ AttachTokenIterator(ctx);
+ AttachSimpleFile(ctx);
+ AttachLocale(ctx);
+ AttachUndo(ctx);
+ AttachIMGUI(ctx);
+ AttachIMGUIModding(ctx);
+ AttachOnline(ctx);
+ hud_images.AttachToContext(ctx);
+ AttachSaveFile(ctx, &Engine::Instance()->save_file_);
+ character_script_getter_.AttachToScript(ctx, "character_getter");
+ as_collisions->AttachToContext(ctx);
+
+ as_contexts_[i].as_funcs.init = ctx->RegisterExpectedFunction("void Init(string level_name)", true);
+ as_contexts_[i].as_funcs.update = ctx->RegisterExpectedFunction("void Update(int is_paused)", false);
+ as_contexts_[i].as_funcs.update_deprecated = ctx->RegisterExpectedFunction("void Update()", false);
+
+ as_contexts_[i].as_funcs.hotspot_exit = ctx->RegisterExpectedFunction("void HotspotExit(string event, MovementObject @mo)", false);
+ as_contexts_[i].as_funcs.hotspot_enter = ctx->RegisterExpectedFunction("void HotspotEnter(string event, MovementObject @mo)", false);
+ as_contexts_[i].as_funcs.receive_message = ctx->RegisterExpectedFunction("void ReceiveMessage(string message)", false);
+ as_contexts_[i].as_funcs.draw_gui = ctx->RegisterExpectedFunction("void DrawGUI()", false);
+ as_contexts_[i].as_funcs.draw_gui2 = ctx->RegisterExpectedFunction("void DrawGUI2()", false);
+ as_contexts_[i].as_funcs.draw_gui3 = ctx->RegisterExpectedFunction("void DrawGUI3()", false);
+ as_contexts_[i].as_funcs.has_focus = ctx->RegisterExpectedFunction("bool HasFocus()", false);
+ as_contexts_[i].as_funcs.dialogue_camera_control = ctx->RegisterExpectedFunction("bool DialogueCameraControl()", false);
+ as_contexts_[i].as_funcs.save_history_state = ctx->RegisterExpectedFunction("void SaveHistoryState(SavedChunk@ chunk)", false);
+ as_contexts_[i].as_funcs.read_chunk = ctx->RegisterExpectedFunction("void ReadChunk(SavedChunk@ chunk)", false);
+ as_contexts_[i].as_funcs.set_window_dimensions = ctx->RegisterExpectedFunction("void SetWindowDimensions(int width, int height)", false);
+ as_contexts_[i].as_funcs.incoming_tcp_data = ctx->RegisterExpectedFunction("void IncomingTCPData(uint socket, array<uint8>@ data)", false);
+
+ as_contexts_[i].as_funcs.pre_script_reload = ctx->RegisterExpectedFunction("void PreScriptReload()", false);
+ as_contexts_[i].as_funcs.post_script_reload = ctx->RegisterExpectedFunction("void PostScriptReload()", false);
+
+ as_contexts_[i].as_funcs.menu = ctx->RegisterExpectedFunction("void Menu()", false);
+ as_contexts_[i].as_funcs.register_mp_callbacks = ctx->RegisterExpectedFunction("void RegisterMPCallBacks()", false);
+ as_contexts_[i].as_funcs.start_dialogue = ctx->RegisterExpectedFunction("void StartDialogue(const string &in name", false);
+
+ ctx->LoadScript(as_contexts_[i].path);
+
+ as_contexts_[i].ctx = ctx;
+ }
+
+ for(unsigned i = 0; i < as_contexts_.size(); i++) {
+ LOGI << "Calling void Init(string level_name) for level hook file." << std::endl;
+ ASArglist args;
+ args.AddObject(&scenegraph->level_name_);
+ as_contexts_[i].ctx->CallScriptFunction(as_contexts_[i].as_funcs.init, &args);
+
+ }
+
+ RegisterMPCallbacks();
+
+ PROFILER_ENTER(g_profiler_ctx, "Exporting docs");
+ char path[kPathSize];
+ FormatString(path, kPathSize, "%saslevel_docs.h", GetWritePath(CoreGameModID).c_str());
+ as_contexts_[0].ctx->ExportDocs(path);
+ PROFILER_LEAVE(g_profiler_ctx);
+}
+
+void Level::Update(bool paused) {
+ ASArglist args;
+ ASArglist args2;
+ args.Add(paused?1:0);
+ std::vector<HookedASContext >::iterator cit = as_contexts_.begin();
+ PROFILER_ENTER(g_profiler_ctx, "Level script Update()");
+ for( unsigned i = 0; i < as_contexts_.size(); i++ ) {
+ if( as_contexts_[i].ctx->CallScriptFunction(as_contexts_[i].as_funcs.update, &args) == false) {
+ if(paused == false) {
+ as_contexts_[i].ctx->CallScriptFunction(as_contexts_[i].as_funcs.update_deprecated, &args2);
+ }
+ }
+ }
+
+ Online* online = Online::Instance();
+ if (online->IsActive()) {
+ AngelScriptUpdate update;
+ if (online->GetIfPendingAngelScriptUpdates()) {
+ update = online->GetAngelScriptUpdate();
+
+ std::vector<char>& temp = update.data;
+
+ bool angelscriptCallSuccesfull = false;
+
+ for (unsigned i = 0; i < as_contexts_.size(); i++) {
+ if (as_contexts_[i].ctx->CallMPCallBack(update.state, temp) ) {
+ angelscriptCallSuccesfull = true;
+ }
+ }
+
+ if (angelscriptCallSuccesfull) {
+ online->MoveAngelScriptQueueForward();
+ }
+ }
+
+ if (online->GetIfPendingAngelScriptStates()) {
+
+ update = online->GetAngelScriptStates();
+ std::vector<char>& temp = update.data;
+
+ bool angelscriptCallSuccesfull = false;
+
+ for (unsigned i = 0; i < as_contexts_.size(); i++) {
+ if (as_contexts_[i].ctx->CallMPCallBack(update.state, temp)) {
+ angelscriptCallSuccesfull = true;
+ }
+ }
+
+ if (angelscriptCallSuccesfull) {
+ online->MoveAngelStateQueueForward();
+ }
+ }
+ }
+
+ PROFILER_LEAVE(g_profiler_ctx);
+}
+
+void Level::LiveUpdateCheck() {
+ std::vector<HookedASContext>::iterator cit = as_contexts_.begin();
+ for(; cit != as_contexts_.end(); cit++ ) {
+ cit->ctx->CallScriptFunction(cit->as_funcs.pre_script_reload);
+ cit->ctx->Reload();
+ cit->ctx->CallScriptFunction(cit->as_funcs.post_script_reload);
+ }
+}
+
+void Level::HandleCollisions( CollisionPtrMap &col_map, SceneGraph &scenegraph ) {
+ exit_call_queue.clear();
+ enter_call_queue.clear();
+
+ // Convert old collision ids into pointers
+ {
+ PROFILER_ZONE(g_profiler_ctx, "A");
+ old_col_ptr_map.clear();
+ {
+ CollisionMap &map = old_col_map_;
+ for(CollisionMap::iterator iter = map.begin(); iter != map.end(); ++iter){
+ int id = iter->first;
+ Object* obj_a_ptr = scenegraph.GetObjectFromID(id);
+ if(obj_a_ptr){
+ CollisionSet &set = iter->second;
+ for(CollisionSet::iterator iter = set.begin(); iter != set.end(); ++iter){
+ int id = *iter;
+ Object* obj_b_ptr = scenegraph.GetObjectFromID(id);
+ if(obj_b_ptr){
+ old_col_ptr_map[obj_a_ptr].insert(obj_b_ptr);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "B");
+ // Check for collisions that used to be happening, and are now gone
+ CollisionPtrMap::iterator oci = old_col_ptr_map.begin();
+ for(;oci != old_col_ptr_map.end(); ++oci){
+ CollisionPtrMap::iterator ci = col_map.find(oci->first);
+ Object* obj_a = (Object*)oci->first;
+ CollisionPtrSet::iterator oci2 = oci->second.begin();
+ for(;oci2 != oci->second.end(); ++oci2){
+ Object* obj_b = (Object*)(*oci2);
+ bool not_found = false;
+ if(ci == col_map.end()){
+ not_found = true;
+ } else {
+ CollisionPtrSet::iterator ci2 = ci->second.find(*oci2);
+ if(ci2 == ci->second.end()){
+ not_found = true;
+ }
+ }
+ if(!not_found){
+ continue;
+ }
+ Object* temp_obj_a = obj_a;
+ if(obj_b->GetType() == _hotspot_object){
+ std::swap(temp_obj_a, obj_b);
+ }
+ if(temp_obj_a->GetType() == _hotspot_object && obj_b->GetType() == _movement_object){
+ Hotspot* hotspot = (Hotspot*)temp_obj_a;
+ MovementObject* mo = (MovementObject*)obj_b;
+
+ exit_call_queue.push_back(std::pair<Hotspot*,MovementObject*>(hotspot,mo));
+ }
+ if(temp_obj_a->GetType() == _hotspot_object && obj_b->GetType() == _item_object){
+ Hotspot* hotspot = (Hotspot*)temp_obj_a;
+ ItemObject* obj = (ItemObject*)obj_b;
+ hotspot->HandleEventItem("exit", obj);
+ }
+ }
+ }
+ }
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "C");
+ // Check for collisions that now exist
+ CollisionPtrMap::iterator ci = col_map.begin();
+ for(;ci != col_map.end(); ++ci){
+ CollisionPtrMap::iterator oci = old_col_ptr_map.find(ci->first);
+ Object* obj_a = (Object*)ci->first;
+ std::set<void*>::iterator ci2 = ci->second.begin();
+ for(;ci2 != ci->second.end(); ++ci2){
+ Object* obj_b = (Object*)(*ci2);
+ if(obj_a->GetType() == _movement_object && obj_b->GetType() == _movement_object){
+ ((MovementObject*)obj_a)->CollideWith((MovementObject*)obj_b);
+ continue;
+ }
+
+ bool not_found = false;
+ if(oci == old_col_ptr_map.end()){
+ not_found = true;
+ } else {
+ std::set<void*>::iterator oci2 = oci->second.find(*ci2);
+ if(oci2 == oci->second.end()){
+ not_found = true;
+ }
+ }
+ if(!not_found){
+ continue;
+ }
+ Object* temp_obj_a = obj_a;
+ if(obj_b->GetType() == _hotspot_object){
+ std::swap(temp_obj_a, obj_b);
+ }
+ if(temp_obj_a->GetType() == _hotspot_object && obj_b->GetType() == _movement_object){
+ Hotspot* hotspot = (Hotspot*)temp_obj_a;
+ MovementObject* mo = (MovementObject*)obj_b;
+ enter_call_queue.push_back(std::pair<Hotspot*,MovementObject*>(hotspot,mo));
+ }
+ if(temp_obj_a->GetType() == _hotspot_object && obj_b->GetType() == _item_object){
+ Hotspot* hotspot = (Hotspot*)temp_obj_a;
+ ItemObject* obj = (ItemObject*)obj_b;
+ hotspot->HandleEventItem("enter", obj);
+ }
+ }
+ }
+ }
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "D");
+ // Convert old collision pointers to ids
+ CollisionPtrMap &map = col_map;
+ old_col_map_.clear();
+ for(CollisionPtrMap::iterator iter = map.begin(); iter != map.end(); ++iter){
+ int id_a = ((Object*)iter->first)->GetID();
+ CollisionPtrSet &set = iter->second;
+ for(CollisionPtrSet::iterator iter = set.begin(); iter != set.end(); ++iter){
+ int id_b = ((Object*)(*iter))->GetID();
+ old_col_map_[id_a].insert(id_b);
+ }
+ }
+ }
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "E");
+ //Do the delayed calls
+ std::vector<std::pair<Hotspot*,MovementObject*> >::iterator exit_call_queue_it = exit_call_queue.begin();
+ for(;exit_call_queue_it != exit_call_queue.end(); exit_call_queue_it++) {
+ exit_call_queue_it->first->HandleEvent("exit", exit_call_queue_it->second);
+ ASArglist args;
+ args.AddObject((void*)&exit_call_queue_it->first->GetScriptFile());
+ args.AddAddress((void*)exit_call_queue_it->second);
+
+ for(unsigned i = 0; i < as_contexts_.size(); i++ ) {
+ as_contexts_[i].ctx->CallScriptFunction(as_contexts_[i].as_funcs.hotspot_exit, &args);
+ }
+ }
+
+ std::vector<std::pair<Hotspot*,MovementObject*> >::iterator enter_call_queue_it = enter_call_queue.begin();
+ for(;enter_call_queue_it != enter_call_queue.end(); enter_call_queue_it++) {
+ enter_call_queue_it->first->HandleEvent("enter", enter_call_queue_it->second);
+ ASArglist args;
+ args.AddObject((void*)&enter_call_queue_it->first->GetScriptFile());
+ args.AddAddress((void*)enter_call_queue_it->second);
+
+ for(unsigned i = 0; i < as_contexts_.size(); i++ ) {
+ as_contexts_[i].ctx->CallScriptFunction(as_contexts_[i].as_funcs.hotspot_enter, &args);
+ }
+ }
+ }
+}
+
+bool Level::HasFunction(const std::string& function_definition) {
+ bool result = false;
+
+ if(as_contexts_.size() > 0) {
+ result = as_contexts_[0].ctx->HasFunction(function_definition);
+ }
+
+ return result;
+}
+
+int Level::QueryIntFunction( const std::string &func ) {
+ ASArglist args;
+ int val;
+ ASArg return_val;
+ return_val.type = _as_int;
+ return_val.data = &val;
+ if( as_contexts_.size() > 0 ) {
+ as_contexts_[0].ctx->CallScriptFunction(func, &args, &return_val);
+ }
+ return val;
+}
+
+void Level::Execute( std::string code ) {
+ if( as_contexts_.size() > 0 ) {
+ as_contexts_[0].ctx->Execute(code);
+ }
+}
+
+void Level::Message( const std::string &msg ) {
+ Online* online = Online::Instance();
+ ASArglist args;
+ args.AddObject((void*)&msg);
+
+ if (online->IsHosting()) {
+ online->SendLevelMessage(msg);
+ }
+
+ for( unsigned i = 0; i < as_contexts_.size(); i++ ) {
+ as_contexts_[i].ctx->CallScriptFunction(as_contexts_[i].as_funcs.receive_message, &args);
+ }
+
+ for(int i=0, len=level_event_receivers.size(); i<len; ++i){
+ Object* obj = scenegraph->GetObjectFromID(level_event_receivers[i]);
+ if(obj){
+ obj->ReceiveScriptMessage("level_event " + msg);
+ } else {
+ const int kBufSize = 256;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "Could not find level event receiver with id %d", level_event_receivers[i]);
+ DisplayError("Error", buf);
+ }
+ }
+}
+
+int GetNumParamElements(const ScriptParams &sp, const char* name){
+ const ScriptParamMap &spm = sp.GetParameterMap();
+ ScriptParamMap::const_iterator spm_iter = spm.find(name);
+ if(spm_iter == spm.end()){
+ return 0;
+ }
+ CSLIterator iter(spm_iter->second.GetString());
+ int num = 0;
+ while(iter.GetNext(NULL)){
+ ++num;
+ }
+ return num;
+}
+
+int Level::GetNumObjectives() {
+ return GetNumParamElements(sp_, "Objectives");
+}
+
+int Level::GetNumAchievements() {
+ return GetNumParamElements(sp_, "Achievements");
+}
+
+void GetParamElement(const ScriptParams &sp, const char* name, int which, std::string &str){
+ if(which >= 0) {
+ CSLIterator iter(sp.GetStringVal(name));
+ int num = 0;
+ while(iter.GetNext(&str)) {
+ if(num == which){
+ return;
+ }
+ ++num;
+ }
+ }
+ FatalError("Error", "There is no \"%s\" id: %d", name, which);
+ return;
+}
+
+std::string Level::GetObjective(int which) {
+ std::string str;
+ GetParamElement(sp_, "Objectives", which, str);
+ return str;
+}
+
+std::string Level::GetAchievement(int which) {
+ std::string str;
+ GetParamElement(sp_, "Achievements", which, str);
+ return str;
+}
+
+// Set's the script for this level (no path)
+void Level::SetLevelSpecificScript( std::string& script_name ) {
+ metaDataDirty = true;
+ level_specific_script_ = script_name;
+}
+
+void Level::SetPCScript( std::string& script_name ) {
+ metaDataDirty = true;
+ pc_script_ = script_name;
+}
+
+void Level::SetNPCScript( std::string& script_name ) {
+ metaDataDirty = true;
+ npc_script_ = script_name;
+}
+
+void Level::ClearNPCScript() {
+ metaDataDirty = true;
+ npc_script_ = "";
+}
+
+void Level::ClearPCScript() {
+ metaDataDirty = true;
+ pc_script_ = "";
+}
+
+std::string Level::GetLevelSpecificScript() {
+ return level_specific_script_;
+}
+
+std::string Level::GetPCScript(const MovementObject* object) {
+ if(object) {
+ if(!object->GetPCObjectScript().empty()) {
+ return object->GetPCObjectScript();
+ } else if(!pc_script_.empty()) {
+ return pc_script_;
+ } else {
+ return "playercontrol.as";
+ }
+ } else {
+ return pc_script_.empty() ? DEFAULT_PLAYER_SCRIPT : pc_script_;
+ }
+}
+
+std::string Level::GetNPCScript(const MovementObject* object) {
+ if(object) {
+ if(!object->GetNPCObjectScript().empty()) { // First is per-object script
+ return object->GetNPCObjectScript();
+ } else if(!npc_script_.empty()) { // Then per-level script
+ return npc_script_;
+ } else if(!object->GetActorScript().empty()) { // Then actor script
+ return object->GetActorScript();
+ } else { // Finally the default script
+ return "enemycontrol.as";
+ }
+ } else {
+ return npc_script_.empty() ? DEFAULT_ENEMY_SCRIPT : npc_script_;
+ }
+}
+
+// Return the id of an unused text element, or -1 if can't find one
+int Level::CreateTextElement() {
+ // Return first unused text element, and mark it as in use
+ for(int i=0; i<kMaxTextElements; ++i){
+ if(!text_elements[i].in_use){
+ text_elements[i].in_use = true;
+ LOGD << "Allocated Text Element: " << i << std::endl;
+ return i;
+ }
+ }
+ FatalError("Error", "Too many level text elements allocated");
+ return -1;
+}
+
+// Free a text element so someone else can use it, and dispose of its resources
+void Level::DeleteTextElement(int which) {
+ LOGD << "Freed Text Element: " << which << std::endl;
+ text_elements[which].in_use = false;
+ text_elements[which].text_canvas_texture.Reset();
+}
+
+// Get a pointer to a given text element canvas texture
+TextCanvasTexture* Level::GetTextElement(int which) {
+ return &text_elements[which].text_canvas_texture;
+}
+
+namespace {
+ bool GetLevelBoundaries(Level* level) {
+ return level->script_params().ASGetInt("Level Boundaries") == 1;
+ }
+}
+
+static ScriptParams* ASGetScriptParams(Level* level){
+ return &level->script_params();
+}
+
+static bool ASHasVar( Level* level, const std::string &name ) {
+ bool result = false;
+ if( level->as_contexts_.size() > 0 ) {
+ result = level->as_contexts_[0].ctx->GetVarPtr(name.c_str()) != NULL;
+ }
+ return result;
+}
+
+static int ASGetIntVar( Level* level, const std::string &name ) {
+ if( level->as_contexts_.size() > 0 ) {
+ return *((int*)level->as_contexts_[0].ctx->GetVarPtr(name.c_str()));
+ }
+ return 0;
+}
+
+static int ASGetArrayIntVar( Level* level, const std::string &name, int index ) {
+ if( level->as_contexts_.size() > 0 ) {
+ return *((int*)level->as_contexts_[0].ctx->GetArrayVarPtr(name, index));
+ }
+ return 0;
+}
+
+static float ASGetFloatVar( Level* level, const std::string &name ) {
+ if( level->as_contexts_.size() > 0 ) {
+ return *((float*)level->as_contexts_[0].ctx->GetVarPtr(name.c_str()));
+ }
+ return 0.0f;
+}
+
+static bool ASGetBoolVar( Level* level, const std::string &name ) {
+ if( level->as_contexts_.size() > 0 ) {
+ return *((bool*)level->as_contexts_[0].ctx->GetVarPtr(name.c_str()));
+ }
+ return false;
+}
+
+static void ASSetIntVar( Level* level, const std::string &name, int value ) {
+ if( level->as_contexts_.size() > 0 ) {
+ *(int*)level->as_contexts_[0].ctx->GetVarPtr(name.c_str()) = value;
+ }
+}
+
+static void ASSetArrayIntVar( Level* level, const std::string &name, int index, int value ) {
+ if( level->as_contexts_.size() > 0 ) {
+ *(int*)level->as_contexts_[0].ctx->GetArrayVarPtr(name, index) = value;
+ }
+}
+
+static void ASSetFloatVar( Level* level, const std::string &name, float value ) {
+ if( level->as_contexts_.size() > 0 ) {
+ *(float*)level->as_contexts_[0].ctx->GetVarPtr(name.c_str()) = value;
+ }
+}
+
+static void ASSetBoolVar( Level* level, const std::string &name, bool value ) {
+ if( level->as_contexts_.size() > 0 ) {
+ *(bool*)level->as_contexts_[0].ctx->GetVarPtr(name.c_str()) = value;
+ }
+}
+
+static bool ASWaitingForInput() {
+ return Engine::Instance()->waiting_for_input_;
+}
+
+void Level::DefineLevelTypePublic(ASContext *as_context) {
+ as_context->RegisterObjectType("Level", 0, asOBJ_REF | asOBJ_NOCOUNT);
+ as_context->RegisterObjectMethod("Level", "bool HasFunction(const string &in function_definition)", asMETHOD(Level,HasFunction), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Level", "int QueryIntFunction(const string &in function)", asMETHOD(Level,QueryIntFunction), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Level", "int GetNumObjectives()", asMETHOD(Level,GetNumObjectives), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Level", "int GetNumAchievements()", asMETHOD(Level,GetNumAchievements), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Level", "string GetObjective(int which)", asMETHOD(Level,GetObjective), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Level", "string GetAchievement(int which)", asMETHOD(Level,GetAchievement), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Level", "void SendMessage(const string& in message)", asMETHOD(Level,Message), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Level", "void Execute(string code)", asMETHOD(Level,Execute), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Level", "const string& GetPath(const string& in key)", asMETHOD(Level,GetPath), asCALL_THISCALL, "Get path from paths xml file");
+ as_context->RegisterObjectMethod("Level", "bool HasFocus()", asMETHOD(Level,HasFocus), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Level", "bool DialogueCameraControl()", asMETHOD(Level,DialogueCameraControl), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Level", "bool LevelBoundaries()", asFUNCTION(GetLevelBoundaries), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("Level", "int CreateTextElement()", asMETHOD(Level,CreateTextElement), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Level", "void DeleteTextElement(int which)", asMETHOD(Level,DeleteTextElement), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Level", "TextCanvasTexture@ GetTextElement(int which)", asMETHOD(Level,GetTextElement), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Level", "void GetCollidingObjects(int id, array<int> @array)", asMETHOD(Level,GetCollidingObjects), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Level", "void ReceiveLevelEvents(int id)", asMETHOD(Level, ReceiveLevelEvents), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Level", "void StopReceivingLevelEvents(int id)", asMETHOD(Level, StopReceivingLevelEvents), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Level", "bool HasVar(const string &in name)", asFUNCTION(ASHasVar), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("Level", "int GetIntVar(const string &in name)", asFUNCTION(ASGetIntVar), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("Level", "int GetArrayIntVar(const string &in name, int index)", asFUNCTION(ASGetArrayIntVar), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("Level", "float GetFloatVar(const string &in name)", asFUNCTION(ASGetIntVar), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("Level", "bool GetBoolVar(const string &in name)", asFUNCTION(ASGetIntVar), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("Level", "void SetIntVar(const string &in name, int value)", asFUNCTION(ASSetIntVar), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("Level", "void SetArrayIntVar(const string &in name, int index, int value)", asFUNCTION(ASSetArrayIntVar), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("Level", "void SetFloatVar(const string &in name, float value)", asFUNCTION(ASSetIntVar), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("Level", "void SetBoolVar(const string &in name, bool value)", asFUNCTION(ASSetIntVar), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("Level", "bool WaitingForInput()", asFUNCTION(ASWaitingForInput), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("Level", "ScriptParams@ GetScriptParams()", asFUNCTION(ASGetScriptParams), asCALL_CDECL_OBJFIRST);
+ as_context->DocsCloseBrace();
+}
+
+
+void Level::GetCollidingObjects( int id, CScriptArray *array ) {
+ int collision[2];
+ for(CollisionMap::iterator iter = old_col_map_.begin(); iter != old_col_map_.end(); ++iter){
+ collision[0] = iter->first;
+ CollisionSet &set = iter->second;
+ for(CollisionSet::iterator iter = set.begin(); iter != set.end(); ++iter){
+ collision[1] = *iter;
+ if(collision[0] == id){
+ array->InsertLast(&collision[1]);
+ } else if(collision[1] == id){
+ array->InsertLast(&collision[0]);
+ }
+ }
+ }
+}
+
+void Level::Draw() {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Level::Draw");
+ for(unsigned i = 0; i < as_contexts_.size(); i++ ) {
+ as_contexts_[i].ctx->CallScriptFunction(as_contexts_[i].as_funcs.draw_gui);
+ }
+ hud_images.Draw();
+ for(unsigned i = 0; i < as_contexts_.size(); i++ ) {
+ as_contexts_[i].ctx->CallScriptFunction(as_contexts_[i].as_funcs.draw_gui2);
+ }
+ hud_images.Draw();
+ for(unsigned i = 0; i < as_contexts_.size(); i++ ) {
+ as_contexts_[i].ctx->CallScriptFunction(as_contexts_[i].as_funcs.draw_gui3);
+ }
+ hud_images.Draw();
+}
+
+void Level::SetFromLevelInfo( const LevelInfo &li ) {
+ level_specific_script_ = li.script_;
+ pc_script_ = li.pc_script_;
+ npc_script_ = li.npc_script_;
+ level_script_paths.clear();
+ for(int i=0, len=li.script_paths_.size(); i<len; ++i){
+ const LevelInfo::StrPair &script_path = li.script_paths_[i];
+ level_script_paths[script_path.first] = script_path.second;
+ }
+ recently_created_items_ = li.recently_created_items_;
+ nav_mesh_parameters_ = li.nav_mesh_parameters_;
+ loading_screen_ = li.loading_screen_;
+}
+
+std::string Level::GetAchievementsString() const {
+ return sp_.GetStringVal("Achievements");
+}
+
+bool Level::HasFocus() {
+ ASArglist args;
+ bool has_focus = false;
+ ASArg return_arg;
+ return_arg.data = &has_focus;
+ return_arg.type = _as_bool;
+ for(unsigned i = 0; i < as_contexts_.size(); i++ ) {
+ as_contexts_[i].ctx->CallScriptFunction(as_contexts_[i].as_funcs.has_focus, &args, &return_arg);
+ if(has_focus) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Level::DialogueCameraControl() {
+ bool dialogue_control = false;
+ ASArglist args;
+ ASArg return_arg;
+ return_arg.data = &dialogue_control;
+ return_arg.type = _as_bool;
+ for(unsigned i = 0; i < as_contexts_.size(); i++ ) {
+ as_contexts_[i].ctx->CallScriptFunction(as_contexts_[i].as_funcs.dialogue_camera_control, &args, &return_arg);
+ if(dialogue_control) {
+ return true;
+ }
+ }
+ return false;
+}
+
+ScriptParams& Level::script_params() {
+ return sp_;
+}
+
+void Level::SetScriptParams(const ScriptParamMap& spm) {
+ sp_.SetParameterMap(spm);
+}
+
+const std::string & Level::GetPath(const std::string& key) const {
+ StringMap::const_iterator iter = level_script_paths.find(key);
+ if(iter == level_script_paths.end()){
+ FatalError("Error", "No path found with key: %s", key.c_str());
+ }
+ return iter->second;
+}
+
+void Level::SaveHistoryState(std::list<SavedChunk> & chunks, int state_id) {
+ SavedChunk saved_chunk;
+ saved_chunk.obj_id = 0;
+ saved_chunk.type = ChunkType::LEVEL;
+
+ ASArglist args;
+ args.AddAddress(&saved_chunk);
+ for(unsigned i = 0; i < as_contexts_.size(); i++ ) {
+ as_contexts_[i].ctx->CallScriptFunction(as_contexts_[i].as_funcs.save_history_state, &args);
+ }
+ AddChunkToHistory(chunks, state_id, saved_chunk);
+}
+
+void Level::ReadChunk(const SavedChunk &the_chunk) {
+ ASArglist args;
+ args.AddAddress((void*)&the_chunk);
+
+ for(unsigned i = 0; i < as_contexts_.size(); i++ ) {
+ as_contexts_[i].ctx->CallScriptFunction(as_contexts_[i].as_funcs.read_chunk, &args);
+ }
+}
+
+bool Level::isMetaDataDirty() {
+ if(metaDataDirty) {
+ return true;
+ }
+
+ for(size_t i = 0; i < scenegraph->objects_.size(); ++i) {
+ if(scenegraph->objects_[i]->GetType() == _placeholder_object) {
+ PlaceholderObject* obj = (PlaceholderObject*)scenegraph->objects_[i];
+ if(obj->GetScriptParams()->HasParam("Dialogue")) {
+ if(obj->unsaved_changes) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+void Level::setMetaDataClean() {
+ metaDataDirty = false;
+ for(size_t i = 0; i < scenegraph->objects_.size(); ++i) {
+ if(scenegraph->objects_[i]->GetType() == _placeholder_object) {
+ ((PlaceholderObject*)scenegraph->objects_[i])->unsaved_changes = false;
+ }
+ }
+}
+
+void Level::setMetaDataDirty() {
+ metaDataDirty = true;
+}
+
+void Level::ReceiveLevelEvents(int id) {
+ level_event_receivers.push_back(id);
+}
+
+void Level::StopReceivingLevelEvents(int id) {
+ for(int i=0; i<(int)level_event_receivers.size(); ++i){
+ while(i<(int)level_event_receivers.size() && level_event_receivers[i] == id){
+ level_event_receivers[i] = level_event_receivers.back();
+ level_event_receivers.resize(level_event_receivers.size()-1);
+ }
+ }
+}
+
+void Level::WindowResized(ivec2 value ) {
+ ASArglist args;
+ args.Add(value[0]);
+ args.Add(value[1]);
+ for(unsigned i = 0; i < as_contexts_.size(); i++ ) {
+ as_contexts_[i].ctx->CallScriptFunction(as_contexts_[i].as_funcs.set_window_dimensions, &args);
+ }
+}
+
+void Level::PushSpawnerItemRecent(const SpawnerItem& item) {
+ std::vector<SpawnerItem>::iterator spawner_it = recently_created_items_.begin();
+ while( spawner_it != recently_created_items_.end()) {
+ if( *spawner_it == item ) {
+ recently_created_items_.erase(spawner_it);
+ spawner_it = recently_created_items_.begin();
+ } else {
+ ++spawner_it;
+ }
+ }
+ recently_created_items_.push_back(item);
+}
+
+std::vector<SpawnerItem> Level::GetRecentlyCreatedItems() {
+ return recently_created_items_;
+}
+
+void Level::IncomingTCPData(SocketID socket, uint8_t* data, size_t len) {
+ for(unsigned i = 0; i < as_contexts_.size(); i++ ) {
+ asIScriptEngine *engine = as_contexts_[i].ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoByDecl("array<uint8>");
+
+ CScriptArray* array = CScriptArray::Create(arrayType);
+ array->Reserve(len);
+
+ for( unsigned i = 0; i < len; i++ ) {
+ array->InsertLast(&data[i]);
+ }
+
+ ASArglist args;
+ args.Add(socket);
+ args.AddAddress((void*)array);
+
+ as_contexts_[i].ctx->CallScriptFunction(as_contexts_[i].as_funcs.incoming_tcp_data, &args, NULL);
+
+ array->Release();
+ }
+}
diff --git a/Source/Game/level.h b/Source/Game/level.h
new file mode 100644
index 00000000..d57cb072
--- /dev/null
+++ b/Source/Game/level.h
@@ -0,0 +1,193 @@
+//-----------------------------------------------------------------------------
+// Name: level.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Graphics/hudimage.h>
+#include <Graphics/text.h>
+
+#include <Scripting/angelscript/asarglist.h>
+#include <Scripting/scriptparams.h>
+
+#include <Game/loadingscreeninfo.h>
+#include <Game/characterscript.h>
+
+#include <Editors/save_state.h>
+#include <Main/engine.h>
+#include <Network/asnetwork.h>
+#include <Internal/spawneritem.h>
+#include <AI/navmeshparameters.h>
+
+#include <set>
+#include <map>
+#include <vector>
+#include <string>
+#include <memory>
+
+class ASContext;
+class SceneGraph;
+class Hotspot;
+class MovementObject;
+class SaveFile;
+struct LevelInfo;
+class GUI;
+class CScriptArray;
+class ASCollisions;
+
+const int kMaxTextElements = 40;
+struct TextElement {
+ bool in_use;
+ TextCanvasTexture text_canvas_texture;
+};
+
+class Level :
+ public ASNetworkCallback
+{
+private:
+ SceneGraph* scenegraph;
+public:
+ const static char* DEFAULT_ENEMY_SCRIPT;// = "enemycontrol.as";
+ const static char* DEFAULT_PLAYER_SCRIPT;// = "playercontrol.as";
+
+ struct HookedASContext {
+ struct {
+ ASFunctionHandle init;
+ ASFunctionHandle update;
+ ASFunctionHandle update_deprecated;
+ ASFunctionHandle hotspot_exit;
+ ASFunctionHandle hotspot_enter;
+ ASFunctionHandle receive_message;
+ ASFunctionHandle draw_gui;
+ ASFunctionHandle draw_gui2;
+ ASFunctionHandle draw_gui3;
+ ASFunctionHandle has_focus;
+ ASFunctionHandle dialogue_camera_control;
+ ASFunctionHandle save_history_state;
+ ASFunctionHandle read_chunk;
+ ASFunctionHandle set_window_dimensions;
+ ASFunctionHandle incoming_tcp_data;
+ ASFunctionHandle pre_script_reload;
+ ASFunctionHandle post_script_reload;
+ ASFunctionHandle menu;
+ ASFunctionHandle register_mp_callbacks;
+ ASFunctionHandle start_dialogue;
+ } as_funcs;
+
+ Path path;
+ std::string context_name;
+ ASContext* ctx;
+ };
+
+ std::vector<HookedASContext> as_contexts_;
+
+ typedef std::set<void*> CollisionPtrSet;
+ typedef std::map<void*, CollisionPtrSet> CollisionPtrMap;
+
+ Level();
+ virtual ~Level();
+
+ virtual void IncomingTCPData(SocketID socket, uint8_t* data, size_t len);
+
+ void StartDialogue(const std::string& dialogue) const;
+ void RegisterMPCallbacks() const;
+ void Initialize(SceneGraph *scenegraph, GUI* gui);
+ static void DefineLevelTypePublic(ASContext *as_context);
+ void GetCollidingObjects( int id, CScriptArray *array );
+ void Message( const std::string &msg );
+ void Update(bool paused);
+ void LiveUpdateCheck();
+ void Dispose();
+ void HotspotTriggered( Hotspot* hotspot, MovementObject* mo );
+ void HandleCollisions( CollisionPtrMap &col_map, SceneGraph &scenegraph );
+ bool HasFunction(const std::string& function_definition);
+ int QueryIntFunction( const std::string &func );
+ void Execute( std::string code );
+ void Draw();
+ void SetFromLevelInfo( const LevelInfo &li );
+ void SetLevelSpecificScript( std::string& script_name );
+ void SetPCScript( std::string& script_name );
+ void SetNPCScript( std::string& script_name );
+ void ClearNPCScript();
+ void ClearPCScript();
+ std::string GetLevelSpecificScript();
+ std::string GetMPPCScript() const;
+ std::string GetPCScript(const MovementObject* movementObject);
+ std::string GetNPCScript(const MovementObject* movementObject);
+ std::string GetNPCMPScript() const;
+ int GetNumObjectives();
+ int GetNumAchievements();
+ std::string GetObjective(int which);
+ std::string GetAchievement(int which);
+ std::string GetAchievementsString() const;
+ const std::string &GetPath(const std::string& key) const;
+ bool HasFocus();
+ bool DialogueCameraControl();
+ void Reset();
+
+ int CreateTextElement();
+ void DeleteTextElement(int which);
+ TextCanvasTexture* GetTextElement(int which);
+ ScriptParams& script_params();
+ void SetScriptParams(const ScriptParamMap& spm);
+ void SaveHistoryState( std::list<SavedChunk> & chunks, int state_id );
+ void ReadChunk( const SavedChunk &the_chunk );
+ bool isMetaDataDirty();
+ void setMetaDataClean();
+ void setMetaDataDirty();
+ void ReceiveLevelEvents(int id);
+ void StopReceivingLevelEvents(int id);
+ void WindowResized(ivec2 value );
+ void PushSpawnerItemRecent(const SpawnerItem& item);
+
+ std::vector<SpawnerItem> GetRecentlyCreatedItems();
+ NavMeshParameters nav_mesh_parameters_;
+ LoadingScreen loading_screen_;
+private:
+ // These two are just persistent for performance
+ std::vector<std::pair<Hotspot*,MovementObject*> > exit_call_queue;
+ std::vector<std::pair<Hotspot*,MovementObject*> > enter_call_queue;
+
+ bool metaDataDirty; // Has something changed that's not covered by the history system?
+ ScriptParams sp_;
+ std::string level_specific_script_;
+ std::string pc_script_;
+ std::string npc_script_;
+
+ std::vector<SpawnerItem> recently_created_items_;
+
+ std::vector<int> level_event_receivers;
+ TextElement text_elements[kMaxTextElements];
+ CharacterScriptGetter character_script_getter_;
+ typedef std::set<int> CollisionSet;
+ typedef std::map<int, CollisionSet> CollisionMap;
+ CollisionMap old_col_map_;
+ HUDImages hud_images;
+ typedef std::map<std::string, std::string> StringMap;
+ StringMap level_script_paths;
+ // vvv These are just here to avoid per-frame mallocs vvv
+ CollisionPtrMap old_col_ptr_map;
+
+ std::auto_ptr<ASCollisions> as_collisions;
+
+ Path FindScript(const std::string& path);
+};
diff --git a/Source/Game/levelinfo.cpp b/Source/Game/levelinfo.cpp
new file mode 100644
index 00000000..306db12a
--- /dev/null
+++ b/Source/Game/levelinfo.cpp
@@ -0,0 +1,96 @@
+//-----------------------------------------------------------------------------
+// Name: levelinfo.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "levelinfo.h"
+
+#include <Internal/levelxml.h>
+#include <Internal/comma_separated_list.h>
+#include <Internal/filesystem.h>
+#include <Internal/returnpathutil.h>
+#include <Internal/profiler.h>
+
+#include <Asset/Asset/material.h>
+#include <Logging/logdata.h>
+#include <Game/detailobjectlayer.h>
+
+#include <tinyxml.h>
+
+void LevelInfo::Print()
+{
+ LOGI << "XML version: " << xml_version_ << std::endl;
+ LOGI << "Level name: " << level_name_ << std::endl;
+ LOGI << "Shader: " << shader_ << std::endl;
+ LOGI << "Script: " << script_ << std::endl;
+ terrain_info_.Print();
+
+ for(unsigned i=0; i<ambient_sounds_.size(); ++i){
+ LOGI << "Ambient sound: " << ambient_sounds_[i] << std::endl;
+ }
+
+ sky_info_.Print();
+
+ for(unsigned i=0; i<desc_list_.size(); ++i){
+ LOGI << "EntityDesc" << std::endl;
+ }
+}
+
+void LevelInfo::SetDefaults()
+{
+ xml_version_.clear();
+ level_name_.clear();
+ shader_ = "post";
+ ambient_sounds_.clear();
+ terrain_info_.SetDefaults();
+ out_of_date_info_.ao = true;
+ out_of_date_info_.nav_mesh = true;
+ out_of_date_info_.shadow = true;
+ sky_info_.SetDefaults();
+ desc_list_.clear();
+ script_.clear();
+ nav_mesh_parameters_ = NavMeshParameters();
+ shadows_ = true;
+}
+
+void LevelInfo::ReturnPaths( PathSet &path_set )
+{
+ static const int kBufSize = 256;
+ char buf[kBufSize];
+ path_set.insert("level "+path_);
+ for(unsigned i=0; i<ambient_sounds_.size(); ++i){
+ path_set.insert("sound "+ambient_sounds_[i]+"_1.wav");
+ path_set.insert("sound "+ambient_sounds_[i]+"_2.wav");
+ path_set.insert("sound "+ambient_sounds_[i]+"_3.wav");
+ }
+ terrain_info_.ReturnPaths(path_set);
+ sky_info_.ReturnPaths(path_set);
+ for(unsigned i=0; i<script_paths_.size(); ++i){
+ FormatString(buf, kBufSize, "Finding script paths: %s", script_paths_[i].first.c_str());
+ PROFILER_ZONE_DYNAMIC_STRING(g_profiler_ctx, buf);
+ ReturnPathUtil::ReturnPathsFromPath(script_paths_[i].second, path_set);
+ }
+ for(unsigned i=0; i<desc_list_.size(); ++i){
+ FormatString(buf, kBufSize, "Finding desc_list paths: %d", i);
+ PROFILER_ZONE_DYNAMIC_STRING(g_profiler_ctx, buf);
+ desc_list_[i].ReturnPaths(path_set);
+ }
+}
diff --git a/Source/Game/levelinfo.h b/Source/Game/levelinfo.h
new file mode 100644
index 00000000..ce339d9c
--- /dev/null
+++ b/Source/Game/levelinfo.h
@@ -0,0 +1,69 @@
+//-----------------------------------------------------------------------------
+// Name: levelinfo.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Game/EntityDescription.h>
+#include <Game/detailobjectlayer.h>
+#include <Game/terraininfo.h>
+#include <Game/outofdateinfo.h>
+#include <Game/skyinfo.h>
+#include <Game/loadingscreeninfo.h>
+
+#include <Math/mat4.h>
+#include <Math/quaternions.h>
+
+#include <Asset/assets.h>
+#include <Scripting/scriptparams.h>
+#include <Internal/spawneritem.h>
+#include <AI/navmeshparameters.h>
+
+struct LevelInfo {
+ typedef std::pair<std::string, std::string> StrPair;
+ std::string xml_version_;
+ std::string level_name_;
+ std::string path_;
+ std::string shader_;
+ std::string script_;
+ std::string pc_script_;
+ std::string npc_script_;
+ std::string visible_name_;
+ std::string visible_description_;
+ std::vector<std::string> ambient_sounds_;
+ NavMeshParameters nav_mesh_parameters_;
+
+ LoadingScreen loading_screen_;
+ TerrainInfo terrain_info_;
+ OutOfDateInfo out_of_date_info_;
+ SkyInfo sky_info_;
+ EntityDescriptionList desc_list_;
+ ScriptParamMap spm_;
+ std::vector<StrPair> script_paths_;
+ std::vector<SpawnerItem> recently_created_items_;
+
+ bool shadows_;
+
+ void Print();
+ void SetDefaults();
+ void ReturnPaths(PathSet &path_set);
+};
diff --git a/Source/Game/levelset_script.cpp b/Source/Game/levelset_script.cpp
new file mode 100644
index 00000000..f762893b
--- /dev/null
+++ b/Source/Game/levelset_script.cpp
@@ -0,0 +1,83 @@
+//-----------------------------------------------------------------------------
+// Name: levelset_script.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "levelset_script.h"
+
+#include <Asset/Asset/levelset.h>
+#include <Scripting/angelscript/ascontext.h>
+#include <Utility/assert.h>
+#include <Main/engine.h>
+
+#include <cassert>
+
+class LevelSetReader {
+private:
+ LevelSetRef lsr_;
+ LevelSet::LevelPaths::const_iterator iter_;
+ int ref_count_;
+public:
+ LevelSetReader(){
+ LOG_ASSERT(false);
+ }
+ LevelSetReader(const std::string &path){
+ //lsr_ = LevelSets::Instance()->ReturnRef(path);
+ lsr_ = Engine::Instance()->GetAssetManager()->LoadSync<LevelSet>(path);
+ iter_ = lsr_->level_paths().begin();
+ ref_count_ = 1;
+ }
+ void AddRef() {
+ ++ref_count_;
+ }
+ void Release() {
+ --ref_count_;
+ if(ref_count_ == 0){
+ delete this;
+ }
+ }
+ bool Next(std::string& str){
+ if(iter_ == lsr_->level_paths().end()){
+ return false;
+ }
+ str = (*iter_);
+ ++iter_;
+ return true;
+ }
+};
+
+LevelSetReader *LevelSetReader_DefaultFactory() {
+ return new LevelSetReader();
+}
+
+LevelSetReader *LevelSetReader_Factory(const std::string& path) {
+ return new LevelSetReader(path);
+}
+
+void AttachLevelSet( ASContext* as_context ) {
+ as_context->RegisterObjectType("LevelSetReader", 0, asOBJ_REF);
+ as_context->RegisterObjectBehaviour("LevelSetReader", asBEHAVE_FACTORY, "LevelSetReader@ f(const string &in path)", asFUNCTION(LevelSetReader_Factory), asCALL_CDECL);
+ as_context->RegisterObjectBehaviour("LevelSetReader", asBEHAVE_FACTORY, "LevelSetReader@ f()", asFUNCTION(LevelSetReader_DefaultFactory), asCALL_CDECL);
+ as_context->RegisterObjectBehaviour("LevelSetReader", asBEHAVE_ADDREF, "void f()", asMETHOD(LevelSetReader,AddRef), asCALL_THISCALL);
+ as_context->RegisterObjectBehaviour("LevelSetReader", asBEHAVE_RELEASE, "void f()", asMETHOD(LevelSetReader,Release), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("LevelSetReader", "bool Next(string &out str)", asMETHOD(LevelSetReader, Next), asCALL_THISCALL);
+ as_context->DocsCloseBrace();
+}
diff --git a/Source/Game/levelset_script.h b/Source/Game/levelset_script.h
new file mode 100644
index 00000000..e40207dd
--- /dev/null
+++ b/Source/Game/levelset_script.h
@@ -0,0 +1,26 @@
+//-----------------------------------------------------------------------------
+// Name: levelset_script.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+class ASContext;
+void AttachLevelSet(ASContext* as_context);
diff --git a/Source/Game/loadingscreeninfo.h b/Source/Game/loadingscreeninfo.h
new file mode 100644
index 00000000..260ece93
--- /dev/null
+++ b/Source/Game/loadingscreeninfo.h
@@ -0,0 +1,30 @@
+//-----------------------------------------------------------------------------
+// Name: loadingscreeninfo.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <string>
+
+struct LoadingScreen {
+ std::string image;
+};
diff --git a/Source/Game/outofdateinfo.cpp b/Source/Game/outofdateinfo.cpp
new file mode 100644
index 00000000..0eb01e9e
--- /dev/null
+++ b/Source/Game/outofdateinfo.cpp
@@ -0,0 +1,41 @@
+//-----------------------------------------------------------------------------
+// Name: outofdateinfo.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "outofdateinfo.h"
+
+#include <Logging/logdata.h>
+
+void OutOfDateInfo::Print()
+{
+ LOGI << "Out of date: " << std::endl;
+ if(ao){
+ LOGI << "ao " << std::endl;
+ }
+ if(shadow){
+ LOGI << "shadow " << std::endl;
+ }
+ if(nav_mesh){
+ LOGI << "nav_mesh " << std::endl;
+ }
+ LOGI << std::endl;
+}
diff --git a/Source/Game/outofdateinfo.h b/Source/Game/outofdateinfo.h
new file mode 100644
index 00000000..95143ef2
--- /dev/null
+++ b/Source/Game/outofdateinfo.h
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------------
+// Name: outofdateinfo.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Game/EntityDescription.h>
+#include <Game/detailobjectlayer.h>
+
+#include <Math/mat4.h>
+#include <Math/quaternions.h>
+
+#include <Asset/assets.h>
+#include <Scripting/scriptparams.h>
+
+struct OutOfDateInfo {
+ bool shadow;
+ bool ao;
+ bool nav_mesh;
+ int nav_mesh_param_hash;
+
+ void Print();
+};
diff --git a/Source/Game/paths.cpp b/Source/Game/paths.cpp
new file mode 100644
index 00000000..2688cba7
--- /dev/null
+++ b/Source/Game/paths.cpp
@@ -0,0 +1,186 @@
+//-----------------------------------------------------------------------------
+// Name: paths.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "paths.h"
+
+#include <Math/enginemath.h>
+#include <Math/vec3math.h>
+
+#include <Graphics/pxdebugdraw.h>
+#include <Scripting/angelscript/ascontext.h>
+
+#include <sstream>
+
+void AIPaths::Draw() {
+ for(PointMap::const_iterator iter = points.begin(); iter != points.end(); ++iter){
+ const vec3& point = iter->second;
+ DebugDraw::Instance()->AddWireSphere(point,
+ 0.5f,
+ vec4(0.0f,0.5f,0.5f,0.5f),
+ _delete_on_draw);
+ }
+ for(unsigned i=0; i<connections.size(); ++i){
+ DebugDraw::Instance()->AddLine(GetPointPosition(connections[i].point_ids[0]),
+ GetPointPosition(connections[i].point_ids[1]),
+ vec4(0.0f,0.5f,0.5f,0.5f),
+ _delete_on_draw);
+ }
+}
+
+int AIPaths::GetNearestPoint( const vec3 &pos )
+{
+ float closest_distance;
+ float distance;
+ int closest_point = -1;
+ for(PointMap::const_iterator iter = points.begin(); iter != points.end(); ++iter){
+ const vec3 &point = iter->second;
+ int id = iter->first;
+ distance = distance_squared(pos, point);
+ if(closest_point == -1 || distance < closest_distance){
+ closest_point = id;
+ closest_distance = distance;
+ }
+ }
+ return closest_point;
+}
+
+vec3 AIPaths::GetPointPosition( int point_id ) {
+ PointMap::const_iterator iter = points.find(point_id);
+ if(iter != points.end()){
+ return iter->second;
+ } else {
+ std::ostringstream oss;
+ oss << "Could not find waypoint " << point_id;
+ DisplayError("Error",oss.str().c_str());
+ return vec3(0.0f);
+ }
+}
+
+int AIPaths::GetConnectedPoint( int point_id )
+{
+ for(unsigned i=0; i<connections.size(); ++i){
+ if(connections[i].point_ids[0] == point_id){
+ return connections[i].point_ids[1];
+ }
+ if(connections[i].point_ids[1] == point_id){
+ return connections[i].point_ids[0];
+ }
+ }
+ return -1;
+}
+
+int AIPaths::GetOtherConnectedPoint( int point_id, int other_id )
+{
+ for(unsigned i=0; i<connections.size(); ++i){
+ if(connections[i].point_ids[0] == point_id &&
+ connections[i].point_ids[1] != other_id){
+ return connections[i].point_ids[1];
+ }
+ if(connections[i].point_ids[1] == point_id &&
+ connections[i].point_ids[0] != other_id){
+ return connections[i].point_ids[0];
+ }
+ }
+ return -1;
+}
+
+void AIPaths::AddPoint( int id, const vec3 &pos ) {
+ points[id] = pos;
+}
+
+void AIPaths::AddConnection( int a, int b ) {
+ connections.push_back( PathConnection(a, b) );
+}
+
+void AIPaths::RemovePoint( int id ) {
+ PointMap::iterator iter = points.find(id);
+ if(iter != points.end()){
+ points.erase(iter);
+ }
+ for(std::vector<PathConnection>::iterator iter = connections.begin(); iter != connections.end();){
+ PathConnection &connection = (*iter);
+ if(connection.point_ids[0] == id || connection.point_ids[1] == id){
+ iter = connections.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+}
+
+void AIPaths::RemoveConnection( int a, int b ) {
+ for(std::vector<PathConnection>::iterator iter = connections.begin(); iter != connections.end();){
+ PathConnection &connection = (*iter);
+ if(connection.point_ids[0] == a && connection.point_ids[1] == b){
+ iter = connections.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+}
+
+void AIPaths::SetPoint( int id, const vec3 &pos ) {
+ PointMap::iterator iter = points.find(id);
+ if(iter != points.end()){
+ iter->second = pos;
+ }/* else {
+ std::ostringstream oss;
+ oss << "Could not find waypoint " << id;
+ DisplayError("Error", oss.str());
+ }*/
+}
+
+void PathScriptReader::AttachToScript( ASContext *as_context, const std::string& as_name )
+{
+ as_context->RegisterObjectType("PathScriptReader", 0, asOBJ_REF | asOBJ_NOHANDLE);
+ as_context->RegisterObjectMethod("PathScriptReader",
+ "int GetNearestPoint(vec3)",
+ asMETHOD(PathScriptReader, GetNearestPoint), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("PathScriptReader",
+ "vec3 GetPointPosition(int point_id)",
+ asMETHOD(PathScriptReader, GetPointPosition), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("PathScriptReader",
+ "int GetConnectedPoint(int point_id)",
+ asMETHOD(PathScriptReader, GetConnectedPoint), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("PathScriptReader",
+ "int GetOtherConnectedPoint(int point_id, int other_id)",
+ asMETHOD(PathScriptReader, GetOtherConnectedPoint), asCALL_THISCALL);
+ as_context->DocsCloseBrace();
+
+ as_context->RegisterGlobalProperty(("PathScriptReader "+as_name).c_str(), this);
+}
+
+int PathScriptReader::GetNearestPoint( vec3 pos ) {
+ return AIPaths::Instance()->GetNearestPoint(pos);
+}
+
+vec3 PathScriptReader::GetPointPosition( int point_id ) {
+ return AIPaths::Instance()->GetPointPosition(point_id);
+}
+
+int PathScriptReader::GetConnectedPoint( int point_id ) {
+ return AIPaths::Instance()->GetConnectedPoint(point_id);
+}
+
+int PathScriptReader::GetOtherConnectedPoint( int point_id, int other_id ) {
+ return AIPaths::Instance()->GetOtherConnectedPoint(point_id, other_id);
+}
diff --git a/Source/Game/paths.h b/Source/Game/paths.h
new file mode 100644
index 00000000..426cc5c9
--- /dev/null
+++ b/Source/Game/paths.h
@@ -0,0 +1,73 @@
+//-----------------------------------------------------------------------------
+// Name: paths.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+
+#include <map>
+#include <vector>
+#include <string>
+
+struct PathConnection {
+ int point_ids[2];
+ PathConnection(int a, int b){
+ point_ids[0] = a;
+ point_ids[1] = b;
+ }
+};
+
+class PathPointObject;
+
+class AIPaths {
+private:
+ typedef std::map<int, vec3> PointMap;
+ PointMap points;
+ std::vector<PathConnection> connections;
+public:
+ void Draw();
+
+ int GetNearestPoint(const vec3 &pos);
+ vec3 GetPointPosition(int point_id);
+ int GetConnectedPoint(int point_id);
+ int GetOtherConnectedPoint(int point_id, int other_id);
+ static AIPaths *Instance() {
+ static AIPaths paths;
+ return &paths;
+ }
+ void AddPoint( int id, const vec3 &pos );
+ void SetPoint( int id, const vec3 &pos );
+ void AddConnection( int a, int b );
+ void RemovePoint( int id );
+ void RemoveConnection( int a, int b );
+};
+
+class ASContext;
+class PathScriptReader {
+public:
+ int GetNearestPoint(vec3 pos);
+ vec3 GetPointPosition(int point_id);
+ int GetConnectedPoint(int point_id);
+ int GetOtherConnectedPoint(int point_id, int other_id);
+ void AttachToScript( ASContext *as_context, const std::string& as_name );
+};
diff --git a/Source/Game/reactionscript.cpp b/Source/Game/reactionscript.cpp
new file mode 100644
index 00000000..1c28a9da
--- /dev/null
+++ b/Source/Game/reactionscript.cpp
@@ -0,0 +1,86 @@
+//-----------------------------------------------------------------------------
+// Name: reactionscript.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "reactionscript.h"
+
+#include <Scripting/angelscript/ascontext.h>
+#include <Main/engine.h>
+
+void ReactionScriptGetter::Load( std::string _path ) {
+ path = _path;
+ if(_path[_path.size()-2] == ' ' &&
+ _path[_path.size()-1] == 'm')
+ {
+ mirror = true;
+ path.resize(path.size()-2);
+ } else {
+ mirror = false;
+ }
+ for(unsigned i=0; i<items.size(); ++i){
+ if(items[i]->HasReactionOverride(path)){
+ path = items[i]->GetReactionOverride(path);
+ }
+ }
+ //reaction_ref = Reactions::Instance()->ReturnRef(path);
+ reaction_ref = Engine::Instance()->GetAssetManager()->LoadSync<Reaction>(path);
+}
+
+std::string ReactionScriptGetter::GetAnimPath(float severity) {
+ return reaction_ref->GetAnimPath(severity);
+}
+
+void ReactionScriptGetter::ItemsChanged( const std::vector<ItemRef> &_items ) {
+ items = _items;
+}
+
+void ReactionScriptGetter::AttachToScript( ASContext *as_context, const std::string& as_name ) {
+ as_context->RegisterObjectType("ReactionScriptGetter", 0, asOBJ_REF | asOBJ_NOHANDLE);
+ as_context->RegisterObjectMethod("ReactionScriptGetter",
+ "void Load(string path)",
+ asMETHOD(ReactionScriptGetter, Load), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ReactionScriptGetter",
+ "string GetAnimPath(float severity)",
+ asMETHOD(ReactionScriptGetter, GetAnimPath), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ReactionScriptGetter",
+ "int GetMirrored()",
+ asMETHOD(ReactionScriptGetter, GetMirrored), asCALL_THISCALL);
+ as_context->DocsCloseBrace();
+
+ as_context->RegisterGlobalProperty(("ReactionScriptGetter "+as_name).c_str(), this);
+}
+
+void ReactionScriptGetter::AttachExtraToScript( ASContext *as_context, const std::string& as_name ) {
+ as_context->RegisterGlobalProperty(("ReactionScriptGetter "+as_name).c_str(), this);
+}
+
+int ReactionScriptGetter::GetMirrored() {
+ if(reaction_ref->IsMirrored() == 2){
+ return 2;
+ } else {
+ bool mirrored = mirror;
+ if(reaction_ref->IsMirrored()){
+ mirrored = !mirrored;
+ }
+ return (int)(mirrored);
+ }
+}
diff --git a/Source/Game/reactionscript.h b/Source/Game/reactionscript.h
new file mode 100644
index 00000000..3664ee72
--- /dev/null
+++ b/Source/Game/reactionscript.h
@@ -0,0 +1,46 @@
+//-----------------------------------------------------------------------------
+// Name: reactionscript.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/Asset/reactions.h>
+#include <Asset/Asset/item.h>
+
+#include <vector>
+
+class ASContext;
+
+class ReactionScriptGetter {
+ ReactionRef reaction_ref;
+ bool mirror;
+ std::string path;
+ std::vector<ItemRef> items;
+
+ int GetMirrored();
+ void Load( std::string path );
+ std::string GetAnimPath(float severity);
+public:
+ void AttachToScript( ASContext *as_context, const std::string& as_name );
+ void AttachExtraToScript( ASContext *as_context, const std::string& as_name );
+ void ItemsChanged( const std::vector<ItemRef> &_items );
+};
diff --git a/Source/Game/savefile.cpp b/Source/Game/savefile.cpp
new file mode 100644
index 00000000..8d03557b
--- /dev/null
+++ b/Source/Game/savefile.cpp
@@ -0,0 +1,567 @@
+//-----------------------------------------------------------------------------
+// Name: savefile.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "savefile.h"
+
+#include <Internal/integer.h>
+#include <Internal/modloading.h>
+#include <Internal/error.h>
+
+#include <Memory/allocation.h>
+#include <Game/savefile.h>
+#include <Compat/fileio.h>
+#include <Utility/assert.h>
+#include <Logging/logdata.h>
+
+#include <cstdlib>
+#include <cstring>
+
+const char* SaveFile::kOvergrowthSaveMarker = "Overgrowth Save";
+const uint16_t SaveFile::kCurrentVersion = 3;
+
+namespace {
+ // Read a 128-bit MD5 value into uint128_holder
+ // Returns true on success, false on error
+ bool ReadMD5(FileDescriptor *fd, uint128_holder* val){
+ return fd->ReadBytes(val, sizeof(*val));
+ }
+
+ // Write a 128-bit MD5 value from uint128_holder
+ // Returns true on success, false on error
+ bool WriteMD5(FileDescriptor *fd, uint128_holder* val){
+ return fd->WriteBytes(val, sizeof(*val));
+ }
+
+ // Reads an std::string from a file, with spec:
+ // uint16 num_chars
+ // char[num_chars] str
+ // Returns true on success, false on error
+ bool ReadString(FileDescriptor *fd, std::string* val){
+ uint16_t num_chars;
+ if(!fd->ReadBytes(&num_chars, sizeof(num_chars))){
+ return false;
+ }
+ val->resize(num_chars);
+ return fd->ReadBytes(&(*val)[0], num_chars);
+ }
+
+ // Writes an std::string to a file, with spec:
+ // Returns true on success, false on error
+ bool WriteString(FileDescriptor *fd, const std::string* val){
+ uint16_t num_chars = val->length();
+ if(!fd->WriteBytes(&num_chars, sizeof(num_chars))){
+ return false;
+ }
+ return fd->WriteBytes(&(*val)[0], num_chars);
+ }
+
+ // Load file 'path' into head at pointer 'mem'
+ // Allocates memory, mem must be freed by caller if successful
+ // Returns true on success, false on error
+ bool LoadFileIntoRAM(void* &mem, const std::string &abs_path){
+ DiskFileDescriptor file;
+ if(!file.Open(abs_path, "rb")){
+ return false;
+ }
+ int file_size = file.GetSize();
+
+ mem = OG_MALLOC(file_size);
+ if(!mem){
+ FatalError("Error", "Could not allocate memory for file: %s", abs_path.c_str());
+ }
+ if(!file.ReadBytes(mem, file_size)){
+ OG_FREE(mem);
+ return false;
+ }
+ file.Close();
+ return true;
+ }
+ std::string GetTemporaryPath( const std::string &filepath_ ) {
+ return filepath_+"_temp";
+ }
+}
+
+SavedLevel& SaveFile::GetSavedLevelDeprecated( const std::string& name ) {
+ for( size_t i = 0; i < levels_.size(); i++ ) {
+ if( levels_[i].campaign_id_ == "" &&
+ levels_[i].save_category_ == "" &&
+ levels_[i].save_name_ == name ) {
+ return levels_[i];
+ }
+ }
+
+ SavedLevel new_level;
+ new_level.save_name_ = name;
+
+ levels_.push_back(new_level);
+
+ return levels_[levels_.size()-1];
+}
+
+SavedLevel& SaveFile::GetSave(const std::string campaign_id, const std::string save_category, const std::string level_name) {
+ for( size_t i = 0; i < levels_.size(); i++ ) {
+ if( levels_[i].campaign_id_ == campaign_id &&
+ levels_[i].save_category_ == save_category &&
+ levels_[i].save_name_ == level_name ) {
+ return levels_[i];
+ }
+ }
+
+ SavedLevel new_level;
+ new_level.campaign_id_ = campaign_id;
+ new_level.save_category_ = save_category;
+ new_level.save_name_ = level_name;
+
+ levels_.push_back(new_level);
+
+ return levels_[levels_.size()-1];
+}
+
+SavedLevel& SaveFile::GetSaveIndex(size_t index) {
+ return levels_[index];
+}
+
+size_t SaveFile::GetSaveCount() {
+ return levels_.size();
+}
+
+bool SaveFile::SaveExist(const std::string campaign_id, const std::string save_category, const std::string level_name) {
+ for( size_t i = 0; i < levels_.size(); i++ ) {
+ if( levels_[i].campaign_id_ == campaign_id &&
+ levels_[i].save_category_ == save_category &&
+ levels_[i].save_name_ == level_name ) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void SaveFile::Clear() {
+ levels_.clear();
+ loaded_version = 0;
+}
+
+bool SaveFile::LoadFromRAM( void* mem ) {
+ Clear();
+ MemReadFileDescriptor file(mem);
+ {
+ std::string marker;
+ marker.resize(strlen(kOvergrowthSaveMarker));
+ if(!file.ReadBytes(&marker[0], strlen(kOvergrowthSaveMarker)) ||
+ marker != kOvergrowthSaveMarker)
+ {
+ LOGE << "Invalid save file marker, unlikely this is an overgrowth save file." << std::endl;
+ return false;
+ }
+ }
+
+ if(!file.ReadBytes(&loaded_version, sizeof(loaded_version))){
+ LOGE << "Failed at reading loaded_version" << std::endl;
+ return false;
+ }
+
+ if( loaded_version == 1 ) {
+ LOGE << "Version 1 is not supported" << std::endl;
+ return false;
+ }
+
+ if( loaded_version == 2 ) {
+ LOGE << "Version 2 is not supported" << std::endl;
+ return false;
+ }
+
+ if( loaded_version > 3 ) {
+ LOGE << "Failed at reading save file, too new fileformat" << std::endl;
+ return false;
+ }
+
+ uint16_t num_levels;
+ if(!file.ReadBytes(&num_levels, sizeof(num_levels))){
+ LOGE << "Failed at reading num of levels" << std::endl;
+ return false;
+ }
+ std::string key, val;
+ for(uint16_t i=0; i<num_levels; ++i){
+ SavedLevel level;
+
+ if(!ReadString(&file, &level.campaign_id_)){
+ LOGE << "Failed at reading campaign id" << std::endl;
+ return false;
+ }
+
+ if(!ReadString(&file, &level.save_category_)){
+ LOGE << "Failed at reading save category" << std::endl;
+ return false;
+ }
+
+ if(!ReadString(&file, &level.save_name_)){
+ LOGE << "Failed at reading level name" << std::endl;
+ return false;
+ }
+
+ uint128_holder md5_val;
+ if(!ReadMD5(&file, &md5_val)){
+ LOGE << "Failed at reading md5" << std::endl;
+ return false;
+ }
+
+ uint16_t num_saved_vals;
+ if(!file.ReadBytes(&num_saved_vals, sizeof(num_saved_vals))){
+ LOGE << "Failed at reading num_saved_vals" << std::endl;
+ return false;
+ }
+
+ for(uint16_t j=0; j<num_saved_vals; ++j){
+ if(!ReadString(&file, &key)){
+ LOGE << "Failed at reading value key" << std::endl;
+ return false;
+ }
+ if(!ReadString(&file, &val)){
+ LOGE << "Failed at reading value" << std::endl;
+ return false;
+ }
+ level.SetValue(key, val);
+
+ }
+
+ uint16_t num_saved_arr_vals;
+ if(!file.ReadBytes(&num_saved_arr_vals, sizeof(num_saved_arr_vals))){
+ LOGE << "Failed at reading num array values" << std::endl;
+ return false;
+ }
+
+ for(uint16_t j=0; j<num_saved_arr_vals; ++j) {
+ if(!ReadString(&file, &key)){
+ LOGE << "Failed at reading num array value key" << std::endl;
+ return false;
+ }
+
+ uint16_t num_saved_sub_arr_vals;
+ if(!file.ReadBytes(&num_saved_sub_arr_vals, sizeof(num_saved_sub_arr_vals))){
+ LOGE << "Failed at reading num sub array value count" << std::endl;
+ return false;
+ }
+
+ for(uint16_t j=0; j<num_saved_sub_arr_vals; ++j) {
+ if(!ReadString(&file, &val)){
+ LOGE << "Failed at reading num sub array value" << std::endl;
+ return false;
+ }
+
+ level.AppendArrayValue(key, val);
+ }
+ }
+
+ uint16_t num_saved_int_vals;
+ if(!file.ReadBytes(&num_saved_int_vals, sizeof(num_saved_int_vals))){
+ LOGE << "Failed at reading int32 value count" << std::endl;
+ return false;
+ }
+
+ for(uint16_t j=0; j<num_saved_int_vals; ++j){
+ if(!ReadString(&file, &key)){
+ LOGE << "Failed at reading int32 value key" << std::endl;
+ return false;
+ }
+
+ int32_t val;
+ if(!file.ReadBytes(&val,sizeof(val))) {
+ LOGE << "Failed at reading int32 value" << std::endl;
+ return false;
+ }
+
+ level.SetInt32Value(key, val);
+ }
+
+
+ levels_.push_back(level);
+ }
+
+ return true;
+}
+
+bool SaveFile::SerializeToRAM( std::vector<uint8_t> *mem_ptr ) {
+ std::vector<uint8_t> &mem = *mem_ptr;
+ MemWriteFileDescriptor file(mem);
+ if(!file.WriteBytes(kOvergrowthSaveMarker, strlen(kOvergrowthSaveMarker))){
+ return false;
+ }
+ if(!file.WriteBytes(&kCurrentVersion, sizeof(kCurrentVersion))){
+ return false;
+ }
+ uint16_t num_levels = levels_.size();
+ if(!file.WriteBytes(&num_levels, sizeof(num_levels))){
+ return false;
+ }
+ for(std::vector<SavedLevel>::iterator it = levels_.begin(); it != levels_.end(); ++it){
+ if(!WriteString(&file, &it->campaign_id_)){
+ return false;
+ }
+ if(!WriteString(&file, &it->save_category_)){
+ return false;
+ }
+ if(!WriteString(&file, &it->save_name_)){
+ return false;
+ }
+ if(!it->SerializeToRAM(&file)){
+ return false;
+ }
+ }
+ return true;
+}
+
+bool SaveFile::ReadFromFile( const std::string& abs_path ) {
+ filepath_ = abs_path;
+ void *file_mem;
+ if(!LoadFileIntoRAM(file_mem, abs_path)){
+ if(!LoadFileIntoRAM(file_mem, GetTemporaryPath(abs_path))){
+ return false;
+ }
+ }
+ if(!LoadFromRAM(file_mem)){
+ OG_FREE(file_mem);
+ return false;
+ }
+ OG_FREE(file_mem);
+ return true;
+}
+
+void SaveFile::SetWriteFile(const std::string& filepath) {
+ filepath_ = filepath;
+}
+
+bool SaveFile::WriteToFile( const std::string& filepath ) {
+ std::vector<uint8_t> mem;
+ if(!SerializeToRAM(&mem)){
+ return false;
+ }
+ DiskFileDescriptor file;
+ file.Open(filepath, "wb");
+ file.WriteBytes(&mem[0], mem.size());
+ file.Close();
+ return true;
+}
+
+bool SaveFile::WriteInPlace() {
+ LOGI << "Saving data file to: " << filepath_ << std::endl;
+ std::string temp_path = GetTemporaryPath(filepath_);
+ if(!WriteToFile(temp_path)){
+ return false;
+ }
+ if(movefile(temp_path.c_str(), filepath_.c_str())){
+ deletefile(filepath_.c_str());
+ if(movefile(temp_path.c_str(), filepath_.c_str())){
+ return false;
+ }
+ }
+ return true;
+}
+
+void SaveFile::QueueWriteInPlace() {
+ queue_write_in_place_ = true;
+}
+
+void SaveFile::ExecuteQueuedWrite() {
+ if( queue_write_in_place_ ) {
+ WriteInPlace();
+ queue_write_in_place_ = false;
+ }
+}
+
+uint32_t SaveFile::GetLoadedVersion() {
+ return loaded_version;
+}
+
+const std::string& SavedLevel::GetValue( const std::string &key ) {
+ return data_[key];
+}
+
+void SavedLevel::SetValue( const std::string &key, const std::string &val ) {
+ data_[key] = val;
+}
+
+void SavedLevel::SetArrayValue(const std::string &key, const int32_t index, const std::string &val) {
+ std::vector<std::string> &v = array_data_[key];
+
+ LOG_ASSERT(index >= 0 && index <= 32000);
+
+ if( (size_t)index < v.size() ) {
+ v[index] = val;
+ } else {
+ while( v.size() < (size_t)index ) {
+ LOGW << "Told to set value \"" << val << "\" on index " << index << " in " << key << " but currently only have " << v.size() << " elements, padding with an empty string" << std::endl;
+ v.push_back("");
+ }
+ v.push_back(val);
+ }
+}
+
+void SavedLevel::DeleteArrayValue(const std::string &key, const int32_t index) {
+ bool shift = false;
+ std::vector<std::string> &v = array_data_[key];
+
+ for( size_t i = 0; i < v.size(); i++ ) {
+ if( shift ) {
+ v[i-1] = v[i];
+ } else if( i == (size_t)index ) {
+ shift = true;
+ }
+ }
+}
+
+void SavedLevel::AppendArrayValueIfUnique(const std::string &key, const std::string& val) {
+ if( ArrayContainsValue(key,val) == false ) {
+ AppendArrayValue(key,val);
+ }
+}
+
+void SavedLevel::AppendArrayValue(const std::string &key, const std::string& val) {
+ std::vector<std::string> &v = array_data_[key];
+ v.push_back(val);
+}
+
+bool SavedLevel::ArrayContainsValue(const std::string &key, const std::string& val) {
+ std::vector<std::string> &v = array_data_[key];
+ for( size_t i = 0; i < v.size(); i++ ) {
+ if(v[i] == val){
+ return true;
+ }
+ }
+ return false;
+}
+
+uint32_t SavedLevel::GetArraySize(const std::string &key) {
+ std::vector<std::string> &v = array_data_[key];
+ return v.size();
+}
+
+std::string SavedLevel::GetArrayValue( const std::string &key, const int32_t index) {
+ std::vector<std::string> &v = array_data_[key];
+ if( (size_t)index < v.size() ) {
+ return v[index];
+ } else {
+ return std::string();
+ }
+}
+
+std::vector<std::string> SavedLevel::GetArray( const std::string &key) {
+ return array_data_[key];
+}
+
+void SavedLevel::SetInt32Value(const std::string &key, const int32_t value) {
+ data_int32_[key] = value;
+}
+
+int32_t SavedLevel::GetInt32Value(const std::string &key) {
+ return data_int32_[key];
+}
+
+bool SavedLevel::HasInt32Value(const std::string &key) {
+ return (data_int32_.find(key) != data_int32_.end());
+}
+
+void SavedLevel::SetKey(const std::string& campaign_id, const std::string& save_category, const std::string& level_name) {
+ this->campaign_id_ = campaign_id;
+ this->save_category_ = save_category;
+ this->save_name_ = level_name;
+}
+
+bool SavedLevel::SerializeToRAM( MemWriteFileDescriptor *file_ptr ) {
+ MemWriteFileDescriptor &file = *file_ptr;
+ if(!WriteMD5(&file, &md5_)){
+ return false;
+ }
+
+ {
+ uint16_t num_saved_vals = data_.size();
+ if(!file.WriteBytes(&num_saved_vals, sizeof(num_saved_vals))){
+ return false;
+ }
+ for(DataMap::iterator it = data_.begin(); it != data_.end(); ++it){
+ if(!WriteString(&file, &it->first)){
+ return false;
+ }
+ if(!WriteString(&file, &it->second)){
+ return false;
+ }
+ }
+ }
+
+ {
+ uint16_t num_saved_array_vals = array_data_.size();
+ if(!file.WriteBytes(&num_saved_array_vals, sizeof(num_saved_array_vals))){
+ return false;
+ }
+ for(ArrayDataMap::iterator it = array_data_.begin(); it != array_data_.end(); it++) {
+ if(!WriteString(&file, &it->first)){
+ return false;
+ }
+
+ uint16_t num_saved_array_sub_vals = it->second.size();
+ if(!file.WriteBytes(&num_saved_array_sub_vals, sizeof(num_saved_array_sub_vals))){
+ return false;
+ }
+
+ for(std::vector<std::string>::iterator it2 = it->second.begin(); it2 != it->second.end(); it2++) {
+ if(!WriteString(&file, &(*it2))){
+ return false;
+ }
+ }
+ }
+ }
+
+ {
+ uint16_t num_saved_int32_vals = data_int32_.size();
+ if(!file.WriteBytes(&num_saved_int32_vals, sizeof(num_saved_int32_vals))){
+ return false;
+ }
+ for(Int32Map::iterator it = data_int32_.begin(); it != data_int32_.end(); ++it){
+ if(!WriteString(&file, &it->first)){
+ return false;
+ }
+ int32_t val = it->second;
+ if(!file.WriteBytes(&val, sizeof(val))){
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+void SavedLevel::ClearData() {
+ data_.clear();
+ array_data_.clear();
+ data_int32_.clear();
+}
+
+const ArrayDataMap& SavedLevel::GetArrayDataMap() {
+ return array_data_;
+}
+
+const DataMap& SavedLevel::GetDataMap() {
+ return data_;
+}
+
+const Int32Map& SavedLevel::GetIntMap() {
+ return data_int32_;
+}
diff --git a/Source/Game/savefile.h b/Source/Game/savefile.h
new file mode 100644
index 00000000..7cb0e5e0
--- /dev/null
+++ b/Source/Game/savefile.h
@@ -0,0 +1,110 @@
+//-----------------------------------------------------------------------------
+// Name: savefile.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/integer.h>
+#include <Internal/file_descriptor.h>
+#include <Internal/modid.h>
+
+#include <string>
+#include <map>
+
+struct uint128_holder {
+ uint8_t val[16];
+};
+
+typedef std::map<std::string,std::vector<std::string> > ArrayDataMap;
+typedef std::map<std::string, std::string> DataMap;
+typedef std::map<std::string, uint32_t> Int32Map;
+
+class SavedLevel {
+public:
+ void SetValue(const std::string &key, const std::string &val);
+ const std::string& GetValue(const std::string &key);
+
+ void SetArrayValue(const std::string &key, const int32_t index, const std::string &val);
+ void DeleteArrayValue(const std::string &key, const int32_t index);
+ void AppendArrayValueIfUnique(const std::string &key, const std::string& val);
+ void AppendArrayValue(const std::string &key, const std::string& val);
+ bool ArrayContainsValue(const std::string &key, const std::string& val);
+ uint32_t GetArraySize(const std::string &key);
+ std::string GetArrayValue( const std::string &key, const int32_t index);
+ std::vector<std::string> GetArray(const std::string &key);
+
+ void SetInt32Value(const std::string &key, const int32_t value);
+ int32_t GetInt32Value(const std::string &key);
+ bool HasInt32Value(const std::string &key);
+
+ void SetKey(const std::string& campaign_id, const std::string& save_category, const std::string& save_name);
+ bool SerializeToRAM( MemWriteFileDescriptor *file_ptr );
+
+ void ClearData();
+
+ const ArrayDataMap& GetArrayDataMap();
+ const DataMap& GetDataMap();
+ const Int32Map& GetIntMap();
+
+ std::string campaign_id_;
+ std::string save_category_;
+ std::string save_name_;
+
+private:
+
+ DataMap data_;
+ ArrayDataMap array_data_;
+ Int32Map data_int32_;
+
+ uint128_holder md5_;
+};
+
+class SaveFile {
+public:
+ SavedLevel& GetSave(const std::string campaign_id, const std::string save_category, const std::string save_name);
+ SavedLevel& GetSavedLevelDeprecated(const std::string& save_name);
+
+ SavedLevel& GetSaveIndex(size_t index);
+ size_t GetSaveCount();
+
+ bool SaveExist(const std::string campaign_id, const std::string save_category, const std::string save_name);
+
+ bool ReadFromFile(const std::string& filepath);
+ bool LoadFromRAM( void* mem );
+ void Clear();
+ void SetWriteFile(const std::string& filepath);
+ bool WriteToFile( const std::string& filepath );
+ bool SerializeToRAM( std::vector<uint8_t> *mem_ptr );
+ bool WriteInPlace();
+ void QueueWriteInPlace();
+ void ExecuteQueuedWrite();
+ uint32_t GetLoadedVersion();
+
+ static const char* kOvergrowthSaveMarker;
+ static const uint16_t kCurrentVersion;
+private:
+ std::vector<SavedLevel> levels_;
+ std::string filepath_;
+ uint16_t loaded_version;
+ bool queue_write_in_place_;
+};
+
diff --git a/Source/Game/savefile_script.cpp b/Source/Game/savefile_script.cpp
new file mode 100644
index 00000000..3faf061f
--- /dev/null
+++ b/Source/Game/savefile_script.cpp
@@ -0,0 +1,161 @@
+//-----------------------------------------------------------------------------
+// Name: savefile_script.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "savefile_script.h"
+
+#include <Scripting/angelscript/add_on/scriptarray/scriptarray.h>
+#include <Scripting/angelscript/ascontext.h>
+
+#include <Game/savefile.h>
+
+#include <angelscript.h>
+
+static ASContext* local_ctx = NULL;
+
+static CScriptArray* AS_GetArray(SavedLevel* save_file, const std::string& key) {
+ asIScriptContext *ctx = local_ctx->ctx;
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<string>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+
+ std::vector<std::string> source_arr = save_file->GetArray(key);
+
+ array->Reserve(source_arr.size());
+
+ std::vector<std::string>::iterator layerit = source_arr.begin();
+
+ for( ; layerit != source_arr.end(); layerit++ ) {
+ array->InsertLast((void*)&(*layerit));
+ }
+
+ return array;
+}
+
+void AttachSaveFile( ASContext *ctx, SaveFile* save_file ) {
+ local_ctx = ctx;
+ ctx->RegisterObjectType("SavedLevel", 0, asOBJ_REF | asOBJ_NOCOUNT);
+ ctx->RegisterObjectMethod(
+ "SavedLevel",
+ "void SetValue(const string &in key, const string &in value)",
+ asMETHOD(SavedLevel, SetValue),
+ asCALL_THISCALL);
+ ctx->RegisterObjectMethod(
+ "SavedLevel",
+ "const string& GetValue(const string &in key)",
+ asMETHOD(SavedLevel, GetValue),
+ asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod(
+ "SavedLevel",
+ "void SetArrayValue(const string &in key, const int32 index, const string &in value)",
+ asMETHOD(SavedLevel, SetArrayValue),
+ asCALL_THISCALL);
+ ctx->RegisterObjectMethod(
+ "SavedLevel",
+ "void DeleteArrayValue(const string &in key, const int32 index)",
+ asMETHOD(SavedLevel, DeleteArrayValue),
+ asCALL_THISCALL);
+ ctx->RegisterObjectMethod(
+ "SavedLevel",
+ "void AppendArrayValueIfUnique(const string &in key, const string &in val)",
+ asMETHOD(SavedLevel, AppendArrayValueIfUnique),
+ asCALL_THISCALL);
+ ctx->RegisterObjectMethod(
+ "SavedLevel",
+ "void AppendArrayValue(const string &in key, const string &in val)",
+ asMETHOD(SavedLevel, AppendArrayValue),
+ asCALL_THISCALL);
+ ctx->RegisterObjectMethod(
+ "SavedLevel",
+ "uint32 GetArraySize(const string &in key)",
+ asMETHOD(SavedLevel, GetArraySize),
+ asCALL_THISCALL);
+ ctx->RegisterObjectMethod(
+ "SavedLevel",
+ "string GetArrayValue(const string &in key, const int32 index)",
+ asMETHOD(SavedLevel, GetArrayValue),
+ asCALL_THISCALL);
+ ctx->RegisterObjectMethod(
+ "SavedLevel",
+ "array<string>@ GetArray(const string &in key)",
+ asFUNCTION(AS_GetArray),
+ asCALL_CDECL_OBJFIRST);
+
+ ctx->RegisterObjectMethod(
+ "SavedLevel",
+ "void SetInt32Value(const string &in key, const int32 value)",
+ asMETHOD(SavedLevel, SetInt32Value),
+ asCALL_THISCALL);
+ ctx->RegisterObjectMethod(
+ "SavedLevel",
+ "int32 GetInt32Value(const string &in key)",
+ asMETHOD(SavedLevel, GetInt32Value),
+ asCALL_THISCALL);
+ ctx->RegisterObjectMethod(
+ "SavedLevel",
+ "bool HasInt32Value(const string &in key)",
+ asMETHOD(SavedLevel, HasInt32Value),
+ asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod(
+ "SavedLevel",
+ "void SetKey(const string &in modsource_id, const string &in campaign_name, const string &in level_name)",
+ asMETHOD(SavedLevel, SetKey),
+ asCALL_THISCALL);
+ ctx->DocsCloseBrace();
+
+ ctx->RegisterObjectType("SaveFile", 0, asOBJ_REF | asOBJ_NOHANDLE);
+ ctx->RegisterObjectMethod(
+ "SaveFile",
+ "SavedLevel& GetSavedLevel(const string &in name)",
+ asMETHOD(SaveFile, GetSavedLevelDeprecated),
+ asCALL_THISCALL);
+ ctx->RegisterObjectMethod(
+ "SaveFile",
+ "SavedLevel& GetSave(const string campaign_id, const string save_category, const string save_name)",
+ asMETHOD(SaveFile, GetSave),
+ asCALL_THISCALL);
+ ctx->RegisterObjectMethod(
+ "SaveFile",
+ "bool SaveExist(const string modsource_id, const string save_category, const string save_name)",
+ asMETHOD(SaveFile, SaveExist),
+ asCALL_THISCALL);
+ ctx->RegisterObjectMethod(
+ "SaveFile",
+ "bool WriteInPlace()",
+ asMETHOD(SaveFile, WriteInPlace),
+ asCALL_THISCALL);
+ ctx->RegisterObjectMethod(
+ "SaveFile",
+ "void QueueWriteInPlace()",
+ asMETHOD(SaveFile, WriteInPlace),
+ asCALL_THISCALL);
+ ctx->RegisterObjectMethod(
+ "SaveFile",
+ "uint GetLoadedVersion()",
+ asMETHOD(SaveFile, GetLoadedVersion),
+ asCALL_THISCALL);
+ ctx->DocsCloseBrace();
+
+ ctx->RegisterGlobalProperty("SaveFile save_file", save_file);
+}
diff --git a/Source/Game/savefile_script.h b/Source/Game/savefile_script.h
new file mode 100644
index 00000000..68dbd021
--- /dev/null
+++ b/Source/Game/savefile_script.h
@@ -0,0 +1,29 @@
+//-----------------------------------------------------------------------------
+// Name: savefile_script.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+class ASContext;
+class SaveFile;
+
+void AttachSaveFile( ASContext *ctx, SaveFile* save_file );
diff --git a/Source/Game/scriptablecampaign.cpp b/Source/Game/scriptablecampaign.cpp
new file mode 100644
index 00000000..41677c90
--- /dev/null
+++ b/Source/Game/scriptablecampaign.cpp
@@ -0,0 +1,155 @@
+//-----------------------------------------------------------------------------
+// Name: scriptablecampaign.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "scriptablecampaign.h"
+
+#include <Scripting/angelscript/ascontext.h>
+#include <Scripting/angelscript/asfuncs.h>
+
+#include <Game/levelset_script.h>
+#include <Game/savefile.h>
+#include <Game/savefile_script.h>
+#include <Game/level.h>
+
+#include <Internal/levelxml_script.h>
+#include <Internal/filesystem.h>
+#include <Internal/modloading.h>
+#include <Internal/common.h>
+#include <Internal/profiler.h>
+
+#include <Graphics/text.h>
+#include <Utility/assert.h>
+#include <Main/engine.h>
+
+ScriptableCampaign::ScriptableCampaign() {
+
+}
+
+ScriptableCampaign::~ScriptableCampaign() {
+
+}
+
+static void ASSendLevelMessage( const std::string& msg ) {
+ SceneGraph* s = Engine::Instance()->GetSceneGraph();
+
+ if(s) {
+ s->level->Message(msg);
+ } else {
+ LOGW << "Sending message from campaign to level, but there is no scenegraph, going to the void, it was: " << msg << std::endl;
+ }
+}
+
+void ScriptableCampaign::Initialize(Path script_path, std::string _campaign_id) {
+ campaign_id = _campaign_id;
+
+ ASData as_data;
+
+ as_context = new ASContext("scriptable_campaign", as_data);
+
+ as_context->RegisterGlobalFunction("void SendLevelMessage(const string& in msg)", asFUNCTION(ASSendLevelMessage), asCALL_CDECL);
+
+ AttachLevelSet(as_context);
+ AttachLevelXML(as_context);
+ AttachUIQueries(as_context);
+ AttachTokenIterator(as_context);
+ AttachSimpleFile(as_context);
+ AttachInterlevelData(as_context);
+ AttachEngine(as_context);
+ AttachOnline(as_context);
+
+ AttachSaveFile( as_context, &Engine::Instance()->save_file_ );
+
+ as_funcs.init = as_context->RegisterExpectedFunction("void Init()", false);
+ as_funcs.receive_message = as_context->RegisterExpectedFunction("void ReceiveMessage(string)", false);
+ as_funcs.set_window_dimensions = as_context->RegisterExpectedFunction("void SetWindowDimensions(int width, int height)", false);
+
+ as_funcs.update = as_context->RegisterExpectedFunction("void Update()", true);
+ as_funcs.dispose = as_context->RegisterExpectedFunction("void Dispose()", true);
+ as_funcs.enter_campaign = as_context->RegisterExpectedFunction("void EnterCampaign()", true);
+ as_funcs.enter_level = as_context->RegisterExpectedFunction("void EnterLevel()", true);
+ as_funcs.leave_campaign = as_context->RegisterExpectedFunction("void LeaveCampaign()", true);
+ as_funcs.leave_level = as_context->RegisterExpectedFunction("void LeaveLevel()", true);
+
+ char path[kPathSize];
+ FormatString(path, kPathSize, "%sasscriptable_campaign_docs.h", GetWritePath(CoreGameModID).c_str());
+ as_context->ExportDocs(path);
+
+ // Load script and run init function
+ as_context->LoadScript(script_path);
+
+ as_context->CallScriptFunction(as_funcs.init);
+}
+
+void ScriptableCampaign::Dispose() {
+ as_context->CallScriptFunction(as_funcs.dispose);
+ delete as_context;
+}
+
+void ScriptableCampaign::Update() {
+ if(as_context) {
+ as_context->CallScriptFunction(as_funcs.update);
+ }
+}
+
+void ScriptableCampaign::EnterCampaign() {
+ if(as_context) {
+ as_context->CallScriptFunction(as_funcs.enter_campaign);
+ }
+}
+
+void ScriptableCampaign::EnterLevel() {
+ if(as_context) {
+ as_context->CallScriptFunction(as_funcs.enter_level);
+ }
+}
+
+void ScriptableCampaign::LeaveCampaign() {
+ if(as_context) {
+ as_context->CallScriptFunction(as_funcs.leave_campaign);
+ }
+}
+
+void ScriptableCampaign::LeaveLevel() {
+ if(as_context) {
+ as_context->CallScriptFunction(as_funcs.leave_level);
+ }
+}
+
+void ScriptableCampaign::ReceiveMessage(std::string message) {
+ if(as_context) {
+ ASArglist args;
+ args.AddObject(&message);
+ as_context->CallScriptFunction(as_funcs.receive_message, &args);
+ }
+}
+
+void ScriptableCampaign::WindowResized(ivec2 value ) {
+ ASArglist args;
+ args.Add(value[0]);
+ args.Add(value[1]);
+ as_context->CallScriptFunction(as_funcs.set_window_dimensions, &args);
+}
+
+std::string ScriptableCampaign::GetCampaignID() {
+ return campaign_id;
+}
diff --git a/Source/Game/scriptablecampaign.h b/Source/Game/scriptablecampaign.h
new file mode 100644
index 00000000..25be5c89
--- /dev/null
+++ b/Source/Game/scriptablecampaign.h
@@ -0,0 +1,79 @@
+//-----------------------------------------------------------------------------
+// Name: scriptablecampaign.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/modloading.h>
+#include <Internal/path.h>
+
+#include <Utility/disallow_copy_and_assign.h>
+#include <Scripting/angelscript/ascontext.h>
+#include <Math/ivec2.h>
+
+#include <string>
+#include <memory>
+
+struct ASData;
+class SaveFile;
+class ASContext;
+
+class ScriptableCampaign {
+private:
+ std::string campaign_id;
+ ASContext *as_context;
+
+ struct {
+ ASFunctionHandle init;
+
+ ASFunctionHandle update;
+ ASFunctionHandle dispose;
+ ASFunctionHandle enter_campaign;
+ ASFunctionHandle enter_level;
+ ASFunctionHandle leave_campaign;
+ ASFunctionHandle leave_level;
+ ASFunctionHandle receive_message;
+ ASFunctionHandle set_window_dimensions;
+ } as_funcs;
+
+public:
+
+ ScriptableCampaign();
+ ~ScriptableCampaign();
+
+ void Initialize(Path campaign_script, std::string _campaign_id);
+ void Dispose();
+
+ void Update();
+
+ void EnterCampaign();
+ void LeaveCampaign();
+ void EnterLevel();
+ void LeaveLevel();
+
+ void ReceiveMessage(std::string message);
+ void WindowResized(ivec2 value);
+
+ std::string GetCampaignID();
+
+ DISALLOW_COPY_AND_ASSIGN(ScriptableCampaign);
+};
diff --git a/Source/Game/skyinfo.cpp b/Source/Game/skyinfo.cpp
new file mode 100644
index 00000000..15c4b782
--- /dev/null
+++ b/Source/Game/skyinfo.cpp
@@ -0,0 +1,47 @@
+//-----------------------------------------------------------------------------
+// Name: skyinfo.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "skyinfo.h"
+
+#include <Internal/levelxml.h>
+#include <Internal/comma_separated_list.h>
+#include <Internal/filesystem.h>
+#include <Internal/returnpathutil.h>
+
+#include <Asset/Asset/material.h>
+#include <Logging/logdata.h>
+#include <Game/detailobjectlayer.h>
+
+#include <tinyxml.h>
+
+void SkyInfo::Print()
+{
+ LOGI << "DomeTexture: " << dome_texture_path << std::endl;
+ LOGI << "Sun angular rad: " << sun_angular_rad << std::endl;
+ LOGI << "Sun color angle: " << sun_color_angle << std::endl;
+ LOGI << "Ray to sun: " << ray_to_sun << std::endl;
+}
+
+void SkyInfo::SetDefaults() {
+ dome_texture_path.clear();
+}
diff --git a/Source/Game/skyinfo.h b/Source/Game/skyinfo.h
new file mode 100644
index 00000000..8fbeef14
--- /dev/null
+++ b/Source/Game/skyinfo.h
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------------
+// Name: skyinfo.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/mat4.h>
+#include <Math/quaternions.h>
+
+#include <Game/EntityDescription.h>
+#include <Asset/assets.h>
+#include <Scripting/scriptparams.h>
+
+struct SkyInfo {
+ std::string dome_texture_path;
+ float sun_angular_rad;
+ float sun_color_angle;
+ vec3 ray_to_sun;
+
+ void Print();
+ void SetDefaults();
+ void ReturnPaths(PathSet &path_set){};
+};
diff --git a/Source/Game/terraininfo.cpp b/Source/Game/terraininfo.cpp
new file mode 100644
index 00000000..9f5c9643
--- /dev/null
+++ b/Source/Game/terraininfo.cpp
@@ -0,0 +1,85 @@
+//-----------------------------------------------------------------------------
+// Name: terraininfo.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "terraininfo.h"
+
+#include <Internal/comma_separated_list.h>
+#include <Internal/filesystem.h>
+#include <Internal/returnpathutil.h>
+#include <Internal/levelxml.h>
+
+#include <Asset/Asset/material.h>
+#include <Logging/logdata.h>
+#include <Game/detailobjectlayer.h>
+
+#include <tinyxml.h>
+
+void TerrainInfo::Print()
+{
+ LOGI << "Minimal: " << minimal << std::endl;
+ LOGI << "Heightmap: " << heightmap << std::endl;
+ LOGI << "Colormap: " << colormap << std::endl;
+ LOGI << "Weightmap: " << weightmap << std::endl;
+ LOGI << "Model Override: " << model_override << std::endl;
+ LOGI << "Shader Extra: " << shader_extra << std::endl;
+ LOGI << "Detail maps:" << std::endl;
+ for (unsigned i=0; i<detail_map_info.size(); ++i)
+ {
+ detail_map_info[i].Print();
+ }
+ LOGI << "Detail objects: " << std::endl;
+ for (unsigned i=0; i<detail_object_info.size(); ++i)
+ {
+ LOGI << "Obj path: " << detail_object_info[i].obj_path << std::endl;
+ }
+}
+
+void TerrainInfo::SetDefaults() {
+ minimal = false;
+ colormap.clear();
+ detail_map_info.clear();
+ detail_object_info.clear();
+ heightmap.clear();
+ model_override.clear();
+ weightmap.clear();
+ shader_extra.clear();
+}
+
+void TerrainInfo::ReturnPaths( PathSet &path_set )
+{
+ path_set.insert("heightmap "+heightmap);
+ path_set.insert("texture "+colormap);
+ if(!weightmap.empty()){
+ path_set.insert("texture "+weightmap);
+ path_set.insert("image_sample "+weightmap);
+ }
+ if(!model_override.empty()){
+ path_set.insert("model "+model_override);
+ }
+ for(unsigned i=0; i<detail_map_info.size(); ++i){
+ detail_map_info[i].ReturnPaths(path_set);
+ }
+ for(unsigned i=0; i<detail_object_info.size(); ++i){
+ detail_object_info[i].ReturnPaths(path_set);
+ }
+}
diff --git a/Source/Game/terraininfo.h b/Source/Game/terraininfo.h
new file mode 100644
index 00000000..7031294a
--- /dev/null
+++ b/Source/Game/terraininfo.h
@@ -0,0 +1,48 @@
+//-----------------------------------------------------------------------------
+// Name: terraininfo.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Game/EntityDescription.h>
+#include <Game/detailobjectlayer.h>
+
+#include <Math/mat4.h>
+#include <Math/quaternions.h>
+
+#include <Asset/assets.h>
+#include <Scripting/scriptparams.h>
+#include <Graphics/detailmapinfo.h>
+
+struct TerrainInfo {
+ bool minimal;
+ std::string heightmap;
+ std::string colormap;
+ std::string weightmap;
+ std::string model_override;
+ std::string shader_extra;
+ std::vector<DetailMapInfo> detail_map_info;
+ std::vector<DetailObjectLayer> detail_object_info;
+ void Print();
+ void SetDefaults();
+ void ReturnPaths(PathSet &path_set);
+};
diff --git a/Source/Global/global_config.h b/Source/Global/global_config.h
new file mode 100644
index 00000000..ec9e8d37
--- /dev/null
+++ b/Source/Global/global_config.h
@@ -0,0 +1,24 @@
+//-----------------------------------------------------------------------------
+// Name: global_config.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+static const int kPathSize = 2048;
diff --git a/Source/Graphics/Billboard.cpp b/Source/Graphics/Billboard.cpp
new file mode 100644
index 00000000..2941254d
--- /dev/null
+++ b/Source/Graphics/Billboard.cpp
@@ -0,0 +1,131 @@
+//-----------------------------------------------------------------------------
+// Name: Billboard.cpp
+// Developer: Wolfire Games LLC
+// Author: Phillip Isola
+// Description: Images that rotate to face the camera
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "Billboard.h"
+
+#include <Graphics/vbocontainer.h>
+#include <Graphics/camera.h>
+#include <Graphics/graphics.h>
+#include <Graphics/shaders.h>
+#include <Graphics/textures.h>
+
+#include <Math/overgrowth_geometry.h>
+#include <Math/vec3math.h>
+#include <Math/vec4math.h>
+
+#include <Internal/profiler.h>
+
+#include <cmath>
+
+static RC_VBOContainer vbo;
+
+void DrawBillboard(const TextureRef& texture_ref, const vec3& pos, float scale, const vec4& color, AlphaMode alpha_mode){
+ if (!texture_ref.valid()) return;
+ Camera* cam = ActiveCameras::Get();
+ if(!cam->checkSphereInFrustum(pos, scale*0.25f)){
+ return;
+ }
+ PROFILER_GPU_ZONE(g_profiler_ctx, "DrawBillboard()");
+ if( !vbo->valid())
+ {
+ const float origo[] = {
+ 0.0f,0.0f,0.0f,
+ };
+
+ vbo->Fill(kVBOFloat | kVBOStatic, sizeof(origo), (void*)&origo[0]);
+ }
+
+ Shaders* shaders = Shaders::Instance();
+ Graphics* graphics = Graphics::Instance();
+
+ GLState gl_state;
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "GLState");
+ gl_state.blend = (alpha_mode == kStraightBlend);
+ gl_state.cull_face = false;
+ gl_state.depth_write = !gl_state.blend;
+ gl_state.depth_test = true;
+ if(alpha_mode == kPremultiplied){
+ gl_state.blend_src = GL_ONE;
+ }
+ CHECK_GL_ERROR();
+ graphics->setGLState(gl_state);
+ }
+
+ int shader_id;
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Shader");
+ if(graphics->use_sample_alpha_to_coverage || gl_state.blend){
+ shader_id = shaders->returnProgram("billboard #ALPHA_TO_COVERAGE #FLIP_Y", Shaders::kGeometry);
+ } else {
+ shader_id = shaders->returnProgram("billboard #FLIP_Y", Shaders::kGeometry);
+ }
+
+ shaders->setProgram(shader_id);
+
+ }
+
+ Textures::Instance()->bindTexture(texture_ref);
+
+ mat4 model;
+ model.SetTranslation(pos);
+
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Uniforms");
+ shaders->SetUniformMat4("mv", cam->GetViewMatrix() * model);
+ shaders->SetUniformMat4("p", cam->GetProjMatrix() );
+ shaders->SetUniformVec2("billboardDim", vec2(1,1));
+ vec4 temp_color = color;
+ float mult = powf(Graphics::Instance()->hdr_white_point, 2.2f);
+ temp_color[0] *= mult;
+ temp_color[1] *= mult;
+ temp_color[2] *= mult;
+ shaders->SetUniformVec4("color", temp_color);
+ //shaders->SetUniformVec2("screenDim", vec2(graphics->config_.screen_width(), graphics->config_.screen_height()));
+ shaders->SetUniformFloat("scale", scale);
+ }
+
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Attrib");
+ int vert_attrib_id = shaders->returnShaderAttrib("vert_attrib", shader_id);
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ vbo->Bind();
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, sizeof(float) * 3, (const void*)0);
+ }
+
+ if(graphics->use_sample_alpha_to_coverage){
+ glEnable( GL_SAMPLE_ALPHA_TO_COVERAGE );
+ }
+
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "DrawArrays");
+ graphics->DrawArrays( GL_POINTS, 0, vbo->size()/sizeof(float)/3 );
+ }
+
+ if(graphics->use_sample_alpha_to_coverage){
+ glDisable( GL_SAMPLE_ALPHA_TO_COVERAGE );
+ }
+
+ graphics->ResetVertexAttribArrays();
+}
diff --git a/Source/Graphics/Billboard.h b/Source/Graphics/Billboard.h
new file mode 100644
index 00000000..40312129
--- /dev/null
+++ b/Source/Graphics/Billboard.h
@@ -0,0 +1,35 @@
+//-----------------------------------------------------------------------------
+// Name: Billboard.h
+// Developer: Wolfire Games LLC
+// Author: Phillip Isola
+// Description: Images that rotate to face the camera
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+#include <Math/vec4.h>
+
+#include <Asset/Asset/texture.h>
+#include <Graphics/glstate.h>
+
+#include <string>
+
+void DrawBillboard(const TextureRef& texture_ref, const vec3& pos, float scale, const vec4& color, AlphaMode alpha_mode);
diff --git a/Source/Graphics/ColorWheel.cpp b/Source/Graphics/ColorWheel.cpp
new file mode 100644
index 00000000..f480d442
--- /dev/null
+++ b/Source/Graphics/ColorWheel.cpp
@@ -0,0 +1,130 @@
+//-----------------------------------------------------------------------------
+// Name: ColorWheel.cpp
+// Developer: Wolfire Games LLC
+// Author: Phillip Isola
+// Description: Radial parameterization of color; color theory functions.
+// Hue parameterized by angle in degrees. Saturation and value
+// parameterized by range [0,1].
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "ColorWheel.h"
+
+#include <Math/enginemath.h>
+
+#include <algorithm>
+
+vec3 HSVtoRGB(const vec3 &hsv) {
+ float h = hsv[0];
+ while (h < 0) h += 360;
+ int hi = (int)(h/60);
+ float f = h/60 - hi;
+ hi = hi%6;
+ float p = hsv[2]*(1-hsv[1]);
+ float q = hsv[2]*(1-(f*hsv[1]));
+ float t = hsv[2]*(1-((1-f)*hsv[1]));
+
+ switch (hi) {
+ case 0:
+ return vec3(hsv[2],t,p);
+ break;
+ case 1:
+ return vec3(q,hsv[2],p);
+ break;
+ case 2:
+ return vec3(p,hsv[2],t);
+ break;
+ case 3:
+ return vec3(p,q,hsv[2]);
+ break;
+ case 4:
+ return vec3(t,p,hsv[2]);
+ break;
+ case 5:
+ return vec3(hsv[2],p,q);
+ break;
+ }
+ return vec3(0,0,0);
+}
+
+vec4 HSVtoRGB(float h, float s, float v) {
+ vec3 rgb = HSVtoRGB(vec3(h,s,v));
+ return vec4(rgb,0.0f);
+}
+
+vec3 RGBtoHSV(const vec3 &rgb){
+ vec3 hsv;
+
+ // calc hue
+ float max_val = max(max(rgb[0],rgb[1]), rgb[2]);
+ float min_val = min(min(rgb[0],rgb[1]), rgb[2]);
+ float range = max_val - min_val;
+
+ if (max_val == min_val) hsv[0] = 0;
+ else if (max_val == rgb[0]) {
+ hsv[0] = (float)(((int)(60 * ((rgb[1] - rgb[2])/range)))%360);
+ }
+ else if (max_val == rgb[1]) {
+ hsv[0] = (float)(60 * ((rgb[2] - rgb[0])/range) + 120);
+ }
+ else {
+ hsv[0] = (float)(60 * ((rgb[0] - rgb[1])/range) + 240);
+ }
+
+ // calc saturation
+ if (max_val == 0) {
+ hsv[1] = 0;
+ }
+ else {
+ hsv[1] = range/max_val;
+ }
+
+ // calc value
+ hsv[2] = max_val;
+
+ return hsv;
+}
+
+void RGBtoHSV(const vec4& rgb, float& h, float& s, float& v) {
+ vec3 hsv = RGBtoHSV(rgb.xyz());
+ h = hsv[0];
+ s = hsv[1];
+ v = hsv[2];
+}
+
+vec4 RotateHueRGB(const vec4& rgb, float angle) {
+
+ float h, s, v;
+ RGBtoHSV(rgb, h, s, v);
+ RotateHueHSV(h,angle);
+ return HSVtoRGB(h,s,v);
+}
+
+vec4 GetComplementRGB(const vec4& rgb) {
+ return RotateHueRGB(rgb,180);
+}
+
+void RotateHueHSV(float& h, float angle) {
+ h += angle;
+}
+
+void GetComplementHSV(float& h, float& s, float& v) {
+ RotateHueHSV(h,180);
+}
+
diff --git a/Source/Graphics/ColorWheel.h b/Source/Graphics/ColorWheel.h
new file mode 100644
index 00000000..92fcf168
--- /dev/null
+++ b/Source/Graphics/ColorWheel.h
@@ -0,0 +1,37 @@
+//-----------------------------------------------------------------------------
+// Name: ColorWheel.h
+// Developer: Wolfire Games LLC
+// Author: Phillip Isola
+// Description: Radial parameterization of color; color theory functions.
+// Hue parameterized by angle in degrees. Saturation and value
+// parameterized by range [0,1].
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec4.h>
+
+vec4 HSVtoRGB(float h, float s, float v);
+void RGBtoHSV(const vec4& rgb, float& h, float& s, float& v);
+vec3 RGBtoHSV(const vec3 &rgb);
+vec4 GetComplementRGB(const vec4& rgb);
+vec4 RotateHueRGB(const vec4& rgb, float angle);
+void GetComplementHSV(float& h, float& s, float& v);
+void RotateHueHSV(float& h, float angle);
diff --git a/Source/Graphics/Cursor.cpp b/Source/Graphics/Cursor.cpp
new file mode 100644
index 00000000..4fecb6a6
--- /dev/null
+++ b/Source/Graphics/Cursor.cpp
@@ -0,0 +1,177 @@
+//-----------------------------------------------------------------------------
+// Name: Cursor.cpp
+// Developer: Wolfire Games LLC
+// Author: Phillip Isola
+// Description: Singleton graphical cursor.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "Cursor.h"
+
+#include <Graphics/textures.h>
+#include <Graphics/graphics.h>
+
+#include <UserInput/input.h>
+#include <Main/engine.h>
+
+void GameCursor::Draw() {
+ if(Graphics::Instance()->media_mode()) {
+ UIShowCursor(true);
+ return;
+ }
+
+ if (!visible || !m_texture_ref.valid()) return;
+
+ CHECK_GL_ERROR();
+ int* mouse_pos = Input::Instance()->getMouse().pos_;
+ int* window_dims = Graphics::Instance()->window_dims;
+ vec3 draw_pos(
+ (float)( mouse_pos[0] + m_offset_x ),
+ (float)( (window_dims[1]-mouse_pos[1]) - m_offset_y ),
+ 0.0f);
+ Textures::Instance()->drawTexture(m_texture_ref, draw_pos,
+ m_size, m_rotation);
+ CHECK_GL_ERROR();
+}
+
+void GameCursor::SetCursor(_cursor_type t) {
+
+ //std::pair<int, int> dimensions;
+ switch(t) {
+ case DEFAULT_CURSOR:
+ if(!default_cursor_asset.valid()){
+ default_cursor_asset = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/Cursors/cursor_nocompress.png", PX_SRGB | PX_NOREDUCE, 0x0);
+ }
+ m_texture_ref = default_cursor_asset->GetTextureRef();
+ m_type = DEFAULT_CURSOR;
+ m_offset_x = 12;
+ m_offset_y = 15;
+ m_size = 32.0f;
+ break;
+ case TRANSLATE_CURSOR:
+ if(!translate_cursor_asset.valid()){
+ translate_cursor_asset = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/Cursors/translate_nocompress.png",PX_SRGB | PX_NOREDUCE, 0x0);
+ }
+ m_texture_ref = translate_cursor_asset->GetTextureRef();
+ m_type = TRANSLATE_CURSOR;
+ m_size = 32.0f;
+ m_offset_x = 12;
+ m_offset_y = 15;
+ break;
+ case SCALE_CURSOR:
+ if(!scale_cursor_asset.valid()){
+ scale_cursor_asset = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/Cursors/scale_nocompress.png",PX_SRGB | PX_NOREDUCE, 0x0);
+ }
+ m_texture_ref = scale_cursor_asset->GetTextureRef();
+ m_type = SCALE_CURSOR;
+ m_size = 32.0f;
+ m_offset_x = 12;
+ m_offset_y = 15;
+ break;
+ case ROTATE_CURSOR:
+ if(!rotate_cursor_asset.valid()){
+ rotate_cursor_asset = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/Cursors/rotate_nocompress.png",PX_SRGB | PX_NOREDUCE, 0x0);
+ }
+ m_texture_ref = rotate_cursor_asset->GetTextureRef();
+ m_type = ROTATE_CURSOR;
+ m_size = 32.0f;
+ m_offset_x = 12;
+ m_offset_y = 15;
+ break;
+ case ROTATE_CIRCLE_CURSOR:
+ if(!rotate_circle_cursor_asset.valid()){
+ rotate_circle_cursor_asset = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/Cursors/rotate_circle_nocompress.png", PX_SRGB | PX_NOREDUCE, 0x0);
+ }
+ m_texture_ref = rotate_circle_cursor_asset->GetTextureRef();
+ m_type = ROTATE_CIRCLE_CURSOR;
+ m_size = 36.0f;
+ m_offset_x = m_offset_y = 0;
+ break;
+ case ADD_CURSOR:
+ if(!add_cursor_asset.valid()){
+ add_cursor_asset = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/Cursors/plus_nocompress.png", PX_SRGB | PX_NOREDUCE, 0x0);
+ }
+ m_texture_ref = add_cursor_asset->GetTextureRef();
+ m_type = ADD_CURSOR;
+ m_size = 32.0f;
+ m_offset_x = 12;
+ m_offset_y = 15;
+ break;
+ case REMOVE_CURSOR:
+ if(!remove_cursor_asset.valid()){
+ remove_cursor_asset = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/Cursors/minus_nocompress.png", PX_SRGB | PX_NOREDUCE, 0x0);
+ }
+ m_texture_ref = remove_cursor_asset->GetTextureRef();
+ m_type = REMOVE_CURSOR;
+ m_size = 32.0f;
+ m_offset_x = 12;
+ m_offset_y = 15;
+ break;
+ case LINK_CURSOR:
+ if(!link_cursor_asset.valid()){
+ link_cursor_asset = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/Cursors/link_nocompress.png", PX_SRGB | PX_NOREDUCE, 0x0);
+ }
+ m_texture_ref = link_cursor_asset->GetTextureRef();
+ m_type = LINK_CURSOR;
+ m_size = 32.0f;
+ m_offset_x = 12;
+ m_offset_y = 15;
+ break;
+ case UNLINK_CURSOR:
+ if(!unlink_cursor_asset.valid()){
+ unlink_cursor_asset = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/Cursors/unlink_nocompress.png", PX_SRGB | PX_NOREDUCE, 0x0);
+ }
+ m_texture_ref = unlink_cursor_asset->GetTextureRef();
+ m_type = UNLINK_CURSOR;
+ m_size = 32.0f;
+ m_offset_x = 12;
+ m_offset_y = 15;
+ break;
+ case HAND_CURSOR:
+ if(!hand_cursor_asset.valid()){
+ hand_cursor_asset = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/Cursors/pointer_nocompress.png", PX_SRGB | PX_NOREDUCE, 0x0);
+ }
+ m_texture_ref = hand_cursor_asset->GetTextureRef();
+ m_type = HAND_CURSOR;
+ m_size = 32.0f;
+ m_offset_x = 12;
+ m_offset_y = 15;
+ break;
+
+ }
+}
+
+void GameCursor::SetVisible(bool _visible) {
+ visible = _visible;
+}
+
+void GameCursor::SetCursor(const TextureAssetRef &t_id) {
+ external_cursor_asset = t_id;
+ m_texture_ref = external_cursor_asset->GetTextureRef();
+}
+
+void GameCursor::setCursor(std::string texture_name) {
+ external_cursor_asset = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(texture_name.c_str(), PX_SRGB | PX_NOREDUCE, 0x0);
+ m_texture_ref = external_cursor_asset->GetTextureRef();
+}
+
+void GameCursor::Rotate(float angle) {
+ m_rotation += angle;
+}
+
diff --git a/Source/Graphics/Cursor.h b/Source/Graphics/Cursor.h
new file mode 100644
index 00000000..60adfae5
--- /dev/null
+++ b/Source/Graphics/Cursor.h
@@ -0,0 +1,68 @@
+//-----------------------------------------------------------------------------
+// Name: Cursor.h
+// Developer: Wolfire Games LLC
+// Author: Phillip Isola
+// Description: Singleton graphical cursor.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/Asset/texture.h>
+
+#include <string>
+
+enum _cursor_type { DEFAULT_CURSOR, TRANSLATE_CURSOR, SCALE_CURSOR, ROTATE_CURSOR, ROTATE_CIRCLE_CURSOR, ADD_CURSOR, REMOVE_CURSOR, LINK_CURSOR, UNLINK_CURSOR, HAND_CURSOR };
+
+class GameCursor {
+public:
+ GameCursor() { m_type = DEFAULT_CURSOR; m_size = 40.0f; m_width = 32; m_height = 32; m_rotation = 0;
+ m_offset_x = m_offset_y = 0; visible = true;}
+ void Draw();
+ void SetVisible(bool _visible);
+ void SetCursor(_cursor_type t);
+ void SetCursor(const TextureAssetRef &t_id);
+ void setCursor(std::string texture_name);
+ void Rotate(float angle);
+ inline void SetRotation(float angle) { m_rotation = angle; }
+ inline void SetSize(int width, int height) { m_width = width; m_height = height; }
+
+ bool visible;
+
+private:
+ TextureAssetRef hand_cursor_asset;
+ TextureAssetRef link_cursor_asset;
+ TextureAssetRef unlink_cursor_asset;
+ TextureAssetRef remove_cursor_asset;
+ TextureAssetRef add_cursor_asset;
+ TextureAssetRef rotate_circle_cursor_asset;
+ TextureAssetRef rotate_cursor_asset;
+ TextureAssetRef scale_cursor_asset;
+ TextureAssetRef translate_cursor_asset;
+ TextureAssetRef default_cursor_asset;
+
+ TextureAssetRef external_cursor_asset;
+
+ _cursor_type m_type;
+ TextureRef m_texture_ref;
+ float m_rotation;
+ float m_size;
+ int m_width, m_height;
+ int m_offset_x, m_offset_y;
+};
diff --git a/Source/Graphics/animationclient.cpp b/Source/Graphics/animationclient.cpp
new file mode 100644
index 00000000..023b64a8
--- /dev/null
+++ b/Source/Graphics/animationclient.cpp
@@ -0,0 +1,577 @@
+//-----------------------------------------------------------------------------
+// Name: animationclient.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "animationclient.h"
+
+#include <Graphics/retargetfile.h>
+#include <Graphics/skeleton.h>
+
+#include <Math/enginemath.h>
+#include <Math/vec3math.h>
+
+#include <Internal/timer.h>
+#include <Internal/profiler.h>
+
+#include <Asset/Asset/syncedanimation.h>
+#include <Scripting/angelscript/ascontext.h>
+#include <Logging/logdata.h>
+#include <Utility/assert.h>
+
+#include <cassert>
+
+extern AnimationConfig animation_config;
+extern Timer game_timer;
+
+void AnimationClient::SwapAnimation( const std::string &path, char flags ) {
+ current_anim.clear();
+ reader.SetCallbackString("");
+ SetAnimation(path, 100.0f, flags | _ANM_SWAP);
+}
+
+void AnimationClient::ApplyAnimationFlags(AnimationReader &reader, AnimationAssetRef &new_ref, char _flags) {
+ flags = _flags;
+ bool swap = (flags & _ANM_SWAP)!=0;
+ bool mobile = (flags & _ANM_MOBILE)!=0;
+ bool super_mobile = (flags & _ANM_SUPER_MOBILE)!=0;
+ bool mirrored = (flags & _ANM_MIRRORED)!=0;
+ float last_rotation;
+ float old_time;
+ if(swap){
+ old_time = reader.GetAbsoluteTime();
+ mobile = reader.GetMobile();
+ super_mobile = reader.GetSuperMobile();
+ mirrored = reader.GetMirrored();
+ last_rotation = reader.GetLastRotation();
+ }
+ reader.AttachTo(new_ref);
+ if(swap){
+ reader.SetAbsoluteTime(old_time);
+ reader.SetLastRotation(last_rotation);
+ } else {
+ reader.SetSpeedMult(1.0f);
+ }
+ reader.SetMobile(mobile);
+ reader.SetSuperMobile(super_mobile);
+
+ std::vector<mat4> initial_mats(skeleton->physics_bones.size());
+ for(unsigned i=0; i<skeleton->physics_bones.size(); ++i){
+ initial_mats[i] = skeleton->physics_bones[i].initial_rotation;
+ }
+
+ if(mirrored){
+ reader.SetMirrored(true);
+ } else {
+ reader.SetMirrored(false);
+ }
+
+ reader.ClearBlendAnim();
+}
+
+void AnimationClient::SetAnimation( const std::string &path,
+ float fade_speed,
+ char flags)
+{
+ if(flags & _ANM_FROM_START){
+ current_anim.clear();
+ }
+ if(current_anim == path){
+ return;
+ }
+
+ AnimationAssetRef new_ref;
+ if(animation_config.kForceIdleAnim){
+ new_ref = ReturnAnimationAssetRef("Data/Animations/r_actionidle.xml");
+ } else {
+ Path new_path = FindFilePath(path);
+ if(new_path.isValid()){
+ new_ref = ReturnAnimationAssetRef(path);
+ if(new_ref.valid() == false ){
+ new_ref = ReturnAnimationAssetRef("Data/Animations/r_actionidle.xml");
+ }
+ }
+ }
+
+ if(new_ref.valid()){
+ if(reader.valid()){
+ fade_out.AddFadingAnimation(reader, blendmap, fade_speed);
+ }
+ ApplyAnimationFlags(reader, new_ref, flags);
+ current_anim = path;
+ }
+}
+
+std::queue<AnimationEvent> AnimationClient::GetActiveEvents() {
+ std::queue<AnimationEvent> total_events;
+ {
+ std::queue<AnimationEvent> &events = reader.GetActiveEvents();
+ while(!events.empty()){
+ total_events.push(events.front());
+ events.pop();
+ }
+ }
+ for(unsigned i=0; i<layers.size(); ++i){
+ std::queue<AnimationEvent> &events = layers[i].reader.GetActiveEvents();
+ while(!events.empty()){
+ total_events.push(events.front());
+ events.pop();
+ }
+ }
+ return total_events;
+}
+
+void AnimationClient::SetSpeedMult(float speed_mult) {
+ if(reader.valid()){
+ reader.SetSpeedMult(speed_mult);
+ }
+}
+
+void AnimationClient::Update(float timestep) {
+ if(!reader.valid()){
+ return;
+ }
+ if(reader.IncrementTime(blendmap, timestep)){
+ const std::string& callback = reader.GetCallbackString();
+ if(!callback.empty()){
+ as_context->CallScriptFunction(callback,NULL,NULL,true);
+ reader.SetCallbackString("");
+ }
+ }
+ fade_out.Update(blendmap, as_context, timestep);
+ for(unsigned i=0; i<layers.size(); ++i){
+ layers[i].fade_out.Update(blendmap, as_context, timestep);
+ }
+ UpdateLayers(timestep);
+}
+
+const float _three_halves_pi = 4.7124f;
+const float _pi = 3.1416f;
+const float _overshoot_amount = 0.3f;
+
+void AnimationClient::GetMatrices( AnimOutput &anim_output, const std::vector<int> *parents) {
+ if(!reader.valid()){
+ return;
+ }
+ AnimInput ang_anim_input(blendmap, parents);
+ ang_anim_input.retarget_new = retarget_new;
+ {
+ PROFILER_ZONE(g_profiler_ctx, "reader.GetMatrices()");
+ reader.GetMatrices(ang_anim_output, ang_anim_input);
+ }
+
+ if(special_rotation != 0.0f){
+ mat4 special_rotation_mat;
+ special_rotation_mat.SetRotationY(special_rotation);
+ ang_anim_output.delta_offset = special_rotation_mat * ang_anim_output.delta_offset;
+ special_rotation = 0.0f;
+ }
+
+ ang_anim_output.delta_offset += invert(rotation_matrix) * delta_offset_overflow;
+ ang_anim_output.delta_rotation += delta_rotation_overflow;
+ if(ang_anim_output.delta_rotation > (float)PI){
+ ang_anim_output.delta_rotation -= (float)PI * 2.0f;
+ }
+ if(ang_anim_output.delta_rotation < (float)-PI){
+ ang_anim_output.delta_rotation += (float)PI * 2.0f;
+ }
+ float opac = fade_out.GetOpac();
+ delta_offset_overflow = rotation_matrix * (ang_anim_output.delta_offset * (1.0f-opac));
+ delta_rotation_overflow = ang_anim_output.delta_rotation * (1.0f-opac);
+ ang_anim_output.delta_offset *= opac;
+ ang_anim_output.delta_rotation *= opac;
+
+ vec3 old_delta_offset = ang_anim_output.delta_offset;
+ ang_anim_output.delta_offset = vec3(0.0f);
+
+ float old_delta_rotation = ang_anim_output.delta_rotation;
+ ang_anim_output.delta_rotation = 0.0f;
+
+ if(!animation_config.kDisableAnimationTransition){
+ // Mix with angular matrices from fading animations
+ fade_out.ApplyAngular(ang_anim_input, ang_anim_output, temp_ang_anim_output, retarget_new, parents);
+ }
+ if(!animation_config.kDisableAnimationLayers){
+ for(int i=(int)layers.size()-1; i>=0; i--){
+ AnimInput old_ang_anim_input(blendmap, parents);
+ old_ang_anim_input.retarget_new = retarget_new;
+ layers[i].reader.GetMatrices(temp_ang_anim_output, old_ang_anim_input);
+ if(layers[i].fade_out.fade_out.size()){
+ layers[i].fade_out.ApplyAngular(old_ang_anim_input, temp_ang_anim_output, temp_ang_anim_output2, retarget_new, parents);
+ }
+ ang_anim_output = add_mix(ang_anim_output,temp_ang_anim_output,layers[i].opac);
+ }
+ }
+ ang_anim_output.delta_offset += old_delta_offset;
+ ang_anim_output.delta_rotation += old_delta_rotation;
+
+ std::vector<BoneTransform> matrices(ang_anim_output.matrices.size());
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Apply Parent Rotations");
+ // Blend between angular and linear matrices based on IK weights
+ if(parents){
+ std::vector<BoneTransform> temp_matrices(matrices.size());
+ for(unsigned j=0; j<matrices.size(); j++){
+ temp_matrices[j] = ApplyParentRotations(ang_anim_output.matrices, j, *parents);
+ }
+ matrices = temp_matrices;
+ }
+ }
+
+ std::vector<BoneTransform> weapon_matrices = ang_anim_output.weapon_matrices;
+
+ // Apply character rotation modifier
+ std::map<int, WeapAnimInfo> &weap_anim_info_map = ang_anim_output.weap_anim_info_map;
+ for(int i=0, len=ang_anim_output.ik_bones.size(); i<len; ++i){
+ ang_anim_output.ik_bones[i].unmodified_transform = ang_anim_output.ik_bones[i].transform;
+ }
+
+ anim_output.unmodified_matrices = matrices;
+
+ // Put together output
+ anim_output.matrices = matrices;
+ anim_output.weap_anim_info_map = weap_anim_info_map;
+ anim_output.weapon_matrices = weapon_matrices;
+ anim_output.weapon_weights = ang_anim_output.weapon_weights;
+ anim_output.weapon_weight_weights = ang_anim_output.weapon_weight_weights;
+ anim_output.weapon_relative_ids = ang_anim_output.weapon_relative_ids;
+ anim_output.weapon_relative_weights = ang_anim_output.weapon_relative_weights;
+ anim_output.physics_weights = ang_anim_output.physics_weights;
+ anim_output.ik_bones = ang_anim_output.ik_bones;
+ anim_output.shape_keys = ang_anim_output.shape_keys;
+ anim_output.status_keys = ang_anim_output.status_keys;
+ anim_output.center_offset = rotation_matrix * ang_anim_output.center_offset;
+ anim_output.delta_offset = rotation_matrix * ang_anim_output.delta_offset;
+ anim_output.rotation = ang_anim_output.rotation;
+ anim_output.delta_rotation = ang_anim_output.delta_rotation;
+}
+
+vec3 AnimationClient::GetAvgTranslation(const std::vector<BoneTransform> &matrices ) {
+ vec3 center(0.0f);
+ float total_mass = 0.0f;
+ for(unsigned j=0; j<matrices.size(); j++){
+ center += matrices[j].origin*bone_masses[j];
+ total_mass += bone_masses[j];
+ }
+ LOG_ASSERT(total_mass != 0.0f);
+ center /= total_mass;
+ return center;
+}
+
+void AnimationClient::SetBlendCoord( const std::string &label, float coord ) {
+ blendmap[label] = coord;
+}
+
+void FadeCollection::Update(const BlendMap& blendmap, ASContext *as_context, float timestep) {
+ std::vector<AnimationFadeOut>::iterator iter;
+ for(iter = fade_out.begin(); iter != fade_out.end();){
+ AnimationFadeOut &the_fade_out = (*iter);
+
+ the_fade_out.reader.IncrementTime(blendmap, timestep);
+ the_fade_out.opac -= timestep *
+ the_fade_out.fade_speed;
+
+ if(the_fade_out.opac <= 0.0f){
+ const std::string& callback = the_fade_out.reader.GetCallbackString();
+ if(!callback.empty()){
+ as_context->CallScriptFunction(callback);
+ the_fade_out.reader.SetCallbackString("");
+ }
+ iter = fade_out.erase(iter);
+ } else {
+ iter++;
+ }
+ }
+}
+
+void FadeCollection::ApplyAngular( AnimInput &ang_anim_input,
+ AnimOutput &ang_anim_output,
+ AnimOutput &temp_ang_anim_output,
+ const std::string &retarget_new,
+ const std::vector<int> *parents)
+{
+ for(int i=(int)fade_out.size()-1; i>=0; --i){
+ AnimInput temp_ang_anim_input(fade_out[i].blendmap, parents);
+ temp_ang_anim_input.retarget_new = retarget_new;
+ fade_out[i].reader.GetMatrices(temp_ang_anim_output, temp_ang_anim_input);
+ float temp_opac;
+ if(fade_out[i].overshoot){
+ temp_opac = sinf(fade_out[i].opac*_three_halves_pi+_pi)*fade_out[i].opac;
+ if(temp_opac<0.0f){
+ temp_opac *= _overshoot_amount;
+ }
+ } else {
+ temp_opac = fade_out[i].opac;
+ }
+ ang_anim_output = mix(ang_anim_output,temp_ang_anim_output,temp_opac);
+ }
+}
+
+void FadeCollection::AddFadingAnimation( AnimationReader &reader, const BlendMap& blendmap, float fade_speed )
+{
+ fade_out.resize(fade_out.size()+1);
+ fade_out.back().opac = 1.0f;
+ fade_out.back().reader = reader;
+ fade_out.back().blendmap = blendmap;
+ if(fade_speed<0.0f){
+ fade_out.back().fade_speed = fade_speed * -0.33f;
+ fade_out.back().overshoot = true;
+ } else {
+ fade_out.back().fade_speed = fade_speed;
+ fade_out.back().overshoot = false;
+ }
+}
+
+void FadeCollection::clear()
+{
+ fade_out.clear();
+}
+
+float FadeCollection::GetOpac()
+{
+ float opac = 1.0f;
+ for(unsigned i=0; i<fade_out.size(); ++i){
+ opac *= (1.0f-fade_out[i].opac);
+ }
+ return opac;
+}
+
+
+void AnimationClient::UpdateLayers(float timestep) {
+ bool deleted = false;
+ std::vector<AnimationLayer>::iterator iter;
+ for(iter = layers.begin(); iter != layers.end();){
+ AnimationLayer &the_layer = (*iter);
+
+ the_layer.reader.IncrementTime(blendmap, timestep);
+
+ if(the_layer.reader.Finished() || the_layer.fading){
+ the_layer.fade_opac -= timestep * the_layer.fade_speed;
+ } else {
+ the_layer.fade_opac += timestep * the_layer.fade_speed;
+ the_layer.fade_opac = min(1.0f, the_layer.fade_opac);
+ }
+ the_layer.opac = the_layer.fade_opac * the_layer.opac_mult;
+ if(the_layer.fade_opac <= 0.0f){
+ ASArglist args;
+ args.Add(the_layer.id);
+ as_context->CallScriptFunction(as_funcs.layer_removed, &args);
+ iter = layers.erase(iter);
+ deleted = true;
+ } else {
+ iter++;
+ }
+ }
+ if(deleted){
+ /*printf("Layers after remove:\n");
+ for(unsigned i=0; i<layers.size(); ++i){
+ printf("%d %s\n", layers[i].id, layers[i].reader.GetPath().c_str());
+ }*/
+ }
+}
+
+void AnimationClient::SetASContext( ASContext * _as_context ) {
+ as_context = _as_context;
+
+ as_funcs.layer_removed = as_context->RegisterExpectedFunction("void LayerRemoved(int id)",true);
+
+ as_context->LoadExpectedFunctions();
+}
+
+void AnimationClient::SetCallbackString( const std::string &_callback_string ) {
+ reader.SetCallbackString(_callback_string);
+}
+
+void AnimationClient::SetRotationMatrix( const mat4 &matrix ) {
+ rotation_matrix = matrix;
+}
+
+const mat4 &AnimationClient::GetRotationMatrix( ) {
+ return rotation_matrix;
+}
+
+void AnimationClient::SetBoneMasses( const std::vector<float> &_bone_masses ) {
+ bone_masses = _bone_masses;
+}
+
+void AnimationClient::Reset() {
+ current_anim.clear();
+ fade_out.clear();
+ layers.clear();
+ reader.clear();
+ delta_offset_overflow = vec3(0.0f);
+ delta_rotation_overflow = 0.0f;
+ special_rotation = 0.0f;
+}
+
+void AnimationClient::SetSkeleton( Skeleton *_skeleton ) {
+ skeleton = _skeleton;
+}
+
+void AnimationClient::SetRetargeting( const std::string &new_path) {
+ retarget_new = new_path;
+}
+
+int AnimationClient::AddLayer( const std::string &path, float fade_speed /*= _default_fade_speed*/, char flags /*= NULL*/ ) {
+ AnimationAssetRef new_ref = ReturnAnimationAssetRef(path);
+ if(new_ref.valid()){
+ layers.resize(layers.size()+1);
+ layers.back().fade_opac = fade_speed*game_timer.timestep;
+ layers.back().opac_mult = 1.0f;
+ layers.back().opac = layers.back().fade_opac * layers.back().opac_mult;
+ layers.back().fade_speed = fade_speed;
+ layers.back().fading = false;
+ bool conflict = true;
+ int new_id = 0;
+ while(conflict){
+ conflict = false;
+ for(unsigned i=0; i<layers.size()-1; ++i){
+ if(new_id == layers[i].id){
+ ++new_id;
+ conflict = true;
+ }
+ }
+ }
+ layers.back().id = new_id;
+ ApplyAnimationFlags(layers.back().reader, new_ref, flags);
+ return layers.back().id;
+ }
+ return -1;
+}
+
+void AnimationClient::RemoveLayer( int which, float fade_speed ) {
+ int which_layer = -1;
+ for(unsigned i=0; i<layers.size(); ++i){
+ if(layers[i].id == which){
+ which_layer = i;
+ }
+ }
+ if(which_layer != -1){
+ layers[which_layer].fade_speed = fade_speed;
+ layers[which_layer].fading = true;
+ }
+}
+
+void AnimationClient::SetLayerOpacity( int which, float opac ) {
+ if( opac > 1.0f ) {
+ opac = 1.0f;
+ }
+
+ if( opac < 0.00001f ) {
+ opac = 0.00001f;
+ }
+
+ int which_layer = -1;
+ for(unsigned i=0; i<layers.size(); ++i){
+ if(layers[i].id == which){
+ which_layer = i;
+ }
+ }
+ if(which_layer != -1){
+ layers[which_layer].opac_mult = opac;
+ }
+}
+
+void AnimationClient::SetBlendAnimation( const std::string & path ) {
+ LOGD << "Setting blend animation: " << path << std::endl;
+
+ AnimationAssetRef new_ref = ReturnAnimationAssetRef(path);
+
+ std::vector<mat4> initial_mats(skeleton->physics_bones.size());
+ for(unsigned i=0; i<skeleton->physics_bones.size(); ++i){
+ initial_mats[i] = skeleton->physics_bones[i].initial_rotation;
+ }
+
+ reader.SetBlendAnim(new_ref);
+}
+
+void AnimationClient::AddAnimationOffset( const vec3 & offset )
+{
+ delta_offset_overflow += offset;
+}
+
+void AnimationClient::AddAnimationRotOffset( float offset )
+{
+ delta_rotation_overflow += offset;
+ special_rotation = offset;
+}
+
+void AnimationClient::ClearBlendAnimation()
+{
+ reader.ClearBlendAnim();
+}
+
+float AnimationClient::GetAnimationEventTime( const std::string & event_name ) const
+{
+ return reader.GetAnimationEventTime(event_name, blendmap);
+}
+
+AnimationClient::AnimationClient() {
+ Reset();
+}
+
+float AnimationClient::GetTimeUntilEvent( const std::string &event ) {
+ float time = reader.GetTimeUntilEvent(event);
+ int num_layers = layers.size();
+ for(int i=0; i<num_layers; ++i){
+ float layer_time = layers[i].reader.GetTimeUntilEvent(event);
+ if(layer_time != -1.0f){
+ if(time == -1.0f || layer_time < time){
+ time = layer_time;
+ }
+ }
+ }
+ return time;
+}
+
+void AnimationClient::SetAnimatedItemID( int index, int id )
+{
+ reader.SetAnimatedItemID(index, id);
+}
+
+void AnimationClient::SetLayerItemID( int layer_id, int index, int item_id )
+{
+ int which_layer = -1;
+ for(unsigned i=0; i<layers.size(); ++i){
+ if(layers[i].id == layer_id){
+ which_layer = i;
+ }
+ }
+ if(which_layer != -1){
+ layers[which_layer].reader.SetAnimatedItemID(index, item_id);
+ }
+}
+
+const std::string& AnimationClient::GetCurrAnim() const {
+ return current_anim;
+}
+
+float AnimationClient::GetNormalizedAnimTime() const {
+ return reader.GetTime();
+}
+
+float AnimationClient::GetAnimationSpeed() const {
+ return reader.GetSpeedNormalized(blendmap);
+}
+
+void AnimationClient::RemoveAllLayers() {
+ layers.clear();
+}
diff --git a/Source/Graphics/animationclient.h b/Source/Graphics/animationclient.h
new file mode 100644
index 00000000..ba7037d1
--- /dev/null
+++ b/Source/Graphics/animationclient.h
@@ -0,0 +1,133 @@
+//-----------------------------------------------------------------------------
+// Name: animationclient.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Graphics/animationreader.h>
+#include <Graphics/animationflags.h>
+
+#include <Asset/Asset/animation.h>
+#include <Scripting/angelscript/ascontext.h>
+
+class Skeleton;
+class ASContext;
+const float _default_fade_speed = 5.0f;
+
+struct AnimationFadeOut {
+ AnimationReader reader;
+ float opac;
+ BlendMap blendmap;
+ float fade_speed;
+ bool overshoot;
+};
+
+struct FadeCollection {
+ std::vector<AnimationFadeOut> fade_out;
+ void Update(const BlendMap& blendmap, ASContext *as_context, float timestep);
+ float GetOpac();
+ void ApplyAngular( AnimInput &ang_anim_input, AnimOutput &ang_anim_output, AnimOutput &old_ang_anim_output, const std::string &retarget_new, const std::vector<int> *parents);
+ void AddFadingAnimation( AnimationReader &reader, const BlendMap& blendmap, float fade_speed );
+ void clear();
+};
+
+struct AnimationLayer {
+ FadeCollection fade_out;
+ AnimationReader reader;
+ float fade_opac;
+ float opac;
+ float opac_mult;
+ float fade_speed;
+ bool fading;
+ int id;
+};
+
+class AnimationClient {
+ // Keeping these here instead of local just to avoid having to do as much
+ // memory allocation/deallocation
+ AnimOutput ang_anim_output;
+ AnimOutput temp_ang_anim_output;
+ AnimOutput temp_ang_anim_output2;
+ AnimOutput lin_anim_output;
+ AnimOutput temp_lin_anim_output;
+ AnimOutput temp_lin_anim_output2;
+
+ FadeCollection fade_out;
+ std::vector<AnimationLayer> layers;
+ AnimationReader reader;
+ BlendMap blendmap;
+ std::string current_anim;
+ mat4 rotation_matrix;
+ std::vector<float> bone_masses;
+ vec3 delta_offset_overflow;
+ float delta_rotation_overflow;
+ float special_rotation;
+
+ ASContext *as_context;
+ Skeleton *skeleton;
+ char flags;
+
+ std::string retarget_new;
+
+ struct {
+ ASFunctionHandle layer_removed;
+ } as_funcs;
+
+ void ApplyAnimationFlags( AnimationReader &reader, AnimationAssetRef &new_ref, char flags);
+
+public:
+ void SetAnimation( const std::string &path, float fade_speed = _default_fade_speed, char flags = 0);
+ int AddLayer( const std::string &path, float fade_speed = _default_fade_speed, char flags = 0);
+ void SetBlendCoord(const std::string &label, float coord);
+ void Update(float timestep);
+
+ void SetBoneMasses(const std::vector<float> &bone_masses);
+
+ void SetRotationMatrix(const mat4 &matrix);
+ void GetMatrices( AnimOutput &anim_output, const std::vector<int> *parents);
+ void SetASContext( ASContext * _as_context );
+ void SetCallbackString( const std::string &_callback_string );
+ void SwapAnimation( const std::string &path, char flags = 0);
+ const mat4 &GetRotationMatrix( );
+ vec3 GetAvgTranslation(const std::vector<BoneTransform> &matrices );
+ void Reset();
+ void SetSkeleton( Skeleton *_skeleton );
+ std::queue<AnimationEvent> GetActiveEvents();
+ void SetRetargeting( const std::string &new_path);
+ void UpdateLayers(float timestep);
+ void SetLayerOpacity( int layer, float opac );
+ void RemoveLayer( int which, float fade_speed );
+ void SetBlendAnimation( const std::string & path );
+ void AddAnimationOffset( const vec3 & offset );
+ void AddAnimationRotOffset( float offset );
+ void ClearBlendAnimation();
+ float GetAnimationEventTime( const std::string & event_name ) const;
+ AnimationClient();
+ void SetSpeedMult(float speed_mult);
+ float GetTimeUntilEvent(const std::string &event);
+ void SetAnimatedItemID( int index, int id );
+ void SetLayerItemID( int layer_id, int index, int item_id );
+ const std::string& GetCurrAnim() const;
+ float GetNormalizedAnimTime() const;
+ float GetAnimationSpeed() const;
+ void RemoveAllLayers();
+};
diff --git a/Source/Graphics/animationeffectsystem.cpp b/Source/Graphics/animationeffectsystem.cpp
new file mode 100644
index 00000000..4b72460f
--- /dev/null
+++ b/Source/Graphics/animationeffectsystem.cpp
@@ -0,0 +1,64 @@
+//-----------------------------------------------------------------------------
+// Name: animationeffectsystem.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "animationeffectsystem.h"
+
+#include <Internal/common.h>
+#include <Internal/filesystem.h>
+#include <Internal/timer.h>
+
+#include <TheoraPlayer/TheoraVideoManager.h>
+#include <TheoraPlayer/TheoraDataSource.h>
+#include <TheoraPlayer/TheoraVideoFrame.h>
+
+#include <XML/xml_helper.h>
+#include <Graphics/textures.h>
+#include <Logging/logdata.h>
+
+#include <tinyxml.h>
+
+#include <cstdio>
+
+AnimationEffectSystem::AnimationEffectSystem()
+{
+ mgr = new TheoraVideoManager();
+}
+
+AnimationEffectSystem::~AnimationEffectSystem()
+{
+ if( mgr )
+ delete mgr;
+ mgr = NULL;
+}
+
+void AnimationEffectSystem::Update(float timestep)
+{
+ mgr->update(timestep);
+}
+
+void AnimationEffectSystem::Dispose()
+{
+ if( mgr )
+ delete mgr;
+ mgr = NULL;
+}
diff --git a/Source/Graphics/animationeffectsystem.h b/Source/Graphics/animationeffectsystem.h
new file mode 100644
index 00000000..8ad4b606
--- /dev/null
+++ b/Source/Graphics/animationeffectsystem.h
@@ -0,0 +1,46 @@
+//-----------------------------------------------------------------------------
+// Name: animationeffectsystem.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assetbase.h>
+#include <Asset/assetinfobase.h>
+
+#include <Math/quaternions.h>
+#include <Graphics/textureref.h>
+
+#include <vector>
+#include <string>
+
+class TheoraVideoManager;
+
+class AnimationEffectSystem
+{
+public:
+ TheoraVideoManager* mgr;
+
+ AnimationEffectSystem();
+ ~AnimationEffectSystem();
+ void Dispose();
+ void Update(float timestep);
+};
diff --git a/Source/Graphics/animationflags.h b/Source/Graphics/animationflags.h
new file mode 100644
index 00000000..7dbf31d5
--- /dev/null
+++ b/Source/Graphics/animationflags.h
@@ -0,0 +1,32 @@
+//-----------------------------------------------------------------------------
+// Name: animationflags.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+enum AnimationClientFlags{
+ _ANM_MOBILE = (1<<0),
+ _ANM_MIRRORED = (1<<1),
+ _ANM_SWAP = (1<<2),
+ _ANM_SUPER_MOBILE = (1<<3),
+ _ANM_FROM_START = (1<<4)
+};
diff --git a/Source/Graphics/animationreader.cpp b/Source/Graphics/animationreader.cpp
new file mode 100644
index 00000000..3e0f662c
--- /dev/null
+++ b/Source/Graphics/animationreader.cpp
@@ -0,0 +1,380 @@
+//-----------------------------------------------------------------------------
+// Name: animationreader.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "animationreader.h"
+
+#include <Internal/timer.h>
+#include <Internal/error.h>
+
+#include <Graphics/pxdebugdraw.h>
+#include <Math/vec3math.h>
+#include <Logging/logdata.h>
+
+#include <algorithm>
+
+void AnimationReader::ActivateEventsInRange(float start, float end, int active_id){
+ while(next_event < events.size() &&
+ start <= events[next_event].time &&
+ events[next_event].time < end)
+ {
+ LOGD << "Active id: " << active_id << std::endl;
+ LOGD << "Activating event:" << events[next_event].anim_id << " " << events[next_event].event.event_string << std::endl;
+
+ if((int)events[next_event].anim_id == active_id){
+ active_events.push(events[next_event].event);
+ }
+ next_event++;
+ if(next_event < events.size()){
+ LOGD << "Next event: " << events[next_event].event.event_string << std::endl;
+ }
+ }
+
+ if(next_event == events.size() && !events.empty()){
+ next_event = 0;
+ LOGD << "Next event: " << events[next_event].event.event_string << std::endl;
+ }
+}
+
+std::queue<AnimationEvent>& AnimationReader::GetActiveEvents(){
+ return active_events;
+}
+
+float AnimationReader::GetSpeedNormalized(const BlendMap &blendmap) const {
+ const AnimationAsset &anim_asset = (*anim);
+ return anim_asset.GetFrequency(blendmap) * speed_mult;
+}
+
+bool AnimationReader::IncrementTime( const BlendMap &blendmap, float timestep ) {
+ if(!anim.valid()){
+ FatalError("Error", "No animation bound to reader");
+ }
+
+ AnimationAsset &anim_asset = (*anim);
+
+ float old_time = normalized_time;
+
+ // Increment time by clock time multiplied by animation frequency
+ normalized_time += timestep * GetSpeedNormalized(blendmap);
+
+ int anim_id = 0;
+ int active_id = (*anim).GetActiveID(blendmap, anim_id);
+ ActivateEventsInRange(old_time, normalized_time, active_id);
+
+ // Wrap looped time around if it is greater than 1
+ while(anim_asset.IsLooping() && normalized_time >= 1.0f){
+ if(!events.empty()){
+ LOGD << "Old times: " << old_time << " " << normalized_time << std::endl;
+ }
+ old_time -= 1.0f;
+ normalized_time -= 1.0f;
+ if(!events.empty()){
+ LOGD << "Wrapped times: " << old_time << " " << normalized_time << std::endl;
+ }
+ ActivateEventsInRange(old_time, normalized_time, active_id);
+ }
+ if(!anim_asset.IsLooping()){
+ if(old_time < 1.0f && normalized_time >= 1.0f){
+ return true;
+ }
+ }
+
+ return false;
+}
+
+float AnimationReader::GetTimeUntilEvent(const std::string &event){
+ for(unsigned i=0; i<events.size(); ++i){
+ if(events[i].event.event_string == event){
+ if(events[i].time > normalized_time){
+ return (*anim).AbsoluteTimeFromNormalized(events[i].time - normalized_time) / speed_mult * 0.001f;
+ }
+ }
+ }
+ return -1.0f;
+}
+
+void AnimationReader::GetMatrices( AnimOutput &anim_output,
+ AnimInput &anim_input)
+{
+ anim_input.mirrored = mirrored;
+ (*anim).GetMatrices(normalized_time, anim_output, anim_input);
+ if(anim_output.old_path != "retargeted"){
+ Retarget(anim_input, anim_output, anim_output.old_path);
+ }
+
+ if(!skip_offset){
+ anim_output.delta_offset = anim_output.center_offset - last_offset;
+ } else {
+ skip_offset = false;
+ }
+ anim_output.delta_rotation = anim_output.rotation - last_rotation;
+ last_offset = anim_output.center_offset;
+ last_rotation = anim_output.rotation;
+
+ if(blend_anim.valid()){
+ AnimOutput blend_anim_output;
+ (*blend_anim).GetMatrices(normalized_time,
+ blend_anim_output,
+ anim_input);
+ if(blend_anim_output.old_path != "retargeted"){
+ Retarget(anim_input, blend_anim_output, blend_anim_output.old_path);
+ }
+ anim_output = add_mix(anim_output,blend_anim_output,1.0f);
+ }
+
+ for(unsigned i=0; i<anim_output.weapon_matrices.size(); ++i){
+ int item_id = animated_item_ids[i];
+ if(item_id != -1){
+ WeapAnimInfo& weap_anim_info = anim_output.weap_anim_info_map[item_id];
+ weap_anim_info.bone_transform = anim_output.weapon_matrices[i];
+ weap_anim_info.weight = anim_output.weapon_weights[i] * anim_output.weapon_weight_weights[i];
+ weap_anim_info.relative_id = anim_output.weapon_relative_ids[i];
+ weap_anim_info.relative_weight = anim_output.weapon_relative_weights[i];
+ }
+ }
+
+ if(!super_mobile){
+ for(unsigned i=0; i<anim_output.matrices.size(); ++i){
+ if(anim_input.parents && anim_input.parents->at(i) != -1){
+ continue;
+ }
+ anim_output.matrices[i].origin[1] += anim_output.center_offset[1];
+ if(!mobile){
+ anim_output.matrices[i].origin[0] += anim_output.center_offset[0];
+ anim_output.matrices[i].origin[2] += anim_output.center_offset[2];
+ }
+ }
+ for(int i=0, len=anim_output.ik_bones.size(); i<len; ++i){
+ vec3 &origin = anim_output.ik_bones[i].transform.origin;
+ origin[1] += anim_output.center_offset[1];
+ if(!mobile){
+ origin[0] += anim_output.center_offset[0];
+ origin[2] += anim_output.center_offset[2];
+ }
+ }
+ anim_output.center_offset[1] = 0.0f;
+ anim_output.delta_offset[1] = 0.0f;
+ if(!mobile){
+ anim_output.center_offset[0] = 0.0f;
+ anim_output.center_offset[2] = 0.0f;
+ anim_output.delta_offset[0] = 0.0f;
+ anim_output.delta_offset[2] = 0.0f;
+ }
+ }
+}
+
+float AnimationReader::GetTime() const {
+ return normalized_time;
+}
+
+void AnimationReader::SetTime(float time) {
+ normalized_time = time;
+ skip_offset = true;
+
+ while(next_event < events.size() &&
+ events[next_event].time < time)
+ {
+ ++next_event;
+ }
+ if(next_event == events.size() && !events.empty()){
+ next_event = 0;
+ }
+}
+
+void AnimationReader::AttachTo( const AnimationAssetRef &_ref ) {
+ anim = _ref;
+ normalized_time = 0.0f;
+ last_offset = vec3(0.0f);
+ last_rotation = 0.0f;
+ skip_offset = false;
+ callback_string.clear();
+ for(int i=0; i<_max_animated_items; ++i){
+ animated_item_ids[i] = -1;
+ }
+ const AnimationAsset &anim = (*_ref);
+ int anim_id = 0;
+ events = anim.GetEvents(anim_id, mirrored);
+ std::sort(events.begin(),
+ events.end(),
+ NormalizedAnimationEventCompare());
+
+ next_event = 0;
+ while((int)next_event < (int)events.size()-1 && events[next_event].time == 0.0f) {
+ next_event++;
+ }
+}
+
+bool AnimationReader::valid() {
+ return anim.valid();
+}
+
+const AnimationReader& AnimationReader::operator=( const AnimationReader& other ) {
+ anim = other.anim;
+ blend_anim = other.blend_anim;
+ normalized_time = other.normalized_time;
+ events = other.events;
+ next_event = other.next_event;
+ active_events = other.active_events;
+ mobile = other.mobile;
+ super_mobile = other.super_mobile;
+ mirrored = other.mirrored;
+ last_offset = other.last_offset;
+ last_rotation = other.last_rotation;
+ skip_offset = other.skip_offset;
+ speed_mult = other.speed_mult;
+ callback_string = other.callback_string;
+ for(int i=0; i<_max_animated_items; ++i){
+ animated_item_ids[i] = other.animated_item_ids[i];
+ }
+ return *this;
+}
+
+void AnimationReader::clear()
+{
+ anim.clear();
+}
+
+float AnimationReader::GetAbsoluteTime(){
+ return (*anim).AbsoluteTimeFromNormalized(normalized_time);
+}
+
+void AnimationReader::SetAbsoluteTime(float time){
+ Animation* anm = (Animation*)&(*anim);
+ SetTime(anm->NormalizedFromLocal(time));
+}
+
+bool AnimationReader::GetMobile() {
+ return mobile;
+}
+
+bool AnimationReader::GetMirrored() {
+ return mirrored;
+}
+
+void AnimationReader::SetMobile( bool _mobile ) {
+ mobile = _mobile;
+}
+
+AnimationReader::AnimationReader():
+ mobile(false),
+ super_mobile(false),
+ mirrored(false),
+ speed_mult(1.0f)
+{
+ for(int i=0; i<_max_animated_items; ++i){
+ animated_item_ids[i] = -1;
+ }
+}
+
+void AnimationReader::SetMirrored( bool _mirrored ) {
+ mirrored = _mirrored;
+
+ int anim_id = 0;
+ events = anim->GetEvents(anim_id, mirrored);
+ std::sort(events.begin(),
+ events.end(),
+ NormalizedAnimationEventCompare());
+}
+
+bool AnimationReader::Finished() {
+ return normalized_time >= 1.0f;
+}
+
+const std::string & AnimationReader::GetPath() {
+ return anim->path_;
+}
+
+void AnimationReader::SetSuperMobile( bool _super_mobile ) {
+ super_mobile = _super_mobile;
+}
+
+bool AnimationReader::GetSuperMobile() {
+ return super_mobile;
+}
+
+bool AnimationReader::SameAnim( const AnimationAssetRef &_anim ) {
+ return anim == _anim;
+}
+
+void AnimationReader::SetBlendAnim( AnimationAssetRef anim ) {
+ LOGD << "Setting blend anim: " << anim->path_ << std::endl;
+ blend_anim = anim;
+}
+
+void AnimationReader::ClearBlendAnim()
+{
+ blend_anim.clear();
+}
+
+void AnimationReader::SetLastRotation( float val )
+{
+ last_rotation = val;
+}
+
+float AnimationReader::GetLastRotation()
+{
+ return last_rotation;
+}
+
+void AnimationReader::GetPrevEvents( const BlendMap &blendmap, float timestep ) {
+ AnimationAsset &anim_asset = (*anim);
+ float old_time = normalized_time -
+ timestep *
+ anim_asset.GetFrequency(blendmap);
+
+ int anim_id = 0;
+ int active_id = (*anim).GetActiveID(blendmap, anim_id);
+ ActivateEventsInRange(old_time, normalized_time, active_id);
+}
+
+float AnimationReader::GetAnimationEventTime( const std::string & event_name, const BlendMap &blend_map ) const
+{
+ for(unsigned i=0; i<events.size(); ++i){
+ if(events[i].time > normalized_time && events[i].event.event_string == event_name){
+ float time = events[i].time - normalized_time;
+ const AnimationAsset &anim_asset = (*anim);
+ time /= anim_asset.GetFrequency(blend_map);
+ return time;
+ }
+ }
+ return 0.0f;
+}
+
+void AnimationReader::SetSpeedMult( float _speed_mult ) {
+ speed_mult = _speed_mult;
+}
+
+void AnimationReader::SetCallbackString( const std::string& str ) {
+ callback_string = str;
+}
+
+const std::string& AnimationReader::GetCallbackString() {
+ return callback_string;
+}
+
+int AnimationReader::GetAnimatedItemID( int i ) {
+ return animated_item_ids[i];
+}
+
+void AnimationReader::SetAnimatedItemID( int index, int id ) {
+ animated_item_ids[index] = id;
+}
diff --git a/Source/Graphics/animationreader.h b/Source/Graphics/animationreader.h
new file mode 100644
index 00000000..7c161159
--- /dev/null
+++ b/Source/Graphics/animationreader.h
@@ -0,0 +1,86 @@
+//-----------------------------------------------------------------------------
+// Name: animationreader.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/Asset/animation.h>
+
+#include <queue>
+
+const int _max_animated_items = 4;
+
+class AnimationReader {
+ AnimationAssetRef anim;
+ AnimationAssetRef blend_anim;
+ float normalized_time;
+ std::vector<NormalizedAnimationEvent> events;
+ unsigned next_event;
+ void ActivateEventsInRange(float start, float end, int active_id);
+ std::queue<AnimationEvent> active_events;
+ bool mobile;
+ bool super_mobile;
+ bool mirrored;
+ vec3 last_offset;
+ float last_rotation;
+ bool skip_offset;
+ float speed_mult;
+ std::string callback_string;
+ int animated_item_ids[_max_animated_items];
+
+ public:
+ AnimationReader();
+ const std::string& GetCallbackString();
+ int GetAnimatedItemID(int i);
+ void SetCallbackString(const std::string& str);
+ void SetMobile(bool _mobile);
+ void SetSuperMobile(bool _super_mobile);
+ void clear();
+ void SetMirrored(bool _mirrored);
+ bool valid();
+ bool GetMirrored();
+ bool GetMobile();
+ bool GetSuperMobile();
+ float GetLastRotation();
+ void SetLastRotation(float val);
+ float GetAbsoluteTime();
+ void SetAbsoluteTime(float time);
+ float GetTime() const;
+ void SetTime(float time);
+ void AppliedOffset(float amount);
+ void AttachTo( const AnimationAssetRef &_ref );
+ bool IncrementTime( const BlendMap &blendmap, float timestep);
+ const std::string &GetPath();
+ const AnimationReader& operator=(const AnimationReader& other);
+ std::queue<AnimationEvent>& GetActiveEvents();
+ void GetMatrices( AnimOutput &anim_output, AnimInput &anim_input);
+ bool Finished();
+ bool SameAnim(const AnimationAssetRef &_anim);
+ void SetBlendAnim( AnimationAssetRef anim );
+ void ClearBlendAnim();
+ void GetPrevEvents( const BlendMap &blendmap, float timestep );
+ float GetAnimationEventTime( const std::string & event_name, const BlendMap &blend_map ) const;
+ void SetSpeedMult( float speed_mult );
+ float GetTimeUntilEvent(const std::string &event);
+ void SetAnimatedItemID( int index, int id );
+ float GetSpeedNormalized(const BlendMap &blendmap) const;
+};
diff --git a/Source/Graphics/atlasnodetree.cpp b/Source/Graphics/atlasnodetree.cpp
new file mode 100644
index 00000000..4cb99ab4
--- /dev/null
+++ b/Source/Graphics/atlasnodetree.cpp
@@ -0,0 +1,259 @@
+//-----------------------------------------------------------------------------
+// Name: atlasnodetree.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "atlasnodetree.h"
+
+#include <Math/ivec2math.h>
+#include <Utility/math_macro.h>
+#include <Logging/logdata.h>
+
+#include <cstdlib>
+#include <cassert>
+#include <cmath>
+#include <algorithm>
+#include <climits>
+
+static int c = 1;
+
+AtlasNodeTree::AtlasNode::AtlasNode():
+allocated(false),
+suballocated(false),
+parent(NULL),
+pos(0),
+dim(0),
+id(-1),
+uv_start(0),
+uv_end(0)
+{
+ subnodes[0] = NULL;
+ subnodes[1] = NULL;
+ subnodes[2] = NULL;
+ subnodes[3] = NULL;
+}
+
+AtlasNodeTree::AtlasNode::AtlasNode(ivec2 _pos, ivec2 _dim, ivec2 _top_dim, AtlasNode* mem, int memcount, AtlasNode* _parent, AtlasNode *self):
+allocated(false),
+suballocated(false),
+parent(_parent),
+pos(_pos),
+dim(_dim),
+id(c++)
+{
+ assert(IS_PWR2(dim[0]));
+ assert(IS_PWR2(dim[1]));
+
+ uv_start = vec2(pos) / vec2(_top_dim);
+ uv_end = vec2(dim) / vec2(_top_dim);
+
+ if( memcount == 0 )
+ {
+ subnodes[0] = NULL;
+ subnodes[1] = NULL;
+ subnodes[2] = NULL;
+ subnodes[3] = NULL;
+ }
+ else if( memcount >= 4 )
+ {
+ ivec2 halfwidth = dim/2;
+ int step = (memcount-4)/4;
+
+ subnodes[0] = &mem[(step+1)*0];
+ subnodes[1] = &mem[(step+1)*1];
+ subnodes[2] = &mem[(step+1)*2];
+ subnodes[3] = &mem[(step+1)*3];
+
+ *(subnodes[0]) = AtlasNode(pos , halfwidth, _top_dim, subnodes[0]+1, step, self,subnodes[0]);
+ *(subnodes[1]) = AtlasNode(pos + ivec2(halfwidth[0],0), halfwidth, _top_dim, subnodes[1]+1, step, self,subnodes[1]);
+ *(subnodes[2]) = AtlasNode(pos + ivec2(0,halfwidth[1]), halfwidth, _top_dim, subnodes[2]+1, step, self,subnodes[2]);
+ *(subnodes[3]) = AtlasNode(pos + halfwidth , halfwidth, _top_dim, subnodes[3]+1, step, self,subnodes[3]);
+ }
+ else
+ {
+ assert(false);
+ }
+}
+
+AtlasNodeTree::AtlasNodeRef::AtlasNodeRef():
+treeid(-1),
+node(NULL)
+{
+}
+
+AtlasNodeTree::AtlasNodeRef::AtlasNodeRef( int _treeid, AtlasNodeTree::AtlasNode* _node ) : treeid(_treeid), node(_node)
+{
+}
+
+bool AtlasNodeTree::AtlasNodeRef::valid()
+{
+ return node != NULL && treeid != -1;
+}
+
+AtlasNodeTree::AtlasNodeRef AtlasNodeTree::RetrieveNode(ivec2 dim)
+{
+ int needed_area = dim[0]*dim[1];
+
+ int best_area = INT_MAX;
+ AtlasNode* best_node = NULL;
+
+ for( int i = 0; i < nodecount; i++ )
+ {
+ AtlasNode* cur_node = &data[i];
+ int cur_area;
+
+ //See if the node is a potential candidate
+ if( cur_node->allocated == false
+ && cur_node->suballocated == false )
+ {
+ if( dim[0] <= cur_node->dim[0] && dim[1] <= cur_node->dim[1] )
+ {
+ cur_area = cur_node->dim[0] * cur_node->dim[1];
+
+ if( cur_area < best_area )
+ {
+ best_area = cur_area;
+ best_node = cur_node;
+
+ //Fast path out, we already have a perfect match
+ if( cur_area == needed_area )
+ {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if( best_node != NULL)
+ {
+ //Mark this and all subnodes as allocated, because they are.
+ best_node->SinkAllocation();
+
+ //Mark all node above that they have a subnode with allocation
+ best_node->LiftSubAllocation();
+ }
+
+ return AtlasNodeRef( this->treeid, best_node );
+}
+
+void AtlasNodeTree::FreeNode(const AtlasNodeTree::AtlasNodeRef& ref)
+{
+ if( ref.treeid == this->treeid)
+ {
+ if(ref.node != NULL)
+ {
+ ref.node->allocated = false;
+ ref.node->suballocated = false;
+ }
+ else
+ {
+ LOGE << "Trying to free node already free" << std::endl;
+ }
+ }
+ else
+ {
+ LOGE << "You're trying to free a node that did not come from this tree" << std::endl;
+ }
+}
+
+void AtlasNodeTree::Clear()
+{
+ for( int i = 0; i < nodecount; i++ )
+ {
+ AtlasNode* cur_node = &data[i];
+
+ cur_node->allocated = false;
+ cur_node->suballocated = false;
+ }
+}
+
+void AtlasNodeTree::AtlasNode::LiftSubAllocation()
+{
+ this->suballocated = true;
+
+ if( this->parent != NULL)
+ {
+ this->parent->LiftSubAllocation();
+ }
+}
+
+void AtlasNodeTree::AtlasNode::SinkAllocation()
+{
+ this->allocated = true;
+
+ if( subnodes[0] != NULL )
+ subnodes[0]->SinkAllocation();
+
+ if( subnodes[1] != NULL )
+ subnodes[1]->SinkAllocation();
+
+ if( subnodes[2] != NULL )
+ subnodes[2]->SinkAllocation();
+
+ if( subnodes[3] != NULL )
+ subnodes[3]->SinkAllocation();
+}
+
+bool AtlasNodeTree::AtlasNode::IsUsed()
+{
+ //If a given node has both set, it's the "starting point" for a allocation propagation,
+ //meaning it's the one returned by RetrieveNode function at some point.
+ return this->suballocated && this->allocated;
+}
+
+AtlasNodeTree::AtlasNodeTree( ivec2 dim, unsigned smallest_size )
+{
+ static int treeidc = 0;
+ this->treeid = treeidc++;
+
+ //The smallest allowed size also must be power of 2 as dimensions are
+ assert(IS_PWR2(smallest_size));
+ //Dimensions have to be a power of two to allow some assumptions
+ //mainly that each level is a power of two down.
+ assert(IS_PWR2(dim[0]) && IS_PWR2(dim[1]));
+ //We currently demand that the area is squared
+ assert(dim[0] == dim[1]);
+
+ unsigned minv = std::min( dim[0], dim[1] );
+ int level_count = 0;
+
+ while( minv > smallest_size )
+ {
+ minv = minv >> 1;
+ level_count++;
+ }
+
+ nodecount = 1;
+ for( int i = 1; i <= level_count; i++ )
+ {
+ nodecount += (int) pow(4,i);
+ }
+
+ data = new AtlasNode[nodecount];
+
+ data[0] = AtlasNode(ivec2(0), dim, dim, data+1, nodecount-1, NULL, &data[0]);
+}
+
+AtlasNodeTree::~AtlasNodeTree()
+{
+ delete[](data);
+}
diff --git a/Source/Graphics/atlasnodetree.h b/Source/Graphics/atlasnodetree.h
new file mode 100644
index 00000000..3da7e2f5
--- /dev/null
+++ b/Source/Graphics/atlasnodetree.h
@@ -0,0 +1,121 @@
+//-----------------------------------------------------------------------------
+// Name: atlasnodetree.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/ivec2.h>
+#include <Math/vec2.h>
+
+#include <iostream>
+
+class AtlasNodeTree
+{
+public:
+ class AtlasNode
+ {
+ private:
+ bool allocated;
+ bool suballocated;
+ AtlasNode* subnodes[4];
+ AtlasNode* parent;
+ public:
+ ivec2 pos;
+ ivec2 dim;
+
+ int id;
+
+ vec2 uv_start;
+ vec2 uv_end;
+
+ AtlasNode();
+ AtlasNode(ivec2 _pos, ivec2 _dim, ivec2 _top_dim, AtlasNode* mem, int memcount, AtlasNode* parent, AtlasNode* self);
+
+ /*
+ Lift suballocation flag from this node and upwards
+ */
+ void LiftSubAllocation();
+
+ /*
+ Mark self and all subnodes as allocated, in depth first order (because memory layout)
+ */
+ void SinkAllocation();
+
+ /*
+ Is this function the one returned to be used by a given item?
+ */
+ bool IsUsed();
+
+ friend class AtlasNodeTree;
+ friend std::ostream& operator<<(std::ostream& os, const AtlasNodeTree::AtlasNode &v );
+ };
+
+public:
+ class AtlasNodeRef
+ {
+ public:
+ AtlasNodeRef();
+ AtlasNodeRef(int treeid, AtlasNode * node);
+
+ bool valid();
+
+ int treeid;
+ AtlasNode * node;
+ };
+public:
+ AtlasNodeTree( ivec2 _dim, unsigned smallest_size );
+ ~AtlasNodeTree();
+
+ /*
+ Find the node that wastes least surface of the atlas to fit
+ the given dimensions and mark it as allocated, returns null if there are no fitting slots.
+ */
+ AtlasNodeRef RetrieveNode(ivec2 _dim);
+ void FreeNode(const AtlasNodeRef& ref);
+ void Clear();
+
+ inline int GetNodeCount(){return nodecount;}
+ inline AtlasNode* GetNodeRoot(){return data;}
+
+private:
+ int nodecount;
+ AtlasNode* data;
+ int treeid;
+ /*
+ Disable dangerous functions
+ */
+ AtlasNodeTree( AtlasNodeTree &rhs );
+ AtlasNodeTree& operator=( AtlasNodeTree &rhs);
+
+};
+
+inline std::ostream& operator<<(std::ostream& os, const AtlasNodeTree::AtlasNode &v )
+{
+ if( v.parent != NULL )
+ os << "p:" << v.parent->id;
+ else
+ os << "p:no parent";
+
+ os << " a:" << v.allocated << " sv:" << v.suballocated << " id:" << v.id << " " << v.pos << " " << v.dim;
+ return os;
+
+}
diff --git a/Source/Graphics/bloodsurface.cpp b/Source/Graphics/bloodsurface.cpp
new file mode 100644
index 00000000..0f855201
--- /dev/null
+++ b/Source/Graphics/bloodsurface.cpp
@@ -0,0 +1,871 @@
+//-----------------------------------------------------------------------------
+// Name: bloodsurface.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "bloodsurface.h"
+
+#include <Graphics/graphics.h>
+#include <Graphics/textures.h>
+#include <Graphics/model.h>
+#include <Graphics/particles.h>
+#include <Graphics/pxdebugdraw.h>
+#include <Graphics/vboringcontainer.h>
+#include <Graphics/shaders.h>
+
+#include <Math/vec3.h>
+#include <Math/vec2math.h>
+#include <Math/vec3math.h>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <Internal/profiler.h>
+#include <Internal/timer.h>
+
+#include <Main/scenegraph.h>
+#include <Physics/physics.h>
+#include <Wrappers/glm.h>
+#include <Logging/logdata.h>
+#include <Utility/assert.h>
+
+extern Timer game_timer;
+extern bool g_debug_runtime_disable_blood_surface_pre_draw;
+
+BloodSurface::BloodSurface(TransformedVertexGetter* transformed_vertex_getter):
+ drip_update_delay(0),
+ dry_delay(0),
+ dry_steps(0),
+ framebuffer_created(false),
+ transformed_vertex_getter_(transformed_vertex_getter),
+ simple_2d_id(0),
+ simple_2d_stab_id(0),
+ character_accumulate_id(0),
+ ignite_id(0),
+ extinguish_id(0),
+ blood_tex_mipmap_dirty(false),
+ blood_framebuffer(INVALID_FRAMEBUFFER),
+ blood_work_framebuffer(INVALID_FRAMEBUFFER),
+ simple_2d("simple_2d"),
+ simple_2d_stab_texture("simple_2d #STAB #TEXTURE"),
+ character_accumulate_dry("character_accumulate #DRY"),
+ character_accumulate_ignite("character_accumulate #IGNITE"),
+ character_accumulate_extinguish("character_accumulate #EXTINGUISH")
+{
+}
+
+BloodSurface::~BloodSurface() {
+ LOG_ASSERT(!blood_tex.valid());
+ LOG_ASSERT(!blood_work_tex.valid());
+ LOG_ASSERT(blood_framebuffer == INVALID_FRAMEBUFFER);
+ LOG_ASSERT(blood_work_framebuffer == INVALID_FRAMEBUFFER);
+}
+
+void BloodSurface::Dispose()
+{
+ blood_tex.clear();
+ blood_work_tex.clear();
+ if(blood_framebuffer != INVALID_FRAMEBUFFER){
+ Graphics::Instance()->deleteFramebuffer(&blood_framebuffer);
+ blood_framebuffer = -1;
+ }
+ if(blood_work_framebuffer != INVALID_FRAMEBUFFER){
+ Graphics::Instance()->deleteFramebuffer(&blood_work_framebuffer);
+ blood_work_framebuffer = -1;
+ }
+}
+
+void BloodSurface::InitShaders() {
+ simple_2d_id = Shaders::Instance()->returnProgram(simple_2d);
+ simple_2d_stab_id = Shaders::Instance()->returnProgram(simple_2d_stab_texture);
+ character_accumulate_id = Shaders::Instance()->returnProgram(character_accumulate_dry);
+ ignite_id = Shaders::Instance()->returnProgram(character_accumulate_ignite);
+ extinguish_id = Shaders::Instance()->returnProgram(character_accumulate_extinguish);
+
+ if (!blood_uniforms_inited) {
+ InitUniforms();
+ blood_uniforms_inited = true;
+ }
+}
+
+void BloodSurface::InitUniforms() {
+ // Pre-warm uniforms
+ Shaders::Instance()->setProgram(simple_2d_id);
+ Shaders::Instance()->returnShaderVariable("mvp_mat", simple_2d_id);
+ Shaders::Instance()->returnShaderVariable("color", simple_2d_id);
+ Shaders::Instance()->setProgram(simple_2d_stab_id);
+ Shaders::Instance()->returnShaderVariable("mvp_mat", simple_2d_stab_id);
+ Shaders::Instance()->returnShaderVariable("color", simple_2d_stab_id);
+ Shaders::Instance()->setProgram(character_accumulate_id);
+ Shaders::Instance()->returnShaderVariable("tex_width", character_accumulate_id);
+ Shaders::Instance()->returnShaderVariable("tex_height", character_accumulate_id);
+ Shaders::Instance()->returnShaderVariable("time", character_accumulate_id);
+ Shaders::Instance()->setProgram(ignite_id);
+ Shaders::Instance()->returnShaderVariable("tex_width", ignite_id);
+ Shaders::Instance()->returnShaderVariable("tex_height", ignite_id);
+ Shaders::Instance()->returnShaderVariable("time", ignite_id);
+ Shaders::Instance()->setProgram(extinguish_id);
+ Shaders::Instance()->returnShaderVariable("tex_width", extinguish_id);
+ Shaders::Instance()->returnShaderVariable("tex_height", extinguish_id);
+ Shaders::Instance()->returnShaderVariable("time", extinguish_id);
+}
+
+void BloodSurface::PreDrawFrame(int width, int height) {
+ if (g_debug_runtime_disable_blood_surface_pre_draw) {
+ return;
+ }
+
+ if(!framebuffer_created){
+ CHECK_GL_ERROR();
+ Textures *textures = Textures::Instance();
+ CHECK_GL_ERROR();
+ blood_tex = textures->makeTextureColor(width,height,GL_RGBA,GL_RGBA,0.0f,0.0f,0.0f,0.0f,true);
+ textures->SetTextureName(blood_tex, "Blood Surface");
+ blood_work_tex = textures->makeTextureColor(width,height,GL_RGBA,GL_RGBA,0.0f,0.0f,0.0f,0.0f,true);
+ textures->SetTextureName(blood_work_tex, "Blood Surface - Work");
+ CHECK_GL_ERROR();
+ Graphics *graphics = Graphics::Instance();
+ graphics->PushFramebuffer();
+ CHECK_GL_ERROR();
+ graphics->genFramebuffers(&blood_framebuffer,"blood_framebuffer");
+ CHECK_GL_ERROR();
+ graphics->bindFramebuffer(blood_framebuffer);
+ graphics->framebufferColorTexture2D(blood_tex);
+ CHECK_FBO_ERROR();
+ CHECK_GL_ERROR();
+ graphics->genFramebuffers(&blood_work_framebuffer, "blood_work_framebuffer");
+ CHECK_GL_ERROR();
+ graphics->bindFramebuffer(blood_work_framebuffer);
+ graphics->framebufferColorTexture2D(blood_work_tex);
+ CHECK_FBO_ERROR();
+ CHECK_GL_ERROR();
+ graphics->PopFramebuffer();
+ framebuffer_created = true;
+ CHECK_GL_ERROR();
+
+ InitShaders();
+ }
+}
+
+void BloodSurface::AttachToModel( Model * attach_model ) {
+ model = attach_model;
+ model_surface_walker.AttachTo(attach_model->vertices, attach_model->faces);
+}
+
+vec2 GetTexCoords(Model *model, int tri, vec3 weights){
+ unsigned tri_index = tri*3;
+ const std::vector<GLuint> &faces = model->faces;
+ const std::vector<GLfloat> &tc = model->tex_coords;
+ vec2 tex_coords[3];
+ for(int i=0; i<3; ++i){
+ int index = faces[tri_index+i]*2;
+ for(int j=0; j<2; ++j){
+ tex_coords[i][j] = tc[index+j];
+ }
+ }
+ return tex_coords[0] * weights[0] + tex_coords[1] * weights[1] + tex_coords[2] * weights[2];
+}
+
+const int DRIP_DELAY = 4;
+
+void BloodSurface::Update(SceneGraph *scenegraph, float timestep, bool force_update) {
+ std::vector<WalkLine> trace[3]; // Trails left since the last blood update
+ if(drip_update_delay <= 0){ // Update blood if it is time
+ PROFILER_ZONE(g_profiler_ctx, "Update drips");
+ vec3 points[3];
+ for(SurfaceWalkerList::iterator iter = surface_walkers.begin(); iter != surface_walkers.end();) {
+ if(iter->delay > 0.0f){ // This walker does not need to be updated yet
+ iter->delay -= timestep * DRIP_DELAY;
+ ++iter;
+ continue;
+ }
+ if((*iter).amount < 0.0f){ // This walker ran out of liquid, so delete it
+ iter = surface_walkers.erase(iter);
+ continue;
+ }
+ (*iter).amount -= timestep;
+ // Move walker across surface
+ SurfaceWalker& sw = (*iter);
+ float drip_dist = 0.005f;
+ float total_moved = 0.0f;
+ int max_triangles = 10;
+ int num_triangles = 0;
+ int at_drip_point = -1;
+ while(total_moved < 1.0f && num_triangles < max_triangles){
+ LOG_ASSERT_LT(sw.tri, (int)model->faces.size()/3);
+ for(int tri_vert=0; tri_vert<3; ++tri_vert){
+ LOG_ASSERT_LT(sw.tri*3+tri_vert, (int)model->faces.size());
+ LOG_ASSERT_LT(model->faces[sw.tri*3+tri_vert], model->vertices.size()/3);
+ if(model->faces[sw.tri*3+tri_vert] > model->vertices.size()){
+ LOGE << "sw.tri: " << sw.tri << "tri_vert: " << tri_vert << "index: " << sw.tri*3+tri_vert << "model->faces.size()*3: " << model->faces.size() << "model->faces[sw.tri*3+tri_vert]: " << model->faces[sw.tri*3+tri_vert] << std::endl;
+ }
+ points[tri_vert] = transformed_vertex_getter_->GetTransformedVertex(model->faces[sw.tri*3+tri_vert]);
+ }
+ //DebugDraw::Instance()->AddWireSphere(points[0], 0.05f, vec4(1.0f), _delete_on_update);
+ SWresults swr;
+
+ switch(iter->type){
+ case SurfaceWalker::FIRE:
+ swr = model_surface_walker.Move(sw, points, vec3(0.0f,1.0f,0.0f), drip_dist, &trace[2]);
+ break;
+ case SurfaceWalker::WATER:
+ swr = model_surface_walker.Move(sw, points, vec3(0.0f,-1.0f,0.0f), drip_dist, &trace[1]);
+ break;
+ case SurfaceWalker::BLOOD:
+ swr = model_surface_walker.Move(sw, points, vec3(0.0f,-1.0f,0.0f), drip_dist, &trace[0]);
+ break;
+ }
+
+ total_moved += swr.dist_moved;
+ if(swr.at_point != -1){
+ at_drip_point = swr.at_point;
+ break;
+ }
+ ++num_triangles;
+ }
+ if(num_triangles >= max_triangles){
+ // We touched too many triangles
+ LOGE << "Max reps exceeded" << std::endl;
+ }
+
+
+ if(at_drip_point == -1){
+ ++iter;
+ } else {
+ if(iter->can_drip){
+ for(int tri_vert=0; tri_vert<3; ++tri_vert){
+ points[tri_vert] = transformed_vertex_getter_->GetTransformedVertex(model->faces[sw.tri*3+tri_vert]);
+ }
+ vec3 point = points[0] * sw.pos[0] +
+ points[1] * sw.pos[1] +
+ points[2] * sw.pos[2];
+ if(iter->type == SurfaceWalker::BLOOD){
+ scenegraph->particle_system->MakeParticle(scenegraph,
+ "Data/Particles/blooddrop.xml",
+ point,
+ vec3(0.0f),
+ Graphics::Instance()->config_.blood_color());
+ } else if(iter->type == SurfaceWalker::WATER){
+ scenegraph->particle_system->MakeParticle(scenegraph,
+ "Data/Particles/waterdrop.xml",
+ point,
+ vec3(0.0f),
+ vec3(1.0f));
+ }
+ }
+ iter = surface_walkers.erase(iter);
+ }
+ }
+ drip_update_delay = DRIP_DELAY;
+ }
+ drip_update_delay--;
+
+ glm::mat4 proj = glm::ortho(0.0f, 1.0f, 0.0f, 1.0f);
+
+ bool needs_update = (!trace[0].empty() ||
+ !trace[1].empty() ||
+ !trace[2].empty() ||
+ !cut_lines.empty());
+ if((framebuffer_created && needs_update) || force_update){
+ PROFILER_ZONE(g_profiler_ctx, "Update texture with drips and cuts");
+ StartDrawingToBloodTexture();
+ Shaders::Instance()->setProgram(simple_2d_id);
+ float ts = 1.0f/(float)Textures::Instance()->getWidth(blood_tex);
+ Graphics::Instance()->SetBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ Shaders::Instance()->SetUniformMat4("mvp_mat", (const GLfloat*)&proj);
+ Graphics::Instance()->SetLineWidth(1);
+ std::vector<float> blood_points;
+ for(int i=0; i<3; ++i){
+ if(trace[i].empty() && (i!=0 || cut_lines.empty())){
+ continue;
+ }
+ switch(i){
+ case 0:
+ Shaders::Instance()->SetUniformVec4("color", vec4(1.0f,253.0f/255.0f,0.0f,1.0f));
+ break;
+ case 1:
+ Shaders::Instance()->SetUniformVec4("color", vec4(0.0f,253.0f/255.0f,0.0f,1.0f));
+ break;
+ case 2:
+ Shaders::Instance()->SetUniformVec4("color", vec4(0.0f,0.0f,1.0f,1.0f));
+ break;
+ }
+
+ PROFILER_ENTER(g_profiler_ctx, "Fill blood_points in Update texture");
+ for(std::vector<WalkLine>::iterator iter = trace[i].begin(); iter != trace[i].end(); ++iter) {
+ const int face_index = (*iter).tri*3;
+ vec2 tex_coords[3];
+ for(int i=0; i<3; ++i){
+ int index = model->faces[face_index+i]*2;
+ for(int j=0; j<2; ++j){
+ tex_coords[i][j] = model->tex_coords[index+j];
+ }
+ }
+
+ const vec3 &start = (*iter).start;
+ vec2 start_uv = tex_coords[0] * start[0] +
+ tex_coords[1] * start[1] +
+ tex_coords[2] * start[2];
+
+ const vec3 &end = (*iter).end;
+ vec2 end_uv = tex_coords[0] * end[0] +
+ tex_coords[1] * end[1] +
+ tex_coords[2] * end[2];
+ blood_points.reserve(blood_points.size()+40);
+ for(unsigned i=0; i<10; ++i){
+ vec2 test_offset(RangedRandomFloat(-ts,ts),
+ RangedRandomFloat(-ts,ts));
+ blood_points.push_back(start_uv[0]+test_offset[0]);
+ blood_points.push_back(start_uv[1]+test_offset[1]);
+ blood_points.push_back(end_uv[0]+test_offset[0]);
+ blood_points.push_back(end_uv[1]+test_offset[1]);
+ }
+ }
+ if(i==0){
+ blood_points.reserve(blood_points.size()+cut_lines.size()*40);
+ for(std::vector<CutLine>::iterator iter = cut_lines.begin();
+ iter != cut_lines.end();
+ ++iter)
+ {
+ for(unsigned i=0; i<10; ++i){
+ vec2 test_offset(RangedRandomFloat(-ts,ts),
+ RangedRandomFloat(-ts,ts));
+ blood_points.push_back(iter->start[0]+test_offset[0]);
+ blood_points.push_back(iter->start[1]+test_offset[1]);
+ blood_points.push_back(iter->end[0]+test_offset[0]);
+ blood_points.push_back(iter->end[1]+test_offset[1]);
+ }
+ }
+ cut_lines.clear();
+ }
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ PROFILER_ENTER(g_profiler_ctx, "Fill VBO in Update texture");
+ static VBORingContainer blood_point_vbo(V_MIBIBYTE, kVBODynamic | kVBOFloat);
+ blood_point_vbo.Fill(sizeof(float) * blood_points.size(), (void*)&blood_points[0]);
+ blood_point_vbo.Bind();
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw arrays in Update texture");
+ int vert_attrib_id = Shaders::Instance()->returnShaderAttrib("vert_coord", simple_2d_id);
+ Graphics::Instance()->EnableVertexAttribArray(vert_attrib_id);
+ glVertexAttribPointer(vert_attrib_id, 2, GL_FLOAT, false, 2*sizeof(float), (const void*)blood_point_vbo.offset());
+ Graphics::Instance()->DrawArrays(GL_LINES, 0, blood_points.size()/2);
+ Graphics::Instance()->ResetVertexAttribArrays();
+ }
+ dry_steps = 50;
+
+ EndDrawingToBloodTexture();
+ blood_tex_mipmap_dirty = true;
+ }
+ if((framebuffer_created/* && blood_dry_steps > 0 */&& dry_delay <= 0) || force_update){
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Update dry");
+ std::swap(blood_tex, blood_work_tex);
+ std::swap(blood_framebuffer, blood_work_framebuffer);
+ StartDrawingToBloodTexture();
+
+ const bool kCheckForWetnessDrips = false;
+ if(kCheckForWetnessDrips){
+ int dims[2] = {Textures::Instance()->getWidth(blood_tex), Textures::Instance()->getHeight(blood_tex)};
+ std::vector<GLubyte> bytes (dims[0] * dims[1] * 4);
+ PROFILER_ENTER(g_profiler_ctx, "Read pixels in Update dry");
+ glReadPixels(0, 0, dims[0], dims[1], GL_RGBA, GL_UNSIGNED_BYTE, &bytes[0]);
+ PROFILER_LEAVE(g_profiler_ctx);
+ int total_wet = 0;
+ int total_possible = 0;
+ for(int i=0, len=bytes.size(); i<len; i+=4){
+ if(bytes[i+3] == 255){
+ total_wet += bytes[i+1];
+ total_possible += 255;
+ }
+ }
+ for(int i=0, len=model->faces.size(); i<len; i+=3){
+ if(rand()%20 == 0){
+ vec2 center;
+ for(int j=0; j<3; ++j){
+ int vert_index = model->faces[i+j]*2;
+ for(int k=0; k<2; ++k){
+ center[k] += model->tex_coords[vert_index+k];
+ }
+ }
+ center /= 3.0f;
+ int u = (int) (center[0] * dims[0] + 0.5f);
+ int v = (int) (center[1] * dims[1] + 0.5f);
+ if(u >= 0 && u < dims[0] && v >= 0 && v < dims[1]){
+ int index = (u + v * dims[0])*4;
+ int wet = bytes[index+1];
+ if(wet == 254){
+ CreateDripInTri(i/3, vec3(1.0f/3.0f), 1.0f, RangedRandomFloat(0.0f, 4.0f), true, SurfaceWalker::WATER);
+ }
+ }
+ }
+ /*
+ if(rand()%500 == 0){
+ vec2 center;
+ for(int j=0; j<3; ++j){
+ int vert_index = model->faces[i+j]*2;
+ for(int k=0; k<2; ++k){
+ center[k] += model->tex_coords[vert_index+k];
+ }
+ }
+ center /= 3.0f;
+ int u = center[0] * dims[0] + 0.5f;
+ int v = center[1] * dims[1] + 0.5f;
+ if(u >= 0 && u < dims[0] && v >= 0 && v < dims[1]){
+ int index = (u + v * dims[0])*4;
+ int wet = bytes[index+1];
+ if(bytes[index+2] == 255){ // fire
+ CreateDripInTri(i/3, vec3(1.0f/3.0f), 1.0f, RangedRandomFloat(0.0f, 4.0f), true, SurfaceWalker::FIRE);
+ }
+ }
+ }*/
+ }
+ LOGI << "Wetness: " << (int)(total_wet * 100.0f / total_possible) << "%" << std::endl;
+ }
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Setup");
+ Graphics::Instance()->setBlend(false);
+ Graphics::Instance()->SetBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
+ Shaders::Instance()->setProgram(character_accumulate_id);
+ Shaders::Instance()->SetUniformInt("tex_width", Textures::Instance()->getWidth(blood_work_tex));
+ Shaders::Instance()->SetUniformInt("tex_height", Textures::Instance()->getHeight(blood_work_tex));
+ Shaders::Instance()->SetUniformFloat("time", game_timer.game_time);
+ Textures::Instance()->bindTexture(blood_work_tex);
+
+ static const GLfloat data[] = {0,0, 1,0, 1,1, 0,1};
+ static const GLuint index[] = {0,1,2, 0,2,3};
+ static VBOContainer square_vbo;
+ static VBOContainer index_vbo;
+ if(!square_vbo.valid()){
+ square_vbo.Fill(kVBOFloat | kVBOStatic, sizeof(data), (void*)data);
+ index_vbo.Fill(kVBOElement | kVBOStatic, sizeof(index), (void*)index);
+ }
+ square_vbo.Bind();
+ index_vbo.Bind();
+
+ int vert_attrib_id = Shaders::Instance()->returnShaderAttrib("vert_coord", character_accumulate_id);
+ Graphics::Instance()->EnableVertexAttribArray(vert_attrib_id);
+ glVertexAttribPointer(vert_attrib_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0);
+ }
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Draw");
+ Graphics::Instance()->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ }
+ {
+ PROFILER_ZONE(g_profiler_ctx, "End");
+ Graphics::Instance()->ResetVertexAttribArrays();
+
+ EndDrawingToBloodTexture();
+ }
+ blood_tex_mipmap_dirty = true;
+ --dry_steps;
+ dry_delay = 6;
+ }
+ dry_delay--;
+}
+
+void BloodSurface::CreateBloodDripNearestPointDirection( const vec3 &bone_pos,
+ const vec3 &direction,
+ float blood_amount,
+ bool can_drip )
+{
+ if(Graphics::Instance()->config_.blood() != BloodLevel::kFull){
+ return;
+ }
+ int closest_tri = -1;
+ float best_val;
+ float val;
+ vec3 center;
+ for(int i=0, len=model->faces.size(); i<len; i+=3){
+ vec3 center;
+ for(int j=0; j<3; ++j){
+ int vert_index = model->faces[i+j]*3;
+ for(int k=0; k<3; ++k){
+ center[k] += model->vertices[vert_index+k];
+ }
+ }
+ center /= 3.0f;
+ val = dot(normalize(center-bone_pos), direction);
+ if(closest_tri == -1 || val > best_val){
+ best_val = val;
+ closest_tri = i/3;
+ }
+ }
+
+
+
+ if(closest_tri == -1){
+ return;
+ }
+ SurfaceWalker new_walker;
+ new_walker.tri = closest_tri;
+ new_walker.last_tri = -1;
+ new_walker.pos = vec3(1.0f/3.0f);
+ new_walker.amount = blood_amount;
+ new_walker.can_drip = can_drip;
+ new_walker.type = SurfaceWalker::BLOOD;
+ surface_walkers.push_back(new_walker);
+}
+
+
+void BloodSurface::CreateDripInTri( int tri,
+ const vec3 &bary_coords,
+ float blood_amount,
+ float delay,
+ bool can_drip,
+ SurfaceWalker::Type type)
+{
+ if(Graphics::Instance()->config_.blood() != BloodLevel::kFull){
+ return;
+ }
+ LOG_ASSERT_LT(tri, (int)model->faces.size()/3);
+ SurfaceWalker new_walker;
+ new_walker.tri = tri;
+ new_walker.last_tri = -1;
+ new_walker.pos = bary_coords;
+ new_walker.amount = blood_amount;
+ new_walker.can_drip = can_drip;
+ new_walker.delay = delay;
+ new_walker.type = type;
+ surface_walkers.push_back(new_walker);
+}
+
+void BloodSurface::CreateBloodDripNearestPointTransformed( const vec3 &bone_pos,
+ float blood_amount,
+ bool can_drip )
+{
+ if(Graphics::Instance()->config_.blood() != BloodLevel::kFull){
+ return;
+ }
+ int closest_tri = -1;
+ float best_val;
+ float val;
+ for(int i=0, len=model->faces.size(); i<len; i+=3){
+ vec3 center;
+ for(int j=0; j<3; ++j) {
+ center += transformed_vertex_getter_->GetTransformedVertex(model->faces[i+j]);
+ }
+ center /= 3.0f;
+ val = distance_squared(center,bone_pos);
+ if(closest_tri == -1 || val < best_val){
+ best_val = val;
+ closest_tri = i/3;
+ }
+ }
+
+ if(closest_tri == -1){
+ return;
+ }
+ SurfaceWalker new_walker;
+ new_walker.tri = closest_tri;
+ new_walker.last_tri = -1;
+ new_walker.pos = vec3(1.0f/3.0f);
+ new_walker.amount = blood_amount;
+ new_walker.can_drip = can_drip;
+ new_walker.type = SurfaceWalker::BLOOD;
+ surface_walkers.push_back(new_walker);
+}
+
+void BloodSurface::CleanBlood() {
+ if(framebuffer_created){
+ Graphics::Instance()->PushFramebuffer();
+ int width = Textures::Instance()->getWidth(blood_tex);
+ int height = Textures::Instance()->getHeight(blood_tex);
+ Graphics::Instance()->bindFramebuffer(blood_framebuffer);
+ Graphics::Instance()->StartTextureSpaceRenderingCustom(0, 0, width, height);
+ CHECK_FBO_ERROR();
+
+ glClearColor(0.0f,0.0f,0.0f,0.0f);
+ glColorMask(true, false, false, false);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glColorMask(true, true, true, true);
+ surface_walkers.clear();
+ dry_steps = 0;
+
+ Graphics::Instance()->EndTextureSpaceRendering();
+ Graphics::Instance()->PopFramebuffer();
+ blood_tex_mipmap_dirty = true;
+ }
+}
+
+
+void BloodSurface::SetFire(float fire) {
+ if(framebuffer_created){
+ Graphics::Instance()->PushFramebuffer();
+ int width = Textures::Instance()->getWidth(blood_tex);
+ int height = Textures::Instance()->getHeight(blood_tex);
+ Graphics::Instance()->bindFramebuffer(blood_framebuffer);
+ Graphics::Instance()->StartTextureSpaceRenderingCustom(0, 0, width, height);
+ CHECK_FBO_ERROR();
+
+ glClearColor(0.0f,
+ 0.0f,
+ fire,
+ 0.0f);
+ glColorMask(false, false, true, false);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glColorMask(true, true, true, true);
+
+ Graphics::Instance()->EndTextureSpaceRendering();
+ Graphics::Instance()->PopFramebuffer();
+ blood_tex_mipmap_dirty = true;
+ }
+}
+
+void BloodSurface::SetWet(float wet) {
+ if(framebuffer_created){
+ Graphics::Instance()->PushFramebuffer();
+ int width = Textures::Instance()->getWidth(blood_tex);
+ int height = Textures::Instance()->getHeight(blood_tex);
+ Graphics::Instance()->bindFramebuffer(blood_framebuffer);
+ Graphics::Instance()->StartTextureSpaceRenderingCustom(0, 0, width, height);
+ CHECK_FBO_ERROR();
+
+ glClearColor(0.0f,
+ wet,
+ 0.0f,
+ 0.0f);
+ glColorMask(false, true, false, false);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glColorMask(true, true, true, true);
+
+ Graphics::Instance()->EndTextureSpaceRendering();
+ Graphics::Instance()->PopFramebuffer();
+ blood_tex_mipmap_dirty = true;
+ }
+}
+
+void BloodSurface::AddBloodLine( const vec2 &start, const vec2 &end ) {
+ if(Graphics::Instance()->config_.blood() == BloodLevel::kNone){
+ return;
+ }
+ cut_lines.resize(cut_lines.size()+1);
+ cut_lines.back().start = start;
+ cut_lines.back().end = end;
+}
+
+struct tri_data {
+ vec3 p[3];
+ vec2 decal_coord[3];
+};
+
+void BloodSurface::AddDecalToTriangles( const std::vector<int> &hit_list, vec3 pos, vec3 dir, float size, const TextureAssetRef& texture_ref ) {
+ if(!framebuffer_created){
+ return;
+ }
+ PROFILER_ZONE(g_profiler_ctx, "BloodSurface::AddDecalToTriangles");
+
+ PROFILER_ENTER(g_profiler_ctx, "Get transformed verts");
+ std::vector<tri_data> tris(hit_list.size());
+ for(unsigned i=0; i<hit_list.size(); ++i){
+ tris[i].p[0] = transformed_vertex_getter_->GetTransformedVertex(model->faces[hit_list[i]*3+0]);
+ tris[i].p[1] = transformed_vertex_getter_->GetTransformedVertex(model->faces[hit_list[i]*3+1]);
+ tris[i].p[2] = transformed_vertex_getter_->GetTransformedVertex(model->faces[hit_list[i]*3+2]);
+ }
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ PROFILER_ENTER(g_profiler_ctx, "Calculate decal coords");
+ vec3 proj_z = dir;
+ //vec3 proj_y(0.0f,1.0f,0.0f);
+ vec3 proj_y(RangedRandomFloat(-1.0f,1.0f),
+ RangedRandomFloat(-1.0f,1.0f),
+ RangedRandomFloat(-1.0f,1.0f));
+ proj_y = normalize(proj_y);
+ vec3 proj_x = normalize(cross(proj_z, proj_y));
+ proj_y = normalize(cross(proj_x, proj_z));
+
+ {
+ vec3 temp;
+ for(unsigned i=0; i<hit_list.size(); ++i){
+ for(unsigned j=0; j<3; ++j){
+ temp = tris[i].p[j] - pos;
+ tris[i].decal_coord[j] = vec2(dot(temp, proj_x) / size + 0.5f,
+ dot(temp, proj_y) / size + 0.5f);
+ }
+ }
+ }
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ PROFILER_ENTER(g_profiler_ctx, "Fill data");
+ std::vector<GLfloat> data;
+ data.reserve(hit_list.size() * 12);
+ for(unsigned i=0; i<hit_list.size(); ++i){
+ for(unsigned j=0; j<3; ++j){
+ data.push_back(tris[i].decal_coord[j][0]);
+ data.push_back(tris[i].decal_coord[j][1]);
+ int index = model->faces[hit_list[i]*3+j]*2;
+ data.push_back(model->tex_coords[index+0]);
+ data.push_back(model->tex_coords[index+1]);
+ }
+ }
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ PROFILER_ENTER(g_profiler_ctx, "Setup shader");
+ Graphics* graphics = Graphics::Instance();
+ Shaders* shaders = Shaders::Instance();
+ Textures* textures = Textures::Instance();
+ StartDrawingToBloodTexture();
+ glm::mat4 proj = glm::ortho(0.0f, 1.0f, 0.0f, 1.0f);
+ shaders->setProgram(simple_2d_stab_id);
+ graphics->SetBlendFunc(GL_SRC_ALPHA, GL_ONE);
+ shaders->SetUniformMat4("mvp_mat", (const GLfloat*)&proj);
+ shaders->SetUniformVec4("color", vec4(1.0f,1.0f,0.0f,1.0f));
+ textures->bindTexture(texture_ref->GetTextureRef());
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ PROFILER_ENTER(g_profiler_ctx, "Fill VBO");
+ static VBORingContainer data_vbo(V_MIBIBYTE,kVBOFloat | kVBODynamic );
+ data_vbo.Fill(sizeof(GLfloat) * data.size(), (void*)&data[0]);
+ data_vbo.Bind();
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ PROFILER_ENTER(g_profiler_ctx, "Draw");
+ int vert_attrib_id = shaders->returnShaderAttrib("vert_coord", simple_2d_stab_id);
+ int tex_attrib_id = shaders->returnShaderAttrib("tex_coord", simple_2d_stab_id);
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ graphics->EnableVertexAttribArray(tex_attrib_id);
+ glVertexAttribPointer(vert_attrib_id, 2, GL_FLOAT, false, 4*sizeof(GLfloat), (const void*)(data_vbo.offset()+2*sizeof(GLfloat)));
+ glVertexAttribPointer(tex_attrib_id, 2, GL_FLOAT, false, 4*sizeof(GLfloat), (const void*)(data_vbo.offset()));
+ graphics->DrawArrays(GL_TRIANGLES, 0, hit_list.size()*3);
+ graphics->ResetVertexAttribArrays();
+ EndDrawingToBloodTexture();
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ blood_tex_mipmap_dirty = true;
+ dry_steps = 50;
+}
+
+void BloodSurface::StartDrawingToBloodTexture() {
+ PROFILER_ZONE(g_profiler_ctx, "BloodSurface::StartDrawingToBloodTexture");
+ PROFILER_ENTER(g_profiler_ctx, "Push framebuffer");
+ Graphics* graphics = Graphics::Instance();
+ graphics->PushFramebuffer();
+ PROFILER_LEAVE(g_profiler_ctx);
+ PROFILER_ENTER(g_profiler_ctx, "bindFramebuffer");
+ Graphics::Instance()->bindFramebuffer(blood_framebuffer);
+ PROFILER_LEAVE(g_profiler_ctx);
+ PROFILER_ENTER(g_profiler_ctx, "StartTextureSpaceRenderingCustom");
+ int width = Textures::Instance()->getWidth(blood_tex);
+ int height = Textures::Instance()->getHeight(blood_tex);
+ graphics->StartTextureSpaceRenderingCustom(0, 0, width, height);
+ CHECK_FBO_ERROR();
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ PROFILER_ENTER(g_profiler_ctx, "setGLState");
+ GLState gl_state;
+ gl_state.blend = true;
+ gl_state.cull_face = false;
+ gl_state.depth_test = false;
+ gl_state.depth_write = false;
+ graphics->setGLState(gl_state);
+ PROFILER_LEAVE(g_profiler_ctx);
+}
+
+void BloodSurface::EndDrawingToBloodTexture() {
+ CHECK_GL_ERROR();
+ Graphics::Instance()->EndTextureSpaceRendering();
+ CHECK_GL_ERROR();
+ Graphics::Instance()->PopFramebuffer();
+}
+
+void BloodSurface::Ignite() {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "BloodSurface::Ignite");
+ if(framebuffer_created){
+ CHECK_GL_ERROR();
+ std::swap(blood_tex, blood_work_tex);
+ std::swap(blood_framebuffer, blood_work_framebuffer);
+ StartDrawingToBloodTexture();
+ Graphics::Instance()->setBlend(false);
+ Graphics::Instance()->SetBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
+ Shaders::Instance()->setProgram(ignite_id);
+ Shaders::Instance()->SetUniformInt("tex_width", Textures::Instance()->getWidth(blood_work_tex));
+ Shaders::Instance()->SetUniformInt("tex_height", Textures::Instance()->getHeight(blood_work_tex));
+ Shaders::Instance()->SetUniformFloat("time", game_timer.game_time);
+ Textures::Instance()->bindTexture(blood_work_tex);
+
+ static const GLfloat data[] = {0,0, 1,0, 1,1, 0,1};
+ static const GLuint index[] = {0,1,2, 0,2,3};
+ static VBOContainer square_vbo;
+ static VBOContainer index_vbo;
+ if(!square_vbo.valid()){
+ square_vbo.Fill(kVBOFloat | kVBOStatic, sizeof(data), (void*)data);
+ index_vbo.Fill(kVBOElement | kVBOStatic, sizeof(index), (void*)index);
+ }
+ square_vbo.Bind();
+ index_vbo.Bind();
+ CHECK_GL_ERROR();
+
+ int vert_attrib_id = Shaders::Instance()->returnShaderAttrib("vert_coord", ignite_id);
+ Graphics::Instance()->EnableVertexAttribArray(vert_attrib_id);
+ glVertexAttribPointer(vert_attrib_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0);
+ Graphics::Instance()->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ Graphics::Instance()->ResetVertexAttribArrays();
+
+ CHECK_GL_ERROR();
+ EndDrawingToBloodTexture();
+ CHECK_GL_ERROR();
+ blood_tex_mipmap_dirty = true;
+ }
+}
+
+void BloodSurface::Extinguish() {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "BloodSurface::Extinguish");
+ if(framebuffer_created){
+ std::swap(blood_tex, blood_work_tex);
+ std::swap(blood_framebuffer, blood_work_framebuffer);
+ StartDrawingToBloodTexture();
+ Graphics::Instance()->setBlend(false);
+ Graphics::Instance()->SetBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
+ Shaders::Instance()->setProgram(extinguish_id);
+ Shaders::Instance()->SetUniformInt("tex_width", Textures::Instance()->getWidth(blood_work_tex));
+ Shaders::Instance()->SetUniformInt("tex_height", Textures::Instance()->getHeight(blood_work_tex));
+ Shaders::Instance()->SetUniformFloat("time", game_timer.game_time);
+ Textures::Instance()->bindTexture(blood_work_tex);
+
+ static const GLfloat data[] = {0,0, 1,0, 1,1, 0,1};
+ static const GLuint index[] = {0,1,2, 0,2,3};
+ static VBOContainer square_vbo;
+ static VBOContainer index_vbo;
+ if(!square_vbo.valid()){
+ square_vbo.Fill(kVBOFloat | kVBOStatic, sizeof(data), (void*)data);
+ index_vbo.Fill(kVBOElement | kVBOStatic, sizeof(index), (void*)index);
+ }
+ square_vbo.Bind();
+ index_vbo.Bind();
+
+ int vert_attrib_id = Shaders::Instance()->returnShaderAttrib("vert_coord", extinguish_id);
+ Graphics::Instance()->EnableVertexAttribArray(vert_attrib_id);
+ glVertexAttribPointer(vert_attrib_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0);
+ Graphics::Instance()->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ Graphics::Instance()->ResetVertexAttribArrays();
+
+ EndDrawingToBloodTexture();
+ blood_tex_mipmap_dirty = true;
+ }
+}
+
+void BloodSurface::GetShaderNames(std::map<std::string, int>& shaders) {
+ shaders[simple_2d] = 0;
+ shaders[simple_2d_stab_texture] = 0;
+ shaders[character_accumulate_dry] = 0;
+ shaders[character_accumulate_ignite] = 0;
+ shaders[character_accumulate_extinguish] = 0;
+}
diff --git a/Source/Graphics/bloodsurface.h b/Source/Graphics/bloodsurface.h
new file mode 100644
index 00000000..5f3c0559
--- /dev/null
+++ b/Source/Graphics/bloodsurface.h
@@ -0,0 +1,105 @@
+//-----------------------------------------------------------------------------
+// Name: bloodsurface.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec2.h>
+#include <Asset/Asset/texture.h>
+#include <Graphics/modelsurfacewalker.h>
+
+#include <list>
+#include <map>
+
+class Object;
+class Model;
+class SceneGraph;
+
+static bool blood_inited = false;
+static bool blood_uniforms_inited = false;
+
+class BloodSurface {
+public:
+ class TransformedVertexGetter { // Used to get transformed geometry from items or characters
+ public:
+ virtual vec3 GetTransformedVertex(int val) = 0;
+ };
+
+ bool blood_tex_mipmap_dirty;
+ TextureRef blood_tex;
+ TextureRef blood_work_tex;
+ float sleep_time;
+ BloodSurface(TransformedVertexGetter* transformed_vertex_getter);
+ ~BloodSurface();
+ void Dispose();
+ void PreDrawFrame(int width, int height);
+ void AttachToModel( Model * attach_model );
+ void Update(SceneGraph *scenegraph, float timestep, bool force_update = false);
+ void CreateBloodDripNearestPointTransformed( const vec3 &bone_pos, float blood_amount, bool can_drip );
+ void CreateBloodDripNearestPointDirection( const vec3 &bone_pos, const vec3 &direction, float blood_amount, bool can_drip );
+ void CleanBlood();
+ void SetFire(float fire);
+ void SetWet(float wet);
+ void AddDecalToTriangles( const std::vector<int> &hit_list, vec3 pos, vec3 dir, float size, const TextureAssetRef& texture_ref );
+ void AddBloodLine( const vec2 &start, const vec2 &end );
+ void CreateDripInTri( int tri, const vec3 &bary_coords, float blood_amount, float delay, bool can_drip, SurfaceWalker::Type type);
+ void StartDrawingToBloodTexture();
+ void EndDrawingToBloodTexture();
+ void Ignite();
+ void Extinguish();
+ void GetShaderNames(std::map<std::string, int>& shaders);
+
+private:
+ struct CutLine {
+ vec2 start;
+ vec2 end;
+ };
+
+ void InitShaders();
+ void InitUniforms();
+
+ TransformedVertexGetter* transformed_vertex_getter_;
+ Model *model;
+ int drip_update_delay;
+ int dry_delay;
+ int dry_steps;
+ bool framebuffer_created;
+ GLuint blood_framebuffer;
+ GLuint blood_work_framebuffer;
+ typedef std::list<SurfaceWalker> SurfaceWalkerList;
+ SurfaceWalkerList surface_walkers;
+ std::vector<CutLine> cut_lines;
+ ModelSurfaceWalker model_surface_walker;
+
+ int simple_2d_id;
+ int simple_2d_stab_id;
+ int character_accumulate_id;
+ int ignite_id;
+ int extinguish_id;
+
+ const char* simple_2d;
+ const char* simple_2d_stab_texture;
+ const char* character_accumulate_dry;
+ const char* character_accumulate_ignite;
+ const char* character_accumulate_extinguish;
+};
+
diff --git a/Source/Graphics/bonetransform.cpp b/Source/Graphics/bonetransform.cpp
new file mode 100644
index 00000000..46a50e49
--- /dev/null
+++ b/Source/Graphics/bonetransform.cpp
@@ -0,0 +1,113 @@
+//-----------------------------------------------------------------------------
+// Name: bonetransform.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "bonetransform.h"
+
+#include <Math/enginemath.h>
+#include <Math/vec3math.h>
+#include <Math/quaternions.h>
+
+#include <cmath>
+
+const BoneTransform& BoneTransform::operator=( const mat4 &other )
+{
+ rotation = QuaternionFromMat4(other);
+ origin = other.GetTranslationPart();
+ return (*this);
+}
+
+BoneTransform invert( const BoneTransform& transform )
+{
+ BoneTransform inverted;
+ inverted.rotation = invert(transform.rotation);
+ inverted.origin = inverted.rotation * -transform.origin;
+ return inverted;
+}
+
+BoneTransform operator*( const BoneTransform& a, const BoneTransform& b )
+{
+ BoneTransform result;
+ result.rotation = a.rotation * b.rotation;
+ result.origin = a.rotation * b.origin + a.origin;
+ //result = a.GetMat4() * b.GetMat4();
+ return result;
+}
+
+BoneTransform operator*( const quaternion& a, const BoneTransform& b )
+{
+ BoneTransform result;
+ result.rotation = a * b.rotation;
+ result.origin = a * b.origin;
+ return result;
+}
+
+vec3 operator*( const BoneTransform& a, const vec3& b )
+{
+ vec3 result = a.rotation * b + a.origin;
+ return result;
+}
+
+BoneTransform mix( const BoneTransform &a, const BoneTransform &b, float alpha )
+{
+ BoneTransform result;
+ result.origin = mix(a.origin, b.origin, alpha);
+ result.rotation = mix(a.rotation, b.rotation, alpha);
+ return result;
+}
+
+mat4 BoneTransform::GetMat4() const
+{
+ mat4 matrix = Mat4FromQuaternion(rotation);
+ matrix.SetTranslationPart(origin);
+ return matrix;
+}
+
+BoneTransform::BoneTransform( const mat4 &other )
+{
+ (*this) = other;
+}
+
+BoneTransform::BoneTransform()
+{}
+
+BoneTransform ApplyParentRotations(const std::vector<BoneTransform> &matrices,
+ int id,
+ const std::vector<int> &parents)
+{
+ int parent_id = parents[id];
+ if(parent_id == -1){
+ return matrices[id];
+ } else {
+ return ApplyParentRotations(matrices,parent_id,parents) *
+ matrices[id];
+ }
+}
+
+bool operator==(const BoneTransform& a, const BoneTransform& b) {
+ return (a.origin == b.origin && a.rotation == b.rotation);
+}
+
+void BoneTransform::LoadIdentity() {
+ origin = vec3(0.0f);
+ rotation = quaternion();
+}
diff --git a/Source/Graphics/bonetransform.h b/Source/Graphics/bonetransform.h
new file mode 100644
index 00000000..a96c37cd
--- /dev/null
+++ b/Source/Graphics/bonetransform.h
@@ -0,0 +1,56 @@
+//-----------------------------------------------------------------------------
+// Name: bonetransform.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/quaternions.h>
+
+#include <vector>
+
+struct BoneTransform {
+ quaternion rotation;
+ vec3 origin;
+
+ BoneTransform(const mat4 &other);
+ BoneTransform();
+
+ const BoneTransform& operator=(const mat4 &other);
+ mat4 GetMat4() const;
+ void LoadIdentity();
+};
+
+BoneTransform invert(const BoneTransform& transform);
+BoneTransform operator*( const BoneTransform& a,
+ const BoneTransform& b );
+vec3 operator*( const BoneTransform& a,
+ const vec3& b );
+bool operator==( const BoneTransform& a,
+ const BoneTransform& b );
+BoneTransform operator*( const quaternion& a, const BoneTransform& b );
+BoneTransform mix(const BoneTransform &a,
+ const BoneTransform &b,
+ float alpha);
+
+BoneTransform ApplyParentRotations(const std::vector<BoneTransform> &matrices,
+ int id,
+ const std::vector<int> &parents);
diff --git a/Source/Graphics/bytecolor.cpp b/Source/Graphics/bytecolor.cpp
new file mode 100644
index 00000000..f3e5ea79
--- /dev/null
+++ b/Source/Graphics/bytecolor.cpp
@@ -0,0 +1,73 @@
+//-----------------------------------------------------------------------------
+// Name: bytecolor.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "bytecolor.h"
+
+#include <Math/enginemath.h>
+#include <Graphics/ColorWheel.h>
+#include <Images/texture_data.h>
+
+unsigned distance_squared( const ByteColor &a, const ByteColor &b ) {
+ return square((int)a.color[0]-(int)b.color[0]) +
+ square((int)a.color[1]-(int)b.color[1]) +
+ square((int)a.color[2]-(int)b.color[2]);
+}
+
+float hue_saturation_distance_squared( const ByteColor &a, const ByteColor &b ) {
+ vec3 hsv_a = RGBtoHSV(vec3(a.color[0]/256.0f,a.color[1]/256.0f,a.color[2]/256.0f));
+ vec3 hsv_b = RGBtoHSV(vec3(b.color[0]/256.0f,b.color[1]/256.0f,b.color[2]/256.0f));
+ float hue_a = hsv_a[0] / 360.0f;
+ float hue_b = hsv_b[0] / 360.0f;
+ float hue_difference = min(fabs(hue_a-hue_b),min(fabs(hue_a-(1.0f+hue_b)),fabs(1.0f+hue_a-hue_b)));
+ return square(hue_difference) +
+ square(hsv_a[1] - hsv_b[1]) +
+ square(hsv_a[2] - hsv_b[2]);
+}
+
+int WeightIndex( const TextureData &data, int i, int j ) {
+ // TODO: check this
+ return (i+j*data.GetWidth())*4;
+}
+
+ByteColor::ByteColor() {
+ color[0] = 0;
+ color[1] = 0;
+ color[2] = 0;
+}
+
+ByteColor::ByteColor( unsigned char r, unsigned char g, unsigned char b ) {
+ Set(r,g,b);
+}
+
+void ByteColor::Set( unsigned char r, unsigned char g, unsigned char b ) {
+ color[0] = r;
+ color[1] = g;
+ color[2] = b;
+}
+
+const ByteColor & ByteColor::operator=( const ByteColor &other ) {
+ color[0] = other.color[0];
+ color[1] = other.color[1];
+ color[2] = other.color[2];
+ return *this;
+}
diff --git a/Source/Graphics/bytecolor.h b/Source/Graphics/bytecolor.h
new file mode 100644
index 00000000..37136d4c
--- /dev/null
+++ b/Source/Graphics/bytecolor.h
@@ -0,0 +1,43 @@
+//-----------------------------------------------------------------------------
+// Name: bytecolor.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec4.h>
+#include <Math/vec3.h>
+
+#include <Graphics/textures.h>
+
+struct ByteColor {
+ unsigned char color[3];
+
+ ByteColor();
+ ByteColor(unsigned char r, unsigned char g, unsigned char b);
+ void Set(unsigned char r, unsigned char g, unsigned char b);
+ const ByteColor &operator=(const ByteColor &other);
+};
+
+vec4 GetAverageColor( const char* abs_path );
+unsigned distance_squared(const ByteColor &a, const ByteColor &b);
+float hue_saturation_distance_squared(const ByteColor &a, const ByteColor &b);
+int WeightIndex(const TextureData &data, int i, int j);
diff --git a/Source/Graphics/camera.cpp b/Source/Graphics/camera.cpp
new file mode 100644
index 00000000..ec3533b3
--- /dev/null
+++ b/Source/Graphics/camera.cpp
@@ -0,0 +1,1162 @@
+//-----------------------------------------------------------------------------
+// Name: camera.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "camera.h"
+
+#include <Graphics/graphics.h>
+#include <Graphics/vbocontainer.h>
+#include <Graphics/shaders.h>
+
+#include <Internal/timer.h>
+#include <Internal/profiler.h>
+
+#include <UserInput/input.h>
+#include <Math/vec3math.h>
+#include <Wrappers/glm.h>
+#include <Main/engine.h>
+#include <Online/online.h>
+
+#include <cstring>
+#include <cmath>
+
+#define USE_SSE
+
+//-----------------------------------------------------------------------------
+//Functions
+//-----------------------------------------------------------------------------
+
+extern Timer game_timer;
+
+const vec3& Camera::GetFacing() const {
+ return facing;
+}
+
+const vec3& Camera::GetFlatFacing() const {
+ return flat_facing;
+}
+
+void Camera::SetFlatFacing(const vec3 &v) {
+ flat_facing = v;
+}
+
+void Camera::FixDiscontinuity()
+{
+ old_chase_distance = chase_distance;
+ old_y_rotation = y_rotation;
+ old_x_rotation = x_rotation;
+ old_pos = pos;
+ old_z_rotation = z_rotation;
+}
+
+Camera::Camera() :
+ m_camera_object(NULL),
+ interp_chase_distance(0.0f),
+ near_blur_amount(0.0f),
+ far_blur_amount(0.0f),
+ near_sharp_dist(3.0f),
+ far_sharp_dist((float)pow(5.0f, 0.5f)),
+ near_blur_transition_size(1.0f),
+ far_blur_transition_size(2.0f) ,
+ biasMatrix(0.5f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.5f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.5f, 0.0f,
+ 0.5f, 0.5f, 0.5f, 1.0f){
+ for (unsigned int i = 0; i < 16; i++) {
+ modelview_matrix[i] = 0.0;
+ projection_matrix[i] = 0.0;
+ }
+ reset();
+}
+
+void Camera::SetDistance(const float new_val) {
+ old_chase_distance = chase_distance;
+ chase_distance=new_val;
+}
+
+void Camera::SetYRotation(const float new_val) {
+ old_y_rotation = y_rotation;
+ y_rotation = new_val;
+
+ while(old_y_rotation>=y_rotation+180){
+ old_y_rotation-=360;
+ }
+ while(old_y_rotation<=y_rotation-180){
+ old_y_rotation+=360;
+ }
+}
+
+void Camera::SetXRotation(const float new_val) {
+ old_x_rotation = x_rotation;
+ x_rotation = new_val;
+}
+
+void Camera::SetFacing(const vec3 &_facing) {
+ facing = _facing;
+}
+
+void Camera::SetUp(const vec3 &_up) {
+ up = _up;
+}
+
+void Camera::SetZRotation(const float new_val) {
+ old_z_rotation = z_rotation;
+ z_rotation = new_val;
+}
+
+void Camera::SetPos(const vec3 &new_val) {
+ old_pos = pos;
+ pos = new_val;
+}
+
+void Camera::LookAt(const vec3 &new_val) {
+ vec3 vector = normalize(new_val-pos);
+ float new_y_rotation = YAxisRotationFromVector(vector);
+ float new_x_rotation = asinf(vector.y())/3.14f*180.0f;
+ SetYRotation(new_y_rotation);
+ SetXRotation(new_x_rotation);
+}
+
+float Camera::GetYRotation() const {
+ return mix(old_y_rotation,y_rotation,GetInterpWeight());
+}
+
+float Camera::GetChaseDistance() const {
+ return mix(old_chase_distance,chase_distance,GetInterpWeight());
+}
+
+float Camera::GetXRotation() const {
+ return mix(old_x_rotation,x_rotation,GetInterpWeight());
+}
+
+float Camera::GetZRotation() const {
+ return mix(old_z_rotation,z_rotation,GetInterpWeight());
+}
+
+vec3 Camera::GetPos() const {
+ return interp_pos-facing*interp_chase_distance;
+}
+
+const vec3& Camera::GetMouseRay() const {
+ return mouseray;
+}
+
+const vec3& Camera::GetUpVector() const {
+ return up;
+}
+
+const float& Camera::GetNearPlane() const {
+ return near_plane;
+}
+
+const float& Camera::GetFarPlane() const {
+ return far_plane;
+}
+
+const float& Camera::GetFOV() const {
+ return target_horz_fov;
+}
+
+const mat4& Camera::getInverseCameraMatrix() const {
+ return inverseCameraViewMatrix;
+}
+
+const mat4& Camera::getCameraMatrix() const {
+ return cameraViewMatrix;
+}
+
+vec3 Camera::getRelative(const vec3 &absolute) {
+ vec3 relative = absolute - GetPos();
+
+ relative = doRotation(relative, 0, -y_rotation, 0);
+ relative = doRotation(relative, -x_rotation, 0, 0);
+ relative = doRotation(relative, 0, 0, -z_rotation);
+
+ return relative;
+}
+
+void Camera::applyShadowViewpoint(vec3 direction, vec3 center, float scale, mat4 *proj_matrix, mat4 *view_matrix, float far_back) {
+ if(!proj_matrix){
+ proj_matrix = &lightProjectionMatrix;
+ }
+ if(!view_matrix){
+ view_matrix = &lightViewMatrix;
+ }
+ proj_matrix->SetOrtho(-scale*0.5f,
+ scale*0.5f,
+ -scale*0.5f,
+ scale*0.5f,
+ 10.0f,
+ 2000.0f);
+
+ facing = normalize(direction) * -1.0f;
+
+ view_matrix->SetLookAt(center+direction*far_back, center-direction, vec3(0.0f,1.0f,0.0f));
+
+ calcFrustumPlanes(*proj_matrix, *view_matrix);
+
+ interp_pos = mix(old_pos, pos, GetInterpWeight());
+}
+
+//Apply the camera viewpoint to the view matrix
+void Camera::applyViewpoint() {
+ Online* online = Online::Instance();
+ Graphics *gi = Graphics::Instance();
+ mat4 projection = GetPerspectiveMatrix();
+
+ CalcFacing(); //Find the forward vector
+ calcUp(); //Find the up vector
+
+ float interp_weight = GetInterpWeight();
+ interp_pos = mix(old_pos, pos, interp_weight);
+ interp_chase_distance = mix(old_chase_distance, chase_distance, interp_weight);
+
+ //Apply camera transformation
+ vec3 final_cam_pos;
+
+
+ final_cam_pos = interp_pos - facing * interp_chase_distance;
+
+
+ cameraViewMatrix.SetLookAt(final_cam_pos, final_cam_pos + facing, up);
+ calcFrustumPlanes(projection, cameraViewMatrix);
+ inverseCameraViewMatrix = invert(cameraViewMatrix);
+
+ Input *userInput = Input::Instance();
+
+ //Find mouse direction in world space
+ vec3 mouse;
+ mouse.x()=(float)userInput->getMouse().pos_[0];
+ mouse.y()=(float)(userInput->getMouse().pos_[1]*-1+gi->window_dims[1]);
+ mouse.z()=0.5f;
+
+ for(unsigned i=0; i<16; ++i){
+ modelview_matrix[i] = cameraViewMatrix.entries[i];
+ projection_matrix[i] = projection.entries[i];
+ }
+
+ viewport[0]=0;
+ viewport[1]=0;
+ viewport[2]=gi->window_dims[0];
+ viewport[3]=gi->window_dims[1];
+
+ GLdouble mouserayx,mouserayy,mouserayz;
+ gluUnProject ( mouse.x(), mouse.y(), mouse.z(),
+ modelview_matrix,
+ projection_matrix,
+ viewport,
+ &mouserayx,
+ &mouserayy,
+ &mouserayz);
+
+ mouseray.x() = (float)mouserayx;
+ mouseray.y() = (float)mouserayy;
+ mouseray.z() = (float)mouserayz;
+
+ mouse.x()=(float)(gi->window_dims[0])/2.0f;
+ mouse.y()=(float)(gi->window_dims[1])/2.0f;
+
+ gluUnProject ( mouse.x(), mouse.y(), mouse.z(),
+ modelview_matrix,
+ projection_matrix,
+ viewport,
+ &mouserayx,
+ &mouserayy,
+ &mouserayz);
+
+ vec3 center = vec3((float)mouserayx,(float)mouserayy,(float)mouserayz);
+ center-=interp_pos;
+ float ray_length = length(center);
+
+ mouseray-=(interp_pos - facing * interp_chase_distance);
+ //mouseoffset=length(mouseray)/ray_length;
+
+ mouseray/=ray_length;
+}
+
+vec3 Camera::GetRayThroughPixel(int x, int y) {
+ vec3 ray = UnProjectPixel(x,y);
+ ray -= vec3((interp_pos - facing * interp_chase_distance));
+ ray = normalize(ray);
+ return ray;
+}
+
+vec3 Camera::UnProjectPixel(int x, int y) {
+ float z = 0.5f;
+ GLdouble world_x, world_y, world_z;
+ gluUnProject ( x, y, z,
+ modelview_matrix,
+ projection_matrix,
+ viewport,
+ &world_x,
+ &world_y,
+ &world_z);
+
+ vec3 world_coords((float)world_x, (float)world_y, (float)world_z);
+ return world_coords;
+}
+
+vec3 Camera::ProjectPoint(const vec3 &point) {
+ GLdouble screen_x, screen_y, screen_z;
+ gluProject (point[0], point[1], point[2],
+ modelview_matrix,
+ projection_matrix,
+ viewport,
+ &screen_x,
+ &screen_y,
+ &screen_z);
+
+ vec3 screen_coords((float)screen_x, (float)screen_y, (float)screen_z);
+ return screen_coords;
+}
+
+vec3 Camera::ProjectPoint(float x, float y, float z) {
+ return ProjectPoint(vec3(x,y,z));
+}
+
+//Transform from world coordinates to screen coordinates
+vec3 Camera::worldToScreen(const vec3 point) const {
+ GLdouble windowx,windowy,windowz;
+ gluProject ( point.x(), point.y(), point.z(),
+ modelview_matrix,
+ projection_matrix,
+ viewport,
+ &windowx,
+ &windowy,
+ &windowz);
+
+ vec3 window;
+ window.x() = (float)windowx;
+ window.y() = (float)windowy;
+ window.z() = (float)windowz;
+
+ return window;
+}
+
+float GetAspectRatio() {
+ Graphics *gi = Graphics::Instance();
+ return gi->viewport_dim[2]/(float)gi->viewport_dim[3];
+}
+
+float TargetVertFovFromHorz(float target_horz_fov) {
+ return target_horz_fov / (4.0f/3.0f);
+}
+
+float VertFOVFromHorz(float target_horz_fov, float aspect_ratio) {
+ // Enforce minimum fov on each axis
+ float target_horz_radians = target_horz_fov * deg2radf;
+ float horz_unit = tan(target_horz_fov * deg2radf * 0.5f);
+ float vert_unit = horz_unit / aspect_ratio;
+ float vert_angle = atan(vert_unit) * 2.0f * rad2degf;
+ vert_angle = max(vert_angle, TargetVertFovFromHorz(target_horz_fov));
+ return vert_angle;
+}
+
+mat4 Camera::GetPerspectiveMatrix() const {
+ float aspect_ratio = GetAspectRatio();
+ mat4 matrix;
+ if(flexible_fov){
+ matrix.SetPerspectiveInfinite(VertFOVFromHorz(target_horz_fov, aspect_ratio), aspect_ratio, near_plane, far_plane);
+ } else {
+ matrix.SetPerspectiveInfinite(target_horz_fov, aspect_ratio, near_plane, far_plane);
+ }
+ return matrix;
+}
+
+//Set the camera field of view
+void Camera::SetFOV(const float degrees) {
+ target_horz_fov = degrees;
+}
+
+void Camera::DrawSafeZone() {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "DrawSafeZone");
+ CHECK_GL_ERROR();
+ GLState gl_state;
+ gl_state.blend = true;
+ gl_state.cull_face = false;
+ gl_state.depth_test = false;
+ gl_state.depth_write = false;
+
+ Graphics::Instance()->setGLState(gl_state);
+ int shader_id = Shaders::Instance()->returnProgram("simple_2d");
+ Shaders::Instance()->setProgram(shader_id);
+
+ float aspect_ratio = GetAspectRatio();
+ mat4 perspective_matrix;
+ perspective_matrix.SetPerspectiveInfinite(VertFOVFromHorz(target_horz_fov, aspect_ratio), aspect_ratio, near_plane, far_plane);
+ float vert_fov = tanf(TargetVertFovFromHorz(target_horz_fov)*3.14159266f/180.0f*0.45f);
+ float horz_fov = tanf(target_horz_fov*3.14159266f/180.0f*0.45f);
+ vec3 safe_pos(horz_fov,vert_fov,1);
+ safe_pos = perspective_matrix * safe_pos;
+ const float safe_pos_extend = 0.8f;
+
+ glm::mat4 proj = glm::ortho(-1.0f, 1.0f, -1.0f, 1.0f);
+ Shaders::Instance()->SetUniformMat4("mvp_mat", (const GLfloat*)&proj);
+ Shaders::Instance()->SetUniformVec4("color", vec4(1.0f));
+
+ Graphics::Instance()->SetLineWidth(1);
+
+ float data[] = {
+ safe_pos[0], safe_pos[1], safe_pos[2],
+ safe_pos[0]*safe_pos_extend, safe_pos[1], safe_pos[2],
+ safe_pos[0], safe_pos[1], safe_pos[2],
+ safe_pos[0], safe_pos[1]*safe_pos_extend, safe_pos[2],
+
+ -safe_pos[0], safe_pos[1], safe_pos[2],
+ -safe_pos[0]*safe_pos_extend, safe_pos[1], safe_pos[2],
+ -safe_pos[0], safe_pos[1], safe_pos[2],
+ -safe_pos[0], safe_pos[1]*safe_pos_extend, safe_pos[2],
+
+ safe_pos[0], -safe_pos[1], safe_pos[2],
+ safe_pos[0]*safe_pos_extend, -safe_pos[1], safe_pos[2],
+ safe_pos[0], -safe_pos[1], safe_pos[2],
+ safe_pos[0], -safe_pos[1]*safe_pos_extend, safe_pos[2],
+
+ -safe_pos[0], -safe_pos[1], safe_pos[2],
+ -safe_pos[0]*safe_pos_extend, -safe_pos[1], safe_pos[2],
+ -safe_pos[0], -safe_pos[1], safe_pos[2],
+ -safe_pos[0], -safe_pos[1]*safe_pos_extend, safe_pos[2],
+
+ -safe_pos[0] * 2.0f, -safe_pos[1] * 0.5f, safe_pos[2],
+ safe_pos[0] * 2.0f, -safe_pos[1] * 0.5f, safe_pos[2]
+ };
+
+ static VBOContainer safe_zone_vbo;
+ safe_zone_vbo.Fill(kVBODynamic | kVBOFloat, sizeof(data), data);
+ safe_zone_vbo.Bind();
+
+ int vert_attrib_id = Shaders::Instance()->returnShaderAttrib("vert_coord", shader_id);
+ Graphics::Instance()->EnableVertexAttribArray(vert_attrib_id);
+ CHECK_GL_ERROR();
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, 3*sizeof(float), 0);
+ Graphics::Instance()->DrawArrays(GL_LINES, 0, 16);
+ CHECK_GL_ERROR();
+ Graphics::Instance()->DrawArrays(GL_LINES, 16, 2);
+ Graphics::Instance()->ResetVertexAttribArrays();
+ CHECK_GL_ERROR();
+}
+
+//Calculate camera forward vector
+void Camera::CalcFacing() {
+ flat_facing = vec3(0.0f,0.0f,-1.0f);
+ facing = flat_facing;
+
+ float interp_weight = GetInterpWeight();
+ float interp_y_rotation = mix(old_y_rotation,y_rotation,interp_weight);
+ float interp_x_rotation = mix(old_x_rotation,x_rotation,interp_weight);
+
+ facing=doRotation(facing,interp_x_rotation,0,0);
+ facing=doRotation(facing,0,interp_y_rotation,0);
+
+ flat_facing=doRotation(flat_facing,0,interp_y_rotation,0);
+}
+
+//Calculate camera up vector
+void Camera::calcUp() {
+ up = vec3(0.0f,1.0f,0.0f);
+
+ float interp_weight = GetInterpWeight();
+ float interp_y_rotation = mix(old_y_rotation,y_rotation,interp_weight);
+ float interp_x_rotation = mix(old_x_rotation,x_rotation,interp_weight);
+ float interp_z_rotation = mix(old_z_rotation,z_rotation,interp_weight);
+
+ up=doRotation(up,0,0,interp_z_rotation);
+ up=doRotation(up,interp_x_rotation,0,0);
+ up=doRotation(up,0,interp_y_rotation,0);
+}
+
+//Calculate the view frustum planes
+void Camera::calcFrustumPlanes( const mat4 &_p, const mat4 &_mv ) {
+ const GLfloat *p = &_p.entries[0]; // projection matrix
+ const GLfloat *mv = &_mv.entries[0]; // model-view matrix
+ float mvp[16]; // model-view-projection matrix
+ float t;
+
+ //
+ // Concatenate the projection matrix and the model-view matrix to produce
+ // a combined model-view-projection matrix.
+ //
+
+ mvp[ 0] = mv[ 0] * p[ 0] + mv[ 1] * p[ 4] + mv[ 2] * p[ 8] + mv[ 3] * p[12];
+ mvp[ 1] = mv[ 0] * p[ 1] + mv[ 1] * p[ 5] + mv[ 2] * p[ 9] + mv[ 3] * p[13];
+ mvp[ 2] = mv[ 0] * p[ 2] + mv[ 1] * p[ 6] + mv[ 2] * p[10] + mv[ 3] * p[14];
+ mvp[ 3] = mv[ 0] * p[ 3] + mv[ 1] * p[ 7] + mv[ 2] * p[11] + mv[ 3] * p[15];
+
+ mvp[ 4] = mv[ 4] * p[ 0] + mv[ 5] * p[ 4] + mv[ 6] * p[ 8] + mv[ 7] * p[12];
+ mvp[ 5] = mv[ 4] * p[ 1] + mv[ 5] * p[ 5] + mv[ 6] * p[ 9] + mv[ 7] * p[13];
+ mvp[ 6] = mv[ 4] * p[ 2] + mv[ 5] * p[ 6] + mv[ 6] * p[10] + mv[ 7] * p[14];
+ mvp[ 7] = mv[ 4] * p[ 3] + mv[ 5] * p[ 7] + mv[ 6] * p[11] + mv[ 7] * p[15];
+
+ mvp[ 8] = mv[ 8] * p[ 0] + mv[ 9] * p[ 4] + mv[10] * p[ 8] + mv[11] * p[12];
+ mvp[ 9] = mv[ 8] * p[ 1] + mv[ 9] * p[ 5] + mv[10] * p[ 9] + mv[11] * p[13];
+ mvp[10] = mv[ 8] * p[ 2] + mv[ 9] * p[ 6] + mv[10] * p[10] + mv[11] * p[14];
+ mvp[11] = mv[ 8] * p[ 3] + mv[ 9] * p[ 7] + mv[10] * p[11] + mv[11] * p[15];
+
+ mvp[12] = mv[12] * p[ 0] + mv[13] * p[ 4] + mv[14] * p[ 8] + mv[15] * p[12];
+ mvp[13] = mv[12] * p[ 1] + mv[13] * p[ 5] + mv[14] * p[ 9] + mv[15] * p[13];
+ mvp[14] = mv[12] * p[ 2] + mv[13] * p[ 6] + mv[14] * p[10] + mv[15] * p[14];
+ mvp[15] = mv[12] * p[ 3] + mv[13] * p[ 7] + mv[14] * p[11] + mv[15] * p[15];
+
+ //
+ // Extract the frustum's right clipping plane and normalize it.
+ //
+
+ frustumPlanes[0][0] = mvp[ 3] - mvp[ 0];
+ frustumPlanes[0][1] = mvp[ 7] - mvp[ 4];
+ frustumPlanes[0][2] = mvp[11] - mvp[ 8];
+ frustumPlanes[0][3] = mvp[15] - mvp[12];
+
+ t = (float) sqrtf( frustumPlanes[0][0] * frustumPlanes[0][0] +
+ frustumPlanes[0][1] * frustumPlanes[0][1] +
+ frustumPlanes[0][2] * frustumPlanes[0][2] );
+
+ frustumPlanes[0][0] /= t;
+ frustumPlanes[0][1] /= t;
+ frustumPlanes[0][2] /= t;
+ frustumPlanes[0][3] /= t;
+
+ //
+ // Extract the frustum's left clipping plane and normalize it.
+ //
+
+ frustumPlanes[1][0] = mvp[ 3] + mvp[ 0];
+ frustumPlanes[1][1] = mvp[ 7] + mvp[ 4];
+ frustumPlanes[1][2] = mvp[11] + mvp[ 8];
+ frustumPlanes[1][3] = mvp[15] + mvp[12];
+
+ t = (float) sqrtf( frustumPlanes[1][0] * frustumPlanes[1][0] +
+ frustumPlanes[1][1] * frustumPlanes[1][1] +
+ frustumPlanes[1][2] * frustumPlanes[1][2] );
+
+ frustumPlanes[1][0] /= t;
+ frustumPlanes[1][1] /= t;
+ frustumPlanes[1][2] /= t;
+ frustumPlanes[1][3] /= t;
+
+ //
+ // Extract the frustum's bottom clipping plane and normalize it.
+ //
+
+ frustumPlanes[2][0] = mvp[ 3] + mvp[ 1];
+ frustumPlanes[2][1] = mvp[ 7] + mvp[ 5];
+ frustumPlanes[2][2] = mvp[11] + mvp[ 9];
+ frustumPlanes[2][3] = mvp[15] + mvp[13];
+
+ t = (float) sqrtf( frustumPlanes[2][0] * frustumPlanes[2][0] +
+ frustumPlanes[2][1] * frustumPlanes[2][1] +
+ frustumPlanes[2][2] * frustumPlanes[2][2] );
+
+ frustumPlanes[2][0] /= t;
+ frustumPlanes[2][1] /= t;
+ frustumPlanes[2][2] /= t;
+ frustumPlanes[2][3] /= t;
+
+ //
+ // Extract the frustum's top clipping plane and normalize it.
+ //
+
+ frustumPlanes[3][0] = mvp[ 3] - mvp[ 1];
+ frustumPlanes[3][1] = mvp[ 7] - mvp[ 5];
+ frustumPlanes[3][2] = mvp[11] - mvp[ 9];
+ frustumPlanes[3][3] = mvp[15] - mvp[13];
+
+ t = (float) sqrtf( frustumPlanes[3][0] * frustumPlanes[3][0] +
+ frustumPlanes[3][1] * frustumPlanes[3][1] +
+ frustumPlanes[3][2] * frustumPlanes[3][2] );
+
+ frustumPlanes[3][0] /= t;
+ frustumPlanes[3][1] /= t;
+ frustumPlanes[3][2] /= t;
+ frustumPlanes[3][3] /= t;
+
+ //
+ // Extract the frustum's far clipping plane and normalize it.
+ //
+
+ frustumPlanes[4][0] = mvp[ 3] - mvp[ 2];
+ frustumPlanes[4][1] = mvp[ 7] - mvp[ 6];
+ frustumPlanes[4][2] = mvp[11] - mvp[10];
+ frustumPlanes[4][3] = mvp[15] - mvp[14];
+
+ t = (float) sqrtf( frustumPlanes[4][0] * frustumPlanes[4][0] +
+ frustumPlanes[4][1] * frustumPlanes[4][1] +
+ frustumPlanes[4][2] * frustumPlanes[4][2] );
+
+ frustumPlanes[4][0] /= t;
+ frustumPlanes[4][1] /= t;
+ frustumPlanes[4][2] /= t;
+ frustumPlanes[4][3] /= t;
+
+ //
+ // Extract the frustum's near clipping plane and normalize it.
+ //
+
+ frustumPlanes[5][0] = mvp[ 3] + mvp[ 2];
+ frustumPlanes[5][1] = mvp[ 7] + mvp[ 6];
+ frustumPlanes[5][2] = mvp[11] + mvp[10];
+ frustumPlanes[5][3] = mvp[15] + mvp[14];
+
+ t = (float) sqrtf( frustumPlanes[5][0] * frustumPlanes[5][0] +
+ frustumPlanes[5][1] * frustumPlanes[5][1] +
+ frustumPlanes[5][2] * frustumPlanes[5][2] );
+
+ frustumPlanes[5][0] /= t;
+ frustumPlanes[5][1] /= t;
+ frustumPlanes[5][2] /= t;
+ frustumPlanes[5][3] /= t;
+
+ // Copy to SIMD values (duplicated out 4 times each) for SIMD optimized implementations
+ // TODO: Remove non-SIMD values above if all frustum culling can be made to use SIMD,
+ // and if functions that call frustum culling can all be made to be 4x aligned?
+
+ simdFrustumPlanes[0].normal_x = _mm_set_ps1(frustumPlanes[0][0]);
+ simdFrustumPlanes[0].normal_y = _mm_set_ps1(frustumPlanes[0][1]);
+ simdFrustumPlanes[0].normal_z = _mm_set_ps1(frustumPlanes[0][2]);
+ simdFrustumPlanes[0].d = _mm_set_ps1(frustumPlanes[0][3]);
+
+ simdFrustumPlanes[1].normal_x = _mm_set_ps1(frustumPlanes[1][0]);
+ simdFrustumPlanes[1].normal_y = _mm_set_ps1(frustumPlanes[1][1]);
+ simdFrustumPlanes[1].normal_z = _mm_set_ps1(frustumPlanes[1][2]);
+ simdFrustumPlanes[1].d = _mm_set_ps1(frustumPlanes[1][3]);
+
+ simdFrustumPlanes[2].normal_x = _mm_set_ps1(frustumPlanes[2][0]);
+ simdFrustumPlanes[2].normal_y = _mm_set_ps1(frustumPlanes[2][1]);
+ simdFrustumPlanes[2].normal_z = _mm_set_ps1(frustumPlanes[2][2]);
+ simdFrustumPlanes[2].d = _mm_set_ps1(frustumPlanes[2][3]);
+
+ simdFrustumPlanes[3].normal_x = _mm_set_ps1(frustumPlanes[3][0]);
+ simdFrustumPlanes[3].normal_y = _mm_set_ps1(frustumPlanes[3][1]);
+ simdFrustumPlanes[3].normal_z = _mm_set_ps1(frustumPlanes[3][2]);
+ simdFrustumPlanes[3].d = _mm_set_ps1(frustumPlanes[3][3]);
+
+ simdFrustumPlanes[4].normal_x = _mm_set_ps1(frustumPlanes[4][0]);
+ simdFrustumPlanes[4].normal_y = _mm_set_ps1(frustumPlanes[4][1]);
+ simdFrustumPlanes[4].normal_z = _mm_set_ps1(frustumPlanes[4][2]);
+ simdFrustumPlanes[4].d = _mm_set_ps1(frustumPlanes[4][3]);
+
+ simdFrustumPlanes[5].normal_x = _mm_set_ps1(frustumPlanes[5][0]);
+ simdFrustumPlanes[5].normal_y = _mm_set_ps1(frustumPlanes[5][1]);
+ simdFrustumPlanes[5].normal_z = _mm_set_ps1(frustumPlanes[5][2]);
+ simdFrustumPlanes[5].d = _mm_set_ps1(frustumPlanes[5][3]);
+}
+
+//Check if a sphere is visible
+int Camera::checkSphereInFrustum( vec3 where, float radius ) const {
+ int sphere_in_frustum = 2;
+ for (int i = 0; i < 6; ++i ) {
+ float d =
+ frustumPlanes[i][0] * where.x() +
+ frustumPlanes[i][1] * where.y() +
+ frustumPlanes[i][2] * where.z() +
+ frustumPlanes[i][3];
+ if( d <= -radius ) {
+ return 0; // Sphere is entirely outside one of the frustum planes
+ } else if(d < radius){
+ sphere_in_frustum = 1; // Sphere is intersecting a frustum plane
+ }
+ }
+ return sphere_in_frustum;
+}
+
+#if defined(USE_SSE)
+static __m128 simd_dot_product(__m128 x1, __m128 y1, __m128 z1, __m128 x2, __m128 y2, __m128 z2)
+{
+ const auto mul_x = _mm_mul_ps(x1, x2);
+ const auto mul_y = _mm_mul_ps(y1, y2);
+ const auto mul_z = _mm_mul_ps(z1, z2);
+ return _mm_add_ps(_mm_add_ps(mul_x, mul_y), mul_z);
+}
+
+static __m128 simd_distance_squared(__m128 x1, __m128 y1, __m128 z1, __m128 x2, __m128 y2, __m128 z2)
+{
+ const auto diff_x = _mm_sub_ps(x1, x2);
+ const auto mul_x = _mm_mul_ps(diff_x, diff_x);
+ const auto diff_y = _mm_sub_ps(y1, y2);
+ const auto mul_y = _mm_mul_ps(diff_y, diff_y);
+ const auto diff_z = _mm_sub_ps(z1, z2);
+ const auto mul_z = _mm_mul_ps(diff_z, diff_z);
+ return _mm_add_ps(_mm_add_ps(mul_x, mul_y), mul_z);
+}
+#endif // defined(USE_SSE)
+
+void Camera::checkSpheresInFrustum(int count, float* where_x, float* where_y, float* where_z, float radius, float cull_distance_squared, uint32_t* is_visible_result) const {
+ const vec3 cam_pos = GetPos();
+ int i = 0;
+
+#if defined(USE_SSE)
+ // This loop exhausts all up to the last 0-3
+ const __m128 all_true = _mm_set1_ps((float) 0xFFFFFFFF);
+ const __m128 neg_radius4 = _mm_set1_ps(-radius);
+
+ const __m128 cam_pos_x4 = _mm_set1_ps(cam_pos.x());
+ const __m128 cam_pos_y4 = _mm_set1_ps(cam_pos.y());
+ const __m128 cam_pos_z4 = _mm_set1_ps(cam_pos.z());
+ const __m128 cull_distance_squared4 = _mm_set1_ps(cull_distance_squared);
+
+ for (; i <= count - 4; i += 4) {
+ const __m128 where_x4 = _mm_load_ps(&where_x[i]);
+ const __m128 where_y4 = _mm_load_ps(&where_y[i]);
+ const __m128 where_z4 = _mm_load_ps(&where_z[i]);
+
+ __m128 inside = all_true;
+
+ __m128 distance_squared = simd_distance_squared(where_x4, where_y4, where_z4, cam_pos_x4, cam_pos_y4, cam_pos_z4);
+ __m128 is_in_view_distance = _mm_cmplt_ps(distance_squared, cull_distance_squared4);
+ inside = _mm_and_ps(inside, is_in_view_distance);
+
+ for (unsigned p = 0; p < 6; ++p) {
+ const __m128& plane_n_x4 = simdFrustumPlanes[p].normal_x;
+ const __m128& plane_n_y4 = simdFrustumPlanes[p].normal_y;
+ const __m128& plane_n_z4 = simdFrustumPlanes[p].normal_z;
+ __m128 n_dot_pos = simd_dot_product(where_x4, where_y4, where_z4, plane_n_x4, plane_n_y4, plane_n_z4);
+
+ __m128 plane_test = _mm_cmpgt_ps(_mm_add_ps(n_dot_pos, simdFrustumPlanes[p].d), neg_radius4);
+ inside = _mm_and_ps(inside, plane_test);
+ }
+
+ _mm_store_ps((float*)&is_visible_result[i], inside);
+ }
+#endif // defined(USE_SSE)
+
+ for (; i < count; ++i) {
+ bool inside = true;
+
+ float center_to_cam[3] = {
+ where_x[i] - cam_pos.x(),
+ where_y[i] - cam_pos.y(),
+ where_z[i] - cam_pos.z(),
+ };
+ float distance_squared = center_to_cam[0] * center_to_cam[0] +
+ center_to_cam[1] * center_to_cam[1] +
+ center_to_cam[2] * center_to_cam[2];
+
+ if (distance_squared < cull_distance_squared) {
+ for (int p = 0; p < 6; ++p) {
+ float n_dot_pos =
+ frustumPlanes[p][0] * where_x[i] +
+ frustumPlanes[p][1] * where_y[i] +
+ frustumPlanes[p][2] * where_z[i];
+ bool plane_test = n_dot_pos + frustumPlanes[p][3] > -radius;
+ inside = inside && plane_test;
+ }
+ } else {
+ inside = false;
+ }
+
+ is_visible_result[i] = inside ? 0xFFFFFFFF : 0;
+ }
+}
+
+
+//Check if a box is visible
+int Camera::checkBoxInFrustum(vec3 start, vec3 end) const
+{
+ int total_in = 0;
+ vec3 point;
+
+ for(int p = 0; p < 6; ++p) {
+
+ int in_count = 8;
+ bool box_in_frustum = 1;
+
+ for (int i = 0; i < 8; ++i) {
+ if(i<4)point.x()=start.x();
+ else point.x()=end.x();
+ if(i%4<2)point.y()=start.y();
+ else point.y()=end.y();
+ if(i%2==0)point.z()=start.z();
+ else point.z()=end.z();
+
+ // test this point against the planes
+ if( frustumPlanes[p][0] * point.x() +
+ frustumPlanes[p][1] * point.y() +
+ frustumPlanes[p][2] * point.z() +
+ frustumPlanes[p][3] < 0) {
+ box_in_frustum = 0;
+ in_count--;
+ }
+ }
+
+ // were all the points outside of plane p?
+ if(in_count == 0)
+ return(0);
+
+ // check if they were all on the right side of the plane
+ total_in += box_in_frustum;
+ }
+
+ // so if iTotalIn is 6, then all are inside the view
+ if(total_in == 6)
+ return(2);
+
+ // we must be partly in then otherwise
+ return(1);
+}
+
+const vec3& Camera::GetVelocity() const
+{
+ return velocity;
+}
+
+void Camera::SetVelocity( const vec3 &vel )
+{
+ velocity = vel;
+}
+
+void Camera::ASSetPos( vec3 pos )
+{
+ SetPos(pos);
+}
+
+vec3 Camera::ASGetPos() const
+{
+ return GetPos();
+}
+
+void Camera::ASSetVelocity(vec3 vel)
+{
+ SetVelocity(vel);
+}
+
+vec3 Camera::ASGetUpVector() const
+{
+ return GetUpVector();
+}
+
+void Camera::ASSetFacing( vec3 _facing ) {
+ SetFacing(_facing);
+}
+
+void Camera::ASSetUp( vec3 _up )
+{
+ SetUp(_up);
+}
+
+void Camera::ASLookAt(vec3 new_val)
+{
+ LookAt(new_val);
+}
+
+vec3 Camera::ASGetMouseRay() const
+{
+ return GetMouseRay();
+}
+
+void Camera::reset() {
+ target_horz_fov=90.0f;
+ near_plane=0.1f;
+ far_plane=1000.0f;
+ chase_distance=0.0f;
+ old_chase_distance=0.0f;
+ y_rotation=0.0f;
+ x_rotation=0.0f;
+ z_rotation=0.0f;
+ old_y_rotation=0.0f;
+ old_x_rotation=0.0f;
+ old_z_rotation=0.0f;
+ interp_steps = 1;
+ interp_progress = 0;
+ flags_ = 0;
+ flexible_fov = true;
+}
+
+void Camera::SetInterpSteps( int num_steps )
+{
+ interp_steps = num_steps;
+ interp_progress = 0;
+}
+
+void Camera::IncrementProgress()
+{
+ ++interp_progress;
+ if(interp_progress >= interp_steps){
+ interp_progress = 0;
+ }
+}
+
+mat4 Camera::GetViewMatrix()
+{
+ mat4 view_mat;
+ for(int i=0; i<16; ++i){
+ view_mat.entries[i] = (float)modelview_matrix[i];
+ }
+ return view_mat;
+}
+
+mat4 Camera::GetProjMatrix()
+{
+ mat4 proj_mat;
+ for(int i=0; i<16; ++i){
+ proj_mat.entries[i] = (float)projection_matrix[i];
+ }
+ return proj_mat;
+}
+
+void Camera::SetAutoCamera( bool val )
+{
+ auto_camera = val;
+}
+
+bool Camera::GetAutoCamera()
+{
+ return auto_camera;
+}
+
+float Camera::GetInterpWeight() const {
+ return game_timer.GetInterpWeightX(interp_steps,interp_progress);
+}
+
+vec3 Camera::GetCenterVel() const {
+ return (pos - old_pos) * (float)game_timer.simulations_per_second / (float)interp_steps;
+}
+
+int Camera::GetFlags() {
+ return flags_;
+}
+
+void Camera::SetFlags(int val) {
+ flags_ = val;
+}
+
+void Camera::SetMatrices(const mat4& view, const mat4& proj) {
+ for(int i=0; i<16; ++i){
+ modelview_matrix[i] = (double)view[i];
+ }
+ for(int i=0; i<16; ++i){
+ projection_matrix[i] = (double)proj[i];
+ }
+}
+
+/*
+void Camera::PrintDifferences( const Camera& cam )
+{
+ LOGI << "Following things are different" << std::endl;
+
+ if( facing != cam.facing )
+ {
+ LOGI << "facing" << std::endl;
+ }
+
+ if( flat_facing != cam.flat_facing )
+ {
+ LOGI << "flat_facing" << std::endl;
+ }
+
+ if( target_horz_fov != cam.target_horz_fov )
+ {
+ LOGI << "target_horz_fov" << std::endl;
+ }
+
+ if( near_plane != cam.near_plane )
+ {
+ LOGI << "near_plane" << std::endl;
+ }
+
+ if( far_plane != cam.far_plane )
+ {
+ LOGI << "far_plane" << std::endl;
+ }
+
+ if( chase_distance != cam.chase_distance )
+ {
+ LOGI << "chase_distance" << std::endl;
+ }
+
+ if( old_chase_distance != cam.old_chase_distance )
+ {
+ LOGI << "old_chase_distance" << std::endl;
+ }
+
+ if( interp_chase_distance != cam.interp_chase_distance )
+ {
+ LOGI << "interp_chase_distance" << std::endl;
+ }
+
+ if( velocity != cam.velocity )
+ {
+ LOGI << "velocity" << std::endl;
+ }
+
+ if( up != cam.up )
+ {
+ LOGI << "up" << std::endl;
+ }
+
+ if( shake_facing != cam.shake_facing )
+ {
+ LOGI << "shake_facing" << std::endl;
+ }
+
+ if( pos != cam.pos )
+ {
+ LOGI << "pos" << std::endl;
+ }
+
+ if( old_pos != cam.old_pos )
+ {
+ LOGI << "old_pos" << std::endl;
+ }
+
+ if( interp_pos != cam.interp_pos )
+ {
+ LOGI << "interp_pos" << std::endl;
+ }
+
+ if( x_rotation != cam.x_rotation )
+ {
+ LOGI << "x_rotation" << std::endl;
+ }
+
+ if( y_rotation != cam.y_rotation )
+ {
+ LOGI << "y_rotation" << std::endl;
+ }
+
+ if( z_rotation != cam.z_rotation )
+ {
+ LOGI << "z_rotation" << std::endl;
+ }
+
+ if( old_x_rotation != cam.old_x_rotation )
+ {
+ LOGI << "old_x_rotation" << std::endl;
+ }
+
+ if( old_y_rotation != cam.old_y_rotation )
+ {
+ LOGI << "old_y_rotation" << std::endl;
+ }
+
+ if( old_z_rotation != cam.old_z_rotation )
+ {
+ LOGI << "old_z_rotation" << std::endl;
+ }
+
+ if( collision_detection != cam.collision_detection )
+ {
+ LOGI << "collision_detection" << std::endl;
+ }
+
+ if( shake_amount != cam.shake_amount )
+ {
+ LOGI << "shake_amount" << std::endl;
+ }
+
+ if( cameraViewMatrix != cam.cameraViewMatrix )
+ {
+ LOGI << "cameraViewMatrix" << std::endl;
+ }
+
+ if( inverseCameraViewMatrix != cam.inverseCameraViewMatrix )
+ {
+ LOGI << "inverseCameraViewMatrix" << std::endl;
+ }
+
+ if( lightProjectionMatrix != cam.lightProjectionMatrix )
+ {
+ LOGI << "lightProjectionMatrix" << std::endl;
+ }
+
+ if( lightViewMatrix != cam.lightViewMatrix )
+ {
+ LOGI << "lightViewMatrix" << std::endl;
+ }
+
+ if( mouseray != cam.mouseray )
+ {
+ LOGI << "mouseray" << std::endl;
+ }
+
+ //modelview_matrix[16];
+ //projection_matrix[16];
+ //viewport[4];
+
+ if( interp_steps != cam.interp_steps )
+ {
+ LOGI << "interp_steps" << std::endl;
+ }
+
+ if( interp_progress != cam.interp_progress )
+ {
+ LOGI << "interp_progress" << std::endl;
+ }
+
+ if( auto_camera != cam.auto_camera )
+ {
+ LOGI << "auto_camera" << std::endl;
+ }
+
+ if( flags_ != cam.flags_ )
+ {
+ LOGI << "flags_" << std::endl;
+ }
+
+ if( prev_view_mat != cam.prev_view_mat )
+ {
+ LOGI << "prev_view_mat" << std::endl;
+ }
+
+ if( near_blur_amount != cam.near_blur_amount )
+ {
+ LOGI << "near_blur_amount" << std::endl;
+ }
+
+ if( far_blur_amount != cam.far_blur_amount )
+ {
+ LOGI << "far_blur_amount" << std::endl;
+ }
+
+ if( near_sharp_dist != cam.near_sharp_dist )
+ {
+ LOGI << "near_sharp_dist" << std::endl;
+ }
+
+ if( far_sharp_dist != cam.far_sharp_dist )
+ {
+ LOGI << "far_sharp_dist" << std::endl;
+ }
+
+ if( near_blur_transition_size != cam.near_blur_transition_size )
+ {
+ LOGI << "near_blur_transition_size" << std::endl;
+ }
+
+ if( far_blur_transition_size != cam.far_blur_transition_size )
+ {
+ LOGI << "far_blur_transition_size" << std::endl;
+ }
+
+ //frustumPlanes[6][4];
+
+ if( biasMatrix != cam.biasMatrix )
+ {
+ LOGI << "biasMatrix" << std::endl;
+ }
+
+ if( flexible_fov != cam.flexible_fov )
+ {
+ LOGI << "flexible_fov" << std::endl;
+ }
+
+ LOGI << "end camera diff" << std::endl;
+}
+*/
diff --git a/Source/Graphics/camera.h b/Source/Graphics/camera.h
new file mode 100644
index 00000000..1abb2e13
--- /dev/null
+++ b/Source/Graphics/camera.h
@@ -0,0 +1,463 @@
+//-----------------------------------------------------------------------------
+// Name: camera.cpp
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: The camera class holds the position, and rotation of the
+// camera, and the view settings
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Graphics/graphics.h>
+#include <Math/vec4.h>
+
+#include <opengl.h>
+
+#include <vector>
+#include <list>
+#include <emmintrin.h>
+
+class CameraObject;
+
+/// The Camera class handles the view orientation in 3D space.
+class Camera
+{
+private:
+ vec3 facing; ///< Vector for which direction the camera is pointing
+ vec3 flat_facing; ///< Facing on the XZ plane
+ float target_horz_fov; ///< The field of view, in degrees.
+ float near_plane; ///< Distance to the near plane
+ float far_plane; ///< Distance to the far plane
+ float chase_distance; ///< How far the chase camera is from the player.
+ float old_chase_distance;
+ float interp_chase_distance;
+
+ vec3 velocity;
+ vec3 up; ///< Up vector (combined with facing, gives complete orientation)
+
+ vec3 pos; ///< Current position
+ vec3 old_pos; ///< Previous timestep position
+ public: // Made this public temporarily to fix VR problem TODO: do this more cleanly
+ vec3 interp_pos; ///< Interpolated position (for smooth rendering)
+ private:
+ float y_rotation; ///< Rotation on the y axis (left and right)
+ float x_rotation; ///< Rotation on the x axis (up and down)
+ float z_rotation; ///< Rotation on the z axis (roll clockwise and ccw)
+ float old_y_rotation; ///< Previous timestep y_rotation
+ float old_x_rotation; ///< Previous timestep x_rotation
+ float old_z_rotation; ///< Previous timestep z_rotation
+
+ mat4 cameraViewMatrix; ///< Matrix to transform world to view
+ mat4 inverseCameraViewMatrix; ///< Matrix to transform view to world
+ mat4 lightProjectionMatrix; ///< Ortographic projection matrix for shadows
+ mat4 lightViewMatrix; ///< View matrix from light pov for shadows
+
+ vec3 mouseray; ///< Vector in the direction that the mouse is pointing
+
+ GLdouble modelview_matrix[16]; ///< Camera modelview matrix
+ GLdouble projection_matrix[16]; ///< Camera projection matrix
+ GLint viewport[4]; ///< Viewport dimensions (in pixels)
+
+ int interp_steps;
+ int interp_progress;
+ float GetInterpWeight() const;
+
+ bool auto_camera;
+ int flags_;
+
+ /// Initializes the camera member variables to default
+ void reset();
+
+public:
+ vec3 tint;
+ vec3 vignette_tint;
+ mat4 prev_view_mat;
+ // Depth of field filter settings
+ float near_blur_amount;
+ float far_blur_amount;
+ float near_sharp_dist;
+ float far_sharp_dist;
+ float near_blur_transition_size;
+ float far_blur_transition_size;
+
+ float frustumPlanes[6][4]; ///< Frustum plane parameters (ax+by+cz+d)
+
+ struct SIMDPlane { // TODO: Comment out if SIMD not supported?
+ __m128 normal_x; // same value pre-replicated 4 times for each of these
+ __m128 normal_y;
+ __m128 normal_z;
+ __m128 d;
+ };
+ SIMDPlane simdFrustumPlanes[6];
+
+ mat4 biasMatrix; ///< Bias matrix for shadow projection
+ bool flexible_fov;
+ enum CameraFlags {
+ kEditorCamera = 1 << 0,
+ kPreviewCamera = 1 << 1
+ };
+
+ vec3 GetCenterVel() const;
+ const vec3& GetFacing() const; ///< Accessor for facing
+ const vec3& GetFlatFacing() const; ///< Accessor for flat_facing
+ void SetFlatFacing(const vec3 &v); ///< Mutator for flat_facing
+ void FixDiscontinuity();
+
+ /// The default Camera constructer initializes the biasMatrix and calls reset()
+ Camera();
+
+ CameraObject* m_camera_object; ///<CameraObject exposed for global access (fix)
+
+ /**
+ * Sets the CameraObject so that it can be accessed globally.
+ * @param camera_object A pointer to the CameraObject.
+ */
+ inline void SetCameraObject(CameraObject* camera_object) {
+ reset();
+ m_camera_object = camera_object;
+ };
+
+ /**
+ * Get the CameraObject that is controlling the camera.
+ */
+ CameraObject* getCameraObject() {
+ return m_camera_object;
+ }
+
+ /**
+ * Set how far the camera is behind the player.
+ * @param chase_distance The new chase distance.
+ */
+ void SetDistance(const float chase_distance);
+
+ /**
+ * Set the Y-axis (left and right) rotation of the camera.
+ * @param y_rotation The new Y-axis rotation in degrees.
+ */
+ void SetYRotation(const float y_rotation);
+
+ /**
+ * Set the X-axis (up and down) rotation of the camera.
+ * @param x_rotation The new X-axis rotation in degrees.
+ */
+ void SetXRotation(const float x_rotation);
+
+ /**
+ * Set the Z-axis (roll) rotation of the camera.
+ * @param z_rotation The new Z-axis rotation in degrees.
+ */
+ void SetZRotation(const float z_rotation);
+
+ void SetInterpSteps(int num_steps);
+ void IncrementProgress();
+
+ /**
+ * Set the position of the camera.
+ * @param pos The new position.
+ */
+ void SetPos(const vec3 &pos);
+ void ASSetPos(vec3 pos);
+
+ /**
+ * Set the field of view of the camera.
+ * @param fov The new FOV in degrees.
+ */
+ void SetFOV(const float fov);
+
+ /**
+ * Set the x and y rotation of the camera to look at a point.
+ * @param point The point to look at.
+ */
+ void LookAt(const vec3 &point);
+
+
+ // Lots of accessors
+ float GetYRotation() const;
+ float GetXRotation() const;
+ float GetZRotation() const;
+ vec3 GetPos() const;
+ vec3 ASGetPos() const;
+ const vec3& GetMouseRay() const;
+ vec3 ASGetMouseRay() const;
+ const vec3& GetUpVector() const;
+ vec3 ASGetUpVector() const;
+ const float& GetNearPlane() const;
+ const float& GetFarPlane() const;
+ const float& GetFOV() const;
+ const vec3& GetVelocity() const;
+ void SetVelocity(const vec3 &vel);
+
+ void SetAutoCamera( bool val );
+ bool GetAutoCamera( );
+
+ /**
+ * Get the Camera->World transformation matrix.
+ * @return The 4x4 transform matrix.
+ */
+ const mat4& getInverseCameraMatrix() const;
+
+ /**
+ * Get the World->Camera transformation matrix.
+ * @return The 4x4 transform matrix.
+ */
+ const mat4& getCameraMatrix() const;
+
+ /**
+ * Convert a point from world coordinates to camera coordinates.
+ * @param absolute The point in world space to convert.
+ * @return The point in camera space.
+ */
+ vec3 getRelative(const vec3 &absolute);
+
+ /**
+ * Convert a point from world coordinates to screen coordinates.
+ * @param point The point in world space to convert.
+ * @return The point in screen space.
+ */
+ vec3 worldToScreen(const vec3 point) const;
+
+ /**
+ * Set up an orthographic projection for use with shadow rendering.
+ * @param direction The direction that the light is coming from.
+ * The projection will face the opposite direction.
+ * @param center Where the projection will start.
+ * @param scale The dimensions of the projection square.
+ */
+ void applyShadowViewpoint(vec3 direction, vec3 center, float scale, mat4 *proj_matrix = NULL, mat4 *view_matrix = NULL, float far_back = 1000.0f);
+
+ /**
+ * Apply the camera view to the OpenGL matrices.
+ */
+ void applyViewpoint();
+
+ /**
+ * Gets a vector that points through a specific pixel on the screen.
+ * @param x X-coordinate in pixels
+ * @param y Y-coordinate in pixels
+ * @return The 3D vector that would go through that pixel.
+ */
+ vec3 GetRayThroughPixel(int x, int y);
+
+ /**
+ * Get the coordinates of a pixel (un)projected into world space.
+ * @param x X-coordinate in pixels
+ * @param y Y-coordinate in pixels
+ * @return The 3D coordinates of the (un)projected pixel.
+ */
+ vec3 UnProjectPixel(int x, int y);
+
+ /**
+ * Get the coordinates of a world space point projected on the screen.
+ * @param point The world space point to project
+ * @return The coordinates of the pixel.
+ */
+ vec3 ProjectPoint(const vec3 &point);
+ vec3 ProjectPoint(float x, float y, float z);
+
+ /**
+ * Calculate facing vectors based on rotation values.
+ */
+ void CalcFacing();
+
+ /**
+ * Calculate up vector based on facing vector and global up (0,1,0).
+ */
+ void calcUp();
+
+ /**
+ * Calculate frustum plane parameters based on modelview and
+ * projection matrices.
+ */
+ void calcFrustumPlanes( const mat4 &p, const mat4 &mv );
+
+ /**
+ * Check if a sphere is within the view frustum.
+ * @param where The center of the sphere.
+ * @param radius The radius of the sphere.
+ * @return Returns 2 if the sphere is entirely in the frustum, 1 if partially, 0 if not at all
+ */
+ int checkSphereInFrustum(vec3 where, float radius) const;
+
+ /**
+ * Check if an array of spheres are within the view frustum.
+ * @param count The number of spheres to check.
+ * @param where_x The center of the sphere (x).
+ * @param where_y The center of the sphere (y).
+ * @param where_z The center of the sphere (z).
+ * @param radius The radius of the spheres (same across all the spheres).
+ * @param radius The view distance to cull against before checking against frustum planes.
+ * @param is_visible_result The result of the visibility checks will be written here, not bit-packed.
+ */
+ void checkSpheresInFrustum(int count, float* where_x, float* where_y, float* where_z, float radius, float cull_distance_squared, uint32_t* is_visible_result) const;
+
+ /**
+ * Check if an axis-aligned box is within the view frustum.
+ * @param start The minimum corner of the box.
+ * @param end The maximum corner of the box.
+ * @return Returns 2 if fully in, 1 if partly in, 0 if out of frustum.
+ */
+ int checkBoxInFrustum(vec3 start, vec3 end) const;
+
+ /**
+ * Camera uses the Singleton pattern, this returns a pointer to the static Camera.
+ * @return Returns a pointer to the static Camera.
+ */
+ void PushState();
+ void PopState();
+ mat4 GetPerspectiveMatrix() const;
+ void ASSetVelocity(vec3 vel);
+ void UpdateShake();
+ float GetChaseDistance() const;
+ void SetFacing(const vec3 &_facing);
+ void SetUp(const vec3 &_up);
+ void ASSetUp( vec3 _up );
+ void ASSetFacing( vec3 _facing );
+ void ASLookAt(vec3 new_val);
+ mat4 GetViewMatrix();
+ mat4 GetProjMatrix();
+ void DrawSafeZone();
+ int GetFlags();
+ void SetFlags(int val);
+
+ //void PrintDifferences( const Camera& cam );
+ void SetMatrices(const mat4& view, const mat4& proj);
+};
+
+class ActiveCameras {
+private:
+ //Cameras used logically to represent a remote virtual player, but isn't used for rendering.
+ std::vector<Camera> virtual_cameras;
+ std::vector<int> free_virtual_cameras;
+
+ std::list<Camera> cameras;
+ std::vector<Camera*> camera_ptrs;
+ Camera* active;
+ int active_id;
+
+ void mSet(int which) {
+ if(which >= (int)cameras.size()){
+ cameras.resize(which+1);
+ camera_ptrs.resize(cameras.size());
+ int counter = 0;
+ for(std::list<Camera>::iterator iter = cameras.begin();
+ iter != cameras.end(); ++iter)
+ {
+ camera_ptrs[counter++] = &(*iter);
+ }
+ }
+ active = camera_ptrs[which];
+ active_id = which;
+ }
+public:
+ std::vector<Camera>& GetVirtualCameras() {
+ return virtual_cameras;
+ }
+
+
+ int CreateVirtualCameraInstance() {
+ int virtual_camera_index = 0;
+ if(free_virtual_cameras.size() > 0) {
+ virtual_camera_index = *free_virtual_cameras.rbegin();
+ free_virtual_cameras.resize(free_virtual_cameras.size() - 1);
+ } else {
+ virtual_cameras.resize(virtual_cameras.size() + 1);
+ virtual_camera_index = (int) virtual_cameras.size() - 1;
+ }
+
+ return virtual_camera_index + 255;
+ }
+
+ void FreeVirtualCameraInstance(int index) {
+ int virtual_camera_index = index - 255;
+
+ if(virtual_camera_index >= 0) {
+ free_virtual_cameras.push_back(virtual_camera_index);
+ }
+ }
+
+ static ActiveCameras* Instance() {
+ static ActiveCameras instance;
+ return &instance;
+ }
+
+ static Camera* Get() {
+ return Instance()->active;
+ }
+
+ static Camera* GetCamera(int camera_id) {
+ if (camera_id >= 255) {
+ int virtual_camera_index = camera_id - 255;
+
+ if (virtual_camera_index >= 0 && virtual_camera_index < Instance()->virtual_cameras.size()) {
+ return &Instance()->virtual_cameras[virtual_camera_index];
+ } else {
+ return nullptr;
+ }
+ } else {
+#ifdef DEBUG
+ if(NumCameras() <= camera_id) {
+ LOGW << "Trying to get camera with id " << camera_id << ", but only " << NumCameras() << " are available" << std::endl;
+ return Instance()->camera_ptrs[camera_id];
+ }
+#endif
+ if (camera_id >= 0 && camera_id < Instance()->camera_ptrs.size()) {
+ return Instance()->camera_ptrs[camera_id];
+ } else {
+ return nullptr;
+ }
+ }
+ }
+
+ static int NumCameras() {
+ return (int) Instance()->cameras.size();
+ }
+
+ void UpdatePrevViewMats() {
+ for(std::list<Camera>::iterator iter = cameras.begin();
+ iter != cameras.end(); ++iter)
+ {
+ Camera* camera = &(*iter);
+ camera->prev_view_mat = camera->GetViewMatrix();
+ }
+
+ for(std::vector<Camera>::iterator iter = virtual_cameras.begin();
+ iter != virtual_cameras.end(); ++iter)
+ {
+ Camera* camera = &(*iter);
+ camera->prev_view_mat = camera->GetViewMatrix();
+ }
+ }
+
+ static int GetID() {
+ return Instance()->active_id;
+ }
+
+ static void Set(int which) {
+ Instance()->mSet(which);
+ }
+
+ void Dispose() {
+ virtual_cameras.clear();
+ cameras.clear();
+ camera_ptrs.clear();
+ active = NULL;
+ }
+
+};
diff --git a/Source/Graphics/converttexture.cpp b/Source/Graphics/converttexture.cpp
new file mode 100644
index 00000000..f375974d
--- /dev/null
+++ b/Source/Graphics/converttexture.cpp
@@ -0,0 +1,145 @@
+//-----------------------------------------------------------------------------
+// Name: converttexture.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "converttexture.h"
+
+#include <Internal/common.h>
+#include <Internal/checksum.h>
+#include <Internal/filesystem.h>
+#include <Internal/datemodified.h>
+#include <Internal/error.h>
+
+#include <Compat/fileio.h>
+#include <Compat/compat.h>
+
+#include <Images/freeimage_wrapper.h>
+#include <Logging/logdata.h>
+
+#include <cstring>
+#include <sstream>
+#include <string>
+
+using std::string;
+using std::endl;
+using std::ostringstream;
+
+extern const int overgrowth_dds_cache_version;
+extern const char* overgrowth_dds_cache_intro;
+
+bool ConvertImage( string src, string dst, string temp_conversion_path, TextureData::ConversionQuality quality ) {
+ LOGI << "Converting " << src << " to " << dst << " by way of " << temp_conversion_path << endl;
+
+ bool dds_suffix = false;
+ int end = dst.length() - 1;
+ if (end >= 3 &&
+ dst[end - 3] == '.' &&
+ dst[end - 2] == 'd' &&
+ dst[end - 1] == 'd' &&
+ dst[end - 0] == 's')
+ {
+ dds_suffix = true;
+ }
+
+ TextureData textureData;
+ if (!textureData.Load(src.c_str())) {
+ LOGE << "Failed loading texture " << src << endl;
+ return false;
+ }
+
+ if (!textureData.GenerateMipmaps()) {
+ LOGE << "Failed generating mipmaps for texture " << src << endl;
+ return false;
+ }
+
+ //const char* hint;
+ //hint = strrchr( src.c_str(), '_');
+ #ifdef _WIN32
+ ShortenWindowsPath(src);
+ ShortenWindowsPath(dst);
+ ShortenWindowsPath(temp_conversion_path);
+ #endif
+
+ CreateParentDirs(temp_conversion_path);
+
+ bool successful_conversion = false;
+
+ if (dds_suffix) {
+ if (!textureData.ConvertDXT(crnlib::PIXEL_FMT_DXT5, quality)) {
+ LOGE << "Failed converting texture " << src << endl;
+ return false;
+ }
+ successful_conversion = textureData.SaveDDS(temp_conversion_path.c_str());
+ } else {
+ successful_conversion = textureData.SaveCRN(temp_conversion_path.c_str(), cCRNFmtDXT5, quality);
+ }
+
+ if( successful_conversion ) {
+ unsigned short checksum = Checksum(src);
+ FILE *file = my_fopen(temp_conversion_path.c_str(), "a");
+ if(file){
+ fwrite(overgrowth_dds_cache_intro, strlen(overgrowth_dds_cache_intro), 1, file);
+ fwrite(&overgrowth_dds_cache_version, sizeof(overgrowth_dds_cache_version), 1, file);
+ fwrite(&checksum, sizeof(checksum), 1, file);
+ fclose(file);
+ }
+
+ if(movefile(temp_conversion_path.c_str(), dst.c_str())){
+ LOGI << "Removing existing dst file" << endl;
+ deletefile(dst.c_str());
+ CreateParentDirs(dst);
+ if(movefile(temp_conversion_path.c_str(), dst.c_str())){
+ #ifndef NO_ERR
+ DisplayError("Error",
+ (string("Problem moving file \"") + strerror(errno) + "\". From " +
+ temp_conversion_path +
+ " to " + dst).c_str());
+ #endif
+ return false;
+ }
+ }
+ LOGI << "Conversion completed" << endl;
+ //Textures::Instance()->NotifyConvertedTexture(src);
+ return true;
+ //return textureData;
+ } else {
+ LOGE << "Conversion failed, unable to write texture to " << temp_conversion_path << endl;
+ return false;
+ }
+}
+
+string GetTempDDSPath(string dst, bool write_path, int concurrent_id){
+ string temp_conversion_path;
+ if(write_path) {
+ ostringstream oss;
+ oss << "Data/Temp/conversion" << concurrent_id << ".dds";
+ temp_conversion_path = oss.str();
+ } else {
+ temp_conversion_path = dst+"_tmp";
+ }
+ if(write_path){
+ char path[kPathSize];
+ FormatString(path, kPathSize, "%s%s", GetWritePath(CoreGameModID).c_str(), temp_conversion_path.c_str());
+ temp_conversion_path = path;
+ }
+ return temp_conversion_path;
+}
diff --git a/Source/Graphics/converttexture.h b/Source/Graphics/converttexture.h
new file mode 100644
index 00000000..a33e869a
--- /dev/null
+++ b/Source/Graphics/converttexture.h
@@ -0,0 +1,34 @@
+//-----------------------------------------------------------------------------
+// Name: converttexture.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Images/texture_data.h>
+
+#include <string>
+
+bool ConvertImage( std::string src, std::string dst, std::string temp_conversion_path, TextureData::ConversionQuality quality );
+
+// Get a location that we can use to write a temporary .dds file, so that we don't end
+// up with an incomplete dds file if the conversion fails halfway through
+std::string GetTempDDSPath(std::string dst, bool write_path, int concurrent_id);
diff --git a/Source/Graphics/csg.cpp b/Source/Graphics/csg.cpp
new file mode 100644
index 00000000..932d894e
--- /dev/null
+++ b/Source/Graphics/csg.cpp
@@ -0,0 +1,1320 @@
+//-----------------------------------------------------------------------------
+// Name: csg.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "csg.h"
+
+#include <Graphics/model.h>
+#include <Graphics/models.h>
+#include <Graphics/pxdebugdraw.h>
+
+#include <Math/enginemath.h>
+#include <Math/triangle.h>
+#include <Math/vec2math.h>
+
+#include <Physics/bulletworld.h>
+#include <Memory/allocation.h>
+#include <Logging/logdata.h>
+#include <Utility/compiler_macros.h>
+
+#include <opengl.h>
+
+#include <algorithm>
+#include <vector>
+
+namespace Test {
+ struct HalfEdge {
+ int points[2];
+ HalfEdge *twin, *next;
+ };
+
+ struct HalfEdgeSort {
+ HalfEdge *edge;
+ int id;
+ };
+
+ bool operator<(const HalfEdgeSort& a, const HalfEdgeSort& b) {
+ int temp_points[2][2];
+ temp_points[0][0] = a.edge->points[0];
+ temp_points[0][1] = a.edge->points[1];
+ temp_points[1][0] = b.edge->points[0];
+ temp_points[1][1] = b.edge->points[1];
+ for(int i=0; i<2; ++i){
+ if(temp_points[i][0] > temp_points[i][1]){
+ std::swap(temp_points[i][0], temp_points[i][1]);
+ }
+ }
+ if(temp_points[0][0] == temp_points[1][0]){
+ return temp_points[0][1] < temp_points[1][1];
+ } else {
+ return temp_points[0][0] < temp_points[1][0];
+ }
+ }
+
+ struct Edge {
+ int points[2];
+ };
+
+ bool operator<(const Edge& a, const Edge& b){
+ if(a.points[0] == b.points[0]){
+ return a.points[1] < b.points[1];
+ } else {
+ return a.points[0] < b.points[0];
+ }
+ }
+
+ bool operator!=(const Edge& a, const Edge& b){
+ return (a.points[0] != b.points[0] || a.points[1] != b.points[1]);
+ }
+}
+
+namespace {
+ /*
+ float orient2d(const float *a, const float *b, const float *c) {
+ return (b[0]-a[0])*(c[1]-a[1]) - (b[1]-a[1])*(c[0]-a[0]);
+ }
+ */
+
+ double orient2d_double(const double *a, const double *b, const double *c) {
+ return (b[0]-a[0])*(c[1]-a[1]) - (b[1]-a[1])*(c[0]-a[0]);
+ }
+
+ struct TriangleEdge {
+ int points[2];
+ int tri;
+ };
+
+ /*
+ bool operator<(const TriangleEdge& a, const TriangleEdge &b){
+ if(a.points[0] == b.points[0]){
+ return a.points[1] < b.points[1];
+ } else {
+ return a.points[0] < b.points[0];
+ }
+ }
+ */
+
+ bool NeighborSort(const TriangleEdge& a, const TriangleEdge &b){
+ int points[2][2];
+ for(int i=0; i<2; ++i){
+ points[0][i] = a.points[i];
+ points[1][i] = b.points[i];
+ }
+ for(int i=0; i<2; ++i){
+ if(points[i][0] > points[i][1]){
+ std::swap(points[i][0], points[i][1]);
+ }
+ }
+ if(points[0][0] == points[1][0]){
+ return points[0][1] < points[1][1];
+ } else {
+ return points[0][0] < points[1][0];
+ }
+ }
+
+ typedef std::pair<int, int> TriNeighbor;
+
+ /*
+ bool operator<(const TriNeighbor& a, const TriNeighbor& b){
+ return a.first < b.first;
+ }
+ */
+
+ struct TriNeighborInfo {
+ std::vector<TriNeighbor> tri_neighbors;
+ std::vector<int> num_tri_neighbors;
+ std::vector<int> tri_neighbor_index;
+ };
+
+ void GetTriNeighbors(const std::vector<int> &faces, const std::vector<double> &verts, TriNeighborInfo *info, bool display){
+ info->tri_neighbors.clear();
+ info->num_tri_neighbors.clear();
+ info->tri_neighbor_index.clear();
+ std::vector<TriangleEdge> edges;
+ edges.reserve(faces.size());
+ for(int i=0, len=faces.size(); i<len; i+=3){
+ for(int j=0; j<3; ++j){
+ TriangleEdge edge;
+ edge.points[0] = faces[i+j];
+ edge.points[1] = faces[i+(j+1)%3];
+ edge.tri = i/3;
+ edges.push_back(edge);
+ }
+ }
+ std::sort(edges.begin(), edges.end(), NeighborSort);
+ std::vector<int> edge_twin(edges.size(), -1);
+ for(int i=1, len=edges.size(); i<len; ++i){
+ if(edges[i].points[0] == edges[i-1].points[1] && edges[i].points[1] == edges[i-1].points[0]){
+ edge_twin[i] = i-1;
+ edge_twin[i-1] = i;
+ }
+ }
+ for(int i=0, len=edge_twin.size(); i<len; ++i){
+ if(edge_twin[i] != -1 && edge_twin[edge_twin[i]] == i){
+ TriNeighbor neighbor;
+ neighbor.first = edges[i].tri;
+ neighbor.second = edges[edge_twin[i]].tri;
+ info->tri_neighbors.push_back(neighbor);
+ } else {
+ if(display){
+ vec3 points[2];
+ TriangleEdge &edge = edges[i];
+ for(int j=0; j<2; ++j){
+ int vert_index = edge.points[j]*3;
+ for(int i=0; i<3; ++i){
+ points[j][i] = (float)verts[vert_index+i];
+ }
+ }
+ DebugDraw::Instance()->AddLine(points[0], points[1], vec4(1.0f), _persistent, _DD_XRAY);
+ }
+ }
+ }
+ std::sort(info->tri_neighbors.begin(), info->tri_neighbors.end());
+ info->num_tri_neighbors.resize(faces.size()/3, 0);
+ info->tri_neighbor_index.resize(faces.size()/3, -1);
+ for(int i=0, len=info->tri_neighbors.size(); i<len; ++i){
+ int tri = info->tri_neighbors[i].first;
+ if(info->tri_neighbor_index[tri] == -1){
+ info->tri_neighbor_index[tri] = i;
+ }
+ ++info->num_tri_neighbors[tri];
+ }
+ }
+
+ struct vec3d {
+ double entries[3];
+ vec3d(double val){
+ for(int i=0; i<3; ++i){
+ entries[i] = val;
+ }
+ }
+ vec3d(){
+ for(int i=0; i<3; ++i){
+ entries[i] = 0.0;
+ }
+ }
+ double& operator[](int which){
+ return entries[which];
+ }
+ const double& operator[](int which) const {
+ return entries[which];
+ }
+ };
+
+ struct TriIntersect {
+ std::pair<int, int> edge_id[2];
+ vec3d true_intersect[2];
+ bool valid;
+ };
+
+ struct TriIntersectInfo {
+ int tris[2];
+ TriIntersect tri_intersect;
+ };
+
+ bool TriIntersectInfoSortFirstTri(const TriIntersectInfo& a, const TriIntersectInfo& b) {
+ return a.tris[0] < b.tris[0];
+ }
+
+ bool TriIntersectInfoSortSecondTri(const TriIntersectInfo& a, const TriIntersectInfo& b) {
+ return a.tris[1] < b.tris[1];
+ }
+
+ vec3d operator-(const vec3d &a, const vec3d &b){
+ vec3d result;
+ for(int i=0; i<3; ++i){
+ result[i] = a[i] - b[i];
+ }
+ return result;
+ }
+
+ vec3d operator+(const vec3d &a, const vec3d &b){
+ vec3d result;
+ for(int i=0; i<3; ++i){
+ result[i] = a[i] + b[i];
+ }
+ return result;
+ }
+
+ vec3d operator*(const vec3d &a, double b){
+ vec3d result;
+ for(int i=0; i<3; ++i){
+ result[i] = a[i] * b;
+ }
+ return result;
+ }
+
+ vec3d cross(const vec3d &vec_a, const vec3d &vec_b) {
+ vec3d result;
+ result[0] = vec_a[1] * vec_b[2] - vec_a[2] * vec_b[1];
+ result[1] = vec_a[2] * vec_b[0] - vec_a[0] * vec_b[2];
+ result[2] = vec_a[0] * vec_b[1] - vec_a[1] * vec_b[0];
+ return result;
+ }
+
+ double length_squared(const vec3d &vec) {
+ return vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2];
+ }
+
+ vec3d normalize(const vec3d &vec) {
+ double length_squared_val = length_squared(vec);
+ if(length_squared_val == 0.0){
+ return vec3d(0.0);
+ }
+ return vec*(1.0/sqrt(length_squared_val));
+ }
+
+ double dot(const vec3d &vec_a, const vec3d &vec_b) {
+ return vec_a[0]*vec_b[0] + vec_a[1]*vec_b[1] + vec_a[2]*vec_b[2];
+ }
+
+ void GetTriIntersection(const vec3d tri[][3], TriIntersect &tri_intersect){
+ double tri_intersect_t[2][2];
+ int tri_intersect_outlier[2];
+ // Get normal for each triangle
+ vec3d norm[2];
+ for(int i=0; i<2; ++i){
+ norm[i] = normalize(cross(tri[i][1] - tri[i][0], tri[i][2] - tri[i][0]));
+ }
+ // Find position of each triangle along its normal
+ double plane_d[2];
+ for(int i=0; i<2; ++i){
+ plane_d[i] = dot(tri[i][0], norm[i]);
+ }
+ // Get position of each triangle vertex relative to the plane of the other triangle
+ double other_plane_d[2][3];
+ for(int i=0; i<2; ++i){
+ for(int j=0; j<3; ++j){
+ other_plane_d[i][j] = dot(tri[i][j], norm[1-i]) - plane_d[1-i];
+ }
+ }
+ // Get intersection points of each triangle with the other triangle's plane
+ for(int i=0; i<2; ++i){
+ // Get number of vertices on each side of other triangle's plane
+ int num_pos = 0;
+ int num_neg = 0;
+ for(int j=0; j<3; ++j){
+ if(other_plane_d[i][j] > 0.0){
+ ++num_pos;
+ } else {
+ ++num_neg;
+ }
+ }
+ if(num_pos == 3 || num_neg == 3){
+ // All vertices of triangle are on the same side of the other triangle
+ // No intersection!
+ tri_intersect.valid = false;
+ return;
+ }
+ // Get the vertex that is on the opposite side of the plane from the other vertices
+ int outlier = -1;
+ tri_intersect_outlier[i] = -1;
+ if(num_pos == 1){
+ for(int j=0; j<3; ++j){
+ if(other_plane_d[i][j] > 0.0){
+ outlier = j;
+ }
+ }
+ } else {
+ for(int j=0; j<3; ++j){
+ if(other_plane_d[i][j] <= 0.0){
+ outlier = j;
+ }
+ }
+ }
+ // Get intersection points
+ double t[2];
+ t[0] = other_plane_d[i][outlier] / (other_plane_d[i][outlier] - other_plane_d[i][(outlier+1)%3]);
+ t[1] = other_plane_d[i][outlier] / (other_plane_d[i][outlier] - other_plane_d[i][(outlier+2)%3]);
+ tri_intersect_outlier[i] = outlier;
+ tri_intersect_t[i][0] = t[0];
+ tri_intersect_t[i][1] = t[1];
+ }
+ vec3d intersect[2][2];
+ for(int i=0; i<2; ++i){
+ intersect[i][0] = mix(tri[i][tri_intersect_outlier[i]], tri[i][(tri_intersect_outlier[i]+1)%3], tri_intersect_t[i][0]);
+ intersect[i][1] = mix(tri[i][tri_intersect_outlier[i]], tri[i][(tri_intersect_outlier[i]+2)%3], tri_intersect_t[i][1]);
+ }
+ // Find the intersection of the two segments
+ vec3d intersect_dir = normalize(intersect[0][1] - intersect[0][0]);
+ int t_edge[2][2]; // Is this edge outlier+1 or outlier+2?
+ double t[2][2];
+ for(int i=0; i<2; ++i){
+ for(int j=0; j<2; ++j){
+ t[i][j] = dot(intersect[i][j] - intersect[0][0], intersect_dir);
+ t_edge[i][j] = j;
+ }
+ if(t[i][0] > t[i][1]){
+ std::swap(t[i][0], t[i][1]);
+ std::swap(t_edge[i][0], t_edge[i][1]);
+ }
+ }
+ if(t[0][1] < t[1][0] || t[1][1] < t[0][0]){
+ // The intersection segments do not overlap
+ // Therefore the triangles do not actually intersect
+ tri_intersect.valid = false;
+ return;
+ }
+ double intersect_bounds[2];
+ if(t[0][0] > t[1][0]){
+ tri_intersect.edge_id[0].first = 0;
+ tri_intersect.edge_id[0].second = t_edge[0][0];
+ intersect_bounds[0] = t[0][0];
+ } else {
+ tri_intersect.edge_id[0].first = 1;
+ tri_intersect.edge_id[0].second = t_edge[1][0];
+ intersect_bounds[0] = t[1][0];
+ }
+ if(t[0][1] < t[1][1]){
+ tri_intersect.edge_id[1].first = 0;
+ tri_intersect.edge_id[1].second = t_edge[0][1];
+ intersect_bounds[1] = t[0][1];
+ } else {
+ tri_intersect.edge_id[1].first = 1;
+ tri_intersect.edge_id[1].second = t_edge[1][1];
+ intersect_bounds[1] = t[1][1];
+ }
+ tri_intersect.true_intersect[0] = intersect_dir * (float)intersect_bounds[0] + intersect[0][0];
+ tri_intersect.true_intersect[1] = intersect_dir * (float)intersect_bounds[1] + intersect[0][0];
+ tri_intersect.valid = true;
+ }
+
+ typedef std::pair<double, int> EdgeAngleSort;
+
+ /*
+ bool operator<(const EdgeAngleSort &a, const EdgeAngleSort &b) {
+ return a.first < b.first;
+ }
+ */
+
+
+ void TriangulateWrapper(std::vector<float> &points_in, std::vector<int> &edges, std::vector<GLuint> &faces_out) {
+ struct triangulateio in;
+ struct triangulateio out;
+
+ in.numberofpoints = (int)points_in.size()/2;
+ in.numberofpointattributes = 1;
+ in.pointmarkerlist = 0;
+ std::vector<tREAL> in_points(in.numberofpoints*2);
+ for(int i=0, len=points_in.size(); i<len; ++i){
+ in_points[i] = points_in[i];
+ }
+ in.pointlist = &in_points.front();
+ std::vector<tREAL> in_point_attributes(in.numberofpoints);
+ for (int i=0; i<in.numberofpoints; i++){
+ in_point_attributes[i] = i;
+ }
+ in.pointattributelist = &in_point_attributes.front();
+
+ std::vector<int> new_edges = edges;
+ in.numberofsegments = new_edges.size()/2;
+ in.segmentmarkerlist = NULL;
+ if(!new_edges.empty()){
+ in.segmentlist = &new_edges.front();
+ } else {
+ in.segmentlist = NULL;
+ }
+
+ in.numberofholes = 0;
+ in.holelist = NULL;
+ in.numberofregions = 0;
+ in.regionlist = NULL;
+
+ out.pointlist = NULL;
+ out.pointattributelist = NULL;
+ out.trianglelist = NULL;
+ out.segmentlist = NULL;
+ out.segmentmarkerlist = NULL;
+ out.pointmarkerlist = NULL;
+
+ triangulate("zQ", &in, &out, 0);
+
+ faces_out.resize(out.numberoftriangles*3);
+
+ int face_index = 0;
+ for (int i = 0; i<out.numberoftriangles; i++){
+ faces_out[face_index++]=(GLuint)(out.pointattributelist[out.trianglelist[i*3+2]]);
+ faces_out[face_index++]=(GLuint)(out.pointattributelist[out.trianglelist[i*3+1]]);
+ faces_out[face_index++]=(GLuint)(out.pointattributelist[out.trianglelist[i*3+0]]);
+ }
+
+ OG_FREE(out.pointlist);
+ OG_FREE(out.trianglelist);
+ OG_FREE(out.segmentlist);
+ OG_FREE(out.segmentmarkerlist);
+ OG_FREE(out.pointmarkerlist);
+ OG_FREE(out.pointattributelist);
+ }
+
+ struct vec2d {
+ double entries[2];
+ double& operator[](int which){
+ return entries[which];
+ }
+ const double& operator[](int which) const{
+ return entries[which];
+ }
+ };
+
+ vec2d operator-(const vec2d &a, const vec2d &b){
+ vec2d result;
+ for(int i=0; i<2; ++i){
+ result[i] = a[i] - b[i];
+ }
+ return result;
+ }
+
+ vec2d operator+(const vec2d &a, const vec2d &b){
+ vec2d result;
+ for(int i=0; i<2; ++i){
+ result[i] = a[i] + b[i];
+ }
+ return result;
+ }
+
+ vec2d operator*(const vec2d &a, double b){
+ vec2d result;
+ for(int i=0; i<2; ++i){
+ result[i] = a[i] * b;
+ }
+ return result;
+ }
+
+ double length_squared(const vec2d &a){
+ return a[0]*a[0] + a[1]*a[1];
+ }
+
+ double distance_squared(const vec2d &a, const vec2d &b){
+ vec2d diff = b-a;
+ return length_squared(diff);
+ }
+
+ struct TrianglePoint {
+ vec2d flat_coord;
+ vec3d coord;
+ vec3d norm;
+ vec3d bary;
+ int id;
+ bool on_edge[3];
+ enum Side {
+ OUTSIDE, EDGE, INSIDE, UNKNOWN
+ };
+ Side side;
+ };
+
+ bool operator<(const TrianglePoint& a, const TrianglePoint& b){
+ if(a.flat_coord[0] == b.flat_coord[0]){
+ return a.flat_coord[1] < b.flat_coord[1];
+ } else {
+ return a.flat_coord[0] < b.flat_coord[0];
+ }
+ }
+
+ struct TriIntersectionShape {
+ ShapeDisposalData sdd;
+ std::vector<double> vertices;
+ std::vector<float> vertices_float;
+ std::vector<int> faces;
+ btGImpactMeshShape *gimpact_shape;
+ btCollisionObject col_obj;
+ };
+
+ struct VertSorter {
+ double coord[3];
+ int old_id;
+ int merge_target;
+ };
+
+ bool VertSortByCoord(const VertSorter &a, const VertSorter &b){
+ if(a.coord[0] == b.coord[0]){
+ if(a.coord[1] == b.coord[1]){
+ return a.coord[2] < b.coord[2];
+ }
+ return a.coord[1] < b.coord[1];
+ }
+ return a.coord[0] < b.coord[0];
+ }
+
+ bool VertSortById(const VertSorter &a, const VertSorter &b){
+ return a.old_id < b.old_id;
+ }
+
+ bool vec3d_equal(const double *a, const double *b){
+ return (a[0] == b[0] && a[1] == b[1] && a[2] == b[2]);
+ }
+
+ void MergeDoubleVerts(const std::vector<double>& vertices, std::vector<int>& faces, bool remove_degenerate_faces){
+ // Merge overlapping vertices
+ // -- sort vertices by coordinate
+ std::vector<VertSorter> vert_sort;
+ vert_sort.resize(vertices.size()/3);
+ for(int i=0, vert=0, len=vertices.size(); i<len; i+=3, ++vert){
+ for(int j=0; j<3; ++j){
+ vert_sort[vert].coord[j] = vertices[i+j];
+ }
+ vert_sort[vert].old_id = vert;
+ vert_sort[vert].merge_target = vert;
+ }
+ std::sort(vert_sort.begin(), vert_sort.end(), VertSortByCoord);
+ // -- get merge target for each vert
+ for(int i=1, len=vert_sort.size(); i<len; ++i){
+ if(vec3d_equal(vert_sort[i-1].coord, vert_sort[i].coord)){
+ vert_sort[i].merge_target = vert_sort[i-1].merge_target;
+ }
+ }
+ std::sort(vert_sort.begin(), vert_sort.end(), VertSortById);
+ // -- set faces to use merge targets
+ for(int i=0, len=faces.size(); i<len; ++i){
+ faces[i] = vert_sort[faces[i]].merge_target;
+ }
+ if(remove_degenerate_faces){
+ // -- remove degenerate faces
+ for(int i=faces.size()-3; i>=0; i-=3){
+ bool degenerate = false;
+ for(int j=0; j<3; ++j){
+ if(faces[i+j] == faces[i+(j+1)%3]){
+ degenerate = true;
+ }
+ }
+ if(degenerate){
+ for(int j=0; j<3; ++j){
+ faces[i+j] = faces[faces.size()-3+j];
+ }
+ faces.resize(faces.size()-3);
+ }
+ }
+ }
+ }
+
+ void FillTriIntersectionShape(const Model& model, const mat4& transform, TriIntersectionShape* shape){
+ // Transform vertices
+ for(int i=0, len=model.vertices.size(); i<len; i += 3){
+ double vec[3];
+ vec[0] = (double)model.vertices[i+0];
+ vec[1] = (double)model.vertices[i+1];
+ vec[2] = (double)model.vertices[i+2];
+ MultiplyMat4Vec3D(transform, vec);
+ shape->vertices.push_back(vec[0]);
+ shape->vertices.push_back(vec[1]);
+ shape->vertices.push_back(vec[2]);
+ }
+ shape->faces.reserve(model.faces.size());
+ for(int i=0, len=model.faces.size(); i<len; ++i){
+ shape->faces.push_back(model.faces[i]);
+ }
+ // Create Bullet collision shape
+ shape->vertices_float.reserve(shape->vertices.size());
+ for(int i=0, len=shape->vertices.size(); i<len; ++i){
+ shape->vertices_float.push_back((float)shape->vertices[i]);
+ }
+ shape->gimpact_shape = BulletWorld::CreateDynamicMeshShape(shape->faces, shape->vertices_float, shape->sdd);
+ shape->col_obj.setCollisionShape((btCollisionShape*)shape->gimpact_shape);
+ }
+
+ struct LabeledEdge {
+ int points[3];
+ TrianglePoint::Side side;
+ vec3d norm;
+ int id;
+ int tri;
+ };
+
+ bool operator<(const LabeledEdge& a, const LabeledEdge& b){
+ if(a.points[0] == b.points[0]){
+ return a.points[1] < b.points[1];
+ } else {
+ return a.points[0] < b.points[0];
+ }
+ }
+
+ struct TriIntersectOutput {
+ std::vector<int> *faces_to_remove;
+ std::vector<double> *new_bary;
+ std::vector<double> *new_vertices;
+ std::vector<int> *new_vert_tri_points;
+ std::vector<int> *new_faces;
+ std::vector<TrianglePoint::Side> *new_triangle_sides;
+ };
+
+ void GetNewTris(int which, std::vector<TriIntersectInfo> &tri_intersect_info, TriIntersectionShape shape[], TriIntersectOutput &output) {
+ // sort triangle intersections based on which object we are dealing with
+ if(which == 0){
+ std::sort(tri_intersect_info.begin(), tri_intersect_info.end(), TriIntersectInfoSortFirstTri);
+ } else {
+ std::sort(tri_intersect_info.begin(), tri_intersect_info.end(), TriIntersectInfoSortSecondTri);
+ }
+ // Handle one triangle at a time
+ int old_index = 0;
+ for(int new_index=1, len=tri_intersect_info.size(); new_index<=len; ++new_index){
+ if(new_index==len || tri_intersect_info[new_index].tris[which] != tri_intersect_info[old_index].tris[which]){
+ std::vector<LabeledEdge> labeled_edges;
+ // Look at tri_intersect_info old_index - i to get intersecting edges
+ int tri_index = tri_intersect_info[old_index].tris[which]*3;
+ output.faces_to_remove->push_back(tri_intersect_info[old_index].tris[which]);
+ vec3d tri_vert[3];
+ int tri_vert_id[3];
+ for(int j=0; j<3; ++j){
+ tri_vert_id[j] = shape[1-which].faces[tri_index+j];
+ int vert_index = tri_vert_id[j]*3;
+ for(int k=0; k<3; ++k){
+ tri_vert[j][k] = shape[1-which].vertices[vert_index+k];
+ }
+ }
+ // Create orthonormal basis on plane of triangle
+ vec3d norm = normalize(cross(tri_vert[1]-tri_vert[0], tri_vert[2]-tri_vert[0]));
+ vec3d basis[2];
+ basis[0] = normalize(tri_vert[1] - tri_vert[0]);
+ basis[1] = tri_vert[2] - tri_vert[0];
+ basis[1] = normalize(basis[1] - basis[0] * dot(basis[0], basis[1]));
+ // Add initial points to set
+ std::vector<TrianglePoint> triangle_points;
+ std::vector<int> on_edge;
+ for(int j=0; j<3; ++j){
+ on_edge.push_back(triangle_points.size());
+ TrianglePoint point;
+ point.bary = vec3d(0.0);
+ point.bary[j] = 1.0;
+ point.coord = tri_vert[j];
+ point.flat_coord[0] = dot(basis[0], tri_vert[j]);
+ point.flat_coord[1] = dot(basis[1], tri_vert[j]);
+ point.id = triangle_points.size();
+ point.on_edge[j] = true;
+ point.on_edge[(j+2)%3] = true;
+ point.on_edge[(j+1)%3] = false;
+ point.side = TrianglePoint::UNKNOWN;
+ triangle_points.push_back(point);
+ }
+ std::vector<int> edges;
+ // Loop through triangle intersections
+ for(int j=old_index; j<new_index; ++j){
+ TriIntersect &tri_intersect = tri_intersect_info[j].tri_intersect;
+ // Add triangle point for each intersection vertex
+ for(int k=0; k<2; ++k){
+ TrianglePoint point;
+ point.coord = tri_intersect.true_intersect[k];
+ point.flat_coord[0] = dot(basis[0], tri_intersect.true_intersect[k]);
+ point.flat_coord[1] = dot(basis[1], tri_intersect.true_intersect[k]);
+ double vec_a[2], vec_b[2];
+ for(int l=0; l<2; ++l){
+ vec_a[l] = triangle_points[1].flat_coord[l] - triangle_points[0].flat_coord[l];
+ vec_b[l] = triangle_points[2].flat_coord[l] - triangle_points[0].flat_coord[l];
+ }
+ double twice_area = vec_a[0] * vec_b[1] - vec_a[1] * vec_b[0];
+ point.bary[0] = orient2d_double(triangle_points[1].flat_coord.entries, triangle_points[2].flat_coord.entries, point.flat_coord.entries);
+ point.bary[1] = orient2d_double(triangle_points[2].flat_coord.entries, triangle_points[0].flat_coord.entries, point.flat_coord.entries);
+ point.bary[2] = orient2d_double(triangle_points[0].flat_coord.entries, triangle_points[1].flat_coord.entries, point.flat_coord.entries);
+ point.bary[0] /= twice_area;
+ point.bary[1] /= twice_area;
+ point.bary[2] /= twice_area;
+ point.id = triangle_points.size();
+ point.on_edge[0] = false;
+ point.on_edge[1] = false;
+ point.on_edge[2] = false;
+ point.side = TrianglePoint::EDGE;
+ if(tri_intersect.edge_id[k].first == which){
+ on_edge.push_back(triangle_points.size());
+ }
+ triangle_points.push_back(point);
+ }
+ // Add edge for triangulation
+ edges.push_back(triangle_points.size()-2);
+ edges.push_back(triangle_points.size()-1);
+ // Add labeled_edge for processing later
+ LabeledEdge labeled_edge;
+ labeled_edge.points[0] = triangle_points.size()-2;
+ labeled_edge.points[1] = triangle_points.size()-1;
+ labeled_edge.points[2] = -1;
+ if(labeled_edge.points[0] > labeled_edge.points[1]){
+ std::swap(labeled_edge.points[0], labeled_edge.points[1]);
+ }
+ labeled_edge.side = TrianglePoint::EDGE;
+ // Get vertices of intersected triangle
+ vec3d other_tri_vert[3];
+ int other_tri_index = tri_intersect_info[j].tris[1-which]*3;
+ for(int j=0; j<3; ++j){
+ int vert_index = shape[which].faces[other_tri_index+j]*3;
+ for(int k=0; k<3; ++k){
+ other_tri_vert[j][k] = shape[which].vertices[vert_index+k];
+ }
+ }
+ // Label the edge with the normal of the other triangle
+ labeled_edge.norm = normalize(cross(other_tri_vert[1] - other_tri_vert[0], other_tri_vert[2] - other_tri_vert[0]));
+ labeled_edges.push_back(labeled_edge);
+ }
+ // Calculate the angle from the center of the tri to each edge tri
+ std::vector<EdgeAngleSort> edge_angle;
+ edge_angle.reserve(on_edge.size());
+ vec2d mid_point;
+ mid_point = (triangle_points[0].flat_coord + triangle_points[1].flat_coord + triangle_points[2].flat_coord) * (1.0 / 3.0);
+ for(int j=0, len=on_edge.size(); j<len; ++j){
+ EdgeAngleSort eas;
+ eas.second = on_edge[j];
+ vec2d offset = triangle_points[on_edge[j]].flat_coord - mid_point;
+ eas.first = atan2(offset[1], offset[0]);
+ edge_angle.push_back(eas);
+ }
+ // Add edges sorted by angle
+ std::sort(edge_angle.begin(), edge_angle.end());
+ for(int j=0, len=edge_angle.size(); j<len; ++j){
+ edges.push_back(edge_angle[j].second);
+ edges.push_back(edge_angle[(j+1)%len].second);
+ }
+ // Get positions of each triangle corner on edge loop
+ int edge_pos[3];
+ for(int j=0, len=edge_angle.size(); j<len; ++j){
+ for(int k=0; k<3; ++k){
+ if(edge_angle[j].second == k){
+ edge_pos[k] = j;
+ }
+ }
+ }
+ // Mark which edge each non-corner edge vertex is on
+ for(int k=0; k<3; ++k){
+ for(int j=edge_pos[k]+1;; ++j){
+ if(j >= (int)edge_angle.size()){
+ j = 0;
+ }
+ if(j == edge_pos[(k+1)%3]){
+ break;
+ }
+ triangle_points[edge_angle[j].second].on_edge[k] = true;
+ }
+ }
+ // Remove duplicate points
+ std::vector<int> triangle_point_remap(triangle_points.size(), 0);
+ std::sort(triangle_points.begin(), triangle_points.end());
+ if(!triangle_points.empty()){
+ triangle_point_remap[triangle_points[0].id] = 0;
+ int index=0;
+ for(int i=1, len=triangle_points.size(); i<len; ++i){
+ if(distance_squared(triangle_points[i].flat_coord, triangle_points[i-1].flat_coord) > 0.0000001){
+ ++index;
+ triangle_point_remap[triangle_points[i].id] = index;
+ triangle_points[index] = triangle_points[i];
+ } else {
+ triangle_point_remap[triangle_points[i].id] = index;
+ }
+ }
+ triangle_points.resize(index+1);
+ }
+ // Remap edges to new points
+ for(int i=0, len=edges.size(); i<len; ++i){
+ edges[i] = triangle_point_remap[edges[i]];
+ }
+ for(int i=0, len=labeled_edges.size(); i<len; ++i){
+ labeled_edges[i].points[0] = triangle_point_remap[labeled_edges[i].points[0]];
+ labeled_edges[i].points[1] = triangle_point_remap[labeled_edges[i].points[1]];
+ if(labeled_edges[i].points[0] > labeled_edges[i].points[1]){
+ std::swap(labeled_edges[i].points[0], labeled_edges[i].points[1]);
+ }
+ }
+ // Get points to input to triangulation
+ std::vector<float> points;
+ for(int i=0, len=triangle_points.size(); i<len; ++i){
+ for(int j=0; j<2; ++j){
+ points.push_back((float)triangle_points[i].flat_coord[j]);
+ }
+ }
+ // Triangulate
+ std::vector<GLuint> faces;
+ TriangulateWrapper(points, edges, faces);
+ // Add new vertices
+ int vert_start = output.new_bary->size()/3;
+ for(int j=0, len=triangle_points.size(); j<len; ++j){
+ for(int k=0; k<3; ++k){
+ output.new_vertices->push_back(triangle_points[j].coord[k]);
+ output.new_bary->push_back(triangle_points[j].bary[k]);
+ output.new_vert_tri_points->push_back(tri_vert_id[k]);
+ }
+ }
+ // Remove triangles that are all on same edge
+ int face_index = 0;
+ for(int j=0, len=faces.size(); j<len; j+=3){
+ bool includes_bad_vert = false;
+ for(int k=0; k<3; ++k){
+ if(triangle_points[faces[j+0]].on_edge[k] &&
+ triangle_points[faces[j+1]].on_edge[k] &&
+ triangle_points[faces[j+2]].on_edge[k])
+ {
+ includes_bad_vert = true;
+ }
+ }
+ if(!includes_bad_vert){
+ for(int k=0; k<3; ++k){
+ faces[face_index+k] = faces[j+k];
+ }
+ face_index += 3;
+ }
+ }
+ faces.resize(face_index);
+ // Make sure faces all face the proper direction
+ for(int j=0, len=faces.size(); j<len; j+=3){
+ vec3d vert[3];
+ for(int k=0; k<3; ++k){
+ int vert_index = faces[j+k];
+ vert[k] = triangle_points[vert_index].coord;
+ }
+ vec3d new_norm = normalize(cross(vert[1]-vert[0], vert[2]-vert[0]));
+ if(dot(new_norm, norm) < 0.0f){
+ std::swap(faces[j+1], faces[j+2]);
+ }
+ }
+ // Create unknown edge for each triangle side
+ for(int i=0, len=faces.size(); i<len; i+=3){
+ for(int j=0; j<3; ++j){
+ LabeledEdge labeled_edge;
+ labeled_edge.tri = i/3;
+ labeled_edge.points[0] = faces[i+j];
+ labeled_edge.points[1] = faces[i+(j+1)%3];
+ labeled_edge.points[2] = faces[i+(j+2)%3];
+ if(labeled_edge.points[0] > labeled_edge.points[1]){
+ std::swap(labeled_edge.points[0], labeled_edge.points[1]);
+ }
+ labeled_edge.id = labeled_edges.size();
+ labeled_edge.side = TrianglePoint::UNKNOWN;
+ labeled_edges.push_back(labeled_edge);
+ }
+ }
+ std::vector<TriNeighbor> tri_neighbors;
+ // Propagate edge info
+ {
+ std::vector<LabeledEdge> sorted = labeled_edges;
+ std::sort(sorted.begin(), sorted.end());
+ int old_index = 0;
+ for(int i=1, len=sorted.size(); i<=len; ++i){
+ if(i==len || sorted[i].points[0] != sorted[old_index].points[0] || sorted[i].points[1] != sorted[old_index].points[1]){
+ bool edge = false;
+ vec3d norm;
+ for(int j=old_index; j<i; ++j){
+ if(sorted[j].side == TrianglePoint::EDGE){
+ edge = true;
+ norm = sorted[j].norm;
+ }
+ }
+ if(edge){
+ for(int j=old_index; j<i; ++j){
+ if(sorted[j].side != TrianglePoint::EDGE && sorted[j].points[2] != -1){
+ int index = sorted[j].id;
+ labeled_edges[index].side = TrianglePoint::EDGE;
+ labeled_edges[index].norm = norm;
+ }
+ }
+ } else { // if edge is not an intersection edge, mark tri neighbors
+ for(int j=old_index; j<i; ++j){
+ if(sorted[j].points[2] == -1){
+ continue;
+ }
+ for(int k=old_index; k<i; ++k){
+ if(j == k || sorted[k].points[2] == -1){
+ continue;
+ }
+ tri_neighbors.push_back(TriNeighbor(sorted[j].tri, sorted[k].tri));
+ }
+ }
+ }
+ old_index = i;
+ }
+ }
+ }
+ std::vector<TrianglePoint::Side> triangle_side(faces.size()/3, TrianglePoint::UNKNOWN);
+ // Get outside/inside of edges neighboring edge
+ for(int i=0, len=labeled_edges.size(); i<len; ++i){
+ LabeledEdge &edge = labeled_edges[i];
+ if(edge.side == TrianglePoint::EDGE && edge.points[2] != -1){
+ vec3d opposite_vert = triangle_points[edge.points[2]].coord;
+ vec3d mid_point = (triangle_points[edge.points[0]].coord + triangle_points[edge.points[1]].coord)*0.5f;
+ vec3d dir = opposite_vert - mid_point;
+ if(dot(dir, edge.norm) > 0.0f){
+ triangle_side[edge.tri] = TrianglePoint::OUTSIDE;
+ } else {
+ triangle_side[edge.tri] = TrianglePoint::INSIDE;
+ }
+ continue;
+ }
+ }
+ // Propagate triangle side
+ std::sort(tri_neighbors.begin(), tri_neighbors.end());
+ std::vector<int> num_tri_neighbors(triangle_side.size(), 0);
+ std::vector<int> tri_neighbor_index(triangle_side.size(), -1);
+ for(int i=0, len=tri_neighbors.size(); i<len; ++i){
+ if(tri_neighbor_index[tri_neighbors[i].first] == -1){
+ tri_neighbor_index[tri_neighbors[i].first] = i;
+ }
+ ++num_tri_neighbors[tri_neighbors[i].first];
+ }
+ std::queue<int> tri_queue;
+ for(int i=0, len=triangle_side.size(); i<len; ++i){
+ if(triangle_side[i] != TrianglePoint::UNKNOWN){
+ tri_queue.push(i);
+ }
+ }
+ while(!tri_queue.empty()){
+ int tri = tri_queue.front();
+ for(int i=0; i<num_tri_neighbors[tri]; ++i){
+ int neighbor = tri_neighbors[tri_neighbor_index[tri]+i].second;
+ if(triangle_side[neighbor] == TrianglePoint::UNKNOWN){
+ triangle_side[neighbor] = triangle_side[tri];
+ tri_queue.push(neighbor);
+ }
+ }
+ tri_queue.pop();
+ }
+ for(int j=0, len=faces.size(); j<len; ++j){
+ output.new_faces->push_back(faces[j]+vert_start);
+ }
+ for(int j=0, len=triangle_side.size(); j<len; ++j){
+ output.new_triangle_sides->push_back(triangle_side[j]);
+ }
+ old_index = new_index;
+ }
+ }
+ }
+
+
+ struct MergeDist {
+ double dist;
+ int point[2];
+ };
+
+ bool operator<(const MergeDist &a, const MergeDist &b){
+ if(a.point[0] == b.point[0]){
+ return a.point[1] < b.point[1];
+ } else {
+ return a.point[0] < b.point[0];
+ }
+ }
+
+ void MergeTriIntersects(std::vector<TriIntersectInfo> &tri_intersect_info) {
+ std::vector<vec3d> test_points;
+ std::vector<MergeDist> test_point_distances;
+ for(int i=0, len=tri_intersect_info.size(); i<len; ++i){
+ TriIntersectInfo& tii = tri_intersect_info[i];
+ test_points.push_back(tii.tri_intersect.true_intersect[0]);
+ test_points.push_back(tii.tri_intersect.true_intersect[1]);
+ }
+ double merge_squared_threshold = 1.0e-10;
+ //double merge_threshold = 1.0e-5;
+ for(int i=0, len=test_points.size(); i<len; ++i){
+ for(int j=i+1, len=test_points.size(); j<len; ++j){
+ vec3d rel = test_points[i] - test_points[j];
+ MergeDist md;
+ md.dist = (dot(rel, rel));
+ md.point[0] = i;
+ md.point[1] = j;
+ if(md.point[0] > md.point[1]){
+ std::swap(md.point[0], md.point[1]);
+ }
+ if(md.dist < merge_squared_threshold){
+ test_point_distances.push_back(md);
+ }
+ }
+ }
+ std::sort(test_point_distances.begin(), test_point_distances.end());
+ for(int i=0, len=test_point_distances.size(); i<len; ++i){
+ MergeDist &tpd = test_point_distances[i];
+ test_points[tpd.point[1]] = test_points[tpd.point[0]];
+ }
+ for(int i=0, index=0, len=tri_intersect_info.size(); i<len; ++i, index+=2){
+ TriIntersectInfo& tii = tri_intersect_info[i];
+ tii.tri_intersect.true_intersect[0] = test_points[index+0];
+ tii.tri_intersect.true_intersect[1] = test_points[index+1];
+ }
+ }
+} // namespace ""
+
+bool CheckShapeValid(const std::vector<int> &faces, const std::vector<double> &vertices) {
+ std::vector<int> merged_faces = faces;
+ MergeDoubleVerts(vertices, merged_faces, true);
+ TriNeighborInfo tri_neighbor_info;
+ GetTriNeighbors(merged_faces, vertices, &tri_neighbor_info, true);
+ bool non_three_neighbor = false;
+ for(int i=0, len=tri_neighbor_info.num_tri_neighbors.size(); i<len; ++i){
+ if(tri_neighbor_info.num_tri_neighbors[i] != 3){
+ non_three_neighbor = true;
+ }
+ }
+ return !non_three_neighbor;
+}
+
+void AddCSGResult(const CSGResult &result, CSGModelInfo *csg_model, const Model& model, bool flip) {
+ csg_model->faces.reserve(csg_model->faces.size()+result.indices.size());
+ int old_faces = csg_model->verts.size()/3;
+ if(!flip){
+ for(int i=0, len=result.indices.size(); i<len; ++i){
+ csg_model->faces.push_back(result.indices[i] + old_faces);
+ }
+ } else {
+ for(int i=0, len=result.indices.size(); i<len; i+=3){
+ csg_model->faces.push_back(result.indices[i+0] + old_faces);
+ csg_model->faces.push_back(result.indices[i+2] + old_faces);
+ csg_model->faces.push_back(result.indices[i+1] + old_faces);
+ }
+ }
+ csg_model->verts.reserve(csg_model->verts.size()+result.verts.size());
+ csg_model->tex_coords.reserve(csg_model->verts.size()+result.verts.size()/3*2);
+ csg_model->tex_coords2.reserve(csg_model->verts.size()+result.verts.size()/3*2);
+ for(int i=0, len=result.bary.size(); i<len; i+=3){
+ const double *bary = &result.bary[i];
+ const double *result_vert = &result.verts[i];
+ const int *vert_id = &result.vert_id[i];
+ vec3d vert;
+ for(int j=0; j<3; ++j){
+ vert[j] = result_vert[j];
+ }
+ //MultiplyMat4Vec3D(eo->GetTransform(), &vert[0]);
+ csg_model->verts.push_back(vert[0]);
+ csg_model->verts.push_back(vert[1]);
+ csg_model->verts.push_back(vert[2]);
+
+ vec2 tex_coord;
+ for(int j=0; j<3; ++j){
+ vec2 tri_tex_coord;
+ int vert_index = vert_id[j]*2;
+ for(int k=0; k<2; ++k){
+ tri_tex_coord[k] = model.tex_coords[vert_index+k];
+ }
+ tex_coord += tri_tex_coord * (float)bary[j];
+ }
+ csg_model->tex_coords.push_back(tex_coord[0]);
+ csg_model->tex_coords.push_back(tex_coord[1]);
+
+ for(int j=0; j<3; ++j){
+ vec2 tri_tex_coord;
+ int vert_index = vert_id[j]*2;
+ for(int k=0; k<2; ++k){
+ tri_tex_coord[k] = model.tex_coords2[vert_index+k];
+ }
+ tex_coord += tri_tex_coord * (float)bary[j];
+ }
+ csg_model->tex_coords2.push_back(tex_coord[0]);
+ csg_model->tex_coords2.push_back(tex_coord[1]);
+ };
+}
+
+int ModelFromCSGModelInfo(const CSGModelInfo &model_info){
+ int model_id = Models::Instance()->AddModel();
+ Model *model = &Models::Instance()->GetModel(model_id);
+ model->vertices.resize(model_info.verts.size());
+ for(int i=0, len=model_info.verts.size(); i<len; ++i){
+ model->vertices[i] = (float)model_info.verts[i];
+ }
+ model->tex_coords.resize(model_info.verts.size()/3*2, 0.0f);
+ model->tex_coords2.resize(model_info.verts.size()/3*2, 0.0f);
+ for(int i=0, len=model_info.tex_coords.size(); i<len; ++i){
+ model->tex_coords[i] = model_info.tex_coords[i];
+ model->tex_coords2[i] = model_info.tex_coords2[i];
+ }
+ model->faces.resize(model_info.faces.size(), 0);
+ for(int i=0, len=model_info.faces.size(); i<len; ++i){
+ model->faces[i] = model_info.faces[i];
+ }
+ model->normals.resize(model_info.verts.size());
+ model->tangents.resize(model_info.verts.size());
+ model->bitangents.resize(model_info.verts.size());
+ model->face_normals.resize(model_info.faces.size()/3);
+ model->calcNormals();
+ model->calcTangents();
+ return model_id;
+}
+
+bool CollideObjects(BulletWorld& bw, const Model &model_a, const mat4 &transform_a, const Model &model_b, const mat4 &transform_b, CSGResultCombined *results) {
+ // Get transformed and merged meshes
+ TriIntersectionShape shape[2];
+ FillTriIntersectionShape(model_a, transform_a, &shape[0]);
+ FillTriIntersectionShape(model_b, transform_b, &shape[1]);
+ // Check triangle neighbors
+ if(!CheckShapeValid(shape[1].faces, shape[1].vertices)){
+ LOGE << "Model is invalid" << std::endl;
+ return false;
+ }
+
+ // Use Bullet GIMPACT collision to get candidate triangle pairs
+ MeshCollisionCallback cb;
+ bw.GetPairCollisions(shape[1].col_obj, shape[0].col_obj, cb);
+ // Process triangle intersections to eliminate false positives and get precise intersection segments
+ std::vector<TriIntersectInfo> tri_intersect_info;
+ for(MeshCollisionCallback::TriPairSet::iterator iter = cb.tri_pairs.begin(); iter != cb.tri_pairs.end(); ++iter){
+ // Get triangle vertices
+ vec3d tri[2][3];
+ const MeshCollisionCallback::TriPair &tri_pair = *iter;
+ int tri_index = tri_pair.first*3;
+ for(int j=0; j<3; ++j){
+ int vert_index = shape[1].faces[tri_index+j]*3;
+ for(int k=0; k<3; ++k){
+ tri[0][j][k] = shape[1].vertices[vert_index+k];
+ }
+ }
+ tri_index = tri_pair.second*3;
+ for(int j=0; j<3; ++j){
+ int vert_index = shape[0].faces[tri_index+j]*3;
+ for(int k=0; k<3; ++k){
+ tri[1][j][k] = shape[0].vertices[vert_index+k];
+ }
+ }
+ // Get intersection line
+ TriIntersect tri_intersect;
+ GetTriIntersection(tri, tri_intersect);
+ // Add to list
+ if(tri_intersect.valid){
+ TriIntersectInfo info;
+ info.tri_intersect = tri_intersect;
+ info.tris[0] = tri_pair.first;
+ info.tris[1] = tri_pair.second;
+ tri_intersect_info.push_back(info);
+ }
+ }
+ MergeTriIntersects(tri_intersect_info);
+ std::vector<int> new_faces[2];
+ std::vector<double> new_vertices[2];
+ std::vector<int> faces_to_remove[2];
+ std::vector<double> new_bary[2];
+ std::vector<int> new_vert_tri_points[2];
+ std::vector<TrianglePoint::Side> new_triangle_sides[2];
+ for(int i=0; i<2; ++i){
+ TriIntersectOutput output;
+ output.faces_to_remove = &faces_to_remove[i];
+ output.new_faces = &new_faces[i];
+ output.new_triangle_sides = &new_triangle_sides[i];
+ output.new_bary = &new_bary[i];
+ output.new_vert_tri_points = &new_vert_tri_points[i];
+ output.new_vertices = &new_vertices[i];
+ GetNewTris(i, tri_intersect_info, shape, output);
+ }
+ // Get old faces minus removed ones
+ std::vector<int> old_faces[2];
+ std::vector<double> old_vertices[2];
+ std::vector<double> old_bary[2];
+ std::vector<int> old_vert_id[2];
+ std::vector<TrianglePoint::Side> merge_triangle_sides[2];
+ for(int l=0; l<2; ++l){
+ old_faces[l].resize(shape[l].faces.size());
+ for(int i=0, len=shape[l].faces.size(); i<len; ++i){
+ old_faces[l][i] = shape[l].faces[i];
+ }
+ // Remove old intersecting faces
+ for(int i=faces_to_remove[1-l].size()-1; i>=0; --i){
+ int index = faces_to_remove[1-l][i]*3;
+ int last_index = old_faces[l].size()-3;
+ for(int j=0; j<3; ++j){
+ old_faces[l][index+j] = old_faces[l][last_index+j];
+ }
+ old_faces[l].resize(old_faces[l].size()-3);
+ }
+
+ old_vertices[l] = shape[l].vertices;
+ for(int i=0, len=old_vertices[l].size(); i<len; i+=3){
+ old_vert_id[l].push_back(i/3);
+ old_vert_id[l].push_back(i/3);
+ old_vert_id[l].push_back(i/3);
+ old_bary[l].push_back(1.0);
+ old_bary[l].push_back(0.0);
+ old_bary[l].push_back(0.0);
+ }
+
+ // Add new faces and vertices
+ merge_triangle_sides[l].resize(old_faces[l].size()/3, TrianglePoint::UNKNOWN);
+ for(int i=0, len=new_faces[1-l].size(); i<len; ++i){
+ old_faces[l].push_back(new_faces[1-l][i]+old_vertices[l].size()/3);
+ }
+ for(int i=0, len=new_triangle_sides[1-l].size(); i<len; ++i){
+ merge_triangle_sides[l].push_back(new_triangle_sides[1-l][i]);
+ }
+ old_vertices[l].insert(old_vertices[l].end(), new_vertices[1-l].begin(), new_vertices[1-l].end());
+ old_bary[l].insert(old_bary[l].end(), new_bary[1-l].begin(), new_bary[1-l].end());
+ old_vert_id[l].insert(old_vert_id[l].end(), new_vert_tri_points[1-l].begin(), new_vert_tri_points[1-l].end());
+
+ // Flood fill triangle sides
+ // -- merge overlapping verts
+ std::vector<int> merged_faces = old_faces[l];
+ MergeDoubleVerts(old_vertices[l], merged_faces, false);
+
+ TriNeighborInfo tri_neighbor_info;
+ GetTriNeighbors(merged_faces, shape[l].vertices, &tri_neighbor_info, false);
+
+ std::queue<int> tri_queue;
+ for(int i=0, len=merge_triangle_sides[l].size(); i<len; ++i){
+ if(merge_triangle_sides[l][i] != TrianglePoint::UNKNOWN){
+ tri_queue.push(i);
+ }
+ }
+ while(!tri_queue.empty()){
+ int tri = tri_queue.front();
+ for(int i=0; i<tri_neighbor_info.num_tri_neighbors[tri]; ++i){
+ int neighbor = tri_neighbor_info.tri_neighbors[tri_neighbor_info.tri_neighbor_index[tri]+i].second;
+ if(merge_triangle_sides[l][neighbor] == TrianglePoint::UNKNOWN){
+ merge_triangle_sides[l][neighbor] = merge_triangle_sides[l][tri];
+ tri_queue.push(neighbor);
+ }
+ }
+ tri_queue.pop();
+ }
+ }
+ if(!CheckShapeValid(shape[1].faces, shape[1].vertices)){
+ LOGE << "Model is invalid" << std::endl;
+ return false;
+ }
+ for(int which_model=0; which_model<2; ++which_model){
+ for(int which_side=0; which_side<2; ++which_side){
+ TrianglePoint::Side side;
+ switch(which_side){
+ case 0: side = TrianglePoint::INSIDE; break;
+ case 1: side = TrianglePoint::OUTSIDE; break;
+ default: __builtin_unreachable(); break;
+ }
+
+ const std::vector<TrianglePoint::Side> &sides = merge_triangle_sides[which_model];
+ std::vector<double> vertices;
+ std::vector<int> indices;
+ std::vector<double> bary;
+ std::vector<int> vert_id;
+ for(int i=0, side_index=0, len=old_faces[which_model].size(); i<len; i+=3, ++side_index){
+ if(sides[side_index] == side) {
+ for(int j=0; j<3; ++j){
+ int vert_index = old_faces[which_model][i+j]*3;
+ indices.push_back(vertices.size()/3);
+ for(int j=0; j<3; ++j){
+ vertices.push_back(old_vertices[which_model][vert_index+j]);
+ bary.push_back(old_bary[which_model][vert_index+j]);
+ vert_id.push_back(old_vert_id[which_model][vert_index+j]);
+ }
+ }
+ }
+ }
+ results->result[which_model][which_side].verts = vertices;
+ results->result[which_model][which_side].bary = bary;
+ results->result[which_model][which_side].vert_id = vert_id;
+ results->result[which_model][which_side].indices = indices;
+ }
+ }
+ return true;
+}
+
+void MultiplyMat4Vec3D(const mat4& mat, double vec[]) {
+ double new_vec[3];
+ const float *mat_e = mat.entries;
+ for(int i=0; i<3; ++i){
+ new_vec[i] = mat_e[0+i] * vec[0] + mat_e[4+i] * vec[1] + mat_e[8+i] * vec[2] + mat_e[12+i];
+ }
+ for(int i=0; i<3; ++i){
+ vec[i] = new_vec[i];
+ }
+}
diff --git a/Source/Graphics/csg.h b/Source/Graphics/csg.h
new file mode 100644
index 00000000..177b3e11
--- /dev/null
+++ b/Source/Graphics/csg.h
@@ -0,0 +1,53 @@
+//-----------------------------------------------------------------------------
+// Name: csg.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <vector>
+
+struct CSGResult {
+ std::vector<int> indices;
+ std::vector<int> vert_id;
+ std::vector<double> bary;
+ std::vector<double> verts;
+};
+
+struct CSGResultCombined {
+ CSGResult result[2][2];
+};
+
+struct CSGModelInfo {
+ std::vector<int> faces;
+ std::vector<double> verts;
+ std::vector<float> tex_coords;
+ std::vector<float> tex_coords2;
+};
+
+class BulletWorld;
+class Model;
+class mat4;
+bool CollideObjects(BulletWorld& bw, const Model &model_a, const mat4 &transform_a, const Model &model_b, const mat4 &transform_b, CSGResultCombined *results);
+void AddCSGResult(const CSGResult &result, CSGModelInfo *csg_model, const Model& model, bool flip);
+bool CheckShapeValid(const std::vector<int> &faces, const std::vector<double> &vertices);
+void MultiplyMat4Vec3D(const mat4& mat, double vec[]);
+int ModelFromCSGModelInfo(const CSGModelInfo &model_info);
diff --git a/Source/Graphics/cubemap.cpp b/Source/Graphics/cubemap.cpp
new file mode 100644
index 00000000..72d3f221
--- /dev/null
+++ b/Source/Graphics/cubemap.cpp
@@ -0,0 +1,143 @@
+//-----------------------------------------------------------------------------
+// Name: cubemap.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "cubemap.h"
+
+#include <Graphics/graphics.h>
+#include <Graphics/vbocontainer.h>
+#include <Graphics/vboringcontainer.h>
+#include <Graphics/textures.h>
+#include <Graphics/shaders.h>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <Math/enginemath.h>
+#include <Internal/profiler.h>
+
+mat4 GetCubeMapRotation( int i ) {
+ mat4 rot[2];
+ if (i==0) {
+ rot[0].SetRotationY(PI_f*-0.5f);
+ rot[1].SetRotationZ(PI_f);
+ return rot[0]*rot[1];
+ } else if (i==1){
+ rot[0].SetRotationY(PI_f*0.5f);
+ rot[1].SetRotationZ(PI_f);
+ return rot[0]*rot[1];
+ } else if (i==2){
+ rot[0].SetRotationX(PI_f*0.5f);
+ return rot[0];
+ } else if (i==3){
+ rot[0].SetRotationX(PI_f*-0.5f);
+ return rot[0];
+ } else if (i==4){
+ rot[0].SetRotationY(PI_f);
+ rot[1].SetRotationZ(PI_f);
+ return rot[0]*rot[1];
+ } else if(i==5){
+ rot[0].SetRotationZ(PI_f);
+ return rot[0];
+ }
+ FatalError("Error", "Getting cube map rotation that is not 0-5");
+ return mat4();
+}
+
+GLuint cube_map_fbo = UINT_MAX;
+
+void CubemapMipChain(TextureRef cube_map_tex_ref, Cubemap::HemisphereEnabled hemisphere_enabled, const vec3 *hemisphere_vec) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "CubemapMipChain");
+ int cube_map_tex_id = Textures::Instance()->returnTexture(cube_map_tex_ref);
+ Graphics* graphics = Graphics::Instance();
+ Shaders* shaders = Shaders::Instance();
+ CHECK_GL_ERROR();
+ graphics->PushFramebuffer();
+ CHECK_GL_ERROR();
+ if(cube_map_fbo == UINT_MAX){
+ graphics->genFramebuffers(&cube_map_fbo,"cube_map_fbo");
+ CHECK_GL_ERROR();
+ }
+ graphics->bindFramebuffer(cube_map_fbo);
+ CHECK_GL_ERROR();
+ int tex_width = 128;
+ int level = 0;
+ float angle = PI_f * 0.5f / 32.0f;
+ while(tex_width > 1){
+ ++level;
+ tex_width /= 2;
+ angle *= 2.0f;
+ for(int i=0; i<6; ++i){
+ CHECK_GL_ERROR();
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT+i, cube_map_tex_id, level);
+ CHECK_GL_ERROR();
+ graphics->setViewport(0,0,tex_width,tex_width);
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ graphics->Clear(true);
+ CHECK_GL_ERROR();
+ mat4 rot = GetCubeMapRotation(i);
+ CHECK_GL_ERROR();
+ int shader_id;
+ if(hemisphere_enabled == Cubemap::HEMISPHERE){
+ shader_id = shaders->returnProgram("cubemapblur #HEMISPHERE");
+ shaders->setProgram(shader_id);
+ shaders->SetUniformVec3("hemisphere_dir", *hemisphere_vec);
+ } else {
+ shader_id = shaders->returnProgram("cubemapblur");
+ shaders->setProgram(shader_id);
+ }
+ CHECK_GL_ERROR();
+ shaders->SetUniformMat4("rotate", rot);
+ CHECK_GL_ERROR();
+ shaders->SetUniformFloat("max_angle", angle);
+ CHECK_GL_ERROR();
+ shaders->SetUniformFloat("src_mip", (float)(level-1));
+ CHECK_GL_ERROR();
+ Textures::Instance()->bindTexture(cube_map_tex_ref);
+ CHECK_GL_ERROR();
+ static const GLfloat data[] = {
+ -1, -1,
+ 1, -1,
+ 1, 1,
+ -1, 1
+ };
+ static const GLuint indices[] = {
+ 0, 1, 2, 0, 3, 2
+ };
+ static VBOContainer data_vbo;
+ static VBOContainer index_vbo;
+ if(!data_vbo.valid()){
+ data_vbo.Fill(kVBOFloat | kVBOStatic, sizeof(data), (void*)data);
+ index_vbo.Fill(kVBOElement | kVBOStatic, sizeof(indices), (void*)indices);
+ }
+ data_vbo.Bind();
+ index_vbo.Bind();
+ int vert_attrib_id = shaders->returnShaderAttrib("vertex", shader_id);
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ glVertexAttribPointer(vert_attrib_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0);
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ graphics->ResetVertexAttribArrays();
+ CHECK_GL_ERROR();
+ }
+ }
+ graphics->PopFramebuffer();
+}
diff --git a/Source/Graphics/cubemap.h b/Source/Graphics/cubemap.h
new file mode 100644
index 00000000..ebf1458f
--- /dev/null
+++ b/Source/Graphics/cubemap.h
@@ -0,0 +1,36 @@
+//-----------------------------------------------------------------------------
+// Name: cubemap.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/mat4.h>
+#include <Math/vec3.h>
+
+#include <Asset/Asset/texture.h>
+
+namespace Cubemap {
+ enum HemisphereEnabled {HEMISPHERE, SPHERE};
+}
+
+mat4 GetCubeMapRotation(int i);
+void CubemapMipChain(TextureRef cube_map_tex_ref, Cubemap::HemisphereEnabled hemisphere_enabled, const vec3 *hemisphere_vec);
diff --git a/Source/Graphics/decaltextures.cpp b/Source/Graphics/decaltextures.cpp
new file mode 100644
index 00000000..89636eb3
--- /dev/null
+++ b/Source/Graphics/decaltextures.cpp
@@ -0,0 +1,194 @@
+//-----------------------------------------------------------------------------
+// Name: decaltextures.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "decaltextures.h"
+
+#include <Internal/config.h>
+#include <Internal/hardware_specs.h>
+#include <Internal/profiler.h>
+
+#include <Main/engine.h>
+#include <Math/ivec2math.h>
+#include <Logging/logdata.h>
+
+#include <cmath>
+
+bool kUseDecalNormals = false;
+
+//We define this because windows is missing log2
+double Log2( double n )
+{
+ // log(n)/log(2) is log2.
+ return log( n ) / log( 2 );
+}
+
+
+DecalTexture::DecalTexture()
+{
+
+}
+
+DecalTexture::~DecalTexture()
+{
+ DecalTextures* dt = DecalTextures::Instance();
+
+ if( dt->initialized )
+ {
+ if(colornode_ref.valid())
+ dt->colornodetree->FreeNode( colornode_ref );
+ if(normalnode_ref.valid())
+ dt->normalnodetree->FreeNode( normalnode_ref );
+ }
+}
+
+DecalTextures::DecalTextures() :
+colornodetree(NULL),
+normalnodetree(NULL),
+coloratlas(NULL),
+normalatlas(NULL),
+initialized(false)
+{
+
+}
+
+DecalTextures::~DecalTextures()
+{
+ delete colornodetree;
+ colornodetree = NULL;
+ delete normalnodetree;
+ normalnodetree = NULL;
+ delete coloratlas;
+ coloratlas = NULL;
+ delete normalatlas;
+ normalatlas = NULL;
+ initialized = false;
+}
+
+void DecalTextures::Init()
+{
+ kUseDecalNormals = config["decal_normals"].toNumber<bool>();
+ int s = GetGLMaxTextureSize();
+ s = (int)std::pow(2.0,(int)std::min((int)Log2(s),13));
+
+ s = s/Graphics::Instance()->config_.texture_reduction_factor();
+
+ ivec2 dim(s,s);
+ PROFILER_ENTER(g_profiler_ctx, "Create AtlasNodeTrees");
+ colornodetree = new AtlasNodeTree(dim,32);
+ normalnodetree = new AtlasNodeTree(dim,32);
+ PROFILER_LEAVE(g_profiler_ctx);
+ PROFILER_ENTER(g_profiler_ctx, "Create Texture Atlases");
+ coloratlas = new TextureAtlas(*colornodetree, TextureData::sRGB);
+ Textures::Instance()->SetTextureName(coloratlas->atlas_texture, "Decal Color Atlas");
+ normalatlas = new TextureAtlas(*normalnodetree, TextureData::Linear);
+ Textures::Instance()->SetTextureName(normalatlas->atlas_texture, "Decal Normal Atlas");
+ PROFILER_LEAVE(g_profiler_ctx);
+ initialized = true;
+}
+
+void DecalTextures::Draw()
+{
+ coloratlas->Draw();
+ if (kUseDecalNormals) {
+ normalatlas->Draw();
+ }
+}
+
+static DecalTextures* instance = NULL;
+
+DecalTextures* DecalTextures::Instance()
+{
+ if( instance == NULL )
+ {
+ instance = new DecalTextures();
+ }
+
+ return instance;
+}
+
+void DecalTextures::Dispose()
+{
+ delete instance;
+ instance = NULL;
+}
+
+void DecalTextures::Clear()
+{
+ colornodetree->Clear();
+ normalnodetree->Clear();
+}
+
+RC_DecalTexture DecalTextures::allocateTexture( std::string color, std::string norm )
+{
+ //This will, thankfully, not go straight into VRAM.
+ RC_DecalTexture rc_dt;
+
+ TextureAssetRef color_tex = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>( color );
+ TextureAssetRef normal_tex = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>( norm );
+
+ // we put these in a compressed atlas so they must be compressed
+ if (!Textures::Instance()->IsCompressed(color_tex)) {
+ LOGW << "Reloading a texture as compressed, this is unexpected" << std::endl;
+ if (!Textures::Instance()->ReloadAsCompressed(color_tex)) {
+ LOGE << "Failed to re-save color texture " << color << " as compressed" << std::endl;
+ }
+ }
+ if (!Textures::Instance()->IsCompressed(normal_tex)) {
+ LOGW << "Reloading a texture as compressed, this is unexpected" << std::endl;
+ if (!Textures::Instance()->ReloadAsCompressed(normal_tex)) {
+ LOGE << "Failed to re-save normal texture " << norm << " as compressed" << std::endl;
+ }
+ }
+
+ int reduction_factor = Graphics::Instance()->config_.texture_reduction_factor();
+ ivec2 color_tex_dim = ivec2(Textures::Instance()->getWidth(color_tex),Textures::Instance()->getWidth(color_tex)) / reduction_factor;
+ ivec2 normal_tex_dim = ivec2(Textures::Instance()->getHeight(normal_tex),Textures::Instance()->getHeight(normal_tex)) / reduction_factor;
+
+ {
+ rc_dt->colornode_ref = colornodetree->RetrieveNode(color_tex_dim);
+
+ if( rc_dt->colornode_ref.valid() )
+ {
+ rc_dt->color_texture_ref = coloratlas->SetTextureToNode(color,rc_dt->colornode_ref);
+ }
+ else
+ {
+ LOGE << "Unable to allocate atlas space for decal color." << std::endl;
+ }
+ }
+
+ {
+ rc_dt->normalnode_ref = normalnodetree->RetrieveNode(normal_tex_dim);
+
+ if( rc_dt->normalnode_ref.valid() )
+ {
+ rc_dt->normal_texture_ref = normalatlas->SetTextureToNode(norm, rc_dt->normalnode_ref);
+ }
+ else
+ {
+ LOGE << "Unable to allocate atlas space for decal normals." << std::endl;
+ }
+ }
+
+ return rc_dt;
+}
diff --git a/Source/Graphics/decaltextures.h b/Source/Graphics/decaltextures.h
new file mode 100644
index 00000000..1bb2ccb9
--- /dev/null
+++ b/Source/Graphics/decaltextures.h
@@ -0,0 +1,74 @@
+//-----------------------------------------------------------------------------
+// Name: decaltextures.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Graphics/textureatlas.h>
+#include <Graphics/atlasnodetree.h>
+
+#include <Internal/referencecounter.h>
+
+class EnvObject;
+/*
+ Combined instance creating a reference to a texture
+*/
+class DecalTexture
+{
+ AtlasNodeTree::AtlasNodeRef colornode_ref;
+ AtlasNodeTree::AtlasNodeRef normalnode_ref;
+ friend class DecalTextures;
+public:
+ TextureAtlasRef color_texture_ref;
+ TextureAtlasRef normal_texture_ref;
+
+ DecalTexture();
+ ~DecalTexture();
+};
+
+typedef ReferenceCounter<DecalTexture> RC_DecalTexture;
+
+class DecalTextures
+{
+private:
+ AtlasNodeTree* colornodetree;
+ AtlasNodeTree* normalnodetree;
+ TextureAtlas* coloratlas;
+ TextureAtlas* normalatlas;
+ bool initialized;
+ friend class DecalTexture;
+ friend class EnvObject;
+ friend class SceneGraph;
+
+ DecalTextures();
+ ~DecalTextures();
+public:
+
+ void Init();
+ void Draw();
+ void Clear();
+
+ RC_DecalTexture allocateTexture( std::string color_tex, std::string norm_tex );
+
+ static DecalTextures* Instance();
+ static void Dispose();
+};
diff --git a/Source/Graphics/detailmapinfo.cpp b/Source/Graphics/detailmapinfo.cpp
new file mode 100644
index 00000000..b4009571
--- /dev/null
+++ b/Source/Graphics/detailmapinfo.cpp
@@ -0,0 +1,51 @@
+//-----------------------------------------------------------------------------
+// Name: detailmapinfo.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "detailmapinfo.h"
+
+#include <Internal/comma_separated_list.h>
+#include <Internal/filesystem.h>
+#include <Internal/returnpathutil.h>
+#include <Internal/levelxml.h>
+
+#include <Asset/Asset/material.h>
+#include <Logging/logdata.h>
+#include <Game/detailobjectlayer.h>
+#include <Main/engine.h>
+
+#include <tinyxml.h>
+
+void DetailMapInfo::Print()
+{
+ LOGI << "Colorpath: " << colorpath << std::endl;
+ LOGI << "Normalpath: " << normalpath << std::endl;
+ LOGI << "Materialpath: " << materialpath << std::endl;
+}
+
+void DetailMapInfo::ReturnPaths( PathSet &path_set )
+{
+ path_set.insert("texture "+colorpath);
+ path_set.insert("texture "+normalpath);
+ //Materials::Instance()->ReturnRef(materialpath)->ReturnPaths(path_set);
+ Engine::Instance()->GetAssetManager()->LoadSync<Material>(materialpath)->ReturnPaths(path_set);
+}
diff --git a/Source/Graphics/detailmapinfo.h b/Source/Graphics/detailmapinfo.h
new file mode 100644
index 00000000..5f7d4039
--- /dev/null
+++ b/Source/Graphics/detailmapinfo.h
@@ -0,0 +1,50 @@
+//-----------------------------------------------------------------------------
+// Name: detailmapinfo.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/mat4.h>
+#include <Math/quaternions.h>
+
+#include <Game/EntityDescription.h>
+#include <Game/detailobjectlayer.h>
+
+#include <Asset/assets.h>
+#include <Scripting/scriptparams.h>
+
+#include <string>
+
+struct DetailMapInfo {
+ std::string colorpath;
+ std::string normalpath;
+ std::string materialpath;
+
+ inline bool operator==(const DetailMapInfo& other) const {
+ return colorpath == other.colorpath &&
+ normalpath == other.normalpath &&
+ materialpath == other.materialpath;
+ }
+
+ void Print();
+ void ReturnPaths(PathSet &path_set);
+};
diff --git a/Source/Graphics/detailobjectsurface.cpp b/Source/Graphics/detailobjectsurface.cpp
new file mode 100644
index 00000000..babe4fd9
--- /dev/null
+++ b/Source/Graphics/detailobjectsurface.cpp
@@ -0,0 +1,1050 @@
+//-----------------------------------------------------------------------------
+// Name: detailobjectsurface.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "detailobjectsurface.h"
+
+#include <Internal/timer.h>
+#include <Internal/profiler.h>
+#include <Internal/common.h>
+
+#include <Graphics/model.h>
+#include <Graphics/pxdebugdraw.h>
+#include <Graphics/models.h>
+#include <Graphics/sky.h>
+#include <Graphics/shaders.h>
+#include <Graphics/camera.h>
+
+#include <Math/vec2math.h>
+#include <Math/vec3math.h>
+
+#include <Main/scenegraph.h>
+#include <Main/engine.h>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <Physics/bulletworld.h>
+#include <Utility/assert.h>
+#include <Graphics/lightprobecollection.hpp>
+#include <Logging/logdata.h>
+
+#include <algorithm>
+
+#define USE_SSE
+
+static const float _tile_size = 10.0f; // How big is each tile? (in meters)
+
+extern char* global_shader_suffix;
+extern bool g_simple_shadows;
+extern bool g_level_shadows;
+extern bool g_detail_objects_reduced;
+extern bool g_detail_objects_reduced_dirty;
+extern Timer game_timer;
+extern bool g_debug_runtime_disable_detail_object_surface_draw;
+extern bool g_debug_runtime_disable_detail_object_surface_pre_draw;
+extern bool g_ubo_batch_multiplier_force_1x;
+
+int num_detail_object_draw_calls;
+
+void DetailObjectSurface::AttachTo( const Model& model, mat4 transform ) {
+ PROFILER_ZONE(g_profiler_ctx, "DetailObjectSurface::AttachTo");
+ base_model_ptr = &model;
+ triangle_area.resize(model.faces.size()/3);
+ vec3 verts[3];
+ int face_index = 0;
+ int vert_index;
+ for(unsigned i=0; i<triangle_area.size(); ++i){
+ for(unsigned j=0; j<3; ++j){
+ vert_index = model.faces[face_index+j]*3;
+ verts[j] = vec3(model.vertices[vert_index+0],
+ model.vertices[vert_index+1],
+ model.vertices[vert_index+2]);
+ verts[j] = transform * verts[j];
+ }
+ triangle_area[i] = length(cross(verts[2]-verts[0], verts[1]-verts[0]))*0.5f;
+ face_index += 3;
+ static const bool debug_draw = false;
+ if(debug_draw){
+ vec3 pos = (verts[0] + verts[1] + verts[2])/3.0f;
+ DebugDraw::Instance()->AddPoint(pos+vec3(0.0f,0.2f,0.0f), vec4(1.0f), _persistent, _DD_NO_FLAG);
+ }
+ }
+}
+
+//void DetailObjectSurface::GetTrisInSphere( const vec3 &center, float radius )
+//{
+// BulletWorld bw;
+// bw.Init();
+// bw.InitWorld();
+// BulletObject* bo = bw.CreateStaticMesh(model_ptr);
+// TriListResults tlr;
+// TriListCallback cb(tlr);
+// bw.GetSphereCollisions(center, radius, cb);
+// const std::vector<int> &tris = cb.tri_list[bo];
+// const Model& model = *model_ptr;
+// int face_index;
+// int vert_index;
+// int tex_index;
+// vec3 verts[3];
+// vec2 tex_coords[3];
+// vec2 tex_coords2[3];
+// for(unsigned i=0; i<tris.size(); ++i){
+// face_index = tris[i]*3;
+// for(unsigned j=0; j<3; ++j){
+// vert_index = model.faces[face_index+j]*3;
+// tex_index = model.faces[face_index+j]*2;
+// verts[j] = vec3(model.vertices[vert_index+0],
+// model.vertices[vert_index+1],
+// model.vertices[vert_index+2]);
+// tex_coords[j] = vec2(model.tex_coords[tex_index+0],
+// model.tex_coords[tex_index+1]);
+// tex_coords2[j] = vec2(model.tex_coords2[tex_index+0],
+// model.tex_coords2[tex_index+1]);
+// }
+// unsigned num_dots = (unsigned)(triangle_area[tris[i]]*density);
+// for(unsigned j=0; j<num_dots; ++j){
+// vec2 coord(RangedRandomFloat(0.0f,1.0f),
+// RangedRandomFloat(0.0f,1.0f));
+// if(coord[0] + coord[1] > 1){
+// std::swap(coord[0], coord[1]);
+// coord[0] = 1.0f - coord[0];
+// coord[1] = 1.0f - coord[1];
+// }
+// while(coord[0] + coord[1] > 1){
+// coord[0] = RangedRandomFloat(0.0f,1.0f);
+// coord[1] = RangedRandomFloat(0.0f,1.0f);
+// }
+// vec3 pos = (verts[0] + (verts[1]-verts[0])*coord[0] + (verts[2]-verts[0])*coord[1]);///3.0f;
+// if(distance_squared(pos, center)<radius*radius){
+// mat4 matrix;
+// matrix.SetRotationY(RangedRandomFloat(0.0f,6.28f));
+// matrix.SetTranslationPart(pos+vec3(0.0f,0.1f,0.0f));
+// vec2 tex_coord;
+// vec2 tex_coord2;
+// tex_coord = (tex_coords[0] + (tex_coords[1]-tex_coords[0])*coord[0] + (tex_coords[2]-tex_coords[0])*coord[1]);
+// tex_coord2 = (tex_coords2[0] + (tex_coords2[1]-tex_coords2[0])*coord[0] + (tex_coords2[2]-tex_coords2[0])*coord[1]);
+// DetailInstance di;
+// di.transform = matrix;
+// di.tex_coord[0] = tex_coord;
+// di.tex_coord[1] = tex_coord2;
+// TriInt ti = GetTriInt(pos);
+// DOPatch& current_patch = patches[ti];
+// current_patch.detail_instances.push_back(di);
+// #if defined(USE_SSE)
+// vec4 centerVec4 = Mat4Vec4SimdMul(di.transform, vec4(model.bounding_sphere_origin));
+// vec3 center = *(vec3*)&centerVec4;
+// #else
+// vec3 center = di.transform * model.bounding_sphere_origin;
+// #endif
+// current_patch.detail_instance_origins_x.push_back(center.x());
+// current_patch.detail_instance_origins_y.push_back(center.y());
+// current_patch.detail_instance_origins_z.push_back(center.z());
+// //detail_instances.push_back(di);
+// //DebugDraw::Instance()->AddPoint(pos+vec3(0.0f,0.2f,0.0f), vec4(1.0f), _persistent, 0);
+// }
+// }
+// }
+// PatchesMap::iterator iter;
+// for(iter = patches.begin(); iter != patches.end(); ++iter){
+// DOPatch &patch = iter->second;
+// vec3 sphere_center;
+// for(unsigned j=0; j<patch.detail_instances.size(); ++j){
+// sphere_center += patch.detail_instances[j].transform.GetTranslationPart();
+// }
+// sphere_center /= (float)patch.detail_instances.size();
+// float least_distance = distance_squared(sphere_center, patch.detail_instances[0].transform.GetTranslationPart());
+// for(unsigned j=0; j<patch.detail_instances.size(); ++j){
+// least_distance = max(least_distance,
+// distance_squared(sphere_center, patch.detail_instances[j].transform.GetTranslationPart()));
+// }
+// patch.sphere_center = sphere_center;
+// patch.sphere_radius = sqrtf(least_distance);
+// }
+// /*PatchesMap::iterator iter;
+// for(iter = patches.begin(); iter != patches.end(); ++iter){
+// vec3 patch_color;
+// patch_color[0] = RangedRandomFloat(0.0f,1.0f);
+// patch_color[1] = RangedRandomFloat(0.0f,1.0f);
+// patch_color[2] = RangedRandomFloat(0.0f,1.0f);
+//
+// const std::vector<DetailInstance> &di_vec = iter->second;
+// for(unsigned j=0; j<di_vec.size(); ++j){
+// if(rand()%20 == 0){
+// DebugDraw::Instance()->AddPoint(di_vec[j].transform.GetTranslationPart()+vec3(0.0f,0.2f,0.0f), patch_color, _persistent, 0);
+// }
+// }
+// }*/
+//
+// //Cluster();
+//}
+
+
+void DetailObjectSurface::GetTrisInPatches( mat4 transform ) {
+ PROFILER_ZONE(g_profiler_ctx, "DetailObjectSurface::GetTrisInPatches");
+ const Model& model = *base_model_ptr;
+ int face_index;
+ int vert_index;
+ int tex_index;
+ vec3 verts[3];
+ vec3 normals[3];
+ vec2 tex_coords[3];
+ vec2 tex_coords2[3];
+ TriInt patch_vert[3];
+ TriInt patch_bounds[2];
+
+ bounding_box[0] = INT_MAX;
+ bounding_box[1] = INT_MIN;
+ bounding_box[2] = INT_MAX;
+ bounding_box[3] = INT_MIN;
+ bounding_box[4] = INT_MAX;
+ bounding_box[5] = INT_MIN;
+
+ for(int i=0, len=base_model_ptr->faces.size()/3; i<len; ++i){
+ face_index = i*3;
+ for(unsigned j=0; j<3; ++j){
+ vert_index = model.faces[face_index+j]*3;
+ tex_index = model.faces[face_index+j]*2;
+ verts[j] = vec3(model.vertices[vert_index+0],
+ model.vertices[vert_index+1],
+ model.vertices[vert_index+2]);
+ verts[j] = transform * verts[j];
+ normals[j] = vec3(model.normals[vert_index+0],
+ model.normals[vert_index+1],
+ model.normals[vert_index+2]);
+ normals[j] = normalize(transform.GetRotatedvec3(normals[j]));
+ tex_coords[j] = vec2(model.tex_coords[tex_index+0],
+ model.tex_coords[tex_index+1]);
+ tex_coords2[j] = vec2(model.tex_coords2[tex_index+0],
+ model.tex_coords2[tex_index+1]);
+ patch_vert[j] = GetTriInt(verts[j]);
+ }
+ patch_bounds[0] = patch_vert[0];
+ patch_bounds[1] = patch_vert[0];
+ for(unsigned k=0; k<3; ++k){
+ for(unsigned j=1; j<3; ++j){
+ patch_bounds[0].val[k] = min(patch_bounds[0].val[k], patch_vert[j].val[k]);
+ patch_bounds[1].val[k] = max(patch_bounds[1].val[k], patch_vert[j].val[k]);
+ }
+ }
+ for(int x_patch=patch_bounds[0].val[0]; x_patch<=patch_bounds[1].val[0]; ++x_patch){
+ for(int y_patch=patch_bounds[0].val[1]; y_patch<=patch_bounds[1].val[1]; ++y_patch){
+ for(int z_patch=patch_bounds[0].val[2]; z_patch<=patch_bounds[1].val[2]; ++z_patch){
+ bounding_box[0] = min(bounding_box[0], x_patch);
+ bounding_box[1] = max(bounding_box[1], x_patch);
+ bounding_box[2] = min(bounding_box[2], y_patch);
+ bounding_box[3] = max(bounding_box[3], y_patch);
+ bounding_box[4] = min(bounding_box[4], z_patch);
+ bounding_box[5] = max(bounding_box[5], z_patch);
+ TriInt ti(x_patch, y_patch, z_patch);
+ DOPatch& patch = patches[ti];
+ patch.tris.push_back(i);
+ for(int k=0; k<3; ++k){
+ patch.verts.push_back(verts[k]);
+ patch.normals.push_back(normals[k]);
+ patch.tex_coords.push_back(tex_coords[k]);
+ patch.tex_coords2.push_back(tex_coords2[k]);
+ }
+ }
+ }
+ }
+ }
+}
+
+void DetailObjectSurface::LoadDetailModel( const std::string &path ) {
+ ofr = Engine::Instance()->GetAssetManager()->LoadSync<ObjectFile>(path);
+ double_sided = ofr->double_sided;
+ if(Graphics::Instance()->config_.detail_object_lowres()) {
+ std::string model_name = ofr->model_name.substr(0, ofr->model_name.rfind('.')) + "_low" + ofr->model_name.substr(ofr->model_name.rfind('.'));
+ if(FindFilePath(model_name, kAnyPath, false).isValid())
+ detail_model_id = Models::Instance()->loadModel(model_name);
+ else
+ detail_model_id = Models::Instance()->loadModel(ofr->model_name);
+ }
+ else
+ detail_model_id = Models::Instance()->loadModel(ofr->model_name);
+}
+
+#ifdef USE_SSE
+static vec4 Mat4Vec4SimdMul(const mat4& lhsMat, const vec4& rhsVec) {
+ __m128 simdMat[4];
+ simdMat[0] = _mm_load_ps(&lhsMat.entries[0]);
+ simdMat[1] = _mm_load_ps(&lhsMat.entries[4]);
+ simdMat[2] = _mm_load_ps(&lhsMat.entries[8]);
+ simdMat[3] = _mm_load_ps(&lhsMat.entries[12]);
+
+ __m128 simdVec;
+ simdVec = _mm_load_ps(&rhsVec.entries[0]);
+
+ __m128 v0 = _mm_shuffle_ps(simdVec, simdVec, _MM_SHUFFLE(0, 0, 0, 0));
+ __m128 v1 = _mm_shuffle_ps(simdVec, simdVec, _MM_SHUFFLE(1, 1, 1, 1));
+ __m128 v2 = _mm_shuffle_ps(simdVec, simdVec, _MM_SHUFFLE(2, 2, 2, 2));
+ __m128 v3 = _mm_shuffle_ps(simdVec, simdVec, _MM_SHUFFLE(3, 3, 3, 3));
+
+ __m128 m0 = _mm_mul_ps(simdMat[0], v0);
+ __m128 m1 = _mm_mul_ps(simdMat[1], v1);
+ __m128 m2 = _mm_mul_ps(simdMat[2], v2);
+ __m128 m3 = _mm_mul_ps(simdMat[3], v3);
+
+ __m128 a0 = _mm_add_ps(m0, m1);
+ __m128 a1 = _mm_add_ps(m2, m3);
+ __m128 a2 = _mm_add_ps(a0, a1);
+
+ vec4 result;
+ _mm_store_ps(&result.entries[0], a2);
+
+ return result;
+}
+#endif
+
+void DetailObjectSurface::PreDrawCamera( const mat4 &transform ) {
+ if (g_debug_runtime_disable_detail_object_surface_pre_draw) {
+ return;
+ }
+
+ draw_detail_instances.clear();
+ draw_detail_instance_transforms.clear();
+
+ Camera* cam = ActiveCameras::Get();
+ float temp_view_dist = view_dist * min(3.0f, 90.0f / cam->GetFOV());
+ const int _tile_radius = (int)ceilf((temp_view_dist)/_tile_size); // How many tiles are visible in each direction
+ vec3 cam_pos = cam->GetPos();
+
+ TriInt cam_ti = GetTriInt(cam_pos);
+ TriInt test_ti(0,0,0);
+
+ const Model& model = Models::Instance()->GetModel(detail_model_id);
+ float cull_dist_squared = square(temp_view_dist + model.bounding_sphere_radius);
+
+ if(Graphics::Instance()->config_.detail_objects())
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Calculate visible detail instances");
+
+ int bounds[6];
+ bounds[0] = max(bounding_box[0], cam_ti.val[0]-_tile_radius);
+ bounds[1] = min(bounding_box[1], cam_ti.val[0]+_tile_radius);
+ bounds[2] = max(bounding_box[2], cam_ti.val[1]-_tile_radius);
+ bounds[3] = min(bounding_box[3], cam_ti.val[1]+_tile_radius);
+ bounds[4] = max(bounding_box[4], cam_ti.val[2]-_tile_radius);
+ bounds[5] = min(bounding_box[5], cam_ti.val[2]+_tile_radius);
+
+ const vec4 modelBoundingSphereOrigin = vec4(model.bounding_sphere_origin);
+
+ PatchesMap::iterator iter;
+ for(int i=bounds[0]; i<=bounds[1]; ++i){
+ for(int j=bounds[2]; j<=bounds[3]; ++j){
+ for(int k=bounds[4]; k<=bounds[5]; ++k){
+ test_ti.val[0] = i;
+ test_ti.val[1] = j;
+ test_ti.val[2] = k;
+ iter = patches.find(test_ti);
+ if(iter != patches.end()){
+ if(!iter->second.calculated || g_detail_objects_reduced_dirty){
+ CalcPatchInstances(iter->first, iter->second, transform);
+ }
+ DOPatch& patch = iter->second;
+ if(!patch.detail_instances.empty()){
+ float square_patch_dist = distance_squared(patch.sphere_center, cam_pos);
+ if(square_patch_dist < square(temp_view_dist + patch.sphere_radius)) {
+ int in_frustum = cam->checkSphereInFrustum(patch.sphere_center, patch.sphere_radius);
+ if(in_frustum == 2 && square_patch_dist > square(temp_view_dist - patch.sphere_radius)){
+ in_frustum = 1;
+ }
+ if(in_frustum == 1){
+ static std::vector<uint32_t> is_visible;
+ is_visible.clear();
+ is_visible.resize(patch.detail_instances.size());
+ cam->checkSpheresInFrustum(patch.detail_instances.size(), &patch.detail_instance_origins_x[0], &patch.detail_instance_origins_y[0], &patch.detail_instance_origins_z[0], model.bounding_sphere_radius, cull_dist_squared, &is_visible[0]);
+ for(int l=0, len=patch.detail_instances.size(); l<len; ++l){
+ if(is_visible[l]){
+ draw_detail_instances.push_back(patch.detail_instances[l]);
+ }
+ }
+ for(int l=0, len=patch.detail_instance_transforms.size(); l<len; ++l){
+ if(is_visible[l]){
+ draw_detail_instance_transforms.push_back(patch.detail_instance_transforms[l]);
+ }
+ }
+ } else if(in_frustum == 2){
+ draw_detail_instances.insert( draw_detail_instances.end(), patch.detail_instances.begin(), patch.detail_instances.end() );
+ draw_detail_instance_transforms.insert( draw_detail_instance_transforms.end(), patch.detail_instance_transforms.begin(), patch.detail_instance_transforms.end() );
+ }
+ //DebugDraw::Instance()->AddWireSphere(patch.sphere_center, patch.sphere_radius + 0.01f, vec4(1.0), _delete_on_draw);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ g_detail_objects_reduced_dirty = false;
+ }
+
+ float cur_time = game_timer.game_time;
+ CalculatedPatchesSet::iterator cpi;
+ for(cpi = calculated_patches.begin(); cpi!=calculated_patches.end();){
+ const TriInt& coords = (*cpi).coords;
+ if((coords.val[0] < cam_ti.val[0]-_tile_radius ||
+ coords.val[1] < cam_ti.val[1]-_tile_radius ||
+ coords.val[2] < cam_ti.val[2]-_tile_radius ||
+ coords.val[0] > cam_ti.val[0]+_tile_radius ||
+ coords.val[1] > cam_ti.val[1]+_tile_radius ||
+ coords.val[2] > cam_ti.val[2]+_tile_radius) &&
+ ((*cpi).last_used_time < cur_time - 0.2f ||
+ (*cpi).last_used_time > cur_time))
+ {
+ ClearPatch(patches[coords]);
+ calculated_patches.erase(cpi++);
+ } else {
+ (*cpi).last_used_time = cur_time;
+ ++cpi;
+ }
+ }
+}
+
+void DetailObjectSurface::Draw( const mat4 &transform, DetailObjectShaderType shader_type, vec3 color_tint, const TextureRef& light_cube, LightProbeCollection* light_probe_collection, SceneGraph* scenegraph_) {
+ if (g_debug_runtime_disable_detail_object_surface_draw) {
+ return;
+ }
+
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw detail object surface");
+ Shaders* shaders = Shaders::Instance();
+ Graphics* graphics = Graphics::Instance();
+ Camera* cam = ActiveCameras::Get();
+
+ if(!bm.created){
+ bm.Create(Models::Instance()->GetModel(detail_model_id));
+ }
+ batch.Dispose();
+
+ batch.gl_state.depth_test = true;
+ batch.gl_state.cull_face = !double_sided;
+ batch.gl_state.depth_write = true;
+ batch.gl_state.blend = false;
+
+ batch.AddUniformFloat("fade",0.0f);
+ batch.AddUniformFloat("height",bm.height);
+ batch.AddUniformFloat("max_distance",view_dist * min(3.0f, 90.0f / cam->GetFOV()));
+ batch.AddUniformFloat("overbright",overbright);
+ batch.AddUniformVec3("color_tint", color_tint);
+ batch.AddUniformFloat("haze_mult", scenegraph_->haze_mult);
+ batch.AddUniformFloat("tint_weight", tint_weight);
+
+ const int kShaderStrSize = 1024;
+ char buf[2][kShaderStrSize];
+ char* shader_str[2] = {buf[0], buf[1]};
+ FormatString(shader_str[0], kShaderStrSize, "envobject #DETAIL_OBJECT");
+
+ if(shader_type == TERRAIN) {
+ FormatString(shader_str[1], kShaderStrSize, "%s #TERRAIN", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(ofr->shader_name == "plant" || ofr->shader_name == "envobject #TANGENT #ALPHA #PLANT"){
+ FormatString(shader_str[1], kShaderStrSize, "%s #PLANT", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(ofr->shader_name == "plant_less_movement"){
+ FormatString(shader_str[1], kShaderStrSize, "%s #PLANT #LESS_PLANT_MOVEMENT", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+
+ if(!Graphics::Instance()->config_.detail_object_decals()) {
+ FormatString(shader_str[1], kShaderStrSize, "%s %s", shader_str[0], "#NO_DECALS");
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(!Graphics::Instance()->config_.detail_object_shadows()) {
+ FormatString(shader_str[1], kShaderStrSize, "%s %s", shader_str[0], "#NO_DETAIL_OBJECT_SHADOWS");
+ std::swap(shader_str[0], shader_str[1]);
+ }
+
+ FormatString(shader_str[1], kShaderStrSize, "%s %s", shader_str[0], global_shader_suffix);
+ std::swap(shader_str[0], shader_str[1]);
+
+ batch.shader_id = shaders->returnProgram(shader_str[0]);
+ batch.use_cam_pos = true;
+ batch.use_light = true;
+
+ bool transparent = shaders->IsProgramTransparent(batch.shader_id);
+ if(transparent) {
+ batch.use_time = true;
+ }
+
+ if(ofr->clamp_texture){
+ Textures::Instance()->setWrap(GL_CLAMP_TO_EDGE);
+ } else {
+ Textures::Instance()->setWrap(GL_REPEAT);
+ }
+ batch.texture_ref[0] = ofr->color_map_texture->GetTextureRef();
+ batch.texture_ref[1] = ofr->normal_map_texture->GetTextureRef();
+ batch.texture_ref[2] = light_cube;
+ if(g_simple_shadows || !g_level_shadows){
+ batch.texture_ref[4] = graphics->static_shadow_depth_ref;
+ } else {
+ batch.texture_ref[4] = graphics->cascade_shadow_depth_ref;
+ }
+
+ if(!ofr->translucency_map.empty()){
+ batch.texture_ref[5] = ofr->translucency_map_texture->GetTextureRef();
+ }
+ batch.texture_ref[6] = base_color_ref->GetTextureRef();
+ batch.texture_ref[7] = base_normal_ref->GetTextureRef();
+ if(transparent){
+ batch.transparent = true;
+ }
+ batch.AddUniformVec3("avg_color", vec3(ofr->avg_color_srgb[0]/255.0f,
+ ofr->avg_color_srgb[1]/255.0f,
+ ofr->avg_color_srgb[2]/255.0f));
+
+ if(!graphics->config_.detail_objects()){
+ return;
+ }
+
+ CHECK_GL_ERROR();
+ batch.SetStartState();
+
+ Textures::Instance()->bindTexture(graphics->screen_depth_tex, 18);
+
+ if(light_probe_collection->light_probe_buffer_object_id != -1){
+ shaders->SetUniformInt("num_tetrahedra", light_probe_collection->ShaderNumTetrahedra());
+ shaders->SetUniformInt("num_light_probes", light_probe_collection->ShaderNumLightProbes());
+ shaders->SetUniformInt(shaders->GetTexUniform(TEX_AMBIENT_COLOR_BUFFER), TEX_AMBIENT_COLOR_BUFFER);
+ glBindBuffer(GL_TEXTURE_BUFFER, light_probe_collection->light_probe_buffer_object_id);
+ }
+
+ scenegraph_->BindLights(batch.shader_id);
+
+ CHECK_GL_ERROR();
+ std::vector<mat4> shadow_matrix;
+ shadow_matrix.resize(4);
+ for(int i=0; i<4; ++i){
+ shadow_matrix[i] = cam->biasMatrix * graphics->cascade_shadow_mat[i];
+ }
+ if(g_simple_shadows || !g_level_shadows){
+ shadow_matrix[3] = cam->biasMatrix * graphics->simple_shadow_mat;
+ }
+ shaders->SetUniformMat4("projection_view_mat", cam->GetProjMatrix() * cam->GetViewMatrix());
+ shaders->SetUniformMat4Array("shadow_matrix",shadow_matrix);
+
+ static const int kNumAttribs = 5;
+ static const char* attrib_labels[] = {
+ "vertex_attrib", "tex_coord_attrib", "tangent_attrib", "bitangent_attrib", "normal_attrib"
+ };
+ static const int attrib_offset[] = {
+ 0, 12, 6, 9, 3
+ };
+ static const int attrib_size[] = {
+ 3, 2, 3, 3, 3
+ };
+
+ CHECK_GL_ERROR();
+
+ bm.vboc.Bind();
+ bm.vboc_el.Bind();
+ CHECK_GL_ERROR();
+ int shader_id = shaders->bound_program;
+ int attrib_ids[kNumAttribs];
+ for(int i=0; i<kNumAttribs; ++i){
+ CHECK_GL_ERROR();
+ attrib_ids[i] = shaders->returnShaderAttrib(attrib_labels[i], shader_id);
+ CHECK_GL_ERROR();
+ if(attrib_ids[i] != -1){
+ graphics->EnableVertexAttribArray(attrib_ids[i]);
+ CHECK_GL_ERROR();
+ glVertexAttribPointer(attrib_ids[i], attrib_size[i], GL_FLOAT, false,
+ sizeof(BatchVertex), (void*)(sizeof(GLfloat)*attrib_offset[i]));
+ CHECK_GL_ERROR();
+ }
+ }
+ CHECK_GL_ERROR();
+ shaders->SetUniformMat4("model_mat", transform);
+ CHECK_GL_ERROR();
+ shaders->SetUniformMat3("normal_matrix", mat3(transform.GetInverseTranspose()));
+
+ CHECK_GL_ERROR();
+
+ int instance_block_index = shaders->GetUBOBindIndex(batch.shader_id, "InstanceInfo");
+ if ((unsigned)instance_block_index != GL_INVALID_INDEX)
+ {
+ const GLchar *names[] = {
+ "transforms[0]",
+ "texcoords2[0]",
+ // These long names were necessary on a Mac OS 10.7 ATI card
+ "InstanceInfo.transforms[0]",
+ "InstanceInfo.texcoords2[0]",
+ };
+
+ GLuint indices[2];
+ for(int i=0; i<2; ++i){
+ indices[i] = shaders->returnShaderVariableIndex(names[i], shader_id);
+ if(indices[i] == GL_INVALID_INDEX){
+ indices[i] = shaders->returnShaderVariableIndex(names[i+2], shader_id);
+ }
+ }
+ GLint offset[2];
+ for(int i=0; i<2; ++i){
+ if(indices[i] != GL_INVALID_INDEX){
+ offset[i] = shaders->returnShaderVariableOffset(indices[i], shader_id);
+ }
+ }
+ GLint block_size = shaders->returnShaderBlockSize(instance_block_index, shader_id);
+
+ static GLubyte blockBuffer[131072]; // Big enough for 8x the OpenGL guaranteed minimum size. Max supported by shader flags currently. 16x or higher could be added if new platforms end up having > 128k frequently
+
+ static UniformRingBuffer detail_object_instance_buffer;
+ if(detail_object_instance_buffer.gl_id == -1){
+ detail_object_instance_buffer.Create(2 * 1024 * 1024);
+ }
+
+ mat4* transforms = (mat4*)((uintptr_t)blockBuffer + offset[0]);
+ vec4* texcoords2 = (vec4*)((uintptr_t)blockBuffer + offset[1]);
+
+ static int ubo_batch_size_multiplier = 1;
+ static GLint max_ubo_size = -1;
+ if(max_ubo_size == -1) {
+ glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &max_ubo_size);
+
+ if(max_ubo_size >= 131072) {
+ ubo_batch_size_multiplier = 8;
+ }
+ else if(max_ubo_size >= 65536) {
+ ubo_batch_size_multiplier = 4;
+ }
+ else if(max_ubo_size >= 32768) {
+ ubo_batch_size_multiplier = 2;
+ }
+ }
+
+ int kBatchSize = 200 * (!g_ubo_batch_multiplier_force_1x ? ubo_batch_size_multiplier : 1);
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Issue draw calls");
+ for(int i=0, len=draw_detail_instances.size(); i<len; i+=kBatchSize){
+ glUniformBlockBinding(shaders->programs[shader_id].gl_program, instance_block_index, 0);
+ int to_draw = min(kBatchSize, len-i);
+ if(indices[0] != GL_INVALID_INDEX){
+ memcpy(&transforms[0], &draw_detail_instance_transforms[i], to_draw * sizeof(mat4));
+ }
+ if(indices[1] != GL_INVALID_INDEX){
+ memcpy(&texcoords2[0], &draw_detail_instances[i], to_draw * sizeof(vec4));
+ }
+ {
+ // Copying over the whole block because fields aren't contiguous (struct of arrays), so at best can only save the final field's gap until the end of the buffer
+ detail_object_instance_buffer.Fill(block_size, blockBuffer);
+ glBindBufferRange(GL_UNIFORM_BUFFER, 0, detail_object_instance_buffer.gl_id, detail_object_instance_buffer.offset, detail_object_instance_buffer.next_offset - detail_object_instance_buffer.offset);
+ }
+ CHECK_GL_ERROR();
+ graphics->DrawElementsInstanced(GL_TRIANGLES, bm.instance_num_faces, GL_UNSIGNED_INT, 0, to_draw);
+ CHECK_GL_ERROR();
+ ++num_detail_object_draw_calls;
+ }
+ }
+ }
+
+ graphics->ResetVertexAttribArrays();
+
+ CHECK_GL_ERROR();
+
+ batch.SetEndState();
+ CHECK_GL_ERROR();
+}
+
+void DetailObjectSurface::SetBaseTextures( const TextureAssetRef& color_ref,
+ const TextureAssetRef& normal_ref )
+{
+ base_color_ref = color_ref;
+ base_normal_ref = normal_ref;
+}
+
+TriInt DetailObjectSurface::GetTriInt( const vec3 &pos ) {
+ TriInt ti((int)ceil(pos[0]/_tile_size),
+ (int)ceil(pos[1]/_tile_size),
+ (int)ceil(pos[2]/_tile_size));
+ return ti;
+}
+
+void DetailObjectSurface::CalcPatchInstances( const TriInt &coords, DOPatch &patch, mat4 transform ) {
+ PROFILER_ZONE(g_profiler_ctx, "CalcPatchInstances");
+ patch.detail_instances.clear();
+ patch.detail_instance_transforms.clear();
+ patch.detail_instance_origins_x.clear();
+ patch.detail_instance_origins_y.clear();
+ patch.detail_instance_origins_z.clear();
+ const Model& model = Models::Instance()->GetModel(detail_model_id);
+ const vec4 modelBoundingSphereOrigin = vec4(model.bounding_sphere_origin);
+ //int face_index;
+ int index=0;
+ vec3 verts[3];
+ vec3 normals[3];
+ vec2 tex_coords[3];
+ vec2 tex_coords2[3];
+ for(unsigned i=0; i<patch.tris.size(); ++i){
+ double int_part;
+ float float_part = (float)modf(triangle_area[patch.tris[i]]*density, &int_part);
+ int num_dots = int(int_part) + (RangedRandomFloat(0.0f, 1.0f) < float_part);
+ if(num_dots == 0){
+ continue;
+ }
+ if(g_detail_objects_reduced && i % 2 == 0) {
+ continue;
+ }
+ //face_index = patch.tris[i]*3;
+ for(unsigned j=0; j<3; ++j){
+ verts[j] = patch.verts[index];
+ normals[j] = patch.normals[index];
+ tex_coords[j] = patch.tex_coords[index];
+ tex_coords2[j] = patch.tex_coords2[index];
+ ++index;
+ }
+ PROFILER_ZONE(g_profiler_ctx, "Fill dots");
+ for(int j=0; j<num_dots; ++j){
+ vec2 coord(RangedRandomFloat(0.0f,1.0f),
+ RangedRandomFloat(0.0f,1.0f));
+ if(coord[0] + coord[1] > 1){
+ std::swap(coord[0], coord[1]);
+ coord[0] = 1.0f - coord[0];
+ coord[1] = 1.0f - coord[1];
+ }
+ while(coord[0] + coord[1] > 1){
+ coord[0] = RangedRandomFloat(0.0f,1.0f);
+ coord[1] = RangedRandomFloat(0.0f,1.0f);
+ }
+ vec2 tex_coord;
+ tex_coord = (tex_coords[0] + (tex_coords[1]-tex_coords[0])*coord[0] + (tex_coords[2]-tex_coords[0])*coord[1]);
+ vec4 color = weight_map->GetInterpolatedColorUV(tex_coord[0], tex_coord[1]);
+ float grass_val = RangedRandomFloat(0.0f,1.0f);
+ if(color[0] <= grass_val){
+ continue;
+ }
+ vec3 pos = (verts[0] + (verts[1]-verts[0])*coord[0] + (verts[2]-verts[0])*coord[1]);///3.0f;
+ vec3 normal = (normals[0] + (normals[1]-normals[0])*coord[0] + (normals[2]-normals[0])*coord[1]);
+ //pos[1] -= (1.0f - normal[1]) * bm.height;
+ quaternion rand_rot(true,
+ vec3(RangedRandomFloat(-jitter_degrees, jitter_degrees),
+ RangedRandomFloat(0.0f,360.0f),
+ RangedRandomFloat(-jitter_degrees, jitter_degrees)));
+ mat4 matrix = Mat4FromQuaternion(rand_rot);
+ if(normal_conform > 0.0f){
+ mat4 normal_conform_mat;
+ vec3 x_axis = normalize(cross(normal, vec3(0.0f,0.0f,1.0f)));
+ vec3 z_axis = normalize(cross(x_axis, normal));
+ normal_conform_mat.SetColumn(0, x_axis);
+ normal_conform_mat.SetColumn(1, normal);
+ normal_conform_mat.SetColumn(2, z_axis);
+ quaternion normal_conform_quat = QuaternionFromMat4(normal_conform_mat);
+ normal_conform_quat = normal_conform_quat * normal_conform;
+ normal_conform_mat = Mat4FromQuaternion(normal_conform_quat);
+ matrix = normal_conform_mat * matrix;
+ }
+ float scale_val = RangedRandomFloat(min_scale, max_scale);
+ mat4 scale_matrix;
+ scale_matrix.SetUniformScale(scale_val);
+ matrix = scale_matrix * matrix;
+ matrix.SetTranslationPart(pos);
+ //vec2 tex_coord2 = (tex_coords2[0] + (tex_coords2[1]-tex_coords2[0])*coord[0] + (tex_coords2[2]-tex_coords2[0])*coord[1]); // Not setting this because it's currently not used in drawing
+ DetailInstance di;
+ di.embed = RangedRandomFloat(min_embed,max_embed);
+ di.tex_coord0 = tex_coord;
+ di.height_scale = scale_val;
+ TriInt ti = GetTriInt(pos);
+ if(ti == coords){
+ DOPatch& current_patch = patches[ti];
+ current_patch.detail_instances.push_back(di);
+ current_patch.detail_instance_transforms.push_back(matrix);
+#if defined(USE_SSE)
+ vec4 centerVec4 = Mat4Vec4SimdMul(matrix, modelBoundingSphereOrigin);
+ vec3 center = *(vec3*)&centerVec4;
+#else
+ vec3 center = di.transform * model.bounding_sphere_origin;
+#endif
+ current_patch.detail_instance_origins_x.push_back(center.x());
+ current_patch.detail_instance_origins_y.push_back(center.y());
+ current_patch.detail_instance_origins_z.push_back(center.z());
+ }
+ }
+ }
+ if(!patch.detail_instance_transforms.empty()){
+ PROFILER_ZONE(g_profiler_ctx, "Get bounding sphere");
+ vec3 sphere_center;
+ for(unsigned j=0; j<patch.detail_instance_transforms.size(); ++j){
+ sphere_center += patch.detail_instance_transforms[j].GetTranslationPart();
+ }
+ sphere_center /= (float)patch.detail_instance_transforms.size();
+ float least_distance = distance_squared(sphere_center, patch.detail_instance_transforms[0].GetTranslationPart());
+ for(unsigned j=0; j<patch.detail_instance_transforms.size(); ++j){
+ least_distance = max(least_distance,
+ distance_squared(sphere_center, patch.detail_instance_transforms[j].GetTranslationPart()));
+ }
+ patch.sphere_center = sphere_center;
+ patch.sphere_radius = sqrtf(least_distance);
+ }
+ patch.calculated = true;
+ CalculatedPatch cp;
+ cp.coords = coords;
+ cp.last_used_time = game_timer.game_time;
+ calculated_patches.insert(cp);
+}
+
+void DetailObjectSurface::ClearPatch( DOPatch &patch ) {
+ LOGD << "Clearing patch..." << std::endl;
+ patch.detail_instances.clear();
+ patch.detail_instance_transforms.clear();
+ patch.detail_instance_origins_x.clear();
+ patch.detail_instance_origins_y.clear();
+ patch.detail_instance_origins_z.clear();
+ patch.calculated = false;
+}
+
+void DetailObjectSurface::SetDensity( float _density ) {
+ density = _density;
+}
+
+void DetailObjectSurface::LoadWeightMap( const std::string &weight_path ) {
+ //weight_map = ImageSamplers::Instance()->ReturnRef(weight_path);
+ weight_map = Engine::Instance()->GetAssetManager()->LoadSync<ImageSampler>(weight_path);
+}
+
+void DetailObjectSurface::SetMinEmbed( float _min_embed ) {
+ min_embed = _min_embed;
+}
+
+void DetailObjectSurface::SetMaxEmbed( float _max_embed ) {
+ max_embed = _max_embed;
+}
+
+void DetailObjectSurface::SetMinScale( float _min_scale ) {
+ min_scale = _min_scale;
+}
+
+void DetailObjectSurface::SetMaxScale( float _max_scale ) {
+ max_scale = _max_scale;
+}
+
+void DetailObjectSurface::SetViewDist( float _view_dist ) {
+ view_dist = _view_dist;
+}
+
+void DetailObjectSurface::SetCollisionType( CollisionType _collision ) {
+ collision_type = _collision;
+}
+
+void DetailObjectSurface::SetNormalConform( float _normal_conform ) {
+ normal_conform = _normal_conform;
+}
+
+void DetailObjectSurface::SetOverbright( float _overbright ) {
+ overbright = _overbright;
+}
+
+void DetailObjectSurface::SetJitterDegrees( float _jitter_degrees ) {
+ jitter_degrees = _jitter_degrees;
+}
+
+void DetailObjectSurface::SetColorTint(const vec3& tint) {
+ batch.SetUniformVec3("color_tint", tint);
+}
+
+TriInt::TriInt( int a, int b, int c ) {
+ val[0] = a;
+ val[1] = b;
+ val[2] = c;
+}
+
+bool TriInt::operator<( const TriInt &other ) const {
+ return (val[0] < other.val[0] ||
+ (val[0] == other.val[0] &&
+ (val[1] < other.val[1] ||
+ (val[1] == other.val[1] &&
+ val[2] < other.val[2]))));
+}
+
+bool TriInt::operator==( const TriInt &other ) const {
+ return val[0] == other.val[0] &&
+ val[1] == other.val[1] &&
+ val[2] == other.val[2];
+}
+
+DOPatch::DOPatch():
+ calculated(false)
+{
+}
+
+void BatchModel::Create( const Model& detail_model ) {
+ if(created){
+ return;
+ }
+ created = true;
+ vec3 vec;
+ int bvi = 0; // Batch vertex index
+ int bti = 0; // Batch tex index
+ int bfi = 0; // Batch face index
+ int start_faces;
+ std::vector<GLuint> indices;
+ std::vector<GLfloat> vertices;
+ std::vector<GLfloat> normals;
+ std::vector<GLuint> faces;
+ std::vector<GLfloat> tangents;
+ std::vector<GLfloat> bitangents;
+ std::vector<GLfloat> tex_coords;
+ unsigned num_vertices = 0;
+ unsigned num_faces = 0;
+
+ float vert_bounds[2];
+ vert_bounds[0] = detail_model.vertices[1];
+ vert_bounds[1] = detail_model.vertices[1];
+ for(unsigned j=3; j<detail_model.vertices.size(); j+=3){
+ vert_bounds[0] = min(vert_bounds[0], detail_model.vertices[j+1]);
+ vert_bounds[1] = max(vert_bounds[1], detail_model.vertices[j+1]);
+ }
+ height = vert_bounds[1] - vert_bounds[0];
+
+ for(unsigned i=0; i<1; ++i){
+ start_faces = num_vertices;
+ num_faces += detail_model.faces.size()/3;
+ num_vertices += detail_model.vertices.size()/3;
+ vertices.resize(vertices.size() + detail_model.vertices.size());
+ normals.resize(normals.size() + detail_model.normals.size());
+ tangents.resize(tangents.size() + detail_model.tangents.size());
+ bitangents.resize(bitangents.size() + detail_model.bitangents.size());
+ tex_coords.resize(tex_coords.size() + detail_model.tex_coords.size());
+ faces.resize(faces.size() + detail_model.faces.size());
+ indices.resize(indices.size() + detail_model.vertices.size()/3, i);
+ for(unsigned j=0; j<detail_model.vertices.size(); j+=3){
+ vertices[bvi+0] = detail_model.vertices[j];
+ vertices[bvi+1] = detail_model.vertices[j+1]-vert_bounds[0];
+ vertices[bvi+2] = detail_model.vertices[j+2];
+ normals[bvi+0] = detail_model.normals[j];
+ normals[bvi+1] = detail_model.normals[j+1];
+ normals[bvi+2] = detail_model.normals[j+2];
+ tangents[bvi+0] = detail_model.tangents[j];
+ tangents[bvi+1] = detail_model.tangents[j+1];
+ tangents[bvi+2] = detail_model.tangents[j+2];
+ bitangents[bvi+0] = detail_model.bitangents[j];
+ bitangents[bvi+1] = detail_model.bitangents[j+1];
+ bitangents[bvi+2] = detail_model.bitangents[j+2];
+ bvi += 3;
+ }
+ for(unsigned j=0; j<detail_model.tex_coords.size(); j+=2){
+ tex_coords[bti+0] = detail_model.tex_coords[j];
+ tex_coords[bti+1] = detail_model.tex_coords[j+1];
+ bti += 2;
+ }
+ for(unsigned j=0; j<detail_model.faces.size(); j+=3){
+ faces[bfi+0] = detail_model.faces[j]+start_faces;
+ faces[bfi+1] = detail_model.faces[j+1]+start_faces;
+ faces[bfi+2] = detail_model.faces[j+2]+start_faces;
+ bfi += 3;
+ }
+ }
+
+ std::vector<BatchVertex> bvs(num_vertices);
+ unsigned vert_index = 0;
+ unsigned tex_index = 0;
+ for(unsigned i=0; i<num_vertices; ++i){
+ BatchVertex &bv = bvs[i];
+ bv.px = vertices[vert_index+0];
+ bv.py = vertices[vert_index+1];
+ bv.pz = vertices[vert_index+2];
+ bv.nx = normals[vert_index+0];
+ bv.ny = normals[vert_index+1];
+ bv.nz = normals[vert_index+2];
+ bv.tx = tangents[vert_index+0];
+ bv.ty = tangents[vert_index+1];
+ bv.tz = tangents[vert_index+2];
+ bv.bx = bitangents[vert_index+0];
+ bv.by = bitangents[vert_index+1];
+ bv.bz = bitangents[vert_index+2];
+ bv.tu = tex_coords[tex_index+0];
+ bv.tv = tex_coords[tex_index+1];
+ vert_index += 3;
+ tex_index += 2;
+ }
+ vboc.Fill(kVBOFloat | kVBOStatic, sizeof(BatchVertex)*num_vertices, &bvs[0]);
+ vboc_el.Fill(kVBOElement | kVBOStatic, sizeof(GLuint)*num_faces*3, &faces[0]);
+ instance_num_faces = detail_model.faces.size();
+}
+
+void BatchModel::StopDraw() {
+ Graphics *graphics = Graphics::Instance();
+ graphics->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY2,false);
+ graphics->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY1,false);
+ GLuint index_attrib = Shaders::Instance()->returnShaderAttrib("index", Shaders::Instance()->bound_program);
+ graphics->ResetVertexAttribArrays();
+
+ graphics->SetClientActiveTexture(0);
+ graphics->BindArrayVBO(0);
+ graphics->BindElementVBO(0);
+}
+
+void BatchModel::Draw( const std::vector<DetailInstance> &di_vec, const std::vector<mat4> &di_vec_transforms ) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "BatchModel::Draw");
+ if(di_vec.empty()){
+ return;
+ }
+
+ unsigned pass_size;
+ unsigned index = 0;
+ std::vector<mat4> transforms(batch_size);
+ std::vector<vec4> texcoords2(batch_size);
+ unsigned num_passes = (di_vec.size()-1) / batch_size + 1;
+
+ vboc_el.Bind();
+ for(unsigned i=0; i<num_passes; ++i){
+ pass_size = batch_size;
+ int temp_index_mat = index; // Need to work off a copy of index because loop was split up
+ for(unsigned j=0; j<batch_size; ++j){
+ if(temp_index_mat >= di_vec.size()){
+ pass_size = j;
+ break;
+ }
+ transforms[j] = di_vec_transforms[temp_index_mat];
+ ++temp_index_mat;
+ }
+ for(unsigned j=0; j<batch_size; ++j){
+ if(index >= di_vec.size()){
+ pass_size = j;
+ break;
+ }
+ texcoords2[j][0] = di_vec[index].tex_coord0[0];
+ texcoords2[j][1] = di_vec[index].tex_coord0[1];
+ texcoords2[j][2] = di_vec[index].embed;
+ texcoords2[j][3] = di_vec[index].height_scale;
+ ++index;
+ }
+ Shaders::Instance()->SetUniformMat4Array("transforms",transforms);
+ Shaders::Instance()->SetUniformVec4Array("texcoords2",texcoords2);
+ Graphics::Instance()->DrawElementsInstanced(GL_TRIANGLES, instance_num_faces, GL_UNSIGNED_INT, 0, pass_size);
+ ++num_detail_object_draw_calls;
+ }
+}
+
+BatchModel::BatchModel():
+ created(false)
+{
+}
+
+bool CalculatedPatch::operator<( const CalculatedPatch &other ) const {
+ return coords < other.coords;
+}
+
+bool CalculatedPatch::operator==( const CalculatedPatch &other ) const {
+ return coords == other.coords;
+}
diff --git a/Source/Graphics/detailobjectsurface.h b/Source/Graphics/detailobjectsurface.h
new file mode 100644
index 00000000..8c3c19b2
--- /dev/null
+++ b/Source/Graphics/detailobjectsurface.h
@@ -0,0 +1,198 @@
+//-----------------------------------------------------------------------------
+// Name: detailobjectsurface.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec2.h>
+#include <Math/vec4.h>
+
+#include <Asset/Asset/objectfile.h>
+#include <Asset/Asset/image_sampler.h>
+
+#include <Utility/flat_hash_map.hpp>
+#include <Graphics/drawbatch.h>
+
+#include <vector>
+#include <set>
+#include <map>
+
+class Model;
+class LightProbeCollection;
+class SceneGraph;
+
+// One instance of a detail object
+struct DetailInstance {
+ // mat4 transform; // Transformation matrix for this detail object - note: now stored separately for better bulk memory transfer patterns
+ vec2 tex_coord0; // Base texture coordinates for color/light matching - note: reduced to one coord because the other was not being used in drawing
+ float embed; // How embedded it is in the ground
+ float height_scale; // How much the height has been scaled
+};
+
+struct BatchVertex {
+ GLfloat px, py, pz; // position (0-12 bytes)
+ GLfloat nx, ny, nz; // normal (12-24 bytes)
+ GLfloat tx, ty, tz; // tangent (24-36 bytes)
+ GLfloat bx, by, bz; // bitangent (36-48 bytes)
+ GLfloat tu, tv; // texcoords (48-56 bytes)
+ float padding[2]; // (to reach 64 bytes)
+};
+
+// A patch containing many detail object instances
+struct DOPatch {
+ bool calculated;
+ std::vector<int> tris;
+ std::vector<vec3> verts;
+ std::vector<vec3> normals;
+ std::vector<vec2> tex_coords;
+ std::vector<vec2> tex_coords2;
+ std::vector<DetailInstance> detail_instances;
+ std::vector<mat4> detail_instance_transforms;
+ std::vector<float> detail_instance_origins_x;
+ std::vector<float> detail_instance_origins_y;
+ std::vector<float> detail_instance_origins_z;
+ vec3 sphere_center;
+ float sphere_radius;
+ DOPatch();
+};
+
+// A comparable array of three ints
+struct TriInt {
+ int val[3];
+ TriInt(int a = 0, int b = 0, int c = 0);
+ bool operator<(const TriInt &other) const;
+ bool operator==(const TriInt &other) const;
+};
+
+namespace std
+{
+ template <>
+ struct hash<TriInt>
+ {
+ size_t operator()(const TriInt& k) const
+ {
+ // Compute individual hash values for first, second and third
+ // http://stackoverflow.com/a/1646913/126995
+ size_t res = 17;
+ res = res * 31 + hash<float>()( (const float)k.val[0] );
+ res = res * 31 + hash<float>()( (const float)k.val[1] );
+ res = res * 31 + hash<float>()( (const float)k.val[2] );
+ return res;
+ }
+ };
+}
+
+struct CalculatedPatch {
+ TriInt coords;
+ mutable float last_used_time;
+ bool operator<(const CalculatedPatch &other) const;
+ bool operator==(const CalculatedPatch &other) const;
+};
+
+namespace std
+{
+ template <>
+ struct hash<CalculatedPatch>
+ {
+ size_t operator()(const CalculatedPatch& k) const
+ {
+ // Compute individual hash values for first, second and third
+ // http://stackoverflow.com/a/1646913/126995
+ size_t res = 17;
+ res = res * 31 + hash<TriInt>()(k.coords);
+ res = res * 31 + hash<float>()(k.last_used_time);
+ return res;
+ }
+ };
+}
+
+const unsigned batch_size = 40;
+// A model that contains "batch_size" copies of a model to reduce draw calls
+struct BatchModel {
+ bool created;
+ float height; // The height is important for controlling how embedded it is in the base surface
+ VBOContainer vboc; // The vbo containing the batch vertex, normal, and uv information
+ VBOContainer vboc_el; // The vbo containing the batch triangle indices
+ unsigned instance_num_faces; // The number of faces in each instance, so we can control how many instances we draw
+ void Create(const Model& model);
+ void Draw( const std::vector<DetailInstance> &di_vec, const std::vector<mat4> &di_vec_transforms );
+ BatchModel();
+ void StartDraw();
+ void StopDraw();
+};
+
+class DetailObjectSurface {
+ std::vector<DetailInstance> draw_detail_instances;
+ std::vector<mat4> draw_detail_instance_transforms;
+ BatchModel bm;
+ ObjectFileRef ofr;
+ const Model* base_model_ptr;
+ int detail_model_id;
+ std::vector<float> triangle_area;
+ typedef ska::flat_hash_map<TriInt, DOPatch> PatchesMap;
+ PatchesMap patches;
+ int bounding_box[6];
+ DrawBatch batch;
+ TextureAssetRef base_color_ref;
+ TextureAssetRef base_normal_ref;
+ ImageSamplerRef weight_map;
+ typedef ska::flat_hash_set<CalculatedPatch> CalculatedPatchesSet;
+ CalculatedPatchesSet calculated_patches;
+ float density;
+ float normal_conform;
+ float min_embed;
+ float max_embed;
+ float min_scale;
+ float max_scale;
+ float view_dist;
+ float overbright;
+ float jitter_degrees;
+ CollisionType collision_type;
+ bool double_sided;
+public:
+ float tint_weight;
+
+ enum DetailObjectShaderType {TERRAIN, ENVOBJECT};
+ void AttachTo( const Model& model, mat4 transform );
+ void SetBaseTextures( const TextureAssetRef& color_ref, const TextureAssetRef& normal_ref );
+ void LoadDetailModel(const std::string &path);
+ void PreDrawCamera( const mat4 &transform );
+ //void GetTrisInSphere( const vec3 &center, float radius );
+ void Cluster();
+ void Draw( const mat4 &transform, DetailObjectShaderType shader_type, vec3 color_tint, const TextureRef& light_cube, LightProbeCollection* light_probe_collection, SceneGraph *scenegraph);
+ TriInt GetTriInt( const vec3 &pos );
+ void GetTrisInPatches( mat4 transform );
+ void CalcPatchInstances( const TriInt &coords, DOPatch &patch, mat4 transform );
+ void ClearPatch( DOPatch &patch );
+ void SetDensity( float _density );
+ void SetColorTint( const vec3& tint );
+ void SetNormalConform( float _normal_conform );
+ void SetMinEmbed( float _min_embed );
+ void SetMaxEmbed( float _max_embed );
+ void SetMinScale( float _min_scale );
+ void SetMaxScale( float _max_scale );
+ void LoadWeightMap( const std::string &weight_path );
+ void SetViewDist( float _view_dist );
+ void SetJitterDegrees( float _jitter_degrees );
+ void SetOverbright( float _overbright );
+ void SetCollisionType( CollisionType _collision );
+};
diff --git a/Source/Graphics/drawbatch.cpp b/Source/Graphics/drawbatch.cpp
new file mode 100644
index 00000000..112ffa43
--- /dev/null
+++ b/Source/Graphics/drawbatch.cpp
@@ -0,0 +1,520 @@
+//-----------------------------------------------------------------------------
+// Name: drawbatch.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "drawbatch.h"
+
+#include <Graphics/textures.h>
+#include <Graphics/shaders.h>
+#include <Graphics/graphics.h>
+#include <Graphics/camera.h>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <Internal/timer.h>
+#include <Main/scenegraph.h>
+
+extern SceneLight* primary_light;
+extern Timer game_timer;
+
+//-----------------------------------------------------------------------------
+//Functions
+//-----------------------------------------------------------------------------
+
+void DrawBatch::AddUniformMat3(std::string _name, const GLfloat *_data) {
+ uniforms.resize(uniforms.size()+1);
+ uniforms.back().name = _name;
+ uniforms.back().type = _mat3;
+ uniforms.back().data.resize(9);
+ memcpy(&uniforms.back().data[0], _data, 9*sizeof(GLfloat));
+}
+
+void DrawBatch::AddUniformMat4(std::string _name, const GLfloat *_data) {
+ uniforms.resize(uniforms.size()+1);
+ uniforms.back().name = _name;
+ uniforms.back().type = _mat4;
+ uniforms.back().data.resize(16);
+ memcpy(&uniforms.back().data[0], _data, 16*sizeof(GLfloat));
+}
+
+void DrawBatch::AddUniformFloat( std::string _name, GLfloat data ) {
+ uniforms.resize(uniforms.size()+1);
+ uniforms.back().name = _name;
+ uniforms.back().type = _float;
+ uniforms.back().data.resize(1);
+ uniforms.back().data[0] = data;
+}
+
+void DrawBatch::AddUniformVec3( std::string _name, const GLfloat *_data ) {
+ uniforms.resize(uniforms.size()+1);
+ uniforms.back().name = _name;
+ uniforms.back().type = _vec3;
+ uniforms.back().data.resize(3);
+ memcpy(&uniforms.back().data[0], _data, 3*sizeof(GLfloat));
+}
+
+void DrawBatch::AddUniformVec3( std::string _name, const vec3 &_data )
+{
+ AddUniformVec3(_name, (const GLfloat*)&_data);
+}
+
+DrawBatch::DrawBatch() : visible(true),
+ no_vbo(false),
+ normal_ptr(NULL),
+ vertex_ptr(NULL),
+ face_ptr(NULL),
+ num_face_ids(0),
+ draw_times(0),
+ use_cam_pos(false),
+ use_time(false),
+ use_light(false),
+ transparent(false),
+ depth_func(GL_LEQUAL),
+ polygon_offset(false),
+ shader_id(-1),
+ to_delete(false)
+{
+ color = vec4(1.0f);
+ for(int i=0; i<8; i++){
+ tex_coord_ptr[i] = NULL;
+ tex_coord_elements[i] = 0;
+ use_vbo_texcoords[i] = true;
+ }
+ for(int i=0; i<kMaxTextures; i++){
+ texture_ref[i].clear();
+ }
+}
+
+void DrawBatch::Draw() {
+ if(to_delete || vertices.empty()){
+ return;
+ }
+
+ if(!vbo_vertices->valid() && draw_times > _vbo_threshold && !no_vbo){
+ CreateVBO(DrawBatch::FACE_VBO);
+ }
+
+ if(added_batches.empty()){
+ SetStartState();
+ SetupVertexArrays();
+
+ Graphics::Instance()->SetModelMatrix(transform, inv_transpose_transform);
+
+ DrawVertexArrays();
+ SetEndState();
+ EndVertexArrays();
+ } else {
+ const Camera& camera = (*ActiveCameras::Get());
+ SetupVertexArrays();
+ for(unsigned i=0; i<added_batches.size(); i++){
+ if(camera.checkSphereInFrustum(added_batches[i].batch->bounding_sphere_center,
+ added_batches[i].batch->bounding_sphere_radius)){
+ added_batches[i].batch->SetStartState();
+ //glPushMatrix();
+ //glMultMatrixf(added_batches[i].batch->transform);
+
+ Graphics::Instance()->SetModelMatrix(added_batches[i].batch->transform, added_batches[i].batch->inv_transpose_transform);
+
+ DrawVertexArrayRange(added_batches[i].start_face,
+ added_batches[i].end_face,
+ added_batches[i].start_vertex,
+ added_batches[i].end_vertex);
+ //glPopMatrix();
+ added_batches[i].batch->SetEndState();
+ }
+ }
+ EndVertexArrays();
+ }
+
+ draw_times++;
+}
+
+void DrawBatch::CreateVBO(CreateVBOParam create_vbo_param) {
+ if(create_vbo_param == FACE_VBO){
+ vbo_faces->Fill(kVBOElement | kVBOStatic, faces.size()*sizeof(GLuint),&faces[0]);
+ }
+ vbo_vertices->Fill(kVBOFloat | kVBOStatic, vertices.size()*sizeof(GLfloat),&vertices[0]);
+ if(!normals.empty()){
+ vbo_normals->Fill(kVBOFloat | kVBOStatic, normals.size()*sizeof(GLfloat),&normals[0]);
+ }
+ for(int i=0; i<8; i++){
+ vbo_tex_coords[i]->Dispose();
+ if(!tex_coords[i].empty() && use_vbo_texcoords[i]) {
+ vbo_tex_coords[i]->Fill(kVBOFloat | kVBOStatic, tex_coords[i].size()*sizeof(GLfloat),&tex_coords[i][0]);
+ }
+ }
+}
+
+void DrawBatch::TexCoordPointer( int size, GLfloat* data, int which_tex ) {
+ tex_coord_ptr[which_tex] = data;
+ tex_coord_elements[which_tex] = size;
+}
+
+void DrawBatch::VertexPointer( GLfloat* data ) {
+ vertex_ptr = data;
+}
+
+void DrawBatch::NormalPointer( GLfloat* data ) {
+ normal_ptr = data;
+}
+
+void DrawBatch::DrawElements( GLuint size, const GLuint* data ) {
+ face_ptr = data;
+ num_face_ids = size;
+
+ faces.resize(num_face_ids);
+ memcpy(&faces[0],face_ptr,num_face_ids*sizeof(GLuint));
+
+ GLuint max_vertex_id = 0;
+ for(unsigned i=0; i<num_face_ids; i++){
+ max_vertex_id = max(max_vertex_id,face_ptr[i]);
+ }
+
+ int num_vertices = max_vertex_id + 1;
+
+ if(normal_ptr){
+ normals.resize(num_vertices*3);
+ memcpy(&normals[0],normal_ptr,normals.size()*sizeof(GLfloat));
+ }
+
+ if(vertex_ptr){
+ vertices.resize(num_vertices*3);
+ memcpy(&vertices[0],vertex_ptr,vertices.size()*sizeof(GLfloat));
+ }
+
+ for(int i=0; i<8; i++){
+ if(tex_coord_ptr[i]){
+ tex_coords[i].resize(num_vertices*tex_coord_elements[i]);
+ memcpy(&tex_coords[i][0],tex_coord_ptr[i],tex_coords[i].size()*sizeof(GLfloat));
+ }
+ }
+
+ face_ptr = NULL;
+ normal_ptr = NULL;
+ vertex_ptr = NULL;
+ for(int i=0; i<8; i++){
+ tex_coord_ptr[i] = NULL;
+ }
+}
+
+void DrawBatch::AddBatch( const DrawBatch &other ) {
+ if(other.to_delete || other.faces.empty()){
+ return;
+ }
+
+ if(other.added_batches.size()){
+ for(unsigned i=0; i<other.added_batches.size(); i++){
+ AddBatch(*(other.added_batches[i].batch));
+ }
+ return;
+ }
+
+ if(vertices.empty()){
+ (*this)=other;
+ added_batches.resize(added_batches.size()+1);
+ added_batches.back().batch = &other;
+ added_batches.back().start_face = 0;
+ added_batches.back().end_face = other.faces.size();
+ added_batches.back().start_vertex = 0;
+ added_batches.back().end_vertex = other.vertices.size()/3;
+ return;
+ }
+
+ int existing_vertices = vertices.size();
+ int existing_faces = faces.size();
+
+ added_batches.resize(added_batches.size()+1);
+ added_batches.back().batch = &other;
+ added_batches.back().start_face = existing_faces;
+ added_batches.back().end_face = existing_faces+other.faces.size();
+
+ faces.resize(faces.size()+other.faces.size());
+ memcpy(&faces[existing_faces],
+ &other.faces[0],
+ other.faces.size()*sizeof(GLuint));
+
+ num_face_ids += other.num_face_ids;
+
+ int existing_complete_vertices = existing_vertices/3;
+ for(unsigned i=existing_faces; i<faces.size(); i++){
+ faces[i] += existing_complete_vertices;
+ }
+
+ added_batches.back().start_vertex = existing_complete_vertices;
+ added_batches.back().end_vertex = existing_complete_vertices + other.vertices.size()/3;
+
+ normals.resize(normals.size()+other.normals.size());
+ memcpy(&normals[existing_vertices],
+ &other.normals[0],
+ other.normals.size()*sizeof(GLfloat));
+
+ vertices.resize(vertices.size()+other.vertices.size());
+ memcpy(&vertices[existing_vertices],
+ &other.vertices[0],
+ other.vertices.size()*sizeof(GLfloat));
+
+
+ for(int i=0; i<8; i++){
+ if(!tex_coords[i].empty()){
+ tex_coords[i].resize(tex_coords[i].size()+other.tex_coords[i].size());
+ memcpy(&tex_coords[i][existing_vertices],
+ &other.tex_coords[i][0],
+ other.tex_coords[i].size()*sizeof(GLfloat));
+ }
+ }
+}
+
+
+void Uniform::Apply() const {
+ CHECK_GL_ERROR();
+ int bound_program = Shaders::Instance()->bound_program;
+ GLint var = Shaders::Instance()->returnShaderVariable(name,bound_program);
+
+ switch(type){
+ case _mat3:
+ Shaders::Instance()->SetUniformMat3(var,&data[0]);
+ break;
+ case _mat4:
+ Shaders::Instance()->SetUniformMat4(var,&data[0]);
+ break;
+ case _vec3:
+ Shaders::Instance()->SetUniformVec3(var,&data[0]);
+ break;
+ case _vec4:
+ Shaders::Instance()->SetUniformVec4(var,&data[0]);
+ break;
+ case _float:
+ Shaders::Instance()->SetUniformFloat(var,data[0]);
+ break;
+ default:
+ //mjh - what's the reasonable thing to do here?
+ break;
+ }
+ CHECK_GL_ERROR();
+}
+
+Uniform::Uniform():
+ individual(false)
+{
+}
+
+void DrawBatch::Dispose() {
+ normals.clear();
+ vertices.clear();
+ for(int i=0; i<8; i++){
+ tex_coords[i].clear();
+ }
+ faces.clear();
+ uniforms.clear();
+
+ vbo_faces->Dispose();
+ vbo_vertices->Dispose();
+ vbo_normals->Dispose();
+ for(int i=0; i<8; i++){
+ vbo_tex_coords[i]->Dispose();
+ }
+
+ draw_times = 0;
+}
+
+void DrawBatch::SetStartState() const {
+ Graphics* graphics = Graphics::Instance();
+ Shaders* shaders = Shaders::Instance();
+ Textures* textures = Textures::Instance();
+
+ graphics->setDepthFunc(depth_func);
+ graphics->setPolygonOffset(polygon_offset);
+ graphics->setGLState(gl_state);
+
+ shaders->setProgram(shader_id);
+
+ for(unsigned i=0; i<uniforms.size(); i++){
+ uniforms[i].Apply();
+ }
+
+ if(use_cam_pos){
+ shaders->SetUniformVec3("cam_pos",ActiveCameras::Get()->GetPos());
+ }
+
+ if(use_time){
+ shaders->SetUniformFloat("time",game_timer.GetRenderTime());
+ }
+
+ if(use_light){
+ shaders->SetUniformVec3("ws_light",primary_light->pos);
+ shaders->SetUniformVec4("primary_light_color",vec4(primary_light->color, primary_light->intensity));
+ }
+
+ shaders->SetUniformVec4("uniform_color", color);
+
+ for(int i=0; i<kMaxTextures; i++) {
+ if(texture_ref[i].valid()) {
+ textures->bindTexture(texture_ref[i], i);
+ }
+ }
+
+ if(transparent && graphics->use_sample_alpha_to_coverage){
+ glEnable( GL_SAMPLE_ALPHA_TO_COVERAGE );
+ }
+}
+
+void DrawBatch::EndVertexArrays() const {
+ for(int i=1; i<8; i++){
+ if(!tex_coords[i].empty()){
+ Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY0 + i,false);
+ }
+ }
+ Graphics::Instance()->SetClientActiveTexture(0);
+
+ Graphics::Instance()->BindArrayVBO(0);
+ Graphics::Instance()->BindElementVBO(0);
+}
+
+void DrawBatch::SetEndState() const {
+ if(transparent && Graphics::Instance()->use_sample_alpha_to_coverage){
+ glDisable( GL_SAMPLE_ALPHA_TO_COVERAGE );
+ }
+}
+
+void DrawBatch::DrawVertexArrays() const {
+ if(vbo_faces.GetConst().valid()){
+ Graphics::Instance()->DrawElements(GL_TRIANGLES, num_face_ids, GL_UNSIGNED_INT, 0);
+ } else {
+ Graphics::Instance()->DrawElements(GL_TRIANGLES, num_face_ids, GL_UNSIGNED_INT, &faces[0]);
+ }
+}
+
+void DrawBatch::DrawVertexArrayRange(GLuint start, GLuint end, GLuint start_vertex, GLuint end_vertex) const {
+ if(vbo_faces.GetConst().valid()){
+ //glDrawElements(GL_TRIANGLES, num_face_ids, GL_UNSIGNED_INT, 0);
+ Graphics::Instance()->DrawRangeElements(GL_TRIANGLES, start_vertex, end_vertex, end-start, GL_UNSIGNED_INT, (char*)NULL+start*sizeof(GLuint));
+ } else {
+ Graphics::Instance()->DrawRangeElements(GL_TRIANGLES, start_vertex, end_vertex, end-start, GL_UNSIGNED_INT, &faces[start]);
+ }
+}
+
+void DrawBatch::SetupVertexArrays() const {
+ Graphics *graphics = Graphics::Instance();
+ graphics->SetClientStates(F_VERTEX_ARRAY | F_NORMAL_ARRAY | F_TEXTURE_COORD_ARRAY0);
+
+ for(int i=0; i<8; i++){
+ if(!tex_coords[i].empty()){
+ graphics->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY0 + i,true);
+ if(vbo_tex_coords[i].GetConst().valid()){
+ vbo_tex_coords[i].GetConst().Bind();
+ glTexCoordPointer(tex_coord_elements[i], GL_FLOAT, 0, 0);
+ } else {
+ graphics->BindArrayVBO(0);
+ glTexCoordPointer(tex_coord_elements[i], GL_FLOAT, 0, &tex_coords[i][0]);
+ }
+ }
+ }
+
+ if(vbo_normals.GetConst().valid()){
+ vbo_normals.GetConst().Bind();
+ glNormalPointer(GL_FLOAT, 0, 0);
+ } else if(!normals.empty()) {
+ graphics->BindArrayVBO(0);
+ glNormalPointer(GL_FLOAT, 0, &normals[0]);
+ }
+
+ if(vbo_vertices.GetConst().valid()){
+ vbo_vertices.GetConst().Bind();
+ glVertexPointer(3, GL_FLOAT, 0, 0);
+ } else {
+ graphics->BindArrayVBO(0);
+ glVertexPointer(3, GL_FLOAT, 0, &vertices[0]);
+ }
+
+ if(vbo_faces.GetConst().valid()){
+ vbo_faces.GetConst().Bind();
+ }
+}
+
+void DrawBatch::SetUniformFloat( std::string _name, GLfloat data )
+{
+ for(unsigned i=0; i<uniforms.size(); ++i){
+ if(uniforms[i].name == _name){
+ uniforms[i].data[0] = data;
+ return;
+ }
+ }
+ AddUniformFloat(_name, data);
+}
+
+void DrawBatch::MakeUniformIndividual( std::string _name )
+{
+ for(unsigned i=0; i<uniforms.size(); ++i){
+ if(uniforms[i].name == _name){
+ uniforms[i].individual = true;
+ }
+ }
+}
+
+void DrawBatch::ApplyIndividualUniforms() const
+{
+ for(unsigned i=0; i<uniforms.size(); i++){
+ if(uniforms[i].individual){
+ uniforms[i].Apply();
+ }
+ }
+}
+
+void DrawBatch::SetUniformVec3( std::string _name, const vec3 &_data )
+{
+ for(unsigned i=0; i<uniforms.size(); ++i){
+ if(uniforms[i].name == _name){
+ memcpy(&uniforms[i].data[0],
+ (const GLfloat*)_data.entries,
+ 3*sizeof(GLfloat));
+ }
+ }
+}
+
+void DrawBatch::AddUniformVec4( std::string _name, const GLfloat *_data ) {
+ uniforms.resize(uniforms.size()+1);
+ uniforms.back().name = _name;
+ uniforms.back().type = _vec4;
+ uniforms.back().data.resize(4);
+ memcpy(&uniforms.back().data[0], _data, 4*sizeof(GLfloat));
+}
+
+void DrawBatch::AddUniformVec4( std::string _name, const vec4 &_data ) {
+ AddUniformVec4(_name, (const GLfloat*)&_data);
+}
+
+void DrawBatch::SetUniformVec4( std::string _name, const vec4 &_data ) {
+ for(unsigned i=0; i<uniforms.size(); ++i){
+ if(uniforms[i].name == _name){
+ memcpy(&uniforms[i].data[0],
+ (const GLfloat*)_data.entries,
+ 4*sizeof(GLfloat));
+ }
+ }
+}
+
+void DrawBatch::SetTransform(const mat4 &_transform)
+{
+ transform = _transform;
+ inv_transpose_transform = mat3(transform.GetInverseTranspose());
+}
diff --git a/Source/Graphics/drawbatch.h b/Source/Graphics/drawbatch.h
new file mode 100644
index 00000000..fcc717e3
--- /dev/null
+++ b/Source/Graphics/drawbatch.h
@@ -0,0 +1,152 @@
+//-----------------------------------------------------------------------------
+// Name: drawbatch.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Graphics/glstate.h>
+#include <Graphics/vbocontainer.h>
+
+#include <Math/vec4.h>
+#include <Math/mat4.h>
+#include <Math/mat3.h>
+
+#include <Asset/Asset/texture.h>
+
+#include <opengl.h>
+
+#include <vector>
+#include <string>
+
+enum UniformType {_mat3, _mat4, _vec3, _vec4, _float};
+
+class Uniform {
+public:
+ UniformType type;
+ std::string name;
+ std::vector<GLfloat> data;
+ bool individual;
+ void Apply() const;
+ Uniform();
+};
+
+class DrawBatch;
+
+class AddedBatch {
+public:
+ const DrawBatch* batch;
+ GLuint start_face;
+ GLuint end_face;
+ GLuint start_vertex;
+ GLuint end_vertex;
+};
+
+const int _vbo_threshold = 5;
+
+class DrawBatch {
+public:
+ bool visible;
+ bool no_vbo;
+ vec4 shadow_offset;
+ vec4 color;
+ std::vector<GLfloat> normals;
+ std::vector<GLfloat> vertices;
+ std::vector<GLfloat> tex_coords[8];
+ int tex_coord_elements[8];
+ std::vector<GLuint> faces;
+
+ bool use_vbo_texcoords[8];
+
+ RC_VBOContainer vbo_normals;
+ RC_VBOContainer vbo_vertices;
+ RC_VBOContainer vbo_tex_coords[8];
+ RC_VBOContainer vbo_faces;
+
+ vec3 bounding_sphere_center;
+ float bounding_sphere_radius;
+
+ GLfloat* normal_ptr;
+ GLfloat* vertex_ptr;
+ GLfloat* tex_coord_ptr[8];
+ const GLuint* face_ptr;
+ GLuint num_face_ids;
+
+ int draw_times;
+
+ std::vector<AddedBatch> added_batches;
+
+ bool use_cam_pos;
+ bool use_time;
+ bool use_light;
+
+ bool transparent;
+
+ GLState gl_state;
+
+ mat4 shadow_proj_matrix;
+ mat4 shadow_view_matrix;
+ GLenum depth_func;
+ bool polygon_offset;
+ mat4 transform;
+ mat3 inv_transpose_transform;
+
+ int shader_id;
+
+ static const int kMaxTextures = 32;
+ TextureRef texture_ref[kMaxTextures];
+
+ bool to_delete;
+
+ std::vector<Uniform> uniforms;
+ void SetTransform(const mat4 &_transform);
+ DrawBatch();
+
+ enum CreateVBOParam {NO_FACE_VBO, FACE_VBO};
+
+ void Draw();
+
+ void SetupVertexArrays() const;
+ void SetEndState() const;
+ void SetStartState() const;
+ void ApplyIndividualUniforms() const;
+ void AddUniformMat3(std::string _name, const GLfloat *_data);
+ void AddUniformMat4(std::string _name, const GLfloat *_data);
+ void AddUniformVec3(std::string _name, const GLfloat *_data);
+ void AddUniformVec3(std::string _name, const vec3 &_data);
+ void AddUniformVec4( std::string _name, const GLfloat *_data );
+ void AddUniformVec4(std::string _name, const vec4 &_data);
+ void TexCoordPointer( int size, GLfloat* data, int which_tex );
+ void VertexPointer( GLfloat* data );
+ void NormalPointer( GLfloat* data );
+ void DrawElements( GLuint size, const GLuint* data );
+ void AddBatch(const DrawBatch &other);
+ void Dispose();
+ void CreateVBO(CreateVBOParam create_vbo_param);
+ void DrawVertexArrays() const;
+ void DrawVertexArrayRange(GLuint start, GLuint end, GLuint start_vertex, GLuint end_vertex) const;
+ void EndVertexArrays() const;
+ void AddUniformFloat( std::string _name, GLfloat data );
+ void SetUniformFloat( std::string _name, GLfloat data );
+ void SetUniformVec3( std::string _name, const vec3 &_data );
+ void SetUniformVec4( std::string _name, const vec4 &_data );
+ void MakeUniformIndividual( std::string _name );
+};
diff --git a/Source/Graphics/dynamiclightcollection.cpp b/Source/Graphics/dynamiclightcollection.cpp
new file mode 100644
index 00000000..2f6e3b18
--- /dev/null
+++ b/Source/Graphics/dynamiclightcollection.cpp
@@ -0,0 +1,96 @@
+//-----------------------------------------------------------------------------
+// Name: dynamiclightcollection.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "dynamiclightcollection.hpp"
+
+#include <Graphics/camera.h>
+#include <Graphics/pxdebugdraw.h>
+#include <Graphics/graphics.h>
+#include <Graphics/shaders.h>
+#include <Graphics/sky.h>
+#include <Graphics/textures.h>
+
+#include <Physics/bulletworld.h>
+#include <Internal/profiler.h>
+#include <Game/hardcoded_assets.h>
+#include <Math/vec3math.h>
+#include <Wrappers/glm.h>
+#include <Logging/logdata.h>
+#include <Utility/assert.h>
+
+#include <cfloat>
+
+DynamicLightCollection::DynamicLightCollection() {
+ next_id = 0;
+}
+
+DynamicLightCollection::~DynamicLightCollection() {
+}
+
+int DynamicLightCollection::AddLight(const vec3& pos, const vec3 &color, float radius) {
+ dynamic_lights.resize(dynamic_lights.size() + 1);
+ DynamicLight &light = dynamic_lights.back();
+ light.pos = pos;
+ light.id = next_id++;
+ light.color = color;
+ light.radius = radius;
+ return light.id;
+}
+
+bool DynamicLightCollection::MoveLight(int id, const vec3& pos) {
+ DynamicLight* light = GetLightFromID(id);
+ if(light){
+ light->pos = pos;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool DynamicLightCollection::DeleteLight(int id) {
+ for(int i=0, len=dynamic_lights.size(); i<len; ++i){
+ if(dynamic_lights[i].id == id) {
+ dynamic_lights.erase(dynamic_lights.begin() + i);
+ return true;
+ }
+ }
+ return false;
+}
+
+void DynamicLightCollection::Init() {
+}
+
+void DynamicLightCollection::Dispose() {
+}
+
+DynamicLight* DynamicLightCollection::GetLightFromID(int id) {
+ for(int i=0, len=dynamic_lights.size(); i<len; ++i){
+ if(dynamic_lights[i].id == id) {
+ return &dynamic_lights[i];
+ }
+ }
+ return NULL;
+}
+
+void DynamicLightCollection::Draw(BulletWorld& bw) {
+}
diff --git a/Source/Graphics/dynamiclightcollection.hpp b/Source/Graphics/dynamiclightcollection.hpp
new file mode 100644
index 00000000..621ee3f8
--- /dev/null
+++ b/Source/Graphics/dynamiclightcollection.hpp
@@ -0,0 +1,72 @@
+//-----------------------------------------------------------------------------
+// Name: dynamiclightcollection.hpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Graphics/textureref.h>
+#include <Graphics/model.h>
+
+#include <Math/vec3.h>
+
+#include <opengl.h>
+
+#include <vector>
+#include <queue>
+
+struct DynamicLight {
+ int id;
+ vec3 pos;
+ vec3 color;
+ float radius;
+ // TODO: need intensity
+ // NOTE: this is just the CPU data structure, see scenegraph.cpp
+ // for ShaderLight which is the GPU data struct
+};
+
+
+// when you want to add spot lights, DO NOT change DynamicLight
+// add new DynamicSpotLight since they need different data and are processed differently
+// also DynamicLight should be renamed to DynamicPointLight to clarify its meaning
+
+
+class BulletWorld;
+class SceneGraph;
+
+class DynamicLightCollection {
+public:
+ DynamicLightCollection();
+ ~DynamicLightCollection();
+ int AddLight(const vec3& pos, const vec3 &color, float radius);
+ bool MoveLight(int id, const vec3& pos);
+ bool DeleteLight(int id);
+ void Draw(BulletWorld& bw);
+ void Init();
+ void Dispose();
+ DynamicLight* GetLightFromID(int id);
+
+private:
+ std::vector<DynamicLight> dynamic_lights;
+ int next_id;
+
+ friend class SceneGraph;
+};
diff --git a/Source/Graphics/flares.cpp b/Source/Graphics/flares.cpp
new file mode 100644
index 00000000..f199dd41
--- /dev/null
+++ b/Source/Graphics/flares.cpp
@@ -0,0 +1,481 @@
+//-----------------------------------------------------------------------------
+// Name: flares.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "flares.h"
+
+#include <Graphics/shaders.h>
+#include <Graphics/graphics.h>
+#include <Graphics/camera.h>
+
+#include <Math/vec4.h>
+#include <Math/mat4.h>
+#include <Math/vec3math.h>
+
+#include <Internal/timer.h>
+#include <Internal/profiler.h>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <Main/engine.h>
+#include <Main/scenegraph.h>
+
+#include <Physics/bulletworld.h>
+#include <Wrappers/glm.h>
+
+#include <opengl.h>
+
+#include <cassert>
+
+extern Timer game_timer;
+
+static const int _occlusion_size = 5;
+
+extern bool g_perform_occlusion_query;
+extern bool g_debug_runtime_disable_flares_draw;
+
+Flares::Flares():
+ animation(0.0f),
+ shader("flare")
+{
+ old_angle = 0.0f;
+ new_angle = 90.0f;
+
+ gl_state.blend = true;
+ gl_state.cull_face = false;
+ gl_state.depth_write = false;
+
+ shader_id = Shaders::Instance()->returnProgram(shader);
+ flare_texture_matte = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/eyeflarematte.tga", PX_SRGB, 0x0);
+ flare_texture_streaks = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/eyeflarestreaks_nocompress.tga", PX_SRGB, 0x0);
+ flare_texture_blur1 = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/eyeflarestreaksblur1_nocompress.tga", PX_SRGB, 0x0);
+ flare_texture_blur2 = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/eyeflarestreaksblur2_nocompress.tga", PX_SRGB, 0x0);
+ flare_texture_color = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/eyeflarecolor_nocompress.tga", PX_SRGB, 0x0);
+}
+
+Flares::~Flares() {
+ CleanupFlares();
+}
+
+const float _flare_grow = 0.1f;
+
+static void DrawQuad() {
+ Graphics::Instance()->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+}
+
+void Flares::DrawFlare(int which) {
+ Flare* flare = flares[which];
+ if(flare->tex_opac[0]<=0.0f &&
+ flare->tex_opac[1]<=0.0f &&
+ flare->tex_opac[2]<=0.0f)
+ {
+ return;
+ }
+
+ if(flare->visible == 0.0f &&
+ flare->slow_visible == 0.0f)
+ {
+ return;
+ }
+
+ Graphics* graphics = Graphics::Instance();
+ Textures* textures = Textures::Instance();
+ Shaders* shaders = Shaders::Instance();
+ Camera* cam = ActiveCameras::Get();
+
+ vec3 cam_to_flare;
+ vec3 cam_pos = cam->GetPos();
+
+ if(flare->distant) {
+ cam_to_flare = flare->position;
+ } else {
+ cam_to_flare = flare->position - cam_pos;
+ }
+ cam_to_flare = normalize(cam_to_flare);
+ float dot_prod = dot(cam_to_flare, cam->GetFacing());
+ if(dot_prod < 0.0f){
+ return;
+ }
+
+ float zoom = graphics->config_.screen_height() / 1100.0f;
+
+ glm::mat4 model_view;
+
+ vec3 flare_pos;
+ vec3 interp_pos = mix(flare->old_position, flare->position, game_timer.GetInterpWeight());
+ if (flare->distant) {
+ flare_pos = interp_pos + cam_pos;
+ } else {
+ flare_pos = interp_pos;
+ }
+ vec3 projected = cam->ProjectPoint(flare_pos);
+ float size = 512 * zoom * flare->size_mult;
+ model_view = glm::translate(model_view, glm::vec3(projected[0], projected[1], 0.0f));
+
+ // Draw flare itself
+ glm::mat4 model_view2 = glm::scale(model_view, glm::vec3(size*0.5f, size*0.5f, 1.0f));
+ shaders->SetUniformMat4("modelview_mat", (const GLfloat*)&model_view2);
+ float color_alpha;
+ float total_opac = flare->tex_opac[0]+
+ flare->tex_opac[1];
+ total_opac = max(0.0f,min(1.0f,total_opac));
+ float mult = powf(Graphics::Instance()->hdr_white_point, 2.2f);
+ if(total_opac>0.0f) {
+ float base_alpha = flare->slow_visible +
+ max(0.0f,(flare->slow_visible-flare->visible));
+ color_alpha = base_alpha*flare->brightness*total_opac;
+ shaders->SetUniformVec4("color", vec4(flare->color, color_alpha*0.5f * mult));
+ graphics->SetBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
+ textures->bindTexture( flare_texture_matte->GetTextureRef() );
+ DrawQuad();
+ }
+ graphics->SetBlendFunc(GL_SRC_ALPHA,GL_ONE);
+ TextureAssetRef texref;
+ for(int i=0; i<3; ++i){
+ switch(i){
+ case 0: texref = flare_texture_streaks; break;
+ case 1: texref = flare_texture_blur1; break;
+ case 2: texref = flare_texture_blur2; break;
+ }
+ float opac_mult = flare->tex_opac[i];
+
+ textures->bindTexture(texref->GetTextureRef());
+ float color_alpha = (1.0f-animation)*flare->visible*flare->brightness*opac_mult;
+ shaders->SetUniformVec4("color", vec4(flare->color, color_alpha * mult));
+ glm::mat4 scaled_model_view = glm::scale(
+ model_view2, glm::vec3(1.0f+animation*_flare_grow, 1.0f+animation*_flare_grow, 1.0f));
+ scaled_model_view = glm::rotate(
+ scaled_model_view, old_angle, glm::vec3(0.0f, 0.0f, 1.0f));
+ shaders->SetUniformMat4("modelview_mat", (const GLfloat*)&scaled_model_view);
+ DrawQuad();
+
+ color_alpha = animation*flare->visible*flare->brightness*opac_mult;
+ shaders->SetUniformVec4("color", vec4(flare->color, color_alpha * mult));
+ scaled_model_view = glm::scale(
+ model_view2, glm::vec3(1.0f-_flare_grow+animation*_flare_grow, 1.0f-_flare_grow+animation*_flare_grow, 1.0f));
+ scaled_model_view = glm::rotate(
+ scaled_model_view, new_angle, glm::vec3(0.0f, 0.0f, 1.0f));
+ shaders->SetUniformMat4("modelview_mat", (const GLfloat*)&scaled_model_view);
+ DrawQuad();
+ }
+
+ // Draw rainbow streaks
+ if(flare->tex_opac[0]>0.0f){
+ textures->bindTexture(flare_texture_color->GetTextureRef());
+
+ color_alpha = dot_prod*dot_prod*flare->visible*flare->brightness*0.1f*flare->tex_opac[0];
+ shaders->SetUniformVec4("color", vec4(flare->color, color_alpha * 0.7f * mult));
+ glm::mat4 inner_model_view = glm::translate(
+ model_view, glm::vec3(
+ ((graphics->config_.screen_width()/2)-(GLfloat)projected[0])*0.3f,
+ ((graphics->config_.screen_height()/2)-(GLfloat)projected[1])*0.3f,
+ 0.0f));
+ inner_model_view = glm::scale(
+ inner_model_view, glm::vec3(size * 1.5f, size * 1.5f, 1.0f));
+ shaders->SetUniformMat4("modelview_mat", (const GLfloat*)&inner_model_view);
+ DrawQuad();
+
+ shaders->SetUniformVec4("color", vec4(flare->color, color_alpha * mult));
+ inner_model_view = glm::translate(
+ model_view, glm::vec3(
+ ((graphics->config_.screen_width()/2)-(GLfloat)projected[0])*0.6f,
+ ((graphics->config_.screen_height()/2)-(GLfloat)projected[1])*0.6f,
+ 0.0f));
+ inner_model_view = glm::scale(
+ inner_model_view, glm::vec3(size * 3.0f, size * 3.0f, 1.0f));
+ shaders->SetUniformMat4("modelview_mat", (const GLfloat*)&inner_model_view);
+ DrawQuad();
+ }
+}
+
+void Flares::OcclusionQuery(int which){
+ Graphics* graphics = Graphics::Instance();
+ Textures* textures = Textures::Instance();
+ Shaders* shaders = Shaders::Instance();
+ Camera* cam = ActiveCameras::Get();
+ Flare* flare = flares[which];
+ CHECK_GL_ERROR();
+ int active_cam_id = ActiveCameras::Instance()->GetID();
+ if((int)flare->query.size() <= active_cam_id){
+ flare->query.resize(active_cam_id+1);
+ }
+ OccQuery& query = flare->query[active_cam_id];
+ if( g_perform_occlusion_query ) {
+ if(!query.created){
+ glGenQueries( 1, &query.id );
+ query.created = true;
+ }
+ CHECK_GL_ERROR();
+ GLuint count = 0;
+ if(query.started){
+ PROFILER_ZONE(g_profiler_ctx, "glGetQueryObjectuiv a");
+ glGetQueryObjectuiv( query.id, GL_QUERY_RESULT_AVAILABLE, &count);
+ } else {
+ count = 1;
+ }
+ CHECK_GL_ERROR();
+ if(count) {
+ if(query.started){
+ PROFILER_ZONE(g_profiler_ctx, "glGetQueryObjectuiv b");
+ glGetQueryObjectuiv( query.id, GL_QUERY_RESULT, &count);
+ unsigned total = _occlusion_size * _occlusion_size *
+ max(1,graphics->config_.FSAA_samples());
+ flare->visible = min(1.0f,max(0.0f,((float)count)/((float)total)));
+ flare->visible *= flare->visible_mult;
+ } else {
+ query.started = true;
+ flare->visible = 0.0f;
+ }
+ {
+ PROFILER_ZONE(g_profiler_ctx, "glBeginQuery");
+ //LOGI << "flare_begin_query " << query.id << std::endl;
+ glBeginQuery( GL_SAMPLES_PASSED, query.id );
+ }
+ if(flare->visible_mult > 0.0f){
+ vec3 cam_to_flare;
+ vec3 cam_pos = cam->GetPos();
+
+ if(flare->distant) {
+ cam_to_flare = flare->position;
+ } else {
+ cam_to_flare = flare->position - cam_pos;
+ }
+ cam_to_flare = normalize(cam_to_flare);
+ float dot_prod = dot(cam_to_flare, cam->GetFacing());
+ if(dot_prod > 0.0f) {
+ CHECK_GL_ERROR();
+ vec3 flare_pos;
+ if (flare->distant) {
+ flare_pos = flare->position + cam_pos;
+ } else {
+ flare_pos = flare->position;
+ }
+ vec3 projected = cam->ProjectPoint(flare_pos);
+
+ CHECK_GL_ERROR();
+ glm::mat4 modelview;
+ if(flare->distant){
+ modelview = glm::translate(modelview, glm::vec3(projected[0],projected[1],-99.9f));
+ } else {
+ modelview = glm::translate(modelview, glm::vec3(projected[0],projected[1],projected[2]*-200.0f+100.0f));
+ }
+ CHECK_GL_ERROR();
+ modelview = glm::scale(modelview, glm::vec3(_occlusion_size*0.5f, _occlusion_size*0.5f, 1.0f));
+ shaders->SetUniformMat4("modelview_mat", (const GLfloat*)&modelview);
+ shaders->SetUniformVec4("color", vec4(1.0f, 0.0f, 0.0f, 0.01f));
+
+ textures->bindBlankTexture();
+ graphics->setDepthTest(true);
+ DrawQuad();
+ graphics->setDepthTest(false);
+ CHECK_GL_ERROR();
+ }
+ }
+ CHECK_GL_ERROR();
+ {
+ PROFILER_ZONE(g_profiler_ctx, "glEndQuery");
+ //LOGI << "flare_end_query " << query.id << std::endl;
+ glEndQuery( GL_SAMPLES_PASSED );
+ }
+ CHECK_GL_ERROR();
+
+ }
+ } else {
+ flare->visible = 1.0f;
+ }
+}
+
+void Flares::Draw(Flares::FlareType flare_type) {
+ if (g_debug_runtime_disable_flares_draw) {
+ return;
+ }
+
+ CHECK_GL_ERROR();
+ Graphics* graphics = Graphics::Instance();
+ Shaders* shaders = Shaders::Instance();
+ // Set basic gl state
+ graphics->setGLState(gl_state);
+ Shaders::Instance()->setProgram(shader_id);
+ // Set viewport projection matrix
+ glm::mat4 proj;
+ proj = glm::ortho(0.0f, (float)graphics->viewport_dim[2], 0.0f, (float)graphics->viewport_dim[3], -100.0f, 100.0f);
+ shaders->SetUniformMat4("proj_mat", (const GLfloat*)&proj);
+ // Make sure quad-drawing VBOs are filled
+ static const GLfloat data[] = {
+ 0, 0, -1, -1,
+ 1, 0, 1, -1,
+ 1, 1, 1, 1,
+ 0, 1, -1, 1
+ };
+ static const GLuint index[] = {
+ 0, 1, 2, 0, 3, 2
+ };
+ if(!vert_vbo.valid()){
+ PROFILER_ZONE(g_profiler_ctx, "vert_vbo fill");
+ vert_vbo.Fill(kVBOStatic | kVBOFloat, sizeof(data), (void*)data);
+ index_vbo.Fill(kVBOStatic | kVBOElement, sizeof(index), (void*)index);
+ }
+ CHECK_GL_ERROR();
+ // Assign vertex attributes
+ int vert_attrib_id = shaders->returnShaderAttrib("vert_attrib", shaders->bound_program);
+ int tex_attrib_id = shaders->returnShaderAttrib("tex_attrib", shaders->bound_program);
+ CHECK_GL_ERROR();
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ graphics->EnableVertexAttribArray(tex_attrib_id);
+ vert_vbo.Bind();
+ index_vbo.Bind();
+ CHECK_GL_ERROR();
+ glVertexAttribPointer(vert_attrib_id, 2, GL_FLOAT, false, 4*sizeof(GLfloat), (const void*)(sizeof(GLfloat)*2));
+ glVertexAttribPointer(tex_attrib_id, 2, GL_FLOAT, false, 4*sizeof(GLfloat), 0);
+ CHECK_GL_ERROR();
+
+ for(unsigned i=0; i<flares.size(); i++) {
+ CHECK_GL_ERROR();
+ bool to_draw = false;
+ if(flare_type == Flares::kSharp){
+ PROFILER_ZONE(g_profiler_ctx, "OcclusionQuery");
+ CHECK_GL_ERROR();
+ OcclusionQuery(i);
+ CHECK_GL_ERROR();
+ if(flares[i]->diffuse<_soft_glare_threshold) {
+ to_draw = true;
+ }
+ } else {
+ if(flares[i]->diffuse>=_soft_glare_threshold) {
+ to_draw = true;
+ }
+ }
+ CHECK_GL_ERROR();
+ if(to_draw) {
+ PROFILER_ZONE(g_profiler_ctx, "DrawFlare");
+ DrawFlare(i);
+ }
+ CHECK_GL_ERROR();
+ }
+
+ // Reset vertex attribute state
+ CHECK_GL_ERROR();
+ graphics->ResetVertexAttribArrays();
+ CHECK_GL_ERROR();
+ graphics->BindElementVBO(0);
+ graphics->BindArrayVBO(0);
+ CHECK_GL_ERROR();
+}
+
+const float _jitter = 0.0f;
+const float _visible_inertia = 0.6f;
+const float _slow_visible_inertia = 0.8f;
+const float _flicker_speed = 1.0f;
+
+void Flares::Update(float timestep) {
+ animation += timestep*_flicker_speed;
+ if(animation > 1.0f) {
+ animation -= 1.0f;
+ old_angle = new_angle;
+ new_angle = RangedRandomFloat(0.0f,360.0f);
+ }
+ for (unsigned i=0; i<flares.size(); i++) {
+ Flare* flare = flares[i];
+ flare->old_position = flare->position;
+ flare->slow_visible = mix(flare->visible,flare->slow_visible,_slow_visible_inertia);
+ if(flare->visible == 0.0f && flare->slow_visible < 0.01f){
+ flare->slow_visible = 0.0f;
+ }
+ const float _fade_speed = 3.0f;
+ const float fade_step = _fade_speed * timestep;
+ int curr_tex = -1;
+ if(flare->diffuse<_sharp_glare_threshold) {
+ curr_tex = 0;
+ } else if (flare->diffuse<_soft_glare_threshold){
+ curr_tex = 1;
+ } else if (flare->diffuse<_no_glare_threshold) {
+ curr_tex = 2;
+ }
+ for(int i=0; i<3; ++i){
+ if(curr_tex == i){
+ flare->tex_opac[i] = min(1.0f, flare->tex_opac[i] + fade_step);
+ } else {
+ flare->tex_opac[i] = max(0.0f, flare->tex_opac[i] - fade_step);
+ }
+ }
+ }
+}
+
+Flare* Flares::MakeFlare(vec3 _position, float _brightness, bool _distant) {
+ Flare* flare = new Flare();
+ flare->position = _position;
+ flare->brightness = _brightness;
+ flare->distant = _distant;
+ flare->diffuse = 1.0f;
+ flare->visible = 0.0f;
+ flare->slow_visible = 0.0f;
+ flare->color = vec3(1.0f, 1.0f, 1.0f);
+ flare->tex_opac[0] = 1.0f;
+ flare->tex_opac[1] = 0.0f;
+ flare->tex_opac[2] = 0.0f;
+ flare->size_mult = 1.0f;
+ flare->visible_mult = 1.0f;
+ flare->old_position = _position;
+ flares.push_back(flare);
+ return flare;
+}
+
+void Flares::DeleteFlare(Flare* flare){
+ std::vector<Flare*>::iterator flare_iter = std::find(flares.begin(), flares.end(), flare);
+ if(flare_iter != flares.end()){
+ Flare* flare = (*flare_iter);
+ for(unsigned i=0; i<flare->query.size(); ++i){
+ if(flare->query[i].created){
+ glDeleteQueries(1,&flare->query[i].id);
+ }
+ }
+ delete flare;
+ flares.erase(flare_iter);
+ }
+}
+
+void Flares::GetShaderNames(std::map<std::string, int>& preload_shaders) {
+ preload_shaders[shader] = 0;
+}
+
+void Flares::CleanupFlares() {
+ std::vector<Flare*>::iterator it = flares.begin();
+ for(;it != flares.end(); ++it)
+ {
+ Flare* flare = (*it);
+ for(unsigned i=0; i<flare->query.size(); ++i){
+ if(flare->query[i].created){
+ glDeleteQueries(1,&flare->query[i].id);
+ }
+ }
+ delete (*it);
+ }
+ flares.clear();
+}
+
+OccQuery::OccQuery():
+ started(false),
+ created(false),
+ id(0)
+{
+}
diff --git a/Source/Graphics/flares.h b/Source/Graphics/flares.h
new file mode 100644
index 00000000..f547bc0c
--- /dev/null
+++ b/Source/Graphics/flares.h
@@ -0,0 +1,114 @@
+//-----------------------------------------------------------------------------
+// Name: flares.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Graphics/glstate.h>
+#include <Graphics/textureref.h>
+#include <Graphics/vbocontainer.h>
+
+#include <Math/vec3.h>
+
+#include <Asset/Asset/texture.h>
+
+#include <opengl.h>
+
+#include <vector>
+#include <map>
+
+//-----------------------------------------------------------------------------
+// Class Definition
+//-----------------------------------------------------------------------------
+
+const float _sharp_glare_threshold = 1.5f;
+const float _soft_glare_threshold = 2.5f;
+const float _no_glare_threshold = 4.0f;
+
+struct OccQuery{
+ bool started;
+ bool created;
+ GLuint id;
+
+ OccQuery();
+};
+
+struct Flare {
+ std::vector<OccQuery> query;
+ vec3 position;
+ vec3 old_position;
+ float brightness;
+ float diffuse;
+ bool distant;
+ float visible;
+ float slow_visible;
+ vec3 color;
+ float tex_opac[3];
+ float size_mult;
+ float visible_mult;
+};
+
+class SceneGraph;
+
+class Flares{
+public:
+ Flares();
+ ~Flares();
+
+ enum FlareType {
+ kDiffuse,
+ kSharp
+ };
+
+ SceneGraph *scenegraph;
+
+ Flare* MakeFlare(vec3 _position, float _brightness, bool _distant);
+ void CleanupFlares();
+ void Draw(FlareType flare_type);
+ void Update(float timestep);
+ void DeleteFlare(Flare* flare);
+ std::vector<Flare*> flares;
+
+ void GetShaderNames(std::map<std::string, int>& preload_shaders);
+
+private:
+ VBOContainer vert_vbo;
+ VBOContainer index_vbo;
+ float animation;
+ float old_angle;
+ float new_angle;
+ GLState gl_state;
+ int shader_id;
+ const char* shader;
+ TextureAssetRef flare_texture_matte;
+ TextureAssetRef flare_texture_streaks;
+ TextureAssetRef flare_texture_blur1;
+ TextureAssetRef flare_texture_blur2;
+ TextureAssetRef flare_texture_color;
+ void DrawFlare(int which);
+ void OcclusionQuery(int which);
+ void DrawOcclusion(int which);
+public:
+
+
+};
+
diff --git a/Source/Graphics/font_renderer.cpp b/Source/Graphics/font_renderer.cpp
new file mode 100644
index 00000000..f6e39015
--- /dev/null
+++ b/Source/Graphics/font_renderer.cpp
@@ -0,0 +1,264 @@
+//-----------------------------------------------------------------------------
+// Name: font_renderer.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "font_renderer.h"
+
+#include <Graphics/textures.h>
+#include <Graphics/graphics.h>
+
+#include <Internal/error.h>
+#include <Compat/fileio.h>
+#include <Internal/filesystem.h>
+#include <Images/texture_data.h>
+#include <Logging/logdata.h>
+
+#include <vector>
+#include <map>
+#include <string>
+#include <list>
+
+// Keep track of which font files have been loaded, so we don't have to load them again
+class FontFiles {
+ public:
+ // Using vector<char> just to have a convenient self-deleting structure
+ // to keep track of a block of memory
+ typedef std::vector<unsigned char> MemoryBlock;
+ MemoryBlock* GetFontFileMemory(const std::string &path);
+ private:
+ typedef std::map<std::string, MemoryBlock*> FontFileMap;
+ FontFileMap font_files_;
+ std::list<MemoryBlock> memory_blocks;
+};
+
+FontFiles::MemoryBlock* FontFiles::GetFontFileMemory( const std::string &abs_path ) {
+ // Check if we already loaded this file
+ FontFileMap::iterator it = font_files_.find(abs_path);
+ if(it != font_files_.end()){
+ return it->second;
+ } else {
+ // File not loaded yet, so load it
+ FILE *file = my_fopen(abs_path.c_str(), "rb");
+ if( file == NULL ) {
+ FatalError("Error","Failed to open font file: %s\n%s", abs_path.c_str(), strerror(errno));
+ }
+ // Determine the size of the file
+ fseek(file, 0, SEEK_END);
+ int len = ftell(file);
+ fseek(file, 0, SEEK_SET);
+ // Read the entire file
+ memory_blocks.resize(memory_blocks.size()+1);
+ MemoryBlock &memory_block = memory_blocks.back();
+ memory_block.resize(len);
+ int c = fread(&memory_block[0], len, 1, file);
+ if( c == 0 ) {
+ FatalError("Error","Failed to read font file: %s\n%s", abs_path.c_str(), strerror(errno));
+ }
+ fclose(file);
+ font_files_[abs_path] = &memory_block;
+ return &memory_block;
+ }
+}
+
+struct FontFace {
+ FT_Face face;
+};
+
+struct FontRendererImpl {
+ FontFiles font_files_;
+ FT_Library freetype_library_;
+ typedef std::pair<std::string, int> FontData;
+ typedef std::map<FontData, int> FontIDMap;
+ typedef std::map<int, FontFace> FontMap;
+ FontIDMap face_ids_;
+ FontMap faces_;
+
+ ~FontRendererImpl();
+
+ int GetFontFaceID(const std::string& file_path, int pixel_height);
+ void SetTransform(int face_id, FT_Matrix *matrix, FT_Vector *vector);
+ FT_GlyphSlot RenderCharacterBitmap(int face_id, uint32_t character, FontRenderer::RCB_Flags flags);
+ FontRendererImpl();
+ void GetKerning(int face_id, char character_a, char character_b, FT_Vector *vec);
+ int GetFontInfo( int font_face_id, FontRenderer::FontInfo font_info );
+};
+
+
+FontRendererImpl::~FontRendererImpl() {
+ for (FontMap::iterator it = faces_.begin(); it != faces_.end(); ++it) {
+ FontFace &face = it->second;
+ FT_Done_Face(face.face);
+ face.face = NULL;
+ }
+
+ faces_.clear();
+
+ FT_Done_FreeType(freetype_library_);
+ freetype_library_ = NULL;
+}
+
+
+int FontRendererImpl::GetFontFaceID( const std::string& file_path, int pixel_height ) {
+ // Check if we have already loaded this font at this size
+ FontData font_data;
+ static const int kBufSize = 1024;
+ char abs_path[kBufSize];
+ if(FindFilePath(file_path.c_str(), abs_path, kBufSize, kDataPaths | kModPaths) == -1){
+ FatalError("Error", "Could not find %s", file_path.c_str());
+ }
+ font_data.first = abs_path;
+ font_data.second = pixel_height;
+ FontIDMap::iterator it = face_ids_.find(font_data);
+ if(it != face_ids_.end()){
+ return it->second;
+ } else {
+ FontFiles::MemoryBlock* memory_block = font_files_.GetFontFileMemory(abs_path);
+ // Find unused id
+ int unused_id=0;
+ while(faces_.find(unused_id) != faces_.end()){
+ ++unused_id;
+ }
+ face_ids_[font_data] = unused_id;
+ // Create face from loaded font file
+ FontFace &font_face = faces_[unused_id];
+ FT_Face &face = font_face.face;
+ int error = FT_New_Memory_Face( freetype_library_,
+ (FT_Byte*)&memory_block->at(0),
+ memory_block->size(),
+ 0 /*Get the first face in file*/,
+ &face );
+ if(error) {
+ FatalError("Error","Failed to extract face from font: %s", abs_path);
+ }
+ // Set face size
+ error = FT_Set_Char_Size(
+ face, /* handle to face object */
+ 0, /* char_width in 1/64th of points */
+ pixel_height*64, /* char_height in 1/64th of points */
+ 72, /* horizontal device resolution */
+ 72); /* vertical device resolution */
+ if(error) {
+ FatalError("Error","Failed to set font face size");
+ }
+ return unused_id;
+ }
+}
+
+FontRendererImpl::FontRendererImpl() {
+ int error = FT_Init_FreeType(&freetype_library_);
+ if(error){
+ FatalError("Error","Could not initialize freetype library");
+ }
+}
+
+void FontRendererImpl::SetTransform( int face_id, FT_Matrix *matrix, FT_Vector *vector ) {
+ FT_Set_Transform(faces_[face_id].face, matrix, vector);
+}
+
+FT_GlyphSlot FontRendererImpl::RenderCharacterBitmap(int face_id, uint32_t character, FontRenderer::RCB_Flags flags){
+ FT_Face face = faces_[face_id].face;
+ // Get index of a glyph
+ int glyph_index = FT_Get_Char_Index(face, character);
+ // Load glyph into slot
+ int error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT );
+ if(error) {
+ FatalError("Error","Failed to load font glyph into slot");
+ }
+ // Render glyph in slot
+ if(flags & FontRenderer::RCB_RENDER){
+ error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL );
+ if(error) {
+ FatalError("Error","Failed to render glyph");
+ }
+ }
+ return face->glyph;
+}
+
+void FontRendererImpl::GetKerning(int face_id, char character_a, char character_b, FT_Vector *vec){
+ FT_Face face = faces_[face_id].face;
+ FT_Bool use_kerning = FT_HAS_KERNING( face );
+ if(use_kerning){
+ int glyph_index_a = FT_Get_Char_Index(face, character_a);
+ int glyph_index_b = FT_Get_Char_Index(face, character_b);
+ FT_Get_Kerning(face, glyph_index_a, glyph_index_b, FT_KERNING_DEFAULT, vec);
+ } else {
+ vec->x = 0;
+ vec->y = 0;
+ }
+}
+
+
+void FontRenderer::SetTransform( int face_id, FT_Matrix *matrix, FT_Vector *vector ) {
+ impl_->SetTransform(face_id, matrix, vector);
+}
+
+FT_GlyphSlot FontRenderer::RenderCharacterBitmap(int face_id, uint32_t character, RCB_Flags flags){
+ return impl_->RenderCharacterBitmap(face_id, character, flags);
+}
+
+int FontRenderer::GetFontFaceID( const std::string& file_path, int pixel_height ) {
+ return impl_->GetFontFaceID(file_path, pixel_height);
+}
+
+FontRenderer::FontRenderer() {
+ impl_ = new FontRendererImpl();
+}
+
+FontRenderer::FontRenderer(const FontRenderer& other) {
+ *impl_ = *other.impl_;
+}
+
+FontRenderer::~FontRenderer() {
+ delete impl_;
+}
+
+void FontRenderer::PreLoadFont( const std::string& rel_path ) {
+ char abs_path[kPathSize];
+ if(FindFilePath(rel_path.c_str(), abs_path, kPathSize, kModPaths|kDataPaths) == -1){
+ FatalError("Error", "Could not find font file: %s", rel_path.c_str());
+ } else {
+ impl_->font_files_.GetFontFileMemory(abs_path);
+ }
+}
+
+void FontRenderer::GetKerning(int face_id, char character_a, char character_b, FT_Vector *vec) {
+ impl_->GetKerning(face_id, character_a, character_b, vec);
+}
+
+int FontRendererImpl::GetFontInfo( int font_face_id, FontRenderer::FontInfo font_info ) {
+ const FT_Face &face = faces_[font_face_id].face;
+ const FT_Size_Metrics &metrics = face->size->metrics;
+ switch(font_info){
+ case FontRenderer::INFO_ASCENDER:
+ return metrics.ascender;
+ case FontRenderer::INFO_DESCENDER:
+ return metrics.descender;
+ case FontRenderer::INFO_HEIGHT:
+ return metrics.height;
+ default:
+ return 0;
+ }
+}
+
+int FontRenderer::GetFontInfo(int font_face_id, FontInfo font_info) {
+ return impl_->GetFontInfo(font_face_id, font_info);
+}
diff --git a/Source/Graphics/font_renderer.h b/Source/Graphics/font_renderer.h
new file mode 100644
index 00000000..6ca50d33
--- /dev/null
+++ b/Source/Graphics/font_renderer.h
@@ -0,0 +1,58 @@
+//-----------------------------------------------------------------------------
+// Name: font_renderer.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/integer.h>
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+#include <string>
+
+struct FontRendererImpl;
+
+class FontRenderer {
+public:
+ enum FontInfo {INFO_ASCENDER, INFO_DESCENDER, INFO_HEIGHT};
+ enum RCB_Flags{RCB_METRIC = 1, RCB_RENDER = 2};
+ void PreLoadFont(const std::string& file_path);
+ int GetFontFaceID(const std::string& file_path, int pixel_height);
+ int GetFontInfo(int font_face_id, FontInfo font_info);
+ void SetTransform(int face_id, FT_Matrix *matrix, FT_Vector *vector);
+ FT_GlyphSlot RenderCharacterBitmap(int face_id, uint32_t character, RCB_Flags flags);
+
+ static FontRenderer* Instance(FontRenderer* ptr = NULL) {
+ static FontRenderer* font_renderer = NULL;
+ if(ptr){
+ font_renderer = ptr;
+ }
+ return font_renderer;
+ }
+ void GetKerning(int face_id, char character_a, char character_b, FT_Vector *vec);
+ FontRenderer();
+ FontRenderer(const FontRenderer& other);
+ ~FontRenderer();
+private:
+ FontRendererImpl *impl_;
+};
diff --git a/Source/Graphics/geometry.cpp b/Source/Graphics/geometry.cpp
new file mode 100644
index 00000000..fa699d7c
--- /dev/null
+++ b/Source/Graphics/geometry.cpp
@@ -0,0 +1,809 @@
+//-----------------------------------------------------------------------------
+// Name: geometry.cpp
+// Author: Freeglut
+// License: X-Consortium license
+// Description: Data and utility functions for rendering several useful
+// geometric shapes. This code is a modified version of the
+// code found in "freeglut_teapot.c" and "freeglut_geometry.c",
+// which is part of the open source project, Freeglut.
+// http://freeglut.sourceforge.net/
+//
+// Modified by Wolfire Games LLC for use in the project
+// Overgrowth.
+//
+// ----------------------------------------------------------------------------
+// Freeglut Copyright
+// ------------------
+//
+// Freeglut code without an explicit copyright is covered by the following
+// copyright :
+//
+// Copyright(c) 1999 - 2000 Pawel W.Olszta.All Rights Reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this softwareand associated documentation files(the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell
+// copies or substantial portions of the Software.
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL
+// PAWEL W.OLSZTA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+// Except as contained in this notice, the name of Pawel W.Olszta shall not be
+// used in advertising or otherwise to promote the sale, use or other dealings
+// in this Software without prior written authorization from Pawel W.Olszta.
+// ---------------------------------------------------------------------------
+/*
+ * freeglut_geometry.c
+ *
+ * Freeglut geometry rendering methods.
+ *
+ * Copyright (c) 1999-2000 Pawel W. Olszta. All Rights Reserved.
+ * Written by Pawel W. Olszta, <olszta@sourceforge.net>
+ * Creation date: Fri Dec 3 1999
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PAWEL W. OLSZTA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+/*
+ * freeglut_teapot.c
+ *
+ * Teapot(tm) rendering code.
+ *
+ * Copyright (c) 1999-2000 Pawel W. Olszta. All Rights Reserved.
+ * Written by Pawel W. Olszta, <olszta@sourceforge.net>
+ * Creation date: Fri Dec 24 1999
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PAWEL W. OLSZTA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+/*
+ * Original teapot code copyright follows:
+ */
+/*
+ * (c) Copyright 1993, Silicon Graphics, Inc.
+ *
+ * ALL RIGHTS RESERVED
+ *
+ * Permission to use, copy, modify, and distribute this software
+ * for any purpose and without fee is hereby granted, provided
+ * that the above copyright notice appear in all copies and that
+ * both the copyright notice and this permission notice appear in
+ * supporting documentation, and that the name of Silicon
+ * Graphics, Inc. not be used in advertising or publicity
+ * pertaining to distribution of the software without specific,
+ * written prior permission.
+ *
+ * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU
+ * "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR
+ * OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. IN NO
+ * EVENT SHALL SILICON GRAPHICS, INC. BE LIABLE TO YOU OR ANYONE
+ * ELSE FOR ANY DIRECT, SPECIAL, INCIDENTAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER,
+ * INCLUDING WITHOUT LIMITATION, LOSS OF PROFIT, LOSS OF USE,
+ * SAVINGS OR REVENUE, OR THE CLAIMS OF THIRD PARTIES, WHETHER OR
+ * NOT SILICON GRAPHICS, INC. HAS BEEN ADVISED OF THE POSSIBILITY
+ * OF SUCH LOSS, HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * ARISING OUT OF OR IN CONNECTION WITH THE POSSESSION, USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ *
+ * US Government Users Restricted Rights
+ *
+ * Use, duplication, or disclosure by the Government is subject to
+ * restrictions set forth in FAR 52.227f.19(c)(2) or subparagraph
+ * (c)(1)(ii) of the Rights in Technical Data and Computer
+ * Software clause at DFARS 252.227f-7013 and/or in similar or
+ * successor clauses in the FAR or the DOD or NASA FAR
+ * Supplement. Unpublished-- rights reserved under the copyright
+ * laws of the United States. Contractor/manufacturer is Silicon
+ * Graphics, Inc., 2011 N. Shoreline Blvd., Mountain View, CA
+ * 94039-7311.
+ *
+ * OpenGL(TM) is a trademark of Silicon Graphics, Inc.
+ */
+//
+// The following functions are defined here:
+//
+// void renderWireTeapot(GLdouble size);
+// void renderSolidTeapot(GLdouble size);
+// void renderWireCube(GLdouble size);
+// void renderSolidCube(GLdouble size);
+// void renderWireSphere(GLdouble radius, GLint slices, GLint stacks);
+// void renderSolidSphere(GLdouble radius, GLint slices, GLint stacks);
+// void renderWireCone(GLdouble base, GLdouble height, GLint slices, GLint stacks);
+// void renderSolidCone(GLdouble base, GLdouble height, GLint slices, GLint stacks);
+// void renderWireCylinder(GLdouble base, GLdouble height, GLint slices, GLint stacks);
+// void renderSolidCyliner(GLdouble base, GLdouble height, GLint slices, GLint stacks);
+// void renderWireTorus(GLdouble innerRadius, GLdouble outerRadius, GLint sides, GLint rings);
+// void renderSolidTorus(GLdouble innerRadius, GLdouble outerRadius, GLint sides, GLint rings);
+// void renderWireDodecahedron(void);
+// void renderSolidDodecahedron(void);
+// void renderWireOctahedron(void);
+// void renderSolidOctahedron(void);
+// void renderWireTetrahedron(void);
+// void renderSolidTetrahedron(void);
+// void renderWireIcosahedron(void);
+// void renderSolidIcosahedron(void);
+// void renderWireSierpinskiSponge(int num_levels, GLdouble offset[3], GLdouble scale);
+// void renderSolidSierpinskiSponge(int num_levels, GLdouble offset[3], GLdouble scale);
+//-----------------------------------------------------------------------------
+
+#include <Graphics/geometry.h>
+#include <Graphics/graphics.h>
+
+#include <Math/vec3math.h>
+#include <Memory/allocation.h>
+
+static void circleTable(double **sint,double **cost,const int n);
+
+/* -- INTERFACE FUNCTIONS -------------------------------------------------- */
+
+/*
+ * Compute lookup table of cos and sin values forming a cirle
+ *
+ * Notes:
+ * It is the responsibility of the caller to free these tables
+ * The size of the table is (n+1) to form a connected loop
+ * The last entry is exactly the same as the first
+ * The sign of n can be flipped to get the reverse loop
+ */
+
+static void circleTable(double **sint,double **cost,const int n)
+{
+ int i;
+
+ /* Table size, the sign of n flips the circle direction */
+
+ const int size = abs(n);
+
+ /* Determine the angle between samples */
+
+ const double angle = 2*PI/(double)n;
+
+ /* Allocate memory for n samples, plus duplicate of first entry at the end */
+
+ *sint = (double *) calloc(sizeof(double), size+1);
+ *cost = (double *) calloc(sizeof(double), size+1);
+
+ /* Bail out if memory allocation fails, fgError never returns */
+
+ if (!(*sint) || !(*cost))
+ {
+ OG_FREE(*sint);
+ *sint = NULL;
+ OG_FREE(*cost);
+ *cost = NULL;
+ FatalError("Error", "Failed to allocate memory in circleTable");
+ }
+
+ /* Compute cos and sin around the circle */
+
+ for (i=0; i<size; i++)
+ {
+ (*sint)[i] = sin(angle*i);
+ (*cost)[i] = cos(angle*i);
+ }
+
+ /* Last sample is duplicate of the first */
+
+ (*sint)[size] = (*sint)[0];
+ (*cost)[size] = (*cost)[0];
+}
+
+void GetWireCylinderVertArray(GLint slices, std::vector<vec3> &data)
+{
+ // x right, y up, z forward
+ float rotationStep = (2.0f*PI_f)/(float)slices;
+ float radius = 0.5f;
+ float halfheight = 0.5f;
+
+ vec3 centerTop( 0,halfheight,0 );
+ vec3 centerBottom( 0, -halfheight,0);
+
+ data.reserve( slices * 2 * 10 );
+
+ for( int i = 0; i < slices; i++ )
+ {
+ float rotation1 = i*rotationStep;
+ float rotation2 = (i+1)*rotationStep;
+
+ vec3 offset1(radius*(cos(rotation1)-sin(rotation1)), 0.0f, radius*(sin(rotation1)+cos(rotation1)));
+ vec3 pointTop1 = offset1+centerTop;
+ vec3 pointBottom1 = offset1+centerBottom;
+
+ vec3 offset2(radius*(cos(rotation2)-sin(rotation2)), 0.0f, radius*(sin(rotation2)+cos(rotation2)));
+ vec3 pointTop2 = offset2+centerTop;
+ vec3 pointBottom2 = offset2+centerBottom;
+
+ data.push_back( centerTop );
+ data.push_back( pointTop1 );
+
+ data.push_back( pointTop1 );
+ data.push_back( pointTop2 );
+
+ data.push_back( pointTop1 );
+ data.push_back( pointBottom1 );
+
+ data.push_back( pointBottom1 );
+ data.push_back( pointBottom2 );
+
+ data.push_back( pointBottom1 );
+ data.push_back( centerBottom );
+ }
+}
+
+void GetWireBoxVertArray( std::vector<vec3> &data )
+{
+ vec3 corners[] =
+ {
+ //Upper four corners
+ vec3( 0.5f, 0.5f, 0.5f ),
+ vec3( 0.5f, 0.5f, -0.5f ),
+ vec3( -0.5f, 0.5f, -0.5f ),
+ vec3( -0.5f, 0.5f, 0.5f ),
+
+ //Lower four corners
+ vec3( 0.5f, -0.5f, 0.5f ),
+ vec3( 0.5f, -0.5f, -0.5f ),
+ vec3( -0.5f, -0.5f, -0.5f ),
+ vec3( -0.5f, -0.5f, 0.5f ),
+ };
+
+ //We start with spinning around the top and the bottom.
+ for( int i = 1; i <= 4; i++ )
+ {
+ int prev_index = i - 1;
+ int cur_index = i % 4;
+
+ data.push_back( corners[prev_index] );
+ data.push_back( corners[cur_index] );
+ }
+
+ for( int i = 1; i <= 4; i++ )
+ {
+ int prev_index = 4 + i - 1;
+ int cur_index = 4 + i % 4;
+
+ data.push_back( corners[prev_index] );
+ data.push_back( corners[cur_index] );
+ }
+
+ //Then we match each bottom point with the one right above.
+ for( int i = 0; i < 4; i++ )
+ {
+ data.push_back( corners[i] );
+ data.push_back( corners[i+4] );
+ }
+}
+
+/*
+ * Draws a solid sphere
+ */
+void renderSolidSphere(GLdouble radius, GLint slices, GLint stacks)
+{
+ int i,j;
+
+ /* Adjust z and radius as stacks are drawn. */
+
+ double z0,z1;
+ double r0,r1;
+
+ /* Pre-computed circle */
+
+ double *sint1,*cost1;
+ double *sint2,*cost2;
+ circleTable(&sint1,&cost1,-slices);
+ circleTable(&sint2,&cost2,stacks*2);
+
+ /* The top stack is covered with a triangle fan */
+
+ z0 = 1.0f;
+ z1 = cost2[1];
+ r0 = 0.0f;
+ r1 = sint2[1];
+
+ glBegin(GL_TRIANGLE_FAN);
+
+ glNormal3d(0,0,1);
+ glVertex3d(0,0,radius);
+
+ for (j=slices; j>=0; j--)
+ {
+ glNormal3d(cost1[j]*r1, sint1[j]*r1, z1 );
+ glVertex3d(cost1[j]*r1*radius, sint1[j]*r1*radius, z1*radius);
+ }
+
+ glEnd();
+
+ /* Cover each stack with a quad strip, except the top and bottom stacks */
+
+ for( i=1; i<stacks-1; i++ )
+ {
+ z0 = z1; z1 = cost2[i+1];
+ r0 = r1; r1 = sint2[i+1];
+
+ glBegin(GL_QUAD_STRIP);
+
+ for(j=0; j<=slices; j++)
+ {
+ glNormal3d(cost1[j]*r1, sint1[j]*r1, z1 );
+ glVertex3d(cost1[j]*r1*radius, sint1[j]*r1*radius, z1*radius);
+ glNormal3d(cost1[j]*r0, sint1[j]*r0, z0 );
+ glVertex3d(cost1[j]*r0*radius, sint1[j]*r0*radius, z0*radius);
+ }
+
+ glEnd();
+ }
+
+ /* The bottom stack is covered with a triangle fan */
+
+ z0 = z1;
+ r0 = r1;
+
+ glBegin(GL_TRIANGLE_FAN);
+
+ glNormal3d(0,0,-1);
+ glVertex3d(0,0,-radius);
+
+ for (j=0; j<=slices; j++)
+ {
+ glNormal3d(cost1[j]*r0, sint1[j]*r0, z0 );
+ glVertex3d(cost1[j]*r0*radius, sint1[j]*r0*radius, z0*radius);
+ }
+
+ glEnd();
+
+ /* Release sin and cos tables */
+
+ OG_FREE(sint1);
+ OG_FREE(cost1);
+ OG_FREE(sint2);
+ OG_FREE(cost2);
+}
+
+// Get interleaved vert array (3 verts) for GL_LINES
+void GetWireSphereVertArray(GLdouble radius, GLint slices, GLint stacks, std::vector<float> &data) {
+ double r,x,y,z;
+ double *sint1,*cost1;
+ double *sint2,*cost2;
+ circleTable(&sint1,&cost1,-slices );
+ circleTable(&sint2,&cost2, stacks*2);
+ for (int i=1; i<stacks; i++) {
+ z = cost2[i];
+ r = sint2[i];
+ for(int j=0; j<=slices; j++){
+ x = cost1[j];
+ y = sint1[j];
+ //data.push_back((float)x);
+ //data.push_back((float)y);
+ //data.push_back((float)z);
+ data.push_back((float)(x*r*radius));
+ data.push_back((float)(y*r*radius));
+ data.push_back((float)(z*radius));
+ if(j!=0 && j!=slices){
+ //data.push_back((float)x);
+ //data.push_back((float)y);
+ //data.push_back((float)z);
+ data.push_back((float)(x*r*radius));
+ data.push_back((float)(y*r*radius));
+ data.push_back((float)(z*radius));
+ }
+ }
+ }
+ for (int i=0; i<slices; i++) {
+ for(int j=0; j<=stacks; j++){
+ x = cost1[i]*sint2[j];
+ y = sint1[i]*sint2[j];
+ z = cost2[j];
+ //data.push_back((float)x);
+ //data.push_back((float)y);
+ //data.push_back((float)z);
+ data.push_back((float)(x*radius));
+ data.push_back((float)(y*radius));
+ data.push_back((float)(z*radius));
+ if(j!=0 && j!=stacks){
+ //data.push_back((float)x);
+ //data.push_back((float)y);
+ //data.push_back((float)z);
+ data.push_back((float)(x*radius));
+ data.push_back((float)(y*radius));
+ data.push_back((float)(z*radius));
+ }
+ }
+ }
+ OG_FREE(sint1);
+ OG_FREE(cost1);
+ OG_FREE(sint2);
+ OG_FREE(cost2);
+}
+
+void GetUnitBoxVertArray(std::vector<float> &verts)
+{
+ verts.push_back(0.5f); verts.push_back(0.5f); verts.push_back(-0.5f);
+ verts.push_back(0.5f); verts.push_back(-0.5f); verts.push_back(-0.5f);
+ verts.push_back(-0.5f); verts.push_back(-0.5f); verts.push_back(-0.5f);
+ verts.push_back(-0.5f); verts.push_back(-0.5f); verts.push_back(-0.5f);
+ verts.push_back(-0.5f); verts.push_back(0.5f); verts.push_back(-0.5f);
+ verts.push_back(0.5f); verts.push_back(0.5f); verts.push_back(-0.5f);
+ verts.push_back(0.5f); verts.push_back(0.5f); verts.push_back(0.5f);
+ verts.push_back(-0.5f); verts.push_back(0.5f); verts.push_back(0.5f);
+ verts.push_back(-0.5f); verts.push_back(-0.5f); verts.push_back(0.5f);
+ verts.push_back(-0.5f); verts.push_back(-0.5f); verts.push_back(0.5f);
+ verts.push_back(0.5f); verts.push_back(-0.5f); verts.push_back(0.5f);
+ verts.push_back(0.5f); verts.push_back(0.5f); verts.push_back(0.5f);
+ verts.push_back(0.5f); verts.push_back(0.5f); verts.push_back(-0.5f);
+ verts.push_back(0.5f); verts.push_back(0.5f); verts.push_back(0.5f);
+ verts.push_back(0.5f); verts.push_back(-0.5f); verts.push_back(0.5f);
+ verts.push_back(0.5f); verts.push_back(-0.5f); verts.push_back(0.5f);
+ verts.push_back(0.5f); verts.push_back(-0.5f); verts.push_back(-0.5f);
+ verts.push_back(0.5f); verts.push_back(0.5f); verts.push_back(-0.5f);
+ verts.push_back(0.5f); verts.push_back(-0.5f); verts.push_back(-0.5f);
+ verts.push_back(0.5f); verts.push_back(-0.5f); verts.push_back(0.5f);
+ verts.push_back(-0.5f); verts.push_back(-0.5f); verts.push_back(0.5f);
+ verts.push_back(-0.5f); verts.push_back(-0.5f); verts.push_back(0.5f);
+ verts.push_back(-0.5f); verts.push_back(-0.5f); verts.push_back(-0.5f);
+ verts.push_back(0.5f); verts.push_back(-0.5f); verts.push_back(-0.5f);
+ verts.push_back(-0.5f); verts.push_back(-0.5f); verts.push_back(-0.5f);
+ verts.push_back(-0.5f); verts.push_back(-0.5f); verts.push_back(0.5f);
+ verts.push_back(-0.5f); verts.push_back(0.5f); verts.push_back(0.5f);
+ verts.push_back(-0.5f); verts.push_back(0.5f); verts.push_back(0.5f);
+ verts.push_back(-0.5f); verts.push_back(0.5f); verts.push_back(-0.5f);
+ verts.push_back(-0.5f); verts.push_back(-0.5f); verts.push_back(-0.5f);
+ verts.push_back(0.5f); verts.push_back(0.5f); verts.push_back(0.5f);
+ verts.push_back(0.5f); verts.push_back(0.5f); verts.push_back(-0.5f);
+ verts.push_back(-0.5f); verts.push_back(0.5f); verts.push_back(-0.5f);
+ verts.push_back(-0.5f); verts.push_back(0.5f); verts.push_back(-0.5f);
+ verts.push_back(-0.5f); verts.push_back(0.5f); verts.push_back(0.5f);
+ verts.push_back(0.5f); verts.push_back(0.5f); verts.push_back(0.5f);
+}
+
+void GetUnitBoxVertArray(std::vector<float> &verts, std::vector<unsigned> &faces)
+{
+
+ verts.push_back(0.500000);
+ verts.push_back(-0.500000);
+ verts.push_back(-0.500000);
+
+ verts.push_back(0.500000);
+ verts.push_back(-0.500000);
+ verts.push_back(0.500000);
+
+ verts.push_back(-0.500000);
+ verts.push_back(-0.500000);
+ verts.push_back(0.500000);
+
+ verts.push_back(-0.500000);
+ verts.push_back(-0.500000);
+ verts.push_back(-0.500000);
+
+ verts.push_back(0.500000);
+ verts.push_back(0.500000);
+ verts.push_back(-0.500000);
+
+ verts.push_back(0.500000);
+ verts.push_back(0.500000);
+ verts.push_back(0.500000);
+
+ verts.push_back(-0.500000);
+ verts.push_back(0.500000);
+ verts.push_back(0.500000);
+
+ verts.push_back(-0.500000);
+ verts.push_back(0.500000);
+ verts.push_back(-0.500000);
+
+ faces.push_back(1-1);
+ faces.push_back(2-1);
+ faces.push_back(4-1);
+ faces.push_back(5-1);
+ faces.push_back(8-1);
+ faces.push_back(6-1);
+ faces.push_back(1-1);
+ faces.push_back(5-1);
+ faces.push_back(2-1);
+ faces.push_back(2-1);
+ faces.push_back(6-1);
+ faces.push_back(3-1);
+ faces.push_back(3-1);
+ faces.push_back(7-1);
+ faces.push_back(4-1);
+ faces.push_back(5-1);
+ faces.push_back(1-1);
+ faces.push_back(8-1);
+ faces.push_back(2-1);
+ faces.push_back(3-1);
+ faces.push_back(4-1);
+ faces.push_back(8-1);
+ faces.push_back(7-1);
+ faces.push_back(6-1);
+ faces.push_back(5-1);
+ faces.push_back(6-1);
+ faces.push_back(2-1);
+ faces.push_back(6-1);
+ faces.push_back(7-1);
+ faces.push_back(3-1);
+ faces.push_back(7-1);
+ faces.push_back(8-1);
+ faces.push_back(4-1);
+ faces.push_back(1-1);
+ faces.push_back(4-1);
+ faces.push_back(8-1);
+}
+
+/*
+ * Draws a solid cylinder
+ */
+void renderSolidCylinder(GLdouble radius, GLdouble height, GLint slices, GLint stacks)
+{
+ int i,j;
+
+ /* Step in z and radius as stacks are drawn. */
+
+
+ double z0,z1;
+ const double zStep = height/stacks;
+
+ /* Pre-computed circle */
+
+ double *sint,*cost;
+ circleTable(&sint,&cost,-slices);
+
+ /* Cover the base and top */
+
+ glBegin(GL_TRIANGLE_FAN);
+ glNormal3d(0.0f, 0.0f, -1.0f );
+ glVertex3d(0.0f, 0.0f, -height/2 );
+ for (j=0; j<=slices; j++)
+ glVertex3d(cost[j]*radius, sint[j]*radius, -height/2);
+ glEnd();
+
+ glBegin(GL_TRIANGLE_FAN);
+ glNormal3d(0.0f, 0.0f, 1.0f );
+ glVertex3d(0.0f, 0.0f, height/2);
+ for (j=slices; j>=0; j--)
+ glVertex3d(cost[j]*radius, sint[j]*radius, height/2);
+ glEnd();
+
+ /* Do the stacks */
+
+ z0 = -height/2;
+ z1 = zStep-height/2;
+
+ for (i=1; i<=stacks; i++)
+ {
+ if (i==stacks)
+ z1 = height/2;
+
+ glBegin(GL_QUAD_STRIP);
+ for (j=0; j<=slices; j++ )
+ {
+ glNormal3d(cost[j], sint[j], 0.0f );
+ glVertex3d(cost[j]*radius, sint[j]*radius, z0 );
+ glVertex3d(cost[j]*radius, sint[j]*radius, z1 );
+ }
+ glEnd();
+
+ z0 = z1; z1 += zStep;
+ }
+
+ /* Release sin and cos tables */
+
+ OG_FREE(sint);
+ OG_FREE(cost);
+}
+
+/*
+ * Draws a wire cylinder
+ */
+void renderWireCylinder(GLdouble radius, GLdouble height, GLint slices, GLint stacks)
+{
+ int i,j;
+
+ /* Step in z and radius as stacks are drawn. */
+
+ double z = 0.0f;
+ const double zStep = height/stacks;
+
+ /* Pre-computed circle */
+
+ double *sint,*cost;
+ circleTable(&sint,&cost,-slices);
+
+ /* Draw the stacks... */
+
+ for (i=0; i<=stacks; i++)
+ {
+ if (i==stacks)
+ z = height;
+
+ glBegin(GL_LINE_LOOP);
+ for( j=0; j<slices; j++ ) {
+ glNormal3d(cost[j], sint[j], 0.0f);
+ glVertex3d(cost[j]*radius, sint[j]*radius, z );
+ }
+ glEnd();
+
+ z += zStep;
+ }
+
+ /* Draw the slices */
+
+ glBegin(GL_LINES);
+
+ for (j=0; j<slices; j++)
+ {
+ glNormal3d(cost[j], sint[j], 0.0f );
+ glVertex3d(cost[j]*radius, sint[j]*radius, 0.0f );
+ glVertex3d(cost[j]*radius, sint[j]*radius, height);
+ }
+
+ glEnd();
+
+ /* Release sin and cos tables */
+
+ OG_FREE(sint);
+ OG_FREE(cost);
+}
+
+
+#undef num_faces
+
+
+#ifdef _WIN32
+#define NOMINMAX
+#include <windows.h>
+#endif
+
+#include "opengl.h"
+
+GLuint GetBoxDisplayList( const vec3 &sides )
+{
+ GLuint display_list = glGenLists(1);
+ glNewList(display_list,GL_COMPILE);
+ glBegin(GL_QUADS);
+ glNormal3f(0,-1,0);
+ glVertex3f(-sides[0]*0.5f,-sides[1]*0.5f,-sides[2]*0.5f);
+ glVertex3f( sides[0]*0.5f,-sides[1]*0.5f,-sides[2]*0.5f);
+ glVertex3f( sides[0]*0.5f,-sides[1]*0.5f, sides[2]*0.5f);
+ glVertex3f(-sides[0]*0.5f,-sides[1]*0.5f, sides[2]*0.5f);
+
+ glNormal3f(0,1,0);
+ glVertex3f(-sides[0]*0.5f, sides[1]*0.5f, sides[2]*0.5f);
+ glVertex3f( sides[0]*0.5f, sides[1]*0.5f, sides[2]*0.5f);
+ glVertex3f( sides[0]*0.5f, sides[1]*0.5f,-sides[2]*0.5f);
+ glVertex3f(-sides[0]*0.5f, sides[1]*0.5f,-sides[2]*0.5f);
+
+ glNormal3f(1,0,0);
+ glVertex3f( sides[0]*0.5f,-sides[1]*0.5f,-sides[2]*0.5f);
+ glVertex3f( sides[0]*0.5f, sides[1]*0.5f,-sides[2]*0.5f);
+ glVertex3f( sides[0]*0.5f, sides[1]*0.5f, sides[2]*0.5f);
+ glVertex3f( sides[0]*0.5f,-sides[1]*0.5f, sides[2]*0.5f);
+
+ glNormal3f(-1,0,0);
+ glVertex3f( -sides[0]*0.5f,-sides[1]*0.5f, sides[2]*0.5f);
+ glVertex3f( -sides[0]*0.5f, sides[1]*0.5f, sides[2]*0.5f);
+ glVertex3f( -sides[0]*0.5f, sides[1]*0.5f,-sides[2]*0.5f);
+ glVertex3f( -sides[0]*0.5f,-sides[1]*0.5f,-sides[2]*0.5f);
+
+ glNormal3f(0,0,-1);
+ glVertex3f(-sides[0]*0.5f,-sides[1]*0.5f, -sides[2]*0.5f);
+ glVertex3f(-sides[0]*0.5f, sides[1]*0.5f, -sides[2]*0.5f);
+ glVertex3f( sides[0]*0.5f, sides[1]*0.5f, -sides[2]*0.5f);
+ glVertex3f( sides[0]*0.5f,-sides[1]*0.5f, -sides[2]*0.5f);
+
+ glNormal3f(0,0,1);
+ glVertex3f( sides[0]*0.5f,-sides[1]*0.5f, sides[2]*0.5f);
+ glVertex3f( sides[0]*0.5f, sides[1]*0.5f, sides[2]*0.5f);
+ glVertex3f(-sides[0]*0.5f, sides[1]*0.5f, sides[2]*0.5f);
+ glVertex3f(-sides[0]*0.5f,-sides[1]*0.5f, sides[2]*0.5f);
+ glEnd();
+ glEndList();
+
+ return display_list;
+}
+
+
+
+GLuint GetMultiSphereDisplayList( const vec3 *positions, const float *radii, int num_spheres )
+{
+ CHECK_GL_ERROR();
+ GLuint display_list = glGenLists(1);
+ CHECK_GL_ERROR();
+ glNewList(display_list,GL_COMPILE);
+ CHECK_GL_ERROR();
+
+ for(int i=0; i<num_spheres; i++){
+ glPushMatrix();
+ glTranslatef(positions[i][0], positions[i][1], positions[i][2]);
+ renderSolidSphere(radii[i],12,12);
+ glPopMatrix();
+ }
+ CHECK_GL_ERROR();
+ for(int i=0; i<num_spheres; i++){
+ for(int j=i+1; j<num_spheres; j++){
+ vec3 connection = normalize(positions[j]-positions[i]);
+ vec3 right;// = normalize(vec3(1.0f,1.0f,1.0f));
+ vec3 up;// = normalize(cross(right,connection));
+ //right = normalize(cross(up,connection));
+
+ PlaneSpace(connection, right, up);
+
+ vec3 avg_pos = (positions[j]+positions[i])*0.5f;
+
+ mat4 transform;
+ transform.SetColumn(0,right);
+ transform.SetColumn(1,up);
+ transform.SetColumn(2,connection);
+ transform.SetColumn(3,avg_pos);
+
+ glPushMatrix();
+ glMultMatrixf(transform);
+ renderSolidCylinder(radii[i],
+ distance(positions[i], positions[j]),
+ 12,12);
+ glPopMatrix();
+ }
+ }
+ CHECK_GL_ERROR();
+ glEndList();
+ return display_list;
+}
+
+GLuint GetWireCylinderDisplayList( float radius, float height, GLint slices, GLint stacks )
+{
+ GLuint display_list = glGenLists(1);
+ glNewList(display_list,GL_COMPILE);
+ glPushMatrix();
+ glTranslatef(0.0f,height*0.5f,0.0f);
+ glRotatef(90,1,0,0);
+ renderWireCylinder(radius,height,slices,stacks);
+ glPopMatrix();
+ glEndList();
+ return display_list;
+}
+
+GLuint GetSphereDisplayList( float radius )
+{
+ GLuint display_list = glGenLists(1);
+ glNewList(display_list,GL_COMPILE);
+ glPushMatrix();
+ renderSolidSphere(radius,12,12);
+ glPopMatrix();
+ glEndList();
+ return display_list;
+}
diff --git a/Source/Graphics/geometry.h b/Source/Graphics/geometry.h
new file mode 100644
index 00000000..a5f67670
--- /dev/null
+++ b/Source/Graphics/geometry.h
@@ -0,0 +1,189 @@
+//-----------------------------------------------------------------------------
+// Name: geometry.h
+// Author: Freeglut
+// License: X-Consortium license
+// Description: Data and utility functions for rendering several useful
+// geometric shapes. This code is a modified version of the
+// code found in "freeglut_teapot.c" and "freeglut_geometry.c",
+// which is part of the open source project, Freeglut.
+// http://freeglut.sourceforge.net/
+//
+// Modified by Wolfire Games LLC for use in the project
+// Overgrowth.
+//
+// ----------------------------------------------------------------------------
+// Freeglut Copyright
+// ------------------
+//
+// Freeglut code without an explicit copyright is covered by the following
+// copyright :
+//
+// Copyright(c) 1999 - 2000 Pawel W.Olszta.All Rights Reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this softwareand associated documentation files(the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell
+// copies or substantial portions of the Software.
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL
+// PAWEL W.OLSZTA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+// Except as contained in this notice, the name of Pawel W.Olszta shall not be
+// used in advertising or otherwise to promote the sale, use or other dealings
+// in this Software without prior written authorization from Pawel W.Olszta.
+/*
+ * freeglut_geometry.c
+ *
+ * Freeglut geometry rendering methods.
+ *
+ * Copyright (c) 1999-2000 Pawel W. Olszta. All Rights Reserved.
+ * Written by Pawel W. Olszta, <olszta@sourceforge.net>
+ * Creation date: Fri Dec 3 1999
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PAWEL W. OLSZTA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+/*
+ * freeglut_teapot.c
+ *
+ * Teapot(tm) rendering code.
+ *
+ * Copyright (c) 1999-2000 Pawel W. Olszta. All Rights Reserved.
+ * Written by Pawel W. Olszta, <olszta@sourceforge.net>
+ * Creation date: Fri Dec 24 1999
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PAWEL W. OLSZTA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+/*
+ * Original teapot code copyright follows:
+ */
+/*
+ * (c) Copyright 1993, Silicon Graphics, Inc.
+ *
+ * ALL RIGHTS RESERVED
+ *
+ * Permission to use, copy, modify, and distribute this software
+ * for any purpose and without fee is hereby granted, provided
+ * that the above copyright notice appear in all copies and that
+ * both the copyright notice and this permission notice appear in
+ * supporting documentation, and that the name of Silicon
+ * Graphics, Inc. not be used in advertising or publicity
+ * pertaining to distribution of the software without specific,
+ * written prior permission.
+ *
+ * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU
+ * "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR
+ * OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. IN NO
+ * EVENT SHALL SILICON GRAPHICS, INC. BE LIABLE TO YOU OR ANYONE
+ * ELSE FOR ANY DIRECT, SPECIAL, INCIDENTAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER,
+ * INCLUDING WITHOUT LIMITATION, LOSS OF PROFIT, LOSS OF USE,
+ * SAVINGS OR REVENUE, OR THE CLAIMS OF THIRD PARTIES, WHETHER OR
+ * NOT SILICON GRAPHICS, INC. HAS BEEN ADVISED OF THE POSSIBILITY
+ * OF SUCH LOSS, HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * ARISING OUT OF OR IN CONNECTION WITH THE POSSESSION, USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ *
+ * US Government Users Restricted Rights
+ *
+ * Use, duplication, or disclosure by the Government is subject to
+ * restrictions set forth in FAR 52.227f.19(c)(2) or subparagraph
+ * (c)(1)(ii) of the Rights in Technical Data and Computer
+ * Software clause at DFARS 252.227f-7013 and/or in similar or
+ * successor clauses in the FAR or the DOD or NASA FAR
+ * Supplement. Unpublished-- rights reserved under the copyright
+ * laws of the United States. Contractor/manufacturer is Silicon
+ * Graphics, Inc., 2011 N. Shoreline Blvd., Mountain View, CA
+ * 94039-7311.
+ *
+ * OpenGL(TM) is a trademark of Silicon Graphics, Inc.
+ */
+// ---------------------------------------------------------------------------
+//
+// The following functions are defined here:
+//
+// void renderWireTeapot(GLdouble size);
+// void renderSolidTeapot(GLdouble size);
+// void renderWireCube(GLdouble size);
+// void renderSolidCube(GLdouble size);
+// void renderWireSphere(GLdouble radius, GLint slices, GLint stacks);
+// void renderSolidSphere(GLdouble radius, GLint slices, GLint stacks);
+// void renderWireCone(GLdouble base, GLdouble height, GLint slices, GLint stacks);
+// void renderSolidCone(GLdouble base, GLdouble height, GLint slices, GLint stacks);
+// void renderWireCylinder(GLdouble base, GLdouble height, GLint slices, GLint stacks);
+// void renderSolidCyliner(GLdouble base, GLdouble height, GLint slices, GLint stacks);
+// void renderWireTorus(GLdouble innerRadius, GLdouble outerRadius, GLint sides, GLint rings);
+// void renderSolidTorus(GLdouble innerRadius, GLdouble outerRadius, GLint sides, GLint rings);
+// void renderWireDodecahedron(void);
+// void renderSolidDodecahedron(void);
+// void renderWireOctahedron(void);
+// void renderSolidOctahedron(void);
+// void renderWireTetrahedron(void);
+// void renderSolidTetrahedron(void);
+// void renderWireIcosahedron(void);
+// void renderSolidIcosahedron(void);
+// void renderWireSierpinskiSponge(int num_levels, GLdouble offset[3], GLdouble scale);
+// void renderSolidSierpinskiSponge(int num_levels, GLdouble offset[3], GLdouble scale);
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <stdlib.h>
+#include <cmath>
+#include "Math/enginemath.h"
+#include "opengl.h"
+#include <vector>
+
+/* -- INTERFACE FUNCTION PROTOTYPES -------------------------------------------------- */
+
+GLuint GetBoxDisplayList(const vec3 &sides);
+GLuint GetSphereDisplayList(float radius);
+GLuint GetMultiSphereDisplayList(const vec3 *positions, const float *radii, int num_spheres);
+
+void renderSolidSphere(GLdouble radius, GLint slices, GLint stacks);
+
+void renderWireCylinder(GLdouble radius, GLdouble height, GLint slices, GLint stacks);
+
+GLuint GetWireCylinderDisplayList( float radius, float height, GLint slices, GLint stacks );
+
+void GetWireCylinderVertArray(GLint slices, std::vector<vec3> &data);
+void GetWireSphereVertArray(GLdouble radius, GLint slices, GLint stacks, std::vector<float> &data);
+void GetWireBoxVertArray( std::vector<vec3> &data );
+void GetUnitBoxVertArray(std::vector<float> &verts, std::vector<unsigned> &faces);
+void GetUnitBoxVertArray(std::vector<float> &verts);
diff --git a/Source/Graphics/glstate.cpp b/Source/Graphics/glstate.cpp
new file mode 100644
index 00000000..917efb9c
--- /dev/null
+++ b/Source/Graphics/glstate.cpp
@@ -0,0 +1,37 @@
+//-----------------------------------------------------------------------------
+// Name: glstate.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "glstate.h"
+
+#include <opengl.h>
+
+GLState::GLState():
+ depth_test(false),
+ cull_face(false),
+ blend(false),
+ depth_write(false),
+ blend_src(GL_SRC_ALPHA),
+ blend_dst(GL_ONE_MINUS_SRC_ALPHA),
+ blend_alpha_dst(GL_ONE_MINUS_SRC_ALPHA),
+ blend_alpha_src(GL_ONE)
+{}
diff --git a/Source/Graphics/glstate.h b/Source/Graphics/glstate.h
new file mode 100644
index 00000000..7a25937a
--- /dev/null
+++ b/Source/Graphics/glstate.h
@@ -0,0 +1,38 @@
+//-----------------------------------------------------------------------------
+// Name: glstate.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+enum AlphaMode {kPremultiplied, kStraight, kStraightBlend};
+
+struct GLState {
+ bool depth_test;
+ bool cull_face;
+ bool blend;
+ bool depth_write;
+ int blend_src;
+ int blend_dst;
+ int blend_alpha_dst;
+ int blend_alpha_src;
+ GLState();
+};
diff --git a/Source/Graphics/graphics.cpp b/Source/Graphics/graphics.cpp
new file mode 100644
index 00000000..b34995ac
--- /dev/null
+++ b/Source/Graphics/graphics.cpp
@@ -0,0 +1,1932 @@
+//-----------------------------------------------------------------------------
+// Name: graphics.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "graphics.h"
+
+#include <Internal/filesystem.h>
+#include <Internal/integer.h>
+#include <Internal/config.h>
+#include <Internal/datemodified.h>
+#include <Internal/hardware_specs.h>
+#include <Internal/profiler.h>
+#include <Internal/common.h>
+#include <Internal/detect_settings.h>
+
+#include <Graphics/textures.h>
+#include <Graphics/shaders.h>
+#include <Graphics/camera.h>
+#include <Graphics/models.h>
+#include <Graphics/flares.h>
+
+#include <Utility/sdl_util.h>
+#include <Utility/assert.h>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <Images/image_export.hpp>
+#include <Math/vec3math.h>
+#include <Main/scenegraph.h>
+#include <Logging/logdata.h>
+#include <Objects/decalobject.h>
+#include <Main/engine.h>
+
+#include <SDL.h>
+
+#include <memory.h>
+#include <cstdio>
+
+#ifdef _WIN32
+// Request more powerful graphics card if on a laptop with both integrated and discrete graphics
+extern "C" { _declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001; }
+extern "C" { _declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; }
+#endif
+
+extern SceneLight* primary_light;
+
+#if PLATFORM_MACOSX
+ #include <OpenGL/OpenGL.h>
+#endif
+
+bool g_level_shadows = true;
+bool g_simple_shadows = true;
+bool g_detail_objects_reduced = true;
+bool g_detail_objects_reduced_dirty = false;
+bool g_albedo_only = false;
+bool g_disable_fog = false;
+bool g_no_reflection_capture = false;
+bool g_no_decals = false;
+bool g_no_decal_elements = false;
+bool g_character_decals_enabled = false; // Note: This is enabled via 'Custom Shaders' level param. No config value for it
+bool g_no_detailmaps = false;
+bool g_single_pass_shadow_cascade = false;
+bool g_simple_water = false;
+bool g_particle_field_simple = false;
+bool g_draw_vr = false;
+bool g_s3tc_dxt5_support = false;
+bool g_s3tc_dxt5_textures = true;
+bool g_opengl_callback_error_dialog = true;
+bool g_perform_occlusion_query = false;
+bool g_gamma_correct_final_output = true;
+bool g_attrib_envobj_intancing_support = false;
+bool g_attrib_envobj_intancing_enabled = true;
+bool g_ubo_batch_multiplier_force_1x = false;
+
+// Variables for turning off features at runtime for testing performance. Not backed by config, so as not to pollute it
+bool g_debug_runtime_disable_blood_surface_pre_draw = false;
+bool g_debug_runtime_disable_debug_draw = false;
+bool g_debug_runtime_disable_debug_ribbon_draw = false;
+bool g_debug_runtime_disable_decal_object_draw = false;
+bool g_debug_runtime_disable_decal_object_pre_draw_frame = false;
+bool g_debug_runtime_disable_detail_object_surface_draw = false;
+bool g_debug_runtime_disable_detail_object_surface_pre_draw = false;
+bool g_debug_runtime_disable_dynamic_light_object_draw = false;
+bool g_debug_runtime_disable_env_object_draw = false;
+bool g_debug_runtime_disable_env_object_draw_depth_map = false;
+bool g_debug_runtime_disable_env_object_draw_detail_object_instances = false;
+bool g_debug_runtime_disable_env_object_draw_instances = false;
+bool g_debug_runtime_disable_env_object_draw_instances_transparent = false;
+bool g_debug_runtime_disable_env_object_pre_draw_camera = false;
+bool g_debug_runtime_disable_flares_draw = false;
+bool g_debug_runtime_disable_gpu_particle_field_draw = false;
+bool g_debug_runtime_disable_group_pre_draw_camera = false;
+bool g_debug_runtime_disable_group_pre_draw_frame = false;
+bool g_debug_runtime_disable_hotspot_draw = false;
+bool g_debug_runtime_disable_hotspot_pre_draw_frame = false;
+bool g_debug_runtime_disable_item_object_draw = false;
+bool g_debug_runtime_disable_item_object_draw_depth_map = false;
+bool g_debug_runtime_disable_item_object_pre_draw_frame = false;
+bool g_debug_runtime_disable_morph_target_pre_draw_camera = false;
+bool g_debug_runtime_disable_movement_object_draw = false;
+bool g_debug_runtime_disable_movement_object_draw_depth_map = false;
+bool g_debug_runtime_disable_movement_object_pre_draw_camera = false;
+bool g_debug_runtime_disable_movement_object_pre_draw_frame = false;
+bool g_debug_runtime_disable_navmesh_connection_object_draw = false;
+bool g_debug_runtime_disable_navmesh_hint_object_draw = false;
+bool g_debug_runtime_disable_particle_draw = false;
+bool g_debug_runtime_disable_particle_system_draw = false;
+bool g_debug_runtime_disable_pathpoint_object_draw = false;
+bool g_debug_runtime_disable_placeholder_object_draw = false;
+bool g_debug_runtime_disable_reflection_capture_object_draw = false;
+bool g_debug_runtime_disable_rigged_object_draw = false;
+bool g_debug_runtime_disable_rigged_object_pre_draw_camera = false;
+bool g_debug_runtime_disable_rigged_object_pre_draw_frame = false;
+bool g_debug_runtime_disable_scene_graph_draw = false;
+bool g_debug_runtime_disable_scene_graph_draw_depth_map = false;
+bool g_debug_runtime_disable_scene_graph_prepare_lights_and_decals = false;
+bool g_debug_runtime_disable_sky_draw = false;
+bool g_debug_runtime_disable_terrain_object_draw_depth_map = false;
+bool g_debug_runtime_disable_terrain_object_draw_terrain = false;
+bool g_debug_runtime_disable_terrain_object_pre_draw_camera = false;
+
+
+unsigned int Graphics::GetShadowSize(unsigned int target_shadow_size) {
+ unsigned int shadow_size = 8;
+ while(shadow_size<target_shadow_size) {
+ shadow_size *= 2;
+ }
+ shadow_size /= 2;
+
+ shadow_size /= config_.texture_reduction_factor();
+
+ const unsigned int kMaxShadowSize = 1024;
+ if(shadow_size>kMaxShadowSize) {
+ shadow_size = kMaxShadowSize;
+ }
+ if(shadow_size<1) {
+ shadow_size = 1;
+ }
+ return shadow_size;
+}
+
+//Clear the framebuffer
+void Graphics::Clear(const bool clear_color_b) {
+ setDepthWrite(true);
+
+ //Only clear the color buffer if will not be overdrawn anyways
+ if(clear_color_b)glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
+ else glClear( GL_DEPTH_BUFFER_BIT );
+}
+
+void Graphics::setPolygonOffset( bool new_polygon_offset ) {
+ if(new_polygon_offset == shadow_state_.polygon_offset_enable_) return;
+ shadow_state_.polygon_offset_enable_ = new_polygon_offset;
+ shadow_state_.polygon_offset_enable_?glEnable(GL_POLYGON_OFFSET_FILL):glDisable(GL_POLYGON_OFFSET_FILL);
+}
+
+void Graphics::setSimpleShadows(const bool val) {
+ g_simple_shadows = val;
+ if(initialized) {
+ InitScreen();
+ }
+}
+
+void Graphics::setSimpleWater(const bool val) {
+ g_simple_water = val || g_draw_vr;
+}
+
+void Graphics::SetParticleFieldSimple(bool val) {
+ g_particle_field_simple = val;
+}
+
+void Graphics::setAttribEnvObjInstancing(bool val) {
+ g_attrib_envobj_intancing_enabled = val;
+}
+
+void Graphics::PushViewport() {
+ ViewportDims dims;
+ for(int i=0; i<4; ++i){
+ dims.entries[i] = viewport_dim[i];
+ }
+ viewport_dims_stack.push(dims);
+}
+
+void Graphics::PopViewport() {
+ LOG_ASSERT(!viewport_dims_stack.empty());
+ ViewportDims dims = viewport_dims_stack.top();
+ viewport_dims_stack.pop();
+ for(int i=0; i<4; ++i){
+ viewport_dim[i] = dims.entries[i];
+ }
+ glScissor(viewport_dim[0], viewport_dim[1], viewport_dim[2], viewport_dim[3]);
+ glViewport(viewport_dim[0], viewport_dim[1], viewport_dim[2], viewport_dim[3]);
+}
+
+void Graphics::setBlend(const bool new_blend)
+{
+ GLState &gl_state_ = shadow_state_.gl_state_;
+ if(new_blend==gl_state_.blend)return;
+ gl_state_.blend=!gl_state_.blend;
+ gl_state_.blend?glEnable(GL_BLEND):glDisable(GL_BLEND);
+}
+
+void Graphics::setDepthTest(const bool new_depth_test)
+{
+ GLState &gl_state_ = shadow_state_.gl_state_;
+ if(new_depth_test==gl_state_.depth_test)return;
+ gl_state_.depth_test=!gl_state_.depth_test;
+ gl_state_.depth_test?glEnable(GL_DEPTH_TEST):glDisable(GL_DEPTH_TEST);
+}
+
+void Graphics::setCullFace(const bool new_cull_face)
+{
+ GLState &gl_state_ = shadow_state_.gl_state_;
+ if(new_cull_face==gl_state_.cull_face)return;
+ gl_state_.cull_face=!gl_state_.cull_face;
+ gl_state_.cull_face?glEnable(GL_CULL_FACE):glDisable(GL_CULL_FACE);
+}
+
+void Graphics::setDepthWrite(const bool new_depth_write)
+{
+ GLState &gl_state_ = shadow_state_.gl_state_;
+ if(new_depth_write==gl_state_.depth_write)return;
+ gl_state_.depth_write=!gl_state_.depth_write;
+ gl_state_.depth_write?glDepthMask(1):glDepthMask(0);
+}
+
+void Graphics::setGLState(const GLState &state) {
+ setDepthTest(state.depth_test);
+ setCullFace(state.cull_face);
+ setBlend(state.blend);
+ setDepthWrite(state.depth_write);
+ SetBlendFunc(state.blend_src, state.blend_dst, state.blend_alpha_src, state.blend_alpha_dst);
+}
+
+void Graphics::RenderFramebufferToTexture(GLuint framebuffer, TextureRef texture_ref) {
+ PROFILER_ENTER(g_profiler_ctx, "bindFramebuffer");
+ bindFramebuffer(framebuffer);
+ PROFILER_LEAVE(g_profiler_ctx);
+ PROFILER_ENTER(g_profiler_ctx, "framebufferColorTexture2D");
+ framebufferColorTexture2D(texture_ref);
+ PROFILER_LEAVE(g_profiler_ctx);
+}
+
+void Graphics::StartTextureSpaceRenderingCustom(int x, int y, int width, int height) {
+ PushViewport();
+ setViewport(x,y,x+width,y+height);
+ CHECK_GL_ERROR();
+}
+
+void Graphics::EndTextureSpaceRendering() {
+ PopViewport();
+}
+
+// Framebuffer wrappers
+void Graphics::bindRenderbuffer(GLuint rb) {
+ CHECK_GL_ERROR();
+ glBindRenderbuffer(GL_RENDERBUFFER, rb);
+ CHECK_GL_ERROR();
+}
+
+void Graphics::PushFramebuffer() {
+ fbo_stack.push(curr_framebuffer);
+}
+
+void Graphics::PopFramebuffer() {
+ curr_framebuffer = fbo_stack.top();
+ fbo_stack.pop();
+ bindFramebuffer(curr_framebuffer);
+}
+
+void Graphics::bindFramebuffer(GLuint fb) {
+ curr_framebuffer = fb;
+ CHECK_GL_ERROR();
+ glBindFramebuffer(GL_FRAMEBUFFER, fb);
+ CHECK_GL_ERROR();
+}
+
+void Graphics::framebufferDepthTexture2D(TextureRef t, int mipmap_level) {
+ CHECK_GL_ERROR();
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, Textures::Instance()->returnTexture(t), mipmap_level);
+ CHECK_GL_ERROR();
+}
+
+//#pragma optimize("",off)
+void Graphics::framebufferColorTexture2D(TextureRef t, int mipmap_level) {
+ /*if(Textures::Instance()->IsCompressed(t)){
+ Textures::Instance()->Uncompress(t);
+ }*/
+ PROFILER_ENTER(g_profiler_ctx, "EnsureInVRAM");
+ Textures::Instance()->EnsureInVRAM(t);
+ PROFILER_LEAVE(g_profiler_ctx);
+ CHECK_GL_ERROR();
+ GLuint tex = Textures::Instance()->returnTexture(t);
+ LOGS << "Binding gl texture " << tex << " to framebuffer" << std::endl;
+ if(!t.valid()) {
+ LOGE << "Not valid..." << std::endl;
+ }
+ //if(!glIsTexture(tex)){
+ // LOGE << "Not a texture..." << std::endl;
+ //}
+ PROFILER_ENTER(g_profiler_ctx, "glFramebufferTexture2D");
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, mipmap_level);
+ PROFILER_LEAVE(g_profiler_ctx);
+ CHECK_GL_ERROR();
+}
+//#pragma optimize("",on)
+
+
+void Graphics::genRenderbuffers(GLuint *rb) {
+ CHECK_GL_ERROR();
+ glGenRenderbuffers(1, rb);
+ CHECK_GL_ERROR();
+}
+
+void Graphics::genFramebuffers (GLuint *fb, const char* human_name) {
+ CHECK_GL_ERROR();
+ glGenFramebuffers(1, fb);
+ framebuffernames[*fb] = std::string(human_name);
+
+ LOGI << "Created framebuffer: " << *fb << " " << framebuffernames[*fb] << std::endl;
+ CHECK_GL_ERROR();
+}
+
+void Graphics::deleteFramebuffer(GLuint* fb) {
+ if(*fb != (GLuint)INVALID_FRAMEBUFFER) {
+ CHECK_GL_ERROR();
+ glDeleteFramebuffers(1, fb);
+
+ LOGI << "Deleted framebuffer: " << *fb << " " << framebuffernames[*fb] << std::endl;
+ CHECK_GL_ERROR();
+ *fb = INVALID_FRAMEBUFFER;
+ }
+}
+
+void Graphics::renderbufferStorage(GLuint res) {
+ CHECK_GL_ERROR();
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, res, res);
+ CHECK_GL_ERROR();
+}
+
+void Graphics::framebufferRenderbuffer(GLuint rb) {
+ CHECK_GL_ERROR();
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rb);
+ CHECK_GL_ERROR();
+}
+
+GLenum Graphics::checkFramebufferStatus() {
+ return glCheckFramebufferStatus(GL_FRAMEBUFFER);
+}
+
+void Graphics::BindVBO(int target, int val) {
+ switch(target){
+ case GL_ARRAY_BUFFER:
+ LOGS << "Binding array VBO " << val << std::endl;;
+ BindArrayVBO(val);
+ break;
+ case GL_ELEMENT_ARRAY_BUFFER:
+ LOGS << "Binding element VBO " << val << std::endl;;
+ BindElementVBO(val);
+ break;
+ default:
+ // No valid target
+ SDL_assert(false);
+ break;
+ }
+}
+
+//Helping function to explicitally unbind a buffer (if it's bound)
+//This will prevent the rebind-saving routine from messing up the state.
+void Graphics::UnbindVBO( int target, int val )
+{
+ if( target == GL_ARRAY_BUFFER && vbo_array_bound == val )
+ {
+ BindVBO( GL_ARRAY_BUFFER, 0 );
+ }
+
+ if( target == GL_ELEMENT_ARRAY_BUFFER && vbo_element_bound == val )
+ {
+ BindVBO( GL_ELEMENT_ARRAY_BUFFER, 0 );
+ }
+}
+
+void Graphics::BindArrayVBO(int val) {
+ LOGS << "Current vbo_array_bound " << vbo_array_bound << std::endl;
+ LOGS << "Value " << val << std::endl;
+ if(vbo_array_bound != val){
+ vbo_array_bound = val;
+ glBindBuffer( GL_ARRAY_BUFFER, val );
+ }
+ else
+ {
+ LOGS << "Tried to rebind " << val << std::endl;
+ }
+}
+
+void Graphics::BindElementVBO(int val) {
+ if(vbo_element_bound != val){
+ vbo_element_bound = val;
+ glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, val );
+ }
+}
+
+//Start the scene drawing by clearing and setting the matrixmode
+void Graphics::startDraw(vec2 start, vec2 end, ScreenType screen_type) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Graphics::startDraw()");
+ if(screen_type == kRender){
+ setViewport((GLint)((float)render_dims[0] * start[0]),
+ (GLint)((float)render_dims[1] * start[1]),
+ (GLint)((float)render_dims[0] * end[0]),
+ (GLint)((float)render_dims[1] * end[1]));
+ } else if(screen_type == kWindow){
+ setViewport((GLint)((float)window_dims[0] * start[0]),
+ (GLint)((float)window_dims[1] * start[1]),
+ (GLint)((float)window_dims[0] * end[0]),
+ (GLint)((float)window_dims[1] * end[1]));
+ } else if(screen_type == kTexture){
+ setViewport((GLint)((float)start[0]),
+ (GLint)((float)start[1]),
+ (GLint)((float)end[0]),
+ (GLint)((float)end[1]));
+ }
+}
+
+void Graphics::TakeScreenshot() {
+ unsigned long width = window_dims[0];
+ unsigned long height = window_dims[1];
+
+ unsigned long size = width * height;
+ unsigned char *pixels = new unsigned char[size * 4];
+ ::memset(pixels, 0, size * 4);
+
+ //glReadBuffer(GL_COLOR_ATTACHMENT0);
+ glReadPixels(0, 0, width, height,
+ GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, (GLvoid *)pixels);
+
+ if(screenshot_mode == kGameplay){
+ int index3=0;
+ int index4=0;
+ for(unsigned i=0; i<width*height; i++){
+ pixels[index3+0] = pixels[index4+0];
+ pixels[index3+1] = pixels[index4+1];
+ pixels[index3+2] = pixels[index4+2];
+ index3 += 3;
+ index4 += 4;
+ }
+ std::string path = ImageExport::FindEmptySequentialFile("Screenshots/OvergrowthScreenshot",".jpg");
+ ImageExport::SaveJPEG(path.c_str(), pixels, width, height);
+ } else if(screenshot_mode == kTransparentGameplay){
+ std::string path = ImageExport::FindEmptySequentialFile("Screenshots/OvergrowthScreenshot",".png");
+ ImageExport::SavePNGTransparent(path.c_str(), pixels, width, height);
+ }
+
+ delete [] pixels;
+}
+
+//End the scene drawing by drawing the backbuffer to the screen
+void Graphics::SwapToScreen() {
+ LOG_ASSERT(viewport_dims_stack.empty());
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Graphics::SwapToScreen()"); //glFlush(); Unnecessary on Mac OS X, as CGLFlushDrawable auto-flushes the command buffer
+ {
+ PROFILER_ZONE_STALL(g_profiler_ctx,"SDL_GL_SwapWindow");
+ SDL_GL_SwapWindow(sdl_window_);
+ }
+ GL_SWAP();
+
+ if(queued_screenshot == 2) {
+ TakeScreenshot();
+ queued_screenshot = 0;
+ SetMediaMode(pre_screenshot_media_mode_state);
+ }
+
+ if(queued_screenshot){
+ ++queued_screenshot;
+ }
+
+ settings_changed = false;
+}
+
+//Set area of screen to draw to
+void Graphics::setViewport(const GLint startx, const GLint starty, const GLint endx, const GLint endy) {
+ viewport_dim[0] = startx;
+ viewport_dim[1] = starty;
+ viewport_dim[2] = endx-startx;
+ viewport_dim[3] = endy-starty;
+ glScissor(viewport_dim[0], viewport_dim[1], viewport_dim[2], viewport_dim[3]);
+ glViewport(viewport_dim[0], viewport_dim[1], viewport_dim[2], viewport_dim[3]);
+}
+
+void Graphics::setAdditiveBlend(const bool additive)
+{
+ if(additive)SetBlendFunc(GL_SRC_ALPHA,GL_ONE);
+ else SetBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
+}
+
+void Graphics::SetBlendFunc(const int src, const int dst, const int alpha_src, const int alpha_dst)
+{
+ GLState &gl_state_ = shadow_state_.gl_state_;
+ if(src != gl_state_.blend_src || dst != gl_state_.blend_dst || alpha_src != gl_state_.blend_alpha_src || alpha_dst != gl_state_.blend_alpha_dst) {
+ //glBlendFunc(src,dst);
+ glBlendFuncSeparate(src, dst, alpha_src, alpha_dst);
+ gl_state_.blend_src = src;
+ gl_state_.blend_dst = dst;
+ gl_state_.blend_alpha_src = alpha_src;
+ gl_state_.blend_alpha_dst = alpha_dst;
+ }
+}
+
+void Graphics::setDepthFunc(const int type) {
+
+ if (shadow_state_.depth_func_unset_||type!=shadow_state_.depth_func_type_) {
+ glDepthFunc(type);
+ shadow_state_.depth_func_type_ = type;
+ shadow_state_.depth_func_unset_ = false;
+ }
+}
+
+Graphics::Graphics():
+ gl_lib_loaded_(false),
+ depth_prepass(true),
+ shadow_size(20),
+ cascade_shadow_res(4096),
+ shadow_res(1024*2),
+ vbo_array_bound(-1),
+ vbo_element_bound(-1),
+ multisample_framebuffer_exists(false),
+ framebuffer_exists(false),
+ old_FSAA_samples(0),
+ old_post_effects_enable(false),
+ first_load(true),
+ initialized(false),
+ settings_changed(false),
+ sdl_window_(NULL),
+ gl_context(NULL),
+ active_vertex_attrib_arrays(0),
+ wanted_vertex_attrib_arrays(0),
+ max_vertex_attrib_arrays(0),
+ cascade_shadow_fb((GLuint)INVALID_FRAMEBUFFER),
+ static_shadow_fb((GLuint)INVALID_FRAMEBUFFER),
+ cascade_shadow_color_fb((GLuint)INVALID_FRAMEBUFFER),
+ shader("post")
+{
+ old_render_dims[0] = 0;
+ old_render_dims[1] = 0;
+ config_.SetDetailObjectDecals(true);
+ config_.SetDetailObjectShadows(true);
+ config_.SetDetailObjectLowres(false);
+ config_.SetSimpleFog(false);
+ config_.SetSplitScreen(false);
+ config_.SetFSAASamples(0);
+ config_.SetAnisotropy(0);
+ config_.SetTargetMonitor(0);
+ config_.SetFullscreen(FullscreenMode::kWindowed);
+ config_.SetVSync(false);
+ config_.SetLimitFpsInGame(false);
+ config_.SetMaxFrameRate(60);
+ config_.dynamic_character_cubemap_ = false;
+ shadow_state_.depth_func_unset_ = true;
+ media_mode_ = false;
+ memset(shadow_state_.client_states_,0,sizeof(shadow_state_.client_states_));
+ shadow_state_.client_active_textures_ = 0;
+}
+
+void Graphics::Initialize()
+{
+ initialized = true;
+}
+
+void Graphics::GetShaderNames(std::map<std::string, int>& preload_shaders) {
+ preload_shaders[shader] = 0;
+}
+
+void Graphics::WindowResized( ivec2 value ) {
+ if(config_.full_screen() == FullscreenMode::kWindowed) {
+ CheckForWindowResize();
+ }
+}
+
+void Graphics::Dispose() {
+ GL_PERF_FINALIZE();
+
+ static_shadow_depth_ref.clear();
+ cascade_shadow_depth_ref.clear();
+ cascade_shadow_color_ref.clear();
+
+ screen_color_tex.clear();
+ screen_vel_tex.clear();
+ screen_depth_tex.clear();
+
+ post_effects.temp_screen_tex.clear();
+ post_effects.tone_mapped_tex.clear();
+
+ if (gl_context) {
+ SDL_GL_DeleteContext(gl_context);
+ gl_context = NULL;
+ }
+
+ if( sdl_window_ )
+ {
+ SDL_DestroyWindow(sdl_window_);
+ sdl_window_ = NULL;
+ }
+}
+
+bool CheckGLError(int line, const char* file, const char* exterrmsg) {
+ GLenum errCode;
+ const char *errString;
+ errCode = glGetError();
+ if (errCode != GL_NO_ERROR) {
+ errString = (const char*)gluErrorString(errCode);
+ if(errCode == 0x0506) {
+ errString = "Invalid framebuffer operation";
+ }
+ char error_msg[1024];
+ int i = 0;
+ int last_slash = 0;
+ while(file[i] != '\0') {
+ if(file[i] == '\\' || file[i] == '/') last_slash = i+1;
+ i++;
+ }
+ if( exterrmsg ) {
+ FormatString(error_msg, 1024, "%s\n%s", exterrmsg, errString);
+ } else {
+ FormatString(error_msg, 1024, "On line %d of %s: \n%s", line, &file[last_slash], errString);
+ }
+ DisplayError("OpenGL error", error_msg);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool CheckGLErrorStr(char* output, unsigned length) {
+ GLenum errCode;
+ const char *errString;
+ errCode = glGetError();
+ if (errCode != GL_NO_ERROR) {
+ errString = (const char*)gluErrorString(errCode);
+ if(errCode == 0x0506) {
+ errString = "Invalid framebuffer operation";
+ }
+ FormatString(output,length,"%s",errString);
+ return true;
+ }
+ return false;
+}
+
+void CheckFBOError(int line, const char* file) {
+ if(!glCheckFramebufferStatus){
+ return;
+ }
+ GLenum errCode;
+ const int ERR_STRING_BUF_SIZE = 256;
+ char errString[ERR_STRING_BUF_SIZE];
+ errCode = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ if (errCode != GL_FRAMEBUFFER_COMPLETE) {
+ switch (errCode) {
+ case GL_FRAMEBUFFER_UNSUPPORTED:
+ FormatString(errString, ERR_STRING_BUF_SIZE, "Framebuffer - unsupported");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
+ FormatString(errString, ERR_STRING_BUF_SIZE, "Framebuffer - incomplete attachment");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
+ FormatString(errString, ERR_STRING_BUF_SIZE, "Framebuffer - missing attachment");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
+ FormatString(errString, ERR_STRING_BUF_SIZE, "Framebuffer - incomplete dimensions");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
+ FormatString(errString, ERR_STRING_BUF_SIZE, "Framebuffer - incomplete formats");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
+ FormatString(errString, ERR_STRING_BUF_SIZE, "Framebuffer - incomplete draw buffer");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
+ FormatString(errString, ERR_STRING_BUF_SIZE, "Framebuffer - incomplete read buffer");
+ break;
+ default:
+ FormatString(errString, ERR_STRING_BUF_SIZE, "Framebuffer error");
+ break;
+ }
+ char error_msg[1024];
+ int i = 0;
+ int last_slash = 0;
+ while(file[i] != '\0') {
+ if(file[i] == '\\' || file[i] == '/') last_slash = i+1;
+ i++;
+ }
+ FormatString(error_msg, 1024, "On line %d of %s: \n%s", line, &file[last_slash], errString);
+ DisplayError("OpenGL error", error_msg);
+ }
+}
+
+void Graphics::BlitDepthBuffer() {
+ Shaders::Instance()->setProgram(Shaders::Instance()->returnProgram(shader));
+ GLState state;
+ state.depth_test = true;
+ state.depth_write = true;
+ state.blend = false;
+ state.cull_face = false;
+ setGLState(state);
+ // The above code is here to fix a mysterious problem
+ GLint depth_attachment_object_type[2];
+ GLint depth_attachment_object_name[2];
+ bindFramebuffer(multisample_framebuffer);
+ glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &depth_attachment_object_type[0]);
+ glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &depth_attachment_object_name[0]);
+ bindFramebuffer(framebuffer);
+ glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &depth_attachment_object_type[1]);
+ glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &depth_attachment_object_name[1]);
+ CHECK_FBO_ERROR();
+ CHECK_GL_ERROR();
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, multisample_framebuffer);
+ CHECK_FBO_ERROR();
+ CHECK_GL_ERROR();
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
+ CHECK_FBO_ERROR();
+ CHECK_GL_ERROR();
+ glBlitFramebuffer(0, 0, render_dims[0], render_dims[1], 0, 0, render_dims[0], render_dims[1], GL_DEPTH_BUFFER_BIT, GL_NEAREST);
+ CHECK_FBO_ERROR();
+ CHECK_GL_ERROR();
+ glBindFramebuffer(GL_FRAMEBUFFER, curr_framebuffer);
+ CHECK_FBO_ERROR();
+ CHECK_GL_ERROR();
+}
+
+static void ApplyVsync(bool val) {
+ // Set VSync
+ if (val) {
+ /* try late-swap-tearing first. If not supported, try normal vsync. */
+ if (SDL_GL_SetSwapInterval(-1) == -1) {
+ SDL_GL_SetSwapInterval(1);
+ }
+ } else {
+ SDL_GL_SetSwapInterval(0); /* disable vsync. */
+ }
+}
+
+void Graphics::SetAnisotropy(float val){
+ if(GLEW_EXT_texture_filter_anisotropic){
+ static float max_anisotropy = -1.0f;
+ if(max_anisotropy == -1.0f){
+ glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_anisotropy);
+ }
+ config_.SetAnisotropy(min(val,max_anisotropy));
+ if(config_.anisotropy() != 0.0f){
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, config_.anisotropy());
+ }
+ } else {
+ config_.SetAnisotropy(0.0f);
+ }
+}
+
+static void SDL_GL_SetAttributeErrCheck(SDL_GLattr attr, int value) {
+ if(SDL_GL_SetAttribute(attr, value) != 0){
+ FatalError("Error", "Could not set SDL_GL_attribute %d", (int)attr);
+ }
+}
+
+void GLAPIENTRY amd_debug_callback(GLuint id, GLenum category, GLenum severity, GLsizei length, const GLchar* message, void* userParam) {
+ LOGW << message << std::endl;
+ if(g_opengl_callback_error_dialog) {
+ DisplayError("GL debug error", message);
+ }
+}
+
+const char* arb_debug_enum_string(GLenum val){
+ switch(val){
+ case GL_DEBUG_SOURCE_API_ARB: return "Source: API";
+ case GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB: return "Source: Window System";
+ case GL_DEBUG_SOURCE_SHADER_COMPILER_ARB: return "Source: Shader Compiler";
+ case GL_DEBUG_SOURCE_THIRD_PARTY_ARB: return "Source: Third Party";
+ case GL_DEBUG_SOURCE_APPLICATION_ARB: return "Source: Application";
+ case GL_DEBUG_SOURCE_OTHER_ARB: return "Source: Other";
+ case GL_DEBUG_TYPE_ERROR_ARB: return "Type: Error";
+ case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB: return "Type: Deprecated Behavior";
+ case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB: return "Type: Undefined Behavior";
+ case GL_DEBUG_TYPE_PORTABILITY_ARB: return "Type: Portability";
+ case GL_DEBUG_TYPE_PERFORMANCE_ARB: return "Type: Performance";
+ case GL_DEBUG_TYPE_OTHER_ARB: return "Type: Other";
+ case GL_DEBUG_SEVERITY_HIGH_ARB: return "Severity: High";
+ case GL_DEBUG_SEVERITY_MEDIUM_ARB: return "Severity: Medium";
+ case GL_DEBUG_SEVERITY_LOW_ARB: return "Severity: Low";
+ default: return "Unknown ARB Debug Enum";
+ }
+}
+
+void GLAPIENTRY arb_debug_callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) {
+ const int kBufSize = 1024;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "%s\n%s\nID: %d\n%s\n%s",
+ arb_debug_enum_string(source), arb_debug_enum_string(type),
+ id, arb_debug_enum_string(severity), message);
+
+ if(source == GL_DEBUG_SOURCE_API_ARB && type == GL_DEBUG_TYPE_OTHER_ARB && id == 131185){
+ //LOGD << buf << std::endl;
+ return; // Ignore NVIDIA notifications that VBO will use Video RAM as source
+ }
+ if(source == GL_DEBUG_SOURCE_API_ARB && id == 131186){
+ //LOGD << buf << std::endl;
+ return; // Ignore NVIDIA notifications that VBO will use Video RAM as source
+ }
+ if(source == GL_DEBUG_SOURCE_API_ARB && type == GL_DEBUG_TYPE_PERFORMANCE_ARB && id == 131218){
+ LOGD << buf << std::endl;
+ return; // Ignore NVIDIA notifications that shader is going to be recompiled
+ // because the shader key based on GL state mismatches, because I don't
+ // know what to do about that right now
+ // TODO: fix this and turn the warning back on
+ }
+ if(source == GL_DEBUG_SOURCE_API_ARB && type == GL_DEBUG_TYPE_PERFORMANCE_ARB && id == 2){
+ LOGD << buf << std::endl;
+ return; // Same as above but for Intel
+ }
+ if(source == GL_DEBUG_SOURCE_API_ARB && type == GL_DEBUG_TYPE_PERFORMANCE_ARB && id == 131154){
+ LOGD << buf << std::endl;
+ return; // Ignore NVIDIA warning that pixel transfer is synchronized with 3d rendering
+ }
+
+ LOGW << buf << std::endl;
+ if(g_opengl_callback_error_dialog) {
+ DisplayError("GL debug error", buf);
+ }
+}
+
+extern float last_shadow_update_time;
+
+//Set up the screen
+void Graphics::InitScreen(){
+ PROFILER_ZONE(g_profiler_ctx, "Graphics::InitScreen");
+ Textures* textures = Textures::Instance();
+ Shaders* shaders = Shaders::Instance();
+ if(first_load){
+ PROFILER_ENTER(g_profiler_ctx, "Setting up driver and window");
+ int n_video_driver = SDL_GetNumVideoDrivers();
+ LOGI << "There are " << n_video_driver << " available video drivers." << std::endl;
+ LOGI << "Video drivers are following:" << std::endl;
+
+ for( int i = 0; i < n_video_driver; i++ )
+ {
+ LOGI << "Video driver " << i << ": " << SDL_GetVideoDriver(i) << std::endl;
+ }
+
+ // Initialize default video driver
+ if (SDL_VideoInit(NULL) < 0) {
+ FatalError("Error", "Could not initialize default video driver");
+ } else {
+ LOGI << "Initialized video driver: " << SDL_GetCurrentVideoDriver() << std::endl;
+ }
+ //Set GL attributes
+ SDL_GL_SetAttributeErrCheck( SDL_GL_RED_SIZE, 8 );
+ SDL_GL_SetAttributeErrCheck( SDL_GL_GREEN_SIZE, 8 );
+ SDL_GL_SetAttributeErrCheck( SDL_GL_BLUE_SIZE, 8 );
+ SDL_GL_SetAttributeErrCheck( SDL_GL_ALPHA_SIZE, 8 );
+ SDL_GL_SetAttributeErrCheck( SDL_GL_DEPTH_SIZE, 24 );
+ SDL_GL_SetAttributeErrCheck( SDL_GL_DOUBLEBUFFER, 1 );
+ SDL_GL_SetAttributeErrCheck( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
+ SDL_GL_SetAttributeErrCheck( SDL_GL_CONTEXT_MAJOR_VERSION, 3);
+ SDL_GL_SetAttributeErrCheck( SDL_GL_CONTEXT_MINOR_VERSION, 2);
+ SDL_GL_SetAttributeErrCheck( SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, 1);
+#ifndef NO_GL_ERROR_CHECKING
+ SDL_GL_SetAttributeErrCheck( SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
+#endif
+ // MSAA is handled later with renderbuffers and fbo
+ SDL_GL_SetAttributeErrCheck( SDL_GL_MULTISAMPLEBUFFERS, false);
+ SDL_GL_SetAttributeErrCheck( SDL_GL_MULTISAMPLESAMPLES, 0);
+
+ LOGI << "Attribute group 1 set" << std::endl;
+
+ // Create window
+ const char* window_title = "Overgrowth";
+ uint32_t window_flags = SDL_WINDOW_OPENGL;
+
+ int target_monitor = config_.target_monitor();
+ if(target_monitor >= config.GetMonitorCount()) {
+ target_monitor = config.GetMonitorCount() - 1;
+ config.GetRef("target_monitor") = target_monitor;
+ }
+
+ currentFullscreenType = config_.full_screen();
+ switch (config_.full_screen()) {
+ case FullscreenMode::kWindowed: {
+ window_flags |= SDL_WINDOW_RESIZABLE;
+
+ window_dims[0] = config_.screen_width();
+ window_dims[1] = config_.screen_height();
+ break;
+ }
+ case FullscreenMode::kFullscreen: {
+ window_flags |= SDL_WINDOW_FULLSCREEN;
+
+ window_dims[0] = config_.screen_width();
+ window_dims[1] = config_.screen_height();
+ break;
+ }
+ case FullscreenMode::kWindowed_borderless: {
+ window_flags |= SDL_WINDOW_BORDERLESS;
+
+ window_dims[0] = config_.screen_width();
+ window_dims[1] = config_.screen_height();
+ break;
+ }
+ case FullscreenMode::kFullscreen_borderless: {
+ window_flags |= SDL_WINDOW_BORDERLESS;
+
+ SDL_DisplayMode displayMode;
+ SDL_GetCurrentDisplayMode(target_monitor, &displayMode);
+
+ window_dims[0] = displayMode.w;
+ window_dims[1] = displayMode.h;
+ }
+ }
+
+ SDL_DisplayMode desktop_mode;
+ SDL_GetCurrentDisplayMode(target_monitor, &desktop_mode);
+
+ if (window_dims[0] > desktop_mode.w || window_dims[1] > desktop_mode.h) {
+ bool fitsW = false;
+ bool fitsH = false;
+
+ int displayModeCount = SDL_GetNumDisplayModes(target_monitor);
+ for (int i = 0; i < displayModeCount; ++i) {
+ SDL_DisplayMode mode;
+ SDL_GetDisplayMode(target_monitor, i, &mode);
+
+ if (mode.w >= window_dims[0])
+ fitsW = true;
+ if (mode.h >= window_dims[1])
+ fitsH = true;
+
+ if (fitsW && fitsH)
+ break;
+ }
+
+ // When DPI scaling is active in windows the window might not fit (says windows)
+ // but if it's fullscreen the display settings will be overridden anyway
+ if (!fitsW || !fitsH || config_.full_screen() != FullscreenMode::kFullscreen)
+ {
+ window_dims[0] = std::min(window_dims[0], desktop_mode.w);
+ window_dims[1] = std::min(window_dims[1], desktop_mode.h);
+
+ config.GetRef("screenwidth") = window_dims[0];
+ config.GetRef("screenheight") = window_dims[1];
+
+ config_.SetScreenWidth(window_dims[0]);
+ config_.SetScreenHeight(window_dims[1]);
+ }
+ }
+
+ sdl_window_ = SDL_CreateWindow(
+ window_title,
+ SDL_WINDOWPOS_UNDEFINED_DISPLAY(target_monitor), SDL_WINDOWPOS_UNDEFINED_DISPLAY(target_monitor),
+ window_dims[0], window_dims[1],
+ window_flags);
+
+ if(!sdl_window_){
+ const char* sdl_error = SDL_GetError();
+ LOGE << "Unable to create window, sdl reason: " << sdl_error << std::endl;
+ if(strcmp("No matching GL pixel format available", sdl_error) == 0) {
+ FatalError("Error", "Could not create window. Make sure your computer is set to 32 bit color depth");
+ } else {
+ FatalError("Error", "Could not create window");
+ }
+
+ }
+ LOGI << "Created window" << std::endl;
+ SDL_SetWindowMinimumSize(sdl_window_, 640, 480);
+ LOGI << "Set minimum size" << std::endl;
+ // Set window to be fullscreen (or not)
+ SDL_DisplayMode fullscreen_mode;
+ SDL_zero(fullscreen_mode);
+ fullscreen_mode.format = SDL_PIXELFORMAT_RGB888;
+ fullscreen_mode.w = window_dims[0];
+ fullscreen_mode.h = window_dims[1];
+ SDL_DisplayMode closest_fullscreen_mode;
+ SDL_GetClosestDisplayMode(target_monitor, &fullscreen_mode, &closest_fullscreen_mode);
+ if(SDL_SetWindowDisplayMode(sdl_window_, &closest_fullscreen_mode) != 0){
+ FatalError("Error", "Could not set window display mode");
+ }
+ LOGI << "Set display mode" << std::endl;
+
+ gl_context = SDL_GL_CreateContext(sdl_window_);
+ LOGI << "Created OpenGL Context" << std::endl;
+ if (!gl_context) {
+ FatalError("Error", "SDL_GL_CreateContext(): %s\n%s", SDL_GetError(), "It's likely your computer can't create an OpenGL 3.2 context. Do you have the latest drivers installed?");
+ }
+ SDL_GL_GetDrawableSize(sdl_window_, &window_dims[0], &window_dims[1]);
+ // Check what GL context we actually received
+ int data[5];
+ SDL_GL_GetAttribute( SDL_GL_RED_SIZE, &data[0] );
+ SDL_GL_GetAttribute( SDL_GL_GREEN_SIZE, &data[1] );
+ SDL_GL_GetAttribute( SDL_GL_BLUE_SIZE, &data[2] );
+ SDL_GL_GetAttribute( SDL_GL_ALPHA_SIZE, &data[3] );
+ SDL_GL_GetAttribute( SDL_GL_DEPTH_SIZE, &data[4] );
+ LOGI << "RGBA bits, depth: " << data[0] << " " << data[1] << " " << data[2] << " " << data[3] << " " << data[4] << std::endl;
+ SDL_GL_GetAttribute( SDL_GL_DOUBLEBUFFER, &data[0]);
+ SDL_GL_GetAttribute(SDL_GL_MULTISAMPLEBUFFERS, &data[1]);
+ SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &data[2]);
+ if(!data[0]){
+ FatalError("Error","Could not create double-buffered OpenGL context");
+ }
+ LOGI << "Anti-aliasing samples: " << data[2] << std::endl;
+ int sdl_gl_major, sdl_gl_minor, sdl_gl_profile_mask;
+ SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &sdl_gl_profile_mask);
+ SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &sdl_gl_major);
+ SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &sdl_gl_minor);
+
+ LOGI << "SDL OpenGL Context Result: " << sdl_gl_major << "." << sdl_gl_minor << " With a " << SDL_GLprofile_string((SDL_GLprofile)sdl_gl_profile_mask) << " profile context" << std::endl;
+
+ // Initialize GLEW
+ GLenum err = glewInit();
+ GL_PERF_INIT( );
+ if (err != GLEW_OK) {
+ FatalError("Error", "GLEW error: %s", glewGetErrorString(err));
+ }
+ LOGI << "GLEW Version loaded: " << glewGetString(GLEW_VERSION) << std::endl;
+
+ GLint gl_major = 0, gl_minor = 0;
+ if( sdl_gl_major >= 3 ) {
+ glGetIntegerv(GL_MAJOR_VERSION, &gl_major);
+ glGetIntegerv(GL_MINOR_VERSION, &gl_minor);
+
+ LOGI << "OpenGL Self Reported version string: " << glGetString(GL_VERSION) << std::endl;
+ LOGI << "OpenGL Self Reported version values: " << gl_major << "." << gl_minor << std::endl;
+ } else if( sdl_gl_major >= 2 ) {
+ LOGI << "OpenGL Self Reported version: " << glGetString(GL_VERSION) << std::endl;
+ } else {
+ LOGI << "OpenGL has no self reporting routine for 1.x contexts." << std::endl;
+ }
+
+ if (!GLEW_VERSION_3_2) {
+ LOGE << "GLEW claims it doesn't have an OpenGL 3.2 context" << std::endl;
+ }
+
+ if( GLEW_VERSION_3_2 || (sdl_gl_major == 3 && sdl_gl_minor >= 2) || sdl_gl_major > 3 ) {
+ LOGI << "Context seems acceptable for running the application, continuing." << std::endl;
+ } else {
+ FatalError("Error", "OpenGL-3.2-compatible graphics drivers not found");
+ }
+
+ DetectAndSetOpenGLFeatureRestrictions();
+
+ g_s3tc_dxt5_textures = config["gl_load_s3tc"].toBool() && g_s3tc_dxt5_support;
+
+ if(g_s3tc_dxt5_textures == false) {
+ FatalError("Error", "No support for S3TC DXT5 textures detected. This means either your GPU is too old to run the game, or your drivers are out-of-date");
+ }
+
+ bool g_opengl_callback_error_dialog = config["opengl_callback_error_dialoge"].toNumber<bool>();
+
+ if(config["opengl_callback_errors"].toNumber<bool>()) {
+ LOGI << "Activating OpenGL callback errors, [opengl_callback_errors]" << std::endl;
+ if(GLEW_ARB_debug_output){
+ glDebugMessageCallbackARB(&arb_debug_callback, NULL);
+ glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
+ } else if(GLEW_AMD_debug_output){
+ glDebugMessageEnableAMD(0, 0, 0, NULL, true);
+ glDebugMessageCallbackAMD(&amd_debug_callback, NULL);
+ // GL_DEBUG_OUTPUT_SYNCHRONOUS is part of the KHR and ARB
+ // extension, but not the AMD one
+ //glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
+ }
+ } else {
+ LOGI << "Deactivating OpenGL callback errors, [opengl_callback_errors]" << std::endl;
+ }
+
+ while(glGetError() != GL_NO_ERROR) {
+ // Throw away GL errors from SDL/GLEW init
+ }
+ CHECK_GL_ERROR();
+ // Determine what framebuffer features are supported
+ if (GLEW_VERSION_3_0 || GLEW_EXT_framebuffer_object || GLEW_ARB_framebuffer_object) {
+ } else {
+ FatalError("Error", "No support for Framebuffer Objects detected");
+ }
+ features_.SetFrameBufferFSAAEnabled(GLEW_EXT_framebuffer_multisample!=0);
+ CHECK_GL_ERROR();
+ // Determine what instanced array features are supported
+ if (GLEW_VERSION_3_3 || GLEW_ARB_instanced_arrays) {
+ g_attrib_envobj_intancing_support = true;
+ } else {
+ g_attrib_envobj_intancing_support = false;
+ }
+ ApplyVsync(config_.vSync());
+ // Clear screen
+ glClearColor( 0.5f, 0.5f, 0.5f, 0 );
+ setViewport( 0, 0, window_dims[0], window_dims[1] );
+ glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
+ SDL_GL_SwapWindow(sdl_window_);
+ CHECK_GL_ERROR();
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ textures->ResetVRAM();
+ shaders->ResetVRAM();
+ CHECK_GL_ERROR();
+
+ vendor = GetGLVendor();
+ SetAnisotropy(config_.anisotropy());
+
+ features_.SetHDREnable( GLEW_VERSION_3_0 || GLEW_ARB_texture_float );
+
+ // Set initial GL state so it is in sync with shadow state
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_CULL_FACE);
+ glDisable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
+ glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+
+ shadow_state_.gl_state_.depth_test = false;
+ shadow_state_.gl_state_.cull_face = false;
+ shadow_state_.gl_state_.blend = false;
+ shadow_state_.gl_state_.blend_src = GL_SRC_ALPHA;
+ shadow_state_.gl_state_.blend_dst = GL_ONE_MINUS_SRC_ALPHA;
+ shadow_state_.gl_state_.blend_alpha_src = GL_ONE;
+ shadow_state_.gl_state_.blend_alpha_dst = GL_ONE_MINUS_SRC_ALPHA;
+ shadow_state_.polygon_offset_enable_ = false;
+
+ curr_framebuffer = 0;
+
+ drawing_shadow = false;
+
+ line_width = -1;
+
+ // Warn if not enough shader varyings are supported
+ #ifndef __APPLE__ // Why does this not work on Mac OS 10.7.5 core profile?
+ int assumed_floats = 32;
+ int max_varying_floats;
+ glGetIntegerv( GL_MAX_VARYING_FLOATS, &max_varying_floats );
+ if(max_varying_floats < assumed_floats) {
+ std::ostringstream oss;
+ oss << "Only " << max_varying_floats << " varying floats supported, "
+ << "up to " << assumed_floats << " are used in shaders.";
+ DisplayError("Warning",oss.str().c_str());
+ }
+ #endif
+
+ textures->setWrap(GL_CLAMP_TO_EDGE);
+ CHECK_GL_ERROR();
+
+ int w = config_.screen_width();
+ int h = config_.screen_height();
+ SetUpWindowDims(w, h);
+
+ PROFILER_ENTER(g_profiler_ctx, "Initializing decaltextures");
+ DecalTextures::Instance()->Init();
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ if(GLEW_ARB_tessellation_shader){
+ glPatchParameteri( GL_PATCH_VERTICES, 3 );
+ }
+ }
+
+ last_shadow_update_time = 0.0f;
+
+ use_sample_alpha_to_coverage = true;
+ if(config_.FSAA_samples()<2){
+ use_sample_alpha_to_coverage = false;
+ }
+
+ if (render_dims[0] != old_render_dims[0] || render_dims[1] != old_render_dims[1] || config_.FSAA_samples() != old_FSAA_samples) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Setting up framebuffers for post effects");
+ PushFramebuffer();
+
+ if(multisample_framebuffer_exists){
+ deleteFramebuffer(&multisample_framebuffer);
+ glDeleteRenderbuffers(1, &multisample_color);
+ glDeleteRenderbuffers(1, &multisample_depth);
+ glDeleteRenderbuffers(1, &multisample_vel);
+ deleteFramebuffer(&multisample_framebuffer);
+ multisample_framebuffer_exists = false;
+ }
+ if (features_.frame_buffer_fsaa_enabled() && config_.FSAA_samples() > 1) {
+ CHECK_GL_ERROR();
+ genFramebuffers(&multisample_framebuffer, "multisample");
+ bindFramebuffer(multisample_framebuffer);
+
+ CHECK_GL_ERROR();
+ int samples = config_.FSAA_samples();
+ glGenRenderbuffers(1, &multisample_color);
+ CHECK_GL_ERROR();
+ glBindRenderbuffer(GL_RENDERBUFFER, multisample_color);
+ CHECK_GL_ERROR();
+ glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, features_.HDR_enable()?GL_RGBA16F:GL_RGBA, render_dims[0], render_dims[1]);
+ CHECK_GL_ERROR();
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, multisample_color);
+ CHECK_GL_ERROR();
+
+ glGenRenderbuffers(1, &multisample_vel);
+ CHECK_GL_ERROR();
+ glBindRenderbuffer(GL_RENDERBUFFER, multisample_vel);
+ CHECK_GL_ERROR();
+ glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, features_.HDR_enable()?GL_RGBA16F:GL_RGBA, render_dims[0], render_dims[1]);
+ CHECK_GL_ERROR();
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_RENDERBUFFER, multisample_vel);
+ CHECK_GL_ERROR();
+
+ glGenRenderbuffers( 1, &multisample_depth );
+ glBindRenderbuffer( GL_RENDERBUFFER, multisample_depth );
+ glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_DEPTH_COMPONENT24, render_dims[0], render_dims[1]);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, multisample_depth);
+ CHECK_GL_ERROR();
+ multisample_framebuffer_exists = true;
+ }
+ if(framebuffer_exists){
+ deleteFramebuffer(&framebuffer);
+ screen_color_tex.clear();
+ screen_depth_tex.clear();
+ deleteFramebuffer(&post_effects.post_framebuffer);
+ post_effects.temp_screen_tex.clear();
+ post_effects.tone_mapped_tex.clear();
+ textures->DeleteUnusedTextures();
+ }
+
+ genFramebuffers(&framebuffer, "generic");
+ bindFramebuffer(framebuffer);
+
+ screen_color_tex = textures->makeRectangularTexture(render_dims[0],render_dims[1],features_.HDR_enable()?GL_RGBA16F:GL_RGBA,GL_RGBA);
+ textures->SetTextureName(screen_color_tex, "Post::Screen Color");
+ screen_vel_tex = textures->makeRectangularTexture(render_dims[0],render_dims[1],features_.HDR_enable()?GL_RGBA16F:GL_RGBA,GL_RGBA);
+ textures->SetTextureName(screen_vel_tex, "Post::Screen Velocity");
+ screen_depth_tex = textures->makeRectangularTexture(render_dims[0],render_dims[1],GL_DEPTH_COMPONENT24,GL_DEPTH_COMPONENT);
+ textures->SetTextureName(screen_depth_tex, "Post::Screen Depth");
+
+ genFramebuffers(&post_effects.post_framebuffer, "post_effects");
+ post_effects.temp_screen_tex = textures->makeRectangularTexture(
+ render_dims[0],render_dims[1],
+ features().HDR_enable()?GL_RGBA16F:GL_RGBA,GL_RGBA);
+ textures->SetTextureName(post_effects.temp_screen_tex, "Post::Screen Color - Temp");
+
+ post_effects.tone_mapped_tex = textures->makeRectangularTexture(
+ render_dims[0],render_dims[1],
+ features().HDR_enable()?GL_RGBA16F:GL_RGBA,GL_RGBA);
+ textures->SetTextureName(post_effects.tone_mapped_tex, "Post::Tone-Mapped");
+
+ framebufferColorTexture2D(screen_color_tex);
+ {
+ TextureRef t = screen_vel_tex;
+ Textures::Instance()->EnsureInVRAM(t);
+ CHECK_GL_ERROR();
+ GLuint tex = Textures::Instance()->returnTexture(t);
+ LOGS << "Binding gl texture " << tex << " to framebuffer" << std::endl;
+ if(!t.valid()) {
+ LOGE << "Not valid..." << std::endl;
+ }
+ if(!glIsTexture(tex)){
+ LOGE << "Not a texture..." << std::endl;
+ }
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, tex, 0);
+ }
+ CHECK_FBO_ERROR();
+ framebufferDepthTexture2D(screen_depth_tex);
+ CHECK_FBO_ERROR();
+ framebuffer_exists = true;
+
+ PopFramebuffer();
+ }
+
+ SetUpShadowTextures();
+
+ if(first_load){
+ textures->setWrap(GL_REPEAT);
+ noise_ref = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/noise.tga", PX_NOREDUCE | PX_NOCONVERT, 0x0);
+ pure_noise_ref = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/purenoise_nocompress.tga", PX_NOREDUCE | PX_NOCONVERT, 0x0);
+
+ glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,&max_texture_units);
+ LOGI << max_texture_units << " texture units supported." << std::endl;
+ if(max_texture_units<16){
+ DisplayError("Warning","Fewer than 16 texture units supported!");
+ }
+
+ #ifdef __APPLE__
+ // Mac multithreaded OpenGL?
+ /*CGLContextObj cctx = CGLGetCurrentContext();
+ CGLError err = CGLEnable( cctx, kCGLCEMPEngine);
+ if(err != kCGLNoError){
+ DisplayError("Error","Problem initializing multi-threaded OpenGL");
+ }*/
+ #endif
+
+ GLint param;
+ glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &param);
+ LOGI << "GL_MAX_VERTEX_UNIFORM_COMPONENTS: " << param << std::endl;
+ }
+
+ glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &max_vertex_attrib_arrays);
+ old_render_dims[0] = render_dims[0];
+ old_render_dims[1] = render_dims[1];
+ old_FSAA_samples = config_.FSAA_samples();
+ first_load = false;
+ gl_lib_loaded_ = true;
+ settings_changed = true;
+ SetSeamlessCubemaps(config_.seamless_cubemaps_);
+
+ if(!glGenVertexArrays){
+ FatalError("Error", "No support detected for vertex array objects");
+ }
+ // Needed for GL 3.2 core
+ GLuint vao;
+ glGenVertexArrays(1, &vao);
+ glBindVertexArray(vao);
+ CHECK_GL_ERROR();
+
+ if(GLEW_ARB_texture_cube_map_array){
+ LOGI << "GLEW_ARB_texture_cube_map_array is available" << std::endl;
+ } else {
+ LOGI << "GLEW_ARB_texture_cube_map_array is NOT available" << std::endl;
+ }
+
+ if( !GLEW_EXT_texture_compression_s3tc ) {
+ LOGF << "Missing necessary GL extension: EXT_texture_compression_s3tc" << std::endl;
+ }
+
+ if( !GLEW_EXT_texture_sRGB ) {
+ LOGF << "Missing necessary GL extension: GLEW_EXT_texture_sRGB" << std::endl;
+ }
+
+ //Assume that the bindings in the state aren't valid anymore.
+ //There have been indications in the NVIDIA driver on linux that this is
+ //the case.
+ LOGI << "Unassuming that any EBO or VBO is bound" << std::endl;
+ vbo_element_bound = -1;
+ vbo_array_bound = -1;
+
+}
+
+void Graphics::SetModelMatrix( const mat4& matrix, const mat3& normal_matrix ) {
+ Shaders::Instance()->GetCurrentProgram()->SetUniform(MODEL_MATRIX,matrix);
+ Shaders::Instance()->GetCurrentProgram()->SetUniform(NORMAL_MATRIX,normal_matrix);
+}
+
+struct SimpleLineDrawState {
+ GLState state;
+ SimpleLineDrawState() {
+ state.blend = true;
+ state.cull_face = false;
+ state.depth_test = true;
+ state.depth_write = false;
+ }
+};
+
+static SimpleLineDrawState simple_line_draw_state;
+
+void Graphics::SetSimpleLineDrawState() {
+ setGLState(simple_line_draw_state.state);
+ Shaders::Instance()->noProgram();
+}
+
+void Graphics::BlitColorBuffer()
+{
+ CHECK_FBO_ERROR();
+ CHECK_GL_ERROR();
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, multisample_framebuffer);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
+ glReadBuffer(GL_COLOR_ATTACHMENT0);
+ glDrawBuffer(GL_COLOR_ATTACHMENT0);
+ glBlitFramebuffer(0, 0, render_dims[0], render_dims[1], 0, 0, render_dims[0], render_dims[1], GL_COLOR_BUFFER_BIT, GL_NEAREST);
+ glReadBuffer(GL_COLOR_ATTACHMENT1);
+ glDrawBuffer(GL_COLOR_ATTACHMENT1);
+ glBlitFramebuffer(0, 0, render_dims[0], render_dims[1], 0, 0, render_dims[0], render_dims[1], GL_COLOR_BUFFER_BIT, GL_NEAREST);
+ glBindFramebuffer(GL_FRAMEBUFFER, curr_framebuffer);
+ CHECK_FBO_ERROR();
+ CHECK_GL_ERROR();
+}
+
+void Graphics::SetClientActiveTexture(int index) {
+ if (shadow_state_.client_active_textures_ != index) {
+ glClientActiveTexture(GL_TEXTURE0 + index);
+ shadow_state_.client_active_textures_ = index;
+ }
+}
+
+GLenum GLEnumTransformer(uint32_t e)
+{
+ switch(e)
+ {
+ case CS_VERTEX_ARRAY: return GL_VERTEX_ARRAY;
+ case CS_NORMAL_ARRAY: return GL_NORMAL_ARRAY;
+ case CS_COLOR_ARRAY: return GL_COLOR_ARRAY;
+ case CS_TEXTURE_COORD_ARRAY0: return GL_TEXTURE_COORD_ARRAY;
+ case CS_TEXTURE_COORD_ARRAY1: return GL_TEXTURE_COORD_ARRAY;
+ case CS_TEXTURE_COORD_ARRAY2: return GL_TEXTURE_COORD_ARRAY;
+ case CS_TEXTURE_COORD_ARRAY3: return GL_TEXTURE_COORD_ARRAY;
+ case CS_TEXTURE_COORD_ARRAY4: return GL_TEXTURE_COORD_ARRAY;
+ case CS_TEXTURE_COORD_ARRAY5: return GL_TEXTURE_COORD_ARRAY;
+ case CS_TEXTURE_COORD_ARRAY6: return GL_TEXTURE_COORD_ARRAY;
+ case CS_TEXTURE_COORD_ARRAY7: return GL_TEXTURE_COORD_ARRAY;
+ default: return 0;
+ }
+}
+
+void Graphics::SetClientStateEnabled(int type, bool enabled) {
+ LOG_ASSERT(type < CS_MAX_SHADOWED_CLIENT_STATES);
+ if (enabled != shadow_state_.client_states_[type]) {
+ if (type >= CS_TEXTURE_COORD_ARRAY0 && type <= CS_TEXTURE_COORD_ARRAY7) {
+ SetClientActiveTexture(type - CS_TEXTURE_COORD_ARRAY0);
+ }
+ if (enabled) {
+ glEnableClientState(GLEnumTransformer(type));
+ } else {
+ glDisableClientState(GLEnumTransformer(type));
+ }
+ shadow_state_.client_states_[type] = enabled;
+ }
+}
+
+void Graphics::SetFromConfig( const Config &config, bool dynamic ) {
+ if(dynamic && config_.full_screen() == FullscreenMode::kWindowed){
+ config_.SetScreenWidth(config["screenwidth"].toNumber<int>());
+ config_.SetScreenHeight(config["screenheight"].toNumber<int>());
+ }
+ if(!dynamic){
+ config_.SetScreenWidth(config["screenwidth"].toNumber<int>());
+ config_.SetScreenHeight(config["screenheight"].toNumber<int>());
+ config_.SetFullscreen(static_cast<FullscreenMode::Mode>(config["fullscreen"].toNumber<int>()));
+ config_.SetTextureReduce(config["texture_reduce"].toNumber<int>());
+ config_.SetFSAASamples(config["multisample"].toNumber<int>());
+ config_.SetAnisotropy(config["anisotropy"].toNumber<float>());
+ config_.SetVSync(config["vsync"].toNumber<bool>());
+ config_.SetLimitFpsInGame(config["limit_fps_in_game"].toNumber<bool>());
+ config_.SetMaxFrameRate(config["max_frame_rate"].toNumber<int>());
+ config_.SetGPUSkinning(config["gpu_skinning"].toNumber<bool>());
+ config_.motion_blur_amount_ = config["motion_blur_amount"].toNumber<float>();
+ config_.SetSimpleFog(config["simple_fog"].toNumber<bool>());
+ config_.SetDetailObjects(config["detail_objects"].toNumber<bool>());
+ config_.SetDetailObjectDecals(config["detail_object_decals"].toNumber<bool>());
+ config_.SetDetailObjectLowres(config["detail_object_lowres"].toNumber<bool>());
+ config_.SetDetailObjectShadows(!config["detail_object_disable_shadows"].toNumber<bool>());
+ config_.SetDepthOfField(config["depth_of_field"].toNumber<bool>());
+ config_.SetDepthOfFieldReduced(config["depth_of_field_reduced"].toNumber<bool>());
+ }
+ config_.SetBlood(config["blood"].toNumber<int>());
+ config_.SetBloodColor(GraphicsConfig::BloodColorFromString(config["blood_color"].str()));
+ config_.SetSplitScreen(config["split_screen"].toNumber<bool>());
+ config_.seamless_cubemaps_ = config["seamless_cubemaps"].toNumber<bool>();
+ setSimpleShadows(config["simple_shadows"].toNumber<bool>());
+ setSimpleWater(config["simple_water"].toNumber<bool>());
+ SetParticleFieldSimple(config["particle_field_simple"].toNumber<bool>());
+ SetDetailObjectsReduced(config["detail_objects_reduced"].toNumber<bool>());
+ g_attrib_envobj_intancing_enabled = config["attrib_envobj_instancing"].toNumber<bool>();
+ g_perform_occlusion_query = config["occlusion_query"].toNumber<bool>();
+ g_gamma_correct_final_output = config["gamma_correct_final_output"].toNumber<bool>();
+
+ config_.SetTargetMonitor(config["target_monitor"].toNumber<int>());
+}
+
+void Graphics::SetWindowGrab( bool val ) {
+ if(!val || SDL_GetKeyboardFocus() == sdl_window_){
+ SDL_SetWindowGrab(sdl_window_, val?SDL_TRUE:SDL_FALSE);
+ }
+}
+
+void Graphics::SetClientStates( int flags ) {
+ SetClientStateEnabled(CS_VERTEX_ARRAY, (flags & F_VERTEX_ARRAY)!=0);
+ SetClientStateEnabled(CS_NORMAL_ARRAY, (flags & F_NORMAL_ARRAY)!=0);
+ SetClientStateEnabled(CS_COLOR_ARRAY, (flags & F_COLOR_ARRAY)!=0);
+ SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY0, (flags & F_TEXTURE_COORD_ARRAY0)!=0);
+ SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY1, (flags & F_TEXTURE_COORD_ARRAY1)!=0);
+ SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY2, (flags & F_TEXTURE_COORD_ARRAY2)!=0);
+ SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY3, (flags & F_TEXTURE_COORD_ARRAY3)!=0);
+ SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY4, (flags & F_TEXTURE_COORD_ARRAY4)!=0);
+ SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY5, (flags & F_TEXTURE_COORD_ARRAY5)!=0);
+ SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY6, (flags & F_TEXTURE_COORD_ARRAY6)!=0);
+ SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY7, (flags & F_TEXTURE_COORD_ARRAY7)!=0);
+}
+
+
+void Graphics::SetLineWidth( int val ) {
+ if(line_width != val){
+ line_width = val;
+ #ifndef PLATFORM_MACOSX
+ glLineWidth((float)val);
+ #endif
+ }
+}
+
+void Graphics::ResizeWindow(int& w, int& h) {
+ if (config_.full_screen() == FullscreenMode::kFullscreen) {
+ SDL_DisplayMode fullscreen_mode;
+ SDL_zero(fullscreen_mode);
+ fullscreen_mode.format = SDL_PIXELFORMAT_RGB888;
+ fullscreen_mode.w = w;
+ fullscreen_mode.h = h;
+ SDL_DisplayMode closest_fullscreen_mode;
+ if (SDL_GetClosestDisplayMode(config_.target_monitor(), &fullscreen_mode, &closest_fullscreen_mode) == NULL) {
+ LOGF << SDL_GetError() << std::endl;
+ }
+ if (SDL_SetWindowDisplayMode(sdl_window_, &closest_fullscreen_mode) != 0)
+ LOGF << SDL_GetError() << std::endl;
+
+ SDL_SetWindowSize(sdl_window_, closest_fullscreen_mode.w, closest_fullscreen_mode.h);
+
+ w = closest_fullscreen_mode.w;
+ h = closest_fullscreen_mode.h;
+ }
+ else
+ SDL_SetWindowSize(sdl_window_, w, h);
+
+ if (config_.full_screen() == FullscreenMode::kFullscreen_borderless
+ || config_.full_screen() == FullscreenMode::kWindowed_borderless) {
+ int targetMonitor = config_.target_monitor();
+ SDL_SetWindowPosition(sdl_window_, SDL_WINDOWPOS_CENTERED_DISPLAY(targetMonitor), SDL_WINDOWPOS_CENTERED_DISPLAY(targetMonitor));
+ }
+
+ SDL_GL_GetDrawableSize(sdl_window_, &window_dims[0], &window_dims[1]);
+}
+
+void Graphics::SetUpWindowDims(int& w, int& h) {
+ switch (config_.full_screen()) {
+ case FullscreenMode::kWindowed:
+ case FullscreenMode::kWindowed_borderless:
+ ResizeWindow(w, h);
+ window_dims[0] = w;
+ window_dims[1] = h;
+ render_dims[0] = window_dims[0];
+ render_dims[1] = window_dims[1];
+ render_output_dims[0] = window_dims[0];
+ render_output_dims[1] = window_dims[1];
+ break;
+ case FullscreenMode::kFullscreen:
+ ResizeWindow(w, h);
+ SDL_GetWindowSize(sdl_window_, &render_dims[0], &render_dims[1]);
+ SDL_GetWindowSize(sdl_window_, &render_output_dims[0], &render_output_dims[1]);
+ break;
+ case FullscreenMode::kFullscreen_borderless:
+ render_dims[0] = w;
+ render_dims[1] = h;
+ render_output_dims[0] = window_dims[0];
+ render_output_dims[1] = window_dims[1];
+ if(render_output_dims[0] > render_output_dims[1] * render_dims[0] / render_dims[1]){
+ render_output_dims[0] = render_output_dims[1] * render_dims[0] / render_dims[1];
+ } else if(render_output_dims[0] < render_output_dims[1] * render_dims[0] / render_dims[1]){
+ render_output_dims[1] = render_output_dims[0] * render_dims[1] / render_dims[0];
+ }
+ break;
+ }
+
+ config_.SetScreenWidth(w);
+ config_.SetScreenHeight(h);
+}
+
+void Graphics::SetResolution(int w, int h, bool force) {
+ LOGI << "SetResolution has been called" << std::endl;
+ if(render_dims[0] == w && render_dims[1] == h && !force){
+ return;
+ }
+ SetUpWindowDims(w, h);
+ if( initialized ) {
+ InitScreen();
+ }
+ LOGI << "Updating the screen resolution in the config file." << std::endl;
+ config.GetRef("screenwidth") = w;
+ config.GetRef("screenheight") = h;
+ if( initialized ) {
+ Engine::Instance()->InjectWindowResizeEvent(ivec2(w,h));
+ }
+}
+
+void Graphics::SetTargetMonitor(int targetMonitor) {
+ if (SDL_GetWindowDisplayIndex(sdl_window_) != targetMonitor) {
+ if (currentFullscreenType == FullscreenMode::kFullscreen) {
+ SDL_SetWindowFullscreen(sdl_window_, SDL_FALSE);
+ SDL_SetWindowPosition(sdl_window_, SDL_WINDOWPOS_CENTERED_DISPLAY(targetMonitor), SDL_WINDOWPOS_CENTERED_DISPLAY(targetMonitor));
+ SDL_SetWindowFullscreen(sdl_window_, SDL_TRUE);
+ }
+ else
+ SDL_SetWindowPosition(sdl_window_, SDL_WINDOWPOS_CENTERED_DISPLAY(targetMonitor), SDL_WINDOWPOS_CENTERED_DISPLAY(targetMonitor));
+
+ SDL_DisplayMode displayMode;
+ SDL_GetDesktopDisplayMode(config_.target_monitor(), &displayMode);
+
+ if (window_dims[0] > displayMode.w || window_dims[1] > displayMode.h)
+ SetResolution(displayMode.w, displayMode.h, true);
+ }
+}
+
+void Graphics::CheckForWindowResize() {
+ int test[2];
+ SDL_GL_GetDrawableSize(sdl_window_, &test[0], &test[1]);
+ if(test[0] != window_dims[0] || test[1] != window_dims[1]){
+ SetResolution(test[0], test[1], true);
+ }
+}
+
+void Graphics::SetFullscreen(FullscreenMode::Mode val) {
+ config_.SetFullscreen(val);
+
+ // If entering windowed mode, resize the window to half the display resolution and center it,
+ // Otherwise the title bar (on windows at least) might appear outside of the screen bounds
+ bool resize = false;
+ bool recenter = false;
+
+ switch (val) {
+ case FullscreenMode::kWindowed: {
+ if (currentFullscreenType != FullscreenMode::kWindowed) {
+ resize = true;
+ recenter = true;
+ }
+
+ SDL_SetWindowFullscreen(sdl_window_, SDL_FALSE);
+ SDL_SetWindowBordered(sdl_window_, SDL_TRUE);
+#if PLATFORM_WINDOWS //Following function added in SDL 2.0.5
+ SDL_SetWindowResizable(sdl_window_, SDL_TRUE);
+#endif
+ break;
+ }
+ case FullscreenMode::kFullscreen: {
+ int w = config_.screen_width();
+ int h = config_.screen_height();
+ ResizeWindow(w, h);
+
+ SDL_SetWindowFullscreen(sdl_window_, SDL_WINDOW_FULLSCREEN);
+ break;
+ }
+ case FullscreenMode::kWindowed_borderless: {
+ SDL_SetWindowFullscreen(sdl_window_, SDL_WINDOW_BORDERLESS);
+ SDL_SetWindowBordered(sdl_window_, SDL_FALSE);
+#if PLATFORM_WINDOWS //Following function added in SDL 2.0.5
+ SDL_SetWindowResizable(sdl_window_, SDL_FALSE);
+#endif
+ recenter = true;
+ break;
+ }
+ case FullscreenMode::kFullscreen_borderless: {
+ SDL_SetWindowFullscreen(sdl_window_, SDL_WINDOW_BORDERLESS);
+ SDL_SetWindowBordered(sdl_window_, SDL_FALSE);
+#if PLATFORM_WINDOWS //Following function added in SDL 2.0.5
+ SDL_SetWindowResizable(sdl_window_, SDL_FALSE);
+#endif
+
+ SDL_DisplayMode displayMode;
+ SDL_GetDesktopDisplayMode(config_.target_monitor(), &displayMode);
+
+ ResizeWindow(displayMode.w, displayMode.h); // Recenters window
+ break;
+ }
+ }
+
+
+ if(resize) {
+ SDL_DisplayMode displayMode;
+ SDL_GetDesktopDisplayMode(config_.target_monitor(), &displayMode);
+ SetResolution(displayMode.w / 2, displayMode.h / 2, true);
+ } else {
+ SetResolution(render_dims[0], render_dims[1], true);
+ }
+
+ if(recenter) {
+ SDL_SetWindowPosition(sdl_window_, SDL_WINDOWPOS_CENTERED_DISPLAY(config_.target_monitor()), SDL_WINDOWPOS_CENTERED_DISPLAY(config_.target_monitor()));
+ }
+
+ SDL_GL_GetDrawableSize(sdl_window_, &window_dims[0], &window_dims[1]);
+ currentFullscreenType = val;
+}
+
+void Graphics::SetFSAA(int val) {
+ if(config_.FSAA_samples() != val){
+ config_.SetFSAASamples(val);
+ if( initialized ) {
+ InitScreen();
+ }
+ }
+}
+
+void Graphics::SetVsync(bool val) {
+ config_.SetVSync(val);
+ ApplyVsync(val);
+}
+
+void Graphics::SetLimitFpsInGame(bool val) {
+ config_.SetLimitFpsInGame(val);
+}
+
+void Graphics::SetMaxFrameRate(int val) {
+ config_.SetMaxFrameRate(val);
+}
+
+void Graphics::SetSimpleFog(bool val){
+ config_.SetSimpleFog(val);
+}
+
+void Graphics::SetDepthOfField(bool val){
+ config_.SetDepthOfField(val);
+}
+
+void Graphics::SetDepthOfFieldReduced(bool val){
+ config_.SetDepthOfFieldReduced(val);
+}
+
+void Graphics::SetDetailObjects(bool val){
+ config_.SetDetailObjects(val);
+}
+
+void Graphics::SetDetailObjectDecals(bool val){
+ config_.SetDetailObjectDecals(val);
+}
+
+void Graphics::SetDetailObjectLowres(bool val){
+ config_.SetDetailObjectLowres(val);
+}
+
+void Graphics::SetDetailObjectsReduced(bool val) {
+ g_detail_objects_reduced = val;
+ g_detail_objects_reduced_dirty = true;
+}
+
+void Graphics::SetDetailObjectShadows(bool val) {
+ config_.SetDetailObjectShadows(val);
+}
+
+void Graphics::SetSeamlessCubemaps(bool val) {
+ if(GL_VERSION_3_2 || GLEW_ARB_seamless_cube_map || GLEW_ARB_seamless_cubemap_per_texture){
+ if(val){
+ glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
+ } else {
+ glDisable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
+ }
+ config_.seamless_cubemaps_ = val;
+ }
+}
+
+void Graphics::DrawArrays(GLenum mode, int first, unsigned int count) {
+ SetVertexAttribArrays();
+ GL_PERF_START();
+ glDrawArrays(mode, first, count);
+ GL_PERF_END();
+}
+
+void Graphics::DrawElements(GLenum mode, unsigned int count, GLenum type, const void * indices) {
+ SetVertexAttribArrays();
+ GL_PERF_START();
+ glDrawElements(mode, count, type, indices);
+ GL_PERF_END();
+}
+
+void Graphics::DrawElementsInstanced(GLenum mode, unsigned int count, GLenum type, const void * indices, unsigned int primcount) {
+ SetVertexAttribArrays();
+ GL_PERF_START();
+ glDrawElementsInstanced(mode, count, type, indices, primcount);
+ GL_PERF_END();
+}
+
+void Graphics::DrawRangeElements(GLenum mode, unsigned int start, unsigned int end, unsigned int count, GLenum type, const void * indices) {
+ SetVertexAttribArrays();
+ GL_PERF_START();
+ glDrawRangeElements(mode, start, end, count, type, indices);
+ GL_PERF_END();
+}
+
+void Graphics::EnableVertexAttribArray(unsigned int index) {
+ assert(index < sizeof(wanted_vertex_attrib_arrays) * 8);
+ wanted_vertex_attrib_arrays |= (1 << index);
+}
+
+void Graphics::DisableVertexAttribArray(unsigned int index) {
+ assert(index < sizeof(wanted_vertex_attrib_arrays) * 8);
+ wanted_vertex_attrib_arrays &= ~(1 << index);
+}
+
+void Graphics::ResetVertexAttribArrays() {
+ wanted_vertex_attrib_arrays = 0;
+}
+
+
+#ifdef __GNUC__
+
+
+unsigned char BitScanForward(unsigned long *index, unsigned long mask) {
+ int temp = __builtin_ffs(mask);
+
+ if (temp == 0) {
+ *index = 0;
+ return 0;
+ } else {
+ *index = temp - 1;
+ return 1;
+ }
+}
+
+
+#else // __GNUC__
+
+#ifndef _MSC_VER
+#error Unknown compiler
+#endif // _MSC_VER
+
+#define BitScanForward _BitScanForward
+
+#endif // __GNUC__
+
+
+void Graphics::SetVertexAttribArrays() {
+ uint32_t changed_vertex_attrib_arrays = active_vertex_attrib_arrays ^ wanted_vertex_attrib_arrays;
+
+ while (changed_vertex_attrib_arrays != 0) {
+ unsigned long bit = 0;
+ unsigned char nonZero = BitScanForward(&bit, changed_vertex_attrib_arrays);
+ // can't be zero
+ LOG_ASSERT(nonZero != 0);
+ unsigned int mask = 1u << bit;
+ LOG_ASSERT(mask != 0);
+
+ if ((wanted_vertex_attrib_arrays & mask) != 0) {
+ glEnableVertexAttribArray(bit);
+ } else {
+ glDisableVertexAttribArray(bit);
+ }
+
+ changed_vertex_attrib_arrays ^= mask;
+
+#ifdef DEBUG
+ active_vertex_attrib_arrays ^= mask;
+#endif // DEBUG
+ }
+
+#ifdef DEBUG
+ LOG_ASSERT_EQ(active_vertex_attrib_arrays, wanted_vertex_attrib_arrays);
+#endif // DEBUG
+
+ active_vertex_attrib_arrays = wanted_vertex_attrib_arrays;
+}
+
+void Graphics::ClearGLState() {
+ // reset everything in case someone else has changed these
+ active_vertex_attrib_arrays = 0;
+ wanted_vertex_attrib_arrays = 0;
+ for (int i = 0; i < max_vertex_attrib_arrays; i++) {
+ glDisableVertexAttribArray(i);
+ }
+}
+
+void Graphics::SetUpShadowTextures() {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Set up shadow textures");
+ Textures* textures = Textures::Instance();
+
+ PushFramebuffer();
+
+ if( first_load ) {
+ static_shadow_depth_ref = textures->makeTexture(cascade_shadow_res,cascade_shadow_res,GL_DEPTH_COMPONENT24,GL_DEPTH_COMPONENT);
+ textures->SetTextureName(static_shadow_depth_ref, "Static Shadow Depth");
+
+ textures->setFilters(GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR);
+ genFramebuffers(&static_shadow_fb, "static_shadow_fb");
+ bindFramebuffer(static_shadow_fb);
+ framebufferDepthTexture2D(static_shadow_depth_ref);
+
+ textures->bindTexture(static_shadow_depth_ref);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
+ CHECK_FBO_ERROR();
+ }
+
+ deleteFramebuffer(&cascade_shadow_fb);
+ deleteFramebuffer(&cascade_shadow_color_fb);
+
+ cascade_shadow_depth_ref = TextureRef();
+ cascade_shadow_color_ref = TextureRef();
+
+ if(!g_simple_shadows && g_level_shadows) {
+ cascade_shadow_depth_ref = textures->makeTexture(cascade_shadow_res,cascade_shadow_res,GL_DEPTH_COMPONENT24,GL_DEPTH_COMPONENT);
+ textures->SetTextureName(cascade_shadow_depth_ref, "Cascade Shadow Depth");
+
+ textures->setFilters(GL_NEAREST, GL_NEAREST);
+ cascade_shadow_color_ref = textures->makeTexture(2048,2048,GL_RGBA,GL_RGBA);
+ textures->SetTextureName(cascade_shadow_color_ref, "Cascade Shadow Color");
+
+ textures->setFilters(GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR);
+ genFramebuffers(&cascade_shadow_fb, "cascade_shadow_fb");
+ bindFramebuffer(cascade_shadow_fb);
+ framebufferDepthTexture2D(cascade_shadow_depth_ref);
+
+ genFramebuffers(&cascade_shadow_color_fb, "cascade_shadow_color_fb");
+ bindFramebuffer(cascade_shadow_color_fb);
+ framebufferColorTexture2D(cascade_shadow_color_ref);
+ framebufferDepthTexture2D(cascade_shadow_depth_ref);
+
+ textures->bindTexture(cascade_shadow_depth_ref);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
+ }
+
+ PopFramebuffer();
+
+ Textures::Instance()->InvalidateBindCache();
+}
+
+
+void Graphics::DebugTracePrint(const char *message) {
+#if GPU_MARKERS
+ if (GLEW_KHR_debug) {
+ glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_MARKER, 0, GL_DEBUG_SEVERITY_NOTIFICATION, -1, message);
+ }
+#endif // GPU_MARKERS
+}
+
+
+vec3 GraphicsConfig::BloodColorFromString(const std::string& blood_color_str) {
+ vec3 blood_color(0.5f,0.0f,0.0f);
+ {
+ int num_chars = blood_color_str.length();
+ blood_color[0] = (float)atof(&blood_color_str[0]);
+ int which_num = 1;
+ for(int i=0; i<num_chars-1; ++i){
+ if(blood_color_str[i] == ' '){
+ blood_color[which_num] = (float)atof(&blood_color_str[i+1]);
+ ++which_num;
+ if(which_num > 2){
+ break;
+ }
+ }
+ }
+ }
+ return blood_color;
+}
diff --git a/Source/Graphics/graphics.h b/Source/Graphics/graphics.h
new file mode 100644
index 00000000..93bd8f64
--- /dev/null
+++ b/Source/Graphics/graphics.h
@@ -0,0 +1,445 @@
+//-----------------------------------------------------------------------------
+// Name: graphics.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec2.h>
+#include <Math/mat4.h>
+#include <Math/mat3.h>
+
+#include <Internal/error.h>
+#include <Internal/hardware_specs.h>
+
+#include <Graphics/glstate.h>
+#include <Asset/Asset/texture.h>
+
+#include <opengl.h>
+
+#include <queue>
+#include <stack>
+#include <climits>
+#include <stdint.h> // cstdint would be more c++ but os x doesn't understand it
+
+#define UBO_CLUSTER_DATA 1
+#define UBO_SHADOW_CASCADES 2
+
+const GLuint INVALID_FRAMEBUFFER = UINT_MAX;
+
+enum ShadowedClientStateFlags
+{
+ F_VERTEX_ARRAY = (1<<0),
+ F_NORMAL_ARRAY = (1<<1),
+ F_COLOR_ARRAY = (1<<2),
+ F_TEXTURE_COORD_ARRAY0 = (1<<3),
+ F_TEXTURE_COORD_ARRAY1 = (1<<4),
+ F_TEXTURE_COORD_ARRAY2 = (1<<5),
+ F_TEXTURE_COORD_ARRAY3 = (1<<6),
+ F_TEXTURE_COORD_ARRAY4 = (1<<7),
+ F_TEXTURE_COORD_ARRAY5 = (1<<8),
+ F_TEXTURE_COORD_ARRAY6 = (1<<9),
+ F_TEXTURE_COORD_ARRAY7 = (1<<10)
+};
+
+enum ShadowedClientStateID
+{
+ CS_VERTEX_ARRAY,
+ CS_NORMAL_ARRAY,
+ CS_COLOR_ARRAY,
+ CS_TEXTURE_COORD_ARRAY0,
+ CS_TEXTURE_COORD_ARRAY1,
+ CS_TEXTURE_COORD_ARRAY2,
+ CS_TEXTURE_COORD_ARRAY3,
+ CS_TEXTURE_COORD_ARRAY4,
+ CS_TEXTURE_COORD_ARRAY5,
+ CS_TEXTURE_COORD_ARRAY6,
+ CS_TEXTURE_COORD_ARRAY7,
+
+ CS_MAX_SHADOWED_CLIENT_STATES
+};
+
+class Config;
+
+namespace BloodLevel {
+ enum Type {
+ kNone = 0,
+ kSimple = 1,
+ kFull = 2
+ };
+}
+
+namespace FullscreenMode {
+ enum Mode {
+ kWindowed = 0 // Resizable floating window
+ , kFullscreen = 1 // Exclusive fullscreen
+ , kWindowed_borderless = 2
+ , kFullscreen_borderless = 3
+ };
+}
+
+class GraphicsConfig {
+public:
+ bool dynamic_character_cubemap_;
+ bool gpu_skinning() const {return gpu_skinning_;}
+ float anisotropy() const {return anisotropy_;}
+ int FSAA_samples() const {return FSAA_samples_;}
+ FullscreenMode::Mode full_screen() const {return full_screen_;}
+ int target_monitor() const {return target_monitor_;}
+ int screen_width() const {return screen_width_;}
+ int screen_height() const {return screen_height_;}
+ bool split_screen() const {return split_screen_;}
+ int texture_reduce() const {return texture_reduce_;}
+ int texture_reduction_factor() const {return texture_reduction_factor_;}
+ bool vSync() const {return vSync_;}
+ bool limit_fps_in_game() const { return limit_fps_in_game_; }
+ int max_frame_rate() const {return max_frame_rate_;}
+ int blood() const {return blood_;}
+ bool simple_fog() const {return simple_fog_;}
+ bool depth_of_field() const {return depth_of_field_;}
+ bool depth_of_field_reduced() const {return depth_of_field_reduced_;}
+ bool detail_objects() const {return detail_objects_;}
+ bool detail_object_decals() const {return detail_object_decals_;}
+ bool detail_object_lowres() const {return detail_object_lowres_;}
+ bool detail_object_shadows() const {return detail_object_shadows_;}
+ const vec3 &blood_color() const {return blood_color_;}
+ static vec3 BloodColorFromString(const std::string& str);
+
+ void SetGPUSkinning(bool val) {gpu_skinning_ = val;}
+ void SetAnisotropy(float val) {anisotropy_ = val;}
+ void SetFSAASamples(int val) {FSAA_samples_ = val;}
+ void SetFullscreen(FullscreenMode::Mode val) {full_screen_ = val;}
+ void SetTargetMonitor(int val) {target_monitor_ = val;}
+ void SetScreenWidth(int val) {screen_width_ = val;}
+ void SetScreenHeight(int val) {screen_height_ = val;}
+ void SetSplitScreen(bool val) {split_screen_ = val;}
+ void SetBlood(int val) {blood_ = val;}
+ void SetBloodColor(const vec3 &val) {blood_color_ = val;}
+ void SetTextureReduce(int val) {
+ texture_reduce_ = val;
+ texture_reduction_factor_ = 1 << texture_reduce_;
+ }
+ void SetVSync(bool val) {vSync_ = val;}
+ void SetLimitFpsInGame(bool val) { limit_fps_in_game_ = val; }
+ void SetMaxFrameRate(int val) {max_frame_rate_ = val;}
+ void SetSimpleFog(bool val) {simple_fog_ = val;}
+ void SetDepthOfField(bool val) {depth_of_field_ = val;}
+ void SetDepthOfFieldReduced(bool val) {depth_of_field_reduced_ = val;}
+ void SetDetailObjects(bool val) {detail_objects_ = val;}
+ void SetDetailObjectDecals(bool val) {detail_object_decals_ = val;}
+ void SetDetailObjectLowres(bool val) {detail_object_lowres_ = val;}
+ void SetDetailObjectShadows(bool val) {detail_object_shadows_ = val; }
+
+ bool seamless_cubemaps_;
+ float motion_blur_amount_;
+
+private:
+ bool simple_fog_;
+ bool depth_of_field_;
+ bool depth_of_field_reduced_;
+ bool detail_objects_;
+ bool detail_object_decals_;
+ bool detail_object_lowres_;
+ bool detail_object_shadows_;
+ bool gpu_skinning_;
+ float anisotropy_;
+ int FSAA_samples_;
+ FullscreenMode::Mode full_screen_;
+ int target_monitor_; // Used when retrieving monitor data from SDL, though not 100% supported. Currently hard-coded to 0
+ int screen_width_;
+ int screen_height_;
+ bool split_screen_;
+ int texture_reduce_;
+ int texture_reduction_factor_;
+ bool vSync_;
+ bool limit_fps_in_game_;
+ int max_frame_rate_;
+ int blood_;
+ vec3 blood_color_;
+};
+
+class GraphicsFeatures {
+public:
+ bool HDR_enable() const {return HDR_enable_;}
+ bool frame_buffer_fsaa_enabled() const {return frame_buffer_fsaa_enabled_;}
+
+ void SetHDREnable(bool val) {HDR_enable_ = val;}
+ void SetFrameBufferFSAAEnabled(bool val) {frame_buffer_fsaa_enabled_ = val;}
+private:
+ bool HDR_enable_;
+ bool frame_buffer_fsaa_enabled_;
+};
+
+struct GraphicsShadowState {
+ int depth_func_type_; // Shadow the glDepthFunc values
+ bool depth_func_unset_;
+
+ GLState gl_state_;
+
+ bool client_states_[CS_MAX_SHADOWED_CLIENT_STATES];
+ int client_active_textures_;
+ bool polygon_offset_enable_;
+};
+
+struct SDL_Window;
+
+struct ViewportDims {
+ int entries[4];
+};
+
+struct PostEffects {
+ GLuint post_framebuffer;
+ TextureRef temp_screen_tex;
+ TextureRef tone_mapped_tex;
+};
+
+class Graphics
+{
+ public:
+ void Dispose();
+private:
+ Graphics();
+
+ bool gl_lib_loaded_; // To avoid loading the gl library multiple times
+
+ GraphicsFeatures features_;
+ GraphicsShadowState shadow_state_;
+ bool media_mode_;
+ void *gl_context;
+ uint32_t active_vertex_attrib_arrays;
+ uint32_t wanted_vertex_attrib_arrays;
+ int max_vertex_attrib_arrays;
+
+ const char* shader;
+
+ void SetVertexAttribArrays();
+
+ FullscreenMode::Mode currentFullscreenType;
+ public:
+ GraphicsConfig config_;
+ SDL_Window *sdl_window_;
+ bool depth_prepass;
+ PostEffects post_effects;
+ enum ScreenType {kWindow, kRender, kTexture};
+
+ void Initialize();
+ void GetShaderNames(std::map<std::string, int>& preload_shaders);
+ void WindowResized( ivec2 value );
+ GraphicsFeatures &features(){return features_;}
+ void SetMediaMode( bool val ){ media_mode_ = val;}
+ void SetSeamlessCubemaps( bool val );
+ bool media_mode() const {return media_mode_;}
+ GLVendor vendor;
+ TextureAssetRef noise_ref;
+ TextureAssetRef pure_noise_ref;
+
+ int queued_screenshot;
+ bool pre_screenshot_media_mode_state;
+ enum ScreenshotMode {kGameplay, kTransparentGameplay};
+ ScreenshotMode screenshot_mode;
+
+ float shadow_size;
+
+ TextureRef static_shadow_depth_ref;
+ TextureRef cascade_shadow_depth_ref;
+ TextureRef cascade_shadow_color_ref;
+ GLuint cascade_shadow_res;
+ GLuint cascade_shadow_fb;
+ GLuint static_shadow_fb;
+ GLuint cascade_shadow_color_fb;
+ mat4 cascade_shadow_mat[4];
+ mat4 simple_shadow_mat;
+ float cascade_shadow_radius[4];
+
+ float hdr_white_point;
+ float hdr_black_point;
+ float hdr_bloom_mult;
+
+ GLuint shadow_res;
+ bool drawing_shadow;
+ std::stack<ViewportDims> viewport_dims_stack;
+ int viewport_dim[4];
+
+ int vbo_array_bound;
+ int vbo_element_bound;
+
+ bool multisample_framebuffer_exists;
+ GLuint multisample_framebuffer;
+ GLuint multisample_depth;
+ GLuint multisample_color;
+ GLuint multisample_vel;
+
+ bool framebuffer_exists;
+ GLuint framebuffer;
+ TextureRef screen_color_tex;
+ TextureRef screen_vel_tex;
+ TextureRef screen_depth_tex;
+ std::string post_shader_name; // Just used for saving
+
+ bool use_sample_alpha_to_coverage;
+
+ int max_texture_units;
+
+ int old_FSAA_samples;
+ int old_render_dims[2];
+ bool old_post_effects_enable;
+ bool first_load;
+ bool initialized;
+ bool settings_changed; // for detail objects
+
+ std::stack<GLuint> fbo_stack;
+ GLuint curr_framebuffer;
+ bool nav_mesh_out_of_date;
+ int nav_mesh_out_of_date_chunk;
+ int line_width;
+
+ // The following allow us to render the game, UI, and window at different resolutions
+ // E.g.
+ int window_dims[2]; // dimensions of OS window
+ int render_dims[2]; // dimensions of game render target. Same as window_dims if windowed, otherwise internal resolution
+ int render_output_dims[2]; // dimensions of render target output in window. Same as window_dims, unless the aspect ratio is off?
+
+ void Clear(const bool clear_color);
+ void setViewport(const GLint startx, const GLint starty, const GLint endx, const GLint endy);
+ void setAdditiveBlend(const bool what);
+ void SetBlendFunc(const int src, const int dest, const int alpha_src = GL_ONE, const int alpha_dst = GL_ONE_MINUS_SRC_ALPHA);
+ void setDepthFunc(const int type);
+ void setBlend(const bool what);
+ void setDepthTest(const bool what);
+ void setCullFace(const bool what);
+ void setDepthWrite(const bool what);
+ void setPolygonOffset( const bool polygon_offset );
+ void setSimpleShadows(const bool val);
+ void setSimpleWater(const bool val);
+ void SetParticleFieldSimple(bool val);
+ void setDepthOfField(const bool val);
+ void setAttribEnvObjInstancing(bool val);
+
+ void PushViewport();
+ void PopViewport();
+
+ void TakeScreenshot();
+
+ void setGLState(const GLState &state);
+
+ void RenderFramebufferToTexture(GLuint framebuffer, TextureRef texture_ref);
+ void EndTextureSpaceRendering();
+
+ void PushFramebuffer();
+ void PopFramebuffer();
+ void bindRenderbuffer(GLuint rb);
+ void bindFramebuffer(GLuint fb);
+ void framebufferDepthTexture2D(TextureRef t, int mipmap_level=0);
+ void framebufferColorTexture2D(TextureRef t, int mipmap_level=0);
+ void genRenderbuffers(GLuint *rb);
+
+ std::map<GLuint, std::string> framebuffernames;
+ void genFramebuffers (GLuint *fb, const char* human_name);
+ void deleteFramebuffer(GLuint *fb);
+ void renderbufferStorage(GLuint res);
+ void framebufferRenderbuffer(GLuint rb);
+ GLenum checkFramebufferStatus();
+
+ unsigned int GetShadowSize(unsigned int target_shadow_size);
+ void startDraw(vec2 start = vec2(0.0f), vec2 end = vec2(1.0f), ScreenType screen_type = kRender);
+ void SwapToScreen();
+ void InitScreen();
+ void ResizeWindow(int& w, int& h);
+
+ void SetUpWindowDims(int& w, int& h);
+ static Graphics* Instance() {
+ static Graphics instance;
+ return &instance;
+ }
+ void SetModelMatrix( const mat4& matrix, const mat3& normal_matrix );
+ void SetSimpleLineDrawState();
+ void BlitDepthBuffer();
+ void BlitColorBuffer();
+
+ void SetClientStates(int flags);
+ void SetClientStateEnabled(int shadowStateID, bool enabled);
+ void SetClientActiveTexture(int index);
+ void SetFromConfig( const Config &config, bool dynamic = false);
+ void SetWindowGrab( bool val );
+ void BindVBO(int target, int val);
+ void UnbindVBO(int target, int val);
+ void BindArrayVBO(int val);
+ void BindElementVBO(int val);
+ void SetLineWidth( int val );
+ //void SetFullscreen(bool val);
+ void SetFullscreen(FullscreenMode::Mode val);
+
+ void SetFSAA(int val);
+ void SetVsync(bool val);
+ void SetLimitFpsInGame(bool val);
+ void SetMaxFrameRate(int val);
+ void SetSimpleFog(bool val);
+ void SetDepthOfField(bool val);
+ void SetDepthOfFieldReduced(bool val);
+ void SetDetailObjects(bool val);
+ void SetDetailObjectDecals(bool val);
+ void SetDetailObjectLowres(bool val);
+ void SetDetailObjectShadows(bool val);
+ void SetDetailObjectsReduced(bool val);
+ void SetAnisotropy(float val);
+ //void SetWindowDimensions( int w, int h );
+ void SetResolution(int w, int h, bool force);
+ void SetTargetMonitor(int targetMonitor);
+ void CheckForWindowResize();
+ void StartTextureSpaceRenderingCustom(int x, int y, int width, int height);
+
+ void DrawArrays(GLenum mode, int first, unsigned int count);
+ void DrawElements(GLenum mode, unsigned int count, GLenum type, const void * indices);
+ void DrawElementsInstanced(GLenum mode, unsigned int count, GLenum type, const void * indices, unsigned int primcount);
+ void DrawRangeElements(GLenum mode, unsigned int start, unsigned int end, unsigned int count, GLenum type, const void * indices);
+
+ void EnableVertexAttribArray(unsigned int index);
+ void DisableVertexAttribArray(unsigned int index);
+ void ResetVertexAttribArrays();
+ void ClearGLState();
+
+ void SetUpShadowTextures();
+
+ // output a debug message to OpenGL trace
+ void DebugTracePrint(const char *message);
+};
+
+bool CheckGLError(int line, const char* file, const char* errstring);
+void CheckFBOError(int line, const char* file);
+
+bool CheckGLErrorStr(char* output, unsigned length);
+
+
+#if GPU_MARKERS
+#undef NO_GL_ERROR_CHECKING
+#define NO_GL_ERROR_CHECKING 1
+#endif // GPU_MARKERS
+
+
+#define FORCE_CHECK_GL_ERROR() CheckGLError(__LINE__, __FILE__,NULL)
+#define FORCE_CHECK_FBO_ERROR() CheckFBOError(__LINE__, __FILE__)
+#ifndef NO_GL_ERROR_CHECKING
+#define CHECK_GL_ERROR() CheckGLError(__LINE__, __FILE__,NULL)
+#define CHECK_FBO_ERROR() CheckFBOError(__LINE__, __FILE__)
+#else
+#define CHECK_GL_ERROR()
+#define CHECK_FBO_ERROR()
+#endif
diff --git a/Source/Graphics/halfedge.cpp b/Source/Graphics/halfedge.cpp
new file mode 100644
index 00000000..d5ded797
--- /dev/null
+++ b/Source/Graphics/halfedge.cpp
@@ -0,0 +1,283 @@
+//-----------------------------------------------------------------------------
+// Name: halfedge.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "halfedge.h"
+
+#include <Graphics/simplify.hpp>
+
+using namespace WOLFIRE_SIMPLIFY;
+
+bool operator<(const HalfEdgeNode& a, const HalfEdgeNode& b) {
+ return a.edge->err < b.edge->err;
+}
+
+// Sort half edges with sorted vert lists, so pairs end up next to one another
+// Warning: This might not swap the contents fully enough for general use.
+bool HalfEdgePairFind(const HalfEdge& a, const HalfEdge& b){
+ int vert_a[2];
+ int vert_b[2];
+ vert_a[0] = a.vert[0];
+ vert_a[1] = a.vert[1];
+ vert_b[0] = b.vert[0];
+ vert_b[1] = b.vert[1];
+ if(vert_a[0] > vert_a[1]){
+ std::swap(vert_a[0], vert_a[1]);
+ }
+ if(vert_b[0] > vert_b[1]){
+ std::swap(vert_b[0], vert_b[1]);
+ }
+ if(vert_a[0] == vert_b[0]){
+ return vert_a[1]<vert_b[1];
+ } else {
+ return vert_a[0]<vert_b[0];
+ }
+}
+
+void CollapseHalfEdge(HalfEdgeNodeHeap &heap, HalfEdge *edge) {
+ edge->valid = false;
+ heap.erase(edge->handle);
+ edge->next->valid = false;
+ heap.erase(edge->next->handle);
+ if(edge->next->twin){
+ edge->next->twin->twin = edge->prev->twin;
+ }
+ edge->prev->valid = false;
+ heap.erase(edge->prev->handle);
+ if(edge->prev->twin){
+ edge->prev->twin->twin = edge->next->twin;
+ }
+}
+
+void CollapseVertPositions(std::vector<float> &verts, int edge[], float pos){
+ int vert_index[2];
+ for(int j=0; j<2; ++j){
+ vert_index[j] = edge[j]*3;
+ }
+ for(int i=0; i<3; ++i){
+ float mid = verts[vert_index[0]+i]*(1.0f-pos) + verts[vert_index[1]+i]*pos;
+ verts[vert_index[0]+i] = mid;
+ }
+}
+
+void CollapseTexPositions(std::vector<float> &tex, int edge[], float pos){
+ int tex_index[2];
+ for(int j=0; j<2; ++j){
+ tex_index[j] = edge[j]*2;
+ }
+ for(int i=0; i<2; ++i){
+ float mid = tex[tex_index[0]+i]*(1.0f-pos) + tex[tex_index[1]+i]*pos;
+ tex[tex_index[0]+i] = mid;
+ }
+}
+
+int GetNumTexCoords(const HalfEdge *edge, int which) {
+ std::set<int> tex_ids;
+ const HalfEdge *spin_edge = edge;
+ if(which == 0){
+ while(true){
+ tex_ids.insert(spin_edge->tex[0]);
+ spin_edge = spin_edge->prev;
+ if(!spin_edge->twin){
+ break;
+ }
+ spin_edge = spin_edge->twin;
+ if(spin_edge == edge){
+ break;
+ }
+ }
+ } else {
+ while(true){
+ tex_ids.insert(spin_edge->tex[1]);
+ spin_edge = spin_edge->next;
+ if(!spin_edge->twin){
+ break;
+ }
+ spin_edge = spin_edge->twin;
+ if(spin_edge == edge){
+ break;
+ }
+ }
+ }
+ return tex_ids.size();
+}
+
+void CollapseParentRecord(ParentRecordList &a, ParentRecordList &b, float weight){
+ if(&a == &b){
+ return;
+ }
+ for(ParentRecordList::iterator iter = a.begin(); iter != a.end(); ++iter){
+ ParentRecord &pr = *iter;
+ pr.weight *= (1.0f - weight);
+ }
+ for(ParentRecordList::iterator iter = b.begin(); iter != b.end(); ++iter){
+ ParentRecord &pr = *iter;
+ pr.weight *= weight;
+ }
+ a.splice(a.end(), b);
+ for(ParentRecordList::iterator iter = a.begin(); iter != a.end(); ++iter){
+ //ParentRecord &pr = *iter;
+ }
+}
+
+void CollapseEdge(HalfEdgeNodeHeap &heap, HalfEdge *edge, std::vector<float>& vertices, std::vector<float>& tex_coords, std::vector<glm::mat4> &quadrics, ParentRecordListVec &vert_parents, ParentRecordListVec &tex_parents, HalfEdgeSetVec &vert_edges, HalfEdgeSetVec &tex_edges, bool include_tex) {
+ quadrics[edge->vert[0]] += quadrics[edge->vert[1]];
+
+ int c = 0;
+ CollapseParentRecord(vert_parents[edge->vert[0]], vert_parents[edge->vert[1]], edge->pos);
+ CollapseVertPositions(vertices, edge->vert, edge->pos);
+ HalfEdgeSet vert_edge_set = vert_edges[edge->vert[1]];
+ for(HalfEdgeSet::iterator iter = vert_edge_set.begin(); iter != vert_edge_set.end(); ++iter){
+ HalfEdge* change_edge = *iter;
+ if(change_edge == edge || !change_edge->valid){
+ continue;
+ }
+ if(change_edge->vert[0] == edge->vert[1]){
+ change_edge->vert[0] = edge->vert[0];
+ }
+ if(change_edge->vert[1] == edge->vert[1]){
+ change_edge->vert[1] = edge->vert[0];
+ }
+ vert_edges[edge->vert[0]].insert(change_edge);
+ }
+ vert_edge_set.clear();
+ if(include_tex) {
+ CollapseParentRecord(tex_parents[edge->tex[0]], tex_parents[edge->tex[1]], edge->pos);
+ CollapseTexPositions(tex_coords, edge->tex, edge->pos);
+ HalfEdgeSet tex_edge_set = tex_edges[edge->tex[1]];
+ for(HalfEdgeSet::iterator iter = tex_edge_set.begin(); iter != tex_edge_set.end(); ++iter){
+ HalfEdge* change_edge = *iter;
+ if(change_edge == edge || !change_edge->valid){
+ continue;
+ }
+ if(change_edge->tex[0] == edge->tex[1]){
+ change_edge->tex[0] = edge->tex[0];
+ }
+ if(change_edge->tex[1] == edge->tex[1]){
+ change_edge->tex[1] = edge->tex[0];
+ }
+ tex_edges[edge->tex[0]].insert(change_edge);
+ }
+ tex_edge_set.clear();
+ if(edge->twin && (edge->tex[1] != edge->twin->tex[0] && edge->tex[0] != edge->twin->tex[1])){
+ int rev_tex[2];
+ rev_tex[0] = edge->twin->tex[1];
+ rev_tex[1] = edge->twin->tex[0];
+ CollapseTexPositions(tex_coords, rev_tex, edge->pos);
+ CollapseParentRecord(tex_parents[rev_tex[0]], tex_parents[rev_tex[1]], edge->pos);
+ HalfEdgeSet tex_edge_set = tex_edges[rev_tex[1]];
+ for(HalfEdgeSet::iterator iter = tex_edge_set.begin(); iter != tex_edge_set.end(); ++iter){
+ HalfEdge* change_edge = *iter;
+ if(change_edge == edge || !change_edge->valid){
+ continue;
+ }
+ if(change_edge->tex[0] == rev_tex[1]){
+ change_edge->tex[0] = rev_tex[0];
+ }
+ if(change_edge->tex[1] == rev_tex[1]){
+ change_edge->tex[1] = rev_tex[0];
+ }
+ tex_edges[rev_tex[0]].insert(change_edge);
+ }
+ tex_edge_set.clear();
+ }
+ }
+ HalfEdgeSet &affected_edges = vert_edges[edge->vert[0]];
+ // Remove deleted edges
+ CollapseHalfEdge(heap, edge);
+ if(edge->twin){
+ CollapseHalfEdge(heap, edge->twin);
+ }
+ // Recalc err and update heap
+ for(std::set<HalfEdge*>::iterator iter = affected_edges.begin(); iter != affected_edges.end(); ++iter){
+ HalfEdge* affected_edge = *iter;
+ if(!affected_edge->valid){
+ continue;
+ }
+ int edge_tc[2];
+ if(include_tex) {
+ edge_tc[0] = GetNumTexCoords(affected_edge, 0);
+ edge_tc[1] = GetNumTexCoords(affected_edge, 1);
+ } else {
+ edge_tc[0] = 0;
+ edge_tc[1] = 0;
+ }
+
+ affected_edge->err = WOLFIRE_SIMPLIFY::CalculateError(&affected_edge->pos, affected_edge->vert, vertices, quadrics, edge_tc);
+
+ HalfEdgeNode node;
+ node.edge = affected_edge;
+
+ HalfEdgeNodeHeap::iterator new_handle = heap.insert(node);
+ heap.erase(affected_edge->handle);
+ affected_edge->handle = new_handle;
+
+ //heap.update(affected_edge->handle);
+ }
+}
+
+void ReconstructModel(const std::vector<HalfEdge> &half_edges, SimplifyModel *model, bool include_tex){
+ // Collapse vert edges
+ std::vector<char> added(half_edges.size(), 0);
+ model->vert_indices.clear();
+ model->tex_indices.clear();
+ for(int i=0, len=half_edges.size(); i<len; ++i){
+ const HalfEdge* edge = &half_edges[i];
+ if(!added[i] && edge->valid){
+ model->vert_indices.push_back(edge->vert[0]);
+ model->vert_indices.push_back(edge->next->vert[0]);
+ model->vert_indices.push_back(edge->prev->vert[0]);
+ if(include_tex) {
+ model->tex_indices.push_back(edge->tex[0]);
+ model->tex_indices.push_back(edge->next->tex[0]);
+ model->tex_indices.push_back(edge->prev->tex[0]);
+ }
+ added[edge->id] = 1;
+ added[edge->next->id] = 1;
+ added[edge->prev->id] = 1;
+ }
+ }
+}
+
+bool SortEdgeSortable(const EdgeSortable& a, const EdgeSortable& b){
+ if(a.verts[0] < b.verts[0]){
+ return false;
+ } else if(a.verts[0] > b.verts[0]){
+ return true;
+ } else if(a.verts[1] < b.verts[1]){
+ return false;
+ } else {
+ return true;
+ }
+}
+
+bool SortVertSortable(const VertSortable& a, const VertSortable& b){
+ if(a.vert[0] == b.vert[0]){
+ if(a.vert[1] == b.vert[1]){
+ return a.vert[2] < b.vert[2];
+ } else {
+ return a.vert[1] < b.vert[1];
+ }
+ } else {
+ return a.vert[0] < b.vert[0];
+ }
+}
diff --git a/Source/Graphics/halfedge.h b/Source/Graphics/halfedge.h
new file mode 100644
index 00000000..e970f679
--- /dev/null
+++ b/Source/Graphics/halfedge.h
@@ -0,0 +1,82 @@
+//-----------------------------------------------------------------------------
+// Name: halfedge.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/enginemath.h>
+#include <Math/mat4.h>
+
+#include <Wrappers/glm.h>
+#include <Graphics/simplify_types.h>
+
+#include <vector>
+#include <set>
+
+struct HalfEdge;
+
+struct HalfEdgeNode {
+ HalfEdge *edge;
+};
+
+typedef std::multiset<HalfEdgeNode> HalfEdgeNodeHeap;
+
+struct HalfEdge {
+ int vert[2];
+ int tex[2];
+ float err;
+ float pos;
+ HalfEdge *twin, *next, *prev;
+ int id;
+ bool valid;
+ HalfEdgeNodeHeap::iterator handle;
+};
+
+struct EdgeSortable {
+ int verts[2];
+ int id;
+};
+
+struct VertSortable {
+ vec3 vert;
+ int id;
+};
+
+static const float UNDEFINED_ERROR = -1.0f;
+
+bool operator<(const HalfEdgeNode& a, const HalfEdgeNode& b);
+
+typedef std::set<HalfEdge*> HalfEdgeSet;
+typedef std::vector<HalfEdgeSet> HalfEdgeSetVec;
+
+bool HalfEdgePairFind(const HalfEdge& a, const HalfEdge& b);
+void CollapseHalfEdge(HalfEdgeNodeHeap &heap, HalfEdge *edge);
+void CollapseVertPositions(std::vector<float> &verts, int edge[], float pos);
+void CollapseTexPositions(std::vector<float> &tex, int edge[], float pos);
+int GetNumTexCoords(const HalfEdge *edge, int which);
+void CollapseParentRecord(WOLFIRE_SIMPLIFY::ParentRecordList &a, WOLFIRE_SIMPLIFY::ParentRecordList &b, float weight);
+void CollapseEdge(HalfEdgeNodeHeap &heap, HalfEdge *edge, std::vector<float>& vertices, std::vector<float>& tex_coords, std::vector<glm::mat4> &quadrics, WOLFIRE_SIMPLIFY::ParentRecordListVec &vert_parents, WOLFIRE_SIMPLIFY::ParentRecordListVec &tex_parents, HalfEdgeSetVec &vert_edges, HalfEdgeSetVec &tex_edges, bool include_tex);
+void ReconstructModel(const std::vector<HalfEdge> &half_edges, WOLFIRE_SIMPLIFY::SimplifyModel *model, bool include_tex);
+
+bool HalfEdgePairFind(const HalfEdge& a, const HalfEdge& b);
+bool SortEdgeSortable(const EdgeSortable& a, const EdgeSortable& b);
+bool SortVertSortable(const VertSortable& a, const VertSortable& b);
diff --git a/Source/Graphics/heightmap.cpp b/Source/Graphics/heightmap.cpp
new file mode 100644
index 00000000..df74b309
--- /dev/null
+++ b/Source/Graphics/heightmap.cpp
@@ -0,0 +1,198 @@
+//-----------------------------------------------------------------------------
+// Name: heightmap.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "heightmap.h"
+
+#include <Internal/checksum.h>
+#include <Internal/common.h>
+#include <Internal/error.h>
+#include <Internal/datemodified.h>
+#include <Internal/cachefile.h>
+#include <Internal/filesystem.h>
+
+#include <Compat/fileio.h>
+#include <Images/freeimage_wrapper.h>
+
+bool HeightmapImage::ReadCacheFile(const std::string& path, uint16_t checksum) {
+ FILE* file = my_fopen(path.c_str(),"rb");
+ if(!file){
+ return false;
+ }
+ CacheFile::ScopedFileCloser file_closer(file);
+ unsigned char version;
+ fread(&version, sizeof(version), 1, file);
+ if(version != kHMCacheVersion){
+ return false;
+ }
+ uint16_t file_checksum;
+ fread(&file_checksum, sizeof(file_checksum), 1, file);
+ if(checksum != 0 && checksum != file_checksum){
+ return false;
+ }
+ checksum_ = file_checksum;
+ fread(&width_, sizeof(width_), 1, file);
+ fread(&depth_, sizeof(depth_), 1, file);
+ height_data_.resize(width_*depth_);
+ fread(&height_data_[0], sizeof(height_data_[0])*width_*depth_, 1, file);
+ return true;
+}
+
+void HeightmapImage::WriteCacheFile(const std::string& path) {
+ FILE* file = my_fopen(path.c_str(),"wb");
+ CacheFile::ScopedFileCloser file_closer(file);
+ unsigned char version = kHMCacheVersion;
+ fwrite(&version, sizeof(version), 1, file);
+ fwrite(&checksum_, sizeof(checksum_), 1, file);
+ fwrite(&width_, sizeof(width_), 1, file);
+ fwrite(&depth_, sizeof(depth_), 1, file);
+ fwrite(&height_data_[0], sizeof(height_data_[0]), width_*depth_, file);
+}
+
+bool HeightmapImage::LoadData(const std::string& rel_path, HMScale scaled) {
+ height_data_.clear();
+
+ rel_path_ = rel_path;
+
+ std::string cache_suffix = "_hmcache";
+ if(scaled == DOWNSAMPLED){
+ cache_suffix += "_scaled";
+ }
+
+ std::string load_path;
+ ModID load_modsource;
+ if(CacheFile::CheckForCache(rel_path_, cache_suffix, &load_path, &load_modsource, &checksum_)){
+ if(ReadCacheFile(load_path, checksum_)){
+ modsource_ = load_modsource;
+ return true;
+ }
+ }
+
+ char abs_path[kPathSize];
+ ModID modsource;
+ FindFilePath(rel_path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths, true, NULL, &modsource);
+ modsource_ = modsource;
+ FIBitmapContainer image_container(GenericLoader(abs_path, 0));
+ FIBITMAP* image = image_container.get();
+ //FreeImage_Save(FIF_TARGA, image, (file_name+".tga").c_str());
+ if (image == NULL) {
+ FatalError("Error", "Could not load heightmap: %s", rel_path.c_str());
+ } else {
+ fiTYPE image_type = getImageType(image);
+ int bytes_per_pixel = getBPP(image) / 8;
+ if (scaled) {
+ width_ = kTerrainDimX;//FreeImage_GetWidth(image);
+ depth_ = kTerrainDimY;//FreeImage_GetHeight(image);
+ } else {
+ width_ = getWidth(image);
+ depth_ = getHeight(image);
+ }
+
+ int heightDataSize = width_ * depth_;
+ int imageDataSize = heightDataSize * bytes_per_pixel;
+
+ if (0 < heightDataSize && 0 < imageDataSize) {
+
+ // ...allocate enough space for the height data...
+ height_data_.resize(heightDataSize,0);
+ //m_flowData.resize(heightDataSize,vecf(2,0));
+
+ float scale_factor = kTerrainHeightScale;
+ float x_scale = getWidth(image)/(float)width_;
+ float z_scale = getHeight(image)/(float)depth_;
+
+ if (image_type == FIWT_UINT16) { // e.g. 16 bit PNGs
+ for(int z = 0; z < depth_; z++) { // flipped
+ unsigned short *bits = (unsigned short *)getScanLine(image, (int)(z*z_scale));
+ for(int x = 0; x < width_; x++) {
+ height_data_[x + (((depth_-1) - z) * width_)] = ((float)bits[(int)(x*x_scale)])/scale_factor;
+ }
+ }
+ } else if (image_type == FIWT_BITMAP) {
+ for(int z = 0; z < depth_; z++) {
+ uint8_t *bits = (uint8_t *)getScanLine(image, (int)(z*z_scale));
+ for(int x = 0; x < width_; x++) {
+ height_data_[x + (((depth_-1) - z) * width_)] = ((float)bits[(int)(x*x_scale)*bytes_per_pixel])/scale_factor*256.0f;
+ }
+ }
+ } else {
+ FatalError("Error","Heightmaps must be 16-bit grayscale PNGs.");
+ }
+ char path[kPathSize];
+ FormatString(path, kPathSize, "%s%s%s", GetWritePath(modsource).c_str(), rel_path.c_str(), cache_suffix.c_str());
+ WriteCacheFile(path);
+ return true;
+ }
+ }
+ return false;
+}
+
+float HeightmapImage::GetHeight(int x, int z) const
+{
+ if (!width_ || !depth_) {
+ return (0);
+ }
+
+ x = (x < 0) ? (0) : (x >= width_) ? (width_ - 1) : (x);
+ z = (z < 0) ? (0) : (z >= depth_) ? (depth_ - 1) : (z);
+
+ const int pos = x + (z * width_);
+ return float(height_data_[pos]);
+}
+
+float HeightmapImage::GetHeightXZ(float x, float z) const
+{
+ // ensure the heightmap is valid
+ if (!width_ || !depth_)
+ {
+ return (0);
+ }
+
+ // clamp values to the resolution of the heightmap
+ x = (x < 0) ? (0) : (x >= width_) ? (width_ - 1) : (x);
+ z = (z < 0) ? (0) : (z >= depth_) ? (depth_ - 1) : (z);
+
+ const int ix = int(x);
+ const int iz = int(z);
+ float xoffset = x - ix;
+ float zoffset = z - iz;
+
+ xoffset = 1 - xoffset;
+ zoffset = 1 - zoffset;
+
+ float weightTL = xoffset*zoffset;
+ float weightTR = (1-xoffset)*zoffset;
+ float weightBL = xoffset*(1-zoffset);
+ float weightBR = (1-xoffset)*(1-zoffset);
+ float total = weightTL+weightTR+weightBL+weightBR;
+
+ weightTL /= total;
+ weightTR /= total;
+ weightBL /= total;
+ weightBR /= total;
+
+ return (
+ GetHeight(ix, iz) * weightTL +
+ GetHeight(ix+1, iz) * weightTR +
+ GetHeight(ix, iz+1) * weightBL +
+ GetHeight(ix+1, iz+1) * weightBR);
+}
diff --git a/Source/Graphics/heightmap.h b/Source/Graphics/heightmap.h
new file mode 100644
index 00000000..7880d8cb
--- /dev/null
+++ b/Source/Graphics/heightmap.h
@@ -0,0 +1,69 @@
+//-----------------------------------------------------------------------------
+// Name: heightmap.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/integer.h>
+#include <Internal/modid.h>
+
+#include <string>
+#include <vector>
+
+class HeightmapImage
+{
+public:
+ enum HMScale {
+ DOWNSAMPLED = true,
+ ORIGINAL_RES = false
+ };
+
+ HeightmapImage():width_(0),depth_(0),checksum_(0){}
+
+ bool LoadData(const std::string& fileName, HMScale scaled);
+
+ float GetHeight(int x, int z) const;
+ float GetHeightXZ(float x, float z) const;
+
+ inline int32_t width() const {return width_;}
+ inline int32_t depth() const {return depth_;}
+ inline const std::string& path() const {return rel_path_;}
+ inline uint16_t checksum() const {return checksum_;}
+
+ ModID modsource_;
+
+private:
+ static const int kTerrainDimX = 1024;
+ static const int kTerrainDimY = 1024;
+ static const int kTerrainHeightScale = 32;
+ static const int kHMCacheVersion = 1;
+
+ bool ReadCacheFile(const std::string& path, uint16_t checksum);
+ void WriteCacheFile(const std::string& path);
+ bool LoadFromCacheIfAvailable(HMScale scaled);
+
+ int32_t width_;
+ int32_t depth_;
+ uint16_t checksum_;
+ std::vector<float> height_data_;
+ std::string rel_path_;
+};
diff --git a/Source/Graphics/hudimage.cpp b/Source/Graphics/hudimage.cpp
new file mode 100644
index 00000000..c3518849
--- /dev/null
+++ b/Source/Graphics/hudimage.cpp
@@ -0,0 +1,235 @@
+//-----------------------------------------------------------------------------
+// Name: hudimage.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "hudimage.h"
+
+#include <Graphics/shaders.h>
+#include <Graphics/graphics.h>
+#include <Graphics/vboringcontainer.h>
+#include <Graphics/text.h>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <Scripting/angelscript/ascontext.h>
+#include <Wrappers/glm.h>
+#include <Internal/profiler.h>
+#include <Main/engine.h>
+
+class HUDImageZCompare {
+public:
+ bool operator()(const HUDImage &a, const HUDImage &b) {
+ return a.pos[2] < b.pos[2];
+ }
+};
+
+struct HUDImageGLState {
+ GLState gl_state;
+ HUDImageGLState() {
+ gl_state.blend = true;
+ gl_state.blend_src = GL_SRC_ALPHA;
+ gl_state.blend_dst = GL_ONE_MINUS_SRC_ALPHA;
+ gl_state.cull_face = false;
+ gl_state.depth_test = false;
+ gl_state.depth_write = false;
+ }
+};
+
+static const HUDImageGLState hud_gl_state;
+
+void HUDImages::Draw() {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "HUDImages::Draw()");
+ if(hud_images.empty()){
+ return;
+ }
+
+ CHECK_GL_ERROR();
+ Graphics* graphics = Graphics::Instance();
+ Shaders* shaders = Shaders::Instance();
+ Textures* textures = Textures::Instance();
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "HUDImages sort");
+ // Sort HUD images so we can draw them in order of depth
+ std::sort(hud_images.begin(), hud_images.end(), HUDImageZCompare());
+ }
+
+ PROFILER_ENTER(g_profiler_ctx, "HUDImages setup");
+ int shader_id = shaders->returnProgram("simple_2d #TEXTURE #FLIPPED");
+ shaders->setProgram(shader_id);
+ int vert_coord_id = shaders->returnShaderAttrib("vert_coord", shader_id);
+ int tex_coord_id = shaders->returnShaderAttrib("tex_coord", shader_id);
+ int mvp_mat_id = shaders->returnShaderVariable("mvp_mat", shader_id);
+ int color_id = shaders->returnShaderVariable("color", shader_id);
+ graphics->setGLState(hud_gl_state.gl_state);
+ glm::mat4 proj_mat;
+ proj_mat = glm::ortho(0.0f, (float)graphics->window_dims[0], 0.0f, (float)graphics->window_dims[1]);
+ glUniformMatrix4fv(mvp_mat_id, 1, false, (GLfloat*)&proj_mat);
+ graphics->SetClientActiveTexture(0);
+ graphics->EnableVertexAttribArray(vert_coord_id);
+ graphics->EnableVertexAttribArray(tex_coord_id);
+
+ CHECK_GL_ERROR();
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "HUDImages draw loop");
+ for(unsigned i=0; i<hud_images.size(); ++i){
+ PROFILER_GPU_ZONE(g_profiler_ctx, "HUDImages draw iteration");
+ CHECK_GL_ERROR();
+ const HUDImage& hi = hud_images[i];
+ if(hi.color[3] <= 0.0f){
+ continue;
+ }
+ int width = textures->getWidth(hi.texture_ref);
+ int height = textures->getHeight(hi.texture_ref);
+ if(!hi.text){
+ // Image need to be scaled up to compensate for texture reduction
+ width = textures->getReducedWidth(hi.texture_ref);
+ height = textures->getReducedHeight(hi.texture_ref);
+ // Image use non-premultiplied alpha
+ graphics->SetBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ } else {
+ graphics->SetBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // Text uses premultiplied alpha
+ }
+ CHECK_GL_ERROR();
+ if( hi.texture_ref.valid() ) {
+ textures->bindTexture(hi.texture_ref);
+ if(hi.tex_scale[0] != 1.0f){
+ glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
+ } else {
+ glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
+ }
+ if(hi.tex_scale[1] != 1.0f){
+ glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
+ } else {
+ glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
+ }
+ glUniform4fv(color_id, 1, (GLfloat*)&hi.color);
+ CHECK_GL_ERROR();
+
+ const vec2 &to = hi.tex_offset;
+ const vec2 &ts = hi.tex_scale;
+ const vec3 &s = hi.scale;
+ const vec3 &p = hi.pos;
+ float w = (float)width;
+ float h = (float)height;
+ GLfloat data[] = {
+ to[0], to[1], p[0], p[1],
+ ts[0]+to[0], to[1], p[0]+w*s[0], p[1],
+ ts[0]+to[0], ts[1]+to[1], p[0]+w*s[0], p[1]+h*s[1],
+ to[0], ts[1]+to[1], p[0], p[1]+h*s[1],
+ };
+ if(hi.text){
+ data[1] = 1.0f - data[1];
+ data[5] = 1.0f - data[5];
+ data[9] = 1.0f - data[9];
+ data[13] = 1.0f - data[13];
+ }
+ static const GLuint indices[] = {0, 1, 2, 0, 2, 3};
+ static VBORingContainer data_vbo(sizeof(data), kVBOFloat | kVBODynamic );
+ static VBOContainer index_vbo;
+ if(!index_vbo.valid()){
+ index_vbo.Fill(kVBOElement | kVBOStatic, sizeof(indices), (void*)indices);
+ }
+ index_vbo.Bind();
+ data_vbo.Fill(sizeof(data), (void*)data);
+ data_vbo.Bind();
+ index_vbo.Bind();
+ CHECK_GL_ERROR();
+ glVertexAttribPointer(vert_coord_id, 2, GL_FLOAT, false, 4*sizeof(GLfloat), (const void*)(data_vbo.offset()+(2*sizeof(GLfloat))));
+ CHECK_GL_ERROR();
+ glVertexAttribPointer(tex_coord_id, 2, GL_FLOAT, false, 4*sizeof(GLfloat), (const void*)(data_vbo.offset()));
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ }
+ CHECK_GL_ERROR();
+ }
+ }
+ graphics->ResetVertexAttribArrays();
+
+ hud_images.clear();
+}
+
+HUDImages::HUDImages()
+{}
+
+HUDImage* HUDImages::AddImage() {
+ hud_images.resize(hud_images.size()+1);
+ HUDImage& hud_image = hud_images.back();
+ hud_image.pos = vec3(0.0f);
+ hud_image.tex_offset = vec2(0.0f);
+ hud_image.tex_scale = vec2(1.0f);
+ hud_image.scale = vec3(1.0f);
+ hud_image.color = vec4(1.0f);
+ return &hud_image;
+}
+
+void HUDImages::AttachToContext( ASContext *as_context ) {
+ as_context->RegisterObjectType("HUDImage", 0, asOBJ_REF | asOBJ_NOCOUNT );
+ as_context->RegisterObjectProperty("HUDImage","vec3 position",asOFFSET(HUDImage,pos));
+ as_context->RegisterObjectProperty("HUDImage","vec3 scale",asOFFSET(HUDImage,scale));
+ as_context->RegisterObjectProperty("HUDImage","vec2 tex_scale",asOFFSET(HUDImage,tex_scale));
+ as_context->RegisterObjectProperty("HUDImage","vec2 tex_offset",asOFFSET(HUDImage,tex_offset));
+ as_context->RegisterObjectProperty("HUDImage","vec4 color",asOFFSET(HUDImage,color));
+ as_context->RegisterObjectMethod("HUDImage", "float GetWidth() const", asMETHOD(HUDImage, GetWidth), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("HUDImage", "float GetHeight() const", asMETHOD(HUDImage, GetHeight), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("HUDImage", "bool SetImageFromPath(const string &in)", asMETHOD(HUDImage, SetImageFromPath), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("HUDImage", "void SetImageFromText(const TextCanvasTexture@)", asMETHOD(HUDImage, SetImageFromText), asCALL_THISCALL);
+ as_context->DocsCloseBrace();
+
+ as_context->RegisterObjectType("HUDImages", 0, asOBJ_REF | asOBJ_NOHANDLE);
+ as_context->RegisterObjectMethod("HUDImages","HUDImage@ AddImage()",asMETHOD(HUDImages, AddImage), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("HUDImages","void Draw()", asMETHOD(HUDImages, Draw), asCALL_THISCALL);
+ as_context->DocsCloseBrace();
+
+ as_context->RegisterGlobalProperty("HUDImages hud", this);
+}
+
+void HUDImage::SetImageFromText( const TextCanvasTexture *text_canvas_texture) {
+ texture_asset.clear();
+ texture_ref = text_canvas_texture->GetTexture();
+ text = true;
+}
+
+bool HUDImage::SetImageFromPath( const std::string &path ) {
+ texture_asset = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(path, PX_NOMIPMAP | PX_NOREDUCE, 0x0);
+ if( texture_asset.valid() ) {
+ texture_ref = texture_asset->GetTextureRef();
+ text = false;
+ return true;
+ } else {
+ LOGE << "Loading " << path << "for HUDImage failed, will disable rendering for this object";
+ texture_ref.clear();
+ return false;
+ }
+}
+
+float HUDImage::GetWidth() const {
+ return (float)(Textures::Instance()->getReducedWidth(texture_ref));
+}
+
+float HUDImage::GetHeight() const {
+ return (float)(Textures::Instance()->getReducedHeight(texture_ref));
+}
+
+
diff --git a/Source/Graphics/hudimage.h b/Source/Graphics/hudimage.h
new file mode 100644
index 00000000..3e0bd78f
--- /dev/null
+++ b/Source/Graphics/hudimage.h
@@ -0,0 +1,63 @@
+//-----------------------------------------------------------------------------
+// Name: hudimage.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec2.h>
+#include <Math/vec3.h>
+#include <Math/vec4.h>
+
+#include <Graphics/textureref.h>
+#include <Asset/Asset/texture.h>
+
+#include <string>
+#include <vector>
+
+class ASContext;
+class TextCanvasTexture;
+
+struct HUDImage {
+ TextureRef texture_ref;
+ TextureAssetRef texture_asset;
+ vec3 pos;
+ vec3 scale;
+ vec2 tex_scale;
+ vec2 tex_offset;
+ vec4 color;
+ bool text;
+ bool flipped;
+
+ float GetWidth() const;
+ float GetHeight() const;
+ bool SetImageFromPath(const std::string &path);
+ void SetImageFromText( const TextCanvasTexture *text_canvas_texture);
+};
+
+struct HUDImages {
+ std::vector<HUDImage> hud_images;
+ HUDImages();
+ void AttachToContext(ASContext *as_context);
+ void Draw();
+private:
+ HUDImage* AddImage();
+};
diff --git a/Source/Graphics/ikbone.h b/Source/Graphics/ikbone.h
new file mode 100644
index 00000000..00b45d20
--- /dev/null
+++ b/Source/Graphics/ikbone.h
@@ -0,0 +1,47 @@
+//-----------------------------------------------------------------------------
+// Name: ikbone.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+#include <Graphics/bonetransform.h>
+
+#include <vector>
+#include <string>
+
+typedef std::vector<int> BonePath;
+
+struct IKBone {
+ BonePath bone_path;
+ std::string label;
+};
+
+struct BlendedBonePath {
+ IKBone ik_bone;
+ float weight;
+ BoneTransform transform;
+ BoneTransform unmodified_transform; // Set by animationclient
+ BlendedBonePath():
+ weight(0.0f)
+ {}
+};
diff --git a/Source/Graphics/kdtreecluster.cpp b/Source/Graphics/kdtreecluster.cpp
new file mode 100644
index 00000000..9d1a7d84
--- /dev/null
+++ b/Source/Graphics/kdtreecluster.cpp
@@ -0,0 +1,389 @@
+//-----------------------------------------------------------------------------
+// Name: kdtreecluster.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "kdtreecluster.h"
+
+#include <Graphics/pxdebugdraw.h>
+#include <Math/vec3math.h>
+#include <Logging/logdata.h>
+
+#include <algorithm>
+#include <cstdio>
+
+void GetClosestClusters( KDNode &node, const std::vector<vec3> &cluster_center, std::vector<int> candidates )
+{
+ node.cluster_info.closest_clusters.clear();
+ if(candidates.size() == 1){
+ node.cluster_info.closest_clusters.push_back(candidates[0]);
+ for(unsigned i=0; i<2; ++i){
+ if(node.child[i]){
+ GetClosestClusters(*node.child[i],
+ cluster_center,
+ node.cluster_info.closest_clusters);
+ }
+ }
+ }
+ vec3 node_center = node.content_bounds[0]+node.content_bounds[1];
+ int closest_candidate = -1;
+ float closest_dist;
+ float dist;
+ for(unsigned i=0; i<candidates.size(); ++i){
+ dist = distance_squared(node_center, cluster_center[candidates[i]]);
+ if(closest_candidate == -1 || dist < closest_dist){
+ closest_dist = dist;
+ closest_candidate = i;
+ }
+ }
+ node.cluster_info.closest_clusters.clear();
+ node.cluster_info.closest_clusters.push_back(candidates[closest_candidate]);
+ const vec3 &closest_pos = cluster_center[candidates[closest_candidate]];
+ vec3 dir;
+ //int closest_point;
+ //float greatest_dot_amount;
+ //float dot_amount;
+ vec3 corner;
+ vec3 closest_corner;
+ for(int i=0; i<(int)candidates.size(); ++i){
+ if(i == closest_candidate){
+ continue;
+ }
+ const vec3 &other_pos = cluster_center[candidates[i]];
+ if(other_pos[0] >= node.content_bounds[0][0] &&
+ other_pos[1] >= node.content_bounds[0][1] &&
+ other_pos[2] >= node.content_bounds[0][2] &&
+ other_pos[0] <= node.content_bounds[1][0] &&
+ other_pos[1] <= node.content_bounds[1][1] &&
+ other_pos[2] <= node.content_bounds[1][2])
+ {
+ node.cluster_info.closest_clusters.push_back(candidates[i]);
+ continue;
+ }
+ dir = other_pos - closest_pos;
+ //closest_point = -1;
+ for(unsigned j=0; j<8; ++j){
+ corner = vec3(node.content_bounds[j/4][0],
+ node.content_bounds[j%2][1],
+ node.content_bounds[(j/2)%2][2]);
+ /*dot_amount = dot(corner, dir);
+ if(closest_point == -1 || dot_amount > greatest_dot_amount){
+ greatest_dot_amount = dot_amount;
+ closest_point = j;
+ closest_corner = corner;
+ }*/
+ if(distance_squared(corner, other_pos) <
+ distance_squared(corner, closest_pos))
+ {
+ node.cluster_info.closest_clusters.push_back(candidates[i]);
+ break;
+ }
+ }
+ /*if(distance_squared(closest_corner, other_pos) <
+ distance_squared(closest_corner, closest_pos))
+ {
+ node.cluster_info.closest_clusters.push_back(candidates[i]);
+ }*/
+ }
+ for(unsigned i=0; i<2; ++i){
+ if(node.child[i]){
+ GetClosestClusters(*node.child[i],
+ cluster_center,
+ node.cluster_info.closest_clusters);
+ }
+ }
+}
+
+//void DetailObjectSurface::Cluster()
+//{
+ //std::vector<vec3> points(detail_instances.size());
+ //for(unsigned i=0; i<points.size(); ++i){
+ // points[i] = detail_instances[i].transform.GetTranslationPart();
+ //}
+
+ //vec3 min_bound,max_bound;
+ //min_bound = points[0];
+ //max_bound = points[0];
+ //for(unsigned i=0; i<points.size(); ++i){
+ // for(unsigned j=0; j<3; ++j){
+ // if(points[i][j] < min_bound[j]){
+ // min_bound[j] = points[i][j];
+ // }
+ // if(points[i][j] > max_bound[j]){
+ // max_bound[j] = points[i][j];
+ // }
+ // }
+ //}
+
+ //KDNode kd_node(min_bound, max_bound, points);
+ //std::vector<KDNode*> point_leaves(points.size());
+ //for(unsigned i=0; i<points.size(); ++i){
+ // point_leaves[i] = kd_node.GetLeaf(points[i]);
+ //}
+
+ //const int cluster_density = 1000;
+ //unsigned num_clusters = (points.size()-1)/cluster_density+1; // Num clusters = num points / density (rounded up)
+ //std::vector<vec3> cluster_center(num_clusters);
+ //std::vector<int> cluster_population(num_clusters, 0);
+ //std::vector<int> cluster_assignment(points.size());
+ //for(unsigned i=0; i<cluster_assignment.size(); ++i){ // Start by assigning each point to a random cluster
+ // cluster_assignment[i] = rand()%num_clusters;
+ //}
+ //std::vector<int> all_candidates(num_clusters); // Create list of all candidate clusters
+ //for(unsigned i=0; i<num_clusters; ++i){
+ // all_candidates[i] = i;
+ //}
+
+ //int num_iterations = 0;
+ //bool something_changed = true;
+ //while(something_changed){ // Repeat k-means until nothing changes in an iteration
+ // for(unsigned i=0; i<num_clusters; ++i){
+ // cluster_center[i] = vec3(0.0f);
+ // cluster_population[i] = 0;
+ // }
+ // for(unsigned i=0; i<cluster_assignment.size(); ++i){ // Get center position of each cluster by averaging
+ // cluster_center[cluster_assignment[i]] += points[i]; // all member point positions
+ // ++cluster_population[cluster_assignment[i]];
+ // }
+ // for(unsigned i=0; i<num_clusters; ++i){
+ // if(cluster_population[i] != 0.0f){
+ // cluster_center[i] /= float(cluster_population[i]);
+ // }
+ // }
+ // GetClosestClusters(kd_node, cluster_center, all_candidates); // Calculate kd-tree cluster center candidates
+ // something_changed = false;
+ // int closest_cluster;
+ // float closest_distance;
+ // float dist;
+ // for(unsigned i=0; i<points.size(); ++i){
+ // closest_cluster = -1;
+ // const std::vector<int> &closest_clusters =
+ // point_leaves[i]->cluster_info.closest_clusters;
+ // for(unsigned j=0; j<closest_clusters.size(); ++j){
+ // dist = distance_squared(points[i], cluster_center[closest_clusters[j]]);
+ // if(closest_cluster == -1 || dist < closest_distance){
+ // closest_distance = dist;
+ // closest_cluster = closest_clusters[j];
+ // }
+ // }
+ // if(closest_cluster != cluster_assignment[i]){
+ // cluster_assignment[i] = closest_cluster;
+ // something_changed = true;
+ // }
+ // }
+ // ++num_iterations;
+ //}
+
+ //printf("Clustering completed after %d iterations\n", num_iterations);
+
+ //std::vector<vec3> cluster_colors(num_clusters);
+ //for(unsigned i=0; i<num_clusters; ++i){
+ // cluster_colors[i][0] = RangedRandomFloat(0.0f,1.0f);
+ // cluster_colors[i][1] = RangedRandomFloat(0.0f,1.0f);
+ // cluster_colors[i][2] = RangedRandomFloat(0.0f,1.0f);
+ //}
+ //for(unsigned i=0; i<points.size(); ++i){
+ // DebugDraw::Instance()->AddPoint(
+ // points[i],
+ // vec4(cluster_colors[cluster_assignment[i]],1.0f),
+ // _persistent,
+ // 0);
+ //}
+//}
+
+class Vec3XCompare {
+public:
+ bool operator()(const vec3 &a, const vec3 &b) {
+ return a[0] < b[0];
+ }
+};
+
+class Vec3YCompare {
+public:
+ bool operator()(const vec3 &a, const vec3 &b) {
+ return a[1] < b[1];
+ }
+};
+
+
+class Vec3ZCompare {
+public:
+ bool operator()(const vec3 &a, const vec3 &b) {
+ return a[2] < b[2];
+ }
+};
+
+const int _kd_split_threshold = 4;
+
+KDNode::KDNode( const vec3 &_min_bounds, const vec3 &_max_bounds, std::vector<vec3> points, int depth )
+{
+ bounds[0] = _min_bounds;
+ bounds[1] = _max_bounds;
+ content_bounds[0] = bounds[0];
+ content_bounds[1] = bounds[1];
+
+ child[0] = NULL;
+ child[1] = NULL;
+
+ if((int)points.size() < _kd_split_threshold){
+ return;
+ }
+
+ axis = depth%3;
+
+ vec3 median_point;
+ const int _rand_pick_threshold = 20;
+ if((int)points.size()>_rand_pick_threshold){
+ std::vector<vec3> median_points(_rand_pick_threshold);
+ for(unsigned i=0; i<median_points.size(); ++i){
+ median_points[i] = points[rand()%points.size()];
+ }
+ if(axis == 0){
+ std::sort(median_points.begin(), median_points.end(), Vec3XCompare());
+ } else if(axis == 1){
+ std::sort(median_points.begin(), median_points.end(), Vec3YCompare());
+ } else if(axis == 2){
+ std::sort(median_points.begin(), median_points.end(), Vec3ZCompare());
+ }
+
+ int median = median_points.size()/2;
+ plane_coord = median_points[median][axis];
+ median_point = median_points[median];
+ } else {
+ if(axis == 0){
+ std::sort(points.begin(), points.end(), Vec3XCompare());
+ } else if(axis == 1){
+ std::sort(points.begin(), points.end(), Vec3YCompare());
+ } else if(axis == 2){
+ std::sort(points.begin(), points.end(), Vec3ZCompare());
+ }
+
+ int median = points.size()/2;
+ plane_coord = points[median][axis];
+ median_point = points[median];
+ }
+
+ const bool _debug_draw_cut_plane = false;
+ if(_debug_draw_cut_plane){
+ vec4 color = vec4(vec3(1.0f),1.0f-depth*0.04f);
+ vec3 plane_points[4];
+ plane_points[0][axis] = plane_coord;
+ plane_points[0][(axis+1)%3] = bounds[0][(axis+1)%3];
+ plane_points[0][(axis+2)%3] = bounds[0][(axis+2)%3];
+ plane_points[1][axis] = plane_coord;
+ plane_points[1][(axis+1)%3] = bounds[1][(axis+1)%3];
+ plane_points[1][(axis+2)%3] = bounds[0][(axis+2)%3];
+ plane_points[2][axis] = plane_coord;
+ plane_points[2][(axis+1)%3] = bounds[1][(axis+1)%3];
+ plane_points[2][(axis+2)%3] = bounds[1][(axis+2)%3];
+ plane_points[3][axis] = plane_coord;
+ plane_points[3][(axis+1)%3] = bounds[0][(axis+1)%3];
+ plane_points[3][(axis+2)%3] = bounds[1][(axis+2)%3];
+
+ DebugDraw::Instance()->AddLine(plane_points[0], plane_points[1], color, color, _persistent);
+ DebugDraw::Instance()->AddLine(plane_points[1], plane_points[2], color, color, _persistent);
+ DebugDraw::Instance()->AddLine(plane_points[2], plane_points[3], color, color, _persistent);
+ DebugDraw::Instance()->AddLine(plane_points[3], plane_points[0], color, color, _persistent);
+ }
+
+ vec3 child_min = _min_bounds;
+ vec3 child_max = _max_bounds;
+ child_max[axis] = plane_coord;
+ std::vector<vec3> child_points;
+ for(unsigned i=0; i<points.size(); ++i){
+ if(points[i].entries[axis] < plane_coord){
+ child_points.push_back(points[i]);
+ }
+ }
+ child[0] = new KDNode(child_min, child_max, child_points, depth+1);
+
+ child[0]->content_bounds[0] = median_point;
+ child[0]->content_bounds[1] = median_point;
+ for(unsigned i=0; i<child_points.size(); ++i){
+ for(unsigned j=0; j<3; ++j){
+ if(child_points[i][j] < child[0]->content_bounds[0][j]){
+ child[0]->content_bounds[0][j] = child_points[i][j];
+ }
+ if(child_points[i][j] > child[0]->content_bounds[1][j]){
+ child[0]->content_bounds[1][j] = child_points[i][j];
+ }
+ }
+ }
+
+ child_min = _min_bounds;
+ child_max = _max_bounds;
+ child_min[axis] = plane_coord;
+ child_points.clear();
+ for(unsigned i=0; i<points.size(); ++i){
+ if(points[i].entries[axis] > plane_coord){
+ child_points.push_back(points[i]);
+ }
+ }
+ child[1] = new KDNode(child_min, child_max, child_points, depth+1);
+
+ child[1]->content_bounds[0] = child_points[0];
+ child[1]->content_bounds[1] = child_points[0];
+ for(unsigned i=0; i<child_points.size(); ++i){
+ for(unsigned j=0; j<3; ++j){
+ if(child_points[i][j] < child[1]->content_bounds[0][j]){
+ child[1]->content_bounds[0][j] = child_points[i][j];
+ }
+ if(child_points[i][j] > child[1]->content_bounds[1][j]){
+ child[1]->content_bounds[1][j] = child_points[i][j];
+ }
+ }
+ }
+
+}
+
+KDNode::~KDNode()
+{
+ delete child[0];
+ delete child[1];
+}
+
+KDNode* KDNode::GetLeaf( const vec3 &point )
+{
+ if(point[0] < bounds[0][0] ||
+ point[1] < bounds[0][1] ||
+ point[2] < bounds[0][2] ||
+ point[0] > bounds[1][0] ||
+ point[1] > bounds[1][1] ||
+ point[2] > bounds[1][2])
+ {
+ LOGE << "Get leaf failure" << std::endl;
+ return this;
+ }
+ if(!child[0]){
+ return this;
+ }
+ if(point[0] >= child[0]->bounds[0][0] &&
+ point[1] >= child[0]->bounds[0][1] &&
+ point[2] >= child[0]->bounds[0][2] &&
+ point[0] <= child[0]->bounds[1][0] &&
+ point[1] <= child[0]->bounds[1][1] &&
+ point[2] <= child[0]->bounds[1][2])
+ {
+ return child[0]->GetLeaf(point);
+ } else {
+ return child[1]->GetLeaf(point);
+ }
+}
diff --git a/Source/Graphics/kdtreecluster.h b/Source/Graphics/kdtreecluster.h
new file mode 100644
index 00000000..c6aa841e
--- /dev/null
+++ b/Source/Graphics/kdtreecluster.h
@@ -0,0 +1,44 @@
+//-----------------------------------------------------------------------------
+// Name: kdtreecluster.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+
+#include <vector>
+
+struct ClusterInfo {
+ std::vector<int> closest_clusters;
+};
+
+struct KDNode {
+ vec3 bounds[2];
+ vec3 content_bounds[2];
+ int axis;
+ float plane_coord;
+ ClusterInfo cluster_info;
+ KDNode *child[2];
+ KDNode(const vec3 &_min_bounds, const vec3 &_max_bounds, std::vector<vec3> points, int depth=0);
+ ~KDNode();
+ KDNode* GetLeaf( const vec3 &point );
+};
diff --git a/Source/Graphics/lightprobecollection.cpp b/Source/Graphics/lightprobecollection.cpp
new file mode 100644
index 00000000..78906493
--- /dev/null
+++ b/Source/Graphics/lightprobecollection.cpp
@@ -0,0 +1,1020 @@
+//-----------------------------------------------------------------------------
+// Name: lightprobecollection.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "lightprobecollection.hpp"
+
+#include <Graphics/camera.h>
+#include <Graphics/pxdebugdraw.h>
+#include <Graphics/graphics.h>
+#include <Graphics/shaders.h>
+#include <Graphics/sky.h>
+#include <Graphics/textures.h>
+#include <Graphics/models.h>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <Physics/bulletworld.h>
+#include <Memory/allocation.h>
+#include <Internal/profiler.h>
+#include <Game/hardcoded_assets.h>
+#include <Math/vec3math.h>
+#include <Wrappers/glm.h>
+#include <Logging/logdata.h>
+#include <Utility/assert.h>
+
+#include <cfloat>
+
+const float kQuantize = 10.0f;
+
+LightProbeCollection::LightProbeCollection():
+ tet_mesh_viz_enabled(false),
+ show_probes(false),
+ show_probes_through_walls(false),
+ probe_lighting_enabled(true),
+ light_volume_enabled(true),
+ probe_model_id(-1)
+{
+ cube_map_fbo = INVALID_FRAMEBUFFER;
+ next_id = 0;
+ tet_mesh.display_id = -1;
+ tet_mesh_needs_update = false;
+ grid_lookup.bounds[0] = vec3(0.0f, 0.0f, 0.0f);
+ grid_lookup.bounds[1] = vec3(0.0f, 0.0f, 0.0f);
+ grid_lookup.subdivisions[0] = 0;
+ grid_lookup.subdivisions[1] = 0;
+ grid_lookup.subdivisions[2] = 0;
+}
+
+LightProbeCollection::~LightProbeCollection() {
+ LOG_ASSERT(cube_map_fbo == INVALID_FRAMEBUFFER);
+}
+
+int LightProbeCollection::AddProbe(const vec3& pos, bool negative, const float *coeff) {
+ light_probes.resize(light_probes.size()+1);
+ LightProbe &probe = light_probes.back();
+ probe.pos = pos;
+ probe.negative = negative;
+ probe.id = next_id++;
+ if(!negative && !coeff){
+ LightProbeUpdateEntry entry;
+ entry.id = probe.id;
+ entry.pass = 0;
+ to_process.push(entry);
+ } else if (coeff) {
+ for (unsigned int i = 0; i < 6; i++) {
+ probe.ambient_cube_color[i].entries[0] = coeff[i * 3 + 0];
+ probe.ambient_cube_color[i].entries[1] = coeff[i * 3 + 1];
+ probe.ambient_cube_color[i].entries[2] = coeff[i * 3 + 2];
+ }
+ }
+ tet_mesh_needs_update = true;
+ return probe.id;
+}
+
+bool LightProbeCollection::MoveProbe(int id, const vec3& pos) {
+ LightProbe* probe = GetProbeFromID(id);
+ if(probe){
+ probe->pos = pos;
+ LightProbeUpdateEntry entry;
+ entry.id = id;
+ entry.pass = 0;
+ to_process.push(entry);
+ tet_mesh_needs_update = true;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool LightProbeCollection::SetNegative(int id, bool negative) {
+ LightProbe* probe = GetProbeFromID(id);
+ if(probe){
+ probe->negative = negative;
+ LightProbeUpdateEntry entry;
+ entry.id = id;
+ entry.pass = 0;
+ to_process.push(entry);
+ tet_mesh_needs_update = true;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool LightProbeCollection::DeleteProbe(int id) {
+ for(int i=0, len=light_probes.size(); i<len; ++i){
+ if(light_probes[i].id == id) {
+ light_probes.erase(light_probes.begin() + i);
+ tet_mesh_needs_update = true;
+ return true;
+ }
+ }
+ return false;
+}
+
+void LightProbeCollection::Init() {
+ if(cube_map_fbo == INVALID_FRAMEBUFFER){
+ Graphics::Instance()->genFramebuffers(&cube_map_fbo, "light_probe_cube_map");
+ }
+ if(!cube_map.valid()){
+ cube_map = Textures::Instance()->makeCubemapTexture(128, 128, GL_RGBA16F, GL_RGBA, Textures::MIPMAPS);
+ }
+ Textures::Instance()->SetTextureName(cube_map, "Light Probe Collection Cubemap");
+ probe_model_id = Models::Instance()->loadModel(HardcodedPaths::paths[HardcodedPaths::kLightProbe]);
+ if(!light_probes.empty()){
+ tet_mesh_needs_update = true;
+ }
+ light_probe_texture_buffer_id = -1;
+ light_probe_buffer_object_id = -1;
+ grid_texture_buffer_id = -1;
+ grid_buffer_object_id = -1;
+}
+
+void LightProbeCollection::Dispose() {
+ Graphics::Instance()->deleteFramebuffer(&cube_map_fbo);
+ cube_map_fbo = INVALID_FRAMEBUFFER;
+ cube_map.clear();
+ ambient_3d_tex.clear();
+ light_probes.clear();
+}
+
+int LightProbeCollection::ShaderNumLightProbes()
+{
+ return probe_lighting_enabled ? light_probes.size() : 0;
+}
+
+int LightProbeCollection::ShaderNumTetrahedra()
+{
+ return probe_lighting_enabled ? tet_mesh.points.size()/4 : 0;
+}
+
+LightProbe* LightProbeCollection::GetNextProbeToProcess() {
+ if(!to_process.empty()){
+ LightProbeUpdateEntry entry = to_process.front();
+ int id = entry.id;
+ to_process.pop();
+ LightProbe* probe = GetProbeFromID(id);
+ if(probe){
+ return probe;
+ }
+ }
+ return NULL;
+}
+
+LightProbe* LightProbeCollection::GetProbeFromID(int id) {
+ for(int i=0, len=light_probes.size(); i<len; ++i){
+ if(light_probes[i].id == id) {
+ return &light_probes[i];
+ }
+ }
+ return NULL;
+}
+
+float GetTetrahedronVolume(vec3 points[4]){
+ glm::mat4 tet_volume_mat;
+ for(int column=0; column<4; ++column){
+ tet_volume_mat[column] = glm::vec4(points[column][0], points[column][1], points[column][2], 1.0f);
+ }
+ float volume = fabs(glm::determinant(tet_volume_mat) / 6.0f);
+ return volume;
+}
+
+void LightProbeCollection::UpdateTetMesh(BulletWorld& bw) {
+ PROFILER_ZONE(g_profiler_ctx, "LightProbeCollection::UpdateTetMesh");
+ tet_mesh_needs_update = false;
+ // Clear existing tet mesh
+ tet_mesh.points.clear();
+ tet_mesh.point_id.clear();
+ if(tet_mesh.display_id != -1){
+ DebugDraw::Instance()->Remove(tet_mesh.display_id);
+ }
+ // Don't bother if we don't even have four probes
+ if(light_probes.size() < 4) {
+ return;
+ }
+
+ //Following block is commented out, disabling light probes, to disconnect tetgen from the project
+// // Prepare tetgen
+// tetgenio in, out;
+// std::vector<REAL> points;
+// std::vector<REAL> point_attributes;
+// points.resize(light_probes.size()*3);
+// for(int tet=0, index=0, len=light_probes.size(); tet<len; ++tet, index+=3){
+// // Quantize points how they will be bit-packed later for shaders
+// for(int j=0; j<3; ++j){
+// unsigned encoded = (unsigned)(light_probes[tet].pos[j] * kQuantize + 32767.5f);
+// float decoded = (encoded - 32767.5f) / kQuantize;
+// points[index+j] = (REAL)decoded;
+// }
+// // Add point id
+// point_attributes.push_back((REAL)((float)tet+0.5f));
+// }
+// in.pointlist = &points[0];
+// in.numberofpoints = light_probes.size();
+// in.numberofpointattributes = 1;
+// in.pointattributelist = &point_attributes[0];
+//
+// bool success = true;
+// tetgenbehavior behavior;
+// behavior.quiet = true;
+// behavior.neighout = true; // We want to preserve neighbor info so we don't have to recalculate that
+// try {
+// tetrahedralize(&behavior, &in, &out);
+// } catch(int) {
+// success = false;
+// }
+// in.initialize();
+// if(success && out.numberofpoints > 0){
+// tet_mesh.points.resize(out.numberofpoints);
+// tet_mesh.tet_points.resize(out.numberoftetrahedra*4);
+// tet_mesh.neighbors.resize(out.numberoftetrahedra*4);
+//
+// for(int i=0, index=0, len=out.numberofpoints;
+// i<len;
+// ++i, index+=3)
+// {
+// for(int j=0; j<3; ++j){
+// tet_mesh.points[i][j] = (float)out.pointlist[index+j];
+// }
+// tet_mesh.point_id.push_back((int)out.pointattributelist[i]);
+// }
+//
+// for(int i=0, len=out.numberoftetrahedra*4; i<len; ++i) {
+// tet_mesh.tet_points[i] = out.tetrahedronlist[i];
+// }
+//
+// for(int i=0, len=out.numberoftetrahedra*4; i<len; ++i) {
+// tet_mesh.neighbors[i] = out.neighborlist[i];
+// }
+//
+// if(tet_mesh_viz_enabled) {
+// std::vector<GLfloat> line_verts;
+// std::vector<GLuint> line_indices;
+//
+// for(int i=0, index=0, len=tet_mesh.tet_points.size()/4;
+// i<len;
+// ++i, index+=4)
+// {
+// for(int j=0; j<6; ++j){
+// int points[2];
+// const int *tet_points = &tet_mesh.tet_points[index];
+// switch(j){
+// case 0: points[0] = tet_points[0]; points[1] = tet_points[1]; break;
+// case 1: points[0] = tet_points[0]; points[1] = tet_points[2]; break;
+// case 2: points[0] = tet_points[0]; points[1] = tet_points[3]; break;
+// case 3: points[0] = tet_points[1]; points[1] = tet_points[2]; break;
+// case 4: points[0] = tet_points[1]; points[1] = tet_points[3]; break;
+// case 5: points[0] = tet_points[2]; points[1] = tet_points[3]; break;
+// }
+// //if(!light_probes[tet_mesh.point_id[points[0]]].negative &&
+// // !light_probes[tet_mesh.point_id[points[1]]].negative)
+// //{
+// for(int i=0; i<2; ++i){
+// vec3 pos = light_probes[tet_mesh.point_id[points[i]]].pos;
+// line_indices.push_back(line_verts.size()/3);
+// for(int j=0; j<3; ++j){
+// unsigned encoded = (unsigned)(pos[j] * kQuantize + 32767.5f);
+// float decoded = (encoded - 32767.5f) / kQuantize;
+// line_verts.push_back(decoded);
+// }
+// }
+// //}
+// }
+// }
+// tet_mesh.display_id = DebugDraw::Instance()->AddLines(line_verts, line_indices, vec4(vec3(1.0f), 0.2f), _persistent, _DD_NO_FLAG);
+// }
+// }
+//
+// // Update grid lookup
+// grid_lookup.bounds[0] = vec3(FLT_MAX);
+// grid_lookup.bounds[1] = vec3(-FLT_MAX);
+// for(int tet=0, len=tet_mesh.points.size(); tet<len; ++tet){
+// for(int j=0; j<3; ++j){
+// grid_lookup.bounds[0][j] = min(grid_lookup.bounds[0][j], tet_mesh.points[tet][j]);
+// grid_lookup.bounds[1][j] = max(grid_lookup.bounds[1][j], tet_mesh.points[tet][j]);
+// }
+// }
+// static const float kCellSize = 4.0f; // Target dimensions of each grid cell
+// for(int tet=0; tet<3; ++tet){
+// grid_lookup.subdivisions[tet] = (int)ceilf((grid_lookup.bounds[1][tet] - grid_lookup.bounds[0][tet]) / kCellSize);
+// }
+// int total_cells = grid_lookup.subdivisions[0] * grid_lookup.subdivisions[1] * grid_lookup.subdivisions[2];
+// if(total_cells > GridLookup::kMaxGridCells){
+// float scale = 0.99f;
+// while((int)ceilf(grid_lookup.subdivisions[0]*scale) *
+// (int)ceilf(grid_lookup.subdivisions[1]*scale) *
+// (int)ceilf(grid_lookup.subdivisions[2]*scale) > GridLookup::kMaxGridCells)
+// {
+// scale -= 0.01f;
+// }
+// for(int i=0; i<3; ++i){
+// grid_lookup.subdivisions[i] = (int)ceilf(grid_lookup.subdivisions[i]*scale);
+// }
+// }
+// const bool kDebugGridInfo = false;
+// if(kDebugGridInfo){
+// LOGI << "Grid bounds: " << "(" << grid_lookup.bounds[0][0] << " " << grid_lookup.bounds[0][1] << " " << grid_lookup.bounds[0][2] << ")"
+// << " (" << grid_lookup.bounds[1][0] << " " << grid_lookup.bounds[1][1] << " " << grid_lookup.bounds[1][2] << ")" << std::endl;
+// LOGI << "Grid subdivisions: (" << grid_lookup.subdivisions[0] << " " << grid_lookup.subdivisions[1] << " " << grid_lookup.subdivisions[2] << ")" << std::endl;
+// }
+// grid_lookup.cell_tet.resize(grid_lookup.subdivisions[0] * grid_lookup.subdivisions[1] * grid_lookup.subdivisions[2], UINT_MAX);
+// // Go through each tetrahedron and tag overlapping cells
+// for(int tet=0, index=0, len=tet_mesh.tet_points.size()/4;
+// tet<len;
+// ++tet, index+=4)
+// {
+// const int *tet_points = &tet_mesh.tet_points[index];
+// // Get world-space bounds of tetrahedron
+// vec3 ws_bounds[2] = {vec3(FLT_MAX), vec3(-FLT_MAX)};
+// for(int vert=0; vert<4; ++vert){
+// for(int axis=0; axis<3; ++axis){
+// ws_bounds[0][axis] = min(ws_bounds[0][axis], tet_mesh.points[tet_points[vert]][axis]);
+// ws_bounds[1][axis] = max(ws_bounds[1][axis], tet_mesh.points[tet_points[vert]][axis]);
+// }
+// }
+// // Get grid-space bounds
+// int gs_bounds[2][3];
+// for(int corner=0; corner<2; ++corner){
+// for(int axis=0; axis<3; ++axis){
+// gs_bounds[corner][axis] = (int)((ws_bounds[corner][axis] - grid_lookup.bounds[0][axis]) / (grid_lookup.bounds[1][axis] - grid_lookup.bounds[0][axis]) * (float)grid_lookup.subdivisions[axis]);
+// gs_bounds[corner][axis] = min(gs_bounds[corner][axis], grid_lookup.subdivisions[axis]-1);
+// }
+// }
+// // Get tetrahedron planes for later use
+// vec4 tet_planes[4];
+// for(int face=0; face<4; ++face){
+// int vert_ids[3];
+// switch(face) {
+// case 0: vert_ids[0] = 0; vert_ids[1] = 1; vert_ids[2] = 2; break;
+// case 1: vert_ids[0] = 0; vert_ids[1] = 2; vert_ids[2] = 3; break;
+// case 2: vert_ids[0] = 0; vert_ids[1] = 3; vert_ids[2] = 1; break;
+// case 3: vert_ids[0] = 1; vert_ids[1] = 3; vert_ids[2] = 2; break;
+// }
+// vec3 verts[3];
+// for(int i=0; i<3; ++i){
+// verts[i] = tet_mesh.points[tet_points[vert_ids[i]]];
+// }
+// vec3 normal = normalize(cross(verts[1] - verts[0], verts[2] - verts[0]));
+// float d = dot(normal, verts[0]);
+// tet_planes[face] = vec4(normal, d);
+// }
+// // TODO: use the tetrahedron info to calculate the volume that overlaps each grid cell
+// vec3 tet_verts[4];
+// for(int vert=0; vert<4; ++vert){
+// tet_verts[vert] = tet_mesh.points[tet_points[vert]];
+// }
+// //float volume = GetTetrahedronVolume(tet_verts);
+//
+// const bool kCheckMidpointIsInTet = true;
+// if(kCheckMidpointIsInTet){ // This is a sanity check -- if the midpoint of the tet is not inside all the planes, something is weird
+// vec3 mid_point(0.0f);
+// for(int vert=0; vert<4; ++vert){
+// mid_point += tet_mesh.points[tet_points[vert]] * 0.25f;
+// }
+// for(int face=0; face<4; ++face){
+// float dot_val = dot(mid_point, tet_planes[face].xyz());
+// LOG_ASSERT(dot_val > tet_planes[face][3]);
+// }
+// }
+//
+// vec3 grid_dims = grid_lookup.bounds[1] - grid_lookup.bounds[0];
+// vec3 grid_cell_dims;
+// for(int axis=0; axis<3; ++axis){
+// grid_cell_dims[axis] = grid_dims[axis] / (float)grid_lookup.subdivisions[axis];
+// }
+// btBoxShape* box_shape = new btBoxShape(btVector3(grid_cell_dims[0]*0.5f,
+// grid_cell_dims[1]*0.5f,
+// grid_cell_dims[2]*0.5f));
+// btCollisionObject aabb_col_obj;
+// aabb_col_obj.setCollisionShape(box_shape);
+// btTransform aabb_transform;
+// aabb_transform.setIdentity();
+//
+// btCollisionObject tet_col_obj;
+//
+// // Tag each of these grid cells with the current tetrahedron
+// for(int x_cell = gs_bounds[0][0]; x_cell <= gs_bounds[1][0]; ++x_cell){
+// for(int y_cell = gs_bounds[0][1]; y_cell <= gs_bounds[1][1]; ++y_cell){
+// for(int z_cell = gs_bounds[0][2]; z_cell <= gs_bounds[1][2]; ++z_cell){
+// /*aabb_transform.setOrigin(btVector3(
+// grid_lookup.bounds[0][0] + (x_cell + 0.5f) * grid_cell_dims[0],
+// grid_lookup.bounds[0][1] + (y_cell + 0.5f) * grid_cell_dims[1],
+// grid_lookup.bounds[0][2] + (z_cell + 0.5f) * grid_cell_dims[2]));
+// aabb_col_obj.setWorldTransform(aabb_transform);
+//
+// btConvexHullShape* tet_shape = new btConvexHullShape();
+// for(int i=0; i<4; ++i){
+// tet_shape->addPoint(btVector3(tet_verts[i][0],tet_verts[i][1],tet_verts[i][2]));
+// }
+// tet_col_obj.setCollisionShape(tet_shape);
+//
+// ContactInfoCallback cb;
+// bw.GetPairCollisions(aabb_col_obj, tet_col_obj, cb);
+//
+// delete tet_shape;
+//
+// if(cb.contact_info.size() != 0){*/
+// // TODO: eventually only label the tet with most volume in cell
+// int cell_id = ((x_cell * grid_lookup.subdivisions[1]) + y_cell)*grid_lookup.subdivisions[2] + z_cell;
+// grid_lookup.cell_tet[cell_id] = tet;
+// //}
+// }
+// }
+// }
+//
+// delete box_shape;
+// }
+}
+
+//The following is adapted from http://dennis2society.de/main/painless-tetrahedral-barycentric-mapping
+namespace {
+
+/**
+ * Calculate the determinant for a 4x4 matrix based on this example:
+ * http://www.euclideanspace.com/maths/algebra/matrix/functions/determinant/fourD/index.htm
+ * This function takes four Vec4f as row vectors and calculates the resulting matrix' determinant
+ * using the Laplace expansion.
+ *
+ */
+const float Determinant4x4( const vec4& v0,
+ const vec4& v1,
+ const vec4& v2,
+ const vec4& v3 )
+{
+ float det = v0[3]*v1[2]*v2[1]*v3[0] - v0[2]*v1[3]*v2[1]*v3[0] -
+ v0[3]*v1[1]*v2[2]*v3[0] + v0[1]*v1[3]*v2[2]*v3[0] +
+
+ v0[2]*v1[1]*v2[3]*v3[0] - v0[1]*v1[2]*v2[3]*v3[0] -
+ v0[3]*v1[2]*v2[0]*v3[1] + v0[2]*v1[3]*v2[0]*v3[1] +
+
+ v0[3]*v1[0]*v2[2]*v3[1] - v0[0]*v1[3]*v2[2]*v3[1] -
+ v0[2]*v1[0]*v2[3]*v3[1] + v0[0]*v1[2]*v2[3]*v3[1] +
+
+ v0[3]*v1[1]*v2[0]*v3[2] - v0[1]*v1[3]*v2[0]*v3[2] -
+ v0[3]*v1[0]*v2[1]*v3[2] + v0[0]*v1[3]*v2[1]*v3[2] +
+
+ v0[1]*v1[0]*v2[3]*v3[2] - v0[0]*v1[1]*v2[3]*v3[2] -
+ v0[2]*v1[1]*v2[0]*v3[3] + v0[1]*v1[2]*v2[0]*v3[3] +
+
+ v0[2]*v1[0]*v2[1]*v3[3] - v0[0]*v1[2]*v2[1]*v3[3] -
+ v0[1]*v1[0]*v2[2]*v3[3] + v0[0]*v1[1]*v2[2]*v3[3];
+ return det;
+}
+
+/**
+ * Calculate the actual barycentric coordinate from a point p0_ and the four
+ * vertices v0_ .. v3_ from a tetrahedron.
+ */
+const vec4 GetBarycentricCoordinate( const vec3& v0_,
+ const vec3& v1_,
+ const vec3& v2_,
+ const vec3& v3_,
+ const vec3& p0_)
+{
+ vec4 v0(v0_, 1.0f);
+ vec4 v1(v1_, 1.0f);
+ vec4 v2(v2_, 1.0f);
+ vec4 v3(v3_, 1.0f);
+ vec4 p0(p0_, 1.0f);
+ vec4 barycentricCoord;
+ const float det0 = Determinant4x4(v0, v1, v2, v3);
+ const float det1 = Determinant4x4(p0, v1, v2, v3);
+ const float det2 = Determinant4x4(v0, p0, v2, v3);
+ const float det3 = Determinant4x4(v0, v1, p0, v3);
+ const float det4 = Determinant4x4(v0, v1, v2, p0);
+ barycentricCoord[0] = (det1/det0);
+ barycentricCoord[1] = (det2/det0);
+ barycentricCoord[2] = (det3/det0);
+ barycentricCoord[3] = (det4/det0);
+ return barycentricCoord;
+}
+
+} // namespace ""
+
+static unsigned LeftShift(unsigned val, int amount) {
+ if(amount >-32 && amount < 32){
+ if(amount >= 0){
+ return val << amount;
+ } else {
+ return val >> -amount;
+ }
+ } else {
+ return 0;
+ }
+}
+
+void LightProbeCollection::UpdateTextureBuffer(BulletWorld& bw) {
+ PROFILER_ZONE(g_profiler_ctx, "LightProbeCollection::UpdateTextureBuffer");
+ if(tet_mesh_needs_update){
+ UpdateTetMesh(bw);
+ }
+
+ const int kMaxTextureBufferSize = 4 * 1024 * 1024;
+ // Create texture buffer object if it is not already created
+ if(light_probe_texture_buffer_id == -1){
+ PROFILER_ZONE(g_profiler_ctx, "Create light probe texture buffer");
+ int max_size;
+ glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, &max_size);
+ LOG_ASSERT(max_size >= kMaxTextureBufferSize);
+ GLuint buffer_object;
+ glGenBuffers(1, &buffer_object);
+ light_probe_buffer_object_id = buffer_object;
+ glBindBuffer(GL_TEXTURE_BUFFER, light_probe_buffer_object_id);
+ char* buf = new char[kMaxTextureBufferSize];
+ float *buf_f = (float*)buf;
+ // Start by filling buffer with noise
+ for(int i=0, len=kMaxTextureBufferSize/sizeof(float); i<len; ++i){
+ buf_f[i] = RangedRandomFloat(0.0f, 1.0f);
+ }
+ glBufferData(GL_TEXTURE_BUFFER, kMaxTextureBufferSize, buf, GL_DYNAMIC_DRAW);
+ delete[] buf;
+ GLuint tex;
+ glGenTextures(1, &tex);
+ light_probe_texture_buffer_id = tex;
+ glActiveTexture(GL_TEXTURE0 + TEX_AMBIENT_COLOR_BUFFER);
+ glBindTexture(GL_TEXTURE_BUFFER, light_probe_texture_buffer_id);
+ glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32UI, light_probe_buffer_object_id);
+ }
+ LOG_ASSERT(light_probe_texture_buffer_id != -1);
+ if(tet_mesh.point_id.size() >= 4){
+ // Calculate colors of tet corners, flood-filling from positive probes to negative probes
+ // TODO: allocate this using stack allocator for speed
+ std::vector<unsigned char> tet_colors(tet_mesh.tet_points.size() * 6 * 3);
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Flood fill tet mesh corners");
+ // Start by filling in positive probes
+ for(int i=0, len=tet_mesh.tet_points.size();
+ i<len;
+ i+=4)
+ {
+ int point_id[4];
+ for(int j=0; j<4; ++j){
+ point_id[j] = tet_mesh.point_id[tet_mesh.tet_points[i+j]];
+ }
+ for(int point=0; point<4; ++point){
+ int index = (i+point)*18;
+ LightProbe& probe = light_probes[point_id[point]];
+ if(probe.negative){
+ for(int face=0; face<6; ++face){
+ tet_colors[index] = 255;
+ ++index;
+ tet_colors[index] = 0;
+ ++index;
+ tet_colors[index] = 255;
+ ++index;
+ }
+ } else {
+ for(int face=0; face<6; ++face){
+ for(int channel=0; channel<3; ++channel){
+ unsigned val = (unsigned)(probe.ambient_cube_color[face][channel] * 255.0f / 4.0f);
+ tet_colors[index] = (val > 255)?255:val;
+ ++index;
+ }
+ }
+ }
+ }
+ }
+ // Next fill in negatives that share tet with positives
+ for(int i=0, len=tet_mesh.tet_points.size();
+ i<len;
+ i+=4)
+ {
+ int num_negative = 0;
+ int num_positive = 0;
+ int point_id[4];
+ for(int j=0; j<4; ++j){
+ point_id[j] = tet_mesh.point_id[tet_mesh.tet_points[i+j]];
+ LightProbe& probe = light_probes[point_id[j]];
+ if(probe.negative){
+ ++num_negative;
+ } else {
+ ++num_positive;
+ }
+ }
+ if(num_negative && num_positive){
+ // Find average color of positive probes in this tet
+ int avg_col[18] = {0};
+ for(int point=0; point<4; ++point){
+ int index = (i+point)*18;
+ LightProbe& probe = light_probes[point_id[point]];
+ if(!probe.negative){
+ for(int avg_index=0; avg_index<18; ++avg_index){
+ avg_col[avg_index] += tet_colors[index+avg_index];
+ }
+ }
+ }
+ for(int avg_index=0; avg_index<18; ++avg_index){
+ avg_col[avg_index] /= num_positive;
+ }
+ // Assign average color to negative probes in this tet
+ for(int point=0; point<4; ++point){
+ int index = (i+point)*18;
+ LightProbe& probe = light_probes[point_id[point]];
+ if(probe.negative){
+ for(int avg_index=0; avg_index<18; ++avg_index){
+ tet_colors[index+avg_index] = avg_col[avg_index];
+ }
+ }
+ }
+ }
+ }
+ }
+ PROFILER_ZONE(g_profiler_ctx, "Fill tet mesh buffers");
+ // We can load 128-bits at a time from the shader
+ // Each tetrahedron contains:
+ // 16 bits * 4 points * 3 axes = 192 bits for coords ~ 2 * 128
+ // 32 bits * 4 uints = 128 bits for neighbor ids ~ 1 * 128
+ // 8 bits * 3 channels * 6 cube faces = 576 bits ~ 5 * 128
+ // total ~ 8 * 128
+ glBindBuffer(GL_TEXTURE_BUFFER, light_probe_buffer_object_id);
+ int space = 16 * tet_mesh.tet_points.size() * 8;
+ char* buf = new char[space];
+ unsigned *buf_u = (unsigned*)buf;
+ int buf_index = 0;
+
+ for(int i=0, len=tet_mesh.tet_points.size();
+ i<len;
+ i+=4)
+ {
+ int point_id[4];
+ for(int j=0; j<4; ++j){
+ point_id[j] = tet_mesh.point_id[tet_mesh.tet_points[i+j]];
+ }
+
+ vec3 pos[4];
+ for(int j=0; j<4; ++j){
+ pos[j] = light_probes[point_id[j]].pos;
+ }
+
+ // Store coordinates of each point as 16-bit unsigned ints
+ unsigned points[12];
+ for(int j=0; j<12; ++j){
+ int axis = j%3;
+ int point = j/3;
+ points[j] = (unsigned)(pos[point][axis] * kQuantize + 32767.5f);
+ LOG_ASSERT(points[j] == (points[j] & 0xFFFF)); // Make sure coord fits in 16 bits
+ }
+ // Pack point values together
+ unsigned point_coord_u[6];
+ for(int j=0; j<6; ++j){
+ point_coord_u[j] = (points[j*2] << 16) + points[j*2+1];
+ }
+ // Pack neighbors
+ unsigned neighbors_u[4];
+ for(int j=0; j<4; ++j) {
+ neighbors_u[j] = (unsigned)(tet_mesh.neighbors[i+j]);
+ }
+ // Store negative info
+ unsigned negative = 0;
+ for(int j=0; j<4; ++j){
+ negative += (light_probes[point_id[j]].negative?0:1) << j;
+ }
+ // Pack colors
+ unsigned colors_u[18] = {0};
+ int offset = 0;
+ for(int point=0; point<4; ++point){
+ int index = (i+point)*18;
+ for(int face=0; face<6; ++face){
+ for(int channel=0; channel<3; ++channel){
+ unsigned val = tet_colors[index];
+ colors_u[offset/32] |= LeftShift(val, (24-offset%32));
+ offset += 8;
+ ++index;
+ }
+
+ }
+ }
+ buf_u[buf_index++] = point_coord_u[0];
+ buf_u[buf_index++] = point_coord_u[1];
+ buf_u[buf_index++] = point_coord_u[2];
+ buf_u[buf_index++] = point_coord_u[3];
+
+ buf_u[buf_index++] = point_coord_u[4];
+ buf_u[buf_index++] = point_coord_u[5];
+ buf_u[buf_index++] = negative;
+ buf_u[buf_index++] = 0;
+
+ buf_u[buf_index++] = neighbors_u[0];
+ buf_u[buf_index++] = neighbors_u[1];
+ buf_u[buf_index++] = neighbors_u[2];
+ buf_u[buf_index++] = neighbors_u[3];
+
+ for(int i=0; i<18; ++i){
+ buf_u[buf_index++] = colors_u[i];
+ }
+ buf_u[buf_index++] = 0;
+ buf_u[buf_index++] = 0;
+ }
+
+ glBufferData(GL_TEXTURE_BUFFER, kMaxTextureBufferSize, NULL, GL_DYNAMIC_DRAW); // Orphan buffer?
+
+ void* mapped = glMapBufferRange(GL_TEXTURE_BUFFER, 0, space, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT );
+ memcpy(mapped, buf, space);
+ glUnmapBuffer(GL_TEXTURE_BUFFER);
+
+ delete[] buf;
+ }
+ if(grid_texture_buffer_id == -1 && !grid_lookup.cell_tet.empty()){
+ PROFILER_ZONE(g_profiler_ctx, "Generate grid texture buffer");
+ int max_size;
+ glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, &max_size);
+ LOG_ASSERT(max_size >= kMaxTextureBufferSize);
+ GLuint buffer_object;
+ glGenBuffers(1, &buffer_object);
+ grid_buffer_object_id = buffer_object;
+ glBindBuffer(GL_TEXTURE_BUFFER, grid_buffer_object_id);
+ char* buf = new char[kMaxTextureBufferSize];
+ float *buf_f = (float*)buf;
+ for(int i=0, len=kMaxTextureBufferSize/sizeof(float); i<len; ++i){
+ buf_f[i] = RangedRandomFloat(0.0f, 1.0f);
+ }
+ glBufferData(GL_TEXTURE_BUFFER, kMaxTextureBufferSize, buf, GL_DYNAMIC_DRAW);
+ delete[] buf;
+ GLuint tex;
+ glGenTextures(1, &tex);
+ grid_texture_buffer_id = tex;
+ glActiveTexture(GL_TEXTURE0 + TEX_AMBIENT_GRID_DATA);
+ glBindTexture(GL_TEXTURE_BUFFER, grid_texture_buffer_id);
+ glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32UI, grid_buffer_object_id);
+ }
+ if(grid_texture_buffer_id != -1 && !grid_lookup.cell_tet.empty()){
+ PROFILER_ZONE(g_profiler_ctx, "Update grid texture buffer");
+ glBindBuffer(GL_TEXTURE_BUFFER, grid_buffer_object_id);
+ int space = 4 * grid_lookup.cell_tet.size();
+ char* buf = new char[space];
+ unsigned *buf_u = (unsigned*)buf;
+ //int buf_index = 0;
+
+ for(int i=0, len=grid_lookup.cell_tet.size();
+ i<len;
+ ++i)
+ {
+ buf_u[i] = grid_lookup.cell_tet[i];
+ }
+
+ glBufferData(GL_TEXTURE_BUFFER, kMaxTextureBufferSize, NULL, GL_DYNAMIC_DRAW); // Orphan buffer?
+
+ void* mapped = glMapBufferRange(GL_TEXTURE_BUFFER, 0, space, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT );
+ memcpy(mapped, buf, space);
+ glUnmapBuffer(GL_TEXTURE_BUFFER);
+
+ delete[] buf;
+ }
+}
+
+int LightProbeCollection::GetTetrahedron(const vec3& position, vec3 ambient_cube_color[], int best_guess) {
+ std::set<int> visited_nodes;
+ if(!tet_mesh.points.empty()){
+ int prev_to_check = -1;
+ int prev_prev_to_check = -1;
+ int to_check = best_guess;
+
+ if(to_check == -1){
+ if(position[0] > grid_lookup.bounds[0][0] &&
+ position[1] > grid_lookup.bounds[0][1] &&
+ position[2] > grid_lookup.bounds[0][2] &&
+ position[0] < grid_lookup.bounds[1][0] &&
+ position[1] < grid_lookup.bounds[1][1] &&
+ position[2] < grid_lookup.bounds[1][2])
+ {
+ vec3 grid_coord;
+ grid_coord[0] = (float) int((position[0] - grid_lookup.bounds[0][0]) / (grid_lookup.bounds[1][0] - grid_lookup.bounds[0][0]) * float(grid_lookup.subdivisions[0]));
+ grid_coord[1] = (float) int((position[1] - grid_lookup.bounds[0][1]) / (grid_lookup.bounds[1][1] - grid_lookup.bounds[0][1]) * float(grid_lookup.subdivisions[1]));
+ grid_coord[2] = (float) int((position[2] - grid_lookup.bounds[0][2]) / (grid_lookup.bounds[1][2] - grid_lookup.bounds[0][2]) * float(grid_lookup.subdivisions[2]));
+ int cell_id = (int) (((grid_coord[0] * grid_lookup.subdivisions[1]) + grid_coord[1])*grid_lookup.subdivisions[2] + grid_coord[2]);
+ to_check = grid_lookup.cell_tet[cell_id];
+ if(to_check == -1){
+ to_check = 0;
+ }
+ } else {
+ to_check = 0;
+ }
+ }
+
+ bool success = false;
+ while(!success){
+ vec3 points[4];
+ for(int j=0, index = to_check*4; j<4; ++j){
+ points[j] = tet_mesh.points[tet_mesh.tet_points[index+j]];
+ }
+ vec4 bary_coords = GetBarycentricCoordinate(points[0], points[1], points[2], points[3], position);
+ float lowest_val = FLT_MAX;
+ int lowest_id = -1;
+ for(int i=0; i<4; ++i){
+ if(bary_coords[i] < lowest_val){
+ lowest_val = bary_coords[i];
+ lowest_id = i;
+ }
+ }
+ if(lowest_val >= 0.0f){
+ success = true;
+ } else {
+ visited_nodes.insert(to_check);
+
+ to_check = tet_mesh.neighbors[to_check*4+lowest_id];
+
+ //We've already been here before.
+ if( visited_nodes.find(to_check) != visited_nodes.end() )
+ {
+ return to_check;
+ //LOGE << "Returned to node we have already visited, exit execution" << std::endl;
+ //return -1;
+ }
+ }
+
+ if(to_check == -1){
+ return -1;
+ }
+
+ if(success || prev_to_check == to_check || prev_prev_to_check == to_check){
+ vec3 ambient_cube[24];
+ vec4 modified_bary_coords = bary_coords;
+ float total_modified_bary_coords = FLT_MIN;
+ for(int j=0, index=0; j<4; ++j, index+=6){
+ LightProbe* probe = &light_probes[tet_mesh.point_id[tet_mesh.tet_points[to_check*4+j]]];
+ if(probe) {
+ for(int k=0; k<6; ++k){
+ ambient_cube[index+k] = probe->ambient_cube_color[k];
+ }
+ if(probe->negative){
+ modified_bary_coords[j] = min(modified_bary_coords[j], FLT_MIN);
+ }
+ total_modified_bary_coords += modified_bary_coords[j];
+ }
+ }
+ for(int j=0; j<4; ++j){
+ modified_bary_coords[j] /= total_modified_bary_coords;
+ }
+
+ for(int j=0; j<6; ++j){
+ ambient_cube_color[j] = ambient_cube[0+j] * modified_bary_coords[0] +
+ ambient_cube[6+j] * modified_bary_coords[1] +
+ ambient_cube[12+j] * modified_bary_coords[2] +
+ ambient_cube[18+j] * modified_bary_coords[3];
+ }
+ return to_check;
+ }
+
+ prev_prev_to_check = prev_to_check;
+ prev_to_check = to_check;
+ }
+ }
+ return -1;
+}
+
+void LightProbeCollection::Draw(BulletWorld& bw) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw light probe collection");
+ Shaders* shaders = Shaders::Instance();
+ Graphics* graphics = Graphics::Instance();
+
+ if(show_probes){
+ for(int i=0; i<2; ++i){
+ GLState gl_state;
+ gl_state.blend = false;
+ gl_state.cull_face = true;
+ gl_state.depth_test = true;
+ gl_state.depth_write = true;
+
+ if(i==0 && !show_probes_through_walls){
+ continue;
+ }
+ if(i==0 && show_probes_through_walls){
+ gl_state.depth_test = false;
+ gl_state.depth_write = false;
+ }
+
+ graphics->setGLState(gl_state);
+
+ int shader_id = shaders->returnProgram("lightprobe");
+ if(i==0 && show_probes_through_walls){
+ shader_id = shaders->returnProgram("lightprobe #STIPPLE");
+ }
+ shaders->setProgram(shader_id);
+
+ int programHandle = shaders->programs[shader_id].gl_program;
+ GLuint blockIndex = glGetUniformBlockIndex(programHandle, "LightProbeInfo");
+
+ const GLchar *names[] = {
+ "center[0]",
+ "view_mat",
+ "proj_mat",
+ "cam_pos",
+ "ambient_cube_color[0]",
+ // These long names were necessary on a Mac OS 10.7 ATI card
+ "LightProbeInfo.center[0]",
+ "LightProbeInfo.view_mat",
+ "LightProbeInfo.proj_mat",
+ "LightProbeInfo.cam_pos",
+ "LightProbeInfo.ambient_cube_color[0]"
+ };
+
+ GLuint indices[5] = {0};
+ glGetUniformIndices(programHandle, 5, names, indices);
+ CHECK_GL_ERROR();
+
+ // Check long name if short name is not found
+ for(int i=0; i<5; ++i){
+ if(indices[i] == GL_INVALID_INDEX){
+ glGetUniformIndices(programHandle, 5, &names[5], indices);
+ break;
+ }
+ }
+
+ for(int i=0; i<5; ++i){
+ if(indices[i] == GL_INVALID_INDEX){
+ return;
+ }
+ }
+
+ GLint offset[5] = {-1};
+ glGetActiveUniformsiv(programHandle, 5, indices, GL_UNIFORM_OFFSET, offset);
+
+ GLint blockSize;
+ glGetActiveUniformBlockiv(programHandle, blockIndex, GL_UNIFORM_BLOCK_DATA_SIZE, &blockSize);
+ GLubyte * blockBuffer= (GLubyte *)OG_MALLOC(blockSize);
+
+ vec4* center = (vec4*)((uintptr_t)blockBuffer + offset[0]);
+ mat4* view_mat = (mat4*)((uintptr_t)blockBuffer + offset[1]);
+ mat4* proj_mat = (mat4*)((uintptr_t)blockBuffer + offset[2]);
+ vec3* cam_pos = (vec3*)((uintptr_t)blockBuffer + offset[3]);
+ vec4* ambient_cube_color_vec = (vec4*)((uintptr_t)blockBuffer + offset[4]);
+ static int ubo_id = -1;
+ if(ubo_id == -1){
+ GLuint uboHandle;
+ glGenBuffers( 1, &uboHandle );
+ ubo_id = uboHandle;
+ glBindBuffer( GL_UNIFORM_BUFFER, ubo_id );
+ glBufferData( GL_UNIFORM_BUFFER, blockSize, blockBuffer, GL_DYNAMIC_DRAW );
+ }
+ glBindBuffer( GL_UNIFORM_BUFFER, ubo_id );
+ glBindBufferBase( GL_UNIFORM_BUFFER, blockIndex, ubo_id );
+
+ Camera* camera = ActiveCameras::Get();
+ *cam_pos = camera->GetPos();
+ *view_mat = camera->GetViewMatrix();
+ *proj_mat = camera->GetProjMatrix();
+
+ int vert_attrib_id = shaders->returnShaderAttrib("vertex", shader_id);
+ Model* probe_model = &Models::Instance()->GetModel(probe_model_id);
+ if(!probe_model->vbo_loaded){
+ probe_model->createVBO();
+ }
+ probe_model->VBO_vertices.Bind();
+ probe_model->VBO_faces.Bind();
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, 3 * sizeof(GL_FLOAT), 0);
+ const int kBatchSize = 128; // Max that fits into 16k UBO
+ for(int i=0, len=light_probes.size(); i<len; i+=kBatchSize){
+ int num_to_render = std::min(kBatchSize, (int)light_probes.size() - i);
+ for(int j=0; j<num_to_render; ++j){
+ center[j] = vec4(light_probes[i+j].pos, 0.0f);
+ if(light_probes[i+j].negative){
+ for(int k=0; k<6; ++k){
+ ambient_cube_color_vec[j*6+k] = vec3(0.0f, 0.0f, 1.0f);
+ }
+ } else {
+ for(int k=0; k<6; ++k){
+ ambient_cube_color_vec[j*6+k] = light_probes[i+j].ambient_cube_color[k];
+ }
+ }
+ }
+ glBindBuffer( GL_UNIFORM_BUFFER, ubo_id );
+ glBufferData( GL_UNIFORM_BUFFER, blockSize, NULL, GL_DYNAMIC_DRAW ); // orphan buffer
+
+ void* mapped = glMapBufferRange(GL_UNIFORM_BUFFER, 0, blockSize, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT );
+ memcpy(mapped, blockBuffer, blockSize);
+ glUnmapBuffer(GL_UNIFORM_BUFFER);
+
+ graphics->DrawElementsInstanced(GL_TRIANGLES, probe_model->faces.size(), GL_UNSIGNED_INT, 0, num_to_render);
+ }
+
+ OG_FREE(blockBuffer);
+ graphics->ResetVertexAttribArrays();
+ }
+ }
+
+ if(tet_mesh_needs_update){
+ UpdateTetMesh(bw);
+ }
+}
diff --git a/Source/Graphics/lightprobecollection.hpp b/Source/Graphics/lightprobecollection.hpp
new file mode 100644
index 00000000..99a582de
--- /dev/null
+++ b/Source/Graphics/lightprobecollection.hpp
@@ -0,0 +1,109 @@
+//-----------------------------------------------------------------------------
+// Name: lightprobecollection.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+#include <Asset/Asset/texture.h>
+#include <Graphics/model.h>
+
+#include <opengl.h>
+
+#include <vector>
+#include <queue>
+
+struct LightProbe {
+ int id;
+ vec3 pos;
+ bool negative;
+ vec3 ambient_cube_color[6];
+ vec3 ambient_cube_color_buf[6]; // Used for calculating second bounce
+};
+
+struct TetMesh {
+ std::vector<vec3> points;
+ std::vector<int> tet_points;
+ std::vector<int> neighbors;
+ std::vector<int> point_id;
+ int display_id;
+};
+
+struct LightProbeUpdateEntry {
+ int id;
+ int pass; // 0 for first bounce, 1 for second bounce
+};
+
+struct GridLookup {
+ static const int kMaxGridCells = 1000000;
+ vec3 bounds[2];
+ int subdivisions[3];
+ std::vector<unsigned> cell_tet; // What tetrahedron is closest to the center of each cell
+};
+
+class BulletWorld;
+
+class LightProbeCollection {
+public:
+ LightProbeCollection();
+ ~LightProbeCollection();
+ int AddProbe(const vec3& pos, bool negative, const float *coeff = NULL);
+ bool MoveProbe(int id, const vec3& pos);
+ bool SetNegative(int id, bool negative);
+ bool DeleteProbe(int id);
+ void Draw(BulletWorld& bw);
+ void Init();
+ void Dispose();
+ int ShaderNumLightProbes();
+ int ShaderNumTetrahedra();
+ LightProbe* GetNextProbeToProcess();
+ // Returns id of tetrahedron containing position,
+ // and fills interpolated ambient cube color
+ int GetTetrahedron(const vec3& position, vec3 ambient_cube_color[], int best_guess);
+ GLuint cube_map_fbo;
+ TextureRef cube_map;
+ TextureRef ambient_3d_tex;
+ std::vector<LightProbe> light_probes;
+ TetMesh tet_mesh;
+ LightProbe* GetProbeFromID(int id);
+ int light_probe_texture_buffer_id;
+ int light_probe_buffer_object_id;
+ int grid_texture_buffer_id;
+ int grid_buffer_object_id;
+ void UpdateTextureBuffer(BulletWorld& bw);
+ std::queue<LightProbeUpdateEntry> to_process;
+ GridLookup grid_lookup;
+ bool tet_mesh_viz_enabled;
+ bool show_probes;
+ bool show_probes_through_walls;
+ bool probe_lighting_enabled;
+ bool light_volume_enabled;
+ int probe_model_id;
+private:
+ bool tet_mesh_needs_update;
+ int next_id;
+ void UpdateTetMesh(BulletWorld& bw);
+};
+
+static const bool kLightProbeEnabled = true;
+static const unsigned int kLightProbeNumCoeffs = 18;
+extern bool kLightProbe2pass;
diff --git a/Source/Graphics/lipsyncsystem.cpp b/Source/Graphics/lipsyncsystem.cpp
new file mode 100644
index 00000000..c535f553
--- /dev/null
+++ b/Source/Graphics/lipsyncsystem.cpp
@@ -0,0 +1,79 @@
+//-----------------------------------------------------------------------------
+// Name: lipsyncsystem.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "lipsyncsystem.h"
+
+#include <Internal/filesystem.h>
+#include <Compat/fileio.h>
+#include <Internal/error.h>
+
+#include <iostream>
+
+LipSyncSystem::LipSyncSystem()
+{
+ const char* rel_path = "Data/Sounds/voice/phonemes.txt";
+ char abs_path[kPathSize];
+ FindFilePath(rel_path, abs_path, kPathSize, kDataPaths | kModPaths);
+ std::ifstream file;
+ my_ifstream_open(file, abs_path);
+ if(file.fail()){
+ FatalError("Error", "Could not open phonemes file: %s", rel_path);
+ }
+
+ // Create 3 maps that map a phoneme string to a viseme id
+ // for 9, 12 or 17 visemes
+ phn2vis.resize(3);
+ vis2phn.resize(3);
+ char the_char;
+ std::string label;
+ int level = 0;
+ int group[3] = {-1,-1,-1};
+ while(!file.eof()){
+ file.get(the_char);
+ if(the_char == '\n' || the_char == ' '){
+ continue;
+ }
+ if(the_char == '['){
+ ++group[level];
+ ++level;
+ continue;
+ }
+ if(the_char == ']' || the_char == ','){
+ if(!label.empty()){
+ for(int i=0; i<3; ++i){
+ phn2vis[i][label]=group[i];
+ vis2phn[i][group[i]]=label;
+ }
+ label.clear();
+ }
+ if(the_char == ']'){
+ --level;
+ }
+ continue;
+ }
+ if(the_char == '\r'){
+ continue;
+ }
+ label.push_back(the_char);
+ }
+}
diff --git a/Source/Graphics/lipsyncsystem.h b/Source/Graphics/lipsyncsystem.h
new file mode 100644
index 00000000..bd42257d
--- /dev/null
+++ b/Source/Graphics/lipsyncsystem.h
@@ -0,0 +1,37 @@
+//-----------------------------------------------------------------------------
+// Name: lipsyncsystem.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <vector>
+#include <string>
+#include <map>
+
+class LipSyncSystem
+{
+public:
+ LipSyncSystem();
+
+ std::vector<std::map<std::string, int> > phn2vis;
+ std::vector<std::map<int, std::string> > vis2phn;
+};
diff --git a/Source/Graphics/model.cpp b/Source/Graphics/model.cpp
new file mode 100644
index 00000000..1dbcfeaf
--- /dev/null
+++ b/Source/Graphics/model.cpp
@@ -0,0 +1,2504 @@
+//-----------------------------------------------------------------------------
+// Name: model.cpp
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: This class loads and displays models
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "model.h"
+
+#include <Graphics/graphics.h>
+#include <Graphics/shaders.h>
+#include <Graphics/drawbatch.h>
+
+#include <Internal/collisiondetection.h>
+#include <Internal/checksum.h>
+#include <Internal/common.h>
+#include <Internal/datemodified.h>
+#include <Internal/stopwatch.h>
+#include <Internal/error.h>
+#include <Internal/filesystem.h>
+#include <Internal/profiler.h>
+
+#include <Math/vec2.h>
+#include <Math/vec2math.h>
+#include <Math/vec3math.h>
+#include <Math/overgrowth_geometry.h>
+
+#include <Compat/filepath.h>
+#include <Asset/Asset/image_sampler.h>
+#include <Compat/fileio.h>
+#include <Logging/logdata.h>
+#include <Utility/assert.h>
+#include <Main/engine.h>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <cmath>
+#include <set>
+
+//-----------------------------------------------------------------------------
+//Functions
+//-----------------------------------------------------------------------------
+
+namespace {
+ void RearrangeVertices(Model& model, const std::vector<int> &new_order){
+ std::vector<float> old_vertices = model.vertices;
+ std::vector<float> old_normals = model.normals;
+ std::vector<float> old_tex_coords = model.tex_coords;
+ std::vector<float> old_tex_coords2 = model.tex_coords2;
+ std::vector<float> old_tangents = model.tangents;
+ std::vector<float> old_bitangents = model.bitangents;
+ std::vector<float> old_aux = model.aux;
+ std::vector<vec4> old_bone_weights = model.bone_weights;
+ std::vector<vec4> old_bone_ids = model.bone_ids;
+
+ for(unsigned i=0; i<new_order.size(); ++i){
+ model.vertices[i*3+0] = old_vertices[new_order[i]*3+0];
+ model.vertices[i*3+1] = old_vertices[new_order[i]*3+1];
+ model.vertices[i*3+2] = old_vertices[new_order[i]*3+2];
+ if(!model.normals.empty()){
+ model.normals[i*3+0] = old_normals[new_order[i]*3+0];
+ model.normals[i*3+1] = old_normals[new_order[i]*3+1];
+ model.normals[i*3+2] = old_normals[new_order[i]*3+2];
+ }
+ if(!model.tex_coords.empty()){
+ model.tex_coords[i*2+0] = old_tex_coords[new_order[i]*2+0];
+ model.tex_coords[i*2+1] = old_tex_coords[new_order[i]*2+1];
+ }
+ if(!model.tex_coords2.empty()){
+ model.tex_coords2[i*2+0] = old_tex_coords2[new_order[i]*2+0];
+ model.tex_coords2[i*2+1] = old_tex_coords2[new_order[i]*2+1];
+ }
+ if(!model.tangents.empty()){
+ model.tangents[i*3+0] = old_tangents[new_order[i]*3+0];
+ model.tangents[i*3+1] = old_tangents[new_order[i]*3+1];
+ model.tangents[i*3+2] = old_tangents[new_order[i]*3+2];
+ }
+ if(!model.bitangents.empty()){
+ model.bitangents[i*3+0] = old_bitangents[new_order[i]*3+0];
+ model.bitangents[i*3+1] = old_bitangents[new_order[i]*3+1];
+ model.bitangents[i*3+2] = old_bitangents[new_order[i]*3+2];
+ }
+ if(!model.aux.empty()){
+ model.aux[i*3+0] = old_aux[new_order[i]*3+0];
+ model.aux[i*3+1] = old_aux[new_order[i]*3+1];
+ model.aux[i*3+2] = old_aux[new_order[i]*3+2];
+ }
+ if(!model.bone_weights.empty()){
+ model.bone_weights[i] = old_bone_weights[new_order[i]];
+ }
+ if(!model.bone_ids.empty()){
+ model.bone_ids[i] = old_bone_ids[new_order[i]];
+ }
+ }
+ model.ResizeVertices(new_order.size());
+ }
+} //namespace ""
+
+//Initialize model
+Model::Model()
+:vbo_loaded(false),
+ vbo_enabled(false),
+ texel_density(1.0f),
+ average_triangle_edge_length(1.0f),
+ path("DEFAULT")
+{
+}
+
+Model::Model( const Model &copy )
+{
+ (*this)=copy;
+}
+
+Model& Model::operator=( const Model &copy )
+{
+ vbo_loaded = false;
+ vbo_enabled = false;
+
+ vertices = copy.vertices;
+ normals = copy.normals;
+ tangents = copy.tangents;
+ bitangents = copy.bitangents;
+ face_normals = copy.face_normals;
+ tex_coords = copy.tex_coords;
+ tex_coords2 = copy.tex_coords2;
+ aux = copy.aux;
+ faces = copy.faces;
+ bone_weights = copy.bone_weights;
+ bone_ids = copy.bone_ids;
+ checksum = copy.checksum;
+
+ min_coords = copy.min_coords;
+ max_coords = copy.max_coords;
+ center_coords = copy.center_coords;
+ old_center = copy.old_center;
+
+ bounding_sphere_origin = copy.bounding_sphere_origin;
+ bounding_sphere_radius = copy.bounding_sphere_radius;
+
+ texel_density = copy.texel_density;
+
+ precollapse_vert_reorder = copy.precollapse_vert_reorder;
+ optimize_vert_reorder = copy.optimize_vert_reorder;
+
+ path = copy.path;
+
+ return (*this);
+}
+
+
+void Model::WriteToFile(FILE *file) {
+ int count;
+ count = vertices.size()/3;
+ fwrite(&count, sizeof(int), 1, file);
+ fwrite(&vertices.front(), sizeof(GLfloat), count*3, file);
+ fwrite(&normals.front(), sizeof(GLfloat), count*3, file);
+ fwrite(&tangents.front(), sizeof(GLfloat), count*3, file);
+ fwrite(&bitangents.front(), sizeof(GLfloat), count*3, file);
+ fwrite(&tex_coords.front(), sizeof(GLfloat), count*2, file);
+
+ count = faces.size()/3;
+ fwrite(&count, sizeof(int), 1, file);
+ fwrite(&faces.front(), sizeof(GLfloat), count*3, file);
+ fwrite(&face_normals.front(), sizeof(vec3), count, file);
+
+ fwrite(&precollapse_num_vertices, sizeof(int), 1, file);
+ count = precollapse_vert_reorder.size();
+ fwrite(&count, sizeof(int), 1, file);
+ fwrite(&precollapse_vert_reorder.front(), sizeof(int), count, file);
+ count = optimize_vert_reorder.size();
+ fwrite(&count, sizeof(int), 1, file);
+ fwrite(&optimize_vert_reorder.front(), sizeof(int), count, file);
+
+ count = tex_coords2.size()/2;
+ fwrite(&count, sizeof(int), 1, file);
+ if (count>0) {
+ fwrite(&tex_coords2.front(), sizeof(GLfloat), count*2, file);
+ }
+
+ fwrite(&min_coords, sizeof(vec3), 1, file);
+ fwrite(&max_coords, sizeof(vec3), 1, file);
+ fwrite(&center_coords, sizeof(vec3), 1, file);
+ fwrite(&old_center, sizeof(vec3), 1, file);
+ fwrite(&bounding_sphere_origin, sizeof(vec3), 1, file);
+ fwrite(&bounding_sphere_radius, sizeof(float), 1, file);
+
+ fwrite(&texel_density, sizeof(float), 1, file);
+ fwrite(&average_triangle_edge_length, sizeof(float), 1, file);
+}
+
+void Model::ReadFromFile(FILE *file)
+{
+ int count;
+ fread(&count, sizeof(int), 1, file);
+ ResizeVertices(count);
+ fread(&vertices.front(), sizeof(GLfloat), count*3, file);
+ fread(&normals.front(), sizeof(GLfloat), count*3, file);
+ fread(&tangents.front(), sizeof(GLfloat), count*3, file);
+ fread(&bitangents.front(), sizeof(GLfloat), count*3, file);
+ fread(&tex_coords.front(), sizeof(GLfloat), count*2, file);
+ fread(&count, sizeof(int), 1, file);
+ ResizeFaces(count);
+ fread(&faces.front(), sizeof(GLfloat), count*3, file);
+ fread(&face_normals.front(), sizeof(vec3), count, file);
+
+ fread(&precollapse_num_vertices, sizeof(int), 1, file);
+ fread(&count, sizeof(int), 1, file);
+ precollapse_vert_reorder.resize(count);
+ fread(&precollapse_vert_reorder.front(), sizeof(int), count, file);
+
+ fread(&count, sizeof(int), 1, file);
+ optimize_vert_reorder.resize(count);
+ fread(&optimize_vert_reorder.front(), sizeof(int), count, file);
+
+ fread(&count, sizeof(int), 1, file);
+ if (count > 0) {
+ tex_coords2.resize(count*2);
+ fread(&tex_coords2.front(), sizeof(GLfloat), count*2, file);
+ }
+
+ fread(&min_coords, sizeof(vec3), 1, file);
+ fread(&max_coords, sizeof(vec3), 1, file);
+ fread(&center_coords, sizeof(vec3), 1, file);
+ fread(&old_center, sizeof(vec3), 1, file);
+ fread(&bounding_sphere_origin, sizeof(vec3), 1, file);
+ fread(&bounding_sphere_radius, sizeof(float), 1, file);
+
+ fread(&texel_density, sizeof(float), 1, file);
+ fread(&average_triangle_edge_length, sizeof(float), 1, file);
+}
+
+void Model::Dispose() {
+ vertices.clear();
+ normals.clear();
+ tangents.clear();
+ bitangents.clear();
+ face_normals.clear();
+ tex_coords.clear();
+ tex_coords2.clear();
+ aux.clear();
+ faces.clear();
+
+ bone_weights.clear();
+ bone_ids.clear();
+ transform_vec.clear();
+ tex_transform_vec.clear();
+ morph_transform_vec.clear();
+
+ image_samplers.clear();
+
+ optimize_vert_reorder.clear();
+ precollapse_vert_reorder.clear();
+
+ if(vbo_enabled&&vbo_loaded)
+ {
+ VBO_vertices.Dispose();
+ VBO_tex_coords.Dispose();
+ VBO_normals.Dispose();
+ VBO_faces.Dispose();
+ VBO_tangents.Dispose();
+ VBO_bitangents.Dispose();
+ VBO_tex_coords2.Dispose();
+ VBO_aux.Dispose();
+ }
+ vbo_loaded = false;
+}
+
+//Dispose of model
+Model::~Model() {
+ Dispose();
+}
+
+float StaticCalcTexelDensity(const std::vector<float> &vertices, const std::vector<float> &tex_coords, const std::vector<unsigned> &faces){
+ float total_tex = 0.0f;
+ float total_vert = 0.0f;
+ int next;
+ for (int i=0, len=faces.size()/3; i<len; i++) {
+ int face_vertex_index[3];
+ const float *vert[3];
+ const float *tc[3];
+ for(int j=0; j<3; ++j) {
+ face_vertex_index[j] = faces[i*3+j];
+ vert[j] = &vertices[face_vertex_index[j]*3];
+ tc[j] = &tex_coords[face_vertex_index[j]*2];
+ }
+ for(int j=0; j<3; j++) {
+ next = (j+1)%3;
+ float tex_distance = sqrtf( square(tc[j][0] - tc[next][0]) +
+ square(tc[j][1] - tc[next][1]));
+ float vert_distance = sqrtf( square(vert[j][0] - vert[next][0]) +
+ square(vert[j][1] - vert[next][1]) +
+ square(vert[j][2] - vert[next][2]));
+ if(vert_distance != 0.0f){
+ total_tex += tex_distance;
+ total_vert += vert_distance;
+ }
+ }
+ }
+ return (total_tex / total_vert);
+}
+
+void Model::CalcTexelDensity() {
+ texel_density = StaticCalcTexelDensity(vertices, tex_coords, faces);
+}
+
+struct ObjFileStats {
+ int num_normal;
+ int num_tex;
+ int num_vert;
+ int num_face;
+ int objects; //Number of objects specified in the file, for this game this should only be 1
+};
+
+bool GetObjFileStats(std::string rel_path, ObjFileStats &stats, bool file_necessary) {
+ stats.num_normal = 0;
+ stats.num_tex = 0;
+ stats.num_vert = 0;
+ stats.num_face = 0;
+ stats.objects = 0;
+
+ char abs_path[kPathSize];
+ FindFilePath(rel_path.c_str(), abs_path, kPathSize, kAnyPath, file_necessary);
+ FILE *fp = my_fopen(abs_path,"r");
+
+ if(!fp){
+ if(file_necessary) {
+ FatalError("Error", "Could not open file fs \"%s\"", rel_path.c_str());
+ } else {
+ return false;
+ }
+ }
+
+ char buffer[256];
+ while(!feof(fp))
+ {
+ memset(buffer,0,200);
+ fgets(buffer,256,fp);
+
+ if( strncmp("vn ",buffer,3) == 0 )
+ ++stats.num_normal;
+ else if( strncmp("vt ",buffer,3) == 0 )
+ ++stats.num_tex;
+ else if( strncmp("v ",buffer,2) == 0 )
+ ++stats.num_vert;
+ else if( strncmp("f ",buffer,2) == 0 )
+ ++stats.num_face;
+ else if( strncmp("o ",buffer,2) == 0 )
+ ++stats.objects;
+ }
+
+ //There is always at least one object in a file.
+ if( stats.objects == 0 )
+ stats.objects = 1;
+
+ fclose(fp);
+
+ return true;
+}
+
+struct TempModel {
+ std::vector<GLfloat> vertices;
+ std::vector<GLfloat> tangents;
+ std::vector<GLfloat> bitangents;
+ std::vector<GLfloat> normals;
+ std::vector<GLfloat> tex_coords;
+ std::vector<GLfloat> tex_coords2;
+ std::vector<GLuint> vert_indices;
+ std::vector<GLuint> tex_indices;
+ std::vector<GLuint> tex_indices2;
+ std::vector<GLuint> norm_indices;
+ unsigned int vc,nc,tc,fc;
+};
+
+void LoadTempModel(std::string rel_path, TempModel &temp, const ObjFileStats &obj_stats) {
+ temp.vertices.resize(obj_stats.num_vert*3);
+ temp.normals.resize(obj_stats.num_normal*3);
+ temp.tangents.resize(obj_stats.num_tex*3);
+ temp.bitangents.resize(obj_stats.num_tex*3);
+ temp.tex_coords.resize(obj_stats.num_tex*2);
+ temp.tex_coords2.resize(0);
+ temp.vert_indices.resize(obj_stats.num_face*6);
+ temp.tex_indices.resize(obj_stats.num_face*6);
+ temp.norm_indices.resize(obj_stats.num_face*6);
+
+ char abs_path[kPathSize];
+ FindFilePath(rel_path.c_str(), abs_path, kPathSize, kAnyPath);
+ FILE *fp = my_fopen(abs_path,"r");
+
+ int num_read;
+
+ temp.fc = 0;
+ temp.nc = 0;
+ temp.tc = 0;
+ temp.vc = 0;
+
+ char buffer[256];
+ while(!feof(fp))
+ {
+ memset(buffer,0,255);
+ fgets(buffer,256,fp);
+
+ if( strncmp("vn ",buffer,3) == 0 )
+ {
+ sscanf((buffer+2),"%f%f%f",
+ &temp.normals[temp.nc*3+0],
+ &temp.normals[temp.nc*3+1],
+ &temp.normals[temp.nc*3+2]);
+ ++temp.nc;
+ }
+ else if( strncmp("vt ",buffer,3) == 0 )
+ {
+ sscanf((buffer+2),"%f%f",
+ &temp.tex_coords[temp.tc*2+0],
+ &temp.tex_coords[temp.tc*2+1]);
+ ++temp.tc;
+ }
+ else if( strncmp("v ",buffer,2) == 0 )
+ {
+ sscanf((buffer+1),"%f%f%f",
+ &temp.vertices[temp.vc*3+0],
+ &temp.vertices[temp.vc*3+1],
+ &temp.vertices[temp.vc*3+2]);
+ ++temp.vc;
+ }
+ else if( strncmp("f ",buffer,2) == 0 )
+ {
+ if(obj_stats.num_tex&&obj_stats.num_normal) {
+ num_read = sscanf(buffer+1,"%u/%u/%u %u/%u/%u %u/%u/%u %u/%u/%u",
+ &temp.vert_indices[temp.fc*3+0],
+ &temp.tex_indices[temp.fc*3+0],
+ &temp.norm_indices[temp.fc*3+0],
+
+ &temp.vert_indices[temp.fc*3+1],
+ &temp.tex_indices[temp.fc*3+1],
+ &temp.norm_indices[temp.fc*3+1],
+
+ &temp.vert_indices[temp.fc*3+2],
+ &temp.tex_indices[temp.fc*3+2],
+ &temp.norm_indices[temp.fc*3+2],
+
+ &temp.vert_indices[temp.fc*3+3],
+ &temp.tex_indices[temp.fc*3+3],
+ &temp.norm_indices[temp.fc*3+3]);
+ if(num_read == 12) {
+ temp.vert_indices[temp.fc*3+4] = temp.vert_indices[temp.fc*3+0];
+ temp.tex_indices[temp.fc*3+4] = temp.tex_indices[temp.fc*3+0];
+ temp.norm_indices[temp.fc*3+4] = temp.norm_indices[temp.fc*3+0];
+
+ temp.vert_indices[temp.fc*3+5] = temp.vert_indices[temp.fc*3+2];
+ temp.tex_indices[temp.fc*3+5] = temp.tex_indices[temp.fc*3+2];
+ temp.norm_indices[temp.fc*3+5] = temp.norm_indices[temp.fc*3+2];
+
+ temp.fc++;
+ }
+ } else if (obj_stats.num_normal) {
+ num_read = sscanf(buffer+1,"%u//%u %u//%u %u//%u %u//%u",
+ &temp.vert_indices[temp.fc*3+0],
+ &temp.norm_indices[temp.fc*3+0],
+
+ &temp.vert_indices[temp.fc*3+1],
+ &temp.norm_indices[temp.fc*3+1],
+
+ &temp.vert_indices[temp.fc*3+2],
+ &temp.norm_indices[temp.fc*3+2],
+
+ &temp.vert_indices[temp.fc*3+3],
+ &temp.norm_indices[temp.fc*3+3]);
+
+ if(num_read == 8) {
+ temp.vert_indices[temp.fc*3+4] = temp.vert_indices[temp.fc*3+0];
+ temp.norm_indices[temp.fc*3+4] = temp.norm_indices[temp.fc*3+0];
+
+ temp.vert_indices[temp.fc*3+5] = temp.vert_indices[temp.fc*3+2];
+ temp.norm_indices[temp.fc*3+5] = temp.norm_indices[temp.fc*3+2];
+
+ temp.fc++;
+ }
+ } else if (obj_stats.num_tex) {
+ num_read = sscanf(buffer+1,"%u/%u %u/%u %u/%u %u/%u",
+ &temp.vert_indices[temp.fc*3+0],
+ &temp.tex_indices[temp.fc*3+0],
+
+ &temp.vert_indices[temp.fc*3+1],
+ &temp.tex_indices[temp.fc*3+1],
+
+ &temp.vert_indices[temp.fc*3+2],
+ &temp.tex_indices[temp.fc*3+2],
+
+ &temp.vert_indices[temp.fc*3+3],
+ &temp.tex_indices[temp.fc*3+3]);
+
+ if(num_read == 8) {
+ temp.vert_indices[temp.fc*3+4] = temp.vert_indices[temp.fc*3+0];
+ temp.tex_indices[temp.fc*3+4] = temp.tex_indices[temp.fc*3+0];
+
+ temp.vert_indices[temp.fc*3+5] = temp.vert_indices[temp.fc*3+2];
+ temp.tex_indices[temp.fc*3+5] = temp.tex_indices[temp.fc*3+2];
+
+ temp.fc++;
+ }
+ } else {
+ num_read = sscanf(buffer+1,"%u %u %u %u",
+ &temp.vert_indices[temp.fc*3+0],
+ &temp.vert_indices[temp.fc*3+1],
+ &temp.vert_indices[temp.fc*3+2],
+ &temp.vert_indices[temp.fc*3+3]);
+
+ if(num_read == 4) {
+ temp.vert_indices[temp.fc*3+4] = temp.vert_indices[temp.fc*3+0];
+ temp.vert_indices[temp.fc*3+5] = temp.vert_indices[temp.fc*3+2];
+ temp.fc++;
+ }
+ }
+ temp.fc++;
+ }
+ }
+ fclose(fp);
+
+ bool vert_index_error = false;
+ bool tex_index_error = false;
+ bool norm_index_error = false;
+
+ for (unsigned j = 0;j<temp.fc*3;j++){
+
+ if( temp.vert_indices[j] > 0 )
+ temp.vert_indices[j]--;
+
+ if( temp.tex_indices[j] > 0 )
+ temp.tex_indices[j]--;
+
+ if( temp.norm_indices[j] > 0 )
+ temp.norm_indices[j]--;
+
+
+ if( temp.vert_indices[j] > temp.vertices.size()/3 && temp.vert_indices[j] != 0)
+ {
+ temp.vert_indices[j] = 0; //This is better than NaN data or a segfault.
+ vert_index_error = true;
+ }
+
+ if( temp.tex_indices[j] > temp.tex_coords.size()/2 && temp.tex_indices[j] != 0)
+ {
+ temp.tex_indices[j] = 0;
+ tex_index_error = true;
+ }
+
+ if( temp.norm_indices[j] > temp.normals.size()/3 && temp.norm_indices[j] != 0 )
+ {
+ temp.norm_indices[j] = 0;
+ norm_index_error = true;
+ }
+ }
+
+ if( vert_index_error )
+ {
+ LOGE << "Error when loading: " << rel_path << " face (v) indices are higher than parsed data, file is probably corrupt." << std::endl;
+ }
+
+ if( tex_index_error )
+ {
+ LOGE << "Error when loading: " << rel_path << " face (vt) indices are higher than parsed data, file is probably corrupt." << std::endl;
+ }
+
+ if( norm_index_error )
+ {
+ LOGE << "Error when loading: " << rel_path << " face (vn) indices are higher than parsed data, file is probably corrupt." << std::endl;
+ }
+}
+
+void LoadSecondUVs(std::string name, TempModel &temp, const ObjFileStats &obj_stats) {
+ temp.tex_coords2.resize(obj_stats.num_tex*2);
+ temp.tex_indices2.resize(obj_stats.num_face*6);
+
+ char abs_path[kPathSize];
+ FindFilePath(name.c_str(), abs_path, kPathSize, kDataPaths|kModPaths);
+ FILE *fp = my_fopen(abs_path,"r");
+
+ int tc2 = 0;
+ int fc2 = 0;
+ unsigned int trash;
+ int num_read;
+
+ char buffer[256];
+ while(!feof(fp))
+ {
+ memset(buffer,0,255);
+ fgets(buffer,256,fp);
+
+ if( strncmp("vt ",buffer,3) == 0 ) {
+ sscanf((buffer+2),"%f%f",
+ &temp.tex_coords2[tc2*2+0],
+ &temp.tex_coords2[tc2*2+1]);
+ ++tc2;
+ } else if( strncmp("f ",buffer,2) == 0 ) {
+ if(obj_stats.num_tex&&obj_stats.num_normal) {
+ num_read = sscanf(buffer+1,"%u/%u/%u %u/%u/%u %u/%u/%u %u/%u/%u",
+ &trash,
+ &temp.tex_indices2[fc2*3+0],
+ &trash,
+
+ &trash,
+ &temp.tex_indices2[fc2*3+1],
+ &trash,
+
+ &trash,
+ &temp.tex_indices2[fc2*3+2],
+ &trash,
+
+ &trash,
+ &temp.tex_indices2[fc2*3+3],
+ &trash);
+ if(num_read == 12) {
+ temp.tex_indices2[fc2*3+4] = temp.tex_indices2[fc2*3+0];
+
+ temp.tex_indices2[fc2*3+5] = temp.tex_indices2[fc2*3+2];
+
+ ++fc2;
+ }
+ } else if (obj_stats.num_tex) {
+ num_read = sscanf(buffer+1,"%u/%u %u/%u %u/%u %u/%u",
+ &trash,
+ &temp.tex_indices2[fc2*3+0],
+
+ &trash,
+ &temp.tex_indices2[fc2*3+1],
+
+ &trash,
+ &temp.tex_indices2[fc2*3+2],
+
+ &trash,
+ &temp.tex_indices2[fc2*3+3]);
+
+ if(num_read == 8) {
+ temp.tex_indices2[fc2*3+4] = temp.tex_indices2[fc2*3+0];
+
+ temp.tex_indices2[fc2*3+5] = temp.tex_indices2[fc2*3+2];
+
+ ++fc2;
+ }
+ }
+ ++fc2;
+ }
+ }
+
+ for (int j = 0;j<fc2*3;j++){
+ temp.tex_indices2[j]--;
+ }
+
+ fclose(fp);
+}
+
+void Model::SaveObj(std::string name) {
+ std::ofstream file;
+ char abs_path[kPathSize];
+ FormatString(abs_path, kPathSize, "%s%s", GetWritePath(modsource_).c_str(), name.c_str());
+ my_ofstream_open(file, abs_path);
+
+ unsigned index = 0;
+ for(int i=0, len=vertices.size()/3; i<len; ++i){
+ file << "v ";
+ file << vertices[index++] << " ";
+ file << vertices[index++] << " ";
+ file << vertices[index++] << "\n";
+ }
+
+ if(!tex_coords.empty()){
+ index = 0;
+ for(int i=0, len=vertices.size()/3; i<len; ++i){
+ file << "vt ";
+ file << tex_coords[index++] << " ";
+ file << tex_coords[index++] << "\n";
+ }
+ }
+
+ if(!normals.empty()){
+ index = 0;
+ for(int i=0, len=vertices.size()/3; i<len; ++i){
+ file << "vn ";
+ file << normals[index++] << " ";
+ file << normals[index++] << " ";
+ file << normals[index++] << "\n";
+ }
+ }
+
+ index = 0;
+
+ if(tex_coords.empty() && normals.empty()){
+ for(int i=0, len=faces.size()/3; i<len; ++i){
+ file << "f ";
+ file << faces[index++]+1 << " ";
+ file << faces[index++]+1 << " ";
+ file << faces[index++]+1 << "\n";
+ }
+ } else if(tex_coords.empty() || normals.empty()){
+ for(int i=0, len=faces.size()/3; i<len; ++i){
+ file << "f ";
+ file << faces[index]+1 << "/" << faces[index]+1 << " ";
+ index++;
+ file << faces[index]+1 << "/" << faces[index]+1 << " ";
+ index++;
+ file << faces[index]+1 << "/" << faces[index]+1 << "\n";
+ index++;
+ }
+ } else {
+ for(int i=0, len=faces.size()/3; i<len; ++i){
+ file << "f ";
+ file << faces[index]+1 << "/" << faces[index]+1 << "/" << faces[index]+1 << " ";
+ index++;
+ file << faces[index]+1 << "/" << faces[index]+1 << "/" << faces[index]+1 << " ";
+ index++;
+ file << faces[index]+1 << "/" << faces[index]+1 << "/" << faces[index]+1 << "\n";
+ index++;
+ }
+ }
+
+ file.close();
+}
+
+const int Model::ERROR_MORE_THAN_ONE_OBJECT = 1;
+
+int Model::SimpleLoadTriangleCutObj(const std::string &name_to_load) {
+ ObjFileStats obj_stats;
+ GetObjFileStats(name_to_load, obj_stats, true);
+
+ TempModel temp;
+ LoadTempModel(name_to_load, temp, obj_stats);
+
+ //Check if the obj file has more than one model object packed into it.
+ if( obj_stats.objects == 1 )
+ {
+ int num_faces = temp.fc;
+ int num_vertices = num_faces*3;
+
+ vertices.resize(num_vertices*3);
+ if(obj_stats.num_normal){
+ normals.resize(num_vertices*3);
+ } else {
+ normals.clear();
+ }
+ tangents.resize(num_vertices*3,0.0f);
+ bitangents.resize(num_vertices*3,0.0f);
+ tex_coords.resize(num_vertices*2);
+ faces.resize(num_faces*3);
+ face_normals.resize(num_faces);
+
+ for (int i=0, len=faces.size(); i<len; i++){
+ faces[i] = i;
+
+ vertices[i*3+0] = temp.vertices[temp.vert_indices[i]*3+0];
+ vertices[i*3+1] = temp.vertices[temp.vert_indices[i]*3+1];
+ vertices[i*3+2] = temp.vertices[temp.vert_indices[i]*3+2];
+
+ if(obj_stats.num_normal){
+ normals[i*3+0] = temp.normals[temp.norm_indices[i]*3+0];
+ normals[i*3+1] = temp.normals[temp.norm_indices[i]*3+1];
+ normals[i*3+2] = temp.normals[temp.norm_indices[i]*3+2];
+ }
+
+ if(obj_stats.num_tex){
+ tangents[i*3+0] = temp.tangents[temp.tex_indices[i]*3+0];
+ tangents[i*3+1] = temp.tangents[temp.tex_indices[i]*3+1];
+ tangents[i*3+2] = temp.tangents[temp.tex_indices[i]*3+2];
+
+ bitangents[i*3+0] = temp.bitangents[temp.tex_indices[i]*3+0];
+ bitangents[i*3+1] = temp.bitangents[temp.tex_indices[i]*3+1];
+ bitangents[i*3+2] = temp.bitangents[temp.tex_indices[i]*3+2];
+
+ tex_coords[i*2+0] = temp.tex_coords[temp.tex_indices[i]*2+0];
+ tex_coords[i*2+1] = temp.tex_coords[temp.tex_indices[i]*2+1];
+ }
+ }
+
+ tex_coords2.resize(num_vertices*2);
+ for (int i = 0;i<num_vertices;i++){
+ tex_coords2[i*2+0] = tex_coords[i*2+0];
+ tex_coords2[i*2+1] = tex_coords[i*2+1];
+ }
+ }
+ else
+ {
+ return ERROR_MORE_THAN_ONE_OBJECT;
+ }
+
+ return 0;
+}
+
+const char* Model::GetLoadErrorString(int err)
+{
+ switch( err )
+ {
+ case 0:
+ return "No error";
+ case ERROR_MORE_THAN_ONE_OBJECT:
+ return "More than one object in .obj file, incompatible with loader.";
+ default:
+ return "Unknown error";
+ }
+}
+
+void CopyTexCoords2( Model &a, const Model& b ) {
+ a.tex_coords2.resize(a.vertices.size()/3*2);
+ std::map<float,std::map<float,std::map<float,
+ std::map<float,std::map<float,std::map<float,
+ std::vector<int> > > > > > > dup_map;
+ unsigned vert_index = 0;
+ unsigned normal_index = 0;
+ vec3 n_i;
+ for(int i=0, len=a.vertices.size()/3; i<len; ++i){
+ a.tex_coords2[i*2+0] = 0.0f;
+ a.tex_coords2[i*2+1] = 0.0f;
+ if(!a.normals.empty() && !b.normals.empty()){
+ n_i = vec3(a.normals[normal_index+0],
+ a.normals[normal_index+1],
+ a.normals[normal_index+2]);
+ }
+ std::vector<int> &indices = dup_map[floorf(a.vertices[vert_index+0]*100.0f)]
+ [floorf(a.vertices[vert_index+1]*100.0f)]
+ [floorf(a.vertices[vert_index+2]*100.0f)]
+ [floorf(n_i[0]*100.0f)]
+ [floorf(n_i[1]*100.0f)]
+ [floorf(n_i[2]*100.0f)];
+ normal_index += 3;
+ vert_index += 3;
+ indices.push_back(i);
+ }
+
+ vert_index = 0;
+ normal_index = 0;
+ for(int i=0, len=b.vertices.size()/3; i<len; ++i){
+ if(!a.normals.empty() && !b.normals.empty()){
+ n_i = vec3(b.normals[normal_index+0],
+ b.normals[normal_index+1],
+ b.normals[normal_index+2]);
+ }
+ std::vector<int> &indices = dup_map[floorf(b.vertices[vert_index+0]*100.0f)]
+ [floorf(b.vertices[vert_index+1]*100.0f)]
+ [floorf(b.vertices[vert_index+2]*100.0f)]
+ [floorf(n_i[0]*100.0f)]
+ [floorf(n_i[1]*100.0f)]
+ [floorf(n_i[2]*100.0f)];
+ normal_index += 3;
+ vert_index += 3;
+ for(unsigned j=0; j<indices.size(); ++j){
+ a.tex_coords2[indices[j]*2+0] = b.tex_coords[i*2+0];
+ a.tex_coords2[indices[j]*2+1] = b.tex_coords[i*2+1];
+ }
+ }
+
+ dup_map.clear();
+ vert_index = 0;
+ normal_index = 0;
+ for(int i=0, len=a.vertices.size()/3; i<len; ++i){
+ if(!a.normals.empty() && !b.normals.empty()){
+ n_i = vec3(a.normals[normal_index+0],
+ a.normals[normal_index+1],
+ a.normals[normal_index+2]);
+ }
+ std::vector<int> &indices = dup_map[floorf(a.vertices[vert_index+0]*10.0f+0.5f)]
+ [floorf(a.vertices[vert_index+1]*10.0f+0.5f)]
+ [floorf(a.vertices[vert_index+2]*10.0f+0.5f)]
+ [floorf(n_i[0]*10.0f+0.5f)]
+ [floorf(n_i[1]*10.0f+0.5f)]
+ [floorf(n_i[2]*10.0f+0.5f)];
+ normal_index += 3;
+ vert_index += 3;
+ indices.push_back(i);
+ }
+
+ vert_index = 0;
+ normal_index = 0;
+ for(int i=0, len=b.vertices.size()/3; i<len; ++i){
+ if(!a.normals.empty() && !b.normals.empty()){
+ n_i = vec3(b.normals[normal_index+0],
+ b.normals[normal_index+1],
+ b.normals[normal_index+2]);
+ }
+ std::vector<int> &indices = dup_map[floorf(b.vertices[vert_index+0]*10.0f+0.5f)]
+ [floorf(b.vertices[vert_index+1]*10.0f+0.5f)]
+ [floorf(b.vertices[vert_index+2]*10.0f+0.5f)]
+ [floorf(n_i[0]*10.0f+0.5f)]
+ [floorf(n_i[1]*10.0f+0.5f)]
+ [floorf(n_i[2]*10.0f+0.5f)];
+ normal_index += 3;
+ vert_index += 3;
+ for(unsigned j=0; j<indices.size(); ++j){
+ if(a.tex_coords2[indices[j]*2+0] == 0.0f &&
+ a.tex_coords2[indices[j]*2+1] == 0.0f)
+ {
+ a.tex_coords2[indices[j]*2+0] = b.tex_coords[i*2+0];
+ a.tex_coords2[indices[j]*2+1] = b.tex_coords[i*2+1];
+ }
+ }
+ }
+
+ dup_map.clear();
+ vert_index = 0;
+ normal_index = 0;
+ for(int i=0, len=a.vertices.size()/3; i<len; ++i){
+ if(!a.normals.empty() && !b.normals.empty()){
+ n_i = vec3(a.normals[normal_index+0],
+ a.normals[normal_index+1],
+ a.normals[normal_index+2]);
+ }
+ std::vector<int> &indices = dup_map[floorf(a.vertices[vert_index+0]*1.0f+0.5f)]
+ [floorf(a.vertices[vert_index+1]*1.0f+0.5f)]
+ [floorf(a.vertices[vert_index+2]*1.0f+0.5f)]
+ [floorf(n_i[0]*1.0f+0.5f)]
+ [floorf(n_i[1]*1.0f+0.5f)]
+ [floorf(n_i[2]*1.0f+0.5f)];
+ normal_index += 3;
+ vert_index += 3;
+ indices.push_back(i);
+ }
+
+ vert_index = 0;
+ normal_index = 0;
+ for(int i=0, len=b.vertices.size()/3; i<len; ++i){
+ if(!a.normals.empty() && !b.normals.empty()){
+ n_i = vec3(b.normals[normal_index+0],
+ b.normals[normal_index+1],
+ b.normals[normal_index+2]);
+ }
+ std::vector<int> &indices = dup_map[floorf(b.vertices[vert_index+0]*1.0f+0.5f)]
+ [floorf(b.vertices[vert_index+1]*1.0f+0.5f)]
+ [floorf(b.vertices[vert_index+2]*1.0f+0.5f)]
+ [floorf(n_i[0]*1.0f+0.5f)]
+ [floorf(n_i[1]*1.0f+0.5f)]
+ [floorf(n_i[2]*1.0f+0.5f)];
+ normal_index += 3;
+ vert_index += 3;
+ for(unsigned j=0; j<indices.size(); ++j){
+ if(a.tex_coords2[indices[j]*2+0] == 0.0f &&
+ a.tex_coords2[indices[j]*2+1] == 0.0f)
+ {
+ a.tex_coords2[indices[j]*2+0] = b.tex_coords[i*2+0];
+ a.tex_coords2[indices[j]*2+1] = b.tex_coords[i*2+1];
+ }
+ }
+ }
+}
+
+
+void Model::LoadObj(const std::string &rel_path, char flags, const std::string &alt_name, const PathFlags searchPaths) {
+
+ //LOGI << "Loading model: " << rel_path << std::endl;
+ const char* fail_whale = "./Data/Models/Default/default_model_2.obj";
+
+ bool center = (flags & _MDL_CENTER)!=0;
+ bool simple = (flags & _MDL_SIMPLE)!=0;
+ use_tangent = (flags & _MDL_USE_TANGENT)!=0;
+
+ bool load_model = true;
+
+ Dispose();
+
+ path = rel_path;
+
+ std::string name_to_load = rel_path;
+ const std::string &cache_name_to_load = SanitizePath(alt_name.empty()?rel_path:alt_name);
+
+ char abs_path[kPathSize], abs_cache_path[kPathSize];
+ ModID modsource, cache_modsource;
+ bool found_model = (FindFilePath(name_to_load.c_str(), abs_path, kPathSize, searchPaths, false, NULL, &modsource) != -1);
+ bool found_cache = (FindFilePath((cache_name_to_load+".cache").c_str(), abs_cache_path, kPathSize, searchPaths, false, NULL, &cache_modsource) != -1);
+
+ if (!found_model && !found_cache) { // To do: fix bug when only cache exists
+ std::string error_msg = "Whale fail :(\nModel file \"" + std::string(rel_path) + "\" not found. Loading the fail whale instead.";
+ DisplayError("Error", error_msg.c_str(), _ok, false);
+ name_to_load = fail_whale;
+ }
+
+ if(found_model){
+ checksum = Checksum(abs_path);
+ }
+
+ if(found_cache){
+ FILE *cache_file = my_fopen(abs_cache_path, "rb");
+ if(cache_file){
+ unsigned short file_checksum = 0;
+ fread(&file_checksum, sizeof(unsigned short), 1, cache_file);
+ unsigned short version;
+ fread(&version, sizeof(unsigned short), 1, cache_file);
+ if(checksum == file_checksum && version == _model_cache_version){
+ ReadFromFile(cache_file);
+ load_model = false;
+ modsource_ = cache_modsource;
+ }
+ fclose(cache_file);
+ }
+ }
+
+ if (load_model) {
+ int modelLoadError = SimpleLoadTriangleCutObj(name_to_load);
+
+ if( 0 == modelLoadError )
+ {
+ precollapse_num_vertices = vertices.size()/3;
+
+ ObjFileStats obj_stats2;
+ bool uv2_file = GetObjFileStats(name_to_load+"_UV2", obj_stats2, false);
+ if (uv2_file) {
+ Model temp2;
+ int uv2_err = temp2.SimpleLoadTriangleCutObj(name_to_load+"_UV2");
+
+ if( uv2_err == 0 )
+ {
+ //CopyTexCoords2((*this), temp2);
+ if(temp2.vertices.size() != vertices.size()){
+ DisplayError("Error", ("Mismatched number of vertices in: "+
+ (name_to_load+"_UV2") + ". Will not use data.").c_str() );
+ }
+ else
+ {
+ for(int i=0, len=vertices.size()/3*2; i<len; ++i){
+ tex_coords2[i] = temp2.tex_coords[i];
+ }
+ }
+ }
+ else
+ {
+ DisplayError("Error", ("Malformed data in: "+
+ (name_to_load+"_UV2" + ", reason: " + GetLoadErrorString(uv2_err) + " Will not use data.")).c_str() );
+ }
+ }
+
+
+ if(flags & _MDL_FLIP_FACES){
+ for(unsigned i=0; i<faces.size(); i+=3){
+ std::swap(faces[i+0], faces[i+2]);
+ }
+ }
+
+ if(!simple){
+ if(normals.empty()) {
+ calcNormals();
+ } else {
+ calcFaceNormals();
+ for(int i=0, len=normals.size(); i<len; i+=3){
+ float length = sqrtf(square(normals[i]) + square(normals[i+1]) + square(normals[i+2]));
+ if(length != 0.0f){
+ normals[i+0] /= length;
+ normals[i+1] /= length;
+ normals[i+2] /= length;
+ }
+ }
+ }
+ calcTangents();
+ } else {
+ tex_coords.clear();
+ tex_coords.resize(vertices.size()/3*2, 0.0f);
+ normals.resize(vertices.size(), 0.0f);
+ }
+ calcBoundingBox();
+ old_center = center_coords;
+ if(center) {
+ CenterModel();
+ }
+ calcBoundingSphere();
+
+ CalcTexelDensity();
+ CalcAverageTriangleEdge();
+
+ RemoveDuplicatedVerts();
+ RemoveDegenerateTriangles();
+ OptimizeTriangleOrder();
+ OptimizeVertexOrder();
+
+ FILE *cache_file = my_fopen((GetWritePath(modsource) + cache_name_to_load + ".cache").c_str(), "wb");
+ if(cache_file){
+ fwrite(&checksum, sizeof(unsigned short), 1, cache_file);
+ fwrite(&_model_cache_version, sizeof(unsigned short), 1, cache_file);
+ WriteToFile(cache_file);
+ fclose(cache_file);
+ }
+ modsource_ = modsource;
+ }
+ else
+ {
+ if( fail_whale == rel_path )
+ {
+ FatalError( "Error", "Unable to load fail whale, this is bad.");
+ }
+ else
+ {
+ std::string error_msg = "Whale fail :(\nModel file \"" + std::string(rel_path) + "\" is corrupt because: "+ GetLoadErrorString(modelLoadError) + ". Loading the fail whale instead.";
+ DisplayError("Error", error_msg.c_str(), _ok, false);
+
+ LoadObj(fail_whale, flags, fail_whale);
+ return; //Quick out.
+ }
+ }
+ }
+ vbo_enabled = true;
+ //PrintACMR();
+}
+
+void DrawModelVerts(Model& model, const char* vert_attrib_str, Shaders* shaders, Graphics* graphics, int shader) {
+ if(!model.vbo_loaded){
+ model.createVBO();
+ }
+ model.VBO_vertices.Bind();
+ model.VBO_faces.Bind();
+ int vert_attrib_id = shaders->returnShaderAttrib(vert_attrib_str, shader);
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, 3*sizeof(GLfloat), 0);
+ graphics->DrawElements(GL_TRIANGLES, model.faces.size(), GL_UNSIGNED_INT, 0);
+ graphics->ResetVertexAttribArrays();
+ graphics->BindArrayVBO(0);
+ graphics->BindElementVBO(0);
+}
+
+//Draw model
+void Model::Draw() {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Model::Draw");
+ Graphics *graphics = Graphics::Instance();
+ CHECK_GL_ERROR();
+ if(vbo_enabled){
+ if(!vbo_loaded){
+ createVBO();
+ }
+ CHECK_GL_ERROR();
+ int flags = F_VERTEX_ARRAY | F_NORMAL_ARRAY | F_TEXTURE_COORD_ARRAY0;
+ if(!tangents.empty()){
+ flags |= F_TEXTURE_COORD_ARRAY1 | F_TEXTURE_COORD_ARRAY2;
+ }
+ if (!tex_coords2.empty()) {
+ flags |= F_TEXTURE_COORD_ARRAY3;
+ }
+ if (!aux.empty()) {
+ flags |= F_COLOR_ARRAY;
+ }
+ CHECK_GL_ERROR();
+ graphics->SetClientStates(flags);
+ CHECK_GL_ERROR();
+ VBO_vertices.Bind();
+ CHECK_GL_ERROR();
+ glVertexPointer(3, GL_FLOAT, 0, 0);
+ CHECK_GL_ERROR();
+ VBO_normals.Bind();
+ CHECK_GL_ERROR();
+ glNormalPointer(GL_FLOAT, 0, 0);
+ CHECK_GL_ERROR();
+ graphics->SetClientActiveTexture(0);
+ CHECK_GL_ERROR();
+ VBO_tex_coords.Bind();
+ glTexCoordPointer(2, GL_FLOAT, 0, 0);
+ CHECK_GL_ERROR();
+ if(!tangents.empty()){
+ graphics->SetClientActiveTexture(1);
+ CHECK_GL_ERROR();
+ VBO_tangents.Bind();
+ glTexCoordPointer(3, GL_FLOAT, 0, 0);
+ graphics->SetClientActiveTexture(2);
+ CHECK_GL_ERROR();
+ VBO_bitangents.Bind();
+ glTexCoordPointer(3, GL_FLOAT, 0, 0);
+ CHECK_GL_ERROR();
+ }
+ if (!tex_coords2.empty()) {
+ graphics->SetClientActiveTexture(3);
+ CHECK_GL_ERROR();
+ VBO_tex_coords2.Bind();
+ glTexCoordPointer(2, GL_FLOAT, 0, 0);
+ CHECK_GL_ERROR();
+ }
+ if (!aux.empty()) {
+ VBO_aux.Bind();
+ glColorPointer(3, GL_FLOAT, 0, 0);
+ CHECK_GL_ERROR();
+ }
+ VBO_faces.Bind();
+ graphics->DrawElements(GL_TRIANGLES, faces.size(), GL_UNSIGNED_INT, 0);
+ CHECK_GL_ERROR();
+ if (!tex_coords2.empty()) {
+ Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY3,false);
+ }
+ if(!tangents.empty()){
+ Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY2,false);
+ Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY1,false);
+ }
+ CHECK_GL_ERROR();
+ Graphics::Instance()->SetClientActiveTexture(0);
+ CHECK_GL_ERROR();
+ graphics->BindArrayVBO(0);
+ CHECK_GL_ERROR();
+ graphics->BindElementVBO(0);
+ CHECK_GL_ERROR();
+ }
+ else if (transform_vec.empty()) {
+ int flags = F_VERTEX_ARRAY | F_NORMAL_ARRAY | F_TEXTURE_COORD_ARRAY0;
+ if(!tangents.empty()){
+ flags |= F_TEXTURE_COORD_ARRAY1 | F_TEXTURE_COORD_ARRAY2;
+ }
+ if (!tex_coords2.empty()) {
+ flags |= F_TEXTURE_COORD_ARRAY3;
+ }
+ if (!aux.empty()) {
+ flags |= F_COLOR_ARRAY;
+ }
+ if(!bone_weights.empty()){
+ flags |= F_TEXTURE_COORD_ARRAY5;
+ }
+ if(!bone_ids.empty()){
+ flags |= F_TEXTURE_COORD_ARRAY6;
+ }
+ graphics->SetClientStates(flags);
+ graphics->SetClientActiveTexture(0);
+ glTexCoordPointer(2, GL_FLOAT, 0, &tex_coords[0]);
+ glNormalPointer(GL_FLOAT, 0, &normals[0]);
+ glVertexPointer(3, GL_FLOAT, 0, &vertices[0]);
+ if(!tangents.empty()){
+ graphics->SetClientActiveTexture(1);
+ glTexCoordPointer(3, GL_FLOAT, 0, &tangents[0]);
+ graphics->SetClientActiveTexture(2);
+ glTexCoordPointer(3, GL_FLOAT, 0, &bitangents[0]);
+ }
+ if(!tex_coords2.empty()){
+ graphics->SetClientActiveTexture(3);
+ glTexCoordPointer(2, GL_FLOAT, 0, &tex_coords2[0]);
+ }
+ if(!aux.empty()){
+ glColorPointer(3, GL_FLOAT, 0, &aux[0]);
+ }
+ if(!bone_weights.empty()){
+ graphics->SetClientActiveTexture(5);
+ glTexCoordPointer(4, GL_FLOAT, 0, &bone_weights[0]);
+ }
+ if(!bone_ids.empty()){
+ graphics->SetClientActiveTexture(6);
+ glTexCoordPointer(4, GL_FLOAT, 0, &bone_ids[0]);
+ }
+ graphics->DrawElements(GL_TRIANGLES, faces.size(), GL_UNSIGNED_INT, &faces[0]);
+ if(!tex_coords2.empty()){
+ Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY3,false);
+ }
+ if(!bone_weights.empty()){
+ Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY5,false);
+ }
+ if(!bone_ids.empty()){
+ Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY6,false);
+ }
+ if(!tangents.empty()){
+ Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY2,false);
+ Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY1,false);
+ }
+ Graphics::Instance()->SetClientActiveTexture(0);
+ } else {
+ int flags = F_VERTEX_ARRAY | F_NORMAL_ARRAY | F_TEXTURE_COORD_ARRAY0 | F_TEXTURE_COORD_ARRAY1 | F_TEXTURE_COORD_ARRAY2 | F_TEXTURE_COORD_ARRAY4 | F_TEXTURE_COORD_ARRAY5;
+ if(!tangents.empty()){
+ flags |= F_TEXTURE_COORD_ARRAY3;
+ }
+ if (!tex_coords2.empty()) {
+ flags |= F_TEXTURE_COORD_ARRAY3;
+ }
+ if (!aux.empty()) {
+ flags |= F_COLOR_ARRAY;
+ }
+ if(!bone_weights.empty()){
+ flags |= F_TEXTURE_COORD_ARRAY5;
+ }
+ if(!bone_ids.empty()){
+ flags |= F_TEXTURE_COORD_ARRAY6;
+ }
+ graphics->SetClientStates(flags);
+ glNormalPointer(GL_FLOAT, 0, &normals[0]);
+ glVertexPointer(3, GL_FLOAT, 0, &vertices[0]);
+ graphics->SetClientActiveTexture(0);
+ glTexCoordPointer(2, GL_FLOAT, 0, &tex_coords[0]);
+ graphics->SetClientActiveTexture(1);
+ glTexCoordPointer(4, GL_FLOAT, 0, &transform_vec[0][0]);
+ graphics->SetClientActiveTexture(2);
+ glTexCoordPointer(4, GL_FLOAT, 0, &transform_vec[1][0]);
+ if(!tangents.empty()){
+ graphics->SetClientActiveTexture(3);
+ glTexCoordPointer(3, GL_FLOAT, 0, &tangents[0]);
+ }
+ graphics->SetClientActiveTexture(4);
+ glTexCoordPointer(4, GL_FLOAT, 0, &transform_vec[2][0]);
+ graphics->SetClientActiveTexture(5);
+ glTexCoordPointer(4, GL_FLOAT, 0, &transform_vec[3][0]);
+ graphics->DrawElements(GL_TRIANGLES, faces.size(), GL_UNSIGNED_INT, &faces[0]);
+ if(!tangents.empty()){
+ Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY3,false);
+ }
+ Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY4,false);
+ Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY5,false);
+ Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY2,false);
+ Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY1,false);
+ Graphics::Instance()->SetClientActiveTexture(0);
+ }
+
+ CHECK_GL_ERROR();
+}
+
+void Model::StartPreset() {
+ Graphics *graphics = Graphics::Instance();
+ if(vbo_enabled){
+ if(!vbo_loaded){
+ createVBO();
+ }
+ int flags = F_VERTEX_ARRAY | F_NORMAL_ARRAY | F_TEXTURE_COORD_ARRAY0;
+ if(!tangents.empty()){
+ flags |= F_TEXTURE_COORD_ARRAY1 | F_TEXTURE_COORD_ARRAY2;
+ }
+ if (!tex_coords2.empty()) {
+ flags |= F_TEXTURE_COORD_ARRAY3;
+ }
+ if (!aux.empty()) {
+ flags |= F_COLOR_ARRAY;
+ }
+ graphics->SetClientStates(flags);
+ VBO_vertices.Bind();
+ glVertexPointer(3, GL_FLOAT, 0, 0);
+ VBO_normals.Bind();
+ glNormalPointer(GL_FLOAT, 0, 0);
+ graphics->SetClientActiveTexture(0);
+ VBO_tex_coords.Bind();
+ glTexCoordPointer(2, GL_FLOAT, 0, 0);
+ if(!tangents.empty()){
+ graphics->SetClientActiveTexture(1);
+ VBO_tangents.Bind();
+ glTexCoordPointer(3, GL_FLOAT, 0, 0);
+ graphics->SetClientActiveTexture(2);
+ VBO_bitangents.Bind();
+ glTexCoordPointer(3, GL_FLOAT, 0, 0);
+ }
+ if (!tex_coords2.empty()) {
+ graphics->SetClientActiveTexture(3);
+ VBO_tex_coords2.Bind();
+ glTexCoordPointer(2, GL_FLOAT, 0, 0);
+ }
+ if (!aux.empty()) {
+ VBO_aux.Bind();
+ glColorPointer(3, GL_FLOAT, 0, 0);
+ }
+ VBO_faces.Bind();
+ }
+}
+
+void Model::EndPreset() {
+ if(vbo_enabled){
+ if (!aux.empty()) {
+ //glClientActiveTexture(GL_TEXTURE4);
+ //glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+ }
+
+ if (!tex_coords2.empty()) {
+ Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY3,false);
+ }
+
+ if(!tangents.empty()){
+ Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY2,false);
+ Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY1,false);
+ }
+ Graphics::Instance()->SetClientActiveTexture(0);
+ Graphics::Instance()->BindArrayVBO(0);
+ Graphics::Instance()->BindElementVBO(0);
+ }
+}
+
+void Model::DrawPreset()
+{
+ if(vbo_enabled){
+ Graphics::Instance()->DrawElements(GL_TRIANGLES, faces.size(), GL_UNSIGNED_INT, 0);
+ }
+}
+
+struct AuxVertData {
+ std::vector<int> verts;
+ vec3 total_aux;
+};
+
+bool Model::LoadAuxFromImage(const std::string &image_path) {
+ bool ret = true;
+ if(aux.empty()) {
+ ImageSamplerRef image = Engine::Instance()->GetAssetManager()->LoadSync<ImageSampler>(image_path);
+
+ if(image.valid()) {
+ image_samplers.insert(image);
+
+ aux.resize(vertices.size());
+ for(int i=0, len=vertices.size()/3; i<len; i++) {
+ vec4 color = image->GetInterpolatedColorUV(tex_coords[i*2+0], tex_coords[i*2+1]);
+ aux[i*3+0] = color.r();
+ aux[i*3+1] = color.g();
+ aux[i*3+2] = color.b();
+ }
+
+ // Duplicate map keeps a list of all vertices that share the same position
+ std::map<vec3, AuxVertData> dup_map;
+ unsigned vert_index = 0;
+ for(int i=0, len=vertices.size()/3; i<len; i++) {
+ vec3 index(vertices[vert_index+0],
+ vertices[vert_index+1],
+ vertices[vert_index+2]);
+ AuxVertData &avd = dup_map[index];
+ avd.verts.push_back(i);
+ avd.total_aux[0] += aux[vert_index + 0];
+ avd.total_aux[1] += aux[vert_index + 1];
+ avd.total_aux[2] += aux[vert_index + 2];
+ vert_index += 3;
+ }
+ std::map<vec3, AuxVertData>::iterator iter;
+ for(iter = dup_map.begin(); iter != dup_map.end(); ++iter){
+ AuxVertData &avd = iter->second;
+ if(avd.verts.size() > 1){
+ avd.total_aux /= (float)avd.verts.size();
+ for(unsigned i=0; i<avd.verts.size(); ++i){
+ int index = avd.verts[i]*3;
+ aux[index + 0] = avd.total_aux[0];
+ aux[index + 1] = avd.total_aux[2];
+ aux[index + 2] = avd.total_aux[1];
+ }
+ }
+ }
+ } else {
+ LOGE << "Failed to load wind map from image: " << image_path << std::endl;
+ ret = false;
+ }
+ } else {
+ //Nop instruction, this state is ok.
+ }
+ return ret;
+}
+
+void Model::DrawAltTexCoords()
+{
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Model::DrawAltTexCoords");
+ CHECK_GL_ERROR();
+
+ Graphics *graphics = Graphics::Instance();
+ int flags = F_VERTEX_ARRAY | F_NORMAL_ARRAY | F_TEXTURE_COORD_ARRAY0;
+ if(!tangents.empty()){
+ flags |= F_TEXTURE_COORD_ARRAY1 | F_TEXTURE_COORD_ARRAY2;
+ }
+ graphics->SetClientStates(flags);
+
+ if(vbo_enabled){
+ if(!vbo_loaded){
+ createVBO();
+ }
+ VBO_vertices.Bind();
+ glVertexPointer(3, GL_FLOAT, 0, 0);
+ VBO_normals.Bind();
+ glNormalPointer(GL_FLOAT, 0, 0);
+ graphics->SetClientActiveTexture(0);
+ VBO_tex_coords.Bind();
+ glTexCoordPointer(2, GL_FLOAT, 0, 0);
+ if (!tex_coords2.empty()) {
+ VBO_tex_coords2.Bind();
+ glTexCoordPointer(2, GL_FLOAT, 0, 0);
+ }
+ if(!tangents.empty()){
+ graphics->SetClientActiveTexture(1);
+ VBO_tangents.Bind();
+ glTexCoordPointer(3, GL_FLOAT, 0, 0);
+ graphics->SetClientActiveTexture(2);
+ VBO_bitangents.Bind();
+ glTexCoordPointer(3, GL_FLOAT, 0, 0);
+ }
+
+ VBO_faces.Bind();
+ graphics->DrawElements(GL_TRIANGLES, faces.size(), GL_UNSIGNED_INT, 0);
+
+ if(!tangents.empty()){
+ Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY2,false);
+ Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY1,false);
+ }
+ Graphics::Instance()->SetClientActiveTexture(0);
+ Graphics::Instance()->BindArrayVBO(0);
+ Graphics::Instance()->BindElementVBO(0);
+ }
+ else
+ {
+ graphics->SetClientActiveTexture(0);
+ glTexCoordPointer(2, GL_FLOAT, 0, &tex_coords[0]);
+ glNormalPointer(GL_FLOAT, 0, &normals[0]);
+ glVertexPointer(3, GL_FLOAT, 0, &vertices[0]);
+
+ if(!tex_coords2.empty()){
+ glTexCoordPointer(2, GL_FLOAT, 0, &tex_coords2[0]);
+ }
+ if(!tangents.empty()){
+ graphics->SetClientActiveTexture(1);
+ glTexCoordPointer(3, GL_FLOAT, 0, &tangents[0]);
+ graphics->SetClientActiveTexture(2);
+ glTexCoordPointer(3, GL_FLOAT, 0, &bitangents[0]);
+ }
+
+ graphics->DrawElements(GL_TRIANGLES, faces.size(), GL_UNSIGNED_INT, &faces[0]);
+
+ if(!tangents.empty()){
+ Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY2,false);
+ Graphics::Instance()->SetClientStateEnabled(CS_TEXTURE_COORD_ARRAY1,false);
+ }
+ Graphics::Instance()->SetClientActiveTexture(0);
+ }
+
+ CHECK_GL_ERROR();
+}
+
+void Model::DrawToTextureCoords()
+{
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Model::DrawToTextureCoords");
+ CHECK_GL_ERROR();
+
+ Graphics *graphics = Graphics::Instance();
+ int flags = F_VERTEX_ARRAY | F_NORMAL_ARRAY | F_TEXTURE_COORD_ARRAY0;
+ if(!tangents.empty()){
+ flags |= F_TEXTURE_COORD_ARRAY1 | F_TEXTURE_COORD_ARRAY2;
+ }
+ if (!tex_coords2.empty()) {
+ flags |= F_TEXTURE_COORD_ARRAY3;
+ }
+ CHECK_GL_ERROR();
+ graphics->SetClientStates(flags);
+ CHECK_GL_ERROR();
+
+ if(vbo_enabled){
+ if(!vbo_loaded){
+ createVBO();
+ }
+ VBO_tex_coords.Bind();
+ glVertexPointer(2, GL_FLOAT, 0, 0);
+ VBO_normals.Bind();
+ glNormalPointer(GL_FLOAT, 0, 0);
+ graphics->SetClientActiveTexture(0);
+ VBO_tex_coords.Bind();
+ glTexCoordPointer(2, GL_FLOAT, 0, 0);
+ if(!tangents.empty()){
+ graphics->SetClientActiveTexture(1);
+ VBO_tangents.Bind();
+ glTexCoordPointer(3, GL_FLOAT, 0, 0);
+ graphics->SetClientActiveTexture(2);
+ VBO_bitangents.Bind();
+ glTexCoordPointer(3, GL_FLOAT, 0, 0);
+ }
+ if (!tex_coords2.empty()) {
+ graphics->SetClientActiveTexture(3);
+ VBO_tex_coords2.Bind();
+ glTexCoordPointer(2, GL_FLOAT, 0, 0);
+ }
+ VBO_faces.Bind();
+ graphics->DrawElements(GL_TRIANGLES, faces.size(), GL_UNSIGNED_INT, 0);
+ Graphics::Instance()->SetClientActiveTexture(0);
+ Graphics::Instance()->BindArrayVBO(0);
+ Graphics::Instance()->BindElementVBO(0);
+ } else {
+ graphics->SetClientActiveTexture(0);
+ glTexCoordPointer(2, GL_FLOAT, 0, &tex_coords[0]);
+ glNormalPointer(GL_FLOAT, 0, &normals[0]);
+ glVertexPointer(2, GL_FLOAT, 0, &tex_coords[0]);
+
+ if(!tangents.empty()){
+ graphics->SetClientActiveTexture(1);
+ glTexCoordPointer(3, GL_FLOAT, 0, &tangents[0]);
+ graphics->SetClientActiveTexture(2);
+ glTexCoordPointer(3, GL_FLOAT, 0, &bitangents[0]);
+ }
+ if(!tex_coords2.empty()){
+ graphics->SetClientActiveTexture(3);
+ glTexCoordPointer(2, GL_FLOAT, 0, &tex_coords2[0]);
+ }
+ graphics->DrawElements(GL_TRIANGLES, faces.size(), GL_UNSIGNED_INT, &faces[0]);
+ Graphics::Instance()->SetClientActiveTexture(0);
+ }
+ graphics->SetClientStates(F_VERTEX_ARRAY | F_NORMAL_ARRAY | F_TEXTURE_COORD_ARRAY0);
+
+ CHECK_GL_ERROR();
+}
+
+void Model::calcBoundingBox() {
+ min_coords=0;
+ max_coords=0;
+ center_coords = 0;
+
+ for(int i=0, len=vertices.size()/3, vert_index=0; i<len; i++, vert_index+=3) {
+ for(int j=0; j<3; ++j){
+ float coord = vertices[vert_index+j];
+ if(i==0 || coord > max_coords[j]){
+ max_coords[j] = coord;
+ }
+ if(i==0 || coord < min_coords[j]){
+ min_coords[j] = coord;
+ }
+ }
+ }
+
+ center_coords = (min_coords + max_coords)/2;
+}
+
+//Calculate bounding sphere
+void Model::calcBoundingSphere() {
+ bounding_sphere_origin = center_coords;
+
+ float longest_distance=0;
+ vec3 vert;
+ for (int i=0, len=vertices.size()/3; i<len; i++) {
+ vert.x()=vertices[i*3];
+ vert.y()=vertices[i*3+1];
+ vert.z()=vertices[i*3+2];
+ if(distance_squared(bounding_sphere_origin, vert)>longest_distance)
+ longest_distance=distance_squared(bounding_sphere_origin, vert);
+ }
+ bounding_sphere_radius=sqrtf(longest_distance);
+}
+
+// must be called after center_coords has been set (i.e. after call to calcBoundingSphere
+void Model::CenterModel() {
+ for(int i=0, len=vertices.size()/3; i<len; i++) {
+ vertices[3*i] -= center_coords.x();
+ vertices[3*i+1] -= center_coords.y();
+ vertices[3*i+2] -= center_coords.z();
+ }
+ center_coords = vec3(0,0,0);
+
+ calcBoundingBox();
+}
+
+void Model::ResizeVertices(int size) {
+ vertices.resize(size*3);
+ normals.resize(size*3);
+ tangents.resize(size*3);
+ bitangents.resize(size*3);
+ tex_coords.resize(size*2);
+ if(!bone_weights.empty()){
+ bone_weights.resize(size);
+ }if(!bone_ids.empty()){
+ bone_ids.resize(size);
+ }
+}
+
+void Model::ResizeFaces(int size) {
+ bool success = false;
+ while(!success){
+ try {
+ faces.resize(size*3);
+ face_normals.resize(size);
+ success = true;
+ } catch (std::bad_alloc const&) {
+ success = false;
+ SDL_Delay(100);
+ LOGE << "Vector alloc failed, trying again..." << std::endl;
+ }
+ }
+}
+
+
+void Model::calcFaceNormals() {
+ face_normals.resize(faces.size() / 3);
+ #pragma omp parallel for
+ for(int i=0, len=faces.size()/3; i<len; i++){
+ face_normals[i][0] = (vertices[faces[i*3+1]*3+1] - vertices[faces[i*3+0]*3+1])*(vertices[faces[i*3+2]*3+2] - vertices[faces[i*3+0]*3+2]) - (vertices[faces[i*3+1]*3+2] - vertices[faces[i*3+0]*3+2])*(vertices[faces[i*3+2]*3+1] - vertices[faces[i*3+0]*3+1]);
+ face_normals[i][1] = (vertices[faces[i*3+1]*3+2] - vertices[faces[i*3+0]*3+2])*(vertices[faces[i*3+2]*3+0] - vertices[faces[i*3+0]*3+0]) - (vertices[faces[i*3+1]*3+0] - vertices[faces[i*3+0]*3+0])*(vertices[faces[i*3+2]*3+2] - vertices[faces[i*3+0]*3+2]);
+ face_normals[i][2] = (vertices[faces[i*3+1]*3+0] - vertices[faces[i*3+0]*3+0])*(vertices[faces[i*3+2]*3+1] - vertices[faces[i*3+0]*3+1]) - (vertices[faces[i*3+1]*3+1] - vertices[faces[i*3+0]*3+1])*(vertices[faces[i*3+2]*3+0] - vertices[faces[i*3+0]*3+0]);
+ }
+}
+
+void Model::calcNormals() {
+ normals.resize(vertices.size());
+
+ #pragma omp parallel for
+ for(int i=0, len=vertices.size()/3; i<len; i++){
+ normals[i*3+0]=0;
+ normals[i*3+1]=0;
+ normals[i*3+2]=0;
+ }
+
+ //vec3 edge1,edge2;
+
+ calcFaceNormals();
+ for(int i=0, len=faces.size()/3; i<len; i++){
+ normals[faces[i*3+0]*3+0]+=face_normals[i].x();
+ normals[faces[i*3+0]*3+1]+=face_normals[i].y();
+ normals[faces[i*3+0]*3+2]+=face_normals[i].z();
+
+ normals[faces[i*3+1]*3+0]+=face_normals[i].x();
+ normals[faces[i*3+1]*3+1]+=face_normals[i].y();
+ normals[faces[i*3+1]*3+2]+=face_normals[i].z();
+
+ normals[faces[i*3+2]*3+0]+=face_normals[i].x();
+ normals[faces[i*3+2]*3+1]+=face_normals[i].y();
+ normals[faces[i*3+2]*3+2]+=face_normals[i].z();
+ }
+ #pragma omp parallel for
+ for(int i=0, len=vertices.size()/3; i<len; i++){
+ float length=sqrtf(square(normals[i*3+0])+square(normals[i*3+1])+square(normals[i*3+2]));
+
+ //For some reasons the normals are sometimes 0,0,0 We have to check for this to prevent NaN.
+ if( length != 0.0f )
+ {
+ normals[i*3+0]/=length;
+ normals[i*3+1]/=length;
+ normals[i*3+2]/=length;
+ }
+ }
+ #pragma omp parallel for
+ for(int i=0, len=faces.size()/3; i<len; i++){
+ face_normals[i] = normalize(face_normals[i]);
+ }
+}
+
+void Model::calcTangents() {
+ tangents.resize(vertices.size());
+ bitangents.resize(vertices.size());
+#pragma omp parallel for
+ for(int i=0, len=vertices.size(); i<len; i+=3){
+ tangents[i+0]=0;
+ tangents[i+1]=0;
+ tangents[i+2]=0;
+
+ bitangents[i+0]=0;
+ bitangents[i+1]=0;
+ bitangents[i+2]=0;
+ }
+#pragma omp parallel for
+ for (long a=0, len=faces.size()/3; a<len; a++)
+ {
+ float x1, x2, y1, y2, z1, z2;
+ float s1, s2, t1, t2;
+ float r;
+
+ vec3 sdir;
+ vec3 tdir;
+
+ x1 = vertices[faces[a*3+1]*3+0] - vertices[faces[a*3+0]*3+0];
+ x2 = vertices[faces[a*3+2]*3+0] - vertices[faces[a*3+0]*3+0];
+ y1 = vertices[faces[a*3+1]*3+1] - vertices[faces[a*3+0]*3+1];
+ y2 = vertices[faces[a*3+2]*3+1] - vertices[faces[a*3+0]*3+1];
+ z1 = vertices[faces[a*3+1]*3+2] - vertices[faces[a*3+0]*3+2];
+ z2 = vertices[faces[a*3+2]*3+2] - vertices[faces[a*3+0]*3+2];
+
+ s1 = tex_coords[faces[a*3+1]*2+0] - tex_coords[faces[a*3+0]*2+0];
+ s2 = tex_coords[faces[a*3+2]*2+0] - tex_coords[faces[a*3+0]*2+0];
+ t1 = tex_coords[faces[a*3+1]*2+1] - tex_coords[faces[a*3+0]*2+1];
+ t2 = tex_coords[faces[a*3+2]*2+1] - tex_coords[faces[a*3+0]*2+1];
+
+ float denom = (s1 * t2 - s2 * t1);
+ if(denom != 0.0f) {
+ r = 1.0F / denom;
+ } else {
+ r = 99999.0f;
+ }
+ sdir = vec3((t2 * x1 - t1 * x2) * r,
+ (t2 * y1 - t1 * y2) * r,
+ (t2 * z1 - t1 * z2) * r);
+
+ tdir = vec3((s1 * x2 - s2 * x1) * r,
+ (s1 * y2 - s2 * y1) * r,
+ (s1 * z2 - s2 * z1) * r);
+
+ #pragma omp critical
+ {
+ tangents[faces[a*3+0]*3+0] += sdir.x();
+ tangents[faces[a*3+0]*3+1] += sdir.y();
+ tangents[faces[a*3+0]*3+2] += sdir.z();
+ tangents[faces[a*3+1]*3+0] += sdir.x();
+ tangents[faces[a*3+1]*3+1] += sdir.y();
+ tangents[faces[a*3+1]*3+2] += sdir.z();
+ tangents[faces[a*3+2]*3+0] += sdir.x();
+ tangents[faces[a*3+2]*3+1] += sdir.y();
+ tangents[faces[a*3+2]*3+2] += sdir.z();
+
+ bitangents[faces[a*3+0]*3+0] += tdir.x();
+ bitangents[faces[a*3+0]*3+1] += tdir.y();
+ bitangents[faces[a*3+0]*3+2] += tdir.z();
+ bitangents[faces[a*3+1]*3+0] += tdir.x();
+ bitangents[faces[a*3+1]*3+1] += tdir.y();
+ bitangents[faces[a*3+1]*3+2] += tdir.z();
+ bitangents[faces[a*3+2]*3+0] += tdir.x();
+ bitangents[faces[a*3+2]*3+1] += tdir.y();
+ bitangents[faces[a*3+2]*3+2] += tdir.z();
+ }
+ }
+
+#pragma omp parallel for
+ for(int i=0, len=vertices.size(); i<len; i+=3) {
+ vec3 n(normals[i+0],normals[i+1],normals[i+2]);
+ vec3 t(tangents[i+0],tangents[i+1],tangents[i+2]);
+
+ // Gram-Schmidt orthogonalize
+ vec3 tangent = normalize(t - n * dot(n, t));
+
+ tangents[i+0]=tangent.x();
+ tangents[i+1]=tangent.y();
+ tangents[i+2]=tangent.z();
+ }
+
+#pragma omp parallel for
+ for(int i=0, len=vertices.size(); i<len; i+=3){
+ vec3 n(normals[i+0],normals[i+1],normals[i+2]);
+ vec3 t(bitangents[i+0],bitangents[i+1],bitangents[i+2]);
+
+ // Gram-Schmidt orthogonalize
+ vec3 tangent = normalize(t - n * dot(n, t));
+
+ bitangents[i+0]=tangent.x();
+ bitangents[i+1]=tangent.y();
+ bitangents[i+2]=tangent.z();
+ }
+}
+
+//Set up simp_vertex buffer object
+void Model::createVBO() {
+ CHECK_GL_ERROR();
+
+ VBO_vertices.Fill(kVBOFloat | kVBOStatic, vertices.size()*sizeof(float), &vertices[0]);
+ if(!normals.empty()){
+ VBO_normals.Fill(kVBOFloat | kVBOStatic, vertices.size()*sizeof(float), &normals[0]);
+ }
+ CHECK_GL_ERROR();
+ if(!tangents.empty()){
+ VBO_tangents.Fill(kVBOFloat | kVBOStatic, vertices.size()*sizeof(float), &tangents[0]);
+ }
+ CHECK_GL_ERROR();
+ if(!bitangents.empty()){
+ VBO_bitangents.Fill(kVBOFloat | kVBOStatic, vertices.size()*sizeof(float), &bitangents[0]);
+ }
+
+ CHECK_GL_ERROR();
+ VBO_tex_coords.Fill(kVBOFloat | kVBOStatic, vertices.size()/3*2*sizeof(float), &tex_coords[0]);
+
+ CHECK_GL_ERROR();
+ if (!tex_coords2.empty()) {
+ VBO_tex_coords2.Fill(kVBOFloat | kVBOStatic, vertices.size()/3*2*sizeof(float), &tex_coords2[0]);
+ }
+
+ if (!aux.empty()) {
+ VBO_aux.Fill(kVBOFloat | kVBOStatic, vertices.size()*sizeof(float), &aux[0]);
+ }
+
+ CHECK_GL_ERROR();
+ VBO_faces.Fill(kVBOElement | kVBOStatic, faces.size()*sizeof(GLuint), &faces[0]);
+
+ CHECK_GL_ERROR();
+ Graphics *graphics = Graphics::Instance();
+ graphics->BindArrayVBO(0);
+ graphics->BindElementVBO(0);
+ vbo_loaded = true;
+
+ CHECK_GL_ERROR();
+}
+
+struct ChildHit {
+ float time;
+ bool hit;
+ int child;
+};
+
+class ChildHitCompare {
+ public:
+ bool operator()(const ChildHit &a, const ChildHit &b) {
+ return a.time < b.time;
+ }
+};
+
+//Check a line against the model for collision
+int Model::lineCheck(const vec3& p1,const vec3& p2, vec3* p, vec3 *normal, bool backface) const {
+ if(!sphere_line_intersection(p1,p2,bounding_sphere_origin,bounding_sphere_radius))return -1;
+
+ float distance;
+ float olddistance=0;
+ int intersecting=0;
+ int firstintersecting=-1;
+ vec3 point;
+ vec3 end = p2;
+ int face_index, vert_index;
+ vec3 points[3];
+ for (int j=0, len=faces.size()/3;j<len;j++){
+ face_index = j*3;
+ for(unsigned k=0; k<3; ++k){
+ vert_index = faces[face_index+k]*3;
+ points[k][0] = vertices[vert_index+0];
+ points[k][1] = vertices[vert_index+1];
+ points[k][2] = vertices[vert_index+2];
+ }
+ intersecting=LineFacet(p1,end,points[0],points[1],points[2],&point,face_normals[j]);
+ if(intersecting){
+ distance=distance_squared(p1, point);
+ if(distance<olddistance||firstintersecting==-1){
+ olddistance=distance; firstintersecting=j; if(p)*p=point; end=point; if(normal)*normal = face_normals[j];}
+ }
+ }
+ if(firstintersecting!=-1)return firstintersecting;
+
+ return -1;
+}
+
+//Check a line against the model for collision against front face only
+int Model::lineCheckNoBackface(const vec3& p1,const vec3& p2, vec3* p, vec3 *normal) const {
+ return lineCheck(p1, p2, p, normal, false);
+}
+
+float StaticCalcAverageTriangleEdge(const std::vector<unsigned> &faces, const std::vector<float> &vertices) {
+ float total_vert = 0.0f;
+ int num_sample = 0;
+ for(int i=0, len=faces.size(); i<len; i+=3){
+ const float *vert[3];
+ for(int j=0; j<3; j++) {
+ int vert_index = faces[i+j];
+ vert[j] = &vertices[vert_index*3];
+ }
+ for(int j=0; j<3; j++) {
+ int next = (j+1)%3;
+ float vert_distance = sqrtf( square(vert[j][0]-vert[next][0]) +
+ square(vert[j][1]-vert[next][1]) +
+ square(vert[j][2]-vert[next][2]));
+ if(vert_distance != 0.0f){
+ ++num_sample;
+ total_vert += vert_distance;
+ }
+ }
+ }
+ return total_vert / ((float)num_sample);
+}
+
+void Model::CalcAverageTriangleEdge() {
+ average_triangle_edge_length = StaticCalcAverageTriangleEdge(faces, vertices);
+}
+
+void Model::CopyFacesFromModel( const Model &source_model, const std::vector<int> &copy_faces ) {
+ vbo_loaded = false;
+ vbo_enabled = false;
+
+ int num_faces = copy_faces.size();
+ faces.resize(num_faces*3);
+ face_normals.resize(num_faces);
+
+ for(int i=0; i<num_faces; i++){
+ const int index = i*3;
+ const int copy_index = copy_faces[i]*3;
+ faces[index+0] = source_model.faces[copy_index+0];
+ faces[index+1] = source_model.faces[copy_index+1];
+ faces[index+2] = source_model.faces[copy_index+2];
+
+ face_normals[i] = source_model.face_normals[copy_faces[i]];
+ }
+
+ std::vector<bool> vertex_included(source_model.vertices.size()/3, false);
+ for(int i=0; i<num_faces; i++){
+ const int index = i*3;
+ vertex_included[faces[index+0]] = true;
+ vertex_included[faces[index+1]] = true;
+ vertex_included[faces[index+2]] = true;
+ }
+
+ std::vector<int> vertex_index(source_model.vertices.size()/3);
+ std::vector<int> reverse_vertex_index;
+ {
+ int counter = 0;
+ for(unsigned i=0; i<vertex_index.size(); i++){
+ if(vertex_included[i]){
+ reverse_vertex_index.push_back(i);
+ vertex_index[i] = counter++;
+ }
+ }
+ }
+
+ for(int i=0; i<num_faces; i++){
+ const int index = i*3;
+ faces[index+0] = vertex_index[faces[index+0]];
+ faces[index+1] = vertex_index[faces[index+1]];
+ faces[index+2] = vertex_index[faces[index+2]];
+ }
+
+ int num_vertices = reverse_vertex_index.size();
+
+ vertices.resize(num_vertices*3);
+ normals.resize(num_vertices*3);
+
+ if(!source_model.tangents.empty()){
+ tangents.resize(num_vertices*3);
+ bitangents.resize(num_vertices*3);
+ }
+
+ tex_coords.resize(num_vertices*2);
+ if(!source_model.tex_coords2.empty()){
+ tex_coords2.resize(num_vertices*2);
+ }
+
+ for(int i=0; i<num_vertices; i++){
+ const int index = i*3;
+ const int copy_index = reverse_vertex_index[i]*3;
+ vertices[index+0] = source_model.vertices[copy_index+0];
+ vertices[index+1] = source_model.vertices[copy_index+1];
+ vertices[index+2] = source_model.vertices[copy_index+2];
+
+ normals[index+0] = source_model.normals[copy_index+0];
+ normals[index+1] = source_model.normals[copy_index+1];
+ normals[index+2] = source_model.normals[copy_index+2];
+
+ if(!source_model.tangents.empty()){
+ tangents[index+0] = source_model.tangents[copy_index+0];
+ tangents[index+1] = source_model.tangents[copy_index+1];
+ tangents[index+2] = source_model.tangents[copy_index+2];
+
+ bitangents[index+0] = source_model.bitangents[copy_index+0];
+ bitangents[index+1] = source_model.bitangents[copy_index+1];
+ bitangents[index+2] = source_model.bitangents[copy_index+2];
+ }
+
+ const int tex_index = i*2;
+ const int copy_tex_index = reverse_vertex_index[i]*2;
+
+ tex_coords[tex_index+0] = source_model.tex_coords[copy_tex_index+0];
+ tex_coords[tex_index+1] = source_model.tex_coords[copy_tex_index+1];
+
+ if(!source_model.tex_coords2.empty()){
+ tex_coords2[tex_index+0] = source_model.tex_coords2[copy_tex_index+0];
+ tex_coords2[tex_index+1] = source_model.tex_coords2[copy_tex_index+1];
+ }
+ }
+
+ texel_density = source_model.texel_density;
+ vbo_enabled = true;
+}
+
+void Model::LoadObjMorph(const std::string &rel_path, const std::string &rel_base_path){
+ Dispose();
+ char abs_path[kPathSize];
+ ModID modsource;
+ if( FindFilePath(rel_path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths, true, NULL, &modsource) == -1 ){
+ FatalError("Error", "Could not find morph file: \"%s\"", rel_path.c_str());
+ }
+
+ bool load_model = true;
+
+ size_t read_count = 0;
+ unsigned short checksum = Checksum(abs_path);
+ FILE *cache_file = my_fopen((GetWritePath(modsource) + rel_path + ".mcache").c_str(), "rb");
+ if(cache_file){
+ unsigned short file_checksum = 0;
+ read_count = fread(&file_checksum, sizeof(unsigned short), 1, cache_file);
+ LOG_ASSERT(read_count == 1);
+ unsigned short version;
+ read_count = fread(&version, sizeof(unsigned short), 1, cache_file);
+ LOG_ASSERT(read_count == 1);
+ if(checksum == file_checksum && version == _morph_cache_version){
+ size_t count = 0;
+ read_count = fread(&count, sizeof(int), 1, cache_file);
+ LOG_ASSERT(read_count == 1);
+ ResizeVertices(count);
+ read_count = fread(&vertices[0], sizeof(GLfloat), count*3, cache_file);
+ LOG_ASSERT_EQ(read_count, count*3);
+ read_count = fread(&tex_coords[0], sizeof(GLfloat), count*2, cache_file);
+ LOG_ASSERT_EQ(read_count, count*2);
+ load_model = false;
+ }
+ fclose(cache_file);
+ }
+
+ if (load_model) {
+ // Load base model to get bounding box
+ ObjFileStats obj_stats;
+ GetObjFileStats(rel_base_path, obj_stats, true);
+ TempModel base_model;
+ LoadTempModel(rel_base_path, base_model, obj_stats);
+ GetObjFileStats(rel_path, obj_stats, true);
+ TempModel morph_model;
+ LoadTempModel(rel_path, morph_model, obj_stats);
+
+ for(unsigned i=0; i<base_model.fc*3; ++i){
+ vertices.push_back(base_model.vertices[base_model.vert_indices[i]*3+0]);
+ vertices.push_back(base_model.vertices[base_model.vert_indices[i]*3+1]);
+ vertices.push_back(base_model.vertices[base_model.vert_indices[i]*3+2]);
+ }
+
+ std::vector<GLfloat> old_tex_coords;
+ for(unsigned i=0; i<base_model.fc*3; ++i){
+ old_tex_coords.push_back(base_model.tex_coords[base_model.tex_indices[i]*2+0]);
+ old_tex_coords.push_back(base_model.tex_coords[base_model.tex_indices[i]*2+1]);
+ }
+
+ std::vector<GLfloat> old_vertices = vertices;
+
+ // Load morph vertices and tex coords
+ vertices.clear();
+ tex_coords.clear();
+ for(unsigned i=0; i<base_model.fc*3; ++i){
+ vertices.push_back(morph_model.vertices[morph_model.vert_indices[i]*3+0]);
+ vertices.push_back(morph_model.vertices[morph_model.vert_indices[i]*3+1]);
+ vertices.push_back(morph_model.vertices[morph_model.vert_indices[i]*3+2]);
+ }
+ for(unsigned i=0; i<base_model.fc*3; ++i){
+ tex_coords.push_back(morph_model.tex_coords[morph_model.tex_indices[i]*2+0]);
+ tex_coords.push_back(morph_model.tex_coords[morph_model.tex_indices[i]*2+1]);
+ }
+
+ for(unsigned i=0, len=vertices.size(); i<len; ++i){
+ vertices[i] -= old_vertices[i];
+ }
+ for(unsigned i=0, len=tex_coords.size(); i<len; ++i){
+ tex_coords[i] -= old_tex_coords[i];
+ }
+
+ FILE *cache_file = my_fopen((GetWritePath(modsource) + rel_path + ".mcache").c_str(), "wb");
+ if(cache_file){
+ fwrite(&checksum, sizeof(unsigned short), 1, cache_file);
+ fwrite(&_morph_cache_version, sizeof(unsigned short), 1, cache_file);
+ size_t count = vertices.size()/3;
+ fwrite(&count, sizeof(int), 1, cache_file);
+ fwrite(&vertices[0], sizeof(GLfloat), count*3, cache_file);
+ fwrite(&tex_coords[0], sizeof(GLfloat), count*2, cache_file);
+ fclose(cache_file);
+ }
+ }
+}
+
+namespace {
+ struct VertInfo {
+ static const int NUM_VERT_INFO_ENTRIES = 8;
+ float entries[NUM_VERT_INFO_ENTRIES];
+ int old_id;
+ };
+
+ bool operator<(const VertInfo &a, const VertInfo &b){
+ static const int LEN = VertInfo::NUM_VERT_INFO_ENTRIES - 1;
+ for(int i=0; i<LEN; ++i){
+ if(a.entries[i] != b.entries[i]){
+ return (a.entries[i] < b.entries[i]);
+ }
+ }
+ return (a.entries[LEN] < b.entries[LEN]);
+ }
+
+ bool operator==(const VertInfo &a, const VertInfo &b){
+ for(int i=0; i<VertInfo::NUM_VERT_INFO_ENTRIES; ++i){
+ if(a.entries[i] != b.entries[i]){
+ return false;
+ }
+ }
+ return true;
+ }
+
+ bool operator!=(const VertInfo &a, const VertInfo &b){
+ return !(a==b);
+ }
+} // namespace ""
+
+void Model::RemoveDuplicatedVerts() {
+ if(vertices.empty()){
+ DisplayError("Warning", "Calling RemoveDuplicatedVerts on empty mesh.");
+ return;
+ }
+ // Sort vector of vertices so that identical vertices are neighbors
+ std::vector<VertInfo> vert_info;
+ for(int i=0, vert_index=0, tc_index=0, len=vertices.size(); vert_index<len; ++i, vert_index += 3, tc_index += 2){
+ VertInfo vi;
+ vi.old_id = i;
+ for(int i=0; i<3; ++i){
+ vi.entries[i] = vertices[vert_index+i];
+ }
+ if(use_tangent && !normals.empty()){ // Only check normal info if the model uses tangent
+ for(int i=0; i<3; ++i){
+ vi.entries[i+3] = normals[vert_index+i];
+ }
+ } else {
+ for(int i=0; i<3; ++i){
+ vi.entries[i+3] = 0.0f;
+ }
+ }
+ if(tex_coords.empty() == false) {
+ for(int i=0; i<2; ++i){
+ vi.entries[i+6] = tex_coords[tc_index+i];
+ }
+ }
+ vert_info.push_back(vi);
+ }
+ std::sort(vert_info.begin(), vert_info.end());
+ // Collect a vector of each first unique vertex, and the mapping from each old vertex to new unique vertices
+ std::vector<int> unique;
+ std::vector<int> new_vert(vert_info.size(),-1);
+ for(int i=0, len=vert_info.size(); i<len; ++i){
+ if(i==0 || vert_info[i] != vert_info[i-1]){
+ unique.push_back(i);
+ }
+ new_vert[vert_info[i].old_id] = unique.size()-1;
+ }
+ // Invert to precollapse vert reorder
+ precollapse_vert_reorder.resize(unique.size());
+ for(int i=0, len=unique.size(); i<len; ++i){
+ precollapse_vert_reorder[i] = vert_info[unique[i]].old_id;
+ }
+ for(int i=0, len=faces.size(); i<len; ++i){
+ faces[i] = new_vert[faces[i]];
+ }
+
+ RearrangeVertices(*this, precollapse_vert_reorder);
+
+ LOGI << vert_info.size() - unique.size() << " of " << vert_info.size() << " vertices are duplicates." << std::endl;
+ LOGI << "New vertex size: " << vertices.size()/3 << std::endl;
+}
+
+
+void Model::RemoveDegenerateTriangles() {
+ unsigned count = 0;
+ unsigned index = 0;
+ unsigned copy_index = 0;
+ for(int i=0, len=faces.size()/3; i<len; i++){
+ if(faces[index] == faces[index+1] ||
+ faces[index+1] == faces[index+2] ||
+ faces[index] == faces[index+2])
+ {
+ count++;
+ index += 3;
+ continue;
+ }
+ faces[copy_index] = faces[index];
+ faces[copy_index+1] = faces[index+1];
+ faces[copy_index+2] = faces[index+2];
+ copy_index += 3;
+ index += 3;
+ }
+ faces.resize(copy_index);
+ LOGI << "Removed " << count << " degenerate triangles." << std::endl;
+}
+
+void Model::CopyVertCollapse( const Model &source_model ) {
+ RearrangeVertices(*this, source_model.precollapse_vert_reorder);
+ RearrangeVertices(*this, source_model.optimize_vert_reorder);
+}
+
+struct VertData {
+ int cache_pos;
+ float score;
+ unsigned not_added_triangles;
+};
+
+struct TriData {
+ bool added;
+ float score;
+ unsigned verts[3];
+};
+
+const unsigned _vertex_cache_size = 32;
+const float _fvs_cache_decay_power = 1.5f;
+const float _fvs_last_tri_score = 0.75f;
+const float _fvs_valence_boost_scale = 2.0f;
+const float _fvs_valence_boost_power = 0.5f;
+
+// Based on http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html
+float FindVertexScore ( VertData *vertex_data )
+{
+ if ( vertex_data->not_added_triangles == 0 )
+ {
+ // No tri needs this vertex!
+ return -1.0f;
+ }
+
+ float score = 0.0f;
+ int cache_pos = vertex_data->cache_pos;
+ if ( cache_pos < 0 )
+ {
+ // Vertex is not in FIFO cache - no score.
+ }
+ else
+ {
+ if ( cache_pos < 3 )
+ {
+ // This vertex was used in the last triangle,
+ // so it has a fixed score, whichever of the three
+ // it's in. Otherwise, you can get very different
+ // answers depending on whether you add
+ // the triangle 1,2,3 or 3,1,2 - which is silly.
+ score = _fvs_last_tri_score;
+ }
+ else
+ {
+ assert ( cache_pos < (int)_vertex_cache_size );
+ // Points for being high in the cache.
+ const float scaler = 1.0f / ( _vertex_cache_size - 3 );
+ score = 1.0f - ( cache_pos - 3 ) * scaler;
+ score = pow ( score, _fvs_cache_decay_power );
+ }
+ }
+
+ // Bonus points for having a low number of tris still to
+ // use the vert, so we get rid of lone verts quickly.
+ float ValenceBoost = pow ( (float)(vertex_data->not_added_triangles),
+ -_fvs_valence_boost_power );
+ score += _fvs_valence_boost_scale * ValenceBoost;
+
+ return score;
+}
+
+void Model::PrintACMR(){
+ unsigned cache_hits = 0;
+ unsigned total = 0;
+ std::vector<int> fifo(32, -1);
+ unsigned index = 0;
+ for(unsigned i=0; i<faces.size(); ++i){
+ for(unsigned j=0; j<fifo.size(); ++j){
+ if(fifo[j] == (int)faces[i]){
+ cache_hits++;
+ break;
+ }
+ }
+ fifo[index] = faces[i];
+ index = (index+1)%fifo.size();
+ total++;
+ }
+ LOGI << "Opt: " << cache_hits << " cache hits out of " << total << "." << std::endl;
+ LOGI << "Opt: ACMR: " << ((float)(total-cache_hits))/(float)total*3.0f << std::endl;
+}
+
+void Model::OptimizeTriangleOrder() {
+ std::vector<TriData> tris(faces.size()/3);
+ std::vector<VertData> verts(vertices.size()/3);
+
+ unsigned index = 0;
+ for(int i=0, len=faces.size()/3; i<len; i++){
+ tris[i].verts[0] = faces[index++];
+ tris[i].verts[1] = faces[index++];
+ tris[i].verts[2] = faces[index++];
+ for(unsigned j=0; j<3; ++j){
+ verts[tris[i].verts[j]].not_added_triangles++;
+ }
+ }
+
+ for(int i=0, len=vertices.size()/3; i<len; ++i){
+ verts[i].cache_pos = -1;
+ verts[i].score = FindVertexScore(&verts[i]);
+ }
+
+ int best_triangle = 0;
+ float best_score = 0.0f;
+ for(int i=0, len=faces.size()/3; i<len; i++){
+ tris[i].score = verts[tris[i].verts[0]].score +
+ verts[tris[i].verts[1]].score +
+ verts[tris[i].verts[2]].score;
+ if(tris[i].score > best_score){
+ best_score = tris[i].score;
+ best_triangle = i;
+ }
+ }
+
+ int num_faces = faces.size()/3;;
+ std::vector<int> lru(_vertex_cache_size,-1);
+ std::vector<unsigned> draw_list(num_faces);
+ int draw_list_index = 0;
+ while(draw_list_index < num_faces){
+ draw_list[draw_list_index++] = best_triangle;
+ if(draw_list_index == num_faces){
+ break;
+ }
+ tris[best_triangle].added = true;
+ // Update not_added_triangles
+ for(unsigned i=0; i<3; ++i){
+ unsigned vert_id = tris[best_triangle].verts[i];
+ --verts[vert_id].not_added_triangles;
+ }
+ // Update LRU cache
+ for(unsigned i=0; i<3; ++i){
+ unsigned vert_id = tris[best_triangle].verts[i];
+ int cp = verts[vert_id].cache_pos;
+ // If vert is in cache, remove it and slide other verts down
+ if(cp != -1){
+ for(unsigned j=cp; j<_vertex_cache_size; ++j){
+ if(j == _vertex_cache_size-1){
+ lru[j] = -1;
+ } else {
+ lru[j] = lru[j+1];
+ if(lru[j] != -1){
+ verts[lru[j]].cache_pos--;
+ }
+ }
+ }
+ }
+ // Slide other verts up and add vert to cache
+ for(int j=_vertex_cache_size-1; j>=0; --j){
+ if(lru[j] != -1){
+ verts[lru[j]].cache_pos++;
+ if(j >= (int)_vertex_cache_size-1){
+ verts[lru[j]].cache_pos = -1; //Out of cache entirely
+ }
+ verts[lru[j]].score = FindVertexScore(&verts[lru[j]]);
+ }
+ if(j != 0){
+ lru[j] = lru[j-1];
+ }
+ }
+ lru[0] = vert_id;
+ verts[vert_id].cache_pos = 0;
+ verts[vert_id].score = FindVertexScore(&verts[vert_id]);
+ }
+ // This can be optimized a lot, Forsyth's method just checks triangles
+ // thave have a vertex in the LRU cache
+ best_score = 0.0f;
+ best_triangle = -1;
+ for(int i=0; i<num_faces; ++i){
+ if(tris[i].added){
+ continue;
+ }
+ tris[i].score = verts[tris[i].verts[0]].score +
+ verts[tris[i].verts[1]].score +
+ verts[tris[i].verts[2]].score;
+ if(best_triangle == -1 || tris[i].score > best_score){
+ best_score = tris[i].score;
+ best_triangle = i;
+ }
+ }
+ }
+
+ {
+ unsigned index = 0;
+ for(unsigned i=0; i<draw_list.size(); ++i){
+ faces[index++] = tris[draw_list[i]].verts[0];
+ faces[index++] = tris[draw_list[i]].verts[1];
+ faces[index++] = tris[draw_list[i]].verts[2];
+ }
+ }
+}
+
+
+void Model::OptimizeVertexOrder() {
+ std::vector<int> order(vertices.size()/3, -1);
+ unsigned index = 0;
+ for(unsigned i=0; i<faces.size(); ++i){
+ if(order[faces[i]] == -1){
+ order[faces[i]] = index++;
+ }
+ faces[i] = order[faces[i]];
+ }
+ optimize_vert_reorder.clear();
+ optimize_vert_reorder.resize(index, -1);
+ for(int i=0, len=order.size(); i<len; i++)
+ {
+ if( order[i] >= 0 && order[i] < (int)optimize_vert_reorder.size() )
+ {
+ optimize_vert_reorder[order[i]] = i;
+ }
+ else
+ {
+ LOGW << "Oddity when running " << __FUNCTION__ << ", order[i] is out of bounds, value is " << order[i] << " on index " << i << std::endl;
+ }
+ }
+ RearrangeVertices(*this, optimize_vert_reorder);
+}
+
+struct edge {
+ int vert_id[2];
+};
+
+class TriScoreSorter {
+public:
+ bool operator()(const TriData &a, const TriData &b) {
+ return a.score > b.score;
+ }
+};
+
+void Model::SortTrianglesBackToFront( const vec3 &camera )
+{
+ std::vector<TriData> tris(faces.size()/3);
+
+ unsigned index = 0;
+ for(int i=0, len=faces.size()/3; i<len; ++i){
+ tris[i].added = false;
+ tris[i].score = 0.0f;
+ tris[i].verts[0] = faces[index++];
+ tris[i].verts[1] = faces[index++];
+ tris[i].verts[2] = faces[index++];
+ }
+
+ vec3 center;
+ for(int i=0, len=faces.size()/3; i<len; ++i){
+ center[0] = (vertices[tris[i].verts[0]*3+0] +
+ vertices[tris[i].verts[1]*3+0] +
+ vertices[tris[i].verts[2]*3+0])/3.0f;
+ center[1] = (vertices[tris[i].verts[0]*3+1] +
+ vertices[tris[i].verts[1]*3+1] +
+ vertices[tris[i].verts[2]*3+1])/3.0f;
+ center[2] = (vertices[tris[i].verts[0]*3+2] +
+ vertices[tris[i].verts[1]*3+2] +
+ vertices[tris[i].verts[2]*3+2])/3.0f;
+ tris[i].score = distance_squared(center, camera);
+ }
+
+ std::sort(tris.begin(), tris.end(), TriScoreSorter());
+
+ index = 0;
+ for(unsigned i=0; i<tris.size(); ++i){
+ faces[index++] = tris[i].verts[0];
+ faces[index++] = tris[i].verts[1];
+ faces[index++] = tris[i].verts[2];
+ }
+}
diff --git a/Source/Graphics/model.h b/Source/Graphics/model.h
new file mode 100644
index 00000000..6a2f2d66
--- /dev/null
+++ b/Source/Graphics/model.h
@@ -0,0 +1,165 @@
+//-----------------------------------------------------------------------------
+// Name: model.h
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: This class loads and displays models
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "Math/vec2.h"
+#include "Math/mat3.h"
+
+#include "Graphics/vbocontainer.h"
+#include "Internal/filesystem.h"
+#include "Asset/Asset/image_sampler.h"
+
+#include <opengl.h>
+
+#include <vector>
+#include <stdio.h>
+#include <string>
+
+class Mesh;
+class DrawBatch;
+
+//-----------------------------------------------------------------------------
+// Class Definition
+//-----------------------------------------------------------------------------
+
+enum ModelFlags{
+ _MDL_CENTER = (1<<0),
+ _MDL_SIMPLE = (1<<1),
+ _MDL_FLIP_FACES = (1<<2),
+ _MDL_USE_TANGENT = (1<<3)
+};
+
+const unsigned short _model_cache_version = 41;
+const unsigned short _morph_cache_version = 3;
+
+class Model;
+class vec4;
+
+class Model {
+public:
+ std::vector<GLfloat> vertices;
+ std::vector<GLfloat> normals;
+ std::vector<GLfloat> tangents;
+ std::vector<GLfloat> bitangents;
+ std::vector<vec3> face_normals;
+ std::vector<GLfloat> tex_coords;
+ std::vector<GLfloat> tex_coords2;
+ std::vector<GLfloat> aux;
+ std::vector<GLuint> faces;
+
+ std::vector<vec4> bone_weights;
+ std::vector<vec4> bone_ids;
+ std::vector<std::vector<vec4> > transform_vec;
+ std::vector<vec2> tex_transform_vec;
+ std::vector<vec3> morph_transform_vec;
+
+ std::set<ImageSamplerRef> image_samplers;
+
+ vec3 min_coords, max_coords, center_coords;
+
+ bool vbo_loaded;
+ bool vbo_enabled;
+ unsigned short checksum;
+
+ VBOContainer VBO_vertices;
+ VBOContainer VBO_normals;
+ VBOContainer VBO_tangents;
+ VBOContainer VBO_bitangents;
+ VBOContainer VBO_tex_coords;
+ VBOContainer VBO_tex_coords2;
+ VBOContainer VBO_aux;
+ VBOContainer VBO_faces;
+
+ std::vector<int> optimize_vert_reorder;
+ std::vector<int> precollapse_vert_reorder;
+ int precollapse_num_vertices;
+
+ bool use_tangent;
+ vec3 old_center;
+
+ vec3 bounding_sphere_origin;
+ float bounding_sphere_radius;
+
+ float texel_density;
+ float average_triangle_edge_length;
+
+ std::string path; //Source file. Primarily for debugging purposes.
+ ModID modsource_;
+
+ int lineCheckNoBackface(const vec3& p1,const vec3& p2, vec3* p, vec3 *normal=0) const;
+ int lineCheck(const vec3& p1,const vec3& p2, vec3* p, vec3 *normal=0, bool backface = true) const;
+ void LoadObj(const std::string &name, char flags = _MDL_CENTER, const std::string &alt_name = "", const PathFlags searchPaths = kAnyPath);
+ void RemoveDoubledTriangles();
+ void Draw();
+ void DrawAltTexCoords();
+ void DrawToTextureCoords();
+ void SmoothNormals();
+ void SmoothTangents();
+ void calcFaceNormals();
+ void calcBoundingBox();
+ void calcBoundingSphere();
+ void CenterModel();
+ void calcNormals();
+ void calcTangents();
+ void calcNormals(bool smooth);
+ void createVBO();
+ void ResizeVertices(int size);
+ void ResizeFaces(int size);
+ void WriteToFile(FILE *file);
+ void ReadFromFile(FILE *file);
+ void CalcTexelDensity();
+ void CalcAverageTriangleEdge();
+ void Dispose();
+ bool LoadAuxFromImage(const std::string &image_path);
+
+ Model();
+ Model(const Model &copy);
+ Model& operator=( const Model &copy );
+
+ virtual ~Model();
+
+ void StartPreset();
+ void EndPreset();
+ void DrawPreset();
+ void CopyFacesFromModel( const Model &source_model, const std::vector<int> &faces );
+ void LoadObjMorph( const std::string &name, const std::string &base_name );
+ void CopyVertCollapse( const Model &source_model );
+ void RemoveDuplicatedVerts();
+ void OptimizeTriangleOrder();
+ void SortTrianglesBackToFront(const vec3 &camera);
+ void PrintACMR();
+ void OptimizeVertexOrder();
+ void SaveObj(std::string name);
+ void RemoveDegenerateTriangles();
+
+ const static int ERROR_MORE_THAN_ONE_OBJECT;
+ int SimpleLoadTriangleCutObj(const std::string &name_to_load);
+ const char* GetLoadErrorString(int err);
+};
+void CopyTexCoords2( Model &a, const Model& b );
+
+class Shaders;
+class Graphics;
+void DrawModelVerts(Model& model, const char* vert_attrib_str, Shaders* shaders, Graphics* graphics, int shader);
diff --git a/Source/Graphics/models.cpp b/Source/Graphics/models.cpp
new file mode 100644
index 00000000..9a454fe1
--- /dev/null
+++ b/Source/Graphics/models.cpp
@@ -0,0 +1,235 @@
+//-----------------------------------------------------------------------------
+// Name: models.cpp
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: The models class keeps track of all loaded models
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "models.h"
+
+#include <Internal/common.h>
+#include <Internal/filesystem.h>
+
+#include <Graphics/simplify.hpp>
+#include <Compat/compat.h>
+#include <Logging/logdata.h>
+#include <Utility/waveform_obj_serializer.h>
+#include <Utility/fqms_simplify.hpp>
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable:4244)
+#pragma warning(disable:4291)
+#endif
+
+#ifdef __GNU_LIBRARY__
+#define HAVE_RINT
+#endif
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+//-----------------------------------------------------------------------------
+//Functions
+//-----------------------------------------------------------------------------
+
+void SimplifyModel(std::string name, Model& model, int edge_target) {
+ LOGI << "Simplification - starting..." << std::endl;
+
+ char path[kPathSize];
+ FormatString(path, kPathSize, "%s%slow.obj", GetWritePath(CoreGameModID).c_str(), name.c_str());
+ CreateParentDirs(path);
+
+#ifdef _WIN32
+ createfile(path);
+ string short_path = path;
+ ShortenWindowsPath(short_path);
+ string output_filename = short_path.c_str();
+#else
+ string output_filename = path;
+#endif
+
+ Simplify::vertices.clear();
+ for(int i = 0; i < model.vertices.size(); i+=3) {
+ Simplify::Vertex v;
+ v.p.x = model.vertices[i+0];
+ v.p.y = model.vertices[i+1];
+ v.p.z = model.vertices[i+2];
+ Simplify::vertices.push_back(v);
+ }
+
+ Simplify::triangles.clear();
+ for(int i = 0; i < model.faces.size(); i+=3){
+ Simplify::Triangle t;
+
+ t.v[0] = model.faces[i+0];
+ t.v[1] = model.faces[i+1];
+ t.v[2] = model.faces[i+2];
+ t.deleted = false;
+ t.dirty = false;
+ t.attr = 0UL;
+ t.material = -1;
+ Simplify::triangles.push_back(t);
+ }
+
+ LOGW << "Simplifying terrain mesh, face count in: " << model.faces.size() << std::endl;
+ Simplify::simplify_mesh(edge_target);
+ LOGW << "Simplified terrain mesh, face count out: " << Simplify::triangle_count() << std::endl;
+
+ Simplify::write_obj(output_filename.c_str());
+
+ LOGW << "Saving simplified terrain mesh to \"" << output_filename << "\"" << std::endl;
+}
+
+int Models::CopyModel(int model_id) {
+ int id = AddModel();
+ GetModel(id) = GetModel(model_id);
+ return id;
+}
+
+
+//Load a model and return its ID
+int Models::loadModel(const std::string& rel_path, char flags, const std::string& store_name) {
+ int which_model=-1;
+ const std::string& check_name = store_name.empty()?rel_path:store_name;
+ //Check if the model is already loaded
+ std::map<std::string, int>::iterator iter = name_map.find(check_name);
+ if(iter != name_map.end()){
+ which_model = iter->second;
+ }
+ //If model is not loaded; load it
+ unsigned int i=0;
+ if(which_model==-1){
+ which_model = AddModel();
+ ModelData& md = GetModelData(which_model);
+ //Check extension
+ while(i<rel_path.size()&&rel_path[i]!='.'){
+ i++;
+ }
+ if(rel_path[i+1]=='o'){
+ md.model.LoadObj(rel_path,flags,check_name);
+ }
+ md.name = rel_path;
+ name_map[check_name] = which_model;
+ }
+ return which_model;
+}
+
+//Select which model to draw
+void Models::drawModel(int which_model)
+{
+ _ASSERT(which_model != -1);
+ GetModel(which_model).Draw();
+}
+
+void Models::drawModelAltTexCoords(int which_model)
+{
+ _ASSERT(which_model != -1);
+ GetModel(which_model).DrawAltTexCoords();
+}
+
+//Return the radius of a model
+float Models::Radius(int which_model)
+{
+ return GetModel(which_model).bounding_sphere_radius;
+}
+
+int Models::LoadModelAsMorph( const std::string& rel_path, int base_model_id, const std::string &base_model_name )
+{
+ MorphIndex index;
+ index.first = rel_path;
+ index.second = base_model_id;
+ MorphMap::iterator iter = morph_map_.find(index);
+ if(iter != morph_map_.end()){
+ return iter->second;
+ }
+
+ int morph_model_id = AddModel();
+ Model &morph_model = GetModel(morph_model_id);
+ const std::string &name = base_model_name;
+ morph_model.LoadObjMorph(rel_path, name);
+ GetModelData(morph_model_id).name = std::string(rel_path);
+
+ GetModel(morph_model_id).CopyVertCollapse(GetModel(base_model_id));
+
+ morph_map_[index] = morph_model_id;
+
+ return morph_model_id;
+}
+
+void Models::Dispose()
+{
+ models.clear();
+ name_map.clear();
+ id_map.clear();
+}
+
+void Models::DeleteModel(int id) {
+ IDMap::iterator id_iter = id_map.find(id);
+ if(id_iter == id_map.end()){
+ return;
+ }
+ ModelData *data = id_iter->second;
+ id_map.erase(id_iter);
+ ModelList::iterator model_iter;
+ for(model_iter = models.begin(); model_iter != models.end(); ++model_iter){
+ ModelData& model_data = *model_iter;
+ if(&model_data == data){
+ break;
+ }
+ }
+ if(model_iter != models.end()){
+ NameMap::iterator name_iter = name_map.find(data->name);
+ if(name_iter != name_map.end()){
+ name_map.erase(name_iter);
+ }
+ models.erase(model_iter);
+ }
+}
+
+int Models::AddModel()
+{
+ models.push_back(ModelData());
+ int the_id = next_id++;
+ id_map[the_id] = &models.back();
+ return the_id;
+}
+
+Model& Models::GetModel( int id )
+{
+ return id_map[id]->model;
+}
+
+ModelData& Models::GetModelData( int id )
+{
+ return *id_map[id];
+}
+
+int Models::loadFlippedModel( const std::string& name, char flags /*= 0*/ )
+{
+ flags = flags | _MDL_FLIP_FACES;
+ int id = loadModel(name, flags, name+"_flipped");
+ return id;
+}
+
+int Models::NumModels() {
+ return id_map.size();
+}
diff --git a/Source/Graphics/models.h b/Source/Graphics/models.h
new file mode 100644
index 00000000..1c16642f
--- /dev/null
+++ b/Source/Graphics/models.h
@@ -0,0 +1,81 @@
+//-----------------------------------------------------------------------------
+// Name: models.h
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: The models class keeps track of all loaded models
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Graphics/model.h>
+#include <Graphics/drawbatch.h>
+
+#include <vector>
+#include <string>
+#include <list>
+#include <map>
+
+struct ModelData {
+ Model model;
+ std::string name;
+};
+
+class Models {
+ private:
+ int next_id;
+ typedef std::list<ModelData> ModelList;
+ ModelList models;
+ typedef std::map<std::string, int> NameMap;
+ NameMap name_map;
+ typedef std::map<int, ModelData*> IDMap;
+ IDMap id_map;
+
+ typedef std::pair<std::string, int> MorphIndex;
+ typedef std::map<MorphIndex, int> MorphMap;
+ MorphMap morph_map_;
+
+ public:
+ int AddModel();
+ int getModelGroups(int which);
+ void drawModelGroup(int which, int group);
+ void drawModel(int which);
+ void drawModelAltTexCoords(int which);
+ float Radius(int which);
+ int loadModel(const std::string& name, char flags = 0, const std::string& store_name = "");
+ int loadFlippedModel(const std::string& name, char flags = 0);
+ int CopyModel(int model_id);
+ void Dispose();
+ Model& GetModel(int id);
+ ModelData& GetModelData( int id );
+
+ Models()
+ {}
+
+ static Models* Instance()
+ {
+ static Models instance;
+ return &instance;
+ }
+ int LoadModelAsMorph( const std::string& path, int model_id, const std::string &base_model_name );
+ void DeleteModel(int id);
+ int NumModels();
+};
+
+void SimplifyModel(std::string name, Model& model, int edge_target);
diff --git a/Source/Graphics/modelsurfacewalker.cpp b/Source/Graphics/modelsurfacewalker.cpp
new file mode 100644
index 00000000..f33f6c06
--- /dev/null
+++ b/Source/Graphics/modelsurfacewalker.cpp
@@ -0,0 +1,351 @@
+//-----------------------------------------------------------------------------
+// Name: modelsurfacewalker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "modelsurfacewalker.h"
+
+#include <Math/vec2math.h>
+#include <Math/vec3math.h>
+#include <Logging/logdata.h>
+
+#include <map>
+#include <cfloat>
+#include <cstdlib>
+
+namespace {
+ struct Edge {
+ int points[2];
+
+ bool operator<(const Edge& other) const{
+ if(points[0] < other.points[0]){
+ return true;
+ } else if(points[0] > other.points[0]){
+ return false;
+ }
+ if(points[1] < other.points[1]){
+ return true;
+ } else {
+ return false;
+ }
+ }
+ bool operator==(const Edge& other) const{
+ return (points[0] == other.points[0] && points[1] == other.points[1]);
+ }
+ };
+
+ struct Tri {
+ Edge edges[3];
+ bool edge_swapped[3];
+ };
+
+ bool line_intersect_2d(const vec2 &a, const vec2 &b, const vec2 &c, const vec2 &d, float &t) {
+ float denom = ((d[1] - c[1])*(b[0] - a[0]) - (d[0] - c[0])*(b[1] - a[1]));
+ if(denom == 0.0f){
+ return false;
+ }
+ t = ((d[0] - c[0])*(a[1] - c[1]) - (d[1] - c[1])*(a[0] - c[0]))/denom;
+ return true;
+ }
+
+ void GetVertexDuplicates( const std::vector<GLfloat> &vertices, std::vector<int> &first_dup ) {
+ // For each vertex, store the id of the first vertex with the same position
+ first_dup.resize(vertices.size()/3);
+ std::map<vec3, int> point_map;
+ std::map<vec3, int>::iterator iter;
+ vec3 vec;
+ int index = 0;
+ for(int i=0, len=vertices.size()/3; i<len; i++){
+ vec = vec3(vertices[index+0], vertices[index+1], vertices[index+2]);
+ index += 3;
+ iter = point_map.find(vec);
+ if(iter == point_map.end()){
+ point_map[vec] = i;
+ first_dup[i] = i;
+ } else {
+ first_dup[i] = iter->second;
+ }
+ }
+ }
+} // namespace ""
+
+vec3 bary_coords(const vec2 &p, const vec2 &a, const vec2 &b, const vec2&c) {
+ float denom = (b[1]-c[1])*(a[0]-c[0])+(c[0]-b[0])*(a[1]-c[1]);
+ if(denom == 0.0f){
+ return vec3(0.0f);
+ }
+ vec3 coord;
+ coord[0] = ((b[1]-c[1])*(p[0]-c[0])+(c[0]-b[0])*(p[1]-c[1]))/denom;
+ coord[1] = ((c[1]-a[1])*(p[0]-c[0])+(a[0]-c[0])*(p[1]-c[1]))/denom;
+ coord[2] = 1.0f - coord[0] - coord[1];
+ return coord;
+}
+
+void ModelSurfaceWalker::AttachTo(const std::vector<float> &vertices, const std::vector<unsigned> &faces) {
+ std::vector<int> first_dup;
+ GetVertexDuplicates(vertices, first_dup);
+
+ // Store the edges of each triangle, with the vert ids in ascending order
+ std::vector<Tri> tris(faces.size()/3);
+
+ int index = 0;
+ for(int i=0, len=tris.size(); i<len; ++i){
+ for(int j=0; j<3; ++j) {
+ for(int k=0; k<2; ++k){
+ tris[i].edges[j].points[k] = first_dup[faces[index+(j+k)%3]];
+ }
+ if(tris[i].edges[j].points[0] > tris[i].edges[j].points[1]){
+ std::swap(tris[i].edges[j].points[0], tris[i].edges[j].points[1]);
+ tris[i].edge_swapped[j] = true;
+ } else {
+ tris[i].edge_swapped[j] = false;
+ }
+ }
+ index += 3;
+ }
+
+ // Store a list of the triangles that share each edge
+ std::map<Edge, std::vector<int> > edge_tris;
+ for(int i=0, len=tris.size(); i<len; ++i){
+ edge_tris[tris[i].edges[0]].push_back(i);
+ edge_tris[tris[i].edges[1]].push_back(i);
+ edge_tris[tris[i].edges[2]].push_back(i);
+ }
+
+ // Use the list of triangles for each edge to get a list of neighbors for
+ // each triangle
+ tri_info.resize(tris.size());
+ for(std::map<Edge, std::vector<int> >::iterator iter = edge_tris.begin();
+ iter != edge_tris.end();
+ ++iter)
+ {
+ const Edge& edge = iter->first;
+ const std::vector<int> &faces = iter->second;
+ if(faces.size() == 2){
+ for(unsigned i=0; i<2; ++i){
+ const int &face = faces[i];
+ int which_edge = 0;
+ if(tris[face].edges[1] == edge){
+ which_edge = 1;
+ }
+ if(tris[face].edges[2] == edge){
+ which_edge = 2;
+ }
+ tri_info[face].neighbors[which_edge] = faces[1-i];
+ tri_info[face].edge_swapped[which_edge] = tris[face].edge_swapped[which_edge];
+ }
+ }
+ }
+
+ // Get the length of each edge of each triangle
+ vec3 points[3];
+ int face_index = 0;
+ for(unsigned i=0; i<tri_info.size(); ++i){
+ for(unsigned j=0; j<3; ++j){
+ points[j] = vec3(vertices[faces[face_index]*3+0],
+ vertices[faces[face_index]*3+1],
+ vertices[faces[face_index]*3+2]);
+ ++face_index;
+ }
+ tri_info[i].edge_length[0] = distance(points[0], points[1]);
+ tri_info[i].edge_length[1] = distance(points[1], points[2]);
+ tri_info[i].edge_length[2] = distance(points[2], points[0]);
+ }
+}
+
+int ModelSurfaceWalker::GetNeighbor( int tri, int neighbor ) {
+ return tri_info[tri].neighbors[neighbor];
+}
+
+SWresults ModelSurfaceWalker::Move( SurfaceWalker& sw, vec3* points, const vec3& dir, float drip_dist, std::vector<WalkLine>* trace ) {
+ // Get transformed edges;
+ vec3 edges[3];
+ edges[0] = points[1]-points[0];
+ edges[1] = points[2]-points[1];
+ edges[2] = points[0]-points[2];
+
+ // Use normal and first edge to construct local space
+ vec3 normal = normalize(cross(edges[0], edges[1]));
+ vec3 right = normalize(cross(normal, edges[0]));
+ vec3 up = normalize(edges[0]);
+
+ // Convert points and edges to local space
+ vec2 points_local[3];
+ for(unsigned i=0; i<3; ++i){
+ vec3 temp = points[i]-points[0];
+ points_local[i] = vec2(dot(temp, right),
+ dot(temp, up));
+ }
+ vec2 edge_local[3];
+ for(unsigned i=0; i<3; ++i){
+ edge_local[i] = points_local[(i+1)%3]-points_local[i];
+ }
+
+ // Get inner edge normals
+ vec2 edge_local_normal[3];
+ for(unsigned i=0; i<3; ++i){
+ edge_local_normal[i] = vec2(edge_local[i][1], -edge_local[i][0]);
+ }
+
+ // Calculate surface walker movement in local space
+ vec2 old_pos_local = sw.pos[1] * points_local[1] +
+ sw.pos[2] * points_local[2];
+ vec2 dir_local = normalize(vec2(dot(dir, right), dot(dir, up)));
+
+ // Are we exiting the edge we came in on? If so, run along edge instead
+ bool drip_down_edge = false;
+ if(sw.on_edge != -1 &&
+ dot(dir_local, edge_local_normal[sw.on_edge]) <= 0.0f)
+ {
+ vec2 normalized_edge = normalize(edge_local[sw.on_edge]);
+ dir_local = normalized_edge * dot(normalized_edge, dir_local);
+ drip_down_edge = true;
+ //printf("Drip down edge\n");
+ }
+
+ // Predict drip position
+ vec2 new_pos_local = old_pos_local + dir_local * drip_dist;
+
+ // Check for edge intersections
+ bool edge_intersected[3] = {false, false, false};
+ float edge_intersect_time[3];
+ for(unsigned i=0; i<3; ++i){
+ if(dot(dir_local, edge_local_normal[i]) >= 0.0f)
+ {
+ continue;
+ }
+ bool valid = line_intersect_2d(old_pos_local,
+ new_pos_local,
+ points_local[i],
+ points_local[i] + edge_local[i],
+ edge_intersect_time[i]);
+ if(valid &&
+ edge_intersect_time[i] >= 0.0f &&
+ edge_intersect_time[i] <= 1.0f)
+ {
+ edge_intersected[i] = true;
+ }
+ }
+
+ int cie = -1; // Closest intersected edge
+ float closest_time = FLT_MAX;
+ for(unsigned i=0; i<3; ++i){
+ if(edge_intersected[i] &&
+ (cie == -1 ||
+ edge_intersect_time[i] < closest_time))
+ {
+ cie = i;
+ closest_time = edge_intersect_time[i];
+ }
+ }
+
+ // If intersection occured, update surface walker to move to neighboring
+ // triangle
+ if(cie != -1){
+ if(tri_info[sw.tri].neighbors[cie] == -1){
+ SWresults results;
+ results.dist_moved = 1.0f;
+ return results;
+ }
+ new_pos_local = old_pos_local + dir_local * drip_dist * closest_time;
+ if(drip_down_edge){
+ SWresults results;
+ results.dist_moved = closest_time;
+ float dists[3];
+ for(int i=0; i<3; ++i){
+ dists[i] = distance_squared(new_pos_local, points_local[i]);
+ }
+ if(dists[0] < dists[1] && dists[0] < dists[2]){
+ results.at_point = 0;
+ } else if(dists[1] < dists[2]){
+ results.at_point = 1;
+ } else {
+ results.at_point = 2;
+ }
+ return results;
+ LOGI << "Drip intersection" << std::endl;
+ }
+ trace->resize(trace->size()+1);
+ trace->back().tri = sw.tri;
+ trace->back().start = sw.pos;
+ trace->back().end = bary_coords(new_pos_local, points_local[0], points_local[1], points_local[2]);
+ if(sw.last_tri == tri_info[sw.tri].neighbors[cie]){
+ //printf("Backwards progress\n");
+ }
+ sw.last_tri = sw.tri;
+ sw.tri = tri_info[sw.tri].neighbors[cie];
+ int reverse_edge = 0;
+ bool reverse_edge_swapped = false;
+ for(unsigned i=0; i<3; ++i){
+ if(tri_info[sw.tri].neighbors[i] == sw.last_tri){
+ reverse_edge = i;
+ reverse_edge_swapped = tri_info[sw.tri].edge_swapped[i] !=
+ tri_info[sw.last_tri].edge_swapped[cie];
+ }
+ }
+ vec2 val;
+ val[0] = distance(new_pos_local, points_local[(cie + 1)%3]) /
+ length(edge_local[cie]);
+ val[1] = 1.0f - val[0];
+ sw.pos = vec3(0.0f);
+ sw.on_edge = reverse_edge;
+
+ if(reverse_edge_swapped){
+ sw.pos[reverse_edge] = val[1];
+ sw.pos[(reverse_edge+1)%3] = val[0];
+ } else {
+ sw.pos[reverse_edge] = val[0];
+ sw.pos[(reverse_edge+1)%3] = val[1];
+ }
+ SWresults results;
+ results.dist_moved = closest_time;
+ return results;
+ }
+
+ trace->resize(trace->size()+1);
+ trace->back().tri = sw.tri;
+ trace->back().start = sw.pos;
+ trace->back().end = bary_coords(new_pos_local, points_local[0], points_local[1], points_local[2]);
+ // If no intersection occured, keep moving on current triangle, and convert
+ // local coords back into barycentric space
+ sw.pos = bary_coords(new_pos_local, points_local[0], points_local[1], points_local[2]);
+ SWresults results;
+ results.dist_moved = 1.0f;
+ if(!drip_down_edge){
+ sw.on_edge = -1;
+ }
+ return results;
+}
+
+MSWTriInfo::MSWTriInfo() {
+ neighbors[0] = -1;
+ neighbors[1] = -1;
+ neighbors[2] = -1;
+}
+
+SurfaceWalker::SurfaceWalker():
+ on_edge(-1),
+ delay(0.0f)
+{}
+
+SWresults::SWresults():
+ at_point(-1)
+{}
diff --git a/Source/Graphics/modelsurfacewalker.h b/Source/Graphics/modelsurfacewalker.h
new file mode 100644
index 00000000..e1e66f51
--- /dev/null
+++ b/Source/Graphics/modelsurfacewalker.h
@@ -0,0 +1,73 @@
+//-----------------------------------------------------------------------------
+// Name: modelsurfacewalker.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+#include <Math/vec2.h>
+
+#include <opengl.h>
+
+#include <vector>
+
+struct MSWTriInfo {
+ int neighbors[3];
+ float edge_length[3];
+ bool edge_swapped[3];
+ MSWTriInfo();
+};
+
+struct SurfaceWalker {
+ enum Type {BLOOD, WATER, FIRE};
+ int on_edge;
+ int tri;
+ int last_tri;
+ vec3 pos;
+ float amount;
+ float delay;
+ bool can_drip;
+ Type type;
+ SurfaceWalker();
+};
+
+struct SWresults {
+ float dist_moved;
+ int at_point;
+ SWresults();
+};
+
+struct WalkLine {
+ int tri;
+ vec3 start;
+ vec3 end;
+};
+
+class ModelSurfaceWalker {
+public:
+ std::vector<MSWTriInfo> tri_info; // Neighbors and edge lengths of each triangle
+ void AttachTo(const std::vector<float> &vertices, const std::vector<unsigned> &faces);
+ int GetNeighbor( int tri, int neighbor );
+ SWresults Move( SurfaceWalker& sw, vec3* points, const vec3& dir, float drip_dist, std::vector<WalkLine>* trace );
+};
+
+vec3 bary_coords(const vec2 &p, const vec2 &a, const vec2 &b, const vec2&c);
diff --git a/Source/Graphics/navmeshrenderer.cpp b/Source/Graphics/navmeshrenderer.cpp
new file mode 100644
index 00000000..7965ec78
--- /dev/null
+++ b/Source/Graphics/navmeshrenderer.cpp
@@ -0,0 +1,325 @@
+//-----------------------------------------------------------------------------
+// Name: navmeshrenderer.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "navmeshrenderer.h"
+
+#include <Graphics/camera.h>
+#include <Graphics/graphics.h>
+#include <Graphics/shaders.h>
+#include <Graphics/textures.h>
+
+#include <Math/vec3math.h>
+#include <Math/vec4math.h>
+#include <Math/overgrowth_geometry.h>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <Memory/allocation.h>
+#include <AI/mesh_loader_obj.h>
+#include <Logging/logdata.h>
+
+NavMeshRenderer::NavMeshRenderer() :
+updated_nav_mesh_(false),
+nav_mesh_(NULL),
+nav_mesh_visible_(false),
+collision_mesh_visible_(false)
+{
+}
+
+NavMeshRenderer::~NavMeshRenderer()
+{
+}
+
+void NavMeshRenderer::LoadNavMesh( const NavMesh* _nav_mesh )
+{
+ nav_mesh_ = _nav_mesh;
+ updated_nav_mesh_ = true;
+ //We can't do any OPENGL fill from here because this function might be called from a thread.
+}
+
+void NavMeshRenderer::Draw( )
+{
+ DrawCollision();
+ DrawNavMesh();
+
+ updated_nav_mesh_ = false;
+}
+
+
+void NavMeshRenderer::SetCollisionMeshVisible( bool v )
+{
+ collision_mesh_visible_ = v;
+}
+
+void NavMeshRenderer::SetNavMeshVisible( bool v )
+{
+ nav_mesh_visible_ = v;
+}
+
+bool NavMeshRenderer::IsCollisionMeshVisible()
+{
+ return collision_mesh_visible_;
+}
+
+bool NavMeshRenderer::IsNavMeshVisible()
+{
+ return nav_mesh_visible_;
+}
+
+void NavMeshRenderer::DrawCollision( )
+{
+ if( updated_nav_mesh_ && nav_mesh_ && nav_mesh_->getCollisionMesh() )
+ {
+ const rcMeshLoaderObj* col_mesh = nav_mesh_->getCollisionMesh();
+
+ collision_mesh_vertices_.Fill( kVBODynamic | kVBOFloat, col_mesh->getVertCount() * 3 * sizeof(float), (void*)col_mesh->getVerts());
+
+ GLuint *faces = (GLuint*)OG_MALLOC(col_mesh->getTriCount()*3*sizeof(GLuint));
+
+ for( int i = 0; i < col_mesh->getTriCount()*3; i++ )
+ {
+ faces[i] = (GLuint)col_mesh->getTris()[i];
+ }
+
+ collision_mesh_faces_.Fill( kVBODynamic | kVBOElement, col_mesh->getTriCount() * 3 * sizeof(GLuint), (void*)faces);
+ }
+
+ if( IsCollisionMeshVisible() )
+ {
+ if( collision_mesh_vertices_.valid() && collision_mesh_faces_.valid() )
+ {
+ Shaders* shaders = Shaders::Instance();
+ Camera* cam = ActiveCameras::Get();
+ Graphics* graphics = Graphics::Instance();
+
+ int shader_id = shaders->returnProgram("debug_fill #NAV_COLLISION_MESH", Shaders::kGeometry);
+ shaders->setProgram(shader_id);
+ int vert_attrib_id = shaders->returnShaderAttrib("vert_attrib", shader_id);
+
+ shaders->SetUniformMat4("mvp", cam->GetProjMatrix() * cam->GetViewMatrix());
+ shaders->SetUniformVec3("camera_forward", cam->GetFacing());
+
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ collision_mesh_vertices_.Bind();
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, 0, 0);
+
+ collision_mesh_faces_.Bind();
+ graphics->DrawElements(GL_TRIANGLES, collision_mesh_faces_.size() / sizeof(unsigned), GL_UNSIGNED_INT, 0);
+
+ graphics->ResetVertexAttribArrays();
+ }
+ else
+ {
+ LOGE << "Error" << std::endl;
+ }
+ }
+}
+
+void NavMeshRenderer::DrawNavMesh()
+{
+ //We could make this selective and only render nearby tiles instead of all global ones.
+ if( updated_nav_mesh_ && nav_mesh_)
+ {
+ const dtNavMesh* mesh = nav_mesh_->getNavMesh();
+
+ std::vector<float> data;
+ std::vector<float> edge_data;
+ std::vector<float> thick_edge_data;
+
+ for (int i = 0; i < mesh->getMaxTiles(); ++i)
+ {
+ const dtMeshTile* tile = mesh->getTile(i);
+ if (!tile->header) continue;
+ //dtPolyRef base = mesh->getPolyRefBase(tile);
+ //
+
+ for (int j = 0; j < tile->header->offMeshConCount; j++ )
+ {
+ LOGI << "There is an off mesh con in tile " << i << std::endl;
+ const dtOffMeshConnection* off_mesh_con = &tile->offMeshCons[j];
+
+ if( off_mesh_con )
+ {
+ }
+ }
+
+ for (int j = 0; j < tile->header->polyCount; ++j)
+ {
+ const dtPoly* poly = &tile->polys[j];
+
+ const unsigned int ip = (unsigned int)(poly - tile->polys);
+
+ if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION)
+ {
+ LOGI << "Offmesh connection!" << std::endl;
+ //dtOffMeshConnection* con = &tile->offMeshCons[ip - tile->header->offMeshBase];
+ //Off mesh connections are user defined connections between different vertices, they are signified by two points, creatig a line.
+ }
+ else
+ {
+ const dtPolyDetail* pd = &tile->detailMeshes[ip];
+
+ for (int i = 0; i < pd->triCount; ++i)
+ {
+ const unsigned char* t = &tile->detailTris[(pd->triBase+i)*4];
+ for (int j = 0; j < 3; ++j)
+ {
+ if (t[j] < poly->vertCount)
+ {
+ data.push_back(tile->verts[poly->verts[t[j]]*3]);
+ data.push_back(tile->verts[poly->verts[t[j]]*3+1]);
+ data.push_back(tile->verts[poly->verts[t[j]]*3+2]);
+ }
+ else
+ {
+ data.push_back(tile->detailVerts[(pd->vertBase+t[j]-poly->vertCount)*3]);
+ data.push_back(tile->detailVerts[(pd->vertBase+t[j]-poly->vertCount)*3+1]);
+ data.push_back(tile->detailVerts[(pd->vertBase+t[j]-poly->vertCount)*3+2]);
+ }
+ }
+ }
+
+ for( int i = 1; i < poly->vertCount; i++ )
+ {
+
+ std::vector<float>* dest;
+ //Check if outer edge.
+ if( poly->neis[i-1] == 0 )
+ {
+ dest = &thick_edge_data;
+ }
+ else
+ {
+ dest = &edge_data;
+ }
+
+ dest->push_back(tile->verts[poly->verts[i-1]*3+0]);
+ dest->push_back(tile->verts[poly->verts[i-1]*3+1]);
+ dest->push_back(tile->verts[poly->verts[i-1]*3+2]);
+
+ dest->push_back(tile->verts[poly->verts[i]*3+0]);
+ dest->push_back(tile->verts[poly->verts[i]*3+1]);
+ dest->push_back(tile->verts[poly->verts[i]*3+2]);
+ }
+
+ if( poly->vertCount > 2 )
+ {
+ std::vector<float>* dest;
+ //Check if outer edge.
+ if( poly->neis[poly->vertCount-1] == 0 )
+ {
+ dest = &thick_edge_data;
+ }
+ else
+ {
+ dest = &edge_data;
+ }
+
+ dest->push_back(tile->verts[poly->verts[poly->vertCount-1]*3+0]);
+ dest->push_back(tile->verts[poly->verts[poly->vertCount-1]*3+1]);
+ dest->push_back(tile->verts[poly->verts[poly->vertCount-1]*3+2]);
+
+ dest->push_back(tile->verts[poly->verts[0]*3+0]);
+ dest->push_back(tile->verts[poly->verts[0]*3+1]);
+ dest->push_back(tile->verts[poly->verts[0]*3+2]);
+ }
+ }
+ }
+ }
+
+ if(!data.empty()){
+ navmesh_vertices_.Fill( kVBODynamic | kVBOFloat, data.size() * sizeof(float), (void*)&data[0]);
+ }
+ if(!edge_data.empty()){
+ navmesh_edges_.Fill( kVBODynamic | kVBOFloat, edge_data.size() * sizeof(float), (void*)&edge_data[0] );
+ }
+ if(!thick_edge_data.empty()){
+ navmesh_thick_edges_.Fill( kVBODynamic | kVBOFloat, thick_edge_data.size() * sizeof(float), (void*)&thick_edge_data[0] );
+ }
+ }
+
+ if( IsNavMeshVisible() )
+ {
+ if( navmesh_vertices_.valid() )
+ {
+ Shaders* shaders = Shaders::Instance();
+ Camera* cam = ActiveCameras::Get();
+ Graphics* graphics = Graphics::Instance();
+
+ int shader_id = shaders->returnProgram("debug_fill #STIPPLING");
+ shaders->setProgram(shader_id);
+ int vert_attrib_id = shaders->returnShaderAttrib("vert_attrib", shader_id);
+
+ shaders->SetUniformMat4("mvp", cam->GetProjMatrix() * cam->GetViewMatrix());
+ shaders->SetUniformVec3("close_stipple_color", vec3(0.1f, 0.8f, 1.0f));
+ shaders->SetUniformVec3("far_stipple_color", vec3(0.25f, 0.29f, 0.3f));
+ shaders->SetUniformVec3("camera_forward", cam->GetFacing());
+ shaders->SetUniformVec3("camera_position", cam->GetPos());
+
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ navmesh_vertices_.Bind();
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, 0, 0);
+
+ graphics->DrawArrays(GL_TRIANGLES, 0, navmesh_vertices_.size() / (sizeof(float)*3));
+
+ graphics->ResetVertexAttribArrays();
+ }
+ else
+ {
+ LOGE << "Error" << std::endl;
+ }
+
+ if( navmesh_edges_.valid() || navmesh_thick_edges_.valid() )
+ {
+ Shaders* shaders = Shaders::Instance();
+ Graphics* graphics = Graphics::Instance();
+ Camera* cam = ActiveCameras::Get();
+
+ int shader_id = shaders->returnProgram("3d_color #COLOR_UNIFORM #NO_VELOCITY_BUF");
+ shaders->setProgram(shader_id);
+ int vert_attrib_id = shaders->returnShaderAttrib("vert_attrib", shader_id);
+
+ shaders->SetUniformMat4("mvp", cam->GetProjMatrix() * cam->GetViewMatrix());
+
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ if(navmesh_edges_.valid()){
+ shaders->SetUniformVec4("color_uniform", vec4( 0.1f, 0.1f, 0.1f, 1.0f));
+ navmesh_edges_.Bind();
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, 0, 0);
+ graphics->DrawArrays(GL_LINES, 0, navmesh_edges_.size() / (sizeof(float)*3));
+ }
+
+
+ if(navmesh_thick_edges_.valid()){
+ graphics->SetLineWidth(3);
+ shaders->SetUniformVec4("color_uniform", vec4( 0.0, 0.0, 0.0, 1.0));
+ navmesh_thick_edges_.Bind();
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, 0, 0);
+ graphics->DrawArrays(GL_LINES, 0, navmesh_thick_edges_.size() / (sizeof(float)*3));
+ }
+
+ graphics->ResetVertexAttribArrays();
+ }
+ }
+}
diff --git a/Source/Graphics/navmeshrenderer.h b/Source/Graphics/navmeshrenderer.h
new file mode 100644
index 00000000..13794aee
--- /dev/null
+++ b/Source/Graphics/navmeshrenderer.h
@@ -0,0 +1,60 @@
+//-----------------------------------------------------------------------------
+// Name: navmeshrenderer.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <AI/navmesh.h>
+#include <Graphics/vbocontainer.h>
+
+//Note that this renderer only renders the state which the nav-mesh was in when it was last set.
+//The data will be reloaded if updated_nav_mesh_ flag is set to true.
+
+class NavMeshRenderer {
+public:
+ NavMeshRenderer();
+ ~NavMeshRenderer();
+ void LoadNavMesh( const NavMesh* _navmesh );
+ void Draw();
+
+ void SetCollisionMeshVisible( bool v );
+ void SetNavMeshVisible( bool v );
+
+ bool IsCollisionMeshVisible();
+ bool IsNavMeshVisible();
+
+private:
+ void DrawCollision();
+ void DrawNavMesh();
+
+ bool updated_nav_mesh_;
+ const NavMesh* nav_mesh_;
+
+ VBOContainer collision_mesh_vertices_;
+ VBOContainer collision_mesh_faces_;
+
+ VBOContainer navmesh_vertices_;
+ VBOContainer navmesh_edges_;
+ VBOContainer navmesh_thick_edges_;
+
+ bool nav_mesh_visible_, collision_mesh_visible_;
+};
diff --git a/Source/Graphics/palette.cpp b/Source/Graphics/palette.cpp
new file mode 100644
index 00000000..d66afc83
--- /dev/null
+++ b/Source/Graphics/palette.cpp
@@ -0,0 +1,100 @@
+//-----------------------------------------------------------------------------
+// Name: palette.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "palette.h"
+
+#include <Internal/memwrite.h>
+
+#include <tinyxml.h>
+
+void ReadPaletteFromRAM( OGPalette &palette, const std::vector<char> &data )
+{
+ int index=0;
+ int num_colors;
+ memread(&num_colors, sizeof(int), 1, data, index);
+ palette.resize(num_colors);
+ for(int i=0; i<num_colors; ++i){
+ LabeledColor &lc = palette[i];
+ int str_len;
+ memread(&str_len, sizeof(int), 1, data, index);
+ lc.label.resize(str_len);
+ memread(&lc.label[0], sizeof(char), str_len, data, index);
+ memread(&lc.color, sizeof(vec3), 1, data, index);
+ memread(&lc.channel, sizeof(char), 1, data, index);
+ }
+}
+
+void WritePaletteToRAM( const OGPalette& palette, std::vector<char> &data )
+{
+ int num_colors = palette.size();
+ memwrite(&num_colors, sizeof(int), 1, data);
+ for(int i=0; i<num_colors; ++i){
+ const LabeledColor &lc = palette[i];
+ int str_len = lc.label.size();
+ memwrite(&str_len, sizeof(int), 1, data);
+ memwrite(&lc.label[0], sizeof(char), str_len, data);
+ memwrite(&lc.color, sizeof(vec3), 1, data);
+ memwrite(&lc.channel, sizeof(char), 1, data);
+ }
+}
+
+void WritePaletteToXML( const OGPalette & palette, TiXmlElement* palette_el )
+{
+ for(unsigned i=0; i<palette.size(); ++i){
+ TiXmlElement* color_el = new TiXmlElement("Color");
+ palette_el->LinkEndChild(color_el);
+ const std::string &label = palette[i].label;
+ const vec3 &color = palette[i].color;
+ const char& channel = palette[i].channel;
+ color_el->SetAttribute("label", label.c_str());
+ color_el->SetAttribute("channel", channel);
+ color_el->SetDoubleAttribute("red", color[0]);
+ color_el->SetDoubleAttribute("green", color[1]);
+ color_el->SetDoubleAttribute("blue", color[2]);
+ }
+}
+
+void ReadPaletteFromXML( OGPalette &palette, const TiXmlElement* palette_el )
+{
+ const TiXmlElement* color_el = palette_el->FirstChildElement("Color");
+ while(color_el){
+ LabeledColor lc;
+ vec3 &color = lc.color;
+ std::string &label = lc.label;
+ char &channel = lc.channel;
+ color_el->QueryFloatAttribute("red", &color[0]);
+ color_el->QueryFloatAttribute("green", &color[1]);
+ color_el->QueryFloatAttribute("blue", &color[2]);
+ int channel_int;
+ color_el->QueryIntAttribute("channel", &channel_int);
+ channel = channel_int;
+ const char* c = color_el->Attribute("label");
+ if(c){
+ label = c;
+ }
+ if(!label.empty() && channel < max_palette_elements){
+ palette.push_back(lc);
+ }
+ color_el = color_el->NextSiblingElement();
+ }
+}
diff --git a/Source/Graphics/palette.h b/Source/Graphics/palette.h
new file mode 100644
index 00000000..aa200620
--- /dev/null
+++ b/Source/Graphics/palette.h
@@ -0,0 +1,47 @@
+//-----------------------------------------------------------------------------
+// Name: palette.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+
+#include <string>
+#include <vector>
+
+enum PaletteChannel{_pm_red, _pm_green, _pm_blue, _pm_alpha, _pm_other};
+
+const int max_palette_elements = 5;
+
+struct LabeledColor {
+ std::string label;
+ vec3 color;
+ char channel;
+};
+
+typedef std::vector<LabeledColor> OGPalette;
+
+class TiXmlElement;
+void ReadPaletteFromXML( OGPalette &palette, const TiXmlElement* palette_el );
+void WritePaletteToXML( const OGPalette & palette, TiXmlElement* palette_el );
+void ReadPaletteFromRAM( OGPalette &palette, const std::vector<char> &data );
+void WritePaletteToRAM( const OGPalette& palette, std::vector<char> &data );
diff --git a/Source/Graphics/particles.cpp b/Source/Graphics/particles.cpp
new file mode 100644
index 00000000..dd4f166c
--- /dev/null
+++ b/Source/Graphics/particles.cpp
@@ -0,0 +1,1342 @@
+//-----------------------------------------------------------------------------
+// Name: particles.cpp
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: This class handles particle animation and rendering
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "particles.h"
+
+#include <Graphics/graphics.h>
+#include <Graphics/camera.h>
+#include <Graphics/textures.h>
+#include <Graphics/camera.h>
+#include <Graphics/pxdebugdraw.h>
+#include <Graphics/shaders.h>
+
+#include <Internal/common.h>
+#include <Internal/timer.h>
+#include <Internal/profiler.h>
+
+#include <Objects/movementobject.h>
+#include <Objects/decalobject.h>
+#include <Objects/lightvolume.h>
+
+#include <Physics/physics.h>
+#include <Physics/bulletworld.h>
+
+#include <Math/enginemath.h>
+#include <Math/vec3math.h>
+#include <Math/vec4math.h>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <Online/online_datastructures.h>
+#include <Main/scenegraph.h>
+#include <XML/xml_helper.h>
+#include <Sound/sound.h>
+#include <Asset/Asset/material.h>
+#include <Graphics/sky.h>
+#include <Editors/actors_editor.h>
+#include <Main/engine.h>
+#include <Logging/logdata.h>
+#include <Utility/assert.h>
+
+#include <tinyxml.h>
+
+#include <cassert>
+
+extern std::string script_dir_path;
+extern Timer game_timer;
+extern bool g_no_reflection_capture;
+extern bool g_debug_runtime_disable_gpu_particle_field_draw;
+extern bool g_debug_runtime_disable_particle_draw;
+extern bool g_debug_runtime_disable_particle_system_draw;
+
+//-----------------------------------------------------------------------------
+//Functions
+//-----------------------------------------------------------------------------
+
+bool CParticleCompare(Particle *p1, Particle *p2) {
+ if(p1->particle_type == p2->particle_type){
+ return p1->cam_dist > p2->cam_dist;
+ } else {
+ return p1->particle_type->asset_id < p2->particle_type->asset_id;
+ }
+}
+
+extern bool g_simple_shadows;
+extern bool g_level_shadows;
+extern bool g_no_decals;
+extern bool g_particle_field_simple;
+extern char* global_shader_suffix;
+
+
+TextureAssetRef smoke_texture;
+
+void DrawGPUParticleField(SceneGraph *scenegraph, const char* type) {
+ if (g_debug_runtime_disable_gpu_particle_field_draw) {
+ return;
+ }
+
+ static bool initialized = false;
+
+ if( initialized == false ) {
+ smoke_texture = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/smoke.tga");
+ initialized = true;
+ }
+
+ Graphics* graphics = Graphics::Instance();
+ Shaders* shaders = Shaders::Instance();
+ Camera* cam = ActiveCameras::Get();
+ Textures* textures = Textures::Instance();
+ mat4 proj_view_matrix = cam->GetProjMatrix() * cam->GetViewMatrix();
+ CHECK_GL_ERROR();
+
+ GLState gl_state;
+ gl_state.blend = true;
+ gl_state.depth_test = true;
+ gl_state.depth_write = false;
+ gl_state.cull_face = false;
+ graphics->setGLState(gl_state);
+ graphics->setAdditiveBlend(false);
+ glDisable( GL_SAMPLE_ALPHA_TO_COVERAGE );
+
+ int shader_id;
+ {
+ // Get shader id with flags
+ char shader_name[ParticleType::kMaxNameLen];
+ if(g_particle_field_simple) {
+ FormatString(shader_name, ParticleType::kMaxNameLen, "envobject #GPU_PARTICLE_FIELD #GPU_PARTICLE_FIELD_SIMPLE %s %s", type, global_shader_suffix);
+ } else {
+ FormatString(shader_name, ParticleType::kMaxNameLen, "envobject #GPU_PARTICLE_FIELD %s %s", type, global_shader_suffix);
+ }
+ shader_id = shaders->returnProgram(shader_name);
+ }
+ shaders->setProgram(shader_id);
+ shaders->SetUniformMat4("mvp",proj_view_matrix);
+ shaders->SetUniformMat4("projection_matrix",cam->GetProjMatrix());
+ shaders->SetUniformMat4("view_matrix",cam->GetViewMatrix());
+ shaders->SetUniformFloat("time",game_timer.GetRenderTime());
+ if(Engine::Instance()->paused){
+ shaders->SetUniformFloat("time_scale",0.05f);
+ } else {
+ shaders->SetUniformFloat("time_scale",game_timer.time_scale);
+ }
+
+ shaders->SetUniformFloat("haze_mult", scenegraph->haze_mult);
+
+ textures->bindTexture(smoke_texture->GetTextureRef());
+
+ { // Bind shadow matrices to shader
+ std::vector<mat4> shadow_matrix;
+ shadow_matrix.resize(4);
+ for(int i=0; i<4; ++i){
+ shadow_matrix[i] = cam->biasMatrix * graphics->cascade_shadow_mat[i];
+ }
+ std::vector<mat4> temp_shadow_matrix = shadow_matrix;
+ if(g_simple_shadows || !g_level_shadows){
+ temp_shadow_matrix[3] = cam->biasMatrix * graphics->simple_shadow_mat;
+ }
+ shaders->SetUniformMat4Array("shadow_matrix", temp_shadow_matrix);
+ }
+
+ // Bind shadow texture
+ if(g_simple_shadows || !g_level_shadows){
+ textures->bindTexture(graphics->static_shadow_depth_ref, TEX_SHADOW);
+ } else {
+ textures->bindTexture(graphics->cascade_shadow_depth_ref, TEX_SHADOW);
+ }
+ CHECK_GL_ERROR();
+ shaders->SetUniformVec3("ws_light",scenegraph->primary_light.pos);
+ shaders->SetUniformVec4("primary_light_color",vec4(scenegraph->primary_light.color, scenegraph->primary_light.intensity));
+ shaders->SetUniformVec3("cam_pos",cam->GetPos());
+ shaders->SetUniformVec3("cam_dir",cam->GetFacing());
+ scenegraph->BindLights(shader_id);
+ vec2 viewport_dims;
+ for(int i=0; i<2; ++i){
+ viewport_dims[i] = (float)graphics->viewport_dim[i+2];
+ }
+ shaders->SetUniformVec2("viewport_dims",viewport_dims);
+ CHECK_GL_ERROR();
+
+ textures->bindTexture(scenegraph->sky->GetSpecularCubeMapTexture(), 2);
+ textures->bindTexture(scenegraph->sky->GetSpecularCubeMapTexture(), 3);
+
+ graphics->SetBlendFunc(
+ shaders->GetProgramBlendSrc(shader_id),
+ shaders->GetProgramBlendDst(shader_id));
+
+ textures->bindTexture(graphics->screen_depth_tex, 5);
+ scenegraph->BindLights(shader_id);
+
+ shaders->SetUniformInt("reflection_capture_num", scenegraph->ref_cap_matrix.size());
+ if(!scenegraph->ref_cap_matrix.empty()){
+ assert(!scenegraph->ref_cap_matrix_inverse.empty());
+ shaders->SetUniformMat4Array("reflection_capture_matrix", scenegraph->ref_cap_matrix);
+ shaders->SetUniformMat4Array("reflection_capture_matrix_inverse", scenegraph->ref_cap_matrix_inverse);
+ }
+
+ std::vector<mat4> light_volume_matrix;
+ std::vector<mat4> light_volume_matrix_inverse;
+ for(int i=0, len=scenegraph->light_volume_objects_.size(); i<len; ++i){
+ Object* obj = scenegraph->light_volume_objects_[i];
+ const mat4 &mat = obj->GetTransform();
+ light_volume_matrix.push_back(mat);
+ light_volume_matrix_inverse.push_back(invert(mat));
+ }
+ shaders->SetUniformInt("light_volume_num", light_volume_matrix.size());
+ if(!light_volume_matrix.empty()){
+ assert(!light_volume_matrix_inverse.empty());
+ shaders->SetUniformMat4Array("light_volume_matrix", light_volume_matrix);
+ shaders->SetUniformMat4Array("light_volume_matrix_inverse", light_volume_matrix_inverse);
+ }
+ if( g_no_reflection_capture == false) {
+ textures->bindTexture(scenegraph->cubemaps, 19);
+ }
+ shaders->SetUniformInt("num_tetrahedra", scenegraph->light_probe_collection.ShaderNumTetrahedra());
+ shaders->SetUniformInt("num_light_probes", scenegraph->light_probe_collection.ShaderNumLightProbes());
+ shaders->SetUniformVec3("grid_bounds_min", scenegraph->light_probe_collection.grid_lookup.bounds[0]);
+ shaders->SetUniformVec3("grid_bounds_max", scenegraph->light_probe_collection.grid_lookup.bounds[1]);
+ shaders->SetUniformInt("subdivisions_x", scenegraph->light_probe_collection.grid_lookup.subdivisions[0]);
+ shaders->SetUniformInt("subdivisions_y", scenegraph->light_probe_collection.grid_lookup.subdivisions[1]);
+ shaders->SetUniformInt("subdivisions_z", scenegraph->light_probe_collection.grid_lookup.subdivisions[2]);
+ shaders->SetUniformInt(shaders->GetTexUniform(TEX_AMBIENT_COLOR_BUFFER), TEX_AMBIENT_COLOR_BUFFER);
+ shaders->SetUniformInt(shaders->GetTexUniform(TEX_AMBIENT_GRID_DATA), TEX_AMBIENT_GRID_DATA);
+ if(scenegraph->light_probe_collection.light_probe_buffer_object_id != -1){
+ glBindBuffer(GL_TEXTURE_BUFFER, scenegraph->light_probe_collection.light_probe_buffer_object_id);
+ }
+
+ if(scenegraph->light_probe_collection.light_volume_enabled && scenegraph->light_probe_collection.ambient_3d_tex.valid()){
+ textures->bindTexture(scenegraph->light_probe_collection.ambient_3d_tex, 16);
+ }
+
+ CHECK_GL_ERROR();
+ PROFILER_LEAVE(g_profiler_ctx); // Prepare instanced particle draw
+
+ static VBOContainer data_vbo;
+ if(!data_vbo.valid()){
+ static const GLfloat data[] = {
+ 1, 1, 1, 1, 0,
+ 0, 1, -1, 1, 0,
+ 0, 0, -1, -1, 0,
+ 1, 0, 1, -1, 0
+ };
+ static GLfloat data_vec[20*1000];
+ int val=0;
+ for(int i=0; i<1000; ++i){
+ for(int j=0; j<20; ++j){
+ data_vec[val++] = data[j];
+ }
+ }
+ data_vbo.Fill(kVBOStatic | kVBOFloat,sizeof(data_vec), (void*)data_vec);
+ }
+ static VBOContainer index_vbo;
+ if(!index_vbo.valid()){
+ static const GLuint index[] = {0, 1, 2, 0, 3, 2};
+ static GLuint index_vec[6*1000];
+ int val = 0;
+ int val2 = 0;
+ for(int i=0; i<1000; ++i){
+ for(int j=0; j<6; ++j){
+ index_vec[val2++]=index[j]+val;
+ }
+ val += 4;
+ }
+ index_vbo.Fill(kVBOStatic | kVBOElement, sizeof(index_vec), (void*)index_vec);
+ }
+ CHECK_GL_ERROR();
+ int vert_attrib_id = shaders->returnShaderAttrib("vertex_attrib", shader_id);
+ int tex_coord_attrib_id = shaders->returnShaderAttrib("tex_coord_attrib", shader_id);
+ data_vbo.Bind();
+ CHECK_GL_ERROR();
+ if(vert_attrib_id != -1){
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, 5*sizeof(GLfloat), (const void*)(data_vbo.offset()+2*sizeof(GLfloat)));
+ }
+ CHECK_GL_ERROR();
+ if(tex_coord_attrib_id != -1){
+ graphics->EnableVertexAttribArray(tex_coord_attrib_id);
+ glVertexAttribPointer(tex_coord_attrib_id, 2, GL_FLOAT, false, 5*sizeof(GLfloat), (const void*)data_vbo.offset());
+ }
+ CHECK_GL_ERROR();
+ index_vbo.Bind();
+ CHECK_GL_ERROR();
+ {
+ PROFILER_ZONE(g_profiler_ctx, "glDrawElementsInstanced");
+ graphics->DrawElementsInstanced(GL_TRIANGLES, 6000, GL_UNSIGNED_INT, 0, 1000);
+ }
+ graphics->ResetVertexAttribArrays();
+}
+
+//Draw a particle
+void Particle::Draw(SceneGraph *scenegraph, DrawType draw_type, const mat4& proj_view_matrix) {
+ if (g_debug_runtime_disable_particle_draw) {
+ return;
+ }
+
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Particle::Draw");
+ Graphics* graphics = Graphics::Instance();
+ Shaders* shaders = Shaders::Instance();
+ Camera* cam = ActiveCameras::Get();
+ Textures* textures = Textures::Instance();
+ CHECK_GL_ERROR();
+
+ int shader_id;
+ {
+ // Get shader id with flags
+ char shader_name[ParticleType::kMaxNameLen];
+
+ FormatString(shader_name, ParticleType::kMaxNameLen, "%s %s", particle_type->shader_name, global_shader_suffix);
+
+ shader_id = shaders->returnProgram(shader_name);
+ //LOGI << shader_name << std::endl;
+ }
+
+ shaders->setProgram(shader_id);
+ shaders->SetUniformMat4("mvp",proj_view_matrix);
+ shaders->SetUniformVec4("color_tint",interp_color);
+ shaders->SetUniformFloat("time",game_timer.GetRenderTime());
+
+ shaders->SetUniformFloat("haze_mult", scenegraph->haze_mult);
+
+ if(particle_type->color_map.valid()){
+ textures->bindTexture(particle_type->color_map->GetTextureRef());
+ } else if (ae_reader.valid()){
+ textures->bindTexture(ae_reader.GetTextureAssetRef());
+ }
+
+ if(draw_type == COLOR){
+ { // Bind shadow matrices to shader
+ std::vector<mat4> shadow_matrix;
+ shadow_matrix.resize(4);
+ for(int i=0; i<4; ++i){
+ shadow_matrix[i] = cam->biasMatrix * graphics->cascade_shadow_mat[i];
+ }
+ std::vector<mat4> temp_shadow_matrix = shadow_matrix;
+ if(g_simple_shadows || !g_level_shadows){
+ temp_shadow_matrix[3] = cam->biasMatrix * graphics->simple_shadow_mat;
+ }
+ shaders->SetUniformMat4Array("shadow_matrix", temp_shadow_matrix);
+ }
+
+ // Bind shadow texture
+ if(g_simple_shadows || !g_level_shadows){
+ textures->bindTexture(graphics->static_shadow_depth_ref, TEX_SHADOW);
+ } else {
+ textures->bindTexture(graphics->cascade_shadow_depth_ref, TEX_SHADOW);
+ }
+ CHECK_GL_ERROR();
+ shaders->SetUniformVec3("ws_light",scenegraph->primary_light.pos);
+ shaders->SetUniformVec4("primary_light_color",vec4(scenegraph->primary_light.color, scenegraph->primary_light.intensity));
+ shaders->SetUniformVec3("cam_pos",cam->GetPos());
+ scenegraph->BindLights(shader_id);
+ vec2 viewport_dims;
+ for(int i=0; i<2; ++i){
+ viewport_dims[i] = (float)graphics->viewport_dim[i+2];
+ }
+ shaders->SetUniformVec2("viewport_dims",viewport_dims);
+ CHECK_GL_ERROR();
+
+ if(particle_type->normal_map.valid()){
+ textures->bindTexture(particle_type->normal_map->GetTextureRef(), 1);
+ }
+ textures->bindTexture(scenegraph->sky->GetSpecularCubeMapTexture(), 2);
+ textures->bindTexture(scenegraph->sky->GetSpecularCubeMapTexture(), 3);
+
+ graphics->SetBlendFunc(
+ shaders->GetProgramBlendSrc(shader_id),
+ shaders->GetProgramBlendDst(shader_id));
+
+ textures->bindTexture(graphics->screen_depth_tex, 5);
+ shaders->SetUniformFloat("size",interp_size);
+ scenegraph->BindLights(shader_id);
+
+ shaders->SetUniformInt("reflection_capture_num", scenegraph->ref_cap_matrix.size());
+ if(!scenegraph->ref_cap_matrix.empty()){
+ assert(!scenegraph->ref_cap_matrix_inverse.empty());
+ shaders->SetUniformMat4Array("reflection_capture_matrix", scenegraph->ref_cap_matrix);
+ shaders->SetUniformMat4Array("reflection_capture_matrix_inverse", scenegraph->ref_cap_matrix_inverse);
+ }
+
+ std::vector<mat4> light_volume_matrix;
+ std::vector<mat4> light_volume_matrix_inverse;
+ for(int i=0, len=scenegraph->objects_.size(); i<len; ++i){
+ Object* obj = scenegraph->objects_[i];
+ const mat4 &mat = obj->GetTransform();
+ light_volume_matrix.push_back(mat);
+ light_volume_matrix_inverse.push_back(invert(mat));
+ }
+ shaders->SetUniformInt("light_volume_num", light_volume_matrix.size());
+ if(!light_volume_matrix.empty()){
+ assert(!light_volume_matrix_inverse.empty());
+ shaders->SetUniformMat4Array("light_volume_matrix", light_volume_matrix);
+ shaders->SetUniformMat4Array("light_volume_matrix_inverse", light_volume_matrix_inverse);
+ }
+
+ if( g_no_reflection_capture == false) {
+ textures->bindTexture(scenegraph->cubemaps, 19);
+ }
+ shaders->SetUniformInt("num_tetrahedra", scenegraph->light_probe_collection.ShaderNumTetrahedra());
+ shaders->SetUniformInt("num_light_probes", scenegraph->light_probe_collection.ShaderNumLightProbes());
+ shaders->SetUniformVec3("grid_bounds_min", scenegraph->light_probe_collection.grid_lookup.bounds[0]);
+ shaders->SetUniformVec3("grid_bounds_max", scenegraph->light_probe_collection.grid_lookup.bounds[1]);
+ shaders->SetUniformInt("subdivisions_x", scenegraph->light_probe_collection.grid_lookup.subdivisions[0]);
+ shaders->SetUniformInt("subdivisions_y", scenegraph->light_probe_collection.grid_lookup.subdivisions[1]);
+ shaders->SetUniformInt("subdivisions_z", scenegraph->light_probe_collection.grid_lookup.subdivisions[2]);
+ shaders->SetUniformInt(shaders->GetTexUniform(TEX_AMBIENT_COLOR_BUFFER), TEX_AMBIENT_COLOR_BUFFER);
+ shaders->SetUniformInt(shaders->GetTexUniform(TEX_AMBIENT_GRID_DATA), TEX_AMBIENT_GRID_DATA);
+ if(scenegraph->light_probe_collection.light_probe_buffer_object_id != -1){
+ glBindBuffer(GL_TEXTURE_BUFFER, scenegraph->light_probe_collection.light_probe_buffer_object_id);
+ }
+
+ if(scenegraph->light_probe_collection.light_volume_enabled && scenegraph->light_probe_collection.ambient_3d_tex.valid()){
+ textures->bindTexture(scenegraph->light_probe_collection.ambient_3d_tex, 16);
+ }
+ }
+
+ CHECK_GL_ERROR();
+
+ if(connected.empty()){
+ vec3 up,right;
+ float temp_size;
+ vec3 temp_pos;
+ vec3 particle_facing;
+ if(draw_type == COLOR){
+ vec3 look;
+ look = cam->GetPos() - interp_position;
+ // Don't draw if cam is not facing towards particle
+ if(dot(look,cam->GetFacing())>0.0f){
+ return;
+ }
+ float cam_distance = length(look);
+ if(cam_distance > 0.0f){
+ look /= cam_distance;
+ }
+
+ bool velocity_axis = particle_type->velocity_axis;
+ if(velocity_axis || particle_type->speed_stretch){
+ particle_facing = look;
+ } else {
+ particle_facing = cam->GetFacing();
+ }
+
+ if(velocity_axis){
+ if(particle_type->speed_stretch){
+ up = velocity / sqrtf(length(velocity)) * 2.0f * particle_type->speed_mult;
+ } else {
+ up = normalize(velocity);
+ }
+ } else {
+ up = AngleAxisRotation(cam->GetUpVector(), particle_facing, interp_rotation);
+ }
+
+ right = normalize(cross(up, particle_facing));
+ if(particle_type->min_squash){
+ if(particle_type->speed_stretch){
+ float velocity_length = length(up);
+ if(fabs(dot(normalize(particle_facing),normalize(up)))>1.0f-0.2f/velocity_length/velocity_length){
+ float v = 1.0f-0.2f/velocity_length/velocity_length;
+ if( v < -1.0f ) {
+ v = -1.0f;
+ } else if( v > 1.0f ) {
+ v = 1.0f;
+ }
+
+ up = AngleAxisRotationRadian(particle_facing*velocity_length,right,acosf(v));
+ }
+ }
+ if(!particle_type->speed_stretch && fabs(dot(particle_facing,up))>0.8f){
+ up = AngleAxisRotationRadian(particle_facing,right,acosf(0.8f));
+ }
+ }
+ if(velocity_axis){
+ particle_facing = normalize(cross(up, right));
+ }
+
+ temp_pos = interp_position;
+ temp_size = interp_size;
+
+ float offset = interp_size * 0.5f;
+ temp_pos += look * offset;
+ temp_size *= (cam_distance-offset)/cam_distance;
+ } else {
+ temp_size = interp_size;
+ temp_pos = interp_position;
+ particle_facing = normalize(scenegraph->primary_light.pos);
+ right = cross(particle_facing, vec3(0,-1,0));
+ up = cross(right, particle_facing);
+ }
+
+ vec3 vertices[4];
+ vertices[0]=temp_pos+(up+right)*temp_size;
+ vertices[1]=temp_pos+(up-right)*temp_size;
+ vertices[2]=temp_pos-(up+right)*temp_size;
+ vertices[3]=temp_pos-(up-right)*temp_size;
+
+ GLfloat data[] = {
+ 1, 1, vertices[0][0], vertices[0][1], vertices[0][2], right[0], right[1], right[2], particle_facing[0], particle_facing[1], particle_facing[2],
+ 0, 1, vertices[1][0], vertices[1][1], vertices[1][2], right[0], right[1], right[2], particle_facing[0], particle_facing[1], particle_facing[2],
+ 0, 0, vertices[2][0], vertices[2][1], vertices[2][2], right[0], right[1], right[2], particle_facing[0], particle_facing[1], particle_facing[2],
+ 1, 0, vertices[3][0], vertices[3][1], vertices[3][2], right[0], right[1], right[2], particle_facing[0], particle_facing[1], particle_facing[2],
+ };
+
+ CHECK_GL_ERROR();
+ static VBORingContainer data_vbo(sizeof(data), kVBODynamic | kVBOFloat);
+ data_vbo.Fill(sizeof(data), data);
+ static VBOContainer index_vbo;
+ if(!index_vbo.valid()){
+ static const GLuint index[] = {0, 1, 2, 0, 3, 2};
+ index_vbo.Fill(kVBOStatic | kVBOElement, sizeof(index), (void*)index);
+ }
+ CHECK_GL_ERROR();
+ int vert_attrib_id = shaders->returnShaderAttrib("vertex_attrib", shader_id);
+ int normal_attrib_id = shaders->returnShaderAttrib("normal_attrib", shader_id);
+ int tangent_attrib_id = shaders->returnShaderAttrib("tangent_attrib", shader_id);
+ int tex_coord_attrib_id = shaders->returnShaderAttrib("tex_coord_attrib", shader_id);
+ data_vbo.Bind();
+ CHECK_GL_ERROR();
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ CHECK_GL_ERROR();
+ graphics->EnableVertexAttribArray(tex_coord_attrib_id);
+ CHECK_GL_ERROR();
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, 11*sizeof(GLfloat), (const void*)(data_vbo.offset()+2*sizeof(GLfloat)));
+ CHECK_GL_ERROR();
+ glVertexAttribPointer(tex_coord_attrib_id, 2, GL_FLOAT, false, 11*sizeof(GLfloat), (const void*)(data_vbo.offset()));
+ CHECK_GL_ERROR();
+ if(normal_attrib_id != -1){
+ graphics->EnableVertexAttribArray(normal_attrib_id);
+ glVertexAttribPointer(normal_attrib_id, 3, GL_FLOAT, false, 11*sizeof(GLfloat), (const void*)(data_vbo.offset()+8*sizeof(GLfloat)));
+ }
+ if(tangent_attrib_id != -1){
+ graphics->EnableVertexAttribArray(tangent_attrib_id);
+ glVertexAttribPointer(tangent_attrib_id, 3, GL_FLOAT, false, 11*sizeof(GLfloat), (const void*)(data_vbo.offset()+5*sizeof(GLfloat)));
+ }
+ CHECK_GL_ERROR();
+ index_vbo.Bind();
+ CHECK_GL_ERROR();
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (const void*)index_vbo.offset());
+ CHECK_GL_ERROR();
+ graphics->ResetVertexAttribArrays();
+ CHECK_GL_ERROR();
+ }
+ for(ParticleList::iterator iter = connected.begin(); iter != connected.end(); ++iter) {
+ if(this < (*iter)) {
+ float thickness = interp_size;
+ vec3 a = interp_position;
+ vec3 b = (*iter)->interp_position;
+
+ vec3 up,right,cam_to_particle;
+ vec3 vertices[4];
+
+ if(draw_type == COLOR){
+ cam_to_particle = cam->GetPos() - (a+b)*0.5f;
+ float cam_distance = length(cam_to_particle);
+ if(cam_distance > 0.0f){
+ cam_to_particle /= cam_distance; // Normalize cam_to_particle
+ }
+
+ if(dot(cam_to_particle,cam->GetFacing())>0.0f){
+ return; // Return if particle is behind cam
+ }
+ } else {
+ cam_to_particle = scenegraph->primary_light.pos;
+ }
+
+ vec3 particle_facing = cam_to_particle;
+ up = b-a;
+ right = normalize(cross(normalize(up), particle_facing));
+ vec3 true_facing = normalize(cross(up, right));
+
+ vec3 temp_pos = (a+b)*0.5f;
+
+ vertices[0]=temp_pos+up+right*thickness;
+ vertices[1]=temp_pos+up-right*thickness;
+ vertices[2]=temp_pos-up-right*thickness;
+ vertices[3]=temp_pos-up+right*thickness;
+
+ GLfloat data[] = {
+ 1, 1, vertices[0][0], vertices[0][1], vertices[0][2], right[0], right[1], right[2], true_facing[0], true_facing[1], true_facing[2],
+ 0, 1, vertices[1][0], vertices[1][1], vertices[1][2], right[0], right[1], right[2], true_facing[0], true_facing[1], true_facing[2],
+ 0, 0, vertices[2][0], vertices[2][1], vertices[2][2], right[0], right[1], right[2], true_facing[0], true_facing[1], true_facing[2],
+ 1, 0, vertices[3][0], vertices[3][1], vertices[3][2], right[0], right[1], right[2], true_facing[0], true_facing[1], true_facing[2],
+ };
+
+ CHECK_GL_ERROR();
+ static VBOContainer data_vbo;
+ data_vbo.Fill(kVBODynamic | kVBOFloat, sizeof(data), data);
+ static VBOContainer index_vbo;
+ if(!index_vbo.valid()){
+ static const GLuint index[] = {0, 1, 2, 0, 3, 2};
+ index_vbo.Fill(kVBOStatic | kVBOElement, sizeof(index), (void*)index);
+ }
+ CHECK_GL_ERROR();
+ int vert_attrib_id = shaders->returnShaderAttrib("vertex_attrib", shader_id);
+ int normal_attrib_id = shaders->returnShaderAttrib("normal_attrib", shader_id);
+ int tangent_attrib_id = shaders->returnShaderAttrib("tangent_attrib", shader_id);
+ int tex_coord_attrib_id = shaders->returnShaderAttrib("tex_coord_attrib", shader_id);
+ data_vbo.Bind();
+ CHECK_GL_ERROR();
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ CHECK_GL_ERROR();
+ graphics->EnableVertexAttribArray(tex_coord_attrib_id);
+ CHECK_GL_ERROR();
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, 11*sizeof(GLfloat), (const void*)(2*sizeof(GLfloat)));
+ CHECK_GL_ERROR();
+ glVertexAttribPointer(tex_coord_attrib_id, 2, GL_FLOAT, false, 11*sizeof(GLfloat), 0);
+ CHECK_GL_ERROR();
+ if(normal_attrib_id != -1){
+ graphics->EnableVertexAttribArray(normal_attrib_id);
+ glVertexAttribPointer(normal_attrib_id, 3, GL_FLOAT, false, 11*sizeof(GLfloat), (const void*)(8*sizeof(GLfloat)));
+ }
+ if(tangent_attrib_id != -1){
+ graphics->EnableVertexAttribArray(tangent_attrib_id);
+ glVertexAttribPointer(tangent_attrib_id, 3, GL_FLOAT, false, 11*sizeof(GLfloat), (const void*)(5*sizeof(GLfloat)));
+ }
+ CHECK_GL_ERROR();
+ index_vbo.Bind();
+ CHECK_GL_ERROR();
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ CHECK_GL_ERROR();
+ graphics->ResetVertexAttribArrays();
+ CHECK_GL_ERROR();
+ }
+ }
+ CHECK_GL_ERROR();
+}
+
+//Update particle
+void Particle::Update(SceneGraph *scenegraph, float timestep, float curr_game_time) {
+ old_position = position;
+ old_color = color;
+ old_size = size;
+ old_rotation = rotation;
+
+ if(ae_reader.valid()){
+ ae_reader.Update(timestep);
+ if(ae_reader.Done()){
+ size = 0.0f;
+ color[3] = 0.0f;
+ }
+ }
+
+ position+=velocity*timestep;
+ alive_time+=timestep;
+
+ velocity *= particle_type->inertia;
+ Physics *pi = Physics::Instance();
+ velocity += pi->gravity * particle_type->gravity * timestep;
+ velocity += pi->GetWind(position, curr_game_time, 1.0f) * particle_type->wind * timestep;
+ if(particle_type->quadratic_expansion){
+ size=initial_size * sqrtf(alive_time * particle_type->qe_speed);
+ } else {
+ size-= particle_type->size_decay_rate * timestep;
+ }
+ if(particle_type->quadratic_dispersion){
+ color[3]=square(initial_size/(size+initial_size))*particle_type->qd_mult;
+ color[3]=min(1.0f,color[3])*min((alive_time+0.05f)*10,1.0f)*initial_opacity-0.1f;
+ } else {
+ color[3] = initial_opacity - particle_type->opacity_decay_rate * alive_time;
+ if(alive_time < particle_type->opacity_ramp_time) {
+ color[3] *= alive_time/particle_type->opacity_ramp_time;
+ }
+ }
+ rotation+=timestep*rotate_speed*0.3f/(size/initial_size);
+
+ if(!connected.empty()){
+ has_last_connected = true;
+ last_connected_pos = (*connected.begin())->position;
+ }
+
+ if(particle_type->collision){
+ SimpleRayResultCallbackInfo cb;
+ vec3 point, normal;
+ bool hit = (scenegraph->bullet_world_->CheckRayCollision(
+ old_position,
+ position,
+ &point,
+ &normal) != NULL);
+ vec3 new_pos = hit?point:position;
+ int char_id = -1;
+ if(particle_type->character_collide){
+ char_id = scenegraph->CheckRayCollisionCharacters(
+ old_position,
+ new_pos,
+ &point,
+ &normal,
+ NULL);
+ }
+ if(hit && char_id == -1){
+ //SDL.position = point;
+ //SDL.velocity = reflect(SDL.velocity, normal);
+ if(particle_type->collision_destroy){
+ color[3] = 0.0f;
+ }
+ if (!particle_type->collision_decal.empty() && !collided){
+ // Create decal at collision point
+ EntityDescriptionList desc_list;
+ std::string file_type;
+ Path source;
+ ActorsEditor_LoadEntitiesFromFile(particle_type->collision_decal, desc_list, &file_type, &source);
+ Object* obj = CreateObjectFromDesc(desc_list[0]);
+ if (obj) {
+ obj->permission_flags = obj->permission_flags & ~Object::CAN_SELECT;
+ // align decal up (y+) with surface normal
+ vec3 up = vec3(0.0f, 1.0f, 0.0f);
+ vec3 rot_axis = cross(up, normal);
+ float angle2_cos = dot(up, normal);
+
+ //Sometimes rounding errors in combination to similar vectors cause the dot product to be more than 1.0f
+ //Which results in a NaN when used in the sqrtf later.
+ if( angle2_cos > 1.0f ) {
+ angle2_cos = 1.0f;
+ }
+
+ // constructing quaternion requires half-angle
+ // use trig identities to calculate sin and cos without
+ // actually calculating the angle
+ float angle_cos = sqrtf((1.0f + angle2_cos) / 2.0f);
+ float angle_sin = sqrtf(1.0f - (angle_cos * angle_cos));
+
+ quaternion rot;
+ rot.entries[0] = rot_axis.x() * angle_sin;
+ rot.entries[1] = rot_axis.y() * angle_sin;
+ rot.entries[2] = rot_axis.z() * angle_sin;
+ rot.entries[3] = angle_cos;
+ obj->SetRotation(rot);
+
+ if (has_last_connected){
+ vec3 dir = normalize(velocity);
+ vec3 direction = normal * -1.0f;
+ dir = last_connected_pos - position;
+ float length_dir = length(dir);
+ if (length_dir < 0.01f){
+ // if dir is zero, just construct any vector that is non-collinear with direction
+ // so that we can construct orthogonal u and v properly later
+ if (direction[0] != 0.0f || direction[1] != 0.0f){
+ dir = vec3(-direction[1], direction[0], direction[2]);
+ } else {
+ dir = vec3(direction[0], -direction[2], direction[1]);
+ }
+ }
+ float decal_size = size * particle_type->collision_decal_size_mult;
+ length_dir = min(length_dir, decal_size * 3);
+ // Decal placement position and direction
+ obj->SetTranslation((position + last_connected_pos) * 0.5f);
+ obj->SetScale(vec3(decal_size, length_dir + decal_size, length_dir + decal_size));
+ {
+ vec3 axes[3];
+ axes[1] = normal;
+ axes[2] = normalize(dir);
+ axes[0] = normalize(cross(axes[1], axes[2]));
+ axes[2] = normalize(cross(axes[1], axes[0]));
+ mat4 temp;
+ temp.SetColumn(0, -axes[0]);
+ temp.SetColumn(1, axes[1]);
+ temp.SetColumn(2, axes[2]);
+ obj->SetRotation(QuaternionFromMat4(temp));
+ }
+ } else {
+ obj->SetTranslation(point);
+ obj->SetRotation(obj->GetRotation()*quaternion(vec4(0.0f, 1.0f, 0.0f, RangedRandomFloat(0.0f, PI_f * 2.0f))));
+ obj->SetScale(vec3(size * particle_type->collision_decal_size_mult));
+ }
+ obj->exclude_from_undo = true;
+ obj->exclude_from_save = true;
+
+ DecalObject *dec = static_cast<DecalObject*>(obj);
+
+ if (dec->decal_file_ref->special_type == 2) {
+ // Only blood decals should use user specified color tint
+ const vec3 blood_color = Graphics::Instance()->config_.blood_color();
+ dec->color_tint_component_.tint_ = blood_color;
+ }
+
+ if( scenegraph->AddDynamicDecal(dec) ) {
+ } else {
+ delete dec;
+ }
+ } else {
+ LOGE << "Failed at loading object for particle system." << std::endl;
+ }
+ //DebugDraw::Instance()->AddLine(decal_info.point-decal_info.u * decal_info.
+ /*
+ std::vector<Decal*> decals =
+ scenegraph->decals->makeDecalOnScene(scenegraph, decal_info, NULL, curr_game_time);
+ for(unsigned i=0; i<decals.size(); ++i){
+ scenegraph->decals->AddToDecalList(decals[i]);
+ }
+ */
+ }
+ if(!particle_type->collision_event.empty() && !collided){
+ const MaterialEvent* me = scenegraph->GetMaterialEvent(particle_type->collision_event, point);
+ if(me && !me->soundgroup.empty()){
+ //SoundGroupRef sgr = SoundGroups::Instance()->ReturnRef(me->soundgroup);
+ SoundGroupRef sgr = Engine::Instance()->GetAssetManager()->LoadSync<SoundGroup>(me->soundgroup);
+ SoundGroupPlayInfo sgpi(*sgr, point);
+ sgpi.gain = min(1.0f,length_squared(velocity)*0.2f*size);
+ sgpi.priority = _sound_priority_low;
+ unsigned long handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->PlayGroup(handle,sgpi);
+ }
+ }
+ collided = true;
+ } else if (char_id != -1) {
+ if(particle_type->collision_destroy){
+ color[3] = 0.0f;
+ }
+ if(!particle_type->collision_event.empty() && !collided){
+ Object* o = scenegraph->GetObjectFromID(char_id);
+ const MaterialEvent& me = o->GetMaterialEvent(particle_type->collision_event, point);
+ if(!me.soundgroup.empty()){
+ //SoundGroupRef sgr = SoundGroups::Instance()->ReturnRef(me.soundgroup);
+ SoundGroupRef sgr = Engine::Instance()->GetAssetManager()->LoadSync<SoundGroup>(me.soundgroup);
+ SoundGroupPlayInfo sgpi(*sgr, point);
+ sgpi.gain = min(1.0f,length_squared(velocity)*0.2f*size);
+ sgpi.priority = _sound_priority_low;
+ unsigned long handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->PlayGroup(handle,sgpi);
+ }
+ }
+ if(particle_type->character_add_blood && !collided){
+ MovementObject* mo = (MovementObject*)scenegraph->GetObjectFromID(char_id);
+ mo->rigged_object()->AddBloodAtPoint(point);
+ }
+ collided = true;
+ }
+ }
+
+ if(alive_time == timestep){
+ old_position = position;
+ old_color = color;
+ old_size = size;
+ old_rotation = rotation;
+ }
+}
+
+UniformRingBuffer particle_uniform_buffer;
+
+//Draw all particles
+void ParticleSystem::Draw(SceneGraph *scenegraph) {
+ if (g_debug_runtime_disable_particle_system_draw) {
+ return;
+ }
+
+ Graphics *graphics = Graphics::Instance();
+ CHECK_GL_ERROR();
+ if(!particles.empty() && !graphics->drawing_shadow){
+ Camera* cam = ActiveCameras::Get();
+ GLubyte* blockBuffer = (GLubyte*)alloca(16384);
+ GLState gl_state;
+ gl_state.blend = true;
+ gl_state.depth_test = true;
+ gl_state.depth_write = false;
+ gl_state.cull_face = false;
+ graphics->setGLState(gl_state);
+ glDisable( GL_SAMPLE_ALPHA_TO_COVERAGE );
+
+ vec2 viewport_dims;
+ for(int i=0; i<2; ++i){
+ viewport_dims[i] = (float)graphics->viewport_dim[i+2];
+ }
+
+ static std::vector<mat4> shadow_matrix;
+ shadow_matrix.resize(4);
+ for(int i=0; i<4; ++i){
+ shadow_matrix[i] = cam->biasMatrix * graphics->cascade_shadow_mat[i];
+ }
+ static std::vector<mat4> temp_shadow_matrix;
+ temp_shadow_matrix = shadow_matrix;
+ if(g_simple_shadows || !g_level_shadows){
+ temp_shadow_matrix[3] = cam->biasMatrix * graphics->simple_shadow_mat;
+ }
+
+ PROFILER_ENTER(g_profiler_ctx, "Interpolate particles");
+ float interp = game_timer.GetInterpWeight();
+ for (unsigned i=0, len=particles.size(); i<len; ++i) {
+ Particle& p = *particles[i];
+ p.interp_position = mix(p.old_position, p.position, interp);
+ p.interp_rotation = mix(p.old_rotation, p.rotation, interp);
+ p.interp_size = mix(p.old_size, p.size, interp);
+ p.interp_color = mix(p.old_color, p.color, interp);
+ }
+ PROFILER_LEAVE(g_profiler_ctx); // Interpolate particles
+
+ vec3 cam_pos = ActiveCameras::Get()->GetPos();
+ for (unsigned i=0;i<particles.size();i++){
+ particles[i]->cam_dist=distance_squared(particles[i]->interp_position, cam_pos);
+ }
+ PROFILER_ENTER(g_profiler_ctx, "Sort particles");
+ std::sort(particles.begin(), particles.end(), CParticleCompare);
+ PROFILER_LEAVE(g_profiler_ctx); // Sort particles
+ mat4 proj_view_matrix = cam->GetProjMatrix() * cam->GetViewMatrix();
+ ParticleTypeRef curr_type = particles[0]->particle_type;
+ bool unconnected = particles[0]->connected.empty();
+ unsigned start = 0;
+ PROFILER_ENTER(g_profiler_ctx, "Draw Loop");
+ for (unsigned i=0, len=particles.size(); i<=len; i++){
+ if(i== particles.size() || particles[i]->particle_type != curr_type || particles[i]->connected.empty() != unconnected){
+ ParticleTypeRef particle_type = particles[i-1]->particle_type;
+ if(!particle_type->color_map.valid() || !particles[i-1]->connected.empty()){
+ for(unsigned index=start; index<i; ++index){
+ particles[index]->Draw(scenegraph, Particle::COLOR, proj_view_matrix);
+ }
+ } else {
+ PROFILER_ENTER(g_profiler_ctx, "Prepare instanced particle draw");
+ Graphics* graphics = Graphics::Instance();
+ Shaders* shaders = Shaders::Instance();
+ Camera* cam = ActiveCameras::Get();
+ Textures* textures = Textures::Instance();
+ CHECK_GL_ERROR();
+
+ PROFILER_ENTER(g_profiler_ctx, "Set up shader");
+ PROFILER_ENTER(g_profiler_ctx, "Bind shader");
+ int shader_id;
+ {
+ // Get shader id with flags
+ char shader_name[ParticleType::kMaxNameLen];
+
+ FormatString(shader_name, ParticleType::kMaxNameLen, "%s #INSTANCED %s", particle_type->shader_name, global_shader_suffix);
+ shader_id = shaders->returnProgram(shader_name);
+ //LOGI << shader_name << std::endl;
+ }
+
+ shaders->setProgram(shader_id);
+ PROFILER_LEAVE(g_profiler_ctx);
+ PROFILER_ENTER(g_profiler_ctx, "Uniforms");
+ shaders->SetUniformMat4("mvp",proj_view_matrix);
+ shaders->SetUniformFloat("time",game_timer.GetRenderTime());
+ shaders->SetUniformFloat("haze_mult", scenegraph->haze_mult);
+ { // Bind shadow matrices to shader
+ shaders->SetUniformMat4Array("shadow_matrix", temp_shadow_matrix);
+ }
+ CHECK_GL_ERROR();
+ shaders->SetUniformVec3("ws_light",scenegraph->primary_light.pos);
+ shaders->SetUniformVec4("primary_light_color",vec4(scenegraph->primary_light.color, scenegraph->primary_light.intensity));
+ shaders->SetUniformVec3("cam_pos",cam->GetPos());
+ shaders->SetUniformVec2("viewport_dims",viewport_dims);
+ PROFILER_LEAVE(g_profiler_ctx);
+ PROFILER_ENTER(g_profiler_ctx, "BindLights");
+ scenegraph->BindLights(shader_id);
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ if(scenegraph->light_probe_collection.ShaderNumLightProbes() == 0){
+ shaders->SetUniformInt("light_volume_num", 0);
+ shaders->SetUniformInt("num_tetrahedra", 0);
+ shaders->SetUniformInt("num_light_probes", 0);
+ } else {
+ std::vector<mat4> light_volume_matrix;
+ std::vector<mat4> light_volume_matrix_inverse;
+ for(int i=0, len=scenegraph->light_volume_objects_.size(); i<len; ++i){
+ Object* obj = scenegraph->light_volume_objects_[i];
+ const mat4 &mat = obj->GetTransform();
+ light_volume_matrix.push_back(mat);
+ light_volume_matrix_inverse.push_back(invert(mat));
+ }
+ shaders->SetUniformInt("light_volume_num", light_volume_matrix.size());
+ if(!light_volume_matrix.empty()){
+ assert(!light_volume_matrix_inverse.empty());
+ shaders->SetUniformMat4Array("light_volume_matrix", light_volume_matrix);
+ shaders->SetUniformMat4Array("light_volume_matrix_inverse", light_volume_matrix_inverse);
+ }
+ shaders->SetUniformInt("num_tetrahedra", scenegraph->light_probe_collection.ShaderNumTetrahedra());
+ shaders->SetUniformInt("num_light_probes", scenegraph->light_probe_collection.ShaderNumLightProbes());
+ shaders->SetUniformVec3("grid_bounds_min", scenegraph->light_probe_collection.grid_lookup.bounds[0]);
+ shaders->SetUniformVec3("grid_bounds_max", scenegraph->light_probe_collection.grid_lookup.bounds[1]);
+ shaders->SetUniformInt("subdivisions_x", scenegraph->light_probe_collection.grid_lookup.subdivisions[0]);
+ shaders->SetUniformInt("subdivisions_y", scenegraph->light_probe_collection.grid_lookup.subdivisions[1]);
+ shaders->SetUniformInt("subdivisions_z", scenegraph->light_probe_collection.grid_lookup.subdivisions[2]);
+ shaders->SetUniformInt(shaders->GetTexUniform(TEX_AMBIENT_COLOR_BUFFER), TEX_AMBIENT_COLOR_BUFFER);
+ shaders->SetUniformInt(shaders->GetTexUniform(TEX_AMBIENT_GRID_DATA), TEX_AMBIENT_GRID_DATA);
+ }
+
+ PROFILER_ENTER(g_profiler_ctx, "reflection capture");
+ shaders->SetUniformInt("reflection_capture_num", scenegraph->ref_cap_matrix.size());
+ if(!scenegraph->ref_cap_matrix.empty()){
+ assert(!scenegraph->ref_cap_matrix_inverse.empty());
+ shaders->SetUniformMat4Array("reflection_capture_matrix", scenegraph->ref_cap_matrix);
+ shaders->SetUniformMat4Array("reflection_capture_matrix_inverse", scenegraph->ref_cap_matrix_inverse);
+ }
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ graphics->SetBlendFunc(
+ shaders->GetProgramBlendSrc(shader_id),
+ shaders->GetProgramBlendDst(shader_id));
+
+ CHECK_GL_ERROR();
+ PROFILER_LEAVE(g_profiler_ctx); // "Set up shader"
+
+ PROFILER_ENTER(g_profiler_ctx, "Textures");
+ if(particle_type->color_map.valid()){
+ textures->bindTexture(particle_type->color_map);
+ }
+ if(g_simple_shadows || !g_level_shadows){
+ textures->bindTexture(graphics->static_shadow_depth_ref, TEX_SHADOW);
+ } else {
+ textures->bindTexture(graphics->cascade_shadow_depth_ref, TEX_SHADOW);
+ }
+ if(particle_type->normal_map.valid()){
+ textures->bindTexture(particle_type->normal_map, 1);
+ }
+ textures->bindTexture(scenegraph->sky->GetSpecularCubeMapTexture(), 2);
+ textures->bindTexture(scenegraph->sky->GetSpecularCubeMapTexture(), 3);
+ textures->bindTexture(graphics->screen_depth_tex, 5);
+
+ if( g_no_reflection_capture == false) {
+ textures->bindTexture(scenegraph->cubemaps, 19);
+ }
+ if(scenegraph->light_probe_collection.light_probe_buffer_object_id != -1){
+ glBindBuffer(GL_TEXTURE_BUFFER, scenegraph->light_probe_collection.light_probe_buffer_object_id);
+ }
+ if(scenegraph->light_probe_collection.light_volume_enabled && scenegraph->light_probe_collection.ambient_3d_tex.valid()){
+ textures->bindTexture(scenegraph->light_probe_collection.ambient_3d_tex, 16);
+ }
+ PROFILER_LEAVE(g_profiler_ctx); //"Textures"
+ CHECK_GL_ERROR();
+ PROFILER_LEAVE(g_profiler_ctx); // Prepare instanced particle draw
+
+ int instance_block_index = shaders->GetUBOBindIndex(shader_id, "InstanceInfo");
+ if ((unsigned)instance_block_index != GL_INVALID_INDEX)
+ {
+ const GLchar *names[] = {
+ "instance_color[0]",
+ "instance_transform[0]",
+ // These long names were necessary on a Mac OS 10.7 ATI card
+ "InstanceInfo.instance_color[0]",
+ "InstanceInfo.instance_transform[0]"
+ };
+
+ GLuint indices[2];
+ for(int i=0; i<2; ++i){
+ indices[i] = shaders->returnShaderVariableIndex(names[i], shader_id);
+ if(indices[i] == GL_INVALID_INDEX){
+ indices[i] = shaders->returnShaderVariableIndex(names[i+2], shader_id);
+ }
+ }
+ GLint offset[2];
+ for(int i=0; i<2; ++i){
+ if(indices[i] != GL_INVALID_INDEX){
+ offset[i] = shaders->returnShaderVariableOffset(indices[i], shader_id);
+ }
+ }
+ GLint block_size = shaders->returnShaderBlockSize(instance_block_index, shader_id);
+
+ //TODO: Allocate this memory on a stack_allocator, it's safer, this solution is less predictable and might blow the stack in certain cases, but at least it has a slightly higher chance of running than a statically defined array size.
+ LOG_ASSERT_GT(block_size, 0 );
+ LOG_ASSERT_LT(block_size, 32 * 1024 ); //Assert that the block size doesn't exceed a hefty 32kib
+
+ PROFILER_ENTER(g_profiler_ctx, "Setup uniform block");
+ if(particle_uniform_buffer.gl_id == -1){
+ particle_uniform_buffer.Create(128*1024);
+ }
+ PROFILER_LEAVE(g_profiler_ctx); // Setup uniform block
+
+ vec4* instance_color = (vec4*)((uintptr_t)blockBuffer + offset[0]);
+ mat4* instance_transform = (mat4*)((uintptr_t)blockBuffer + offset[1]);
+
+ const unsigned kBatchSize = 100;
+
+ for(unsigned index=start; index<i; index+=kBatchSize){
+ glUniformBlockBinding(shaders->programs[shader_id].gl_program, instance_block_index, 0);
+ unsigned to_draw = min(kBatchSize, ((int)i-index));
+ PROFILER_ENTER(g_profiler_ctx, "Setup batch data");
+
+ for(unsigned j=0; j<to_draw; ++j){
+ Particle* particle = particles[index+j];
+ mat4 transform;
+ vec3 up,right;
+ float temp_size;
+ vec3 temp_pos;
+ vec3 particle_facing;
+
+ vec3 look;
+ look = cam->GetPos() - particle->interp_position;
+
+ float cam_distance = length(look);
+ if(cam_distance > 0.0f){
+ look /= cam_distance;
+ }
+
+ bool velocity_axis = particle_type->velocity_axis;
+ if(velocity_axis || particle_type->speed_stretch){
+ particle_facing = look;
+ } else {
+ particle_facing = cam->GetFacing();
+ }
+
+ if(velocity_axis){
+ if(particle_type->speed_stretch){
+ up = particle->velocity / sqrtf(length(particle->velocity)) * 2.0f * particle_type->speed_mult;
+ } else {
+ up = normalize(particle->velocity);
+ }
+ } else {
+ up = AngleAxisRotation(cam->GetUpVector(), particle_facing, particle->interp_rotation);
+ }
+
+ right = normalize(cross(up, particle_facing));
+ if(particle_type->min_squash){
+ if(particle_type->speed_stretch){
+ float velocity_length = length(up);
+ if(fabs(dot(normalize(particle_facing),normalize(up)))>1.0f-0.2f/velocity_length/velocity_length){
+ float v = 1.0f-0.2f/velocity_length/velocity_length;
+
+ if( v < -1.0f ) {
+ v = -1.0f;
+ } else if( v > 1.0f ) {
+ v = 1.0f;
+ }
+
+ up = AngleAxisRotationRadian(particle_facing*velocity_length,right,acosf(v));
+ }
+ }
+ if(!particle_type->speed_stretch && fabs(dot(particle_facing,up))>0.8f){
+ up = AngleAxisRotationRadian(particle_facing,right,acosf(0.8f));
+ }
+ }
+ if(velocity_axis){
+ particle_facing = normalize(cross(up, right));
+ }
+
+ temp_pos = particle->interp_position;
+ temp_size = particle->interp_size;
+
+ float offset = particle->interp_size * 0.5f;
+ temp_pos += look * offset;
+ temp_size *= (cam_distance-offset)/cam_distance;
+
+ mat4 translation;
+ translation.SetTranslation(temp_pos);
+ mat4 rotation;
+ rotation.SetColumn(0, right);
+ rotation.SetColumn(1, normalize(up));
+ rotation.SetColumn(2, particle_facing);
+ mat4 scale;
+ scale[0] = temp_size;
+ scale[5] = temp_size*length(up);
+ scale[10] = temp_size;
+ transform = translation * rotation * scale;
+
+ if(indices[0] != GL_INVALID_INDEX){
+ instance_color[j] = particle->interp_color;
+ }
+ if(indices[1] != GL_INVALID_INDEX){
+ instance_transform[j] = transform;
+ }
+ }
+ PROFILER_LEAVE(g_profiler_ctx); // Setup batch data
+ {
+ particle_uniform_buffer.Fill(16384-1, blockBuffer);
+ glBindBuffer( GL_UNIFORM_BUFFER, particle_uniform_buffer.gl_id );
+ glBindBufferRange( GL_UNIFORM_BUFFER, 0, particle_uniform_buffer.gl_id, particle_uniform_buffer.offset, particle_uniform_buffer.next_offset - particle_uniform_buffer.offset);
+ }
+ CHECK_GL_ERROR();
+ {
+ static VBOContainer data_vbo;
+ if(!data_vbo.valid()){
+ static const GLfloat data[] = {
+ 1, 1, 1, 1, 0,
+ 0, 1, -1, 1, 0,
+ 0, 0, -1, -1, 0,
+ 1, 0, 1, -1, 0
+ };
+ data_vbo.Fill(kVBOStatic | kVBOFloat,sizeof(data), (void*)data);
+ }
+ static VBOContainer index_vbo;
+ if(!index_vbo.valid()){
+ static const GLuint index[] = {0, 1, 2, 0, 3, 2};
+ index_vbo.Fill(kVBOStatic | kVBOElement, sizeof(index), (void*)index);
+ }
+ CHECK_GL_ERROR();
+ int vert_attrib_id = shaders->returnShaderAttrib("vertex_attrib", shader_id);
+ int tex_coord_attrib_id = shaders->returnShaderAttrib("tex_coord_attrib", shader_id);
+ data_vbo.Bind();
+ CHECK_GL_ERROR();
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ CHECK_GL_ERROR();
+ graphics->EnableVertexAttribArray(tex_coord_attrib_id);
+ CHECK_GL_ERROR();
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, 5*sizeof(GLfloat), (const void*)(data_vbo.offset()+2*sizeof(GLfloat)));
+ CHECK_GL_ERROR();
+ glVertexAttribPointer(tex_coord_attrib_id, 2, GL_FLOAT, false, 5*sizeof(GLfloat), (const void*)data_vbo.offset());
+ CHECK_GL_ERROR();
+ index_vbo.Bind();
+ CHECK_GL_ERROR();
+ {
+ PROFILER_ZONE(g_profiler_ctx, "glDrawElementsInstanced");
+ graphics->DrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0, to_draw);
+ }
+ graphics->ResetVertexAttribArrays();
+ CHECK_GL_ERROR();
+ }
+ CHECK_GL_ERROR();
+ }
+ }
+ }
+ if(i != particles.size()){
+ curr_type = particles[i]->particle_type;
+ unconnected = particles[i]->connected.empty();
+ start = i;
+ }
+ }
+ }
+ PROFILER_LEAVE(g_profiler_ctx); //"Draw Loop"
+ }
+ CHECK_GL_ERROR();
+}
+
+//Update particles
+void ParticleSystem::Update(SceneGraph *scenegraph, float timestep, float curr_game_time) {
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Particle script update");
+ script_context_.CallScriptFunction(as_funcs.update);
+ }
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Looping through all particles");
+ for(int i=(int)particles.size()-1;i>=0;i--){
+ particles[i]->Update(scenegraph, timestep, curr_game_time);
+ if(particles[i]->color[3]<=0||particles[i]->size<=0)deleteParticle(i);
+ }
+ }
+}
+
+ParticleSystem::ParticleSystem(const ASData& as_data):
+ script_context_("particle_system", as_data), last_id_created(0)
+{
+ Path script_path = FindFilePath(script_dir_path+"particle_system.as", kModPaths | kDataPaths);
+ as_funcs.update = script_context_.RegisterExpectedFunction("void Update()", false);
+ script_context_.LoadScript(script_path);
+}
+
+//Delete particles
+void ParticleSystem::deleteParticle(unsigned int which)
+{
+ if(which<particles.size()){
+ Particle* deleted_particle = particles[which];
+ for(std::list<Particle*>::iterator iter = deleted_particle->connected_from.begin();
+ iter != deleted_particle->connected_from.end();
+ ++iter)
+ {
+ (*iter)->connected.remove(deleted_particle);
+ }
+ for(std::list<Particle*>::iterator iter = deleted_particle->connected.begin();
+ iter != deleted_particle->connected.end();
+ ++iter)
+ {
+ (*iter)->connected_from.remove(deleted_particle);
+ }
+ particle_map.erase(particle_map.find(particles[which]->id));
+ std::swap(particles[which],particles.back());
+ delete particles.back();
+ particles.pop_back();
+ }
+}
+
+unsigned ParticleSystem::CreateID() {
+ ++last_id_created;
+ return last_id_created;
+}
+
+unsigned ParticleSystem::MakeParticle( SceneGraph *scenegraph, const std::string &path, const vec3 &pos, const vec3 &vel, const vec3 &tint) {
+ //ParticleTypeRef particle_type_ref = particle_types->ReturnRef(path);
+ ParticleTypeRef particle_type_ref = Engine::Instance()->GetAssetManager()->LoadSync<ParticleType>(path);
+ Particle *particle = new Particle();
+ if(particle_type_ref->ae_ref.valid()){
+ particle->ae_reader.AttachTo(particle_type_ref->ae_ref);
+ }
+ particle->size = RangedRandomFloat(particle_type_ref->size_range[0], particle_type_ref->size_range[1]) * 0.5f;
+ for(int i=0; i<4; ++i){
+ particle->color[i] = RangedRandomFloat(particle_type_ref->color_range[0][i], particle_type_ref->color_range[1][i]);
+ }
+ particle->position = pos;
+ particle->velocity = vel;
+ for(int i=0; i<3; ++i){
+ particle->color[i] *= tint[i];
+ }
+ particle->particle_type = particle_type_ref;
+ particle->collided = false;
+ particle->alive_time=0.0f;
+ particle->initial_size = particle->size;
+ particle->initial_opacity = particle->color[3];
+ if(particle_type_ref->opacity_ramp_time > 0.0f){
+ particle->color[3] = 0.0f;
+ }
+ if(!particle_type_ref->no_rotation){
+ particle->rotate_speed = RangedRandomFloat(particle_type_ref->rotation_range[0], particle_type_ref->rotation_range[1]);
+ particle->rotation=(float)abs(rand()%720)-360;
+ } else {
+ particle->rotate_speed = 0.0f;
+ particle->rotation = 0.0f;
+ }
+ particle->old_position = particle->position;
+ particle->old_color = particle->color;
+ particle->old_size = particle->size;
+ particle->old_rotation = particle->rotation;
+ particle->has_last_connected = false;
+ int id = CreateID();
+ particle->id = id;
+
+ particles.push_back(particle);
+ particle_map[particle->id] = particle;
+ // Only allow one theora-reading particle at any given time
+ if(particle->ae_reader.valid()){
+ for(int i=(int)particles.size()-2; i>=0; --i){
+ if(particles[i]->ae_reader.valid()){
+ deleteParticle(i);
+ }
+ }
+ }
+ return id;
+}
+
+//Dispose of particle system
+void ParticleSystem::Dispose() {
+ for(ParticleVector::iterator it = particles.begin(); it != particles.end(); ++it){
+ delete (*it);
+ }
+ particles.clear();
+}
+
+void ParticleSystem::ConnectParticles( unsigned a, unsigned b ) {
+ ParticleMap::iterator a_iter = particle_map.find(a);
+ ParticleMap::iterator b_iter = particle_map.find(b);
+ if(a_iter == particle_map.end() || b_iter == particle_map.end()){
+ return;
+ }
+ a_iter->second->connected.push_back(b_iter->second);
+ b_iter->second->connected_from.push_back(a_iter->second);
+}
+
+void ParticleSystem::TintParticle( unsigned id, const vec3& color ) {
+ ParticleMap::iterator iter = particle_map.find(id);
+ if(iter != particle_map.end()){
+ Particle& particle = *(iter->second);
+ particle.color[0] *= color[0];
+ particle.color[1] *= color[1];
+ particle.color[2] *= color[2];
+ particle.old_color = particle.color;
+ }
+}
+
+ParticleSystem::~ParticleSystem() {
+ Dispose();
+}
+
diff --git a/Source/Graphics/particles.h b/Source/Graphics/particles.h
new file mode 100644
index 00000000..680c6dbc
--- /dev/null
+++ b/Source/Graphics/particles.h
@@ -0,0 +1,107 @@
+//-----------------------------------------------------------------------------
+// Name: particles.cpp
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: This class handles particle animation and rendering
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+#include <Math/vec4.h>
+
+#include <Graphics/textureref.h>
+#include <Graphics/vboringcontainer.h>
+
+#include <Asset/assets.h>
+#include <Asset/Asset/animationeffect.h>
+#include <Asset/Asset/particletype.h>
+
+#include <Scripting/angelscript/ascontext.h>
+
+#include <map>
+#include <vector>
+
+class SceneGraph;
+class AnimationEffectSystem;
+
+class Particle {
+public:
+ enum DrawType {DEPTH, COLOR};
+ vec3 position;
+ vec3 old_position;
+ vec3 interp_position;
+ float size;
+ float old_size;
+ float interp_size;
+ vec3 velocity;
+ vec4 color;
+ vec4 old_color;
+ vec4 interp_color;
+ float rotation;
+ float old_rotation;
+ float interp_rotation;
+ AnimationEffectReader ae_reader;
+ unsigned id;
+ float alive_time;
+ float rotate_speed;
+ float cam_dist;
+ float initial_size;
+ float initial_opacity;
+ ParticleTypeRef particle_type;
+ typedef std::list<Particle*> ParticleList;
+ ParticleList connected;
+ ParticleList connected_from;
+ vec3 last_connected_pos;
+ bool has_last_connected;
+ bool collided;
+
+ void Draw(SceneGraph *scenegraph, DrawType draw_type, const mat4& proj_view_matrix);
+ void Update(SceneGraph *scenegraph, float timestep, float curr_game_time);
+};
+
+class ParticleSystem {
+public:
+ AnimationEffectSystem* particle_types;
+ ParticleSystem(const ASData& as_data);
+ ~ParticleSystem();
+ void deleteParticle(unsigned int which);
+ unsigned MakeParticle( SceneGraph *scenegraph, const std::string &path, const vec3 &pos, const vec3 &vel, const vec3 &tint);
+ void Dispose();
+ void Draw(SceneGraph *scenegraph);
+ void Update(SceneGraph *scenegraph, float timestep, float curr_game_time);
+ unsigned CreateID();
+ void ConnectParticles( unsigned a, unsigned b );
+ void TintParticle( unsigned id, const vec3& color );
+
+ ASContext script_context_;
+private:
+ struct {
+ ASFunctionHandle update;
+ } as_funcs;
+
+ unsigned last_id_created;
+ typedef std::vector<Particle*> ParticleVector;
+ ParticleVector particles;
+ typedef std::map<unsigned, Particle*> ParticleMap;
+ ParticleMap particle_map;
+};
+
+void DrawGPUParticleField(SceneGraph *scenegraph, const char* type);
diff --git a/Source/Graphics/pxdebugdraw.cpp b/Source/Graphics/pxdebugdraw.cpp
new file mode 100644
index 00000000..8aadee4e
--- /dev/null
+++ b/Source/Graphics/pxdebugdraw.cpp
@@ -0,0 +1,1143 @@
+//-----------------------------------------------------------------------------
+// Name: pxdebugdraw.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "pxdebugdraw.h"
+
+#include <Graphics/graphics.h>
+#include <Graphics/geometry.h>
+#include <Graphics/camera.h>
+#include <Graphics/textures.h>
+#include <Graphics/shaders.h>
+#include <Graphics/model.h>
+#include <Graphics/Billboard.h>
+#include <Graphics/font_renderer.h>
+#include <Graphics/text.h>
+#include <Graphics/vboringcontainer.h>
+
+#include <Internal/timer.h>
+#include <Internal/profiler.h>
+#include <Internal/common.h>
+
+#include <Math/vec3math.h>
+#include <Math/vec4math.h>
+
+#include <Logging/logdata.h>
+
+#include <set>
+
+extern Timer game_timer;
+
+extern TextAtlas g_text_atlas[kNumTextAtlas];
+extern TextAtlasRenderer g_text_atlas_renderer;
+
+extern bool g_debug_runtime_disable_debug_draw;
+extern bool g_debug_runtime_disable_debug_ribbon_draw;
+
+DebugDraw::~DebugDraw() {
+ Dispose();
+}
+
+void DebugDraw::Dispose() {
+ DebugDrawElementMap::iterator iter = elements.begin();
+ for(;iter != elements.end(); ++iter){
+ DebugDrawElement* element = (*iter).second;
+ delete element;
+ }
+ elements.clear();
+}
+
+void DebugDraw::Draw() {
+ // Can't return early for g_debug_runtime_disable_debug_draw without memory usage ramping up every frame
+
+ bool editor_draw = true;
+ if(Graphics::Instance()->media_mode() ||
+ ActiveCameras::Instance()->Get()->GetFlags() == Camera::kPreviewCamera ||
+ elements.empty())
+ {
+ editor_draw = false;
+ }
+
+ CHECK_GL_ERROR();
+ GLState gl_state;
+ gl_state.blend = true;
+ gl_state.cull_face = false;
+ gl_state.depth_test = true;
+ gl_state.depth_write = false;
+ Graphics::Instance()->setGLState(gl_state);
+
+ if (!g_debug_runtime_disable_debug_draw) {
+ delete_on_update.Draw();
+ delete_on_draw.Draw();
+ }
+ delete_on_draw.verts.clear();
+
+ int old_key = -1;
+ DebugDrawElementMap::iterator iter = elements.begin();
+ for(;iter != elements.end();){
+ DebugDrawElement* element = (*iter).second;
+ CHECK_GL_ERROR();
+ if(!g_debug_runtime_disable_debug_draw && (element->type == kRibbon || editor_draw) && element->visible){
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw element");
+ Graphics::Instance()->setDepthTest(true);
+ element->Draw();
+ }
+ CHECK_GL_ERROR();
+ if(element->GetLifespan() == _delete_on_draw){
+ LOGS << "Removing element" << std::endl;
+ delete element;
+ elements.erase(iter);
+ if(old_key == -1){
+ iter = elements.begin();
+ } else {
+ iter = ++elements.find(old_key);
+ }
+ } else {
+ old_key = (*iter).first;
+ ++iter;
+ }
+ }
+ CHECK_GL_ERROR();
+}
+
+void DebugDraw::Update(float timestep) {
+ delete_on_update.verts.clear();
+ int old_key = -1;
+ DebugDrawElementMap::iterator iter = elements.begin();
+ for(;iter != elements.end();){
+ DebugDrawElement* element = (*iter).second;
+ if(element->GetLifespan() == _fade){
+ element->fade_amount += timestep;
+ }
+ bool to_delete = element->GetLifespan() == _delete_on_update ||
+ element->fade_amount >= 1.0f;
+ if(to_delete){
+ delete element;
+ elements.erase(iter);
+ if(old_key == -1){
+ iter = elements.begin();
+ } else {
+ iter = ++elements.find(old_key);
+ }
+ } else {
+ old_key = (*iter).first;
+ ++iter;
+ }
+ }
+}
+
+void DebugDraw::Remove( int which )
+{
+ LOGS << "Erasing element" << std::endl;
+ DebugDrawElementMap::iterator iter;
+ iter = elements.find(which);
+ if(iter != elements.end()){
+ free_ids.push((*iter).first);
+ DebugDrawElement* element = (*iter).second;
+ delete element;
+ elements.erase(iter);
+ }
+}
+
+int DebugDraw::AddElement( DebugDrawElement* element,
+ const DDLifespan lifespan)
+{
+ LOGS << "Adding element" << std::endl;
+ int new_id;
+ if(free_ids.empty()){
+ new_id = id_index++;
+ } else {
+ new_id = free_ids.front();
+ free_ids.pop();
+ }
+ element->SetLifespan(lifespan);
+ elements.insert(std::pair<int, DebugDrawElement*> (new_id, element));
+ return new_id;
+}
+
+DebugDraw::DebugDraw():
+ id_index(0)
+{}
+
+int DebugDraw::AddLine( const vec3 &start,
+ const vec3 &end,
+ const vec4 &start_color,
+ const vec4 &end_color,
+ const DDLifespan lifespan,
+ const DDFlag& flags)
+{
+ if(lifespan == _delete_on_update && flags == _DD_NO_FLAG){
+ size_t start_count = delete_on_update.verts.size();
+ delete_on_update.verts.resize(start_count + 14);
+ memcpy(&delete_on_update.verts[start_count + 0], &start[0], sizeof(vec3));
+ memcpy(&delete_on_update.verts[start_count + 3], &start_color[0], sizeof(vec4));
+ memcpy(&delete_on_update.verts[start_count + 7], &end[0], sizeof(vec3));
+ memcpy(&delete_on_update.verts[start_count + 10], &end_color[0], sizeof(vec4));
+ return -1;
+ } else if(lifespan == _delete_on_draw && flags == _DD_NO_FLAG){
+ size_t start_count = delete_on_draw.verts.size();
+ delete_on_draw.verts.resize(start_count + 14);
+ memcpy(&delete_on_draw.verts[start_count + 0], &start[0], sizeof(vec3));
+ memcpy(&delete_on_draw.verts[start_count + 3], &start_color[0], sizeof(vec4));
+ memcpy(&delete_on_draw.verts[start_count + 7], &end[0], sizeof(vec3));
+ memcpy(&delete_on_draw.verts[start_count + 10], &end_color[0], sizeof(vec4));
+ return -1;
+ } else {
+ DebugDrawElement* element = new DebugDrawLine(start, end, start_color, end_color, flags);
+ return AddElement(element, lifespan);
+ }
+}
+
+int DebugDraw::AddLine( const vec3 &start,
+ const vec3 &end,
+ const vec4 &color,
+ const DDLifespan lifespan,
+ const DDFlag& flags)
+{
+ // sync_attach_3 vore coolt att skicka över detta
+ // om vi kommer från sync_attach_1
+ return AddLine(start,end,color,color,lifespan,flags);
+}
+
+
+int DebugDraw::AddRibbon(const vec3 &start, const vec3 &end, const vec4 &start_color, const vec4 &end_color, const float start_width, const float end_width, const DDLifespan lifespan, const DDFlag& flags /*= _DD_NO_FLAG*/) {
+ DebugDrawElement* element = new DebugDrawRibbon(start, end, start_color, end_color, start_width, end_width, flags);
+ return AddElement(element, lifespan);
+}
+
+int DebugDraw::AddRibbon(const DDLifespan lifespan, const DDFlag& flags /*= _DD_NO_FLAG*/) {
+ DebugDrawElement* element = new DebugDrawRibbon(flags);
+ return AddElement(element, lifespan);
+}
+
+int DebugDraw::AddPoint( const vec3 &point,
+ const vec4 &color,
+ const DDLifespan lifespan,
+ const DDFlag& flags)
+{
+ DebugDrawElement* element = new DebugDrawPoint(point, color, flags);
+ return AddElement(element, lifespan);
+}
+
+int DebugDraw::AddWireSphere( const vec3 &position,
+ const float radius,
+ const vec4 &color,
+ const DDLifespan lifespan )
+{
+ if(radius == 0.0f){
+ DisplayError("Error", "Creating debug draw sphere with radius zero");
+ }
+ DebugDrawElement* element = new DebugDrawWireSphere(position, radius, vec3(1.0f), color);
+ return AddElement(element, lifespan);
+}
+
+int DebugDraw::AddText( const vec3 &position, const std::string &content, const float& scale, const DDLifespan lifespan, const DDFlag& flags, const vec4& color )
+{
+ DebugDrawElement* element = new DebugDrawText(position, scale, content, flags, color);
+ return AddElement(element, lifespan);
+}
+
+bool DebugDraw::SetPosition( int id, const vec3 &position )
+{
+ DebugDrawElement* element = GetElement( id );
+ if( element ) {
+ return element->SetPosition(position);
+ } else {
+ LOGE << "No element with id " << id << std::endl;
+ return false;
+ }
+}
+
+bool DebugDraw::SetVisible(int id, bool visible)
+{
+ DebugDrawElement* element = GetElement( id );
+ if( element ) {
+ element->visible = visible;
+ return true;
+ } else {
+ LOGE << "No element with id " << id << std::endl;
+ return false;
+ }
+}
+
+DebugDrawElement* DebugDraw::GetElement( int id )
+{
+ DebugDrawElementMap::iterator it = elements.find(id);
+
+ if( it != elements.end() ) {
+ return it->second;
+ } else {
+ return NULL;
+ }
+}
+
+int DebugDraw::AddWireBox( const vec3 &position, const vec3 &dimensions, const vec4 &color, const DDLifespan lifespan )
+{
+ DebugDrawElement* element = new DebugDrawWireBox(position,
+ dimensions,
+ color);
+ return AddElement(element, lifespan);
+}
+
+int DebugDraw::AddWireCylinder( const vec3 &position, const float radius, const float height, const vec4 &color, const DDLifespan lifespan )
+{
+ DebugDrawElement* element = new DebugDrawWireCylinder(position,
+ radius,
+ height,
+ color);
+ return AddElement(element, lifespan);
+}
+
+int DebugDraw::AddWireScaledSphere( const vec3 &position, const float radius, const vec3 &scale, const vec4 &color, const DDLifespan lifespan )
+{
+ DebugDrawElement* element = new DebugDrawWireSphere(position, radius, scale, color);
+ return AddElement(element, lifespan);
+}
+
+int DebugDraw::AddWireMesh( const std::string &path, const mat4 &transform, const vec4 &color, const DDLifespan lifespan )
+{
+ RC_VBOContainer vbo;
+ std::vector<vec3> vertices;
+
+ std::map<std::string, RC_VBOContainer>::iterator me_iter = mesh_edges_map.find(path);
+
+ if(me_iter == mesh_edges_map.end())
+ {
+ Model model;
+ model.LoadObj(path, _MDL_SIMPLE);
+ model.RemoveDuplicatedVerts();
+
+
+ for(unsigned i=0; i < (model.faces.size()/3)*3; i +=3)
+ {
+ for( unsigned j=0; j < 3; j++ )
+ {
+ int f = model.faces[i+j];
+ int s = model.faces[i+((j+1)%3)];
+
+ vertices.push_back(vec3(model.vertices[f*3+0],
+ model.vertices[f*3+1],
+ model.vertices[f*3+2]));
+ vertices.push_back(vec3(model.vertices[s*3+0],
+ model.vertices[s*3+1],
+ model.vertices[s*3+2]));
+ }
+ }
+
+ //One of the few cases in this codebase where kVBOStream makes sense
+ //(Creating the buffer, filling it once, reading a couple of times then destroying, as per spec)
+ vbo->Fill( kVBOFloat | kVBOStream, vertices.size() * sizeof(vertices[0]), &vertices[0] );
+
+ mesh_edges_map[path] = vbo;
+ }
+ else
+ {
+ vbo = me_iter->second;
+ }
+
+ DebugDrawElement* element = new DebugDrawWireMesh(vbo, transform, color);
+
+ return AddElement(element, lifespan);
+}
+
+int DebugDraw::AddLineObject(const RC_VBOContainer &vbo,
+ const mat4 &transform,
+ const vec4 &color,
+ const DDLifespan lifespan)
+{
+ DebugDrawElement* element = new DebugDrawWire( vbo, transform, color, _DD_NO_FLAG );
+ return AddElement(element,lifespan);
+}
+
+int DebugDraw::AddStippleMesh(const RC_VBOContainer &vbo,
+ const mat4 &transform,
+ const vec4 &color,
+ const DDLifespan lifespan)
+{
+ DebugDrawElement* element = new DebugDrawStippleMesh(vbo, transform, color, _DD_NO_FLAG);
+ return AddElement(element, lifespan);
+}
+
+int DebugDraw::AddLines( const std::vector<float> &vertices, const std::vector<unsigned> &indices, const vec4 &color, const DDLifespan lifespan, const DDFlag& flags ) {
+ DebugDrawElement* element = new DebugDrawLines(vertices, indices, color, flags);
+ return AddElement(element, lifespan);
+}
+
+int DebugDraw::AddLines( const std::vector<vec3> &vertices, const vec4 &color, const DDLifespan lifespan, const DDFlag& flags ) {
+ DebugDrawElement* element = new DebugDrawLines(vertices, color, flags);
+ return AddElement(element, lifespan);
+}
+
+int DebugDraw::AddCircle( const mat4 &transform, const vec4 &color, const DDLifespan lifespan, const DDFlag& flags ) {
+ DebugDrawElement* element = new DebugDrawCircle(transform, color, flags);
+ return AddElement(element, lifespan);
+}
+
+int DebugDraw::AddBillboard(const TextureRef &ref, const vec3 &position, float scale, const vec4& color, AlphaMode mode, const DDLifespan lifespan) {
+ DebugDrawElement* element = new DebugDrawBillboard(ref, position, scale, color, mode);
+ return AddElement(element, lifespan);
+}
+
+int DebugDraw::AddTransformedWireScaledSphere( const mat4 &transform, const vec4 &color, const DDLifespan lifespan ) {
+
+ DebugDrawWireSphere* element = new DebugDrawWireSphere(transform, color);
+ return AddElement((DebugDrawElement*)element, lifespan);
+}
+
+DebugDrawLine::DebugDrawLine(
+ const vec3 &start,
+ const vec3 &end,
+ const vec4 &start_color,
+ const vec4 &end_color,
+ const DDFlag& _flags ) :
+DebugDrawWire((DDFlag)(_flags | _DD_COLOR))
+{
+
+ if( _flags & _DD_DASHED )
+ {
+ const float segment_length = 0.2f;
+ std::vector<GLfloat> line_data;
+
+ vec3 diff = end - start;
+ vec3 dir = normalize(diff);
+ float len2 = length_squared(diff);
+
+ vec3 cur = start;
+ while( length_squared((cur+dir*segment_length)-start) < len2 )
+ {
+ vec4 mix_color = lerp(start_color, end_color, (len2-length_squared(cur-start))/len2);
+ line_data.push_back(cur[0]);
+ line_data.push_back(cur[1]);
+ line_data.push_back(cur[2]);
+ line_data.push_back(mix_color[0]);
+ line_data.push_back(mix_color[1]);
+ line_data.push_back(mix_color[2]);
+ line_data.push_back(mix_color[3]);
+
+ vec3 next = cur+dir*segment_length;
+ line_data.push_back(next[0]);
+ line_data.push_back(next[1]);
+ line_data.push_back(next[2]);
+ line_data.push_back(mix_color[0]);
+ line_data.push_back(mix_color[1]);
+ line_data.push_back(mix_color[2]);
+ line_data.push_back(mix_color[3]);
+
+ cur = cur+dir*segment_length*2.0f;
+ }
+
+ if( length_squared(cur-start) < length_squared(end-start) )
+ {
+ line_data.push_back(cur[0]);
+ line_data.push_back(cur[1]);
+ line_data.push_back(cur[2]);
+
+ line_data.push_back(end_color[0]);
+ line_data.push_back(end_color[1]);
+ line_data.push_back(end_color[2]);
+ line_data.push_back(end_color[3]);
+
+ line_data.push_back(end[0]);
+ line_data.push_back(end[1]);
+ line_data.push_back(end[2]);
+
+ line_data.push_back(end_color[0]);
+ line_data.push_back(end_color[1]);
+ line_data.push_back(end_color[2]);
+ line_data.push_back(end_color[3]);
+ }
+
+ vbo->Fill(kVBODynamic | kVBOFloat, sizeof(GLfloat)*line_data.size(), &line_data[0]);
+ }
+ else
+ {
+ GLfloat data[] = {
+ start[0], start[1], start[2],
+ start_color[0], start_color[1], start_color[2], start_color[3],
+ end[0], end[1], end[2],
+ end_color[0], end_color[1], end_color[2], end_color[3]
+ };
+
+ vbo->Fill(kVBODynamic | kVBOFloat, sizeof(data), data);
+ }
+ type = kLine;
+}
+
+DebugDrawLines::DebugDrawLines(
+const std::vector<float> &vertices,
+const std::vector<unsigned> &indices,
+const vec4 &_color,
+const DDFlag& _flags):
+DebugDrawWire(_color, _flags)
+{
+ std::vector<vec3> data;
+ data.reserve( indices.size() );
+
+ for( unsigned i = 0; i < indices.size(); i++ )
+ {
+ data.push_back( vec3(
+ vertices[i*3+0],
+ vertices[i*3+1],
+ vertices[i*3+2]));
+ }
+
+ vbo->Fill(kVBOFloat | kVBOStatic, data.size() * sizeof(data[0]), &data[0]);
+ type = kLines;
+}
+
+DebugDrawLines::DebugDrawLines(
+const std::vector<vec3> &vertices,
+const vec4 &_color,
+const DDFlag& _flags):
+DebugDrawWire(_color, _flags)
+{
+ vbo->Fill(kVBOFloat | kVBOStatic, vertices.size() * sizeof(vertices[0]), (void*)&vertices[0]);
+ type = kLines;
+}
+
+void DebugDrawWire::DecreaseOpacity( float how_much )
+{
+ opacity -= how_much;
+ if( opacity < 0.0f ) opacity = 0.0f;
+}
+
+bool DebugDrawWire::IsInvisible()
+{
+ return opacity<=0;
+}
+
+DDLifespan LifespanFromInt(int lifespan_int) {
+ DDLifespan lifespan;
+ switch(lifespan_int){
+ case _delete_on_draw:
+ lifespan = _delete_on_draw;
+ break;
+ case _delete_on_update:
+ lifespan = _delete_on_update;
+ break;
+ case _persistent:
+ lifespan = _persistent;
+ break;
+ case _fade:
+ lifespan = _fade;
+ break;
+ default:
+ std::ostringstream os;
+ os << "Invalid lifespan int: " << lifespan_int;
+ DisplayError("Error", os.str().c_str());
+ return _delete_on_update;
+ }
+ return lifespan;
+}
+
+RC_VBOContainer DebugDrawWireCylinder::wire_cylinder_vbo;
+
+DebugDrawWireCylinder::DebugDrawWireCylinder( const vec3 &_position,
+ const float _radius,
+ const float _height,
+ const vec4 &_color ):
+ DebugDrawWire(_color, _DD_NO_FLAG)
+{
+ transform.AddTranslation( _position );
+ transform.AddRotation(vec3(0,0,0));
+
+ mat4 scaleMatrix( 1.0f );
+ scaleMatrix.SetScale( vec3( _radius, _height, _radius ) );
+
+ transform *= scaleMatrix;
+
+ if(!wire_cylinder_vbo->valid()){
+ //TODO: fix cleanup by correct referencecounting.
+ std::vector<vec3> vert_array;
+ GetWireCylinderVertArray(12,vert_array);
+ wire_cylinder_vbo->Fill(kVBOFloat | kVBOStatic, vert_array.size() * sizeof(vert_array[0]), &vert_array[0]);
+ }
+
+ vbo = wire_cylinder_vbo;
+ type = kWireCylinder;
+}
+
+DebugDrawWireCylinder::~DebugDrawWireCylinder()
+{
+}
+
+RC_VBOContainer DebugDrawWireSphere::wire_sphere_vbo;
+
+DebugDrawWireSphere::DebugDrawWireSphere( const vec3 &_position,
+ const float _radius,
+ const vec3 &_scale,
+ const vec4 &_color ):
+ DebugDrawWire(_color, _DD_NO_FLAG)
+{
+ if(!wire_sphere_vbo->valid()){
+ std::vector<float> vert_array;
+ GetWireSphereVertArray(1.0f,12,12,vert_array);
+ wire_sphere_vbo->Fill(kVBOFloat | kVBOStatic, vert_array.size() * sizeof(vert_array[0]), &vert_array[0]);
+ }
+ vbo = wire_sphere_vbo;
+
+ transform.AddTranslation( _position );
+ transform.AddRotation(vec3(PI_f/2.0f,0,0));
+ vec3 swizzle_scale = _scale;
+ std::swap(swizzle_scale[2], swizzle_scale[1]);
+
+
+ mat4 scaleMatrix( 1.0f );
+ scaleMatrix.SetScale( swizzle_scale * _radius );
+ transform *= scaleMatrix;
+ type = kWireSphere;
+}
+
+
+DebugDrawWireSphere::DebugDrawWireSphere(const mat4 _transform,
+ const vec4 &_color ):
+ DebugDrawWire(_transform, _color, _DD_NO_FLAG)
+{
+ if(!wire_sphere_vbo->valid()){
+ std::vector<float> vert_array;
+ GetWireSphereVertArray(1.0f,12,12,vert_array);
+ wire_sphere_vbo->Fill(kVBOFloat | kVBOStatic, vert_array.size() * sizeof(vert_array[0]), &vert_array[0]);
+ }
+ vbo = wire_sphere_vbo;
+ type = kWireSphere;
+}
+
+DebugDrawWireSphere::~DebugDrawWireSphere() {
+}
+
+RC_VBOContainer DebugDrawText::vbo;
+
+DebugDrawText::DebugDrawText( const vec3 &_position,
+ const float &_scale,
+ const std::string &_content,
+ const DDFlag& _flags,
+ const vec4& _color ):
+ DebugDrawElement(_flags),
+ scale(_scale),
+ color(_color)
+{
+ if( !vbo->valid())
+ {
+ const float origo[] = {
+ 0.0f,0.0f,0.0f,
+ };
+
+ vbo->Fill(kVBOFloat | kVBOStatic, sizeof(origo), (void*)&origo[0]);
+ }
+
+ transform = mat4(1.0f);
+ transform.SetTranslation( _position );
+
+ FormatString(text, kBufSize, "%s", _content.c_str());
+ type = kText;
+}
+
+DebugDrawText::~DebugDrawText()
+{
+}
+
+bool DebugDrawText::SetPosition(const vec3& position)
+{
+ transform = mat4(1.0f);
+ transform.SetTranslation( position );
+ return true;
+}
+
+void DebugDrawText::Draw()
+{
+ PROFILER_GPU_ZONE(g_profiler_ctx, "DebugDrawText::Draw()");
+ vec3 screen_point = ActiveCameras::Get()->worldToScreen(transform.GetTranslationPart());
+ if(screen_point[2] < 1.0){
+ int pos[] = {(int)screen_point[0], (int)(Graphics::Instance()->window_dims[1] - screen_point[1])};
+ g_text_atlas_renderer.num_characters = 0;
+ CHECK_GL_ERROR();
+ FontRenderer* font_renderer = FontRenderer::Instance();
+ TextMetrics metrics = g_text_atlas_renderer.GetMetrics(
+ &g_text_atlas[kTextAtlasMono],
+ text,
+ font_renderer,
+ 512);
+ pos[0] -= metrics.bounds[2] / 2;
+ g_text_atlas_renderer.AddText(
+ &g_text_atlas[kTextAtlasMono],
+ text,
+ pos, font_renderer,
+ 512);
+ CHECK_GL_ERROR();
+ vec4 gamma_corrected_color = vec4(powf(color.r(), 2.2f), powf(color.g(), 2.2f), powf(color.b(), 2.2f), color.a());
+ g_text_atlas_renderer.Draw(&g_text_atlas[kTextAtlasMono], Graphics::Instance(), TextAtlasRenderer::kTextShadow, gamma_corrected_color);
+ Textures::Instance()->InvalidateBindCache();
+ }
+}
+
+DebugDrawElement::DebugDrawElement(const DDFlag& _flags):
+ flags(_flags),
+ fade_amount(0.0f),
+ type(kUndefined),
+ visible(true)
+ {}
+
+DebugDrawElement::~DebugDrawElement( )
+{
+}
+
+bool DebugDrawElement::SetPosition(const vec3 &position)
+{
+ LOGW << "Function not implemented for current DebugDraw element" << std::endl;
+ return false;
+}
+
+DebugDrawWire::DebugDrawWire(
+ const DDFlag& _flags ) :
+DebugDrawElement(_flags),
+transform(1.0f),
+color(vec4(1.0f,0,0,1.0f)),
+opacity(1.0f)
+{
+ type = kWire;
+}
+
+DebugDrawWire::DebugDrawWire(
+ const vec4 &_color,
+ const DDFlag& _flags ) :
+DebugDrawElement(_flags),
+transform(1.0f),
+color(_color),
+opacity(1.0f)
+{
+ type = kWire;
+}
+
+DebugDrawWire::DebugDrawWire(
+ const mat4 &_transform,
+ const vec4 &_color,
+ const DDFlag& _flags ) :
+DebugDrawElement( _flags ),
+transform(_transform),
+color(_color),
+opacity(1.0f)
+{
+ type = kWire;
+}
+
+DebugDrawWire::DebugDrawWire(
+ const RC_VBOContainer& _vbo,
+ const mat4 &_transform,
+ const vec4 &_color,
+ const DDFlag& _flags):
+DebugDrawElement( _flags ),
+vbo(_vbo),
+transform(_transform),
+color(_color),
+opacity(1.0f)
+{
+ type = kWire;
+}
+
+RC_VBOContainer DebugDrawWireBox::wire_box_vbo;
+
+DebugDrawWireBox::DebugDrawWireBox( const vec3 &pos,
+ const vec3 &dim,
+ const vec4 &color ):
+ DebugDrawWire(color, _DD_NO_FLAG)
+{
+ transform.AddTranslation( pos );
+
+ mat4 scaleMatrix( 1.0f );
+ scaleMatrix.SetScale( dim );
+ transform *= scaleMatrix;
+
+ if( !wire_box_vbo->valid() )
+ {
+ //TODO: fix cleanup by correct referencecounting.
+ std::vector<vec3> vert_array;
+ GetWireBoxVertArray(vert_array);
+ wire_box_vbo->Fill(kVBOFloat | kVBOStatic, vert_array.size() * sizeof(vert_array[0]), &vert_array[0]);
+ }
+ vbo = wire_box_vbo;
+ type = kWireBox;
+}
+
+RC_VBOContainer DebugDrawPoint::vbo;
+
+DebugDrawPoint::DebugDrawPoint( const vec3 &_point,
+ const vec4 &_color,
+ const DDFlag& _flags ):
+ DebugDrawElement(_flags),
+ color(_color)
+{
+ if( !vbo->valid() )
+ {
+ vec3 origin(0.0f);
+ vbo->Fill( kVBOFloat | kVBOStatic, sizeof(origin), (void*)&origin );
+ }
+ transform.SetTranslation( _point );
+ type = kPoint;
+}
+
+void DebugDrawPoint::Draw()
+{
+ Graphics* graphics = Graphics::Instance();
+ Shaders* shaders = Shaders::Instance();
+ Camera* cam = ActiveCameras::Get();
+
+ graphics->SetLineWidth(2);
+
+ int shader_id = shaders->returnProgram("3d_color #COLOR_UNIFORM #NO_VELOCITY_BUF");
+ shaders->setProgram(shader_id);
+ int vert_attrib_id = shaders->returnShaderAttrib("vert_attrib", shader_id);
+ shaders->SetUniformMat4("mvp", cam->GetProjMatrix() * cam->GetViewMatrix() * transform );
+ shaders->SetUniformVec4("color_uniform", color);
+ shaders->SetUniformFloat("opacity", (1.0f-fade_amount) );
+
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+
+ vbo->Bind();
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, sizeof(float) * 3, (const void*)0);
+
+ graphics->DrawArrays( GL_POINTS, 0, vbo->size()/sizeof(float)/3 );
+
+ graphics->ResetVertexAttribArrays();
+}
+
+void DebugDrawAux::Update(float timestep) {
+}
+
+DebugDrawWireMesh::DebugDrawWireMesh( const RC_VBOContainer& _vbo, const mat4 &_transform, const vec4 &_color ):
+DebugDrawWire( _vbo, _transform, _color, _DD_NO_FLAG )
+{
+ type = kWireMesh;
+}
+
+DebugDrawWireMesh::~DebugDrawWireMesh() {
+}
+
+RC_VBOContainer DebugDrawCircle::circle_vert_vbo;
+
+DebugDrawCircle::DebugDrawCircle(
+ const mat4 &_transform,
+ const vec4 &_color,
+ const DDFlag& _flags ):
+DebugDrawWire( _transform, _color, _flags )
+{
+ static const unsigned numLines = 50;
+ if(!circle_vert_vbo->valid()){
+ std::vector<vec3> circle_verts;
+ for(unsigned i=0; i<numLines; ++i){
+ float angle = (float)i/(float)(numLines) * PI_f * 2.0f;
+ float angle2 = (float)(i+1)/(float)(numLines) * PI_f * 2.0f;
+
+ circle_verts.push_back(vec3(
+ sinf(angle),
+ cosf(angle),
+ 0.0f));
+
+ circle_verts.push_back(vec3(
+ sinf(angle2),
+ cosf(angle2),
+ 0.0f));
+ }
+ circle_vert_vbo->Fill(kVBOFloat | kVBOStatic, sizeof(circle_verts[0]) * circle_verts.size(), (void*)&circle_verts[0]);
+ }
+
+ vbo = circle_vert_vbo;
+ type = kCircle;
+}
+
+DebugDrawBillboard::DebugDrawBillboard(const TextureRef& ref, const vec3 &position, float scale, const vec4& color, AlphaMode mode)
+:DebugDrawElement(_DD_NO_FLAG) {
+ ref_ = ref;
+ position_ = position;
+ scale_ = scale;
+ mode_ = mode;
+ color_ = color;
+ type = kBillboard;
+}
+
+bool DebugDrawBillboard::SetPosition(const vec3 &position) {
+ position_ = position;
+ return true;
+}
+
+void DebugDrawBillboard::SetScale(float scale) {
+ scale_ = scale;
+}
+
+void DebugDrawBillboard::Draw() {
+ DrawBillboard(ref_, position_, scale_, color_, mode_);
+ Graphics::Instance()->setBlend(true);
+}
+
+void DebugDrawWire::Draw()
+{
+ Graphics* graphics = Graphics::Instance();
+
+ if((flags & _DD_XRAY) == _DD_XRAY )
+ {
+ graphics->setDepthTest(false);
+ }
+
+ if((flags & _DD_COLOR) == _DD_COLOR )
+ {
+ DrawVertColor();
+ }
+ else
+ {
+ DrawVert();
+ }
+
+ if((flags & _DD_XRAY) == _DD_XRAY )
+ {
+ graphics->setDepthTest(true);
+ }
+}
+
+void DebugDrawWire::DrawVert()
+{
+ Graphics* graphics = Graphics::Instance();
+ Shaders* shaders = Shaders::Instance();
+ Camera* cam = ActiveCameras::Get();
+
+ graphics->SetLineWidth(1);
+
+ int shader_id = shaders->returnProgram("3d_color #COLOR_UNIFORM #NO_VELOCITY_BUF");
+ shaders->setProgram(shader_id);
+ int vert_attrib_id = shaders->returnShaderAttrib("vert_attrib", shader_id);
+ shaders->SetUniformMat4("mvp", cam->GetProjMatrix() * cam->GetViewMatrix() * transform );
+ shaders->SetUniformVec4("color_uniform", color);
+ shaders->SetUniformFloat("opacity", opacity * (1.0f-fade_amount) );
+
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+
+ vbo->Bind();
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, sizeof(float) * 3, (const void*)0);
+
+ graphics->DrawArrays( GL_LINES, 0, vbo->size()/sizeof(float)/3 );
+
+ graphics->ResetVertexAttribArrays();
+}
+
+void DebugDrawWire::DrawVertColor()
+{
+ Graphics* graphics = Graphics::Instance();
+ Shaders* shaders = Shaders::Instance();
+ Camera* cam = ActiveCameras::Get();
+
+ graphics->SetLineWidth(1);
+
+ int shader_id = shaders->returnProgram("3d_color #COLOR_ATTRIB #NO_VELOCITY_BUF");
+ shaders->setProgram(shader_id);
+ int vert_attrib_id = shaders->returnShaderAttrib("vert_attrib", shader_id);
+ int color_attrib_id = shaders->returnShaderAttrib("color_attrib", shader_id);
+ shaders->SetUniformMat4("mvp", cam->GetProjMatrix() * cam->GetViewMatrix() * transform );
+ shaders->SetUniformFloat("opacity", opacity * (1.0f-fade_amount));
+
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ graphics->EnableVertexAttribArray(color_attrib_id);
+
+ vbo->Bind();
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, sizeof(float) * 7, (const void*)0);
+ glVertexAttribPointer(color_attrib_id, 4, GL_FLOAT, false, sizeof(float) * 7, (const void*)(sizeof(float)*3));
+
+ graphics->DrawArrays( GL_LINES, 0, vbo->size()/sizeof(float)/7 );
+
+ graphics->ResetVertexAttribArrays();
+}
+
+DebugDrawStippleMesh::DebugDrawStippleMesh(const RC_VBOContainer& _vbo,
+ const mat4 &_transform,
+ const vec4 &_color,
+ const DDFlag& _flags):
+DebugDrawElement(_DD_NO_FLAG),
+vbo(_vbo),
+transform(_transform),
+color(_color)
+{
+ type = kStippleMesh;
+}
+
+void DebugDrawStippleMesh::Draw()
+{
+ Shaders* shaders = Shaders::Instance();
+ Camera* cam = ActiveCameras::Get();
+ Graphics* graphics = Graphics::Instance();
+
+ int shader_id = shaders->returnProgram("debug_fill #STIPPLING #NO_VELOCITY_BUF");
+ shaders->setProgram(shader_id);
+ int vert_attrib_id = shaders->returnShaderAttrib("vert_attrib", shader_id);
+
+ shaders->SetUniformMat4("mvp", cam->GetProjMatrix() * cam->GetViewMatrix() * transform);
+ shaders->SetUniformVec3("close_stipple_color", vec3(0.1f, 0.8f, 1.0f));
+ shaders->SetUniformVec3("far_stipple_color", vec3(0.25f, 0.29f, 0.3f));
+ shaders->SetUniformVec3("camera_forward", cam->GetFacing());
+ shaders->SetUniformVec3("camera_position", cam->GetPos());
+
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ vbo->Bind();
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, 0, 0);
+
+ graphics->DrawArrays(GL_TRIANGLES, 0, vbo->size() / (sizeof(float)*3));
+
+ graphics->ResetVertexAttribArrays();
+}
+
+void DebugDrawRibbon::Draw() {
+ if (g_debug_runtime_disable_debug_ribbon_draw) {
+ return;
+ }
+
+ PROFILER_GPU_ZONE(g_profiler_ctx, "DebugDrawRibbon::Draw()");
+ if(ribbon_points.size() < 2){
+ return;
+ }
+
+ Graphics* graphics = Graphics::Instance();
+ Shaders* shaders = Shaders::Instance();
+ Camera* cam = ActiveCameras::Get();
+
+ int shader_id = shaders->returnProgram("3d_color #COLOR_ATTRIB #FIRE");
+ shaders->setProgram(shader_id);
+
+ shaders->SetUniformMat4("mvp", cam->GetProjMatrix() * cam->GetViewMatrix());
+
+ std::vector<vec3> right;
+ right.resize(ribbon_points.size()-1);
+ for(int i=0, len=ribbon_points.size()-1; i<len; ++i) {
+ vec3 vec = ribbon_points[i+1].pos - ribbon_points[i].pos;
+ right[i] = normalize(cross(cam->GetFacing(), vec));
+ }
+
+ std::vector<GLfloat> data;
+ data.resize(7*ribbon_points.size()*2);
+ int index = 0;
+ for(int i=0, len=ribbon_points.size(); i<len; ++i) {
+ vec3 temp_right;
+ if(i==0) {
+ temp_right = right[i];
+ } else if(i==len-1){
+ temp_right = right[i-1];
+ } else {
+ temp_right = normalize(right[i] + right[i-1]);
+ }
+ ribbon_points[i].color[3] = 0.0f;
+ for(int j=0; j<4; ++j){
+ data[index++] = ribbon_points[i].color[j];
+ }
+ for(int j=0; j<3; ++j){
+ data[index++] = ribbon_points[i].pos[j] + temp_right[j] * ribbon_points[i].width;
+ }
+ ribbon_points[i].color[3] = 1.0f;
+ for(int j=0; j<4; ++j){
+ data[index++] = ribbon_points[i].color[j];
+ }
+ for(int j=0; j<3; ++j){
+ data[index++] = ribbon_points[i].pos[j] - temp_right[j] * ribbon_points[i].width;
+ }
+ }
+
+ vec2 viewport_dims;
+ for(int i=0; i<2; ++i){
+ viewport_dims[i] = (float)Graphics::Instance()->viewport_dim[i+2];
+ }
+ shaders->SetUniformVec2("viewport_dims",viewport_dims);
+ shaders->SetUniformVec3("cam_pos",cam->GetPos());
+ shaders->SetUniformFloat("time",game_timer.GetRenderTime());
+ Textures::Instance()->bindTexture(Graphics::Instance()->screen_depth_tex, 5);
+
+ static VBORingContainer data_vbo(V_MIBIBYTE, kVBODynamic | kVBOFloat);
+ data_vbo.Fill(data.size() * sizeof(GLfloat), &data[0]);
+ data_vbo.Bind();
+ int vert_attrib_id = shaders->returnShaderAttrib("vert_attrib", shader_id);
+ int color_attrib_id = shaders->returnShaderAttrib("color_attrib", shader_id);
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ graphics->EnableVertexAttribArray(color_attrib_id);
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, 7*sizeof(GLfloat), (const void*)(data_vbo.offset() + sizeof(GLfloat) * 4));
+ glVertexAttribPointer(color_attrib_id, 4, GL_FLOAT, false, 7*sizeof(GLfloat), (const void*)data_vbo.offset());
+ Graphics::Instance()->DrawArrays(GL_TRIANGLE_STRIP, 0, ribbon_points.size()*2);
+ graphics->ResetVertexAttribArrays();
+
+ /*glEnableVertexAttribArray(vert_attrib_id);
+ vbo->Bind();
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, 0, 0);
+
+ glDrawArrays(GL_TRIANGLES, 0, vbo->size() / (sizeof(float)*3));
+
+ glDisableVertexAttribArray(vert_attrib_id);*/
+}
+
+DebugDrawRibbon::DebugDrawRibbon(const vec3 &start, const vec3 &end, const vec4 &start_color, const vec4 &end_color, const float start_width, const float end_width, const DDFlag& flags)
+ :DebugDrawElement(flags)
+{
+ ribbon_points.resize(2);
+ ribbon_points[0].pos = start;
+ ribbon_points[1].pos = end;
+ ribbon_points[0].color = start_color;
+ ribbon_points[1].color = end_color;
+ ribbon_points[0].width = start_width;
+ ribbon_points[1].width = end_width;
+ type = kRibbon;
+}
+
+DebugDrawRibbon::DebugDrawRibbon(const DDFlag& flags)
+ :DebugDrawElement(flags)
+{
+ type = kRibbon;
+}
+
+void DebugDrawRibbon::AddPoint(const vec3 &pos, const vec4 &color, float width) {
+ RibbonPoint point;
+ point.pos = pos;
+ point.color = color;
+ point.width = width;
+ ribbon_points.push_back(point);
+}
+
+void TempLines::Draw() {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw TempLines");
+ if(!verts.empty()){
+ vbo.Fill(verts.size() * sizeof(verts[0]), &verts[0]);
+
+ Graphics* graphics = Graphics::Instance();
+ Shaders* shaders = Shaders::Instance();
+ Camera* cam = ActiveCameras::Get();
+
+ graphics->SetLineWidth(1);
+
+ int shader_id = shaders->returnProgram("3d_color #COLOR_ATTRIB #NO_VELOCITY_BUF");
+ shaders->setProgram(shader_id);
+ int vert_attrib_id = shaders->returnShaderAttrib("vert_attrib", shader_id);
+ int color_attrib_id = shaders->returnShaderAttrib("color_attrib", shader_id);
+ shaders->SetUniformMat4("mvp", cam->GetProjMatrix() * cam->GetViewMatrix() );
+ shaders->SetUniformFloat("opacity", 1.0f);
+
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ graphics->EnableVertexAttribArray(color_attrib_id);
+
+ vbo.Bind();
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, sizeof(float) * 7, (const void*)(vbo.offset()));
+ glVertexAttribPointer(color_attrib_id, 4, GL_FLOAT, false, sizeof(float) * 7, (const void*)(vbo.offset()+sizeof(float)*3));
+
+ graphics->DrawArrays( GL_LINES, 0, vbo.size()/sizeof(float)/7 );
+
+ graphics->ResetVertexAttribArrays();
+ }
+}
diff --git a/Source/Graphics/pxdebugdraw.h b/Source/Graphics/pxdebugdraw.h
new file mode 100644
index 00000000..1604a787
--- /dev/null
+++ b/Source/Graphics/pxdebugdraw.h
@@ -0,0 +1,419 @@
+//-----------------------------------------------------------------------------
+// Name: pxdebugdraw.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+#include <Math/vec4.h>
+#include <Math/mat4.h>
+
+#include <Graphics/textureref.h>
+#include <Graphics/glstate.h>
+#include <Graphics/vbocontainer.h>
+#include <Graphics/vboringcontainer.h>
+
+#include <Internal/referencecounter.h>
+#include <Asset/Asset/texture.h>
+
+#include <opengl.h>
+
+#include <queue>
+#include <map>
+#include <string>
+
+enum DDLifespan {
+ _delete_on_draw,
+ _delete_on_update,
+ _fade,
+ _persistent
+};
+
+enum DDFlag {
+ _DD_NO_FLAG = 0,
+ //Request disabled depth buffer.
+ _DD_XRAY = (1<<0),
+ _DD_STIPPLE = (1<<1),
+ //Means there is a vec4(color) element interleaved in vbo after verts
+ _DD_COLOR = (1<<2),
+ //Whether or not text should be rendered at a set size or in-world.
+ _DD_SCREEN_SPACE = (1<<3),
+ //Dashed line segment.
+ _DD_DASHED = (1<<4)
+};
+
+enum DDElement {
+ kUndefined,
+ kWire,
+ kText,
+ kBillboard,
+ kRibbon,
+ kLine,
+ kLines,
+ kWireMesh,
+ kStippleMesh,
+ kPoint,
+ kWireSphere,
+ kWireBox,
+ kCircle,
+ kWireCylinder
+};
+
+class DebugDrawElement {
+ DDLifespan lifespan;
+ protected:
+ DDFlag flags;
+
+ public:
+ bool visible;
+ DDElement type;
+ float fade_amount;
+ DebugDrawElement(const DDFlag& _flags );
+
+ virtual ~DebugDrawElement();
+ virtual void Draw()=0;
+
+ //Move the origin of the object.
+ virtual bool SetPosition(const vec3& pos);
+
+ inline void SetLifespan(const DDLifespan &_lifespan)
+ {lifespan = _lifespan;}
+ inline const DDLifespan& GetLifespan()
+ {return lifespan;}
+};
+
+class DebugDrawWire : public DebugDrawElement {
+
+private:
+ void DrawVert();
+ void DrawVertColor();
+
+protected:
+ RC_VBOContainer vbo;
+ mat4 transform;
+ vec4 color;
+ float opacity;
+
+public:
+ void DecreaseOpacity(float how_much);
+ bool IsInvisible();
+ DebugDrawWire(const DDFlag& _flags);
+ DebugDrawWire(const vec4 &_color, const DDFlag& _flags);
+ DebugDrawWire(const mat4 &_transform, const vec4 &_color, const DDFlag& _flags);
+ DebugDrawWire(const RC_VBOContainer& _vbo, const mat4 &_transform, const vec4 &_color, const DDFlag& _flags);
+ void Draw();
+};
+
+class LabelText;
+class DebugDrawText : public DebugDrawElement {
+ static RC_VBOContainer vbo;
+ static const int kBufSize = 256;
+ char text[kBufSize];
+ vec4 color;
+ mat4 transform;
+ float scale;
+ public:
+ void Draw();
+ DebugDrawText(const vec3 &_position,
+ const float &_scale,
+ const std::string &_content,
+ const DDFlag& _flags,
+ const vec4 & _color);
+ ~DebugDrawText();
+ virtual bool SetPosition(const vec3& position);
+};
+
+
+class DebugDrawBillboard: public DebugDrawElement {
+public:
+ void Draw();
+ virtual bool SetPosition(const vec3 &position);
+ void SetScale(float scale);
+ DebugDrawBillboard(const TextureRef& ref, const vec3 &position, float scale, const vec4& color, AlphaMode mode);
+private:
+ TextureRef ref_;
+ vec3 position_;
+ vec4 color_;
+ float scale_;
+ AlphaMode mode_;
+};
+
+class DebugDrawRibbon: public DebugDrawElement {
+public:
+ void Draw();
+ DebugDrawRibbon(const DDFlag& flags);
+ DebugDrawRibbon(const vec3 &start,
+ const vec3 &end,
+ const vec4 &start_color,
+ const vec4 &end_color,
+ const float start_width,
+ const float end_width,
+ const DDFlag& flags);
+ void AddPoint( const vec3 &pos, const vec4 &color, float width );
+private:
+ struct RibbonPoint {
+ vec3 pos;
+ vec4 color;
+ float width;
+ };
+ std::vector<RibbonPoint> ribbon_points;
+};
+
+class DebugDrawLine : public DebugDrawWire {
+public:
+ DebugDrawLine(const vec3 &start,
+ const vec3 &end,
+ const vec4 &start_color,
+ const vec4 &end_color,
+ const DDFlag& flags = _DD_NO_FLAG);
+};
+
+class DebugDrawLines : public DebugDrawWire {
+public:
+ DebugDrawLines(
+ const std::vector<float> &vertices,
+ const std::vector<unsigned> &indices,
+ const vec4 &color,
+ const DDFlag& flags = _DD_NO_FLAG);
+
+ DebugDrawLines(
+ const std::vector<vec3> &vertices,
+ const vec4 &color,
+ const DDFlag& flags = _DD_NO_FLAG);
+};
+
+class DebugDrawWireMesh : public DebugDrawWire {
+public:
+ DebugDrawWireMesh(const RC_VBOContainer& _vbo,
+ const mat4 &_transform,
+ const vec4 &_color);
+ ~DebugDrawWireMesh();
+};
+
+class DebugDrawStippleMesh : public DebugDrawElement {
+
+protected:
+ RC_VBOContainer vbo;
+ mat4 transform;
+ vec4 color;
+public:
+ DebugDrawStippleMesh(const RC_VBOContainer& _vbo,
+ const mat4 &_transform,
+ const vec4 &_color,
+ const DDFlag& _flags);
+ void Draw();
+};
+
+
+class DebugDrawPoint : public DebugDrawElement {
+ static RC_VBOContainer vbo;
+ vec4 color;
+ mat4 transform;
+ public:
+ void Draw();
+ DebugDrawPoint(const vec3 &point,
+ const vec4 &color,
+ const DDFlag& _flags = _DD_NO_FLAG);
+};
+
+class DebugDrawWireSphere : public DebugDrawWire {
+ private:
+ static RC_VBOContainer wire_sphere_vbo;
+ public:
+ DebugDrawWireSphere(const mat4 _transform,
+ const vec4 &_color );
+
+ DebugDrawWireSphere(const vec3 &position,
+ const float radius,
+ const vec3 &scale = vec3(1.0f,1.0f,1.0f),
+ const vec4 &color = vec4(1.0f,1.0f,1.0f,1.0f));
+ ~DebugDrawWireSphere();
+};
+
+class DebugDrawCircle : public DebugDrawWire {
+private:
+ static RC_VBOContainer circle_vert_vbo;
+public:
+ DebugDrawCircle(const mat4 &transform,
+ const vec4 &color = vec4(1.0f,1.0f,1.0f,1.0f),
+ const DDFlag& flags = _DD_NO_FLAG);
+};
+
+class DebugDrawWireCylinder : public DebugDrawWire {
+ private:
+ static RC_VBOContainer wire_cylinder_vbo;
+ public:
+ DebugDrawWireCylinder(const vec3 &position,
+ const float radius,
+ const float height,
+ const vec4 &color);
+ ~DebugDrawWireCylinder();
+};
+
+
+class DebugDrawWireBox : public DebugDrawWire {
+ private:
+ static RC_VBOContainer wire_box_vbo;
+ public:
+ DebugDrawWireBox(const vec3 &position,
+ const vec3 &dimensions,
+ const vec4 &color);
+};
+
+typedef std::map<int,DebugDrawElement*> DebugDrawElementMap;
+
+class TempLines {
+public:
+ TempLines():vbo(1024*1024, kVBOStream | kVBOFloat){}
+ VBORingContainer vbo;
+ std::vector<float> verts;
+ void Draw();
+};
+
+class DebugDraw {
+private:
+ std::map<std::string, RC_VBOContainer> mesh_edges_map;
+ TempLines delete_on_update;
+ TempLines delete_on_draw;
+
+ std::queue<int> free_ids;
+ int id_index;
+ DebugDrawElementMap elements;
+ int AddElement( DebugDrawElement* object, const DDLifespan lifespan);
+public:
+ void Draw();
+ void Update(float timestep);
+ void Remove(int);
+ DebugDrawElement* GetElement(int id);
+ int AddLine(const vec3 &start,
+ const vec3 &end,
+ const vec4 &color,
+ const DDLifespan lifespan,
+ const DDFlag& flags = _DD_NO_FLAG);
+ int AddLine(const vec3 &start,
+ const vec3 &end,
+ const vec4 &start_color,
+ const vec4 &end_color,
+ const DDLifespan lifespan,
+ const DDFlag& flags = _DD_NO_FLAG);
+ int AddRibbon(const vec3 &start,
+ const vec3 &end,
+ const vec4 &start_color,
+ const vec4 &end_color,
+ const float start_width,
+ const float end_width,
+ const DDLifespan lifespan,
+ const DDFlag& flags = _DD_NO_FLAG);
+ int AddRibbon( const DDLifespan lifespan,
+ const DDFlag& flags = _DD_NO_FLAG);
+ int AddPoint( const vec3 &point,
+ const vec4 &color,
+ const DDLifespan lifespan,
+ const DDFlag& flags);
+ int AddWireSphere(const vec3 &position,
+ const float radius,
+ const vec4 &color,
+ const DDLifespan lifespan);
+ int AddWireScaledSphere(const vec3 &position,
+ const float radius,
+ const vec3 &scale,
+ const vec4 &color,
+ const DDLifespan lifespan);
+ int AddTransformedWireScaledSphere(const mat4 &transform,
+ const vec4 &color,
+ const DDLifespan lifespan);
+ int AddWireCylinder(const vec3 &position,
+ const float radius,
+ const float height,
+ const vec4 &color,
+ const DDLifespan lifespan);
+ int AddWireBox(const vec3 &position,
+ const vec3 &dimensions,
+ const vec4 &color,
+ const DDLifespan lifespan);
+ int AddBillboard(const TextureRef &ref,
+ const vec3 &position,
+ float scale,
+ const vec4 &color,
+ AlphaMode mode,
+ const DDLifespan lifespan);
+ int AddWireMesh(const std::string &path,
+ const mat4 &transform,
+ const vec4 &color,
+ const DDLifespan lifespan);
+ int AddLineObject(const RC_VBOContainer &vbo,
+ const mat4 &transform,
+ const vec4 &color,
+ const DDLifespan lifespan);
+ int AddStippleMesh(const RC_VBOContainer &vbo,
+ const mat4 &transform,
+ const vec4 &color,
+ const DDLifespan lifespan);
+ int AddCircle(const mat4 &transform,
+ const vec4 &color,
+ const DDLifespan lifespan,
+ const DDFlag& flags = _DD_NO_FLAG);
+ int AddText(const vec3 &position,
+ const std::string &content,
+ const float& scale,
+ const DDLifespan lifespan,
+ const DDFlag& flags,
+ const vec4& color = vec4(1.0f, 1.0f, 1.0f, 1.0f));
+ bool SetPosition( int id,
+ const vec3 &position );
+ bool SetColor( int id,
+ const vec4 &color );
+ bool SetVisible( int id, bool visible );
+ int AddLines(
+ const std::vector<float> &vertices,
+ const std::vector<unsigned> &indices,
+ const vec4 &color,
+ const DDLifespan lifespan,
+ const DDFlag& flags );
+ int AddLines(
+ const std::vector<vec3> &vertices,
+ const vec4 &color,
+ const DDLifespan lifespan,
+ const DDFlag& flags );
+ void Dispose();
+ DebugDraw();
+ ~DebugDraw();
+
+ static DebugDraw* Instance() {
+ static DebugDraw instance;
+ return &instance;
+ }
+};
+
+class DebugDrawAux {
+public:
+ void Update(float timestep);
+
+ bool visible_sound_spheres;
+
+ static DebugDrawAux* Instance() {
+ static DebugDrawAux instance;
+ return &instance;
+ }
+};
+
+DDLifespan LifespanFromInt(int lifespan_int);
diff --git a/Source/Graphics/retargetfile.cpp b/Source/Graphics/retargetfile.cpp
new file mode 100644
index 00000000..5b4dbd38
--- /dev/null
+++ b/Source/Graphics/retargetfile.cpp
@@ -0,0 +1,111 @@
+//-----------------------------------------------------------------------------
+// Name: retargetfile.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "retargetfile.h"
+
+#include <Internal/error.h>
+#include <Internal/common.h>
+#include <Internal/filesystem.h>
+
+#include <Memory/allocation.h>
+#include <XML/xml_helper.h>
+
+#include <tinyxml.h>
+
+void AnimationRetargeter::Reload() {
+ int64_t new_date_modified = GetDateModifiedInt64(path.c_str());
+ if(new_date_modified > date_modified){
+ Load(path.c_str());
+ }
+}
+
+void AnimationRetargeter::Load( const char* _path ) {
+ static const int kBufSize = 512;
+ char buf[kBufSize];
+ if(FindFilePath(_path, buf, kBufSize, kDataPaths | kModPaths) == -1){
+ FatalError("Error", "Could not find: %s", _path);
+ }
+ void* mem;
+ if(!LoadText(mem, buf)){
+ FatalError("Error", "Could not read: %s", _path);
+ }
+ TiXmlDocument doc;
+ doc.Parse((const char*)mem);
+ OG_FREE(mem);
+
+ if( doc.Error() ) {
+ LOGE << "Unable to load xml file " << _path << ". Error: \"" << doc.ErrorDesc() << "\" on row " << doc.ErrorRow() << std::endl;
+ } else {
+ path = buf;
+ date_modified = GetDateModifiedInt64(buf);
+
+ anim_skeleton.clear();
+
+ TiXmlHandle h_doc(&doc);
+ TiXmlHandle h_root = h_doc.FirstChildElement();
+ TiXmlElement* field = h_root.ToElement()->FirstChildElement();
+ if(!field){
+ FatalError("Error", "Retargeter file has no first field");
+ }
+ for( ; field; field = field->NextSiblingElement()) {
+ std::string field_str(field->Value());
+ if(field_str == "rig"){
+ const char* path_str = field->Attribute("path");
+ if(!path_str){
+ FatalError("Error", "Retargeter field has no path attribute");
+ }
+ std::string skeleton_path = path_str;
+ TiXmlElement* anim = field->FirstChildElement();
+ while(anim){
+ anim_skeleton[anim->GetText()] = skeleton_path;
+ const char* path_str = anim->Attribute("no_retarget");
+ if(path_str && strcmp(path_str, "true")==0){
+ anim_no_retarget[anim->GetText()] = true;
+ }
+ anim = anim->NextSiblingElement();
+ }
+ }
+ }
+ }
+}
+
+const std::string &AnimationRetargeter::GetSkeletonFile(const std::string &anim) {
+ std::map<std::string, std::string>::iterator iter = anim_skeleton.find(anim);
+ if (iter == anim_skeleton.end()) {
+ iter = anim_skeleton.find("Data/Animations/r_ledge.anm");
+ if (iter == anim_skeleton.end()) {
+ iter = anim_skeleton.begin();
+ }
+ }
+ return iter->second;
+}
+
+bool AnimationRetargeter::GetNoRetarget(const std::string &anim) {
+ std::map<std::string, bool>::iterator iter = anim_no_retarget.find(anim);
+ if (iter != anim_no_retarget.end()) {
+ return iter->second;
+ } else {
+ return false;
+ }
+}
+
diff --git a/Source/Graphics/retargetfile.h b/Source/Graphics/retargetfile.h
new file mode 100644
index 00000000..7197831e
--- /dev/null
+++ b/Source/Graphics/retargetfile.h
@@ -0,0 +1,46 @@
+//-----------------------------------------------------------------------------
+// Name: retargetfile.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/datemodified.h>
+
+#include <map>
+#include <string>
+
+class AnimationRetargeter {
+ std::string path;
+ int64_t date_modified;
+ std::map<std::string, bool> anim_no_retarget;
+ std::map<std::string, std::string> anim_skeleton;
+public:
+ static AnimationRetargeter *Instance() {
+ static AnimationRetargeter anim_retargeter;
+ return &anim_retargeter;
+ }
+
+ void Load(const char* path);
+ void Reload();
+ const std::string &GetSkeletonFile(const std::string &anim);
+ bool GetNoRetarget(const std::string &anim);
+};
diff --git a/Source/Graphics/shaders.cpp b/Source/Graphics/shaders.cpp
new file mode 100644
index 00000000..36bdf9fb
--- /dev/null
+++ b/Source/Graphics/shaders.cpp
@@ -0,0 +1,1298 @@
+//-----------------------------------------------------------------------------
+// Name: shaders.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "shaders.h"
+
+#include <Graphics/textures.h>
+#include <Graphics/graphics.h>
+
+#include <Internal/common.h>
+#include <Internal/textfile.h>
+#include <Internal/datemodified.h>
+#include <Internal/filesystem.h>
+#include <Internal/profiler.h>
+#include <Internal/config.h>
+
+#include <Logging/logdata.h>
+#include <Compat/fileio.h>
+#include <Utility/assert.h>
+
+#include <string>
+
+#define SHADER_WARNINGS
+
+extern bool g_single_pass_shadow_cascade;
+
+void Shaders::Dispose() {
+ CHECK_GL_ERROR();
+ for(unsigned i=0; i<programs.size(); ++i){
+ Program& program = programs[i];
+ if(program.gl_program != UNLOADED_SHADER_ID) {
+ for(int i=0; i<kMaxShaderTypes; ++i){
+ if( program.shader_ids[i] >= 0 ) {
+ glDetachShader(program.gl_program, shaders[program.shader_ids[i]].gl_shader);
+ }
+ }
+ glDeleteProgram(program.gl_program);
+ }
+ }
+ for(unsigned i=0; i<shaders.size(); ++i){
+ if(shaders[i].gl_shader != UNLOADED_SHADER_ID) {
+ glDeleteShader(shaders[i].gl_shader);
+ }
+ }
+
+ CHECK_GL_ERROR();
+
+ shaders.clear();
+ programs.clear();
+}
+
+namespace {
+static bool IsCharInString(char chr, const char* str) {
+ for(int i=0; str[i] != '\0'; ++i){
+ if(chr == str[i]){
+ return true;
+ }
+ }
+ return false;
+}
+
+static void CopySubString(const char* src, char *dst, const char *delim, int max_chars){
+ int i = 0;
+ while(i < max_chars && !IsCharInString(src[i], delim)){
+ dst[i] = src[i];
+ ++i;
+ }
+ dst[i] = '\0';
+}
+
+static int FindCharInString(char chr, const char *str, int max_chars){
+ for(int i=0; i<max_chars; ++i){
+ if(str[i] == chr){
+ return i;
+ }
+ }
+ return -1;
+}
+
+namespace PREPROC {
+ enum Type {
+ INCLUDE,
+ UNKNOWN,
+ PRAGMA,
+ VERSION
+ };
+}
+
+struct PreprocessorDirective {
+ PREPROC::Type type;
+ std::string data;
+};
+
+// Looks at a line of text and fills *pd with information about the preprocessor directive in that line
+static void ParsePreprocessorDirective(const char* text, int line_break, PreprocessorDirective *pd){
+ pd->type = PREPROC::UNKNOWN;
+ // Copy # directive into buf e.g. get 'include' from '#include "blah"'
+ const int BUF_SIZE = 256;
+ char buf[BUF_SIZE];
+ CopySubString(&text[1], buf, " \t", min(BUF_SIZE-1, line_break-1));
+ // If #include directive, read info between "" signs
+ if(strcmp(buf, "include") == 0){
+ int open_quote_sign = FindCharInString('\"', text, line_break);
+ if(open_quote_sign == -1){
+ return;
+ }
+ int close_quote_sign = FindCharInString('\"', &text[open_quote_sign+1], line_break-(open_quote_sign+1)) + open_quote_sign+1;
+ if(close_quote_sign == -1){
+ return;
+ }
+ CopySubString(&text[open_quote_sign+1],buf,"",min(BUF_SIZE-1, close_quote_sign - open_quote_sign - 1));
+ pd->data = buf;
+ pd->type = PREPROC::INCLUDE;
+ }
+ // If #pragma directive, read proceeding non-whitespace
+ if(strcmp(buf, "pragma") == 0){
+ int first_char = strlen("#pragma");
+ while(IsCharInString(text[first_char], " \t") && first_char < line_break){
+ ++first_char;
+ }
+ int second_char = first_char;
+ while(!IsCharInString(text[second_char], " \t") && second_char < line_break){
+ ++second_char;
+ }
+ CopySubString(&text[first_char],buf,"",min(BUF_SIZE-1, second_char - first_char));
+ pd->data = buf;
+ pd->type = PREPROC::PRAGMA;
+ }
+ if(strcmp(buf, "version") == 0){
+ int first_char = strlen("#version");
+ while(IsCharInString(text[first_char], " \t") && first_char < line_break){
+ ++first_char;
+ }
+ int second_char = first_char;
+ while(!IsCharInString(text[second_char], " \t") && second_char < line_break){
+ ++second_char;
+ }
+ CopySubString(&text[first_char],buf,"",min(BUF_SIZE-1, second_char - first_char));
+ pd->data = buf;
+ pd->type = PREPROC::VERSION;
+ }
+ return;
+}
+
+static void HandleInclude(std::string *text, int line_start, const char* abs_path, int *cursor){
+ text->insert(line_start, "//");
+ *cursor += 2;
+ text->insert(*cursor, "\n");
+ *cursor += 1;
+ std::string include_text;
+ textFileRead(abs_path, &include_text);
+ text->insert(*cursor, include_text);
+ --*cursor;
+}
+
+static void AddShaderDefinition(std::string *text, const std::string &def){
+ text->insert(0,"#define "+def+"\n");
+}
+
+static void Preprocess(Shader* shader, ShaderType type, const std::vector<std::string> &definitions, const std::string& shader_dir_path) {
+ shader->include_files.clear();
+ shader->include_modified.clear();
+ shader->use_tangent = false;
+ shader->transparent = false;
+ shader->bind_out_color = false;
+ shader->bind_out_vel = false;
+ shader->depth_only = false;
+ shader->particle = false;
+ BlendMode blend_mode = _BM_NORMAL;
+ std::string &text = shader->text;
+
+ bool no_velocity_buf_defined = false;
+
+ for(unsigned i=0; i<definitions.size(); ++i){
+ if(definitions[i] == "TANGENT"){
+ shader->use_tangent = true;
+ }
+ if(definitions[i] == "ALPHA"){
+ shader->transparent = true;
+ }
+ if(definitions[i] == "DEPTH_ONLY") {
+ shader->depth_only = true;
+ }
+ if(definitions[i] == "PARTICLE") {
+ shader->particle = true;
+ }
+ if(definitions[i] == "NO_VELOCITY_BUF") {
+ no_velocity_buf_defined = true;
+ }
+ }
+
+ int version = -1;
+ int line_start = 0;
+ for(int cursor=0; text[cursor] != '\0'; ++cursor){
+ // Check each line one at a time
+ if(IsCharInString(text[cursor],"\n\r")){
+ // Search line for # sign
+ int pound_sign = -1;
+ for(int j=line_start; j<cursor; ++j){
+ if(text[j] == '#'){
+ pound_sign = j;
+ break;
+ }
+ }
+ if(pound_sign != -1){
+ PreprocessorDirective pd;
+ ParsePreprocessorDirective(&text[pound_sign], cursor-pound_sign, &pd);
+ bool recognized_pragma = false;
+ switch(pd.type){
+ case PREPROC::UNKNOWN:
+ LOGD << "Unknown preprocessor directive" << std::endl;
+ break;
+ case PREPROC::INCLUDE:
+ LOGD << "Include file \"" << pd.data << "\"." << std::endl;;
+ char rel_path[kPathSize], abs_path[kPathSize];
+ FormatString(rel_path, kPathSize, "%s%s", shader_dir_path.c_str(), pd.data.c_str());
+ if(FindFilePath(rel_path, abs_path, kPathSize, kDataPaths | kModPaths) == -1){
+ FatalError("Error", "Could not find shader include file: %s", rel_path);
+ }
+ HandleInclude(&text, line_start, abs_path, &cursor);
+ shader->include_files.push_back(abs_path);
+ shader->include_modified.push_back(GetDateModifiedInt64(shader->include_files.back().c_str()));
+ break;
+ case PREPROC::PRAGMA:
+ LOGD << "Pragma \"" << pd.data << "\"" << std::endl;
+ if (pd.data == "transparent"){
+ shader->transparent = true;
+ recognized_pragma = true;
+ } else if (pd.data == "use_tangent") {
+ shader->use_tangent = true;
+ recognized_pragma = true;
+ } else if (pd.data == "blendmode_add") {
+ blend_mode = _BM_ADD;
+ recognized_pragma = true;
+ } else if (pd.data == "blendmode_multiply") {
+ blend_mode = _BM_MULTIPLY;
+ recognized_pragma = true;
+ } else if (pd.data == "bind_out_color" ) {
+ shader->bind_out_color = true;
+ recognized_pragma = true;
+ } else if (pd.data == "bind_out_vel" ) {
+ recognized_pragma = true;
+ if(no_velocity_buf_defined) {
+ shader->bind_out_vel = false;
+ } else {
+ shader->bind_out_vel = true;
+ }
+ }
+ if( recognized_pragma ) {
+ text.insert(line_start, "//");
+ }
+ break;
+ case PREPROC::VERSION:
+ // Comment out current version definition
+ // So we can add it later at the beginning
+ text.insert(line_start, "//");
+ cursor += 2;
+ text.insert(cursor, "\n");
+ cursor += 1;
+ version = atoi(pd.data.c_str());
+ break;
+ }
+ }
+ line_start = cursor+1;
+ }
+ }
+
+ AddShaderDefinition(&text, "GAMMA_CORRECT");
+ if (type == _vertex) {
+ AddShaderDefinition(&text, "VERTEX_SHADER");
+ } else if( type == _fragment ) {
+ AddShaderDefinition(&text, "FRAGMENT_SHADER");
+ } else if( type == _geom ) {
+ AddShaderDefinition(&text, "GEOMETRY_SHADER");
+ } else if( type == _tess_ctrl ) {
+ AddShaderDefinition(&text, "TESSELATION_CONTROL_SHADER");
+ } else if( type == _tess_eval ) {
+ AddShaderDefinition(&text, "TESSELATION_EVALUATOR_SHADER");
+ }
+
+ for(unsigned i=0; i<definitions.size(); ++i){
+ AddShaderDefinition(&text, definitions[i]);
+ }
+ if (GLEW_ARB_sample_shading) {
+ AddShaderDefinition(&text, "ARB_sample_shading_available");
+ }
+ if(GLEW_ARB_shader_texture_lod && type == _fragment){
+ text.insert(0,"#extension GL_ARB_shader_texture_lod : enable\n");
+ }
+
+ // This must be the last directive added to make sure that #version is at the beginning of the file
+ if(version == -1){
+ version = 130;
+ }
+ {
+ static const int kBufSize = 256;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "#version %d\n", version);
+ text.insert(0,buf);
+ }
+
+ if(blend_mode == _BM_ADD){
+ shader->blend_src = GL_SRC_ALPHA;
+ shader->blend_dst = GL_ONE;
+ } else if(blend_mode == _BM_MULTIPLY){
+ shader->blend_src = GL_DST_COLOR;
+ shader->blend_dst = GL_ONE_MINUS_SRC_ALPHA;
+ } else {
+ shader->blend_src = GL_SRC_ALPHA;
+ shader->blend_dst = GL_ONE_MINUS_SRC_ALPHA;
+ }
+}
+} //namespace ""
+
+void Shaders::ReloadShader(int which, ShaderType type) {
+ if( which >= 0 )
+ {
+ Shader &shader = shaders[which];
+ if(shader.gl_shader != UNLOADED_SHADER_ID) {
+ glDeleteShader(shader.gl_shader);
+ shader.gl_shader = UNLOADED_SHADER_ID;
+ }
+ textFileRead(shader.name, &shader.text);
+ Preprocess(&shader, type, shader.definitions, shader_dir_path);
+ shader.modified = GetDateModifiedInt64(shader.name.c_str());
+ }
+}
+
+void Shaders::Reload(bool force) {
+ CHECK_GL_ERROR();
+ for(unsigned i=0; i<shaders.size(); ++i){
+ Shader &shader = shaders[i];
+ bool modified = (shader.modified != GetDateModifiedInt64(shader.name.c_str()));
+ if(!modified) { // Check included files to see if they were modified
+ for(unsigned j=0; j<shader.include_files.size(); j++){
+ modified = (shader.include_modified[j] != GetDateModifiedInt64(shader.include_files[j].c_str()));
+ if(modified) {
+ break;
+ }
+ }
+ }
+ if(modified || force) {
+ ReloadShader(i, shader.type);
+ }
+ }
+ unsigned num_programs = programs.size();
+ for(unsigned i=0; i<num_programs; ++i){
+ Program& program = programs[i];
+ if(program.gl_program != UNLOADED_SHADER_ID) {
+ bool unloaded = false;
+ for(int j=0; j<kMaxShaderTypes; ++j){
+ if(program.shader_ids[j] >= 0 && shaders[program.shader_ids[j]].gl_shader == UNLOADED_SHADER_ID){
+ unloaded = true;
+ break;
+ }
+ }
+ if(unloaded){
+ glDeleteProgram(program.gl_program);
+ program.gl_program = UNLOADED_SHADER_ID;
+ }
+ }
+ }
+ CHECK_GL_ERROR();
+}
+
+//Return the id of a shader
+int Shaders::returnShader(const char *path, ShaderType type, const std::vector<std::string> &definitions) {
+ std::string full_path;
+ full_path = path;
+ for(unsigned i=0; i<definitions.size(); ++i){
+ full_path += " #" + definitions[i];
+ }
+
+ for (unsigned i=0; i<shaders.size(); ++i){
+ if(full_path == shaders[i].full_name){
+ return i;
+ }
+ }
+
+ static const int kBufSize = 512;
+ char abs_path[kBufSize];
+ if(FindFilePath(path, abs_path, kBufSize, kDataPaths | kModPaths) == -1) {
+ FatalError("Error", "Could not find path for shader: %s", path);
+ }
+
+ shaders.push_back(Shader());
+ Shader& shader = shaders.back();
+ shader.name = abs_path;
+ shader.full_name = full_path;
+ shader.gl_shader = UNLOADED_SHADER_ID;
+ shader.definitions = definitions;
+ textFileRead(shader.name, &shader.text);
+ Preprocess(&shader, type, definitions, shader_dir_path);
+ shader.modified = GetDateModifiedInt64(shader.name.c_str());
+ return shaders.size()-1;
+}
+
+bool Shaders::createShader(int which, ShaderType type){
+ CHECK_GL_ERROR();
+ Shader &shader = shaders[which];
+ if (shader.gl_shader != UNLOADED_SHADER_ID) {
+ return true;
+ }
+
+ if (type == _vertex) {
+ shader.gl_shader = glCreateShader(GL_VERTEX_SHADER);
+ } else if (type == _fragment) {
+ shader.gl_shader = glCreateShader(GL_FRAGMENT_SHADER);
+ } else if (type == _geom ) {
+ shader.gl_shader = glCreateShader(GL_GEOMETRY_SHADER);
+ } else if (type == _tess_ctrl ) {
+ shader.gl_shader = glCreateShader(GL_TESS_CONTROL_SHADER);
+ } else if (type == _tess_eval ) {
+ shader.gl_shader = glCreateShader(GL_TESS_EVALUATION_SHADER);
+ }
+ shader.type = type;
+
+ const char* sources[] = {shader.text.c_str()};
+ glShaderSource(shader.gl_shader, 1, sources, NULL);
+ glCompileShader(shader.gl_shader);
+
+ // Output shader source to file
+ if (config["shader_debug"].toNumber<bool>()) {
+ size_t last_slash = shader.full_name.rfind('/');
+ if (last_slash == std::string::npos) {
+ last_slash = shader.full_name.rfind('\\');
+ }
+ size_t hashpos = shader.full_name.find('#');
+ std::string shader_name;
+
+ if (hashpos != std::string::npos) {
+ // If shader string contains '#' move type identifier to end
+ int hashlen = shader.full_name.length() - hashpos;
+ shader_name = shader.full_name.substr(last_slash + 1, shader.full_name.length() - last_slash - 7 - hashlen);
+ shader_name += shader.full_name.substr(hashpos, hashlen);
+ switch (shader.type) {
+ case _vertex:
+ shader_name += ".vert";
+ break;
+ case _fragment:
+ shader_name += ".frag";
+ break;
+ case _geom:
+ shader_name += ".geom";
+ break;
+ case _tess_eval:
+ shader_name += ".tess_eval";
+ break;
+ case _tess_ctrl:
+ shader_name += ".tess_ctrl";
+ break;
+ case kMaxShaderTypes:
+ LOGE << "Invalid enum value" << std::endl;
+ break;
+ }
+ } else {
+ // Otherwise just remove directory from full name
+ shader_name = shader.full_name.substr(last_slash + 1, shader.full_name.length() - last_slash - 1);
+ }
+ char buf[kPathSize];
+ FormatString(buf, kPathSize, "%sData/CompiledShaders/%s", GetWritePath(CoreGameModID).c_str(), shader_name.c_str());
+ FILE *file = my_fopen(buf, "w");
+ if (file){
+ fwrite(shader.text.c_str(), sizeof(char), shader.text.length(), file);
+ fclose(file);
+ }
+ }
+
+ int status = 0;
+ glGetShaderiv(shader.gl_shader, GL_COMPILE_STATUS, &status);
+ if (!status) {
+ int len = 0;
+ char buffer[1025] = {0};
+ glGetShaderInfoLog(shader.gl_shader, 1024, &len, buffer);
+ if(len != 0){
+ char errorbuffer[512];
+ int last_slash = 0;
+ for(int i=0, len=shader.full_name.size(); i<len; ++i){
+ char chr = shader.full_name[i];
+ if(chr == '/' || chr == '\\'){
+ last_slash = i+1;
+ }
+ }
+ FormatString(errorbuffer, 512, "Error(s) in %s",&(shader.full_name.c_str()[last_slash]));
+ if (status == 0 || config["shader_debug"].toNumber<bool>()) {
+ const int kBufSize = 2048;
+ char err_buf[kBufSize];
+ FormatString(err_buf, kBufSize, "%s\n%s", &(shader.full_name.c_str()[last_slash]), buffer);
+ // fatal error or shader debugging requested, show to user
+ ErrorResponse response = DisplayError(errorbuffer, err_buf, _ok_cancel_retry);
+ if( response == _retry) {
+ ReloadShader(which, type);
+ return createShader(which, type);
+ }
+ } else {
+ // only log it
+ LOGE << errorbuffer << " " << buffer << std::endl;
+ }
+ }
+
+ if (type == _geom || type == _tess_eval || type == _tess_ctrl) {
+ glDeleteShader(shader.gl_shader);
+ shader.gl_shader = UNLOADED_SHADER_ID;
+ return false;
+ }
+ }
+ CHECK_GL_ERROR();
+
+ return true;
+}
+
+//Return the id of a shader variable
+GLint Shaders::returnShaderVariable(const std::string &name, int which){
+ Program::UniformAddressCacheMap::iterator iter =
+ programs[which].uniform_address_cache.find(name);
+ if(iter == programs[which].uniform_address_cache.end()) {
+ GLint addr = glGetUniformLocation(programs[which].gl_program,name.c_str());
+ programs[which].uniform_address_cache[name] = addr;
+ return addr;
+ } else {
+ return iter->second;
+ }
+}
+
+GLint Shaders::returnShaderVariableIndex(const std::string &name, int program_id){
+ Program::UniformAddressCacheMap::iterator iter =
+ programs[program_id].uniform_address_cache.find(name);
+ if(iter == programs[program_id].uniform_address_cache.end()) {
+ GLuint index;
+ const char* name_cstr = name.c_str();
+ glGetUniformIndices(programs[program_id].gl_program, 1, &name_cstr, &index);
+ programs[program_id].uniform_address_cache[name] = index;
+ return index;
+ } else {
+ return iter->second;
+ }
+}
+
+GLint Shaders::returnShaderVariableOffset(int uniform_index, int program_id){
+ Program::UniformOffsetCacheMap::iterator iter =
+ programs[program_id].uniform_offset_cache.find(uniform_index);
+ if(iter == programs[program_id].uniform_offset_cache.end()) {
+ GLuint index = uniform_index;
+ GLint offset;
+ glGetActiveUniformsiv(programs[program_id].gl_program, 1, &index, GL_UNIFORM_OFFSET, &offset);
+ programs[program_id].uniform_offset_cache[uniform_index] = offset;
+ return offset;
+ } else {
+ return iter->second;
+ }
+}
+
+GLint Shaders::returnShaderBlockSize(int block_index, int program_id){
+ Program::UniformOffsetCacheMap::iterator iter =
+ programs[program_id].uniform_offset_cache.find(-block_index-1);
+ if(iter == programs[program_id].uniform_offset_cache.end()) {
+ GLint block_size;
+ glGetActiveUniformBlockiv(programs[program_id].gl_program, block_index, GL_UNIFORM_BLOCK_DATA_SIZE, &block_size);
+ programs[program_id].uniform_offset_cache[-block_index-1] = block_size;
+ return block_size;
+ } else {
+ return iter->second;
+ }
+}
+
+GLint Shaders::returnShaderAttrib(const std::string &name, int which){
+ Program::AttribAddressCacheMap::iterator iter =
+ programs[which].attrib_address_cache.find(name);
+ if(iter == programs[which].attrib_address_cache.end()) {
+ GLint addr = glGetAttribLocation(programs[which].gl_program,name.c_str());
+ programs[which].attrib_address_cache[name] = addr;
+ return addr;
+ } else {
+ return iter->second;
+ }
+}
+
+
+int Shaders::returnProgram(std::string name, OptionalShaders optional_shaders) {
+ std::string short_name;
+ if(name.find(' ') != std::string::npos){
+ short_name = name.substr(0, name.find(' '));
+ } else {
+ short_name = name;
+ }
+
+ unsigned num_programs = programs.size();
+ for(unsigned i=0; i<num_programs; ++i) {
+ if(name == programs[i].name){
+ return i;
+ }
+ }
+
+ PROFILER_ZONE(g_profiler_ctx, "Assemble shader program");
+
+ programs.push_back(Program());
+ Program& program = programs.back();
+
+ program.name = name;
+ program.gl_program = UNLOADED_SHADER_ID;
+
+ std::vector<std::string> definitions;
+ size_t name_len = name.length()+1;
+ int def_start = -1;
+ const int min_definition_length = 3; // Must be at least 3 letters long
+
+ for(size_t i=0; i<name_len; ++i){
+ switch(name[i]){
+ case '#':
+ def_start = i+1;
+ break;
+ case ' ':
+ case '\0':
+ if(def_start != -1 && def_start < static_cast<int>(i) - (min_definition_length - 1)) {
+ definitions.push_back(
+ std::string(&name[def_start], i-def_start));
+ }
+ def_start = -1;
+ break;
+ }
+ }
+
+ std::string vertex_path = GetShaderPath(short_name, shader_dir_path,_vertex);
+ std::string fragment_path = GetShaderPath(short_name, shader_dir_path,_fragment);
+ std::string geom_path = GetShaderPath(short_name, shader_dir_path,_geom);
+ std::string tess_eval_path = GetShaderPath(short_name, shader_dir_path,_tess_eval);
+ std::string tess_ctrl_path = GetShaderPath(short_name, shader_dir_path,_tess_ctrl);
+
+ if(optional_shaders & kGeometry && FileExists( geom_path.c_str(), kDataPaths | kModPaths) ) {
+ definitions.push_back("HAS_GEOM");
+ }
+
+ if(optional_shaders & kTesselation){
+ if( FileExists( tess_eval_path.c_str(), kDataPaths | kModPaths)) {
+ definitions.push_back("HAS_TESS_EVAL");
+ }
+
+ if( FileExists( tess_ctrl_path.c_str(), kDataPaths | kModPaths)) {
+ definitions.push_back("HAS_TESS_CTRL");
+ }
+ }
+
+ for(int i=0; i<kMaxShaderTypes; ++i){
+ program.shader_ids[i] = MISSING_GEOM_SHADER_ID;
+ }
+
+ program.shader_ids[_vertex] = returnShader(vertex_path.c_str(), _vertex, definitions);
+ program.shader_ids[_fragment] = returnShader(fragment_path.c_str(), _fragment, definitions);
+
+ if(optional_shaders & kGeometry && FileExists( geom_path.c_str(), kDataPaths | kModPaths) ) {
+ program.shader_ids[_geom] = returnShader(geom_path.c_str(), _geom, definitions);
+ }
+
+ if(optional_shaders & kTesselation){
+ if( FileExists( tess_eval_path.c_str(), kDataPaths | kModPaths)) {
+ program.shader_ids[_tess_eval] = returnShader(tess_eval_path.c_str(), _tess_eval, definitions);
+ }
+
+ if( FileExists( tess_ctrl_path.c_str(), kDataPaths | kModPaths)) {
+ program.shader_ids[_tess_ctrl] = returnShader(tess_ctrl_path.c_str(), _tess_ctrl, definitions);
+ }
+ }
+
+ return programs.size()-1;
+}
+
+bool Shaders::IsProgramTransparent (int which_program) {
+ return shaders[programs[which_program].shader_ids[_fragment]].transparent;
+}
+
+bool Shaders::DoesProgramUseTangent (int which_program) {
+ return shaders[programs[which_program].shader_ids[_fragment]].use_tangent ||
+ shaders[programs[which_program].shader_ids[_vertex]].use_tangent;
+}
+
+
+void Shaders::createProgram(int which_program) {
+ PROFILER_ZONE(g_profiler_ctx, "Shaders::createProgram");
+ CHECK_GL_ERROR();
+ Program& program = programs[which_program];
+ if(program.gl_program != UNLOADED_SHADER_ID){
+ return;
+ }
+ program.gl_program = glCreateProgram();
+ createShader(program.shader_ids[_vertex], _vertex);
+ for(int i=_geom; i<kMaxShaderTypes; ++i){
+ if( program.shader_ids[i] >= 0 ) {
+ bool success = createShader(program.shader_ids[i], (ShaderType)i );
+ // FIXME: this is a really bad hack
+ if (!success) {
+ program.shader_ids[i] = MISSING_GEOM_SHADER_ID;
+ }
+ }
+ }
+ createShader(program.shader_ids[_fragment], _fragment);
+
+ //So, the specification says that if the out refered to in the shader
+ //doesn't exist, this call should be ignored, but the driver on MacOSX
+ //for iris doesn't. Which causes a large amount of warnings to be generated
+ //So far only envobject has two frag outputs, so the quick simple solution it to
+ //check for envobject shader
+ //Is this a good argument for using .xml files to define shader programs?
+ //Or should we rather use a pragma in the shader to define this value?
+
+ Shader* frag_shader = &shaders[program.shader_ids[_fragment]];
+ if( frag_shader->depth_only == false ) {
+ if( frag_shader->bind_out_color ) {
+ glBindFragDataLocation(program.gl_program, 0, "out_color");
+ LOGI << "Binding out color for " << frag_shader->name << std::endl;
+ }
+
+ if( frag_shader->particle == false ) {
+ if( frag_shader->bind_out_vel ) {
+ glBindFragDataLocation(program.gl_program, 1, "out_vel");
+ LOGI << "Binding out vel for " << frag_shader->name << std::endl;
+ }
+ }
+ }
+
+ glAttachShader(program.gl_program,shaders[program.shader_ids[_vertex]].gl_shader);
+ for(int i=_geom; i<kMaxShaderTypes; ++i){
+ if( program.shader_ids[i] >= 0 ) {
+ glAttachShader(program.gl_program,shaders[program.shader_ids[(ShaderType)i]].gl_shader);
+ }
+ }
+ glAttachShader(program.gl_program,shaders[program.shader_ids[_fragment]].gl_shader);
+ glLinkProgram(program.gl_program);
+
+ int status = 0;
+ glGetProgramiv(program.gl_program, GL_LINK_STATUS, &status);
+#ifndef SHADER_WARNINGS
+ if (!status) {
+#endif
+ int len = 0;
+ char buffer[1025] = {0};
+ glGetProgramInfoLog(program.gl_program, 1024, &len, buffer);
+ if(len != 0){
+ char errorbuffer[512];
+ FormatString(errorbuffer, 512,"Error(s) linking program \"%s\"",program.name.c_str());
+ if (status == 0 || config["shader_debug"].toNumber<bool>()) {
+ // fatal error or shader debugging requested, show to user
+ ErrorResponse response = DisplayError(errorbuffer, buffer,_ok_cancel_retry);
+
+ if( response == _retry) {
+ ReloadShader(program.shader_ids[_vertex], _vertex);
+ ReloadShader(program.shader_ids[_geom], _geom);
+ ReloadShader(program.shader_ids[_fragment], _fragment);
+
+ glDeleteProgram(program.gl_program);
+ program.gl_program = UNLOADED_SHADER_ID;
+
+ createProgram(which_program);
+ return;
+ }
+ } else {
+ // only log it
+ LOGE << errorbuffer << " " << buffer << std::endl;
+ }
+ }
+#ifndef SHADER_WARNINGS
+ }
+#endif
+
+ if (config["shader_debug"].toNumber<bool>()) {
+ // Log some uniform data from shader
+ LOGI << "Shader: " << program.name << std::endl;
+ GLint num_raw_uniforms = 0;
+ glGetProgramiv(program.gl_program, GL_ACTIVE_UNIFORMS, &num_raw_uniforms);
+ LOGI << "Active uniforms: " << num_raw_uniforms << std::endl;
+
+ GLint max_name_uni_len = 0;
+ glGetProgramiv(program.gl_program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &max_name_uni_len);
+ LOGI << "Uniform name max length: " << max_name_uni_len << std::endl;
+
+ std::vector<char> name_buf(max_name_uni_len + 1, '\0');
+
+ LOGI << "Uniforms:" << std::endl;
+ for (int i = 0; i < num_raw_uniforms; i++) {
+ GLsizei real_len = 0;
+ GLint attr_size = 0;
+ GLenum gltype = GL_NONE;
+ glGetActiveUniform(program.gl_program, i, max_name_uni_len, &real_len, &attr_size, &gltype, &name_buf[0]);
+ LOGI << "Uniform #" << i << " " << &name_buf[0] << " length " << real_len << ", size " << attr_size << " type " << gltype << std::endl;
+ }
+ }
+
+ int active_attribs = 0;
+ glGetProgramiv(program.gl_program, GL_ACTIVE_ATTRIBUTES, &active_attribs);
+ LOGD << program.name << " attribs: " << active_attribs << std::endl;
+
+ CHECK_GL_ERROR();
+
+ program.tex_uniform.resize(32);
+ program.tex_uniform[0] = glGetUniformLocation(program.gl_program,"tex0");
+ program.tex_uniform[1] = glGetUniformLocation(program.gl_program,"tex1");
+ program.tex_uniform[2] = glGetUniformLocation(program.gl_program,"tex2");
+ program.tex_uniform[3] = glGetUniformLocation(program.gl_program,"tex3");
+ program.tex_uniform[4] = glGetUniformLocation(program.gl_program,"tex4");
+ program.tex_uniform[5] = glGetUniformLocation(program.gl_program,"tex5");
+ program.tex_uniform[6] = glGetUniformLocation(program.gl_program,"tex6");
+ program.tex_uniform[7] = glGetUniformLocation(program.gl_program,"tex7");
+ program.tex_uniform[8] = glGetUniformLocation(program.gl_program,"tex8");
+ program.tex_uniform[9] = glGetUniformLocation(program.gl_program,"tex9");
+ program.tex_uniform[10] = glGetUniformLocation(program.gl_program,"tex10");
+ program.tex_uniform[11] = glGetUniformLocation(program.gl_program,"tex11");
+ program.tex_uniform[12] = glGetUniformLocation(program.gl_program,"tex12");
+ program.tex_uniform[13] = glGetUniformLocation(program.gl_program,"tex13");
+ program.tex_uniform[14] = glGetUniformLocation(program.gl_program,"tex14");
+ program.tex_uniform[15] = glGetUniformLocation(program.gl_program,"tex15");
+ program.tex_uniform[16] = glGetUniformLocation(program.gl_program,"tex16");
+ program.tex_uniform[17] = glGetUniformLocation(program.gl_program,"tex17");
+ program.tex_uniform[18] = glGetUniformLocation(program.gl_program,"tex18");
+ program.tex_uniform[19] = glGetUniformLocation(program.gl_program,"tex19");
+ program.tex_uniform[20] = glGetUniformLocation(program.gl_program,"tex20");
+ program.tex_uniform[21] = glGetUniformLocation(program.gl_program,"tex21");
+ program.tex_uniform[22] = glGetUniformLocation(program.gl_program,"tex22");
+ program.tex_uniform[23] = glGetUniformLocation(program.gl_program,"tex23");
+ program.tex_uniform[24] = glGetUniformLocation(program.gl_program,"tex24");
+ program.tex_uniform[25] = glGetUniformLocation(program.gl_program,"tex25");
+ program.tex_uniform[26] = glGetUniformLocation(program.gl_program,"tex26");
+ program.tex_uniform[27] = glGetUniformLocation(program.gl_program,"tex27");
+ program.tex_uniform[28] = glGetUniformLocation(program.gl_program,"tex28");
+ program.tex_uniform[29] = glGetUniformLocation(program.gl_program,"tex29");
+ program.tex_uniform[30] = glGetUniformLocation(program.gl_program,"tex30");
+ program.tex_uniform[31] = glGetUniformLocation(program.gl_program,"tex31");
+ program.attrib_address_cache.clear();
+ program.uniform_address_cache.clear();
+ program.uniform_value_cache.clear();
+ program.uniform_offset_cache.clear();
+
+ program.commonShaderUniforms.resize(MAX_COMMON_UNIFORMS);
+ program.commonShaderUniforms[NORMAL_MATRIX] = glGetUniformLocation(program.gl_program,"normalMatrix");
+ program.commonShaderUniforms[MODEL_MATRIX] = glGetUniformLocation(program.gl_program,"modelMatrix");
+
+ for(unsigned i=0; i<32; ++i){
+ if(program.tex_uniform[i] > 1000){
+ std::ostringstream oss;
+ oss << "Program \"" << program.name <<
+ "\" tex uniform " << i << " is set to " <<
+ program.tex_uniform[i] << " which is outside" <<
+ " the normal range";
+ LOGE << oss.str().c_str() << std::endl;
+ }
+ }
+
+ GLuint shadow_cascade_idx = glGetUniformBlockIndex(program.gl_program, "ShadowCascades");
+ if (shadow_cascade_idx != GL_INVALID_INDEX) {
+ glUniformBlockBinding(program.gl_program, shadow_cascade_idx, UBO_SHADOW_CASCADES);
+ }
+}
+
+Program *Shaders::GetCurrentProgram()
+{
+ return bound_program == -1 ? 0 : &programs[bound_program];
+}
+
+GLint Shaders::GetTexUniform(int which) {
+ return programs[bound_program].tex_uniform[which];
+}
+
+//Bind a shader program
+void Shaders::setProgram(int which) {
+ if(which == -1){
+ noProgram();
+ return;
+ }
+ if(bound_program==which)return;
+ CHECK_GL_ERROR();
+ if(programs[which].gl_program == UNLOADED_SHADER_ID) {
+ if(create_program_warning) {
+ LOGW << "Loading shader which should be preloaded. " << level_path.GetOriginalPath() << ": " << programs[which].name << std::endl;
+ }
+ createProgram(which);
+ }
+ glUseProgram(programs[which].gl_program);
+ Textures::Instance()->InvalidateBindCache();
+ bound_program=which;
+ CHECK_GL_ERROR();
+}
+
+//Stop using shaders
+void Shaders::noProgram() {
+ if(bound_program == -1)return;
+ glUseProgram(0);
+ bound_program=-1;
+}
+
+void Shaders::SetUniformMat4(const std::string &var_name, const GLfloat* data) {
+ GLint var_id = returnShaderVariable(var_name,bound_program);
+ SetUniformMat4(var_id, data);
+}
+
+void Shaders::SetUniformMat4(GLint var_id, const GLfloat* data) {
+ if (var_id == -1) {
+ return;
+ }
+
+ std::vector<GLfloat> &value = programs[bound_program].uniform_value_cache[var_id];
+
+ bool changed = false;
+ if(value.size()!=16){
+ value.resize(16);
+ changed = true;
+ }
+
+ for(int i=0; i<16; i++){
+ if(value[i] != data[i]){
+ changed = true;
+ value[i] = data[i];
+ }
+ }
+
+ if(changed){
+ glUniformMatrix4fv(var_id, 1, GL_FALSE, data);
+ }
+}
+
+
+void Shaders::SetUniformMat3(const std::string & var_name, const GLfloat* data) {
+ GLint var_id = returnShaderVariable(var_name,bound_program);
+ SetUniformMat3(var_id, data);
+}
+
+void Shaders::SetUniformMat3(GLint var_id, const GLfloat* data) {
+ if (var_id == -1) {
+ return;
+ }
+
+ std::vector<GLfloat> &value = programs[bound_program].uniform_value_cache[var_id];
+
+ bool changed = false;
+ if(value.size()!=9){
+ value.resize(9);
+ changed = true;
+ }
+
+ for(int i=0; i<9; i++){
+ if(value[i] != data[i]){
+ changed = true;
+ value[i] = data[i];
+ }
+ }
+
+ if(changed){
+ glUniformMatrix3fv(var_id, 1, GL_FALSE, data);
+ }
+}
+
+void Shaders::SetUniformVec3( const std::string & var_name, const vec3& data_vec ) {
+ CHECK_GL_ERROR();
+ GLint var_id = returnShaderVariable(var_name,bound_program);
+ SetUniformVec3(var_id, data_vec);
+ CHECK_GL_ERROR();
+}
+
+void Shaders::SetUniformVec3( GLint var_id, const vec3& data_vec ) {
+ if (var_id == -1) {
+ return;
+ }
+
+ CHECK_GL_ERROR();
+ GLfloat *data = (GLfloat*)&data_vec;
+ SetUniformVec3(var_id, data);
+ CHECK_GL_ERROR();
+}
+
+void Shaders::SetUniformVec3( GLint var_id, const GLfloat* data ) {
+ if (var_id == -1) {
+ return;
+ }
+
+ CHECK_GL_ERROR();
+ std::vector<GLfloat> &value = programs[bound_program].uniform_value_cache[var_id];
+
+ bool changed = false;
+ if(value.size()!=3){
+ value.resize(3);
+ changed = true;
+ }
+
+ for(int i=0; i<3; i++){
+ if(value[i] != data[i]){
+ changed = true;
+ value[i] = data[i];
+ }
+ }
+
+ if(changed){
+ glUniform3fv(var_id, 1, data);
+ }
+ CHECK_GL_ERROR();
+}
+
+
+void Shaders::SetUniformVec2( const std::string & var_name, const vec2& data_vec ) {
+ CHECK_GL_ERROR();
+ GLint var_id = returnShaderVariable(var_name,bound_program);
+ SetUniformVec2(var_id, data_vec);
+ CHECK_GL_ERROR();
+}
+
+void Shaders::SetUniformVec2( GLint var_id, const vec2& data_vec ) {
+ if (var_id == -1) {
+ return;
+ }
+
+ CHECK_GL_ERROR();
+ GLfloat *data = (GLfloat*)&data_vec;
+ SetUniformVec2(var_id, data);
+ CHECK_GL_ERROR();
+}
+
+void Shaders::SetUniformVec2( GLint var_id, const GLfloat* data ) {
+ if (var_id == -1) {
+ return;
+ }
+
+ CHECK_GL_ERROR();
+ std::vector<GLfloat> &value = programs[bound_program].uniform_value_cache[var_id];
+
+ bool changed = false;
+ if(value.size()!=2){
+ value.resize(2);
+ changed = true;
+ }
+
+ for(int i=0; i<2; i++){
+ if(value[i] != data[i]){
+ changed = true;
+ value[i] = data[i];
+ }
+ }
+
+ if(changed){
+ glUniform2fv(var_id, 1, data);
+ }
+ CHECK_GL_ERROR();
+}
+
+void Shaders::SetUniformFloat( const std::string &var_name, const float& data ) {
+ CHECK_GL_ERROR();
+ GLint var_id = returnShaderVariable(var_name,bound_program);
+ SetUniformFloat(var_id, data);
+ CHECK_GL_ERROR();
+}
+
+void Shaders::SetUniformFloat( GLint var_id, const float& data) {
+ if (var_id == -1) {
+ return;
+ }
+
+ std::vector<GLfloat> &value = programs[bound_program].uniform_value_cache[var_id];
+
+ bool changed = false;
+ if(value.size()!=1){
+ value.resize(1);
+ changed = true;
+ }
+
+ if(value[0] != data){
+ changed = true;
+ value[0] = data;
+ }
+
+ if(changed){
+ glUniform1f(var_id, data);
+ }
+}
+
+void Shaders::SetUniformInt( const std::string &var_name, const int& data, ForceFlag force_flag ) {
+ GLint var_id = returnShaderVariable(var_name,bound_program);
+ SetUniformInt(var_id, data, force_flag);
+}
+
+void Shaders::SetUniformInt( GLint var_id, const int& data, ForceFlag force_flag) {
+ if (var_id == -1) {
+ return;
+ }
+
+ std::vector<GLfloat> &value = programs[bound_program].uniform_value_cache[var_id];
+
+ bool changed = false;
+ if(value.size()!=1){
+ value.resize(1);
+ changed = true;
+ }
+
+ if(value[0] != (GLfloat)data){
+ changed = true;
+ value[0] = (GLfloat)data;
+ }
+
+ if(changed || force_flag == kForce){
+ glUniform1i(var_id, data);
+ }
+}
+
+int Shaders::GetProgramBlendSrc( int which_program ) {
+ return shaders[programs[which_program].shader_ids[_fragment]].blend_src;
+}
+
+int Shaders::GetProgramBlendDst( int which_program ) {
+ return shaders[programs[which_program].shader_ids[_fragment]].blend_dst;
+}
+
+void Shaders::SetUniformMat4Array( const std::string &var_name, const std::vector<mat4> &transforms )
+{
+ GLint var_id = returnShaderVariable(var_name,bound_program);
+ SetUniformMat4Array(var_id, transforms);
+}
+
+void Shaders::SetUniformMat4Array(GLint var_id, const std::vector<mat4> &transforms) {
+ if (var_id == -1) {
+ return;
+ }
+
+ glUniformMatrix4fv(var_id, transforms.size(), GL_FALSE, &transforms[0].entries[0]);
+}
+
+void Shaders::SetUniformVec4Array( const std::string &var_name, const std::vector<vec4> &val )
+{
+ GLint var_id = returnShaderVariable(var_name,bound_program);
+ SetUniformVec4Array(var_id, val);
+}
+
+void Shaders::SetUniformVec4Array(GLint var_id, const std::vector<vec4> &val) {
+ if (var_id == -1) {
+ return;
+ }
+
+ glUniform4fv(var_id, val.size(), &val[0].entries[0]);
+}
+
+void Shaders::SetUniformVec3Array( const std::string &var_name, const std::vector<vec3> &val ) {
+ GLint var_id = returnShaderVariable(var_name,bound_program);
+ SetUniformVec3Array(var_id, val);
+}
+
+void Shaders::SetUniformVec3Array( GLint var_id, const std::vector<vec3> &val )
+{
+ if (var_id == -1) {
+ return;
+ }
+
+ glUniform3fv(var_id, val.size(), &val[0].entries[0]);
+}
+
+void Shaders::SetUniformVec3Array(GLint var_id, const vec3* val, int size) {
+ if (var_id == -1) {
+ return;
+ }
+
+ glUniform3fv(var_id, size, (const GLfloat*)val);
+}
+
+void Shaders::ResetVRAM()
+{
+ for(unsigned i=0; i<shaders.size(); ++i){
+ shaders[i].gl_shader = UNLOADED_SHADER_ID;
+ }
+ for(unsigned i=0; i<programs.size(); ++i){
+ programs[i].gl_program = UNLOADED_SHADER_ID;
+ programs[i].attrib_address_cache.clear();
+ programs[i].uniform_address_cache.clear();
+ programs[i].uniform_value_cache.clear();
+ programs[i].uniform_offset_cache.clear();
+ }
+}
+
+int Shaders::GetUBOBindIndex(int shader_id, const char* name) {
+ LOG_ASSERT(shader_id >= 0 && shader_id < (int)programs.size());
+ Program* program = &programs[shader_id];
+ LOG_ASSERT(program->gl_program != UNLOADED_SHADER_ID);
+ Program::UniformAddressCacheMap::iterator iter =
+ program->uniform_address_cache.find(name);
+ if(iter == program->uniform_address_cache.end()) {
+ int programHandle = program->gl_program;
+ GLuint blockIndex = glGetUniformBlockIndex(programHandle, name);
+ program->uniform_address_cache[name] = blockIndex;
+ return blockIndex;
+ } else {
+ return iter->second;
+ }
+}
+
+void Shaders::SetUniformVec4( const std::string & var_name, const vec4& data_vec ) {
+ CHECK_GL_ERROR();
+ GLint var_id = returnShaderVariable(var_name,bound_program);
+ SetUniformVec4(var_id, data_vec);
+ CHECK_GL_ERROR();
+}
+
+void Shaders::SetUniformVec4( GLint var_id, const vec4& data_vec ) {
+ if (var_id == -1) {
+ return;
+ }
+
+ CHECK_GL_ERROR();
+ GLfloat *data = (GLfloat*)&data_vec;
+ SetUniformVec4(var_id, data);
+ CHECK_GL_ERROR();
+}
+
+void Shaders::SetUniformVec4( GLint var_id, const GLfloat* data ) {
+ if (var_id == -1) {
+ return;
+ }
+
+ if (data == NULL) {
+ LOGE << "Data pointer is null." << std::endl;
+ return;
+ }
+
+ CHECK_GL_ERROR();
+ std::vector<GLfloat> &value = programs[bound_program].uniform_value_cache[var_id];
+
+ bool changed = false;
+ if(value.size()!=4){
+ value.resize(4);
+ changed = true;
+ }
+
+ for(int i=0; i<4; i++){
+ if(value[i] != data[i]){
+ changed = true;
+ value[i] = data[i];
+ }
+ }
+
+ if(changed){
+ glUniform4fv(var_id, 1, data);
+ }
+ CHECK_GL_ERROR();
+}
+
+std::string GetShaderPath( const std::string &shader_name, const std::string& shader_dir_path, ShaderType type ) {
+ switch(type){
+ case _vertex: return shader_dir_path+shader_name+".vert";
+ case _fragment: return shader_dir_path+shader_name+".frag";
+ case _geom: return shader_dir_path+shader_name+".geom";
+ case _tess_eval: return shader_dir_path+shader_name+".tess_eval";
+ case _tess_ctrl: return shader_dir_path+shader_name+".tess_ctrl";
+ default: return "";
+ }
+}
+
+void ShaderUniform::Submit()
+{
+ switch(type) {
+ case ShaderUniformType::UNKNOWN:
+ case ShaderUniformType::SAMPLER2D:
+ case ShaderUniformType::SAMPLER3D:
+ case ShaderUniformType::SAMPLERCUBE:
+ break;
+ case ShaderUniformType::INT:
+ glUniform1fv(address, 1, cachedValue);
+ break;
+ case ShaderUniformType::FLOAT:
+ glUniform1fv(address, 1, cachedValue);
+ break;
+ case ShaderUniformType::VEC2:
+ glUniform2fv(address, 1, cachedValue);
+ break;
+ case ShaderUniformType::VEC3:
+ glUniform3fv(address, 1, cachedValue);
+ break;
+ case ShaderUniformType::VEC4:
+ glUniform4fv(address, 1, cachedValue);
+ break;
+ case ShaderUniformType::MAT3:
+ glUniformMatrix3fv(address, 1, GL_FALSE, cachedValue);
+ break;
+ case ShaderUniformType::MAT4:
+ glUniformMatrix4fv(address, 1, GL_FALSE, cachedValue);
+ break;
+ default:
+ break;
+ }
+}
diff --git a/Source/Graphics/shaders.h b/Source/Graphics/shaders.h
new file mode 100644
index 00000000..7fcd709d
--- /dev/null
+++ b/Source/Graphics/shaders.h
@@ -0,0 +1,278 @@
+//-----------------------------------------------------------------------------
+// Name: shaders.h
+// Author: David Rosen
+// Developer: Wolfire Games LLC
+// Description: The shaders class holds an array of shader ID's, the
+// shader creation states, and the current bound shader.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/enginemath.h>
+#include <Math/vec2.h>
+
+#include <Graphics/textures.h>
+#include <Internal/datemodified.h>
+#include <Utility/flat_hash_map.hpp>
+
+#include <opengl.h>
+
+#define maximum_shaders 200
+#define maximum_programs 200
+
+enum ShaderType {_vertex = 0,
+ _fragment,
+ _geom,
+ _tess_ctrl,
+ _tess_eval,
+ kMaxShaderTypes};
+
+const unsigned int UNLOADED_SHADER_ID = (unsigned int)-2;
+const int MISSING_GEOM_SHADER_ID = -1;
+
+enum BlendMode {
+ _BM_NORMAL = 0,
+ _BM_ADD = 1,
+ _BM_MULTIPLY = 2
+};
+
+struct ShaderUniformType {
+ enum Type {
+ UNKNOWN = 0,
+
+ SAMPLER2D,
+ SAMPLER3D,
+ SAMPLERCUBE,
+
+ INT,
+ FLOAT,
+
+ VEC2,
+ VEC3,
+ VEC4,
+
+ MAT3,
+ MAT4,
+
+ NUM_TYPES
+ } type;
+
+ operator Type() const { return type; }
+
+ ShaderUniformType(Type t) : type(t) {}
+ ShaderUniformType(const std::string &t) {
+ if (t == "sampler2D") {
+ type = SAMPLER2D;
+ } else if (t == "sampler3D") {
+ type = SAMPLER3D;
+ } else if (t == "samplerCube") {
+ type = SAMPLERCUBE;
+ } else if (t == "int" || t == "bool") {
+ type = INT;
+ } else if (t == "float") {
+ type = FLOAT;
+ } else if (t == "vec2") {
+ type = VEC2;
+ } else if (t == "vec3") {
+ type = VEC3;
+ } else if (t == "vec4") {
+ type = VEC4;
+ } else if (t == "mat3") {
+ type = MAT3;
+ } else if (t == "mat4") {
+ type = MAT4;
+ } else {
+ type = UNKNOWN;
+ }
+ }
+ unsigned int size() const {
+ static const unsigned int SIZES[NUM_TYPES] = {
+ 0, //UNKNOWN = 0,
+ 0, //SAMPLER2D,
+ 0, //SAMPLER3D,
+ 0, //SAMPLERCUBE,
+ 1, //INT,
+ 1, //FLOAT,
+ 2, //VEC2,
+ 3, //VEC3,
+ 4, //VEC4,
+ 9, //MAT3,
+ 16 //MAT4
+ };
+ return SIZES[type];
+ }
+};
+
+struct ShaderUniform {
+ ShaderUniformType type;
+ std::string name;
+ GLint address; //uninitialized
+ float *cachedValue;
+
+ ShaderUniform() : type(ShaderUniformType::UNKNOWN), cachedValue(0) {}
+ ShaderUniform(const std::string &t, const std::string &n) : type(t), name(n), cachedValue(0) {}
+ void Submit();
+};
+
+class Shader {
+public:
+ GLuint gl_shader;
+ std::string name;
+ std::string full_name;
+ std::string text;
+ ShaderType type;
+ int64_t modified;
+ bool transparent;
+ bool use_tangent;
+ bool bind_out_color;
+ bool bind_out_vel;
+ bool depth_only;
+ bool particle;
+ int blend_src;
+ int blend_dst;
+ std::vector<std::string> definitions;
+ std::vector<int64_t> include_modified;
+ std::vector<std::string> include_files;
+};
+
+enum CommonShaderUniformIDs {
+ NORMAL_MATRIX = 0,
+ MODEL_MATRIX,
+
+ MAX_COMMON_UNIFORMS
+};
+
+struct UniformBufferObject {
+ GLuint id;
+};
+
+class Program {
+public:
+ GLuint gl_program;
+ std::string name;
+ int shader_ids[kMaxShaderTypes];
+ std::vector<GLint> tex_uniform;
+ typedef ska::flat_hash_map<std::string, GLint> UniformAddressCacheMap;
+ UniformAddressCacheMap uniform_address_cache;
+ typedef ska::flat_hash_map<GLint, GLint> UniformOffsetCacheMap;
+ UniformOffsetCacheMap uniform_offset_cache;
+ typedef ska::flat_hash_map<std::string, GLint> AttribAddressCacheMap;
+ AttribAddressCacheMap attrib_address_cache;
+ typedef ska::flat_hash_map<GLint, std::vector<GLfloat> > UniformValueCacheMap;
+ UniformValueCacheMap uniform_value_cache;
+ std::vector<GLint> commonShaderUniforms;
+
+public:
+ template <typename T> void SetUniform(CommonShaderUniformIDs uniformID, const T& value)
+ {
+ SetUniform(commonShaderUniforms[uniformID],value);
+ }
+ void SetUniform(GLint uniformID, const mat4& value)
+ {
+ //note that this avoids all of the caching that would otherwise happen
+ glUniformMatrix4fv(uniformID, 1, GL_FALSE, value);
+ }
+ void SetUniform(GLint uniformID, const mat3& value)
+ {
+ //note that this avoids all of the caching that would otherwise happen
+ glUniformMatrix3fv(uniformID, 1, GL_FALSE, value);
+ }
+};
+
+class Shaders {
+ private:
+ std::vector<Shader> shaders;
+
+ Shaders():bound_program(-1), create_program_warning(false) {
+ // Help fix race conditions in loading screen
+ programs.reserve(1000);
+ shaders.reserve(1000);
+ }
+
+ public:
+ std::vector<Program> programs;
+ enum ForceFlag {kNoForce, kForce}; // To determine if a value should be set even if the shadow state matches
+ enum OptionalShaders {kNone = 0, kGeometry = 1, kTesselation = 2};
+
+ int bound_program;
+ std::string shader_dir_path;
+ // If true, print a warning when a shader program is created.
+ // Useful to make sure all shaders are preloaded
+ Path level_path;
+ bool create_program_warning;
+
+ void setProgram(int which);
+ GLint returnShaderVariable(const std::string &name, int which);
+ GLint returnShaderVariableIndex(const std::string &name, int which);
+ GLint returnShaderVariableOffset(int uniform_index, int program_id);
+ GLint returnShaderBlockSize(int block_index, int program_id);
+ int returnShader(const char *path, ShaderType type, const std::vector<std::string> &definitions);
+ int returnProgram(std::string name, OptionalShaders optional_shaders = kNone);
+ bool createShader(int which, ShaderType type);
+ void createProgram(int which_program);
+ Program *GetCurrentProgram();
+
+ GLint GetTexUniform(int which);
+ void SetUniformMat4(const std::string & var_name, const GLfloat* data);
+ void SetUniformMat4(GLint var_id, const GLfloat* data);
+ void SetUniformMat3(const std::string & var_name, const GLfloat* data);
+ void SetUniformMat3(GLint var_id, const GLfloat* data);
+ void SetUniformVec3(const std::string & var_name, const vec3& data);
+ void SetUniformVec3(GLint var_id, const vec3& data);
+ void SetUniformVec3(GLint var_id, const GLfloat* data);
+ void SetUniformVec4(const std::string & var_name, const vec4& data);
+ void SetUniformVec4(GLint var_id, const vec4& data);
+ void SetUniformVec4(GLint var_id, const GLfloat* data);
+
+ bool IsProgramTransparent (int which_program);
+ int GetProgramBlendSrc (int which_program);
+ int GetProgramBlendDst (int which_program);
+ void noProgram();
+
+ void Reload(bool force = false);
+ void ReloadShader(int which, ShaderType type);
+ void Dispose();
+
+ static Shaders* Instance()
+ {
+ static Shaders instance;
+ return &instance;
+ }
+ void SetUniformVec2( const std::string & var_name, const vec2& data_vec );
+ void SetUniformVec2( GLint var_id, const vec2& data_vec );
+ void SetUniformVec2( GLint var_id, const GLfloat* data );
+ void SetUniformFloat( const std::string &var_name, const float& data );
+ void SetUniformFloat( GLint var_id, const float& data);
+ void SetUniformInt( const std::string & var_name, const int& data, ForceFlag force_flag = kNoForce );
+ void SetUniformInt( GLint var_id, const int& data, ForceFlag force_flag = kNoForce);
+ bool DoesProgramUseTangent (int which_program);
+ void SetUniformMat4Array( const std::string &var_name, const std::vector<mat4> &transforms );
+ void SetUniformMat4Array(GLint var_id, const std::vector<mat4> &transforms);
+ GLint returnShaderAttrib(const std::string &name, int which);
+ void SetUniformVec4Array( const std::string &var_name, const std::vector<vec4> &val );
+ void SetUniformVec4Array(GLint var_id, const std::vector<vec4> &val);
+ void SetUniformVec3Array( const std::string &var_name, const std::vector<vec3> &val );
+ void SetUniformVec3Array(GLint var_id, const std::vector<vec3> &val);
+ void SetUniformVec3Array(GLint var_id, const vec3* val, int size);
+ void ResetVRAM();
+ int GetUBOBindIndex( int shader_id, const char* name );
+};
+
+std::string GetShaderPath( const std::string &shader_name, const std::string& shader_dir_path, ShaderType type );
diff --git a/Source/Graphics/simplify.cpp b/Source/Graphics/simplify.cpp
new file mode 100644
index 00000000..e14442b0
--- /dev/null
+++ b/Source/Graphics/simplify.cpp
@@ -0,0 +1,756 @@
+//-----------------------------------------------------------------------------
+// Name: simplify.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "simplify.hpp"
+
+#include <Graphics/halfedge.h>
+#include <Graphics/model.h>
+#include <Logging/logdata.h>
+#include <Wrappers/glm.h>
+
+#include <algorithm>
+#include <list>
+#include <vector>
+#include <set>
+
+using namespace WOLFIRE_SIMPLIFY;
+
+namespace {
+ struct TextureEdge {
+ int id[2];
+ bool swapped;
+ };
+ /*
+ bool EdgeCompare(TextureEdge &a, TextureEdge &b){
+ if(a.id[0] != b.id[0]){
+ return a.id[0] < b.id[0];
+ } else {
+ return a.id[1] < b.id[1];
+ }
+ }
+ */
+ struct EdgeCollapseRecord {
+ int vert[2];
+ float pos;
+ };
+
+ class VertTris {
+ public:
+ void Initialize(const std::vector<float> &vertices, const std::vector<int> &vert_indices){
+ // Make space to store the number of triangles connected to each vertex
+ unsigned num_verts = vertices.size()/3;
+ num_vert_tris.clear();
+ num_vert_tris.resize(num_verts, 0);
+ // Loop through vertex indices and increment triangle count for each vertex
+ unsigned num_vert_indices = vert_indices.size();
+ for(unsigned i=0; i<num_vert_indices; ++i){
+ ++num_vert_tris[vert_indices[i]];
+ }
+ // Index list of vertex triangles for fast lookup
+ int index = 0;
+ vert_tris_index.clear();
+ vert_tris_index.resize(num_verts);
+ for(unsigned i=0; i<num_verts; ++i){
+ vert_tris_index[i] = index;
+ index += num_vert_tris[i];
+ num_vert_tris[i] = 0; // Reset so we can use this to fill vert_tris
+ }
+ // Fill list of triangles for each vertex
+ vert_tris.clear();
+ vert_tris.resize(num_vert_indices);
+ for(unsigned i=0; i<num_vert_indices; ++i){
+ int vert = vert_indices[i];
+ int &nvt = num_vert_tris[vert];
+ vert_tris[vert_tris_index[vert]+nvt] = i;
+ ++nvt;
+ }
+ }
+
+ // Get number of tris that include vertex
+ inline int NumTrisConnectedToVert(int i) const {
+ return num_vert_tris[i];
+ }
+
+ // Get array of tris that include vertex
+ // The int is the index of vert_indices, so the tri
+ // id is really vert_tris[i]%3
+ inline const int* GetTrisConnectedToVert(int i) const {
+ return &vert_tris[vert_tris_index[i]];
+ }
+ private:
+ std::vector<int> num_vert_tris;
+ std::vector<int> vert_tris;
+ std::vector<int> vert_tris_index;
+ };
+} // namespace ""
+
+namespace {
+ struct TexSort {
+ glm::vec2 coord;
+ int old_index;
+ int old_id;
+ };
+
+ struct VertSort {
+ glm::vec3 coord;
+ int old_index;
+ int old_id;
+ };
+
+ bool operator<(const TexSort &a, const TexSort &b) {
+ if(a.coord[0] == b.coord[0]){
+ return a.coord[1] < b.coord[1];
+ } else {
+ return a.coord[0] < b.coord[0];
+ }
+ }
+
+ bool operator<(const VertSort &a, const VertSort &b) {
+ if(a.coord[0] == b.coord[0]){
+ if(a.coord[1] == b.coord[1]){
+ return a.coord[2] < b.coord[2];
+ } else {
+ return a.coord[1] < b.coord[1];
+ }
+ } else {
+ return a.coord[0] < b.coord[0];
+ }
+ }
+} // namespace ""
+
+static void GetTexCoordNumPerVertex(const SimplifyModel *model, std::vector<int> &num_tc){
+ // Count number of times each vertex is called in vert_indices
+ num_tc.clear();
+ num_tc.resize(model->vertices.size()/3, 0);
+ for(int i=0, len=model->vert_indices.size(); i<len; ++i){
+ if(model->vert_indices[i] != -1){
+ ++num_tc[model->vert_indices[i]];
+ }
+ }
+ // Index for fast access
+ std::vector<int> tc_index(num_tc.size(), 0);
+ for(int i=0, len=num_tc.size(), index=0; i<len; ++i){
+ tc_index[i] = index;
+ index += num_tc[i];
+ }
+ // Clear num_tc for reuse
+ for(int i=0, len=num_tc.size(); i<len; ++i){
+ num_tc[i] = 0;
+ }
+ // Record texture indices
+ std::vector<int> tc(model->vert_indices.size());
+ for(int i=0, len=model->vert_indices.size(); i<len; ++i){
+ int vert = model->vert_indices[i];
+ if(vert != -1){
+ tc[tc_index[vert]+num_tc[vert]] = model->tex_indices[i];
+ ++num_tc[vert];
+ }
+ }
+ // Sort texture indices
+ for(int i=0, len=num_tc.size(); i<len; ++i){
+ std::sort(tc.begin()+tc_index[i], tc.begin()+tc_index[i]+num_tc[i]);
+ }
+ // Clear num_tc for reuse
+ for(int i=0, len=num_tc.size(); i<len; ++i){
+ if(num_tc[i] != 0) {
+ int count = 1;
+ for(int j=1, index=tc_index[i]+1; j<num_tc[i]; ++j, ++index){
+ if(tc[index] != tc[index-1]){
+ ++count;
+ }
+ }
+ num_tc[i] = count;
+ }
+ }
+}
+
+static void ProcessModel( const SimplifyModelInput &smi, SimplifyModel *model, float merge_threshold, bool include_tex) {
+ // Create sorted list of vertices
+ int num_indices = smi.vertices.size()/3;
+ std::vector<VertSort> sorted_verts(num_indices);
+ std::vector<TexSort> sorted_tex(num_indices);
+ for(int i=0, vci=0, tci=0; i<num_indices; ++i, vci+=3, tci+=2){
+ for(int k=0; k<3; ++k){
+ sorted_verts[i].coord[k] = smi.vertices[vci+k];
+ }
+ if(include_tex) {
+ for(int k=0; k<2; ++k){
+ sorted_tex[i].coord[k] = smi.tex_coords[tci+k];
+ }
+ }
+ sorted_verts[i].old_index = i;
+ sorted_verts[i].old_id = smi.old_vert_id[i];
+ if(include_tex) {
+ sorted_tex[i].old_index = i;
+ sorted_tex[i].old_id = smi.old_tex_id[i];
+ }
+ }
+ std::sort(sorted_verts.begin(), sorted_verts.end());
+ if(include_tex) {
+ std::sort(sorted_tex.begin(), sorted_tex.end());
+ }
+ // Remove duplicate vertices and form vertex index list
+ std::vector<int> vert_indices(num_indices);
+ if(!sorted_verts.empty()) {
+ vert_indices[sorted_verts[0].old_index] = 0;
+ int index = 1;
+ for(int i=1, len=sorted_verts.size(); i<len; ++i){
+ // Check if squared distance between vertices is greater than merge_threshold
+ glm::vec3 vec = sorted_verts[i].coord - sorted_verts[i-1].coord;
+ if(glm::dot(vec, vec) > merge_threshold) {
+ sorted_verts[index].coord = sorted_verts[i].coord;
+ sorted_verts[index].old_id = sorted_verts[i].old_id;
+ ++index;
+ }
+ vert_indices[sorted_verts[i].old_index] = index-1;
+ }
+ sorted_verts.resize(index);
+ }
+
+ // Remove duplicate tex coords and form tex index list
+ std::vector<int> tex_indices(num_indices);
+ if(include_tex) {
+ if(!sorted_tex.empty()) {
+ tex_indices[sorted_tex[0].old_index] = 0;
+ int index = 1;
+ for(int i=1, len=sorted_tex.size(); i<len; ++i){
+ if(sorted_tex[i].coord != sorted_tex[i-1].coord) {
+ sorted_tex[index].coord = sorted_tex[i].coord;
+ sorted_tex[index].old_id = sorted_tex[i].old_id;
+ ++index;
+ }
+ tex_indices[sorted_tex[i].old_index] = index-1;
+ }
+ sorted_tex.resize(index);
+ }
+ }
+
+ // Remove degenerate triangles
+ int garbage_index = num_indices;
+ for(int i=0; i<garbage_index; i+=3){
+ while(i<=garbage_index &&
+ (vert_indices[i+0] == vert_indices[i+1] ||
+ vert_indices[i+1] == vert_indices[i+2] ||
+ vert_indices[i+2] == vert_indices[i+0]))
+ {
+ garbage_index -= 3;
+ for(int j=0; j<3; ++j){
+ vert_indices[i+j] = vert_indices[garbage_index+j];
+ }
+ if(include_tex) {
+ for(int j=0; j<3; ++j){
+ tex_indices[i+j] = tex_indices[garbage_index+j];
+ }
+ }
+ }
+ }
+ vert_indices.resize(garbage_index);
+ if(include_tex) {
+ tex_indices.resize(garbage_index);
+ }
+ // Copy processed info into SimplifyModel
+ if(include_tex) {
+ model->tex_indices = tex_indices;
+ }
+ model->vert_indices = vert_indices;
+ model->vertices.resize(sorted_verts.size()*3);
+ model->old_vert_id.resize(sorted_verts.size());
+ for(int i=0, len=sorted_verts.size(), index=0; i<len; ++i, index+=3){
+ for(int j=0; j<3; ++j){
+ model->vertices[index+j] = sorted_verts[i].coord[j];
+ }
+ model->old_vert_id[i] = sorted_verts[i].old_id;
+ }
+
+ if(include_tex) {
+ model->tex_coords.resize(sorted_tex.size()*2);
+ model->old_tex_id.resize(sorted_tex.size());
+ for(int i=0, len=sorted_tex.size(), index=0; i<len; ++i, index+=2){
+ for(int j=0; j<2; ++j){
+ model->tex_coords[index+j] = sorted_tex[i].coord[j];
+ }
+ model->old_tex_id[i] = sorted_tex[i].old_id;
+ }
+ }
+}
+
+static bool GenerateEdgePairs(WOLFIRE_SIMPLIFY::SimplifyModel& processed_model, vector<HalfEdge>& half_edges, bool include_tex) {
+ half_edges.resize(processed_model.vert_indices.size());
+ LOGI << "Setting up half-edges..." << std::endl;
+ // Set up edges
+ {
+ int edge_index = 0;
+ for(size_t i=0, len=processed_model.vert_indices.size(); i<len; i+=3){
+ for(int j=0; j<3; ++j){
+ HalfEdge he;
+ he.vert[0] = processed_model.vert_indices[i+j];
+ he.vert[1] = processed_model.vert_indices[i+(j+1)%3];
+ if(include_tex) {
+ he.tex[0] = processed_model.tex_indices[i+j];
+ he.tex[1] = processed_model.tex_indices[i+(j+1)%3];
+ }
+ he.next = &half_edges[edge_index+(j+1)%3];
+ he.prev = &half_edges[edge_index+(j+2)%3];
+ he.twin = NULL;
+ he.err = UNDEFINED_ERROR;
+ he.id = edge_index + j;
+ he.valid = true;
+ half_edges[edge_index+j] = he;
+ }
+ edge_index += 3;
+ }
+ }
+ LOGI << "Finding edge pairs..." << std::endl;
+ // Find pairs
+ {
+ std::vector<HalfEdge> half_edge_pairs = half_edges;
+ std::sort(half_edge_pairs.begin(), half_edge_pairs.end(), HalfEdgePairFind);
+
+ for(size_t i=1, len=half_edge_pairs.size(); i<len; ++i){
+ if(half_edge_pairs[i].vert[0] == half_edge_pairs[i-1].vert[1] &&
+ half_edge_pairs[i].vert[1] == half_edge_pairs[i-1].vert[0])
+ {
+ half_edges[half_edge_pairs[i].id].twin = &half_edges[half_edge_pairs[i-1].id];
+ half_edges[half_edge_pairs[i-1].id].twin = &half_edges[half_edge_pairs[i].id];
+ }
+ }
+ }
+ LOGI << "Validating edge pairs." << std::endl;
+ bool missing_pair = false;
+ bool invalid_pair = false;
+ for(size_t i=0, len=half_edges.size(); i<len; ++i){
+ if(half_edges[i].twin == NULL){
+ missing_pair = true;
+ } else if(half_edges[i].twin->twin == NULL || half_edges[i].twin->twin != &half_edges[i]){
+ invalid_pair = true;
+ }
+ }
+ if(!missing_pair && !invalid_pair){
+ LOGI << "All edge pairs are valid!" << std::endl;
+ } else if(missing_pair && !invalid_pair){
+ LOGI << "All edge pairs are valid but some are missing." << std::endl;
+ } else if(!missing_pair && invalid_pair){
+ LOGI << "No edge pairs are missing but some are INVALID." << std::endl;
+ } else if(missing_pair && invalid_pair){
+ LOGI << "MISSING AND INVALID EDGE PAIRS." << std::endl;
+ }
+ if(invalid_pair){
+ LOGI << "Aborting simplification due to invalid edge pairs" << std::endl;
+ return false;
+ }
+ return true;
+}
+
+static void CalculateEdgeErrors(vector<HalfEdge>& half_edges, SimplifyModel& processed_model, vector<glm::mat4>& quadrics_, bool include_tex) {
+ LOGI << "Calculating edge error..." << std::endl;
+ for(int i=0, len=half_edges.size(); i<len; ++i){
+ HalfEdge& edge = half_edges[i];
+ if(edge.err == UNDEFINED_ERROR){
+ int edge_tc[2];
+ if(include_tex) {
+ edge_tc[0] = GetNumTexCoords(&edge, 0);
+ edge_tc[1] = GetNumTexCoords(&edge, 1);
+ } else {
+ edge_tc[0] = 0;
+ edge_tc[1] = 0;
+ }
+ edge.err = WOLFIRE_SIMPLIFY::CalculateError(&edge.pos, edge.vert, processed_model.vertices, quadrics_, edge_tc);
+ if(edge.twin){
+ edge.twin->err = edge.err;
+ edge.twin->pos = 1.0f - edge.pos;
+ }
+ }
+ }
+}
+
+static void InitHeap(vector<HalfEdge>& half_edges, HalfEdgeNodeHeap& heap, HalfEdgeSetVec& vert_edges, HalfEdgeSetVec& tex_edges, bool include_tex) {
+ LOGI << "Adding edges to heap..." << std::endl;
+ for(int i=0, len=half_edges.size(); i<len; ++i){
+ HalfEdgeNode node;
+ node.edge = &half_edges[i];
+ half_edges[i].handle = heap.insert(node);
+ }
+ for(int i=0, len=half_edges.size(); i<len; ++i){
+ HalfEdge *edge = &half_edges[i];
+ vert_edges[edge->vert[0]].insert(edge);
+ vert_edges[edge->vert[1]].insert(edge);
+ if(include_tex) {
+ tex_edges[edge->tex[0]].insert(edge);
+ tex_edges[edge->tex[1]].insert(edge);
+ }
+ }
+}
+
+static void InitParents(ParentRecordListVec& vert_parents, ParentRecordListVec& tex_parents, bool include_tex) {
+ for(int i=0, len=vert_parents.size(); i<len; ++i){
+ ParentRecord pr;
+ pr.id = i;
+ pr.weight = 1.0f;
+ vert_parents[i].push_back(pr);
+ }
+ if(include_tex) {
+ for(int i=0, len=tex_parents.size(); i<len; ++i){
+ ParentRecord pr;
+ pr.id = i;
+ pr.weight = 1.0f;
+ tex_parents[i].push_back(pr);
+ }
+ }
+}
+
+static void ToModel(SimplifyModel& from_simplify_model, Model* to_model, bool include_tex) {
+ to_model->vertices.clear();
+ to_model->tex_coords.clear();
+ to_model->faces.clear();
+
+ for(int i = 0; i < from_simplify_model.vert_indices.size(); i++) {
+ for(int k = 0; k < 3; k++) {
+ to_model->vertices.push_back(from_simplify_model.vertices[from_simplify_model.vert_indices[i] * 3 + k]);
+ }
+
+ if(include_tex) {
+ if(i < from_simplify_model.tex_indices.size()) {
+ for(int k = 0; k < 2; k++) {
+ to_model->tex_coords.push_back(from_simplify_model.tex_coords[from_simplify_model.tex_indices[i] * 2 + k]);
+ }
+ }
+ }
+ to_model->faces.push_back(i);
+ }
+}
+
+float WOLFIRE_SIMPLIFY::CalculateError(float* pos, int edge[2], const std::vector<float> &vertices, const std::vector<glm::mat4> &quadrics, const int edge_tc[]) {
+ int vert_index[2];
+ vert_index[0] = edge[0]*3;
+ vert_index[1] = edge[1]*3;
+ glm::vec4 vert_vec[2];
+ vert_vec[0] = glm::vec4(vertices[vert_index[0]+0],
+ vertices[vert_index[0]+1],
+ vertices[vert_index[0]+2],
+ 1.0f);
+ vert_vec[1] = glm::vec4(vertices[vert_index[1]+0],
+ vertices[vert_index[1]+1],
+ vertices[vert_index[1]+2],
+ 1.0f);
+ float quadric_error = 0;
+ glm::mat4 total_quadric = quadrics[edge[0]] + quadrics[edge[1]];
+ if(edge_tc[0] > edge_tc[1]){
+ *pos = 0.0f;
+ quadric_error = glm::dot(vert_vec[0], total_quadric * vert_vec[0]);
+ } else if(edge_tc[0] < edge_tc[1]){
+ *pos = 1.0f;
+ quadric_error = glm::dot(vert_vec[1], total_quadric * vert_vec[1]);
+ } else {
+ //glm::vec4 offset = vert_vec[1] - vert_vec[0];
+ float best_err = FLT_MAX;
+ for(int i=0; i<=10; ++i){
+ float amount = i*0.1f;
+ glm::vec4 temp_vec;
+ for(int j=0; j<3; ++j){
+ temp_vec[j] = vertices[vert_index[0]+j] * (1.0f - amount) + vertices[vert_index[1]+j] * amount;
+ }
+ temp_vec[3] = 1.0f;
+ float err = glm::dot(temp_vec, total_quadric * temp_vec);
+ if(i==0 || err<best_err){
+ best_err = err;
+ *pos = amount;
+ }
+ }
+ quadric_error = best_err;
+ }
+ return quadric_error;
+}
+
+void WOLFIRE_SIMPLIFY::CalculateQuadrics(std::vector<glm::mat4> *quadrics, const std::vector<float> &vertices, const std::vector<int> &vert_indices){
+ VertTris vert_tris;
+ vert_tris.Initialize(vertices, vert_indices);
+ int num_verts = vertices.size()/3;
+ quadrics->clear();
+ quadrics->resize(num_verts, glm::mat4(0.0f));
+ for(int i=0, len = num_verts; i<len; ++i){
+ int num_tris = vert_tris.NumTrisConnectedToVert(i);
+ const int *tris = vert_tris.GetTrisConnectedToVert(i);
+ for(int j=0; j<num_tris; ++j) {
+ glm::vec3 vec[3];
+ int tri = tris[j]/3;
+ for(int k=0; k<3; ++k){
+ int vert_index = vert_indices[tri*3+k]*3;
+ vec[k] = glm::vec3(vertices[vert_index+0],
+ vertices[vert_index+1],
+ vertices[vert_index+2]);
+ }
+ glm::vec3 cross_prod = glm::cross(vec[2]-vec[0], vec[1]-vec[0]);
+ float area = glm::length(cross_prod) * 0.5f;
+ if(area != 0.0f){
+ glm::vec3 normal = glm::normalize(cross_prod);
+ float offset = glm::dot(normal, vec[0]);
+ glm::vec4 plane = glm::vec4(normal[0], normal[1], normal[2], offset * -1.0f);
+ glm::mat4 quadric;
+ quadric[0][0] = plane[0] * plane[0];
+ quadric[0][1] = plane[0] * plane[1];
+ quadric[0][2] = plane[0] * plane[2];
+ quadric[0][3] = plane[0] * plane[3];
+ quadric[1][1] = plane[1] * plane[1];
+ quadric[1][2] = plane[1] * plane[2];
+ quadric[1][3] = plane[1] * plane[3];
+ quadric[2][2] = plane[2] * plane[2];
+ quadric[2][3] = plane[2] * plane[3];
+ quadric[3][3] = plane[3] * plane[3];
+ quadric[1][0] = quadric[0][1];
+ quadric[2][0] = quadric[0][2];
+ quadric[2][1] = quadric[1][2];
+ quadric[3][0] = quadric[0][3];
+ quadric[3][1] = quadric[1][3];
+ quadric[3][2] = quadric[2][3];
+ for(int a=0; a<4; ++a){
+ for(int b=0; b<4; ++b){
+ quadric[a][b] *= area;
+ }
+ }
+ quadrics->at(i) += quadric;
+ }
+ }
+ }
+}
+
+bool WOLFIRE_SIMPLIFY::Process(const Model &model, WOLFIRE_SIMPLIFY::SimplifyModel& processed_model, std::vector<HalfEdge>& half_edges, bool include_tex) {
+ WOLFIRE_SIMPLIFY::SimplifyModelInput model_input;
+ // Prepare model to pass to simplify algorithm
+ for(size_t i=0, len=model.faces.size(); i<len; ++i){
+ int vert_index = model.faces[i]*3;
+ for(int j=0; j<3; ++j){
+ model_input.vertices.push_back(model.vertices[vert_index+j]);
+ }
+ model_input.old_vert_id.push_back(model.faces[i]);
+ if(include_tex) {
+ int tex_index = model.faces[i]*2;
+ for(int j=0; j<2; ++j){
+ model_input.tex_coords.push_back(model.tex_coords[tex_index+j]);
+ }
+ model_input.old_tex_id.push_back(model.faces[i]);
+ }
+ }
+
+ LOGI << "Starting to simplify mesh..." << std::endl;
+ ProcessModel(model_input, &processed_model, 0.0f, include_tex);
+
+ return GenerateEdgePairs(processed_model, half_edges, include_tex);
+}
+
+bool WOLFIRE_SIMPLIFY::SimplifySimpleModel(const Model& input, Model* output, int edge_target, bool include_tex) {
+ SimplifyModelInput smi;
+ for(int i=0, len=input.faces.size(); i<len; ++i){
+ int vert_index = input.faces[i]*3;
+ for(int j=0; j<3; ++j){
+ smi.vertices.push_back(input.vertices[vert_index+j]);
+ }
+ smi.old_vert_id.push_back(input.faces[i]);
+
+ if(include_tex) {
+ int tex_index = input.faces[i]*2;
+ for(int j=0; j<2; ++j){
+ smi.tex_coords.push_back(input.tex_coords[tex_index+j]);
+ }
+
+ smi.old_tex_id.push_back(input.faces[i]);
+ }
+ }
+
+ LOGI << "Starting to simplify mesh..." << std::endl;
+ SimplifyModel processed_model;
+ std::vector<HalfEdge> half_edges;
+ ProcessModel(smi, &processed_model, 0.0f, include_tex);
+
+ bool edge_pair_res = GenerateEdgePairs(processed_model, half_edges, include_tex);
+
+ if(edge_pair_res == false) return false;
+
+ LOGI << "Calculating quadrics..." << std::endl;
+ // Calculate quadrics and edge error
+ std::vector<glm::mat4> quadrics_;
+ WOLFIRE_SIMPLIFY::CalculateQuadrics(&quadrics_, processed_model.vertices, processed_model.vert_indices);
+
+ CalculateEdgeErrors(half_edges, processed_model, quadrics_, include_tex);
+
+ // Add edges to heap
+ HalfEdgeNodeHeap heap;
+ HalfEdgeSetVec vert_edges(processed_model.vertices.size()/3);
+ HalfEdgeSetVec tex_edges(processed_model.tex_coords.size()/2);
+
+ InitHeap(half_edges, heap, vert_edges, tex_edges, include_tex);
+
+ // Get parents
+ ParentRecordListVec vert_parents(processed_model.vertices.size()/3);
+ ParentRecordListVec tex_parents(processed_model.tex_coords.size()/2);
+ InitParents(vert_parents, tex_parents, include_tex);
+
+ vector<float> vert_target;
+ vector<float> tex_target;
+
+ LOGI << "Collapsing edges... heap size: " << heap.size() << " edge_target: " << edge_target << " half_edges: " << half_edges.size() << std::endl;
+ int starting_heap_size = heap.size();
+ int count = 0;
+ while(!heap.empty() && (int)heap.size() > edge_target){
+ HalfEdge* lowest_err_edge = heap.begin()->edge;
+ if(lowest_err_edge->valid){
+ CollapseEdge(heap, lowest_err_edge, processed_model.vertices, processed_model.tex_coords, quadrics_, vert_parents, tex_parents, vert_edges, tex_edges, include_tex);
+ } else {
+ heap.erase(heap.begin());
+ }
+ if(count > 1000) {
+ LOGI << "Collapsing " << 100 - ((heap.size() - edge_target) / ((starting_heap_size - edge_target)/100)) << "%..." << std::endl;
+ count = 0;
+ }
+ count++;
+ }
+ LOGI << "Done collapsing ... heap size: " << heap.size() << " edge_target: " << edge_target << " half_edges: " << half_edges.size() << std::endl;
+
+ ReconstructModel(half_edges, &processed_model, include_tex);
+
+ vert_target.resize(processed_model.vertices.size());
+ std::vector<int> reverse_vert_parents(vert_parents.size()), reverse_tex_parents(tex_parents.size());
+ for(int i=0, len=vert_parents.size(); i<len; ++i) {
+ for(ParentRecordList::iterator iter = vert_parents[i].begin(); iter != vert_parents[i].end(); ++iter) {
+ ParentRecord &pr = (*iter);
+ for(int j=0; j<3; ++j){
+ vert_target[pr.id*3+j] = processed_model.vertices[i*3+j];
+ }
+ reverse_vert_parents[pr.id] = i;
+ }
+ }
+
+ tex_target.resize(processed_model.tex_coords.size());
+ for(int i=0, len=tex_parents.size(); i<len; ++i) {
+ for(ParentRecordList::iterator iter = tex_parents[i].begin(); iter != tex_parents[i].end(); ++iter) {
+ ParentRecord &pr = (*iter);
+ for(int j=0; j<2; ++j){
+ tex_target[pr.id*2+j] = processed_model.tex_coords[i*2+j];
+ }
+ reverse_tex_parents[pr.id] = i;
+ }
+ }
+
+ for(int i=0, len=processed_model.vert_indices.size(); i<len; ++i){
+ if(vert_parents[processed_model.vert_indices[i]].empty()) {
+ processed_model.vert_indices[i] = reverse_vert_parents[processed_model.vert_indices[i]];
+ }
+ }
+
+ for(int i=0, len=processed_model.tex_indices.size(); i<len; ++i){
+ if(tex_parents[processed_model.tex_indices[i]].empty()){
+ processed_model.tex_indices[i] = reverse_tex_parents[processed_model.tex_indices[i]];
+ }
+ }
+
+ LOGI << "Simplification completed." << std::endl;
+
+ ToModel(processed_model, output, include_tex);
+
+ return true;
+}
+
+bool WOLFIRE_SIMPLIFY::SimplifyMorphLOD( const SimplifyModelInput &model_input, MorphModel* lod, ParentRecordListVec *vert_parent_vec , ParentRecordListVec *tex_parent_vec, int lod_levels) {
+ bool include_tex = true;
+ LOGI << "Starting to simplify mesh..." << std::endl;
+ SimplifyModel processed_model;
+ std::vector<HalfEdge> half_edges;
+ ProcessModel(model_input, &processed_model, 0.0f, include_tex );
+
+ bool edge_pair_res = GenerateEdgePairs(processed_model, half_edges, include_tex);
+
+ if(edge_pair_res == false) return false;
+
+ LOGI << "Calculating quadrics..." << std::endl;
+ // Calculate quadrics and edge error
+ std::vector<glm::mat4> quadrics_;
+ WOLFIRE_SIMPLIFY::CalculateQuadrics(&quadrics_, processed_model.vertices, processed_model.vert_indices);
+
+ CalculateEdgeErrors(half_edges, processed_model, quadrics_, include_tex);
+
+ // Add edges to heap
+ HalfEdgeNodeHeap heap;
+ HalfEdgeSetVec vert_edges(processed_model.vertices.size()/3);
+ HalfEdgeSetVec tex_edges(processed_model.tex_coords.size()/2);
+ InitHeap(half_edges, heap, vert_edges, tex_edges, include_tex);
+
+ // Get parents
+ ParentRecordListVec vert_parents(processed_model.vertices.size()/3);
+ ParentRecordListVec tex_parents(processed_model.tex_coords.size()/2);
+ InitParents(vert_parents, tex_parents, include_tex);
+
+ int curr_lod = 0;
+ int tri_threshold = heap.size()/2;
+ SimplifyModel last_lod_model = processed_model;
+ SimplifyModel working_model = processed_model;
+ LOGI << "Collapsing edges..." << std::endl;
+ while(!heap.empty()){
+ if(curr_lod < lod_levels && (int)heap.size() <= tri_threshold){
+ ReconstructModel(half_edges, &working_model, include_tex);
+ lod[curr_lod].model = last_lod_model;
+ lod[curr_lod].vert_target.resize(working_model.vertices.size());
+ std::vector<int> reverse_vert_parents(vert_parents.size()), reverse_tex_parents(tex_parents.size());
+ for(int i=0, len=vert_parents.size(); i<len; ++i){
+ for(ParentRecordList::iterator iter = vert_parents[i].begin(); iter != vert_parents[i].end(); ++iter){
+ ParentRecord &pr = (*iter);
+ for(int j=0; j<3; ++j){
+ lod[curr_lod].vert_target[pr.id*3+j] = working_model.vertices[i*3+j];
+ }
+ reverse_vert_parents[pr.id] = i;
+ }
+ }
+ lod[curr_lod].tex_target.resize(working_model.tex_coords.size());
+ for(int i=0, len=tex_parents.size(); i<len; ++i){
+ for(ParentRecordList::iterator iter = tex_parents[i].begin(); iter != tex_parents[i].end(); ++iter){
+ ParentRecord &pr = (*iter);
+ for(int j=0; j<2; ++j){
+ lod[curr_lod].tex_target[pr.id*2+j] = working_model.tex_coords[i*2+j];
+ }
+ reverse_tex_parents[pr.id] = i;
+ }
+ }
+ vert_parent_vec[curr_lod] = vert_parents;
+ tex_parent_vec[curr_lod] = tex_parents;
+ for(int i=0, len=working_model.vert_indices.size(); i<len; ++i){
+ if(vert_parents[working_model.vert_indices[i]].empty()){
+ working_model.vert_indices[i] = reverse_vert_parents[working_model.vert_indices[i]];
+ }
+ }
+ for(int i=0, len=working_model.tex_indices.size(); i<len; ++i){
+ if(tex_parents[working_model.tex_indices[i]].empty()){
+ working_model.tex_indices[i] = reverse_tex_parents[working_model.tex_indices[i]];
+ }
+ }
+ last_lod_model = working_model;
+ tri_threshold /= 2;
+ ++curr_lod;
+ }
+ HalfEdge* lowest_err_edge = heap.begin()->edge;
+ if(lowest_err_edge->valid){
+ CollapseEdge(heap, lowest_err_edge, working_model.vertices, working_model.tex_coords, quadrics_, vert_parents, tex_parents, vert_edges, tex_edges, include_tex);
+ } else {
+ heap.erase(heap.begin());
+ }
+ }
+ LOGI << "Simplification completed." << std::endl;
+ return true;
+}
diff --git a/Source/Graphics/simplify.hpp b/Source/Graphics/simplify.hpp
new file mode 100644
index 00000000..3837a049
--- /dev/null
+++ b/Source/Graphics/simplify.hpp
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------------
+// Name: simplify.hpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Wrappers/glm.h>
+#include <Graphics/model.h>
+#include <Graphics/halfedge.h>
+#include <Graphics/simplify_types.h>
+
+#include <vector>
+#include <list>
+
+using std::vector;
+
+namespace WOLFIRE_SIMPLIFY {
+ void CalculateQuadrics(vector<glm::mat4> *quadrics, const vector<float> &vertices, const vector<int> &vert_indices);
+ bool SimplifyMorphLOD( const SimplifyModelInput &model_input, MorphModel* lod, ParentRecordListVec* vert_parents, ParentRecordListVec* tex_parents, int lod_levels );
+ bool SimplifySimpleModel(const Model& input, Model* output, int edge_target, bool include_tex);
+ bool Process( const Model &model, SimplifyModel& processed_model, vector<HalfEdge>& half_edges, bool include_tex);
+ float CalculateError(float* pos, int edge[2], const vector<float> &vertices, const vector<glm::mat4> &quadrics, const int edge_tc[]);
+}
diff --git a/Source/Graphics/simplify_types.h b/Source/Graphics/simplify_types.h
new file mode 100644
index 00000000..bdf0438a
--- /dev/null
+++ b/Source/Graphics/simplify_types.h
@@ -0,0 +1,62 @@
+//-----------------------------------------------------------------------------
+// Name: simplify_types.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <vector>
+#include <list>
+
+using std::vector;
+using std::list;
+
+namespace WOLFIRE_SIMPLIFY {
+ struct SimplifyModel {
+ vector<float> vertices;
+ vector<float> tex_coords;
+ vector<int> vert_indices;
+ vector<int> tex_indices;
+ vector<int> old_vert_id;
+ vector<int> old_tex_id;
+ };
+
+ struct MorphModel {
+ SimplifyModel model;
+ vector<float> vert_target;
+ vector<float> tex_target;
+ };
+
+ struct SimplifyModelInput {
+ vector<float> vertices;
+ vector<float> tex_coords;
+ vector<int> old_vert_id;
+ vector<int> old_tex_id;
+ };
+
+ struct ParentRecord {
+ int id;
+ float weight;
+ };
+
+ typedef list<ParentRecord> ParentRecordList;
+ typedef vector<ParentRecordList> ParentRecordListVec;
+}
diff --git a/Source/Graphics/skeleton.cpp b/Source/Graphics/skeleton.cpp
new file mode 100644
index 00000000..8428f909
--- /dev/null
+++ b/Source/Graphics/skeleton.cpp
@@ -0,0 +1,1603 @@
+//-----------------------------------------------------------------------------
+// Name: skeleton.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "skeleton.h"
+
+#include <Internal/filesystem.h>
+#include <Internal/timer.h>
+#include <Internal/snprintf.h>
+
+#include <Graphics/model.h>
+#include <Graphics/models.h>
+#include <Graphics/camera.h>
+#include <Graphics/pxdebugdraw.h>
+
+#include <Physics/bulletworld.h>
+#include <Physics/bulletobject.h>
+#include <Physics/physics.h>
+
+#include <Utility/compiler_macros.h>
+#include <Utility/assert.h>
+
+#include <Math/vec3math.h>
+#include <Math/enginemath.h>
+
+#include <Asset/Asset/skeletonasset.h>
+#include <Scripting/angelscript/ascollisions.h>
+#include <GUI/gui.h>
+#include <Logging/logdata.h>
+#include <Main/engine.h>
+
+#include <btBulletDynamicsCommon.h>
+
+#include <cmath>
+#include <set>
+
+struct SelectedJoint {
+ std::vector<int> bones;
+};
+
+static std::string LabelFromBone(int bone, const Skeleton::SimpleIKBoneMap &simple_ik_bones, const std::vector<int> &parents) {
+ for(Skeleton::SimpleIKBoneMap::const_iterator iter = simple_ik_bones.begin(); iter != simple_ik_bones.end(); ++iter){
+ const SimpleIKBone &ik_bone = iter->second;
+ const char *label = iter->first.c_str();
+ int curr_bone = ik_bone.bone_id;
+ for(int i=0; i<ik_bone.chain_length; ++i){
+ if(curr_bone == bone){
+ const int BUF_SIZE = 256;
+ char temp[BUF_SIZE];
+ snprintf(temp, BUF_SIZE, "%s%d", label, i);
+ return std::string(temp);
+ }
+ curr_bone = parents[curr_bone];
+ if(curr_bone == -1){
+ break;
+ }
+ }
+ }
+ return "unknown_bone";
+}
+
+void Skeleton::CreateHinge(const SelectedJoint &selected_joint, const vec3 &force_axis, float *initial_angle_ptr) {
+ int shared_point = -1;
+ Bone *joint_bones[2];
+ joint_bones[0] = &bones[selected_joint.bones[0]];
+ joint_bones[1] = &bones[selected_joint.bones[1]];
+ for(int i=0; i<2; i++){
+ for(int j=0; j<2; j++) {
+ if(joint_bones[0]->points[i] == joint_bones[1]->points[j]){
+ shared_point = joint_bones[0]->points[i];
+ }
+ }
+ }
+ if(shared_point == -1) {
+ std::vector<int> point_ids(2, -1);
+ for(unsigned i=0; i<points.size(); ++i){
+ if(joint_bones[0]->points[0] == (int)i){
+ point_ids[0] = i;
+ }
+ if(joint_bones[0]->points[1] == (int)i){
+ point_ids[1] = i;
+ }
+ }
+ if(!parents.empty() && parents[point_ids[0]] == point_ids[1]){
+ shared_point = point_ids[1];
+ } else {
+ shared_point = point_ids[0];
+ }
+ }
+
+ std::vector<BulletObject*> selected_bullet_bones(2);
+ GetPhysicsObjectsFromSelectedJoint(selected_bullet_bones, selected_joint);
+ DeleteJointOnBones(selected_bullet_bones);
+
+ vec3 anchor = points[shared_point];
+ vec3 axis = force_axis;
+ if(axis == vec3(0.0f)) {
+ axis = normalize(ActiveCameras::Get()->GetPos() - anchor);
+ }
+
+ btTypedConstraint* bt_joint = bullet_world->AddHingeJoint(selected_bullet_bones[0], selected_bullet_bones[1], anchor, axis, 0.0f, (float)PI*0.5f, initial_angle_ptr);
+
+ btTypedConstraint* fixed_joint =
+ bullet_world->AddFixedJoint(selected_bullet_bones[0],
+ selected_bullet_bones[1],
+ anchor);
+
+ physics_joints.resize(physics_joints.size()+1);
+ physics_joints.back().type = HINGE_JOINT;
+ physics_joints.back().anchor = anchor;
+ physics_joints.back().bt_joint = bt_joint;
+ physics_joints.back().fixed_joint = fixed_joint;
+ physics_joints.back().bt_bone[0] = selected_bullet_bones[0];
+ physics_joints.back().bt_bone[1] = selected_bullet_bones[1];
+ physics_joints.back().stop_angle[0] = 0.0f;
+ physics_joints.back().stop_angle[1] = (float)PI*0.5f;
+
+ mat4 rotation1 = selected_bullet_bones[0]->GetRotation();
+ mat4 rotation2 = selected_bullet_bones[1]->GetRotation();
+
+ physics_joints.back().initial_axis = axis;
+}
+
+void Skeleton::SetGravity( bool enable ) {
+ if(enable){
+ for(unsigned i=0; i<physics_bones.size(); i++){
+ if(!physics_bones[i].bullet_object){
+ continue;
+ }
+ physics_bones[i].bullet_object->SetGravity(true);
+ //physics_bones[i].bullet_object->SetDamping(0.0f);
+ }
+ } else {
+ for(unsigned i=0; i<physics_bones.size(); i++){
+ if(!physics_bones[i].bullet_object){
+ continue;
+ }
+ physics_bones[i].bullet_object->SetGravity(true);
+ //physics_bones[i].bullet_object->SetDamping(1.0f);
+ }
+ }
+}
+
+void Skeleton::UnlinkFromBulletWorld() {
+ for(unsigned i=0; i<physics_bones.size(); i++){
+ if(physics_bones[i].bullet_object){
+ bullet_world->UnlinkObject(physics_bones[i].bullet_object);
+ }
+ }
+ for(unsigned i=0; i<physics_joints.size(); i++){
+ PhysicsJoint &joint = physics_joints[i];
+ if(joint.bt_joint){
+ bullet_world->UnlinkConstraint(joint.bt_joint);
+ }
+ if(joint.fixed_joint && joint.fixed_joint_enabled){
+ bullet_world->UnlinkConstraint(joint.fixed_joint);
+ }
+ }
+ for(unsigned i=0; i<null_constraints.size(); i++){
+ bullet_world->UnlinkConstraint(null_constraints[i]);
+ }
+}
+
+void Skeleton::LinkToBulletWorld() {
+ int num = 0;
+ for(unsigned i=0; i<physics_bones.size(); i++){
+ if(!physics_bones[i].bullet_object || fixed_obj[i]){
+ continue;
+ }
+ bullet_world->LinkObject(physics_bones[i].bullet_object);
+ ++num;
+ }
+ num = 0;
+ int num2 = 0;
+ int num3 = 0;
+ for(unsigned i=0; i<physics_joints.size(); i++){
+ PhysicsJoint &joint = physics_joints[i];
+ if(joint.bt_joint){
+ bullet_world->LinkConstraint(joint.bt_joint);
+ if(joint.type == FIXED_JOINT){
+ ++num3;
+ } else {
+ ++num;
+ }
+ }
+ if(joint.fixed_joint && joint.fixed_joint_enabled){
+ bullet_world->LinkConstraint(joint.fixed_joint);
+ ++num2;
+ }
+ }
+ num = 0;
+ for(unsigned i=0; i<null_constraints.size(); i++){
+ bullet_world->LinkConstraint(null_constraints[i]);
+ ++num;
+ }
+}
+
+void Skeleton::AddModifiedSphere(btCompoundShape* compound_shape, const vec3 &scale, const vec3 &offset, const quaternion &rotation, const mat4 &capsule_transform, const vec3& skel_offset, float body_scale) {
+ quaternion quat(rotation[1], rotation[3], -rotation[2], rotation[0]);
+ float radius = (scale[0] + scale[1] + scale[2])/3.0f*body_scale;
+ vec3 modified_scale = scale / radius*body_scale;
+ modified_scale = vec3(modified_scale[0],modified_scale[2],modified_scale[1]);
+ btVector3 position(0.0f,0.0f,0.0f);
+ btMultiSphereShape* sphere_shape = new btMultiSphereShape(&position, &radius, 1);
+ child_shapes.push_back(sphere_shape);
+ sphere_shape->setLocalScaling(btVector3(modified_scale[0], modified_scale[1], modified_scale[2]));
+
+ quat = invert(QuaternionFromMat4(capsule_transform)) * quat;
+
+ btTransform trans;
+ trans.setIdentity();
+ trans.setRotation(btQuaternion(quat.entries[0],quat.entries[1],quat.entries[2],quat.entries[3]));
+ vec3 test = invert(capsule_transform) * ((vec3(offset[0], offset[2], -offset[1]) - skel_offset)*body_scale);
+ trans.setOrigin(btVector3(test[0], test[1], test[2]));
+ compound_shape->addChildShape(trans,sphere_shape);
+
+ /*mat4 offset_mat;
+ offset_mat.SetTranslationPart(test);
+ mat4 transform = capsule_transform * offset_mat * Mat4FromQuaternion(quat);
+ transform.AddTranslation(vec3(0,20,0));
+ DebugDraw::Instance()->AddTransformedWireScaledSphere(transform, radius, modified_scale, vec4(1.0f), _persistent);*/
+}
+
+void Skeleton::AddModifiedCapsule(btCompoundShape* compound_shape, const vec3 &scale, const vec3 &offset, const quaternion &rotation, const mat4 &capsule_transform, const vec3& skel_offset, float body_scale) {
+ quaternion quat(rotation[1], rotation[3], -rotation[2], rotation[0]);
+ float radius[2] = {scale[0]*body_scale, scale[0]*body_scale};
+ btVector3 position[2];
+ position[0] = btVector3(0.0f,-scale[2]*body_scale,0.0f);
+ position[1] = btVector3(0.0f,scale[2]*body_scale,0.0f);
+ btMultiSphereShape* sphere_shape = new btMultiSphereShape(position, radius, 2);
+ child_shapes.push_back(sphere_shape);
+
+ quat = invert(QuaternionFromMat4(capsule_transform)) * quat;
+
+ btTransform trans;
+ trans.setIdentity();
+ trans.setRotation(btQuaternion(quat.entries[0],quat.entries[1],quat.entries[2],quat.entries[3]));
+ vec3 test = invert(capsule_transform) * ((vec3(offset[0], offset[2], -offset[1]) - skel_offset)*body_scale);
+ trans.setOrigin(btVector3(test[0], test[1], test[2]));
+ compound_shape->addChildShape(trans,sphere_shape);
+
+ vec3 vec_pos[2];
+ vec_pos[0] = vec3(position[0][0], position[0][1], position[0][2]);
+ vec_pos[1] = vec3(position[1][0], position[1][1], position[1][2]);
+ vec_pos[0] = test + quat * vec_pos[0];
+ vec_pos[1] = test + quat * vec_pos[1];
+
+ /*for(int i=0; i<2; ++i){
+ DebugDraw::Instance()->AddWireSphere(capsule_transform * vec_pos[i] + vec3(0,20,0), radius[i], vec4(1.0f), _persistent);
+ }*/
+}
+
+void Skeleton::AddModifiedBox(btCompoundShape* compound_shape, const vec3 &scale, const vec3 &offset, const quaternion &rotation, const mat4 &capsule_transform, const vec3& skel_offset, float body_scale) {
+ quaternion quat(rotation[1], rotation[3], -rotation[2], rotation[0]);
+ vec3 new_scale(scale[0]*body_scale,scale[2]*body_scale,scale[1]*body_scale);
+ btBoxShape* box_shape = new btBoxShape(btVector3(new_scale[0], new_scale[1], new_scale[2]));
+ child_shapes.push_back(box_shape);
+
+ quat = invert(QuaternionFromMat4(capsule_transform)) * quat;
+
+ btTransform trans;
+ trans.setIdentity();
+ trans.setRotation(btQuaternion(quat.entries[0],quat.entries[1],quat.entries[2],quat.entries[3]));
+ vec3 test = invert(capsule_transform) * ((vec3(offset[0], offset[2], -offset[1]) - skel_offset)*body_scale);
+ trans.setOrigin(btVector3(test[0], test[1], test[2]));
+ compound_shape->addChildShape(trans,box_shape);
+}
+
+namespace FZX {
+ const char* key_words[] = {"l","r","m","capsule", "box", "sphere","eartip","earbase","head","chest","upperarm","forearm","abdomen","hip","thigh","shin","foot","tail1","tail2","tail3","tail4","tail5","tail6"};
+ enum {left,right,mirrored,capsule,box,sphere,eartip,earbase,head,chest,upperarm,forearm,abdomen,hip,thigh,shin,foot,tail1,tail2,tail3,tail4,tail5,tail6};
+ const int num_key_words = sizeof(key_words)/sizeof(key_words[0]);
+}
+
+static int GetChainMember(const std::vector<int> &parents, int tip, int depth){
+ int bone = tip;
+ for(int i=0; i<depth; ++i){
+ bone = parents[bone];
+ }
+ return bone;
+}
+
+enum ShapeType {NONE, SPHERE, CAPSULE, BOX};
+struct CustomShape {
+ ShapeType shape;
+ quaternion rotation;
+ vec3 scale;
+ vec3 location;
+ CustomShape():shape(NONE){}
+};
+const int MAX_SHAPES_PER_BONE = 5;
+
+void GetShapeCOMAndVolume(const CustomShape &custom_shape, const vec3& skel_offset, float body_scale, vec3 *com, float *volume) {
+ const vec3 &offset = custom_shape.location;
+ *com = (vec3(offset[0], offset[2], -offset[1]) - skel_offset) * body_scale;
+ switch(custom_shape.shape) {
+ case CAPSULE: {
+ float radius = custom_shape.scale[0]*body_scale;
+ float sphere_volume = 4.0f/3.0f*3.1417f*pow(radius,3);
+ float cylinder_volume = 3.1417f*pow(radius,2)*custom_shape.scale[2]*2.0f;
+ *volume = sphere_volume + cylinder_volume;
+ break;}
+ case SPHERE: {
+ vec3 scale = custom_shape.scale * body_scale;
+ *volume = 4.0f/3.0f*3.1417f*scale[0]*scale[1]*scale[2];
+ break;}
+ case BOX: {
+ vec3 scale = custom_shape.scale * body_scale * 2.0f;
+ *volume = scale[0]*scale[1]*scale[2];
+ break;}
+ case NONE:
+ LOGW << "Requested volume of NONE shape" << std::endl;
+ break;
+ }
+}
+
+static void ShapesFromFZX(const FZXAssetRef& fzx_ref,
+ std::vector<CustomShape> *custom_shapes_ptr,
+ std::vector<int> *num_custom_shapes_ptr,
+ const Skeleton::SimpleIKBoneMap &simple_ik_bones,
+ const std::vector<int> &parents,
+ const std::vector<int> &symmetry)
+{
+ std::vector<CustomShape> &custom_shapes = *custom_shapes_ptr;
+ std::vector<int> &num_custom_shapes = *num_custom_shapes_ptr;
+ for(int i=0, len=fzx_ref->objects.size(); i<len; ++i){
+ const FZXObject &object = fzx_ref->objects[i];
+ // Parse label string
+ const std::string &label = object.label;
+ int last_space_index = 0;
+ int side = -1;
+ int bodypart = -1;
+ int shape = -1;
+ bool mirrored = false;
+ for(int j=0, len=label.size()+1; j<len; ++j){
+ if(label[j] == ' ' || label[j] == '\0'){
+ std::string sub_str_buf;
+ sub_str_buf.resize(j-last_space_index);
+ int count = 0;
+ for(int k=last_space_index; k<j; ++k){
+ char c = label[k];
+ if(c >= 'A' && c <= 'Z'){
+ c -= ('A' - 'a');
+ }
+ sub_str_buf[count] = c;
+ ++count;
+ }
+ for(int k=0; k<FZX::num_key_words; ++k){
+ bool word_match = (strcmp(FZX::key_words[k], sub_str_buf.c_str())==0);
+ if(word_match){
+ switch(k){
+ case FZX::left:
+ case FZX::right:
+ side = k; break;
+ case FZX::capsule:
+ case FZX::box:
+ case FZX::sphere:
+ shape = k; break;
+ case FZX::mirrored:
+ mirrored = true; break;
+ default:
+ bodypart = k; break;
+ };
+ }
+ }
+ last_space_index = j+1;
+ }
+ }
+ const bool print_info = false;
+ if(print_info){
+ if(side != -1){
+ LOGI << "Side: " << FZX::key_words[side] << std::endl;
+ } else {
+ LOGI << "Side: undefined" << std::endl;
+ }
+ if(shape != -1){
+ LOGI << "Shape: " << FZX::key_words[shape] << std::endl;
+ } else {
+ LOGI << "Shape: undefined" << std::endl;
+ }
+ if(bodypart != -1){
+ LOGI << "Bodypart: " << FZX::key_words[bodypart] << std::endl;
+ } else {
+ LOGI << "Bodypart: undefined" << std::endl;
+ }
+ }
+ if(shape != -1 && bodypart != -1){
+ int bone = -1;
+ switch(bodypart){
+ case FZX::eartip:
+ if(side == FZX::right){
+ bone = simple_ik_bones.find("rightear")->second.bone_id;
+ } else if(side == FZX::left){
+ bone = simple_ik_bones.find("leftear")->second.bone_id;
+ }
+ break;
+ case FZX::earbase:
+ if(side == FZX::right){
+ bone = GetChainMember(parents, simple_ik_bones.find("rightear")->second.bone_id, 1);
+ } else if(side == FZX::left){
+ bone = GetChainMember(parents, simple_ik_bones.find("leftear")->second.bone_id, 1);
+ }
+ break;
+ case FZX::upperarm:
+ if(side == FZX::right){
+ bone = GetChainMember(parents, simple_ik_bones.find("rightarm")->second.bone_id, 5);
+ } else if(side == FZX::left){
+ bone = GetChainMember(parents, simple_ik_bones.find("leftarm")->second.bone_id, 5);
+ }
+ break;
+ case FZX::forearm:
+ if(side == FZX::right){
+ bone = GetChainMember(parents, simple_ik_bones.find("rightarm")->second.bone_id, 3);
+ } else if(side == FZX::left){
+ bone = GetChainMember(parents, simple_ik_bones.find("leftarm")->second.bone_id, 3);
+ }
+ break;
+ case FZX::head:
+ bone = simple_ik_bones.find("head")->second.bone_id;
+ break;
+ case FZX::chest:
+ bone = simple_ik_bones.find("torso")->second.bone_id;
+ break;
+ case FZX::abdomen:
+ bone = GetChainMember(parents, simple_ik_bones.find("torso")->second.bone_id, 1);
+ break;
+ case FZX::hip:
+ bone = GetChainMember(parents, simple_ik_bones.find("torso")->second.bone_id, 2);
+ break;
+ case FZX::tail1:
+ bone = GetChainMember(parents, simple_ik_bones.find("tail")->second.bone_id, simple_ik_bones.find("tail")->second.chain_length-1);
+ break;
+ case FZX::tail2:
+ bone = GetChainMember(parents, simple_ik_bones.find("tail")->second.bone_id, simple_ik_bones.find("tail")->second.chain_length-2);
+ break;
+ case FZX::tail3:
+ bone = GetChainMember(parents, simple_ik_bones.find("tail")->second.bone_id, simple_ik_bones.find("tail")->second.chain_length-3);
+ break;
+ case FZX::tail4:
+ bone = GetChainMember(parents, simple_ik_bones.find("tail")->second.bone_id, simple_ik_bones.find("tail")->second.chain_length-4);
+ break;
+ case FZX::tail5:
+ bone = GetChainMember(parents, simple_ik_bones.find("tail")->second.bone_id, simple_ik_bones.find("tail")->second.chain_length-5);
+ break;
+ case FZX::tail6:
+ bone = GetChainMember(parents, simple_ik_bones.find("tail")->second.bone_id, simple_ik_bones.find("tail")->second.chain_length-6);
+ break;
+ case FZX::thigh:
+ if(side == FZX::right){
+ bone = GetChainMember(parents, simple_ik_bones.find("right_leg")->second.bone_id, 5);
+ } else if(side == FZX::left){
+ bone = GetChainMember(parents, simple_ik_bones.find("left_leg")->second.bone_id, 5);
+ }
+ break;
+ case FZX::shin:
+ if(side == FZX::right){
+ bone = GetChainMember(parents, simple_ik_bones.find("right_leg")->second.bone_id, 3);
+ } else if(side == FZX::left){
+ bone = GetChainMember(parents, simple_ik_bones.find("left_leg")->second.bone_id, 3);
+ }
+ break;
+ case FZX::foot:
+ if(side == FZX::right){
+ bone = GetChainMember(parents, simple_ik_bones.find("right_leg")->second.bone_id, 1);
+ } else if(side == FZX::left){
+ bone = GetChainMember(parents, simple_ik_bones.find("left_leg")->second.bone_id, 1);
+ }
+ break;
+ }
+ if(bone != -1){
+ CustomShape &custom_shape = custom_shapes[bone*MAX_SHAPES_PER_BONE+num_custom_shapes[bone]];
+ switch(shape){
+ case FZX::sphere:
+ custom_shape.shape = SPHERE;
+ break;
+ case FZX::capsule:
+ custom_shape.shape = CAPSULE;
+ break;
+ case FZX::box:
+ custom_shape.shape = BOX;
+ break;
+ }
+ custom_shape.location = object.location;
+ custom_shape.scale = object.scale;
+ custom_shape.rotation = object.rotation;
+ int symmetry_bone = symmetry[bone];
+ if(mirrored && symmetry_bone != -1){
+ CustomShape &sym_custom_shape = custom_shapes[symmetry_bone*MAX_SHAPES_PER_BONE+num_custom_shapes[symmetry_bone]];
+ sym_custom_shape = custom_shape;
+ sym_custom_shape.location[0] *= -1.0f;
+ sym_custom_shape.rotation[2] *= -1.0f;
+ sym_custom_shape.rotation[3] *= -1.0f;
+ ++num_custom_shapes[symmetry_bone];
+ }
+ ++num_custom_shapes[bone];
+ }
+ }
+ }
+}
+
+void Skeleton::CreatePhysicsSkeleton(float scale, const FZXAssetRef& fzx_ref) {
+ // Parse FZX file to get collision shapes for each bone
+ std::vector<int> num_custom_shapes(bones.size(), 0);
+ std::vector<CustomShape> custom_shapes;
+ custom_shapes.resize(bones.size()*MAX_SHAPES_PER_BONE);
+ if(fzx_ref.valid()){
+ ShapesFromFZX(fzx_ref, &custom_shapes, &num_custom_shapes, simple_ik_bones, parents, skeleton_asset_ref->GetData().symmetry);
+ }
+
+ std::map<int,std::vector<BulletObject*> > joints;
+ std::map<int,std::vector<BulletObject*> >::iterator iter;
+ for(unsigned i=0; i<bones.size(); i++){
+ Bone* bone = &bones[i];
+ vec3 bone_points[2];
+ bone_points[0] = points[bone->points[0]];
+ bone_points[1] = points[bone->points[1]];
+
+ //DebugDraw::Instance()->AddLine(bone_points[0]+vec3(0,20,0), bone_points[1]+vec3(0,20,0), vec4(1.0f), _persistent);
+
+ vec3 old_midpoint = (bone_points[1] + bone_points[0])*0.5f;
+
+ BulletObject* b_capsule = NULL;
+ if(!fzx_ref.valid()){
+ b_capsule = bullet_world->CreateCapsule(bone_points[0], bone_points[1], 0.05f, physics_bones[i].physics_mass);
+ } else {
+ if(num_custom_shapes[i] == 0){
+ b_capsule = bullet_world->CreateCapsule(bone_points[0],
+ bone_points[1],
+ 0.05f,
+ physics_bones[i].physics_mass,
+ BW_NO_DYNAMIC_COLLISIONS | BW_NO_STATIC_COLLISIONS);
+ b_capsule->SetVisibility(true);
+ } else {
+ mat4 capsule_transform = BulletWorld::GetCapsuleTransform(bone_points[0],bone_points[1]);
+ btCompoundShape* compound_shape = new btCompoundShape();
+ SharedShapePtr shape(compound_shape);
+ float total_volume = 0.0f;
+ vec3 com;
+ for(int j=0; j<num_custom_shapes[i]; ++j){
+ CustomShape custom_shape = custom_shapes[i*MAX_SHAPES_PER_BONE+j];
+ vec3 shape_com;
+ float shape_volume;
+ GetShapeCOMAndVolume(custom_shape, skeleton_asset_ref->GetData().old_model_center, scale, &shape_com, &shape_volume);
+ com += shape_com * shape_volume;
+ total_volume += shape_volume;
+ }
+ com /= total_volume;
+ capsule_transform.SetTranslationPart(com);
+ for(int j=0; j<num_custom_shapes[i]; ++j){
+ CustomShape custom_shape = custom_shapes[i*MAX_SHAPES_PER_BONE+j];
+ switch (custom_shape.shape) {
+ case CAPSULE:
+ AddModifiedCapsule(compound_shape, custom_shape.scale, custom_shape.location, custom_shape.rotation, capsule_transform, skeleton_asset_ref->GetData().old_model_center, scale);
+ break;
+
+ case SPHERE:
+ AddModifiedSphere(compound_shape, custom_shape.scale, custom_shape.location, custom_shape.rotation, capsule_transform, skeleton_asset_ref->GetData().old_model_center, scale);
+ break;
+
+ case BOX:
+ AddModifiedBox(compound_shape, custom_shape.scale, custom_shape.location, custom_shape.rotation, capsule_transform, skeleton_asset_ref->GetData().old_model_center, scale);
+ break;
+
+ case NONE:
+ // This should never happen, if it does something's gone wrong ShapesFromFZX
+ LOG_ASSERT_MSG(false, "Unhandled case of custom shape");
+ break;
+ }
+ }
+ b_capsule = bullet_world->CreateRigidBody(shape, physics_bones[i].physics_mass);
+ b_capsule->SetVisibility(true);
+ b_capsule->SetTransform(capsule_transform);
+ }
+ }
+
+ /*if(!fixed_obj[i] && has_verts_assigned[i]){
+ LOGI < "Creating bone " << LabelFromBone(i, simple_ik_bones, parents) << " with mass " << physics_bones[i].physics_mass << std::endl;
+ }*/
+
+ b_capsule->SetGravity(false);
+ b_capsule->color = vec3(0.8f);
+ b_capsule->com_offset = invert(b_capsule->GetTransform()) * (old_midpoint) * -1.0f;
+
+ PhysicsBone& physics_bone = physics_bones[i];
+ physics_bone.bullet_object = b_capsule;
+ physics_bone.bone = i;
+ physics_bone.initial_position = b_capsule->GetPosition();
+ physics_bone.initial_rotation = b_capsule->GetRotation();
+
+ BulletObject* object = b_capsule;
+ joints[bone->points[0]].push_back(object);
+ joints[bone->points[1]].push_back(object);
+ }
+
+ int editor;
+ btTypedConstraint* bt_joint;
+ physics_joints.clear();
+ for(iter = joints.begin(); iter != joints.end(); ++iter){
+ editor = iter->first;
+ std::vector<BulletObject*> &shared_objects = iter->second;
+ for(unsigned i=0; i<shared_objects.size(); i++){
+ for(unsigned j=i+1; j<shared_objects.size(); j++){
+ bt_joint = bullet_world->AddBallJoint(shared_objects[i],
+ shared_objects[j],
+ points[editor]);
+
+ physics_joints.resize(physics_joints.size()+1);
+ physics_joints.back().type = BALL_JOINT;
+ physics_joints.back().bt_joint = bt_joint;
+ physics_joints.back().bt_bone[0] = shared_objects[i];
+ physics_joints.back().bt_bone[1] = shared_objects[j];
+ }
+ }
+ }
+}
+
+int Skeleton::GetAttachedBone( BulletObject* object ) {
+ for(unsigned i=0; i<physics_bones.size(); i++){
+ if(physics_bones[i].bullet_object == object) {
+ return physics_bones[i].bone;
+ }
+ }
+ return -1;
+}
+
+int Skeleton::Read( const std::string &path, float scale, float mass_scale, const FZXAssetRef &fzx_ref) {
+ //skeleton_asset_ref = SkeletonAssets::Instance()->ReturnRef(path);
+ skeleton_asset_ref = Engine::Instance()->GetAssetManager()->LoadSync<SkeletonAsset>(path);
+ const SkeletonFileData &data = skeleton_asset_ref->GetData();
+
+ RiggingStage rigging_stage = data.rigging_stage;
+
+ // Load points
+ int num_points = data.points.size();
+ for(int i=0; i<num_points; i++){
+ points.push_back(data.points[i] * scale);
+ }
+
+ // Load bones that connect points
+ int num_bones = data.bone_ends.size()/2;
+ bones.resize(num_bones);
+ for(int i=0; i<num_bones; i++){
+ bones[i].points[0] = data.bone_ends[i*2+0];
+ bones[i].points[1] = data.bone_ends[i*2+1];
+ }
+
+ // Load mass of each bone
+ if(!data.bone_mass.empty()){
+ for(int i=0; i<num_bones; i++){
+ bones[i].mass = data.bone_mass[i] * mass_scale * mass_scale * mass_scale;
+ }
+ }
+
+ // Load center of mass for each bone
+ if(!data.bone_com.empty()){
+ for(int i=0; i<num_bones; i++){
+ bones[i].center_of_mass = data.bone_com[i] * scale;
+ }
+ }
+
+ simple_ik_bones = data.simple_ik_bones;
+
+ if(rigging_stage == _control_joints){
+ PhysicsDispose();
+ col_bullet_world.reset(new BulletWorld());
+ col_bullet_world->Init();
+ physics_bones.resize(bones.size());
+
+ has_verts_assigned.clear();
+ has_verts_assigned.resize(physics_bones.size(), false);
+ for(unsigned i=0; i<data.model_bone_ids.size(); ++i){
+ for(int j=0; j<4; ++j){
+ if(data.model_bone_weights[i][j] > 0.0f){
+ has_verts_assigned[int(data.model_bone_ids[i][j])] = true;
+ }
+ }
+ }
+
+ parents = data.hier_parents;
+
+ for(unsigned i=0; i<physics_bones.size(); ++i){
+ physics_bones[i].physics_mass = bones[i].mass;
+ }
+
+ // Identify objects with fixed joint to parent
+ fixed_obj.resize(physics_bones.size(), false);
+ for(unsigned i=0; i<data.joints.size(); ++i){
+ const JointData& joint = data.joints[i];
+ if(joint.type == _fixed_joint){
+ int id[2];
+ id[0] = joint.bone_id[0];
+ id[1] = joint.bone_id[1];
+ if(parents[id[0]] == id[1]){
+ fixed_obj[id[0]] = true;
+ physics_bones[id[1]].physics_mass += bones[id[0]].mass;
+ } else if(parents[id[1]] == id[0]){
+ fixed_obj[id[1]] = true;
+ physics_bones[id[0]].physics_mass += bones[id[1]].mass;
+ }
+ }
+ }
+
+ // Remove hands and toes from physics world
+ int toe_bones[2];
+ toe_bones[0] = simple_ik_bones["left_leg"].bone_id;
+ toe_bones[1] = simple_ik_bones["right_leg"].bone_id;
+ int hand_bones[2];
+ hand_bones[0] = simple_ik_bones["leftarm"].bone_id;
+ hand_bones[1] = simple_ik_bones["rightarm"].bone_id;
+ for(int i=0, len=physics_bones.size(); i<len; ++i){
+ int parent = parents[i];
+ int grandparent = -1;
+ if(parent != -1){
+ grandparent = parents[parent];
+ }
+ if(i == toe_bones[0] || i == toe_bones[1]){
+ fixed_obj[i] = true;
+ physics_bones[parent].physics_mass += bones[i].mass;
+ }
+ if(i == hand_bones[0] || i == hand_bones[1]){
+ fixed_obj[i] = true;
+ physics_bones[parents[grandparent]].physics_mass += bones[i].mass;
+ }
+ if(parent == hand_bones[0] || parent == hand_bones[1]){
+ fixed_obj[i] = true;
+ physics_bones[parents[parents[grandparent]]].physics_mass += bones[i].mass;
+ }
+ if(grandparent == hand_bones[0] || grandparent == hand_bones[1]){
+ fixed_obj[i] = true;
+ physics_bones[parents[parents[parents[grandparent]]]].physics_mass += bones[i].mass;
+ }
+ }
+ CreatePhysicsSkeleton(scale, fzx_ref);
+
+ // Add joints
+ int num_joints = data.joints.size();
+ for(int i=0; i<num_joints; i++){
+ const JointData &joint = data.joints[i];
+ SelectedJoint selected;
+
+ if(joint.bone_id[0] != joint.bone_id[1]) {
+ selected.bones.push_back(joint.bone_id[0]);
+ selected.bones.push_back(joint.bone_id[1]);
+
+ float initial_angle = 0.0f;
+ if(joint.type == _hinge_joint) {
+ CreateHinge(selected, joint.axis, &initial_angle);
+ }
+ else if(joint.type == _amotor_joint) {
+ CreateRotationalConstraint(selected);
+ }
+ else if(joint.type == _fixed_joint) {
+ CreateFixed(selected);
+ }
+ PhysicsJoint& physics_joint = physics_joints.back();
+ physics_joint.initial_angle = initial_angle;
+ physics_joint.stop_angle[0] = joint.stop_angle[0];
+ physics_joint.stop_angle[1] = joint.stop_angle[1];
+ if(joint.type == _amotor_joint) {
+ physics_joint.stop_angle[2] = joint.stop_angle[2];
+ physics_joint.stop_angle[3] = joint.stop_angle[3];
+ physics_joint.stop_angle[4] = joint.stop_angle[4];
+ physics_joint.stop_angle[5] = joint.stop_angle[5];
+ }
+ if(joint.type != _fixed_joint) {
+ ApplyJointRange(&physics_joint);
+ }
+ } else {
+ LOGW << "Error when setting up joints in skeleton from path \"" << path << "\", trying to create a joint between bone " << joint.bone_id[0] << " and itself." << std::endl;
+ }
+ }
+ }
+
+ std::map<BulletObject*, int> bo_id;
+ for(unsigned i=0; i<physics_bones.size(); ++i){
+ bo_id[physics_bones[i].bullet_object] = i;
+ }
+
+ // Remove bones that are fixed in place
+ for(unsigned i=0; i<physics_bones.size(); ++i){
+ if(fixed_obj[i] && has_verts_assigned[i]){
+ PhysicsBone& pbi = physics_bones[i];
+ for(unsigned j=0; j<physics_joints.size(); ++j){
+ PhysicsJoint& joint = physics_joints[j];
+ int id[2];
+ id[0] = bo_id[joint.bt_bone[0]];
+ id[1] = bo_id[joint.bt_bone[1]];
+ if(id[0] != (int)i && id[1] != (int)i) {
+ continue;
+ }
+ int other = id[0]==(int)i?id[1]:id[0];
+ if(other == parents[i]){
+ DeleteJoint(&joint);
+ } else {
+ ShiftJoint(joint,
+ id[0]==(int)i?parents[i]:id[0],
+ id[1]==(int)i?parents[i]:id[1]);
+ }
+ }
+ PhysicsBone& pb = physics_bones[parents[i]];
+ pb.bullet_object->color = vec3(1.0f,0.0f,0.0f);
+ pbi.bullet_object->color = vec3(0.5f,0.5f,0.5f);
+ }
+ }
+
+ // Delete joints that involve fixed objects
+ for(unsigned i=0; i<physics_bones.size(); ++i){
+ if(fixed_obj[i] && has_verts_assigned[i]){
+ PhysicsBone& pbi = physics_bones[i];
+ for(unsigned j=0; j<physics_joints.size(); ++j){
+ PhysicsJoint& joint = physics_joints[j];
+ if(joint.bt_bone[0] == pbi.bullet_object || joint.bt_bone[1] == pbi.bullet_object) {
+ DeleteJoint(&joint);
+ }
+ }
+ bullet_world->UnlinkObjectPermanent(pbi.bullet_object);
+ }
+ }
+
+ // Remove invisible bones from parent hierarchy as well as physics world
+ for(unsigned i=0; i<parents.size(); ++i){
+ while(parents[i] != -1 && !has_verts_assigned[parents[i]]){
+ parents[i] = parents[parents[i]];
+ }
+ }
+ for(unsigned i=0; i<physics_bones.size(); ++i){
+ if(!has_verts_assigned[i]){
+ PhysicsBone& pbi = physics_bones[i];
+ for(unsigned j=0; j<physics_joints.size(); ++j){
+ PhysicsJoint& joint = physics_joints[j];
+ if(joint.bt_bone[0] == pbi.bullet_object ||
+ joint.bt_bone[1] == pbi.bullet_object)
+ {
+ DeleteJoint(&joint);
+ }
+ }
+ bullet_world->RemoveObject(&pbi.bullet_object);
+ }
+ }
+
+ // Add constraints that do nothing but prevent collision detection between bones
+ // that start out overlapping
+ AddNullConstraints();
+
+ phys_id.clear();
+ for(unsigned i=0; i<physics_bones.size(); ++i){
+ phys_id[physics_bones[i].bullet_object] = i;
+ }
+
+ return (int)rigging_stage;
+}
+
+void Skeleton::ReduceROM(float how_much) {
+ std::map<BulletObject*, int> boid;
+ for(unsigned i=0; i<physics_bones.size(); ++i){
+ boid[physics_bones[i].bullet_object] = i;
+ }
+ std::vector<int> affected(physics_bones.size(), 1);
+ if(simple_ik_bones.find("tail") != simple_ik_bones.end()){
+ const SimpleIKBone &ik_bone = simple_ik_bones["tail"];
+ int bone = ik_bone.bone_id;
+ int length = ik_bone.chain_length;
+ for(int i=0; i<length; ++i){
+ affected[bone] = 0;
+ bone = parents[bone];
+ }
+ }
+ for(unsigned i=0; i<physics_joints.size(); i++){
+ if(physics_joints[i].type == ROTATION_JOINT && affected[boid[physics_joints[i].bt_bone[0]]] == 1 && affected[boid[physics_joints[i].bt_bone[1]]] == 1){
+ float mid = (physics_joints[i].stop_angle[0]+
+ physics_joints[i].stop_angle[1])*0.5f;
+ physics_joints[i].stop_angle[0] = mix(physics_joints[i].stop_angle[0],mid,how_much);
+ physics_joints[i].stop_angle[1] = mix(physics_joints[i].stop_angle[1],mid,how_much);
+
+ mid = (physics_joints[i].stop_angle[2]+
+ physics_joints[i].stop_angle[3])*0.5f;
+ physics_joints[i].stop_angle[2] = mix(physics_joints[i].stop_angle[2],mid,how_much);
+ physics_joints[i].stop_angle[3] = mix(physics_joints[i].stop_angle[3],mid,how_much);
+
+ mid = (physics_joints[i].stop_angle[4]+
+ physics_joints[i].stop_angle[5])*0.5f;
+ physics_joints[i].stop_angle[4] = mix(physics_joints[i].stop_angle[4],mid,how_much);
+ physics_joints[i].stop_angle[5] = mix(physics_joints[i].stop_angle[5],mid,how_much);
+
+ ApplyJointRange(&physics_joints[i]);
+ }
+ }
+}
+
+void Skeleton::CreateFixed(const SelectedJoint &selected_joint) {
+ std::vector<BulletObject*> selected_bullet_bones(2);
+ GetPhysicsObjectsFromSelectedJoint(selected_bullet_bones,selected_joint);
+
+ //It doesn't make sense to create a joint between a bone and itself.
+ assert(selected_bullet_bones[0] != selected_bullet_bones[1]);
+
+ DeleteJointOnBones(selected_bullet_bones);
+
+ btTypedConstraint* bt_joint =
+ bullet_world->AddFixedJoint(selected_bullet_bones[0],
+ selected_bullet_bones[1],
+ (selected_bullet_bones[0]->GetPosition()+selected_bullet_bones[1]->GetPosition())*0.5f);
+
+ physics_joints.resize(physics_joints.size()+1);
+ physics_joints.back().type = FIXED_JOINT;
+ physics_joints.back().bt_joint = bt_joint;
+ physics_joints.back().fixed_joint = NULL;
+ physics_joints.back().bt_bone[0] = selected_bullet_bones[0];
+ physics_joints.back().bt_bone[1] = selected_bullet_bones[1];
+}
+
+vec3 Skeleton::GetCenterOfMass() {
+ vec3 center(0.0f);
+ float total_mass = 0.0f;
+ for(unsigned i=0; i<physics_bones.size(); i++){
+ if(!physics_bones[i].bullet_object){
+ continue;
+ }
+ center += physics_bones[i].bullet_object->GetPosition() *
+ physics_bones[i].bullet_object->GetMass();
+ total_mass += physics_bones[i].bullet_object->GetMass();
+ }
+ center /= total_mass;
+ return center;
+}
+
+void Skeleton::CreateBallJoint(const SelectedJoint &selected_joint) {
+ int shared_point = -1;
+ Bone* joint_bones[2];
+ joint_bones[0] = &bones[selected_joint.bones[0]];
+ joint_bones[1] = &bones[selected_joint.bones[1]];
+ for(int i=0; i<2; i++){
+ for(int j=0; j<2; j++) {
+ if(joint_bones[0]->points[i] == joint_bones[1]->points[j]){
+ shared_point = joint_bones[0]->points[i];
+ }
+ }
+ }
+ if(shared_point == -1) {
+ std::vector<int> point_ids(2, -1);
+ for(unsigned i=0; i<points.size(); ++i){
+ if(joint_bones[0]->points[0] == (int)i){
+ point_ids[0] = i;
+ }
+ if(joint_bones[0]->points[1] == (int)i){
+ point_ids[1] = i;
+ }
+ }
+ if(!parents.empty() && parents[point_ids[0]] == point_ids[1]){
+ shared_point = point_ids[1];
+ } else {
+ shared_point = point_ids[0];
+ }
+ }
+
+ std::vector<BulletObject*> selected_bullet_bones(2);
+ GetPhysicsObjectsFromSelectedJoint(selected_bullet_bones, selected_joint);
+ DeleteJointOnBones(selected_bullet_bones);
+
+ vec3 anchor = points[shared_point];
+
+ btTypedConstraint *bt_joint = bullet_world->AddBallJoint(selected_bullet_bones[0],
+ selected_bullet_bones[1],
+ anchor);
+
+ physics_joints.resize(physics_joints.size()+1);
+ physics_joints.back().type = BALL_JOINT;
+ physics_joints.back().anchor = anchor;
+ physics_joints.back().bt_joint = bt_joint;
+ physics_joints.back().bt_bone[0] = selected_bullet_bones[0];
+ physics_joints.back().bt_bone[1] = selected_bullet_bones[1];
+
+ mat4 rotation1 = selected_bullet_bones[0]->GetRotation();
+ mat4 rotation2 = selected_bullet_bones[1]->GetRotation();
+}
+
+void Skeleton::CreateRotationalConstraint(const SelectedJoint &selected_joint) {
+ int shared_point = -1;
+ Bone* joint_bones[2];
+ joint_bones[0] = &bones[selected_joint.bones[0]];
+ joint_bones[1] = &bones[selected_joint.bones[1]];
+ for(int i=0; i<2; i++){
+ for(int j=0; j<2; j++) {
+ if(joint_bones[0]->points[i] == joint_bones[1]->points[j]){
+ shared_point = joint_bones[0]->points[i];
+ }
+ }
+ }
+ if(shared_point == -1) {
+ std::vector<int> point_ids(2, -1);
+ for(unsigned i=0; i<points.size(); ++i){
+ if(joint_bones[0]->points[0] == (int)i){
+ point_ids[0] = i;
+ }
+ if(joint_bones[0]->points[1] == (int)i){
+ point_ids[1] = i;
+ }
+ }
+ if(!parents.empty() && parents[point_ids[0]] == point_ids[1]){
+ shared_point = point_ids[1];
+ } else {
+ shared_point = point_ids[0];
+ }
+ }
+
+ std::vector<BulletObject*> selected_bullet_bones(2);
+ GetPhysicsObjectsFromSelectedJoint(selected_bullet_bones, selected_joint);
+ DeleteJointOnBones(selected_bullet_bones);
+
+ vec3 anchor = points[shared_point];
+
+ btTypedConstraint* bt_joint =
+ bullet_world->AddAngleConstraints(selected_bullet_bones[0],
+ selected_bullet_bones[1],
+ anchor,
+ (float)PI*0.25f);
+
+ btTypedConstraint* fixed_joint =
+ bullet_world->AddFixedJoint(selected_bullet_bones[0],
+ selected_bullet_bones[1],
+ anchor);
+
+ physics_joints.resize(physics_joints.size()+1);
+ physics_joints.back().type = ROTATION_JOINT;
+ physics_joints.back().anchor = anchor;
+ physics_joints.back().bt_joint = bt_joint;
+ physics_joints.back().fixed_joint = fixed_joint;
+ physics_joints.back().bt_bone[0] = selected_bullet_bones[0];
+ physics_joints.back().bt_bone[1] = selected_bullet_bones[1];
+ physics_joints.back().stop_angle[0] = -(float)PI*0.25f;
+ physics_joints.back().stop_angle[1] = (float)PI*0.25f;
+ physics_joints.back().stop_angle[2] = -(float)PI*0.25f;
+ physics_joints.back().stop_angle[3] = (float)PI*0.25f;
+ physics_joints.back().stop_angle[4] = -(float)PI*0.25f;
+ physics_joints.back().stop_angle[5] = (float)PI*0.25f;
+
+ mat4 rotation1 = selected_bullet_bones[0]->GetRotation();
+ mat4 rotation2 = selected_bullet_bones[1]->GetRotation();
+
+ physics_joints.back().initial_axis = bullet_world->GetD6Axis(bt_joint,0);
+ physics_joints.back().initial_axis2 = bullet_world->GetD6Axis(bt_joint,2);
+}
+
+void Skeleton::ApplyJointRange(PhysicsJoint *joint) {
+ if(joint->type == HINGE_JOINT) {
+ btTypedConstraint* bt_joint = joint->bt_joint;
+ if(bt_joint){
+ btHingeConstraint* hinge = (btHingeConstraint*)bt_joint;
+ if(joint->stop_angle[0]<joint->stop_angle[1]) {
+ hinge->setLimit(joint->stop_angle[0] + joint->initial_angle, joint->stop_angle[1] + joint->initial_angle);
+ } else {
+ hinge->setLimit(joint->stop_angle[1] + joint->initial_angle, joint->stop_angle[0] + joint->initial_angle);
+ }
+ }
+ }
+ if(joint->type == ROTATION_JOINT) {
+ btTypedConstraint* bt_joint = joint->bt_joint;
+ if(bt_joint){
+ btGeneric6DofConstraint* constraint = (btGeneric6DofConstraint*)bt_joint;
+ constraint->setAngularLowerLimit(btVector3(joint->stop_angle[0],
+ joint->stop_angle[2],
+ joint->stop_angle[4]));
+ constraint->setAngularUpperLimit(btVector3(joint->stop_angle[1],
+ joint->stop_angle[3],
+ joint->stop_angle[5]));
+ }
+ }
+}
+
+void Skeleton::DeleteJoint(PhysicsJoint *joint){
+ if(joint->bt_joint){
+ bullet_world->RemoveJoint(&joint->bt_joint);
+ }
+ if(joint->fixed_joint){
+ bullet_world->RemoveJoint(&joint->fixed_joint);
+ }
+}
+
+void Skeleton::RefreshFixedJoints(const std::vector<BoneTransform> &mats){
+ for(unsigned i=0; i<physics_joints.size(); ++i){
+ PhysicsJoint& joint = physics_joints[i];
+ if(joint.fixed_joint){
+ BoneTransform a = mats[phys_id[joint.bt_bone[0]]];
+ a.origin += a.rotation * joint.bt_bone[0]->com_offset;
+ BoneTransform b = mats[phys_id[joint.bt_bone[1]]];
+ b.origin += b.rotation * joint.bt_bone[1]->com_offset;
+ BulletWorld::UpdateFixedJoint(joint.fixed_joint,
+ a.GetMat4(),
+ b.GetMat4());
+ }
+ }
+}
+
+void Skeleton::PhysicsDispose() {
+ // Remove child shapes
+ for (unsigned i = 0; i < child_shapes.size(); i++) {
+ delete child_shapes[i];
+ }
+ child_shapes.clear();
+
+ // Remove joints
+ for(unsigned i=0; i<physics_joints.size(); i++){
+ PhysicsJoint &joint = physics_joints[i];
+ if(joint.bt_joint){
+ bullet_world->RemoveJoint(&joint.bt_joint);
+ }
+ if(joint.fixed_joint){
+ bullet_world->RemoveJoint(&joint.fixed_joint);
+ }
+ }
+ physics_joints.clear();
+
+ // Remove null constraints
+ for(unsigned i=0; i<null_constraints.size(); i++){
+ bullet_world->RemoveJoint(&null_constraints[i]);
+ }
+ null_constraints.clear();
+
+ // Remove objects
+ for(unsigned i=0; i<physics_bones.size(); i++){
+ bullet_world->RemoveObject(&physics_bones[i].bullet_object);
+ col_bullet_world->RemoveObject(&physics_bones[i].col_bullet_object);
+ }
+ physics_bones.clear();
+
+ // Remove local physics worlds
+ if(col_bullet_world.get()){
+ col_bullet_world->Dispose();
+ col_bullet_world.reset(NULL);
+ }
+}
+
+void Skeleton::Dispose() {
+ points.clear();
+ bones.clear();
+
+ PhysicsDispose();
+}
+
+void Skeleton::SetBulletWorld( BulletWorld *_bullet_world ) {
+ bullet_world = _bullet_world;
+}
+
+Skeleton::~Skeleton() {
+ Dispose();
+}
+
+void Skeleton::GetPhysicsObjectsFromSelectedJoint(
+ std::vector<BulletObject*> &selected_bullet_bones,
+ const SelectedJoint & selected_joint )
+{
+ for(unsigned i=0; i<physics_bones.size(); i++){
+ if(physics_bones[i].bone == selected_joint.bones[0]) {
+ selected_bullet_bones[0] = physics_bones[i].bullet_object;
+ }
+ if(physics_bones[i].bone == selected_joint.bones[1]) {
+ selected_bullet_bones[1] = physics_bones[i].bullet_object;
+ }
+ }
+}
+
+void Skeleton::DeleteJointOnBones( std::vector<BulletObject*> &selected_bullet_bones ) {
+ std::vector<PhysicsJoint>::iterator iter;
+ for(iter = physics_joints.begin(); iter != physics_joints.end();){
+ if(((*iter).bt_bone[0] == selected_bullet_bones[0] &&
+ (*iter).bt_bone[1] == selected_bullet_bones[1]) ||
+ ((*iter).bt_bone[1] == selected_bullet_bones[0] &&
+ (*iter).bt_bone[0] == selected_bullet_bones[1])) {
+ DeleteJoint(&(*iter));
+ iter = physics_joints.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+}
+
+std::vector<float> Skeleton::GetBoneMasses() {
+ std::vector<float> bone_masses(physics_bones.size(),0.0f);
+ for(unsigned i=0; i<bone_masses.size(); i++){
+ if(physics_bones[i].bullet_object){
+ bone_masses[i] = physics_bones[i].bullet_object->GetMass();
+ }
+ }
+ return bone_masses;
+}
+
+struct JointBoneIDs {
+ int id[2];
+};
+
+void Skeleton::SetGFStrength(PhysicsJoint& joint, float _strength) {
+ if(joint.fixed_joint){
+ if(_strength == 0.0f){
+ if(joint.fixed_joint_enabled){
+ bullet_world->UnlinkConstraint(joint.fixed_joint);
+ joint.fixed_joint_enabled = false;
+ }
+ } else {
+ if(!joint.fixed_joint_enabled){
+ bullet_world->LinkConstraint(joint.fixed_joint);
+ joint.fixed_joint_enabled = true;
+ }
+ bullet_world->SetJointStrength(joint.fixed_joint, _strength);
+ }
+ }
+}
+
+void Skeleton::AddNullConstraints() {
+ std::map<BulletObject*, std::set<BulletObject*> > connected;
+
+ for(unsigned i=0; i<physics_joints.size(); ++i){
+ const PhysicsJoint& pj = physics_joints[i];
+ connected[pj.bt_bone[0]].insert(pj.bt_bone[1]);
+ connected[pj.bt_bone[1]].insert(pj.bt_bone[0]);
+ }
+
+ for(unsigned i=0; i<physics_bones.size(); i++){
+ if(fixed_obj[i]){
+ continue;
+ }
+ const PhysicsBone& pbi = physics_bones[i];
+ std::set<BulletObject*> &connected_set = connected[pbi.bullet_object];
+ for(unsigned j=i+1; j<physics_bones.size(); j++){
+ if(fixed_obj[j]){
+ continue;
+ }
+ const PhysicsBone& pbj = physics_bones[j];
+ if(!pbi.bullet_object || !pbj.bullet_object){
+ continue;
+ }
+ if(connected_set.find(pbj.bullet_object) == connected_set.end() &&
+ bullet_world->CheckCollision(pbi.bullet_object, pbj.bullet_object))
+ {
+ BulletObject *obj_a = pbi.bullet_object;
+ BulletObject *obj_b = pbj.bullet_object;
+ if(obj_a->collision_flags & obj_b->collision_group &&
+ obj_b->collision_flags & obj_a->collision_group)
+ {
+ btTypedConstraint* constraint =
+ bullet_world->AddNullConstraint(obj_a, obj_b);
+ null_constraints.push_back(constraint);
+ LOGD << "Creating null constraint between " << LabelFromBone(pbi.bone, simple_ik_bones, parents) << " and " << LabelFromBone(pbj.bone, simple_ik_bones, parents) << std::endl;
+ //bullet_world->CheckCollision(pbi.bullet_object, pbj.bullet_object);
+ }
+ }
+ }
+ }
+ LOGD << null_constraints.size() << " null constraints." << std::endl;
+}
+
+void Skeleton::ShiftJoint( PhysicsJoint& joint, int obj_a, int obj_b ) {
+ if(joint.type != HINGE_JOINT && joint.type != ROTATION_JOINT){
+ return;
+ }
+ btTypedConstraint* bt_joint;
+ if(joint.type == HINGE_JOINT){
+ bt_joint = bullet_world->AddHingeJoint(
+ physics_bones[obj_a].bullet_object,
+ physics_bones[obj_b].bullet_object,
+ joint.anchor,
+ joint.initial_axis,
+ joint.stop_angle[0],
+ joint.stop_angle[1],
+ &joint.initial_angle);
+ }
+ else if(joint.type == ROTATION_JOINT){
+ bt_joint = bullet_world->AddAngleConstraints(
+ physics_bones[obj_a].bullet_object,
+ physics_bones[obj_b].bullet_object,
+ joint.anchor,
+ (float)PI * 0.25f);
+ }
+ else {
+ // because above we returned if joint.type was not one of these two
+ __builtin_unreachable();
+ }
+ btTypedConstraint* fixed_joint = bullet_world->AddFixedJoint(
+ physics_bones[obj_a].bullet_object,
+ physics_bones[obj_b].bullet_object,
+ joint.anchor);
+
+ DeleteJoint(&joint);
+ joint.bt_joint = bt_joint;
+ joint.fixed_joint = fixed_joint;
+ joint.bt_bone[0] = physics_bones[obj_a].bullet_object;
+ joint.bt_bone[1] = physics_bones[obj_b].bullet_object;
+
+ mat4 rotation1 = physics_bones[obj_a].bullet_object->GetRotation();
+ mat4 rotation2 = physics_bones[obj_b].bullet_object->GetRotation();
+
+ if(joint.type == ROTATION_JOINT){
+ joint.initial_axis = bullet_world->GetD6Axis(bt_joint,0);
+ joint.initial_axis2 = bullet_world->GetD6Axis(bt_joint,2);
+ }
+}
+
+void Skeleton::UpdateTwistBones(bool update_transform) {
+ /*for(unsigned i=0; i<physics_bones.size(); ++i){
+ if(fixed_obj[i]){
+ const PhysicsBone& pbone = physics_bones[parents[i]];
+ mat4 initial_parent_mat = pbone.initial_rotation;
+ initial_parent_mat.SetTranslationPart(pbone.initial_position);
+ mat4 initial_mat = physics_bones[i].initial_rotation;
+ initial_mat.SetTranslationPart(physics_bones[i].initial_position);
+ mat4 offset = invert(initial_parent_mat) * initial_mat;
+
+ int child_id = -1;
+ for(unsigned j=0; j<physics_bones.size(); ++j){
+ if(i==j){
+ continue;
+ }
+ if(parents[j] == (int)i &&
+ physics_bones[j].bullet_object)
+ {
+ child_id = j;
+ break;
+ }
+ }
+
+ if(child_id == -1){
+ continue;
+ }
+ const PhysicsBone& cbone = physics_bones[child_id];
+ mat4 child_mat = cbone.bullet_object->GetTransform();
+ mat4 initial_child_mat = cbone.initial_rotation;
+ initial_child_mat.SetTranslationPart(cbone.initial_position);
+
+ mat4 parent_transform = pbone.bullet_object->GetTransform();
+ parent_transform.AddTranslation(parent_transform.GetRotatedvec3(pbone.bullet_object->com_offset*-1.0f));
+
+ mat4 child_mat_local =
+ invert(parent_transform) * child_mat;
+ vec3 vec_x = child_mat_local.GetColumnVec3(0);
+ vec3 vec_z = vec3(0.0f,0.0f,1.0f);
+ vec3 vec_y = normalize(cross(vec_z, vec_x));
+ vec_x = normalize(cross(vec_y, vec_z));
+ mat4 rot;
+ rot.SetColumn(0, vec_x);
+ rot.SetColumn(1, vec_y);
+
+ mat4 new_mat = parent_transform * offset * rot;
+
+ physics_bones[i].bullet_object->SetTransform(new_mat);
+
+ if(update_transform){
+ physics_bones[i].bullet_object->UpdateTransform();
+ }
+ }
+ }*/
+
+ for(unsigned i=0; i<physics_bones.size(); ++i){
+ if(fixed_obj[i] && physics_bones[i].bullet_object){
+ int bone = parents[i];
+ for(int j=0; j<4; ++j){
+ if(!fixed_obj[bone] && has_verts_assigned[bone]){
+ break;
+ }
+ bone = parents[bone];
+ }
+ const PhysicsBone& pbone = physics_bones[bone];
+ mat4 initial_parent_mat = pbone.initial_rotation;
+ initial_parent_mat.SetTranslationPart(pbone.initial_position);
+
+ mat4 initial_mat = physics_bones[i].initial_rotation;
+ initial_mat.SetTranslationPart(physics_bones[i].initial_position);
+ mat4 offset = invert(initial_parent_mat) * initial_mat;
+
+ mat4 curr_parent_mat = pbone.bullet_object->GetRotation();
+ curr_parent_mat.SetTranslationPart(pbone.bullet_object->GetPosition());
+
+ physics_bones[i].bullet_object->SetRotation(pbone.bullet_object->GetTransform() * offset);
+ physics_bones[i].bullet_object->SetPosition(curr_parent_mat * offset * vec3());
+
+ if(update_transform){
+ physics_bones[i].bullet_object->UpdateTransform();
+ }
+ }
+ }
+}
+
+void Skeleton::CheckForNAN()
+{
+ for(unsigned i=0; i<physics_bones.size(); i++){
+ if(physics_bones[i].bullet_object){
+ physics_bones[i].bullet_object->CheckForNAN();
+ }
+ }
+}
+
+void Skeleton::AlternateHull( const std::string &model_name, const vec3 &old_center, float model_char_scale ) {
+ std::string alt_model = model_name.substr(0, model_name.size()-4) + "_hulls.obj";
+ LOGD << "Checking for " << alt_model << std::endl;
+ if(!FileExists(alt_model.c_str(), kDataPaths|kModPaths)){
+ return;
+ }
+ int hull_id = Models::Instance()->loadModel(alt_model,_MDL_SIMPLE);
+ const Model &hull_model = Models::Instance()->GetModel(hull_id);
+
+ // Create map containing all the neighboring vertices for each vertex
+ std::map<int, std::set<int> > vert_connections;
+ unsigned index = 0;
+ const GLuint *tri_faces;
+ for(int i=0, len=hull_model.faces.size()/3; i<len; ++i){
+ tri_faces = &hull_model.faces[index];
+ for(unsigned j=0; j<3; ++j){
+ vert_connections[tri_faces[j]].insert(tri_faces[(j+1)%3]);
+ vert_connections[tri_faces[j]].insert(tri_faces[(j+2)%3]);
+ }
+ index += 3;
+ }
+
+ // For each vertex, propagate group id to all connected vertices
+ std::vector<std::vector<int> > groups;
+ std::vector<int> vert_remap(hull_model.vertices.size()/3);
+ std::map<int, int> group_id;
+ std::queue<int> verts_to_process;
+ int the_label = 0;
+ int remap_id;
+ for(int i=0, len=hull_model.vertices.size()/3; i<len; ++i){
+ if(group_id.find(i) != group_id.end()){
+ continue;
+ }
+ remap_id = 0;
+ verts_to_process.push(i);
+ while(!verts_to_process.empty()){
+ int vert_id = verts_to_process.front();
+ verts_to_process.pop();
+ if(group_id.find(vert_id) != group_id.end()){
+ continue;
+ }
+ group_id.insert(std::pair<int, int>(vert_id, the_label));
+ groups.resize(the_label+1);
+ groups[the_label].push_back(vert_id);
+ vert_remap[vert_id] = remap_id;
+ ++remap_id;
+ const std::set<int> &connections = vert_connections[vert_id];
+ std::set<int>::const_iterator iter = connections.begin();
+ for(; iter != vert_connections[vert_id].end(); ++iter){
+ verts_to_process.push((*iter));
+ }
+ }
+ ++the_label;
+ }
+
+ // Calculate the average position of each group
+ std::vector<vec3> group_centers(groups.size());
+ for(unsigned i=0; i<groups.size(); ++i){
+ const std::vector<int> &group = groups[i];
+ vec3 center_accum(0.0f);
+ for(unsigned j=0; j<group.size(); ++j){
+ const int vert_index = group[j] * 3;
+ center_accum += vec3(hull_model.vertices[vert_index+0],
+ hull_model.vertices[vert_index+1],
+ hull_model.vertices[vert_index+2]);
+ }
+ group_centers[i] = center_accum / (float)group.size();
+ group_centers[i] -= old_center;
+ group_centers[i] *= model_char_scale;
+ }
+
+ std::vector<int> group_closest_bone(groups.size(), -1);
+ std::vector<float> group_closest_dist(groups.size(), 0.0f);
+
+
+ for(unsigned i=0; i<physics_bones.size(); ++i){
+ if(fixed_obj[i] || !has_verts_assigned[i]){
+ continue;
+ }
+ int closest_group = -1;
+ float closest_dist = 0.0f;
+ float dist;
+ for(unsigned j=0; j<groups.size(); ++j){
+ dist = distance_squared(group_centers[j], physics_bones[i].initial_position);
+ if(closest_group == -1 || dist < closest_dist){
+ closest_group = j;
+ closest_dist = dist;
+ }
+ }
+ if(group_closest_bone[closest_group] == -1 ||
+ closest_dist < group_closest_dist[closest_group])
+ {
+ group_closest_dist[closest_group] = closest_dist;
+ group_closest_bone[closest_group] = i;
+ }
+ }
+
+ std::vector<std::vector<vec3> > groups_verts(groups.size());
+ for(unsigned i=0; i<groups.size(); ++i){
+ std::vector<vec3> &group_verts = groups_verts[i];
+ std::vector<int> &group = groups[i];
+ group_verts.resize(group.size());
+ int vert_index;
+ for(unsigned j=0; j<group_verts.size(); ++j){
+ vert_index = group[j]*3;
+ group_verts[j] = vec3(hull_model.vertices[vert_index+0],
+ hull_model.vertices[vert_index+1],
+ hull_model.vertices[vert_index+2]);
+ group_verts[j] -= old_center;
+ group_verts[j] *= model_char_scale;
+ }
+ }
+
+ for(unsigned i=0; i<groups.size(); ++i){
+ if(group_closest_bone[i] != -1){
+ std::vector<int> faces;
+ for(int j=0, len=hull_model.faces.size()/3; j<len; ++j){
+ if(group_id[hull_model.faces[j*3]] == (int)i){
+ faces.push_back(vert_remap[hull_model.faces[j*3+0]]);
+ faces.push_back(vert_remap[hull_model.faces[j*3+1]]);
+ faces.push_back(vert_remap[hull_model.faces[j*3+2]]);
+ }
+ }
+ PhysicsBone &pb = physics_bones[group_closest_bone[i]];
+ std::vector<vec3> &group_verts = groups_verts[i];
+ mat4 mat;
+ mat.SetTranslationPart(pb.initial_position*-1.0f);
+ mat = transpose(pb.initial_rotation) * mat;
+ for(unsigned j=0; j<group_verts.size(); ++j){
+ group_verts[j] = mat*group_verts[j];
+ }
+
+ pb.col_bullet_object =
+ col_bullet_world->CreateConvexObject(group_verts, faces);
+ pb.col_bullet_object->SetVisibility(true);
+ pb.col_bullet_object->owner_object = (Object*)&physics_bones[group_closest_bone[i]];
+ }
+ }
+}
+
+void Skeleton::GetSweptSphereCollisionCharacter( const vec3 & pos, const vec3 & pos2, float radius, SphereCollision & as_col ) {
+ SweptSlideCallback cb;
+ col_bullet_world->GetSweptSphereCollisions(pos, pos2, radius, cb);
+ as_col.adjusted_position = col_bullet_world->ApplySphereSlide(pos2,
+ radius,
+ cb.collision_info);
+ if(cb.true_closest_hit_fraction != 1.0f){
+ LOGI << "Hit something" << std::endl;
+ }
+ as_col.SetFromCallback(pos, pos2, cb);
+}
+
+void Skeleton::UpdateCollisionWorldAABB() {
+ col_bullet_world->UpdateAABB();
+}
+
+const btCollisionObject* Skeleton::CheckRayCollision( const vec3 & start, const vec3 & end, vec3* point, vec3* normal ) {
+ return col_bullet_world->CheckRayCollision(start, end, point, normal, false);
+}
+
+int Skeleton::CheckRayCollisionBone( const vec3 & start, const vec3 & end ) {
+ SimpleRayResultCallbackInfo cb;
+ col_bullet_world->CheckRayCollisionInfo(start, end, cb, false);
+ if(cb.contact_info.size() == 0){
+ LOGI << "Did not hit anything!" << std::endl;
+ return -1;
+ }
+ float closest_dist = FLT_MAX;
+ int closest_hit = -1;
+ for(int contact_index=0, len=cb.contact_info.size(); contact_index<len; ++contact_index){
+ BulletObject* bo = cb.contact_info[contact_index].object;
+ for(unsigned bone_index=0; bone_index<physics_bones.size(); ++bone_index){
+ if(physics_bones[bone_index].col_bullet_object == bo){
+ float dist = distance_squared(start, cb.contact_info[contact_index].point);
+ if(dist < closest_dist){
+ closest_hit = bone_index;
+ closest_dist = dist;
+ }
+ }
+ }
+ }
+ if(closest_hit != -1){
+ return closest_hit;
+ }
+
+ LOGI << "Hit unknown bone!" << std::endl;
+ return -1;
+}
+
+PhysicsBone::PhysicsBone() :
+ bone(0),
+ bullet_object(NULL),
+ col_bullet_object(NULL),
+ display_scale(1.0f)
+{}
diff --git a/Source/Graphics/skeleton.h b/Source/Graphics/skeleton.h
new file mode 100644
index 00000000..97b89009
--- /dev/null
+++ b/Source/Graphics/skeleton.h
@@ -0,0 +1,148 @@
+//-----------------------------------------------------------------------------
+// Name: skeleton.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+#include <Math/mat4.h>
+
+#include <Asset/Asset/fzx_file.h>
+#include <Asset/Asset/skeletonasset.h>
+
+#include <Editors/save_state.h>
+#include <Objects/softinfo.h>
+#include <Physics/bulletworld.h>
+#include <Graphics/bonetransform.h>
+#include <Utility/flat_hash_map.hpp>
+
+#include <map>
+
+struct SelectedJoint;
+class BulletObject;
+class btTypedConstraint;
+struct SphereCollision;
+
+struct Bone {
+ int points[2];
+ float mass;
+ vec3 center_of_mass;
+};
+
+struct PhysicsBone {
+ int bone;
+ BulletObject* bullet_object;
+ BulletObject* col_bullet_object;
+ float display_scale;
+ vec3 initial_position;
+ mat4 initial_rotation;
+ float physics_mass;
+
+ PhysicsBone();
+};
+
+enum JointType {
+ ROTATION_JOINT,
+ HINGE_JOINT,
+ FIXED_JOINT,
+ BALL_JOINT,
+ NULL_JOINT
+};
+
+struct PhysicsJoint {
+ JointType type;
+ btTypedConstraint *bt_joint;
+ btTypedConstraint *fixed_joint;
+ bool fixed_joint_enabled;
+ BulletObject* bt_bone[2];
+ float stop_angle[6];
+ float initial_angle;
+ vec3 initial_axis;
+ vec3 initial_axis2;
+ vec3 anchor;
+
+ PhysicsJoint() :bt_joint(NULL),
+ fixed_joint(NULL),
+ fixed_joint_enabled(true)
+ {}
+};
+
+class Skeleton {
+ BulletWorld *bullet_world;
+ std::map<BulletObject*, int> phys_id;
+
+ void AddModifiedSphere(btCompoundShape* compound_shape, const vec3 &scale, const vec3 &offset, const quaternion &rotation, const mat4 &capsule_transform, const vec3& skel_offset, float body_scale);
+ void AddModifiedCapsule(btCompoundShape* compound_shape, const vec3 &scale, const vec3 &offset, const quaternion &rotation, const mat4 &capsule_transform, const vec3& skel_offset, float body_scale);
+ void AddModifiedBox(btCompoundShape* compound_shape, const vec3 &scale, const vec3 &offset, const quaternion &rotation, const mat4 &capsule_transform, const vec3& skel_offset, float body_scale);
+
+ // These pointers are owned by us
+ std::vector<btConvexShape *> child_shapes;
+
+
+public:
+ std::auto_ptr<BulletWorld> col_bullet_world;
+ std::vector<int> parents;
+ std::vector<PhysicsBone> physics_bones;
+ std::vector<PhysicsJoint> physics_joints;
+ std::vector<vec3> points;
+ std::vector<Bone> bones;
+ std::vector<btTypedConstraint*> null_constraints;
+ std::vector<bool> has_verts_assigned;
+ typedef ska::flat_hash_map<std::string, SimpleIKBone> SimpleIKBoneMap;
+ SimpleIKBoneMap simple_ik_bones;
+ std::vector<bool> fixed_obj;
+ SkeletonAssetRef skeleton_asset_ref;
+
+ void CreateHinge(const SelectedJoint &selected_joint, const vec3 &axis, float *initial_angle_ptr);
+ void CreateFixed(const SelectedJoint &selected_joint);
+ void CreateRotationalConstraint(const SelectedJoint &selected_joint);
+ void CreateBallJoint(const SelectedJoint &selected_joint);
+ void SetBulletWorld(BulletWorld *_bullet_world);
+ int Read( const std::string &path, float scale, float mass_scale, const FZXAssetRef &fzx_ref);
+ static void ApplyJointRange(PhysicsJoint *joint);
+ void DeleteJoint(PhysicsJoint *joint);
+ void CreatePhysicsSkeleton(float scale, const FZXAssetRef& fzx_ref);
+ int GetAttachedBone( BulletObject* object );
+ void Dispose();
+ void SetGravity( bool enable );
+ void ReduceROM(float how_much);
+ void PhysicsDispose();
+ void GetPhysicsObjectsFromSelectedJoint( std::vector<BulletObject*> &selected_bullet_bones, const SelectedJoint & selected_joint );
+ void DeleteJointOnBones( std::vector<BulletObject*> &selected_bullet_bones );
+ vec3 GetCenterOfMass();
+ std::vector<float> GetBoneMasses();
+ void UnlinkFromBulletWorld();
+ void LinkToBulletWorld();
+ void AddNullConstraints();
+ void ShiftJoint( PhysicsJoint& joint, int obj_a, int obj_b );
+ void UpdateTwistBones(bool update_transform);
+ void SetGFStrength(PhysicsJoint& joint, float _strength);
+ void RefreshFixedJoints(const std::vector<BoneTransform> &mats);
+ void CheckForNAN();
+ void AlternateHull( const std::string &model_name, const vec3 &old_center, float model_char_scale );
+ void DrawCollisionBulletWorld();
+ void GetSweptSphereCollisionCharacter( const vec3 & pos, const vec3 & pos2, float radius, SphereCollision & as_col );
+ void UpdateCollisionWorldAABB();
+ const btCollisionObject* CheckRayCollision( const vec3 & start, const vec3 & end, vec3* point, vec3* normal );
+ int CheckRayCollisionBone( const vec3 & start, const vec3 & end );
+ ~Skeleton();
+};
diff --git a/Source/Graphics/sky.cpp b/Source/Graphics/sky.cpp
new file mode 100644
index 00000000..84597052
--- /dev/null
+++ b/Source/Graphics/sky.cpp
@@ -0,0 +1,636 @@
+//-----------------------------------------------------------------------------
+// Name: sky.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "sky.h"
+
+#include <Graphics/camera.h>
+#include <Graphics/graphics.h>
+#include <Graphics/shaders.h>
+#include <Graphics/textures.h>
+#include <Graphics/cubemap.h>
+
+#include <Internal/common.h>
+#include <Internal/timer.h>
+#include <Internal/datemodified.h>
+#include <Internal/filesystem.h>
+#include <Internal/profiler.h>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <Main/scenegraph.h>
+#include <Images/image_export.hpp>
+#include <Math/vec3math.h>
+#include <Wrappers/glm.h>
+#include <Main/engine.h>
+#include <Compat/compat.h>
+
+#include <cmath>
+
+extern Timer game_timer;
+extern char* global_shader_suffix;
+extern bool g_simple_shadows;
+extern bool g_level_shadows;
+extern bool g_debug_runtime_disable_sky_draw;
+
+namespace {
+ const int kSkyBoxRes = 512; ///Resolution of each skybox face
+ const int kDiffuseBoxRes = 128; ///Resolution of blurred skybox faces
+
+ struct CubemapPixelCoord {
+ int x,y,face;
+ };
+
+ CubemapPixelCoord PixelCoordFromByte(int index, int tex_width, int bytes_per_face) {
+ CubemapPixelCoord cpc;
+ cpc.face = index / bytes_per_face;
+ cpc.y = (index - (cpc.face * bytes_per_face)) / (tex_width * 4);
+ cpc.x = (index - (cpc.face * bytes_per_face) - (cpc.y * tex_width * 4))/4;
+ return cpc;
+ }
+
+ int ByteFromPixelCoord(const CubemapPixelCoord &cpc, int tex_width, int bytes_per_face) {
+ return cpc.face * bytes_per_face + cpc.y * tex_width * 4 + cpc.x * 4;
+ }
+
+ void GetMipmapLevel(std::vector<GLubyte> &pixels, int target) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "GetMipmapLevel");
+ int mipmap_width = (int)sqrt(pixels.size()/4/6);
+ int mipmap_level = 0;
+ std::vector<GLubyte> last_mipmap = pixels;
+ while(mipmap_width > 1 && mipmap_level < target){
+ int last_mipmap_width = mipmap_width;
+ mipmap_width /= 2;
+ ++mipmap_level;
+ std::vector<GLubyte> new_pixels(mipmap_width*mipmap_width*4*6);
+
+ int dst_index = 0;
+ int src_index = 0;
+ for (int face=0; face<6; ++face) {
+ for(int y=0; y<mipmap_width; ++y){
+ for(int x=0; x<mipmap_width; ++x){
+ int temp_index = src_index;
+ vec3 accum = vec3(last_mipmap[temp_index+0], last_mipmap[temp_index+1], last_mipmap[temp_index+2]);
+ temp_index += 4;
+ accum += vec3(last_mipmap[temp_index+0], last_mipmap[temp_index+1], last_mipmap[temp_index+2]);
+ temp_index = src_index + last_mipmap_width*4;
+ accum += vec3(last_mipmap[temp_index+0], last_mipmap[temp_index+1], last_mipmap[temp_index+2]);
+ temp_index += 4;
+
+ accum /= 4.0f;
+ new_pixels[dst_index+0] = (GLubyte)accum[0];
+ new_pixels[dst_index+1] = (GLubyte)accum[1];
+ new_pixels[dst_index+2] = (GLubyte)accum[2];
+ new_pixels[dst_index+3] = 255;
+
+ dst_index += 4;
+ src_index += 8;
+ }
+ src_index += last_mipmap_width*4;
+ }
+ }
+ last_mipmap = new_pixels;
+ }
+ pixels = last_mipmap;
+ }
+
+void ProcessCubeMap(TextureRef src_ref, TextureRef ref, GLuint framebuffer, GLuint framebuffer2) {
+ Graphics* graphics = Graphics::Instance();
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Sky::ProcessCubeMap");
+ // Read source cube map
+ int tex_width = Textures::Instance()->getWidth(src_ref);
+ // Find out how much source has to be scaled down to match target
+ int mip = 0;
+ while(tex_width > kDiffuseBoxRes){
+ ++mip;
+ tex_width /= 2;
+ }
+ graphics->PushFramebuffer();
+ for(int i=0; i<6; ++i){
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT+i, Textures::Instance()->returnTexture(src_ref), mip);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer2);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT+i, Textures::Instance()->returnTexture(ref), 0);
+
+ //LOG_ASSERT(glCheckFramebufferStatus(GL_READ_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+ //LOG_ASSERT(glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
+
+ //LOGE << "Process cubemap blit from " << Textures::Instance()->returnTexture(src_ref) << " to " << Textures::Instance()->returnTexture(ref) << std::endl;
+ //GLint viewport[4];
+ //glGetIntegerv(GL_VIEWPORT,viewport);
+ //LOGE << "CUrrent viewport " << viewport[0] << ", " << viewport[1] << ", "<< viewport[2] << ", "<< viewport[3] << std::endl;
+
+ glClearColor(1,0,1,1);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ //Intel actually cares about the scissor test flag in the blit, so we have to disable it.
+ glDisable(GL_SCISSOR_TEST);
+ //We set these incase someone in the future decides blit should care about these too.
+ glViewport(0,0,kDiffuseBoxRes,kDiffuseBoxRes);
+ glDisable(GL_DEPTH_TEST);
+
+ CHECK_FBO_ERROR();
+ CHECK_GL_ERROR();
+ //glReadBuffer(GL_COLOR_ATTACHMENT0);
+ //glDrawBuffer(GL_COLOR_ATTACHMENT0);
+ glBlitFramebuffer(0, 0, kDiffuseBoxRes, kDiffuseBoxRes, 0, 0, kDiffuseBoxRes, kDiffuseBoxRes, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+
+ }
+ graphics->PopFramebuffer();
+ CubemapMipChain(ref, Cubemap::SPHERE, NULL);
+ if(Graphics::Instance()->vendor == _intel){
+ CubemapMipChain(ref, Cubemap::SPHERE, NULL); // This has to be called twice on the brix for some reason
+ }
+ }
+} //namespace ""
+
+
+static glm::mat4 CubeFaceTransform(int i) {
+ glm::mat4 mat(1.0f);
+ switch(i){
+ case 0:
+ mat = glm::rotate(mat, -90.0f, glm::vec3(0.0f, 1.0f, 0.0f));
+ mat = glm::rotate(mat, 180.0f, glm::vec3(0.0f, 0.0f, 1.0f));
+ break;
+ case 1:
+ mat = glm::rotate(mat, 90.0f, glm::vec3(0.0f, 1.0f, 0.0f));
+ mat = glm::rotate(mat, 180.0f, glm::vec3(0.0f, 0.0f, 1.0f));
+ break;
+ case 2:
+ mat = glm::rotate(mat, -90.0f, glm::vec3(1.0f, 0.0f, 0.0f));
+ break;
+ case 3:
+ mat = glm::rotate(mat, 90.0f, glm::vec3(1.0f, 0.0f, 0.0f));
+ break;
+ case 4:
+ mat = glm::rotate(mat, -180.0f, glm::vec3(0.0f, 1.0f, 0.0f));
+ mat = glm::rotate(mat, 180.0f, glm::vec3(0.0f, 0.0f, 1.0f));
+ break;
+ case 5:
+ mat = glm::rotate(mat, 180.0f, glm::vec3(0.0f, 0.0f, 1.0f));
+ break;
+ }
+ return mat;
+}
+
+void Sky::RenderCubeMap(int flags, const TextureRef* land_texture_ref) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Sky::RenderCubeMap");
+ Graphics* graphics = Graphics::Instance();
+ Textures* textures = Textures::Instance();
+ Shaders* shaders = Shaders::Instance();
+ if(!sky_dome_model.VBO_vertices.valid()){
+ sky_dome_model.createVBO();
+ }
+ if(!sky_land_model.VBO_vertices.valid()){
+ sky_land_model.createVBO();
+ }
+ if(!horizon_model.VBO_vertices.valid()){
+ horizon_model.createVBO();
+ }
+ // Set viewport to cube map size
+ CHECK_GL_ERROR();
+ int size = textures->getWidth(cube_map_texture_ref);
+ graphics->PushViewport();
+ CHECK_GL_ERROR();
+ graphics->setViewport(0, 0, size, size);
+ // Set 90 degree projection matrix
+ glm::mat4 proj_mat = glm::perspective(90.0f, 1.0f, 0.01f, 100.0f);
+ CHECK_GL_ERROR();
+ graphics->PushFramebuffer();
+ CHECK_GL_ERROR();
+ graphics->bindFramebuffer(framebuffer);
+ CHECK_GL_ERROR();
+ GLuint cube_map_texture = textures->returnTexture(cube_map_texture_ref);
+ CHECK_GL_ERROR();
+ for (int i = 0; i < 6; i++) {
+ // Rotate view to correct orientation for cube map face
+ CHECK_GL_ERROR();
+ glm::mat4 view_mat = CubeFaceTransform(i);
+ CHECK_GL_ERROR();
+ // Attach cube map face to framebuffer
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT+i, cube_map_texture, 0);
+ // Set up shader
+ int shader_id = shaders->returnProgram(simple_tex_3d_flipped);
+ CHECK_GL_ERROR();
+ shaders->setProgram(shader_id);
+ CHECK_GL_ERROR();
+ shaders->SetUniformVec4("color", vec4(1.0f));
+ int vert_coord_id = shaders->returnShaderAttrib("vert_coord", shader_id);
+ int tex_coord_id = shaders->returnShaderAttrib("tex_coord", shader_id);
+ graphics->BindArrayVBO(0);
+ CHECK_GL_ERROR();
+
+ GLState temp_gl_state;
+ temp_gl_state.blend = false;
+ temp_gl_state.cull_face = false;
+ temp_gl_state.depth_test = false;
+ temp_gl_state.depth_write = false;
+ graphics->setGLState(temp_gl_state);
+ CHECK_GL_ERROR();
+
+ // Clear framebuffer and draw skydome (if valid texture)
+ if(sky_texture_ref.valid()){
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw skydome");
+ CHECK_GL_ERROR();
+ glClearColor(0,0,0,1);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ CHECK_GL_ERROR();
+
+ glm::mat4 model_mat(1.0f);
+ model_mat = glm::rotate(model_mat, 180.0f+sky_rotation, glm::vec3(0.0f, 1.0f, 0.0f));
+ model_mat = glm::scale(model_mat, glm::vec3(-1.0f, 1.0f, -1.0f));
+ glm::mat4 mvp_mat = proj_mat * view_mat * model_mat;
+ shaders->SetUniformMat4("mvp_mat", (const GLfloat*)&mvp_mat);
+ Textures::Instance()->bindTexture(sky_texture_ref);
+ CHECK_GL_ERROR();
+
+
+ graphics->EnableVertexAttribArray(vert_coord_id);
+ graphics->EnableVertexAttribArray(tex_coord_id);
+ sky_dome_model.VBO_vertices.Bind();
+ glVertexAttribPointer(vert_coord_id, 3, GL_FLOAT, false, 3*sizeof(GLfloat), 0);
+ sky_dome_model.VBO_tex_coords.Bind();
+ glVertexAttribPointer(tex_coord_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0);
+ sky_dome_model.VBO_faces.Bind();
+ graphics->DrawElements(GL_TRIANGLES, sky_dome_model.faces.size(), GL_UNSIGNED_INT, 0);
+ graphics->ResetVertexAttribArrays();
+ CHECK_GL_ERROR();
+ } else {
+ glClearColor(0.15f,0.15f,0.15f,1);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ }
+ CHECK_GL_ERROR();
+
+ if(flags & DRAW_LAND) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw land");
+ CHECK_GL_ERROR();
+ { // Draw terrain plane
+ glm::mat4 model_mat(1.0f);
+ model_mat = glm::translate(model_mat, glm::vec3(0.0f, -0.2f, 0.0f));
+ glm::mat4 mvp_mat = proj_mat * view_mat * model_mat;
+ shaders->SetUniformMat4("mvp_mat", (const GLfloat*)&mvp_mat);
+ if(land_texture_ref && land_texture_ref->valid()){
+ Textures::Instance()->bindTexture(*land_texture_ref);
+ } else {
+ shaders->SetUniformVec4("color", vec4(vec3(0.5f),1.0f));
+ Textures::Instance()->bindBlankTexture();
+ }
+ graphics->EnableVertexAttribArray(vert_coord_id);
+ graphics->EnableVertexAttribArray(tex_coord_id);
+ sky_land_model.VBO_vertices.Bind();
+ glVertexAttribPointer(vert_coord_id, 3, GL_FLOAT, false, 3*sizeof(GLfloat), 0);
+ sky_land_model.VBO_tex_coords.Bind();
+ glVertexAttribPointer(tex_coord_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0);
+ sky_land_model.VBO_faces.Bind();
+ graphics->DrawElements(GL_TRIANGLES, sky_land_model.faces.size(), GL_UNSIGNED_INT, 0);
+ graphics->ResetVertexAttribArrays();
+ }
+ CHECK_GL_ERROR();
+
+ if((flags & DRAW_HORIZON)!=0){
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw horizon");
+ // Draw fog band
+ glm::mat4 model_mat(1.0f);
+ model_mat = glm::translate(model_mat, glm::vec3(0.0f, -0.017f, 0.0f));
+ model_mat = glm::scale(model_mat, glm::vec3(1.0f, 0.3f, 1.0f));
+ graphics->setBlend(true);
+
+ int shader_id = shaders->returnProgram(fog);
+ shaders->setProgram(shader_id);
+ shaders->SetUniformVec4("color", vec4(1.0f));
+ int vert_coord_id = shaders->returnShaderAttrib("vert_coord", shader_id);
+ int tex_coord_id = shaders->returnShaderAttrib("tex_coord", shader_id);
+
+ glm::mat4 mvp_mat = proj_mat * view_mat * model_mat;
+ shaders->SetUniformMat4("mvp_mat", (const GLfloat*)&mvp_mat);
+ shaders->SetUniformMat4("model_mat", (const GLfloat*)&model_mat);
+ Textures::Instance()->bindTexture(horizon_texture_ref);
+ Textures::Instance()->bindTexture(spec_cube_map_texture_ref, 3);
+
+ graphics->EnableVertexAttribArray(vert_coord_id);
+ graphics->EnableVertexAttribArray(tex_coord_id);
+ horizon_model.VBO_vertices.Bind();
+ glVertexAttribPointer(vert_coord_id, 3, GL_FLOAT, false, 3*sizeof(GLfloat), 0);
+ horizon_model.VBO_tex_coords.Bind();
+ glVertexAttribPointer(tex_coord_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0);
+ horizon_model.VBO_faces.Bind();
+ graphics->DrawElements(GL_TRIANGLES, horizon_model.faces.size(), GL_UNSIGNED_INT, 0);
+ graphics->ResetVertexAttribArrays();
+ CHECK_GL_ERROR();
+ }
+ CHECK_GL_ERROR();
+ }
+ }
+ // Restore old state
+ graphics->PopFramebuffer();
+ graphics->PopViewport();
+ Textures::Instance()->bindTexture(cube_map_texture_ref);
+ {
+ PROFILER_ZONE(g_profiler_ctx, "glGenerateMipmap");
+ glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
+ }
+}
+
+static GLState GetSkyGLState() {
+ GLState gl_state;
+ gl_state.depth_test = true;
+ gl_state.cull_face = false;
+ gl_state.depth_write = false;
+ gl_state.blend = false;
+ return gl_state;
+}
+
+void Sky::Draw(SceneGraph* scenegraph) {
+ if (g_debug_runtime_disable_sky_draw) {
+ return;
+ }
+
+ if(load_resources_queued) {
+ return;
+ }
+ Graphics* graphics = Graphics::Instance();
+ Shaders* shaders = Shaders::Instance();
+ Textures* textures = Textures::Instance();
+ Camera* cam = ActiveCameras::Get();
+ graphics->setGLState(GetSkyGLState());
+ mat4 view_mat = cam->GetViewMatrix();
+ mat4 proj_mat = cam->GetProjMatrix();
+ mat4 projection_view_mat = proj_mat * view_mat;
+
+ const int kShaderStrSize = 1024;
+ char buf[2][kShaderStrSize];
+ char* shader_str[2] = {buf[0], buf[1]};
+ FormatString(shader_str[0], kShaderStrSize, "envobject #SKY %s", global_shader_suffix);
+ if(displaying_YCOCG_sky){
+ // YCOCG_SRGB is ignored when preloading beacuse the use-case is unclear.
+ // Perhaps it can be set once? Notice global_shader_suffix should be last for preloading
+ FormatString(shader_str[1], kShaderStrSize, "%s #YCOCG_SRGB", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ int the_shader = shaders->returnProgram(shader_str[0]);
+
+ shaders->setProgram(the_shader);
+ shaders->SetUniformFloat("time", game_timer.GetRenderTime());
+ shaders->SetUniformFloat("fog_amount", scenegraph->fog_amount);
+ shaders->SetUniformFloat("haze_mult", scenegraph->haze_mult);
+ shaders->SetUniformVec3("ws_light", scenegraph->primary_light.pos);
+ shaders->SetUniformVec4("primary_light_color",vec4(scenegraph->primary_light.color, scenegraph->primary_light.intensity));
+ shaders->SetUniformVec3("tint", sky_tint);
+ shaders->SetUniformMat4("projection_view_mat", projection_view_mat);
+ shaders->SetUniformVec3("cam_pos",ActiveCameras::Get()->GetPos());
+ textures->bindTexture(cube_map_texture_ref);
+ textures->bindTexture(original_spec_cube_map_texture_ref, 1);
+ textures->bindTexture(spec_cube_map_texture_ref, 2);
+
+ { // Bind shadow matrices to shader
+ std::vector<mat4> shadow_matrix;
+ shadow_matrix.resize(4);
+ for(int i=0; i<4; ++i){
+ shadow_matrix[i] = cam->biasMatrix * graphics->cascade_shadow_mat[i];
+ }
+ std::vector<mat4> temp_shadow_matrix = shadow_matrix;
+ if(g_simple_shadows || !g_level_shadows){
+ temp_shadow_matrix[3] = cam->biasMatrix * graphics->simple_shadow_mat;
+ }
+ shaders->SetUniformMat4Array("shadow_matrix", temp_shadow_matrix);
+ }
+
+ if(g_simple_shadows || !g_level_shadows){
+ Textures::Instance()->bindTexture(graphics->static_shadow_depth_ref, TEX_SHADOW);
+ } else {
+ Textures::Instance()->bindTexture(graphics->cascade_shadow_depth_ref, TEX_SHADOW);
+ }
+
+ DrawModelVerts(sky_box_model, "vertex_attrib", shaders, graphics, the_shader);
+}
+
+void Sky::Dispose() {
+ cube_map_texture_ref.clear();
+ spec_cube_map_texture_ref.clear();
+ original_spec_cube_map_texture_ref.clear();
+ Textures::Instance()->DeleteUnusedTextures();
+ sky_dome_model.Dispose();
+ sky_land_model.Dispose();
+ sky_box_model.Dispose();
+ horizon_model.Dispose();
+
+ if(framebuffer != INVALID_FRAMEBUFFER){
+ Graphics::Instance()->deleteFramebuffer(&framebuffer);
+ framebuffer = INVALID_FRAMEBUFFER;
+ }
+ if(framebuffer2 != INVALID_FRAMEBUFFER){
+ Graphics::Instance()->deleteFramebuffer(&framebuffer2);
+ framebuffer2 = INVALID_FRAMEBUFFER;
+ }
+}
+
+void Sky::Reload() {
+ char abs_path[kPathSize];
+ FindImagePath(dome_texture_name.c_str(), abs_path, kPathSize, kDataPaths | kModPaths);
+ int64_t check_modified = GetDateModifiedInt64(abs_path);
+ if( check_modified != -1 && check_modified > modified) {
+ live_updated = true;
+ QueueLoadResources();
+ }
+}
+
+void Sky::LightingChanged(bool terrain_exists) {
+ if(modified != -1){
+ live_updated = true;
+ cached = false;
+ lighting_changed = true;
+ QueueLoadResources();
+ BakeFirstPass();
+ if(!terrain_exists){
+ BakeSecondPass(NULL);
+ }
+ }
+}
+
+void Sky::QueueLoadResources() {
+ load_resources_queued = true;
+}
+
+void Sky::LoadResources() {
+ Dispose();
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Sky::LoadResources");
+ // Scale down skybox if using low-res textures
+ int sky_box_res = kSkyBoxRes / Graphics::Instance()->config_.texture_reduction_factor();
+ sky_box_res = max(kDiffuseBoxRes, sky_box_res);
+
+ // Check if the dds cache files are missing
+ //bool missing_a_cubemap = false;
+ char abs_path[kPathSize];
+ FindImagePath(dome_texture_name.c_str(), abs_path, kPathSize, kDataPaths | kModPaths);
+ int64_t src_modified = GetDateModifiedInt64(abs_path);
+ int64_t cache_modified = src_modified;
+ int64_t curr_modified;
+ std::string path[2];
+ for(int i=0; i<2; i++){
+ switch(i) {
+ case 0:
+ path[i] = dome_texture_name+"_"+level_name+"_cube.dds";
+ break;
+ case 1:
+ path[i] = dome_texture_name+"_"+level_name+"_cube_blur.dds";
+ break;
+ }
+
+ // Pick most recently modified cubemap from install dir or user write dir
+ FindFilePath(path[i].c_str(), abs_path, kPathSize, kDataPaths | kModPaths, false);
+ curr_modified = GetDateModifiedInt64(abs_path);
+ FindFilePath(path[i].c_str(), abs_path, kPathSize, kWriteDir | kModWriteDirs, false);
+ int64_t write_modified = GetDateModifiedInt64(abs_path);
+ if(curr_modified == -1 || write_modified > curr_modified) {
+ curr_modified = write_modified;
+ }
+ cache_modified = min(cache_modified, curr_modified);
+ if(curr_modified == -1) {
+ //missing_a_cubemap = true;
+ }
+ }
+
+ // If source texture not found, or cubemap is up to date, then load the cubemap
+ /*if(!missing_a_cubemap && (src_modified == -1 || (src_modified <= cache_modified && !lighting_changed))) {
+ modified = cache_modified;
+ cube_map_texture_ref = Textures::Instance()->returnTextureAssetRef(path[0].c_str());
+ spec_cube_map_texture_ref = Textures::Instance()->returnTextureAssetRef(path[1].c_str(), PX_SRGB);
+ cached = true;
+ displaying_YCOCG_sky = true;
+ } else {*/
+ modified = src_modified;
+ cached = false;
+ //}
+
+ sky_box_model.LoadObj("Data/Models/skybox.obj");
+
+ // Allocate resources for skybox creation and blurring
+ if(!cached) {
+ if(sky_dome_model.vertices.empty()){
+ sky_dome_model.LoadObj("Data/Models/skydome.obj");
+ sky_land_model.LoadObj("Data/Models/skyland.obj");
+ horizon_model.LoadObj("Data/Models/horizonband.obj");
+ }
+
+ Textures::Instance()->setWrap(GL_REPEAT, GL_CLAMP_TO_EDGE);
+ Textures::Instance()->setFilters(GL_LINEAR, GL_LINEAR);
+ sky_texture_ref = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(dome_texture_name.c_str(), PX_NOCONVERT|PX_SRGB|PX_NOMIPMAP|PX_NOREDUCE, 0x0);
+
+ horizon_texture_ref = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/skies/horizonband_nocompress.tga", PX_SRGB, 0x0);
+ Textures::Instance()->setWrap(GL_REPEAT);
+ Textures::Instance()->setFilters(GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR);
+
+ cube_map_texture_ref = Textures::Instance()->makeCubemapTexture(sky_box_res, sky_box_res, GL_RGBA16F, GL_RGBA, Textures::MIPMAPS);
+ Textures::Instance()->SetTextureName(cube_map_texture_ref, "Sky Cubemap");
+ spec_cube_map_texture_ref = Textures::Instance()->makeCubemapTexture(kDiffuseBoxRes, kDiffuseBoxRes, GL_RGBA, GL_RGBA, Textures::MIPMAPS);
+ Textures::Instance()->SetTextureName(spec_cube_map_texture_ref, "Sky Spec Cubemap");
+ original_spec_cube_map_texture_ref = spec_cube_map_texture_ref;
+ }
+}
+
+void Sky::BakeFirstPass() {
+ if(load_resources_queued){
+ LoadResources();
+ load_resources_queued = false;
+ }
+ if(!cached) {
+ if(framebuffer == INVALID_FRAMEBUFFER) {
+ Graphics::Instance()->genFramebuffers(&framebuffer, "bake_sky_1");
+ Graphics::Instance()->genFramebuffers(&framebuffer2, "bake_sky_2");
+ }
+ displaying_YCOCG_sky = false;
+ RenderCubeMap(0, NULL);
+ ProcessCubeMap(cube_map_texture_ref, spec_cube_map_texture_ref, framebuffer, framebuffer2);
+ }
+}
+
+void Sky::BakeSecondPass(const TextureRef *_land_texture_ref) {
+ if(!cached) {
+ if(framebuffer == INVALID_FRAMEBUFFER) {
+ Graphics::Instance()->genFramebuffers(&framebuffer, "second_bake_sky_1");
+ Graphics::Instance()->genFramebuffers(&framebuffer2, "second_bake_sky_2");
+ }
+
+ RenderCubeMap(DRAW_LAND, _land_texture_ref);
+ ProcessCubeMap(cube_map_texture_ref, spec_cube_map_texture_ref, framebuffer, framebuffer2);
+ RenderCubeMap(DRAW_LAND | DRAW_HORIZON, _land_texture_ref);
+ ProcessCubeMap(cube_map_texture_ref, spec_cube_map_texture_ref, framebuffer, framebuffer2);
+
+ if(!lighting_changed){
+ horizon_texture_ref.clear();
+ sky_texture_ref.clear();
+
+ sky_dome_model.Dispose();
+ sky_land_model.Dispose();
+ horizon_model.Dispose();
+ }
+
+ displaying_YCOCG_sky = false;
+
+ Textures::Instance()->DeleteUnusedTextures();
+
+ lighting_changed = false;
+ }
+ live_updated = false;
+ //Textures::Instance()->TextureToVRAM(cube_map_texture_ref);
+}
+
+Sky::Sky() :
+ lighting_changed(false),
+ framebuffer(INVALID_FRAMEBUFFER),
+ framebuffer2(INVALID_FRAMEBUFFER),
+ sky_rotation(0.0f),
+ live_updated(false),
+ sky_tint(1.0f),
+ sky_base_tint(1.0f),
+ modified(-1),
+ simple_tex_3d_flipped("simple_tex_3d #FLIPPED"),
+ fog("fog"),
+ shader("envobject #SKY")
+{}
+
+void Sky::GetShaderNames(std::map<std::string, int>& shader_names) {
+ shader_names[simple_tex_3d_flipped] = 0;
+ shader_names[fog] = 0;
+ shader_names[shader] = SceneGraph::kFullDraw;
+}
+
+void Sky::ResetSpecularCubeMapTexture() {
+ spec_cube_map_texture_ref = original_spec_cube_map_texture_ref;
+}
+
+void Sky::SetSpecularCubeMapTexture( TextureRef new_spec_cube_map_texture_ref ) {
+ spec_cube_map_texture_ref = new_spec_cube_map_texture_ref;
+}
+
+TextureRef Sky::GetSpecularCubeMapTexture() {
+ return spec_cube_map_texture_ref;
+}
diff --git a/Source/Graphics/sky.h b/Source/Graphics/sky.h
new file mode 100644
index 00000000..cec1d339
--- /dev/null
+++ b/Source/Graphics/sky.h
@@ -0,0 +1,94 @@
+//-----------------------------------------------------------------------------
+// Name: sky.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Graphics/model.h>
+#include <Asset/Asset/texture.h>
+#include <Internal/datemodified.h>
+
+#include <map>
+
+class SceneGraph;
+
+class Sky{
+private:
+ TextureRef cube_map_texture_ref;
+ TextureRef spec_cube_map_texture_ref;
+ TextureRef original_spec_cube_map_texture_ref;
+
+ const char* simple_tex_3d_flipped;
+ const char* fog;
+ const char* shader;
+
+public:
+ bool displaying_YCOCG_sky;
+ bool lighting_changed;
+
+ int64_t modified;
+ bool cached;
+ bool load_resources_queued;
+
+ GLuint framebuffer;
+ GLuint framebuffer2;
+
+ TextureAssetRef sky_texture_ref;
+ TextureAssetRef horizon_texture_ref;
+ Model sky_dome_model;
+ Model sky_land_model;
+ Model sky_box_model;
+ Model horizon_model;
+
+ enum CubeMapFlags {
+ DRAW_LAND = 1<<0,
+ DRAW_HORIZON = 1<<1
+ };
+
+ void RenderCubeMap(int flags, const TextureRef* land_texture_ref);
+
+public:
+ Sky();
+ void GetShaderNames(std::map<std::string, int>& shader_names);
+
+ std::string dome_texture_name;
+ std::string level_name;
+ float sky_rotation;
+ vec3 sky_tint; // Current displayed tint
+ vec3 sky_base_tint; // Set by script parameters
+
+
+ bool live_updated;
+
+ void Draw(SceneGraph* scenegraph);
+ void LightingChanged(bool terrain_exists);
+ void Reload();
+ void QueueLoadResources();
+ void LoadResources();
+ void Dispose();
+ void BakeFirstPass(); // Just render the skydome to a cubemap
+ void BakeSecondPass(const TextureRef *_land_texture_ref); // Render skydome, ground plane, and horizon band
+
+ void ResetSpecularCubeMapTexture();
+ void SetSpecularCubeMapTexture( TextureRef new_spec_cube_map_texture_ref );
+ TextureRef GetSpecularCubeMapTexture();
+};
diff --git a/Source/Graphics/terrain.cpp b/Source/Graphics/terrain.cpp
new file mode 100644
index 00000000..d1b84e7f
--- /dev/null
+++ b/Source/Graphics/terrain.cpp
@@ -0,0 +1,1061 @@
+//-----------------------------------------------------------------------------
+// Name: terrain.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "terrain.h"
+
+#include <Graphics/textures.h>
+#include <Graphics/camera.h>
+#include <Graphics/graphics.h>
+#include <Graphics/geometry.h>
+#include <Graphics/bytecolor.h>
+#include <Graphics/shaders.h>
+#include <Graphics/sky.h>
+#include <Graphics/models.h>
+#include <Graphics/ColorWheel.h>
+#include <Graphics/pxdebugdraw.h>
+
+#include <Internal/common.h>
+#include <Internal/checksum.h>
+#include <Internal/collisiondetection.h>
+#include <Internal/filesystem.h>
+
+#include <Images/texture_data.h>
+#include <Images/image_export.hpp>
+
+#include <Math/triangle.h>
+#include <Math/vec3math.h>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <Main/scenegraph.h>
+#include <Main/engine.h>
+
+#include <Physics/physics.h>
+#include <Compat/fileio.h>
+#include <Logging/logdata.h>
+#include <Memory/stack_allocator.h>
+#include <Asset/Asset/averagecolorasset.h>
+#include <GUI/widgetframework.h>
+#include <Memory/allocation.h>
+
+#include <cfloat>
+#include <set>
+#include <algorithm>
+#include <cmath>
+
+extern SceneLight* primary_light;
+
+static const uint16_t _terrain_cache_file_version_number = 27;
+static const float kHeightMapHeight = 140.0f;
+static const float kUniformScale = 2.0f;
+static const float kVerticalScale = 0.1f;
+
+
+Terrain::Terrain():
+ model_id(-1),
+ framebuffer(INVALID_FRAMEBUFFER),
+ shader("secondterrain")
+{}
+
+//Dispose of terrain
+Terrain::~Terrain() {
+ Dispose();
+}
+
+void Terrain::Dispose() {
+ LOGI << "Disposing of terrain" << std::endl;
+ terrain_patches.clear();
+ LOGI << "Clearing color path" << std::endl;
+ color_path.clear();
+ LOGI << "Clearing detail texture ref" << std::endl;
+ detail_texture_ref.clear();
+ LOGI << "Clearing detail normal texture ref" << std::endl;
+ detail_normal_texture_ref.clear();
+ LOGI << "Clearing detail texture color" << std::endl;
+ detail_texture_color.clear();
+ LOGI << "Clearing texture color srgb" << std::endl;
+ detail_texture_color_srgb.clear();
+ LOGI << "Clearing detail maps" << std::endl;
+ detail_maps_info.clear();
+ LOGI << "Clearing detail object surfaces" << std::endl;
+ for(std::list<DetailObjectSurface*>::iterator iter = detail_object_surfaces.begin();
+ iter != detail_object_surfaces.end(); ++iter)
+ {
+ delete (*iter);
+ }
+ detail_object_surfaces.clear();
+ detail_maps_info.clear();
+ LOGI << "Done with Dispose()" << std::endl;
+}
+
+void Terrain::GLInit(Sky* sky) {
+ if(minimal)
+ return;
+
+ // Create framebuffer object
+ Graphics* graphics = Graphics::Instance();
+ if(framebuffer == INVALID_FRAMEBUFFER){
+ graphics->PushFramebuffer();
+ graphics->genFramebuffers(&framebuffer, "sky");
+ graphics->bindFramebuffer(framebuffer);
+ graphics->PopFramebuffer();
+ }
+ if(!normal_map_ref.valid()) {
+ CHECK_GL_ERROR();
+
+ // Create lighting textures
+ Textures* textures = Textures::Instance();
+ baked_texture_ref = textures->makeTexture(terrain_texture_size,terrain_texture_size,GL_RGBA,GL_RGBA,true);
+ textures->SetTextureName(baked_texture_ref, "Terrain Baked Texture");
+
+ // Create skybox without terrain
+ sky->BakeFirstPass();
+
+ { // Draw to texture
+
+ CHECK_GL_ERROR();
+ // Get normal map write path
+ std::string normal_map_path = heightmap_.path() + "_normal.png";
+ std::string w_nmp = GetWritePath(heightmap_.modsource_)+normal_map_path;
+ if(GetDateModifiedInt64(w_nmp.c_str()) >= GetDateModifiedInt64(normal_map_path.c_str())){
+ normal_map_path = w_nmp;
+ }
+
+ // Bake normal map
+ FILE *test_file = my_fopen(normal_map_path.c_str(), "rb");
+ if(test_file){
+ fclose(test_file);
+ } else {
+ float res_scale = 512.0f/heightmap_.width();
+ int size = heightmap_.width();
+ std::vector<unsigned char> color(size*size*4);
+ vec3 points[5];
+ vec3 normals[4];
+ vec3 normal;
+ for(int j=0; j<size; ++j){
+ for(int i=0; i<size; ++i){
+ for(int k=0; k<5; ++k){
+ points[k][0] = (float)(i);
+ points[k][2] = (float)(j);
+ switch(k){
+ case 0: break;
+ case 1: points[k][2] -= 1.0f; break;
+ case 2: points[k][0] += 1.0f; break;
+ case 3: points[k][2] += 1.0f; break;
+ case 4: points[k][0] -= 1.0f; break;
+ }
+ points[k][1] = heightmap_.GetHeight((int)points[k][0],(int)points[k][2])*kVerticalScale / res_scale;
+ }
+
+ for(int k=0; k<4; ++k){
+ normals[k] = cross(normalize(points[(k+1)%4+1] - points[0]), normalize(points[k+1]-points[0]));
+ }
+
+ normal = normalize(normals[0] + normals[1] + normals[2] + normals[3]);
+
+ color[j*size*4+i*4+0] = (unsigned char)((normal[2]+1.0f)*0.5f*255.0f);
+ color[j*size*4+i*4+1] = (unsigned char)((normal[1]+1.0f)*0.5f*255.0f);
+ color[j*size*4+i*4+2] = (unsigned char)((normal[0]+1.0f)*0.5f*255.0f);
+ color[j*size*4+i*4+3] = 255;
+ }
+ }
+ ImageExport::SavePNG(normal_map_path.c_str(), &color[0], size, size);
+ }
+ normal_map_ref = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(heightmap_.path() + "_normal.png");
+
+ // Get lighting texture write path
+ std::string baked_map_path = heightmap_.path() + "_" + level_name + "_baked.png";
+ std::string w_bmp = GetWritePath(heightmap_.modsource_)+baked_map_path;
+ if(GetDateModifiedInt64(w_bmp.c_str()) >= GetDateModifiedInt64(baked_map_path.c_str())){
+ baked_map_path = w_bmp;
+ }
+
+ test_file = my_fopen(baked_map_path.c_str(), "rb");
+ if(test_file){ // Load texture if already baked
+ fclose(test_file);
+ baked_texture_asset = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(heightmap_.path() + "_" + level_name + "_baked.png", PX_SRGB, 0x0);
+ baked_texture_ref = baked_texture_asset->GetTextureRef();
+ } else { // Otherwise calculate it
+ BakeTerrainTexture(framebuffer, sky->GetSpecularCubeMapTexture());
+ }
+ CHECK_GL_ERROR();
+ }
+
+ sky->BakeSecondPass(&baked_texture_ref);
+ heightmap_.LoadData(heightmap_.path(), HeightmapImage::DOWNSAMPLED);
+ for(std::list<DetailObjectSurface*>::iterator iter = detail_object_surfaces.begin();
+ iter != detail_object_surfaces.end(); ++iter)
+ {
+ (*iter)->SetBaseTextures(color_texture_ref, normal_map_ref);
+ }
+ CHECK_GL_ERROR();
+ }
+}
+
+//Draw terrain
+bool TriangleSquareIntersection2D(vec3 &min, vec3 &max, vec3 *tri_point[3]);
+bool PointInTriangle(vec3 &point, vec3 *tri_point[3]);
+void Terrain::drawLayer(int which) {
+ if(which==0){
+ Model &terrain_simplified_model = Models::Instance()->GetModel(model_id);
+ terrain_simplified_model.Draw();
+ }
+}
+
+int Terrain::lineCheck(const vec3& start, const vec3& end, vec3* point, vec3 *normal) {
+ Model &terrain_simplified_model = Models::Instance()->GetModel(model_id);
+ return terrain_simplified_model.lineCheckNoBackface(start, end, point, normal);
+}
+
+const int _down_sample = 1;
+
+void Terrain::CalculateHighResVertices(Model &terrain_high_detail_model) {
+ float res_scale = 512.0f/heightmap_.width();
+ int size = heightmap_.width();
+ int down_sample_size = size/_down_sample;
+ int num_verts = size*size/_down_sample/_down_sample;
+ terrain_high_detail_model.vertices.resize(num_verts*3);
+ #pragma omp parallel for
+ for(int i=0; i<down_sample_size; i++){
+ int index;
+ int fixed_i;
+ int fixed_j;
+ for(int j=0; j<down_sample_size; j++)
+ {
+ fixed_i = i*_down_sample;
+ fixed_j = j*_down_sample;
+ index = (i*down_sample_size + j)*3;
+ terrain_high_detail_model.vertices[index+0] = (fixed_i - size/2) * kUniformScale * res_scale;
+ terrain_high_detail_model.vertices[index+1] = heightmap_.GetHeight(fixed_i, fixed_j) * kVerticalScale * kUniformScale;
+ terrain_high_detail_model.vertices[index+2] = (fixed_j - size/2) * kUniformScale * res_scale;
+ }
+ }
+}
+
+void Terrain::CalculateHighResFaces(Model &terrain_high_detail_model) {
+ int size = heightmap_.width();
+ int down_sample_size = size/_down_sample;
+ //terrain_high_detail_model.ResizeFaces((down_sample_size-1)*(down_sample_size-1)*2);
+ int num_faces = (down_sample_size-1)*(down_sample_size-1)*2;
+ terrain_high_detail_model.faces.resize(num_faces*3);
+ #pragma omp parallel for
+ for(int i=0; i<down_sample_size-1; i++){
+ int index;
+ for(int j=0; j<down_sample_size-1; j++) {
+ index = (i*(down_sample_size-1)+j)*6;
+ terrain_high_detail_model.faces[index+0] = i + j*down_sample_size;
+ terrain_high_detail_model.faces[index+1] = i+1 + j*down_sample_size;
+ terrain_high_detail_model.faces[index+2] = i+1 + (j+1)*down_sample_size;
+ terrain_high_detail_model.faces[index+3] = i + j*down_sample_size;
+ terrain_high_detail_model.faces[index+4] = i+1 + (j+1)*down_sample_size;
+ terrain_high_detail_model.faces[index+5] = i + (j+1)*down_sample_size;
+ }
+ }
+}
+
+bool Terrain::LoadCachedSimplifiedTerrain() {
+ bool success = false;
+ bool rewrite_cache = false;
+
+ char cache_rel_path[kPathSize];
+ FormatString(cache_rel_path, kPathSize, "%s.cache", heightmap_.path().c_str());
+
+ char uv2_rel_path[kPathSize];
+ FormatString(uv2_rel_path, kPathSize, "%s.obj_UV2", heightmap_.path().c_str());
+
+ char abs_uv2_path[kPathSize];
+ //bool found_uv2 = false;
+ unsigned short uv2_checksum = 0;
+ if(FindFilePath(uv2_rel_path, abs_uv2_path, kPathSize, kDataPaths | kModPaths, false, NULL) == 0){
+ //found_uv2 = true;
+ uv2_checksum = Checksum(abs_uv2_path);
+ }
+
+ const int kMaxPaths = 5;
+ char abs_cache_paths[kPathSize * kMaxPaths];
+ int num_paths_found = FindFilePaths(cache_rel_path, abs_cache_paths, kPathSize, kMaxPaths, kAnyPath, true, NULL, NULL );
+
+ if(num_paths_found > 0){
+ for(int path_index=0; path_index<num_paths_found; ++path_index){
+ char* curr_path = &abs_cache_paths[kPathSize * path_index];
+ FILE* cache_file = my_fopen(curr_path, "rb");
+ if(cache_file) { // bug: sometimes cache_file is not null when fopen fails
+ uint16_t version;
+ fread(&version, sizeof(version), 1, cache_file);
+ if(version == _terrain_cache_file_version_number) {
+ uint16_t checksum = 0;
+ fread(&checksum, sizeof(checksum), 1, cache_file);
+ if(checksum == heightmap_.checksum()) {
+ uint16_t uv2_checksum_read = 0;
+ fread(&uv2_checksum_read, sizeof(uv2_checksum_read), 1, cache_file);
+ if(uv2_checksum_read == uv2_checksum){
+ AddLoadingText("Loading cached terrain...");
+ if(model_id == -1){
+ model_id = Models::Instance()->AddModel();
+ }
+ Model &terrain_simplified_model = Models::Instance()->GetModel(model_id);
+ terrain_simplified_model.Dispose();
+ terrain_simplified_model.ReadFromFile(cache_file);
+ terrain_simplified_model.calcBoundingBox();
+ terrain_simplified_model.calcBoundingSphere(); // TO DO: This should be cached
+ terrain_simplified_model.vbo_enabled = true;
+ success = true;
+ LOGI << "Loaded cached terrain: \"" << cache_rel_path << "\"" << std::endl;
+ }
+ }
+ }
+ fclose(cache_file);
+ }
+ if(success){
+ break;
+ }
+ }
+ }
+ return success;
+}
+
+void TextureTerrainModel(Model &model, float size) {
+ model.tex_coords.resize(model.vertices.size());
+ model.tex_coords2.resize(model.vertices.size());
+ for(int i=0, len=model.vertices.size()/3; i<len; i++){
+ model.tex_coords[i*2+0] = model.vertices[i*3+0]/size+0.5f;
+ model.tex_coords[i*2+1] = model.vertices[i*3+2]/size+0.5f;
+ model.tex_coords2[i*2+0] = model.vertices[i*3+0];
+ model.tex_coords2[i*2+1] = model.vertices[i*3+2];
+ }
+}
+
+void RemoveDoubledTriangles(Model *model) {
+ // Used to find faces that share all three vertices
+ std::map<int,std::map<int,std::map<int,std::set<int> > > > face_vertex_map;
+
+ // Stores which faces each vertex is part of
+ std::map<int,std::set<int> > vertex_connections;
+
+ // Mark duplicate triangles as possibly bad
+ std::vector<int> possible_bad_faces;
+ std::vector<int> face_vertices(3);
+ for(int i=0, len=model->faces.size()/3; i<len; i++){
+ face_vertices[0] = model->faces[i*3+0];
+ face_vertices[1] = model->faces[i*3+1];
+ face_vertices[2] = model->faces[i*3+2];
+ vertex_connections[face_vertices[0]].insert(i);
+ vertex_connections[face_vertices[1]].insert(i);
+ vertex_connections[face_vertices[2]].insert(i);
+ std::sort(face_vertices.begin(), face_vertices.end());
+ if(face_vertex_map[face_vertices[0]][face_vertices[1]][face_vertices[2]].size()==1) {
+ possible_bad_faces.push_back(*(face_vertex_map[face_vertices[0]][face_vertices[1]][face_vertices[2]].begin()));
+ }
+ if(!face_vertex_map[face_vertices[0]][face_vertices[1]][face_vertices[2]].empty()) {
+ possible_bad_faces.push_back(i);
+ }
+ face_vertex_map[face_vertices[0]][face_vertices[1]][face_vertices[2]].insert(i);
+ }
+
+ // If possibly-bad triangles have one vertex that is only shared by two triangles,
+ // mark as bad
+ std::vector<int> bad_faces;
+ for(unsigned int i=0; i<possible_bad_faces.size(); i++){
+ if(vertex_connections[model->faces[i*3+0]].size()<=2||vertex_connections[model->faces[i*3+1]].size()<=2||vertex_connections[model->faces[i*3+2]].size()<=2) {
+ bad_faces.push_back(possible_bad_faces[i]);
+ }
+ }
+
+ // Mark triangles with normals facing down as bad
+ for(unsigned i=0; i<possible_bad_faces.size(); i++){
+ if(model->face_normals[possible_bad_faces[i]][1]<0){
+ bad_faces.push_back(possible_bad_faces[i]);
+ }
+ }
+
+ for(unsigned i=0; i<bad_faces.size(); i++){
+ model->faces[bad_faces[i]*3+0] = 0;
+ model->faces[bad_faces[i]*3+1] = 0;
+ model->faces[bad_faces[i]*3+2] = 0;
+ }
+}
+
+void Terrain::CalculateSimplifiedTerrain() {
+ //Model terrain_high_detail_model;
+ if(model_id == -1){
+ model_id = Models::Instance()->AddModel();
+ }
+ Model &terrain_simplified_model = Models::Instance()->GetModel(model_id);
+ CalculateHighResVertices(terrain_simplified_model);
+ CalculateHighResFaces(terrain_simplified_model);
+ /*{
+ terrain_simplified_model.tex_coords.resize(terrain_simplified_model.vertices.size()/3*2);
+ float size = (float)m_heightmap.width();
+ for(int i=0, len=terrain_simplified_model.vertices.size()/3; i<len; i++){
+ terrain_simplified_model.tex_coords[i*2+0] = terrain_simplified_model.vertices[i*3+0]/size+0.5f;
+ terrain_simplified_model.tex_coords[i*2+1] = terrain_simplified_model.vertices[i*3+2]/size+0.5f;
+ }
+ std::fstream file;
+ file.open(GetWritePath("terrain_high.obj").c_str(), std::fstream::out);
+ for(unsigned i=0; i<terrain_simplified_model.vertices.size(); i+=3){
+ file << "v " << terrain_simplified_model.vertices[i+0] << " "
+ << terrain_simplified_model.vertices[i+1] << " "
+ << terrain_simplified_model.vertices[i+2] << "\n";
+ }
+ for(unsigned i=0; i<terrain_simplified_model.tex_coords.size(); i+=2){
+ file << "vt " << terrain_simplified_model.tex_coords[i+0] << " "
+ << terrain_simplified_model.tex_coords[i+1] << "\n";
+ }
+ for(unsigned i=0; i<terrain_simplified_model.faces.size(); i+=3){
+ file << "f " << terrain_simplified_model.faces[i+0]+1 << "/"
+ << terrain_simplified_model.faces[i+0]+1 << " "
+ << terrain_simplified_model.faces[i+1]+1 << "/"
+ << terrain_simplified_model.faces[i+1]+1 << " "
+ << terrain_simplified_model.faces[i+2]+1 << "/"
+ << terrain_simplified_model.faces[i+2]+1 << "\n";
+ }
+ file.close();
+ }*/
+#ifndef _DEPLOY
+ AddLoadingText("Simplifying terrain. If it gets stuck, start without debugger (ctrl-F5 in MSVC).");
+#else
+ AddLoadingText("Simplifying terrain. This may take a while, please be patient!");
+#endif
+ SimplifyModel("Data/Temp/terrain",terrain_simplified_model,70000);
+ char abs_path[kPathSize];
+ if(FindFilePath("Data/Temp/terrainlow.obj", abs_path, kPathSize, kWriteDir) == -1){
+ FatalError("Error", "Could not find: Data/Temp/terrainlow.obj");
+ }
+ LOGI << "Loading terrain hard coded model: " << abs_path << std::endl;
+ terrain_simplified_model.LoadObj(abs_path,0,"",kAbsPath);
+ RemoveDoubledTriangles(&terrain_simplified_model);
+ TextureTerrainModel(terrain_simplified_model, (float)heightmap_.width());
+
+ int index = 1;
+ for(int i=0, len=terrain_simplified_model.vertices.size()/3; i<len; i++){
+ terrain_simplified_model.vertices[index] -= kHeightMapHeight;
+ index += 3;
+ }
+
+ AddLoadingText("Calculating terrain normals...");
+ terrain_simplified_model.calcNormals();
+ AddLoadingText("Calculating terrain tangents...");
+ terrain_simplified_model.calcTangents();
+
+ terrain_simplified_model.calcBoundingBox();
+ terrain_simplified_model.calcBoundingSphere();
+
+ terrain_simplified_model.CalcTexelDensity();
+ terrain_simplified_model.CalcAverageTriangleEdge();
+
+ char uv2_rel_path[kPathSize];
+ FormatString(uv2_rel_path, kPathSize, "%s.obj_UV2", heightmap_.path().c_str());
+
+ char abs_uv2_path[kPathSize];
+ //bool found_uv2 = false;
+ uint16_t uv2_checksum = 0;
+
+ if(FindFilePath(uv2_rel_path, abs_uv2_path, kPathSize, kDataPaths | kModPaths, false, NULL) == 0){
+ //found_uv2 = true;
+ uv2_checksum = Checksum(abs_uv2_path);
+
+ Model temp;
+ temp.SimpleLoadTriangleCutObj(abs_uv2_path);
+ CopyTexCoords2(terrain_simplified_model, temp);
+ for(unsigned i=0; i<terrain_simplified_model.tex_coords2.size(); ++i){
+ terrain_simplified_model.tex_coords2[i] *= 2048.0f;
+ }
+ }
+
+ std::string path = GetWritePath(heightmap_.modsource_)+heightmap_.path() + ".cache";
+ FILE *cache_file = my_fopen(path.c_str(), "wb");
+ if(cache_file){
+ AddLoadingText("Writing terrain cache file...");
+ fwrite(&_terrain_cache_file_version_number, sizeof(_terrain_cache_file_version_number), 1, cache_file);
+ uint16_t checksum = heightmap_.checksum();
+ fwrite(&checksum, sizeof(checksum), 1, cache_file);
+ fwrite(&uv2_checksum, sizeof(uv2_checksum), 1, cache_file);
+ Model &terrain_simplified_model = Models::Instance()->GetModel(model_id);
+ terrain_simplified_model.WriteToFile(cache_file);
+ fclose(cache_file);
+ }
+
+ terrain_simplified_model.SaveObj(heightmap_.path() + ".obj");
+
+ terrain_simplified_model.vbo_enabled = true;
+}
+
+void Terrain::CalculateMinimalTerrain() {
+ if(model_id == -1) {
+ model_id = Models::Instance()->AddModel();
+ }
+ Model& terrain_minimal_model = Models::Instance()->GetModel(model_id);
+ CalculateHighResVertices(terrain_minimal_model);
+ CalculateHighResFaces(terrain_minimal_model);
+ TextureTerrainModel(terrain_minimal_model, (float)heightmap_.width());
+
+ int index = 1;
+ for(int i=0, len=terrain_minimal_model.vertices.size()/3; i<len; i++){
+ terrain_minimal_model.vertices[index] -= kHeightMapHeight;
+ index += 3;
+ }
+
+ terrain_minimal_model.calcNormals();
+ terrain_minimal_model.calcTangents();
+ terrain_minimal_model.calcBoundingBox();
+ terrain_minimal_model.calcBoundingSphere();
+ terrain_minimal_model.texel_density = 0.0f;
+ terrain_minimal_model.average_triangle_edge_length = 0.0f;
+ terrain_minimal_model.vbo_enabled = true;
+}
+
+Model& Terrain::GetModel() const {
+ Model &terrain_simplified_model = Models::Instance()->GetModel(model_id);
+ return terrain_simplified_model;
+}
+const float _light_offset = 0.002f;
+
+void Terrain::BakeTerrainTexture(GLuint framebuffer, const TextureRef& light_cube) {
+ if(minimal)
+ return;
+
+ Shaders* shaders = Shaders::Instance();
+ Textures* textures = Textures::Instance();
+ Graphics* graphics = Graphics::Instance();
+
+ Model &terrain_simplified_model = Models::Instance()->GetModel(model_id);
+ if(!textures->IsRenderable(baked_texture_ref)) {
+ baked_texture_ref = textures->makeTexture(terrain_texture_size,terrain_texture_size,GL_RGBA,GL_RGBA,true);
+ textures->SetTextureName(baked_texture_ref, "Terrain Baked Texture");
+ }
+ graphics->PushFramebuffer();
+ graphics->RenderFramebufferToTexture(framebuffer, baked_texture_ref);
+ graphics->PushViewport();
+ graphics->setViewport(0,0,terrain_texture_size,terrain_texture_size);
+
+ GLState gl_state;
+ gl_state.blend = false;
+ gl_state.cull_face = false;
+ gl_state.depth_test = false;
+ gl_state.depth_write = false;
+ graphics->setGLState(gl_state);
+
+ glClearColor(0.0f,0.0f,0.0f,0.0f);
+ glClear( GL_COLOR_BUFFER_BIT);
+
+ int prepare_shader_id = shaders->returnProgram(shader);
+ shaders->setProgram(prepare_shader_id);
+
+ vec3 light_pos = primary_light->pos;
+ shaders->SetUniformVec3("light_pos",light_pos);
+ shaders->SetUniformVec4("primary_light_color",vec4(primary_light->color, primary_light->intensity));
+
+ textures->bindTexture(color_texture_ref, 0);
+ textures->bindTexture(light_cube, 3);
+ textures->bindTexture(normal_map_ref, 4);
+
+ {
+ CHECK_GL_ERROR();
+ if(!terrain_simplified_model.vbo_loaded){
+ terrain_simplified_model.createVBO();
+ }
+ terrain_simplified_model.VBO_tex_coords.Bind();
+ int vert_attrib_id = shaders->returnShaderAttrib("uv", prepare_shader_id);
+
+ CHECK_GL_ERROR();
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ CHECK_GL_ERROR();
+ glVertexAttribPointer(vert_attrib_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0);
+ CHECK_GL_ERROR();
+ terrain_simplified_model.VBO_faces.Bind();
+ CHECK_GL_ERROR();
+ graphics->DrawElements(GL_TRIANGLES, terrain_simplified_model.faces.size(), GL_UNSIGNED_INT, 0);
+ CHECK_GL_ERROR();
+ graphics->ResetVertexAttribArrays();
+ CHECK_GL_ERROR();
+ graphics->BindArrayVBO(0);
+ CHECK_GL_ERROR();
+ graphics->BindElementVBO(0);
+ CHECK_GL_ERROR();
+ }
+
+ graphics->PopViewport();
+ graphics->PopFramebuffer();
+
+ Textures::Instance()->GenerateMipmap(baked_texture_ref);
+}
+
+//Load terrain
+void Terrain::Load(const char* name, const std::string& model_override) {
+ minimal = false;
+ heightmap_.LoadData(name, HeightmapImage::DOWNSAMPLED);
+
+ LOGI << "Loading terrain \"" << name << "\"" << std::endl;
+ AddLoadingText("Checking for terrain cache file...");
+
+ if(!LoadCachedSimplifiedTerrain()) {
+ LOGI << "Failed to load cached terrain, calculating a simplified terrain" << std::endl;
+ CalculateSimplifiedTerrain();
+ }
+
+ if(!model_override.empty()){
+ Model &terrain_simplified_model = Models::Instance()->GetModel(model_id);
+ terrain_simplified_model.LoadObj(model_override, 0);
+ for(unsigned i=0; i<terrain_simplified_model.tex_coords2.size(); ++i){
+ terrain_simplified_model.tex_coords2[i] *= 2048.0f;
+ }
+ }
+
+ LOGI << "*****************" << std::endl;
+
+ CalculatePatches();
+
+ heightmap_.LoadData(heightmap_.path(), HeightmapImage::ORIGINAL_RES);
+
+ terrain_texture_size = heightmap_.width() / Graphics::Instance()->config_.texture_reduction_factor();
+}
+
+void Terrain::LoadMinimal(const char* name, const std::string& model_override) {
+ minimal = true;
+ heightmap_.LoadData(name, HeightmapImage::DOWNSAMPLED);
+ CalculateMinimalTerrain();
+ CalculatePatches();
+ terrain_texture_size = heightmap_.width() / Graphics::Instance()->config_.texture_reduction_factor();
+}
+
+void Terrain::GetShaderNames(std::map<std::string, int>& preload_shaders) {
+ preload_shaders[shader] = 0;
+}
+
+void Terrain::CalcDetailTextures()
+{
+ LOGI << "Calculating detail textures." << std::endl;
+ unsigned num_colors = detail_maps_info.size();
+
+ Textures::Instance()->setWrap(GL_REPEAT, GL_REPEAT);
+ detail_texture_ref = Textures::Instance()->makeArrayTexture(num_colors, PX_SRGB);
+ Textures::Instance()->SetTextureName(detail_texture_ref, "Terrain Detail Texture Array - Color");
+ detail_texture_color.resize(num_colors);
+ detail_texture_color_srgb.resize(num_colors);
+ detail_normal_texture_ref = Textures::Instance()->makeArrayTexture(num_colors);
+ Textures::Instance()->SetTextureName(detail_normal_texture_ref, "Terrain Detail Texture Array - Normals");
+
+ Textures::Instance()->setWrap(GL_REPEAT, GL_REPEAT);
+
+ // Get average color of each detail texture
+ std::vector<ByteColor> average_color(num_colors);
+ for(unsigned i=0; i<num_colors; i++){
+ Textures::Instance()->loadArraySlice(detail_texture_ref, i, detail_maps_info[i].colorpath);
+ Textures::Instance()->loadArraySlice(detail_normal_texture_ref, i, detail_maps_info[i].normalpath);
+
+ //detail_texture_color[i] = AverageColors::Instance()->ReturnRef(detail_maps_info[i].colorpath)->color();
+ AverageColorRef color_ref = Engine::Instance()->GetAssetManager()->LoadSync<AverageColor>(detail_maps_info[i].colorpath);
+ average_colors.insert(color_ref);
+ detail_texture_color[i] = color_ref->color();
+ for(int channel=0; channel<3; ++channel){
+ average_color[i].color[channel] = (int)(detail_texture_color[i][channel]*255.0f);
+ detail_texture_color_srgb[i][channel] = pow(detail_texture_color[i][channel], 2.2f);
+ }
+ detail_texture_color_srgb[i][3] = detail_texture_color[i][3];
+ }
+
+ char abs_weight_map_path[kPathSize];
+ bool found_weight_path = false;
+ if(!weight_map_path.empty()){
+ found_weight_path =
+ (FindFilePath(weight_map_path.c_str(), abs_weight_map_path, kPathSize, kDataPaths | kModPaths,false) != -1);
+ }
+
+ if(found_weight_path){
+ detail_texture_weights = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(weight_map_path);
+ //weight_bitmap = ImageSamplers::Instance()->ReturnRef(weight_map_path);
+ weight_bitmap = Engine::Instance()->GetAssetManager()->LoadSync<ImageSampler>(weight_map_path);
+ return;
+ }
+
+ std::string path = heightmap_.path() + "_" + level_name + "_weights.png";
+ found_weight_path =
+ (FindImagePath(path.c_str(), abs_weight_map_path, kPathSize, kDataPaths | kModPaths | kWriteDir | kModWriteDirs,false) != -1);
+
+ if(found_weight_path){
+ detail_texture_weights = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(path);
+ //weight_bitmap = ImageSamplers::Instance()->ReturnRef(path);
+ weight_bitmap = Engine::Instance()->GetAssetManager()->LoadSync<ImageSampler>(path);
+ } else {
+ LOGI << "Calculating detail texture weights" << std::endl;
+ // Load terrain color map
+ TextureData texture_data;
+ char abs_path[kPathSize];
+ if(FindFilePath(color_path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths) == -1){
+ //Fall back on finding the .dds if the raw is missing.
+ if(FindImagePath(color_path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths) == -1) {
+ FatalError("Error", "Could not find color path: %s", color_path.c_str());
+ }
+ }
+ texture_data.Load(abs_path);
+ // TODO: check this
+ unsigned total_bytes = texture_data.GetWidth() *
+ texture_data.GetHeight() *
+ 32 / 8;
+
+ // Compare each pixel to each average color
+ // Create weight map and tint texture
+ //#pragma omp parallel for
+ std::vector<unsigned char> image_data;
+ image_data.resize(total_bytes);
+ texture_data.GetUncompressedData(&image_data[0]);
+ for(int i=0; i<(int)total_bytes; i+=4){
+ ByteColor color;
+ color.Set(image_data[i+0],
+ image_data[i+1],
+ image_data[i+2]);
+
+ std::vector<float> distances(num_colors);
+ for(unsigned j=0; j<num_colors; j++){
+ distances[j] = hue_saturation_distance_squared(color,average_color[j]);
+ }
+
+ float lowest_distance = FLT_MIN;
+ unsigned int which_lowest = (unsigned int)-1;
+ for(unsigned int j=0; j<num_colors; j++){
+ if(which_lowest == (unsigned int)-1 || distances[j] < lowest_distance){
+ which_lowest = j;
+ lowest_distance = distances[j];
+ }
+ }
+
+ // Create weight map
+ image_data[i+0] = which_lowest==0?255:0;
+ image_data[i+1] = which_lowest==1?255:0;
+ image_data[i+2] = which_lowest==2?255:0;
+ image_data[i+3] = 255;
+ //texture_data.m_nImageData[i+3] = which_lowest==3?255:0;
+ }
+
+ for(unsigned i=0; i<total_bytes; i+=4){
+ std::swap(image_data[i+0],
+ image_data[i+2]);
+ }
+
+ LOGI << "Saving detail texture weights " << path << std::endl;
+ // Save weight and tint maps as textures
+ path = heightmap_.path() + "_" + level_name + "_weights.png";
+ std::string write_path = GetWritePath(heightmap_.modsource_)+heightmap_.path() + "_" + level_name + "_weights.png";
+ ImageExport::SavePNG(write_path.c_str(), &image_data[0], texture_data.GetWidth(), texture_data.GetHeight());
+ //SavePNG("detail_tint_no_compress.png", tint_texture_data.m_nImageData, tint_texture_data.m_nImageWidth, tint_texture_data.m_nImageHeight);
+
+ // Load weight and tint map textures
+ detail_texture_weights = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(path);
+ //weight_bitmap = ImageSamplers::Instance()->ReturnRef(path);
+ weight_bitmap = Engine::Instance()->GetAssetManager()->LoadSync<ImageSampler>(path);
+
+ //detail_texture_tint = Textures::Instance()->returnTextureAssetRef("detail_tint_no_compress.png");
+ }
+}
+
+const float terrain_size = 500.0;
+const float fade_distance = 50.0;
+const float fade_mult = 1.0f / fade_distance;
+float GetAlpha(const vec3& v){
+ float alpha = min(1.0f,(terrain_size-v[0])*fade_mult)*
+ min(1.0f,(v[0]+500.0f)*fade_mult)*
+ min(1.0f,(terrain_size-v[2])*fade_mult)*
+ min(1.0f,(v[2]+500.0f)*fade_mult);
+
+ alpha = max(0.0f,alpha);
+
+ return alpha;
+}
+
+void Terrain::CalculatePatches()
+{
+ const int patch_resolution = 6;
+
+ // Create 3-dimensional array to store triangles for each patch, e.g.:
+ // patch_vertex_ids[0][2][61] would give 62nd triangle id for patch (0,2)
+ std::vector<std::vector<std::vector<int> > > patch_vertex_ids;
+ patch_vertex_ids.resize(patch_resolution);
+ for(int i=0; i<patch_resolution; i++){
+ patch_vertex_ids[i].resize(patch_resolution);
+ }
+
+ std::vector<std::vector<std::vector<int> > > edge_patch_vertex_ids;
+ edge_patch_vertex_ids.resize(patch_resolution);
+ for(int i=0; i<patch_resolution; i++){
+ edge_patch_vertex_ids[i].resize(patch_resolution);
+ }
+
+ // Loop through terrain faces to find out which patch it belongs to
+ Model &terrain_simplified_model = Models::Instance()->GetModel(model_id);
+ const Model& model = terrain_simplified_model;
+ const vec3 dimensions = (model.max_coords - model.min_coords);
+
+ // added an epsilon so the edge is not exceeded.
+ const float patch_resolution_f = ((float)patch_resolution) - 0.01f;
+ int face_index, vert_index[3];
+ static const float one_third = 1.0f/3.0f;
+ for(int i=0, len=model.faces.size()/3; i<len; i++){
+ face_index = i*3;
+ for(unsigned j=0; j<3; ++j){
+ vert_index[j] = model.faces[face_index+j]*3;
+ }
+ vec3 midpoint;
+ for(unsigned j=0; j<3; ++j){
+ midpoint[j] = (model.vertices[vert_index[0]+j] +
+ model.vertices[vert_index[1]+j] +
+ model.vertices[vert_index[2]+j])*one_third;
+ }
+
+ const vec3 unit_midpoint = (midpoint - model.min_coords) /
+ dimensions;
+
+ const unsigned x_coord = (unsigned)(unit_midpoint[0] *
+ patch_resolution_f);
+
+ const unsigned z_coord = (unsigned)(unit_midpoint[2] *
+ patch_resolution_f);
+
+ float alpha = min(GetAlpha(vec3(model.vertices[vert_index[0]+0],
+ model.vertices[vert_index[0]+1],
+ model.vertices[vert_index[0]+2])),
+ min(GetAlpha(vec3(model.vertices[vert_index[1]+0],
+ model.vertices[vert_index[1]+1],
+ model.vertices[vert_index[1]+2])),
+ GetAlpha(vec3(model.vertices[vert_index[2]+0],
+ model.vertices[vert_index[2]+1],
+ model.vertices[vert_index[2]+2]))));
+ if(alpha == 1.0f){
+ patch_vertex_ids[x_coord][z_coord].push_back(i);
+ } else {
+ edge_patch_vertex_ids[x_coord][z_coord].push_back(i);
+ }
+ }
+
+ terrain_patches.clear();
+ edge_terrain_patches.clear();
+ for(int i=0; i<patch_resolution; i++){
+ for(int j=0; j<patch_resolution; j++){
+ terrain_patches.resize(terrain_patches.size()+1);
+ Model &patch_model = terrain_patches.back();
+ patch_model.CopyFacesFromModel(terrain_simplified_model,
+ patch_vertex_ids[i][j]);
+ patch_model.calcBoundingBox();
+ if(patch_model.vertices.empty()){
+ terrain_patches.resize(terrain_patches.size()-1);
+ }
+
+ if(!edge_patch_vertex_ids[i][j].empty()){
+ edge_terrain_patches.resize(edge_terrain_patches.size()+1);
+ Model &edge_patch_model = edge_terrain_patches.back();
+ edge_patch_model.CopyFacesFromModel(terrain_simplified_model,
+ edge_patch_vertex_ids[i][j]);
+ edge_patch_model.calcBoundingBox();
+ if(edge_patch_model.vertices.empty()){
+ edge_terrain_patches.resize(edge_terrain_patches.size()-1);
+ }
+ }
+ }
+ }
+}
+
+vec2 Terrain::GetUVAtPoint(const vec3 &point, int* tri) const{
+ int face = -1;
+ vec3 intersection_point;
+ Model &terrain_simplified_model = Models::Instance()->GetModel(model_id);
+ const Model& model = terrain_simplified_model;
+ //vec3 normal;
+ if(!tri){
+ vec3 point_high(point[0], point[1]+1000.0f, point[2]);
+ vec3 point_low(point[0], point[1]-1000.0f, point[2]);
+ face = model.lineCheckNoBackface(point_high,
+ point_low,
+ &intersection_point);
+ } else {
+ face = *tri;
+ intersection_point = point;
+ }
+
+ if(face == -1){
+ return vec2(0.0f);
+ }
+
+ int index[3];
+ index[0] = model.faces[face*3+0];
+ index[1] = model.faces[face*3+1];
+ index[2] = model.faces[face*3+2];
+
+ vec3 points[3];
+ for(unsigned j=0; j<3; ++j){
+ points[j][0] = model.vertices[index[j]*3+0];
+ points[j][1] = model.vertices[index[j]*3+1];
+ points[j][2] = model.vertices[index[j]*3+2];
+ }
+
+
+ //DebugDraw::Instance()->AddLine(point_high, point_low, vec4(1.0f), _delete_on_update);
+ //DebugDraw::Instance()->AddWireSphere(intersection_point, 0.1f, vec4(1.0f), _delete_on_update);
+
+ //DebugDraw::Instance()->AddLine(points[0], points[1], vec4(1.0f), _fade, _DD_XRAY);
+ //DebugDraw::Instance()->AddLine(points[1], points[2], vec4(1.0f), _fade, _DD_XRAY);
+ //DebugDraw::Instance()->AddLine(points[2], points[0], vec4(1.0f), _fade, _DD_XRAY);
+
+
+ vec3 normal = normalize(cross(points[1] - points[0], points[2] - points[0]));
+
+ vec3 barycentric_coords = barycentric(intersection_point,
+ normal, points[0], points[1], points[2]);
+
+ float total = 0.0f;
+ for(unsigned i=0; i<3; ++i){
+ barycentric_coords[i] = min(1.0f, max(0.0f, barycentric_coords[i]));
+ total += barycentric_coords[i];
+ }
+
+ if(total == 0.0f){
+ barycentric_coords = vec3(1.0f/3.0f,1.0f/3.0f,1.0f/3.0f);
+ } else {
+ barycentric_coords *= (1.0f / total);
+ }
+
+ vec2 uv;
+ uv[0] = model.tex_coords[index[0]*2+0]*barycentric_coords[0]+
+ model.tex_coords[index[1]*2+0]*barycentric_coords[1]+
+ model.tex_coords[index[2]*2+0]*barycentric_coords[2];
+
+ uv[1] = model.tex_coords[index[0]*2+1]*barycentric_coords[0]+
+ model.tex_coords[index[1]*2+1]*barycentric_coords[1]+
+ model.tex_coords[index[2]*2+1]*barycentric_coords[2];
+ return uv;
+}
+
+vec4 Terrain::SampleWeightMapAtPoint( vec3 point, int* tri )
+{
+ vec2 uv = GetUVAtPoint(point, tri);
+
+ vec3 color = weight_bitmap->GetInterpolatedColorUV(uv[0], uv[1]).xyz();
+
+ //DebugDraw::Instance()->AddWireSphere(point, 0.2f, vec4(color, 1.0f), _fade);
+
+ float missing_component = 1.0f - (color[0] + color[1] + color[2]);
+ vec4 final_color = vec4(color, missing_component);
+ return final_color;
+}
+
+
+vec3 Terrain::SampleColorMapAtPoint( vec3 point, int *tri ) {
+ vec2 uv = GetUVAtPoint(point, tri);
+ vec3 color = color_bitmap->GetInterpolatedColorUV(uv[0], uv[1]).xyz();
+ for(int i=0; i<3; ++i){
+ color[i] = pow(color[i], 2.2f);
+ }
+ return color;
+}
+
+
+const MaterialRef Terrain::GetMaterialAtPoint( vec3 point, int* tri ) {
+ vec4 weights = SampleWeightMapAtPoint(point, tri);
+
+ int strongest_weight = 0;
+ float strongest_weight_amount = weights[0];
+ for(int i=1; i<4; i++){
+ if(weights[i]>strongest_weight_amount){
+ strongest_weight = i;
+ strongest_weight_amount = weights[i];
+ }
+ }
+
+ const std::string &path = detail_maps_info[strongest_weight].materialpath;
+ LOGS << path << std::endl;
+ //Materials* materials = Materials::Instance();
+ //MaterialRef material = materials->ReturnRef(path);
+ MaterialRef material = Engine::Instance()->GetAssetManager()->LoadSync<Material>(path);
+ return material;
+}
+
+void Terrain::HandleMaterialEvent( const std::string &the_event, const vec3 &event_pos, int* tri )
+{
+ MaterialRef material_ref = GetMaterialAtPoint(event_pos, tri);
+ Material& material = (*material_ref);
+ material.HandleEvent(the_event, event_pos);
+}
+
+void Terrain::SetDetailObjectLayers( const std::vector<DetailObjectLayer> &_detail_object_layers )
+{
+ if(detail_object_layers == _detail_object_layers || minimal){
+ return;
+ }
+ detail_object_layers = _detail_object_layers;
+ detail_object_surfaces.clear();
+ detail_object_surfaces.resize(detail_object_layers.size());
+ int counter = 0;
+ Model &terrain_simplified_model = Models::Instance()->GetModel(model_id);
+ for(std::list<DetailObjectSurface*>::iterator iter = detail_object_surfaces.begin();
+ iter != detail_object_surfaces.end(); ++iter)
+ {
+ static const mat4 identity;
+ *iter = new DetailObjectSurface();
+ DetailObjectSurface& dos = *(*iter);
+ dos.AttachTo(terrain_simplified_model, identity);
+ dos.GetTrisInPatches(identity);
+ dos.LoadDetailModel(detail_object_layers[counter].obj_path);
+ dos.LoadWeightMap(detail_object_layers[counter].weight_path);
+ dos.SetDensity(detail_object_layers[counter].density);
+ dos.tint_weight = (detail_object_layers[counter].tint_weight);
+ dos.SetNormalConform(detail_object_layers[counter].normal_conform);
+ dos.SetMinEmbed(detail_object_layers[counter].min_embed);
+ dos.SetMaxEmbed(detail_object_layers[counter].max_embed);
+ dos.SetMinScale(detail_object_layers[counter].min_scale);
+ dos.SetMaxScale(detail_object_layers[counter].max_scale);
+ dos.SetViewDist(detail_object_layers[counter].view_dist);
+ dos.SetJitterDegrees(detail_object_layers[counter].jitter_degrees);
+ dos.SetOverbright(detail_object_layers[counter].overbright);
+ dos.SetCollisionType(detail_object_layers[counter].collision_type);
+ ++counter;
+ }
+ if(normal_map_ref.valid()){
+ for(std::list<DetailObjectSurface*>::iterator iter = detail_object_surfaces.begin();
+ iter != detail_object_surfaces.end(); ++iter)
+ {
+ (*iter)->SetBaseTextures(color_texture_ref, normal_map_ref);
+ }
+ }
+}
+
+void Terrain::SetDetailTextures( const std::vector<DetailMapInfo> &_detail_maps_info )
+{
+ if(normal_map_ref.valid() && (_detail_maps_info != detail_maps_info)) {
+ detail_maps_info = _detail_maps_info;
+ CalcDetailTextures();
+ } else {
+ detail_maps_info = _detail_maps_info;
+ }
+}
+
+void Terrain::SetColorTexture(const char* path)
+{
+ color_path = path;
+ color_bitmap = Engine::Instance()->GetAssetManager()->LoadSync<ImageSampler>(path);
+ color_texture_ref = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(path, PX_SRGB, 0x0);
+}
+
+void Terrain::SetWeightTexture(const char* path)
+{
+ weight_map_path = path;
+ CalcDetailTextures();
+}
diff --git a/Source/Graphics/terrain.h b/Source/Graphics/terrain.h
new file mode 100644
index 00000000..f919c622
--- /dev/null
+++ b/Source/Graphics/terrain.h
@@ -0,0 +1,122 @@
+//-----------------------------------------------------------------------------
+// Name: terrain.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Graphics/model.h>
+#include <Graphics/textures.h>
+#include <Graphics/heightmap.h>
+#include <Graphics/textureref.h>
+#include <Graphics/detailobjectsurface.h>
+
+#include <Math/vec2.h>
+#include <Math/vec3.h>
+#include <Math/vec4.h>
+
+#include <Asset/Asset/material.h>
+#include <Asset/Asset/averagecolorasset.h>
+
+#include <Compat/filepath.h>
+#include <Internal/levelxml.h>
+
+class Sky;
+class LoadingText;
+class SceneGraph;
+
+class Terrain {
+ private:
+ HeightmapImage heightmap_;
+
+ const char* shader;
+
+ bool minimal;
+ int model_id;
+
+ void CalculateHighResVertices(Model &terrain_high_detail_model);
+ void CalculateHighResFaces(Model &terrain_high_detail_model);
+
+ void CalcWorkingPoints(vec3 cam_pos);
+
+ void CalculateSimplifiedTerrain();
+ void CalculateMinimalTerrain();
+ bool LoadCachedSimplifiedTerrain();
+
+ public:
+ std::list<Model> terrain_patches;
+ std::list<Model> edge_terrain_patches;
+ std::list<DetailObjectSurface*> detail_object_surfaces;
+ std::set<AverageColorRef> average_colors;
+
+ void GLInit(Sky* sky);
+ TextureAssetRef normal_map_ref;
+ TextureAssetRef color_texture_ref;
+ TextureAssetRef weight_perturb_ref;
+
+ TextureRef baked_texture_ref;
+ TextureAssetRef baked_texture_asset;
+
+ TextureRef detail_texture_ref; // array texture
+ TextureRef detail_normal_texture_ref; // array texture
+ std::vector<vec4> detail_texture_color;
+ std::vector<vec4> detail_texture_color_srgb;
+ TextureAssetRef detail_texture_weights;
+ TextureAssetRef detail_texture_tint;
+
+ ImageSamplerRef weight_bitmap;
+ ImageSamplerRef color_bitmap;
+
+ std::string color_path;
+
+ std::vector<DetailMapInfo> detail_maps_info;
+ std::vector<DetailObjectLayer> detail_object_layers;
+
+ std::string level_name;
+ std::string weight_map_path;
+
+ int terrain_texture_size;
+ GLuint framebuffer;
+
+ void BakeTerrainTexture(GLuint framebuffer, const TextureRef& light_cube);
+ Model& GetModel() const;
+ void drawLayer(int which);
+ void Load(const char* name, const std::string& model_override = NULL);
+ void LoadMinimal(const char* name, const std::string& model_override = NULL);
+ void GetShaderNames(std::map<std::string, int>& preload_shaders);
+ int lineCheck(const vec3& start, const vec3& end, vec3* point, vec3 *normal=0);
+ ~Terrain();
+ Terrain();
+
+ const MaterialRef GetMaterialAtPoint(vec3 point, int *tri = NULL);
+ void CalcDetailTextures();
+ void CalculatePatches();
+ void Dispose();
+ void HandleMaterialEvent( const std::string &the_event, const vec3 &event_pos, int* tri );
+ vec4 SampleWeightMapAtPoint( vec3 point, int* tri = NULL );
+ vec2 GetUVAtPoint(const vec3 &point, int* tri = NULL) const;
+ vec3 SampleColorMapAtPoint( vec3 point, int *tri );
+ void SetDetailObjectLayers( const std::vector<DetailObjectLayer> &detail_object_layers );
+ void SetDetailTextures( const std::vector<DetailMapInfo> &detail_maps_info );
+
+ void SetColorTexture(const char* path);
+ void SetWeightTexture(const char* path);
+};
diff --git a/Source/Graphics/text.cpp b/Source/Graphics/text.cpp
new file mode 100644
index 00000000..ac743fae
--- /dev/null
+++ b/Source/Graphics/text.cpp
@@ -0,0 +1,1154 @@
+//-----------------------------------------------------------------------------
+// Name: text.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "text.h"
+
+#include <Graphics/textures.h>
+#include <Graphics/graphics.h>
+#include <Graphics/font_renderer.h>
+#include <Graphics/shaders.h>
+#include <Graphics/Billboard.h>
+#include <Graphics/pxdebugdraw.h>
+#include <Graphics/vbocontainer.h>
+
+#include <Internal/error.h>
+#include <Internal/profiler.h>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <Images/texture_data.h>
+#include <Images/image_export.hpp>
+
+#include <Memory/allocation.h>
+#include <Compat/fileio.h>
+#include <Scripting/angelscript/ascontext.h>
+#include <Wrappers/glm.h>
+#include <Logging/logdata.h>
+#include <Utility/assert.h>
+
+#include <SDL_assert.h>
+#include <utf8/utf8.h>
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+#include <vector>
+#include <map>
+#include <string>
+#include <list>
+
+TextAtlas g_text_atlas[kNumTextAtlas];
+TextAtlasRenderer g_text_atlas_renderer;
+
+// A rectangular image that accumulates Freetype bitmaps, and can be copied to
+// an OpenGL texture
+class TextCanvas {
+public:
+ typedef std::vector<unsigned char> MemoryBlock;
+ void Create(int width, int height);
+ void AddBitmap(int x, int y, const FT_Bitmap& bitmap);
+ void GetBGRA(MemoryBlock *mem_to_fill);
+ void Clear();
+ void SetPenColor(int r, int g, int b, int a);
+ void RenderToConsole();
+ int width() {return width_;}
+ int height() {return height_;}
+private:
+ struct PenColor {
+ unsigned char r,g,b,a;
+ };
+ PenColor pen_color_;
+ int width_, height_;
+ std::vector<unsigned char> pixels_;
+};
+
+void TextCanvas::Create( int width, int height ) {
+ width_ = width;
+ height_ = height;
+ // Allocate 4 bytes per pixel
+ pixels_.resize(width_ * height_ * 4, 0);
+ pen_color_.r = 0;
+ pen_color_.g = 0;
+ pen_color_.b = 0;
+ pen_color_.a = 255;
+}
+
+static int min(int a, int b){
+ if(a < b){
+ return a;
+ } else {
+ return b;
+ }
+}
+
+static int max(int a, int b){
+ if(a > b){
+ return a;
+ } else {
+ return b;
+ }
+}
+
+int mix(int a, int b, int amount) {
+ return (a*(255-amount) + b*amount)/255;
+}
+
+void TextCanvas::AddBitmap( int x, int y, const FT_Bitmap& bitmap ) {
+ int left = x;
+ int right = x+bitmap.width;
+ int bottom = y;
+ int top = y+bitmap.rows;
+ left = clamp(left, 0, width_);
+ right = clamp(right, 0, width_);
+ top = clamp(top, 1, height_);
+ bottom = clamp(bottom,1, height_);
+ int pen_alpha = pen_color_.a;
+ for(int i = bottom; i < top; ++i){
+ int index = (height_-i)*width_*4 + left*4;
+ int bitmap_index = (i-y)*bitmap.width;
+ for(int j = left; j < right; ++j){
+ int opac = bitmap.buffer[bitmap_index+(j-x)];
+ opac = (opac * pen_alpha) / 255;
+ unsigned char* pixel = &pixels_[index];
+ pixel[0] = mix((int)pixel[0], (int)pen_color_.r, opac);
+ pixel[1] = mix((int)pixel[1], (int)pen_color_.g, opac);
+ pixel[2] = mix((int)pixel[2], (int)pen_color_.b, opac);
+ pixel[3] = mix((int)pixel[3], 255, opac);
+ index += 4;
+ }
+ }
+}
+
+void TextCanvas::RenderToConsole() {
+ for(int i=0; i<height_; ++i){
+ int index = i*width_;
+ for(int j=0; j<width_; ++j){
+ printf("%d",pixels_[index+j]/26);
+ }
+ printf("\n");
+ }
+}
+
+void TextCanvas::GetBGRA( MemoryBlock *mem_to_fill ) {
+ unsigned num_pixels = pixels_.size();
+ mem_to_fill->resize(num_pixels);
+ for(unsigned i=0; i<num_pixels; ++i){
+ mem_to_fill->at(i) = pixels_[i];
+ }
+}
+
+void TextCanvas::Clear() {
+ for(unsigned i=0, len=pixels_.size(); i<len; ++i){
+ pixels_[i] = 0;
+ }
+}
+
+void TextCanvas::SetPenColor( int r, int g, int b, int a ) {
+ pen_color_.r = r;
+ pen_color_.g = g;
+ pen_color_.b = b;
+ pen_color_.a = a;
+}
+
+
+void RenderCharacterToConsole(int face_id, uint32_t character){
+ FontRenderer* font_renderer = FontRenderer::Instance();
+ FT_GlyphSlot slot = font_renderer->RenderCharacterBitmap(face_id, character, FontRenderer::RCB_RENDER);
+ FT_Bitmap &bitmap = slot->bitmap;
+ for(int i=0; i<bitmap.rows; ++i){
+ int index = i*bitmap.width;
+ for(int j=0; j<bitmap.width; ++j){
+ printf("%d",bitmap.buffer[index+j]/26);
+ }
+ printf("\n");
+ }
+}
+
+TextureRef CreateTextureFromBGRABlock(const TextCanvas::MemoryBlock &bgra_block, int width, int height){\
+ TextureRef texture_ref = Textures::Instance()->makeTexture(width, height, GL_RGBA, GL_RGBA, true);
+ Textures::Instance()->bindTexture(texture_ref);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, &bgra_block[0]);
+ Textures::Instance()->GenerateMipmap(texture_ref);
+ return texture_ref;
+}
+
+struct TextCanvasTextureImpl {
+ TextCanvas::MemoryBlock bgra_block;
+ TextureRef texture_ref;
+ TextCanvas text_canvas;
+ FT_Vector pen;
+ float pen_rotation;
+ TextCanvasTextureImpl():pen_rotation(0.0f) {
+ pen.x = 0;
+ pen.y = 0;
+ }
+};
+
+void TextCanvasTexture::Create( int width, int height) {
+ TextCanvas &text_canvas = impl_->text_canvas;
+ TextCanvas::MemoryBlock &bgra_block = impl_->bgra_block;
+ TextureRef &texture_ref = impl_->texture_ref;
+ text_canvas.Create(width, height);
+ text_canvas.GetBGRA(&bgra_block);
+ texture_ref = CreateTextureFromBGRABlock(bgra_block, width, height);
+ Textures::Instance()->SetTextureName(texture_ref, "Text Canvas Texture");
+}
+
+void FillTextureFromBGRABlock(const TextureRef &texture_ref, const TextCanvas::MemoryBlock &bgra_block, int width, int height) {
+ Textures::Instance()->bindTexture(texture_ref);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, &bgra_block[0]);
+ Textures::Instance()->GenerateMipmap(texture_ref);
+}
+
+void TextCanvasTexture::UploadTextCanvasToTexture() {
+ TextCanvas &text_canvas = impl_->text_canvas;
+ TextCanvas::MemoryBlock &bgra_block = impl_->bgra_block;
+ TextureRef &texture_ref = impl_->texture_ref;
+ text_canvas.GetBGRA(&bgra_block);
+ FillTextureFromBGRABlock(texture_ref, bgra_block, text_canvas.width(), text_canvas.height());
+}
+
+void TextCanvasTexture::ClearTextCanvas() {
+ impl_->text_canvas.Clear();
+}
+
+void TextCanvasTexture::SetPenPosition(const vec2 &point){
+ impl_->pen.x = (FT_Pos)(point[0]*64);
+ impl_->pen.y = (FT_Pos)(-point[1]*64);
+}
+
+void TextCanvasTexture::AddText( const char *str, int length, const CanvasTextStyle& style, uint32_t char_limit ) {
+ TextMetrics metrics;
+ RenderText(str, length, style, metrics, TMF_DRAW, char_limit);
+}
+
+void TextCanvasTexture::AddTextMultiline( const char *str, int length, const CanvasTextStyle& style, uint32_t char_limit ) {
+ TextMetrics metrics;
+ RenderText(str, length, style, metrics, TMF_DRAW | TMF_AUTO_NEWLINE, char_limit);
+}
+
+void TextCanvasTexture::GetTextMetricsInfo( const char *str, int length, const CanvasTextStyle& style, TextMetrics &metrics, uint32_t char_limit ) {
+ RenderText(str, length, style, metrics, TMF_METRICS, char_limit);
+}
+
+static void FTMatrixFromAngle(FT_Matrix *matrix, float degrees) {
+ float angle = degrees / 180.0f * 3.1415927f;
+ matrix->xx = (FT_Fixed)( cos( angle ) * 0x10000L );
+ matrix->xy = (FT_Fixed)(-sin( angle ) * 0x10000L );
+ matrix->yx = (FT_Fixed)( sin( angle ) * 0x10000L );
+ matrix->yy = (FT_Fixed)( cos( angle ) * 0x10000L );
+}
+
+static void ClearTextMetrics(TextMetrics *metrics){
+ metrics->advance[0] = 0;
+ metrics->advance[1] = 0;
+ metrics->bounds[0] = INT_MAX; // min horz
+ metrics->bounds[1] = INT_MIN; // max horz
+ metrics->bounds[2] = INT_MAX; // min vert
+ metrics->bounds[3] = INT_MIN; // max vert
+}
+
+static void ApplyGlyphSlotToTextMetrics(TextMetrics *metrics, FT_GlyphSlot slot){
+ int glyph_bounds[4];
+ glyph_bounds[0] = metrics->advance[0] + slot->metrics.horiBearingX;
+ glyph_bounds[1] = glyph_bounds[0] + slot->metrics.width;
+ glyph_bounds[2] = metrics->advance[1] + slot->metrics.horiBearingY;
+ glyph_bounds[3] = glyph_bounds[2] + slot->metrics.height;
+ metrics->advance[0] += slot->advance.x;
+ metrics->advance[1] += slot->advance.y;
+ metrics->bounds[0] = min(metrics->bounds[0], glyph_bounds[0]);
+ metrics->bounds[1] = max(metrics->bounds[1], glyph_bounds[1]);
+ metrics->bounds[2] = min(metrics->bounds[2], glyph_bounds[2]);
+ metrics->bounds[3] = max(metrics->bounds[3], glyph_bounds[3]);
+}
+
+void TextCanvasTexture::RenderText( const char *str, int length, const CanvasTextStyle& style, TextMetrics &metrics, uint32_t mode, uint32_t char_limit ) {
+ std::vector<uint32_t> utf32_string;
+
+ try {
+ utf8::utf8to32(str,str+length, std::back_inserter(utf32_string));
+ } catch( const utf8::not_enough_room& ner ) {
+ LOGE << "Got utf8 exception \"" << ner.what() << "\" this might indicate invalid utf-8 data" << std::endl;
+ }
+
+ FontRenderer* font_renderer = FontRenderer::Instance();
+ FT_Matrix matrix;
+ FTMatrixFromAngle(&matrix, impl_->pen_rotation);
+ int orig_pen_x = impl_->pen.x;
+ int line_height = font_renderer->GetFontInfo(style.font_face_id, FontRenderer::INFO_HEIGHT);
+ int line_start = 0;
+ FT_Vector zero_vec = {0};
+ ClearTextMetrics(&metrics);
+ for(unsigned i=0; i<=utf32_string.size(); ++i){
+ if(i == utf32_string.size() || utf32_string[i] == '\n'){ // Find line endings to draw entire lines at once
+ TextMetrics line_metrics;
+ bool need_to_reset = true;
+ int next_end_id = 0;
+
+ //Create newlines when the lines are too long.
+ //This includes the entire string, not just what we want to show based on
+ //char limit
+ while( need_to_reset ) {
+ ClearTextMetrics(&line_metrics);
+ need_to_reset = false;
+ for(unsigned j=line_start; j<i; ++j) {
+ font_renderer->SetTransform( style.font_face_id, &matrix, &zero_vec );
+ FT_GlyphSlot slot = font_renderer->RenderCharacterBitmap(style.font_face_id, utf32_string[j], FontRenderer::RCB_METRIC);
+ ApplyGlyphSlotToTextMetrics(&line_metrics, slot);
+
+ if( mode & TMF_AUTO_NEWLINE ) {
+ if( line_metrics.advance[0] + impl_->pen.x + slot->metrics.width >= impl_->text_canvas.width()*64 ) {
+ for( int k = j; k > 0; k-- ) {
+ if( utf32_string[k] == ' ' ) {
+ j = k;
+ break;
+ }
+ }
+ need_to_reset = true;
+ next_end_id = j;
+ break;
+ }
+ }
+ }
+
+ if( need_to_reset ) {
+ i = next_end_id;
+ }
+ }
+
+ switch(style.alignment){
+ case CanvasTextStyle::RIGHT:
+ if(mode & TMF_DRAW){
+ impl_->pen.x = impl_->text_canvas.width()*64 - line_metrics.bounds[1];
+ }
+ metrics.advance[0] = -line_metrics.bounds[1];
+ break;
+ default:
+ LOGD << "Unhandled style.alignment " << CanvasTextStyle::GetAlignmentString(style.alignment) << std::endl;;
+ break;
+ }
+
+ for(unsigned j=line_start; j<i; ++j){
+ if(mode & TMF_DRAW){
+ font_renderer->SetTransform( style.font_face_id, &matrix, &impl_->pen );
+ } else {
+ font_renderer->SetTransform( style.font_face_id, &matrix, &zero_vec );
+ }
+ FT_GlyphSlot slot;
+ if(mode & TMF_DRAW){
+ slot = font_renderer->RenderCharacterBitmap(style.font_face_id, utf32_string[j], FontRenderer::RCB_RENDER);
+ const FT_Bitmap &bitmap = slot->bitmap;
+ if( j < char_limit )
+ {
+ impl_->text_canvas.AddBitmap(slot->bitmap_left, -slot->bitmap_top, bitmap);
+ impl_->pen.x += slot->advance.x;
+ impl_->pen.y += slot->advance.y;
+ }
+ } else {
+ slot = font_renderer->RenderCharacterBitmap(style.font_face_id, utf32_string[j], FontRenderer::RCB_METRIC);
+ }
+ ApplyGlyphSlotToTextMetrics(&metrics, slot);
+ }
+ if(i != utf32_string.size()){
+ impl_->pen.y -= line_height;
+ impl_->pen.x = orig_pen_x;
+ metrics.advance[1] += line_height;
+ metrics.advance[0] = 0;
+ }
+ line_start = i+1;
+ }
+ }
+}
+
+// Get the coordinates of the cursor after a given letter in an edit field
+void TextCanvasTexture::GetLetterPosXY(const char* str, const CanvasTextStyle &style, int letter, int coords[2]){
+ std::vector<uint32_t> utf32_string;
+ try {
+ utf8::utf8to32(str,str+strlen(str), std::back_inserter(utf32_string));
+ } catch( const utf8::not_enough_room& ner ) {
+ LOGE << "Got utf8 exception \"" << ner.what() << "\" this might indicate invalid utf-8 data" << std::endl;
+ }
+
+ FontRenderer* font_renderer = FontRenderer::Instance();
+ FT_Matrix matrix;
+ FTMatrixFromAngle(&matrix, 0.0f);
+ int line_height = font_renderer->GetFontInfo(style.font_face_id, FontRenderer::INFO_HEIGHT);
+ int line_start = 0;
+ FT_Vector zero_vec = {0};
+ TextMetrics metrics;
+ ClearTextMetrics(&metrics);
+ for(int i=0; i<=(int)utf32_string.size(); ++i){
+ if(str[i] == '\n' || i == (int)utf32_string.size()){ // Find line endings to draw entire lines at once
+ TextMetrics line_metrics;
+ ClearTextMetrics(&line_metrics);
+ for(int j=line_start; j<i; ++j){
+ font_renderer->SetTransform( style.font_face_id, &matrix, &zero_vec );
+ FT_GlyphSlot slot = font_renderer->RenderCharacterBitmap(style.font_face_id, str[j], FontRenderer::RCB_METRIC);
+ ApplyGlyphSlotToTextMetrics(&line_metrics, slot);
+ }
+ switch(style.alignment){
+ case CanvasTextStyle::RIGHT:
+ metrics.advance[0] = -line_metrics.bounds[1];
+ break;
+ default:
+ LOGD << "Unhandled style.alignment " << CanvasTextStyle::GetAlignmentString(style.alignment) << std::endl;;
+ break;
+ }
+ for(int j=line_start; j<i; ++j){
+ font_renderer->SetTransform( style.font_face_id, &matrix, &zero_vec );
+ FT_GlyphSlot slot = font_renderer->RenderCharacterBitmap(style.font_face_id, str[j], FontRenderer::RCB_METRIC);
+ if(j == letter){
+ coords[0] = metrics.advance[0]/64;
+ coords[1] = metrics.advance[1]/64;
+ }
+ ApplyGlyphSlotToTextMetrics(&metrics, slot);
+ if(j+1 == letter){
+ coords[0] = metrics.advance[0]/64;
+ coords[1] = metrics.advance[1]/64;
+ }
+ }
+ if(str[i] == '\n'){
+ metrics.advance[1] += line_height;
+ metrics.advance[0] = 0;
+ }
+ line_start = i+1;
+ }
+ }
+ switch(style.alignment){
+ case CanvasTextStyle::RIGHT:
+ coords[0] -= metrics.bounds[0]/64;
+ break;
+ default:
+ LOGD << "Unhandled style.alignment " << CanvasTextStyle::GetAlignmentString(style.alignment) << std::endl;;
+ break;
+
+ }
+}
+
+// Get the cursor index given a mouse click position (text canvas space)
+int TextCanvasTexture::GetCursorPos(const char* str, const CanvasTextStyle& style, const int coords[2], uint32_t char_limit) {
+ int best_letter = 0;
+ int best_distance = INT_MAX;
+ int length = strlen(str);
+ TextMetrics total_metrics;
+ RenderText(str, length, style, total_metrics, TMF_METRICS, char_limit);
+ FontRenderer* font_renderer = FontRenderer::Instance();
+ FT_Matrix matrix;
+ FTMatrixFromAngle(&matrix, 0.0f);
+ int line_height = font_renderer->GetFontInfo(style.font_face_id, FontRenderer::INFO_HEIGHT);
+ int click_line = coords[1] / (line_height/64);
+ int curr_line = 0;
+ int line_start = 0;
+ FT_Vector zero_vec = {0};
+ TextMetrics metrics;
+ ClearTextMetrics(&metrics);
+ for(int i=0; i<=length; ++i){
+ if(str[i] == '\n' || i == length){ // Find line endings to draw entire lines at once
+ TextMetrics line_metrics;
+ ClearTextMetrics(&line_metrics);
+ for(int j=line_start; j<i; ++j){
+ font_renderer->SetTransform( style.font_face_id, &matrix, &zero_vec );
+ FT_GlyphSlot slot = font_renderer->RenderCharacterBitmap(style.font_face_id, str[j], FontRenderer::RCB_METRIC);
+ ApplyGlyphSlotToTextMetrics(&line_metrics, slot);
+ }
+ switch(style.alignment){
+ case CanvasTextStyle::RIGHT:
+ metrics.advance[0] = -line_metrics.bounds[1];
+ break;
+ default:
+ LOGD << "Unhandled style.alignment " << CanvasTextStyle::GetAlignmentString(style.alignment) << std::endl;;
+ break;
+ }
+ for(int j=line_start; j<i; ++j){
+ font_renderer->SetTransform( style.font_face_id, &matrix, &zero_vec );
+ FT_GlyphSlot slot = font_renderer->RenderCharacterBitmap(style.font_face_id, str[j], FontRenderer::RCB_METRIC);
+ if(curr_line == click_line){
+ int pos_x = metrics.advance[0]/64;
+ switch(style.alignment){
+ case CanvasTextStyle::RIGHT:
+ pos_x -= total_metrics.bounds[0]/64;
+ break;
+ default:
+ LOGD << "Unhandled style.alignment " << CanvasTextStyle::GetAlignmentString(style.alignment) << std::endl;;
+ break;
+ }
+ int distance = abs(pos_x - coords[0]);
+ if(distance < best_distance){
+ best_letter = j;
+ best_distance = distance;
+ }
+ }
+ ApplyGlyphSlotToTextMetrics(&metrics, slot);
+ if(curr_line == click_line){
+ int pos_x = metrics.advance[0]/64;
+ switch(style.alignment){
+ case CanvasTextStyle::RIGHT:
+ pos_x -= total_metrics.bounds[0]/64;
+ break;
+ default:
+ LOGD << "Unhandled style.alignment " << CanvasTextStyle::GetAlignmentString(style.alignment) << std::endl;;
+ break;
+ }
+ int distance = abs(pos_x - coords[0]);
+ if(distance < best_distance){
+ best_letter = j+1;
+ best_distance = distance;
+ }
+ }
+ }
+ if(str[i] == '\n'){
+ metrics.advance[1] += line_height;
+ metrics.advance[0] = 0;
+ ++curr_line;
+ }
+ line_start = i+1;
+ }
+ }
+ if(click_line > curr_line){
+ best_letter = length;
+ }
+ return best_letter;
+}
+
+void TextCanvasTexture::DebugDrawBillboard(const vec3 &pos, float scale, int lifespan_int) {
+ DDLifespan lifespan = LifespanFromInt(lifespan_int);
+
+ RemoveDebugDrawBillboard();
+ debug_draw_billboard_id = DebugDraw::Instance()->AddBillboard(impl_->texture_ref, pos, scale, vec4(1.0f), kPremultiplied, lifespan);
+}
+
+void TextCanvasTexture::RemoveDebugDrawBillboard()
+{
+ if( debug_draw_billboard_id != -1 )
+ {
+ DebugDraw::Instance()->Remove(debug_draw_billboard_id);
+ debug_draw_billboard_id = -1;
+ }
+}
+
+TextCanvasTexture::TextCanvasTexture() : debug_draw_billboard_id(-1){
+ impl_ = new TextCanvasTextureImpl();
+}
+
+TextCanvasTexture::~TextCanvasTexture() {
+ RemoveDebugDrawBillboard();
+ delete impl_;
+}
+
+TextCanvasTexture::TextCanvasTexture(const TextCanvasTexture& other) {
+ *impl_ = *other.impl_;
+}
+
+void TextCanvasTexture::Reset() {
+ delete impl_;
+ impl_ = new TextCanvasTextureImpl();
+}
+
+void TextCanvasTexture::SetPenColor( int r, int g, int b, int a ) {
+ impl_->text_canvas.SetPenColor(r,g,b,a);
+}
+
+void TextCanvasTexture::SetPenRotation( float degrees ) {
+ impl_->pen_rotation = degrees;
+}
+
+TextureRef TextCanvasTexture::GetTexture() const {
+ return impl_->texture_ref;
+}
+
+int ASGetFontFaceID(const std::string& path, int pixel_height) {
+ return FontRenderer::Instance()->GetFontFaceID(path, pixel_height);
+}
+
+static void TextStyleDefaultConstructor(CanvasTextStyle *self) {
+ new(self) CanvasTextStyle();
+}
+
+static void TextStyledestructor(void *memory) {
+ ((CanvasTextStyle*)memory)->~CanvasTextStyle();
+}
+
+static void TextStyleAlignment(int alignment, CanvasTextStyle* self) {
+ self->alignment = (CanvasTextStyle::Alignment)alignment;
+}
+
+static void ASAddText(TextCanvasTexture *tct, const std::string& str, const CanvasTextStyle &style, uint32_t char_count_output) {
+ tct->AddText(str.c_str(), str.length(), style, char_count_output);
+}
+static void ASAddTextMultiline(TextCanvasTexture *tct, const std::string& str, const CanvasTextStyle &style, uint32_t char_count_output) {
+ tct->AddTextMultiline(str.c_str(), str.length(), style, char_count_output);
+}
+
+static void ASGetTextMetricsInfo(TextCanvasTexture *tct, const std::string& str, const CanvasTextStyle &style, TextMetrics &metrics, uint32_t char_count_output) {
+ tct->GetTextMetricsInfo(str.c_str(), str.length(), style, metrics, char_count_output);
+}
+
+void ASTextContext::ASDrawTextAtlas(const std::string &path, int pixel_height, int flags, const std::string &txt, int x, int y, vec4 color) {
+ ASDrawTextAtlas2(path,pixel_height,flags,txt,x,y,color,UINT32MAX);
+}
+
+void ASTextContext::ASDrawTextAtlas2(const std::string &path, int pixel_height, int flags, const std::string &txt, int x, int y, vec4 color, uint32_t char_limit) {
+ if(!text_atlas_renderer_setup){
+ text_atlas_renderer.Init();
+ text_atlas_renderer_setup = true;
+ }
+ if(path.length() >= CachedTextAtlas::kPathSize){
+ DisplayError("Error", "Font path is too long");
+ } else {
+ int found = -1;
+ for(int i=0; i<atlases.num_atlases; ++i){
+ const CachedTextAtlas& cached = atlases.cached[i];
+ if(strcmp(cached.path, path.c_str()) == 0 &&
+ pixel_height == cached.pixel_height &&
+ flags == cached.flags)
+ {
+ found = i;
+ }
+ }
+ if(found == -1){
+ if(atlases.num_atlases >= CachedTextAtlases::kMaxAtlases){
+ DisplayError("Error", "Too many cached text atlases");
+ } else {
+ found = atlases.num_atlases++;
+ CachedTextAtlas* new_atlas = &atlases.cached[found];
+ strncpy(new_atlas->path, path.c_str(), CachedTextAtlas::kPathSize);
+ new_atlas->pixel_height = pixel_height;
+ new_atlas->flags = flags;
+ new_atlas->atlas.Create(path.c_str(), pixel_height,
+ font_renderer, flags);
+ }
+ }
+ if(found != -1){
+ TextAtlas* atlas = &atlases.cached[found].atlas;
+ int pos[] = {x, y};
+ text_atlas_renderer.num_characters = 0;
+ text_atlas_renderer.AddText(atlas, txt.c_str(), pos, font_renderer, char_limit);
+ text_atlas_renderer.Draw(atlas, graphics, 0, color);
+ }
+ }
+}
+
+TextMetrics ASTextContext::ASGetTextAtlasMetrics(const std::string &path, int pixel_height, int flags, const std::string &txt ) {
+ return ASGetTextAtlasMetrics2(path,pixel_height,flags,txt,UINT32MAX);
+}
+
+TextMetrics ASTextContext::ASGetTextAtlasMetrics2(const std::string &path, int pixel_height, int flags, const std::string &txt, uint32_t char_limit ) {
+ TextMetrics metrics;
+
+ if(!text_atlas_renderer_setup){
+ text_atlas_renderer.Init();
+ text_atlas_renderer_setup = true;
+ }
+ if(path.length() >= CachedTextAtlas::kPathSize){
+ DisplayError("Error", "Font path is too long");
+ } else {
+ int found = -1;
+ for(int i=0; i<atlases.num_atlases; ++i){
+ CachedTextAtlas& cached = atlases.cached[i];
+ if(strcmp(cached.path, path.c_str()) == 0 &&
+ pixel_height == cached.pixel_height &&
+ flags == cached.flags)
+ {
+ found = i;
+ }
+ }
+ if(found == -1){
+ if(atlases.num_atlases >= CachedTextAtlases::kMaxAtlases){
+ DisplayError("Error", "Too many cached text atlases");
+ } else {
+ found = atlases.num_atlases++;
+ CachedTextAtlas* new_atlas = &atlases.cached[found];
+ strncpy(new_atlas->path, path.c_str(), CachedTextAtlas::kPathSize);
+ new_atlas->pixel_height = pixel_height;
+ new_atlas->flags = flags;
+ new_atlas->atlas.Create(path.c_str(), pixel_height,
+ font_renderer, flags);
+ }
+ }
+ if(found != -1){
+ TextAtlas* atlas = &atlases.cached[found].atlas;
+
+ text_atlas_renderer.num_characters = 0;
+
+ metrics = text_atlas_renderer.GetMetrics(atlas, txt.c_str(), font_renderer, char_limit);
+ }
+ }
+
+ return metrics;
+
+}
+
+// TODO: this should not be a global variable right here
+ASTextContext g_as_text_context;
+
+void DisposeTextAtlases() {
+
+ for(int i=0; i<g_as_text_context.atlases.num_atlases; ++i){
+ g_as_text_context.atlases.cached[i].atlas.Dispose();
+ }
+
+ // Reset the cached atlas counter
+ g_as_text_context.atlases.num_atlases = 0;
+
+}
+
+void AttachTextCanvasTextureToASContext( ASContext* ctx ) {
+ g_as_text_context.font_renderer = FontRenderer::Instance();
+ g_as_text_context.graphics = Graphics::Instance();
+ ctx->RegisterObjectType("TextStyle", sizeof(CanvasTextStyle), asOBJ_VALUE | asOBJ_APP_CLASS_CD);
+ ctx->RegisterObjectProperty("TextStyle", "int font_face_id", offsetof(CanvasTextStyle, font_face_id));
+ ctx->RegisterObjectBehaviour("TextStyle", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(TextStyleDefaultConstructor), asCALL_CDECL_OBJLAST);
+ ctx->RegisterObjectBehaviour("TextStyle", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(TextStyledestructor), asCALL_CDECL_OBJLAST);
+ ctx->RegisterObjectMethod("TextStyle", "void SetAlignment(int i)", asFUNCTION(TextStyleAlignment), asCALL_CDECL_OBJLAST);
+ ctx->DocsCloseBrace();
+ ctx->RegisterObjectType("TextMetrics", sizeof(TextMetrics), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS_CA);
+ ctx->RegisterObjectProperty("TextMetrics", "int advance_x", asOFFSET(TextMetrics, advance));
+ ctx->RegisterObjectProperty("TextMetrics", "int advance_y", asOFFSET(TextMetrics, advance)+sizeof(int));
+ ctx->RegisterObjectProperty("TextMetrics", "int bounds_x", asOFFSET(TextMetrics, bounds)+(2*sizeof(int)));
+ ctx->RegisterObjectProperty("TextMetrics", "int bounds_y", asOFFSET(TextMetrics, bounds)+(3*sizeof(int)));
+ ctx->RegisterObjectProperty("TextMetrics", "float ascenderRatio", asOFFSET(TextMetrics, ascenderRatio));
+
+ ctx->DocsCloseBrace();
+ ctx->RegisterObjectType("TextCanvasTexture", 0, asOBJ_REF | asOBJ_NOCOUNT);
+ ctx->RegisterObjectMethod("TextCanvasTexture", "void Create(int width, int height)", asMETHOD(TextCanvasTexture, Create), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("TextCanvasTexture", "void ClearTextCanvas()", asMETHOD(TextCanvasTexture, ClearTextCanvas), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("TextCanvasTexture", "void UploadTextCanvasToTexture()", asMETHOD(TextCanvasTexture, UploadTextCanvasToTexture), asCALL_THISCALL);
+
+ ctx->RegisterObjectMethod("TextCanvasTexture", "void DebugDrawBillboard(const vec3 &in pos, float scale, int lifespan)", asMETHOD(TextCanvasTexture, DebugDrawBillboard), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("TextCanvasTexture", "void AddText(const string &in, const TextStyle &in, uint char_limit)", asFUNCTION(ASAddText), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("TextCanvasTexture", "void AddTextMultiline(const string &in, const TextStyle &in, uint char_limit)", asFUNCTION(ASAddTextMultiline), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("TextCanvasTexture", "void GetTextMetrics(const string &in, const TextStyle &in, TextMetrics &out, uint char_limit)", asFUNCTION(ASGetTextMetricsInfo), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("TextCanvasTexture", "void SetPenPosition(const vec2 &in)", asMETHOD(TextCanvasTexture, SetPenPosition), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("TextCanvasTexture", "void SetPenColor(int r, int g, int b, int a)", asMETHOD(TextCanvasTexture, SetPenColor), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("TextCanvasTexture", "void SetPenRotation(float rotation)", asMETHOD(TextCanvasTexture, SetPenRotation), asCALL_THISCALL);
+ ctx->DocsCloseBrace();
+ ctx->RegisterEnum("TextAtlasFlags");
+ ctx->RegisterEnumValue("TextAtlasFlags", "kSmallLowercase", TextAtlas::kSmallLowercase);
+ ctx->DocsCloseBrace();
+ ctx->RegisterGlobalFunction("int GetFontFaceID(const string &in path, int pixel_height)", asFUNCTION(ASGetFontFaceID), asCALL_CDECL);
+ ctx->RegisterGlobalFunction("void DisposeTextAtlases()", asFUNCTION(DisposeTextAtlases), asCALL_CDECL);
+
+ ctx->RegisterGlobalFunctionThis("void DrawTextAtlas(const string &in path, int pixel_height, int flags, const string &in txt, int x, int y, vec4 color)",
+ asMETHOD(ASTextContext, ASDrawTextAtlas), asCALL_THISCALL_ASGLOBAL, &g_as_text_context);
+
+ ctx->RegisterGlobalFunctionThis("void DrawTextAtlas2(const string &in path, int pixel_height, int flags, const string &in txt, int x, int y, vec4 color, uint char_limit)",
+ asMETHOD(ASTextContext, ASDrawTextAtlas2), asCALL_THISCALL_ASGLOBAL, &g_as_text_context);
+
+ ctx->RegisterGlobalFunctionThis("TextMetrics GetTextAtlasMetrics(const string &in path, int pixel_height, int flags, const string &in txt )",
+ asMETHOD(ASTextContext, ASGetTextAtlasMetrics), asCALL_THISCALL_ASGLOBAL, &g_as_text_context);
+
+ ctx->RegisterGlobalFunctionThis("TextMetrics GetTextAtlasMetrics2(const string &in path, int pixel_height, int flags, const string &in txt, uint char_limit )",
+ asMETHOD(ASTextContext, ASGetTextAtlasMetrics2), asCALL_THISCALL_ASGLOBAL, &g_as_text_context);
+
+}
+
+bool TextAtlas::Pack(unsigned char* pixels, int atlas_dims[2],
+ int font_face_id, int lowercase_font_face_id,
+ FontRenderer* font_renderer, std::vector<CharacterInfo>& alphabet)
+{
+ PROFILER_ZONE(g_profiler_ctx, "TextAtlas::Pack");
+
+ for(int i=0, len=atlas_dims[0] * atlas_dims[1]; i<len; ++i){
+ pixels[i] = 0;
+ }
+ int draw[] = {0,0};
+ int max_height = 0;
+
+ std::vector<CharacterInfo>::iterator character = alphabet.begin();
+
+ for(; character != alphabet.end(); character++){
+ bool lowercase = (character->lowercase && lowercase_font_face_id != -1);
+ FT_GlyphSlot slot =
+ font_renderer->RenderCharacterBitmap(
+ lowercase ? lowercase_font_face_id : font_face_id,
+ character->codepoint, FontRenderer::RCB_RENDER);
+ FT_Bitmap* bitmap = &slot->bitmap;
+ if(character->width + draw[0] > atlas_dims[0]){
+ draw[0] = 0;
+ draw[1] += max_height;
+ max_height = 0;
+ }
+ character->pos[0] = draw[0];
+ character->pos[1] = draw[1];
+ max_height = max(max_height, character->height);
+ for(int x=0; x < character->width; ++x){
+ for(int y=0; y < character->height; ++y){
+ if(draw[0] + x < atlas_dims[0] && draw[1] + y < atlas_dims[1]) {
+ pixels[(draw[1]+y) * atlas_dims[0] + draw[0]+x] = bitmap->buffer[y*character->width+x];
+ } else {
+ return false;
+ }
+ }
+ }
+ draw[0] += character->width;
+ }
+ return true;
+}
+
+void TextAtlas::Create(const char* path, int _pixel_height, FontRenderer* font_renderer, int flags) {
+ PROFILER_ZONE(g_profiler_ctx, "TextAtlas::Create");
+
+ int font_face_id = font_renderer->GetFontFaceID(path, _pixel_height);
+
+ // For fonts that only have uppercase letters, we might want to use 'lowercase' letters that are just
+ // the uppercase ones at a smaller size
+ int lowercase_font_face_id = -1;
+ if(flags & kSmallLowercase){
+ lowercase_font_face_id = font_renderer->GetFontFaceID(path, _pixel_height * 2/3);
+ }
+
+ // Get metrics for each letter
+ std::vector<CharacterInfo>::iterator character = alphabet.begin();
+ for(; character != alphabet.end(); character++){
+ bool lowercase = (character->lowercase && lowercase_font_face_id != -1);
+ FT_GlyphSlot slot = font_renderer->RenderCharacterBitmap(
+ lowercase ? lowercase_font_face_id : font_face_id,
+ character->codepoint, FontRenderer::RCB_METRIC);
+ //FT_Bitmap* bitmap = &slot->bitmap;
+ character->width = slot->metrics.width / 64;
+ character->height = slot->metrics.height / 64;
+ character->advance_x = slot->metrics.horiAdvance / 64;
+ character->bearing[0] = slot->metrics.horiBearingX / 64;
+ character->bearing[1] = slot->metrics.horiBearingY / 64;
+ }
+
+ // Pack all characters into the smallest possible atlas
+ atlas_dims[0] = 32;
+ atlas_dims[1] = 32;
+ unsigned char* pixels = (unsigned char*)OG_MALLOC(atlas_dims[0] * atlas_dims[1]);
+ while(!Pack(pixels, atlas_dims, font_face_id, lowercase_font_face_id, font_renderer, alphabet)){
+ OG_FREE(pixels);
+ atlas_dims[0] *= 2;
+ atlas_dims[1] *= 2;
+ pixels = (unsigned char*)OG_MALLOC(atlas_dims[0] * atlas_dims[1]);
+ }
+
+ // Prepare atlas to send to GPU as a texture
+ unsigned char* atlas_export;
+ atlas_export = (unsigned char*)OG_MALLOC(atlas_dims[0] * atlas_dims[1] * 4);
+ for(int i=0, len=atlas_dims[0] * atlas_dims[1] * 4; i<len; i+=4){
+ for(int j=0; j<4; ++j){
+ atlas_export[i+j] = 255;
+ }
+ float pixel = pixels[i/4] / 255.0f;
+ pixel = (float)pow(pixel, 1.0/2.2); // Convert from linear to sRGB
+ atlas_export[i+3] = (unsigned char)(pixel*255.0f);
+ }
+
+ // TODO: do this using the Textures class instead of bare OpenGL calls
+ GLuint gl_tex;
+ glGenTextures(1, &gl_tex);
+ tex = gl_tex;
+ glBindTexture(GL_TEXTURE_2D, tex);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
+ atlas_dims[0], atlas_dims[1], 0, GL_RGBA,
+ GL_UNSIGNED_BYTE, atlas_export);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ // Export the atlas for debug purposes?
+ static const bool kSaveAtlas = false;
+ if(kSaveAtlas){
+ ImageExport::SavePNG("text_atlas_test.png", atlas_export, atlas_dims[0], atlas_dims[1], 0);
+ }
+
+ OG_FREE(atlas_export);
+ OG_FREE(pixels);
+
+ pixel_height = _pixel_height;
+}
+
+void TextAtlas::Dispose() {
+ if(tex != -1){
+ const GLuint gl_tex = tex;
+ glDeleteTextures(1, &gl_tex);
+ tex = -1;
+ }
+}
+
+TextAtlas::TextAtlas():
+ tex(-1)
+{
+ static const unsigned int kAsciiFirst = 0x20;
+ static const unsigned int kAsciiLast = 0x7E;
+ static const unsigned int kLatinBase2First = 0xA1;
+ static const unsigned int kLatinBase2Last = 0xFF;
+
+ for( uint32_t i = kAsciiFirst; i <= kAsciiLast; i++ ) {
+ CharacterInfo character;
+ character.codepoint = i;
+
+ //Unicode and ascii lower case range
+ if( i >= 0x61 && i <= 0x7A ) {
+ character.lowercase = true;
+ } else {
+ character.lowercase = false;
+ }
+ alphabet.push_back( character );
+ }
+
+ for( uint32_t i = kLatinBase2First; i <= kLatinBase2Last; i++ ) {
+ CharacterInfo character;
+ character.codepoint = i;
+ //Lower case unicode latin base ranges
+ if( (i >= 0xDF && i <= 0xF6) || ( i > 0xF8 && i <= 0xFF ) ) {
+ character.lowercase = true;
+ } else {
+ character.lowercase = false;
+ }
+ alphabet.push_back( character );
+ }
+}
+
+TextAtlas::~TextAtlas() {
+ LOG_ASSERT(tex == -1); // We should already have disposed of the text atlas GL resources
+}
+
+void TextAtlasRenderer::Init() {
+ CHECK_GL_ERROR();
+ num_characters = 0;
+
+ CHECK_GL_ERROR();
+ Shaders* shaders = Shaders::Instance();
+ shader_id = shaders->returnProgram("simple_2d #TEXTURE");
+ shaders->createProgram(shader_id);
+ shader_attrib_vert_coord = shaders->returnShaderAttrib("vert_coord", shader_id);
+ shader_attrib_tex_coord = shaders->returnShaderAttrib("tex_coord", shader_id);
+ uniform_mvp_mat = shaders->returnShaderVariable("mvp_mat", shader_id);
+ uniform_color = shaders->returnShaderVariable("color", shader_id);
+}
+
+void TextAtlasRenderer::Dispose() {
+ index_vbo.Dispose();
+ vert_vbo.Dispose();
+}
+
+void TextAtlasRenderer::AddText(TextAtlas* atlas, const char* text, int pos[2], FontRenderer* font_renderer, uint32_t char_output_limit ) {
+ PROFILER_ZONE(g_profiler_ctx, "TextAtlas::AddText");
+ //int line_height = font_renderer->GetFontInfo(0, FontRenderer::INFO_HEIGHT) / 64; // This is the unadjusted size -- MjB
+
+ std::vector<uint32_t> utf32_string;
+
+ try {
+ utf8::utf8to32(text,text+strlen(text), std::back_inserter(utf32_string));
+ } catch( const utf8::not_enough_room& ner ) {
+ LOGE << "Got utf8 exception \"" << ner.what() << "\" this might indicate invalid utf-8 data, \"" << text << "\"" << std::endl;
+ }
+
+ int draw_pos[2];
+ memcpy(draw_pos, pos, sizeof(draw_pos));
+ int last_c = -1;
+ for(size_t i=0, len=utf32_string.size(); i<len && i<char_output_limit; ++i){
+ if(num_characters < kMaxCharacters){
+ uint32_t c = utf32_string[i];
+ if(c == '\n'){
+ draw_pos[1] += atlas->pixel_height;
+ draw_pos[0] = pos[0];
+ }
+
+ std::vector<CharacterInfo>::iterator character;
+ for( character = atlas->alphabet.begin();
+ character != atlas->alphabet.end();
+ character++ ) {
+ if( (int64_t)character->codepoint == c )
+ break;
+ }
+
+ if(character != atlas->alphabet.end()){
+ int kerning_x = 0;
+ if(last_c != -1){
+ FT_Vector vec;
+ font_renderer->GetKerning(0, last_c, c, &vec);
+ kerning_x = vec.x / 64;
+ }
+ draw_pos[0] += kerning_x;
+
+ int vert_index = num_characters * 4 * TextAtlasRenderer::kFloatsPerVert;
+ verts[vert_index++] = (float)draw_pos[0] + character->bearing[0];
+ verts[vert_index++] = (float)draw_pos[1] - character->bearing[1];
+ verts[vert_index++] = (character->pos[0]) / (float)atlas->atlas_dims[0];
+ verts[vert_index++] = (character->pos[1]) / (float)atlas->atlas_dims[1];
+
+ verts[vert_index++] = (float)draw_pos[0] + character->width + character->bearing[0];
+ verts[vert_index++] = (float)draw_pos[1] - character->bearing[1];
+ verts[vert_index++] = (character->pos[0] + character->width) / (float)atlas->atlas_dims[0];
+ verts[vert_index++] = (character->pos[1]) / (float)atlas->atlas_dims[1];
+
+ verts[vert_index++] = (float)draw_pos[0] + character->width + character->bearing[0];
+ verts[vert_index++] = (float)draw_pos[1] + character->height - character->bearing[1];
+ verts[vert_index++] = (character->pos[0] + character->width) / (float)atlas->atlas_dims[0];
+ verts[vert_index++] = (character->pos[1] + character->height) / (float)atlas->atlas_dims[1];
+
+ verts[vert_index++] = (float)draw_pos[0] + character->bearing[0];
+ verts[vert_index++] = (float)draw_pos[1] + character->height - character->bearing[1];
+ verts[vert_index++] = (character->pos[0]) / (float)atlas->atlas_dims[0];
+ verts[vert_index++] = (character->pos[1] + character->height) / (float)atlas->atlas_dims[1];
+
+ int indices_index = num_characters * 6;
+ int vert_start = num_characters * 4;
+ indices[indices_index++] = vert_start + 0;
+ indices[indices_index++] = vert_start + 1;
+ indices[indices_index++] = vert_start + 2;
+ indices[indices_index++] = vert_start + 0;
+ indices[indices_index++] = vert_start + 2;
+ indices[indices_index++] = vert_start + 3;
+
+ ++num_characters;
+ draw_pos[0] += character->advance_x;
+ last_c = c;
+ }
+ }
+ }
+}
+
+TextMetrics TextAtlasRenderer::GetMetrics( TextAtlas* atlas, const char* text, FontRenderer* font_renderer, uint32_t char_limit ) {
+ PROFILER_ZONE(g_profiler_ctx, "TextAtlas::GetMetrics");
+
+ TextMetrics metrics;
+ metrics.advance[0] = 0;
+ metrics.advance[1] = 0;
+
+ metrics.bounds[0] = 0;
+ metrics.bounds[1] = 0;
+ metrics.bounds[2] = 0;
+ metrics.bounds[3] = atlas->pixel_height;
+
+ int last_c = -1;
+ for(int i=0, len=strlen(text); i<len; ++i){
+
+ char c = text[i];
+ if(c == '\n') {
+
+ metrics.bounds[2] = max( metrics.bounds[2], metrics.advance[0] );
+ metrics.bounds[3] += atlas->pixel_height;
+
+ metrics.advance[0] = 0;
+ metrics.advance[1] += atlas->pixel_height;
+
+ }
+
+ std::vector<CharacterInfo>::iterator character;
+ for( character = atlas->alphabet.begin();
+ character != atlas->alphabet.end();
+ character++ ) {
+ if( (int64_t)character->codepoint == c )
+ break;
+ }
+
+ if(character != atlas->alphabet.end()){
+
+ int kerning_x = 0;
+ if(last_c != -1) {
+ FT_Vector vec;
+ font_renderer->GetKerning(0, last_c, c, &vec);
+ kerning_x = vec.x / 64;
+ }
+
+ metrics.advance[0] += kerning_x;
+
+ metrics.advance[0] += character->advance_x;
+
+ last_c = c;
+ }
+ }
+
+ metrics.bounds[2] = max( metrics.bounds[2], metrics.advance[0] );
+
+ int line_height = font_renderer->GetFontInfo(0, FontRenderer::INFO_HEIGHT) / 64;
+ int ascender_height = font_renderer->GetFontInfo(0, FontRenderer::INFO_ASCENDER) / 64;
+
+ metrics.ascenderRatio = ((float)abs(ascender_height)/(float)abs(line_height));
+
+ return metrics;
+
+}
+
+void TextAtlasRenderer::Draw(TextAtlas* atlas, Graphics* graphics, char flags, const vec4& color) {
+ if(num_characters == 0){
+ return;
+ }
+ Shaders* shaders = Shaders::Instance();
+
+ PROFILER_GPU_ZONE(g_profiler_ctx, "TextAtlasRenderer::Draw");
+ CHECK_GL_ERROR();
+
+ vert_vbo.Fill(sizeof(verts)*num_characters/kMaxCharacters, verts);
+ index_vbo.Fill(sizeof(indices)*num_characters/kMaxCharacters, indices);
+
+ CHECK_GL_ERROR();
+ GLState gl_state;
+ gl_state.blend = true;
+ gl_state.cull_face = false;
+ gl_state.depth_write = false;
+ gl_state.depth_test = false;
+ CHECK_GL_ERROR();
+
+ graphics->setGLState(gl_state);
+ CHECK_GL_ERROR();
+
+ shaders->setProgram(shader_id);
+ CHECK_GL_ERROR();
+ glm::mat4 proj_mat;
+ proj_mat = glm::ortho(0.0f, (float)graphics->window_dims[0], (float)graphics->window_dims[1], 0.0f);
+
+ CHECK_GL_ERROR();
+ graphics->EnableVertexAttribArray(shader_attrib_vert_coord);
+ graphics->EnableVertexAttribArray(shader_attrib_tex_coord);
+ vert_vbo.Bind();
+ index_vbo.Bind();
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, atlas->tex);
+ glVertexAttribPointer(shader_attrib_vert_coord, 2, GL_FLOAT, false, 4*sizeof(GLfloat), (const void*)vert_vbo.offset());
+ glVertexAttribPointer(shader_attrib_tex_coord, 2, GL_FLOAT, false, 4*sizeof(GLfloat), (const void*)(vert_vbo.offset()+sizeof(GL_FLOAT)*2));
+ CHECK_GL_ERROR();
+
+ int num_indices = sizeof(indices)*num_characters/kMaxCharacters/sizeof(float);
+
+ if(flags & kTextShadow){
+ glm::mat4 offset_mat;
+ offset_mat[3][0] += 1.0f;
+ offset_mat[3][1] += 1.0f;
+ glm::mat4 mat = proj_mat * offset_mat;
+ glUniformMatrix4fv(uniform_mvp_mat, 1, false, (GLfloat*)&mat);
+ glUniform4f(uniform_color, 0.0f, 0.0f, 0.0f, color.a() / 2.0f );
+ graphics->DrawElements(GL_TRIANGLES, num_indices, GL_UNSIGNED_INT, (const void*)index_vbo.offset());
+ }
+
+ glUniformMatrix4fv(uniform_mvp_mat, 1, false, (GLfloat*)&proj_mat);
+ glUniform4fv(uniform_color, 1, &color[0]);
+ graphics->DrawElements(GL_TRIANGLES, num_indices, GL_UNSIGNED_INT, (const void*)index_vbo.offset());
+
+ graphics->BindElementVBO(0);
+ graphics->BindArrayVBO(0);
+ graphics->ResetVertexAttribArrays();
+ CHECK_GL_ERROR();
+}
+
+TextAtlasRenderer::TextAtlasRenderer():
+ vert_vbo( 65536, kVBOFloat | kVBOStream ),
+ index_vbo( 65536/4 , kVBOElement | kVBOStream )
+{
+
+}
diff --git a/Source/Graphics/text.h b/Source/Graphics/text.h
new file mode 100644
index 00000000..3ab96e95
--- /dev/null
+++ b/Source/Graphics/text.h
@@ -0,0 +1,194 @@
+//-----------------------------------------------------------------------------
+// Name: text.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Graphics/textureref.h>
+#include <Graphics/vbocontainer.h>
+#include <Graphics/vboringcontainer.h>
+#include <Graphics/font_renderer.h>
+
+#include <Math/vec3.h>
+#include <Math/vec4.h>
+
+#include <Asset/Asset/texture.h>
+
+#include <vector>
+
+struct TextCanvasTextureImpl;
+class vec2;
+class Graphics;
+
+struct CanvasTextStyle {
+ enum Alignment {LEFT = 1,CENTER = 2,RIGHT = 3};
+ static inline const char* GetAlignmentString( Alignment a )
+ {
+ switch(a)
+ {
+ case LEFT: return "LEFT";
+ case CENTER: return "CENTER";
+ case RIGHT: return "RIGHT";
+ default: return "(unknown alignment)";
+ }
+ }
+ int font_face_id;
+ Alignment alignment;
+ CanvasTextStyle():
+ font_face_id(-1),
+ alignment(LEFT)
+ {}
+};
+
+struct TextMetrics {
+ int advance[2]; // cursor position in points (64/pixel)
+ int bounds[4]; // bounding box of text in points (64/pixel)
+ float ascenderRatio;
+};
+
+// A texture on which text can be rendered
+class TextCanvasTexture {
+public:
+ void Create(int width, int height);
+ void ClearTextCanvas();
+ void UploadTextCanvasToTexture();
+ void AddText( const char *str, int length, const CanvasTextStyle& style, uint32_t char_output_limit );
+ void AddTextMultiline( const char *str, int length, const CanvasTextStyle& style, uint32_t char_limit );
+ void DebugDrawBillboard(const vec3 &pos, float scale, int lifespan);
+ void RemoveDebugDrawBillboard();
+ void Reset();
+ // (0,0) is the top-left corner of the text canvas
+ // The pen position is at the bottom-left corner of the text
+ void SetPenPosition(const vec2 &point);
+ void SetPenColor(int r, int g, int b, int a);
+ void SetPenRotation(float degrees);
+ static void GetLetterPosXY(const char* str, const CanvasTextStyle &style, int letter, int coords[2]);
+ int GetCursorPos(const char* str, const CanvasTextStyle& style, const int coords[2], uint32_t char_limit);
+ TextCanvasTexture();
+ ~TextCanvasTexture();
+ TextCanvasTexture(const TextCanvasTexture& other);
+ TextureRef GetTexture() const;
+ void GetTextMetricsInfo( const char *str, int length, const CanvasTextStyle& style, TextMetrics &metrics, uint32_t char_limit );
+private:
+ enum TextModeFlags {TMF_METRICS = 1, TMF_DRAW = 2, TMF_AUTO_NEWLINE = 4};
+ void RenderText( const char *str, int length, const CanvasTextStyle& style, TextMetrics &metrics, uint32_t mode, uint32_t char_limit );
+ TextCanvasTextureImpl *impl_;
+
+ int debug_draw_billboard_id;
+};
+
+struct CharacterInfo {
+ uint32_t codepoint;
+ bool lowercase;
+ int width;
+ int height;
+ int advance_x;
+ int pos[2];
+ int bearing[2];
+};
+
+// A texture containing a list of common latin characters, including ascii
+class TextAtlas {
+public:
+ void Create(const char* path, int pixel_height, FontRenderer* font_renderer, int flags);
+ void Dispose();
+ TextAtlas();
+ ~TextAtlas();
+ enum Flags {
+ kSmallLowercase = 1 << 0
+ };
+ int atlas_dims[2];
+ std::vector<CharacterInfo> alphabet;
+ static bool Pack(unsigned char* pixels, int atlas_dims[2],
+ int font_face_id, int lowercase_font_face_id,
+ FontRenderer* font_renderer, std::vector<CharacterInfo>& alphabet);
+ int tex;
+ int pixel_height;
+};
+
+struct CachedTextAtlas {
+ static const unsigned kPathSize = 512;
+ char path[kPathSize];
+ int pixel_height;
+ int flags;
+ TextAtlas atlas;
+
+};
+
+struct CachedTextAtlases {
+ static const int kMaxAtlases = 32;
+ int num_atlases;
+ CachedTextAtlas cached[kMaxAtlases];
+ CachedTextAtlases():num_atlases(0) {}
+};
+
+class TextAtlasRenderer {
+public:
+ void Init();
+ void Dispose();
+ void AddText(TextAtlas* text_atlas, const char* text, int pos[2], FontRenderer* font_renderer, uint32_t char_output_limit);
+ TextMetrics GetMetrics( TextAtlas* text_atlas, const char* text, FontRenderer* font_renderer, uint32_t char_output_limit);
+ void Draw(TextAtlas* atlas, Graphics* graphics, char flags, const vec4& color);
+ enum TextFlags {
+ kTextShadow = 1
+ };
+ TextAtlasRenderer();
+
+ int num_characters;
+ static const int kFloatsPerVert = 4;
+ static const int kMaxCharacters = 1024;
+ unsigned indices[kMaxCharacters * 6];
+ float verts[kMaxCharacters * 4 * kFloatsPerVert]; //2v2t
+ VBORingContainer vert_vbo;
+ VBORingContainer index_vbo;
+private:
+ int shader_id;
+ int shader_attrib_vert_coord;
+ int shader_attrib_tex_coord;
+ int uniform_mvp_mat;
+ int uniform_color;
+};
+
+class ASContext;
+void AttachTextCanvasTextureToASContext(ASContext* ctx);
+void DisposeTextAtlases();
+void TestTextAtlas();
+
+enum {
+ kTextAtlasMono,
+ kTextAtlasDynamic,
+ kNumTextAtlas
+};
+
+struct ASTextContext {
+ FontRenderer* font_renderer;
+ Graphics* graphics;
+ CachedTextAtlases atlases;
+ TextAtlasRenderer text_atlas_renderer;
+ bool text_atlas_renderer_setup;
+ ASTextContext():text_atlas_renderer_setup(false){}
+
+ void ASDrawTextAtlas(const std::string &path, int pixel_height, int flags, const std::string &txt, int x, int y, vec4 color);
+ void ASDrawTextAtlas2(const std::string &path, int pixel_height, int flags, const std::string &txt, int x, int y, vec4 color, uint32_t char_limit);
+ TextMetrics ASGetTextAtlasMetrics(const std::string &path, int pixel_height, int flags, const std::string &txt );
+ TextMetrics ASGetTextAtlasMetrics2(const std::string &path, int pixel_height, int flags, const std::string &txt, uint32_t char_limit );
+};
diff --git a/Source/Graphics/textureatlas.cpp b/Source/Graphics/textureatlas.cpp
new file mode 100644
index 00000000..2318fc83
--- /dev/null
+++ b/Source/Graphics/textureatlas.cpp
@@ -0,0 +1,119 @@
+//-----------------------------------------------------------------------------
+// Name: textureatlas.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "textureatlas.h"
+
+#include <Graphics/graphics.h>
+#include <Graphics/shaders.h>
+
+#include <Math/ivec2math.h>
+#include <Logging/logdata.h>
+#include <Internal/profiler.h>
+#include <Utility/stacktrace.h>
+#include <Main/engine.h>
+
+#include <algorithm>
+
+TextureAtlas::TextureAtlas( AtlasNodeTree& atlasNodeTree, TextureData::ColorSpace colorSpace )
+{
+ PROFILER_ENTER(g_profiler_ctx, "Getting dimensions");
+ this->dim = atlasNodeTree.GetNodeRoot()->dim;
+ LOGI << "Creating a decal texture atlas with dimensions " << dim << std::endl;
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ //atlas_texture = Textures::Instance()->makeTextureColor( dim[0], dim[1], GL_RGBA, GL_RGBA, 1,0,0,1, true );
+ //atlas_texture = Textures::Instance()->makeTextureTestPattern( 128, 128, GL_RGBA, GL_RGBA, true );
+ PROFILER_ENTER(g_profiler_ctx, "Making texture");
+ GLenum format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+ if (colorSpace == TextureData::sRGB) {
+ format = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
+ }
+ Textures* textures = Textures::Instance();
+ atlas_texture = textures->makeTexture(dim[0], dim[1], format, format, true);
+ textures->SetTextureName(atlas_texture, "Atlas Texture");
+ PROFILER_LEAVE(g_profiler_ctx);
+}
+
+TextureAtlasRef TextureAtlas::SetTextureToNode(const std::string& rel_path, AtlasNodeTree::AtlasNodeRef ref )
+{
+ TextureAssetRef tex = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>( rel_path );
+
+ int reduction_factor = Graphics::Instance()->config_.texture_reduction_factor();
+ ivec2 texdim = ivec2(Textures::Instance()->getWidth(tex->GetTextureRef()),Textures::Instance()->getHeight(tex->GetTextureRef())) / reduction_factor;
+
+
+ TextureAtlasRef ret;
+
+ ret.source = this;
+ ret.pos = ref.node->pos;
+
+ //I supect that in fact the node dimensions are used when rendering, so the size has to be the same for
+ //textures to render correctly
+ if( texdim == ref.node->dim || (texdim[0]<= ref.node->dim[0] && texdim[1] <= ref.node->dim[1]) )
+ {
+ dirtyNodes.push(std::pair<std::string,AtlasNodeTree::AtlasNode*>(rel_path,ref.node));
+
+ ret.dim = texdim;
+ }
+ else
+ {
+ LOGE << "Texture is bigger than node, this prevents us from rendering the texture to the node because the node size is used as the texture size when rendering. Either make the texture bigger, or make the texture atlas have smaller minimum size." << std::endl;
+ LOGE << "What will be rendered is unset memory as this node will be allocated but not written to." << std::endl;
+ LOGE << "Texture: " << rel_path << std::endl;
+ LOGE << "texdim: " << texdim << " ref.node->dim: " << ref.node->dim << std::endl;
+ //LOGE << GenerateStacktrace() << std::endl;
+
+ ret.dim = ref.node->dim;
+ }
+
+ vec2 texel_size = vec2(1.0f / this->dim[0], 1.0f / this->dim[1]);
+
+ ret.uv_start = vec2(ret.pos)/vec2(this->dim) + vec2(texel_size[0] * 0.5f, texel_size[1] * 0.5f);
+ ret.uv_size = vec2(ret.dim)/vec2(this->dim) - texel_size;
+
+ return ret;
+}
+
+void TextureAtlas::DrawToAtlasNode( const std::string& rel_path, const AtlasNodeTree::AtlasNode& node )
+{
+ TextureAssetRef tex = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>( rel_path );
+
+ if( !rel_path.empty() )
+ {
+ Textures::Instance()->subImage( atlas_texture, tex->GetTextureRef(), node.pos[0], node.pos[1] );
+ }
+ else
+ {
+ //Texture::Instance()->subImageTestPattern( atlas_texture, node.pos[0], node.pos[1] );
+ }
+}
+
+void TextureAtlas::Draw()
+{
+ //Let's load one texture per frame to even out the load a little between frames.
+ if( dirtyNodes.empty() == false )
+ {
+ DrawToAtlasNode( dirtyNodes.front().first, *(dirtyNodes.front().second) );
+ dirtyNodes.pop();
+ }
+}
diff --git a/Source/Graphics/textureatlas.h b/Source/Graphics/textureatlas.h
new file mode 100644
index 00000000..29998ee4
--- /dev/null
+++ b/Source/Graphics/textureatlas.h
@@ -0,0 +1,63 @@
+//-----------------------------------------------------------------------------
+// Name: textureatlas.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Graphics/textures.h>
+#include <Graphics/textureref.h>
+#include <Graphics/atlasnodetree.h>
+#include <Graphics/vbocontainer.h>
+
+#include <Images/texture_data.h>
+
+#include <queue>
+
+class TextureAtlas;
+
+struct TextureAtlasRef
+{
+public:
+ ivec2 pos;
+ ivec2 dim;
+ vec2 uv_start;
+ vec2 uv_size;
+ TextureAtlas* source;
+};
+
+class TextureAtlas
+{
+private:
+ std::queue<std::pair<std::string,AtlasNodeTree::AtlasNode*> > dirtyNodes;
+
+ void DrawToAtlasNode( const std::string& rel_path, const AtlasNodeTree::AtlasNode& node );
+public:
+ GLuint framebuffer;
+ TextureRef atlas_texture;
+ ivec2 dim;
+ TextureAtlas(AtlasNodeTree& atlasNodeTree, TextureData::ColorSpace colorSpace);
+
+ //AtlasTextureAssetRef
+
+ TextureAtlasRef SetTextureToNode(const std::string& rel_path, AtlasNodeTree::AtlasNodeRef node );
+ void Draw();
+};
diff --git a/Source/Graphics/texturepack.cpp b/Source/Graphics/texturepack.cpp
new file mode 100644
index 00000000..9862b81b
--- /dev/null
+++ b/Source/Graphics/texturepack.cpp
@@ -0,0 +1,264 @@
+//-----------------------------------------------------------------------------
+// Name: texturepack.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "texturepack.h"
+
+#include <Graphics/textures.h>
+#include <Graphics/graphics.h>
+#include <Graphics/shaders.h>
+
+#include <Math/vec2math.h>
+
+void DrawRect(const vec2 &rect_min, const vec2 &rect_max){
+ glBegin(GL_QUADS);
+ glTexCoord2f(0,0);
+ glVertex3f(rect_min[0],rect_min[1],0.0f);
+ glTexCoord2f(1,0);
+ glVertex3f(rect_max[0],rect_min[1],0.0f);
+ glTexCoord2f(1,1);
+ glVertex3f(rect_max[0],rect_max[1],0.0f);
+ glTexCoord2f(0,1);
+ glVertex3f(rect_min[0],rect_max[1],0.0f);
+ glEnd();
+}
+
+TexturePackNode::TexturePackNode():
+ filled(false)
+{
+ children[0] = NULL;
+ children[1] = NULL;
+}
+
+void TexturePackNode::SetRect(const vec2 _rect_min , const vec2 _rect_max)
+{
+ rect_min = _rect_min;
+ rect_max = _rect_max;
+}
+
+TexturePackNode* TexturePackNode::Insert( const TextureAssetRef &texture )
+{
+ TexturePackNode* new_node;
+
+ //If node has children, try to insert in each child
+ if(children[0]){
+ new_node = children[0]->Insert(texture);
+ if(new_node){
+ return new_node;
+ } else {
+ return children[1]->Insert(texture);
+ }
+ // If node has no children, try to insert in this node
+ } else {
+ // If node is full, can't do anything here!
+ if(filled){
+ return NULL;
+ }
+
+ // If image is bigger than node, can't fit here!
+ const vec2 rect_dimensions = rect_max - rect_min;
+ const int texture_width = Textures::Instance()->getWidth(texture->GetTextureRef());
+ const int texture_height = Textures::Instance()->getHeight(texture->GetTextureRef());
+ if(texture_width > rect_dimensions[0] ||
+ texture_height > rect_dimensions[1]){
+ return NULL;
+ }
+
+ // If image is exactly the size of this node, draw it in!
+ if(texture_width == rect_dimensions[0] &&
+ texture_height == rect_dimensions[1]){
+ Textures::Instance()->bindTexture(texture->GetTextureRef());
+ DrawRect(rect_min,rect_max);
+ filled = true;
+ return this;
+ }
+
+ // Image fits inside, but not perfectly.
+ // Time to split!
+ children[0] = new TexturePackNode();
+ children[1] = new TexturePackNode();
+
+ int extra_width = (int)rect_dimensions[0] - texture_width;
+ int extra_height = (int)rect_dimensions[1] - texture_height;
+ if(extra_width > extra_height){
+ children[0]->SetRect(rect_min, vec2(rect_min[0]+texture_width, rect_max[1]));
+ children[1]->SetRect(vec2(rect_min[0]+texture_width, rect_min[1]), rect_max);
+ } else {
+ children[0]->SetRect(rect_min, vec2(rect_max[0], rect_min[1]+texture_height));
+ children[1]->SetRect(vec2(rect_min[0], rect_min[1]+texture_height), rect_max);
+ }
+
+ // Insert in the correctly-sized child node
+ return children[0]->Insert(texture);
+ }
+}
+
+void TexturePackNode::Dispose()
+{
+ if(children[0]){
+ delete children[0];
+ children[0] = NULL;
+ }
+ if(children[1]){
+ delete children[1];
+ children[1] = NULL;
+ }
+ filled = false;
+}
+
+TexturePackNode::~TexturePackNode()
+{
+ Dispose();
+}
+
+const vec2& TexturePackNode::GetMin() const
+{
+ return rect_min;
+}
+
+const vec2& TexturePackNode::GetMax() const
+{
+ return rect_max;
+}
+
+TexturePack::TexturePack() {
+}
+
+
+struct TexturePackParamSort {
+ TexturePackParam param;
+ int area;
+ int orig_id;
+};
+
+struct TextureSizeSorter {
+ bool operator()(const TexturePackParamSort& a, const TexturePackParamSort& b){
+ return a.area > b.area;
+ }
+};
+
+void TexturePack::Create( int _size, std::vector<TexturePackParam> &textures )
+{
+ Dispose();
+
+ size = _size;
+
+ int widest = 0;
+ int tallest = 0;
+
+ std::vector<TexturePackParamSort> sorted_textures(textures.size());
+ for(unsigned i=0; i<sorted_textures.size(); i++){
+ sorted_textures[i].param = textures[i];
+ sorted_textures[i].orig_id = i;
+ Textures::Instance()->EnsureInVRAM(textures[i].texture->GetTextureRef());
+ sorted_textures[i].area = Textures::Instance()->getWidth(textures[i].texture->GetTextureRef())*
+ Textures::Instance()->getHeight(textures[i].texture->GetTextureRef());
+ textures[sorted_textures[i].orig_id].inserted = false;
+ }
+ sort(sorted_textures.begin(), sorted_textures.end(), TextureSizeSorter());
+
+ int total_area_remaining = size*size;
+ int cutoff = 0;
+ for(int i=(int)sorted_textures.size()-1; i>=0; i--){
+ total_area_remaining -= sorted_textures[i].area;
+ if(total_area_remaining<0){
+ cutoff = i+1;
+ break;
+ } else {
+ widest = max(widest, Textures::Instance()->getWidth(textures[sorted_textures[i].orig_id].texture->GetTextureRef()));
+ tallest = max(tallest, Textures::Instance()->getHeight(textures[sorted_textures[i].orig_id].texture->GetTextureRef()));
+ }
+ }
+
+ const int area_used = size*size - total_area_remaining;
+ while(area_used < size*size/4 && size >= widest*2 && size >= tallest*2){
+ size/=2;
+ }
+
+ //pack_texture = Textures::Instance()->makeTextureColor(size,size);
+ pack_texture = Textures::Instance()->makeTextureTestPattern(size,size);
+ Textures::Instance()->SetTextureName(pack_texture, "Texture Pack");
+
+ GLuint framebuffer;
+ Graphics::Instance()->genFramebuffers(&framebuffer, "texture_pack");
+ Graphics::Instance()->PushFramebuffer();
+ Graphics::Instance()->bindFramebuffer(framebuffer);
+ Graphics::Instance()->framebufferColorTexture2D(pack_texture);
+
+ Shaders::Instance()->noProgram();
+
+ GLState gl_state;
+ gl_state.blend = false;
+ gl_state.cull_face = false;
+ gl_state.depth_test = false;
+ gl_state.depth_write = false;
+
+ Graphics::Instance()->setGLState(gl_state);
+ glColor4f(1.0f,1.0f,1.0f,1.0f);
+
+ Graphics::Instance()->PushViewport();
+ Graphics::Instance()->setViewport(0,0,size,size);
+
+ glMatrixMode(GL_PROJECTION);
+ glPushMatrix();
+ glLoadIdentity();
+ glOrtho(0,size,0,size,-100,100);
+
+ glMatrixMode(GL_MODELVIEW);
+ glPushMatrix();
+ glLoadIdentity();
+
+ root_node.SetRect(vec2(0.0f,0.0f),vec2((float)size,(float)size));
+
+ TexturePackNode *inserted;
+ for(unsigned i=cutoff; i<sorted_textures.size(); i++){
+ inserted = root_node.Insert(sorted_textures[i].param.texture);
+ if(inserted){
+ textures[sorted_textures[i].orig_id].offset[0] = inserted->GetMin()[0]/(float)size;
+ textures[sorted_textures[i].orig_id].offset[1] = inserted->GetMin()[1]/(float)size;
+ textures[sorted_textures[i].orig_id].offset[2] = Textures::Instance()->getWidth(textures[sorted_textures[i].orig_id].texture->GetTextureRef())/(float)size;
+ textures[sorted_textures[i].orig_id].offset[3] = Textures::Instance()->getHeight(textures[sorted_textures[i].orig_id].texture->GetTextureRef())/(float)size;
+ textures[sorted_textures[i].orig_id].inserted = true;
+ }
+ }
+
+ glMatrixMode(GL_PROJECTION);
+ glPopMatrix();
+ glMatrixMode(GL_MODELVIEW);
+ glPopMatrix();
+
+ Graphics::Instance()->PopViewport();
+ Graphics::Instance()->PopFramebuffer();
+ Graphics::Instance()->deleteFramebuffer(&framebuffer);
+
+ //Textures::Instance()->SetDebugTextureAssetRef(pack_texture);
+ Textures::Instance()->DeleteUnusedTextures();
+}
+
+TexturePack::~TexturePack() {
+ Dispose();
+}
+
+void TexturePack::Dispose()
+{
+ root_node.Dispose();
+}
diff --git a/Source/Graphics/texturepack.h b/Source/Graphics/texturepack.h
new file mode 100644
index 00000000..31673c0c
--- /dev/null
+++ b/Source/Graphics/texturepack.h
@@ -0,0 +1,70 @@
+//-----------------------------------------------------------------------------
+// Name: texturepack.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec4.h>
+#include <Math/vec2.h>
+
+#include <Graphics/glstate.h>
+
+#include <Asset/Asset/texture.h>
+
+#include <vector>
+
+class TexturePackNode {
+private:
+ bool filled;
+ vec2 rect_min;
+ vec2 rect_max;
+ TexturePackNode* children[2];
+
+public:
+ TexturePackNode* Insert(const TextureAssetRef &texture);
+ void SetRect(const vec2 _rect_min , const vec2 _rect_max);
+ const vec2& GetMin() const;
+ const vec2& GetMax() const;
+
+ TexturePackNode();
+ void Dispose();
+ ~TexturePackNode();
+};
+
+struct TexturePackParam {
+ TextureAssetRef texture;
+ vec4 offset; //(vec2 corner, vec2 dimensions)
+ bool inserted;
+};
+
+class TexturePack {
+ int size;
+ TexturePackNode root_node;
+
+public:
+ TextureRef pack_texture;
+
+ void Create(int size, std::vector<TexturePackParam> &textures);
+ TexturePack();
+ void Dispose();
+ ~TexturePack();
+};
diff --git a/Source/Graphics/textureref.cpp b/Source/Graphics/textureref.cpp
new file mode 100644
index 00000000..c21cf586
--- /dev/null
+++ b/Source/Graphics/textureref.cpp
@@ -0,0 +1,74 @@
+//-----------------------------------------------------------------------------
+// Name: textureref.cpp
+// Developer: Wolfire Games LLC
+// Description: TextureRef is used for generated textures. TextureAssetRef
+// for data-related loaded textures.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "textureref.h"
+
+#include <Graphics/textures.h>
+
+using namespace PhoenixTextures;
+
+void TextureRef::clear() {
+ Textures::Instance()->DecrementRefCount(id);
+
+ id = INVALID_ID;
+}
+
+bool TextureRef::valid() const {
+ return id != (int)INVALID_ID;
+}
+
+TextureRef::TextureRef() {
+ id = INVALID_ID;
+}
+
+TextureRef::TextureRef(int _id) {
+ id = _id;
+ Textures::Instance()->IncrementRefCount(id);
+}
+
+TextureRef::TextureRef(const TextureRef &_id) {
+ id = _id.id;
+ Textures::Instance()->IncrementRefCount(id);
+}
+
+TextureRef::~TextureRef() {
+ if(id != (int)INVALID_ID) {
+ Textures::Instance()->DecrementRefCount(id);
+ }
+}
+
+const TextureRef& TextureRef::operator=(const TextureRef& other) {
+ clear();
+ id = other.id;
+ Textures::Instance()->IncrementRefCount(id);
+ return *this;
+}
+
+bool TextureRef::operator!=( const TextureRef& other ) const {
+ return id != other.id;
+}
+
+bool TextureRef::operator<( const TextureRef& other ) const {
+ return id < other.id;
+}
diff --git a/Source/Graphics/textureref.h b/Source/Graphics/textureref.h
new file mode 100644
index 00000000..8f48e714
--- /dev/null
+++ b/Source/Graphics/textureref.h
@@ -0,0 +1,46 @@
+//-----------------------------------------------------------------------------
+// Name: textureref.h
+// Developer: Wolfire Games LLC
+// Description: TextureRef is used for generated textures. TextureAssetRef
+// for data-related loaded textures.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/Asset/texturedummy.h>
+
+class TextureRef {
+public:
+ int id;
+ void clear();
+
+ bool valid() const;
+
+ TextureRef();
+ TextureRef(int id);
+ TextureRef(int id, TextureDummyRef td);
+ TextureRef(const TextureRef &id);
+ ~TextureRef();
+
+ const TextureRef& operator=(const TextureRef& other);
+ bool operator!=(const TextureRef& other) const;
+ bool operator<(const TextureRef& other) const;
+
+};
diff --git a/Source/Graphics/textures.cpp b/Source/Graphics/textures.cpp
new file mode 100644
index 00000000..5377aef9
--- /dev/null
+++ b/Source/Graphics/textures.cpp
@@ -0,0 +1,2818 @@
+//-----------------------------------------------------------------------------
+// Name: textures.cpp
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: The textures class loads, manages, and binds textures
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "textures.h"
+
+#include <Memory/allocation.h>
+
+#include <Utility/gl_util.h>
+
+#include <Compat/processpool.h>
+
+#include <Graphics/textures.h>
+#include <Graphics/shaders.h>
+#include <Graphics/graphics.h>
+#include <Graphics/graphics.h>
+#include <Graphics/textureref.h>
+#include <Graphics/ColorWheel.h>
+#include <Graphics/converttexture.h>
+#include <Graphics/vbocontainer.h>
+
+#include <Internal/datemodified.h>
+#include <Internal/stopwatch.h>
+#include <Internal/timer.h>
+#include <Internal/integer.h>
+#include <Internal/common.h>
+#include <Internal/cachefile.h>
+#include <Internal/profiler.h>
+#include <Internal/filesystem.h>
+#include <Internal/config.h>
+
+#include <Images/texture_data.h>
+#include <Images/freeimage_wrapper.h>
+#include <Images/image_export.hpp>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <Asset/Asset/texturedummy.h>
+#include <Asset/assetmanager.h>
+
+#include <Compat/fileio.h>
+#include <Wrappers/glm.h>
+#include <Logging/logdata.h>
+#include <Memory/allocation.h>
+#include <Utility/assert.h>
+#include <Main/engine.h>
+
+#include <imgui.h>
+
+using namespace PhoenixTextures;
+
+vec4 GetAverageColor( const char* abs_path ) {
+ TextureData texture_data;
+ texture_data.Load(abs_path);
+
+ unsigned total_pixels = texture_data.GetWidth() *
+ texture_data.GetHeight();
+ unsigned total_bytes = total_pixels * 32 / 8;
+
+ float total_color[4] = {0};
+
+ std::vector<unsigned char> img_data;
+ img_data.resize(total_bytes);
+ texture_data.GetUncompressedData(&img_data[0]);
+ for(unsigned j=0; j<total_bytes; j+=4){
+ total_color[0] += img_data[j+0];
+ total_color[1] += img_data[j+1];
+ total_color[2] += img_data[j+2];
+ total_color[3] += img_data[j+3];
+ }
+
+ vec4 average_color;
+ average_color[0] = total_color[0]/total_pixels;
+ average_color[1] = total_color[1]/total_pixels;
+ average_color[2] = total_color[2]/total_pixels;
+ average_color[3] = total_color[3]/total_pixels;
+
+ return average_color;
+}
+
+bool IsCompressedGLFormat(GLenum format) {
+ switch (format) {
+ case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
+ case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
+ case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
+ case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
+ case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
+ case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
+ case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
+ case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
+ return true;
+ case GL_DEPTH_COMPONENT24:
+ case GL_RGB8:
+ case GL_RGBA8:
+ case GL_RGBA:
+ case GL_SRGB8:
+ case GL_SRGB8_ALPHA8:
+ return false;
+ default:
+ LOG_ASSERT(false);
+ return false;
+ }
+}
+
+bool GetLinearGLFormat(GLenum format) {
+ switch (format) {
+ case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
+ return (bool) GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
+
+ case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
+ return (bool) GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
+
+ case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
+ return (bool) GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
+
+ case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
+ return (bool) GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+
+ case GL_SRGB8:
+ return (bool) GL_RGB8;
+
+ case GL_SRGB8_ALPHA8:
+ return (bool) GL_RGBA8;
+
+ case GL_SRGB_ALPHA:
+ return (bool) GL_RGBA;
+ }
+
+ return format;
+}
+
+
+namespace {
+#ifdef _WIN32
+ const char* worker_app_path = "OvergrowthWorker.exe";
+#endif
+#ifdef __APPLE__
+ const char* worker_app_path = "OvergrowthWorker";
+#endif
+#ifdef __LINUX__
+ #ifdef __x86_64__
+ const char* worker_app_path = "OvergrowthWorker.bin.x86_64";
+ #else
+ const char* worker_app_path = "OvergrowthWorker.bin.x86";
+ #endif
+#endif
+}
+
+/*
+struct Texture {
+int width, height, depth;
+unsigned int size; // for texture buffers
+unsigned int num_slices; // >1 only for array textures
+bool cube_map;
+GLuint gl_texture_id;
+GLuint gl_buffer_id; // for texture buffers
+std::vector<SubTexture> sub_textures;
+GLuint wrap_t, wrap_s, min_filter, mag_filter;
+int ref_count;
+bool no_mipmap;
+bool no_reduce;
+bool use_srgb;
+bool no_live_update;
+unsigned char flags;
+GLenum internal_format;
+GLenum format;
+GLenum tex_target;
+#ifdef TRACK_TEXTURE_REF
+std::list<TextureRef*> ref_ptrs;
+#endif
+void Init();
+};*/
+
+const char* CStringFromGLEnum(GLenum val) {
+ switch(val){
+ case GL_ALPHA: return "GL_ALPHA";
+ case GL_RGB: return "GL_RGB";
+ case GL_RGBA: return "GL_RGBA";
+ case GL_LUMINANCE: return "GL_LUMINANCE";
+ case GL_LUMINANCE_ALPHA: return "GL_LUMINANCE_ALPHA";
+
+ case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: return "GL_COMPRESSED_RGBA_S3TC_DXT5_EXT";
+ case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: return "GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT";
+
+ case GL_RGBA32F: return "GL_RGBA32F";
+ case GL_R32UI: return "GL_R32UI";
+ case GL_RGBA16F: return "GL_RGBA16F";
+ case GL_RGB16F: return "GL_RGB16F";
+ case GL_RGB32F: return "GL_RGB32F";
+ case GL_DEPTH_COMPONENT: return "GL_DEPTH_COMPONENT";
+ case GL_DEPTH_COMPONENT24: return "GL_DEPTH_COMPONENT24";
+
+ case GL_UNSIGNED_BYTE: return "GL_UNSIGNED_BYTE";
+ case GL_FLOAT: return "GL_FLOAT";
+
+ case GL_NEAREST: return "GL_NEAREST";
+ case GL_LINEAR: return "GL_LINEAR";
+ case GL_NEAREST_MIPMAP_NEAREST: return "GL_NEAREST_MIPMAP_NEAREST";
+ case GL_LINEAR_MIPMAP_NEAREST: return "GL_LINEAR_MIPMAP_NEAREST";
+ case GL_NEAREST_MIPMAP_LINEAR: return "GL_NEAREST_MIPMAP_LINEAR";
+ case GL_LINEAR_MIPMAP_LINEAR: return "GL_LINEAR_MIPMAP_LINEAR";
+
+ case GL_CLAMP: return "GL_CLAMP";
+ case GL_REPEAT: return "GL_REPEAT";
+ case GL_CLAMP_TO_EDGE: return "GL_CLAMP_TO_EDGE";
+
+ case GL_RGB4: return "GL_RGB4";
+ case GL_RGB5: return "GL_RGB5";
+ case GL_RGB8: return "GL_RGB8";
+ case GL_RGB10: return "GL_RGB10";
+ case GL_RGB12: return "GL_RGB12";
+ case GL_RGB16: return "GL_RGB16";
+ case GL_RGBA2: return "GL_RGBA2";
+ case GL_RGBA4: return "GL_RGBA4";
+ case GL_RGB5_A1: return "GL_RGB5_A1";
+ case GL_RGBA8: return "GL_RGBA8";
+ case GL_RGB10_A2: return "GL_RGB10_A2";
+ case GL_RGBA12: return "GL_RGBA12";
+ case GL_RGBA16: return "GL_RGBA16";
+
+ case 0: return "NULL";
+ default: return "Unknown GL enum";
+ }
+}
+
+void Textures::DrawImGuiDebug() {
+ enum TexType {
+ kSimple, kArray, kCubemap, kBuffer, kUnknown
+ };
+
+ if(ImGui::TreeNode("Textures", "Textures: %d", (int)textures.size())){
+ const char* color_tex_num = "\033FFFFFFFF";
+ const char* color_cubemap = "\03399ecffFF";
+ const char* color_array = "\03399ff87FF";
+ const char* color_simple = "\033d1d1d1FF";
+ const char* color_buffer = "\033bea0ffFF";
+ const char* color_default = "\033FFFFFFFF";
+ const char* color_label = "\0338dff87FF";
+ const char* color_value = "\033FFFFFFFF";
+ const char* color_enum = "\0339ccbd6FF";
+
+ const int kBufSize = 256;
+ char buf[kBufSize];
+ for(int i=0, len=textures.size(); i<len; ++i){
+ const Texture& tex = textures[i];
+ bool drawable = false;
+ TexType type;
+ if(tex.cube_map){
+ FormatString(buf, kBufSize, "%s%d:%s cubemap", color_tex_num, i, color_cubemap);
+ type = kCubemap;
+ } else if(tex.num_slices>1){
+ FormatString(buf, kBufSize, "%s%d:%s texture array (%d slices)", color_tex_num, i, color_array, tex.num_slices);
+ type = kArray;
+ } else if(tex.sub_textures.size() == 1 && !tex.sub_textures[0].texture_name.empty()){
+ FormatString(buf, kBufSize, "%s%d:%s %s", color_tex_num, i, color_simple, tex.sub_textures[0].texture_name.c_str());
+ drawable = true;
+ type = kSimple;
+ } else if(tex.gl_buffer_id != UNLOADED_ID){
+ FormatString(buf, kBufSize, "%s%d:%s texture buffer", color_tex_num, i, color_buffer);
+ type = kBuffer;
+ } else if(tex.gl_texture_id != UNLOADED_ID){
+ FormatString(buf, kBufSize, "%s%d:%s generated texture", color_tex_num, i, color_simple);
+ drawable = true;
+ type = kSimple;
+ } else {
+ FormatString(buf, kBufSize, "%s%d: unknown", color_tex_num, i);
+ type = kUnknown;
+ }
+ if(!tex.name.empty()){
+ const char* color = color_default;
+ switch(type){
+ case kSimple: color = color_simple; break;
+ case kArray: color = color_array; break;
+ case kCubemap: color = color_cubemap; break;
+ case kBuffer: color = color_buffer; break;
+ case kUnknown: break;
+ }
+ FormatString(buf, kBufSize, "%s%d:%s %s", color_tex_num, i, color, tex.name.c_str());
+ }
+
+ if(type != kUnknown){
+ ImGui::PushID(i);
+ if(ImGui::TreeNode("","%s",buf)){
+ ImGui::PopID();
+ ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing());
+ ImGui::Text("%sReferences:%s %d", color_label, color_value, tex.ref_count);
+ if(type == kBuffer){
+ ImGui::Text("%sSize: %s%d", color_label, color_value, tex.size);
+ ImGui::Text("%sFormat: %s%s", color_label, color_value, CStringFromGLEnum(tex.internal_format));
+ } else if(type == kArray){
+ ImGui::Text("%sSize: %s%d x %d x %d", color_label, color_value, tex.width, tex.height, tex.num_slices);
+ } else if(type == kSimple || type == kCubemap){
+ ImGui::Text("%sSize: %s%d x %d", color_label, color_value, tex.width, tex.height);
+ }
+ if(type == kArray || type == kSimple || type == kCubemap){
+ ImGui::Text("%s%s / %s", color_enum, CStringFromGLEnum(tex.internal_format), CStringFromGLEnum(tex.format));
+ ImGui::Text("%s%s / %s", color_enum, CStringFromGLEnum(tex.wrap_s), CStringFromGLEnum(tex.wrap_t));
+ ImGui::Text("%s%s / %s", color_enum, CStringFromGLEnum(tex.min_filter), CStringFromGLEnum(tex.mag_filter));
+ bool temp;
+ temp = tex.use_srgb;
+ ImGui::Checkbox("sRGB", &temp);
+ temp = !tex.no_mipmap;
+ ImGui::Checkbox("Mipmaps", &temp);
+ }
+ ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing());
+ ImGui::TreePop();
+ } else {
+ ImGui::PopID();
+ if (ImGui::IsItemHovered() && drawable) {
+ if( tex.format != GL_DEPTH_COMPONENT ) {
+ ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0.5, 0.5, 1.0, 0.5));
+ ImGui::BeginTooltip();
+ ImGui::PushTextWrapPos(450.0f);
+ Textures::Instance()->EnsureInVRAM(i);
+ ImGui::Image((ImTextureID)((uintptr_t)textures[i].gl_texture_id), ImVec2(128,128), ImVec2(0,0), ImVec2(1,1));
+ ImGui::PopTextWrapPos();
+ ImGui::EndTooltip();
+ ImGui::PopStyleColor(1);
+ }
+ }
+ }
+ }
+ }
+ ImGui::TreePop();
+ }
+}
+
+Textures::Textures() {
+ process_pool = new ProcessPool(worker_app_path);
+ PROFILED_TEXTURE_MUTEX_LOCK
+ for(unsigned int i=0; i<_texture_units; i++){
+ bound_texture[i]=INVALID_ID;
+ }
+ m_wrap_t = _default_wrap;
+ m_wrap_s = _default_wrap;
+ min_filter = _default_min;
+ mag_filter = _default_max;
+ active_texture = 0;
+ // TODO: Dispose thread pool when deleting
+
+ if (config["save_as_crunch"].toNumber<bool>()) {
+ convert_file_type = "_converted.crn";
+ } else {
+ convert_file_type = "_converted.dds";
+ }
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+}
+
+static const float _inv_gamma = 1.0f/2.2f;
+static const float float_to_byte = 255.0f;
+static const float byte_to_float = 1.0f/float_to_byte;
+
+const int kCubeMapHDRVersion = 2;
+
+void Textures::SaveCubeMapMipmapsHDR (TextureRef which, const char* filename, int width) {
+ Textures::Instance()->bindTexture(which, 0);
+ int mip_level = 0;
+ int old_width = width;
+ std::vector<float> data;
+ while(width > 1){
+ for (int i=0; i<6; i++) {
+ int start_size = data.size();
+ data.resize(data.size() + width * width * 4);
+ glGetTexImage( GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, mip_level, GL_BGRA, GL_FLOAT, &data[start_size] );
+ }
+ ++mip_level;
+ width/=2;
+ }
+ std::string path = std::string(filename)+".hdrcube";
+ CreateParentDirs(path.c_str());
+ FILE *cache_file = my_fopen(path.c_str(), "wb");
+ if(cache_file){
+ fwrite(&kCubeMapHDRVersion, sizeof(int), 1, cache_file);
+ {
+ int size = data.size();
+ fwrite(&size, sizeof(int), 1, cache_file);
+ }
+ fwrite(&old_width, sizeof(int), 1, cache_file);
+ fwrite(&data[0], sizeof(float), data.size(), cache_file);
+ fclose(cache_file);
+ }
+}
+
+
+TextureRef Textures::LoadCubeMapMipmapsHDR (const char* filename) {
+ TextureRef tex;
+ FILE *cache_file = my_fopen(filename, "rb");
+ if(cache_file){
+ int version;
+ fread(&version, sizeof(int), 1, cache_file);
+ if(version == kCubeMapHDRVersion){
+ int size, width;
+ fread(&size, sizeof(int), 1, cache_file);
+ fread(&width, sizeof(int), 1, cache_file);
+ float* data = (float*)alloc.stack.Alloc(sizeof(float) * size);
+ fread(&data[0], sizeof(float), size, cache_file);
+ fclose(cache_file);
+ tex = Textures::Instance()->makeCubemapTexture(128, 128, GL_RGBA16F, GL_RGBA, Textures::MIPMAPS);
+ Textures::Instance()->SetTextureName(tex, filename);
+ int mip_level = 0;
+ int index = 0;
+ while(width > 1){
+ for (int i=0; i<6; i++) {
+ glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, mip_level, 0, 0, width, width, GL_BGRA, GL_FLOAT, &data[index]);
+ index += width * width * 4;
+ }
+ ++mip_level;
+ width/=2;
+ }
+ alloc.stack.Free(data);
+ }
+ }
+ return tex;
+}
+
+void Texture::Init() {
+ width = 0;
+ height = 0;
+ size = 0;
+ num_slices = 1;
+ cube_map = false;
+ gl_texture_id = UNLOADED_ID;
+ gl_buffer_id = UNLOADED_ID;
+ wrap_t = 0;
+ wrap_s = 0;
+ min_filter = 0;
+ mag_filter = 0;
+ ref_count = 1;
+ no_mipmap = false;
+ no_reduce = false;
+ use_srgb = false;
+ no_live_update = true;
+ flags = 0;
+ internal_format = GL_NONE;
+ format = GL_NONE;
+ tex_target = GL_NONE;
+ sub_textures.clear();
+ name.clear();
+}
+
+//#pragma optimize(", off)
+void Textures::DisposeTexture(int which, bool free_space) {
+ Texture &texture = textures[which];
+
+ if( texture.notify_on_dispose ) {
+ LOGI << "Disposed texture " << texture.name << " with gl id " << texture.gl_texture_id << std::endl;
+ }
+
+ for (unsigned int i = 0; i < texture.sub_textures.size(); i++) {
+ delete texture.sub_textures[i].texture_data;
+ }
+
+ texture.sub_textures.clear();
+
+ RemoveTextureFromVRAM(which);
+
+ texture.width = 0;
+ texture.height = 0;
+ texture.size = 0;
+ texture.num_slices = 1;
+ texture.name = "Disposed";
+ texture.ref_count = 0;
+ texture.gl_texture_id = UNLOADED_ID;
+ texture.gl_buffer_id = UNLOADED_ID;
+
+ if(free_space){
+ free_spaces.push_back(which);
+ }
+}
+//#pragma optimize("", on)
+
+void Textures::Dispose() {
+ blank_texture_ref.clear();
+ blank_normal_texture_ref.clear();
+
+ detail_color_texture_ref.clear();
+ detail_normal_texture_ref.clear();
+
+ PROFILED_TEXTURE_MUTEX_LOCK
+
+ for (unsigned int i=0;i<textures.size();i++){
+ DisposeTexture(i);
+ }
+ textures.clear();
+
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+}
+
+int conversion_num = 0;
+
+void Textures::ReloadInternal() {
+ for (unsigned int i=0;i<textures.size();i++){
+ Texture &texture = textures[i];
+
+ if (texture.no_live_update) {
+ continue;
+ }
+
+ std::vector<unsigned int>::const_iterator it = std::find(free_spaces.begin(), free_spaces.end(), i);
+ if (it != free_spaces.end()) {
+ // no texture in this slot
+ LOG_ASSERT(texture.width == 0);
+ LOG_ASSERT(texture.height == 0);
+ LOG_ASSERT(texture.sub_textures.empty());
+ LOG_ASSERT(texture.gl_texture_id == UNLOADED_ID);
+ LOG_ASSERT(texture.gl_buffer_id == UNLOADED_ID);
+ continue;
+ }
+
+ LOG_ASSERT(!texture.sub_textures.empty());
+ char abs_path[kPathSize];
+
+ if (texture.sub_textures.size() == 1) {
+ // ordinary texture
+ LOG_ASSERT(texture.num_slices == 1);
+
+ SubTexture &sub = texture.sub_textures[0];
+ LOG_ASSERT(!sub.texture_name.empty());
+
+ if (FindImagePath(sub.texture_name.c_str(), abs_path, kPathSize, kDataPaths | kModPaths | kWriteDir | kModWriteDirs, false) == 0){
+ if(strcmp(abs_path, sub.load_name.c_str()) == 0 && GetDateModifiedInt64(abs_path) == sub.orig_modified){
+ continue;
+ }
+ }else{
+ continue;
+ }
+
+ std::string name = sub.texture_name;
+ unsigned char flags = texture.flags;
+ DisposeTexture(i, false);
+ loadTexture(name, i, flags | PX_NOCONVERT);
+ } else {
+ // array texture
+ LOG_ASSERT(texture.num_slices > 1);
+
+ LogSystem::LogData(LogSystem::debug, "tex", __FILE__, __LINE__) << "Checking if array texture " << i << " (with " << texture.sub_textures.size() << " slices) needs reload..." << std::endl;
+
+ bool needs_reload = false;
+
+ for (unsigned int s = 0; s < texture.sub_textures.size(); s++) {
+ SubTexture &sub = texture.sub_textures[s];
+ LOG_ASSERT(!sub.texture_name.empty());
+ ModID modsource;
+ if (FindImagePath(sub.texture_name.c_str(), abs_path, kPathSize, kAnyPath, false,NULL,true, &modsource) == 0){
+ if(strcmp(abs_path, sub.load_name.c_str()) == 0 && GetDateModifiedInt64(abs_path) == sub.orig_modified){
+ LogSystem::LogData(LogSystem::debug, "tex", __FILE__, __LINE__) << " slice " << s << ": \"" << sub.texture_name << "\" no" << std::endl;
+ } else {
+ LogSystem::LogData(LogSystem::debug, "tex", __FILE__, __LINE__) << " slice " << s << ": \"" << sub.texture_name << "\" yes" << std::endl;
+ delete sub.texture_data;
+ std::string dst_path = AssemblePath(GetWritePath(modsource), SanitizePath(sub.texture_name + convert_file_type));
+ ConvertImage(abs_path, dst_path, GetTempDDSPath(dst_path, true, conversion_num), TextureData::Fast);
+ sub.texture_data = new TextureData();
+ sub.texture_data->Load(dst_path.c_str());
+ sub.orig_modified = GetDateModifiedInt64(abs_path);
+ needs_reload = true;
+ texture.width = 0;
+ texture.height = 0;
+ }
+ } else {
+ LogSystem::LogData(LogSystem::warning, "tex", __FILE__, __LINE__) << " slice " << s << ": \"" << sub.texture_name << "\" no longer exists!" << std::endl;
+ }
+ }
+ if (needs_reload) {
+ RemoveTextureFromVRAM(i);
+ }
+ }
+ }
+}
+
+void Textures::Reload() {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ ReloadInternal();
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+}
+
+//Set texture to wrap or clamp to edge
+void Textures::setWrap(GLenum wrap) {
+ m_wrap_s = wrap;
+ m_wrap_t = wrap;
+}
+
+void Textures::setWrap(GLenum wrap_s, GLenum wrap_t) {
+ m_wrap_s = wrap_s;
+ m_wrap_t = wrap_t;
+}
+
+//Set minification and magnification filters
+void Textures::setFilters(GLenum min, GLenum mag) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ min_filter = min;
+ mag_filter = mag;
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+}
+
+void GetCompletePath(std::string *path_utf8_ptr) {
+ std::string &path_utf8 = *path_utf8_ptr;
+ const int buf_size = 2048;
+ #ifdef _WIN32
+ WCHAR path_utf16[buf_size];
+ if(!MultiByteToWideChar(CP_UTF8, 0, path_utf8.c_str(), -1, path_utf16, buf_size)){
+ FatalError("Error", "Error converting utf8 string to utf16: %s", path_utf8.c_str());
+ }
+ WCHAR full_path_utf16[buf_size];
+ GetFullPathNameW(path_utf16, 2048, full_path_utf16, NULL);
+ char full_path_utf8[buf_size];
+ if(!WideCharToMultiByte(CP_UTF8, 0, full_path_utf16, -1, full_path_utf8, buf_size, NULL, NULL)){
+ DWORD err = GetLastError();
+ FatalError("Error", "Error converting utf16 string to utf8.");
+ }
+ int str_len = strlen(full_path_utf8);
+ for(int i=0; i<str_len; ++i){
+ if(full_path_utf8[i] == '\\'){
+ full_path_utf8[i] = '/';
+ }
+ }
+ path_utf8 = full_path_utf8;
+ #else
+ char full_path_utf8[buf_size];
+ realpath(path_utf8.c_str(), full_path_utf8);
+ path_utf8 = full_path_utf8;
+ #endif
+}
+
+void Textures::IncrementRefCount(unsigned int id) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ if (id != INVALID_ID && id < textures.size()) {
+ textures[id].ref_count++;
+ }
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+}
+
+void Textures::DecrementRefCount(unsigned int id) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ if (id != INVALID_ID && id < textures.size()) {
+ textures[id].ref_count--;
+ }
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+}
+
+#ifdef TRACK_TEXTURE_REF
+void Textures::AddRefPtr(unsigned int id, TextureRef* ref) {
+ texture_mutex.lock();
+ if (id != INVALID_ID && id < textures.size()) {
+ textures[id].ref_ptrs.push_back(ref);
+ }
+ texture_mutex.unlock();
+}
+
+void Textures::RemoveRefPtr(unsigned int id, TextureRef* ref) {
+ texture_mutex.lock();
+ if (id != INVALID_ID && id < textures.size()) {
+ textures[id].ref_ptrs.erase(
+ std::find(textures[id].ref_ptrs.begin(),
+ textures[id].ref_ptrs.end(),
+ ref));
+ }
+ texture_mutex.unlock();
+}
+#endif
+
+void Textures::GenerateMipmap(const TextureRef &tex_ref) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Textures::GenerateMipmap");
+ bindTexture(tex_ref,0);
+ glGenerateMipmap(GL_TEXTURE_2D);
+}
+
+void Textures::setSampleFilter( const TextureRef &tex_ref, enum SampleFilter filter )
+{
+ bindTexture(tex_ref);
+ if( filter == FILTER_NEAREST )
+ {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ }
+ else if( filter == FILTER_LINEAR )
+ {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ }
+}
+
+void Textures::setSampleWrap( const TextureRef &tex_ref, enum SampleWrap wrapping )
+{
+ bindTexture(tex_ref);
+
+ if( wrapping == WRAP_CLAMP_TO_EDGE )
+ {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ }
+}
+
+void Textures::subImage( const TextureRef &tex_ref, void* data )
+{
+ if( tex_ref.valid() )
+ {
+ bindTexture( tex_ref );
+ Texture &texture = textures[tex_ref.id];
+
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture.width, texture.height, texture.format, GL_UNSIGNED_BYTE,data);
+ }
+ else
+ {
+ LOGE << "Unable to load data to texture." << std::endl;
+ }
+}
+
+void Textures::subImage( const TextureRef &destination, const TextureRef &source, int xoffset, int yoffset )
+{
+ if( destination.valid() && source.valid() )
+ {
+ Texture &source_tex = textures[source.id];
+ Texture &dst_tex = textures[destination.id];
+ if(source_tex.sub_textures.empty()){
+ DisplayError("Error", "Sub image has no sub_textures");
+ return;
+ }
+ TextureData* tex_data = source_tex.sub_textures[0].texture_data;
+ if(!tex_data){
+ DisplayError("Error", "Trying to subImage from texture with no data");
+ return;
+ }
+
+ tex_data->EnsureInRAM();
+
+ if(tex_data->GetGLInternalFormat() != dst_tex.internal_format){
+ // only continue if error is because sRGB/linear mismatch
+ if (GetLinearGLFormat(tex_data->GetGLInternalFormat()) != GetLinearGLFormat(dst_tex.internal_format)) {
+ // TODO: could convert source texture to target format
+ LOGE << "Trying to subImage texture of format " << tex_data->GetGLInternalFormat() << " into texture of different format " << dst_tex.internal_format << std::endl;
+ return;
+ }
+ }
+
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Textures::subImage");
+ Graphics::Instance()->DebugTracePrint(source_tex.sub_textures[0].texture_name.c_str());
+
+ bindTexture( destination );
+
+ bool regen_mipmap = (false == textures[destination.id].no_mipmap);
+
+ if(tex_data->IsCompressed()) {
+ int reduction_factor = Graphics::Instance()->config_.texture_reduction_factor();
+ int dataMipLevel = 0;
+ int mipLevels = tex_data->GetMipLevels();
+ LOG_ASSERT(mipLevels > 0);
+
+ int targetWidth = tex_data->GetWidth() / reduction_factor;
+ int targetHeight = tex_data->GetHeight() / reduction_factor;
+
+ if(targetWidth < 4)
+ targetWidth = 4;
+ if(targetHeight < 4)
+ targetHeight = 4;
+
+ for(int i = 0; i < mipLevels; ++i) {
+ int width = tex_data->GetMipWidth(dataMipLevel);
+ int height = tex_data->GetMipHeight(dataMipLevel);
+
+ if (width > targetWidth && height > targetHeight) {
+ dataMipLevel++;
+ } else {
+ break;
+ }
+ }
+
+ for (int i = 0; i < mipLevels; i++) {
+ int width = tex_data->GetMipWidth(dataMipLevel);
+ int height = tex_data->GetMipHeight(dataMipLevel);
+
+ if(width < 4 || height < 4) {
+ // TODO: we should also set TEXTURE_MAX_LEVEL
+ // so we don't end up using the mip levels which are not valid
+ // currently since this is only used by decals and those are 512x512
+ // you really shouldn't get far enough away that it matters
+ break;
+ }
+
+ glCompressedTexSubImage2D(GL_TEXTURE_2D, i, xoffset, yoffset, width, height, dst_tex.internal_format, tex_data->GetMipDataSize(0, dataMipLevel), tex_data->GetMipData(0, dataMipLevel));
+
+ if(dataMipLevel + 1 < mipLevels)
+ dataMipLevel++;
+
+ xoffset = (xoffset + 1) / 2;
+ yoffset = (yoffset + 1) / 2;
+ }
+ regen_mipmap = false;
+ } else {
+ glTexSubImage2D(GL_TEXTURE_2D, 0, xoffset, yoffset, tex_data->GetMipWidth(0), tex_data->GetMipHeight(0), tex_data->GetGLBaseFormat(), tex_data->GetGLType(), tex_data->GetMipData(0, 0));
+ }
+
+ if( regen_mipmap )
+ {
+ glGenerateMipmap(GL_TEXTURE_2D);
+ }
+ } else {
+ LOGE << __FUNCTION__ << ": Source or destination is invalid" << std::endl;
+ }
+}
+
+void Textures::TextureToVRAM(unsigned int which) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "TextureToVRAM");
+ CHECK_GL_ERROR();
+
+ if (!deferred_delete_textures.empty()) {
+ for (std::list<GLuint>::iterator it = deferred_delete_textures.begin(); it != deferred_delete_textures.end(); it++) {
+ GLuint tex_id = *it;
+ LOGI << "Deferred delete of texture " << tex_id << std::endl;
+ glDeleteTextures(1, &tex_id);
+ }
+ deferred_delete_textures.clear();
+ }
+
+ Texture& tex = textures[which];
+
+ if(tex.sub_textures.empty()){
+ LOGW << "Calling TextureToVRAM on a texture with no sub_textures" << std::endl;
+ return;
+ }
+
+ Graphics::Instance()->DebugTracePrint(tex.sub_textures[0].texture_name.c_str());
+
+ bool tex_compressed = tex.sub_textures[0].texture_data->IsCompressed();
+ // TODO: silently fix the issue
+ for(int i=1, len=tex.sub_textures.size(); i<len; ++i){
+ const SubTexture &s = tex.sub_textures[i];
+ if(s.texture_data->IsCompressed() != tex_compressed) {
+ LOGE << "TextureToVRAM failed (compression mismatch) for " << s.load_name << std::endl;
+ return;
+ }
+ }
+
+ if(tex.gl_texture_id != UNLOADED_ID)return;
+
+ for( unsigned i = 0; i < tex.sub_textures.size(); i++ ) {
+ tex.sub_textures[i].texture_data->EnsureInRAM();
+ }
+
+ bool is_array = (tex.sub_textures.size() > 1);
+ bool is_cube = tex.sub_textures[0].texture_data->IsCube();
+
+ if (is_array && is_cube) {
+ // GL 4.0 feature, we have minimum requirement 3.2
+ LOGE << "cube map arrays not supported" << std::endl;
+ return;
+ }
+
+ // FIXME?
+ if (tex.use_srgb) {
+ if (tex.sub_textures[0].texture_data->GetColorSpace() != TextureData::sRGB) {
+ //LOGW << "Texture " << tex.sub_textures[0].load_name << " uses sRGB color space but does not specify it in file name" << std::endl;
+ }
+ tex.sub_textures[0].texture_data->SetColorSpace(TextureData::sRGB);
+ } else {
+ if (tex.sub_textures[0].texture_data->GetColorSpace() != TextureData::Linear) {
+ //LOGW << "Texture " << tex.sub_textures[0].load_name << " specifies sRGB color space in file name but linear internally" << std::endl;
+ }
+ }
+
+ // TODO fill this here
+ GLenum& internal_format = tex.internal_format;
+
+ unsigned int num_mips = tex.sub_textures[0].texture_data->GetMipLevels();
+
+ if (is_array) {
+ // set texture size to size of smallest slice
+ LOG_ASSERT(tex.width == 0);
+ LOG_ASSERT(tex.height == 0);
+
+ unsigned int width = tex.sub_textures[0].texture_data->GetWidth();
+ unsigned int height = tex.sub_textures[0].texture_data->GetHeight();
+
+ for (unsigned int i = 1; i < tex.sub_textures.size(); i++) {
+ width = std::min(width, tex.sub_textures[i].texture_data->GetWidth());
+ height = std::min(height, tex.sub_textures[i].texture_data->GetHeight());
+ num_mips = std::min(num_mips, tex.sub_textures[i].texture_data->GetMipLevels());
+ }
+
+ tex.width = width;
+ tex.height = height;
+ }
+
+ if(!tex.no_reduce){
+ tex.width /= Graphics::Instance()->config_.texture_reduction_factor();
+ tex.height /= Graphics::Instance()->config_.texture_reduction_factor();
+ }
+
+ glGenTextures( 1, &tex.gl_texture_id );
+ LOG_ASSERT(tex.gl_texture_id != INVALID_ID);
+ LOG_ASSERT(tex.gl_texture_id != UNLOADED_ID);
+
+ if( tex_compressed == false ) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Uncompressed");
+ tex.tex_target = is_array ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D;
+ glBindTexture(tex.tex_target, tex.gl_texture_id);
+
+ if(!tex.no_mipmap && num_mips > 1){
+ glTexParameteri(tex.tex_target, GL_TEXTURE_MIN_FILTER, tex.min_filter);
+ } else {
+ glTexParameteri(tex.tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ }
+ glTexParameteri(tex.tex_target, GL_TEXTURE_MAG_FILTER, tex.mag_filter);
+
+ glTexParameteri(tex.tex_target, GL_TEXTURE_WRAP_S, tex.wrap_s);
+ glTexParameteri(tex.tex_target, GL_TEXTURE_WRAP_T, tex.wrap_t);
+
+ if(Graphics::Instance()->config_.anisotropy())
+ glTexParameterf(tex.tex_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, Graphics::Instance()->config_.anisotropy());
+
+ CHECK_GL_ERROR();
+
+ internal_format = GL_RGBA;
+
+ //unsigned int totalTextureSize = 0;
+ if (is_array) {
+ TextureData* tex_data = tex.sub_textures[0].texture_data;
+ tex.internal_format = tex_data->GetGLInternalFormat();
+ tex.format = GL_BGRA;
+ glTexImage3D(tex.tex_target, 0, tex_data->GetGLInternalFormat(), tex_data->GetWidth(), tex_data->GetHeight(), tex.num_slices, 0, GL_BGRA, tex_data->GetGLType(), NULL);
+ }
+
+ for (unsigned int slice = 0; slice < tex.num_slices; slice++) {
+ TextureData* tex_data = tex.sub_textures[slice].texture_data;
+ // TODO: check formats
+ if(!tex_data){
+ FatalError("Error", "Compressed slice in uncompressed texture array");
+ }
+
+ tex_data->EnsureInRAM();
+
+ if (!tex.no_mipmap && !tex_data->HasMipmaps()) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "GenerateMipmaps");
+ bool success = tex_data->GenerateMipmaps();
+ if (!success) {
+ FatalError("Error", "Mipmap generation failed");
+ }
+ assert(tex_data->HasMipmaps());
+ }
+
+ unsigned int startMip = 0;
+ while ( startMip < tex_data->GetMipLevels() && (int)tex_data->GetMipWidth(startMip) > tex.width ) {
+ startMip++;
+ }
+
+ internal_format = tex_data->GetGLInternalFormat();
+ if (is_array) {
+ tex.format = tex_data->GetGLBaseFormat();
+ LOG_ASSERT(tex_data->GetWidth() == tex.sub_textures[0].texture_data->GetWidth() &&
+ tex_data->GetHeight() == tex.sub_textures[0].texture_data->GetHeight());
+ for (unsigned int i = startMip; i < num_mips; i++ ) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "glTexSubImage3D");
+ glTexSubImage3D(tex.tex_target, i - startMip, 0, 0, slice, tex_data->GetMipWidth(i), tex_data->GetMipHeight(i), 1,
+ tex_data->GetGLBaseFormat(),
+ tex_data->GetGLType(),
+ tex_data->GetMipData(0, i));
+ }
+ } else {
+ tex.format = tex_data->GetGLBaseFormat();
+ for (unsigned int i = startMip; i < num_mips; i++ ) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "glTexImage2D");
+ glTexImage2D(tex.tex_target,
+ i - startMip,
+ internal_format,
+ tex_data->GetMipWidth(i),
+ tex_data->GetMipHeight(i),
+ 0,
+ tex_data->GetGLBaseFormat(),
+ tex_data->GetGLType(),
+ tex_data->GetMipData(0, i));
+ }
+ }
+ //totalTextureSize = 4 * tex_data->GetWidth() * tex_data->GetHeight();
+ }
+
+ CHECK_GL_ERROR();
+ } else {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Compressed");
+ int skip_levels = 0;
+ if(!tex.no_reduce){
+ skip_levels = Graphics::Instance()->config_.texture_reduce();
+ }
+
+ if(is_cube) {
+ tex.tex_target = GL_TEXTURE_CUBE_MAP;
+ glBindTexture(tex.tex_target, tex.gl_texture_id);
+
+ glTexParameteri(tex.tex_target, GL_TEXTURE_MIN_FILTER, tex.min_filter);
+ glTexParameteri(tex.tex_target, GL_TEXTURE_MAG_FILTER, tex.mag_filter);
+
+ glTexParameteri(tex.tex_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(tex.tex_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(tex.tex_target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+
+ tex.cube_map = true;
+
+ if(Graphics::Instance()->config_.anisotropy()) {
+ glTexParameterf(tex.tex_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, Graphics::Instance()->config_.anisotropy());
+ }
+
+ TextureData* tex_data = tex.sub_textures[0].texture_data;
+
+ LOG_ASSERT(tex_data);
+
+ internal_format = tex_data->GetGLInternalFormat();
+
+ tex_data->EnsureInRAM();
+
+ uint32_t totalTextureSize = 0;
+ for(unsigned int j = 0; j < tex_data->GetNumFaces(); j++) {
+ for(unsigned int i = 0; i < tex_data->GetMipLevels(); i++) {
+ int width = tex_data->GetMipWidth(i);
+ int height = tex_data->GetMipHeight(i);
+ if( width == 0 || height == 0 ) {
+ break;
+ }
+ if( (int)i>=skip_levels ) {
+ if(tex_data->IsCompressed()) {
+ glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + j,
+ i-skip_levels,
+ internal_format,
+ width,
+ height,
+ 0,
+ tex_data->GetMipDataSize(j, i),
+ tex_data->GetMipData(GL_TEXTURE_CUBE_MAP_POSITIVE_X + j, i));
+ } else {
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + j,
+ i-skip_levels,
+ internal_format,
+ width,
+ height,
+ 0,
+ tex_data->GetGLBaseFormat(),
+ tex_data->GetGLType(),
+ tex_data->GetMipData(GL_TEXTURE_CUBE_MAP_POSITIVE_X + j, i));
+ }
+ totalTextureSize += tex_data->GetMipDataSize(j, i);
+ }
+ }
+ }
+
+ LOG_ASSERT(tex.sub_textures.size() == 1);
+ } else {
+ LOG_ASSERT(!tex.sub_textures.empty());
+ tex.tex_target = is_array ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D;
+
+ glBindTexture(tex.tex_target, tex.gl_texture_id);
+
+ glTexParameteri(tex.tex_target, GL_TEXTURE_MIN_FILTER, tex.min_filter);
+ glTexParameteri(tex.tex_target, GL_TEXTURE_MAG_FILTER, tex.mag_filter);
+
+ glTexParameteri(tex.tex_target, GL_TEXTURE_WRAP_S, tex.wrap_s);
+ glTexParameteri(tex.tex_target, GL_TEXTURE_WRAP_T, tex.wrap_t);
+
+ if(Graphics::Instance()->config_.anisotropy())
+ glTexParameterf(tex.tex_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, Graphics::Instance()->config_.anisotropy());
+
+
+ if (!is_array)
+ {
+ TextureData* tex_data = tex.sub_textures[0].texture_data;
+
+ LOG_ASSERT(tex_data);
+
+ internal_format = tex_data->GetGLInternalFormat();
+
+ tex_data->EnsureInRAM();
+
+ int width = tex_data->GetWidth();
+ int height = tex_data->GetHeight();
+
+ uint32_t totalTextureSize = 0;
+ int temp_skip_levels = skip_levels + 0;
+ for(unsigned int i = 0; i < tex_data->GetMipLevels(); i++)
+ {
+ if( width == 0 && height == 0 ) {
+ break;
+ }
+ if( (int)i >= temp_skip_levels ) {
+ if(tex_data->IsCompressed()) {
+ glCompressedTexImage2D(tex.tex_target,
+ i-temp_skip_levels,
+ internal_format,
+ tex_data->GetMipWidth(i),
+ tex_data->GetMipHeight(i),
+ 0,
+ tex_data->GetMipDataSize(0, i),
+ tex_data->GetMipData(0, i));
+ } else {
+ glTexImage2D(tex.tex_target,
+ i-temp_skip_levels,
+ internal_format,
+ width,
+ height,
+ 0,
+ tex_data->GetGLBaseFormat(),
+ tex_data->GetGLType(),
+ tex_data->GetMipData(0, i));
+ }
+ CHECK_GL_ERROR();
+ totalTextureSize += tex_data->GetMipDataSize(0, i);
+ }
+ }
+ } else {
+ // array texture
+ TextureData* tex_data = tex.sub_textures[0].texture_data;
+
+ LOG_ASSERT(tex_data);
+
+ int width = tex.width;
+ int height = tex.height;
+
+ internal_format = tex_data->GetGLInternalFormat();
+
+ tex_data->EnsureInRAM();
+
+ for (unsigned int mip = 0; mip < num_mips; mip++) {
+ assert( tex.tex_target == GL_TEXTURE_3D || tex.tex_target == GL_TEXTURE_2D_ARRAY );
+ int image_size = tex_data->GetMipDataSize(0, mip + tex_data->GetMipLevels() - num_mips) * tex.num_slices;
+ glTexImage3D(tex.tex_target,
+ mip,
+ internal_format,
+ width,
+ height,
+ tex.num_slices,
+ 0,
+ GL_RGBA,
+ GL_UNSIGNED_BYTE,
+ NULL);
+ width = std::max(1, (width + 1) / 2);
+ height = std::max(1, (height + 1) / 2);
+ }
+
+ for (unsigned int slice = 0; slice < tex.num_slices; slice++) {
+ TextureData* tex_data = tex.sub_textures[slice].texture_data;
+
+ int width = tex_data->GetWidth();
+ int height = tex_data->GetHeight();
+
+ LOG_ASSERT(tex_data->IsCompressed());
+ LOG_ASSERT(num_mips <= tex_data->GetMipLevels());
+
+ // in case the slice is larger than others find the starting level
+ unsigned int start_mip;
+ for (start_mip = 0; start_mip < tex_data->GetMipLevels(); start_mip++) {
+ if (width == tex.width && height == tex.height) {
+ break;
+ }
+ width = std::max(1, (width + 1) / 2);
+ height = std::max(1, (height + 1) / 2);
+ }
+
+ for (unsigned int i = start_mip; i < tex_data->GetMipLevels(); i++) {
+ glCompressedTexSubImage3D(tex.tex_target,
+ i - start_mip,
+ 0,
+ 0,
+ slice,
+ width,
+ height,
+ 1,
+ internal_format,
+ tex_data->GetMipDataSize(0, i),
+ tex_data->GetMipData(0, i));
+
+ width = std::max(1, (width + 1) / 2);
+ height = std::max(1, (height + 1) / 2);
+ }
+ }
+ }
+ glBindTexture(tex.tex_target, 0);
+ }
+ }
+
+ if(tex.gl_texture_id == UNLOADED_ID) {
+ DisplayError("Error","Texture ID is overlapping with UNLOADED_ID");
+ }
+
+ RebindTexture(GetActiveTexture());
+
+
+ if(config["texture_minimize_ram"].toBool() == true){
+ //LOGI << "Unloading texture " << tex.sub_textures[0].texture_data->GetPath() << " from RAM to save space" << std::endl;
+ tex.sub_textures[0].texture_data->UnloadData();
+ }
+
+ LOG_ASSERT(tex.tex_target != 0);
+
+ CHECK_GL_ERROR();
+}
+
+void Textures::drawTexture(TextureRef id, vec3 where, float size, float rotation) {
+ CHECK_GL_ERROR();
+
+ // no coloring
+ Graphics* graphics = Graphics::Instance();
+ Shaders* shaders = Shaders::Instance();
+
+ int shader_id = shaders->returnProgram("simple_2d #TEXTURE #FLIPPED");
+ shaders->createProgram(shader_id);
+ int shader_attrib_vert_coord = shaders->returnShaderAttrib("vert_coord", shader_id);
+ int shader_attrib_tex_coord = shaders->returnShaderAttrib("tex_coord", shader_id);
+ int uniform_mvp_mat = shaders->returnShaderVariable("mvp_mat", shader_id);
+ int uniform_color = shaders->returnShaderVariable("color", shader_id);
+ shaders->setProgram(shader_id);
+ CHECK_GL_ERROR();
+
+ GLState gl_state;
+ gl_state.blend = true;
+ gl_state.cull_face = false;
+ gl_state.depth_write = false;
+ gl_state.depth_test = false;
+ graphics->setGLState(gl_state);
+ CHECK_GL_ERROR();
+
+
+ glm::mat4 proj_mat;
+ proj_mat = glm::ortho(0.0f, (float)graphics->window_dims[0], 0.0f, (float)graphics->window_dims[1]);
+ glm::mat4 modelview_mat(1.0f);
+ modelview_mat = glm::translate(modelview_mat, glm::vec3(where[0]-size*0.5f,where[1]-size*0.5f,0));
+ modelview_mat = glm::scale(modelview_mat, glm::vec3(size,size,1.0f));
+ modelview_mat = glm::translate(modelview_mat, glm::vec3(0.5f,0.5f,0.5f));
+ modelview_mat = glm::rotate(modelview_mat, rotation, glm::vec3(0.0f, 0.0f, 1.0f));
+ modelview_mat = glm::translate(modelview_mat, glm::vec3(-0.5f,-0.5f,-0.5f));
+ glm::mat4 mvp_mat = proj_mat * modelview_mat;
+ CHECK_GL_ERROR();
+
+ graphics->EnableVertexAttribArray(shader_attrib_vert_coord);
+ CHECK_GL_ERROR();
+ graphics->EnableVertexAttribArray(shader_attrib_tex_coord);
+ CHECK_GL_ERROR();
+ static const GLfloat verts[] = {
+ 0, 0, 0, 0,
+ 1, 0, 1, 0,
+ 1, 1, 1, 1,
+ 0, 1, 0, 1
+ };
+ static const GLuint indices[] = {
+ 0, 1, 2,
+ 0, 2, 3
+ };
+ static VBOContainer vert_vbo;
+ static VBOContainer index_vbo;
+ static bool vbo_filled = false;
+ if(!vbo_filled) {
+ vert_vbo.Fill(kVBOFloat | kVBOStatic, sizeof(verts), (void*)verts);
+ index_vbo.Fill(kVBOElement | kVBOStatic, sizeof(indices), (void*)indices);
+ vbo_filled = true;
+ }
+ vert_vbo.Bind();
+ index_vbo.Bind();
+
+ glVertexAttribPointer(shader_attrib_vert_coord, 2, GL_FLOAT, false, 4*sizeof(GLfloat), 0);
+ CHECK_GL_ERROR();
+ glVertexAttribPointer(shader_attrib_tex_coord, 2, GL_FLOAT, false, 4*sizeof(GLfloat), (void*)(sizeof(GL_FLOAT)*2));
+ CHECK_GL_ERROR();
+
+ int num_indices = 6;
+
+ glUniformMatrix4fv(uniform_mvp_mat, 1, false, (GLfloat*)&mvp_mat);
+ CHECK_GL_ERROR();
+ vec4 color(1.0f);
+ glUniform4fv(uniform_color, 1, &color[0]);
+ CHECK_GL_ERROR();
+
+ bindTexture(id, 0);
+ CHECK_GL_ERROR();
+ graphics->DrawElements(GL_TRIANGLES, num_indices, GL_UNSIGNED_INT, 0);
+ CHECK_GL_ERROR();
+
+ graphics->BindElementVBO(0);
+ graphics->BindArrayVBO(0);
+ graphics->ResetVertexAttribArrays();
+ CHECK_GL_ERROR();
+}
+
+int Textures::AllocateFreeSlot() {
+ if(free_spaces.empty()) {
+ int id = (int)textures.size();
+ textures.resize(id+1);
+ textures[id].ref_count = 1;
+ textures[id].notify_on_dispose = false;
+ return id;
+ } else {
+ int which_space = free_spaces.back();
+ free_spaces.resize(free_spaces.size()-1);
+ LOG_ASSERT(textures[which_space].ref_count == 0);
+ LOG_ASSERT(textures[which_space].gl_texture_id == UNLOADED_ID);
+ LOG_ASSERT(textures[which_space].name == "Disposed");
+ textures[which_space].notify_on_dispose = false;
+ textures[which_space].ref_count = 1;
+ return which_space;
+ }
+}
+
+void Textures::DeleteUnusedTextures() {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ int num_deleted = 0;
+ for (unsigned i=0; i<textures.size(); i++) {
+ if( textures[i].ref_count <= 0 &&
+ (textures[i].gl_texture_id != UNLOADED_ID ||
+ !textures[i].sub_textures.empty()))
+ {
+ //printf("Deleting %s\n", textures[i].texture_name.c_str());
+ DisposeTexture(i);
+ ++num_deleted;
+ } else if(textures[i].ref_count > 0){
+ //printf("Keeping %s\n", textures[i].texture_name.c_str());
+ }
+ }
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+}
+
+unsigned int Textures::makeCubemapTextureInternal(int width, int height, GLint internal_format, GLint format, MipmapParam mipmap_param) {
+ CHECK_GL_ERROR();
+
+ unsigned which = AllocateFreeSlot();
+
+ Texture& texture = textures[which];
+ texture.Init();
+ texture.cube_map = true;
+ texture.width = width;
+ texture.height = height;
+ texture.tex_target = GL_TEXTURE_CUBE_MAP;
+ texture.internal_format = internal_format;
+ texture.format = format;
+
+ texture.use_srgb = (internal_format == GL_SRGB_ALPHA);
+
+ glGenTextures(1, &texture.gl_texture_id);
+ LOG_ASSERT(texture.gl_texture_id != INVALID_ID);
+ LOG_ASSERT(texture.gl_texture_id != UNLOADED_ID);
+
+ glBindTexture(texture.tex_target, texture.gl_texture_id);
+ texture.mag_filter = GL_LINEAR;
+ if(mipmap_param == NO_MIPMAPS){
+ texture.min_filter = GL_LINEAR;
+ } else {
+ texture.min_filter = GL_LINEAR_MIPMAP_LINEAR;
+ }
+
+ texture.wrap_s = GL_CLAMP_TO_EDGE;
+ texture.wrap_t = GL_CLAMP_TO_EDGE;
+
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MIN_FILTER, texture.min_filter);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MAG_FILTER, texture.mag_filter);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_S, texture.wrap_s);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_T, texture.wrap_t);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+ int level = 0;
+ unsigned int totalTextureSize = 0;
+ while((width >= 1 && height >= 1) && (mipmap_param == MIPMAPS || level == 0)) {
+ for (int i=0; i<6; i++) {
+ if(internal_format == GL_RGBA16F) {
+ if(GLEW_ARB_texture_float) {
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, level, internal_format, width, height, 0, format, GL_FLOAT, NULL);
+ totalTextureSize += width*height*8;
+ } else {
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, level, GL_RGBA, width, height, 0, format, GL_UNSIGNED_BYTE, NULL);
+ totalTextureSize += width*height*4;
+ }
+ } else {
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, level, internal_format, width, height, 0, format, GL_UNSIGNED_BYTE, NULL);
+ totalTextureSize += width*height*4;
+ }
+ }
+ width /= 2;
+ height /= 2;
+ ++level;
+ }
+ CHECK_GL_ERROR();
+ return which;
+}
+
+TextureRef Textures::makeCubemapTexture(int width, int height, GLint internal_format, GLint format, MipmapParam mipmap_param) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ unsigned int t = makeCubemapTextureInternal(width, height, internal_format, format, mipmap_param);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureRef tex_ref(t);
+ DecrementRefCount(t);
+ return tex_ref;
+}
+
+unsigned int Textures::makeTextureInternal(const TextureData &texture_data) {
+ CHECK_GL_ERROR();
+ unsigned which = AllocateFreeSlot();
+
+ TextureData* texture_data_ptr = new TextureData(texture_data);
+ textures[which].Init();
+ textures[which].sub_textures.resize(1);
+ textures[which].sub_textures[0].texture_data = texture_data_ptr;
+ textures[which].width = texture_data.GetWidth();
+ textures[which].height = texture_data.GetHeight();
+
+ textures[which].wrap_s = m_wrap_s;
+ textures[which].wrap_t = m_wrap_t;
+ textures[which].min_filter = min_filter;
+ textures[which].mag_filter = mag_filter;
+
+ textures[which].no_mipmap = true;
+
+ textures[which].internal_format = texture_data.GetGLInternalFormat();
+ textures[which].format = texture_data.GetGLBaseFormat();
+ textures[which].tex_target = GL_TEXTURE_2D;
+
+ TextureToVRAM(which);
+
+ return which;
+}
+
+TextureRef Textures::makeTexture(const TextureData &texture_data) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ unsigned int t = makeTextureInternal(texture_data);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureRef tex_ref(t);
+ DecrementRefCount(t);
+ return tex_ref;
+}
+
+unsigned int Textures::makeTextureInternal(int width, int height, GLint internal_format, GLint format, bool mipmap, void* data) {
+ PROFILER_ZONE(g_profiler_ctx, "Textures::makeTextureInternal(int width, int height, GLint internal_format, GLint format, bool mipmap, void* data)");
+ CHECK_GL_ERROR();
+ unsigned which = AllocateFreeSlot();
+
+ Texture& texture = textures[which];
+ texture.Init();
+ texture.sub_textures.resize(1);
+ texture.width = width;
+ texture.height = height;
+ texture.internal_format = internal_format;
+ texture.format = format;
+ texture.tex_target = GL_TEXTURE_2D;
+
+ glGenTextures(1, &texture.gl_texture_id);
+ LOG_ASSERT(texture.gl_texture_id != INVALID_ID);
+ LOG_ASSERT(texture.gl_texture_id != UNLOADED_ID);
+
+ CHECK_GL_ERROR();
+ glBindTexture(texture.tex_target, texture.gl_texture_id);
+ if(internal_format == GL_RGBA16F) {
+ if(GLEW_ARB_texture_float) {
+ glTexImage2D(texture.tex_target, 0, internal_format, width, height, 0,
+ format, GL_FLOAT, data);
+ } else {
+ glTexImage2D(texture.tex_target, 0, GL_RGBA, width, height, 0,
+ format, GL_UNSIGNED_BYTE, data);
+ }
+ } else if (IsCompressedGLFormat(internal_format)) {
+ int data_size = ((width + 3) / 4) * ((height + 3) / 4) * 16;
+ std::vector<char> zeros(data_size, 0);
+ PROFILER_ZONE(g_profiler_ctx, "glCompressedTexImage2D");
+ int temp_width = width;
+ int temp_height = height;
+ int temp_level = 0;
+ glCompressedTexImage2D(texture.tex_target, temp_level, internal_format, temp_width, temp_height, 0, data_size, &zeros[0]);
+ while(mipmap && (temp_width > 1 || temp_height > 1)) {
+ ++temp_level;
+ if(temp_width > 1){
+ temp_width /= 2;
+ }
+ if(temp_height > 1){
+ temp_height /= 2;
+ }
+ data_size = ((temp_width + 3) / 4) * ((temp_height + 3) / 4) * 16;
+ glCompressedTexImage2D(texture.tex_target, temp_level, internal_format, temp_width, temp_height, 0, data_size, &zeros[0]);
+ }
+ } else {
+ glTexImage2D(texture.tex_target, 0, internal_format, width, height, 0,
+ format, GL_UNSIGNED_BYTE, data);
+ }
+ CHECK_GL_ERROR();
+ if (mipmap) texture.min_filter = GL_LINEAR_MIPMAP_LINEAR;
+ else texture.min_filter = GL_LINEAR;
+ texture.mag_filter = GL_LINEAR;
+ texture.wrap_s = GL_CLAMP_TO_EDGE;
+ texture.wrap_t = GL_CLAMP_TO_EDGE;
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_S, texture.wrap_s);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_T, texture.wrap_t);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MIN_FILTER, texture.min_filter);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MAG_FILTER, texture.mag_filter);
+ if(Graphics::Instance()->config_.anisotropy()) {
+ glTexParameterf(texture.tex_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, Graphics::Instance()->config_.anisotropy());
+ }
+ CHECK_GL_ERROR();
+ if(mipmap && !IsCompressedGLFormat(internal_format)) {
+ PROFILER_ZONE(g_profiler_ctx, "glGenerateMipmap");
+ glGenerateMipmap(texture.tex_target);
+ }
+ CHECK_GL_ERROR();
+
+ return which;
+}
+
+unsigned int Textures::make3DTextureInternal(int* dims, GLint internal_format, GLint format, bool mipmap, void* data) {
+ CHECK_GL_ERROR();
+ unsigned which = AllocateFreeSlot();
+
+ Texture& texture = textures[which];
+ texture.Init();
+ texture.width = dims[0];
+ texture.height = dims[1];
+ texture.depth = dims[2];
+ texture.internal_format = internal_format;
+ texture.format = format;
+ texture.tex_target = GL_TEXTURE_3D;
+
+ glGenTextures(1, &texture.gl_texture_id);
+ LOG_ASSERT(texture.gl_texture_id != INVALID_ID);
+ LOG_ASSERT(texture.gl_texture_id != UNLOADED_ID);
+
+ CHECK_GL_ERROR();
+ glBindTexture(texture.tex_target, texture.gl_texture_id);
+ if(internal_format == GL_RGBA16F) {
+ glTexImage3D(texture.tex_target, 0, internal_format, dims[0], dims[1], dims[2], 0, format, GL_FLOAT, data);
+ } else {
+ glTexImage3D(texture.tex_target, 0, internal_format, dims[0], dims[1], dims[2], 0, format, GL_UNSIGNED_BYTE, data);
+ }
+ CHECK_GL_ERROR();
+ if (mipmap) glTexParameteri(texture.tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ else glTexParameteri(texture.tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+ if(Graphics::Instance()->config_.anisotropy()) {
+ glTexParameterf(texture.tex_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, Graphics::Instance()->config_.anisotropy());
+ }
+ CHECK_GL_ERROR();
+ if(mipmap) {
+ PROFILER_ZONE(g_profiler_ctx, "glGenerateMipmap");
+ glGenerateMipmap(texture.tex_target);
+ }
+ CHECK_GL_ERROR();
+
+ return which;
+}
+
+TextureRef Textures::makeTexture(int width, int height, GLint internal_format, GLint format, bool mipmap, void* data) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ unsigned int t = makeTextureInternal(width, height, internal_format, format, mipmap, data);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureRef tex_ref(t);
+ DecrementRefCount(t);
+ return tex_ref;
+}
+
+TextureRef Textures::make3DTexture(int width, int height, int depth, GLint internal_format, GLint format, bool mipmap, void* data) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ int dims[3] = {width, height, depth};
+ unsigned int t = make3DTextureInternal(dims, internal_format, format, mipmap, data);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureRef tex_ref(t);
+ DecrementRefCount(t);
+ return tex_ref;
+}
+
+unsigned int Textures::makeTextureColorInternal(int width, int height, GLint internal_format, GLint format, float red, float green, float blue, float alpha, bool mipmap) {
+ CHECK_GL_ERROR();
+ unsigned which = AllocateFreeSlot();
+
+ Texture& texture = textures[which];
+ texture.Init();
+ texture.width = width;
+ texture.height = height;
+ texture.internal_format = internal_format;
+ texture.format = format;
+ texture.no_mipmap = !mipmap;
+ texture.tex_target = GL_TEXTURE_2D;
+
+ std::vector<GLubyte> data(width*height*4);
+
+ unsigned char red_byte = (unsigned char)(red*255);
+ unsigned char green_byte = (unsigned char)(green*255);
+ unsigned char blue_byte = (unsigned char)(blue*255);
+ unsigned char alpha_byte = (unsigned char)(alpha*255);
+
+ int index;
+ for (int j = 0; j < width; j++) {
+ for (int i = 0; i < height; i++) {
+ index = (i+j*height)*4;
+ data[index+0] = red_byte;
+ data[index+1] = green_byte;
+ data[index+2] = blue_byte;
+ data[index+3] = alpha_byte;
+ }
+ }
+
+ glGenTextures(1, &textures[which].gl_texture_id);
+ LOG_ASSERT(textures[which].gl_texture_id != INVALID_ID);
+ LOG_ASSERT(textures[which].gl_texture_id != UNLOADED_ID);
+
+ glBindTexture(texture.tex_target, texture.gl_texture_id);
+
+ unsigned int totalTextureSize = 0;
+ if(internal_format == GL_RGBA16F) {
+ if(GLEW_ARB_texture_float) {
+ glTexImage2D(texture.tex_target, 0, internal_format, width, height, 0,
+ format, GL_FLOAT, &data[0]);
+ totalTextureSize += width*height*8;
+ } else {
+ glTexImage2D(texture.tex_target, 0, GL_RGBA, width, height, 0,
+ format, GL_UNSIGNED_BYTE, &data[0]);
+ totalTextureSize += width*height*4;
+ }
+ } else {
+ glTexImage2D(texture.tex_target, 0, internal_format, width, height, 0,
+ format, GL_UNSIGNED_BYTE, &data[0]);
+ totalTextureSize += width*height*4;
+ }
+ if (mipmap) glTexParameteri(texture.tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ else glTexParameteri(texture.tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ if(mipmap) {
+ glGenerateMipmap(texture.tex_target);
+ totalTextureSize *= 4;
+ totalTextureSize /= 3;
+ }
+
+ CHECK_GL_ERROR();
+
+ RebindTexture(GetActiveTexture());
+
+ return which;
+}
+
+TextureRef Textures::makeTextureColor(int width, int height, GLint internal_format, GLint format, float red, float green, float blue, float alpha, bool mipmap) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ unsigned int t = makeTextureColorInternal(width, height, internal_format, format, red, green, blue, alpha, mipmap);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureRef tex_ref(t);
+ DecrementRefCount(t);
+ return tex_ref;
+}
+
+unsigned int Textures::makeTextureTestPatternInternal(int width, int height, GLint internal_format, GLint format, bool mipmap) {
+ CHECK_GL_ERROR();
+ unsigned which = AllocateFreeSlot();
+
+ Texture& texture = textures[which];
+ texture.Init();
+ texture.width = width;
+ texture.height = height;
+ texture.internal_format = internal_format;
+ texture.format = format;
+ texture.tex_target = GL_TEXTURE_2D;
+
+ std::vector<GLubyte> data(width*height*4);
+
+ int index;
+ int multiple = width/32;
+ int cornerWidth = multiple*4;
+ for (int j = 0; j < width; j++) {
+ for (int i = 0; i < height; i++) {
+ index = (i+j*width)*4;
+ //Color the corners
+ if( (i < cornerWidth && j < cornerWidth ) ||
+ (i > height-cornerWidth && j > height-cornerWidth) ||
+ (i > height-cornerWidth && j < cornerWidth ) ||
+ (i < cornerWidth && j > height-cornerWidth) )
+ {
+ data[index+0] = 255;
+ }
+ else
+ {
+ data[index+0] = ((i/multiple+j/multiple)%2)*255;
+ }
+
+ data[index+1] = data[index];
+ data[index+2] = data[index];
+ data[index+3] = 128;
+ }
+ }
+
+ glGenTextures(1, &texture.gl_texture_id);
+ LOG_ASSERT(texture.gl_texture_id != INVALID_ID);
+ LOG_ASSERT(texture.gl_texture_id != UNLOADED_ID);
+
+ glBindTexture(texture.tex_target, texture.gl_texture_id);
+ if(internal_format == GL_RGBA16F) {
+ if(GLEW_ARB_texture_float) {
+ glTexImage2D(texture.tex_target, 0, internal_format, width, height, 0,
+ format, GL_FLOAT, &data[0]);
+ } else {
+ glTexImage2D(texture.tex_target, 0, GL_RGBA, width, height, 0,
+ format, GL_UNSIGNED_BYTE, &data[0]);
+ }
+ } else {
+ glTexImage2D(texture.tex_target, 0, internal_format, width, height, 0,
+ format, GL_UNSIGNED_BYTE, &data[0]);
+ }
+ if (mipmap) glTexParameteri(texture.tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ else glTexParameteri(texture.tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ if(mipmap) {
+ glGenerateMipmap(texture.tex_target);
+ }//GLuint
+ CHECK_GL_ERROR();
+
+ RebindTexture(GetActiveTexture());
+
+ return which;
+}
+
+TextureRef Textures::makeTextureTestPattern(int width, int height, GLint internal_format, GLint format, bool mipmap) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ unsigned int t = makeTextureTestPatternInternal(width, height, internal_format, format, mipmap);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureRef tex_ref(t);
+ DecrementRefCount(t);
+ return tex_ref;
+}
+
+
+unsigned int Textures::makeArrayTextureInternal(unsigned int num_slices, unsigned char flags) {
+ unsigned which = AllocateFreeSlot();
+
+ Texture& texture = textures[which];
+ texture.Init();
+ texture.num_slices = 0;
+ texture.sub_textures.reserve(num_slices);
+ texture.use_srgb = (flags & PX_SRGB) != 0;
+ texture.no_mipmap = (flags & PX_NOMIPMAP)!=0;
+ texture.no_reduce = (flags & PX_NOREDUCE)!=0;
+ texture.no_live_update = (flags & PX_NOLIVEUPDATE) != 0;;
+
+ texture.wrap_s = m_wrap_s;
+ texture.wrap_t = m_wrap_t;
+ texture.min_filter = min_filter;
+ texture.mag_filter = mag_filter;
+ texture.tex_target = GL_TEXTURE_2D_ARRAY;
+
+ return which;
+}
+
+
+unsigned int Textures::makeCubemapArrayTextureInternal(int num_slices, int width, int height, GLint internal_format, GLint format, unsigned char flags) {
+ unsigned which = AllocateFreeSlot();
+
+ Texture& texture = textures[which];
+ texture.Init();
+ texture.num_slices = num_slices;
+ texture.cube_map = true;
+ texture.width = width;
+ texture.height = height;
+ texture.tex_target = GL_TEXTURE_CUBE_MAP_ARRAY_ARB;
+
+ texture.use_srgb = (flags & PX_SRGB) != 0;
+ texture.no_mipmap = (flags & PX_NOMIPMAP)!=0;
+ texture.no_reduce = (flags & PX_NOREDUCE)!=0;
+
+ CHECK_GL_ERROR();
+ glGenTextures(1, &texture.gl_texture_id);
+ LOG_ASSERT(texture.gl_texture_id != INVALID_ID);
+ LOG_ASSERT(texture.gl_texture_id != UNLOADED_ID);
+
+ CHECK_GL_ERROR();
+ glBindTexture(texture.tex_target, texture.gl_texture_id);
+ if(texture.no_mipmap){
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ } else {
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ }
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+ CHECK_GL_ERROR();
+ int level = 0;
+ std::vector<float> pixels(width*height*num_slices*6*4);
+ for(int i=0, len=pixels.size(); i<len; i+=16){
+ pixels[i+0] = 1.0f;
+ pixels[i+1] = 0.0f;
+ pixels[i+2] = 0.0f;
+ pixels[i+3] = 0.0f;
+
+ pixels[i+4] = 0.0f;
+ pixels[i+5] = 1.0f;
+ pixels[i+6] = 0.0f;
+ pixels[i+7] = 0.0f;
+
+ pixels[i+8] = 0.0f;
+ pixels[i+9] = 0.0f;
+ pixels[i+10] = 1.0f;
+ pixels[i+11] = 0.0f;
+
+ pixels[i+12] = 1.0f;
+ pixels[i+13] = 1.0f;
+ pixels[i+14] = 1.0f;
+ pixels[i+15] = 1.0f;
+ }
+ CHECK_GL_ERROR();
+ glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY_ARB, level, internal_format, width, height, num_slices*6, 0, format, GL_FLOAT, &pixels[0]);
+ CHECK_GL_ERROR();
+ glGenerateMipmap(GL_TEXTURE_CUBE_MAP_ARRAY_ARB);
+ CHECK_GL_ERROR();
+ return which;
+}
+
+
+unsigned int Textures::makeFlatCubemapArrayTextureInternal(int num_slices, int width, int height, GLint internal_format, GLint format, unsigned char flags) {
+ unsigned which = AllocateFreeSlot();
+
+ Texture& texture = textures[which];
+ texture.Init();
+ texture.num_slices = num_slices;
+ texture.width = width*6;
+ texture.height = height;
+ texture.tex_target = GL_TEXTURE_2D_ARRAY;
+
+ texture.format = format;
+ texture.internal_format = internal_format;
+
+ texture.use_srgb = (flags & PX_SRGB) != 0;
+ texture.no_mipmap = (flags & PX_NOMIPMAP)!=0;
+ texture.no_reduce = (flags & PX_NOREDUCE)!=0;
+
+ CHECK_GL_ERROR();
+ glGenTextures(1, &texture.gl_texture_id);
+ LOG_ASSERT(texture.gl_texture_id != INVALID_ID);
+ LOG_ASSERT(texture.gl_texture_id != UNLOADED_ID);
+
+ CHECK_GL_ERROR();
+ glBindTexture(texture.tex_target, texture.gl_texture_id);
+ texture.wrap_s = m_wrap_s;
+ texture.wrap_t = m_wrap_t;
+ texture.min_filter = min_filter;
+ texture.mag_filter = mag_filter;
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MIN_FILTER, texture.min_filter);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MAG_FILTER, texture.mag_filter);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_S, texture.wrap_s);
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_T, texture.wrap_t);
+ CHECK_GL_ERROR();
+ std::vector<float> pixels(width*height*num_slices*6*4, 0.0f);
+ glTexImage3D(texture.tex_target, 0, internal_format, width*6, height, num_slices, 0, format, GL_FLOAT, &pixels[0]);
+ glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
+ CHECK_GL_ERROR();
+
+ RebindTexture(GetActiveTexture());
+ return which;
+}
+
+TextureRef Textures::getDetailColorArray() {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ if (!detail_color_texture_ref.valid()) {
+ setWrap(GL_REPEAT, GL_REPEAT);
+ unsigned int t = makeArrayTextureInternal(0, PX_SRGB);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ detail_color_texture_ref = TextureRef(t);
+ DecrementRefCount(t);
+ SetTextureName(detail_color_texture_ref, "Detail Texture Array - Color");
+ } else {
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ }
+
+ return detail_color_texture_ref;
+}
+
+
+TextureRef Textures::getDetailNormalArray() {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ if (!detail_normal_texture_ref.valid()) {
+ setWrap(GL_REPEAT, GL_REPEAT);
+ unsigned int t = makeArrayTextureInternal(0, 0);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ detail_normal_texture_ref = TextureRef(t);
+ DecrementRefCount(t);
+ SetTextureName(detail_normal_texture_ref, "Detail Texture Array - Normals");
+ } else {
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ }
+
+ return detail_normal_texture_ref;
+}
+
+TextureRef Textures::makeArrayTexture(unsigned int num_slices, unsigned char flags) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ unsigned int t = makeArrayTextureInternal(num_slices, flags);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureRef tex_ref(t);
+ DecrementRefCount(t);
+ return tex_ref;
+}
+
+TextureRef Textures::makeCubemapArrayTexture(int num_slices, int width, int height, GLint internal_format, GLint format, unsigned char flags) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ unsigned int t = makeCubemapArrayTextureInternal(num_slices, width, height, internal_format, format, flags);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureRef tex_ref(t);
+ DecrementRefCount(t);
+ return tex_ref;
+}
+
+TextureRef Textures::makeFlatCubemapArrayTexture(int num_slices, int width, int height, GLint internal_format, GLint format, unsigned char flags) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ unsigned int t = makeFlatCubemapArrayTextureInternal(num_slices, width, height, internal_format, format, flags);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureRef tex_ref(t);
+ DecrementRefCount(t);
+ return tex_ref;
+}
+
+
+bool IsCompressedFile(char* path) {
+ int end = strlen(path) - 1;
+ if (end >= 3 &&
+ path[end - 3] == '.'
+ && (
+ (path[end - 2] == 'd' &&
+ path[end - 1] == 'd' &&
+ path[end - 0] == 's')
+ ||
+ (path[end - 2] == 'c' &&
+ path[end - 1] == 'r' &&
+ path[end - 0] == 'n')
+ ))
+ {
+ return true;
+ }
+ return false;
+}
+
+int Textures::loadTexture(const std::string& rel_path, unsigned int which, unsigned char flags) {
+ static const int kBufSize = 256;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "loadTexture: %s", rel_path.c_str());
+ PROFILER_ZONE_DYNAMIC_STRING(g_profiler_ctx, buf);
+
+ // Extract flag bools from bitfield
+ //bool no_convert = (flags & PX_NOCONVERT)!=0;
+ bool no_mipmap = (flags & PX_NOMIPMAP)!=0;
+ bool use_srgb = (flags & PX_SRGB)!=0;
+ bool no_live_update = (flags & PX_NOLIVEUPDATE)!=0;
+ bool no_reduce = (flags & PX_NOREDUCE)!=0;
+ bool no_convert = (flags & PX_NOCONVERT)!=0;
+
+ if(config["no_texture_convert"].toBool() == true){
+ no_convert = true;
+ }
+
+ // Get absolute path to texture (or compressed equivalent)
+ char abs_path[kPathSize];
+ PathFlags res_source;
+ ModID modsource;
+ int err = FindImagePath(rel_path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths | kWriteDir | kAbsPath | kModWriteDirs, true, &res_source, true, &modsource );
+ if(err == -1){
+ //DisplayError("Error", "Could not find texture: %s", rel_path.c_str());
+ return kLoadErrorMissingFile;
+ }
+
+ // Check if dds file
+ bool dds_suffix = IsCompressedFile(abs_path);
+
+ if(!dds_suffix && !no_convert && err == 0 && res_source != kAbsPath){
+ std::string dst_path = AssemblePath(GetWritePath(modsource), SanitizePath(rel_path + convert_file_type));
+ if(process_pool->NumProcesses() > 0){
+ std::ostringstream oss;
+ std::string src_complete = abs_path;
+ GetCompletePath(&src_complete);
+ std::string dst_complete = dst_path;
+ GetCompletePath(&dst_complete);
+ std::string tmp_complete = GetTempDDSPath(dst_path,true,conversion_num);
+ GetCompletePath(&tmp_complete);
+ oss << "ConvertTexture \"" << src_complete << "\" \"" << dst_complete <<
+ "\" \"" << tmp_complete << "\"";
+ ++conversion_num;
+ process_pool->Schedule(oss.str());
+ } else {
+ ConvertImage(abs_path, dst_path, GetTempDDSPath(dst_path,true,conversion_num), TextureData::Fast);
+ }
+
+ strncpy(abs_path, dst_path.c_str(), kPathSize-1);
+ }
+
+ if(which>=textures.size()){
+ textures.resize(which+1);
+ }
+ Texture& tex = textures[which];
+
+ tex.Init();
+ tex.flags = flags;
+ tex.sub_textures.resize(1);
+ tex.sub_textures[0].texture_name = rel_path;
+ tex.sub_textures[0].load_name = abs_path;
+ tex.sub_textures[0].modsource = modsource;
+ tex.no_mipmap = no_mipmap;
+ tex.no_reduce = no_reduce;
+ tex.no_live_update = no_live_update;
+ tex.sub_textures[0].orig_modified = GetDateModifiedInt64(abs_path);
+ tex.sub_textures[0].texture_data = new TextureData();
+
+ // Try loading as .crn
+ if (dds_suffix) {
+ std::string crn_path(abs_path);
+ unsigned int crnlength = crn_path.length();
+ crn_path[crnlength - 3] = 'c';
+ crn_path[crnlength - 2] = 'r';
+ crn_path[crnlength - 1] = 'n';
+ if (FileExists(crn_path.c_str(), kDataPaths | kModPaths)) {
+ PROFILER_ZONE(g_profiler_ctx, "Loading crunch texture");
+ if(!tex.sub_textures[0].texture_data->Load(crn_path.c_str())) {
+ //DisplayError("Error", "Failed to load texture: %s", crn_path.c_str());
+ return kLoadErrorMissingFile;
+ }
+ } else {
+ PROFILER_ZONE(g_profiler_ctx, "Loading dds texture");
+ if (!tex.sub_textures[0].texture_data->Load(abs_path)) {
+ //DisplayError("Error", "Failed to load texture: %s", abs_path);
+ return kLoadErrorMissingFile;
+ }
+
+ ConvertToDXT5IfNeeded(tex.sub_textures[0], modsource);
+ }
+ } else {
+ PROFILER_ZONE(g_profiler_ctx, "Loading uncompressed texture data");
+ tex.sub_textures[0].texture_data->Load(abs_path);
+ }
+
+ tex.width = tex.sub_textures[0].texture_data->GetWidth();
+ tex.height = tex.sub_textures[0].texture_data->GetHeight();
+ if(/*Graphics::Instance()->config_.texture_reduce() && */!tex.no_reduce) {
+ //int extra_reduce = 1;
+ //if(tex.flags & PX_NOCONVERT){
+ // extra_reduce = 0;
+ //}
+ }
+ // TODO: generate mipmaps?
+
+ // Check if width and height are powers of two
+ bool pot = IsPow2(tex.width) && IsPow2(tex.height);
+
+ if(!pot && !(tex.no_reduce && tex.no_mipmap)){
+ std::ostringstream oss;
+ oss << "Dimensions of " << rel_path << " are " << tex.width
+ << " x " << tex.height << ", should be powers of two.";
+ DisplayError("Warning",oss.str().c_str());
+ }
+
+ tex.wrap_s = m_wrap_s;
+ tex.wrap_t = m_wrap_t;
+ tex.min_filter = min_filter;
+ tex.mag_filter = mag_filter;
+
+ tex.use_srgb = use_srgb;
+ return kLoadOk;
+}
+
+
+void Textures::loadArraySlice(const TextureRef& texref, unsigned int slice, const std::string& rel_path) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+
+ LOG_ASSERT(texref.valid());
+
+ Texture &tex = textures[texref.id];
+ if (tex.gl_texture_id != UNLOADED_ID) {
+ tex.width = 0;
+ tex.height = 0;
+ // adding new slices, need to reupload texture to gl
+ // can't free old texture right here, we're not on main thread
+ LOGI << "Deferring delete of texture " << tex.gl_texture_id << std::endl;
+ deferred_delete_textures.push_back(tex.gl_texture_id);
+ tex.gl_texture_id = UNLOADED_ID;
+ }
+
+ // cube map arrays not supported yet, maybe not ever since it's GL 4.0 feature
+ LOG_ASSERT(!tex.cube_map);
+
+ char abs_path[kPathSize];
+ ModID modsource;
+ int err = FindImagePath(rel_path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths | kWriteDir | kModWriteDirs, true, NULL, true, &modsource);
+ if(err == -1){
+ FatalError("Error", "Could not find texture: %s", rel_path.c_str());
+ }
+
+ bool dds_suffix = IsCompressedFile(abs_path);
+
+ std::string dst_path = std::string(abs_path);
+
+ if(!dds_suffix && err == 0){
+ dst_path = AssemblePath(GetWritePath(modsource), SanitizePath(rel_path + convert_file_type));
+ if(process_pool->NumProcesses() > 0){
+ std::ostringstream oss;
+ std::string src_complete = abs_path;
+ GetCompletePath(&src_complete);
+ std::string dst_complete = dst_path;
+ GetCompletePath(&dst_complete);
+ std::string tmp_complete = GetTempDDSPath(dst_path,true,conversion_num);
+ GetCompletePath(&tmp_complete);
+ oss << "ConvertTexture \"" << src_complete << "\" \"" << dst_complete <<
+ "\" \"" << tmp_complete << "\"";
+ ++conversion_num;
+ process_pool->Schedule(oss.str());
+ } else {
+ ConvertImage(abs_path, dst_path, GetTempDDSPath(dst_path,true,conversion_num), TextureData::Fast);
+ dds_suffix = true;
+ }
+ strncpy(abs_path, dst_path.c_str(), kPathSize-1);
+ }
+
+ if (slice >= tex.num_slices) {
+ tex.num_slices++;
+ tex.sub_textures.resize(tex.num_slices);
+ LOG_ASSERT(slice < tex.num_slices);
+ }
+ tex.sub_textures[slice].texture_name = rel_path;
+ tex.sub_textures[slice].load_name = abs_path;
+ tex.sub_textures[slice].modsource = modsource;
+ tex.sub_textures[slice].orig_modified = GetDateModifiedInt64(abs_path);
+
+ const bool kPrintAllSliceLoads = false;
+
+ tex.sub_textures[slice].texture_data = new TextureData();
+
+ // Try loading as .crn
+ if (dds_suffix) {
+ std::string crn_path(dst_path);
+ unsigned int crnlength = crn_path.length();
+ crn_path[crnlength - 3] = 'c';
+ crn_path[crnlength - 2] = 'r';
+ crn_path[crnlength - 1] = 'n';
+ if (FileExists(crn_path.c_str(), kDataPaths | kModPaths)) {
+ if (!tex.sub_textures[slice].texture_data->Load(crn_path.c_str())) {
+ FatalError("Error", "Failed to load texture: %s", crn_path.c_str());
+ }
+ } else {
+ if (!tex.sub_textures[slice].texture_data->Load(dst_path.c_str())) {
+ FatalError("Error", "Failed to load texture: %s", dst_path.c_str());
+ }
+
+ ConvertToDXT5IfNeeded(tex.sub_textures[slice], modsource);
+ }
+ } else {
+ tex.sub_textures[slice].texture_data->Load(abs_path);
+ }
+
+ // TODO: generate mipmaps?
+ if(kPrintAllSliceLoads){
+ LOGI << "Loading texture array slice " << slice << " from " << abs_path
+ << " size " << tex.sub_textures[slice].texture_data->GetWidth()
+ << "x" << tex.sub_textures[slice].texture_data->GetHeight()
+ << std::endl;
+ }
+
+ // first slice we load determines if we're compressed
+ if (tex.num_slices == 1) {
+ LOG_ASSERT(tex.width == 0);
+ LOG_ASSERT(tex.height == 0);
+
+ int width = tex.sub_textures[slice].texture_data->GetWidth();
+ int height = tex.sub_textures[slice].texture_data->GetHeight();
+
+ // Check if width and height are powers of two
+ bool pot = IsPow2(width) && IsPow2(height);
+
+ if(!pot && !(tex.no_reduce && tex.no_mipmap)){
+ std::ostringstream oss;
+ oss << "Dimensions of " << rel_path << " are " << width
+ << " x " << height << ", should be powers of two.";
+ DisplayError("Error",oss.str().c_str());
+ }
+ }
+
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+}
+
+
+unsigned int Textures::loadArraySlice(const TextureRef& texref, const std::string& rel_path) {
+ unsigned int slice;
+
+ {
+ PROFILED_TEXTURE_MUTEX_LOCK
+
+ LOG_ASSERT(texref.valid());
+ Texture &tex = textures[texref.id];
+
+ // check if it's already loaded
+ bool first_slice = tex.sub_textures.empty();
+ // tex.compressed is only valid if we already have loaded something
+ // and if we're loading the first slice we can't reuse anything anyway
+ if (!first_slice) {
+ for (slice = 0; slice < tex.sub_textures.size(); slice++) {
+ if (rel_path == tex.sub_textures[slice].texture_name) {
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ return slice;
+ break;
+ }
+ }
+ } else {
+ slice = 0;
+ }
+
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ }
+
+ loadArraySlice(texref, slice, rel_path);
+
+ return slice;
+}
+
+
+unsigned int Textures::makeRectangularTextureInternal(int width, int height, GLint internal_format, GLint format) {
+ CHECK_GL_ERROR();
+ unsigned which = AllocateFreeSlot();
+
+ Texture& texture = textures[which];
+ texture.Init();
+ texture.width = width;
+ texture.height = height;
+ texture.internal_format = internal_format;
+ texture.format = format;
+ texture.tex_target = GL_TEXTURE_2D;
+
+ glGenTextures(1, &texture.gl_texture_id);
+ LOG_ASSERT(texture.gl_texture_id != INVALID_ID);
+ LOG_ASSERT(texture.gl_texture_id != UNLOADED_ID);
+
+ CHECK_GL_ERROR();
+ glBindTexture(texture.tex_target, texture.gl_texture_id);
+ CHECK_GL_ERROR();
+ unsigned int totalTextureSize = 0;
+ if(internal_format == GL_RGBA16F || internal_format == GL_RGBA32F || internal_format == GL_DEPTH_COMPONENT
+ || internal_format == GL_DEPTH_COMPONENT24) {
+ if(Graphics::Instance()->features().HDR_enable()) {
+ glTexImage2D(texture.tex_target, 0, internal_format, width, height, 0,
+ format, GL_UNSIGNED_BYTE, NULL);
+ totalTextureSize += width*height*8;
+ int level = 0;
+ while(width > 1 || height > 1){
+ width = max(1, width/2);
+ height = max(1, height/2);
+ ++level;
+ glTexImage2D(texture.tex_target, level, internal_format, width, height, 0,
+ format, GL_UNSIGNED_BYTE, NULL);
+ totalTextureSize += width*height*8;
+ }
+ CHECK_GL_ERROR();
+ } else {
+ glTexImage2D(texture.tex_target, 0, GL_RGBA, width, height, 0,
+ format, GL_UNSIGNED_BYTE, NULL);
+ totalTextureSize += width*height*4;
+ int level = 0;
+ while(width > 1 || height > 1){
+ width = max(1, width/2);
+ height = max(1, height/2);
+ ++level;
+ glTexImage2D(texture.tex_target, level, GL_RGBA, width, height, 0,
+ format, GL_UNSIGNED_BYTE, NULL);
+ totalTextureSize += width*height*4;
+ }
+ CHECK_GL_ERROR();
+ }
+ } else {
+ glTexImage2D(texture.tex_target, 0, internal_format, width, height, 0,
+ format, GL_UNSIGNED_BYTE, NULL);
+ totalTextureSize += width*height*4;
+ int level = 0;
+ while(width > 1 || height > 1){
+ width = max(1, width/2);
+ height = max(1, height/2);
+ ++level;
+ glTexImage2D(texture.tex_target, level, internal_format, width, height, 0,
+ format, GL_UNSIGNED_BYTE, NULL);
+ totalTextureSize += width*height*4;
+ }
+ CHECK_GL_ERROR();
+ }
+ CHECK_GL_ERROR();
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ CHECK_GL_ERROR();
+ glTexParameteri(texture.tex_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ CHECK_GL_ERROR();
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ CHECK_GL_ERROR();
+ glTexParameteri(texture.tex_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ CHECK_GL_ERROR();
+
+ CHECK_GL_ERROR();
+ glTexParameteri(texture.tex_target, GL_TEXTURE_COMPARE_MODE, GL_NONE);
+
+ CHECK_GL_ERROR();
+
+ texture.wrap_s = GL_CLAMP_TO_EDGE;
+ texture.wrap_t = GL_CLAMP_TO_EDGE;
+ texture.mag_filter = GL_LINEAR;
+ texture.min_filter = GL_LINEAR_MIPMAP_LINEAR;
+
+ return which;
+}
+
+TextureRef Textures::makeRectangularTexture(int width, int height, GLint internal_format, GLint format) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ unsigned int t = makeRectangularTextureInternal(width, height, internal_format, format);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureRef tex_ref(t);
+ DecrementRefCount(t);
+ return tex_ref;
+}
+
+void Textures::SetTextureName(const TextureRef& texture_ref, const char* name) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ if( textures[texture_ref.id].notify_on_dispose ) {
+ LOGI << "Giving texture with gl texture id " << textures[texture_ref.id].gl_texture_id << " the name " << name << std::endl;
+ }
+
+ textures[texture_ref.id].name = name;
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+}
+
+unsigned int Textures::makeBufferTextureInternal(size_t size, GLint internal_format, const void* data) {
+ CHECK_GL_ERROR();
+ unsigned which = AllocateFreeSlot();
+
+ Texture& texture = textures[which];
+ texture.Init();
+ texture.width = 0;
+ texture.height = 0;
+ texture.size = size;
+ texture.internal_format = internal_format;
+ texture.tex_target = GL_TEXTURE_BUFFER;
+
+ glGenBuffers(1, &texture.gl_buffer_id);
+ CHECK_GL_ERROR();
+ glBindBuffer(GL_TEXTURE_BUFFER, texture.gl_buffer_id);
+ glBufferData(GL_TEXTURE_BUFFER, size, data, GL_DYNAMIC_DRAW);
+ CHECK_GL_ERROR();
+
+ glGenTextures(1, &texture.gl_texture_id);
+ LOG_ASSERT(texture.gl_texture_id != INVALID_ID);
+ LOG_ASSERT(texture.gl_texture_id != UNLOADED_ID);
+
+ CHECK_GL_ERROR();
+ glBindTexture(texture.tex_target, texture.gl_texture_id);
+ glTexBuffer(texture.tex_target, internal_format, texture.gl_buffer_id);
+
+ CHECK_GL_ERROR();
+
+ return which;
+}
+
+TextureRef Textures::makeBufferTexture(size_t size, GLint internal_format, const void* data) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ unsigned int t = makeBufferTextureInternal(size, internal_format, data);
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureRef tex_ref(t);
+ DecrementRefCount(t);
+ return tex_ref;
+}
+
+//Return the OpenGL id of a texture
+GLuint Textures::returnTexture(const TextureRef &which_texture) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ GLuint ret = textures[which_texture.id].gl_texture_id;
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ return ret;
+}
+
+GLuint Textures::returnTexture(const TextureAssetRef &which_texture) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ GLuint ret = textures[which_texture->id].gl_texture_id;
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ return ret;
+}
+
+int Textures::getWidth(const TextureRef &which) {
+ return textures[which.id].width;
+}
+
+int Textures::getHeight(const TextureRef &which) {
+ return textures[which.id].height;
+}
+
+int Textures::getWidth(const TextureAssetRef &which) {
+ return textures[which->id].width;
+}
+
+int Textures::getHeight(const TextureAssetRef &which) {
+ return textures[which->id].height;
+}
+
+/**
+ * @brief Returns the width multiplied with the texture reduction factor if the texture is reduced
+ */
+int Textures::getReducedWidth(const TextureRef &which){
+ return (int) (textures[which.id].width * (textures[which.id].no_reduce ? 1.0f : Graphics::Instance()->config_.texture_reduction_factor()));
+}
+
+/**
+ * @brief Returns the height multiplied with the texture reduction factor if the texture is reduced
+ */
+int Textures::getReducedHeight(const TextureRef &which) {
+ return (int) (textures[which.id].height * (textures[which.id].no_reduce ? 1.0f : Graphics::Instance()->config_.texture_reduction_factor()));
+}
+
+/**
+ * @brief Returns the width multiplied with the texture reduction factor if the texture is reduced
+ */
+int Textures::getReducedWidth(const TextureAssetRef &which){
+ return (int) (textures[which->id].width * (textures[which->id].no_reduce ? 1.0f : Graphics::Instance()->config_.texture_reduction_factor()));
+}
+
+/**
+ * @brief Returns the height multiplied with the texture reduction factor if the texture is reduced
+ */
+int Textures::getReducedHeight(const TextureAssetRef &which) {
+ return (int) (textures[which->id].height * (textures[which->id].no_reduce ? 1.0f : Graphics::Instance()->config_.texture_reduction_factor()));
+}
+
+void Textures::EnsureInVRAM(const TextureAssetRef &id) {
+ EnsureInVRAM(id->id);
+}
+
+void Textures::EnsureInVRAM(const TextureRef &id) {
+ EnsureInVRAM(id.id);
+}
+
+void Textures::EnsureInVRAM(int which_texture) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ if(textures[which_texture].gl_texture_id == UNLOADED_ID) {
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureToVRAM(which_texture);
+ } else {
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ }
+}
+
+void Textures::RebindTexture(int unit) {
+ if(bound_texture[unit] == INVALID_ID) {
+ return;
+ }
+ Texture &texture = textures[bound_texture[unit]];
+ // GL_INVALID_OPERATION raised on first level load without this check
+ if( texture.ref_count <= 0 ) {
+ LOGE << "Trying to rebind texture with no references " << texture.name << std::endl;
+ } else if (texture.gl_texture_id == INVALID_ID || texture.gl_texture_id == UNLOADED_ID) {
+ LOGE << "Trying to rebind invalid texture id." << std::endl;
+ } else {
+ glBindTexture(texture.tex_target, texture.gl_texture_id);
+ }
+}
+
+void Textures::SetActiveTexture(int which_unit) {
+ if(active_texture != which_unit){
+ glActiveTexture(GL_TEXTURE0+which_unit);
+ active_texture = which_unit;
+ }
+}
+
+int Textures::GetActiveTexture() {
+ return active_texture;
+}
+
+void Textures::bindTexture(const TextureAssetRef &which_texture_ref, int which_unit) {
+ bindTexture(which_texture_ref->GetTextureRef(), which_unit);
+}
+
+void Textures::bindTexture(const TextureRef &which_texture_ref, int which_unit) {
+ bindTexture(which_texture_ref.id, which_unit);
+}
+
+void Textures::bindTexture(const unsigned int &which_texture, int which_unit) {
+ CHECK_GL_ERROR();
+ if(which_texture == INVALID_ID) {
+ LOGE << "Binding invalid teture ID" << std::endl;
+ return;
+ }
+ CHECK_GL_ERROR();
+ //Only bind this texture if it is not already bound, to save on overhead
+ PROFILED_TEXTURE_MUTEX_LOCK
+
+ if( textures[which_texture].ref_count <= 0 ) {
+ LOGE << "Trying to rebind texture with no references " << textures[which_texture].name << std::endl;
+ }
+
+ bool load_to_vram = textures[which_texture].gl_texture_id == UNLOADED_ID;
+ if(load_to_vram) {
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ TextureToVRAM(which_texture);
+ PROFILED_TEXTURE_MUTEX_LOCK
+ }
+ if(textures[which_texture].gl_texture_id == UNLOADED_ID){
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ return;
+ }
+
+ SetActiveTexture(which_unit);
+
+ if (bound_texture[which_unit] != INVALID_ID) {
+ // if new texture has different target (2d, cube map) than previous
+ // clear the previous bind
+ GLenum old_tex_target = textures[bound_texture[which_unit]].tex_target;
+ if (textures[which_texture].tex_target != old_tex_target) {
+ if( old_tex_target != GL_NONE ) {
+ glBindTexture(old_tex_target, 0);
+ }
+ }
+ }
+
+ bound_texture[which_unit]=which_texture;
+ RebindTexture(which_unit);
+ Shaders* shaders = Shaders::Instance();
+ if(shaders->bound_program!=-1) {
+ shaders->SetUniformInt(shaders->GetTexUniform(which_unit), which_unit);
+ }
+
+ CHECK_GL_ERROR();
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ return;
+}
+
+void Textures::InvalidateBindCacheInternal() {
+ SetActiveTexture(1); // To force update in case active_texture this is out of sync too
+ SetActiveTexture(0);
+ for (unsigned int i=0; i<_texture_units; i++) {
+ if (bound_texture[i] != INVALID_ID) {
+ SetActiveTexture(i);
+ if (textures[bound_texture[i]].cube_map) {
+ glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
+ } else {
+ glBindTexture(GL_TEXTURE_2D, 0);
+ }
+ bound_texture[i] = INVALID_ID;
+ }
+ }
+}
+
+void Textures::InvalidateBindCache() {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ InvalidateBindCacheInternal();
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+}
+
+void Textures::bindBlankTexture(int which_unit) {
+ if(!blank_texture_ref.valid()) {
+ blank_texture_ref = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/diffuse.tga", PX_SRGB, 0x0);
+ }
+ bindTexture(blank_texture_ref->GetTextureRef(), which_unit);
+}
+
+void Textures::bindBlankNormalTexture(int which_unit) {
+ if(!blank_normal_texture_ref.valid()) {
+ blank_normal_texture_ref = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/diffusebump.tga");
+ }
+ bindTexture(blank_normal_texture_ref->GetTextureRef(), which_unit);
+}
+
+const TextureRef Textures::GetBlankTextureRef() {
+ if(!blank_texture_ref.valid()) {
+ blank_texture_ref = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/diffuse.tga", PX_SRGB, 0x0);
+ }
+ return blank_texture_ref->GetTextureRef();
+}
+
+const TextureAssetRef Textures::GetBlankTextureAssetRef() {
+ if(!blank_texture_ref.valid()) {
+ blank_texture_ref = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/diffuse.tga", PX_SRGB, 0x0);
+ }
+ return blank_texture_ref;
+}
+
+const TextureRef Textures::GetBlankNormalTextureRef() {
+ if(!blank_normal_texture_ref.valid()) {
+ blank_normal_texture_ref = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/diffusebump.tga");
+ }
+ return blank_normal_texture_ref->GetTextureRef();
+}
+
+const TextureAssetRef Textures::GetBlankNormalTextureAssetRef() {
+ if(!blank_normal_texture_ref.valid()) {
+ blank_normal_texture_ref = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/diffusebump.tga");
+ }
+ return blank_normal_texture_ref;
+}
+
+bool Textures::IsRenderable(const TextureRef& texture) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ bool ret = true;
+ if (texture.valid()) {
+ const Texture &t = textures[texture.id];
+ if (t.sub_textures.size() != 1) {
+ ret = false; // array or empty texture
+ } else {
+ const TextureData* td = t.sub_textures[0].texture_data;
+ if (td != NULL) {
+ ret = false; // loaded from disk
+ }
+ }
+ } else {
+ DisplayError("Error", "Checking if an invalid texture is renderable");
+ ret = false;
+ }
+
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ return ret;
+}
+
+bool Textures::IsCompressed(const TextureAssetRef& texture) {
+ return IsCompressed(texture->GetTextureRef());
+}
+
+bool Textures::IsCompressed(const TextureRef& texture) {
+ PROFILED_TEXTURE_MUTEX_LOCK
+ bool ret = false;
+ if(texture.valid()){
+ const Texture &t = textures[texture.id];
+ if (t.sub_textures.empty()) {
+ //DisplayError("Error","Checking if an empty texture is compressed");
+ ret = false;
+ } else {
+ const TextureData* td = t.sub_textures[0].texture_data;
+ if (td == NULL) {
+ // WHY does this happen?
+ //DisplayError("Error","Checking if an empty texture is compressed");
+ ret = false;
+ } else {
+ assert(td != NULL);
+ ret = td->IsCompressed();
+ }
+ }
+ } else {
+ DisplayError("Error","Checking if an invalid texture is compressed");
+ ret = false;
+ }
+
+ PROFILED_TEXTURE_MUTEX_UNLOCK
+ return ret;
+}
+
+bool Textures::ReloadAsCompressed(const TextureAssetRef& texref) {
+ return ReloadAsCompressed(texref->GetTextureRef());
+}
+
+bool Textures::ReloadAsCompressed(const TextureRef& texref) {
+ // takes an uncompressed texture
+ // saves it in compressed format
+ // and replaces the current one with that
+ LOG_ASSERT(texref.valid());
+
+ Texture &texture = textures[texref.id];
+
+ LOG_ASSERT(texture.sub_textures.size() == 1);
+ LOG_ASSERT(!texture.sub_textures[0].texture_data->IsCompressed());
+ std::string newName = texture.sub_textures[0].texture_name + "_converted.dds";
+ std::string newPath = AssemblePath(GetWritePath(texture.sub_textures[0].modsource), newName);
+
+ LOGI << "Compressing texture " << texture.sub_textures[0].texture_name << " saving to " << newPath << std::endl;
+
+ CreateParentDirs(newPath.c_str());
+
+ for (unsigned int i = 0; i < texture.sub_textures.size(); i++) {
+ TextureData *tex_data = texture.sub_textures[i].texture_data;
+ LOG_ASSERT(tex_data != NULL);
+ if (!tex_data->HasMipmaps()) {
+ tex_data->GenerateMipmaps();
+ }
+ LOG_ASSERT(tex_data->HasMipmaps());
+ tex_data->ConvertDXT(crnlib::PIXEL_FMT_DXT5, TextureData::Fast);
+ }
+
+ texture.sub_textures[0].texture_data->SaveDDS(newPath.c_str());
+
+ char abs_path[kPathSize];
+ ModID modsource;
+ if(FindImagePath(newName.c_str(), abs_path, kPathSize, kWriteDir | kModWriteDirs, true, NULL, true, &modsource) == -1) {
+ LOGE << "Texture not found after writing it" << std::endl;
+ return false;
+ }
+
+ RemoveTextureFromVRAM(texref.id);
+
+ texture.sub_textures[0].load_name = abs_path;
+ texture.sub_textures[0].modsource = modsource;
+
+ return true;
+}
+
+void Textures::SaveToPNG( int texture, std::string path ) {
+ GLint width, height;
+
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width );
+ glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height );
+
+ void* data = OG_MALLOC( sizeof(GLuint) * width * height );
+ glGetTexImage( GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, data );
+
+ ImageExport::SavePNG(path.c_str(), (unsigned char*)data, width, height);
+
+ OG_FREE(data);
+}
+
+void Textures::ResetVRAM() {
+ for(unsigned int i=0;i<textures.size();i++){
+ RemoveTextureFromVRAM(i);
+ }
+}
+
+void Textures::RemoveTextureFromVRAM( int which ) {
+ if(textures[which].gl_texture_id != UNLOADED_ID && textures[which].gl_texture_id != INVALID_ID){
+ if(glIsTexture(textures[which].gl_texture_id)){
+ glDeleteTextures(1,&textures[which].gl_texture_id);
+ }
+ textures[which].gl_texture_id = UNLOADED_ID;
+ }
+
+ if(textures[which].gl_buffer_id != UNLOADED_ID && textures[which].gl_buffer_id != INVALID_ID){
+ if(glIsBuffer(textures[which].gl_buffer_id)) {
+ glDeleteBuffers(1,&textures[which].gl_buffer_id);
+ }
+ textures[which].gl_buffer_id = UNLOADED_ID;
+ }
+}
+
+void Textures::ConvertToDXT5IfNeeded(SubTexture& texture, ModID modsource) const {
+ switch (texture.texture_data->GetGLInternalFormat()) {
+ case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
+ case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
+ case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
+ case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
+ case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
+ case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
+ {
+ std::string dst_path = AssemblePath(GetWritePath(modsource), SanitizePath(texture.texture_name + convert_file_type));
+ texture.texture_data->ConvertDXT(crnlib::PIXEL_FMT_DXT5, TextureData::Nice);
+
+ CreateParentDirs(dst_path);
+
+ texture.texture_data->SaveDDS(dst_path.c_str());
+
+ LOGW << texture.texture_name << " is not a DXT5 file, and will be converted" << std::endl;
+ }
+ }
+}
+
+Textures::~Textures() {
+ LOGI << "Waiting for process pool tasks to complete..." << std::endl;
+ process_pool->ClearQueuedTasks();
+ process_pool->WaitForTasksToComplete();
+ delete process_pool;
+}
+
+void Textures::SetProcessPoolsEnabled( bool val ) {
+ if(val){
+ LOGW << "Process pools feature disabled" << std::endl;
+ } else {
+ process_pool->Resize(0);
+ }
+}
+
+void Textures::ApplyAnisotropy() {
+ for (unsigned int i=0;i<textures.size();i++){
+ bindTexture(i,0);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, Graphics::Instance()->config_.anisotropy());
+ }
+}
+
+void Textures::updateBufferTexture(TextureRef& texture_ref, size_t size, const void* data) {
+ PROFILER_ZONE(g_profiler_ctx, "updateBufferTexture");
+ if (!texture_ref.valid()) {
+ return;
+ }
+ Texture &texture = textures[texture_ref.id];
+ LOG_ASSERT(texture.gl_texture_id != 0);
+ LOG_ASSERT(texture.gl_buffer_id != 0);
+ LOG_ASSERT(texture.tex_target == GL_TEXTURE_BUFFER);
+ glBindBuffer(GL_TEXTURE_BUFFER, texture.gl_buffer_id);
+ if (size > texture.size) {
+ PROFILER_ZONE(g_profiler_ctx, "Resize buffer");
+ // resize the buffer
+ glBufferData(GL_TEXTURE_BUFFER, size, NULL, GL_DYNAMIC_DRAW);
+ texture.size = size;
+ } else {
+ PROFILER_ZONE(g_profiler_ctx, "Discard? buffer");
+ glBufferData(GL_TEXTURE_BUFFER, texture.size, NULL, GL_DYNAMIC_DRAW);
+ glBindBuffer(GL_TEXTURE_BUFFER, texture.gl_buffer_id);
+ }
+ const bool kUseMapRange = false;
+ if(kUseMapRange) {
+ PROFILER_ZONE(g_profiler_ctx, "glMapBufferRange");
+ //Again, i don't believe that using unsynchronized here is necessarily safe, can we guarantee this?
+ //same as in vbocontainer
+ void* mapped = glMapBufferRange(GL_TEXTURE_BUFFER, 0, size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT );
+ //void* mapped = glMapBufferRange(GL_TEXTURE_BUFFER, 0, size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_INVALIDATE_RANGE_BIT );
+ CHECK_GL_ERROR();
+ {
+ PROFILER_ZONE(g_profiler_ctx, "memcpy");
+ memcpy(mapped, data, size);
+ }
+ glUnmapBuffer(GL_TEXTURE_BUFFER);
+ } else {
+ PROFILER_ZONE(g_profiler_ctx, "glBufferSubData");
+ glBufferSubData(GL_TEXTURE_BUFFER, 0, size, data);
+ }
+}
+
diff --git a/Source/Graphics/textures.h b/Source/Graphics/textures.h
new file mode 100644
index 00000000..54c4384e
--- /dev/null
+++ b/Source/Graphics/textures.h
@@ -0,0 +1,300 @@
+//-----------------------------------------------------------------------------
+// Name: textures.h
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: The textures class loads, manages, and binds textures
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+#include <Math/vec4.h>
+
+#include <Asset/Asset/texture.h>
+#include <Graphics/textureref.h>
+#include <Images/ddsformat.hpp>
+#include <Internal/modid.h>
+
+#include <opengl.h>
+
+#include <memory.h>
+#include <cstdlib>
+#include <climits>
+#include <vector>
+#include <mutex>
+
+#define TEX_SCREEN_DEPTH 1
+#define TEX_NOISE 2
+#define TEX_PURE_NOISE 3
+
+#define TEX_TONE_MAPPED 2
+#define TEX_INTERMEDIATE 3
+
+#define TEX_COLOR 0
+#define TEX_NORMAL 1
+#define TEX_SPEC_CUBEMAP 2
+#define TEX_SHADOW 4
+#define TEX_PROJECTED_SHADOW 5
+#define TEX_TRANSLUCENCY 5
+#define TEX_BLOOD 6
+#define TEX_FUR 7
+#define TEX_TINT_MAP 8
+
+#define TEX_DECAL_NORMAL 9
+#define TEX_DECAL_COLOR 10
+
+#define TEX_LIGHT_DECAL_DATA_BUFFER 15
+#define TEX_CLUSTER_BUFFER 13
+
+#define TEX_AMBIENT_GRID_DATA 11
+#define TEX_AMBIENT_COLOR_BUFFER 12
+
+
+//-----------------------------------------------------------------------------
+// Class Definition
+//-----------------------------------------------------------------------------
+namespace PhoenixTextures {
+ const unsigned int INVALID_ID = UINT_MAX;
+ const unsigned int UNLOADED_ID = UINT_MAX-1;
+ const unsigned int _texture_units = 32;
+
+ const int _default_wrap = GL_REPEAT;
+ const int _default_min = GL_LINEAR_MIPMAP_LINEAR;
+ const int _default_max = GL_LINEAR;
+}
+
+class TextureData;
+
+struct SubTexture {
+ TextureData* texture_data;
+ std::string texture_name;
+ std::string load_name;
+ ModID modsource;
+ int64_t orig_modified;
+ int64_t load_modified;
+ SubTexture():
+ texture_data(NULL)
+ , orig_modified(0)
+ , load_modified(0)
+ {}
+};
+
+struct Texture {
+ bool notify_on_dispose;
+ std::string name;
+ int width, height, depth;
+ unsigned int size; // for texture buffers
+ unsigned int num_slices; // >1 only for array textures
+ bool cube_map;
+ GLuint gl_texture_id;
+ GLuint gl_buffer_id; // for texture buffers
+ std::vector<SubTexture> sub_textures;
+ GLuint wrap_t, wrap_s, min_filter, mag_filter;
+ int ref_count;
+ bool no_mipmap;
+ bool no_reduce;
+ bool use_srgb;
+ bool no_live_update;
+ unsigned char flags;
+ GLenum internal_format;
+ GLenum format;
+ GLenum tex_target;
+ #ifdef TRACK_TEXTURE_REF
+ std::list<TextureRef*> ref_ptrs;
+ #endif
+ void Init();
+};
+
+inline std::ostream& operator<<(std::ostream& out, const Texture& tex )
+{
+ out << "(w:" << tex.width << ",h:" << tex.height << ",{size:" << tex.size << "})";
+ for( size_t i = 0; i < tex.sub_textures.size(); i++ )
+ {
+ out << tex.sub_textures[i].texture_name << ":";
+ }
+ return out;
+}
+
+enum TextureSaveFlags{
+ _SAV_ALPHA = (1<<0),
+ _SAV_ALREADY_USR = (1<<1)
+};
+
+enum SampleFilter {
+ FILTER_NEAREST,
+ FILTER_LINEAR
+};
+
+enum SampleWrap {
+ WRAP_CLAMP_TO_EDGE
+};
+
+class ProcessPool;
+
+class Textures {
+ public:
+ enum MipmapParam { MIPMAPS, NO_MIPMAPS };
+
+ void DrawImGuiDebug();
+private:
+ ProcessPool *process_pool; // For background compression of textures
+ std::vector<Texture> textures;
+ std::vector<unsigned int> free_spaces; // Unused texture slots in texture array
+ std::mutex texture_mutex;
+ GLuint m_wrap_t, m_wrap_s, min_filter, mag_filter; // These settings are applied to the next texture that is loaded or created
+ unsigned int bound_texture[PhoenixTextures::_texture_units]; // Shadow GL texture units
+ TextureAssetRef blank_texture_ref;
+ TextureAssetRef blank_normal_texture_ref;
+
+ TextureRef detail_color_texture_ref;
+ TextureRef detail_normal_texture_ref;
+
+ std::list<GLuint> deferred_delete_textures;
+
+ int active_texture;
+
+ std::string convert_file_type;
+
+ //Default texture settings, tiling and mipmapped
+ Textures();
+ ~Textures();
+
+ friend class TextureAsset;
+ int AllocateFreeSlot();
+
+ void InvalidateBindCacheInternal();
+ void DisposeTexture(int which, bool free_space = true);
+ void ReloadInternal();
+ void TextureToVRAM(unsigned int which);
+ void RebindTexture(int which_unit);
+ int loadTexture(const std::string& name, unsigned int which, unsigned char flags);
+ unsigned int makeCubemapArrayTextureInternal(int num_slices, int width, int height, GLint internal_format, GLint format, unsigned char flags);
+ unsigned int makeFlatCubemapArrayTextureInternal(int num_slices, int width, int height, GLint internal_format, GLint format, unsigned char flags);
+ unsigned int makeArrayTextureInternal(unsigned int num_slices, unsigned char flags = 0);
+ unsigned int makeRectangularTextureInternal(int width, int height, GLint internal_format = GL_RGBA, GLint format = GL_RGBA);
+ unsigned int makeTextureInternal(int width, int height, GLint internal_format = GL_RGBA, GLint format = GL_RGBA, bool mipmap = false, void* data = NULL);
+ unsigned int makeTextureInternal(const TextureData &texture_data);
+ unsigned int make3DTextureInternal(int* dims, GLint internal_format, GLint format, bool mipmap, void* data);
+ unsigned int makeTextureColorInternal(int width, int height, GLint internal_format = GL_RGBA, GLint format = GL_RGBA, float red = 1.0f, float green = 1.0f, float blue = 1.0f, float alpha = 1.0f, bool mipmap = false);
+ unsigned int makeTextureTestPatternInternal(int width, int height, GLint internal_format = GL_RGBA, GLint format = GL_RGBA, bool mipmap = false);
+ unsigned int makeCubemapTextureInternal(int width, int height, GLint internal_format, GLint format, MipmapParam mipmap_param);
+ unsigned int makeBufferTextureInternal(size_t size, GLint internal_format, const void* data = NULL);
+ void SetActiveTexture(int which_unit);
+ void RemoveTextureFromVRAM(int which);
+ void ConvertToDXT5IfNeeded(SubTexture& texture, ModID modsource) const;
+ public:
+ void IncrementRefCount(unsigned int id);
+ void DecrementRefCount(unsigned int id);
+ static Textures* Instance()
+ {
+ static Textures instance;
+ return &instance;
+ }
+
+ static void SaveCubeMapMipmapsHDR (TextureRef which, const char* filename, int width);
+ static TextureRef LoadCubeMapMipmapsHDR (const char* filename);
+ void FindTexturePath(const std::string& rel_path, char* resulting_path, bool* is_dds );
+ void SaveToPNG( int texture, std::string path );
+ void InvalidateBindCache();
+ void Dispose();
+ void Reload();
+ void EnsureInVRAM(const TextureAssetRef &id);
+ void EnsureInVRAM(const TextureRef &id);
+ void EnsureInVRAM(int id);
+ void drawTexture(TextureRef id, vec3 where, float size, float rotation = 0);
+ void setWrap(GLenum wrap);
+ void setWrap(GLenum wrap_s, GLenum wrap_t);
+ void setFilters(GLenum min, GLenum mag);
+ void bindTexture(const TextureAssetRef &tex_ref, int which_unit=0);
+ void bindTexture(const TextureRef &tex_ref, int which_unit=0);
+ void bindTexture(const unsigned int &tex_ref, int which_unit=0);
+ void bindBlankTexture(int which_unit=0);
+ void bindBlankNormalTexture(int which_unit=0);
+ void GenerateMipmap(const TextureRef &tex_ref);
+ void setSampleFilter( const TextureRef &tex_ref, enum SampleFilter filter );
+ void setSampleWrap( const TextureRef &tex_ref, enum SampleWrap wrapping );
+ void subImage( const TextureRef &tex_ref, void* data );
+ void subImage( const TextureRef &destination, const TextureRef &source, int xoffset, int yoffset );
+
+ TextureRef getDetailColorArray();
+ TextureRef getDetailNormalArray();
+
+ std::string generateTextureName( const char* pre );
+
+ TextureRef makeArrayTexture(unsigned int num_slices, unsigned char flags = 0);
+ TextureRef makeCubemapArrayTexture(int num_slices, int width, int height, GLint internal_format, GLint format, unsigned char flags);
+ TextureRef makeFlatCubemapArrayTexture(int num_slices, int width, int height, GLint internal_format, GLint format, unsigned char flags);
+ TextureRef makeRectangularTexture(int width, int height, GLint internal_format=GL_RGBA, GLint format=GL_RGBA);
+ TextureRef makeTexture(int width, int height, GLint internal_format=GL_RGBA, GLint format=GL_RGBA, bool mipmap = false, void* data = NULL);
+ TextureRef makeTexture(const TextureData &texture_data);
+ TextureRef make3DTexture(int width, int height, int depth, GLint internal_format, GLint format, bool mipmap, void* data);
+ TextureRef makeTextureColor(int width, int height, GLint internal_format=GL_RGBA, GLint format=GL_RGBA, float red = 1.0f, float green = 1.0f, float blue = 1.0f, float alpha = 1.0f, bool mipmap = false);
+ TextureRef makeTextureTestPattern(int width, int height, GLint internal_format=GL_RGBA, GLint format=GL_RGBA, bool mipmap = false);
+ TextureRef makeCubemapTexture(int width, int height, GLint internal_format, GLint format, MipmapParam mipmap_param);
+ TextureRef makeBufferTexture(size_t size, GLint internal_format, const void* data = NULL);
+ void SetTextureName(const TextureRef& texture_ref, const char* name);
+
+ void DeleteUnusedTextures();
+ GLuint returnTexture(const TextureRef &which);
+ GLuint returnTexture(const TextureAssetRef &which);
+
+ //TextureRef returnTextureRefFullDetail(const std::string& name, unsigned char flags = 0);
+ //TextureRef returnTextureRef(const std::string& name, bool no_convert = false, bool no_mipmap = false, bool srgb = false);
+ //TextureRef returnTextureRef(const std::string& name, unsigned char flags = 0);
+
+ int getWidth(const TextureRef &which);
+ int getHeight(const TextureRef &which);
+
+ int getWidth(const TextureAssetRef &which);
+ int getHeight(const TextureAssetRef &which);
+
+ int getReducedWidth(const TextureRef &which);
+ int getReducedHeight(const TextureRef &which);
+
+ int getReducedWidth(const TextureAssetRef &which);
+ int getReducedHeight(const TextureAssetRef &which);
+
+ const TextureRef GetBlankTextureRef();
+ const TextureAssetRef GetBlankTextureAssetRef();
+ const TextureRef GetBlankNormalTextureRef();
+ const TextureAssetRef GetBlankNormalTextureAssetRef();
+
+ bool IsRenderable(const TextureRef& texture);
+
+ bool IsCompressed(const TextureRef& texture);
+ bool IsCompressed(const TextureAssetRef& texture);
+ bool ReloadAsCompressed(const TextureRef& texref);
+ bool ReloadAsCompressed(const TextureAssetRef& texref);
+
+ void loadArraySlice(const TextureRef& texref, unsigned int slice, const std::string& name);
+ unsigned int loadArraySlice(const TextureRef& texref, const std::string& name);
+
+ int GetActiveTexture();
+ void ResetVRAM();
+
+#ifdef TRACK_TEXTURE_REF
+ void AddRefPtr(unsigned int id, TextureRef* ref);
+ void RemoveRefPtr(unsigned int id, TextureRef* ref);
+#endif
+ void SetProcessPoolsEnabled( bool val );
+ void ApplyAnisotropy();
+
+ void updateBufferTexture(TextureRef& texture, size_t size, const void* data);
+};
diff --git a/Source/Graphics/vbocontainer.cpp b/Source/Graphics/vbocontainer.cpp
new file mode 100644
index 00000000..0977a3e5
--- /dev/null
+++ b/Source/Graphics/vbocontainer.cpp
@@ -0,0 +1,198 @@
+//-----------------------------------------------------------------------------
+// Name: vbocontainer.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "vbocontainer.h"
+
+#include <Graphics/graphics.h>
+#include <Logging/logdata.h>
+#include <Utility/assert.h>
+#include <Internal/profiler.h>
+
+#include <SDL_assert.h>
+
+VBOContainer::VBOContainer():
+ is_valid(false),
+ storage(0)
+{}
+
+void VBOContainer::Fill(char flags, GLuint size, void* data) {
+ if( size > 0 ) {
+ LOG_ASSERT(data);
+
+ Graphics* graphics = Graphics::Instance();
+ GLenum target;
+ if(flags & kVBOElement){
+ target = GL_ELEMENT_ARRAY_BUFFER;
+ } else if(flags & kVBOFloat){
+ target = GL_ARRAY_BUFFER;
+ } else {
+ target = 0xFFFFFFFF;
+ // No target flag set
+ SDL_assert(false);
+ }
+ GLenum hint;
+ if(flags & kVBOStatic){
+ hint = GL_STATIC_DRAW;
+ } else if(flags & kVBODynamic){
+ hint = GL_DYNAMIC_DRAW;
+ } else if(flags & kVBOStream){
+ hint = GL_STREAM_DRAW;
+ } else {
+ hint = 0xFFFFFFFF;
+ // No hint flag set
+ SDL_assert(false);
+ }
+ if(flags & kVBOStatic || !is_valid){
+ element = flags & kVBOElement;
+ if(is_valid) {
+ LOGS << "Disposing " << gl_VBO << std::endl;
+ Dispose();
+ }
+ glGenBuffers( 1, &gl_VBO );
+ //LOGI << "Generated vbo: " << gl_VBO << std::endl;
+ graphics->BindVBO(target, gl_VBO);
+ glBufferData( target, size, data, hint );
+ is_valid = true;
+ storage = size;
+ } else {
+ graphics->BindVBO(target, gl_VBO);
+
+ /*
+ * Some early tests indicate that no BufferData orphaning and memory mapped transfers are
+ * the most efficient combination for MacOSX intel.
+ * Generally, i never expect BufferData orphaning in combination with mapping will ever be more efficient
+ * as it tells the driver to invalidate or orphan the data twice.
+ *
+ * MacOSX Intel:
+ * kAlwaysOrphan = false
+ * kUseMapRange = true
+ * MacOSX Nvidia:
+ * ....
+ * ....
+ * Windows ATI:
+ * ....
+ * ....
+ * Windows NVIDIA:
+ * ....
+ * ....
+ * Windows Intel:
+ * ....
+ * ....
+ * Linux NVIDIA:
+ * ....
+ * ....
+ * Linux Intel:
+ * ....
+ * ....
+ * Linux ATI:
+ * ....
+ * ....
+ */
+ const bool kAlwaysOrphan = false;
+ bool orphaned = false;
+ if(size > storage ) {
+ LOGW << "Reallocating buffer because of resize" << std::endl;
+ PROFILER_ZONE(g_profiler_ctx, "glBufferData");
+ storage = size;
+ glBufferData( target, storage, NULL, hint );
+ orphaned = true;
+ }
+
+ //if( !(flags & kVBOForceReBufferData) ){
+ if( orphaned == false && kAlwaysOrphan)
+ {
+ PROFILER_ZONE(g_profiler_ctx, "glBufferData orphan");
+ glBufferData( target, storage, NULL, hint );
+ }
+
+ const bool kUseMapRange = true;
+ if(kUseMapRange) {
+ PROFILER_ZONE(g_profiler_ctx, "glMapBufferRange");
+ //Using the Unsynchronized bit here seems incorrect.
+ //Because we can't actually guarantee on the client side
+ //that the GL environment isn't done with the buffer yet.
+ //For this we have to do a ring buffer implementation instead.
+ //Which we might do anyways..
+ //void* mapped = glMapBufferRange(target, 0, size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_INVALIDATE_RANGE_BIT );
+ void* mapped = glMapBufferRange(target, 0, size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_INVALIDATE_RANGE_BIT );
+ memcpy(mapped, data, size);
+ glUnmapBuffer(target);
+ } else {
+ PROFILER_ZONE(g_profiler_ctx, "glBufferSubData");
+ glBufferSubData( target, 0, size, data);
+ }
+ }
+ graphics->BindVBO(target, 0);
+ }
+ size_ = size;
+}
+
+void VBOContainer::Dispose() {
+ if(!is_valid){
+ return;
+ }
+ Graphics* graphics = Graphics::Instance();
+
+ GLenum target;
+ if( element ) {
+ target = GL_ELEMENT_ARRAY_BUFFER;
+ } else {
+ target = GL_ARRAY_BUFFER;
+ }
+
+ //LOGI << "Disposing vbo: " << gl_VBO << " for target " << target << std::endl;
+ LogSystem::Flush();
+ graphics->UnbindVBO( target, gl_VBO );
+ glDeleteBuffers( 1, &gl_VBO );
+ is_valid = false;
+ size_ = 0;
+}
+
+void VBOContainer::Bind() const {
+ if(!is_valid){
+ LOG_ASSERT(false);
+ return;
+ }
+
+ if(element){
+ Graphics::Instance()->BindElementVBO(gl_VBO);
+ } else {
+ Graphics::Instance()->BindArrayVBO(gl_VBO);
+ }
+}
+
+bool VBOContainer::valid() const {
+ return is_valid;
+}
+
+unsigned VBOContainer::size() {
+ return valid() ? size_ : 0;
+}
+
+uintptr_t VBOContainer::offset() const {
+ return 0;
+}
+
+VBOContainer::~VBOContainer() {
+ Dispose();
+}
diff --git a/Source/Graphics/vbocontainer.h b/Source/Graphics/vbocontainer.h
new file mode 100644
index 00000000..63d7e154
--- /dev/null
+++ b/Source/Graphics/vbocontainer.h
@@ -0,0 +1,55 @@
+//-----------------------------------------------------------------------------
+// Name: vbocontainer.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/referencecounter.h>
+#include <Internal/integer.h>
+
+#include <Graphics/vboenums.h>
+
+#include <opengl.h>
+
+class VBOContainer {
+ GLuint gl_VBO;
+ bool element;
+ GLuint storage;
+
+ GLuint size_;
+public:
+ bool is_valid;
+ void Dispose();
+ void Fill(char flags, GLuint size, void* data);
+ void Bind() const;
+ bool valid() const;
+ uintptr_t offset() const;
+ unsigned size(); //returns size in bytes.
+
+ VBOContainer();
+ ~VBOContainer();
+private:
+ VBOContainer( const VBOContainer& other );
+ VBOContainer& operator=(const VBOContainer& other);
+};
+
+typedef ReferenceCounter<VBOContainer> RC_VBOContainer;
diff --git a/Source/Graphics/vboenums.h b/Source/Graphics/vboenums.h
new file mode 100644
index 00000000..b61d350c
--- /dev/null
+++ b/Source/Graphics/vboenums.h
@@ -0,0 +1,33 @@
+//-----------------------------------------------------------------------------
+// Name: vboenums.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+enum {
+ kVBOElement = 1 << 0,
+ kVBOFloat = 1 << 1,
+ kVBOStatic = 1 << 2,
+ kVBODynamic = 1 << 3,
+ kVBOStream = 1 << 4,
+ kVBOForceReBufferData = 1 << 5
+};
diff --git a/Source/Graphics/vboringcontainer.cpp b/Source/Graphics/vboringcontainer.cpp
new file mode 100644
index 00000000..65d5431f
--- /dev/null
+++ b/Source/Graphics/vboringcontainer.cpp
@@ -0,0 +1,268 @@
+//-----------------------------------------------------------------------------
+// Name: vboringcontainer.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "vboringcontainer.h"
+
+#include <Graphics/graphics.h>
+#include <Logging/logdata.h>
+#include <Utility/assert.h>
+#include <Internal/profiler.h>
+
+#include <SDL_assert.h>
+
+const unsigned kAlignPadding = 64;
+
+const unsigned kBufferSizeMultiplier = 32;
+
+static int buffer_size_multipler_offset = 0;
+
+VBORingContainer::VBORingContainer( GLuint _storage_size, char flags, bool ignore_multiplier ):
+ used_size(0),
+ allocated_size(0),
+ gl_VBO(-1),
+ storage_size(0),
+ hint(0),
+ storage_multiplier( kBufferSizeMultiplier + buffer_size_multipler_offset++ % 32 )
+{
+ SetHint(_storage_size,flags & (kVBOStatic | kVBODynamic | kVBOStream), ignore_multiplier);
+ next_offset = storage_size;
+
+ if(flags & kVBOElement){
+ target = GL_ELEMENT_ARRAY_BUFFER;
+ } else if(flags & kVBOFloat){
+ target = GL_ARRAY_BUFFER;
+ } else {
+ target = 0xFFFFFFFF;
+ // No target flag set
+ SDL_assert(false);
+ }
+
+ element = flags & kVBOElement;
+ force_reload = flags & kVBOForceReBufferData;
+}
+
+void VBORingContainer::SetHint( GLuint storage, char flags, bool ignore_multiplier ) {
+ LOG_ASSERT( ((flags & ~(kVBOStatic | kVBODynamic | kVBOStream)) == 0));
+ GLenum old_hint = hint;
+ if(flags & kVBOStatic){
+ hint = GL_STATIC_DRAW;
+ } else if(flags & kVBODynamic){
+ hint = GL_DYNAMIC_DRAW;
+ } else if(flags & kVBOStream){
+ hint = GL_STREAM_DRAW;
+ } else {
+ hint = 0xFFFFFFFF;
+ // No hint flag set
+ SDL_assert(false);
+ }
+ //Indicate we want to reallocate the buffer;
+ if( old_hint != hint )
+ next_offset = storage_size;
+
+ if( storage % kAlignPadding ) {
+ storage = storage + kAlignPadding - storage % kAlignPadding;
+ }
+
+ if( hint == GL_STATIC_DRAW ) {
+ storage_size_hint = storage;
+ } else {
+ storage_size_hint = storage * (ignore_multiplier ? 1 : storage_multiplier);
+ }
+}
+
+void VBORingContainer::Fill(GLuint size, void* data) {
+ if( size > 0 ) {
+ Graphics* graphics = Graphics::Instance();
+ int old_gl_VBO = gl_VBO;
+ if( gl_VBO == -1 ){
+ GLuint val;
+ glGenBuffers( 1, &val );
+ gl_VBO = val;
+ }
+
+ used_size = size;
+ //pad utilized size to ensure maxixum use of bus.
+ allocated_size = size;
+ if( allocated_size % kAlignPadding ) {
+ allocated_size = allocated_size + kAlignPadding - allocated_size % kAlignPadding;
+ }
+
+ LOG_ASSERT(data);
+
+ while( allocated_size > storage_size ) {
+ if( storage_size == storage_size_hint ) {
+ LOGW << "Requested size is larger than internal preallocated size, resizing to a larger buffer." << std::endl;
+ storage_size_hint = storage_size_hint * 4;
+ }
+
+ storage_size = storage_size_hint;
+ next_offset = storage_size;
+ }
+
+ graphics->BindVBO(target, gl_VBO);
+
+ //if our next size is too big to fit in the remainder of the buffer, create a new one.
+ if( next_offset + allocated_size > storage_size ) {
+ if(old_gl_VBO != -1){
+ PROFILER_ENTER(g_profiler_ctx, "Orphan VBORingContainer buffer");
+ } else {
+ PROFILER_ENTER(g_profiler_ctx, "Create VBORingContainer buffer");
+ }
+ storage_size = storage_size_hint;
+ glBufferData( target, storage_size, NULL, hint );
+ next_offset = 0;
+ PROFILER_LEAVE(g_profiler_ctx);
+ }
+
+ const bool kUseMapBufferRange = false;
+ if(kUseMapBufferRange) {
+ void* mapped = glMapBufferRange(target, next_offset, used_size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT );
+ memcpy(mapped, data, used_size);
+ glUnmapBuffer(target);
+ } else {
+ PROFILER_ZONE(g_profiler_ctx, "glBufferSubData");
+ glBufferSubData( target, next_offset, used_size, data);
+ }
+
+ current_offset = next_offset;
+ next_offset = next_offset + allocated_size;
+
+ graphics->BindVBO(target, 0);
+ }
+}
+
+void VBORingContainer::Dispose() {
+ if(valid() == false){
+ return;
+ }
+ Graphics* graphics = Graphics::Instance();
+
+ GLenum target;
+ if( element ) {
+ target = GL_ELEMENT_ARRAY_BUFFER;
+ } else {
+ target = GL_ARRAY_BUFFER;
+ }
+
+ //LOGI << "Disposing vbo: " << gl_VBO << " for target " << target << std::endl;
+ LogSystem::Flush();
+ graphics->UnbindVBO( target, gl_VBO );
+ GLuint val = gl_VBO;
+ glDeleteBuffers( 1, &val );
+ gl_VBO = -1;
+ next_offset = storage_size;
+ current_offset = 0;
+ allocated_size = 0;
+ used_size = 0;
+}
+
+void VBORingContainer::Bind() const {
+ if(valid() == false){
+ LOG_ASSERT(false);
+ return;
+ }
+
+ if(element){
+ Graphics::Instance()->BindElementVBO(gl_VBO);
+ } else {
+ Graphics::Instance()->BindArrayVBO(gl_VBO);
+ }
+}
+
+bool VBORingContainer::valid() const {
+ return gl_VBO != -1;
+}
+
+unsigned VBORingContainer::size() {
+ return valid() ? used_size : 0;
+}
+
+uintptr_t VBORingContainer::offset() const {
+ if( valid() ) {
+ return (uintptr_t)(current_offset);
+ } else {
+ return 0;
+ }
+}
+
+VBORingContainer::~VBORingContainer() {
+ Dispose();
+}
+
+
+static GLint max_ubo_size = -1;
+static GLint ubo_alignment = -1;
+
+void UniformRingBuffer::Create(int desired_size) {
+ if(max_ubo_size == -1){
+ glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &max_ubo_size);
+ glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &ubo_alignment);
+ }
+ size = desired_size; // Note: Does not have to be smaller than max_ubo_size - that's the max that can be *bound* at once
+
+ GLuint uboHandle;
+ glGenBuffers( 1, &uboHandle );
+ gl_id = uboHandle;
+ glBindBuffer( GL_UNIFORM_BUFFER, gl_id );
+ glBufferData( GL_UNIFORM_BUFFER, size, NULL, GL_DYNAMIC_DRAW );
+
+ offset = 0;
+ next_offset = 0;
+}
+
+void UniformRingBuffer::Fill(int data_size, void* data) {
+ glBindBuffer( GL_UNIFORM_BUFFER, gl_id );
+ if(data_size > size || data_size > max_ubo_size){
+ FatalError("Error", "Data is too big for uniform ring buffer");
+ }
+ if(data_size + next_offset > size) {
+ PROFILER_ZONE(g_profiler_ctx, "orphan buffer");
+ glBufferData( GL_UNIFORM_BUFFER, size, NULL, GL_DYNAMIC_DRAW ); // orphan buffer?
+ offset = 0;
+ next_offset = 0;
+ }
+
+ const bool kUseMemoryMap = false;
+ if(kUseMemoryMap){
+ void* mapped;
+ {
+ PROFILER_ZONE(g_profiler_ctx, "glMapBufferRange");
+ mapped = glMapBufferRange(GL_UNIFORM_BUFFER, next_offset, data_size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT );
+ }
+ {
+ PROFILER_ZONE(g_profiler_ctx, "memcpy");
+ memcpy(mapped, data, data_size);
+ }
+ {
+ PROFILER_ZONE(g_profiler_ctx, "glUnmapBuffer");
+ glUnmapBuffer(GL_UNIFORM_BUFFER);
+ }
+ } else {
+ PROFILER_ZONE(g_profiler_ctx, "glBufferSubData");
+ glBufferSubData( GL_UNIFORM_BUFFER, next_offset, data_size, data );
+ }
+
+ offset = next_offset;
+ next_offset += data_size;
+ next_offset = ((next_offset + (ubo_alignment-1))/ubo_alignment)*ubo_alignment;
+}
diff --git a/Source/Graphics/vboringcontainer.h b/Source/Graphics/vboringcontainer.h
new file mode 100644
index 00000000..4281f211
--- /dev/null
+++ b/Source/Graphics/vboringcontainer.h
@@ -0,0 +1,96 @@
+//-----------------------------------------------------------------------------
+// Name: vboringcontainer.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/referencecounter.h>
+#include <Internal/integer.h>
+
+#include <Graphics/vboenums.h>
+
+#include <opengl.h>
+
+#ifndef V_MIBIBYTE
+#define V_MIBIBYTE 1024*1024
+#endif
+
+#ifndef V_KIBIBYTE
+#define V_KIBIBYTE 1024
+#endif
+
+class VBORingContainer {
+ int gl_VBO;
+ char flags;
+ bool element;
+ bool force_reload;
+
+ GLuint current_offset;
+ GLuint next_offset;
+ //Used space (requested by last loaded buffer) plus alignment padding
+ GLuint allocated_size;
+ //Amount of space requested to use for last filled buffer
+ GLuint used_size;
+
+ //Total size of entire ring buffer
+ GLuint storage_size;
+ GLuint storage_size_hint;
+
+ GLenum target;
+ GLenum hint;
+
+ int storage_multiplier;
+public:
+ void Dispose();
+ void SetHint( GLuint storage_size, char flags, bool ignore_multiplier = false );
+ void Fill(GLuint size, void* data);
+ void Bind() const;
+ bool valid() const;
+ unsigned size(); //returns size in bytes.
+ uintptr_t offset() const;
+
+ VBORingContainer( GLuint storage_size, char flags, bool ignore_multiplier = false );
+ ~VBORingContainer();
+private:
+ VBORingContainer( const VBORingContainer& other );
+ VBORingContainer& operator=(const VBORingContainer& other);
+};
+
+typedef ReferenceCounter<VBORingContainer> RC_VBORingContainer;
+
+class UniformRingBuffer {
+public:
+ int size;
+ int gl_id;
+ int offset;
+ int next_offset;
+
+ UniformRingBuffer():
+ gl_id(-1),
+ size(0),
+ offset(0),
+ next_offset(0)
+ {}
+
+ void Create(int desired_size);
+ void Fill(int data_size, void* data);
+};
diff --git a/Source/Images/ddsformat.hpp b/Source/Images/ddsformat.hpp
new file mode 100644
index 00000000..4150ecfa
--- /dev/null
+++ b/Source/Images/ddsformat.hpp
@@ -0,0 +1,26 @@
+//-----------------------------------------------------------------------------
+// Name: ddsformat.hpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+enum DDSFormat {_DXT1, _DXT1A, _DXT3, _DXT5, _DXT5YCoCg, _RGBA, _RGB};
diff --git a/Source/Images/freeimage_wrapper.cpp b/Source/Images/freeimage_wrapper.cpp
new file mode 100644
index 00000000..3f503948
--- /dev/null
+++ b/Source/Images/freeimage_wrapper.cpp
@@ -0,0 +1,267 @@
+//-----------------------------------------------------------------------------
+// Name: freeimage_wrapper.cpp
+// Developer: Wolfire Games LLC
+// Author: Phillip Isola
+// Description: Wraps up FreeImage library
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "freeimage_wrapper.h"
+
+#include <Compat/fileio.h>
+#include <Compat/compat.h>
+
+#include <Math/enginemath.h>
+#include <Internal/error.h>
+#include <Logging/logdata.h>
+#include <opengl.h>
+
+#include <crnlib.h>
+#include <dds_defs.h>
+#include <FreeImage.h>
+
+#include <cstring>
+#include <vector>
+
+using namespace crnlib;
+using std::string;
+
+//http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn
+static const uint32_t MultiplyDeBruijnBitPosition[32] =
+{
+ 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30,
+ 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31
+};
+
+
+static uint32_t uintLog2(uint32_t v) {
+ v |= v >> 1; // first round down to one less than a power of 2
+ v |= v >> 2;
+ v |= v >> 4;
+ v |= v >> 8;
+ v |= v >> 16;
+
+ return MultiplyDeBruijnBitPosition[(uint32_t)(v * 0x07C4ACDDU) >> 27];
+}
+
+
+vec3 getInterpolatedColorUV(FIBITMAP* image, float x, float y) {
+ x = max(0.01f,min(0.99f, x));
+ y = max(0.01f,min(0.99f, y));
+ return getInterpolatedColor(image, x*FreeImage_GetWidth(image)+0.5f, y*FreeImage_GetHeight(image)+1.0f);
+}
+
+vec3 getInterpolatedColor(FIBITMAP* image, float x, float y) {
+ RGBQUAD top_left, top_right, bottom_left, bottom_right;
+ FreeImage_GetPixelColor(image,
+ (unsigned int)x,
+ (unsigned int)y,
+ &top_left);
+ FreeImage_GetPixelColor(image,
+ (unsigned int)x+1,
+ (unsigned int)y,
+ &top_right);
+ FreeImage_GetPixelColor(image,
+ (unsigned int)x,
+ (unsigned int)y+1,
+ &bottom_left);
+ FreeImage_GetPixelColor(image,
+ (unsigned int)x+1,
+ (unsigned int)y+1,
+ &bottom_right);
+
+ float x_weight = x - (int)x;
+ float y_weight = y - (int)y;
+
+ RGBQUAD left;
+ left.rgbRed = (BYTE)(top_left.rgbRed * (1-y_weight) + bottom_left.rgbRed * (y_weight));
+ left.rgbGreen = (BYTE)(top_left.rgbGreen * (1-y_weight) + bottom_left.rgbGreen * (y_weight));
+ left.rgbBlue = (BYTE)(top_left.rgbBlue * (1-y_weight) + bottom_left.rgbBlue * (y_weight));
+ RGBQUAD right;
+ right.rgbRed = (BYTE)(top_right.rgbRed * (1-y_weight) + bottom_right.rgbRed * (y_weight));
+ right.rgbGreen = (BYTE)(top_right.rgbGreen * (1-y_weight) + bottom_right.rgbGreen * (y_weight));
+ right.rgbBlue = (BYTE)(top_right.rgbBlue * (1-y_weight) + bottom_right.rgbBlue * (y_weight));
+
+ vec3 value(0.0f);
+ value.r() = ((BYTE)(left.rgbRed * (1-x_weight) + right.rgbRed * (x_weight)))/255.0f;
+ value.g() = ((BYTE)(left.rgbGreen * (1-x_weight) + right.rgbGreen * (x_weight)))/255.0f;
+ value.b() = ((BYTE)(left.rgbBlue * (1-x_weight) + right.rgbBlue * (x_weight)))/255.0f;
+
+ return value;
+}
+
+vec4 getInterpolatedRGBAUV(FIBITMAP* image, float x, float y) {
+ x = max(0.01f,min(0.99f, x));
+ y = max(0.01f,min(0.99f, y));
+ return getInterpolatedRGBA(image, x*FreeImage_GetWidth(image)+0.5f, y*FreeImage_GetHeight(image)+1.0f);
+}
+
+vec4 getInterpolatedRGBA(FIBITMAP* image, float x, float y) {
+ RGBQUAD top_left, top_right, bottom_left, bottom_right;
+ FreeImage_GetPixelColor(image,
+ (unsigned int)x,
+ (unsigned int)y,
+ &top_left);
+ FreeImage_GetPixelColor(image,
+ (unsigned int)x+1,
+ (unsigned int)y,
+ &top_right);
+ FreeImage_GetPixelColor(image,
+ (unsigned int)x,
+ (unsigned int)y+1,
+ &bottom_left);
+ FreeImage_GetPixelColor(image,
+ (unsigned int)x+1,
+ (unsigned int)y+1,
+ &bottom_right);
+
+ float x_weight = x - (int)x;
+ float y_weight = y - (int)y;
+
+ RGBQUAD left;
+ left.rgbRed = (BYTE)(top_left.rgbRed * (1-y_weight) + bottom_left.rgbRed * (y_weight));
+ left.rgbGreen = (BYTE)(top_left.rgbGreen * (1-y_weight) + bottom_left.rgbGreen * (y_weight));
+ left.rgbBlue = (BYTE)(top_left.rgbBlue * (1-y_weight) + bottom_left.rgbBlue * (y_weight));
+ left.rgbReserved = (BYTE)(top_left.rgbReserved * (1-y_weight) + bottom_left.rgbReserved * (y_weight));
+ RGBQUAD right;
+ right.rgbRed = (BYTE)(top_right.rgbRed * (1-y_weight) + bottom_right.rgbRed * (y_weight));
+ right.rgbGreen = (BYTE)(top_right.rgbGreen * (1-y_weight) + bottom_right.rgbGreen * (y_weight));
+ right.rgbBlue = (BYTE)(top_right.rgbBlue * (1-y_weight) + bottom_right.rgbBlue * (y_weight));
+ right.rgbReserved = (BYTE)(top_right.rgbReserved * (1-y_weight) + bottom_right.rgbReserved * (y_weight));
+
+ vec4 value(0.0f);
+ value.r() = ((BYTE)(left.rgbRed * (1-x_weight) + right.rgbRed * (x_weight)))/255.0f;
+ value.g() = ((BYTE)(left.rgbGreen * (1-x_weight) + right.rgbGreen * (x_weight)))/255.0f;
+ value.b() = ((BYTE)(left.rgbBlue * (1-x_weight) + right.rgbBlue * (x_weight)))/255.0f;
+ value.a() = ((BYTE)(left.rgbReserved * (1-x_weight) + right.rgbReserved * (x_weight)))/255.0f;
+
+ return value;
+}
+
+vec3 getColor(FIBITMAP* image, float x, float y) {
+ RGBQUAD color;
+ FreeImage_GetPixelColor(image,
+ (unsigned int)x,
+ (unsigned int)y,
+ &color);
+ return vec3(color.rgbRed,color.rgbGreen,color.rgbBlue);
+}
+
+unsigned int getWidth(FIBITMAP* image)
+{
+ return FreeImage_GetWidth(image);
+}
+
+unsigned int getHeight(FIBITMAP* image)
+{
+ return FreeImage_GetHeight(image);
+}
+
+int getPixelColor(FIBITMAP* image, unsigned int x, unsigned int y, FIquad* value )
+{
+ RGBQUAD quad;
+ int ret = FreeImage_GetPixelColor(image,x,y,&quad);
+ value->rgbRed= quad.rgbRed;
+ value->rgbGreen = quad.rgbGreen;
+ value->rgbBlue = quad.rgbBlue;
+ value->rgbReserved = quad.rgbReserved;
+ return ret;
+}
+
+void UnloadBitmap(FIBITMAP* image)
+{
+ FreeImage_Unload(image);
+}
+
+fiTYPE getImageType(FIBITMAP* image)
+{
+ return (fiTYPE)FreeImage_GetImageType(image);
+}
+
+unsigned int getBPP(FIBITMAP* image)
+{
+ return FreeImage_GetBPP(image);
+}
+
+uint8_t* getScanLine(FIBITMAP* image, int scanline)
+{
+ return FreeImage_GetScanLine(image,scanline);
+}
+
+/** Generic image loader -- from FreeImage documentation:
+http://internap.dl.sourceforge.net/sourceforge/freeimage/FreeImage3110.pdf
+@param lpszPathName Pointer to the full file name
+@param flag Optional load flag constant
+@return Returns the loaded dib if successful, returns NULL otherwise
+*/
+FIBITMAP* GenericLoader(const char* abs_path, int flag) {
+#ifdef _WIN32
+ string path_str = abs_path;
+ ShortenWindowsPath(path_str);
+ const char* path = path_str.c_str();
+#else
+ const char *path = abs_path;
+#endif
+ FREE_IMAGE_FORMAT fif = FIF_UNKNOWN;
+ // check the file signature and deduce its format
+ // (the second argument is currently not used by FreeImage)
+ fif = FreeImage_GetFileType(path, 0);
+ if(fif == FIF_UNKNOWN) {
+ // no signature ?
+ // try to guess the file format from the file extension
+ fif = FreeImage_GetFIFFromFilename(path);
+ }
+ // check that the plugin has reading capabilities ...
+ if((fif != FIF_UNKNOWN) && FreeImage_FIFSupportsReading(fif)) {
+ // ok, let's load the file
+ FIBITMAP *dib = FreeImage_Load(fif, path, flag);
+ // unless a bad file format, we are done !
+ return dib;
+ }
+ return NULL;
+}
+
+FIBitmapContainer::FIBitmapContainer(FIBITMAP* _image)
+{
+ image = _image;
+}
+
+FIBitmapContainer::~FIBitmapContainer()
+{
+ reset(NULL);
+}
+
+void FIBitmapContainer::reset(FIBITMAP* _image)
+{
+ if(image){
+ FreeImage_Unload(image);
+ image = NULL;
+ }
+ image = _image;
+}
+
+FIBITMAP* FIBitmapContainer::get()
+{
+ return image;
+}
+
+const FIBITMAP* FIBitmapContainer::get() const
+{
+ return image;
+}
diff --git a/Source/Images/freeimage_wrapper.h b/Source/Images/freeimage_wrapper.h
new file mode 100644
index 00000000..f41dcfb1
--- /dev/null
+++ b/Source/Images/freeimage_wrapper.h
@@ -0,0 +1,129 @@
+//-----------------------------------------------------------------------------
+// Name: freeimage_wrapper.h
+// Developer: Wolfire Games LLC
+// Author: Phillip Isola
+// Description: Wraps up FreeImage library
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+#include <Math/vec4.h>
+
+#include <Images/ddsformat.hpp>
+#include <Internal/integer.h>
+
+#include <string>
+
+struct FIquad{
+#if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR
+ uint8_t rgbBlue;
+ uint8_t rgbGreen;
+ uint8_t rgbRed;
+#else
+ uint8_t rgbRed;
+ uint8_t rgbGreen;
+ uint8_t rgbBlue;
+#endif // FREEIMAGE_COLORORDER
+ uint8_t rgbReserved;
+};
+
+enum fiFORMAT {
+ FIWF_UNKNOWN = -1,
+ FIWF_BMP = 0,
+ FIWF_ICO = 1,
+ FIWF_JPEG = 2,
+ FIWF_JNG = 3,
+ FIWF_KOALA = 4,
+ FIWF_LBM = 5,
+ FIWF_IFF = FIWF_LBM,
+ FIWF_MNG = 6,
+ FIWF_PBM = 7,
+ FIWF_PBMRAW = 8,
+ FIWF_PCD = 9,
+ FIWF_PCX = 10,
+ FIWF_PGM = 11,
+ FIWF_PGMRAW = 12,
+ FIWF_PNG = 13,
+ FIWF_PPM = 14,
+ FIWF_PPMRAW = 15,
+ FIWF_RAS = 16,
+ FIWF_TARGA = 17,
+ FIWF_TIFF = 18,
+ FIWF_WBMP = 19,
+ FIWF_PSD = 20,
+ FIWF_CUT = 21,
+ FIWF_XBM = 22,
+ FIWF_XPM = 23,
+ FIWF_DDS = 24,
+ FIWF_GIF = 25,
+ FIWF_HDR = 26,
+ FIWF_FAXG3 = 27,
+ FIWF_SGI = 28,
+ FIWF_EXR = 29,
+ FIWF_J2K = 30,
+ FIWF_JP2 = 31,
+ FIWF_PFM = 32,
+ FIWF_PICT = 33,
+ FIWF_RAW = 34
+};
+
+enum fiTYPE {
+ FIWT_UNKNOWN = 0, // unknown type
+ FIWT_BITMAP = 1, // standard image : 1-, 4-, 8-, 16-, 24-, 32-bit
+ FIWT_UINT16 = 2, // array of unsigned short : unsigned 16-bit
+ FIWT_INT16 = 3, // array of short : signed 16-bit
+ FIWT_UINT32 = 4, // array of unsigned long : unsigned 32-bit
+ FIWT_INT32 = 5, // array of long : signed 32-bit
+ FIWT_FLOAT = 6, // array of float : 32-bit IEEE floating point
+ FIWT_DOUBLE = 7, // array of double : 64-bit IEEE floating point
+ FIWT_COMPLEX = 8, // array of FICOMPLEX : 2 x 64-bit IEEE floating point
+ FIWT_RGB16 = 9, // 48-bit RGB image : 3 x 16-bit
+ FIWT_RGBA16 = 10, // 64-bit RGBA image : 4 x 16-bit
+ FIWT_RGBF = 11, // 96-bit RGB float image : 3 x 32-bit IEEE floating point
+ FIWT_RGBAF = 12 // 128-bit RGBA float image : 4 x 32-bit IEEE floating point
+};
+
+struct FIBITMAP;
+
+class FIBitmapContainer {
+ FIBITMAP* image;
+
+public:
+ FIBitmapContainer(FIBITMAP* _image = NULL);
+ ~FIBitmapContainer();
+ void reset(FIBITMAP* _image);
+ FIBITMAP* get();
+ const FIBITMAP* get() const;
+};
+
+vec3 getInterpolatedColor(FIBITMAP* image, float x, float y);
+vec3 getInterpolatedColorUV(FIBITMAP* image, float x, float y);
+FIBITMAP* GenericLoader(const char* lpszPathName, int flag=0);
+vec3 getColor(FIBITMAP* image, float x, float y);
+vec4 getInterpolatedRGBAUV(FIBITMAP* image, float x, float y);
+vec4 getInterpolatedRGBA(FIBITMAP* image, float x, float y);
+unsigned int getWidth(FIBITMAP* image);
+unsigned int getHeight(FIBITMAP* image);
+int getPixelColor(FIBITMAP* image, unsigned int x, unsigned int y, FIquad* value );
+void UnloadBitmap(FIBITMAP* image);
+fiTYPE getImageType(FIBITMAP* image);
+unsigned int getBPP(FIBITMAP* image);
+uint8_t* getScanLine(FIBITMAP* image, int scanline);
diff --git a/Source/Images/image_export.cpp b/Source/Images/image_export.cpp
new file mode 100644
index 00000000..b136549f
--- /dev/null
+++ b/Source/Images/image_export.cpp
@@ -0,0 +1,208 @@
+//-----------------------------------------------------------------------------
+// Name: image_export.cpp
+// Developer: Wolfire Games LLC
+// Author: Phillip Isola
+// Description: Wraps up FreeImage library
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "image_export.hpp"
+
+#include <Images/image_export.hpp>
+#include <Images/freeimage_wrapper.h>
+
+#include <Internal/error.h>
+#include <Internal/filesystem.h>
+#include <Internal/common.h>
+
+#include <Compat/fileio.h>
+#include <Compat/compat.h>
+
+#include <Memory/allocation.h>
+#include <Logging/logdata.h>
+
+#include <FreeImage.h>
+#include <crnlib.h>
+
+#include <vector>
+#include <cstring>
+#include <algorithm>
+#include <cstdio>
+
+using std::min;
+using std::max;
+
+namespace {
+ void RGBAtoYCOCG(unsigned char *data, unsigned long width, unsigned long height) {
+ int index = 0;
+ for(unsigned i=0; i<width*height; i++){
+ const int r = data[index+2];
+ const int g = data[index+1];
+ const int b = data[index+0];
+
+ const int Co = r - b;
+ const int t = b + Co/2;
+ const int Cg = g - t;
+ const int Y = t + Cg/2;
+
+ data[index+2] = min(max(Co + 128, 0), 255);
+ data[index+1] = min(max(Cg + 128, 0), 255);
+ data[index+0] = 0;
+ data[index+3] = Y;
+ index+=4;
+ }
+ }
+}
+
+std::string ImageExport::FindEmptySequentialFile(const char* filename, const char* suffix) {
+ char abs_path[kPathSize];
+ for(int i=1; i<99999; i++){
+ FormatString(abs_path, kPathSize,"%s%s%05d%s", GetWritePath(CoreGameModID).c_str(), filename, i, suffix);
+ if(!CheckFileAccess(abs_path)) {
+ return abs_path;
+ }
+ }
+ return filename;
+}
+
+
+void ImageExport::ScaleImageUp(const unsigned char *data,
+ int levels,
+ unsigned long *width_ptr,
+ unsigned long *height_ptr,
+ std::vector<unsigned char> *new_data_ptr)
+{
+ unsigned long& width = *width_ptr;
+ unsigned long& height = *height_ptr;
+ std::vector<unsigned char> &new_data = *new_data_ptr;
+
+ const int bytes_per_pixel = 4;
+ size_t image_size = width*height*bytes_per_pixel;
+ new_data.resize(image_size);
+ memcpy(&(new_data[0]), data, image_size);
+
+ for(int i=0; i<levels; i++){
+ width *= 2;
+ height *= 2;
+ image_size = width*height*bytes_per_pixel;
+ std::vector<unsigned char> new_new_data(image_size);
+ for(unsigned i=0; i<width; i++){
+ for(unsigned j=0; j<height; j++){
+ for(int k=0; k<bytes_per_pixel; k++){
+ new_new_data[(i+j*width)*bytes_per_pixel+k] = new_data[((i/2)+(j/2)*width/2)*bytes_per_pixel+k];
+ }
+ }
+ }
+ new_data = new_new_data;
+ }
+}
+
+void ImageExport::SavePNG(const char *file_path, unsigned char *data, unsigned long width, unsigned long height, unsigned short levels) {
+ std::vector<unsigned char> scaled_data;
+ ImageExport::ScaleImageUp(data, levels, &width, &height, &scaled_data);
+ FREE_IMAGE_FORMAT format = FIF_PNG;
+ FIBITMAP *image = FreeImage_ConvertFromRawBits(&scaled_data[0], width, height, 4*width, 32, 0xC0, 0x38, 0x7);
+ CreateParentDirs(file_path);
+#ifdef _WIN32
+ createfile(file_path);
+ std::string short_path(file_path);
+ ShortenWindowsPath(short_path);
+ if(!FreeImage_Save(format, image, short_path.c_str())) {
+ DisplayError("Error","Problem exporting .png file");
+ }
+#else
+ if(!FreeImage_Save(format, image, file_path)) {
+ DisplayError("Error","Problem exporting .png file");
+ }
+#endif
+ FreeImage_Unload(image);
+}
+
+void ImageExport::SavePNGTransparent(const char *file_path, unsigned char *data, unsigned long width, unsigned long height, unsigned short levels) {
+ std::vector<unsigned char> scaled_data;
+ ImageExport::ScaleImageUp(data, levels, &width, &height, &scaled_data);
+ FREE_IMAGE_FORMAT format = FIF_PNG;
+ FIBITMAP *image = FreeImage_ConvertFromRawBits(&scaled_data[0], width, height, 4*width, 32, 0xC0, 0x38, 0x7);
+ LOGI << "BPP: " << (int)FreeImage_GetBPP(image) << std::endl;
+ LOGI << "IsTransparent: " << (int)FreeImage_IsTransparent(image) << std::endl;
+ FreeImage_SetTransparent(image, true);
+ LOGI << "IsTransparent: " << (int)FreeImage_IsTransparent(image) << std::endl;
+
+ int bytespp = FreeImage_GetLine(image) / FreeImage_GetWidth(image);
+ for(unsigned y = 0; y < FreeImage_GetHeight(image); y++) {
+ BYTE *bits = FreeImage_GetScanLine(image, y);
+ for(unsigned x = 0; x < FreeImage_GetWidth(image); x++) {
+ // Set pixel color to green with a transparency of 128
+ bits[FI_RGBA_RED] = min(255,(int)((bits[FI_RGBA_RED]/255.0f / (scaled_data[y*width*4+x*4+3]/255.0f))*255));
+ bits[FI_RGBA_GREEN] = min(255,(int)((bits[FI_RGBA_GREEN]/255.0f / (scaled_data[y*width*4+x*4+3]/255.0f))*255));
+ bits[FI_RGBA_BLUE] = min(255,(int)((bits[FI_RGBA_BLUE]/255.0f / (scaled_data[y*width*4+x*4+3]/255.0f))*255));
+ bits[FI_RGBA_ALPHA] = scaled_data[y*width*4+x*4+3];
+ // jump to next pixel
+ bits += bytespp;
+ }
+ }
+
+ CreateParentDirs(file_path);
+#ifdef _WIN32
+ createfile(file_path);
+ std::string short_path(file_path);
+ ShortenWindowsPath(short_path);
+ if(!FreeImage_Save(format, image, short_path.c_str())) {
+ DisplayError("Error","Problem exporting .png file");
+ }
+#else
+ if(!FreeImage_Save(format, image, file_path)) {
+ DisplayError("Error","Problem exporting .png file");
+ }
+#endif
+ FreeImage_Unload(image);
+}
+
+void ImageExport::SaveJPEG(const char* abs_path, unsigned char *data, unsigned long width, unsigned long height) {
+ FREE_IMAGE_FORMAT format = FIF_JPEG;
+ FIBITMAP *image = FreeImage_ConvertFromRawBits(data, width, height, 3*width, 24, 0xC0, 0x38, 0x7);
+ CreateParentDirs(abs_path);
+#ifdef _WIN32
+ createfile(abs_path);
+ std::string short_path(abs_path);
+ ShortenWindowsPath(short_path);
+ if(!FreeImage_Save(format, image, short_path.c_str(), JPEG_QUALITYSUPERB)) {
+ DisplayError("Error","Problem exporting .jpeg file");
+ }
+#else
+ if(!FreeImage_Save(format, image, abs_path, JPEG_QUALITYSUPERB)) {
+ DisplayError("Error","Problem exporting .jpeg file");
+ }
+#endif
+ FreeImage_Unload(image);
+}
+
+void ImageExport::SavePNGofGrayscaleFloats(const char *file_path, std::vector<float>& data, unsigned long width, unsigned long height) {
+ if (data.size() != width*height) return;
+ unsigned char* byte_data = (unsigned char*)(OG_MALLOC(sizeof(*byte_data)*data.size()*4));
+ for (unsigned i = 0; i < data.size(); i++) {
+ unsigned char data_i = (unsigned char)(data[i]*255);
+ byte_data[4*i] = data_i;
+ byte_data[4*i+1] = data_i;
+ byte_data[4*i+2] = data_i;
+ byte_data[4*i+3] = 255;
+ }
+ SavePNG(file_path, byte_data, width, height);
+ OG_FREE(byte_data);
+}
diff --git a/Source/Images/image_export.hpp b/Source/Images/image_export.hpp
new file mode 100644
index 00000000..f986e3cf
--- /dev/null
+++ b/Source/Images/image_export.hpp
@@ -0,0 +1,49 @@
+//-----------------------------------------------------------------------------
+// Name: image_export.hpp
+// Developer: Wolfire Games LLC
+// Author: Phillip Isola
+// Description: Wraps up FreeImage library
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Images/ddsformat.hpp>
+
+#include <string>
+#include <vector>
+
+namespace ImageExport {
+ struct CubemapFace {
+ std::vector<unsigned char> pixels;
+ };
+ struct CubemapMipLevel {
+ CubemapFace faces[6];
+ };
+ struct CubemapMipmaps {
+ std::vector<CubemapMipLevel> mips;
+ };
+
+ void SaveJPEG(const char* abs_path, unsigned char *data, unsigned long width, unsigned long height);
+ void SavePNG(const char *file_path, unsigned char *data, unsigned long width, unsigned long height, unsigned short levels = 0);
+ void SavePNGTransparent(const char *file_path, unsigned char *data, unsigned long width, unsigned long height, unsigned short levels = 0);
+ void SavePNGofGrayscaleFloats(const char *file_path, std::vector<float>& data, unsigned long width, unsigned long height);
+ std::string FindEmptySequentialFile(const char* filename, const char* suffix);
+ void ScaleImageUp(const unsigned char *data, int levels, unsigned long *width, unsigned long *height, std::vector<unsigned char> *new_data);
+}
diff --git a/Source/Images/nv_image.cpp b/Source/Images/nv_image.cpp
new file mode 100644
index 00000000..b7b7e1b9
--- /dev/null
+++ b/Source/Images/nv_image.cpp
@@ -0,0 +1,497 @@
+//-----------------------------------------------------------------------------
+// Name: nv_image.cpp
+// Developer: Wolfire Games LLC
+// Description: This is a file from an Nvidia image library, with some
+// modification made by Wolfire.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// nvImage.cpp - Image support class
+//
+// The nvImage class implements an interface for a multipurpose image
+// object. This class is useful for loading and formating images
+// for use as textures. The class supports dds, png, and hdr formats.
+//
+// This file implements the format independent interface.
+//
+// Author: Evan Hart
+// Email: sdkfeedback@nvidia.com
+//
+// Copyright (c) NVIDIA Corporation. All rights reserved.
+////////////////////////////////////////////////////////////////////////////////
+#include "nv_image.h"
+
+#include <Utility/assert.h>
+
+#include <cstring>
+#include <algorithm>
+
+using std::vector;
+using std::max;
+
+#ifdef WIN32
+#define strcasecmp _stricmp
+#endif
+
+namespace nv2 {
+
+Image::FormatInfo Image::formatTable[] = {
+ { "dds", Image::readDDS, 0}
+};
+
+
+//
+//
+////////////////////////////////////////////////////////////
+Image::Image() : _width(0), _height(0), _depth(0), _levelCount(0), _faces(0), _format(GL_RGBA),
+ _internalFormat(GL_RGBA8), _type(GL_UNSIGNED_BYTE), _elementSize(0) {
+}
+
+//
+//
+////////////////////////////////////////////////////////////
+Image::~Image() {
+ freeData();
+}
+
+//
+//
+////////////////////////////////////////////////////////////
+void Image::freeData() {
+ for (vector<GLubyte*>::iterator it = _data.begin(); it != _data.end(); it++) {
+ delete []*it;
+ }
+ _data.clear();
+}
+
+//
+//
+////////////////////////////////////////////////////////////
+int Image::getImageSize( int level) const {
+ bool compressed = isCompressed();
+ int w = _width >> level;
+ int h = _height >> level;
+ int d = _depth >> level;
+ w = (w) ? w : 1;
+ h = (h) ? h : 1;
+ d = (d) ? d : 1;
+ int bw = (compressed) ? ( w + 3 ) / 4 : w;
+ int bh = (compressed) ? ( h + 3 ) / 4 : h;
+ int elementSize = _elementSize;
+
+ return bw*bh*d*elementSize;
+}
+
+
+//
+//
+////////////////////////////////////////////////////////////
+const void* Image::getLevel( int level, GLenum face) const {
+ LOG_ASSERT( level < _levelCount);
+ LOG_ASSERT( _faces == 0 || ( face >= GL_TEXTURE_CUBE_MAP_POSITIVE_X && face <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z));
+
+ face = face - GL_TEXTURE_CUBE_MAP_POSITIVE_X;
+
+ LOG_ASSERT( (face*_levelCount + level) < _data.size());
+ return _data[ face*_levelCount + level];
+}
+
+//
+//
+////////////////////////////////////////////////////////////
+void* Image::getLevel( int level, GLenum face) {
+ LOG_ASSERT( level < _levelCount);
+ LOG_ASSERT( _faces == 0 || ( face >= GL_TEXTURE_CUBE_MAP_POSITIVE_X && face <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z));
+
+ face = face - GL_TEXTURE_CUBE_MAP_POSITIVE_X;
+
+ LOG_ASSERT( (face*_levelCount + level) < _data.size());
+ return _data[ face*_levelCount + level];
+}
+
+//
+//
+////////////////////////////////////////////////////////////
+bool Image::loadImageFromFile( const char* file) {
+ const char* extension;
+ extension = strrchr( file, '.');
+
+ if (extension)
+ extension++; //start looking after the .
+ else
+ return false;
+
+ int formatCount = sizeof(Image::formatTable) / sizeof(Image::FormatInfo);
+
+ //try to match by format first
+ for ( int ii = 0; ii < formatCount; ii++) {
+ if ( ! strcasecmp( formatTable[ii].extension, extension)) {
+ //extension matches, load it
+ return formatTable[ii].reader( file, *this);
+ }
+ }
+
+
+ return false;
+}
+
+//
+//
+////////////////////////////////////////////////////////////
+void Image::flipSurface(GLubyte *surf, int width, int height, int depth)
+{
+ unsigned int lineSize;
+
+ depth = (depth) ? depth : 1;
+
+ if (!isCompressed()) {
+ lineSize = _elementSize * width;
+ unsigned int sliceSize = lineSize * height;
+
+ GLubyte *tempBuf = new GLubyte[lineSize];
+
+ for ( int ii = 0; ii < depth; ii++) {
+ GLubyte *top = surf + ii*sliceSize;
+ GLubyte *bottom = top + (sliceSize - lineSize);
+
+ for ( int jj = 0; jj < (height >> 1); jj++) {
+ memcpy( tempBuf, top, lineSize);
+ memcpy( top, bottom, lineSize);
+ memcpy( bottom, tempBuf, lineSize);
+
+ top += lineSize;
+ bottom -= lineSize;
+ }
+ }
+
+ delete []tempBuf;
+ }
+ else
+ {
+ void (*flipblocks)(GLubyte*, unsigned int);
+ width = (width + 3) / 4;
+ height = (height + 3) / 4;
+ unsigned int blockSize = 0;
+
+ switch (_format)
+ {
+ case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
+ blockSize = 8;
+ flipblocks = &Image::flip_blocks_dxtc1;
+ break;
+ case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
+ blockSize = 16;
+ flipblocks = &Image::flip_blocks_dxtc3;
+ break;
+ case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
+ blockSize = 16;
+ flipblocks = &Image::flip_blocks_dxtc5;
+ break;
+ default:
+ return;
+ }
+
+ lineSize = width * blockSize;
+ GLubyte *tempBuf = new GLubyte[lineSize];
+
+ GLubyte *top = surf;
+ GLubyte *bottom = surf + (height-1) * lineSize;
+
+ for (unsigned int j = 0; j < max( (unsigned int)height >> 1, (unsigned int)1); j++)
+ {
+ if (top == bottom)
+ {
+ flipblocks(top, width);
+ break;
+ }
+
+ flipblocks(top, width);
+ flipblocks(bottom, width);
+
+ memcpy( tempBuf, top, lineSize);
+ memcpy( top, bottom, lineSize);
+ memcpy( bottom, tempBuf, lineSize);
+
+ top += lineSize;
+ bottom -= lineSize;
+ }
+ delete []tempBuf;
+ }
+}
+
+//
+//
+////////////////////////////////////////////////////////////
+bool Image::convertCrossToCubemap() {
+ //can't already be a cubemap
+ if (isCubeMap())
+ return false;
+
+ //mipmaps are not supported
+ if (_levelCount != 1)
+ return false;
+
+ //compressed textures are not supported
+ if (isCompressed())
+ return false;
+
+ //this function only supports vertical cross format for now (3 wide by 4 high)
+ if ( (_width / 3 != _height / 4) || (_width % 3 != 0) || (_height % 4 != 0) || (_depth != 0))
+ return false;
+
+ //get the source data
+ GLubyte *data = _data[0];
+
+ int fWidth = _width / 3;
+ int fHeight = _height / 4;
+
+ //remove the old pointer from the vector
+ _data.pop_back();
+
+ GLubyte *face = new GLubyte[ fWidth * fHeight * _elementSize];
+ GLubyte *ptr;
+
+ //extract the faces
+
+ // positive X
+ ptr = face;
+ for (int j=0; j<fHeight; j++) {
+ memcpy( ptr, &data[((_height - (fHeight + j + 1))*_width + 2 * fWidth) * _elementSize], fWidth*_elementSize);
+ ptr += fWidth*_elementSize;
+ }
+ _data.push_back(face);
+
+ // negative X
+ face = new GLubyte[ fWidth * fHeight * _elementSize];
+ ptr = face;
+ for (int j=0; j<fHeight; j++) {
+ memcpy( ptr, &data[(_height - (fHeight + j + 1))*_width*_elementSize], fWidth*_elementSize);
+ ptr += fWidth*_elementSize;
+ }
+ _data.push_back(face);
+
+ // positive Y
+ face = new GLubyte[ fWidth * fHeight * _elementSize];
+ ptr = face;
+ for (int j=0; j<fHeight; j++) {
+ memcpy( ptr, &data[((4 * fHeight - j - 1)*_width + fWidth)*_elementSize], fWidth*_elementSize);
+ ptr += fWidth*_elementSize;
+ }
+ _data.push_back(face);
+
+ // negative Y
+ face = new GLubyte[ fWidth * fHeight * _elementSize];
+ ptr = face;
+ for (int j=0; j<fHeight; j++) {
+ memcpy( ptr, &data[((2*fHeight - j - 1)*_width + fWidth)*_elementSize], fWidth*_elementSize);
+ ptr += fWidth*_elementSize;
+ }
+ _data.push_back(face);
+
+ // positive Z
+ face = new GLubyte[ fWidth * fHeight * _elementSize];
+ ptr = face;
+ for (int j=0; j<fHeight; j++) {
+ memcpy( ptr, &data[((_height - (fHeight + j + 1))*_width + fWidth) * _elementSize], fWidth*_elementSize);
+ ptr += fWidth*_elementSize;
+ }
+ _data.push_back(face);
+
+ // negative Z
+ face = new GLubyte[ fWidth * fHeight * _elementSize];
+ ptr = face;
+ for (int j=0; j<fHeight; j++) {
+ for (int i=0; i<fWidth; i++) {
+ memcpy( ptr, &data[(j*_width + 2 * fWidth - (i + 1))*_elementSize], _elementSize);
+ ptr += _elementSize;
+ }
+ }
+ _data.push_back(face);
+
+ //set the new # of faces, width and height
+ _faces = 6;
+ _width = fWidth;
+ _height = fHeight;
+
+ //delete the old pointer
+ delete []data;
+
+ return true;
+}
+
+//
+//
+////////////////////////////////////////////////////////////
+bool Image::setImage( int width, int height, GLenum format, GLenum type, const void* data){
+ //check parameters before destroying the old image
+ int elementSize;
+ GLenum internalFormat;
+
+ switch (format) {
+ case GL_ALPHA:
+ switch (type) {
+ case GL_UNSIGNED_BYTE:
+ internalFormat = GL_ALPHA8;
+ elementSize = 1;
+ break;
+ case GL_UNSIGNED_SHORT:
+ internalFormat = GL_ALPHA16;
+ elementSize = 2;
+ break;
+ case GL_FLOAT:
+ internalFormat = GL_ALPHA32F_ARB;
+ elementSize = 4;
+ break;
+ case GL_HALF_FLOAT:
+ internalFormat = GL_ALPHA16F_ARB;
+ elementSize = 2;
+ break;
+ default:
+ return false; //format/type combo not supported
+ }
+ break;
+ case GL_LUMINANCE:
+ switch (type) {
+ case GL_UNSIGNED_BYTE:
+ internalFormat = GL_LUMINANCE8;
+ elementSize = 1;
+ break;
+ case GL_UNSIGNED_SHORT:
+ internalFormat = GL_LUMINANCE16;
+ elementSize = 2;
+ break;
+ case GL_FLOAT:
+ internalFormat = GL_LUMINANCE32F_ARB;
+ elementSize = 4;
+ break;
+ case GL_HALF_FLOAT:
+ internalFormat = GL_LUMINANCE16F_ARB;
+ elementSize = 2;
+ break;
+ default:
+ return false; //format/type combo not supported
+ }
+ break;
+ case GL_LUMINANCE_ALPHA:
+ switch (type) {
+ case GL_UNSIGNED_BYTE:
+ internalFormat = GL_LUMINANCE8_ALPHA8;
+ elementSize = 2;
+ break;
+ case GL_UNSIGNED_SHORT:
+ internalFormat = GL_LUMINANCE16_ALPHA16;
+ elementSize = 4;
+ break;
+ case GL_FLOAT:
+ internalFormat = GL_LUMINANCE_ALPHA32F_ARB;
+ elementSize = 8;
+ break;
+ case GL_HALF_FLOAT:
+ internalFormat = GL_LUMINANCE_ALPHA16F_ARB;
+ elementSize = 4;
+ break;
+ default:
+ return false; //format/type combo not supported
+ }
+ break;
+ case GL_RGB:
+ switch (type) {
+ case GL_UNSIGNED_BYTE:
+ internalFormat = GL_RGB8;
+ elementSize = 3;
+ break;
+ case GL_UNSIGNED_SHORT:
+ internalFormat = GL_RGB16;
+ elementSize = 6;
+ break;
+ case GL_FLOAT:
+ internalFormat = GL_RGB32F;
+ elementSize = 12;
+ break;
+ case GL_HALF_FLOAT:
+ internalFormat = GL_RGB16F;
+ elementSize = 6;
+ break;
+ default:
+ return false; //format/type combo not supported
+ }
+ break;
+ case GL_RGBA:
+ switch (type) {
+ case GL_UNSIGNED_BYTE:
+ internalFormat = GL_RGBA8;
+ elementSize = 4;
+ break;
+ case GL_UNSIGNED_SHORT:
+ internalFormat = GL_RGBA16;
+ elementSize = 8;
+ break;
+ case GL_FLOAT:
+ internalFormat = GL_RGBA32F;
+ elementSize = 16;
+ break;
+ case GL_HALF_FLOAT:
+ internalFormat = GL_RGBA16F;
+ elementSize = 8;
+ break;
+ default:
+ return false; //format/type combo not supported
+ }
+ break;
+ default:
+ //bad format
+ return false;
+ break;
+ }
+
+
+ //clear old data
+ freeData();
+
+ GLubyte *newImage = new GLubyte[width*height*elementSize];
+ memcpy( newImage, data, width*height*elementSize);
+
+ _data.push_back(newImage);
+
+ _width = width;
+ _height = height;
+ _elementSize = elementSize;
+ _internalFormat = internalFormat;
+ _levelCount = 1;
+ _faces = 0;
+ _depth = 0;
+ _format = format;
+ _type = type;
+
+ return true;
+}
+
+//
+//
+////////////////////////////////////////////////////////////
+bool Image::saveImageToFile( const char* file) {
+ const char* extension;
+ extension = strrchr( file, '.');
+
+ if (extension)
+ extension++; //start looking after the .
+ else
+ return false;
+
+ int formatCount = sizeof(Image::formatTable) / sizeof(Image::FormatInfo);
+
+ //try to match by format first
+ for ( int ii = 0; ii < formatCount; ii++) {
+ if ( ! strcasecmp( formatTable[ii].extension, extension)) {
+ //extension matches, load it
+ if (formatTable[ii].writer) {
+ return formatTable[ii].writer( file, *this);
+ }
+ }
+ }
+
+
+ return false;
+}
+
+}
diff --git a/Source/Images/nv_image.h b/Source/Images/nv_image.h
new file mode 100644
index 00000000..f00ea74a
--- /dev/null
+++ b/Source/Images/nv_image.h
@@ -0,0 +1,135 @@
+//-----------------------------------------------------------------------------
+// Name: nv_image.h
+// Developer: Wolfire Games LLC
+// Description: This is a file from an Nvidia image library, with some
+// modification made by Wolfire.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// nvImage.h - Image support class
+//
+// The nvImage class implements an interface for a multipurpose image
+// object. This class is useful for loading and formating images
+// for use as textures. The class supports dds, png, and hdr formats.
+//
+// Author: Evan Hart
+// Email: sdkfeedback@nvidia.com
+//
+// Copyright (c) NVIDIA Corporation. All rights reserved.
+////////////////////////////////////////////////////////////////////////////////
+#pragma once
+
+#include <opengl.h>
+
+#include <vector>
+#include <cassert>
+
+namespace nv2 {
+
+ class Image {
+ public:
+
+ Image();
+ virtual ~Image();
+
+ // return the width of the image
+ int getWidth() const { return _width; }
+
+ //return the height of the image
+ int getHeight() const { return _height; }
+
+ //return the dpeth of the image (0 for images with no depth)
+ int getDepth() const { return _depth; }
+
+ //return the number of mipmap levels available for the image
+ int getMipLevels() const { return _levelCount; }
+
+ //return the number of cubemap faces available for the image (0 for non-cubemap images)
+ int getFaces() const { return _faces; }
+
+ //return the format of the image data (GL_RGB, GL_BGR, etc)
+ GLenum getFormat() const { return _format; }
+
+ //return the suggested internal format for the data
+ GLenum getInternalFormat() const { return _internalFormat; }
+
+ //return the type of the image data
+ GLenum getType() const { return _type; }
+
+ //return the Size in bytes of a level of the image
+ int getImageSize(int level = 0) const;
+
+ //return whether the data is a crompressed format
+ bool isCompressed() const {
+ switch(_format) {
+ case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
+ case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
+ case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
+ case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
+ case GL_COMPRESSED_LUMINANCE_LATC1_EXT:
+ case GL_COMPRESSED_SIGNED_LUMINANCE_LATC1_EXT:
+ case GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT:
+ case GL_COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2_EXT:
+ return true;
+ }
+ return false;
+ }
+
+ //return whether the image represents a cubemap
+ bool isCubeMap() const { return _faces > 0; }
+
+ //return whether the image represents a volume
+ bool isVolume() const { return _depth > 0; }
+
+ //get a pointer to level data
+ const void* getLevel( int level, GLenum face = GL_TEXTURE_CUBE_MAP_POSITIVE_X) const;
+ void* getLevel( int level, GLenum face = GL_TEXTURE_CUBE_MAP_POSITIVE_X);
+
+ //initialize an image from a file
+ bool loadImageFromFile( const char* file);
+
+ //convert a suitable image from a cubemap cross to a cubemap (returns false for unsuitable images)
+ bool convertCrossToCubemap();
+
+ //load an image from memory, for the purposes of saving
+ bool setImage( int width, int height, GLenum format, GLenum type, const void* data);
+
+ //save an image to a file
+ bool saveImageToFile( const char* file);
+
+ protected:
+ int _width;
+ int _height;
+ int _depth;
+ int _levelCount;
+ int _faces;
+ GLenum _format;
+ GLenum _internalFormat;
+ GLenum _type;
+ int _elementSize;
+
+ //pointers to the levels
+ std::vector<GLubyte*> _data;
+
+ void freeData();
+ void flipSurface(GLubyte *surf, int width, int height, int depth);
+
+ //
+ // Static elements used to dispatch to proper sub-readers
+ //
+ //////////////////////////////////////////////////////////////
+ struct FormatInfo {
+ const char* extension;
+ bool (*reader)( const char* file, Image& i);
+ bool (*writer)( const char* file, Image& i);
+ };
+
+ static FormatInfo formatTable[];
+
+ static bool readDDS( const char *file, Image& i);
+
+ static void flip_blocks_dxtc1(GLubyte *ptr, unsigned int numBlocks);
+ static void flip_blocks_dxtc3(GLubyte *ptr, unsigned int numBlocks);
+ static void flip_blocks_dxtc5(GLubyte *ptr, unsigned int numBlocks);
+ };
+}
diff --git a/Source/Images/nv_image_dds.cpp b/Source/Images/nv_image_dds.cpp
new file mode 100644
index 00000000..8db8922e
--- /dev/null
+++ b/Source/Images/nv_image_dds.cpp
@@ -0,0 +1,663 @@
+//-----------------------------------------------------------------------------
+// Name: nv_image_dds.h
+// Developer: Wolfire Games LLC
+// Description: This is a file from an Nvidia image library, with some
+// modification made by Wolfire.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// nvImageDDS.cpp - Image support class
+//
+// The nvImage class implements an interface for a multipurpose image
+// object. This class is useful for loading and formating images
+// for use as textures. The class supports dds, png, and hdr formats.
+//
+// This file implements the DDS specific functionality.
+//
+// Author: Evan Hart
+// Email: sdkfeedback@nvidia.com
+//
+// Copyright (c) NVIDIA Corporation. All rights reserved.
+////////////////////////////////////////////////////////////////////////////////
+
+#include <Images/nv_image.h>
+#include <Compat/fileio.h>
+#include <Internal/integer.h>
+
+#include <cstdio>
+#include <cstring>
+
+using std::vector;
+
+namespace nv2 {
+
+//
+// Structure defines and constants from nvdds
+//
+//////////////////////////////////////////////////////////////////////
+
+// surface description flags
+const uint32_t DDSF_CAPS = 0x00000001l;
+const uint32_t DDSF_HEIGHT = 0x00000002l;
+const uint32_t DDSF_WIDTH = 0x00000004l;
+const uint32_t DDSF_PITCH = 0x00000008l;
+const uint32_t DDSF_PIXELFORMAT = 0x00001000l;
+const uint32_t DDSF_MIPMAPCOUNT = 0x00020000l;
+const uint32_t DDSF_LINEARSIZE = 0x00080000l;
+const uint32_t DDSF_DEPTH = 0x00800000l;
+
+// pixel format flags
+const uint32_t DDSF_ALPHAPIXELS = 0x00000001l;
+const uint32_t DDSF_FOURCC = 0x00000004l;
+const uint32_t DDSF_RGB = 0x00000040l;
+const uint32_t DDSF_RGBA = 0x00000041l;
+
+// dwCaps1 flags
+const uint32_t DDSF_COMPLEX = 0x00000008l;
+const uint32_t DDSF_TEXTURE = 0x00001000l;
+const uint32_t DDSF_MIPMAP = 0x00400000l;
+
+// dwCaps2 flags
+const uint32_t DDSF_CUBEMAP = 0x00000200l;
+const uint32_t DDSF_CUBEMAP_POSITIVEX = 0x00000400l;
+const uint32_t DDSF_CUBEMAP_NEGATIVEX = 0x00000800l;
+const uint32_t DDSF_CUBEMAP_POSITIVEY = 0x00001000l;
+const uint32_t DDSF_CUBEMAP_NEGATIVEY = 0x00002000l;
+const uint32_t DDSF_CUBEMAP_POSITIVEZ = 0x00004000l;
+const uint32_t DDSF_CUBEMAP_NEGATIVEZ = 0x00008000l;
+const uint32_t DDSF_CUBEMAP_ALL_FACES = 0x0000FC00l;
+const uint32_t DDSF_VOLUME = 0x00200000l;
+
+// compressed texture types
+const uint32_t FOURCC_UNKNOWN = 0;
+
+#ifndef MAKEFOURCC
+#define MAKEFOURCC(c0,c1,c2,c3) \
+ ((uint32_t)(unsigned char)(c0)| \
+ ((uint32_t)(unsigned char)(c1) << 8)| \
+ ((uint32_t)(unsigned char)(c2) << 16)| \
+ ((uint32_t)(unsigned char)(c3) << 24))
+#endif
+
+const uint32_t FOURCC_R8G8B8 = 20;
+const uint32_t FOURCC_A8R8G8B8 = 21;
+const uint32_t FOURCC_X8R8G8B8 = 22;
+const uint32_t FOURCC_R5G6B5 = 23;
+const uint32_t FOURCC_X1R5G5B5 = 24;
+const uint32_t FOURCC_A1R5G5B5 = 25;
+const uint32_t FOURCC_A4R4G4B4 = 26;
+const uint32_t FOURCC_R3G3B2 = 27;
+const uint32_t FOURCC_A8 = 28;
+const uint32_t FOURCC_A8R3G3B2 = 29;
+const uint32_t FOURCC_X4R4G4B4 = 30;
+const uint32_t FOURCC_A2B10G10R10 = 31;
+const uint32_t FOURCC_A8B8G8R8 = 32;
+const uint32_t FOURCC_X8B8G8R8 = 33;
+const uint32_t FOURCC_G16R16 = 34;
+const uint32_t FOURCC_A2R10G10B10 = 35;
+const uint32_t FOURCC_A16B16G16R16 = 36;
+
+const uint32_t FOURCC_L8 = 50;
+const uint32_t FOURCC_A8L8 = 51;
+const uint32_t FOURCC_A4L4 = 52;
+const uint32_t FOURCC_DXT1 = 0x31545844l; //(MAKEFOURCC('D','X','T','1'))
+const uint32_t FOURCC_DXT2 = 0x32545844l; //(MAKEFOURCC('D','X','T','1'))
+const uint32_t FOURCC_DXT3 = 0x33545844l; //(MAKEFOURCC('D','X','T','3'))
+const uint32_t FOURCC_DXT4 = 0x34545844l; //(MAKEFOURCC('D','X','T','3'))
+const uint32_t FOURCC_DXT5 = 0x35545844l; //(MAKEFOURCC('D','X','T','5'))
+const uint32_t FOURCC_ATI1 = MAKEFOURCC('A','T','I','1');
+const uint32_t FOURCC_ATI2 = MAKEFOURCC('A','T','I','2');
+
+const uint32_t FOURCC_D16_LOCKABLE = 70;
+const uint32_t FOURCC_D32 = 71;
+const uint32_t FOURCC_D24X8 = 77;
+const uint32_t FOURCC_D16 = 80;
+
+const uint32_t FOURCC_D32F_LOCKABLE = 82;
+
+const uint32_t FOURCC_L16 = 81;
+
+// Floating point surface formats
+
+// s10e5 formats (16-bits per channel)
+const uint32_t FOURCC_R16F = 111;
+const uint32_t FOURCC_G16R16F = 112;
+const uint32_t FOURCC_A16B16G16R16F = 113;
+
+// IEEE s23e8 formats (32-bits per channel)
+const uint32_t FOURCC_R32F = 114;
+const uint32_t FOURCC_G32R32F = 115;
+const uint32_t FOURCC_A32B32G32R32F = 116;
+
+struct DXTColBlock
+{
+ GLushort col0;
+ GLushort col1;
+
+ GLubyte row[4];
+};
+
+struct DXT3AlphaBlock
+{
+ GLushort row[4];
+};
+
+struct DXT5AlphaBlock
+{
+ GLubyte alpha0;
+ GLubyte alpha1;
+
+ GLubyte row[6];
+};
+
+struct DDS_PIXELFORMAT
+{
+ uint32_t dwSize;
+ uint32_t dwFlags;
+ uint32_t dwFourCC;
+ uint32_t dwRGBBitCount;
+ uint32_t dwRBitMask;
+ uint32_t dwGBitMask;
+ uint32_t dwBBitMask;
+ uint32_t dwABitMask;
+};
+
+struct DDS_HEADER
+{
+ uint32_t dwSize;
+ uint32_t dwFlags;
+ uint32_t dwHeight;
+ uint32_t dwWidth;
+ uint32_t dwPitchOrLinearSize;
+ uint32_t dwDepth;
+ uint32_t dwMipMapCount;
+ uint32_t dwReserved1[11];
+ DDS_PIXELFORMAT ddspf;
+ uint32_t dwCaps1;
+ uint32_t dwCaps2;
+ uint32_t dwReserved2[3];
+};
+
+//
+//
+////////////////////////////////////////////////////////////
+bool Image::readDDS( const char *file, Image& i) {
+
+ // open file
+ FILE *fp = my_fopen(file, "rb");
+ if (fp == NULL)
+ return false;
+
+ // read in file marker, make sure its a DDS file
+ char filecode[4];
+ fread(filecode, 1, 4, fp);
+ if (strncmp(filecode, "DDS ", 4) != 0)
+ {
+ fclose(fp);
+ return false;
+ }
+
+ // read in DDS header
+ DDS_HEADER ddsh;
+ fread(&ddsh, sizeof(DDS_HEADER), 1, fp);
+
+ // check if image is a volume texture
+ if ((ddsh.dwCaps2 & DDSF_VOLUME) && (ddsh.dwDepth > 0))
+ i._depth = ddsh.dwDepth;
+ else
+ i._depth = 0;
+
+ // There are flags that are supposed to mark these fields as valid, but some dds files don't set them properly
+ i._width = ddsh.dwWidth;
+ i._height = ddsh.dwHeight;
+
+ if (ddsh.dwFlags & DDSF_MIPMAPCOUNT) {
+ i._levelCount = ddsh.dwMipMapCount;
+ }
+ else
+ i._levelCount = 1;
+
+ //check cube-map faces
+ if ( ddsh.dwCaps2 & DDSF_CUBEMAP) {
+ //this is a cubemap, count the faces
+ i._faces = 0;
+ i._faces += (ddsh.dwCaps2 & DDSF_CUBEMAP_POSITIVEX) ? 1 : 0;
+ i._faces += (ddsh.dwCaps2 & DDSF_CUBEMAP_NEGATIVEX) ? 1 : 0;
+ i._faces += (ddsh.dwCaps2 & DDSF_CUBEMAP_POSITIVEY) ? 1 : 0;
+ i._faces += (ddsh.dwCaps2 & DDSF_CUBEMAP_NEGATIVEY) ? 1 : 0;
+ i._faces += (ddsh.dwCaps2 & DDSF_CUBEMAP_POSITIVEZ) ? 1 : 0;
+ i._faces += (ddsh.dwCaps2 & DDSF_CUBEMAP_NEGATIVEZ) ? 1 : 0;
+
+ //check for a complete cubemap
+ if ( (i._faces != 6) || (i._width != i._height) ) {
+ fclose(fp);
+ return false;
+ }
+ }
+ else {
+ //not a cubemap
+ i._faces = 0;
+ }
+
+ bool btcCompressed = false;
+ int bytesPerElement = 0;
+
+ // figure out what the image format is
+ if (ddsh.ddspf.dwFlags & DDSF_FOURCC)
+ {
+ switch(ddsh.ddspf.dwFourCC)
+ {
+ case FOURCC_DXT1:
+ i._format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
+ i._internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
+ i._type = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
+ bytesPerElement = 8;
+ btcCompressed = true;
+ break;
+
+ case FOURCC_DXT2:
+ case FOURCC_DXT3:
+ i._format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
+ i._internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
+ i._type = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
+ bytesPerElement = 16;
+ btcCompressed = true;
+ break;
+
+ case FOURCC_DXT4:
+ case FOURCC_DXT5:
+ i._format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+ i._internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+ i._type = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+ bytesPerElement = 16;
+ btcCompressed = true;
+ break;
+
+ case FOURCC_ATI1:
+ i._format = GL_COMPRESSED_LUMINANCE_LATC1_EXT;
+ i._internalFormat = GL_COMPRESSED_LUMINANCE_LATC1_EXT;
+ i._type = GL_COMPRESSED_LUMINANCE_LATC1_EXT;
+ bytesPerElement = 8;
+ btcCompressed = true;
+ break;
+
+ case FOURCC_ATI2:
+ i._format = GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT;
+ i._internalFormat = GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT;
+ i._type = GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT;
+ bytesPerElement = 16;
+ btcCompressed = true;
+ break;
+
+ case FOURCC_R8G8B8:
+ i._format = GL_BGR;
+ i._internalFormat = GL_RGB8;
+ i._type = GL_UNSIGNED_BYTE;
+ bytesPerElement = 3;
+ break;
+
+ case FOURCC_A8R8G8B8:
+ i._format = GL_BGRA;
+ i._internalFormat = GL_RGBA8;
+ i._type = GL_UNSIGNED_BYTE;
+ bytesPerElement = 4;
+ break;
+
+ case FOURCC_X8R8G8B8:
+ i._format = GL_BGRA;
+ i._internalFormat = GL_RGB8;
+ i._type = GL_UNSIGNED_INT_8_8_8_8;
+ bytesPerElement = 4;
+ break;
+
+ case FOURCC_R5G6B5:
+ i._format = GL_BGR;
+ i._internalFormat = GL_RGB5;
+ i._type = GL_UNSIGNED_SHORT_5_6_5;
+ bytesPerElement = 2;
+ break;
+
+ case FOURCC_A8:
+ i._format = GL_ALPHA;
+ i._internalFormat = GL_ALPHA8;
+ i._type = GL_UNSIGNED_BYTE;
+ bytesPerElement = 1;
+ break;
+
+ case FOURCC_A2B10G10R10:
+ i._format = GL_RGBA;
+ i._internalFormat = GL_RGB10_A2;
+ i._type = GL_UNSIGNED_INT_10_10_10_2;
+ bytesPerElement = 4;
+ break;
+
+ case FOURCC_A8B8G8R8:
+ i._format = GL_RGBA;
+ i._internalFormat = GL_RGBA8;
+ i._type = GL_UNSIGNED_BYTE;
+ bytesPerElement = 4;
+ break;
+
+ case FOURCC_X8B8G8R8:
+ i._format = GL_RGBA;
+ i._internalFormat = GL_RGB8;
+ i._type = GL_UNSIGNED_INT_8_8_8_8;
+ bytesPerElement = 4;
+ break;
+
+ case FOURCC_A2R10G10B10:
+ i._format = GL_BGRA;
+ i._internalFormat = GL_RGB10_A2;
+ i._type = GL_UNSIGNED_INT_10_10_10_2;
+ bytesPerElement = 4;
+ break;
+
+ case FOURCC_A16B16G16R16:
+ i._format = GL_RGBA;
+ i._internalFormat = GL_RGBA16;
+ i._type = GL_UNSIGNED_SHORT;
+ bytesPerElement = 8;
+ break;
+
+ case FOURCC_L8:
+ i._format = GL_LUMINANCE;
+ i._internalFormat = GL_LUMINANCE8;
+ i._type = GL_UNSIGNED_BYTE;
+ bytesPerElement = 1;
+ break;
+
+ case FOURCC_A8L8:
+ i._format = GL_LUMINANCE_ALPHA;
+ i._internalFormat = GL_LUMINANCE8_ALPHA8;
+ i._type = GL_UNSIGNED_BYTE;
+ bytesPerElement = 2;
+ break;
+
+ case FOURCC_L16:
+ i._format = GL_LUMINANCE;
+ i._internalFormat = GL_LUMINANCE16;
+ i._type = GL_UNSIGNED_SHORT;
+ bytesPerElement = 2;
+ break;
+
+ case FOURCC_R16F:
+ i._format = GL_LUMINANCE; //should use red, once it is available
+ i._internalFormat = GL_LUMINANCE16F_ARB;
+ i._type = GL_HALF_FLOAT;
+ bytesPerElement = 2;
+ break;
+
+ case FOURCC_A16B16G16R16F:
+ i._format = GL_RGBA;
+ i._internalFormat = GL_RGBA16F_ARB;
+ i._type = GL_HALF_FLOAT;
+ bytesPerElement = 8;
+ break;
+
+ case FOURCC_R32F:
+ i._format = GL_LUMINANCE; //should use red, once it is available
+ i._internalFormat = GL_LUMINANCE32F_ARB;
+ i._type = GL_FLOAT;
+ bytesPerElement = 4;
+ break;
+
+ case FOURCC_A32B32G32R32F:
+ i._format = GL_RGBA;
+ i._internalFormat = GL_RGBA32F;
+ i._type = GL_FLOAT;
+ bytesPerElement = 16;
+ break;
+
+ case FOURCC_UNKNOWN:
+ case FOURCC_X1R5G5B5:
+ case FOURCC_A1R5G5B5:
+ case FOURCC_A4R4G4B4:
+ case FOURCC_R3G3B2:
+ case FOURCC_A8R3G3B2:
+ case FOURCC_X4R4G4B4:
+ case FOURCC_A4L4:
+ case FOURCC_D16_LOCKABLE:
+ case FOURCC_D32:
+ case FOURCC_D24X8:
+ case FOURCC_D16:
+ case FOURCC_D32F_LOCKABLE:
+ case FOURCC_G16R16:
+ case FOURCC_G16R16F:
+ case FOURCC_G32R32F:
+ //these are unsupported for now
+ default:
+ fclose(fp);
+ return false;
+ }
+ }
+ else if (ddsh.ddspf.dwFlags == DDSF_RGBA && ddsh.ddspf.dwRGBBitCount == 32)
+ {
+ i._format = GL_BGRA;
+ i._internalFormat = GL_RGBA8;
+ i._type = GL_UNSIGNED_BYTE;
+ bytesPerElement = 4;
+ }
+ else if (ddsh.ddspf.dwFlags == DDSF_RGB && ddsh.ddspf.dwRGBBitCount == 32)
+ {
+ i._format = GL_BGR;
+ i._internalFormat = GL_RGBA8;
+ i._type = GL_UNSIGNED_BYTE;
+ bytesPerElement = 4;
+ }
+ else if (ddsh.ddspf.dwFlags == DDSF_RGB && ddsh.ddspf.dwRGBBitCount == 24)
+ {
+ i._format = GL_BGR;
+ i._internalFormat = GL_RGB8;
+ i._type = GL_UNSIGNED_BYTE;
+ bytesPerElement = 3;
+ }
+ else if (ddsh.ddspf.dwRGBBitCount == 8)
+ {
+ i._format = GL_LUMINANCE;
+ i._internalFormat = GL_LUMINANCE8;
+ i._type = GL_UNSIGNED_BYTE;
+ bytesPerElement = 1;
+ }
+ else
+ {
+ fclose(fp);
+ return false;
+ }
+
+ i._elementSize = bytesPerElement;
+
+ for (int face = 0; face < ((i._faces) ? i._faces : 1); face++) {
+ int w = i._width, h = i._height, d = (i._depth) ? i._depth : 1;
+ for (int level = 0; level < i._levelCount; level++) {
+ int bw = (btcCompressed) ? (w+3)/4 : w;
+ int bh = (btcCompressed) ? (h+3)/4 : h;
+ int size = bw*bh*d*bytesPerElement;
+
+ GLubyte *data = new GLubyte[size];
+
+ fread( data, size, 1, fp);
+
+ i._data.push_back(data);
+
+ if (i._faces != 6)
+ i.flipSurface( data, w, h, d);
+
+ //reduce mip sizes
+ w = ( w > 1) ? w >> 1 : 1;
+ h = ( h > 1) ? h >> 1 : 1;
+ d = ( d > 1) ? d >> 1 : 1;
+ }
+ }
+/*
+ //reverse cube map y faces
+ if (i._faces == 6) {
+ for (int level = 0; level < i._levelCount; level++) {
+ GLubyte *temp = i._data[2*i._levelCount + level];
+ i._data[2*i._levelCount + level] = i._data[3*i._levelCount + level];
+ i._data[3*i._levelCount + level] = temp;
+ }
+ }
+ */
+
+ fclose(fp);
+ return true;
+}
+
+//
+// flip a DXT1 color block
+////////////////////////////////////////////////////////////
+void Image::flip_blocks_dxtc1(GLubyte *ptr, unsigned int numBlocks)
+{
+ DXTColBlock *curblock = (DXTColBlock*)ptr;
+ GLubyte temp;
+
+ for (unsigned int i = 0; i < numBlocks; i++) {
+ temp = curblock->row[0];
+ curblock->row[0] = curblock->row[3];
+ curblock->row[3] = temp;
+ temp = curblock->row[1];
+ curblock->row[1] = curblock->row[2];
+ curblock->row[2] = temp;
+
+ curblock++;
+ }
+}
+
+//
+// flip a DXT3 color block
+////////////////////////////////////////////////////////////
+void Image::flip_blocks_dxtc3(GLubyte *ptr, unsigned int numBlocks)
+{
+ DXTColBlock *curblock = (DXTColBlock*)ptr;
+ DXT3AlphaBlock *alphablock;
+ GLushort tempS;
+ GLubyte tempB;
+
+ for (unsigned int i = 0; i < numBlocks; i++)
+ {
+ alphablock = (DXT3AlphaBlock*)curblock;
+
+ tempS = alphablock->row[0];
+ alphablock->row[0] = alphablock->row[3];
+ alphablock->row[3] = tempS;
+ tempS = alphablock->row[1];
+ alphablock->row[1] = alphablock->row[2];
+ alphablock->row[2] = tempS;
+
+ curblock++;
+
+ tempB = curblock->row[0];
+ curblock->row[0] = curblock->row[3];
+ curblock->row[3] = tempB;
+ tempB = curblock->row[1];
+ curblock->row[1] = curblock->row[2];
+ curblock->row[2] = tempB;
+
+ curblock++;
+ }
+}
+
+//
+// flip a DXT5 alpha block
+////////////////////////////////////////////////////////////
+void flip_dxt5_alpha(DXT5AlphaBlock *block)
+{
+ GLubyte gBits[4][4];
+
+ const uint32_t mask = 0x00000007; // bits = 00 00 01 11
+ uint32_t bits = 0;
+ memcpy(&bits, &block->row[0], sizeof(unsigned char) * 3);
+
+ gBits[0][0] = (GLubyte)(bits & mask);
+ bits >>= 3;
+ gBits[0][1] = (GLubyte)(bits & mask);
+ bits >>= 3;
+ gBits[0][2] = (GLubyte)(bits & mask);
+ bits >>= 3;
+ gBits[0][3] = (GLubyte)(bits & mask);
+ bits >>= 3;
+ gBits[1][0] = (GLubyte)(bits & mask);
+ bits >>= 3;
+ gBits[1][1] = (GLubyte)(bits & mask);
+ bits >>= 3;
+ gBits[1][2] = (GLubyte)(bits & mask);
+ bits >>= 3;
+ gBits[1][3] = (GLubyte)(bits & mask);
+
+ bits = 0;
+ memcpy(&bits, &block->row[3], sizeof(GLubyte) * 3);
+
+ gBits[2][0] = (GLubyte)(bits & mask);
+ bits >>= 3;
+ gBits[2][1] = (GLubyte)(bits & mask);
+ bits >>= 3;
+ gBits[2][2] = (GLubyte)(bits & mask);
+ bits >>= 3;
+ gBits[2][3] = (GLubyte)(bits & mask);
+ bits >>= 3;
+ gBits[3][0] = (GLubyte)(bits & mask);
+ bits >>= 3;
+ gBits[3][1] = (GLubyte)(bits & mask);
+ bits >>= 3;
+ gBits[3][2] = (GLubyte)(bits & mask);
+ bits >>= 3;
+ gBits[3][3] = (GLubyte)(bits & mask);
+
+ // clear existing alpha bits
+ memset(block->row, 0, sizeof(GLubyte) * 6);
+
+ uint32_t *pBits = ((uint32_t*) &(block->row[0]));
+
+ *pBits = *pBits | (gBits[3][0] << 0);
+ *pBits = *pBits | (gBits[3][1] << 3);
+ *pBits = *pBits | (gBits[3][2] << 6);
+ *pBits = *pBits | (gBits[3][3] << 9);
+
+ *pBits = *pBits | (gBits[2][0] << 12);
+ *pBits = *pBits | (gBits[2][1] << 15);
+ *pBits = *pBits | (gBits[2][2] << 18);
+ *pBits = *pBits | (gBits[2][3] << 21);
+
+ pBits = ((uint32_t*) &(block->row[3]));
+
+ *pBits = *pBits | (gBits[1][0] << 0);
+ *pBits = *pBits | (gBits[1][1] << 3);
+ *pBits = *pBits | (gBits[1][2] << 6);
+ *pBits = *pBits | (gBits[1][3] << 9);
+
+ *pBits = *pBits | (gBits[0][0] << 12);
+ *pBits = *pBits | (gBits[0][1] << 15);
+ *pBits = *pBits | (gBits[0][2] << 18);
+ *pBits = *pBits | (gBits[0][3] << 21);
+}
+
+//
+// flip a DXT5 color block
+////////////////////////////////////////////////////////////
+void Image::flip_blocks_dxtc5(GLubyte *ptr, unsigned int numBlocks)
+{
+ DXTColBlock *curblock = (DXTColBlock*)ptr;
+ DXT5AlphaBlock *alphablock;
+ GLubyte temp;
+
+ for (unsigned int i = 0; i < numBlocks; i++)
+ {
+ alphablock = (DXT5AlphaBlock*)curblock;
+
+ flip_dxt5_alpha(alphablock);
+
+ curblock++;
+
+ temp = curblock->row[0];
+ curblock->row[0] = curblock->row[3];
+ curblock->row[3] = temp;
+ temp = curblock->row[1];
+ curblock->row[1] = curblock->row[2];
+ curblock->row[2] = temp;
+
+ curblock++;
+ }
+}
+
+
+}
diff --git a/Source/Images/texture_data.cpp b/Source/Images/texture_data.cpp
new file mode 100644
index 00000000..cea80339
--- /dev/null
+++ b/Source/Images/texture_data.cpp
@@ -0,0 +1,647 @@
+//-----------------------------------------------------------------------------
+// Name: texture_data.cpp
+// Developer: Wolfire Games LLC
+// Author: Phillip Isola
+// Description: Generic storage texture image data.
+// Reading and writing from/to various image formats.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "texture_data.h"
+
+#include <Images/freeimage_wrapper.h>
+#include <Images/image_export.hpp>
+
+#include <Internal/common.h>
+#include <Internal/datemodified.h>
+#include <Internal/error.h>
+#include <Internal/filesystem.h>
+#include <Internal/profiler.h>
+
+#include <Compat/fileio.h>
+#include <Memory/allocation.h>
+#include <Logging/logdata.h>
+#include <Utility/assert.h>
+
+#include <cstdlib>
+#include <cstring>
+#include <cmath>
+
+using std::string;
+using std::endl;
+using std::min;
+// TODO: avoid "using namespace" if possible
+using namespace crnlib;
+
+// TODO: this could be used elsewhere
+// From http://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2
+bool IsPow2(int v) {
+ return (v & (v - 1)) == 0;
+}
+
+bool TextureData::IsCube() const {
+ return is_cube;
+}
+
+bool TextureData::IsCompressed() const {
+ return is_packed;
+}
+
+bool TextureData::HasMipmaps() const {
+ if (mip_levels == 1) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+unsigned int TextureData::GetWidth() const {
+ return width;
+}
+
+
+unsigned int TextureData::GetHeight() const {
+ return height;
+}
+
+
+unsigned int TextureData::GetMipWidth(unsigned int mip) const {
+ const unsigned face = 0;
+ const unsigned index = face*mip_levels+mip;
+ if( index < mip_widths.size() ) {
+ return mip_widths[index];
+ } else {
+ return 0;
+ }
+}
+
+
+unsigned int TextureData::GetMipHeight(unsigned int mip) const {
+ const unsigned face = 0;
+ const unsigned index = face*mip_levels+mip;
+ if( index < mip_heights.size() ) {
+ return mip_heights[index];
+ } else {
+ return 0;
+ }
+}
+
+
+unsigned int TextureData::GetNumFaces() const {
+ return num_faces;
+}
+
+
+unsigned int TextureData::GetMipLevels() const {
+ return mip_levels;
+}
+
+
+GLenum TextureData::GetGLBaseFormat() const {
+ return gl_base_format;
+}
+
+
+GLenum TextureData::GetGLInternalFormat() const {
+ return gl_internal_format;
+}
+
+
+GLenum TextureData::GetGLType() const {
+ return gl_type;
+}
+
+
+unsigned int TextureData::GetMipDataSize(unsigned int face, unsigned int mip) const {
+ const unsigned index = face*mip_levels+mip;
+ if( index < mip_data_sizes.size() ) {
+ return mip_data_sizes[index];
+ } else {
+ return 0;
+ }
+}
+
+
+const char *TextureData::GetMipData(unsigned int face, unsigned int mip) const {
+
+ if( is_loaded ) {
+ const mip_level *level = m_crnTex.get_level(face, mip);
+
+ if (level->is_packed()) {
+ const dxt_image *dxt = level->get_dxt_image();
+ assert(dxt != NULL);
+ return reinterpret_cast<const char *>(dxt->get_element_ptr());
+ } else {
+ const image_u8 *img = level->get_image();
+ assert(img != NULL);
+ return reinterpret_cast<const char *>(img->get_pixels());
+ }
+ } else {
+ LOGE << "Unable to get MipData from texturedata, data is unloaded " << source_path << endl;
+ return NULL;
+ }
+}
+
+bool TextureData::Load(const char *abs_path) {
+ is_loaded = false;
+ m_crnTex.clear();
+
+ source_path = abs_path;
+
+ texture_file_types::format src_file_format = texture_file_types::determine_file_format(abs_path);
+ if (src_file_format == texture_file_types::cFormatInvalid) {
+ LOGE << "Unrecognized file type: " << abs_path << endl;
+ return false;
+ }
+
+ {
+ PROFILER_ENTER(g_profiler_ctx, "TextureData::Load actual file loading");
+ FILE* pFile = my_fopen(abs_path, "rb");
+ if( pFile ) {
+ // obtain file size:
+ fseek (pFile , 0 , SEEK_END);
+ int lSize = ftell (pFile);
+ rewind (pFile);
+
+ // allocate memory to contain the whole file:
+ char* buffer = (char*) alloc.stack.Alloc(sizeof(char)*lSize);
+
+ const int kBufSize = 512;
+ char error_msg[kBufSize];
+ #ifndef NO_ERR
+ if (buffer == NULL) {
+ FormatString(error_msg, kBufSize, "Could not allocate memory to checksum: %s.", abs_path);
+ FatalError("Error", error_msg);
+ }
+ #endif
+
+ // copy the file into the buffer:
+ size_t result = fread (buffer,1,lSize,pFile);
+ #ifndef NO_ERR
+ if (result != (size_t)lSize) {
+ FormatString(error_msg, kBufSize, "Could not read data from file: %s.", abs_path);
+ FatalError("Error", error_msg);
+ }
+ #endif
+
+ crnlib::buffer_stream buf_stream(buffer, lSize);
+ buf_stream.set_name(abs_path);
+ data_stream_serializer serializer(buf_stream);
+
+ // terminate
+ fclose (pFile);
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "TextureData::Load read_from_stream");
+ if (!m_crnTex.read_from_stream(serializer, src_file_format)) {
+ if (m_crnTex.get_last_error().is_empty()) {
+ string contents = "Failed reading source file: " + string(abs_path);
+ LOGE << contents << endl;
+ } else {
+ LOGE << m_crnTex.get_last_error().get_ptr() << endl;
+ }
+ alloc.stack.Free(buffer);
+ return false;
+ }
+ }
+
+ alloc.stack.Free(buffer);
+ } else {
+ LOGE << "fopen on texture file path: " << abs_path << " failed to open" << endl;
+ return false;
+ }
+ }
+
+ // try to determine color space
+ string src(abs_path);
+ for (string::size_type i = 0; i < src.size(); i++) {
+ src[i] = tolower(src[i]);
+ }
+ if (src.rfind("_c.") != string::npos) {
+ m_colorSpace = TextureData::sRGB;
+ } else if (src.rfind("_color.") != string::npos) {
+ m_colorSpace = TextureData::sRGB;
+ } else if (src.rfind("_n.") != string::npos) {
+ // linear
+ } else if (src.rfind("_normal.") != string::npos) {
+ // linear
+ } else if (src.rfind("_norm.") != string::npos) {
+ // linear
+ } else {
+ //LOGW << "File " << abs_path << " does not specify color space" << endl;
+ }
+
+ // vvv commented out the non-flipping exception because it was messing up the spawner thumbnails -David
+
+ // for whatever reason uncompressed non-pow2 textures must not be flipped
+ //if (m_crnTex.is_packed() || (IsPow2(m_crnTex.get_width()) && IsPow2(m_crnTex.get_height()))) {
+ // need to flip it, apparently crunch does something different than nvImage
+ /*{
+ PROFILER_ZONE(g_profiler_ctx, "TextureData::Load flip_y");
+ m_crnTex.flip_y(true);
+ }*/
+ //}
+
+ if (!m_crnTex.is_packed()) {
+ pixel_format format = m_crnTex.get_format();
+ switch (format) {
+ case PIXEL_FMT_A8R8G8B8:
+ break; // nothing to do
+
+ default:
+ {
+ PROFILER_ZONE(g_profiler_ctx, "TextureData::Load m_crnTex.convert");
+ dxt_image::pack_params p;
+ m_crnTex.convert(PIXEL_FMT_A8R8G8B8, true, p);
+ }
+ break;
+ }
+ }
+
+ is_loaded = true;
+ ExtractMetaData();
+ return true;
+}
+
+bool TextureData::EnsureInRAM() {
+ if( !is_loaded ) {
+ LOGW << "Reloading " << source_path << " into ram from disk, it's needed again" << endl;
+ return Load(source_path.c_str());
+ }
+ return true;
+}
+
+void TextureData::GetUncompressedData(unsigned char* data) {
+ if( is_loaded ) {
+ pixel_format format = m_crnTex.get_format();
+ int imageBits = 32;
+
+ int bytesPerPixel = imageBits / 8;
+ int imageWidth = m_crnTex.get_width();
+ int imageHeight = m_crnTex.get_height();
+
+ int heightDataSize = imageWidth * imageHeight;
+ int imageDataSize = heightDataSize * bytesPerPixel;
+
+ image_u8 image;
+ image_u8* pImg = m_crnTex.get_level_image(0, 0, image);
+
+ if (imageBits == 8) {
+ for (int y = 0; y < imageHeight; y++) {
+ color_quad_u8 *bits = pImg->get_scanline(y);
+ for (int x = 0; x < imageWidth; x++) {
+ int curr_index = x + y*imageWidth;
+ data[curr_index] = bits[x].a;
+ }
+ }
+ } else if (imageBits == 24) {
+ for (int y = 0; y < imageHeight; y++) {
+ color_quad_u8 *bits = pImg->get_scanline(y);
+ for (int x = 0; x < imageWidth; x++) {
+ int curr_index = x + y*imageWidth;
+ data[4 * curr_index] = bits[x].b;
+ data[4 * curr_index + 1] = bits[x].g;
+ data[4 * curr_index + 2] = bits[x].r;
+ data[4 * curr_index + 3] = 255;
+ }
+ }
+ } else if (imageBits == 32) {
+ for (int y = 0; y < imageHeight; y++) {
+ color_quad_u8 *bits = pImg->get_scanline(y);
+ for (int x = 0; x < imageWidth; x++) {
+ int curr_index = x + y*imageWidth;
+ data[4 * curr_index] = bits[x].b;
+ data[4 * curr_index + 1] = bits[x].g;
+ data[4 * curr_index + 2] = bits[x].r;
+ data[4 * curr_index + 3] = bits[x].a;
+ }
+ }
+ // TODO: what is this?
+ /*
+ } else if (m_nImageBits == 96) {
+ for(int y = 0; y < m_nImageHeight; y++) {
+ BYTE *bits = FreeImage_GetScanLine(image, y);
+ for(int x = 0; x < m_nImageWidth; x++) {
+ float pixelf[3];
+ for(int i=0; i<3; ++i){
+ pixelf[i] = *((float*)(&(bits[i*4])));
+ pixelf[i] = pow(pixelf[i], 1.0f/2.2f);
+ }
+ int curr_index = x + y*m_nImageWidth;
+ m_nImageData[4*curr_index] = (unsigned char)(min(1.0f, pixelf[2]) * 255.0f);
+ m_nImageData[4*curr_index+1] = (unsigned char)(min(1.0f, pixelf[1]) * 255.0f);
+ m_nImageData[4*curr_index+2] = (unsigned char)(min(1.0f, pixelf[0]) * 255.0f);
+ m_nImageData[4*curr_index+3] = 255;
+ bits += bytesPerPixel;
+ }
+ */
+ }
+ } else {
+ LOGE << "Unable to load LoadUncompressedData from texturedata, data is unloaded " << endl;
+ }
+}
+
+void TextureData::SetColorSpace(ColorSpace color_space)
+{
+ m_colorSpace = color_space;
+ ExtractMetaData();
+}
+
+bool TextureData::GenerateMipmaps() {
+ bool result = false;
+ if( is_loaded ) {
+ mipmapped_texture::generate_mipmap_params mipParams;
+ // TODO: set parameters
+ result = m_crnTex.generate_mipmaps(mipParams, false);
+
+ ExtractMetaData();
+ } else {
+ LOGE << "Unable to GenerateMipmaps for texturedata, data unloaded " << source_path << endl;
+ }
+
+ return result;
+}
+
+bool TextureData::ConvertDXT(pixel_format format, ConversionQuality quality) {
+ bool ret_val = false;
+
+ if( is_loaded ) {
+ dxt_image::pack_params packParams;
+ if (quality == Nice) {
+ packParams.m_quality = cCRNDXTQualityUber;
+ packParams.m_compressor = cCRNDXTCompressorCRN;
+ } else {
+ packParams.m_quality = cCRNDXTQualitySuperFast;
+ packParams.m_compressor = cCRNDXTCompressorRYG;
+ }
+ // for whatever reason non-pow2 textures must be flipped
+ if (!IsPow2(m_crnTex.get_width()) || !IsPow2(m_crnTex.get_height())) {
+ m_crnTex.flip_y(true);
+ ret_val = m_crnTex.convert(format, packParams);
+ m_crnTex.flip_y(true);
+ } else {
+ ret_val = m_crnTex.convert(format, packParams);
+ }
+
+ ExtractMetaData();
+ } else {
+ LOGE << "Can't ConvertDXT to texturedata, data is unloaded " << source_path << endl;
+ }
+
+ return ret_val;
+}
+
+bool TextureData::SaveDDS(const char *abs_path) {
+ bool result = false;
+ if( is_loaded ) {
+ result = m_crnTex.write_to_file(abs_path, texture_file_types::cFormatDDS);
+ if (!result) {
+ LOGE << m_crnTex.get_last_error().get_ptr() << endl;
+ }
+ } else {
+ LOGE << "Can't save texturedata as DDS to " << abs_path << " data is unloaded " << source_path << endl;
+ }
+ return result;
+}
+
+bool TextureData::SaveCRN(const char *abs_path, crn_format format, ConversionQuality quality) {
+ bool result = false;
+ if( is_loaded ) {
+ crn_comp_params params;
+ params.m_format = format;
+ if (quality == Nice) {
+ params.m_quality_level = cCRNMaxQualityLevel;
+ params.m_dxt_quality = cCRNDXTQualityUber;
+ } else {
+ params.m_quality_level = cCRNMinQualityLevel;
+ params.m_dxt_quality = cCRNDXTQualitySuperFast;
+ }
+ result = m_crnTex.write_to_file(abs_path, texture_file_types::cFormatCRN, &params);
+ } else {
+ LOGE << "Trying to run SaveCRN on unloaded texturedata object " << endl;
+ }
+ return result;
+}
+
+bool TextureData::IsInRAM() {
+ return is_loaded;
+}
+
+void TextureData::UnloadData() {
+ m_crnTex.clear();
+ is_loaded = false;
+}
+
+void TextureData::ExtractMetaData() {
+ is_packed = m_crnTex.is_packed();
+ is_cube = (m_crnTex.get_num_faces() == 6);
+
+ width = m_crnTex.get_width();
+ height = m_crnTex.get_height();
+
+ mip_levels = m_crnTex.get_num_levels();
+ num_faces = m_crnTex.get_num_faces();
+
+ mip_widths.clear();
+ mip_heights.clear();
+ mip_data_sizes.clear();
+
+ for( int k = 0; k < num_faces; k++ ) {
+ for( int i = 0; i < mip_levels; i++ ) {
+ const mip_level *level = m_crnTex.get_level(k,i);
+ mip_widths.push_back(level->get_width());
+ mip_heights.push_back(level->get_height());
+ if (level->is_packed()) {
+ const dxt_image *dxt = level->get_dxt_image();
+ LOG_ASSERT(dxt != NULL);
+ mip_data_sizes.push_back(dxt->get_size_in_bytes());
+ } else {
+ const image_u8 *img = level->get_image();
+ assert(img != NULL);
+ mip_data_sizes.push_back(img->get_size_in_bytes());
+ }
+ }
+ }
+
+
+ switch (m_crnTex.get_format()) {
+ case crnlib::PIXEL_FMT_DXT1:
+ case crnlib::PIXEL_FMT_DXT1A:
+ case crnlib::PIXEL_FMT_DXT2:
+ case crnlib::PIXEL_FMT_DXT3:
+ case crnlib::PIXEL_FMT_DXT4:
+ case crnlib::PIXEL_FMT_DXT5:
+ //LOGE << "Unsupported pixel format in texture " << source_path << endl;
+ gl_base_format = GL_NONE;
+ break;
+
+ case crnlib::PIXEL_FMT_R8G8B8:
+ gl_base_format = GL_RGB;
+ break;
+
+ case crnlib::PIXEL_FMT_A8R8G8B8:
+ gl_base_format = GL_RGBA;
+ break;
+
+ case crnlib::PIXEL_FMT_INVALID:
+ case crnlib::PIXEL_FMT_3DC:
+ case crnlib::PIXEL_FMT_DXN:
+ case crnlib::PIXEL_FMT_DXT5A:
+ case crnlib::PIXEL_FMT_DXT5_CCxY:
+ case crnlib::PIXEL_FMT_DXT5_xGxR:
+ case crnlib::PIXEL_FMT_DXT5_xGBR:
+ case crnlib::PIXEL_FMT_DXT5_AGBR:
+ case crnlib::PIXEL_FMT_ETC1:
+ case crnlib::PIXEL_FMT_L8:
+ case crnlib::PIXEL_FMT_A8:
+ case crnlib::PIXEL_FMT_A8L8:
+ LOGE << "Unsupported pixel format in texture " << source_path << endl;
+ gl_base_format = GL_NONE;
+ break;
+ }
+
+ if (m_colorSpace == Linear) {
+ switch (m_crnTex.get_format()) {
+ case crnlib::PIXEL_FMT_DXT1:
+ gl_internal_format = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
+ break;
+
+ case crnlib::PIXEL_FMT_DXT1A:
+ gl_internal_format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
+ break;
+
+ case crnlib::PIXEL_FMT_DXT2:
+ case crnlib::PIXEL_FMT_DXT3:
+ gl_internal_format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
+ break;
+
+ case crnlib::PIXEL_FMT_DXT4:
+ case crnlib::PIXEL_FMT_DXT5:
+ gl_internal_format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+ break;
+
+ case crnlib::PIXEL_FMT_R8G8B8:
+ gl_internal_format = GL_RGB8;
+ break;
+
+ case crnlib::PIXEL_FMT_A8R8G8B8:
+ gl_internal_format = GL_RGBA8;
+ break;
+
+ case crnlib::PIXEL_FMT_INVALID:
+ case crnlib::PIXEL_FMT_3DC:
+ case crnlib::PIXEL_FMT_DXN:
+ case crnlib::PIXEL_FMT_DXT5A:
+ case crnlib::PIXEL_FMT_DXT5_CCxY:
+ case crnlib::PIXEL_FMT_DXT5_xGxR:
+ case crnlib::PIXEL_FMT_DXT5_xGBR:
+ case crnlib::PIXEL_FMT_DXT5_AGBR:
+ case crnlib::PIXEL_FMT_ETC1:
+ case crnlib::PIXEL_FMT_L8:
+ case crnlib::PIXEL_FMT_A8:
+ case crnlib::PIXEL_FMT_A8L8:
+ LOGE << "Unsupported pixel format in texture " << source_path << endl;
+ gl_internal_format = GL_NONE;
+ break;
+
+ }
+ } else {
+ switch (m_crnTex.get_format()) {
+ case crnlib::PIXEL_FMT_DXT1:
+ gl_internal_format = GL_COMPRESSED_SRGB_S3TC_DXT1_EXT;
+ break;
+
+ case crnlib::PIXEL_FMT_DXT1A:
+ gl_internal_format = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;
+ break;
+
+ case crnlib::PIXEL_FMT_DXT2:
+ case crnlib::PIXEL_FMT_DXT3:
+ gl_internal_format = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT;
+ break;
+
+ case crnlib::PIXEL_FMT_DXT4:
+ case crnlib::PIXEL_FMT_DXT5:
+ gl_internal_format = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
+ break;
+
+ case crnlib::PIXEL_FMT_R8G8B8:
+ gl_internal_format = GL_SRGB8;
+ break;
+
+ case crnlib::PIXEL_FMT_A8R8G8B8:
+ gl_internal_format = GL_SRGB8_ALPHA8;
+ break;
+
+ case crnlib::PIXEL_FMT_INVALID:
+ case crnlib::PIXEL_FMT_3DC:
+ case crnlib::PIXEL_FMT_DXN:
+ case crnlib::PIXEL_FMT_DXT5A:
+ case crnlib::PIXEL_FMT_DXT5_CCxY:
+ case crnlib::PIXEL_FMT_DXT5_xGxR:
+ case crnlib::PIXEL_FMT_DXT5_xGBR:
+ case crnlib::PIXEL_FMT_DXT5_AGBR:
+ case crnlib::PIXEL_FMT_ETC1:
+ case crnlib::PIXEL_FMT_L8:
+ case crnlib::PIXEL_FMT_A8:
+ case crnlib::PIXEL_FMT_A8L8:
+ LOGE << "Unsupported pixel format in texture " << source_path << endl;
+ gl_internal_format = GL_NONE;
+ break;
+ }
+ }
+
+ LOG_ASSERT(m_crnTex.is_valid());
+ switch (m_crnTex.get_format()) {
+ case crnlib::PIXEL_FMT_DXT1:
+ case crnlib::PIXEL_FMT_DXT1A:
+ case crnlib::PIXEL_FMT_DXT2:
+ case crnlib::PIXEL_FMT_DXT3:
+ case crnlib::PIXEL_FMT_DXT4:
+ case crnlib::PIXEL_FMT_DXT5:
+ //LOGE << "Unsupported pixel format in texture " << source_path << endl;
+ gl_type = GL_NONE;
+ break;
+
+ case crnlib::PIXEL_FMT_R8G8B8:
+ gl_type = GL_UNSIGNED_BYTE;
+ break;
+
+ case crnlib::PIXEL_FMT_A8R8G8B8:
+ gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
+ break;
+
+ case crnlib::PIXEL_FMT_INVALID:
+ case crnlib::PIXEL_FMT_3DC:
+ case crnlib::PIXEL_FMT_DXN:
+ case crnlib::PIXEL_FMT_DXT5A:
+ case crnlib::PIXEL_FMT_DXT5_CCxY:
+ case crnlib::PIXEL_FMT_DXT5_xGxR:
+ case crnlib::PIXEL_FMT_DXT5_xGBR:
+ case crnlib::PIXEL_FMT_DXT5_AGBR:
+ case crnlib::PIXEL_FMT_ETC1:
+ case crnlib::PIXEL_FMT_L8:
+ case crnlib::PIXEL_FMT_A8:
+ case crnlib::PIXEL_FMT_A8L8:
+ LOGE << "Unsupported pixel format in texture " << source_path << endl;
+ gl_type = GL_NONE;
+ break;
+ }
+}
+
diff --git a/Source/Images/texture_data.h b/Source/Images/texture_data.h
new file mode 100644
index 00000000..975a3034
--- /dev/null
+++ b/Source/Images/texture_data.h
@@ -0,0 +1,118 @@
+//-----------------------------------------------------------------------------
+// Name: texture_data.h
+// Developer: Wolfire Games LLC
+// Author: Phillip Isola
+// Description: Generic storage texture image data.
+// Reading and writing from/to various image formats.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Wrappers/crn.h>
+
+#include <opengl.h>
+
+#include <ostream>
+#include <vector>
+
+class StackAllocator;
+
+class TextureData {
+public:
+ enum ColorSpace {
+ Linear
+ , sRGB
+ };
+
+ enum ConversionQuality {
+ Nice
+ , Fast
+ };
+
+ TextureData() :
+ m_colorSpace(Linear),
+ is_loaded(false),
+ height(0),
+ width(0),
+ num_faces(0),
+ mip_levels(0),
+ gl_base_format(GL_NONE),
+ gl_internal_format(GL_NONE),
+ gl_type(GL_NONE)
+ {}
+
+ bool IsCube() const;
+ bool IsCompressed() const;
+ bool HasMipmaps() const;
+ unsigned int GetWidth() const;
+ unsigned int GetHeight() const;
+ unsigned int GetMipWidth(unsigned int mip) const;
+ unsigned int GetMipHeight(unsigned int mip) const;
+ unsigned int GetNumFaces() const;
+ unsigned int GetMipLevels() const;
+ GLenum GetGLBaseFormat() const; // for glTexImage2D, may only be called on uncompressed textures
+ GLenum GetGLInternalFormat() const;
+ GLenum GetGLType() const; // for glTexImage2D, may only be called on uncompressed textures
+ ColorSpace GetColorSpace() const { return m_colorSpace; }
+ unsigned int GetMipDataSize(unsigned int face, unsigned int mip) const;
+ const char* GetMipData(unsigned int face, unsigned int mip) const;
+ void GetUncompressedData(unsigned char* data);
+ void SetColorSpace(ColorSpace color_space);
+
+ bool Load(const char *abs_path);
+ bool EnsureInRAM();
+
+ bool GenerateMipmaps();
+ bool ConvertDXT(crnlib::pixel_format format, ConversionQuality quality);
+ bool SaveDDS(const char *abs_path);
+ bool SaveCRN(const char *abs_path, crn_format format, ConversionQuality quality);
+
+ bool IsInRAM();
+ void UnloadData();
+
+ inline std::string GetPath() { return source_path; }
+
+private:
+ std::string source_path;
+
+ void ExtractMetaData();
+
+ bool is_loaded;
+ bool is_cube;
+ bool is_packed;
+ unsigned int width, height;
+ std::vector<int> mip_widths;
+ std::vector<int> mip_heights;
+ std::vector<int> mip_data_sizes;
+ int num_faces, mip_levels;
+ GLenum gl_base_format, gl_internal_format, gl_type;
+ int mip_data_size;
+
+ crnlib::mipmapped_texture m_crnTex;
+ ColorSpace m_colorSpace;
+};
+
+inline std::ostream& operator<<(std::ostream& out, const TextureData& td )
+{
+ unsigned int totalBytes = td.GetWidth() * td.GetHeight() * 32 / 8;
+ out << "TextureData(w:" << td.GetWidth() << ",h:" << td.GetHeight() << ",size:" << totalBytes << ")";
+ return out;
+}
+
+bool IsPow2(int v);
diff --git a/Source/Internal/SIMD.h b/Source/Internal/SIMD.h
new file mode 100644
index 00000000..31324560
--- /dev/null
+++ b/Source/Internal/SIMD.h
@@ -0,0 +1,706 @@
+//-----------------------------------------------------------------------------
+// Name: SIMD.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+/*
+ SIMD.h
+
+ A suite of functions for working with single-instruction-multiple-data intrinsics
+ across various platforms. Floating point vectors only for now but integer vectors
+ could be supported later if need be.
+
+TYPES
+ typename vec4 : Float vector type, a 16 byte vector containing 4 floats.
+
+LOAD/STORE
+ vload(ptr,byteoffset) : Load with byte offset. Final address must be 16 byte aligned.
+ Offset must be constant.
+ vstore(val,ptr,byteoffset) : Store with byte offset. Final address must be 16-byte aligned.
+ Offset must be constant.
+ vset(a,b,c,d) : Set an immediate constant, a,b,c,d must be constants.
+ vzero() : Return a vector of zeros.
+ vone() : Return a vector of ones.
+
+MANIPULATION
+ vsplat(P,i) : Component splat, replicates a single component across a vector. Index must be constant.
+ returns { P[i], P[i], P[i], P[i] }
+ vshuffle(P,a,b,c,d) : Shuffles the order of the components of P. Indices a,b,c,d must be constant.
+ returns { P[a], P[b], P[c], P[d] }
+ vmergeh(P,Q) : Merges the first two (or "high") components of each vector into a new one.
+ returns { P[0], Q[0], P[1], Q[1] }
+ vmergel(P,Q) : Merges the second two (or "low") components of each vector into a new one.
+ returns { P[2], Q[2], P[3], Q[3] }
+ vperm(P,Q, a,b,c,d) : Pairs two components of P with two components of Q. Indices a,b,c,d must be constant.
+ returns { P[a], P[b], Q[c], Q[d] }
+
+ARITHMETIC
+ vadd(a,b) : a + b addition
+ vsub(a,b) : a - b subtraction
+ vmul(a,b) : a * b multiplication
+ vmadd(a,b,c) : (a * b) + c fused multiply-add, often faster than separate add & mul
+ vmsub(a,b,c) : c - (a * b) fused multiply-subtract, often faster than separate sub & mul
+ vdiv(a,b) : a / b division, usually slower than mul
+ vrecip(a) : 1 / a reciprocal
+ vsqrt(a) : sqrt(a) square root, typically slower than vrsqrt
+ vrsqrt(a) : 1.0 / sqrt(a) reciprocal square root, typically faster than vsqrt
+ vmin(a,b) : a < b ? a : b minimum
+ vmax(a,b) : a > b ? a : b maximum
+ vabs(a) : a > 0 ? a : -a absolute value
+
+BIT LOGIC
+ vand(a,b) : a & b bitwise and
+ vandc(a,b) : a & ~b bitwise and with complement
+ vor(a,b) : a | b bitwise or
+ vxor(a,b) : a ^ b bitwise exclusive or
+ vnot(a) : ~a bitwise complement
+
+COMPARISON
+ vequal(a,b) : a == b ? ~0 : 0 per-component equal to
+ vless(a,b) : a < b ? ~0 : 0 per-component less than
+ vlessequal(a,b) : a <= b ? ~0 : 0 per-component less than or equal to
+ vgreater(a,b) : a > b ? ~0 : 0 per-component greater than
+ vgreaterequal(a,b) : a >= b ? ~0 : 0 per-component greater than or equal to
+
+TODO:
+ - ps3 implementation
+ - vec4 name conflict?
+*/
+
+#ifndef CPR_SIMD_H
+#define CPR_SIMD_H
+
+namespace SIMD {
+
+#ifdef _WIN32
+#define CPR_WINDOWS
+#endif
+#ifdef __APPLE__
+#define CPR_OSX
+#endif
+
+/*
+inline vec4 vdot4( vec4 a, vec4 b )
+{
+ vec4 t0 = vmul( a, b ); //x,y,z,w
+ vec4 t1 = vshuffle( t0, 3,2,1,0 ); //w,z,y,x
+ t0 = vadd( t0, t1 ); //x+w, y+z, z+y, w+x
+ t1 = vshuffle( t0, 1,0,3,2 ); //y+z, x+w, w+x, z+y
+ t0 = vadd( t0, t1 ); //x+w+y+z, y+z+x+w, z+y+w+x, w+x+z+y
+ return t0;
+}
+
+inline vec4 vdot3( vec4 a, vec4 b )
+{
+ vec4 t0 = vmul( a, b ); //x,y,z,w
+ vec4 t1 = vshuffle( t0, 2,0,1,3 ); //z,x,y,w
+ t1 = vadd( t0, t1 ); //x+z, y+x, z+y, w+w
+ t0 = vshuffle( t0, 1,2,0,3 ); //y,z,x,w
+ t0 = vadd( t0, t1 ); //x+z+y, y+x+z, z+y+x, w+w+w
+ t0 = vsplat( t0, 0 ); //x+z+y, x+z+y, x+z+y, x+z+y
+ return t0;
+}
+*/
+
+//#include "Platform.h"
+
+#if defined(CPR_OSX) && defined(__APPLE_ALTIVEC__)
+
+ /*
+ Apple Altivec implementation
+ */
+
+ typedef vector float vec4;
+
+ #define vload(ptr,byteoffset) vec_ld( byteoffset, ptr )
+ #define vstore(val,ptr,byteoffset) vec_st( val, byteoffset, ptr )
+ #define vset(a,b,c,d) ((vec4)(a,b,c,d))
+ #define vzero() ((vec4)vec_splat_u32(0))
+ #define vone() ((vec4)vec_splat_u32(0x3F800000))
+
+ #define vsplat(P,i) vec_splat( P, i )
+ #define vshuffle(P,a,b,c,d) vec_perm( P, vzero(),\
+ (vector unsigned char)(\
+ 4*(a), 4*(a)+1, 4*(a)+2, 4*(a)+3,\
+ 4*(b), 4*(b)+1, 4*(b)+2, 4*(b)+3,\
+ 4*(c), 4*(c)+1, 4*(c)+2, 4*(c)+3,\
+ 4*(d), 4*(d)+1, 4*(d)+2, 4*(d)+3 ) )
+ #define vmergeh(P,Q) vec_mergeh( P, Q )
+ #define vmergel(P,Q) vec_mergel( P, Q )
+ #define vperm(P,Q,a,b,c,d) vec_perm( P, Q,\
+ (vector unsigned char)(\
+ 4*(a), 4*(a)+1, 4*(a)+2, 4*(a)+3,\
+ 4*(b), 4*(b)+1, 4*(b)+2, 4*(b)+3,\
+ 4*(c)+16, 4*(c)+17, 4*(c)+18, 4*(c)+19,\
+ 4*(d)+16, 4*(d)+17, 4*(d)+18, 4*(d)+19 ) )
+
+ #define vadd(a,b) vec_add( a, b )
+ #define vsub(a,b) vec_sub( a, b )
+ #define vmul(a,b) vec_madd( a, b, vzero() )
+ #define vmadd(a,b,c) vec_madd( a, b, c )
+ #define vmsub(a,b,c) vec_nmsub( a, b, c )
+ #define vdiv(a,b) vec_madd( a, vec_re( b ), vzero() )
+ #define vrecip(a) vec_re( a )
+ #define vsqrt(a) vec_re( vec_rsqrte( a ) )
+ #define vrsqrt(a) vec_rsqrte( a )
+ #define vmin(a,b) vec_min( a, b )
+ #define vmax(a,b) vec_max( a, b )
+ #define vabs(a) vec_abs( a )
+
+ #define vand(a,b) vec_and( a, b )
+ #define vandc(a,b) vec_andc( a, b )
+ #define vor(a,b) vec_or( a, b )
+ #define vxor(a,b) vec_xor( a, b )
+ #define vnot(a) vec_andc( (vec4)vec_splat_u32(0xFFFFFFFF), a )
+
+ #define vequal(a,b) vec_cmpeq(a,b)
+ #define vless(a,b) vec_cmplt(a,b)
+ #define vlessequal(a,b) vec_cmple(a,b)
+ #define vgreater(a,b) vec_cmpgt(a,b)
+ #define vgreaterequal(a,b) vec_cmpge(a,b)
+
+#elif defined(CPR_WINDOWS) || (defined(CPR_OSX) && !defined(__BIG_ENDIAN__))
+
+ /*
+ SSE implementation
+ */
+
+ #include <xmmintrin.h>
+
+ typedef __m128 vec4;
+
+ #define vload(ptr,byteoffset) _mm_load_ps( (const float*) ((const char*)(ptr) + (byteoffset)) )
+ #define vstore(val,ptr,byteoffset) _mm_store_ps( (float*) ((char*)(ptr) + (byteoffset)), val )
+ #define vset(a,b,c,d) _mm_set_ps( a, b, c, d )
+ #define vzero() _mm_setzero_ps()
+ #define vone() _mm_set1_ps(1.0f)
+
+ #define vsplat(P,i) vec4_impl::splat<i>( P )
+ #define vshuffle(P,a,b,c,d) vec4_impl::shuffle<a,b,c,d>( P )
+ #define vmergeh(P,Q) _mm_unpacklo_ps( P, Q )
+ #define vmergel(P,Q) _mm_unpackhi_ps( P, Q )
+ #define vperm(P,Q,a,b,c,d) _mm_shuffle_ps( Q, P, _MM_SHUFFLE((d),(c),(b),(a)) )
+
+ #define vadd(a,b) _mm_add_ps( a, b )
+ #define vsub(a,b) _mm_sub_ps( a, b )
+ #define vmul(a,b) _mm_mul_ps( a, b )
+ #define vmadd(a,b,c) _mm_add_ps( _mm_mul_ps( a, b ), c )
+ #define vmsub(a,b,c) _mm_sub_ps( c, _mm_mul_ps( a, b ) )
+ #define vdiv(a,b) _mm_div_ps( a, b )
+ #define vrecip(a) _mm_rcp_ps( a )
+ #define vsqrt(a) _mm_sqrt_ps( a )
+ #define vrsqrt(a) _mm_rsqrt_ps( a )
+ #define vmin(a,b) _mm_min_ps( a, b )
+ #define vmax(a,b) _mm_max_ps( a, b )
+ #define vabs(a) vec4_impl::abs(a)
+
+ #define vand(a,b) _mm_and_ps( a, b )
+ #define vandc(a,b) _mm_andnot_ps( b, a )
+ #define vor(a,b) _mm_or_ps( a, b )
+ #define vxor(a,b) _mm_xor_ps( a, b )
+ #define vnot(a) vec4_impl::not(a)
+
+ #define vequal(a,b) _mm_cmpeq_ps(a,b)
+ #define vless(a,b) _mm_cmplt_ps(a,b)
+ #define vlessequal(a,b) _mm_cmple_ps(a,b)
+ #define vgreater(a,b) _mm_cmpgt_ps(a,b)
+ #define vgreaterequal(a,b) _mm_cmpge_ps(a,b)
+
+ namespace vec4_impl
+ {
+ template<unsigned Ti>
+ inline vec4 splat( vec4 P )
+ { return _mm_shuffle_ps( P, P, _MM_SHUFFLE(Ti,Ti,Ti,Ti) ); }
+
+ template<unsigned Ta, unsigned Tb, unsigned Tc, unsigned Td>
+ inline vec4 shuffle( vec4 P )
+ { return _mm_shuffle_ps( P, P, _MM_SHUFFLE(Td,Tc,Tb,Ta) ); }
+
+ inline vec4 abs( vec4 a )
+ { return _mm_max_ps( a, _mm_sub_ps( _mm_setzero_ps(), a ) ); }
+
+ inline vec4 not( vec4 a )
+ { return _mm_andnot_ps( a, _mm_cmpeq_ps(a,a) ); }
+ }
+
+#elif defined(CPR_XBOX)
+
+ /*
+ VMX128 aka "xbox altivec" implementation
+ */
+
+ #include <PPCIntrinsics.h>
+
+ typedef __vector4 vec4;
+
+ #define vload(ptr,byteoffset) __lvx( ptr, byteoffset )
+ #define vstore(val,ptr,byteoffset) __stvx( val, ptr, byteoffset )
+ #define vset(a,b,c,d) (vec4){ a, b, c, d }
+ #define vzero() __vspltisw(0)
+ #define vone() __vspltisw(0x3F800000)
+
+ #define vsplat(P,i) __vspltw( P, i )
+ #define vmergeh(P,Q) __vmrghw( P, Q )
+ #define vmergel(P,Q) __vmrglw( P, Q )
+ #define vshuffle(P,a,b,c,d) __vpermwi( P, VPERMWI_CONST(a,b,c,d) )
+ #define vperm(P,Q,a,b,c,d) vec4_impl::perm<a,b,c,d>( P, Q )
+
+ #define vadd(a,b) __vaddfp( a, b )
+ #define vsub(a,b) __vsubfp( a, b )
+ #define vmul(a,b) __vmulfp( a, b )
+ #define vmadd(a,b,c) __vmaddfp( a, b, c )
+ #define vmsub(a,b,c) __vnmsubfp( a, b, c )
+ #define vdiv(a,b) __vmulfp( a, __vrefp( b ) )
+ #define vrecip(a) __vrefp( a )
+ #define vsqrt(a) __vrefp( __vrsqrtefp( a ) )
+ #define vrsqrt(a) __vrsqrtefp( a )
+ #define vmin(a,b) __vminfp( a, b )
+ #define vmax(a,b) __vmaxfp( a, b )
+ #define vabs(a) __vand( a, __vspltisw(0x7FFFFFFF) )
+
+ #define vand(a,b) __vand( a, b )
+ #define vandc(a,b) __vandc( a, b )
+ #define vor(a,b) __vor( a, b )
+ #define vxor(a,b) __vxor( a, b )
+ #define vnot(a) __vandc( __vspltisw(0xFFFFFFFF), a );
+
+ #define vequal(a,b) __vcmpeqfp( a, b )
+ #define vless(a,b) __vcmpgtfp( b, a )
+ #define vlessequal(a,b) __vcmpgefp( b, a )
+ #define vgreater(a,b) __vcmpgtfp( a, b )
+ #define vgreaterequal(a,b) __vcmpgefp( a, b )
+
+ namespace vec4_impl
+ {
+ template <unsigned Ta, unsigned Tb, unsigned Tc, unsigned Td>
+ inline vec4 perm( vec4 P, vec4 Q )
+ {
+ P = __vpermwi( P, VPERMWI_CONST(Ta,Tb,0,0) );
+ Q = __vpermwi( Q, VPERMWI_CONST(Tc,Td,0,0) );
+ return __vpermwi( __vmrghw( P, Q ), VPERMWI_CONST(0,2,1,3) );
+ }
+ }
+
+#elif defined(CPR_PS3)
+
+ /*
+ Cell PPU and SPU vector implementation (untested).
+ */
+ #ifdef __SPU__
+ #include <spu_intrinsics.h>
+ typedef vec_float4 vec4;
+ #else
+ #include <altivec.h>
+ typedef vector float vec4;
+ #endif
+
+ #ifdef __SPU__
+
+ #define vload(ptr,byteoffset) ((const vec4*)((const char*)(ptr)+(byteoffset)))[0]
+ #define vstore(val,ptr,byteoffset) (((vec4*)((char*)(ptr)+(byteoffset)))[0] = (val))
+ #define vset(a,b,c,d) ?
+ #define vzero() spu_splats( 0.f )
+ #define vone() spu_splats( 1.f )
+
+ #define vsplat(P,i) ?
+ #define vshuffle(P,a,b,c,d) ?
+ #define vmergeh(P,Q) ?
+ #define vmergel(P,Q) ?
+ #define vperm(P,Q,a,b,c,d) ?
+
+ #define vadd(a,b) spu_add( a, b )
+ #define vsub(a,b) spu_sub( a, b )
+ #define vmul(a,b) spu_mul( a, b )
+ #define vmadd(a,b,c) spu_madd( a, b, c )
+ #define vmsub(a,b,c) spu_nmsub( a, b, c )
+ #define vdiv(a,b) spu_mul( a, spu_re( b ) )
+ #define vrecip(a) spu_re( a )
+ #define vsqrt(a) spu_re( spu_rsqrte( a ) )
+ #define vrsqrt(a) spu_rsqrte( a )
+ #define vmin(a,b) ?
+ #define vmax(a,b) ?
+ #define vabs(a) ?
+
+ #define vand(a,b) spu_and( a, b )
+ #define vandc(a,b) spu_andc( a, b )
+ #define vor(a,b) spu_or( a, b )
+ #define vxor(a,b) spu_xor( a, b )
+ #define vnot(a) spu_not( a )
+
+ #define vequal(a,b) spu_cmpeq( a,b )
+ #define vless(a,b) spu_cmpgt( b,a )
+ #define vlessequal(a,b) ?
+ #define vgreater(a,b) spu_cmpgt( a,b )
+ #define vgreaterequal(a,b) ?
+
+ #else
+
+ #define vload(ptr,byteoffset) vec_ld( byteoffset, ptr )
+ #define vstore(val,ptr,byteoffset) vec_st( val, byteoffset, ptr )
+ #define vset(a,b,c,d) ((vec4)(a,b,c,d))
+ #define vzero() ((vec4)vec_splat_u32(0))
+ #define vone() ((vec4)vec_splat_u32(0x3F800000))
+
+ #define vsplat(P,i) vec_splat( P, i )
+ #define vshuffle(P,a,b,c,d) vec_perm( P, vzero(),\
+ (vector unsigned char)(\
+ 4*(a), 4*(a)+1, 4*(a)+2, 4*(a)+3,\
+ 4*(b), 4*(b)+1, 4*(b)+2, 4*(b)+3,\
+ 4*(c), 4*(c)+1, 4*(c)+2, 4*(c)+3,\
+ 4*(d), 4*(d)+1, 4*(d)+2, 4*(d)+3 ) )
+ #define vmergeh(P,Q) vec_mergeh( P, Q )
+ #define vmergel(P,Q) vec_mergel( P, Q )
+ #define vperm(P,Q,a,b,c,d) vec_perm( P, Q,\
+ (vector unsigned char)(\
+ 4*(a), 4*(a)+1, 4*(a)+2, 4*(a)+3,\
+ 4*(b), 4*(b)+1, 4*(b)+2, 4*(b)+3,\
+ 4*(c)+16, 4*(c)+17, 4*(c)+18, 4*(c)+19,\
+ 4*(d)+16, 4*(d)+17, 4*(d)+18, 4*(d)+19 ) )
+
+ #define vadd(a,b) vec_add( a, b )
+ #define vsub(a,b) vec_sub( a, b )
+ #define vmul(a,b) vec_madd( a, b, vzero() )
+ #define vmadd(a,b,c) vec_madd( a, b, c )
+ #define vmsub(a,b,c) vec_nmsub( a, b, c )
+ #define vdiv(a,b) vec_madd( a, vec_re( b ), vzero() )
+ #define vrecip(a) vec_re( a )
+ #define vsqrt(a) vec_re( vec_rsqrte( a ) )
+ #define vrsqrt(a) vec_rsqrte( a )
+ #define vmin(a,b) vec_min( a, b )
+ #define vmax(a,b) vec_max( a, b )
+ #define vabs(a) vec_abs( a )
+
+ #define vand(a,b) vec_and( a, b )
+ #define vandc(a,b) vec_andc( a, b )
+ #define vor(a,b) vec_or( a, b )
+ #define vxor(a,b) vec_xor( a, b )
+ #define vnot(a) vec_andc( (vec4)vec_splat_u32(0xFFFFFFFF), a )
+
+ #define vequal(a,b) vec_cmpeq(a,b)
+ #define vless(a,b) vec_cmplt(a,b)
+ #define vlessequal(a,b) vec_cmple(a,b)
+ #define vgreater(a,b) vec_cmpgt(a,b)
+ #define vgreaterequal(a,b) vec_cmpge(a,b)
+
+ #endif
+
+#else
+#warning Using non-accelerated SIMD instructions
+ /*
+ Scalar implementation.
+ Slow, but just here for reference & non-accelerated platforms.
+ */
+
+ #include <math.h>
+
+ typedef union
+ {
+ float f[4];
+ int i[4];
+ } vec4;
+
+ inline vec4 vload( const void* ptr, long byteoffset )
+ {
+ const float* mem = (const float*)((const char*)ptr + byteoffset);
+ vec4 r;
+ r.f[0] = mem[0]; r.f[1] = mem[1];
+ r.f[2] = mem[2]; r.f[3] = mem[3];
+ return r;
+ }
+
+ inline void vstore( const vec4& val, void* ptr, long byteoffset )
+ {
+ float* mem = (float*)((char*)ptr + byteoffset);
+ mem[0] = val.f[0];
+ mem[1] = val.f[1];
+ mem[2] = val.f[2];
+ mem[3] = val.f[3];
+ }
+
+ inline vec4 vset( float a, float b, float c, float d )
+ {
+ vec4 r;
+ r.f[0] = a; r.f[1] = b;
+ r.f[2] = c; r.f[3] = d;
+ return r;
+ }
+
+ inline vec4 vzero( void )
+ {
+ vec4 r;
+ r.f[0] = 0.f; r.f[1] = 0.f;
+ r.f[2] = 0.f; r.f[3] = 0.f;
+ return r;
+ }
+
+ inline vec4 vsplat( const vec4& val, unsigned i )
+ {
+ vec4 r; r.f[0] = r.f[1] = r.f[2] = r.f[3] = val.f[i];
+ return r;
+ }
+
+ inline vec4 vshuffle( const vec4& P, unsigned a, unsigned b, unsigned c, unsigned d )
+ {
+ vec4 r;
+ r.f[0] = P.f[a];
+ r.f[1] = P.f[b];
+ r.f[2] = P.f[c];
+ r.f[3] = P.f[d];
+ return r;
+ }
+
+ inline vec4 vmergeh( const vec4& P, const vec4& Q )
+ {
+ vec4 r;
+ r.f[0] = P.f[0];
+ r.f[1] = Q.f[0];
+ r.f[2] = P.f[1];
+ r.f[3] = Q.f[1];
+ return r;
+ }
+
+ inline vec4 vmergel( const vec4& P, const vec4& Q )
+ {
+ vec4 r;
+ r.f[0] = P.f[2];
+ r.f[1] = Q.f[2];
+ r.f[2] = P.f[3];
+ r.f[3] = Q.f[3];
+ return r;
+ }
+
+ inline vec4 vperm( const vec4& P, const vec4& Q, unsigned a, unsigned b, unsigned c, unsigned d )
+ {
+ vec4 r;
+ r.f[0] = P.f[a];
+ r.f[1] = P.f[b];
+ r.f[2] = Q.f[c];
+ r.f[3] = Q.f[d];
+ return r;
+ }
+
+ inline vec4 vadd( const vec4& a, const vec4& b )
+ {
+ vec4 r;
+ r.f[0] = a.f[0] + b.f[0];
+ r.f[1] = a.f[1] + b.f[1];
+ r.f[2] = a.f[2] + b.f[2];
+ r.f[3] = a.f[3] + b.f[3];
+ return r;
+ }
+
+ inline vec4 vsub( const vec4& a, const vec4& b )
+ {
+ vec4 r;
+ r.f[0] = a.f[0] - b.f[0];
+ r.f[1] = a.f[1] - b.f[1];
+ r.f[2] = a.f[2] - b.f[2];
+ r.f[3] = a.f[3] - b.f[3];
+ return r;
+ }
+
+ inline vec4 vmul( const vec4& a, const vec4& b )
+ {
+ vec4 r;
+ r.f[0] = a.f[0] * b.f[0];
+ r.f[1] = a.f[1] * b.f[1];
+ r.f[2] = a.f[2] * b.f[2];
+ r.f[3] = a.f[3] * b.f[3];
+ return r;
+ }
+
+ inline vec4 vmadd( const vec4& a, const vec4& b, const vec4& c )
+ {
+ vec4 r;
+ r.f[0] = a.f[0] * b.f[0] + c.f[0];
+ r.f[1] = a.f[1] * b.f[1] + c.f[1];
+ r.f[2] = a.f[2] * b.f[2] + c.f[2];
+ r.f[3] = a.f[3] * b.f[3] + c.f[3];
+ return r;
+ }
+
+ inline vec4 vmsub( const vec4& a, const vec4& b, const vec4& c )
+ {
+ vec4 r;
+ r.f[0] = c.f[0] - a.f[0] * b.f[0];
+ r.f[1] = c.f[1] - a.f[1] * b.f[1];
+ r.f[2] = c.f[2] - a.f[2] * b.f[2];
+ r.f[3] = c.f[3] - a.f[3] * b.f[3];
+ return r;
+ }
+
+ inline vec4 vdiv( const vec4& a, const vec4& b )
+ {
+ vec4 r;
+ r.f[0] = a.f[0] / b.f[0];
+ r.f[1] = a.f[1] / b.f[1];
+ r.f[2] = a.f[2] / b.f[2];
+ r.f[3] = a.f[3] / b.f[3];
+ return r;
+ }
+
+ inline vec4 vrecip( const vec4& a )
+ {
+ vec4 r;
+ r.f[0] = 1.f / a.f[0];
+ r.f[1] = 1.f / a.f[1];
+ r.f[2] = 1.f / a.f[2];
+ r.f[3] = 1.f / a.f[3];
+ return r;
+ }
+
+ inline vec4 vsqrt( const vec4& a )
+ {
+ vec4 r;
+ r.f[0] = sqrtf( a.f[0] );
+ r.f[1] = sqrtf( a.f[1] );
+ r.f[2] = sqrtf( a.f[2] );
+ r.f[3] = sqrtf( a.f[3] );
+ return r;
+ }
+
+ inline vec4 vrsqrt( const vec4& a )
+ {
+ vec4 r;
+ r.f[0] = 1.f / sqrtf( a.f[0] );
+ r.f[1] = 1.f / sqrtf( a.f[1] );
+ r.f[2] = 1.f / sqrtf( a.f[2] );
+ r.f[3] = 1.f / sqrtf( a.f[3] );
+ return r;
+ }
+
+ inline vec4 vmin( const vec4& a, const vec4& b )
+ {
+ vec4 r;
+ for( int i=0; i<4; ++i )
+ {
+ float aa = a.f[i];
+ float bb = b.f[i];
+ r.f[i] = aa < bb ? aa : bb;
+ }
+ return r;
+ }
+
+ inline vec4 vmax( const vec4& a, const vec4& b )
+ {
+ vec4 r;
+ for( int i=0; i<4; ++i )
+ {
+ float aa = a.f[i];
+ float bb = b.f[i];
+ r.f[i] = aa > bb ? aa : bb;
+ }
+ return r;
+ }
+
+ inline vec4 vand( const vec4& a, const vec4 b )
+ {
+ vec4 r;
+ r.i[0] = a.i[0] & b.i[0];
+ r.i[1] = a.i[1] & b.i[1];
+ r.i[2] = a.i[2] & b.i[2];
+ r.i[3] = a.i[3] & b.i[3];
+ return r;
+ }
+
+ inline vec4 vandc( const vec4& a, const vec4 b )
+ {
+ vec4 r;
+ r.i[0] = a.i[0] & ~b.i[0];
+ r.i[1] = a.i[1] & ~b.i[1];
+ r.i[2] = a.i[2] & ~b.i[2];
+ r.i[3] = a.i[3] & ~b.i[3];
+ return r;
+ }
+
+ inline vec4 vor( const vec4& a, const vec4 b )
+ {
+ vec4 r;
+ r.i[0] = a.i[0] | b.i[0];
+ r.i[1] = a.i[1] | b.i[1];
+ r.i[2] = a.i[2] | b.i[2];
+ r.i[3] = a.i[3] | b.i[3];
+ return r;
+ }
+
+ inline vec4 vxor( const vec4& a, const vec4 b )
+ {
+ vec4 r;
+ r.i[0] = a.i[0] ^ b.i[0];
+ r.i[1] = a.i[1] ^ b.i[1];
+ r.i[2] = a.i[2] ^ b.i[2];
+ r.i[3] = a.i[3] ^ b.i[3];
+ return r;
+ }
+
+ inline vec4 vnot( const vec4& a )
+ {
+ vec4 r;
+ r.i[0] = ~a.i[0];
+ r.i[1] = ~a.i[1];
+ r.i[2] = ~a.i[2];
+ r.i[3] = ~a.i[3];
+ return r;
+ }
+
+ inline vec4 vequal( const vec4& a, const vec4& b )
+ {
+ vec4 r;
+ r.i[0] = a.i[0] == b.i[0] ? 0xFFFFFFFF : 0;
+ r.i[1] = a.i[1] == b.i[1] ? 0xFFFFFFFF : 0;
+ r.i[2] = a.i[2] == b.i[2] ? 0xFFFFFFFF : 0;
+ r.i[3] = a.i[3] == b.i[3] ? 0xFFFFFFFF : 0;
+ return r;
+ }
+
+ inline vec4 vless( const vec4& a, const vec4& b )
+ {
+ vec4 r;
+ r.i[0] = a.i[0] < b.i[0] ? 0xFFFFFFFF : 0;
+ r.i[1] = a.i[1] < b.i[1] ? 0xFFFFFFFF : 0;
+ r.i[2] = a.i[2] < b.i[2] ? 0xFFFFFFFF : 0;
+ r.i[3] = a.i[3] < b.i[3] ? 0xFFFFFFFF : 0;
+ return r;
+ }
+
+ inline vec4 vlessequal( const vec4& a, const vec4& b )
+ {
+ vec4 r;
+ r.i[0] = a.i[0] <= b.i[0] ? 0xFFFFFFFF : 0;
+ r.i[1] = a.i[1] <= b.i[1] ? 0xFFFFFFFF : 0;
+ r.i[2] = a.i[2] <= b.i[2] ? 0xFFFFFFFF : 0;
+ r.i[3] = a.i[3] <= b.i[3] ? 0xFFFFFFFF : 0;
+ return r;
+ }
+
+ inline vec4 vgreater( const vec4& a, const vec4& b )
+ {
+ return vless( b, a );
+ }
+
+ inline vec4 vgreaterequal( const vec4& a, const vec4& b )
+ {
+ return vlessequal( b, a );
+ }
+#endif
+
+}
+#endif
diff --git a/Source/Internal/assetmanifest.h b/Source/Internal/assetmanifest.h
new file mode 100644
index 00000000..e4e391fb
--- /dev/null
+++ b/Source/Internal/assetmanifest.h
@@ -0,0 +1,24 @@
+//-----------------------------------------------------------------------------
+// Name: assetmanifest.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
diff --git a/Source/Internal/assetpreload.cpp b/Source/Internal/assetpreload.cpp
new file mode 100644
index 00000000..6c2593fc
--- /dev/null
+++ b/Source/Internal/assetpreload.cpp
@@ -0,0 +1,61 @@
+//-----------------------------------------------------------------------------
+// Name: assetpreload.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "assetpreload.h"
+
+#include <Internal/assetmanifest.h>
+#include <Internal/filesystem.h>
+#include <Internal/path.h>
+
+#include <XML/Parsers/levelassetspreloadparser.h>
+
+#include <cstring>
+
+Path AssetPreload::ResolveID(const char* id) {
+
+ return Path();
+}
+
+std::vector<LevelAssetPreloadParser::Asset>& AssetPreload::GetPreloadFiles() {
+ return amp.assets;
+}
+
+static AssetPreload asset_manifest;
+
+AssetPreload& AssetPreload::Instance() {
+ return asset_manifest;
+}
+
+void AssetPreload::Initialize() {
+ Reload();
+}
+
+void AssetPreload::Reload() {
+ Path p = FindFilePath("Data/preload.xml", kDataPaths, true);
+ if( p.isValid() ) {
+ amp.Load(p.GetFullPath());
+ }
+}
+
+void AssetPreload::Dispose() {
+
+}
diff --git a/Source/Internal/assetpreload.h b/Source/Internal/assetpreload.h
new file mode 100644
index 00000000..18af3b1f
--- /dev/null
+++ b/Source/Internal/assetpreload.h
@@ -0,0 +1,51 @@
+//-----------------------------------------------------------------------------
+// Name: assetpreload.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/path.h>
+#include <XML/Parsers/levelassetspreloadparser.h>
+
+
+#include <vector>
+#include <map>
+#include <string>
+
+/* This class isn't a complete one, but it does do partial resolves
+ * It also supports retriving assets that should be pre-loaded, commonly used in all or some levels
+ */
+class AssetPreload {
+
+ std::map<std::string,std::string> id_asset_map;
+
+ LevelAssetPreloadParser amp;
+
+public:
+ Path ResolveID(const char* id);
+ std::vector<LevelAssetPreloadParser::Asset> &GetPreloadFiles();
+
+ static AssetPreload& Instance();
+
+ void Reload();
+ void Initialize();
+ void Dispose();
+};
diff --git a/Source/Internal/buildver.cpp b/Source/Internal/buildver.cpp
new file mode 100644
index 00000000..d1ba8533
--- /dev/null
+++ b/Source/Internal/buildver.cpp
@@ -0,0 +1,75 @@
+//-----------------------------------------------------------------------------
+// Name: global_config.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+// This is in a seperate file so that we can recompile it every time
+// without it forcing a recompile on something ccache would otherwise not
+// have to rebuild...this file's checksum changes every time you build it
+// due to the __DATE__ and __TIME__ macros.
+
+// The makefile will rebuild this file everytime it relinks an executable
+// so that we'll always have a unique build string.
+
+// APPNAME and APPREV need to be predefined in the build system.
+// The rest are supposed to be supplied by the compiler.
+
+#ifndef APPID
+#define APPID UnknownAPPID
+#endif
+
+#ifndef APPREV
+#define APPREV UnknownAPPREV
+#endif
+
+#ifndef __VERSION__
+#define __VERSION__ (Unknown compiler version)
+#endif
+
+#ifndef __DATE__
+#define __DATE__ (Unknown build date)
+#endif
+
+#ifndef __TIME__
+#define __TIME__ (Unknown build time)
+#endif
+
+#ifndef COMPILER
+ #if (defined __GNUC__)
+ #define COMPILER "GCC"
+ #elif (defined _MSC_VER)
+ #define COMPILER "Visual Studio"
+ #else
+ #error Please define your platform.
+ #endif
+#endif
+
+// macro mess so we can turn APPID and APPREV into a string literal...
+#define MAKEBUILDVERSTRINGLITERAL2(id, rev) \
+ #id ", Revision " #rev ", Built " __DATE__ " " __TIME__ ", by " \
+ COMPILER " version " __VERSION__
+
+#define MAKEBUILDVERSTRINGLITERAL(id, rev) MAKEBUILDVERSTRINGLITERAL2(id, rev)
+
+const char *GBuildVer = MAKEBUILDVERSTRINGLITERAL(APPID, APPREV);
+
+// end of buildver.cpp ...
+
diff --git a/Source/Internal/cachefile.cpp b/Source/Internal/cachefile.cpp
new file mode 100644
index 00000000..f255a905
--- /dev/null
+++ b/Source/Internal/cachefile.cpp
@@ -0,0 +1,85 @@
+//-----------------------------------------------------------------------------
+// Name: cachefile.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "cachefile.h"
+
+#include <Internal/checksum.h>
+#include <Internal/datemodified.h>
+#include <Internal/filesystem.h>
+
+bool CacheFile::CheckForCache(const std::string &path, const std::string &suffix, std::string *load_path, uint16_t *checksum) {
+ ModID modsource;
+ return CheckForCache(path,suffix,load_path,&modsource,checksum);
+}
+
+bool CacheFile::CheckForCache(const std::string &path, const std::string &suffix, std::string *load_path, ModID *load_modsource, uint16_t *checksum){
+ std::string cache_str = path + suffix;
+
+ const int kMaxPaths = 5;
+ char abs_cache_paths[kPathSize * kMaxPaths];
+ ModID cache_modsources[kMaxPaths];
+ char abs_base_paths[kPathSize * kMaxPaths];
+ int num_cache_paths_found = FindFilePaths(cache_str.c_str(), abs_cache_paths, kPathSize, kMaxPaths, kAnyPath, false, NULL, cache_modsources );
+ int num_base_paths_found = FindFilePaths(path.c_str(), abs_base_paths, kPathSize, kMaxPaths, kAnyPath, false, NULL, NULL );
+
+ char* cache_path = &abs_cache_paths[0];
+ ModID cache_modsource = cache_modsources[0];
+ int64_t latest_cache_date_modified = -1;
+ if(num_cache_paths_found > 0){
+ for(int path_index=0; path_index<num_cache_paths_found; ++path_index){
+ char* curr_path = &abs_cache_paths[kPathSize * path_index];
+ ModID curr_modsource = cache_modsources[path_index];
+ int64_t date_modified = GetDateModifiedInt64(curr_path);
+ if(date_modified > latest_cache_date_modified){
+ latest_cache_date_modified = date_modified;
+ cache_path = curr_path;
+ cache_modsource = curr_modsource;
+ }
+ }
+ }
+
+ char* base_path = &abs_base_paths[0];
+ int64_t latest_base_date_modified = -1;
+ if(num_base_paths_found > 0){
+ for(int path_index=0; path_index<num_base_paths_found; ++path_index){
+ char* curr_path = &abs_base_paths[kPathSize * path_index];
+ int64_t date_modified = GetDateModifiedInt64(curr_path);
+ if(date_modified > latest_base_date_modified){
+ latest_base_date_modified = date_modified;
+ base_path = curr_path;
+ }
+ }
+ }
+
+ if(latest_base_date_modified != -1){
+ *checksum = Checksum(base_path);
+ } else {
+ *checksum = 0;
+ }
+
+ if(latest_cache_date_modified != -1){
+ *load_path = cache_path;
+ *load_modsource = cache_modsource;
+ return true;
+ }
+ return false;
+}
diff --git a/Source/Internal/cachefile.h b/Source/Internal/cachefile.h
new file mode 100644
index 00000000..1b79a5cb
--- /dev/null
+++ b/Source/Internal/cachefile.h
@@ -0,0 +1,43 @@
+//-----------------------------------------------------------------------------
+// Name: cachefile.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/integer.h>
+#include <Internal/modid.h>
+
+#include <string>
+#include <cstdio>
+
+namespace CacheFile {
+ bool CheckForCache(const std::string &path, const std::string &suffix, std::string *load_path, uint16_t *checksum);
+ bool CheckForCache(const std::string &path, const std::string &suffix, std::string *load_path, ModID *load_modsource, uint16_t *checksum);
+
+ class ScopedFileCloser {
+ public:
+ ScopedFileCloser(FILE* file):file_(file){}
+ ~ScopedFileCloser(){fclose(file_);}
+
+ private:
+ FILE* file_;
+ };
+}
diff --git a/Source/Internal/callstack.cpp b/Source/Internal/callstack.cpp
new file mode 100644
index 00000000..8df1d36d
--- /dev/null
+++ b/Source/Internal/callstack.cpp
@@ -0,0 +1,36 @@
+//-----------------------------------------------------------------------------
+// Name: callstack.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+#include <Memory/allocation.h>
+/*
+ * callstack.cpp
+ * PhoenixMac
+ *
+ * Created by handley on 5/21/10.
+ * Copyright 2010 __MyCompanyName__. All rights reserved.
+ *
+ */
+
+#include "callstack.h"
+
+#ifdef __APPLE__
+#include <stdio.h>
+#include <execinfo.h>
+#include <memory.h>
+
+//mac os implementation borrowed from cocoadev
+uintptr_t *GetCallstack()
+{
+ void *backtraceFrames[128];
+ int frameCount = backtrace(&backtraceFrames[0], 128);
+
+ uintptr_t *callstack = (uintptr_t *)OG_MALLOC(sizeof(uintptr_t)*(frameCount+1));
+ memcpy(callstack,backtraceFrames,sizeof(void*)*frameCount);
+ callstack[frameCount] = 0;
+ return callstack;
+}
+#endif
diff --git a/Source/Internal/callstack.h b/Source/Internal/callstack.h
new file mode 100644
index 00000000..4dcb2fa8
--- /dev/null
+++ b/Source/Internal/callstack.h
@@ -0,0 +1,36 @@
+//-----------------------------------------------------------------------------
+// Name: callstack.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+/*
+ * callstack.h
+ * PhoenixMac
+ *
+ * Created by handley on 5/21/10.
+ * Copyright 2010 __MyCompanyName__. All rights reserved.
+ *
+ */
+#pragma once
+
+#ifdef __APPLE__
+#include <stdint.h>
+uintptr_t *GetCallstack();
+#endif
diff --git a/Source/Internal/casecorrectpath.cpp b/Source/Internal/casecorrectpath.cpp
new file mode 100644
index 00000000..7f031265
--- /dev/null
+++ b/Source/Internal/casecorrectpath.cpp
@@ -0,0 +1,46 @@
+//-----------------------------------------------------------------------------
+// Name: casecorrectpath.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "casecorrectpath.h"
+
+#include <Compat/filepath.h>
+#include <Internal/filesystem.h>
+
+#include <cstdio>
+#include <string>
+#include <cstring>
+
+using std::string;
+
+bool IsPathCaseCorrect(const string& input_path, string *correct_case){
+ char correct_case_buf[kPathSize];
+ GetCaseCorrectPath(input_path.c_str(), correct_case_buf);
+ *correct_case = correct_case_buf;
+ if(input_path.length() != correct_case->length()){
+ return true; // Something weird happened, don't bother the user with error messages
+ }
+ if(input_path != *correct_case){
+ return false;
+ } else {
+ return true;
+ }
+}
diff --git a/Source/Internal/casecorrectpath.h b/Source/Internal/casecorrectpath.h
new file mode 100644
index 00000000..6783d457
--- /dev/null
+++ b/Source/Internal/casecorrectpath.h
@@ -0,0 +1,30 @@
+//-----------------------------------------------------------------------------
+// Name: casecorrectpath.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <string>
+
+// Takes an input_path that is known to exist // e.g. "DaTa/fILE.zIP"
+// Fills correct_case with the true file path case // e.g. "Data/File.zip"
+// Returns whether or not input_path == correct_case
+bool IsPathCaseCorrect(const std::string& input_path, std::string *correct_case);
diff --git a/Source/Internal/checksum.cpp b/Source/Internal/checksum.cpp
new file mode 100644
index 00000000..2e74a378
--- /dev/null
+++ b/Source/Internal/checksum.cpp
@@ -0,0 +1,118 @@
+//-----------------------------------------------------------------------------
+// Name: checksum.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "checksum.h"
+
+#include <Internal/error.h>
+#include <Internal/profiler.h>
+#include <Internal/common.h>
+
+#include <Compat/fileio.h>
+#include <Memory/allocation.h>
+
+#include <stdio.h>
+#include <string>
+
+using std::string;
+using std::endl;
+
+class ProfilerContext;
+
+//TODO: Maybe modifying this to be Adler32 would be a little better CRC32 even more so, lowering the risk for collisions for small changes.
+//(If you make a 'b' a 'c' and another 'b' an 'a' in a file, this function will give you the same checksum value, i would consider this a possible common change)
+unsigned short Checksum(const string& abs_path) {
+ PROFILER_ZONE(g_profiler_ctx, "Checksum");
+ unsigned short sum = 0;
+
+ FILE * pFile;
+ long lSize;
+ unsigned char * buffer;
+ size_t result;
+
+ pFile = my_fopen ( abs_path.c_str() , "rb" );
+
+#ifndef NO_ERR
+ const int kBufSize = 512;
+ char error_msg[kBufSize];
+ while (pFile==NULL) {
+ FormatString(error_msg, kBufSize, "Could not open file: %s. Retry?", abs_path.c_str());
+ DisplayError("Error", error_msg, _ok_cancel);
+ pFile = my_fopen ( abs_path.c_str() , "rb" );
+ }
+#endif
+
+ if( pFile ) {
+ // obtain file size:
+ fseek (pFile , 0 , SEEK_END);
+ lSize = ftell (pFile);
+ rewind (pFile);
+
+ // allocate memory to contain the whole file:
+ buffer = (unsigned char*) alloc.stack.Alloc(sizeof(char)*lSize);
+
+ if( buffer ) {
+ #ifndef NO_ERR
+ if (buffer == NULL) {
+ FormatString(error_msg, kBufSize, "Could not allocate memory to checksum: %s.", abs_path.c_str());
+ FatalError("Error", error_msg);
+ }
+ #endif
+
+ // copy the file into the buffer:
+ result = fread (buffer,1,lSize,pFile);
+ #ifndef NO_ERR
+ if (result != (size_t)lSize) {
+ FormatString(error_msg, kBufSize, "Could not read data from file: %s.", abs_path.c_str());
+ FatalError("Error", error_msg);
+ }
+ #endif
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Actual summation");
+ size_t short_count = lSize/2;
+ for (size_t i = 0; i < short_count; i++){
+ // Might have trouble on wrong-endian machines, but those should probably just have different checksums built in anyhow, to keep this fast
+ sum += ((uint16_t*)buffer)[i];
+ }
+ }
+ alloc.stack.Free(buffer);
+ } else {
+ LOGE << "Failed to allocate data from stack allocator, size: " << lSize << endl;
+ }
+ // terminate
+ fclose (pFile);
+ } else {
+ LOGE << "Failed to open file " << abs_path << endl;
+ }
+ return sum;
+}
+
+// From http://www.cse.yorku.ca/~oz/hash.html
+unsigned long djb2_string_hash(const char *cstr){
+ unsigned long hash = 5381;
+ int c;
+
+ while ((c = *cstr++))
+ hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
+
+ return hash;
+}
diff --git a/Source/Internal/checksum.h b/Source/Internal/checksum.h
new file mode 100644
index 00000000..3e1a60f8
--- /dev/null
+++ b/Source/Internal/checksum.h
@@ -0,0 +1,28 @@
+//-----------------------------------------------------------------------------
+// Name: checksum.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <string>
+
+unsigned short Checksum(const std::string& abs_path);
+unsigned long djb2_string_hash(const char *cstr);
diff --git a/Source/Internal/collisiondetection.cpp b/Source/Internal/collisiondetection.cpp
new file mode 100644
index 00000000..4ac20b7a
--- /dev/null
+++ b/Source/Internal/collisiondetection.cpp
@@ -0,0 +1,1394 @@
+//-----------------------------------------------------------------------------
+// Name: collisiondetection.cpp
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: This file handles collision detection functions
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "collisiondetection.h"
+
+#include <Math/enginemath.h>
+#include <Math/vec3math.h>
+
+#include <cmath>
+#include <cstddef> //For NULL
+
+//-----------------------------------------------------------------------------
+//Functions
+//-----------------------------------------------------------------------------
+
+bool DistancePointLine( const vec3 &point, const vec3 &line_start, const vec3 &line_end, float *the_distance, vec3 *intersection )
+{
+ float LineMag;
+ float U;
+
+ LineMag = distance( line_end, line_start );
+
+ U = ( ( ( point.x() - line_start.x() ) * ( line_end.x() - line_start.x() ) ) +
+ ( ( point.y() - line_start.y() ) * ( line_end.y() - line_start.y() ) ) +
+ ( ( point.z() - line_start.z() ) * ( line_end.z() - line_start.z() ) ) ) /
+ ( LineMag * LineMag );
+
+ if( U < 0.0f || U > 1.0f ){
+ if(distance_squared(point, line_start)<distance_squared(point,line_end)){
+ *intersection = line_start;
+ *the_distance = distance(point, line_start);
+ }
+ else {
+ *intersection = line_end;
+ *the_distance = distance(point, line_end);
+ }
+ return 0; // closest point does not fall within the line segment
+ }
+
+ intersection->x() = line_start.x() + U * ( line_end.x() - line_start.x() );
+ intersection->y() = line_start.y() + U * ( line_end.y() - line_start.y() );
+ intersection->z() = line_start.z() + U * ( line_end.z() - line_start.z() );
+
+ *the_distance = distance( point, *intersection );
+
+ return 1;
+}
+
+bool sphere_line_intersection (
+ const vec3& p1, const vec3& p2, const vec3& p3, const float &r, vec3 *ret )
+{
+ // x1,p1->y,p1->z P1 coordinates (point of line)
+ // p2->x,p2->y,p2->z P2 coordinates (point of line)
+ // p3->x,p3->y,p3->z, r P3 coordinates and radius (sphere)
+ // x,y,z intersection coordinates
+ //
+ // This function returns a pointer array which first index indicates
+ // the number of intersection point, followed by coordinate pairs.
+
+ if(p1.x()>p3.x()+r&&p2.x()>p3.x()+r)return(0);
+ if(p1.x()<p3.x()-r&&p2.x()<p3.x()-r)return(0);
+ if(p1.y()>p3.y()+r&&p2.y()>p3.y()+r)return(0);
+ if(p1.y()<p3.y()-r&&p2.y()<p3.y()-r)return(0);
+ if(p1.z()>p3.z()+r&&p2.z()>p3.z()+r)return(0);
+ if(p1.z()<p3.z()-r&&p2.z()<p3.z()-r)return(0);
+
+ float a, b, c, i ;
+
+ a = square(p2.x() - p1.x()) + square(p2.y() - p1.y()) + square(p2.z() - p1.z());
+ b = 2* ( (p2.x() - p1.x())*(p1.x() - p3.x())
+ + (p2.y() - p1.y())*(p1.y() - p3.y())
+ + (p2.z() - p1.z())*(p1.z() - p3.z()) ) ;
+ c = square(p3.x()) + square(p3.y()) +
+ square(p3.z()) + square(p1.x()) +
+ square(p1.y()) + square(p1.z()) -
+ 2* ( p3.x()*p1.x() + p3.y()*p1.y() + p3.z()*p1.z() ) - square(r) ;
+ i = b * b - 4 * a * c ;
+
+ if ( i < 0.0f )
+ {
+ // no intersection
+ return(0);
+ }
+ else
+ {
+ if(ret){
+ float mu = (-b - sqrtf(i)) / (2*a);
+ ret->x() = p1.x() + mu*(p2.x()-p1.x());
+ ret->y() = p1.y() + mu*(p2.y()-p1.y());
+ ret->z() = p1.z() + mu*(p2.z()-p1.z());
+ }
+ return(1);
+ }
+}
+
+//Check if a point is in a triangle
+bool inTriangle(const vec3 &pointv, const vec3 &normal, const vec3 &p1v, const vec3 &p2v, const vec3 &p3v)
+{
+ float u0, u1, u2;
+ float v0, v1, v2;
+ float a, b;
+ float maximum;
+ int i=0, j=0;
+ bool bInter = 0;
+
+ vec3 new_norm;
+ new_norm.x()=fabsf(normal.x());
+ new_norm.y()=fabsf(normal.y());
+ new_norm.z()=fabsf(normal.z());
+
+ if(new_norm.x()>new_norm.y())maximum = new_norm.x();
+ else maximum = new_norm.y();
+ if(maximum<new_norm.z())maximum=new_norm.z();
+
+ if (maximum == fabs(normal.x())) {i = 1; j = 2;}
+ if (maximum == fabs(normal.y())) {i = 0; j = 2;}
+ if (maximum == fabs(normal.z())) {i = 0; j = 1;}
+
+ u0 = pointv[i] - p1v[i];
+ v0 = pointv[j] - p1v[j];
+ u1 = p2v[i] - p1v[i];
+ v1 = p2v[j] - p1v[j];
+ u2 = p3v[i] - p1v[i];
+ v2 = p3v[j] - p1v[j];
+
+ if (u1 > -0.00001f && u1 < 0.00001f)
+ {
+ b = u0 / u2;
+ if (0.0f <= b && b <= 1.0f)
+ {
+ a = (v0 - b * v2) / v1;
+ if ((a >= 0.0f) && (( a + b ) <= 1.0f))
+ bInter = 1;
+ }
+ }
+ else
+ {
+ b = (v0 * u1 - u0 * v1) / (v2 * u1 - u2 * v1);
+ if (0.0f <= b && b <= 1.0f)
+ {
+ a = (u0 - b * u2) / u1;
+ if ((a >= 0.0f) && (( a + b ) <= 1.0f ))
+ bInter = 1;
+ }
+ }
+
+ return bInter;
+}
+
+//Check if a point is in a triangle
+vec3 barycentric(const vec3 &pointv, const vec3 &normal, const vec3 &p1v, const vec3 &p2v, const vec3 &p3v)
+{
+ float u0, u1, u2;
+ float v0, v1, v2;
+ float a=0, b=0;
+ float maximum;
+ int i=0, j=0;
+
+ vec3 new_norm;
+ new_norm.x()=fabsf(normal.x());
+ new_norm.y()=fabsf(normal.y());
+ new_norm.z()=fabsf(normal.z());
+
+ if(new_norm.x()>new_norm.y())maximum = new_norm.x();
+ else maximum = new_norm.y();
+ if(maximum<new_norm.z())maximum=new_norm.z();
+
+ if (maximum == fabs(normal.x())) {i = 1; j = 2;}
+ if (maximum == fabs(normal.y())) {i = 0; j = 2;}
+ if (maximum == fabs(normal.z())) {i = 0; j = 1;}
+
+ u0 = pointv[i] - p1v[i];
+ v0 = pointv[j] - p1v[j];
+ u1 = p2v[i] - p1v[i];
+ v1 = p2v[j] - p1v[j];
+ u2 = p3v[i] - p1v[i];
+ v2 = p3v[j] - p1v[j];
+
+ if (u1 > -0.00001f && u1 < 0.00001f)
+ {
+ b = u0 / u2;
+ //if (0.0f <= b && b <= 1.0f)
+ //{
+ a = (v0 - b * v2) / v1;
+ //if ((a >= 0.0f) && (( a + b ) <= 1.0f))
+ //bInter = 1;
+ //}
+ }
+ else
+ {
+ b = (v0 * u1 - u0 * v1) / (v2 * u1 - u2 * v1);
+ //if (0.0f <= b && b <= 1.0f)
+ //{
+ a = (u0 - b * u2) / u1;
+ //if ((a >= 0.0f) && (( a + b ) <= 1.0f ))
+ //bInter = 1;
+ //}
+ }
+
+ return vec3(1-a-b, a, b);
+}
+
+
+//Check if a line collides with a triangle
+int LineFacet(const vec3 &p1,const vec3 &p2,const vec3 &pa,const vec3 &pb,const vec3 &pc, vec3 *p, const vec3 &n)
+{
+ /*vec3 n;
+ n.x() = (pb.y() - pa.y())*(pc.z() - pa.z()) - (pb.z() - pa.z())*(pc.y() - pa.y());
+ n.y() = (pb.z() - pa.z())*(pc.x() - pa.x()) - (pb.x() - pa.x())*(pc.z() - pa.z());
+ n.z() = (pb.x() - pa.x())*(pc.y() - pa.y()) - (pb.y() - pa.y())*(pc.x() - pa.x());
+ Normalise(&n);
+
+ if(n!=an)
+ int blah=5;*/
+
+ //Calculate the position on the line that intersects the plane
+ float denom = n.x() * (p2.x() - p1.x()) + n.y() * (p2.y() - p1.y()) + n.z() * (p2.z() - p1.z());
+ if (fabs(denom) < 0.0000001f) // Line and plane don't intersect
+ return 0;
+ float d = - n.x() * pa.x() - n.y() * pa.y() - n.z() * pa.z();
+ float mu = - (d + n.x() * p1.x() + n.y() * p1.y() + n.z() * p1.z()) / denom;
+ if (mu < 0 || mu > 1) // Intersection not along line segment
+ return 0;
+
+ p->x() = p1.x() + mu * (p2.x() - p1.x());
+ p->y() = p1.y() + mu * (p2.y() - p1.y());
+ p->z() = p1.z() + mu * (p2.z() - p1.z());
+
+ return inTriangle( *p, n, pa, pb, pc);
+}
+
+//Check if a line collides with a triangle against the front face only
+int LineFacetNoBackface(const vec3 &p1,const vec3 &p2,const vec3 &pa,const vec3 &pb,const vec3 &pc, vec3 *p, const vec3 &n)
+{
+ /*vec3 n;
+ n.x() = (pb.y() - pa.y())*(pc.z() - pa.z()) - (pb.z() - pa.z())*(pc.y() - pa.y());
+ n.y() = (pb.z() - pa.z())*(pc.x() - pa.x()) - (pb.x() - pa.x())*(pc.z() - pa.z());
+ n.z() = (pb.x() - pa.x())*(pc.y() - pa.y()) - (pb.y() - pa.y())*(pc.x() - pa.x());
+ Normalise(&n);
+
+ if(n!=an)
+ int blah=5;*/
+
+ vec3 dir = p2 - p1;
+ float n_dir_dot_prod = n.x()*dir.x() + n.y()*dir.y() + n.z()*dir.z();
+ if (n_dir_dot_prod > 0) // omit backface collisions
+ return 0;
+
+ //Calculate the position on the line that intersects the plane
+ float denom = n.x() * (p2.x() - p1.x()) + n.y() * (p2.y() - p1.y()) + n.z() * (p2.z() - p1.z());
+ if (fabs(denom) < 0.0000001f) // Line and plane don't intersect
+ return 0;
+ float d = - n.x() * pa.x() - n.y() * pa.y() - n.z() * pa.z();
+ float mu = - (d + n.x() * p1.x() + n.y() * p1.y() + n.z() * p1.z()) / denom;
+ if (mu < 0 || mu > 1) // Intersection not along line segment
+ return 0;
+
+ p->x() = p1.x() + mu * (p2.x() - p1.x());
+ p->y() = p1.y() + mu * (p2.y() - p1.y());
+ p->z() = p1.z() + mu * (p2.z() - p1.z());
+
+ return inTriangle( *p, n, pa, pb, pc);
+}
+
+//Check if a line collides with a triangle
+int LineFacet(const vec3 &p1,const vec3 &p2,const vec3 &pa,const vec3 &pb,const vec3 &pc, vec3 *p)
+{
+ vec3 n;
+ n.x() = (pb.y() - pa.y())*(pc.z() - pa.z()) - (pb.z() - pa.z())*(pc.y() - pa.y());
+ n.y() = (pb.z() - pa.z())*(pc.x() - pa.x()) - (pb.x() - pa.x())*(pc.z() - pa.z());
+ n.z() = (pb.x() - pa.x())*(pc.y() - pa.y()) - (pb.y() - pa.y())*(pc.x() - pa.x());
+ n = normalize(n);
+
+ return LineFacet(p1, p2, pa, pb, pc, p, n);
+}
+
+
+bool lineBox(const vec3 &start, const vec3 &end,const vec3 &box_min,const vec3 &box_max, float *time)
+{
+ float st,et,fst = 0,fet = 1;
+ float const *bmin = &box_min.x();
+ float const *bmax = &box_max.x();
+ float const *si = &start.x();
+ float const *ei = &end.x();
+
+ for (int i = 0; i < 3; i++) {
+ if (*si < *ei) {
+ if (*si > *bmax || *ei < *bmin)
+ return false;
+ float di = *ei - *si;
+ st = (*si < *bmin)? (*bmin - *si) / di: 0;
+ et = (*ei > *bmax)? (*bmax - *si) / di: 1;
+ }
+ else {
+ if (*ei > *bmax || *si < *bmin)
+ return false;
+ float di = *ei - *si;
+ st = (*si > *bmax)? (*bmax - *si) / di: 0;
+ et = (*ei < *bmin)? (*bmin - *si) / di: 1;
+ }
+
+ if (st > fst) fst = st;
+ if (et < fet) fet = et;
+ if (fet < fst)
+ return false;
+ bmin++; bmax++;
+ si++; ei++;
+ }
+
+ if(time)*time = fst;
+ return true;
+}
+
+#define X 0
+#define Y 1
+#define Z 2
+
+#define CROSS(dest,v1,v2) \
+ dest[0]=v1[1]*v2[2]-v1[2]*v2[1]; \
+ dest[1]=v1[2]*v2[0]-v1[0]*v2[2]; \
+ dest[2]=v1[0]*v2[1]-v1[1]*v2[0];
+
+#define DOT(v1,v2) (v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2])
+
+#define SUB(dest,v1,v2) \
+ dest[0]=v1[0]-v2[0]; \
+ dest[1]=v1[1]-v2[1]; \
+ dest[2]=v1[2]-v2[2];
+
+#define FINDMINMAX(x0,x1,x2,min,max) \
+ min = max = x0; \
+ if(x1<min) min=x1;\
+ if(x1>max) max=x1;\
+ if(x2<min) min=x2;\
+ if(x2>max) max=x2;
+
+int planeBoxOverlap(float normal[3],float d, float maxbox[3])
+{
+ int q;
+ float vmin[3],vmax[3];
+ for(q=X;q<=Z;q++)
+ {
+ if(normal[q]>0.0f)
+ {
+ vmin[q]=-maxbox[q];
+ vmax[q]=maxbox[q];
+ }
+ else
+ {
+ vmin[q]=maxbox[q];
+ vmax[q]=-maxbox[q];
+ }
+ }
+ if(DOT(normal,vmin)+d>0.0f) return 0;
+ if(DOT(normal,vmax)+d>=0.0f) return 1;
+
+ return 0;
+}
+
+
+/*======================== X-tests ========================*/
+#define AXISTEST_X01(a, b, fa, fb) \
+ p0 = a*v0[Y] - b*v0[Z]; \
+ p2 = a*v2[Y] - b*v2[Z]; \
+ if(p0<p2) {min=p0; max=p2;} else {min=p2; max=p0;} \
+ rad = fa * boxhalfsize[Y] + fb * boxhalfsize[Z]; \
+ if(min>rad || max<-rad) return 0;
+
+#define AXISTEST_X2(a, b, fa, fb) \
+ p0 = a*v0[Y] - b*v0[Z]; \
+ p1 = a*v1[Y] - b*v1[Z]; \
+ if(p0<p1) {min=p0; max=p1;} else {min=p1; max=p0;} \
+ rad = fa * boxhalfsize[Y] + fb * boxhalfsize[Z]; \
+ if(min>rad || max<-rad) return 0;
+
+/*======================== Y-tests ========================*/
+#define AXISTEST_Y02(a, b, fa, fb) \
+ p0 = -a*v0[X] + b*v0[Z]; \
+ p2 = -a*v2[X] + b*v2[Z]; \
+ if(p0<p2) {min=p0; max=p2;} else {min=p2; max=p0;} \
+ rad = fa * boxhalfsize[X] + fb * boxhalfsize[Z]; \
+ if(min>rad || max<-rad) return 0;
+
+#define AXISTEST_Y1(a, b, fa, fb) \
+ p0 = -a*v0[X] + b*v0[Z]; \
+ p1 = -a*v1[X] + b*v1[Z]; \
+ if(p0<p1) {min=p0; max=p1;} else {min=p1; max=p0;} \
+ rad = fa * boxhalfsize[X] + fb * boxhalfsize[Z]; \
+ if(min>rad || max<-rad) return 0;
+
+/*======================== Z-tests ========================*/
+
+#define AXISTEST_Z12(a, b, fa, fb) \
+ p1 = a*v1[X] - b*v1[Y]; \
+ p2 = a*v2[X] - b*v2[Y]; \
+ if(p2<p1) {min=p2; max=p1;} else {min=p1; max=p2;} \
+ rad = fa * boxhalfsize[X] + fb * boxhalfsize[Y]; \
+ if(min>rad || max<-rad) return 0;
+
+#define AXISTEST_Z0(a, b, fa, fb) \
+ p0 = a*v0[X] - b*v0[Y]; \
+ p1 = a*v1[X] - b*v1[Y]; \
+ if(p0<p1) {min=p0; max=p1;} else {min=p1; max=p0;} \
+ rad = fa * boxhalfsize[X] + fb * boxhalfsize[Y]; \
+ if(min>rad || max<-rad) return 0;
+
+bool triBoxOverlap(const vec3 &box_min, const vec3 &box_max, const vec3 &vert1, const vec3 &vert2, const vec3 &vert3)
+{
+
+ /* use separating axis theorem to test overlap between triangle and box */
+ /* need to test for overlap in these directions: */
+ /* 1) the {x,y,z}-directions (actually, since we use the AABB of the triangle */
+ /* we do not even need to test these) */
+ /* 2) normal of the triangle */
+ /* 3) crossproduct(edge from tri, {x,y,z}-directin) */
+ /* this gives 3x3=9 more tests */
+ float boxcenter[3], boxhalfsize[3], triverts[3][3];
+ boxcenter[0] = (box_min.x()+box_max.x())/2;
+ boxcenter[1] = (box_min.y()+box_max.y())/2;
+ boxcenter[2] = (box_min.z()+box_max.z())/2;
+ boxhalfsize[0] = (box_max.x()-box_min.x())/2;
+ boxhalfsize[1] = (box_max.y()-box_min.y())/2;
+ boxhalfsize[2] = (box_max.z()-box_min.z())/2;
+
+ triverts[0][0] = vert1.x();
+ triverts[0][1] = vert1.y();
+ triverts[0][2] = vert1.z();
+ triverts[1][0] = vert2.x();
+ triverts[1][1] = vert2.y();
+ triverts[1][2] = vert2.z();
+ triverts[2][0] = vert3.x();
+ triverts[2][1] = vert3.y();
+ triverts[2][2] = vert3.z();
+
+ float v0[3],v1[3],v2[3];
+ //float axis[3];
+ float min,max,d,p0,p1,p2,rad,fex,fey,fez;
+ float normal[3],e0[3],e1[3],e2[3];
+
+ /* This is the fastest branch on Sun */
+ /* move everything so that the boxcenter is in (0,0,0) */
+ SUB(v0,triverts[0],boxcenter);
+ SUB(v1,triverts[1],boxcenter);
+ SUB(v2,triverts[2],boxcenter);
+
+ /* compute triangle edges */
+ SUB(e0,v1,v0); /* tri edge 0 */
+ SUB(e1,v2,v1); /* tri edge 1 */
+ SUB(e2,v0,v2); /* tri edge 2 */
+
+ /* Bullet 3: */
+ /* test the 9 tests first (this was faster) */
+ fex = fabsf(e0[X]);
+ fey = fabsf(e0[Y]);
+ fez = fabsf(e0[Z]);
+ AXISTEST_X01(e0[Z], e0[Y], fez, fey);
+ AXISTEST_Y02(e0[Z], e0[X], fez, fex);
+ AXISTEST_Z12(e0[Y], e0[X], fey, fex);
+
+ fex = fabsf(e1[X]);
+ fey = fabsf(e1[Y]);
+ fez = fabsf(e1[Z]);
+ AXISTEST_X01(e1[Z], e1[Y], fez, fey);
+ AXISTEST_Y02(e1[Z], e1[X], fez, fex);
+ AXISTEST_Z0(e1[Y], e1[X], fey, fex);
+
+ fex = fabsf(e2[X]);
+ fey = fabsf(e2[Y]);
+ fez = fabsf(e2[Z]);
+ AXISTEST_X2(e2[Z], e2[Y], fez, fey);
+ AXISTEST_Y1(e2[Z], e2[X], fez, fex);
+ AXISTEST_Z12(e2[Y], e2[X], fey, fex);
+
+ /* Bullet 1: */
+ /* first test overlap in the {x,y,z}-directions */
+ /* find min, max of the triangle each direction, and test for overlap in */
+ /* that direction -- this is equivalent to testing a minimal AABB around */
+ /* the triangle against the AABB */
+
+ /* test in X-direction */
+ FINDMINMAX(v0[X],v1[X],v2[X],min,max);
+ if(min>boxhalfsize[X] || max<-boxhalfsize[X]) return 0;
+
+ /* test in Y-direction */
+ FINDMINMAX(v0[Y],v1[Y],v2[Y],min,max);
+ if(min>boxhalfsize[Y] || max<-boxhalfsize[Y]) return 0;
+
+ /* test in Z-direction */
+ FINDMINMAX(v0[Z],v1[Z],v2[Z],min,max);
+ if(min>boxhalfsize[Z] || max<-boxhalfsize[Z]) return 0;
+
+ /* Bullet 2: */
+ /* test if the box intersects the plane of the triangle */
+ /* compute plane equation of triangle: normal*x+d=0 */
+ CROSS(normal,e0,e1);
+ d=-DOT(normal,v0); /* plane eq: normal.x()+d=0 */
+ if(!planeBoxOverlap(normal,d,boxhalfsize)) return 0;
+
+ return 1; /* box and triangle overlaps */
+}
+
+#undef X
+#undef Y
+#undef Z
+#undef CROSS
+#undef DOT
+#undef SUB
+#undef FINDMINMAX
+#undef AXISTEST_X01
+#undef AXISTEST_X2
+#undef AXISTEST_Y02
+#undef AXISTEST_Y1
+#undef AXISTEST_Z12
+#undef AXISTEST_Z0
+
+/* Triangle/triangle intersection test routine,
+ * by Tomas Moller, 1997.
+ * See article "A Fast Triangle-Triangle Intersection Test",
+ * Journal of Graphics Tools, 2(2), 1997
+ * updated: 2001-06-20 (added line of intersection)
+ *
+ * int tri_tri_intersect(float V0[3],float V1[3],float V2[3],
+ * float U0[3],float U1[3],float U2[3])
+ *
+ * parameters: vertices of triangle 1: V0,V1,V2
+ * vertices of triangle 2: U0,U1,U2
+ * result : returns 1 if the triangles intersect, otherwise 0
+ *
+ * Here is a version withouts divisions (a little faster)
+ * int NoDivTriTriIsect(float V0[3],float V1[3],float V2[3],
+ * float U0[3],float U1[3],float U2[3]);
+ *
+ * This version computes the line of intersection as well (if they are not coplanar):
+ * int tri_tri_intersect_with_isectline(float V0[3],float V1[3],float V2[3],
+ * float U0[3],float U1[3],float U2[3],int *coplanar,
+ * float isectpt1[3],float isectpt2[3]);
+ * coplanar returns whether the tris are coplanar
+ * isectpt1, isectpt2 are the endpoints of the line of intersection
+ */
+
+#include <math.h>
+
+//#define FABS(x) ((float)fabs(x)) /* implement as is fastest on your machine */
+
+/* if USE_EPSILON_TEST is true then we do a check:
+ if |dv|<EPSILON then dv=0.0f;
+ else no check is done (which is less robust)
+*/
+#define USE_EPSILON_TEST TRUE
+#ifndef EPSILON
+#define EPSILON 0.000001 // should be replaced by a const float; the previous def of EPSILON is 0.01
+#endif
+
+
+/* some macros */
+#define CROSS(dest,v1,v2) \
+ dest[0]=v1[1]*v2[2]-v1[2]*v2[1]; \
+ dest[1]=v1[2]*v2[0]-v1[0]*v2[2]; \
+ dest[2]=v1[0]*v2[1]-v1[1]*v2[0];
+
+#define DOT(v1,v2) (v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2])
+
+#define SUB(dest,v1,v2) dest[0]=v1[0]-v2[0]; dest[1]=v1[1]-v2[1]; dest[2]=v1[2]-v2[2];
+
+#define ADD(dest,v1,v2) dest[0]=v1[0]+v2[0]; dest[1]=v1[1]+v2[1]; dest[2]=v1[2]+v2[2];
+
+#define MULT(dest,v,factor) dest[0]=factor*v[0]; dest[1]=factor*v[1]; dest[2]=factor*v[2];
+
+#define SET(dest,src) dest[0]=src[0]; dest[1]=src[1]; dest[2]=src[2];
+
+/* sort so that a<=b */
+/*#define SORT(a,b) \
+ if(a>b) \
+ { \
+ float c; \
+ c=a; \
+ a=b; \
+ b=c; \
+ }*/
+void sort(float& a, float& b) {
+ if (a > b) {
+ float c = a;
+ a = b;
+ b = c;
+ }
+}
+
+#define ISECT(VV0,VV1,VV2,D0,D1,D2,isect0,isect1) \
+ isect0=VV0+(VV1-VV0)*D0/(D0-D1); \
+ isect1=VV0+(VV2-VV0)*D0/(D0-D2);
+
+
+#define COMPUTE_INTERVALS(VV0,VV1,VV2,D0,D1,D2,D0D1,D0D2,isect0,isect1) \
+ if(D0D1>0.0f) \
+ { \
+ /* here we know that D0D2<=0.0 */ \
+ /* that is D0, D1 are on the same side, D2 on the other or on the plane */ \
+ ISECT(VV2,VV0,VV1,D2,D0,D1,isect0,isect1); \
+ } \
+ else if(D0D2>0.0f) \
+ { \
+ /* here we know that d0d1<=0.0 */ \
+ ISECT(VV1,VV0,VV2,D1,D0,D2,isect0,isect1); \
+ } \
+ else if(D1*D2>0.0f || D0!=0.0f) \
+ { \
+ /* here we know that d0d1<=0.0 or that D0!=0.0 */ \
+ ISECT(VV0,VV1,VV2,D0,D1,D2,isect0,isect1); \
+ } \
+ else if(D1!=0.0f) \
+ { \
+ ISECT(VV1,VV0,VV2,D1,D0,D2,isect0,isect1); \
+ } \
+ else if(D2!=0.0f) \
+ { \
+ ISECT(VV2,VV0,VV1,D2,D0,D1,isect0,isect1); \
+ } \
+ else \
+ { \
+ /* triangles are coplanar */ \
+ return coplanar_tri_tri_OG(N1,V0,V1,V2,U0,U1,U2); \
+ }
+
+
+
+/* this edge to edge test is based on Franlin Antonio's gem:
+ "Faster Line Segment Intersection", in Graphics Gems III,
+ pp. 199-202 */
+#define EDGE_EDGE_TEST(V0,U0,U1) \
+ Bx=U0[i0]-U1[i0]; \
+ By=U0[i1]-U1[i1]; \
+ Cx=V0[i0]-U0[i0]; \
+ Cy=V0[i1]-U0[i1]; \
+ f=Ay*Bx-Ax*By; \
+ d=By*Cx-Bx*Cy; \
+ if((f>0 && d>=0 && d<=f) || (f<0 && d<=0 && d>=f)) \
+ { \
+ e=Ax*Cy-Ay*Cx; \
+ if(f>0) \
+ { \
+ if(e>=0 && e<=f) return 1; \
+ } \
+ else \
+ { \
+ if(e<=0 && e>=f) return 1; \
+ } \
+ }
+
+#define EDGE_AGAINST_TRI_EDGES(V0,V1,U0,U1,U2) \
+{ \
+ float Ax,Ay,Bx,By,Cx,Cy,e,d,f; \
+ Ax=V1[i0]-V0[i0]; \
+ Ay=V1[i1]-V0[i1]; \
+ /* test edge U0,U1 against V0,V1 */ \
+ EDGE_EDGE_TEST(V0,U0,U1); \
+ /* test edge U1,U2 against V0,V1 */ \
+ EDGE_EDGE_TEST(V0,U1,U2); \
+ /* test edge U2,U1 against V0,V1 */ \
+ EDGE_EDGE_TEST(V0,U2,U0); \
+}
+
+#define POINT_IN_TRI(V0,U0,U1,U2) \
+{ \
+ float a,b,c,d0,d1,d2; \
+ /* is T1 completly inside T2? */ \
+ /* check if V0 is inside tri(U0,U1,U2) */ \
+ a=U1[i1]-U0[i1]; \
+ b=-(U1[i0]-U0[i0]); \
+ c=-a*U0[i0]-b*U0[i1]; \
+ d0=a*V0[i0]+b*V0[i1]+c; \
+ \
+ a=U2[i1]-U1[i1]; \
+ b=-(U2[i0]-U1[i0]); \
+ c=-a*U1[i0]-b*U1[i1]; \
+ d1=a*V0[i0]+b*V0[i1]+c; \
+ \
+ a=U0[i1]-U2[i1]; \
+ b=-(U0[i0]-U2[i0]); \
+ c=-a*U2[i0]-b*U2[i1]; \
+ d2=a*V0[i0]+b*V0[i1]+c; \
+ if(d0*d1>0.0f) \
+ { \
+ if(d0*d2>0.0f) return 1; \
+ } \
+}
+
+int coplanar_tri_tri_OG(float N[3],float V0[3],float V1[3],float V2[3],
+ float U0[3],float U1[3],float U2[3])
+{
+ float A[3];
+ short i0,i1;
+ /* first project onto an axis-aligned plane, that maximizes the area */
+ /* of the triangles, compute indices: i0,i1. */
+ A[0]=fabs(N[0]);
+ A[1]=fabs(N[1]);
+ A[2]=fabs(N[2]);
+ if(A[0]>A[1])
+ {
+ if(A[0]>A[2])
+ {
+ i0=1; /* A[0] is greatest */
+ i1=2;
+ }
+ else
+ {
+ i0=0; /* A[2] is greatest */
+ i1=1;
+ }
+ }
+ else /* A[0]<=A[1] */
+ {
+ if(A[2]>A[1])
+ {
+ i0=0; /* A[2] is greatest */
+ i1=1;
+ }
+ else
+ {
+ i0=0; /* A[1] is greatest */
+ i1=2;
+ }
+ }
+
+ /* test all edges of triangle 1 against the edges of triangle 2 */
+ EDGE_AGAINST_TRI_EDGES(V0,V1,U0,U1,U2);
+ EDGE_AGAINST_TRI_EDGES(V1,V2,U0,U1,U2);
+ EDGE_AGAINST_TRI_EDGES(V2,V0,U0,U1,U2);
+
+ /* finally, test if tri1 is totally contained in tri2 or vice versa */
+ POINT_IN_TRI(V0,U0,U1,U2);
+ POINT_IN_TRI(U0,V0,V1,V2);
+
+ return 0;
+}
+
+
+int tri_tri_intersect(float V0[3],float V1[3],float V2[3],
+ float U0[3],float U1[3],float U2[3])
+{
+ float E1[3],E2[3];
+ float N1[3],N2[3],d1,d2;
+ float du0,du1,du2,dv0,dv1,dv2;
+ float D[3];
+ float isect1[2], isect2[2];
+ float du0du1,du0du2,dv0dv1,dv0dv2;
+ short index;
+ float vp0,vp1,vp2;
+ float up0,up1,up2;
+ float b,c,max;
+
+ /* compute plane equation of triangle(V0,V1,V2) */
+ SUB(E1,V1,V0);
+ SUB(E2,V2,V0);
+ CROSS(N1,E1,E2);
+ d1=-DOT(N1,V0);
+ /* plane equation 1: N1.X+d1=0 */
+
+ /* put U0,U1,U2 into plane equation 1 to compute signed distances to the plane*/
+ du0=DOT(N1,U0)+d1;
+ du1=DOT(N1,U1)+d1;
+ du2=DOT(N1,U2)+d1;
+
+ /* coplanarity robustness check */
+#if USE_EPSILON_TEST==TRUE
+ if(fabsf(du0)<EPSILON) du0=0.0f;
+ if(fabsf(du1)<EPSILON) du1=0.0f;
+ if(fabsf(du2)<EPSILON) du2=0.0f;
+#endif
+ du0du1=du0*du1;
+ du0du2=du0*du2;
+
+ if(du0du1>0.0f && du0du2>0.0f) /* same sign on all of them + not equal 0 ? */
+ return 0; /* no intersection occurs */
+
+ /* compute plane of triangle (U0,U1,U2) */
+ SUB(E1,U1,U0);
+ SUB(E2,U2,U0);
+ CROSS(N2,E1,E2);
+ d2=-DOT(N2,U0);
+ /* plane equation 2: N2.X+d2=0 */
+
+ /* put V0,V1,V2 into plane equation 2 */
+ dv0=DOT(N2,V0)+d2;
+ dv1=DOT(N2,V1)+d2;
+ dv2=DOT(N2,V2)+d2;
+
+#if USE_EPSILON_TEST==TRUE
+ if(fabsf(dv0)<EPSILON) dv0=0.0f;
+ if(fabsf(dv1)<EPSILON) dv1=0.0f;
+ if(fabsf(dv2)<EPSILON) dv2=0.0f;
+#endif
+
+ dv0dv1=dv0*dv1;
+ dv0dv2=dv0*dv2;
+
+ if(dv0dv1>0.0f && dv0dv2>0.0f) /* same sign on all of them + not equal 0 ? */
+ return 0; /* no intersection occurs */
+
+ /* compute direction of intersection line */
+ CROSS(D,N1,N2);
+
+ /* compute and index to the largest component of D */
+ max=fabsf(D[0]);
+ index=0;
+ b=fabsf(D[1]);
+ c=fabsf(D[2]);
+ if(b>max) max=b,index=1;
+ if(c>max) max=c,index=2;
+
+ /* this is the simplified projection onto L*/
+ vp0=V0[index];
+ vp1=V1[index];
+ vp2=V2[index];
+
+ up0=U0[index];
+ up1=U1[index];
+ up2=U2[index];
+
+ /* compute interval for triangle 1 */
+ COMPUTE_INTERVALS(vp0,vp1,vp2,dv0,dv1,dv2,dv0dv1,dv0dv2,isect1[0],isect1[1]);
+
+ /* compute interval for triangle 2 */
+ COMPUTE_INTERVALS(up0,up1,up2,du0,du1,du2,du0du1,du0du2,isect2[0],isect2[1]);
+
+ sort(isect1[0],isect1[1]);
+ sort(isect2[0],isect2[1]);
+
+ if(isect1[1]<isect2[0] || isect2[1]<isect1[0]) return 0;
+ return 1;
+}
+
+
+#define NEWCOMPUTE_INTERVALS(VV0,VV1,VV2,D0,D1,D2,D0D1,D0D2,A,B,C,X0,X1) \
+{ \
+ if(D0D1>0.0f) \
+ { \
+ /* here we know that D0D2<=0.0 */ \
+ /* that is D0, D1 are on the same side, D2 on the other or on the plane */ \
+ A=VV2; B=(VV0-VV2)*D2; C=(VV1-VV2)*D2; X0=D2-D0; X1=D2-D1; \
+ } \
+ else if(D0D2>0.0f)\
+ { \
+ /* here we know that d0d1<=0.0 */ \
+ A=VV1; B=(VV0-VV1)*D1; C=(VV2-VV1)*D1; X0=D1-D0; X1=D1-D2; \
+ } \
+ else if(D1*D2>0.0f || D0!=0.0f) \
+ { \
+ /* here we know that d0d1<=0.0 or that D0!=0.0 */ \
+ A=VV0; B=(VV1-VV0)*D0; C=(VV2-VV0)*D0; X0=D0-D1; X1=D0-D2; \
+ } \
+ else if(D1!=0.0f) \
+ { \
+ A=VV1; B=(VV0-VV1)*D1; C=(VV2-VV1)*D1; X0=D1-D0; X1=D1-D2; \
+ } \
+ else if(D2!=0.0f) \
+ { \
+ A=VV2; B=(VV0-VV2)*D2; C=(VV1-VV2)*D2; X0=D2-D0; X1=D2-D1; \
+ } \
+ else \
+ { \
+ /* triangles are coplanar */ \
+ return coplanar_tri_tri_OG(N1,V0,V1,V2,U0,U1,U2); \
+ } \
+}
+
+
+
+int NoDivTriTriIsect(float V0[3],float V1[3],float V2[3],
+ float U0[3],float U1[3],float U2[3])
+{
+ float E1[3],E2[3];
+ float N1[3],N2[3],d1,d2;
+ float du0,du1,du2,dv0,dv1,dv2;
+ float D[3];
+ float isect1[2], isect2[2];
+ float du0du1,du0du2,dv0dv1,dv0dv2;
+ short index;
+ float vp0,vp1,vp2;
+ float up0,up1,up2;
+ float bb,cc,max;
+ float a,b,c,x0,x1;
+ float d,e,f,y0,y1;
+ float xx,yy,xxyy,tmp;
+
+ /* compute plane equation of triangle(V0,V1,V2) */
+ SUB(E1,V1,V0);
+ SUB(E2,V2,V0);
+ CROSS(N1,E1,E2);
+ d1=-DOT(N1,V0);
+ /* plane equation 1: N1.X+d1=0 */
+
+ /* put U0,U1,U2 into plane equation 1 to compute signed distances to the plane*/
+ du0=DOT(N1,U0)+d1;
+ du1=DOT(N1,U1)+d1;
+ du2=DOT(N1,U2)+d1;
+
+ /* coplanarity robustness check */
+#if USE_EPSILON_TEST==TRUE
+ if(fabsf(du0)<EPSILON) du0=0.0f;
+ if(fabsf(du1)<EPSILON) du1=0.0f;
+ if(fabsf(du2)<EPSILON) du2=0.0f;
+#endif
+ du0du1=du0*du1;
+ du0du2=du0*du2;
+
+ if(du0du1>0.0f && du0du2>0.0f) /* same sign on all of them + not equal 0 ? */
+ return 0; /* no intersection occurs */
+
+ /* compute plane of triangle (U0,U1,U2) */
+ SUB(E1,U1,U0);
+ SUB(E2,U2,U0);
+ CROSS(N2,E1,E2);
+ d2=-DOT(N2,U0);
+ /* plane equation 2: N2.X+d2=0 */
+
+ /* put V0,V1,V2 into plane equation 2 */
+ dv0=DOT(N2,V0)+d2;
+ dv1=DOT(N2,V1)+d2;
+ dv2=DOT(N2,V2)+d2;
+
+#if USE_EPSILON_TEST==TRUE
+ if(fabsf(dv0)<EPSILON) dv0=0.0f;
+ if(fabsf(dv1)<EPSILON) dv1=0.0f;
+ if(fabsf(dv2)<EPSILON) dv2=0.0f;
+#endif
+
+ dv0dv1=dv0*dv1;
+ dv0dv2=dv0*dv2;
+
+ if(dv0dv1>0.0f && dv0dv2>0.0f) /* same sign on all of them + not equal 0 ? */
+ return 0; /* no intersection occurs */
+
+ /* compute direction of intersection line */
+ CROSS(D,N1,N2);
+
+ /* compute and index to the largest component of D */
+ max=(float)fabsf(D[0]);
+ index=0;
+ bb=(float)fabsf(D[1]);
+ cc=(float)fabsf(D[2]);
+ if(bb>max) max=bb,index=1;
+ if(cc>max) max=cc,index=2;
+
+ /* this is the simplified projection onto L*/
+ vp0=V0[index];
+ vp1=V1[index];
+ vp2=V2[index];
+
+ up0=U0[index];
+ up1=U1[index];
+ up2=U2[index];
+
+ /* compute interval for triangle 1 */
+ NEWCOMPUTE_INTERVALS(vp0,vp1,vp2,dv0,dv1,dv2,dv0dv1,dv0dv2,a,b,c,x0,x1);
+
+ /* compute interval for triangle 2 */
+ NEWCOMPUTE_INTERVALS(up0,up1,up2,du0,du1,du2,du0du1,du0du2,d,e,f,y0,y1);
+
+ xx=x0*x1;
+ yy=y0*y1;
+ xxyy=xx*yy;
+
+ tmp=a*xxyy;
+ isect1[0]=tmp+b*x1*yy;
+ isect1[1]=tmp+c*x0*yy;
+
+ tmp=d*xxyy;
+ isect2[0]=tmp+e*xx*y1;
+ isect2[1]=tmp+f*xx*y0;
+
+ sort(isect1[0],isect1[1]);
+ sort(isect2[0],isect2[1]);
+
+ if(isect1[1]<isect2[0] || isect2[1]<isect1[0]) return 0;
+ return 1;
+}
+
+/* sort so that a<=b */
+/*#define SORT2(a,b,smallest) \
+ if(a>b) \
+ { \
+ float c; \
+ c=a; \
+ a=b; \
+ b=c; \
+ smallest=1; \
+ } \
+ else smallest=0;*/
+void sort2(float& a, float& b, int& smallest) {
+ if (a > b) {
+ float c = a;
+ a = b;
+ b = c;
+ smallest = 1;
+ }
+ else smallest = 0;
+}
+
+
+inline void isect2(float VTX0[3],float VTX1[3],float VTX2[3],float VV0,float VV1,float VV2,
+ float D0,float D1,float D2,float *isect0,float *isect1,float isectpoint0[3],float isectpoint1[3])
+{
+ float tmp=D0/(D0-D1);
+ float diff[3];
+ *isect0=VV0+(VV1-VV0)*tmp;
+ SUB(diff,VTX1,VTX0);
+ MULT(diff,diff,tmp);
+ ADD(isectpoint0,diff,VTX0);
+ tmp=D0/(D0-D2);
+ *isect1=VV0+(VV2-VV0)*tmp;
+ SUB(diff,VTX2,VTX0);
+ MULT(diff,diff,tmp);
+ ADD(isectpoint1,VTX0,diff);
+}
+
+
+#if 0
+#define ISECT2(VTX0,VTX1,VTX2,VV0,VV1,VV2,D0,D1,D2,isect0,isect1,isectpoint0,isectpoint1) \
+ tmp=D0/(D0-D1); \
+ isect0=VV0+(VV1-VV0)*tmp; \
+ SUB(diff,VTX1,VTX0); \
+ MULT(diff,diff,tmp); \
+ ADD(isectpoint0,diff,VTX0); \
+ tmp=D0/(D0-D2);
+/* isect1=VV0+(VV2-VV0)*tmp; \ */
+/* SUB(diff,VTX2,VTX0); \ */
+/* MULT(diff,diff,tmp); \ */
+/* ADD(isectpoint1,VTX0,diff); */
+#endif
+
+inline int compute_intervals_isectline(float VERT0[3],float VERT1[3],float VERT2[3],
+ float VV0,float VV1,float VV2,float D0,float D1,float D2,
+ float D0D1,float D0D2,float *isect0,float *isect1,
+ float isectpoint0[3],float isectpoint1[3])
+{
+ if(D0D1>0.0f)
+ {
+ /* here we know that D0D2<=0.0 */
+ /* that is D0, D1 are on the same side, D2 on the other or on the plane */
+ isect2(VERT2,VERT0,VERT1,VV2,VV0,VV1,D2,D0,D1,isect0,isect1,isectpoint0,isectpoint1);
+ }
+ else if(D0D2>0.0f)
+ {
+ /* here we know that d0d1<=0.0 */
+ isect2(VERT1,VERT0,VERT2,VV1,VV0,VV2,D1,D0,D2,isect0,isect1,isectpoint0,isectpoint1);
+ }
+ else if(D1*D2>0.0f || D0!=0.0f)
+ {
+ /* here we know that d0d1<=0.0 or that D0!=0.0 */
+ isect2(VERT0,VERT1,VERT2,VV0,VV1,VV2,D0,D1,D2,isect0,isect1,isectpoint0,isectpoint1);
+ }
+ else if(D1!=0.0f)
+ {
+ isect2(VERT1,VERT0,VERT2,VV1,VV0,VV2,D1,D0,D2,isect0,isect1,isectpoint0,isectpoint1);
+ }
+ else if(D2!=0.0f)
+ {
+ isect2(VERT2,VERT0,VERT1,VV2,VV0,VV1,D2,D0,D1,isect0,isect1,isectpoint0,isectpoint1);
+ }
+ else
+ {
+ /* triangles are coplanar */
+ return 1;
+ }
+ return 0;
+}
+
+#define COMPUTE_INTERVALS_ISECTLINE(VERT0,VERT1,VERT2,VV0,VV1,VV2,D0,D1,D2,D0D1,D0D2,isect0,isect1,isectpoint0,isectpoint1) \
+ if(D0D1>0.0f) \
+ { \
+ /* here we know that D0D2<=0.0 */ \
+ /* that is D0, D1 are on the same side, D2 on the other or on the plane */ \
+ isect2(VERT2,VERT0,VERT1,VV2,VV0,VV1,D2,D0,D1,&isect0,&isect1,isectpoint0,isectpoint1); \
+ }
+#if 0
+ else if(D0D2>0.0f) \
+ { \
+ /* here we know that d0d1<=0.0 */ \
+ isect2(VERT1,VERT0,VERT2,VV1,VV0,VV2,D1,D0,D2,&isect0,&isect1,isectpoint0,isectpoint1); \
+ } \
+ else if(D1*D2>0.0f || D0!=0.0f) \
+ { \
+ /* here we know that d0d1<=0.0 or that D0!=0.0 */ \
+ isect2(VERT0,VERT1,VERT2,VV0,VV1,VV2,D0,D1,D2,&isect0,&isect1,isectpoint0,isectpoint1); \
+ } \
+ else if(D1!=0.0f) \
+ { \
+ isect2(VERT1,VERT0,VERT2,VV1,VV0,VV2,D1,D0,D2,&isect0,&isect1,isectpoint0,isectpoint1); \
+ } \
+ else if(D2!=0.0f) \
+ { \
+ isect2(VERT2,VERT0,VERT1,VV2,VV0,VV1,D2,D0,D1,&isect0,&isect1,isectpoint0,isectpoint1); \
+ } \
+ else \
+ { \
+ /* triangles are coplanar */ \
+ coplanar=1; \
+ return coplanar_tri_tri_OG(N1,V0,V1,V2,U0,U1,U2); \
+ }
+#endif
+
+// int coplanar_tri_tri_OG(float N[3],float V0[3],float V1[3],float V2[3],
+// float U0[3],float U1[3],float U2[3])
+//{
+// float A[3];
+// short i0,i1;
+// /* first project onto an axis-aligned plane, that maximizes the area */
+// /* of the triangles, compute indices: i0,i1. */
+// A[0]=fabsf(N[0]);
+// A[1]=fabsf(N[1]);
+// A[2]=fabsf(N[2]);
+// if(A[0]>A[1])
+// {
+// if(A[0]>A[2])
+// {
+// i0=1; /* A[0] is greatest */
+// i1=2;
+// }
+// else
+// {
+// i0=0; /* A[2] is greatest */
+// i1=1;
+// }
+// }
+// else /* A[0]<=A[1] */
+// {
+// if(A[2]>A[1])
+// {
+// i0=0; /* A[2] is greatest */
+// i1=1;
+// }
+// else
+// {
+// i0=0; /* A[1] is greatest */
+// i1=2;
+// }
+// }
+//
+// /* test all edges of triangle 1 against the edges of triangle 2 */
+// EDGE_AGAINST_TRI_EDGES(V0,V1,U0,U1,U2);
+// EDGE_AGAINST_TRI_EDGES(V1,V2,U0,U1,U2);
+// EDGE_AGAINST_TRI_EDGES(V2,V0,U0,U1,U2);
+//
+// /* finally, test if tri1 is totally contained in tri2 or vice versa */
+// POINT_IN_TRI(V0,U0,U1,U2);
+// POINT_IN_TRI(U0,V0,V1,V2);
+//
+// return 0;
+//}
+
+int tri_tri_intersect_with_isectline(float V0[3],float V1[3],float V2[3],
+ float U0[3],float U1[3],float U2[3],int *coplanar,
+ float *isectpt1,float *isectpt2)
+{
+ float E1[3],E2[3];
+ float N1[3],N2[3],d1,d2;
+ float du0,du1,du2,dv0,dv1,dv2;
+ float D[3];
+ float isect1[2] = {0.0f,0.0f}, isect2[2] = {0.0f,0.0f};
+ float isectpointA1[3],isectpointA2[3];
+ float isectpointB1[3] = {0.0f,0.0f,0.0f},isectpointB2[3] = {0.0f,0.0f,0.0f};
+ float du0du1,du0du2,dv0dv1,dv0dv2;
+ short index;
+ float vp0,vp1,vp2;
+ float up0,up1,up2;
+ float b,c,max;
+ //float tmp,diff[3];
+ int smallest1,smallest2;
+
+ /* compute plane equation of triangle(V0,V1,V2) */
+ SUB(E1,V1,V0);
+ SUB(E2,V2,V0);
+ CROSS(N1,E1,E2);
+ d1=-DOT(N1,V0);
+ /* plane equation 1: N1.X+d1=0 */
+
+ /* put U0,U1,U2 into plane equation 1 to compute signed distances to the plane*/
+ du0=DOT(N1,U0)+d1;
+ du1=DOT(N1,U1)+d1;
+ du2=DOT(N1,U2)+d1;
+
+ /* coplanarity robustness check */
+#if USE_EPSILON_TEST==TRUE
+ if(fabsf(du0)<EPSILON) du0=0.0f;
+ if(fabsf(du1)<EPSILON) du1=0.0f;
+ if(fabsf(du2)<EPSILON) du2=0.0f;
+#endif
+ du0du1=du0*du1;
+ du0du2=du0*du2;
+
+ if(du0du1>0.0f && du0du2>0.0f) /* same sign on all of them + not equal 0 ? */
+ return 0; /* no intersection occurs */
+
+ /* compute plane of triangle (U0,U1,U2) */
+ SUB(E1,U1,U0);
+ SUB(E2,U2,U0);
+ CROSS(N2,E1,E2);
+ d2=-DOT(N2,U0);
+ /* plane equation 2: N2.X+d2=0 */
+
+ /* put V0,V1,V2 into plane equation 2 */
+ dv0=DOT(N2,V0)+d2;
+ dv1=DOT(N2,V1)+d2;
+ dv2=DOT(N2,V2)+d2;
+
+#if USE_EPSILON_TEST==TRUE
+ if(fabsf(dv0)<EPSILON) dv0=0.0f;
+ if(fabsf(dv1)<EPSILON) dv1=0.0f;
+ if(fabsf(dv2)<EPSILON) dv2=0.0f;
+#endif
+
+ dv0dv1=dv0*dv1;
+ dv0dv2=dv0*dv2;
+
+ if(dv0dv1>0.0f && dv0dv2>0.0f) /* same sign on all of them + not equal 0 ? */
+ return 0; /* no intersection occurs */
+
+ /* compute direction of intersection line */
+ CROSS(D,N1,N2);
+
+ /* compute and index to the largest component of D */
+ max=fabsf(D[0]);
+ index=0;
+ b=fabsf(D[1]);
+ c=fabsf(D[2]);
+ if(b>max) max=b,index=1;
+ if(c>max) max=c,index=2;
+
+ /* this is the simplified projection onto L*/
+ vp0=V0[index];
+ vp1=V1[index];
+ vp2=V2[index];
+
+ up0=U0[index];
+ up1=U1[index];
+ up2=U2[index];
+
+ /* compute interval for triangle 1 */
+ *coplanar=compute_intervals_isectline(V0,V1,V2,vp0,vp1,vp2,dv0,dv1,dv2,
+ dv0dv1,dv0dv2,&isect1[0],&isect1[1],isectpointA1,isectpointA2);
+ if(*coplanar) return coplanar_tri_tri_OG(N1,V0,V1,V2,U0,U1,U2);
+
+
+ /* compute interval for triangle 2 */
+ compute_intervals_isectline(U0,U1,U2,up0,up1,up2,du0,du1,du2,
+ du0du1,du0du2,&isect2[0],&isect2[1],isectpointB1,isectpointB2);
+
+ sort2(isect1[0],isect1[1],smallest1);
+ sort2(isect2[0],isect2[1],smallest2);
+
+ if(isect1[1]<isect2[0] || isect2[1]<isect1[0]) return 0;
+
+ /* at this point, we know that the triangles intersect */
+
+ if(isect2[0]<isect1[0])
+ {
+ if(smallest1==0) { SET(isectpt1,isectpointA1); }
+ else { SET(isectpt1,isectpointA2); }
+
+ if(isect2[1]<isect1[1])
+ {
+ if(smallest2==0) { SET(isectpt2,isectpointB2); }
+ else { SET(isectpt2,isectpointB1); }
+ }
+ else
+ {
+ if(smallest1==0) { SET(isectpt2,isectpointA2); }
+ else { SET(isectpt2,isectpointA1); }
+ }
+ }
+ else
+ {
+ if(smallest2==0) { SET(isectpt1,isectpointB1); }
+ else { SET(isectpt1,isectpointB2); }
+
+ if(isect2[1]>isect1[1])
+ {
+ if(smallest1==0) { SET(isectpt2,isectpointA2); }
+ else { SET(isectpt2,isectpointA1); }
+ }
+ else
+ {
+ if(smallest2==0) { SET(isectpt2,isectpointB2); }
+ else { SET(isectpt2,isectpointB1); }
+ }
+ }
+ return 1;
+}
+
+bool PointInTriangle(vec3 &point, vec3 *tri_point[3])
+{
+ if((((tri_point[1]->z() - tri_point[0]->z()) * (tri_point[0]->x() - point.x()) -
+ (tri_point[1]->x() - tri_point[0]->x()) * (tri_point[0]->z() - point.z()))<0) &&
+ (((tri_point[2]->z() - tri_point[1]->z()) * (tri_point[1]->x() - point.x()) -
+ (tri_point[2]->x() - tri_point[1]->x()) * (tri_point[1]->z() - point.z()))<0) &&
+ (((tri_point[0]->z() - tri_point[2]->z()) * (tri_point[2]->x() - point.x()) -
+ (tri_point[0]->x() - tri_point[2]->x()) * (tri_point[2]->z() - point.z()))<0))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+// Check for intersection between segments p1-p2 and p3-p4
+// Based on http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/
+bool LineIntersection2D(const vec3 &p1, const vec3 &p2, const vec3 &p3, const vec3 &p4)
+{
+ float denom = (p4.z() - p3.z())*(p2.x() - p1.x()) - (p4.x() - p3.x())*(p2.z() - p1.z());
+ float ua = ((p4.x() - p3.x())*(p1.z() - p3.z()) - (p4.z() - p3.z())*(p1.x() - p3.x())) / denom;
+ float ub = ((p2.x() - p1.x())*(p1.z() - p3.z()) - (p2.z() - p1.z())*(p1.x() - p3.x())) / denom;
+
+ if( ua>=0 && ua<=1 && ub>=0 && ub<=1 )
+ return true;
+ return false;
+}
+
+bool LineSquareIntersection2D(vec3 &min, vec3 &max, vec3 &start, vec3 &end)
+{
+ // Discard lines that are entirely on one side of a box plane
+ if(start.x() < min.x() && end.x() < min.x())return false;
+ if(start.z() < min.z() && end.z() < min.z())return false;
+ if(start.x() > max.x() && end.x() > max.x())return false;
+ if(start.z() > max.z() && end.z() > max.z())return false;
+
+ // Check planes
+ if((start.x() > max.x()) != (end.x() > max.x()))
+ if(LineIntersection2D(vec3(max.x(),0,min.z()), vec3(max.x(),0,max.z()), start, end))
+ return true;
+ if((start.x() > min.x()) != (end.x() > min.x()))
+ if(LineIntersection2D(vec3(min.x(),0,min.z()), vec3(min.x(),0,max.z()), start, end))
+ return true;
+ if((start.z() > max.z()) != (end.z() > max.z()))
+ if(LineIntersection2D(vec3(min.x(),0,max.z()), vec3(max.x(),0,max.z()), start, end))
+ return true;
+ if((start.z() > min.z()) != (end.z() > min.z()))
+ if(LineIntersection2D(vec3(min.x(),0,min.z()), vec3(max.x(),0,min.z()), start, end))
+ return true;
+
+ return false;
+}
+
+bool TriangleSquareIntersection2D(vec3 &min, vec3 &max, vec3 *tri_point[3])
+{
+ // Discard triangles that are entirely on one side of a box plane
+ if(tri_point[0]->x() < min.x() && tri_point[1]->x() < min.x() && tri_point[2]->x() < min.x())return false;
+ if(tri_point[0]->z() < min.z() && tri_point[1]->z() < min.z() && tri_point[2]->z() < min.z())return false;
+ if(tri_point[0]->x() > max.x() && tri_point[1]->x() > max.x() && tri_point[2]->x() > max.x())return false;
+ if(tri_point[0]->z() > max.z() && tri_point[1]->z() > max.z() && tri_point[2]->z() > max.z())return false;
+
+ // Accept all triangles that have a vertex in the square
+ for (int i = 0; i<3; i++){
+ if(tri_point[i]->x() < max.x() && tri_point[i]->z() < max.z() &&
+ tri_point[i]->x() > min.x() && tri_point[i]->z() > min.z()){
+ return true;
+ }
+ }
+
+ // Accept all triangles that completely enclose the square
+ if(PointInTriangle(min, tri_point)){
+ return true;
+ }
+
+ // If we are still here, check line-square collisions
+ if(LineSquareIntersection2D(min, max, *tri_point[0], *tri_point[1])) return true;
+ if(LineSquareIntersection2D(min, max, *tri_point[1], *tri_point[2])) return true;
+ if(LineSquareIntersection2D(min, max, *tri_point[0], *tri_point[2])) return true;
+
+ return false;
+}
+
+Collision::Collision() : hit(false), hit_what(NULL), hit_how(-1) {
+
+}
diff --git a/Source/Internal/collisiondetection.h b/Source/Internal/collisiondetection.h
new file mode 100644
index 00000000..6275dff2
--- /dev/null
+++ b/Source/Internal/collisiondetection.h
@@ -0,0 +1,82 @@
+//-----------------------------------------------------------------------------
+// Name: collisiondetection.h
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: This file stores collision detection functions
+// (mostly scavenged from the internet)
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+
+class Object;
+class Collision {
+public:
+ Collision();
+
+ bool hit;
+ Object* hit_what;
+ int hit_how;
+ vec3 hit_normal;
+ vec3 hit_where;
+};
+
+//-----------------------------------------------------------------------------
+// Class Definition
+//-----------------------------------------------------------------------------
+
+int LineFacet(const vec3 &p1,const vec3 &p2,const vec3 &pa,const vec3 &pb,const vec3 &pc, vec3 *p);
+int LineFacetNoBackface(const vec3 &p1,const vec3 &p2,const vec3 &pa,const vec3 &pb,const vec3 &pc, vec3 *p, const vec3 &n);
+int LineFacet(const vec3 &p1,const vec3 &p2,const vec3 &pa,const vec3 &pb,const vec3 &pc, vec3 *p, const vec3 &n);
+int LineFacet(vec3 *p1,vec3 *p2,vec3 *pa,vec3 *pb,vec3 *pc,vec3 *p);
+int LineFacet(vec3 *p1,vec3 *p2,vec3 *pa,vec3 *pb,vec3 *pc,vec3 *p,vec3 *n);
+
+bool inTriangle(const vec3 &pointv, const vec3 &normal, const vec3 &p1v, const vec3 &p2v, const vec3 &p3v);
+vec3 barycentric(const vec3 &pointv, const vec3 &normal, const vec3 &p1v, const vec3 &p2v, const vec3 &p3v);
+
+bool sphere_line_intersection (const vec3& p1, const vec3& p2, const vec3& p3, const float &r , vec3 *ret=0);
+
+bool lineBox(const vec3 &start, const vec3 &end,const vec3 &box_min,const vec3 &box_max, float *time);
+
+bool triBoxOverlap(const vec3 &min, const vec3 &max, const vec3 &vert1, const vec3 &vert2, const vec3 &vert3);
+
+
+int coplanar_tri_tri_OG(float N[3],float V0[3],float V1[3],float V2[3],
+ float U0[3],float U1[3],float U2[3]);
+
+int tri_tri_intersect(float V0[3],float V1[3],float V2[3],
+ float U0[3],float U1[3],float U2[3]);
+
+int NoDivTriTriIsect(float V0[3],float V1[3],float V2[3],
+ float U0[3],float U1[3],float U2[3]);
+
+int tri_tri_intersect_with_isectline(float V0[3],float V1[3],float V2[3],
+ float U0[3],float U1[3],float U2[3],int *coplanar,
+ float *isectpt1,float *isectpt2);
+
+bool PointInTriangle(vec3 &point, vec3 *tri_point[3]);
+
+bool LineIntersection2D(vec3 &p1, vec3 &p2, vec3 &p3, vec3 &p4);
+
+bool LineSquareIntersection2D(vec3 &min, vec3 &max, vec3 &start, vec3 &end);
+
+bool TriangleSquareIntersection2D(vec3 &min, vec3 &max, vec3 *tri_point[3]);
+
+bool DistancePointLine( const vec3 &Point, const vec3 &LineStart, const vec3 &LineEnd, float *Distance, vec3 *Intersection );
diff --git a/Source/Internal/comma_separated_list.h b/Source/Internal/comma_separated_list.h
new file mode 100644
index 00000000..8dc9890d
--- /dev/null
+++ b/Source/Internal/comma_separated_list.h
@@ -0,0 +1,64 @@
+//-----------------------------------------------------------------------------
+// Name: comma_separated_list.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <string>
+
+// The CSL iterator iterates over the tokens of a comma-separated list, e.g.
+// for "a, b, c" it will return "a", "b" and "c".
+struct CSLIterator {
+ std::string str_;
+ int start_, comma_pos_;
+
+ CSLIterator(const std::string& str)
+ :start_(0), comma_pos_(0)
+ {
+ str_ = str;
+ }
+
+ bool GetNext(std::string *str){
+ if(comma_pos_ == (int)str_.length()){
+ return false;
+ }
+ int end = (int) str_.length();
+ comma_pos_ = (int) str_.length();
+ for(int i=start_; i<end; ++i){
+ if(str_[i] == ','){
+ comma_pos_ = i;
+ break;
+ }
+ }
+ while(str_[start_] == ' '){
+ ++start_;
+ }
+ end = comma_pos_ - 1;
+ while(str_[end] == ' '){
+ --end;
+ }
+ if(str){
+ (*str) = str_.substr(start_, end-start_+1);
+ }
+ start_ = comma_pos_ + 1;
+ return true;
+ }
+};
diff --git a/Source/Internal/common.cpp b/Source/Internal/common.cpp
new file mode 100644
index 00000000..91c360d9
--- /dev/null
+++ b/Source/Internal/common.cpp
@@ -0,0 +1,68 @@
+//-----------------------------------------------------------------------------
+// Name: common.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "common.h"
+
+#include <cstdarg>
+#include <stdint.h>
+
+void FormatString(char* buf, int buf_size, const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ VFormatString(buf, buf_size, fmt, args);
+ va_end(args);
+}
+
+//From http://www.cse.yorku.ca/~oz/hash.html
+//djb2 hash function
+int djb2_hash(unsigned char* str) {
+ uint32_t hash_val = 5381;
+ int c;
+ while ((c = *str++)) {
+ hash_val = ((hash_val << 5) + hash_val) + c; /* hash * 33 + c */
+ }
+ return *((int*)&hash_val);
+}
+
+//From http://www.cse.yorku.ca/~oz/hash.html
+//djb2 hash function
+int djb2_hash_len(unsigned char* str, int len) {
+ uint32_t hash_val = 5381;
+ for(int i=0; i<len; ++i) {
+ hash_val = ((hash_val << 5) + hash_val) + *str++; /* hash * 33 + c */
+ }
+ return *((int*)&hash_val);
+}
+
+float MoveTowards(float val, float target, float amount) {
+ float diff = val-target;
+ if(diff < 0.0f){
+ diff = -diff;
+ }
+ if(diff < amount) {
+ return target;
+ } else if(val > target) {
+ return val - amount;
+ } else {
+ return val + amount;
+ }
+}
diff --git a/Source/Internal/common.h b/Source/Internal/common.h
new file mode 100644
index 00000000..b005b882
--- /dev/null
+++ b/Source/Internal/common.h
@@ -0,0 +1,43 @@
+//-----------------------------------------------------------------------------
+// Name: common.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Utility/assert.h>
+
+#include <cstdio>
+#include <cassert>
+
+float MoveTowards(float val, float target, float amount);
+
+inline void VFormatString(char* buf, int buf_size, const char* fmt, va_list args) {
+ int val = vsnprintf(buf, buf_size, fmt, args);
+ if(val == -1 || val >= buf_size){
+ buf[buf_size-1] = '\0';
+ LOG_ASSERT(false); // Failed to format string
+ }
+}
+
+void FormatString(char* buf, int buf_size, const char* fmt, ...);
+
+int djb2_hash(unsigned char* str);
+int djb2_hash_len(unsigned char* str, int len);
diff --git a/Source/Internal/config.cpp b/Source/Internal/config.cpp
new file mode 100644
index 00000000..ed651389
--- /dev/null
+++ b/Source/Internal/config.cpp
@@ -0,0 +1,631 @@
+//-----------------------------------------------------------------------------
+// Name: config.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "config.h"
+
+#include <Internal/config.h>
+#include <Internal/path_utility.h>
+#include <Internal/filesystem.h>
+#include <Internal/datemodified.h>
+
+#include <Compat/fileio.h>
+#include <Logging/logdata.h>
+#include <Main/engine.h>
+
+#include <vector>
+#include <algorithm>
+#include <iostream>
+
+Config config;
+Config default_config;
+
+const vec3 kRedBloodColor(0.4f,0.0f,0.0f);
+const vec3 kGreenBloodColor(0.0f,0.4f,0.0f);
+const vec3 kCyanBloodColor(0.0f,0.4f,0.4f);
+const vec3 kBlackBloodColor(0.1f,0.1f,0.1f);
+
+extern bool g_no_reflection_capture;
+
+// Utility function to trim whitespace off the ends of a string
+inline std::string trim(std::string source) {
+ std::string result = source.erase(source.find_last_not_of(" \t\r") + 1);
+ return result.erase(0, result.find_first_not_of(" \t\r"));
+}
+
+Config::Config(): has_changed_since_save(false) {
+ commonResolutions.push_back(Resolution( 1920, 1080 ));
+ commonResolutions.push_back(Resolution( 2560, 1440 ));
+ commonResolutions.push_back(Resolution( 1366, 768 ));
+ commonResolutions.push_back(Resolution( 3840, 2160 ));
+ commonResolutions.push_back(Resolution( 1680, 1050 ));
+ commonResolutions.push_back(Resolution( 1600, 900 ));
+ commonResolutions.push_back(Resolution( 1440, 900 ));
+ commonResolutions.push_back(Resolution( 1920, 1200 ));
+ commonResolutions.push_back(Resolution( 2560, 1080 ));
+ commonResolutions.push_back(Resolution( 1360, 768 ));
+ commonResolutions.push_back(Resolution( 1280, 800 ));
+ commonResolutions.push_back(Resolution( 1536, 864 ));
+ commonResolutions.push_back(Resolution( 2736, 1824 ));
+ commonResolutions.push_back(Resolution( 1280, 720 ));
+}
+
+bool Config::Load(const std::string& filename, bool just_filling_blanks, bool shadow_variables) {
+
+ std::string configFile = filename;//pathUtility::localPathToGlobal(filename);
+ std::ifstream file;
+ my_ifstream_open(file, configFile.c_str(), std::ios_base::in);
+
+ if(!file.is_open())
+ return false;
+
+ Load( file, just_filling_blanks, shadow_variables );
+
+ file.close();
+
+ if( just_filling_blanks == false && shadow_variables == false ) {
+ date_modified_ = GetDateModifiedInt64(configFile.c_str());
+ primary_path_ = configFile;
+ }
+
+ return true;
+}
+
+bool Config::Load( std::istream& stream, bool just_filling_blanks, bool shadow_variables )
+{
+ std::string line;
+ std::string comment = "//";
+ std::string delimiter = ":";
+
+ Map* _map;
+
+ if( shadow_variables )
+ {
+ _map = &shadow_map_;
+ LOGI << "Shadow loading" << std::endl;
+ }
+ else
+ {
+ _map = &map_;
+ }
+
+ int count = 0;
+ while(stream.good())
+ {
+ getline(stream, line);
+
+ // Remove any comments
+ size_t commIdx = line.find(comment);
+ if(commIdx != std::string::npos)
+ line = line.substr(0, commIdx);
+
+ size_t delimIdx = line.find(delimiter);
+ if(delimIdx == std::string::npos)
+ continue;
+
+ std::string key = trim(line.substr(0, delimIdx));
+ std::string value = trim(line.substr(delimIdx + 1));
+
+ ConfigVal val;
+ val.data = value;
+ val.order = count;
+ ++count;
+
+ if(!key.empty()){
+ //We compare with global map on purpose here, because that is the "true" state.
+ if(!just_filling_blanks || map_.find(key) == map_.end()){
+ (*_map)[key] = val;
+ }
+ }
+ }
+ return true;
+}
+
+static void AddResolution(std::vector<Resolution> &resolutions, int w, int h){
+ // Check if resolution is already there
+ int num_res = resolutions.size();
+ for(int i=0; i<num_res; ++i){
+ if(resolutions[i].w == w && resolutions[i].h == h){
+ return;
+ }
+ }
+
+ resolutions.push_back(Resolution(w,h));
+}
+
+Resolution::Resolution( int _w, int _h ) : w(_w), h(_h) {
+}
+
+int Config::GetMonitorCount() {
+ return SDL_GetNumVideoDisplays();
+}
+
+std::vector<Resolution> Config::GetPossibleResolutions() {
+
+ int targetMonitor = GetRef("target_monitor").toNumber<int>();
+ int displayModeCount = SDL_GetNumDisplayModes(targetMonitor);
+
+ // Populate resolution list
+ std::vector<Resolution> resolutions;
+ resolutions.reserve(displayModeCount);
+
+ SDL_DisplayMode desktopDisplayMode;
+ SDL_GetDesktopDisplayMode(targetMonitor, &desktopDisplayMode);
+
+ float desktopAspect = desktopDisplayMode.w / (float)desktopDisplayMode.h;
+
+ for (int i = 0; i < displayModeCount; ++i) {
+ SDL_DisplayMode mode;
+ SDL_GetDisplayMode(targetMonitor, i, &mode);
+
+ float resolutionAspect = mode.w / (float)mode.h;
+
+ if ((mode.h <= desktopDisplayMode.h && mode.w <= desktopDisplayMode.w && std::fabs(resolutionAspect - desktopAspect) < 0.01f)
+ || static_cast<FullscreenMode::Mode>(config["fullscreen"].toNumber<int>()) == FullscreenMode::kFullscreen) {
+ bool resolutionFound = false;
+ for (size_t i = 0; i < commonResolutions.size(); ++i) {
+ if (commonResolutions[i].w == mode.w && commonResolutions[i].h == mode.h) {
+ resolutionFound = true;
+ break;
+ }
+ }
+
+ if (!resolutionFound)
+ continue;
+
+ AddResolution(resolutions, mode.w, mode.h);
+ }
+ }
+
+ // Add at least this many resolutions to the list in case some user has some weird monitor
+ const static int MIN_RESOLUTION_COUNT = 5;
+ for (int i = resolutions.size(); resolutions.size() < MIN_RESOLUTION_COUNT && i < displayModeCount; ++i) {
+ SDL_DisplayMode mode;
+ SDL_GetDisplayMode(targetMonitor, i - resolutions.size(), &mode);
+
+ if (mode.h <= desktopDisplayMode.h && mode.w <= desktopDisplayMode.w) {
+ AddResolution(resolutions, mode.w, mode.h);
+ }
+ }
+
+ // Sort resolutions
+ std::sort(resolutions.begin(), resolutions.end(), ResolutionCompare());
+ return resolutions;
+}
+
+Config * Config::GetPresets(){
+ static Config global_settings[4];
+ if(global_settings[0].map_.empty()){
+ global_settings[0].GetRef("blood") = 1;
+ global_settings[0].GetRef("texture_reduce") = 1;
+ global_settings[0].GetRef("multisample") = 1;
+ global_settings[0].GetRef("anisotropy") = 2;
+ global_settings[0].GetRef("simple_fog") = 1;
+ global_settings[0].GetRef("depth_of_field") = 0;
+ global_settings[0].GetRef("depth_of_field_reduced") = 1;
+ global_settings[0].GetRef("detail_objects") = 0;
+ global_settings[0].GetRef("detail_object_decals") = 0;
+ global_settings[0].GetRef("detail_object_lowres") = 1;
+ global_settings[0].GetRef("detail_object_disable_shadows") = 1;
+ global_settings[0].GetRef("detail_objects_reduced") = 1;
+ global_settings[0].GetRef("simple_shadows") = 1;
+ global_settings[0].GetRef("tet_mesh_lighting") = 0;
+ global_settings[0].GetRef("motion_blur_amount") = 0;
+ global_settings[0].GetRef("no_reflection_capture") = 1;
+ global_settings[0].GetRef("particle_field") = 0;
+ global_settings[0].GetRef("particle_field_simple") = 1;
+ global_settings[0].GetRef("simple_water") = 1;
+
+ global_settings[1].GetRef("blood") = 2;
+ global_settings[1].GetRef("texture_reduce") = 0;
+ global_settings[1].GetRef("multisample") = 1;
+ global_settings[1].GetRef("anisotropy") = 4;
+ global_settings[1].GetRef("simple_fog") = 1;
+ global_settings[1].GetRef("depth_of_field") = 1;
+ global_settings[1].GetRef("depth_of_field_reduced") = 1;
+ global_settings[1].GetRef("detail_objects") = 1;
+ global_settings[1].GetRef("detail_object_decals") = 0;
+ global_settings[1].GetRef("detail_object_lowres") = 1;
+ global_settings[1].GetRef("detail_object_disable_shadows") = 1;
+ global_settings[1].GetRef("detail_objects_reduced") = 1;
+ global_settings[1].GetRef("simple_shadows") = 1;
+ global_settings[1].GetRef("tet_mesh_lighting") = 0;
+ global_settings[1].GetRef("motion_blur_amount") = 0;
+ global_settings[1].GetRef("no_reflection_capture") = 0;
+ global_settings[1].GetRef("particle_field") = 1;
+ global_settings[1].GetRef("particle_field_simple") = 1;
+ global_settings[1].GetRef("simple_water") = 0;
+
+ global_settings[2].GetRef("blood") = 2;
+ global_settings[2].GetRef("texture_reduce") = 0;
+ global_settings[2].GetRef("multisample") = 1;
+ global_settings[2].GetRef("anisotropy") = 4;
+ global_settings[2].GetRef("simple_fog") = 1;
+ global_settings[2].GetRef("depth_of_field") = 1;
+ global_settings[2].GetRef("depth_of_field_reduced") = 0;
+ global_settings[2].GetRef("detail_objects") = 1;
+ global_settings[2].GetRef("detail_object_decals") = 0;
+ global_settings[2].GetRef("detail_object_lowres") = 1;
+ global_settings[2].GetRef("detail_object_disable_shadows") = 1;
+ global_settings[2].GetRef("detail_objects_reduced") = 0;
+ global_settings[2].GetRef("simple_shadows") = 1;
+ global_settings[2].GetRef("tet_mesh_lighting") = 0;
+ global_settings[2].GetRef("motion_blur_amount") = 0;
+ global_settings[2].GetRef("no_reflection_capture") = 0;
+ global_settings[2].GetRef("particle_field") = 1;
+ global_settings[2].GetRef("particle_field_simple") = 0;
+ global_settings[2].GetRef("simple_water") = 0;
+
+ global_settings[3].GetRef("blood") = 2;
+ global_settings[3].GetRef("texture_reduce") = 0;
+ global_settings[3].GetRef("multisample") = 4;
+ global_settings[3].GetRef("anisotropy") = 4;
+ global_settings[3].GetRef("simple_fog") = 0;
+ global_settings[3].GetRef("depth_of_field") = 1;
+ global_settings[3].GetRef("depth_of_field_reduced") = 0;
+ global_settings[3].GetRef("detail_objects") = 1;
+ global_settings[3].GetRef("detail_object_lowres") = 0;
+ global_settings[3].GetRef("detail_object_decals") = 0;
+ global_settings[3].GetRef("detail_object_disable_shadows") = 0;
+ global_settings[3].GetRef("detail_objects_reduced") = 0;
+ global_settings[3].GetRef("simple_shadows") = 0;
+ global_settings[3].GetRef("tet_mesh_lighting") = 0;
+ global_settings[3].GetRef("motion_blur_amount") = 0;
+ global_settings[3].GetRef("no_reflection_capture") = 0;
+ global_settings[3].GetRef("particle_field") = 1;
+ global_settings[3].GetRef("particle_field_simple") = 0;
+ global_settings[3].GetRef("simple_water") = 0;
+ }
+ return global_settings;
+}
+
+void Config::SetSettingsToPreset(std::string preset_name )
+{
+ int index = 0;
+ if(preset_name == "Low"){
+ index = 0;
+ }else if(preset_name == "Reduced"){
+ index = 1;
+ }else if(preset_name == "Medium"){
+ index = 2;
+ }else if(preset_name == "High"){
+ index = 3;
+ }else{
+ return;
+ }
+ Config::Map& map = GetPresets()[index].map_;
+ for(Config::Map::iterator iter = map.begin(); iter != map.end(); ++iter ){
+ config.GetRef(iter->first) = iter->second.data;
+ }
+}
+
+std::string Config::GetSettingsPreset(){
+ int preset = 0;
+
+ // Determine if we match any of the global_settings presets
+ for(int i=0; i<4; ++i){
+ Config::Map& map = GetPresets()[i].map_;
+ for(Config::Map::iterator iter = map.begin(); iter != map.end(); ++iter ){
+ if(config.GetRef(iter->first) != iter->second.data){
+ preset = i + 1;
+ break;
+ }
+ }
+ if(preset == i){
+ break;
+ }
+ }
+ if(preset == 4){
+ preset = 0;
+ } else {
+ ++preset;
+ }
+ switch(preset) {
+ case 0:
+ return "Custom";
+ case 1:
+ return "Low";
+ case 2:
+ return "Reduced";
+ case 3:
+ return "Medium";
+ case 4:
+ return "High";
+ default:
+ return "Error";
+ }
+}
+
+std::vector<std::string> Config::GetSettingsPresets() {
+ std::vector<std::string> presets;
+ //presets.push_back("Custom");
+ presets.push_back("Low");
+ presets.push_back("Reduced");
+ presets.push_back("Medium");
+ presets.push_back("High");
+ return presets;
+}
+
+static const char* difficulty_presets[] = {
+ "Casual",
+ "Hardcore",
+ "Expert",
+ NULL
+};
+
+static const float difficulty_preset_values[] = {
+ 0.0f, 0.8f,
+ 0.5f, 1.0f,
+ 1.0f, 1.0f
+};
+
+static const bool difficulty_tutorials_preset_values[] = {
+ true,
+ true,
+ false
+};
+
+static const bool difficulty_ledge_grab_preset_values[] = {
+ true,
+ true,
+ false
+};
+
+std::vector<std::string> Config::GetDifficultyPresets() {
+ std::vector<std::string> presets;
+ //presets.push_back("Custom");
+ for( int i = 0; difficulty_presets[i] != NULL; i++ ) {
+ presets.push_back(difficulty_presets[i]);
+ }
+ return presets;
+}
+
+std::string Config::GetDifficultyPreset() {
+ for(int i=0; difficulty_presets[i] != NULL; ++i){
+ if(GetRef("game_difficulty").toNumber<float>() == difficulty_preset_values[i*2] &&
+ GetRef("global_time_scale_mult").toNumber<float>() == difficulty_preset_values[i*2+1] &&
+ GetRef("tutorial").toBool() == difficulty_tutorials_preset_values[i] &&
+ GetRef("auto_ledge_grab").toBool() == difficulty_ledge_grab_preset_values[i])
+ {
+ return difficulty_presets[i];
+ }
+ }
+ return "Custom";
+}
+
+//Used to validate progress, rounds down in difficulty steps
+std::string Config::GetClosestDifficulty() {
+ std::string difficulty = "Custom";
+ for(int i=0; difficulty_presets[i] != NULL; ++i){
+ if( GetRef("game_difficulty").toNumber<float>() >= difficulty_preset_values[i*2] &&
+ GetRef("global_time_scale_mult").toNumber<float>() >= difficulty_preset_values[i*2+1]) {
+ difficulty = difficulty_presets[i];
+ } else {
+ break;
+ }
+ }
+ return difficulty;
+}
+
+void Config::SetDifficultyPreset( std::string name ) {
+ for(int i = 0; difficulty_presets[i] != NULL; i++ ) {
+ if( strmtch(difficulty_presets[i],name.c_str())){
+ GetRef("game_difficulty") = difficulty_preset_values[i*2];
+ GetRef("global_time_scale_mult") = difficulty_preset_values[i*2+1];
+ GetRef("tutorials") = difficulty_tutorials_preset_values[i];
+ GetRef("auto_ledge_grab") = difficulty_ledge_grab_preset_values[i];
+ }
+ }
+}
+
+void Config::ReloadDynamicSettings(){
+ Engine::Instance()->SetGameSpeed(config["global_time_scale_mult"].toNumber<float>(), true);
+ Engine::Instance()->GetSound()->SetMusicVolume(config.GetRef("music_volume").toNumber<float>());
+ Engine::Instance()->GetSound()->SetMasterVolume(config.GetRef("master_volume").toNumber<float>());
+ Graphics::Instance()->config_.motion_blur_amount_ = (float)config["motion_blur_amount"].toNumber<int>();
+ Input::Instance()->SetMouseSensitivity(config["mouse_sensitivity"].toNumber<float>());
+ Input::Instance()->UpdateGamepadLookSensitivity();
+ Input::Instance()->UpdateGamepadDeadzone();
+}
+
+void Config::ReloadStaticSettings(){
+ Input::Instance()->SetFromConfig(config);
+ Graphics::Instance()->SetFromConfig(config, true);
+ Graphics::Instance()->SetTargetMonitor(config["target_monitor"].toNumber<int>());
+ Graphics::Instance()->SetResolution(config["screenwidth"].toNumber<int>(), config["screenheight"].toNumber<int>(), false);
+ Graphics::Instance()->SetFSAA(config["multisample"].toNumber<int>());
+ if(config["anisotropy"].toNumber<int>() != Graphics::Instance()->config_.anisotropy()){
+ Graphics::Instance()->SetAnisotropy((float)config["anisotropy"].toNumber<int>());
+ Textures::Instance()->ApplyAnisotropy();
+ }
+ Graphics::Instance()->SetSimpleFog(config["simple_fog"].toBool());
+ Graphics::Instance()->SetDepthOfField(config["depth_of_field"].toBool());
+ Graphics::Instance()->SetDepthOfFieldReduced(config["depth_of_field_reduced"].toBool());
+ Graphics::Instance()->SetDetailObjects(config["detail_objects"].toBool());
+ Graphics::Instance()->SetDetailObjectDecals(config["detail_object_decals"].toBool());
+ Graphics::Instance()->SetDetailObjectLowres(config["detail_object_lowres"].toBool());
+ Graphics::Instance()->SetDetailObjectShadows(!config["detail_object_disable_shadows"].toBool());
+ Graphics::Instance()->SetDetailObjectsReduced(config["detail_objects_reduced"].toBool());
+ Graphics::Instance()->setAttribEnvObjInstancing(config["attrib_envobj_instancing"].toBool());
+ //Makes the game crash
+ //the_scenegraph->light_probe_collection.light_volume_enabled = config["light_volume_lighting"].toBool();
+ Graphics::Instance()->SetFullscreen(static_cast<FullscreenMode::Mode>(config["fullscreen"].toNumber<int>()));
+ Graphics::Instance()->SetVsync(config["vsync"].toBool());
+ Graphics::Instance()->config_.SetBlood(config.GetRef("blood").toNumber<int>());
+ Graphics::Instance()->config_.SetBloodColor(GraphicsConfig::BloodColorFromString(config["blood_color"].str()));
+ Graphics::Instance()->config_.SetSplitScreen(config["split_screen"].toNumber<bool>());
+ Graphics::Instance()->SetSeamlessCubemaps(config["seamless_cubemaps"].toNumber<bool>());
+ g_no_reflection_capture = config["no_reflection_capture"].toNumber<bool>();
+
+ Input::Instance()->SetInvertXMouseLook(config["invert_x_mouse_look"].toNumber<bool>());
+ Input::Instance()->SetInvertYMouseLook(config["invert_y_mouse_look"].toNumber<bool>());
+ Input::Instance()->UseRawInput(config["use_raw_input"].toNumber<bool>());
+ int num_cameras = ActiveCameras::NumCameras();
+ int curr_id = ActiveCameras::GetID();
+ for(int i=0; i<num_cameras; ++i){
+ ActiveCameras::Set(i);
+ ActiveCameras::Get()->SetAutoCamera(config["auto_camera"].toNumber<bool>());
+ }
+ ActiveCameras::Set(curr_id);
+
+ SceneGraph* scene_graph = Engine::Instance()->GetSceneGraph();
+ if(scene_graph)
+ scene_graph->PreloadShaders();
+}
+
+class ConfigValCompare {
+public:
+ bool operator()(const std::pair<std::string, ConfigVal> &a,
+ const std::pair<std::string, ConfigVal> &b)
+ {
+ return a.second.order < b.second.order;
+ }
+};
+
+bool Config::Save(const std::string& filename) {
+ LOGI << "Saving config to: " << filename << std::endl;
+ CreateParentDirs(filename.c_str());
+ std::ofstream file;
+ my_ofstream_open(file, filename);
+
+ if(!file.is_open())
+ return false;
+
+ std::vector<std::pair<std::string, ConfigVal> > vec(map_.begin(), map_.end());
+ std::sort(vec.begin(), vec.end(), ConfigValCompare());
+
+ for(unsigned i=0; i<vec.size(); ++i)
+ {
+ file << vec[i].first << ": " << vec[i].second.data.str() << "\n";
+ }
+
+ file.close();
+
+ has_changed_since_save = false;
+
+ if( AreSame(filename.c_str(), primary_path_.c_str()) ) {
+ date_modified_ = GetDateModifiedInt64(primary_path_.c_str());
+ }
+
+ return true;
+}
+
+const StringVariant& Config::operator[](const std::string& keyName) const {
+ std::map<std::string, ConfigVal>::const_iterator iter = shadow_map_.find(keyName);
+
+ if(iter != shadow_map_.end())
+ {
+ return iter->second.data;
+ }
+
+ iter = map_.find(keyName);
+ if(iter != map_.end())
+ {
+ return iter->second.data;
+ }
+
+ static StringVariant empty;
+ return empty;
+}
+
+bool Config::HasKey( const char* key )
+{
+ std::string s = std::string(key);
+ return HasKey(s);
+}
+
+bool Config::HasKey( std::string& key )
+{
+ std::map<std::string, ConfigVal>::const_iterator iter = shadow_map_.find(key);
+
+ if(iter != shadow_map_.end())
+ {
+ return true;
+ }
+
+ iter = map_.find(key);
+
+ if(iter != map_.end())
+ {
+ return true;
+ }
+
+ return false;
+}
+
+void Config::RemoveConfig( std::string key ) {
+ std::map<std::string, ConfigVal>::iterator iter = shadow_map_.find(key);
+
+ if(iter != shadow_map_.end())
+ {
+ shadow_map_.erase(iter);
+ }
+
+ iter = map_.find(key);
+
+ if(iter != map_.end())
+ {
+ map_.erase(iter);
+ }
+}
+
+bool Config::HasChangedSinceLastSave()
+{
+ return has_changed_since_save;
+}
+
+bool Config::operator!=( const Config& other ) const
+{
+ return !(*this == other);
+}
+
+bool Config::operator==( const Config& other ) const
+{
+ return map_ == other.map_;
+}
+
+bool Config::PrimarySourceModified() {
+ if( primary_path_.empty() == false ) {
+ return GetDateModifiedInt64(primary_path_.c_str()) != date_modified_;
+ } else {
+ return false;
+ }
+}
+
+std::vector<Resolution>& Config::GetCommonResolutions()
+{
+ return commonResolutions;
+}
+
+bool ConfigVal::operator==( const ConfigVal &other ) const
+{
+ return data == other.data;
+}
+
+bool StringVariant::operator==( const StringVariant &other ) const
+{
+ return data == other.data;
+}
+
+bool StringVariant::operator!=( const StringVariant &other ) const
+{
+ return !((*this) == other);
+}
diff --git a/Source/Internal/config.h b/Source/Internal/config.h
new file mode 100644
index 00000000..5e8cd4d9
--- /dev/null
+++ b/Source/Internal/config.h
@@ -0,0 +1,190 @@
+//-----------------------------------------------------------------------------
+// Name: config.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Graphics/textures.h>
+#include <Graphics/camera.h>
+
+#include <Internal/datemodified.h>
+#include <Math/vec2math.h>
+
+#include <SDL_keycode.h>
+
+#include <map>
+#include <string>
+#include <sstream>
+#include <iostream>
+
+/**
+* The StringVariant class is a simple string-based variant implementation that allows
+* the user to easily convert between simple numeric/string types.
+*/
+class StringVariant
+{
+ std::string data;
+public:
+ StringVariant() : data() {}
+
+ template<typename ValueType> StringVariant(ValueType val) {
+ std::ostringstream stream;
+ stream << val;
+ data = stream.str();
+ }
+
+ template<typename ValueType> StringVariant& operator=(const ValueType val) {
+ std::ostringstream stream;
+ stream << val;
+ data.assign(stream.str());
+
+ return *this;
+ }
+
+ template<typename NumberType> NumberType toNumber() const {
+ NumberType result = 0;
+ std::istringstream stream(data);
+ if(stream >> result) {
+ return result;
+ } else if(data == "yes" || data == "true") {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
+ bool toBool() const {
+ return toNumber<int>() == 1;
+ }
+
+ std::string str() const {
+ return data;
+ }
+
+ bool operator==(const StringVariant &other) const;
+ bool operator!=(const StringVariant &other) const;
+};
+
+struct ConfigVal {
+ StringVariant data;
+ int order;
+ bool operator==(const ConfigVal &other) const;
+};
+
+struct Resolution {
+ Resolution(int w, int h);
+ int w,h;
+};
+
+class ResolutionCompare {
+public:
+ bool operator()(const Resolution &a, const Resolution &b) {
+ return (a.w*a.h) > (b.w*b.h);
+ }
+};
+
+/**
+* The Config class can be used to load simple key/value pairs from a file.
+*
+* @note An example of syntax:
+* // An example of a comment
+* username: Bob
+* gender: male
+* hair-color: black // inline comments are also allowed
+* level: 42
+*
+* @note An example of usage:
+* Config config;
+* config.load("myFile.txt");
+*
+* std::string username = config["username"].str();
+* int level = config["level"].toNumber<int>();
+*/
+
+class Config {
+public:
+ // Keep track of path and date modified so we can live-update
+ typedef std::map<std::string, ConfigVal> Map;
+ Map map_; // The actual map used to store key/value pairs
+ Map shadow_map_; // Runtime hidden values.
+
+ //Boolean keeping track on if we believe this config has changed.
+ bool has_changed_since_save;
+
+ Config();
+
+ // Loads key/value pairs from a file
+ // Returns whether or not this operation was successful
+ bool Load(const std::string& filename, bool just_filling_blanks = false, bool shadow_variables = false );
+ bool Load(std::istream& data, bool just_filling_blanks = false, bool shadow_variables = false );
+ bool Save(const std::string& filename);
+ int GetMonitorCount();
+ std::vector<Resolution> GetPossibleResolutions();
+ void SetSettingsToPreset(std::string preset_name );
+ std::string GetSettingsPreset();
+ std::vector<std::string> GetSettingsPresets();
+ std::vector<std::string> GetDifficultyPresets();
+ std::string GetDifficultyPreset();
+ std::string GetClosestDifficulty();
+ void SetDifficultyPreset( std::string name );
+
+ void ReloadStaticSettings();
+ void ReloadDynamicSettings();
+
+ // Use the [] operator to access values just like a map container
+ // If a key does not exist, will return an empty StringVariant
+ const StringVariant& operator[](const std::string& keyName) const;
+
+ bool HasKey( const char* key );
+ bool HasKey( std::string& key );
+
+ void RemoveConfig( std::string index );
+
+ bool HasChangedSinceLastSave();
+
+ template <typename T>
+ StringVariant& GetRef( T key )
+ {
+ has_changed_since_save = true;
+ return map_[key].data;
+ }
+
+ // Use the [] operator to get/set values just like a map container
+ //StringVariant& operator[](const std::string& keyName){ return map_[keyName].data; }
+
+ bool operator==(const Config& other) const;
+ bool operator!=(const Config& other) const;
+
+ static Config* GetPresets();
+
+ bool PrimarySourceModified();
+ inline std::string GetPrimaryPath() { return primary_path_; }
+
+ std::vector<Resolution>& GetCommonResolutions();
+private:
+ std::string primary_path_;
+ int64_t date_modified_;
+
+ std::vector<Resolution> commonResolutions;
+};
+
+extern Config config;
+
diff --git a/Source/Internal/crashreport.cpp b/Source/Internal/crashreport.cpp
new file mode 100644
index 00000000..60a09519
--- /dev/null
+++ b/Source/Internal/crashreport.cpp
@@ -0,0 +1,499 @@
+//-----------------------------------------------------------------------------
+// Name: crashreport.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "crashreport.h"
+
+#include <Internal/filesystem.h>
+#include <Internal/common.h>
+#include <Internal/error.h>
+
+#include <Utility/strings.h>
+#include <Version/version.h>
+#include <Logging/logdata.h>
+#include <Compat/fileio.h>
+#include <Scripting/angelscript/ascrashdump.h>
+
+void DumpScenegraphState();
+
+#if PLATFORM_LINUX
+#include <stdio.h>
+#include <execinfo.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#endif
+
+#if PLATFORM_MACOSX
+#include <stdio.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#endif
+
+#if PLATFORM_WINDOWS
+#include <tchar.h>
+#include <strsafe.h>
+#endif
+
+#if BREAKPAD && PLATFORM_MACOSX
+#include "client/mac/handler/exception_handler.h"
+#endif
+#if BREAKPAD && PLATFORM_LINUX
+#include "client/linux/handler/exception_handler.h"
+#endif
+#if BREAKPAD && PLATFORM_WINDOWS
+#include "client/windows/handler/exception_handler.h"
+#endif
+
+#include <sstream>
+
+
+/*
+ * The following is the old windows minidump routine, currently replaced with breakpad
+#if PLATFORM_WINDOWS
+#include <windows.h>
+#include <dbghelp.h>
+#include <shellapi.h>
+#include <shlobj.h>
+#include <tchar.h>
+#include <strsafe.h>
+#include <string>
+#include "tinyxml.h"
+#include "Version/version.h"
+#include "Internal/filesystem.h"
+#include "Logging/logdata.h"
+
+int GenerateDump(EXCEPTION_POINTERS* pExceptionPointers) {
+ // Get temp directory path
+ TCHAR szPath[MAX_PATH];
+ DWORD dwBufferSize = MAX_PATH;
+ GetTempPath( dwBufferSize, szPath );
+ // Create a folder in the temp directory with the name of the application
+ TCHAR szFileName[MAX_PATH];
+ TCHAR szFileNameDir[MAX_PATH];
+ LPTSTR szAppName = _T("Overgrowth");
+ StringCchPrintf( szFileName, MAX_PATH, _T("%s%s"), szPath, szAppName );
+ StringCchPrintf( szFileNameDir, MAX_PATH, _T("%s%s"), szPath, szAppName );
+ CreateDirectory( szFileName, NULL );
+ // Create an informative file name including the application name and date of error
+ SYSTEMTIME stLocalTime;
+ GetLocalTime( &stLocalTime );
+ StringCchPrintf( szFileName, MAX_PATH, _T("%s%s\\%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp"),
+ szPath, szAppName, TEXT(GetShortBuildTag().c_str()),
+ stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay,
+ stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond,
+ GetCurrentProcessId(), GetCurrentThreadId());
+ // Open the file and write the minidump to it
+ HANDLE hDumpFile = CreateFile(szFileName, GENERIC_READ|GENERIC_WRITE,
+ FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
+ MINIDUMP_EXCEPTION_INFORMATION ExpParam;
+ ExpParam.ThreadId = GetCurrentThreadId();
+ ExpParam.ExceptionPointers = pExceptionPointers;
+ ExpParam.ClientPointers = TRUE;
+ BOOL bMiniDumpSuccessful = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),
+ hDumpFile, MiniDumpWithDataSegs, &ExpParam, NULL, NULL);
+ // Copy logfile.txt to same location as minidump
+ LogSystem::Flush();
+ StringCchPrintf(szFileName, MAX_PATH, _T("%s%s\\logfile.txt"), szPath, szAppName);
+ TCHAR szLogFileName[MAX_PATH];
+ StringCchPrintf(szLogFileName, MAX_PATH, _T("%slogfile.txt"), GetWritePath(CoreGameModID).c_str());
+ CopyFile(szLogFileName, szFileName, false);
+ // Display error message with instructions
+ MessageBox(NULL,
+ "A fatal error occured! Please send the most recent .dmp and logfile.txt files to bugs@wolfire.com, along with the what version of the game you were playing and a description of what exactly was happening in the game when this error occured.",
+ "Fatal Error",
+ MB_OK |
+ MB_ICONHAND |
+ MB_TOPMOST |
+ MB_SETFOREGROUND);
+ // In Windows Explorer, open the folder containing the .dmp
+ std::string nav_command = "explorer "+std::string(szFileNameDir);
+ system(nav_command.c_str());
+ return EXCEPTION_EXECUTE_HANDLER;
+}
+#elif defined(PLATFORM_LINUX) && defined(__GNUG__) //We use GNUG for the backtrace
+
+#include <stdio.h>
+#include <execinfo.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <Version/version.h>
+#include "unix_compat.h"
+
+#include "Internal/filesystem.h"
+#include "Logging/logdata.h"
+
+
+const static bool do_coredump_on_crash = false;
+
+//Coredump routine crashes on linux.
+//General signal handler for anything that usually cause a coredump
+static void segfault_coredump( int sig )
+{
+ char time_string[256];
+ char final_folder[256];
+ time_t t = time(NULL);
+ struct tm *l_t = localtime(&t);
+
+ //Reset meaning for later.
+ signal( sig, SIG_DFL );
+
+ char full_message[2048];
+ snprintf( full_message, 2048, "A fatal error occured!" );
+
+ LOGF << "Changing directory for core dump" << std::endl; //Change dir for the core dump
+ chdir(GetWritePath(CoreGameModID).c_str());
+
+ strftime( time_string, 256, "%y-%m-%d_%H-%M-%S_%z", l_t );
+ snprintf( final_folder, 256, "crash_%s_%s_%s", GetBuildVersion(), GetBuildIDString(), time_string );
+ mkdir( final_folder, S_IRWXU );
+ chdir( final_folder );
+
+ //Get a backtrace
+ {
+ void *array[10];
+ size_t size;
+
+ // get void*'s for all entries on the stack
+ size = backtrace(array, 10);
+
+ int backtrace_file = creat( "backtrace", S_IRUSR | S_IWUSR );
+ // print out all the frames to stderr
+ LOGF << "Error: signal " << sig << std::endl;
+ backtrace_symbols_fd(array, size, backtrace_file);
+ }
+
+ snprintf( full_message, 2048,
+ "A fatal error occured! Please send the most recent crash folder \"%s%s\" to bugs@wolfire.com, along with a description of what exactly was happening in the game when this error occured.",
+ GetWritePath(CoreGameModID).c_str(),
+ final_folder );
+
+
+ LOGF << "Game crashed, flushing the log systems." << std::endl;
+ LogSystem::Flush();
+
+ MessageBox(NULL,
+ full_message,
+ "Fatal Error",
+ MB_OK |
+ MB_ICONHAND |
+ MB_TOPMOST |
+ MB_SETFOREGROUND);
+
+
+ //We resend ourselves the signal so the default handler will dump the core. and get the correct exit code.
+ kill(getpid(),sig);
+}
+
+int RunWithCrashReport( int argc, char* argv[], int (*func) (int, char*[])) {
+ #if PLATFORM_WINDOWS && !defined(_DEBUG) && defined(_DEPLOY)
+
+ __try
+ {
+ return func(argc, argv);
+ }
+ __except(GenerateDump(GetExceptionInformation()))
+ {
+ LogSystem::Flush();
+ return 1;
+ }
+}
+*/
+
+
+void GetCrashFolder( char* destpath, size_t maxsize )
+{
+ char time_string[256];
+#if PLATFORM_LINUX || PLATFORM_MACOSX
+ time_t t = time(NULL);
+ struct tm *l_t = localtime(&t);
+ strftime( time_string, 256, "%y-%m-%d_%H-%M-%S", l_t );
+#elif PLATFORM_WINDOWS
+ SYSTEMTIME stLocalTime;
+ GetLocalTime( &stLocalTime );
+ FormatString( time_string, 256, "%04d-%02d-%02d_%02d-%02d-%02d",
+ stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay,
+ stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond );
+#endif
+ FormatString( destpath, maxsize, "crash_%s_%s_%s", GetBuildVersion(), GetBuildIDString(), time_string );
+}
+
+#if BREAKPAD
+#if PLATFORM_LINUX
+static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded)
+{
+ char tempPath[kPathSize];
+ char sourcePath[kPathSize];
+ char destPath[kPathSize];
+
+ char crashFolder[kPathSize];
+
+ LOGF << "Game crashed, flushing the log systems. DumpFile: " << descriptor.path() << std::endl;
+ DumpScenegraphState();
+ DumpAngelscriptStates();
+ LogSystem::Flush();
+
+ GetCrashFolder( crashFolder, kPathSize );
+
+ //Create crash folder.
+ FormatString( destPath, kPathSize, "%s/%s", GetWritePath(CoreGameModID).c_str(), crashFolder );
+ mkdir( destPath, S_IRUSR | S_IWUSR | S_IXUSR );
+
+ //Get the name of the dump.
+ FormatString( tempPath, kPathSize, "%s", descriptor.path() );
+ char* last_component = tempPath;
+ for( unsigned i = 0; i < strlen( tempPath ) - 1; i++ )
+ {
+ if( tempPath[i] == '/' )
+ last_component = &tempPath[i+1];
+ }
+
+ //Copy the dump into the crash folder.
+ FormatString( sourcePath, kPathSize, "%s", descriptor.path() );
+ FormatString( destPath, kPathSize, "%s/%s/%s", GetWritePath(CoreGameModID).c_str(), crashFolder, last_component );
+
+ LOGF << sourcePath << " " << destPath << std::endl;
+ LogSystem::Flush();
+ copyfile( sourcePath, destPath );
+
+ GetLogfilePath(sourcePath,kPathSize);
+ FormatString( destPath, kPathSize, "%s/%s/logfile.txt", GetWritePath(CoreGameModID).c_str(), crashFolder);
+ copyfile( sourcePath, destPath );
+
+ GetHWReportPath(sourcePath, kPathSize);
+ FormatString( destPath, kPathSize, "%s/%s/hwreport.txt", GetWritePath(CoreGameModID).c_str(), crashFolder);
+ copyfile( sourcePath, destPath );
+
+ GetScenGraphDumpPath(sourcePath, kPathSize);
+ FormatString( destPath, kPathSize, "%s/%s/scene_dump.txt", GetWritePath(CoreGameModID).c_str(), crashFolder);
+ copyfile( sourcePath, destPath );
+
+ GetASDumpPath(sourcePath, kPathSize);
+ FormatString( destPath, kPathSize, "%s/%s/as_dump.txt", GetWritePath(CoreGameModID).c_str(), crashFolder);
+ copyfile( sourcePath, destPath );
+
+ FormatString( destPath, kPathSize, "%s/%s/version_info.txt", GetWritePath(CoreGameModID).c_str(), crashFolder);
+ FILE* f = my_fopen(destPath, "a");
+
+ if( f )
+ {
+ fwrite(GetBuildIDString(), strlen(GetBuildIDString()), 1,f );
+ fwrite("\n", 1, 1, f );
+ fwrite(GetPlatform(), strlen(GetPlatform()), 1,f );
+ fwrite("\n", 1, 1, f );
+ fwrite(GetArch(), strlen(GetArch()), 1,f );
+ fwrite("\n", 1, 1, f );
+ fwrite(GetBuildVersion(), strlen(GetBuildVersion()), 1, f );
+ fwrite("\n", 1, 1, f );
+ fwrite(GetBuildTimestamp(), strlen(GetBuildTimestamp()), 1, f );
+ fwrite("\n", 1, 1, f );
+
+ fclose(f);
+ }
+
+ DisplayError(
+ "Fatal Error",
+ "A fatal error occured! Please compress the latest crash folder into a .zip and send to bugs@wolfire.com, please include a description of what exactly was happening in the game when this error occured.",
+ _ok);
+
+ return succeeded;
+}
+#elif PLATFORM_MACOSX
+
+static bool minidumpCallback( const char *dump_dir,
+ const char *minidump_id,
+ void *context,
+ bool succeeded)
+{
+ LOGF << "Game crashed, flushing the log systems. DumpFile: " << dump_dir << " " << minidump_id << std::endl;
+ DumpScenegraphState();
+ DumpAngelscriptStates();
+ LogSystem::Flush();
+}
+
+static bool filterCallback(void* context)
+{
+ return true;
+}
+
+#elif PLATFORM_WINDOWS
+static bool dumpCallback(const wchar_t* dump_path,
+ const wchar_t* minidump_id,
+ void* context,
+ EXCEPTION_POINTERS* exinfo,
+ MDRawAssertionInfo* assertion,
+ bool succeeded)
+{
+ char tempPath[kPathSize];
+ char destPath[kPathSize];
+
+ WCHAR szWritePath[kPathSize];
+ WCHAR szCrashFolder[kPathSize];
+ WCHAR szSource[kPathSize];
+ WCHAR szDest[kPathSize];
+
+ LOGF << "Game crashed, flushing the log systems." << std::endl;
+ if( succeeded == false ) {
+ LOGF << "Program failed at generating a minidump" << std::endl;
+ }
+ DumpScenegraphState();
+ DumpAngelscriptStates();
+ LogSystem::Flush();
+
+ //Convert write path to windows UTF16 format
+ GetWritePath(CoreGameModID, tempPath, kPathSize);
+ NormalizePathSeparators(tempPath); //Ensure write path has correct slashes
+ MultiByteToWideChar(CP_UTF8, 0, tempPath, -1, szWritePath, kPathSize);
+
+ //Convert crash folder name to windows UTF16 format
+ GetCrashFolder(tempPath,kPathSize);
+ MultiByteToWideChar(CP_UTF8, 0, tempPath, -1, szCrashFolder, kPathSize);
+
+ //Create the crash folder.
+ StringCchPrintfW( szDest , kPathSize, L"%s\\%s", szWritePath, szCrashFolder );
+ CreateDirectoryW( szDest, NULL );
+
+ //Copy the dump into the crash folder
+ StringCchPrintfW( szSource, kPathSize, L"%s\\%s.dmp", dump_path, minidump_id );
+ StringCchPrintfW( szDest, kPathSize, L"%s\\%s\\%s.dmp", szWritePath, szCrashFolder, minidump_id );
+ MoveFileW( szSource, szDest );
+
+ //Copy the logfile into the crash folder
+ GetLogfilePath( tempPath, kPathSize );
+ NormalizePathSeparators(tempPath);
+ MultiByteToWideChar(CP_UTF8, 0, tempPath, -1, szSource, kPathSize);
+ StringCchPrintfW( szDest, kPathSize, L"%s\\%s\\logfile.txt", szWritePath, szCrashFolder );
+ CopyFileW( szSource, szDest, FALSE );
+
+ //Copy the hwreport into the crash folder
+ GetHWReportPath( tempPath, kPathSize );
+ NormalizePathSeparators(tempPath);
+ MultiByteToWideChar(CP_UTF8, 0, tempPath, -1, szSource, kPathSize);
+ StringCchPrintfW( szDest, kPathSize, L"%s\\%s\\hwreport.txt", szWritePath, szCrashFolder );
+ CopyFileW( szSource, szDest, FALSE );
+
+ //Write version information to file
+ StringCchPrintfW( szDest, kPathSize, L"%s\\%s\\version_info.txt", szWritePath, szCrashFolder );
+ HANDLE versionfile = CreateFileW( szDest, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL );
+
+ //Copy the scene_dump.txt into the crash folder
+ GetScenGraphDumpPath( tempPath, kPathSize );
+ NormalizePathSeparators(tempPath);
+ MultiByteToWideChar(CP_UTF8, 0, tempPath, -1, szSource, kPathSize);
+ StringCchPrintfW( szDest, kPathSize, L"%s\\%s\\scene_dump.txt", szWritePath, szCrashFolder );
+ CopyFileW( szSource, szDest, FALSE );
+
+ //Copy the as_dump.txt into the crash folder
+ GetASDumpPath( tempPath, kPathSize );
+ NormalizePathSeparators(tempPath);
+ MultiByteToWideChar(CP_UTF8, 0, tempPath, -1, szSource, kPathSize);
+ StringCchPrintfW( szDest, kPathSize, L"%s\\%s\\as_dump.txt", szWritePath, szCrashFolder );
+ CopyFileW( szSource, szDest, FALSE );
+
+ if( versionfile != INVALID_HANDLE_VALUE )
+ {
+ DWORD os;
+ WriteFile(versionfile, GetBuildIDString(), strlen(GetBuildIDString()), &os, NULL );
+ WriteFile(versionfile, "\r\n", 2, &os, NULL );
+ WriteFile(versionfile, GetPlatform(), strlen(GetPlatform()), &os, NULL );
+ WriteFile(versionfile, "\r\n", 2, &os, NULL );
+ WriteFile(versionfile, GetArch(), strlen(GetArch()), &os, NULL );
+ WriteFile(versionfile, "\r\n", 2, &os, NULL );
+ WriteFile(versionfile, GetBuildVersion(), strlen(GetBuildVersion()), &os, NULL );
+ WriteFile(versionfile, "\r\n", 2, &os, NULL );
+ WriteFile(versionfile, GetBuildTimestamp(), strlen(GetBuildTimestamp()), &os, NULL );
+ WriteFile(versionfile, "\r\n", 2, &os, NULL );
+ CloseHandle(versionfile);
+ }
+
+ DisplayError(
+ "Fatal Error",
+ "A fatal error occured! Please compress the latest crash folder into a .zip and send to bugs@wolfire.com, please include a description of what exactly was happening in the game when this error occured.",
+ _ok );
+
+ // In Windows Explorer, open the folder containing the .dmp
+ GetWritePath(CoreGameModID, tempPath, kPathSize);
+ NormalizePathSeparators(tempPath);
+ FormatString( destPath, kPathSize, "explorer %s", tempPath );
+ system(destPath);
+
+ return succeeded;
+}
+
+static bool filterCallback(void* context, EXCEPTION_POINTERS* exinfo,
+ MDRawAssertionInfo* assertion)
+{
+ return true;
+}
+#endif
+#endif
+
+int RunWithCrashReport( int argc, char* argv[], int (*func) (int, char*[])) {
+#if BREAKPAD
+#if PLATFORM_LINUX
+ google_breakpad::MinidumpDescriptor descriptor(P_tmpdir);
+ google_breakpad::ExceptionHandler eh(descriptor, NULL, dumpCallback, NULL, true, -1);
+#elif PLATFORM_MACOSX
+ //google_breakpad::ExceptionHandler eh( "/tmp/", filterCallback, minidumpCallback, NULL, true, NULL );
+#elif PLATFORM_WINDOWS
+
+ fprintf( stderr, "Registering things\n" );
+ std::wstring dumpPathStr = L".\\";
+
+ wchar_t dumpPath[MAX_PATH];
+ if( GetTempPathW(MAX_PATH,dumpPath) )
+ dumpPathStr = std::wstring(dumpPath);
+
+ google_breakpad::ExceptionHandler *eh = new google_breakpad::ExceptionHandler(
+ dumpPathStr,
+ filterCallback,
+ dumpCallback,
+ 0,
+ google_breakpad::ExceptionHandler::HANDLER_ALL,
+ MiniDumpNormal,
+ L"",
+ 0 );
+#endif
+ //crash();
+#endif
+ return func(argc, argv);
+
+#if BREAKPAD
+#if PLATFORM_WINDOWS
+ delete eh;
+#endif
+#endif
+}
diff --git a/Source/Internal/crashreport.h b/Source/Internal/crashreport.h
new file mode 100644
index 00000000..5daa239d
--- /dev/null
+++ b/Source/Internal/crashreport.h
@@ -0,0 +1,25 @@
+//-----------------------------------------------------------------------------
+// Name: crashreport.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+int RunWithCrashReport( int argc, char* argv[], int (*func) (int, char*[]));
diff --git a/Source/Internal/datemodified.cpp b/Source/Internal/datemodified.cpp
new file mode 100644
index 00000000..d8a57cc4
--- /dev/null
+++ b/Source/Internal/datemodified.cpp
@@ -0,0 +1,190 @@
+//-----------------------------------------------------------------------------
+// Name: datemodified.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "datemodified.h"
+
+#ifndef NO_ERR
+#include <Internal/error.h>
+#endif
+#include <Internal/profiler.h>
+
+#ifdef _WIN32
+#define NOMINMAX
+#include <windows.h>
+#endif
+
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <errno.h>
+#include <cstring>
+#include <string>
+
+using std::string;
+
+bool GetDateModifiedString(const char *file_name, char *buffer, int buffer_size) {
+ #ifdef _WIN32
+ struct _stat buf;
+ int result;
+ char timebuf[26];
+ errno_t err;
+
+ // Get data associated with "crt_stat.c":
+ const int buf_size = 2048;
+ WCHAR path_utf16[buf_size];
+ if(!MultiByteToWideChar(CP_UTF8, 0, file_name, -1, path_utf16, buf_size)){
+#ifndef NO_ERR
+ FatalError("Error", "Error converting utf8 string to utf16: %s", file_name);
+#endif
+ }
+ result = _wstat( path_utf16, &buf );
+
+ // Check if statistics are valid:
+ while( result != 0 )
+ {
+#ifndef NO_ERR
+ string error = "Could not find: " + string(file_name);
+ ErrorResponse response = DisplayError( "Error", error.c_str(), _ok_cancel_retry);
+ if(response == _retry) {
+ result = _wstat( path_utf16, &buf );
+ } else if(response == _continue) {
+ return false;
+ }
+#else
+ return false;
+#endif
+ }
+
+ err = ctime_s(timebuf, 26, &buf.st_mtime);
+ if (err)
+ {
+#ifndef NO_ERR
+ DisplayError( "Error", "Problem getting date modified string." );
+#endif
+ return false;
+ }
+ strncpy(buffer, timebuf, buffer_size);
+ return true;
+
+ #else // standard unix implementation
+ struct stat buf;
+ int result;
+ char timebuf[26];
+
+ // Get data associated with "crt_stat.c":
+ const char* path = file_name;
+ result = stat( path, &buf );
+
+ // Check if statistics are valid:
+ if( result != 0 )
+ {
+ const int BUF_SIZE = 512;
+ char err_buf[BUF_SIZE];
+#ifndef NO_ERR
+ if ( errno == ENOENT )
+ {
+ snprintf(err_buf, BUF_SIZE, "The file doesn't exist: %s", file_name);
+ DisplayError( "Error", err_buf );
+ }
+ else if ( errno == EACCES )
+ {
+ snprintf(err_buf, BUF_SIZE, "I don't have permission to read that file: %s", file_name);
+ DisplayError( "Error", err_buf );
+ }
+ else
+ {
+ snprintf(err_buf, BUF_SIZE, "Problem getting date modified: %s", file_name);
+ DisplayError( "Error", err_buf );
+ }
+#endif
+ return false;
+ }
+ else
+ {
+ sprintf(timebuf, "%li", buf.st_mtime);
+ strncpy(buffer, timebuf, buffer_size);
+ return true;
+ /*unsigned short sum;
+ for(int i=0; i<26; i++){
+ sum += timebuf[i]*i;
+ }*/
+ }
+ #endif
+}
+
+
+int64_t GetDateModifiedInt64(const Path& path) {
+ return GetDateModifiedInt64(path.GetFullPath());
+}
+
+int64_t GetDateModifiedInt64(const char *abs_path) {
+ PROFILER_ZONE(g_profiler_ctx, "GetDateModifiedInt64");
+#ifdef _WIN32
+ struct _stat buf;
+ int result;
+
+ // Get data associated with "crt_stat.c":
+ const int BUF_SIZE = 4096;
+ WCHAR path_utf16[BUF_SIZE];
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "MultiByteToWideChar");
+ if(!MultiByteToWideChar(CP_UTF8, 0, abs_path, -1, path_utf16, BUF_SIZE)){
+#ifndef NO_ERR
+ FatalError("Error", "Error converting utf8 string to utf16: %s", abs_path);
+#endif
+ return -1;
+ }
+ }
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "_wstat");
+ result = _wstat( path_utf16, &buf );
+ }
+
+ // Check if statistics are valid:
+ while( result != 0 ) {
+ return -1;
+ }
+
+ return (int64_t)buf.st_mtime;
+
+#elif defined(__APPLE__) || defined(__linux__)
+ struct stat buf;
+ int result;
+
+ // Get data associated with "crt_stat.c":
+ const char* path = abs_path;
+ result = stat( path, &buf );
+
+ // Check if statistics are valid:
+ if( result != 0 )
+ {
+ return -1;
+ }
+ else
+ {
+ return (int64_t)buf.st_mtime;
+ }
+#endif
+}
diff --git a/Source/Internal/datemodified.h b/Source/Internal/datemodified.h
new file mode 100644
index 00000000..8a265441
--- /dev/null
+++ b/Source/Internal/datemodified.h
@@ -0,0 +1,31 @@
+//-----------------------------------------------------------------------------
+// Name: datemodified.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/path.h>
+
+#include <cstdint>
+
+bool GetDateModifiedString(const char *file_name, char* buffer, int buffer_size); // returns true on success
+int64_t GetDateModifiedInt64(const char *file_name);
+int64_t GetDateModifiedInt64(const Path& path);
diff --git a/Source/Internal/detect_settings.cpp b/Source/Internal/detect_settings.cpp
new file mode 100644
index 00000000..8961e0e7
--- /dev/null
+++ b/Source/Internal/detect_settings.cpp
@@ -0,0 +1,56 @@
+//-----------------------------------------------------------------------------
+// Name: detect_settings.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "detect_settings.h"
+
+#include <Internal/hardware_specs.h>
+#include <Internal/config.h>
+
+#include <Graphics/graphics.h>
+#include <Logging/logdata.h>
+#include <Main/engine.h>
+
+extern bool g_no_reflection_capture;
+extern bool g_s3tc_dxt5_support;
+
+void DetectAndSetOpenGLFeatureRestrictions() {
+ if( HasHardwareS3TCSupport() ) {
+ LOGI << "Verified support for S3TC DXT5 compressed textures" << std::endl;
+ g_s3tc_dxt5_support = true;
+ } else {
+ LOGI << "Could not Verify support for S3TC DXT5 compressed textures, disabling compressed texture loads." << std::endl;
+ g_s3tc_dxt5_support = false;
+ }
+}
+
+void DetectAndSetSettings() {
+ LOGI << "Detecting hardware and setting graphics settings to match hardware" << std::endl;
+ std::map<std::string,int> gfx_int = GetHardwareLimitationsInt();
+ //Disable reflections if we don't have enough image units to allow it to fit.
+ if( gfx_int["GL_MAX_TEXTURE_IMAGE_UNITS"] <= 16 ) {
+ g_no_reflection_capture = true;
+ config.GetRef("no_reflection_capture") = true;
+ LOGI << "Limited texture samplers, disabling reflection capture" << std::endl;
+ } else {
+ LOGI << "Sufficient amount of samplers to support all features." << std::endl;
+ }
+}
diff --git a/Source/Internal/detect_settings.h b/Source/Internal/detect_settings.h
new file mode 100644
index 00000000..e6c7a9f1
--- /dev/null
+++ b/Source/Internal/detect_settings.h
@@ -0,0 +1,26 @@
+//-----------------------------------------------------------------------------
+// Name: detect_settings.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+void DetectAndSetOpenGLFeatureRestrictions();
+void DetectAndSetSettings();
diff --git a/Source/Internal/dialogues.cpp b/Source/Internal/dialogues.cpp
new file mode 100644
index 00000000..3d5945e3
--- /dev/null
+++ b/Source/Internal/dialogues.cpp
@@ -0,0 +1,438 @@
+//-----------------------------------------------------------------------------
+// Name: dialogues.cpp
+// Developer: Wolfire Games LLC
+// Description: This is a simple wrapper for displaying save/load dialogue boxes
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "dialogues.h"
+
+#include <Internal/filesystem.h>
+#include <Internal/snprintf.h>
+
+#include <Logging/logdata.h>
+#include <UserInput/input.h>
+#include <Compat/compat.h>
+#include <Wrappers/linux_gtk.h>
+
+#ifdef __APPLE__
+#include <Compat/Mac/os_file_dialogs_mac.h>
+#endif
+
+#include <SDL.h>
+
+#ifdef _WIN32
+#define NOMINMAX
+#include <windows.h>
+#include <direct.h>
+#include <CommDlg.h>
+#include <strsafe.h>
+#endif
+
+using namespace Dialog;
+
+#ifdef _WIN32
+static DialogErr SetUpWindowsFilterString(wchar_t* buffer, int buffer_size, int BUFFER_SIZE, const wchar_t* extension) {
+ size_t cur_len = 0;
+
+ // Build description string, e.g.: "(*.tga; *.jpg; *.dds)\0*.tga;*.jpg;*.dds"
+ if(StringCbPrintfW(&buffer[cur_len], BUFFER_SIZE-(cur_len+1), L"(") != S_OK)
+ return INTERNAL_BUFFER_TOO_SMALL;
+ cur_len += wcslen(&buffer[cur_len]);
+
+ for(size_t offset = 0; offset < buffer_size; ) {
+ if(offset > 0) {
+ if(StringCbPrintfW(&buffer[cur_len], BUFFER_SIZE-(cur_len+1), L"; ") != S_OK)
+ return INTERNAL_BUFFER_TOO_SMALL;
+ cur_len += wcslen(&buffer[cur_len]);
+ }
+
+ if(StringCbPrintfW(&buffer[cur_len], BUFFER_SIZE-(cur_len+1), L"*.%s", &extension[offset]) != S_OK)
+ return INTERNAL_BUFFER_TOO_SMALL;
+ cur_len += wcslen(&buffer[cur_len]);
+
+ offset += wcslen(&extension[offset]) + 1;
+ }
+
+ if(StringCbPrintfW(&buffer[cur_len], BUFFER_SIZE-(cur_len+1), L")") != S_OK)
+ return INTERNAL_BUFFER_TOO_SMALL;
+ cur_len += wcslen(&buffer[cur_len]) + 1;
+
+ for(size_t offset = 0; offset < buffer_size; ) {
+ if(offset > 0) {
+ if(StringCbPrintfW(&buffer[cur_len], BUFFER_SIZE-(cur_len+1), L";") != S_OK)
+ return INTERNAL_BUFFER_TOO_SMALL;
+ cur_len += wcslen(&buffer[cur_len]);
+ }
+
+ if(StringCbPrintfW(&buffer[cur_len], BUFFER_SIZE-(cur_len+1), L"*.%s", &extension[offset]) != S_OK)
+ return INTERNAL_BUFFER_TOO_SMALL;
+ cur_len += wcslen(&buffer[cur_len]);
+
+ offset += wcslen(&extension[offset]) + 1;
+ }
+
+ // Separate filter from wildcard option with null
+ cur_len += 1;
+
+ // Add wildcard option description: "All Files (*.*)\0*.*"
+ if(StringCbPrintfW(&buffer[cur_len], BUFFER_SIZE-(cur_len+1), L"All Files (*.*)") != S_OK)
+ return INTERNAL_BUFFER_TOO_SMALL;
+ cur_len += wcslen(&buffer[cur_len]) + 1;
+
+ if(StringCbPrintfW(&buffer[cur_len], BUFFER_SIZE-(cur_len+1), L"*.*") != S_OK)
+ return INTERNAL_BUFFER_TOO_SMALL;
+ cur_len += wcslen(&buffer[cur_len]) + 1;
+
+ // Add final double-null to end the list
+ buffer[cur_len] = '\0';
+
+ return NO_ERR;
+}
+
+static DialogErr FormatFullPath(wchar_t *buffer, int BUFFER_SIZE, const wchar_t* initial_dir) {
+ if(!_wgetcwd(buffer,BUFFER_SIZE)){
+ return GET_CWD_FAILED;
+ }
+ if((int)(wcslen(buffer) + 1 + wcslen(initial_dir)) >= BUFFER_SIZE){
+ return INTERNAL_BUFFER_TOO_SMALL;
+ }
+ wcscat(buffer,L"\\");
+ wcscat(buffer,initial_dir);
+ // Convert / to \ for Windows
+ for(int i=0; i<BUFFER_SIZE; i++){
+ if(buffer[i]=='\0'){
+ break;
+ } else if(buffer[i]=='/'){
+ buffer[i] = '\\';
+ }
+ }
+ return NO_ERR;
+}
+#endif
+
+static bool has_valid_dialogues = false;
+void Dialog::Initialize() {
+ #if PLATFORM_UNIX
+ #ifndef __APPLE__
+ has_valid_dialogues = gtk_init_check( NULL, NULL );
+ #else
+ has_valid_dialogues = true;
+ #endif
+ #else
+ has_valid_dialogues = true;
+ #endif
+}
+
+DialogErr Dialog::readFile( const char* extension, int extension_count, const char* initial_dir, char *path_buffer, int PATH_BUFFER_SIZE) {
+ if( has_valid_dialogues == false ) {
+ LOGE << "Dialogue system was not initialized" << std::endl;
+ return UNKNOWN_ERR;
+ }
+
+ char initial_dir_abs_path[kPathSize];
+ FindFilePath(initial_dir, initial_dir_abs_path, kPathSize, kDataPaths);
+#if PLATFORM_UNIX
+#ifndef __APPLE__
+
+ GtkWidget *dialog;
+ DialogErr err;
+ dialog = gtk_file_chooser_dialog_new ("Open File",
+ NULL,
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ if( initial_dir )
+ {
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), initial_dir);
+ }
+
+ if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
+ {
+ char *filename;
+ filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
+
+ int target_size = snprintf(path_buffer, PATH_BUFFER_SIZE, "%s", filename);
+ if((int)strlen(path_buffer) < target_size){
+ err = USER_BUFFER_TOO_SMALL;
+ }
+ err = NO_ERR; // TODO: This clobbers the previous error. Should it be removed? Should it be placed before the if?
+
+ g_free (filename);
+ }
+ else
+ {
+ err = NO_SELECTION;
+ }
+
+ gtk_widget_hide(dialog);
+ gtk_widget_destroy (dialog);
+ while (gtk_events_pending()) gtk_main_iteration();
+ return err;
+#else
+ UIShowCursor(true);
+
+ char* filename = NULL;
+ DialogErr err = OsFileDialogsMac::OpenDialog(extension, initial_dir, &filename);
+
+ if(err == NO_ERR) {
+ if(filename != NULL) {
+ int target_size = snprintf(path_buffer, PATH_BUFFER_SIZE, "%s", filename);
+
+ if((int)strlen(path_buffer) < target_size) {
+ err = USER_BUFFER_TOO_SMALL;
+ }
+
+ free(filename);
+ } else {
+ err = NO_SELECTION; // TODO: Is this the right error?
+ }
+ }
+
+ UIShowCursor(false);
+
+ return err;
+#endif
+#else
+ //Get the initial home directory
+ wchar_t initial_working_directory[kPathSize];
+ if(!_wgetcwd(initial_working_directory,kPathSize)){
+ return GET_CWD_FAILED;
+ }
+
+ wchar_t wide_extension[kPathSize];
+ size_t length = 0;
+ for(int i = 0; i < extension_count; ++i) {
+ length += strlen(&extension[length]) + 1;
+ }
+
+ MultiByteToWideChar(CP_UTF8, 0, extension, (int) length, wide_extension, kPathSize);
+
+ // Create string specifying file types that can be opened
+ wchar_t buffer[kPathSize];
+ DialogErr err = SetUpWindowsFilterString(buffer, (int) length, kPathSize, wide_extension);
+ if(err != NO_ERR){
+ return err;
+ }
+
+ //Set up file name structure
+ OPENFILENAMEW ofn = {0};
+ wchar_t szFileName[MAX_PATH] = L"";
+ ofn.lpstrFile = szFileName;
+ ofn.lStructSize = sizeof(ofn);
+ ofn.hwndOwner = NULL;
+ ofn.lpstrFilter= buffer;
+ ofn.nMaxFile = MAX_PATH;
+ ofn.Flags = OFN_FILEMUSTEXIST;
+ ofn.lpstrDefExt = wide_extension;
+
+ // Convert / to \ in path; Windows is picky about that
+ for(char* c = initial_dir_abs_path; *c!='\0'; ++c){
+ if(*c == '/'){
+ *c = '\\';
+ }
+ }
+
+ if(initial_dir){
+ wchar_t wide_initial_dir[kPathSize];
+ MultiByteToWideChar(CP_UTF8, 0, initial_dir_abs_path, -1, wide_initial_dir, kPathSize);
+ ofn.lpstrInitialDir = wide_initial_dir;
+ } else {
+ ofn.lpstrInitialDir = NULL;
+ }
+
+ //Open "Open..." dialogue box
+ BOOL selected = GetOpenFileNameW(&ofn);
+
+ // Restore initial working directory
+ _wchdir(initial_working_directory);
+
+ if(!selected) {
+ return NO_SELECTION;
+ } else {
+ for(size_t i = 0; i < MAX_PATH; ++i) {
+ if(szFileName[i] == '\\')
+ szFileName[i] = '/';
+ else if(szFileName[i] == '\0')
+ break;
+ }
+ int target_size = WideCharToMultiByte(CP_UTF8, 0, szFileName, -1, NULL, NULL, NULL, NULL);
+ if(target_size > PATH_BUFFER_SIZE){
+ return USER_BUFFER_TOO_SMALL;
+ }
+ WideCharToMultiByte(CP_UTF8, 0, szFileName, -1, path_buffer, PATH_BUFFER_SIZE, NULL, NULL);
+ return NO_ERR;
+ }
+#endif
+}
+
+DialogErr Dialog::writeFile( const char* extension, int extension_count, const char* initial_dir, char *path_buffer, int PATH_BUFFER_SIZE)
+{
+ if( has_valid_dialogues == false ) {
+ LOGE << "Dialogue system was not initialized" << std::endl;
+ return UNKNOWN_ERR;
+ }
+
+#if PLATFORM_UNIX
+#ifndef __APPLE__
+ GtkWidget *dialog;
+ DialogErr err;
+
+ dialog = gtk_file_chooser_dialog_new ("Save File",
+ NULL,
+ GTK_FILE_CHOOSER_ACTION_SAVE,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
+
+ if (true) //user_edited_a_new_document)
+ {
+ if( initial_dir )
+ {
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), initial_dir);
+ }
+
+ gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), "");
+ }
+ else
+ {
+ if( initial_dir )
+ {
+ gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (dialog), initial_dir);
+ }
+ }
+
+ if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
+ {
+ char *filename;
+ filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
+
+ int target_size = snprintf(path_buffer, PATH_BUFFER_SIZE, "%s", filename);
+ if((int)strlen(path_buffer) < target_size){
+ err = USER_BUFFER_TOO_SMALL;
+ }
+ err = NO_ERR;
+
+ g_free (filename);
+ }
+ else
+ {
+ err = NO_SELECTION;
+ }
+
+ gtk_widget_hide(dialog);
+ gtk_widget_destroy (dialog);
+ while (gtk_events_pending()) gtk_main_iteration();
+ return err;
+#else
+ UIShowCursor(true);
+
+ char* filename = NULL;
+ DialogErr err = OsFileDialogsMac::SaveDialog(extension, initial_dir, &filename);
+
+ if(err == NO_ERR) {
+ if(filename != NULL) {
+ int target_size = snprintf(path_buffer, PATH_BUFFER_SIZE, "%s", filename);
+
+ if((int)strlen(path_buffer) < target_size) {
+ err = USER_BUFFER_TOO_SMALL;
+ }
+
+ free(filename);
+ } else {
+ err = NO_SELECTION; // TODO: Is this the right error?
+ }
+ }
+
+ UIShowCursor(false);
+
+ return err;
+#endif
+#else
+ //Get the initial home directory
+ wchar_t initial_working_directory[kPathSize];
+ if(!_wgetcwd(initial_working_directory,kPathSize)){
+ return GET_CWD_FAILED;
+ }
+
+ wchar_t wide_extension[kPathSize];
+ size_t length = 0;
+ for(int i = 0; i < extension_count; ++i) {
+ length += strlen(&extension[length]) + 1;
+ }
+
+ MultiByteToWideChar(CP_UTF8, 0, extension, (int) length, wide_extension, kPathSize);
+
+ // Create string specifying file types that can be opened
+ wchar_t buffer[kPathSize];
+ DialogErr err = SetUpWindowsFilterString(buffer, (int) length, kPathSize, wide_extension);
+ if(err != NO_ERR){
+ return err;
+ }
+ //Set up file name structure
+ OPENFILENAMEW ofn;
+ wchar_t szFileName[MAX_PATH] = L"";
+ ZeroMemory(&ofn, sizeof(ofn));
+ ofn.lStructSize = sizeof(ofn);
+ ofn.hwndOwner = NULL;
+ ofn.lpstrFilter= buffer;
+ ofn.lpstrFile = szFileName;
+ ofn.nMaxFile = MAX_PATH;
+ ofn.Flags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
+ ofn.lpstrDefExt = wide_extension;
+
+ wchar_t wide_initial_dir[kPathSize];
+ length = MultiByteToWideChar(CP_UTF8, 0, initial_dir, -1, NULL, NULL);
+ if(length > kPathSize)
+ return INTERNAL_BUFFER_TOO_SMALL;
+ MultiByteToWideChar(CP_UTF8, 0, initial_dir, -1, wide_initial_dir, kPathSize);
+
+ wchar_t full_path[kPathSize];
+ if(initial_dir){
+ err = FormatFullPath(full_path, kPathSize, wide_initial_dir);
+ if(err != NO_ERR){
+ return err;
+ }
+ ofn.lpstrInitialDir = full_path;
+ } else {
+ ofn.lpstrInitialDir = NULL;
+ }
+
+ //Open "Save as..." dialogue box
+ BOOL selected = GetSaveFileNameW(&ofn);
+
+ //Restore the home directory
+ _wchdir(initial_working_directory);
+
+ if(!selected) {
+ return NO_SELECTION;
+ } else {
+ int target_size = WideCharToMultiByte(CP_UTF8, 0, szFileName, -1, NULL, NULL, NULL, NULL);
+ if(target_size > PATH_BUFFER_SIZE){
+ return USER_BUFFER_TOO_SMALL;
+ }
+ WideCharToMultiByte(CP_UTF8, 0, szFileName, -1, path_buffer, PATH_BUFFER_SIZE, NULL, NULL);
+ return NO_ERR;
+ }
+ return NO_ERR;
+#endif
+}
diff --git a/Source/Internal/dialogues.h b/Source/Internal/dialogues.h
new file mode 100644
index 00000000..29afe9bc
--- /dev/null
+++ b/Source/Internal/dialogues.h
@@ -0,0 +1,52 @@
+//-----------------------------------------------------------------------------
+// Name: dialogues.h
+// Developer: Wolfire Games LLC
+// Description: This is a simple wrapper for displaying save/load dialogue boxes
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+namespace Dialog {
+enum DialogErr {
+ NO_ERR,
+ NO_SELECTION,
+ INTERNAL_BUFFER_TOO_SMALL,
+ USER_BUFFER_TOO_SMALL,
+ GET_CWD_FAILED,
+ UNKNOWN_ERR
+};
+
+inline const char* DialogErrString( DialogErr e )
+{
+ switch( e )
+ {
+ case NO_ERR: return "NO_ERR";
+ case NO_SELECTION: return "NO_SELECTION";
+ case INTERNAL_BUFFER_TOO_SMALL: return "INTERNAL_BUFFER_TOO_SMALL";
+ case USER_BUFFER_TOO_SMALL: return "USER_BUFFER_TOO_SMALL";
+ case GET_CWD_FAILED: return "GET_CWD_FAILED";
+ case UNKNOWN_ERR: return "UNKNOWN_ERR";
+ default: return "(no string correspondent)";
+ }
+}
+
+void Initialize();
+DialogErr readFile( const char* extension, int extension_count, const char* initial_dir, char *path_buffer, int PATH_BUFFER_SIZE);
+DialogErr writeFile( const char* extension, int extension_count, const char* initial_dir, char *path_buffer, int PATH_BUFFER_SIZE);
+}
diff --git a/Source/Internal/error.cpp b/Source/Internal/error.cpp
new file mode 100644
index 00000000..48e4b453
--- /dev/null
+++ b/Source/Internal/error.cpp
@@ -0,0 +1,295 @@
+//-----------------------------------------------------------------------------
+// Name: error.cpp
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: This is a simple wrapper for displaying error messages
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "error.h"
+
+#include <Internal/config.h>
+#include <Internal/common.h>
+#include <Internal/modloading.h>
+
+#include <UserInput/input.h>
+#include <Logging/logdata.h>
+#include <Graphics/graphics.h>
+#include <Threading/thread_sanity.h>
+#include <Compat/os_dialogs.h>
+#include <Utility/stacktrace.h>
+
+#include <map>
+#include <string>
+#include <thread>
+#include <chrono>
+#include <mutex>
+
+extern Config config;
+
+void FatalError(const char* title, const char* fmt, ...) {
+ static const int kBufSize = 1024;
+ char err_buf[kBufSize];
+ va_list args;
+ va_start(args, fmt);
+ VFormatString(err_buf, kBufSize, fmt, args);
+ va_end(args);
+ DisplayError(title, err_buf, _ok);
+ LOGF << "Showing fatal message: " << title << "," << err_buf << std::endl;
+ LOGF << "Shutting down after fatal error" << std::endl;
+ LogSystem::Flush();
+ _exit(1);
+}
+
+ErrorResponse DisplayFormatError(ErrorType type,
+ bool allow_repetition,
+ const char* title,
+ const char* fmtcontents,
+ ... ) {
+ static const int kBufSize = 2048;
+ char err_buf[kBufSize];
+ va_list args;
+ va_start(args, fmtcontents);
+ VFormatString(err_buf, kBufSize, fmtcontents, args);
+ va_end(args);
+ return DisplayError(title, err_buf, type, allow_repetition);
+}
+
+struct ErrorDisplay {
+ int id;
+ std::string title;
+ std::string pretext;
+ std::string contents;
+ ErrorType type;
+};
+
+std::mutex error_queue_mutex;
+static int error_id_counter = 1;
+std::vector<ErrorDisplay> error_queue;
+std::map<int,ErrorResponse> error_return;
+
+std::mutex display_last_queued_error_mutex;
+
+ErrorResponse DisplayLastQueuedError() {
+ ErrorResponse response = _continue;
+
+ display_last_queued_error_mutex.lock();
+
+ ErrorDisplay ed;
+ ed.id = 0;
+ ed.type = _ok;
+ bool show_error = false;
+
+ error_queue_mutex.lock();
+ if( error_queue.size() > 0 ) {
+ ed = error_queue[0];
+ error_queue.erase(error_queue.begin());
+ show_error = true;
+ }
+
+ error_queue_mutex.unlock();
+
+ std::stringstream ss;
+
+ ss << ed.pretext << std::endl << ed.contents;
+
+ if( show_error ) {
+ response = OSDisplayError(
+ ed.title.c_str(),
+ ss.str().c_str(),
+ ed.type
+ );
+
+ switch( response ) {
+ case _er_exit:
+ LOGI.Format("\"Cancel\" chosen, shutting down program.");
+ LogSystem::Flush();
+ _exit(1);
+ break;
+ case _retry:
+ LOGI.Format("\"Retry\" chosen");
+ break;
+ case _continue:
+ LOGI.Format("\"Continue\" chosen");
+ break;
+ }
+ }
+
+ error_queue_mutex.lock();
+ error_return[ed.id] = response;
+ error_queue_mutex.unlock();
+
+ display_last_queued_error_mutex.unlock();
+ return response;
+}
+
+static int PushDisplayError(ErrorDisplay& ed) {
+ int id;
+ error_queue_mutex.lock();
+ id = error_id_counter++;
+ ed.id = id;
+ error_queue.push_back(ed);
+ error_queue_mutex.unlock();
+ return id;
+}
+
+ErrorResponse WaitResponseForDisplayError(int error_id) {
+ ErrorResponse ret;
+ bool result = false;
+ while(result == false) {
+ error_queue_mutex.lock();
+ result = (error_return.find(error_id) != error_return.end());
+ error_queue_mutex.unlock();
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+
+ error_queue_mutex.lock();
+ ret = error_return[error_id];
+ error_return.erase(error_return.find(error_id));
+ error_queue_mutex.unlock();
+ return ret;
+}
+
+std::mutex display_error_mutex;
+std::map<std::string, int> error_message_history;
+
+ErrorResponse DisplayError(const char* title, const char* contents, ErrorType type, bool allow_repetition) {
+
+ display_error_mutex.lock();
+
+ bool no_log = false;
+ if(type == _ok_no_log){
+ no_log = true;
+ type = _ok;
+ }
+
+ if( !no_log ) {
+ LOGI << "Displaying message: " << title << ", " << contents << std::endl;
+ LOGI << GenerateStacktrace() << std::endl;
+ }
+
+ if( config["no_dialogues"].toBool() )
+ {
+ display_error_mutex.unlock();
+ return _continue;
+ }
+
+ if(!allow_repetition && error_message_history[contents]) {
+ display_error_mutex.unlock();
+ return _continue;
+ }
+
+ error_message_history[contents]++;
+
+ std::stringstream modlist;
+
+ bool active_mods = false;
+ int active_count = 0;
+ std::vector<ModInstance*> mods = ModLoading::Instance().GetAllMods();
+ for( unsigned i = 0; i < mods.size(); i++ ) {
+ if( mods[i]->IsActive() && mods[i]->IsCore() == false) {
+ if( active_mods == false ) {
+ modlist << "Following mods are active" << std::endl;
+ active_mods = true;
+ } else {
+ if( (active_count % 5) == 0 ) {
+ modlist << "," << std::endl;
+ } else {
+ modlist << ", ";
+ }
+ }
+ modlist << mods[i]->id;
+ active_count++;
+ }
+ }
+ modlist << std::endl << "Before reporting, see if disabling mods makes a difference and include this info." << std::endl;
+
+ ErrorDisplay ed;
+
+ ed.title = title;
+ if( active_mods ) {
+ ed.pretext = modlist.str();
+ }
+ ed.contents = contents;
+ ed.type = type;
+
+ int error_id = PushDisplayError(ed);
+
+ ErrorResponse response;
+
+//On windows, showing this dialogue on the non-main window seems fine. (not sure i like it though, might wanna not do this, it's helpful for stacktraces in visual studio however).
+#if PLATFORM_WINDOWS
+ response = DisplayLastQueuedError();
+#else
+ if( IsMainThread() ) {
+ response = DisplayLastQueuedError();
+ } else {
+ response = WaitResponseForDisplayError(error_id);
+ }
+#endif
+
+ display_error_mutex.unlock();
+
+ return response;
+}
+
+void DisplayFormatMessage(const char* title,
+ const char* fmtcontents,
+ ... ) {
+ static const int kBufSize = 2048;
+ char mess_buf[kBufSize];
+ va_list args;
+ va_start(args, fmtcontents);
+ VFormatString(mess_buf, kBufSize, fmtcontents, args);
+ va_end(args);
+ return DisplayMessage(title, mess_buf);
+}
+
+void DisplayMessage(const char* title,
+ const char* contents) {
+ display_error_mutex.lock();
+
+ if( config["no_dialogues"].toBool() )
+ {
+ display_error_mutex.unlock();
+ return;
+ }
+
+ ErrorDisplay ed;
+
+ ed.title = title;
+ ed.contents = contents;
+ ed.type = _ok;
+
+ int error_id = PushDisplayError(ed);
+
+ ErrorResponse response;
+
+//On windows, showing this dialogue on the non-main window seems fine. (not sure i like it though, might wanna not do this, it's helpful for stacktraces in visual studio however).
+#if PLATFORM_WINDOWS
+ response = DisplayLastQueuedError();
+#else
+ if( IsMainThread() ) {
+ response = DisplayLastQueuedError();
+ } else {
+ response = WaitResponseForDisplayError(error_id);
+ }
+#endif
+
+ display_error_mutex.unlock();
+}
diff --git a/Source/Internal/error.h b/Source/Internal/error.h
new file mode 100644
index 00000000..c1286b5d
--- /dev/null
+++ b/Source/Internal/error.h
@@ -0,0 +1,69 @@
+//-----------------------------------------------------------------------------
+// Name: error.h
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: This is a simple wrapper for displaying error messages
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Utility/compiler_macros.h>
+#include <Internal/error_response.h>
+
+#include <string>
+
+/**
+*Displays an error message.
+*@param title The title of the message box.
+*@param contents The contents of the message box.
+*@param type Which buttons are available
+*@param allow_repetition If false, skips error if exact content has already
+* been displayed
+*/
+ErrorResponse DisplayError(const char* title,
+ const char* contents,
+ ErrorType type = _ok_cancel,
+ bool allow_repetition = true);
+
+ErrorResponse DisplayFormatError(ErrorType type,
+ bool allow_repetition,
+ const char* title,
+ const char* fmtcontents,
+ ...
+);
+
+// Display a message with an OK button only
+void DisplayMessage(const char* title,
+ const char* contents);
+void DisplayFormatMessage(const char* title,
+ const char* fmtcontents,
+ ...);
+
+ErrorResponse DisplayLastQueuedError();
+
+/**
+*Displays an error message and then exits the program.
+*@param title The title of the message box.
+*@param contents The contents of the message box.
+*/
+void FatalError(const char* title, const char* fmt, ...) __attribute__((noreturn));
+
+//bool Query(std::string title, std::string contents);
+
+//std::string QueryString(std::string title, std::string contents);
diff --git a/Source/Internal/error.mm b/Source/Internal/error.mm
new file mode 100644
index 00000000..d80f4a4c
--- /dev/null
+++ b/Source/Internal/error.mm
@@ -0,0 +1,153 @@
+//-----------------------------------------------------------------------------
+// Name: error.mm
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <Internal/error.h>
+#include <Internal/config.h>
+
+#include <UserInput/input.h>
+#include <Logging/logdata.h>
+
+#include <map>
+#include <string>
+#include <SDL.h>
+
+#import <AppKit/NSAlert.h>
+#import <AppKit/NSTextField.h>
+#import <AppKit/NSPanel.h>
+
+extern std::map<std::string, int> error_message_history;
+
+std::string QueryString( std::string title, std::string contents )
+{
+ bool old_mouse = Input::Instance()->GetGrabMouse();
+ Input::Instance()->SetGrabMouse(false);
+ UIShowCursor(1);
+
+ NSString *contentsString = [NSString stringWithUTF8String:contents.c_str()];
+ NSAlert *alert = [NSAlert alertWithMessageText:contentsString
+ defaultButton:@"OK"
+ alternateButton:@"Cancel"
+ otherButton:nil
+ informativeTextWithFormat:@""];
+
+ NSTextField *input = [[[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 24)] autorelease];
+ [input setStringValue:@""];
+ [alert setAccessoryView:input];
+ [[alert window] makeFirstResponder:input];
+ NSInteger button = [alert runModal];
+
+ Input::Instance()->SetGrabMouse(old_mouse);
+ UIShowCursor(0);
+ if (button == NSAlertDefaultReturn) {
+ [input validateEditing];
+ std::string return_value = [[input stringValue] UTF8String];
+ return return_value;
+ } else if (button == NSAlertAlternateReturn) {
+ return "";
+ } else {
+ return "";
+ }
+}
+
+// Basic alert code in case we need it
+/*NSAlert *alert = [NSAlert alertWithMessageText:@"Could not open connection to Xgrid server."
+ defaultButton:@"OK" alternateButton:nil otherButton:nil
+ informativeTextWithFormat:
+ @"Check that controller host name was entered correctly, and that it is "
+ @"available."];
+ [alert runModal];*/
+
+bool Query(std::string title, std::string contents)
+{
+ bool old_mouse = Input::Instance()->GetGrabMouse();
+ Input::Instance()->SetGrabMouse(false);
+ UIShowCursor(1);
+
+ NSString *contentsString = [NSString stringWithUTF8String:contents.c_str()];
+ NSAlert *alert = [NSAlert alertWithMessageText:contentsString
+ defaultButton:@"Yes"
+ alternateButton:@"No"
+ otherButton:nil
+ informativeTextWithFormat:@""];
+
+ NSInteger button = [alert runModal];
+
+ Input::Instance()->SetGrabMouse(old_mouse);
+ UIShowCursor(0);
+ return (button == NSAlertDefaultReturn);
+}
+
+ErrorResponse DisplayError(const char* title, const char* contents, ErrorType type, bool allow_repetition)
+{
+ LOGI << "Displaying message: " << title << ", " << contents << std::endl;
+
+ if( config["no_dialogues"].toBool() )
+ {
+ return _continue;
+ }
+
+ if(!allow_repetition && error_message_history[contents]) {
+ return _continue;
+ }
+ error_message_history[contents]++;
+
+ bool old_mouse = Input::Instance()->GetGrabMouse();
+ Input::Instance()->SetGrabMouse(false);
+ UIShowCursor(1);
+
+ NSString *errStr = [NSString stringWithUTF8String:title];
+ NSString *messageStr = [NSString stringWithUTF8String:contents];
+
+ NSAlert *alert = [NSAlert alertWithMessageText:errStr
+ defaultButton:type == _ok_cancel_retry ? @"Continue" : @"Ok"
+ alternateButton:type == _ok ? nil : @"Cancel"
+ otherButton:type == _ok_cancel_retry ? @"Retry" : nil
+ informativeTextWithFormat:messageStr];
+
+ NSInteger button = [alert runModal];
+
+ if (button == NSAlertDefaultReturn) {
+ Input::Instance()->SetGrabMouse(old_mouse);
+ UIShowCursor(0);
+ return _continue;
+ } else if (button == NSAlertAlternateReturn) {
+ exit(1);
+ } else {
+ Input::Instance()->SetGrabMouse(old_mouse);
+ UIShowCursor(0);
+ return _retry;
+ }
+}
+
+ErrorResponse DisplayFormatError(ErrorType type,
+ bool allow_repetition,
+ const char* title,
+ const char* fmtcontents,
+ ... ) {
+ static const int kBufSize = 2048;
+ char err_buf[kBufSize];
+ va_list args;
+ va_start(args, fmtcontents);
+ VFormatString(err_buf, kBufSize, fmtcontents, args);
+ va_end(args);
+ return DisplayError(title, err_buf, type, allow_repetition);
+}
diff --git a/Source/Internal/error_response.h b/Source/Internal/error_response.h
new file mode 100644
index 00000000..6f3c7b03
--- /dev/null
+++ b/Source/Internal/error_response.h
@@ -0,0 +1,29 @@
+//-----------------------------------------------------------------------------
+// Name: error_response_h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+/// Return type to report whether 'retry' or 'continue' was clicked
+enum ErrorResponse {_retry, _continue, _er_exit};
+
+/// Used as a parameter to choose which buttons an error box should have
+enum ErrorType {_ok_cancel_retry, _ok_cancel, _ok, _ok_no_log};
diff --git a/Source/Internal/file_descriptor.cpp b/Source/Internal/file_descriptor.cpp
new file mode 100644
index 00000000..48756b27
--- /dev/null
+++ b/Source/Internal/file_descriptor.cpp
@@ -0,0 +1,94 @@
+//-----------------------------------------------------------------------------
+// Name: file_descriptor.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "file_descriptor.h"
+
+#include <Compat/fileio.h>
+#include <Logging/logdata.h>
+
+#include <cstring>
+
+bool DiskFileDescriptor::ReadBytes( void* dst, int num_bytes ) {
+ int elements_read = fread(dst, num_bytes, 1, file_);
+ if(ferror(file_)){
+ perror("File error:");
+ }
+ if(feof(file_)){
+ LOGE << "End of file" << std::endl;
+ }
+ return (elements_read == 1);
+}
+
+bool DiskFileDescriptor::WriteBytes( const void* src, int num_bytes ) {
+ return (fwrite(src, num_bytes, 1, file_) == 1);
+}
+
+bool DiskFileDescriptor::Open( const std::string& abs_path, const std::string& mode ) {
+ file_ = my_fopen(abs_path.c_str(), mode.c_str());
+ return (file_ != NULL);
+}
+
+bool DiskFileDescriptor::Close() {
+ int ret = fclose(file_);
+ file_ = 0;
+ return ret == 0;
+}
+
+int DiskFileDescriptor::GetSize() {
+ int index = ftell(file_);
+ fseek (file_, 0, SEEK_END);
+ int file_size = ftell(file_);
+ fseek (file_ , index , SEEK_SET );
+ return file_size;
+}
+
+DiskFileDescriptor::~DiskFileDescriptor() {
+ if(file_){
+ fclose(file_);
+ }
+}
+
+int DiskFileDescriptor::ReadBytesPartial(void* dst, int num_bytes) {
+ return fread(dst, 1, num_bytes, file_);
+}
+
+bool MemReadFileDescriptor::ReadBytes( void* dst, int num_bytes ) {
+ if(!ptr_){
+ return false;
+ }
+ if(num_bytes == 0){
+ return true;
+ }
+ memcpy(dst, (char*)ptr_ + index_, num_bytes);
+ index_ += num_bytes;
+ return true; // memcpy performs no error checking
+}
+
+bool MemWriteFileDescriptor::WriteBytes( const void* src, int num_bytes ) {
+ if(num_bytes == 0){
+ return true;
+ }
+ vec_.resize(vec_.size() + num_bytes);
+ uint8_t *dst = &vec_[vec_.size()-num_bytes];
+ memcpy(dst, src, num_bytes);
+ return true; // memcpy performs no error checking
+}
diff --git a/Source/Internal/file_descriptor.h b/Source/Internal/file_descriptor.h
new file mode 100644
index 00000000..60e21884
--- /dev/null
+++ b/Source/Internal/file_descriptor.h
@@ -0,0 +1,82 @@
+//-----------------------------------------------------------------------------
+// Name: file_descriptor.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <string>
+#include <cstdio>
+#include <vector>
+#include <stdint.h>
+
+class FileDescriptor {
+public:
+ // Read num_bytes bytes into memory at dst
+ // Returns false on error
+ virtual bool ReadBytes(void* dst, int num_bytes)=0;
+ // Write num_bytes bytes into file from src
+ // Returns false on error
+ virtual bool WriteBytes(const void* src, int num_bytes)=0;
+
+ virtual ~FileDescriptor() {}
+};
+
+class DiskFileDescriptor : public FileDescriptor {
+public:
+ // See FileDescriptor::ReadBytes
+ virtual bool ReadBytes(void* dst, int num_bytes);
+ int ReadBytesPartial(void* dst, int num_bytes);
+ // See FileDescriptor::WriteBytes
+ virtual bool WriteBytes(const void* src, int num_bytes);
+ // Open file at filename (UTF8 path) with given mode (e.g. r,w,rb,wb)
+ bool Open(const std::string& filename, const std::string& mode);
+ bool Close();
+ int GetSize();
+ DiskFileDescriptor():file_(NULL){}
+ ~DiskFileDescriptor();
+private:
+ FILE* file_;
+};
+
+class MemReadFileDescriptor : public FileDescriptor {
+public:
+ // See FileDescriptor::ReadBytes
+ virtual bool ReadBytes(void* dst, int num_bytes);
+ // Cannot write to this descriptor
+ virtual bool WriteBytes(const void* src, int num_bytes){return false;}
+ MemReadFileDescriptor(void* ptr = NULL)
+ :ptr_(ptr), index_(0) {}
+private:
+ void *ptr_;
+ int index_;
+};
+
+class MemWriteFileDescriptor : public FileDescriptor {
+public:
+ // Cannot read from this descriptor
+ virtual bool ReadBytes(void* dst, int num_bytes){return false;}
+ // See FileDescriptor::WriteBytes
+ virtual bool WriteBytes(const void* src, int num_bytes);
+ MemWriteFileDescriptor(std::vector<uint8_t> &vec):vec_(vec){}
+private:
+ std::vector<uint8_t> &vec_;
+};
+
diff --git a/Source/Internal/filesystem.cpp b/Source/Internal/filesystem.cpp
new file mode 100644
index 00000000..7262efdb
--- /dev/null
+++ b/Source/Internal/filesystem.cpp
@@ -0,0 +1,1472 @@
+//-----------------------------------------------------------------------------
+// Name: filesystem.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include <Internal/modloading.h>
+#include <Internal/profiler.h>
+#include <Internal/checksum.h>
+#include <Internal/datemodified.h>
+#include <Internal/filesystem.h>
+#include <Internal/common.h>
+#include <Internal/error.h>
+
+#include <Utility/assert.h>
+#include <Utility/strings.h>
+
+#include <Compat/fileio.h>
+#include <Compat/compat.h>
+
+#include <Memory/allocation.h>
+#include <Logging/logdata.h>
+
+#include <string>
+#include <vector>
+#include <sstream>
+#include <cstring>
+#include <iterator>
+#include <set>
+#include <algorithm>
+#include <cerrno>
+
+using std::string;
+using std::endl;
+using std::vector;
+using std::ifstream;
+using std::ios;
+using std::streampos;
+using std::istream_iterator;
+using std::stringstream;
+using std::pair;
+using std::size_t;
+using std::make_pair;
+using std::stringstream;
+using std::set;
+using std::sort;
+
+Paths vanilla_data_paths;
+Paths mod_paths;
+char write_path[kPathSize];
+static bool write_path_set = false;
+
+Paths::Paths() : num_paths(0) {
+
+}
+
+int Paths::AddPath( const char* path ) {
+ RemovePath(path);
+ string withTrailing = SanitizePath(AssemblePath(string(path),string()));
+ if(num_paths < kMaxPaths) {
+ FormatString(paths[num_paths], kPathSize, "%s", withTrailing.c_str());
+ mod_path_ids[num_paths] = CoreGameModID;
+ ++num_paths;
+ return num_paths-1;
+ } else {
+
+ LOGE << "All preallocated filesystem paths are utilized, this isn't intended to occur, contact developer." << endl;
+
+ LOGE << "Printing all currently registered paths, are there duplicates, empty or invalid entries?" << endl;
+ for( int i = 0; i < kMaxPaths; i++ ) {
+ LOGE << i << ":" << paths[i] << endl;
+ }
+
+ LOG_ASSERT(false);
+
+ return -1;
+ }
+}
+
+bool Paths::AddModPath( const char* path, ModID modid ) {
+ string withTrailing = SanitizePath(AssemblePath(string(path),string()));
+ int id = AddPath(withTrailing.c_str());
+ if( id >= 0 && id < Paths::kMaxPaths ) {
+ mod_path_ids[id] = modid;
+ return true;
+ } else {
+ LOGE << "Problem loading mod" << endl;
+ return false;
+ }
+}
+
+void Paths::RemovePath( const char* path ) {
+ string withTrailing = SanitizePath(AssemblePath(string(path),string()));
+ bool push_down = false;
+ for( int i = 0; i < num_paths; i++ ) {
+ if( push_down )
+ {
+ memcpy( paths[i-1], paths[i], kPathSize );
+ mod_path_ids[i-1] = mod_path_ids[i];
+ }
+
+ if( strcmp( withTrailing.c_str(), paths[i] ) == 0 )
+ {
+ push_down = true;
+ }
+ }
+
+ //If we found and removed a path by pushing down all after it, we count down.
+ if( push_down )
+ {
+ num_paths--;
+ }
+}
+
+Path FindImagePath( const char* path, PathFlagsBitfield flags, bool is_necessary )
+{
+ Path p;
+
+ strcpy( p.original, path );
+
+ if( FindImagePath(p.original, p.resolved, kPathSize, flags, is_necessary, &p.source) == 0 )
+ {
+ p.valid = true;
+ }
+ else
+ {
+ p.valid = false;
+ }
+
+ return p;
+
+}
+
+Path FindFilePath( const string& path, PathFlagsBitfield flags, bool is_necessary )
+{
+ return FindFilePath( path.c_str(), flags, is_necessary );
+}
+
+Path FindFilePath( const char* path, PathFlagsBitfield flags, bool is_necessary )
+{
+ Path p;
+
+ strcpy( p.original, path );
+
+ if( FindFilePath(p.original, p.resolved, kPathSize, flags, is_necessary, &p.source, &p.mod_source) == 0 )
+ {
+ p.valid = true;
+ }
+ else
+ {
+ p.valid = false;
+ }
+
+ return p;
+}
+
+const char* overgrowth_dds_cache_intro = "OVERGROWTH_DDS_CACHE";
+extern const int overgrowth_dds_cache_version;
+const int overgrowth_dds_cache_version = 1;
+
+//The case for looking for both the normal image and the converted became so common I felt the need for this simplification.
+
+//Currently ignoring the modsource, should set it to the correct source if it's not null TODO
+int FindImagePath( const char* path, char* buf, int buf_size, PathFlagsBitfield flags, bool is_necessary, PathFlags* resulting_path, bool allow_crn, ModID* modsource )
+{
+ const char* fallback = "Data/Textures/error.tga";
+ // We might want a converted image, let's assume it's priority for reasons like performance
+ string dds_converted = string(path) + "_converted.dds";
+ string crn_converted = string(path) + "_converted.crn";
+
+ const int kMaxPaths = 5;
+ char dds_paths[kPathSize * kMaxPaths];
+ char orig_paths[kPathSize * kMaxPaths];
+ PathFlags dds_flags[kMaxPaths];
+ ModID dds_modsources[kMaxPaths];
+ PathFlags orig_flags[kMaxPaths];
+ ModID orig_modsources[kMaxPaths];
+ int num_dds_paths_found = FindFilePaths( dds_converted.c_str(), dds_paths, buf_size, kMaxPaths, flags, false, dds_flags, dds_modsources );
+ if (num_dds_paths_found == 0 && allow_crn) {
+ num_dds_paths_found = FindFilePaths( crn_converted.c_str(), dds_paths, buf_size, kMaxPaths, flags, false, dds_flags, dds_modsources );
+ }
+ int num_orig_paths_found = FindFilePaths( path, orig_paths, buf_size, kMaxPaths, flags, false, orig_flags, orig_modsources );
+ int64_t dds_date_modified[kMaxPaths];
+ int64_t orig_date_modified[kMaxPaths];
+ int64_t latest_dds_modified = -1;
+ int latest_dds_id = -1;
+ int64_t latest_orig_modified = -1;
+ int latest_orig_id = -1;
+ for(int i=0; i<num_dds_paths_found; ++i){
+ dds_date_modified[i] = GetDateModifiedInt64(&dds_paths[i*buf_size]);
+ if(dds_date_modified[i] > latest_dds_modified){
+ latest_dds_modified = dds_date_modified[i];
+ latest_dds_id = i;
+ }
+ }
+ for(int i=0; i<num_orig_paths_found; ++i){
+ orig_date_modified[i] = GetDateModifiedInt64(&orig_paths[i*buf_size]);
+ if(orig_date_modified[i] > latest_orig_modified){
+ latest_orig_modified = orig_date_modified[i];
+ latest_orig_id = i;
+ }
+ }
+
+ if(latest_orig_modified > latest_dds_modified && num_orig_paths_found > 0 ){
+ FormatString(buf, buf_size, "%s", &orig_paths[latest_orig_id * kPathSize]);
+ if(resulting_path) {
+ *resulting_path = orig_flags[latest_orig_id];
+ }
+ if(modsource) {
+ *modsource = orig_modsources[latest_orig_id];
+ }
+ if(num_dds_paths_found > 0){
+ unsigned short checksum = Checksum(&orig_paths[latest_orig_id * kPathSize]);
+ FILE *file = my_fopen(&dds_paths[latest_dds_id * kPathSize], "rb");
+ if(file){
+ int intro_len = strlen(overgrowth_dds_cache_intro);
+ char* intro_check = (char*)OG_MALLOC(intro_len+1);
+ intro_check[intro_len] = 0;
+ int version_check;
+ unsigned short checksum_check;
+ fseek(file, -(intro_len + sizeof(version_check) + sizeof(checksum)), SEEK_END);
+ fread(intro_check, intro_len, 1, file);
+ fread(&version_check, sizeof(version_check), 1, file);
+ fread(&checksum_check, sizeof(checksum), 1, file);
+ fclose(file);
+ if(strcmp(intro_check, overgrowth_dds_cache_intro) == 0 && overgrowth_dds_cache_version == version_check && checksum == checksum_check){
+ FormatString(buf, buf_size, "%s", &dds_paths[latest_dds_id * kPathSize]);
+ if(resulting_path) {
+ *resulting_path = dds_flags[latest_dds_id];
+ }
+ if(modsource) {
+ *modsource = dds_modsources[latest_dds_id];
+ }
+ }
+ OG_FREE(intro_check);
+ }
+ } else {
+
+ }
+ return 0;
+ } else if(latest_dds_modified >= latest_orig_modified && num_dds_paths_found > 0) {
+ FormatString(buf, buf_size, "%s", &dds_paths[latest_dds_id * kPathSize]);
+ if(resulting_path) {
+ *resulting_path = dds_flags[latest_dds_id];
+ }
+ if(modsource) {
+ *modsource = dds_modsources[latest_dds_id];
+ }
+ return 0;
+ /*
+ } else if( strcmp(fallback,path) != 0 ) {
+#ifndef NO_ERR
+ char err[kPathSize];
+ FormatString(err, kPathSize, "Could not find %s, loading fallback", path);
+ if( is_necessary )
+ {
+ DisplayError("Error", err);
+ }
+ else
+ {
+ LOGW << path << endl;
+ }
+#endif
+ FindImagePath( fallback, buf, buf_size, flags, true, resulting_path, modsource );
+ return -1;
+ */
+ } else {
+ return -1;
+ }
+}
+
+int FindFilePath(const char* path, char* buf, int buf_size, PathFlagsBitfield flags, bool is_necessary, PathFlags* resulting_path, ModID* modsource) {
+ PROFILER_ZONE(g_profiler_ctx, "FindFilePath");
+ if(flags & kModPaths){
+ for(int i=0; i<mod_paths.num_paths; ++i){
+ AssemblePath( mod_paths.paths[i], path, buf, buf_size );
+ caseCorrect(buf);
+ if(CheckFileAccess(buf)){
+ if( resulting_path ) {
+ *resulting_path = kModPaths;
+ }
+
+ if( modsource ) {
+ *modsource = mod_paths.mod_path_ids[i];
+ }
+ return 0;
+ }
+ }
+ }
+ if(flags & kDataPaths){
+ for(int i=0; i<vanilla_data_paths.num_paths; ++i){
+ AssemblePath( vanilla_data_paths.paths[i], path, buf, buf_size );
+ caseCorrect(buf);
+ if(CheckFileAccess(buf)){
+ if( resulting_path ) {
+ *resulting_path = kDataPaths;
+ }
+
+ if( modsource ) {
+ *modsource = vanilla_data_paths.mod_path_ids[i];
+ }
+ return 0;
+ }
+ }
+ }
+ if(flags & kModWriteDirs){
+ for(int i=0; i<mod_paths.num_paths; ++i) {
+ string mod_write_path = GetWritePath(mod_paths.mod_path_ids[i]);
+ AssemblePath( mod_write_path.c_str(), path, buf, buf_size );
+ // Not case-correcting write dir for now, since it might interact badly with the many uses of the writedir not using FindFilePath
+ if(CheckFileAccess(buf)) {
+ if( resulting_path ) {
+ *resulting_path = kModWriteDirs;
+ }
+
+ if( modsource ) {
+ *modsource = mod_paths.mod_path_ids[i];
+ }
+ return 0;
+ }
+ }
+ }
+ if(flags & kWriteDir){
+ AssemblePath( write_path, path, buf, buf_size );
+ // Not case-correcting write dir for now, since it might interact badly with the many uses of the writedir not using FindFilePath
+ if(CheckFileAccess(buf)){
+ if( resulting_path )
+ {
+ *resulting_path = kWriteDir;
+ }
+
+ if( modsource ) {
+ *modsource = CoreGameModID;
+ }
+ return 0;
+ }
+ }
+
+ //Check if the path given "just works" assuming that it could be an absolute path
+ if(flags & kAbsPath) {
+ strncpy(buf, path, buf_size);
+ caseCorrect(buf);
+ if( CheckFileAccess(buf) )
+ {
+ //LOGW << "Got file via absolute path: " << path << ", was this intended?" << endl;
+ if( resulting_path )
+ {
+ *resulting_path = kAbsPath;
+ }
+ if( modsource ) {
+ *modsource = CoreGameModID;
+ }
+ return 0;
+ }
+ }
+
+ if( is_necessary )
+ {
+ LOGE << "Unable to find \"" << path << "\" in any of the requested locations." << endl;
+ }
+
+ if( resulting_path )
+ {
+ *resulting_path = kNoPath;
+ }
+
+ return -1;
+}
+
+
+int FindFilePaths(const char* path, char* bufs, int buf_size, int num_bufs, PathFlagsBitfield flags, bool is_necessary, PathFlags* resulting_paths, ModID* sourceids) {
+ int num_paths_found = 0;
+ char* buf = bufs;
+ if(flags & kModPaths){
+ for(int i=0; i<mod_paths.num_paths; ++i){
+ AssemblePath( mod_paths.paths[i], path, buf, buf_size );
+ caseCorrect(buf);
+ if(CheckFileAccess(buf)){
+ if( resulting_paths )
+ {
+ resulting_paths[num_paths_found] = kModPaths;
+ }
+ if( sourceids )
+ {
+ sourceids[num_paths_found] = mod_paths.mod_path_ids[i];
+ }
+ ++num_paths_found;
+ if(num_paths_found >= num_bufs){
+ return num_paths_found;
+ }
+ buf += buf_size;
+ }
+ }
+ }
+ if(flags & kDataPaths){
+ for(int i=0; i<vanilla_data_paths.num_paths; ++i){
+ AssemblePath( vanilla_data_paths.paths[i], path, buf, buf_size );
+ caseCorrect(buf);
+ if(CheckFileAccess(buf)){
+ if( resulting_paths )
+ {
+ resulting_paths[num_paths_found] = kDataPaths;
+ }
+ if( sourceids )
+ {
+ sourceids[num_paths_found] = vanilla_data_paths.mod_path_ids[i];
+ }
+ ++num_paths_found;
+ if(num_paths_found >= num_bufs){
+ return num_paths_found;
+ }
+ buf += buf_size;
+ }
+ }
+ }
+ if(flags & kModWriteDirs) {
+ for(int i=0; i<mod_paths.num_paths; ++i) {
+ string mod_write_path = GetWritePath(mod_paths.mod_path_ids[i]);
+ AssemblePath( mod_write_path.c_str(), path, buf, buf_size );
+ // Not case-correcting write dir for now, since it might interact badly with the many uses of the writedir not using FindFilePath
+ if(CheckFileAccess(buf)) {
+ if(resulting_paths) {
+ resulting_paths[num_paths_found] = kModWriteDirs;
+ }
+ if( sourceids )
+ {
+ sourceids[num_paths_found] = mod_paths.mod_path_ids[i];
+ }
+ ++num_paths_found;
+ if(num_paths_found >= num_bufs) {
+ return num_paths_found;
+ }
+ buf += buf_size;
+ }
+ }
+ }
+ if(flags & kWriteDir){
+ AssemblePath( write_path, path, buf, buf_size );
+ // Not case-correcting write dir for now, since it might interact badly with the many uses of the writedir not using FindFilePath
+ if(CheckFileAccess(buf)){
+ if( resulting_paths )
+ {
+ resulting_paths[num_paths_found] = kWriteDir;
+ }
+
+ if( sourceids )
+ {
+ sourceids[num_paths_found] = CoreGameModID;
+ }
+
+ ++num_paths_found;
+ if(num_paths_found >= num_bufs){
+ return num_paths_found;
+ }
+ buf += buf_size;
+ }
+ }
+
+ //Check if the path given "just works" assuming that it could be an absolute path
+ if(flags & kAbsPath){
+ if( CheckFileAccess(path) )
+ {
+ strncpy(buf, path, buf_size);
+ if(num_paths_found == 0){
+ //LOGW << "Got file via absolute path: " << path << ", was this intended?" << endl;
+ }
+ if( resulting_paths )
+ {
+ resulting_paths[num_paths_found] = kAbsPath;
+ }
+ if( sourceids )
+ {
+ sourceids[num_paths_found] = CoreGameModID;
+ }
+ ++num_paths_found;
+ if(num_paths_found >= num_bufs){
+ return num_paths_found;
+ }
+ buf += buf_size;
+ }
+ }
+
+ if( is_necessary && num_paths_found == 0 )
+ {
+ LOGE << "Unable to find \"" << path << "\" in any of the requested locations." << endl;
+ }
+
+ return num_paths_found;
+}
+
+void AddPath(const char* path, PathFlags type) {
+ //LOGI << "Adding path " << path << endl;
+ string withTrailing = AssemblePath(string(path),string());
+ switch(type){
+ case kWriteDir:
+ LOGI << "Adding write path " << withTrailing << endl;
+ FormatString(write_path, kPathSize, "%s", withTrailing.c_str());
+ write_path_set = true;
+ break;
+ case kModWriteDirs:
+ LOGI << "It's invalid to add specific write dir for mods, it's relative to main write path."
+ << endl;
+ break;
+ case kDataPaths:
+ LOGI << "Adding data path " << withTrailing << endl;
+ vanilla_data_paths.AddPath(withTrailing.c_str());
+ break;
+ case kModPaths:
+ LOG_ASSERT(false);
+ LOGE << "Can't add mod path with this function " << endl;
+ break;
+ case kAbsPath:
+ LOGW << "This can't be done" << endl;
+ break;
+ case kAnyPath:
+ LOGW << "This can't be done" << endl;
+ break;
+ case kNoPath:
+ LOGE << "Error, invalid path set" << endl;
+ break;
+ }
+}
+
+bool AddModPath( const char* path, ModID modid ) {
+ return mod_paths.AddModPath(path, modid);
+}
+
+
+void RemovePath( const char* path, PathFlags type )
+{
+ if( type & kDataPaths )
+ {
+ vanilla_data_paths.RemovePath(path);
+ }
+
+ if( type & kModPaths )
+ {
+ mod_paths.RemovePath(path);
+ }
+
+}
+
+const char* GetDataPath( int i ) {
+ if( i < vanilla_data_paths.num_paths ) {
+ return vanilla_data_paths.paths[i];
+ } else {
+ return "";
+ }
+}
+
+string GetWritePath( const ModID& id ) {
+#if defined(OGDA) || defined(OG_WORKER)
+ return string(write_path);
+#else
+ if( id.Valid() ) {
+ if( id != CoreGameModID ) {
+ ModInstance *modi = ModLoading::Instance().GetMod(id);
+ if( modi ) {
+ return AssemblePath(string(write_path),"ModsDataCache",string(modi->id)) + "/";
+ } else {
+ LOGF << "Got valid mod id to non existant mod" << endl;
+ }
+ }
+ } else {
+ LOGF << "Got invalid mod id" << endl;
+ }
+ return string(write_path);
+#endif
+}
+
+void GetWritePath( const ModID& id, char * buf, size_t len ) {
+ //Fastpath to make the coredump routine a little more robust.
+ if( id == CoreGameModID ) {
+ strscpy(buf,write_path,len);
+ } else {
+ string wp = GetWritePath(id);
+ strscpy(buf,wp.c_str(),len);
+ }
+}
+
+bool FileExists(const Path& path) {
+ return path.isValid() && FileExists( path.resolved, kAbsPath );
+}
+
+bool FileExists(const string& path, PathFlagsBitfield flags) {
+ return FileExists( path.c_str(), flags );
+}
+
+void CreateDirsFromPath(const string& base, const string & path) {
+
+ auto temp = SplitPathFileName(path);
+
+ // all dirs in order of occurance
+ vector<string> allDirs;
+ string pathWithoutFile = temp.first;
+
+ size_t pos = pathWithoutFile.find_first_of("\//");
+ while (pos != string::npos) {
+ allDirs.push_back(pathWithoutFile.substr(0, pos));
+ pathWithoutFile = pathWithoutFile.substr(pos + 1, path.size());
+ pos = pathWithoutFile.find_first_of("\//");
+ }
+
+ allDirs.push_back(pathWithoutFile);
+ string writtenPath = base;
+ for (auto& it : allDirs) {
+ CreateParentDirs(writtenPath + it + string("/"));
+ writtenPath += it + string("/");
+ }
+}
+
+bool FileExists(const char* path, PathFlagsBitfield flags) {
+ char temp_path[kPathSize];
+ if(FindFilePath(path, temp_path, kPathSize, flags, false) != -1) {
+ if( isFile( temp_path )) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+}
+
+string GetConfigPath()
+{
+ char config_path[kPathSize];
+ GetConfigPath(config_path, kPathSize);
+ return string(config_path);
+}
+
+void GetConfigPath( char* buf, size_t len )
+{
+ FormatString(buf, len, "%sData/config.txt", GetWritePath(CoreGameModID).c_str());
+}
+
+string GetLogfilePath( )
+{
+ char logfile_path[kPathSize];
+ GetLogfilePath(logfile_path, kPathSize);
+ return string(logfile_path);
+}
+
+void GetLogfilePath( char * buf, size_t size )
+{
+ FormatString( buf, size, "%slogfile.txt", GetWritePath(CoreGameModID).c_str());
+}
+
+void GetScenGraphDumpPath( char* buf, size_t size )
+{
+ FormatString( buf, size, "%sscene_dump.txt", GetWritePath(CoreGameModID).c_str());
+}
+
+void GetASDumpPath( char* buf, size_t size )
+{
+ FormatString( buf, size, "%sas_dump.txt", GetWritePath(CoreGameModID).c_str());
+}
+
+string GetHWReportPath()
+{
+ char hwreport_path[kPathSize];
+ GetHWReportPath( hwreport_path, kPathSize );
+ return string(hwreport_path);
+}
+
+void GetHWReportPath( char * buf, size_t size )
+{
+ FormatString(buf, size, "%sData/hwreport.txt", GetWritePath(CoreGameModID).c_str());
+}
+
+bool has_suffix(const string &str, const string &suffix)
+{
+ return str.size() >= suffix.size() &&
+ str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
+}
+
+string GuessMime( string path )
+{
+ if( has_suffix( path, ".html" ) || has_suffix( path, ".htm" ) )
+ {
+ return "text/html";
+ }
+
+ if( has_suffix( path, ".js" ) )
+ {
+ return "application/javascript";
+ }
+
+ if( has_suffix( path, ".jpg" ) || has_suffix( path, ".jpeg" ) )
+ {
+ return "image/jpeg";
+ }
+
+ if( has_suffix( path, ".png" ) )
+ {
+ return "image/png";
+ }
+
+ if( has_suffix( path, ".gif" ) )
+ {
+ return "image/gif";
+ }
+
+ if( has_suffix( path, ".json" ) )
+ {
+ return "application/json";
+ }
+
+ if( has_suffix( path, ".ttf" ) )
+ {
+ return "application/x-font-ttf";
+ }
+
+ if( has_suffix( path, ".css" ) )
+ {
+ return "text/css";
+ }
+
+ if( has_suffix( path, ".svg" ) )
+ {
+ return "image/svg+xml";
+ }
+
+ LOGE << "Unknown mime type for " << path << endl;
+ return "text/plain";
+
+}
+
+string StripDriveletter( string path )
+{
+ if( isupper(path[0]) && path[1] == ':' )
+ {
+ return path.substr(4);
+ }
+ return path;
+}
+
+vector<unsigned char> readFile(const char* filename)
+{
+ /*
+ TODO: Fix this faster variant
+ vector<unsigned char> vec;
+ ifstream in(filename, ios::in | ios::binary);
+ if (in)
+ {
+ string contents;
+ in.seekg(0, ios::end);
+ vec.resize(in.tellg());
+ in.seekg(0, ios::beg);
+ in.read(&contents[0], contents.size());
+ in.close();
+ }
+ else
+ {
+ LOGF << "Unable to read file" << filename << endl;
+ }
+ return vec;
+ */
+
+ // open the file:
+ vector<unsigned char> vec;
+
+ if( isFile( filename ) )
+ {
+ ifstream file;
+ my_ifstream_open(file, filename, ios::binary);
+
+ if( file.good() )
+ {
+ // Stop eating new lines in binary mode!!!
+ file.unsetf(ios::skipws);
+
+ // get its size:
+ streampos fileSize;
+
+ file.seekg(0, ios::end);
+ fileSize = file.tellg();
+ file.seekg(0, ios::beg);
+
+ // reserve capacity
+ vec.reserve(fileSize);
+
+ // read the data:
+ vec.insert(vec.begin(),
+ istream_iterator<unsigned char>(file),
+ istream_iterator<unsigned char>());
+ }
+ else
+ {
+ LOGE << "Unable to read file " << filename << endl;
+ }
+ file.close();
+ }
+
+ return vec;
+}
+
+void CreateParentDirs( const string& abs_path )
+{
+ CreateParentDirs(abs_path.c_str());
+}
+
+bool CheckFileAccess(const char* path){
+ return checkFileAccess(path);
+}
+
+void CreateParentDirs(const char* abs_path) {
+ createParentDirs(abs_path);
+}
+
+void GenerateManifest( const char* path, vector<string>& files )
+{
+ getDeepManifest( path, "", files );
+}
+
+const string GetPathFlagsStr( PathFlagsBitfield f )
+{
+ stringstream ss;
+ if( f & kDataPaths ) {
+ ss << "kDataPaths ";
+ }
+ if( f & kWriteDir ) {
+ ss << "kWriteDir ";
+ }
+ if( f & kModPaths ) {
+ ss << "kModPaths ";
+ }
+ if( f & kModWriteDirs ) {
+ ss << "kModWriteDirs ";
+ }
+ if( f & kAbsPath ) {
+ ss << "kAbsPath ";
+ }
+ return ss.str();
+}
+
+string AssemblePath( const char* first, const char* second )
+{
+ return AssemblePath(string(first), string(second));
+}
+
+void AssemblePath( const char* first, const char* second, char* out, size_t outsize )
+{
+ if ( strlen(first) == 0 )
+ {
+ LOGE << "AssemblePath was provided with a zero length string" << endl;
+ FormatString(out, outsize, "%s", second);
+ return;
+ }
+
+ if( first[strlen(first)-1] == '/' || second[0] == '/' )
+ {
+ FormatString(out, outsize, "%s%s", first, second);
+ }
+ else
+ {
+ FormatString(out, outsize, "%s/%s", first, second);
+ }
+}
+
+string AssemblePath( const string& first, const string& second )
+{
+ if ( first.empty() )
+ {
+ LOGW << "AssemblePath was provided with a zero length string" << endl;
+ return second;
+ }
+
+ if( first[first.length()-1] == '/' || second[0] == '/' )
+ {
+ return first + second;
+ }
+ else
+ {
+ return first + "/" + second;
+ }
+}
+
+string AssemblePath( const string& first, const string& second, const string& third )
+{
+ return AssemblePath(AssemblePath(first,second),third);
+}
+
+pair< string, string > SplitPathFileName( char* fullPath )
+{
+ string fP( fullPath );
+ return SplitPathFileName( fP );
+}
+
+pair< string, string > SplitPathFileName( string const& fullPath )
+{
+ size_t pos = fullPath.find_last_of("/\\");
+
+ if( pos == string::npos )
+ {
+ return make_pair( "", fullPath );
+ }
+ else
+ {
+ return make_pair( fullPath.substr(0,pos), fullPath.substr(pos+1) );
+ }
+}
+
+int CheckWritePermissions(const char* dir)
+{
+ char buffer[kPathSize];
+ sprintf(buffer, "%s/ogtestfile.tmp", dir);
+ NormalizePathSeparators(buffer);
+ int error = createfile(buffer);
+ if(error == 0) {
+ deletefile(buffer);
+ return 0;
+ } else {
+ return error;
+ }
+}
+
+int copyfile( const string& source, const string& dest )
+{
+ return os_copyfile( source.c_str(), dest.c_str() );
+}
+
+int copyfile( const char* source, const char* dest )
+{
+ return os_copyfile( source, dest );
+}
+
+int movefile( const char* source, const char* dest )
+{
+ return os_movefile(source, dest);
+}
+
+int deletefile(const char* filename)
+{
+ return os_deletefile(filename);
+}
+
+int createfile(const char* filename)
+{
+ return os_createfile(filename);
+}
+
+int fileexists(const char* filename)
+{
+ return os_fileexists(filename);
+}
+
+string DumpIntoFile( const void *buf, size_t nbyte )
+{
+ return dumpIntoFile( buf, nbyte );
+}
+
+string CaseCorrect(const string& v)
+{
+ char new_path[kPathSize];
+ strncpy(new_path,v.c_str(),kPathSize);
+ caseCorrect(new_path);
+ return string(new_path);
+}
+
+string ApplicationPathSeparators( const string& v )
+{
+ size_t slash_pos = 0;
+ string res = v;
+
+ while( (slash_pos = res.find_first_of("\\")) != string::npos )
+ {
+ res[slash_pos] = '/';
+ }
+
+ return res;
+}
+
+void ApplicationPathSeparators( char* string )
+{
+ for( int i = 0; string[i] != '\0'; i++ )
+ {
+ if( string[i] == '\\' )
+ string[i] = '/';
+ }
+}
+
+string NormalizePathSeparators( const string& v)
+{
+ size_t slash_pos = 0;
+ string res = v;
+#ifdef WIN32
+ while( (slash_pos = res.find_first_of("/")) != string::npos )
+ {
+ res[slash_pos] = '\\';
+ }
+#else
+ while( (slash_pos = res.find_first_of("\\")) != string::npos )
+ {
+ res[slash_pos] = '/';
+ }
+#endif
+ return res;
+}
+
+void NormalizePathSeparators( char* string )
+{
+ for( int i = 0; string[i] != '\0'; i++ )
+ {
+#ifdef WIN32
+ if( string[i] == '/' )
+ string[i] = '\\';
+#else
+ if( string[i] == '\\' )
+ string[i] = '/';
+#endif
+ }
+}
+
+bool AreSame( const char* path1, const char* path2 )
+{
+ return areSame(path1,path2);
+}
+
+Path FindShortestPath2(const string& p1)
+{
+ string v = SanitizePath(p1);
+
+ Path match;
+ string current_path = v;
+ size_t next_slash = 0;
+ int path = kAnyPath;
+ bool try_again = false;
+
+ do
+ {
+ try_again = false;
+ Path new_path = FindFilePath( current_path.c_str(), path, false );
+ if( new_path.isValid() )
+ {
+ if( AreSame(new_path.GetFullPath(), v.c_str() ) )
+ {
+ match = new_path;
+ LOGI << "Found a match for " << v << " that is " << current_path << " in " << new_path << endl;
+ }
+ else
+ {
+ try_again = true;
+ path &= ~new_path.source;
+ }
+ }
+
+ if(!try_again)
+ {
+ next_slash = current_path.find_first_of("/");
+ if( next_slash != string::npos )
+ {
+ current_path = current_path.substr(next_slash+1);
+ }
+ }
+ }
+ while(next_slash != string::npos);
+
+ LOGI << "Chose " << match << endl;
+ return match;
+}
+
+string FindShortestPath(const string& string)
+{
+ return FindShortestPath2(string).GetOriginalPathStr();
+}
+
+static bool string_sort_by_length_r( string a, string b )
+{
+ return a.length() > b.length();
+}
+
+/*
+ * Smart-ish cache clearer, removes files of known types from the local write dir. Intended to function as a sort of sanitation fallback.
+ */
+void ClearCache( bool dry_run )
+{
+ if( write_path_set == false )
+ {
+ LOGE << "Write path is not set, will not clear cache" << endl;
+ return;
+ }
+
+ if( !dry_run )
+ {
+ LOGI << "Clearing Cache Data (For Real)..." << endl;
+ }
+ else
+ {
+ LOGI << "Clearing Cache Data (Dry Run)..." << endl;
+ }
+
+ vector<ModID> modids;
+#if !(defined(OGDA) || defined(OG_WORKER))
+ modids = ModLoading::Instance().GetModsSid();
+#endif
+ modids.push_back(CoreGameModID);
+ for( unsigned i = 0; i < modids.size(); i++ )
+ {
+ stringstream write_data_path;
+ write_data_path << GetWritePath(modids[i]);
+ write_data_path << "Data/";
+
+ vector<string> write_dir_files;
+
+ vector<pair<string,string> > exclude_with_ending;
+
+ exclude_with_ending.push_back(pair<string,string>("Mods",""));
+
+ vector<pair<string,string> > remove_with_ending;
+ //Clearly cache files
+ remove_with_ending.push_back(pair<string,string>("",".mcache"));
+ remove_with_ending.push_back(pair<string,string>("",".cache"));
+ remove_with_ending.push_back(pair<string,string>("",".lod_cache"));
+ remove_with_ending.push_back(pair<string,string>("",".tga_image_sample_cache"));
+ remove_with_ending.push_back(pair<string,string>("",".tga_avg_color_cache"));
+ remove_with_ending.push_back(pair<string,string>("",".png_image_sample_cache"));
+ remove_with_ending.push_back(pair<string,string>("",".png_hmcache"));
+ remove_with_ending.push_back(pair<string,string>("",".png_hmcache_scaled"));
+
+ //Probably cache files
+ remove_with_ending.push_back(pair<string,string>("",".tga_converted.dds"));
+ remove_with_ending.push_back(pair<string,string>("",".png_converted.dds"));
+ remove_with_ending.push_back(pair<string,string>("",".jpg_converted.dds"));
+
+ //Specific subcategories
+ remove_with_ending.push_back(pair<string,string>("Prototypes","weights.png"));
+ remove_with_ending.push_back(pair<string,string>("Textures","weights.png"));
+
+ remove_with_ending.push_back(pair<string,string>("Prototypes","cube.dds"));
+ remove_with_ending.push_back(pair<string,string>("Textures","cube.dds"));
+
+ remove_with_ending.push_back(pair<string,string>("Prototypes","cube_blur.dds"));
+ remove_with_ending.push_back(pair<string,string>("Textures","cube_blur.dds"));
+
+ remove_with_ending.push_back(pair<string,string>("Prototypes",".png_normal.png"));
+ remove_with_ending.push_back(pair<string,string>("Textures",".png_normal.png"));
+
+ remove_with_ending.push_back(pair<string,string>("Prototypes",".png.obj"));
+ remove_with_ending.push_back(pair<string,string>("Textures",".png.obj"));
+
+ remove_with_ending.push_back(pair<string,string>("ScriptBytecode",".bytecode"));
+
+ //Older level cache files.
+ remove_with_ending.push_back(pair<string,string>("Levels",".nav"));
+ remove_with_ending.push_back(pair<string,string>("Levels",".nav.obj"));
+ remove_with_ending.push_back(pair<string,string>("Levels",".nav.xml"));
+
+ //Newer cache files
+ remove_with_ending.push_back(pair<string,string>("LevelNavmeshes",".nav"));
+ remove_with_ending.push_back(pair<string,string>("LevelNavmeshes",".nav.obj"));
+ remove_with_ending.push_back(pair<string,string>("LevelNavmeshes",".nav.xml"));
+
+ remove_with_ending.push_back(pair<string,string>("Temp","navmesh.obj"));
+ remove_with_ending.push_back(pair<string,string>("Temp","terrainlow.obj"));
+
+ remove_with_ending.push_back(pair<string,string>("Models",".hull"));
+
+ remove_with_ending.push_back(pair<string,string>("BattleJSONDumps",".json"));
+
+ remove_with_ending.push_back(pair<string,string>("CompiledShaders",".frag"));
+ remove_with_ending.push_back(pair<string,string>("CompiledShaders",".vert"));
+ remove_with_ending.push_back(pair<string,string>("CompiledShaders",".geom"));
+ remove_with_ending.push_back(pair<string,string>("CompiledShaders",".glsl"));
+
+ GenerateManifest( write_data_path.str().c_str(), write_dir_files );
+
+ vector<string> keep_files;
+ vector<string> remove_files;
+
+ for( vector<string>::iterator pit = write_dir_files.begin();
+ pit != write_dir_files.end();
+ pit++ )
+ {
+ bool found = false;
+ for( vector<pair<string,string> >::iterator fit = remove_with_ending.begin();
+ fit != remove_with_ending.end();
+ fit++ )
+ {
+ if( hasBeginning( *pit, fit->first) && hasEnding( *pit, fit->second ) )
+ {
+ found = true;
+ }
+ }
+
+ if( found )
+ {
+ /* Second pass with exclusion, if there's one match, regret inclusion, exclusion overrides */
+ for( vector<pair<string,string> >::iterator fit = exclude_with_ending.begin();
+ fit != exclude_with_ending.end();
+ fit++ )
+ {
+ if( hasBeginning( *pit, fit->first) && ( fit->second.empty() || hasEnding( *pit, fit->second ) ) )
+ {
+ found = false;
+ }
+ }
+ }
+
+ if( found )
+ {
+ remove_files.push_back( *pit );
+ }
+ else
+ {
+ keep_files.push_back( *pit );
+ }
+ }
+
+ for( unsigned int i = 0; i < remove_files.size(); i++ )
+ {
+ string path = write_data_path.str() + remove_files[i];
+ LOGI << "Removing: " << path << endl;
+ if( dry_run == false )
+ {
+ if( deletefile(path.c_str()) != 0 )
+ {
+ LOGE << "Error removing: " << path << " reason:" << strerror(errno) << endl;
+ }
+ }
+ }
+
+ //Now we want to remove all the empty folders resulting from this purge.
+ if( dry_run == false )
+ {
+ //Create a set with all unique folder paths
+ set<string> folder_paths;
+ for( unsigned int i = 0; i < remove_files.size(); i++ )
+ {
+ string base = SplitPathFileName( remove_files[i] ).first;
+
+ while( base.empty() == false )
+ {
+ folder_paths.insert( base );
+ base = SplitPathFileName( base ).first;
+ }
+ }
+
+ vector<string> empty_folder_paths;
+ set<string>::iterator folder_path_it = folder_paths.begin();
+ for( ;folder_path_it != folder_paths.end(); folder_path_it++ )
+ {
+ vector<string> temp_l_1;
+ GenerateManifest((write_data_path.str()+(*folder_path_it)).c_str(), temp_l_1);
+
+ if( temp_l_1.size() == 0 )
+ {
+ empty_folder_paths.push_back(*folder_path_it);
+ }
+ }
+
+ sort(empty_folder_paths.begin(), empty_folder_paths.end(), string_sort_by_length_r );
+
+ for( unsigned i = 0; i < empty_folder_paths.size(); i++ )
+ {
+ string path = write_data_path.str() + empty_folder_paths[i];
+ LOGI << "Removing Empty Folder:" << path << endl;
+ if( deletefile(path.c_str()) != 0 )
+ {
+ LOGE << "Error removing: " << path << " reason:" << strerror(errno) << endl;
+
+ }
+ }
+ }
+
+ for( unsigned int i = 0; i < keep_files.size(); i++ )
+ {
+ string path = write_data_path.str() + keep_files[i];
+ LOGI << "Keeping: " << path << endl;
+ }
+ }
+}
+
+string GetMorphPath(const string &base_model, const string &morph){
+ return base_model.substr(0, base_model.size()-4) + "_" + morph + ".obj";
+}
+
+bool IsFile(Path& p) {
+ return isFile(p.GetAbsPathStr().c_str());
+}
+
+bool IsImageFile(Path &image) {
+ static const char* ending_arr[] = {
+ ".png",
+ ".dds",
+ ".crn",
+ ".jpg",
+ ".jpeg",
+ ".PNG",
+ ".JPG",
+ ".JPEG",
+ ".DDS",
+ ".CRN",
+ NULL
+ };
+
+ bool has_match = false;
+ const char * str = image.GetOriginalPath();
+ unsigned c = 0;
+ while( ending_arr[c] != NULL ) {
+ if( endswith(str,ending_arr[c]) ){
+ has_match = true;
+ break;
+ }
+ c++;
+ }
+
+ return has_match && IsFile(image);
+}
+
+
+string SanitizePath( const string& path ) {
+ return SanitizePath(path.c_str());
+}
+
+string SanitizePath( const char* path ) {
+ if( path == NULL ) {
+ LOGE << "Told to sanitize null pointer path, returning empty" << endl;
+ return string();
+ } else if(IsPathSane(path)) {
+ return string(path);
+ } else {
+ const string app_path = ApplicationPathSeparators(string(path));
+ string ret;
+ ret.reserve(app_path.size());
+ for( unsigned i = 0; i < app_path.size(); i++ ) {
+ if(i == 0 || ((app_path[i-1] == '/' && app_path[i] == '/') == false)) {
+ ret.push_back(app_path[i]);
+ }
+ }
+ return ret;
+ }
+}
+
+bool IsPathSane( const char* path ) {
+ bool ret = true;
+ for( unsigned i = 0; i < strlen(path); i++ ) {
+ if(path[i] == '\\') {
+ ret = false;
+ }
+ if( i != 0 ) {
+ if( path[i-1] == '/' && path[i] == '/' ) {
+ ret = false;
+ }
+ }
+ }
+ return ret;
+}
+
+string GenerateParallelPath( const char* base, const char* target, const char* postfix, Path asset ) {
+ LOG_ASSERT(strlen(base)>0);
+ LOG_ASSERT(strlen(target)>0);
+ LOG_ASSERT(base[0] != '/');
+ LOG_ASSERT(base[strlen(base)-1] != '/');
+ LOG_ASSERT(target[0] != '/');
+ LOG_ASSERT(target[strlen(target)-1] != '/');
+ LOG_ASSERT(IsPathSane(base));
+ LOG_ASSERT(IsPathSane(target));
+ LOG_ASSERT(asset.isValid());
+
+ Path shortest_path = FindShortestPath2(asset.GetFullPath());
+ string shortest_path_orig_folder = SplitPathFileName(shortest_path.GetOriginalPath()).first;
+ string filename = RemoveFileEnding(SplitPathFileName(shortest_path.GetOriginalPath()).second);
+
+ if( pstrmtch(shortest_path_orig_folder.c_str(), base, strlen(base)) ) {
+ if( shortest_path_orig_folder.size() > strlen(base)+1 ) {
+ if( shortest_path_orig_folder[strlen(base)] == '/' ) {
+ shortest_path_orig_folder = &shortest_path_orig_folder.c_str()[strlen(base)+1];
+ } else {
+ shortest_path_orig_folder = "unknown_source";
+ }
+ } else {
+ shortest_path_orig_folder = "";
+ }
+ } else {
+ shortest_path_orig_folder = "unknown_source";
+ }
+
+ char nav_path[kPathSize];
+ if( shortest_path_orig_folder.size() > 0 ) {
+ FormatString(nav_path, kPathSize, "%s/%s/%s%s", target, shortest_path_orig_folder.c_str(), filename.c_str(), postfix);
+ } else {
+ FormatString(nav_path, kPathSize, "%s/%s%s",target, filename.c_str(), postfix);
+ }
+
+ return string(nav_path);
+}
+
+uint64_t GetFileSize( const Path& file ) {
+ if( file.isValid() ) {
+ FILE* f = my_fopen(file.GetFullPath(),"rb");
+ if(f) {
+ fseek(f,0L,SEEK_END);
+ long size_b = ftell(f);
+ fclose(f);
+
+ return (uint64_t)size_b;
+ } else {
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+}
+
+void CreateBackup(const char* path, int max_backup_count/*= 10*/)
+{
+ pair<string, string> path_file_name = SplitPathFileName(path);
+ path_file_name.first = ApplicationPathSeparators(path_file_name.first);
+
+ char backup_dir[kPathSize];
+ size_t find_index = path_file_name.first.find("Data/");
+ if(find_index != 0) {
+ find_index = path_file_name.first.find("/Data/");
+ }
+ if(find_index != string::npos) {
+ if(find_index == 0) {
+ FormatString(backup_dir, kPathSize, "%sData/backup/%s/", GetWritePath(CoreGameModID).c_str(), path_file_name.first.c_str() + strlen("Data/"));
+ } else {
+ FormatString(backup_dir, kPathSize, "%sData/backup/%s/", GetWritePath(CoreGameModID).c_str(), path_file_name.first.substr(find_index + strlen("/Data/")).c_str());
+ }
+ } else {
+ FormatString(backup_dir, kPathSize, "%s/Data/backup/", GetWritePath(CoreGameModID).c_str());
+ }
+ ApplicationPathSeparators(backup_dir);
+ CreateParentDirs(backup_dir);
+
+ char backup_path[kPathSize];
+ int backup_number = 1;
+ for(int max = max_backup_count; backup_number < max; ++backup_number) {
+ FormatString(backup_path, kPathSize, "%s%s%d", backup_dir, path_file_name.second.c_str(), backup_number);
+ if(fileexists(backup_path) != 0)
+ break;
+ }
+
+ for(int i = backup_number; i > 1; --i) {
+ char target_path[kPathSize];
+ FormatString(backup_path, kPathSize, "%s%s%d", backup_dir, path_file_name.second.c_str(), i - 1);
+ FormatString(target_path, kPathSize, "%s%s%d", backup_dir, path_file_name.second.c_str(), i);
+
+ if(movefile(backup_path, target_path) != 0) {
+ LOGW << "Couldn't move file from " << backup_path << " to " << target_path << " when backing up, trying to copy it instead" << endl;
+ if(copyfile(backup_path, target_path) != 0) {
+ LOGW << "Couldn't copy file from " << backup_path << " to " << target_path << " when backing up, it will be skipped" << endl;
+ }
+ }
+ }
+
+ LOGI << "Saving file backup to " << backup_path << endl;
+ if(movefile(path, backup_path) != 0) {
+ if(copyfile(path, backup_path) != 0) {
+ LOGW << "Couldn't copy file from " << path << " to " << backup_path << " when backing up, it will be skipped" << endl;
+ }
+ }
+}
diff --git a/Source/Internal/filesystem.h b/Source/Internal/filesystem.h
new file mode 100644
index 00000000..21642b46
--- /dev/null
+++ b/Source/Internal/filesystem.h
@@ -0,0 +1,167 @@
+//-----------------------------------------------------------------------------
+// Name: filesystem.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/modid.h>
+#include <Internal/path.h>
+
+#include <Global/global_config.h>
+
+#include <string>
+#include <vector>
+
+// TODO: On Windows these should be DOS-style short paths for UTF compatibility
+struct Paths {
+ Paths();
+ static const int kMaxPaths = 256;
+ char paths[kMaxPaths][kPathSize];
+ ModID mod_path_ids[kMaxPaths];
+ int num_paths;
+ int AddPath(const char* path);
+ bool AddModPath( const char* path, ModID modid );
+ void RemovePath( const char* path );
+};
+
+
+typedef int PathFlagsBitfield;
+
+Path FindImagePath( const char* path, PathFlagsBitfield flags = kAnyPath, bool is_necessary = true );
+Path FindFilePath( const std::string& path, PathFlagsBitfield flags = kAnyPath, bool is_necessary = true );
+Path FindFilePath( const char* path, PathFlagsBitfield flags = kAnyPath, bool is_necessary = true );
+
+int FindImagePath( const char* path, char* buf, int buf_size, PathFlagsBitfield flags, bool is_necessary = true, PathFlags* resulting_path = NULL, bool allow_crn = true, ModID* modsource = NULL);
+int FindFilePath(const char* path, char* buf, int buf_size, PathFlagsBitfield flags, bool is_necessary = true, PathFlags* resulting_path = NULL, ModID* modsource = NULL);
+int FindFilePaths(const char* path, char* bufs, int buf_size, int num_bufs, PathFlagsBitfield flags, bool is_necessary, PathFlags* resulting_paths, ModID* modsourceids );
+void AddPath(const char* path, PathFlags type);
+bool AddModPath(const char* path, ModID modid);
+void RemovePath( const char* path, PathFlags type );
+
+bool CheckFileAccess(const char* path);
+bool FileExists(const Path& path);
+bool FileExists(const char* path, PathFlagsBitfield flags);
+bool FileExists(const std::string& path, PathFlagsBitfield flags);
+void CreateDirsFromPath(const std::string& base, const std::string & path);
+void CreateParentDirs(const char* abs_path);
+void CreateParentDirs(const std::string& abs_path);
+void GenerateManifest(const char* path, std::vector<std::string>& files);
+
+const std::string GetPathFlagsStr( PathFlagsBitfield f );
+
+//Append file to path, adding any missing delimiters.
+void AssemblePath( const char* first, const char* second, char* out, size_t outsize );
+std::string AssemblePath( const char* first, const char* second );
+std::string AssemblePath( const std::string& first, const std::string& second );
+std::string AssemblePath( const std::string& first, const std::string& second, const std::string& third );
+
+const char* GetDataPath( int i );
+
+std::string GetWritePath( const ModID &id );
+void GetWritePath( const ModID &id, char* dest, size_t len );
+
+std::string GetConfigPath();
+void GetConfigPath( char* dest, size_t len );
+
+std::string GetLogfilePath();
+void GetLogfilePath( char* dest, size_t len );
+
+void GetScenGraphDumpPath( char* buf, size_t size );
+
+void GetASDumpPath( char* buf, size_t size );
+
+std::string GetHWReportPath();
+void GetHWReportPath( char* dest, size_t len );
+
+std::string GetVersionXMLPath();
+void GetVersionXMLPath( char* dest, size_t len );
+
+//Will guess mime type based on string, very basic
+std::string GuessMime(std::string path);
+
+std::vector<unsigned char> readFile(const char* filename);
+
+std::string StripDriveletter( std::string path );
+
+// Split a fully qualified filename into path and filename components
+// if path seperator is not found -- assumes all filename
+std::pair< std::string, std::string > SplitPathFileName( char* fullPath );
+std::pair< std::string, std::string > SplitPathFileName( std::string const& fullPath );
+
+extern Paths vanilla_data_paths;
+extern Paths mod_paths;
+extern char write_path[kPathSize];
+
+int CheckWritePermissions(const char* dir);
+
+int copyfile( const std::string& source, const std::string& dest );
+int copyfile( const char* source, const char* dest );
+
+// This would be called DeleteFile, but that is defined in Windows. The rest are just for consistency
+// Generally, using Path is preferred, but there are times when it is not possible to create a Path
+int movefile( const char* source, const char* dest );
+int deletefile(const char* filename);
+int createfile(const char* filename);
+int fileexists(const char* filename);
+
+std::string DumpIntoFile( const void *buf, size_t nbyte );
+
+std::string CaseCorrect(const std::string& string);
+
+//This function will convert all backslashes to frontslashes which is the games internal
+//path separator.
+
+std::string ApplicationPathSeparators( const std::string& string );
+void ApplicationPathSeparators( char * string );
+
+//This function will convert either slashes or backslashes to the pathseparator used by the system
+// Unix-likes: slash
+// Windows: backslash
+// This function is used before paths are sent to system functions.
+std::string NormalizePathSeparators( const std::string& string );
+void NormalizePathSeparators( char* string );
+
+bool AreSame(const char* path1, const char* path2);
+
+std::string FindShortestPath(const std::string& string);
+Path FindShortestPath2(const std::string& string);
+
+void ClearCache( bool dry_run );
+
+std::string GetMorphPath(const std::string &base_model, const std::string &morph);
+
+bool IsFile(Path& path);
+bool IsImageFile(Path& path);
+
+/*
+ * This function will remove duplicate forward slashes in strings and convert it to an internal application format.
+ */
+std::string SanitizePath( const char* path );
+std::string SanitizePath( const std::string& path );
+bool IsPathSane( const char* path );
+
+std::string GenerateParallelPath( const char* base, const char* target, const char* postfix, Path asset );
+
+std::string RemoveFileEnding(std::string in);
+
+uint64_t GetFileSize( const Path& file );
+
+void CreateBackup(const char* path, int max_backup_count = 10);
diff --git a/Source/Internal/hardware_specs.cpp b/Source/Internal/hardware_specs.cpp
new file mode 100644
index 00000000..7cc2bdee
--- /dev/null
+++ b/Source/Internal/hardware_specs.cpp
@@ -0,0 +1,360 @@
+//-----------------------------------------------------------------------------
+// Name: hardware_specs.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "hardware_specs.h"
+
+#include <Internal/datemodified.h>
+#include <Internal/common.h>
+#include <Internal/filesystem.h>
+
+#include <Compat/fileio.h>
+#include <Graphics/graphics.h>
+#include <Logging/logdata.h>
+#include <Memory/allocation.h>
+#include <Utility/strings.h>
+#include <Version/version.h>
+
+#include <algorithm>
+#include <iostream>
+#include <opengl.h>
+
+const unsigned kHardwareReportVersion = 5;
+
+namespace {
+ const int TEMP_STRING_LENGTH = 256;
+}
+
+#include <cctype>
+GLVendor GetGLVendor()
+{
+ const GLubyte *vendor_cstring = glGetString(GL_VENDOR);
+ if( vendor_cstring != NULL ) {
+ std::string vendor_string((const char*)vendor_cstring);
+ std::transform(vendor_string.begin(),
+ vendor_string.end(),
+ vendor_string.begin(),
+ (int(*)(int))std::tolower);
+ if(vendor_string.find("nvidia") != std::string::npos){
+ return _nvidia;
+ } else if(vendor_string.find("ati") != std::string::npos){
+ return _ati;
+ } else if(vendor_string.find("intel") != std::string::npos){
+ return _intel;
+ }
+ } else {
+ LOGE << "Calling glGetString(GL_VENDOR) returned NULL." << std::endl;
+ }
+
+ return _unknown;
+}
+
+#define GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX 0x9047
+
+#define GL_VBO_FREE_MEMORY_ATI 0x87FB
+#define GL_TEXTURE_FREE_MEMORY_ATI 0x87FC
+#define GL_RENDERBUFFER_FREE_MEMORY_ATI 0x87FD
+
+void QueryVRAM(std::string &total_string) {
+ char temp_string[TEMP_STRING_LENGTH];
+ if(GLEW_NVX_gpu_memory_info){
+ GLint param;
+ glGetIntegerv(GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX, &param);
+ FormatString(temp_string,TEMP_STRING_LENGTH,"VRAM: %d MB\n", param/1024);
+ total_string += temp_string;
+ } else if(GLEW_ATI_meminfo){
+ GLint param[4];
+ glGetIntegerv(GL_VBO_FREE_MEMORY_ATI, &param[0]);
+ FormatString(temp_string,TEMP_STRING_LENGTH,"VRAM VBO: %d %d %d %d MB\n",
+ param[0]/1024, param[1]/1024, param[2]/1024, param[3]/1024);
+ total_string += temp_string;
+ glGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI, &param[0]);
+ FormatString(temp_string,TEMP_STRING_LENGTH,"VRAM TEX: %d %d %d %d MB\n",
+ param[0]/1024, param[1]/1024, param[2]/1024, param[3]/1024);
+ total_string += temp_string;
+ glGetIntegerv(GL_RENDERBUFFER_FREE_MEMORY_ATI, &param[0]);
+ FormatString(temp_string,TEMP_STRING_LENGTH,"VRAM RB: %d %d %d %d MB\n",
+ param[0]/1024, param[1]/1024, param[2]/1024, param[3]/1024);
+ total_string += temp_string;
+ } else {
+ FormatString(temp_string,TEMP_STRING_LENGTH,"VRAM query not supported.\n");
+ total_string += temp_string;
+ }
+}
+
+struct GLQUERY_INFO
+{
+ const char* m_name;
+ GLint m_GLint;
+ unsigned int m_numValues;
+};
+
+#ifndef ARRAYSIZE
+#define ARRAYSIZE(p) (sizeof(p)/sizeof(p[0]))
+#endif
+#define GLQUERY_INFO_ENTRY( name, num_values ) { #name, name, num_values },
+static const GLQUERY_INFO GLQUERY_INFOS[] =
+{
+ GLQUERY_INFO_ENTRY(GL_MAX_SAMPLES, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_COLOR_TEXTURE_SAMPLES, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_COMBINED_UNIFORM_BLOCKS, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_COMPUTE_UNIFORM_BLOCKS, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_DEPTH_TEXTURE_SAMPLES, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_FRAGMENT_INPUT_COMPONENTS, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_FRAGMENT_UNIFORM_BLOCKS, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_FRAGMENT_UNIFORM_VECTORS, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_GEOMETRY_UNIFORM_BLOCKS, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_INTEGER_SAMPLES, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_RENDERBUFFER_SIZE, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_TEXTURE_IMAGE_UNITS, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_TEXTURE_SIZE, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_TEXTURE_BUFFER_SIZE, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_UNIFORM_BLOCK_SIZE, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_UNIFORM_BUFFER_BINDINGS, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_UNIFORM_LOCATIONS, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_VARYING_COMPONENTS, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_VARYING_VECTORS, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_VERTEX_ATTRIBS, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_VERTEX_OUTPUT_COMPONENTS, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_VERTEX_UNIFORM_BLOCKS, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_VERTEX_UNIFORM_COMPONENTS, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_VERTEX_UNIFORM_VECTORS, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, 1)
+ GLQUERY_INFO_ENTRY(GL_MAX_VIEWPORT_DIMS, 2)
+};
+#undef GLQUERY_INFO_ENTRY
+
+static bool initialized = false;
+
+static bool support_s3tc = false;
+
+static int max_texture_size = 0;
+
+static std::map<std::string,int> integer_limits;
+static std::map<std::string,ivec2> ivec2_limits;
+static std::vector<std::string> extensions;
+
+static void LazyInit() {
+ if(initialized == false) {
+ for (unsigned int i = 0; i < ARRAYSIZE(GLQUERY_INFOS); ++i) {
+ GLint values[2];
+ glGetIntegerv(GLQUERY_INFOS[i].m_GLint, values);
+ GLenum err = glGetError();
+ if (err != GL_NONE) {
+ integer_limits[GLQUERY_INFOS[i].m_name] = -1;
+ } else if (GLQUERY_INFOS[i].m_numValues == 1) {
+ integer_limits[GLQUERY_INFOS[i].m_name] = values[0];
+
+ if( GLQUERY_INFOS[i].m_GLint == GL_MAX_TEXTURE_SIZE ) {
+ max_texture_size = values[0];
+ }
+ } else {
+ ivec2_limits[GLQUERY_INFOS[i].m_name] = ivec2(values[0],values[1]);
+ }
+ }
+
+ GLint numExtensions = 0;
+ glGetIntegerv(GL_NUM_EXTENSIONS, &numExtensions);
+ if (numExtensions != 0) {
+ for (int i = 0; i < numExtensions; i++) {
+ const char* extension_string = (const char*)glGetStringi(GL_EXTENSIONS, i);
+ if(extension_string) {
+ extensions.push_back(extension_string);
+
+ if(strmtch( "GL_EXT_texture_compression_s3tc", extension_string)) {
+ support_s3tc = true;
+ }
+ }
+ }
+ }
+
+ initialized = true;
+ }
+}
+
+void PrintGPU(std::string &total_string, bool short_output)
+{
+ LazyInit();
+ char temp_string[TEMP_STRING_LENGTH];
+ CHECK_GL_ERROR();
+
+ GLVendor vendor = GetGLVendor();
+ CHECK_GL_ERROR();
+ switch(vendor){
+ case _unknown:
+ FormatString(temp_string,TEMP_STRING_LENGTH,"GPU Vendor: %s\n", glGetString(GL_VENDOR));
+ total_string += temp_string;
+ break;
+ case _ati:
+ FormatString(temp_string,TEMP_STRING_LENGTH,"GPU Vendor: ATI\n");
+ total_string += temp_string;
+ break;
+ case _nvidia:
+ FormatString(temp_string,TEMP_STRING_LENGTH,"GPU Vendor: NVIDIA\n");
+ total_string += temp_string;
+ break;
+ case _intel:
+ FormatString(temp_string,TEMP_STRING_LENGTH,"GPU Vendor: INTEL\n");
+ total_string += temp_string;
+ break;
+ }
+ CHECK_GL_ERROR();
+
+ FormatString(temp_string,TEMP_STRING_LENGTH,"GL Renderer: %s\n", glGetString(GL_RENDERER));
+ total_string += temp_string;
+ FormatString(temp_string,TEMP_STRING_LENGTH,"GL Version: %s\n", glGetString(GL_VERSION));
+ total_string += temp_string;
+ unsigned driver_version = GetDriverVersion(vendor);
+
+ CHECK_GL_ERROR();
+ if(driver_version != 0){
+ FormatString(temp_string,TEMP_STRING_LENGTH,"GL Driver Version: %u\n", driver_version);
+ } else {
+ FormatString(temp_string,TEMP_STRING_LENGTH,"GL Driver Version: unknown\n");
+ }
+ total_string += temp_string;
+
+ CHECK_GL_ERROR();
+
+ QueryVRAM(total_string);
+
+ FormatString(temp_string,TEMP_STRING_LENGTH,"GLSL Version: %s\n", glGetString(GL_SHADING_LANGUAGE_VERSION));
+ total_string += temp_string;
+ CHECK_GL_ERROR();
+
+ if( short_output == false ) {
+ if(GLEW_NV_gpu_program4){
+ FormatString(temp_string,TEMP_STRING_LENGTH,"Shader Model: 4.0 or better\n");
+ total_string += temp_string;
+ } else if(GLEW_NV_vertex_program3){
+ FormatString(temp_string,TEMP_STRING_LENGTH,"Shader Model: 3.0 or better\n");
+ total_string += temp_string;
+ } else if(GLEW_ARB_fragment_program){
+ FormatString(temp_string,TEMP_STRING_LENGTH,"Shader Model: 2.0 or better\n");
+ total_string += temp_string;
+ } else {
+ FormatString(temp_string,TEMP_STRING_LENGTH,"Shader Model: below 2.0\n");
+ total_string += temp_string;
+ }
+
+ total_string += "\n[OpenGL Limits]\n";
+
+ std::map<std::string,int>::iterator integer_limit_it = integer_limits.begin();
+ for (;integer_limit_it != integer_limits.end(); integer_limit_it++) {
+ FormatString(temp_string, TEMP_STRING_LENGTH, "%s = %d\n", integer_limit_it->first.c_str(), integer_limit_it->second);
+ total_string += temp_string;
+ }
+
+ std::map<std::string,ivec2>::iterator ivec2_limit_it = ivec2_limits.begin();
+ for (;ivec2_limit_it != ivec2_limits.end(); ivec2_limit_it++) {
+ FormatString(temp_string, TEMP_STRING_LENGTH, "%s[2] = {%d, %d}\n", ivec2_limit_it->first.c_str(), ivec2_limit_it->second[0], ivec2_limit_it->second[1]);
+ total_string += temp_string;
+ }
+
+ total_string += "\n[Available OpenGL extensions]\n";
+ for (int i = 0; i < extensions.size(); i++) {
+ FormatString(temp_string, TEMP_STRING_LENGTH, "%s\n", extensions[i].c_str());
+ total_string += temp_string;
+ }
+ }
+}
+
+void PrintSpecs() {
+ FILE *test_file;
+ char write_path[kPathSize];
+ GetHWReportPath(write_path, kPathSize);
+ test_file = my_fopen(write_path,"r");
+ if(test_file != NULL){
+ // Get the first line (should contain version number)
+ char read_string[255];
+ fgets(read_string,255,test_file);
+
+ // If first character is not 'R' then it probably doesn't contain
+ // a version number. Otherwise, it should have "Report version: x", so read
+ // so read x as an integer.
+ unsigned int version = 0;
+ if(read_string[0] == 'R'){
+ version = atoi(&read_string[16]);
+ }
+
+ fclose(test_file);
+
+ if(version == kHardwareReportVersion){
+ return;
+ }
+ }
+ CHECK_GL_ERROR();
+
+ LOGI << "Printing specs:" << std::endl;
+ LOGI << "---------------" << std::endl;
+
+ std::string total_string;
+
+ CHECK_GL_ERROR();
+ char temp_string[256];
+ FormatString(temp_string, TEMP_STRING_LENGTH,"Report Version: %u\n", kHardwareReportVersion);
+ total_string += temp_string;
+
+ total_string+="\n";
+
+ FormatString(temp_string, TEMP_STRING_LENGTH,"Build: %s\n", GetBuildIDString());
+ total_string += temp_string;
+ FormatString(temp_string, TEMP_STRING_LENGTH,"Version: %s\n", GetBuildVersion());
+ total_string += temp_string;
+ FormatString(temp_string, TEMP_STRING_LENGTH,"Timestamp: %s\n", GetBuildTimestamp());
+ total_string += temp_string;
+
+ total_string+="\n";
+
+ FormatString(temp_string, TEMP_STRING_LENGTH,"OS: %s\n", GetPlatform());
+ total_string += temp_string;
+ FormatString(temp_string, TEMP_STRING_LENGTH,"Arch: %s\n", GetArch());
+ total_string += temp_string;
+
+ total_string+="\n";
+
+ CHECK_GL_ERROR();
+ PrintGPU(total_string, false);
+ CHECK_GL_ERROR();
+
+ total_string+="\n";
+
+ FILE *file = my_fopen(write_path,"w");
+ fputs(total_string.c_str(),file);
+ fclose(file);
+}
+
+
+std::map<std::string,int> GetHardwareLimitationsInt() {
+ LazyInit();
+ return integer_limits;
+}
+
+bool HasHardwareS3TCSupport() {
+ LazyInit();
+ return support_s3tc;
+}
+
+int GetGLMaxTextureSize() {
+ LazyInit();
+ return max_texture_size;
+}
diff --git a/Source/Internal/hardware_specs.h b/Source/Internal/hardware_specs.h
new file mode 100644
index 00000000..4f45cf89
--- /dev/null
+++ b/Source/Internal/hardware_specs.h
@@ -0,0 +1,38 @@
+//-----------------------------------------------------------------------------
+// Name: hardware_specs.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Compat/hardware_info.h>
+
+#include <map>
+#include <string>
+
+GLVendor GetGLVendor();
+int GetGLMaxTextureSize();
+void PrintGPU(std::string &total_string, bool short_output);
+void PrintSpecs();
+void PrintProcessMemory();
+bool HasHardwareS3TCSupport();
+
+std::map<std::string,int> GetHardwareLimitationsInt();
+
diff --git a/Source/Internal/integer.h b/Source/Internal/integer.h
new file mode 100644
index 00000000..913313b1
--- /dev/null
+++ b/Source/Internal/integer.h
@@ -0,0 +1,56 @@
+//-----------------------------------------------------------------------------
+// Name: integer.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <limits>
+
+#if defined(_MSC_VER) && _MSC_VER < 0x1000 //check for windows versions less than vs2010
+
+#include <limits.h>
+
+typedef signed __int8 int8_t;
+typedef signed __int16 int16_t;
+typedef signed __int32 int32_t;
+typedef signed __int64 int64_t;
+typedef unsigned __int8 uint8_t;
+typedef unsigned __int16 uint16_t;
+typedef unsigned __int32 uint32_t;
+typedef unsigned __int64 uint64_t;
+
+#ifdef _WIN64
+ typedef signed __int64 intptr_t;
+ typedef unsigned __int64 uintptr_t;
+#else
+ typedef signed __int32 intptr_t;
+ typedef unsigned __int32 uintptr_t;
+#endif
+
+#else
+#include <stdint.h>
+#endif
+
+#ifdef max
+#undef max
+#endif
+
+#define UINT32MAX std::numeric_limits<uint32_t>::max()
diff --git a/Source/Internal/levelxml.cpp b/Source/Internal/levelxml.cpp
new file mode 100644
index 00000000..620c2f2f
--- /dev/null
+++ b/Source/Internal/levelxml.cpp
@@ -0,0 +1,402 @@
+//-----------------------------------------------------------------------------
+// Name: levelxml.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <Internal/levelxml.h>
+#include <Internal/comma_separated_list.h>
+#include <Internal/filesystem.h>
+#include <Internal/returnpathutil.h>
+
+#include <Asset/Asset/material.h>
+#include <Asset/Asset/spawnpointinfo.h>
+
+#include <Logging/logdata.h>
+#include <Game/detailobjectlayer.h>
+#include <Utility/strings.h>
+#include <AI/navmeshparameters.h>
+
+#include <tinyxml.h>
+
+void ExtractLevelName(const std::string& path, std::string &str){
+ int slash_position = path.rfind('/')+1;
+ int dot_position = path.rfind('.');
+ str = path.substr(slash_position, dot_position-slash_position);
+}
+
+void GetXMLVersionFromDoc(TiXmlDocument& doc, std::string &str){
+ TiXmlHandle hDoc(&doc);
+ TiXmlDeclaration* decl = ((hDoc.FirstChild()).ToNode())->ToDeclaration();
+ str = decl->Version();
+}
+
+void HandleDetailMaps(std::vector<DetailMapInfo>& dmiv, const TiXmlElement* field){
+ dmiv.clear();
+ while(field) {
+ dmiv.resize(dmiv.size()+1);
+ DetailMapInfo& dmi = dmiv.back();
+ dmi.colorpath = SanitizePath(field->Attribute("colorpath"));
+ dmi.normalpath = SanitizePath(field->Attribute("normalpath"));
+ const char* material = field->Attribute("materialpath");
+ if(material){
+ dmi.materialpath = SanitizePath(material);
+ } else {
+ dmi.materialpath = "Data/Materials/default.xml";
+ }
+ field = field->NextSiblingElement();
+ }
+ if(dmiv.size() < 4){
+ FatalError("Error", "Detail textures are not specified");
+ }
+}
+
+void HandleDetailObjects(std::vector<DetailObjectLayer>& dolv, const TiXmlElement* field){
+ dolv.clear();
+ while(field) {
+ dolv.push_back(ReadDetailObjectLayerXML(field));
+ field = field->NextSiblingElement();
+ }
+}
+
+
+void HandleTerrain(TerrainInfo& ti, const TiXmlElement* old){
+ for(const TiXmlElement* field = old; field; field = field->NextSiblingElement()){
+ const char* val = field->Value();
+ switch(val[0]){
+ case 'C':
+ if(strcmp("ColorMap", val) == 0){ti.colormap = SanitizePath(field->GetText());}
+ break;
+ case 'D':
+ switch(val[6]){
+ case 'M':
+ if(strcmp("DetailMaps", val)==0){ // Distinguish from "DetailMap"
+ HandleDetailMaps(ti.detail_map_info, field->FirstChildElement());
+ }
+ break;
+ case 'O':
+ if(strcmp("DetailObjects", val) == 0){HandleDetailObjects(ti.detail_object_info, field->FirstChildElement());}
+ break;
+ }
+ break;
+ case 'H':
+ if(strcmp("Heightmap", val) == 0){ti.heightmap = SanitizePath(field->GetText());}
+ break;
+ case 'M':
+ if(strcmp("ModelOverride", val) == 0){ti.model_override = SanitizePath(field->GetText());}
+ break;
+ case 'S':
+ if(strcmp("ShaderExtra", val) == 0){
+ if(field->GetText() != NULL){
+ ti.shader_extra = field->GetText();
+ } else {
+ ti.shader_extra = "";
+ }
+ }
+ break;
+ case 'W':
+ if(strcmp("WeightMap", val) == 0){ti.weightmap = SanitizePath(field->GetText());}
+ break;
+ }
+ }
+}
+
+
+void HandleSky(SkyInfo& si, const TiXmlElement* old){
+ for(const TiXmlElement* field = old; field; field = field->NextSiblingElement()){
+ const char* val = field->Value();
+ switch(val[0]){
+ case 'D':
+ if(strcmp("DomeTexture", val) == 0){
+ const char *c = field->GetText();
+ if(c){
+ si.dome_texture_path = SanitizePath(c);
+ }
+ }
+ break;
+ case 'R':
+ if(strcmp("RayToSun", val) == 0){
+ field->QueryFloatAttribute("r0",&si.ray_to_sun[0]);
+ field->QueryFloatAttribute("r1",&si.ray_to_sun[1]);
+ field->QueryFloatAttribute("r2",&si.ray_to_sun[2]);
+ }
+ break;
+ case 'S':
+ switch(val[1]){
+ case 'u':
+ switch(val[3]){
+ case 'A':
+ if(strcmp("SunAngularRad", val) == 0){si.sun_angular_rad = (float)atof(field->GetText());}
+ break;
+ case 'C':
+ if(strcmp("SunColorAngle", val) == 0){si.sun_color_angle = (float)atof(field->GetText());}
+ break;
+ }
+ break;
+ }
+ break;
+ }
+ }
+}
+
+bool GetBoolFieldAttribute(const TiXmlElement* field, const std::string& attr){
+ const char* cstr = field->Attribute(attr.c_str());
+ if(cstr && strcmp(cstr,"false") == 0){
+ return false;
+ }
+ return true;
+}
+
+int GetIntFieldAttribute(const TiXmlElement* field, const std::string& attr){
+ int v = 0;
+ field->QueryIntAttribute(attr.c_str(),&v);
+ return v;
+}
+
+void HandleOutOfDate(OutOfDateInfo& oodi, const TiXmlElement* field){
+ oodi.shadow = GetBoolFieldAttribute(field, "Shadow");
+ oodi.ao = GetBoolFieldAttribute(field, "AO");
+ oodi.nav_mesh = GetBoolFieldAttribute(field, "NavMesh");
+ oodi.nav_mesh_param_hash = GetIntFieldAttribute(field, "NavMeshParamHash");
+}
+
+void HandleAmbientSounds(std::vector<std::string> &as, const TiXmlElement* old){
+ for(const TiXmlElement* field = old; field; field = field->NextSiblingElement()){
+ as.push_back(SanitizePath(field->Attribute("path")));
+ }
+}
+
+void ParseElementTextIntoVector(std::vector<std::string> &vec, const TiXmlElement* field){
+ const char* text = field->GetText();
+ if(!text){
+ return;
+ }
+ CSLIterator iter(text);
+ std::string next_token;
+ while(iter.GetNext(&next_token)){
+ vec.push_back(next_token);
+ }
+}
+
+void HandleSpawnPoints(SpawnPointInfo& spi, const TiXmlElement* el) {
+ GetTSRinfo(el, spi.translation, spi.scale, spi.rotation);
+}
+
+void HandleRecentItems(std::vector<SpawnerItem>& siv, const TiXmlElement* el) {
+ const TiXmlElement* spawner = el->FirstChildElement("SpawnerItem");
+
+ while( spawner != NULL ) {
+ SpawnerItem si;
+ si.display_name = spawner->Attribute("display_name");
+ si.path = SanitizePath(spawner->Attribute("path"));
+ si.thumbnail_path = SanitizePath(spawner->Attribute("thumbnail_path"));
+ siv.push_back(si);
+
+ spawner = spawner->NextSiblingElement("SpawnerItem");
+ }
+}
+
+void HandleLoadingScreen(LoadingScreen& siv, const TiXmlElement* el) {
+ const TiXmlElement* image = el->FirstChildElement("Image");
+ if( image ) {
+ const char* c_image = image->GetText();
+ if( c_image ) {
+ siv.image = c_image;
+ }
+ }
+}
+
+void ParseLevelXML(const std::string &path, LevelInfo &li) {
+ for(int i=0, len=path.size(); i<len; ++i) {
+ if(path[i] == '\\') {
+ std::stringstream ss;
+ ss << "Path to ParseLevelXML should not contain \\ \"" << path.c_str() << "\"";
+ DisplayError("Error", ss.str().c_str());
+ }
+ }
+
+ if(!CheckFileAccess(path.c_str())) {
+ FatalError("Error", "Could not find level file: %s", path.c_str());
+ }
+
+ TiXmlDocument doc;
+ if (!doc.LoadFile(path.c_str())) {
+ FatalError("Error", "Bad xml data in level file %s\n%s on row %d", path.c_str(), doc.ErrorDesc(), doc.ErrorRow());
+ }
+
+ li.SetDefaults();
+
+ GetXMLVersionFromDoc(doc, li.xml_version_);
+ ExtractLevelName(path, li.level_name_);
+ li.path_ = path;
+
+ const TiXmlElement* root = doc.RootElement();
+
+ //Check if this is the newer level format with the "correct" Level root node
+ //If it is, reassign, otherwise iterator on the "bottom" of the document.
+ //LOGW << "test:" << root->Value() << std::endl;
+ if(root && strcmp(root->Value(), "Level") == 0 ) {
+ root = root->FirstChildElement();
+ }
+
+ if( !root ) {
+ LOGE << "Level Root is null!" << std::endl;
+ }
+
+ for(const TiXmlElement* field = root; field; field = field->NextSiblingElement()){
+ const char* val = field->Value();
+ switch(val[0]){
+ case 'A':
+ switch(val[1]){
+ case 'm'://AmbientSounds
+ if(strcmp("AmbientSounds", val) == 0){HandleAmbientSounds(li.ambient_sounds_, field->FirstChildElement());}
+ break;
+ case 'c':
+ switch(val[2]){
+ case 't'://ActorObjects
+ if(strcmp("ActorObjects", val) == 0){LoadEntityDescriptionListFromXML(li.desc_list_, field);}
+ break;
+ case 'h'://Achievements
+ if(strcmp("Achievements", val) == 0){
+ if(field->GetText()){
+ ScriptParam sp;
+ sp.SetString(field->GetText());
+ li.spm_["Achievements"] = sp;
+ }
+ }
+ break;
+ }
+ break;
+ }
+ break;
+ case 'D':
+ switch(val[2]){
+ case 'c': //Decals
+ if(strcmp("Decals", val) == 0){LoadEntityDescriptionListFromXML(li.desc_list_, field);}
+ break;
+ case 's': //Description;
+ if(strcmp("Description", val) == 0){if(field->GetText()){li.visible_description_ = field->GetText();}}
+ break;
+ }
+ break;
+ case 'E':
+ if(strcmp("EnvObjects", val) == 0){
+ LoadEntityDescriptionListFromXML(li.desc_list_, field);
+ }
+ break;
+ case 'G'://Groups
+ if(strcmp("Groups", val) == 0){LoadEntityDescriptionListFromXML(li.desc_list_, field);}
+ break;
+ case 'H'://Hotspots
+ if(strcmp("Hotspots", val) == 0){LoadEntityDescriptionListFromXML(li.desc_list_, field);}
+ break;
+ case 'L'://LevelScriptParameters
+ if(strcmp("LevelScriptParameters", val) == 0){
+ ReadScriptParametersFromXML(li.spm_, field);
+ }
+ if(strcmp("LoadingScreen", val) == 0){
+ HandleLoadingScreen(li.loading_screen_,field);
+ }
+ break;
+ case 'N':
+ if(strmtch("NavMeshParameters", val)) {
+ ReadNavMeshParametersFromXML(li.nav_mesh_parameters_, field);
+ } else if(strmtch("Name", val)) {
+ if(field->GetText()) {
+ li.visible_name_ = field->GetText();
+ }
+ } else if(strcmp("NPCScript", val) == 0) {
+ const char* txt = field->GetText();
+ if(txt) {
+ li.npc_script_ = txt;
+ } else {
+ li.npc_script_ = "";
+ }
+ }
+ break;
+ case 'O':
+ switch(val[1]){
+ case 'u'://OutOfDate
+ if(strcmp("OutOfDate", val) == 0){HandleOutOfDate(li.out_of_date_info_, field);}
+ break;
+ case 'b'://Objectives
+ if(strcmp("Objectives", val) == 0){
+ if(field->GetText()){
+ ScriptParam sp;
+ sp.SetString(field->GetText());
+ li.spm_["Objectives"] = sp;
+ }
+ }
+ break;
+ }
+ break;
+ case 'P':
+ if(strcmp("PCScript", val) == 0){
+ const char* txt = field->GetText();
+ if(txt){
+ li.pc_script_ = txt;
+ } else {
+ li.pc_script_ = "";
+ }
+ }
+ break;
+ case 'R':
+ if(strcmp("RecentItems", val) == 0){ HandleRecentItems(li.recently_created_items_, field); }
+ break;
+ case 'S':
+ switch(val[1]){
+ case 'h'://Shader
+ if(strcmp("Shader", val) == 0){li.shader_ = field->GetText();}
+ else if(strcmp("Shadows", val) == 0){li.shadows_ = (strcmp(field->GetText(), "1") == 0 || strcmp(field->GetText(), "true") == 0);}
+ break;
+ case 'c'://Shader
+ if(strcmp("Script", val) == 0){
+ const char* txt = field->GetText();
+ if(txt){
+ li.script_ = txt;
+ } else {
+ li.script_ = "";
+ }
+ }
+ break;
+ case 'k'://Sky
+ if(strcmp("Sky", val) == 0){HandleSky(li.sky_info_, field->FirstChildElement());}
+ break;
+ case 'p'://SpawnPoints
+ LOGW << "SpawnPoints is outdated" << std::endl;
+ break;
+ }
+ break;
+ case 'T':
+ switch(val[1]){
+ case 'e'://Terrain
+ if(strcmp("Terrain", val) == 0){HandleTerrain(li.terrain_info_, field->FirstChildElement());}
+ break;
+ case 'y'://Type
+ break;
+ }
+ break;
+ }
+ }
+
+ if( li.out_of_date_info_.nav_mesh_param_hash != (int)HashNavMeshParameters(li.nav_mesh_parameters_) ) {
+ LOGW << "Mismatch between navmesh build data and navmesh level parameters" << std::endl;
+ li.out_of_date_info_.nav_mesh = true;
+ }
+}
diff --git a/Source/Internal/levelxml.h b/Source/Internal/levelxml.h
new file mode 100644
index 00000000..1f17d99f
--- /dev/null
+++ b/Source/Internal/levelxml.h
@@ -0,0 +1,35 @@
+//-----------------------------------------------------------------------------
+// Name: levelxml.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Game/EntityDescription.h>
+#include <Game/detailobjectlayer.h>
+#include <Game/levelinfo.h>
+
+#include <Math/mat4.h>
+#include <Math/quaternions.h>
+
+#include <Asset/assets.h>
+#include <Scripting/scriptparams.h>
+
+void ParseLevelXML(const std::string &path, LevelInfo& li);
diff --git a/Source/Internal/levelxml_script.cpp b/Source/Internal/levelxml_script.cpp
new file mode 100644
index 00000000..74ab1914
--- /dev/null
+++ b/Source/Internal/levelxml_script.cpp
@@ -0,0 +1,60 @@
+//-----------------------------------------------------------------------------
+// Name: levelxml_script.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "levelxml_script.h"
+
+#include <Internal/levelxml.h>
+#include <Internal/filesystem.h>
+
+#include <Scripting/angelscript/ascontext.h>
+
+namespace {
+ struct LevelInfoReader {
+ LevelInfo li;
+ void Load(const std::string& rel_path){
+ char abs_path[kPathSize];
+ if(FindFilePath(rel_path.c_str(), abs_path, kPathSize, kDataPaths|kModPaths) == -1){
+ FatalError("Error", "Could not find level info: %s", rel_path.c_str());
+ } else {
+ ParseLevelXML(abs_path, li);
+ }
+ }
+ const std::string& visible_name(){return li.visible_name_;}
+ const std::string& visible_description(){return li.visible_description_;}
+ };
+ void LevelInfoReader_Constructor(void *memory) {
+ new(memory) LevelInfo();
+ }
+ void LevelInfoReader_Destructor(void *memory) {
+ ((LevelInfoReader*)memory)->~LevelInfoReader();
+ }
+} // ANONYMOUS NAMESPACE
+
+void AttachLevelXML( ASContext* as_context ) {
+ as_context->RegisterObjectType("LevelInfoReader", sizeof(LevelInfo), asOBJ_VALUE | asOBJ_APP_CLASS);
+ as_context->RegisterObjectBehaviour("LevelInfoReader", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(LevelInfoReader_Constructor), asCALL_CDECL_OBJLAST);
+ as_context->RegisterObjectBehaviour("LevelInfoReader", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(LevelInfoReader_Destructor), asCALL_CDECL_OBJLAST);
+ as_context->RegisterObjectMethod("LevelInfoReader", "void Load(const string &in)", asMETHOD(LevelInfoReader, Load), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("LevelInfoReader", "const string& visible_name()", asMETHOD(LevelInfoReader, visible_name), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("LevelInfoReader", "const string& visible_description()", asMETHOD(LevelInfoReader, visible_description), asCALL_THISCALL);
+ as_context->DocsCloseBrace();
+}
diff --git a/Source/Internal/levelxml_script.h b/Source/Internal/levelxml_script.h
new file mode 100644
index 00000000..bfde8eff
--- /dev/null
+++ b/Source/Internal/levelxml_script.h
@@ -0,0 +1,26 @@
+//-----------------------------------------------------------------------------
+// Name: levelxml_script.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+class ASContext;
+void AttachLevelXML(ASContext* as_context);
diff --git a/Source/Internal/locale.cpp b/Source/Internal/locale.cpp
new file mode 100644
index 00000000..c7a1d8a9
--- /dev/null
+++ b/Source/Internal/locale.cpp
@@ -0,0 +1,125 @@
+//-----------------------------------------------------------------------------
+// Name: locale.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "locale.h"
+
+#include <Utility/strings.h>
+#include <Scripting/angelscript/add_on/scriptarray/scriptarray.h>
+
+#include <map>
+#include <string>
+
+typedef std::map<std::string, std::string> LocaleMap;
+static LocaleMap locales;
+
+struct LevelLocalizationData {
+ std::string name;
+ std::string loading_tip;
+};
+typedef std::map<std::string, LevelLocalizationData> MapDataMap; // Maps level path -> per-level data
+typedef std::map<std::string, MapDataMap> LocalizedLevelMap; // Maps locale shortcode -> map of level data
+static LocalizedLevelMap localized_levels;
+
+void ClearLocale() {
+ localized_levels.clear();
+ locales.clear();
+}
+
+void AddLocale(const char* shortcode, const char* name, const char* local_name) {
+ if(local_name && strlen(local_name) > 0) {
+ locales.insert(std::pair<std::string, std::string>(std::string(shortcode), std::string(name) + "/" + std::string(local_name)));
+ } else {
+ locales.insert(std::pair<std::string, std::string>(std::string(shortcode), std::string(name)));
+ }
+}
+
+void AddLevelName(const char* shortcode, const char* level, const char* name) {
+ localized_levels[shortcode][level].name = name;
+}
+
+void AddLevelTip(const char* shortcode, const char* level, const char* tip) {
+ localized_levels[shortcode][level].loading_tip = tip;
+}
+
+const char* GetLevelTip(const char* shortcode, const char* level) {
+ LocalizedLevelMap::iterator loc_it = localized_levels.find(shortcode);
+ if(loc_it != localized_levels.end()) {
+ MapDataMap::iterator it = loc_it->second.find(level);
+ if(it != loc_it->second.end()) {
+ return it->second.loading_tip.c_str();
+ }
+ }
+ return NULL;
+}
+
+static CScriptArray* ASGetLocaleShortcodes() {
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<string>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+ array->Reserve(locales.size());
+
+ for(LocaleMap::iterator iter = locales.begin(); iter != locales.end(); ++iter) {
+ // InsertLast doesn't actually do anything but copy from the pointer,
+ // so a const_cast would be fine, but maybe an update to AS could change
+ // that
+ std::string str = iter->first;
+ array->InsertLast(&str);
+ }
+
+ return array;
+}
+
+static CScriptArray* ASGetLocaleNames() {
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<string>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+ array->Reserve(locales.size());
+
+ for(LocaleMap::iterator iter = locales.begin(); iter != locales.end(); ++iter) {
+ // InsertLast doesn't actually do anything but copy from the pointer,
+ // so a const_cast would be fine, but maybe an update to AS could change
+ // that
+ std::string str = iter->second;
+ array->InsertLast(&str);
+ }
+
+ return array;
+}
+
+static std::string ASGetLevelName(const std::string& shortcode, const std::string& path) {
+ LocalizedLevelMap::iterator loc_it = localized_levels.find(shortcode);
+ if(loc_it != localized_levels.end()) {
+ MapDataMap::iterator it = loc_it->second.find("Data/Levels/" + path);
+ if(it != loc_it->second.end()) {
+ return it->second.name;
+ }
+ }
+ return "";
+}
+
+void AttachLocale(ASContext* context) {
+ context->RegisterGlobalFunction("array<string>@ GetLocaleShortcodes()", asFUNCTION(ASGetLocaleShortcodes), asCALL_CDECL);
+ context->RegisterGlobalFunction("array<string>@ GetLocaleNames()", asFUNCTION(ASGetLocaleNames), asCALL_CDECL);
+ context->RegisterGlobalFunction("string GetLocalizedLevelName(const string &in locale_shortcode, const string &in path)", asFUNCTION(ASGetLevelName), asCALL_CDECL);
+}
diff --git a/Source/Internal/locale.h b/Source/Internal/locale.h
new file mode 100644
index 00000000..b215a5bf
--- /dev/null
+++ b/Source/Internal/locale.h
@@ -0,0 +1,37 @@
+//-----------------------------------------------------------------------------
+// Name: locale.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <vector>
+
+#include <Scripting/angelscript/ascontext.h>
+
+void ClearLocale();
+
+void AddLocale(const char* shortcode, const char* name, const char* local_name);
+void AddLevelName(const char* shortcode, const char* level, const char* name);
+void AddLevelTip(const char* shortcode, const char* level, const char* tip);
+
+const char* GetLevelTip(const char* shortcode, const char* level);
+
+void AttachLocale(ASContext* context);
diff --git a/Source/Internal/memwrite.cpp b/Source/Internal/memwrite.cpp
new file mode 100644
index 00000000..b5609036
--- /dev/null
+++ b/Source/Internal/memwrite.cpp
@@ -0,0 +1,51 @@
+//-----------------------------------------------------------------------------
+// Name: memwrite.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <Internal/memwrite.h>
+
+#include <cstring>
+
+void memwrite(const void* source, size_t size, size_t count, std::vector<char> &target) {
+ int start = target.size();
+ int total_size = size*count;
+ target.resize(start + total_size);
+
+ if(total_size > 0){
+ memcpy((void*)&target[start],source,total_size);
+ }
+}
+
+void memread( void* source, size_t size, size_t count, const std::vector<char> &target, int &index ) {
+ int total_size = size*count;
+
+ if(total_size > 0){
+ memcpy(source,(void*)&target[index],total_size);
+ }
+ index += total_size;
+}
+
+void memread( void* source, size_t size, size_t count, const std::vector<char> &target ) {
+ int total_size = size*count;
+ if(total_size > 0){
+ memcpy(source,(void*)&target[0],total_size);
+ }
+}
diff --git a/Source/Internal/memwrite.h b/Source/Internal/memwrite.h
new file mode 100644
index 00000000..2b6d0ae9
--- /dev/null
+++ b/Source/Internal/memwrite.h
@@ -0,0 +1,30 @@
+//-----------------------------------------------------------------------------
+// Name: memwrite.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <vector>
+#include <cstdlib>
+
+void memwrite(const void* source, size_t size, size_t count, std::vector<char> &target);
+void memread(void* source, size_t size, size_t count, const std::vector<char> &target, int &index);
+void memread(void* source, size_t size, size_t count, const std::vector<char> &target);
diff --git a/Source/Internal/message.h b/Source/Internal/message.h
new file mode 100644
index 00000000..eed4059c
--- /dev/null
+++ b/Source/Internal/message.h
@@ -0,0 +1,25 @@
+//-----------------------------------------------------------------------------
+// Name: message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+enum MessageType {_plant_movement_msg, _editor_msg};
diff --git a/Source/Internal/modid.cpp b/Source/Internal/modid.cpp
new file mode 100644
index 00000000..fc6046e6
--- /dev/null
+++ b/Source/Internal/modid.cpp
@@ -0,0 +1,125 @@
+//-----------------------------------------------------------------------------
+// Name: modid.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "modid.h"
+
+#include <Logging/logdata.h>
+
+using std::ostream;
+using std::endl;
+
+const ModID CoreGameModID(-2);
+
+ModID::ModID(): id(-1) {
+}
+
+ModID::ModID(int id) : id(id) {
+}
+
+bool ModID::Valid() const {
+ return id != -1;
+}
+
+bool ModID::operator==( const ModID& modid ) const {
+ return this->id == modid.id;
+}
+
+bool ModID::operator!=( const ModID& modid ) const {
+ return this->id != modid.id;
+}
+
+ModValidity::ModValidity() : upper(0ULL), lower(0ULL) {
+
+}
+
+ModValidity::ModValidity(uint16_t bit) {
+ if( bit < 64 ) {
+ upper = 0ULL;
+ lower |= 1ULL << bit;
+ } else if( bit < 128 ) {
+ upper |= 1ULL << (bit-64);
+ lower = 0ULL;
+ } else {
+ LOGE << "Bit too high" << endl;
+ }
+}
+
+ModValidity::ModValidity(uint64_t upper, uint64_t lower) : upper(upper), lower(lower) {
+
+}
+
+ModValidity ModValidity::Intersection(const ModValidity& other) const {
+ return ModValidity(other.upper & upper, other.lower & lower);
+}
+
+ModValidity ModValidity::Union(const ModValidity& other) const {
+ return ModValidity(other.upper | upper, other.lower | lower);
+}
+
+ModValidity& ModValidity::Append(const ModValidity& other) {
+ upper |= other.upper;
+ lower |= other.lower;
+ return *this;
+}
+
+bool ModValidity::Empty() const {
+ return upper == 0ULL && lower == 0ULL;
+}
+
+bool ModValidity::NotEmpty() const {
+ return !Empty();
+}
+
+bool ModValidity::Intersects(const ModValidity &other) const {
+ return (*this & other).NotEmpty();
+}
+
+ModValidity ModValidity::operator&(const ModValidity& rhs) const {
+ return Intersection(rhs);
+}
+
+ModValidity ModValidity::operator|(const ModValidity& rhs) const {
+ return Union(rhs);
+}
+
+ModValidity& ModValidity::operator|=(const ModValidity& rhs) {
+ Append(rhs);
+ return *this;
+}
+
+ModValidity ModValidity::operator~() const {
+ return ModValidity(~upper,~lower);
+}
+
+bool ModValidity::operator!=(const ModValidity& rhs) const {
+ return upper != rhs.upper || lower != rhs.lower;
+}
+
+ostream& operator<<(ostream& os, const ModID &mi ) {
+ os << mi.id;
+ return os;
+}
+
+ostream& operator<<(ostream& os, const ModValidity &mi ) {
+ os << "ModValidity(" << mi.upper << "," << mi.lower << ")";
+ return os;
+}
diff --git a/Source/Internal/modid.h b/Source/Internal/modid.h
new file mode 100644
index 00000000..b4c45a2a
--- /dev/null
+++ b/Source/Internal/modid.h
@@ -0,0 +1,119 @@
+//-----------------------------------------------------------------------------
+// Name: modid.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/integer.h>
+
+#include <ostream>
+
+
+//Some strict limitations on datatypes to simplify handling and storage assumptions.
+static const size_t MOD_PATH_MAX_LENGTH = 256 + 1;
+static const size_t MOD_ID_MAX_LENGTH = 64 + 1;
+static const size_t MOD_NAME_MAX_LENGTH = 128 + 1; //Number borrowed from steam ugc
+static const size_t MOD_VERSION_MAX_LENGTH = 32 + 1;
+static const size_t MOD_AUTHOR_MAX_LENGTH = 128 + 1;
+static const size_t MOD_CATEGORY_MAX_LENGTH = 64 + 1;
+
+static const size_t MOD_DESCRIPTION_MAX_LENGTH = 8000; //Number borrowed from steam ugc
+static const size_t MOD_THUMBNAIL_MAX_LENGTH = 128 + 1;
+static const size_t MOD_TAGS_MAX_LENGTH = 1024 + 1; //Number borrowed from steam ugc
+
+static const size_t MOD_ATTRIBUTE_ID_MAX_LENGTH = 32 + 1;
+static const size_t MOD_ATTRIBUTE_VALUE_MAX_LENGTH = 256 + 1;
+
+static const size_t MOD_MENU_ITEM_TITLE_MAX_LENGTH = 64 + 1;
+static const size_t MOD_MENU_ITEM_CATEGORY_MAX_LENGTH = 32 + 1;
+static const size_t MOD_MENU_ITEM_PATH_MAX_LENGTH = 256 + 1;
+static const size_t MOD_MENU_ITEM_THUMBNAIL_MAX_LENGTH = 256 + 1;
+
+static const size_t MOD_LEVEL_PARAMETER_NAME_MAX_LENGTH = 32+1;
+static const size_t MOD_LEVEL_PARAMETER_TYPE_MAX_LENGTH = 16+1;
+static const size_t MOD_LEVEL_PARAMETER_VALUE_MAX_LENGTH = 128+1;
+
+static const size_t MOD_LEVEL_ID_MAX_LENGTH = 32 + 1;
+static const size_t MOD_LEVEL_TITLE_MAX_LENGTH = 64 + 1;
+static const size_t MOD_LEVEL_PATH_MAX_LENGTH = 256 + 1;
+static const size_t MOD_LEVEL_THUMBNAIL_MAX_LENGTH = 256 + 1;
+
+static const size_t MOD_CAMPAIGN_ID_MAX_LENGTH = 64 + 1;
+static const size_t MOD_CAMPAIGN_TITLE_MAX_LENGTH = 64 + 1;
+static const size_t MOD_CAMPAIGN_THUMBNAIL_MAX_LENGTH = 256 + 1;
+static const size_t MOD_CAMPAIGN_MENU_MUSIC_PATH_MAX_LENGTH = 256 + 1;
+static const size_t MOD_CAMPAIGN_MENU_SCRIPT_PATH_MAX_LENGTH = 256 + 1;
+static const size_t MOD_CAMPAIGN_MAIN_SCRIPT_PATH_MAX_LENGTH = 256 + 1;
+
+static const size_t MOD_ITEM_TITLE_MAX_LENGTH = 128 + 1;
+static const size_t MOD_ITEM_CATEGORY_MAX_LENGTH = 128 + 1;
+static const size_t MOD_ITEM_PATH_MAX_LENGTH = 256 + 1;
+
+static const size_t MOD_POSE_NAME_MAX_LENGTH = 32 + 1;
+static const size_t MOD_POSE_COMMAND_MAX_LENGTH = 32 + 1;
+static const size_t MOD_POSE_PATH_MAX_LENGTH = 256 + 1;
+
+enum ModSource {
+ ModSourceUnknown,
+ ModSourceLocalModFolder,
+ ModSourceSteamworks
+};
+
+struct ModID {
+ ModID();
+ ModID(int id);
+ int id;
+
+ bool Valid() const;
+ bool operator==( const ModID& modid ) const;
+ bool operator!=( const ModID& modid ) const;
+};
+
+extern const ModID CoreGameModID;
+
+struct ModValidity {
+ ModValidity();
+ ModValidity(uint16_t bit);
+ ModValidity(uint64_t upper, uint64_t lower);
+
+ ModValidity Intersection(const ModValidity& other) const;
+ ModValidity Union(const ModValidity& other) const;
+ ModValidity& Append(const ModValidity& other);
+
+ bool Empty() const;
+ bool NotEmpty() const;
+ bool Intersects(const ModValidity& other) const;
+
+ ModValidity operator&(const ModValidity& rhs) const;
+ ModValidity operator|(const ModValidity& rhs) const;
+ ModValidity operator~() const;
+
+ ModValidity& operator|=(const ModValidity& rhs);
+ bool operator!=(const ModValidity& rhs) const;
+
+private:
+ uint64_t upper;
+ uint64_t lower;
+ friend std::ostream& operator<<(std::ostream& os, const ModValidity &mi );
+};
+
+std::ostream& operator<<(std::ostream& os, const ModID &mi );
+std::ostream& operator<<(std::ostream& os, const ModValidity &mi );
diff --git a/Source/Internal/modloading.cpp b/Source/Internal/modloading.cpp
new file mode 100644
index 00000000..f9b2bf29
--- /dev/null
+++ b/Source/Internal/modloading.cpp
@@ -0,0 +1,2677 @@
+//-----------------------------------------------------------------------------
+// Name: modloading.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "modloading.h"
+
+#include <Internal/filesystem.h>
+#include <Internal/config.h>
+#include <Internal/common.h>
+#include <Internal/modid.h>
+#include <Internal/locale.h>
+
+#include <Utility/strings.h>
+#include <Utility/set.h>
+
+#include <Logging/logdata.h>
+#include <Version/version.h>
+#include <Compat/platformsetup.h>
+
+#if ENABLE_STEAMWORKS
+#include <Steam/ugc_item.h>
+#include <Steam/ugc.h>
+#include <Steam/steamworks.h>
+#endif
+
+#include <trex/trex.h>
+#include <tinyxml.h>
+
+#include <sstream>
+#include <iostream>
+#include <algorithm>
+#include <cassert>
+#include <set>
+
+#define PARSE_ERROR(elem,flag) \
+ do {\
+ TiXmlElement* v = elem;\
+ if(v){\
+ LOGE << "Error when parsing mod.xml for mod " << id << " error " << ModValidityString(flag) << " on row " << v->Row() << std::endl;\
+ }else{\
+ LOGE << "Error when parsing mod.xml for mod " << id << " error " << ModValidityString(flag) << std::endl;\
+ }\
+ valid |= flag;\
+ }while(0)
+
+#define PARSE_ERROR2(elem,flag,file) \
+ do {\
+ TiXmlElement* v = elem;\
+ if(v){\
+ LOGE << "Error when parsing mod.xml for mod " << id << " error " << ModValidityString(flag) << " on row " << v->Row() << " with the path \"" << file << "\"" << std::endl;\
+ }else{\
+ LOGE << "Error when parsing mod.xml" << std::endl;\
+ }\
+ valid |= flag;\
+ }while(0)
+
+static const char* menu_item_id_regex_string = "^[a-z-_]+$";
+static TRexpp menu_item_id_regex;
+static const char* id_regex_string = "^[a-z0-9-]+$";
+static TRexpp id_regex;
+static const char* version_regex_string = "^[0-9a-z-\\.]+$";
+static TRexpp version_regex;
+
+static bool has_compiled_regex = false;
+
+int ModInstance::sid_counter = 1;
+
+static const char* ModValidityString(const ModValidity& value) {
+ if(ModInstance::kValidityValid.Intersects(value)){return "kValidityValid";}
+ if(ModInstance::kValidityBrokenXml.Intersects(value)){return "kValidityBrokenXml";}
+ if(ModInstance::kValidityInvalidVersion.Intersects(value)){return "kValidityInvalidVersion";}
+ if(ModInstance::kValidityMissingReadRights.Intersects(value)){return "kValidityMissingReadRights";}
+ if(ModInstance::kValidityMissingXml.Intersects(value)){return "kValidityMissingXml";}
+ if(ModInstance::kValidityUnloaded.Intersects(value)){return "kValidityUnloaded";}
+ if(ModInstance::kValidityUnsupportedOvergrowthVersion.Intersects(value)){return "kValidityUnsupportedOvergrowthVersion";}
+ if(ModInstance::kValidityNoDataOnDisk.Intersects(value)){return "kValidityNoDataOnDisk";}
+ if(ModInstance::kValiditySteamworksError.Intersects(value)){return "kValiditySteamworksError";}
+ if(ModInstance::kValidityNotInstalled.Intersects(value)){return "kValidityNotInstalled";}
+ if(ModInstance::kValidityNotSubscribed.Intersects(value)){return "kValidityNotSubscribed";}
+ if(ModInstance::kValidityIDTooLong.Intersects(value)){return "kValidityIDTooLong";}
+ if(ModInstance::kValidityIDMissing.Intersects(value)){return "kValidityIDMissing";}
+ if(ModInstance::kValidityIDInvalid.Intersects(value)){return "kValidityIDInvalid";}
+ if(ModInstance::kValidityNameTooLong.Intersects(value)){return "kValidityNameTooLong";}
+ if(ModInstance::kValidityVersionTooLong.Intersects(value)){return "kValidityVersionTooLong";}
+ if(ModInstance::kValidityAuthorTooLong.Intersects(value)){return "kValidityAuthorTooLong";}
+ if(ModInstance::kValidityDescriptionTooLong.Intersects(value)){return "kValidityDescriptionTooLong";}
+ if(ModInstance::kValidityThumbnailMaxLength.Intersects(value)){return "kValidityThumbnailMaxLength";}
+ if(ModInstance::kValidityTagsListTooLong.Intersects(value)){return "kValidityTagsListTooLong";}
+ if(ModInstance::kValidityMenuItemTitleTooLong.Intersects(value)){return "kValidityMenuItemTitleTooLong";}
+ if(ModInstance::kValidityMenuItemTitleMissing.Intersects(value)){return "kValidityMenuItemTitleMissing";}
+ if(ModInstance::kValidityMenuItemCategoryTooLong.Intersects(value)){return "kValidityMenuItemCategoryTooLong";}
+ if(ModInstance::kValidityMenuItemCategoryMissing.Intersects(value)){return "kValidityMenuItemCategoryMissing";}
+ if(ModInstance::kValidityMenuItemPathTooLong.Intersects(value)){return "kValidityMenuItemPathTooLong";}
+ if(ModInstance::kValidityMenuItemPathMissing.Intersects(value)){return "kValidityMenuItemPathMissing";}
+ if(ModInstance::kValidityMenuItemPathInvalid.Intersects(value)){return "kValidityMenuItemPathInvalid";}
+ if(ModInstance::kValidityInvalidTag.Intersects(value)){return "kValidityInvalidTag";}
+ if(ModInstance::kValidityIDCollision.Intersects(value)){return "kValidityIDCollision";}
+ if(ModInstance::kValidityActiveModCollision.Intersects(value)){return "kValidityActiveModCollision";}
+ if(ModInstance::kValidityMissingName.Intersects(value)){return "kValidityMissingName";}
+ if(ModInstance::kValidityMissingAuthor.Intersects(value)){return "kValidityMissingAuthor";}
+ if(ModInstance::kValidityMissingDescription.Intersects(value)){return "kValidityMissingDescription";}
+ if(ModInstance::kValidityMissingThumbnail.Intersects(value)){return "kValidityMissingThumbnail";}
+ if(ModInstance::kValidityMissingThumbnailFile.Intersects(value)){return "kValidityMissingThumbnailFile";}
+ if(ModInstance::kValidityMissingVersion.Intersects(value)){return "kValidityMissingVersion";}
+ if(ModInstance::kValidityCampaignTitleTooLong.Intersects(value)){return "kValidityCampaignTitleTooLong";}
+ if(ModInstance::kValidityCampaignTitleMissing.Intersects(value)){return "kValidityCampaignTitleMissing";}
+ if(ModInstance::kValidityCampaignTypeTooLong.Intersects(value)){return "kValidityCampaignTypeTooLong";}
+ if(ModInstance::kValidityCampaignTypeMissing.Intersects(value)){return "kValidityCampaignTypeMissing";}
+ if(ModInstance::kValidityCampaignIsLinearInvalidEnum.Intersects(value)){return "kValidityCampaignIsLinearInvalidEnum";}
+ if(ModInstance::kValidityLevelTitleTooLong.Intersects(value)){return "kValidityLevelTitleTooLong";}
+ if(ModInstance::kValidityLevelTitleMissing.Intersects(value)){return "kValidityLevelTitleMissing";}
+ if(ModInstance::kValidityLevelPathTooLong.Intersects(value)){return "kValidityLevelPathTooLong";}
+ if(ModInstance::kValidityLevelPathMissing.Intersects(value)){return "kValidityLevelPathMissing";}
+ if(ModInstance::kValidityLevelPathInvalid.Intersects(value)){return "kValidityLevelPathInvalid";}
+ if(ModInstance::kValidityLevelThumbnailTooLong.Intersects(value)){return "kValidityLevelThumbnailTooLong";}
+ if(ModInstance::kValidityLevelThumbnailMissing.Intersects(value)){return "kValidityLevelThumbnailMissing";}
+ if(ModInstance::kValidityLevelThumbnailInvalid.Intersects(value)){return "kValidityLevelThumbnailInvalid";}
+ if(ModInstance::kValidityInvalidPreviewImage.Intersects(value)){return "kValidityInvalidPreviewImage";}
+ if(ModInstance::kValidityInvalidSupportedVersion.Intersects(value)){return "kValidityInvalidSupportedVersion";}
+ if(ModInstance::kValidityInvalidLevelHookFile.Intersects(value)){return "kValidityInvalidLevelHookFile";}
+ if(ModInstance::kValidityInvalidNeedRestart.Intersects(value)){return "kValidityInvalidNeedRestart";}
+ if(ModInstance::kValidityItemTitleTooLong.Intersects(value)){return "kValidityItemTitleTooLong";}
+ if(ModInstance::kValidityItemTitleMissing.Intersects(value)){return "kValidityItemTitleMissing";}
+ if(ModInstance::kValidityItemCategoryTooLong.Intersects(value)){return "kValidityItemCategoryTooLong";}
+ if(ModInstance::kValidityItemCategoryMissing.Intersects(value)){return "kValidityItemCategoryMissing";}
+ if(ModInstance::kValidityItemPathTooLong.Intersects(value)){return "kValidityItemPathTooLong";}
+ if(ModInstance::kValidityItemPathMissing.Intersects(value)){return "kValidityItemPathMissing";}
+ if(ModInstance::kValidityItemPathFileMissing.Intersects(value)){return "kValidityItemPathFileMissing";}
+ if(ModInstance::kValidityItemThumbnailTooLong.Intersects(value)){return "kValidityItemThumbnailTooLong";}
+ if(ModInstance::kValidityItemThumbnailMissing.Intersects(value)){return "kValidityItemThumbnailMissing";}
+ if(ModInstance::kValidityItemThumbnailFileMissing.Intersects(value)){return "kValidityItemThumbnailFileMissing";}
+ if(ModInstance::kValidityCategoryTooLong.Intersects(value)){return "kValidityCategoryTooLong";}
+ if(ModInstance::kValidityCategoryMissing.Intersects(value)){return "kValidityCategoryMissing";}
+ if(ModInstance::kValidityCampaignThumbnailMissing.Intersects(value)){return "kValidityCampaignThumbnailMissing";}
+ if(ModInstance::kValidityCampaignThumbnailTooLong.Intersects(value)){return "kValidityCampaignThumbnailTooLong";}
+ if(ModInstance::kValidityCampaignThumbnailInvalid.Intersects(value)){return "kValidityCampaignThumbnailInvalid";}
+ if(ModInstance::kValidityMenuItemThumbnailTooLong.Intersects(value)){return "kValidityMenuItemThumbnailTooLong";}
+ if(ModInstance::kValidityMenuItemThumbnailInvalid.Intersects(value)){return "kValidityMenuItemThumbnailInvalid";}
+ if(ModInstance::kValidityCampaignMenuMusicTooLong.Intersects(value)){return "kValidityCampaignMenuMusicTooLong";}
+ if(ModInstance::kValidityCampaignMenuMusicMissing.Intersects(value)){return "kValidityCampaignMenuMusicMissing";}
+ if(ModInstance::kValidityCampaignMenuScriptTooLong.Intersects(value)){return "kValidityCampaignMenuScriptTooLong";}
+ if(ModInstance::kValidityMainScriptTooLong.Intersects(value)){return "kValidityMainScriptTooLong";}
+ if(ModInstance::kValidityAttributeIdTooLong.Intersects(value)){return "kValidityAttributeIdTooLong";}
+ if(ModInstance::kValidityAttributeIdInvalid.Intersects(value)){return "kValidityAttributeIdInvalid";}
+ if(ModInstance::kValidityAttributeValueTooLong.Intersects(value)){return "kValidityAttributeValueTooLong";}
+ if(ModInstance::kValidityAttributeValueInvalid.Intersects(value)){return "kValidityAttributeValueInvalid";}
+ if(ModInstance::kValidityLevelIdTooLong.Intersects(value)){return "kValidityLevelIdTooLong";}
+ if(ModInstance::kValidityCampaignIDTooLong.Intersects(value)){return "kValidityCampaignIDTooLong";}
+ if(ModInstance::kValidityCampaignIDMissing.Intersects(value)){return "kValidityCampaignIDMissing";}
+ if(ModInstance::kValidityLevelParameterNameTooLong.Intersects(value)){return "kValidityLevelParameterNameTooLong";}
+ if(ModInstance::kValidityLevelParameterNameMissing.Intersects(value)){return "kValidityLevelParameterNameMissing";}
+ if(ModInstance::kValidityLevelParameterValueTooLong.Intersects(value)){return "kValidityLevelParameterValueTooLong";}
+ if(ModInstance::kValidityLevelParameterValueMissing.Intersects(value)){return "kValidityLevelParameterValueMissing";}
+ if(ModInstance::kValidityLevelParameterTypeTooLong.Intersects(value)){return "kValidityLevelParameterTypeTooLong";}
+ return "UNKNOWN";
+}
+
+const ModValidity ModInstance::kValidityValid = ModValidity();
+const ModValidity ModInstance::kValidityBrokenXml = ModValidity(0);
+const ModValidity ModInstance::kValidityInvalidVersion = ModValidity(1);
+const ModValidity ModInstance::kValidityMissingReadRights = ModValidity(2);
+const ModValidity ModInstance::kValidityMissingXml = ModValidity(3);
+const ModValidity ModInstance::kValidityUnloaded = ModValidity(4);
+const ModValidity ModInstance::kValidityUnsupportedOvergrowthVersion = ModValidity(5);
+const ModValidity ModInstance::kValidityNoDataOnDisk = ModValidity(6);
+const ModValidity ModInstance::kValiditySteamworksError = ModValidity(7);
+const ModValidity ModInstance::kValidityNotInstalled = ModValidity(8);
+const ModValidity ModInstance::kValidityNotSubscribed = ModValidity(9);
+
+const ModValidity ModInstance::kValidityIDTooLong = ModValidity(10);
+const ModValidity ModInstance::kValidityIDMissing = ModValidity(11);
+const ModValidity ModInstance::kValidityIDInvalid = ModValidity(12);
+
+const ModValidity ModInstance::kValidityNameTooLong = ModValidity(13);
+const ModValidity ModInstance::kValidityVersionTooLong = ModValidity(14);
+const ModValidity ModInstance::kValidityAuthorTooLong = ModValidity(15);
+
+const ModValidity ModInstance::kValidityDescriptionTooLong = ModValidity(16);
+const ModValidity ModInstance::kValidityThumbnailMaxLength = ModValidity(17);
+const ModValidity ModInstance::kValidityTagsListTooLong = ModValidity(18);
+
+const ModValidity ModInstance::kValidityMenuItemTitleTooLong = ModValidity(19);
+const ModValidity ModInstance::kValidityMenuItemTitleMissing = ModValidity(20);
+
+const ModValidity ModInstance::kValidityMenuItemCategoryTooLong = ModValidity(21);
+const ModValidity ModInstance::kValidityMenuItemCategoryMissing = ModValidity(22);
+
+const ModValidity ModInstance::kValidityMenuItemPathTooLong = ModValidity(23);
+const ModValidity ModInstance::kValidityMenuItemPathMissing = ModValidity(24);
+const ModValidity ModInstance::kValidityMenuItemPathInvalid = ModValidity(25);
+
+const ModValidity ModInstance::kValidityInvalidTag = ModValidity(26);
+
+const ModValidity ModInstance::kValidityIDCollision = ModValidity(27);
+const ModValidity ModInstance::kValidityActiveModCollision = ModValidity(28);
+
+const ModValidity ModInstance::kValidityMissingName = ModValidity(29);
+const ModValidity ModInstance::kValidityMissingAuthor = ModValidity(30);
+const ModValidity ModInstance::kValidityMissingDescription = ModValidity(31);
+const ModValidity ModInstance::kValidityMissingThumbnail = ModValidity(32);
+const ModValidity ModInstance::kValidityMissingThumbnailFile = ModValidity(33);
+const ModValidity ModInstance::kValidityMissingVersion = ModValidity(34);
+
+const ModValidity ModInstance::kValidityCampaignTitleTooLong = ModValidity(35);
+const ModValidity ModInstance::kValidityCampaignTitleMissing = ModValidity(36);
+
+const ModValidity ModInstance::kValidityCampaignTypeTooLong = ModValidity(37);
+const ModValidity ModInstance::kValidityCampaignTypeMissing = ModValidity(38);
+
+const ModValidity ModInstance::kValidityCampaignIsLinearInvalidEnum = ModValidity(39);
+
+const ModValidity ModInstance::kValidityLevelTitleTooLong = ModValidity(40);
+const ModValidity ModInstance::kValidityLevelTitleMissing = ModValidity(41);
+
+const ModValidity ModInstance::kValidityLevelPathTooLong = ModValidity(42);
+const ModValidity ModInstance::kValidityLevelPathMissing = ModValidity(43);
+const ModValidity ModInstance::kValidityLevelPathInvalid = ModValidity(44);
+
+const ModValidity ModInstance::kValidityLevelThumbnailTooLong = ModValidity(45);
+const ModValidity ModInstance::kValidityLevelThumbnailMissing = ModValidity(46);
+const ModValidity ModInstance::kValidityLevelThumbnailInvalid = ModValidity(47);
+const ModValidity ModInstance::kValidityInvalidPreviewImage = ModValidity(48);
+const ModValidity ModInstance::kValidityInvalidSupportedVersion = ModValidity(49);
+const ModValidity ModInstance::kValidityInvalidLevelHookFile = ModValidity(50);
+const ModValidity ModInstance::kValidityInvalidNeedRestart = ModValidity(51);
+
+const ModValidity ModInstance::kValidityItemTitleTooLong = ModValidity(52);
+const ModValidity ModInstance::kValidityItemTitleMissing = ModValidity(53);
+
+const ModValidity ModInstance::kValidityItemCategoryTooLong = ModValidity(54);
+const ModValidity ModInstance::kValidityItemCategoryMissing = ModValidity(55);
+
+const ModValidity ModInstance::kValidityItemPathTooLong = ModValidity(56);
+const ModValidity ModInstance::kValidityItemPathMissing = ModValidity(57);
+const ModValidity ModInstance::kValidityItemPathFileMissing = ModValidity(58);
+
+const ModValidity ModInstance::kValidityItemThumbnailTooLong = ModValidity(59);
+const ModValidity ModInstance::kValidityItemThumbnailMissing = ModValidity(60);
+const ModValidity ModInstance::kValidityItemThumbnailFileMissing = ModValidity(61);
+
+const ModValidity ModInstance::kValidityCategoryTooLong = ModValidity(62);
+const ModValidity ModInstance::kValidityCategoryMissing = ModValidity(63);
+
+const ModValidity ModInstance::kValidityCampaignThumbnailMissing = ModValidity(64);
+const ModValidity ModInstance::kValidityCampaignThumbnailTooLong = ModValidity(65);
+const ModValidity ModInstance::kValidityCampaignThumbnailInvalid = ModValidity(66);
+
+const ModValidity ModInstance::kValidityMenuItemThumbnailTooLong = ModValidity(67);
+const ModValidity ModInstance::kValidityMenuItemThumbnailInvalid = ModValidity(68);
+
+const ModValidity ModInstance::kValidityCampaignMenuMusicTooLong = ModValidity(69);
+const ModValidity ModInstance::kValidityCampaignMenuMusicMissing = ModValidity(70);
+
+const ModValidity ModInstance::kValidityCampaignMenuScriptTooLong = ModValidity(71);
+const ModValidity ModInstance::kValidityMainScriptTooLong = ModValidity(72);
+
+const ModValidity ModInstance::kValidityAttributeIdTooLong = ModValidity(73);
+const ModValidity ModInstance::kValidityAttributeIdInvalid = ModValidity(74);
+
+const ModValidity ModInstance::kValidityAttributeValueTooLong = ModValidity(75);
+const ModValidity ModInstance::kValidityAttributeValueInvalid = ModValidity(76);
+
+const ModValidity ModInstance::kValidityLevelIdTooLong = ModValidity(77);
+
+const ModValidity ModInstance::kValidityCampaignIDTooLong = ModValidity(78);
+const ModValidity ModInstance::kValidityCampaignIDMissing = ModValidity(79);
+
+const ModValidity ModInstance::kValidityLevelParameterNameTooLong = ModValidity(80);
+const ModValidity ModInstance::kValidityLevelParameterNameMissing = ModValidity(81);
+
+const ModValidity ModInstance::kValidityLevelParameterValueTooLong = ModValidity(82);
+const ModValidity ModInstance::kValidityLevelParameterValueMissing = ModValidity(83);
+
+const ModValidity ModInstance::kValidityLevelParameterTypeTooLong = ModValidity(84);
+
+const ModValidity ModInstance::kValidityPoseNameTooLong = ModValidity(85);
+const ModValidity ModInstance::kValidityPoseNameMissing = ModValidity(86);
+const ModValidity ModInstance::kValidityPosePathTooLong = ModValidity(87);
+const ModValidity ModInstance::kValidityPosePathMissing = ModValidity(88);
+const ModValidity ModInstance::kValidityPoseCommandTooLong = ModValidity(89);
+const ModValidity ModInstance::kValidityPoseCommandMissing = ModValidity(90);
+
+const ModValidity ModInstance::kValidityUploadSteamworksBlockingMask =
+ ~ModValidity()
+ & ~ModInstance::kValidityIDCollision
+ & ~ModInstance::kValidityActiveModCollision
+;
+
+const ModValidity ModInstance::kValidityActivationBreakingErrorMask =
+ ModInstance::kValidityBrokenXml
+ | ModInstance::kValidityInvalidVersion
+ | ModInstance::kValidityMissingReadRights
+ | ModInstance::kValidityMissingXml
+ | ModInstance::kValidityNoDataOnDisk
+ | ModInstance::kValiditySteamworksError
+ | ModInstance::kValidityNotInstalled
+ | ModInstance::kValidityNotSubscribed
+ | ModInstance::kValidityIDTooLong
+ | ModInstance::kValidityIDMissing
+ | ModInstance::kValidityIDInvalid
+ | ModInstance::kValidityNameTooLong
+ | ModInstance::kValidityVersionTooLong
+ | ModInstance::kValidityAuthorTooLong
+ | ModInstance::kValidityDescriptionTooLong
+ | ModInstance::kValidityTagsListTooLong
+ | ModInstance::kValidityMenuItemTitleTooLong
+ | ModInstance::kValidityMenuItemCategoryTooLong
+ | ModInstance::kValidityMenuItemPathTooLong
+ | ModInstance::kValidityIDCollision
+ | ModInstance::kValidityActiveModCollision
+ | ModInstance::kValidityMissingName
+ | ModInstance::kValidityMissingAuthor
+ | ModInstance::kValidityMissingDescription
+ | ModInstance::kValidityMissingVersion
+ | ModInstance::kValidityCampaignTitleTooLong
+ | ModInstance::kValidityCampaignTypeTooLong
+ | ModInstance::kValidityCampaignIsLinearInvalidEnum
+ | ModInstance::kValidityCampaignMenuMusicTooLong
+ | ModInstance::kValidityLevelTitleTooLong
+ | ModInstance::kValidityLevelPathTooLong
+ | ModInstance::kValidityLevelThumbnailTooLong
+ | ModInstance::kValidityInvalidSupportedVersion
+ | ModInstance::kValidityItemTitleTooLong
+ | ModInstance::kValidityItemCategoryTooLong
+ | ModInstance::kValidityItemPathTooLong
+ | ModInstance::kValidityItemThumbnailTooLong
+ | ModInstance::kValidityCampaignMenuScriptTooLong
+ | ModInstance::kValidityMainScriptTooLong
+ | ModInstance::kValidityAttributeIdTooLong
+ | ModInstance::kValidityAttributeIdInvalid
+ | ModInstance::kValidityAttributeValueTooLong
+ | ModInstance::kValidityAttributeValueInvalid
+ | ModInstance::kValidityLevelIdTooLong
+ | ModInstance::kValidityCampaignIDTooLong
+ | ModInstance::kValidityCampaignIDMissing
+ | ModInstance::kValidityLevelParameterNameTooLong
+ | ModInstance::kValidityLevelParameterNameMissing
+ | ModInstance::kValidityLevelParameterValueTooLong
+ | ModInstance::kValidityLevelParameterValueMissing
+ | ModInstance::kValidityLevelParameterTypeTooLong
+ | ModInstance::kValidityPoseNameTooLong
+ | ModInstance::kValidityPoseNameMissing
+ | ModInstance::kValidityPosePathTooLong
+ | ModInstance::kValidityPosePathMissing
+ | ModInstance::kValidityPoseCommandTooLong
+ | ModInstance::kValidityPoseCommandMissing
+;
+
+ModInstance::Parameter::Parameter() : type("empty") {
+
+}
+
+ModInstance::ModInstance( const std::string &_path ) :
+valid(kValidityUnloaded),
+active(false),
+modsource(ModSourceLocalModFolder),
+sid(sid_counter++) {
+ if( !has_compiled_regex ) {
+ menu_item_id_regex.Compile( menu_item_id_regex_string );
+ id_regex.Compile( id_regex_string );
+ version_regex.Compile( version_regex_string );
+ has_compiled_regex = true;
+ }
+ path = _path;
+
+ has_been_activated = false;
+
+ Reload();
+}
+
+ModInstance::ModInstance( const UGCID _ugc_id ) :
+valid(kValidityUnloaded),
+active(false),
+modsource(ModSourceSteamworks),
+ugc_id(_ugc_id),
+sid(sid_counter++)
+{
+ LOGI << "Creating with ugc_id: " << _ugc_id << std::endl;
+ if( !has_compiled_regex ) {
+ menu_item_id_regex.Compile( menu_item_id_regex_string );
+ id_regex.Compile( id_regex_string );
+ version_regex.Compile( version_regex_string );
+ has_compiled_regex = true;
+ }
+
+ has_been_activated = false;
+}
+
+void ModInstance::PurgeActiveSettings() {
+ ModLoading::Instance().acp.RemoveModInstance(id,modsource);
+}
+
+void ModInstance::ParseLevel(ModInstance::Level* level, TiXmlElement* pElemLevel, uint32_t fallback_id) {
+ int err = level->title.set(pElemLevel->Attribute("title"));
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElemLevel, kValidityLevelTitleTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ PARSE_ERROR(pElemLevel, kValidityLevelTitleMissing);
+ }
+
+ err = level->id.set(pElemLevel->Attribute("id"));
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElemLevel, kValidityLevelIdTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ char temp[MOD_LEVEL_ID_MAX_LENGTH];
+ FormatString(temp, MOD_LEVEL_ID_MAX_LENGTH, "%u", fallback_id);
+ temp[MOD_LEVEL_ID_MAX_LENGTH-1] = '\0';
+ level->id.set(temp);
+ }
+
+ err = level->thumbnail.set(pElemLevel->Attribute("thumbnail"));
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElemLevel, kValidityLevelThumbnailTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ level->thumbnail.set("Images/thumb_fallback.png");
+ } else if( strmtch(level->thumbnail,"") ) {
+ level->thumbnail.set("Images/thumb_fallback.png");
+ } //else if( FileExists(AssemblePath(path,AssemblePath("Data",level->thumbnail)),kAbsPath) == false && FileExists(AssemblePath("Data",level->thumbnail),kDataPaths) == false) {
+ // PARSE_ERROR2(pElemLevel, kValidityLevelThumbnailInvalid, level->thumbnail);
+ //}
+
+ const char* supports_online = pElemLevel->Attribute("supports_online");
+ level->supports_online = false;
+
+ if (supports_online != NULL) {
+ if (strcmp(supports_online, "true") == 0) {
+ level->supports_online = true;
+ }
+ }
+
+ const char* requires_online = pElemLevel->Attribute("requires_online");
+ level->requires_online = false;
+
+ if (requires_online != NULL) {
+ if (strcmp(requires_online, "true") == 0) {
+ level->requires_online = true;
+ }
+ }
+
+ const char* optional = pElemLevel->Attribute("completion_optional");
+ level->completion_optional = false;
+ if(optional != NULL) {
+ if(strcmp(optional, "true") == 0) {
+ level->completion_optional = true;
+ } else if(strcmp(optional, "false") != 0) {
+ LOGE << "Error when parsing " << path << "/mod.xml: invalid completion_optional value (can only be true/false)" << std::endl;
+ PARSE_ERROR(NULL, kValidityBrokenXml);
+ }
+ }
+
+ if( pElemLevel->Attribute("path") ) {//Check if we're using the modern Level or the old level->format
+ err = level->path.set(pElemLevel->Attribute("path"));
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElemLevel, kValidityLevelPathTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ PARSE_ERROR(pElemLevel, kValidityLevelPathMissing);
+ } //else if( FileExists( AssemblePath( path, AssemblePath( "Data/Levels", level->path ) ), kAbsPath ) == false && FileExists(AssemblePath( "Data/Levels", level->path ), kDataPaths) == false ) {
+ // PARSE_ERROR2(pElemLevel, kValidityLevelPathInvalid, level->path);
+ //}
+
+ TiXmlElement* pElemParameter = pElemLevel->FirstChildElement("Parameter");
+ if( pElemParameter ) {
+ ParseParameter(&level->parameter,pElemParameter, "");
+ } else {
+ level->parameter.type.set("empty");
+ }
+ } else {//This is the old format, where the body of the <Level> is the path.
+ err = level->path.set(pElemLevel->GetText());
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElemLevel, kValidityLevelPathTooLong);
+ } else if( err == SOURCE_IS_NULL ){
+ PARSE_ERROR(pElemLevel, kValidityLevelPathMissing);
+ } //else if( FileExists(AssemblePath(path, AssemblePath("Data/Levels", level->path)), kAbsPath) == false && FileExists(AssemblePath("Data/Levels", level->path), kDataPaths) == false) {
+ // PARSE_ERROR2(pElemLevel, kValidityLevelPathInvalid, level->path);
+ //}
+
+ level->parameter.type.set("empty");
+ }
+}
+
+void ModInstance::ParseParameter(ModInstance::Parameter* parameter, TiXmlElement* pElemParameter, const char* parent_type) {
+ int err = parameter->type.set(pElemParameter->Attribute("type"));
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElemParameter, kValidityLevelParameterTypeTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ parameter->type.set("string");
+ } else if( strmtch(parameter->type,"") ) {
+ parameter->type.set("string");
+ }
+
+ err = parameter->name.set(pElemParameter->Attribute("name"));
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElemParameter, kValidityLevelParameterNameTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ if( strmtch(parent_type,"array") == false ) {
+ PARSE_ERROR(pElemParameter, kValidityLevelParameterNameMissing);
+ }
+ }
+
+ err = parameter->value.set(pElemParameter->Attribute("value"));
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElemParameter, kValidityLevelParameterValueTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ if( strmtch(parameter->type,"array") == false && strmtch(parameter->type,"table") == false) {
+ PARSE_ERROR(pElemParameter, kValidityLevelParameterValueMissing);
+ }
+ }
+
+ TiXmlElement* pElemParameterSub = pElemParameter->FirstChildElement("Parameter");
+
+ while(pElemParameterSub) {
+ parameter->parameters.resize(parameter->parameters.size()+1);
+ ParseParameter(&parameter->parameters[parameter->parameters.size()-1],pElemParameterSub,parameter->type);
+ pElemParameterSub = pElemParameterSub->NextSiblingElement("Parameter");
+ }
+}
+
+void ModInstance::Reload( ) {
+ LOGD << "Reloading mod data" << std::endl;
+ valid = kValidityValid;
+
+ bool load_from_disk = false;
+
+ id.set("");
+ name.set("");
+ category.set("");
+ version.set("");
+ author.set("");
+ description.set("");
+ thumbnail.set("");
+
+ campaigns.clear();
+ tags.clear();
+ preview_images.clear();
+ supported_versions.clear();
+ level_hook_files.clear();
+ mod_dependencies.clear();
+ main_menu_items.clear();
+ items.clear();
+ levels.clear();
+ invalid_item_paths.clear();
+ poses.clear();
+
+ overload_files.clear();
+ manifest.clear();
+
+ if( modsource == ModSourceSteamworks ) {
+#if ENABLE_STEAMWORKS
+ SteamworksUGC* ugc = Steamworks::Instance()->GetUGC();
+ if(ugc) {
+ std::vector<SteamworksUGCItem*>::iterator ugc_it = ugc->GetItem(ugc_id);
+ if( ugc_it != ugc->GetItemEnd() ) {
+ if( (*ugc_it)->IsSubscribed() ) {
+ if( (*ugc_it)->IsInstalled() ) {
+ path = (*ugc_it)->GetPath();
+ load_from_disk = true;
+ } else {
+ PARSE_ERROR(NULL, kValidityNotInstalled);
+ }
+ } else {
+ PARSE_ERROR(NULL, kValidityNotSubscribed);
+ }
+
+ LOGD << "Getting base data from ugc" << std::endl;
+ id.set( (*ugc_it)->id);
+ version.set( (*ugc_it)->version);
+ name.set( (*ugc_it)->name);
+ author.set( (*ugc_it)->author);
+ description.set((*ugc_it)->description);
+ append(tags, (*ugc_it)->tags);
+ } else {
+ LOGE << "Unable to find mod item with ugcid: " << ugc_id << std::endl;
+ PARSE_ERROR(NULL, kValiditySteamworksError);
+ }
+ } else {
+ LOGE << "Somehow i have a steamworks mod loaded, but no valid UGC instance. This is unexpected, unable to reload mod" << std::endl;
+ PARSE_ERROR(NULL, kValiditySteamworksError);
+ }
+#else
+ LOGE << "Game was not compiled with Steamworks support." << std::endl;
+ PARSE_ERROR(NULL, kValiditySteamworksError);
+#endif
+ } else if (modsource == ModSourceLocalModFolder) {
+ load_from_disk = true;
+ }
+
+ if( load_from_disk ) {
+ std::string modxmlpath = path + "/mod.xml";
+
+ GenerateManifest( path.c_str(), manifest );
+
+ //We need to sort to allow intersectiontests
+ std::sort( manifest.begin(), manifest.end() );
+
+ if( fileExist( modxmlpath.c_str() ) ) {
+ int err = 0;
+
+ if( fileReadable( modxmlpath.c_str() ) ) {
+ TiXmlDocument doc( modxmlpath.c_str() );
+ doc.LoadFile();
+
+ if( doc.Error() ) {
+ LOGE << "Error when parsing " << path << "/mod.xml: " << doc.ErrorDesc() << std::endl;
+ PARSE_ERROR(NULL, kValidityBrokenXml);
+ }
+
+ TiXmlHandle hDoc(&doc);
+ TiXmlElement* pElem;
+ TiXmlHandle hRoot(0);
+
+ pElem = hDoc.FirstChildElement().Element();
+ if( !pElem ) {
+ LOGE << "Unable to load mod.xml " << modxmlpath << " broken xml file." << std::endl;
+ PARSE_ERROR(NULL, kValidityBrokenXml);
+ } else {
+ hRoot = TiXmlHandle(pElem);
+
+ pElem = hRoot.FirstChild( "Id" ).Element();
+ if(pElem) {
+ err = id.set(pElem->GetText());
+
+ if(err == SOURCE_TOO_LONG) {
+ PARSE_ERROR(pElem, kValidityIDTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ PARSE_ERROR(pElem, kValidityIDMissing);
+ } else if( false == id_regex.Match( id ) ) {
+ LOGE << "Mod name: " << id << " is illegal, mod is invalid." << std::endl;
+ PARSE_ERROR(pElem, kValidityIDInvalid);
+ }
+ } else {
+ PARSE_ERROR(pElem, kValidityIDMissing);
+ }
+
+ pElem = hRoot.FirstChild( "Name" ).Element();
+ if( pElem ) {
+ LOGD << "Getting name from xml" << std::endl;
+
+ err = name.set(pElem->GetText());
+
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElem, kValidityNameTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ PARSE_ERROR(pElem, kValidityMissingName);
+ }
+ } else {
+ PARSE_ERROR(pElem, kValidityMissingName);
+ }
+
+ pElem = hRoot.FirstChild( "Category" ).Element();
+ if( pElem ) {
+ LOGD << "Getting category from xml" << std::endl;
+
+ err = category.set(pElem->GetText());
+
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElem, kValidityCategoryTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ PARSE_ERROR(pElem, kValidityCategoryMissing);
+ }
+ } else {
+ PARSE_ERROR(pElem, kValidityCategoryMissing);
+ }
+
+
+ pElem = hRoot.FirstChild( "Author" ).Element();
+ if( pElem ) {
+ err = author.set(pElem->GetText());
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElem, kValidityAuthorTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ PARSE_ERROR(pElem, kValidityMissingAuthor);
+ }
+ } else {
+ PARSE_ERROR(pElem, kValidityMissingAuthor);
+ }
+
+ pElem = hRoot.FirstChild( "Description" ).Element();
+
+ if( pElem ) {
+ err = description.set(pElem->GetText());
+
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElem, kValidityDescriptionTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ PARSE_ERROR(pElem, kValidityMissingDescription);
+ }
+ } else {
+ PARSE_ERROR(pElem, kValidityMissingDescription);
+ }
+
+ pElem = hRoot.FirstChild( "Version" ).Element();
+ if( pElem ) {
+ err = version.set(pElem->GetText());
+
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElem, kValidityVersionTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ PARSE_ERROR(pElem, kValidityMissingVersion);
+ } else if( !version_regex.Match( version ) ) {
+ LOGE << "Mod version: " << version << " is illegal, mod is invalid" << std::endl;
+ PARSE_ERROR(pElem, kValidityInvalidVersion);
+ }
+ } else {
+ PARSE_ERROR(pElem, kValidityMissingVersion);
+ }
+
+ pElem = hRoot.FirstChild( "Thumbnail" ).Element();
+ if( pElem ) {
+ err = thumbnail.set(pElem->GetText());
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElem, kValidityThumbnailMaxLength);
+ } else if ( err == SOURCE_IS_NULL ) {
+ PARSE_ERROR(pElem, kValidityMissingThumbnail);
+ } else {
+ Path thumbpath = FindFilePath( AssemblePath( path.c_str(), thumbnail.c_str() ).c_str(), kAbsPath, true );
+ if( thumbpath.valid == false) {
+ PARSE_ERROR(pElem, kValidityMissingThumbnailFile);
+ }
+ }
+ } else {
+ PARSE_ERROR(pElem, kValidityMissingThumbnail);
+ }
+
+ pElem = hRoot.FirstChild( "PreviewImage" ).Element();
+ while( pElem ) {
+ if( pElem->GetText() ) {
+ preview_images.push_back(pElem->GetText());
+ } else {
+ LOGE << "Got a <PreviewImage> with invalid body" << std::endl;
+ PARSE_ERROR(pElem, kValidityInvalidPreviewImage);
+ }
+ pElem = pElem->NextSiblingElement("PreviewImage");
+ }
+
+ pElem = hRoot.FirstChild( "Tag" ).Element();
+ while( pElem ) {
+ const char* tag = pElem->GetText();
+ if( tag ) {
+ if( false == strcont( tag, "," ) ) {
+ tags.insert(tag);
+ } else {
+ PARSE_ERROR(pElem, kValidityInvalidTag);
+ }
+
+ LOGD << "Tag: " << tag << std::endl;
+ } else {
+ PARSE_ERROR(pElem, kValidityInvalidTag);
+ }
+ pElem = pElem->NextSiblingElement("Tag");
+ }
+
+ if( GetTagsListString().size() > (MOD_TAGS_MAX_LENGTH - 1) ) {
+ PARSE_ERROR(pElem, kValidityTagsListTooLong);
+ }
+
+ pElem = hRoot.FirstChild( "SupportedVersion" ).Element();
+ while( pElem ) {
+ if(pElem->GetText()) {
+ supported_versions.push_back( pElem->GetText() );
+ } else {
+ PARSE_ERROR(pElem, kValidityInvalidSupportedVersion);
+ }
+ LOGD << "SupportedVersion: " << pElem->GetText() << std::endl;
+ pElem = pElem->NextSiblingElement("SupportedVersion");
+ }
+
+ pElem = hRoot.FirstChild( "LevelHookFile" ).Element();
+ while( pElem ) {
+ if( pElem->GetText() ) {
+ level_hook_files.push_back( pElem->GetText() );
+ } else {
+ PARSE_ERROR(pElem, kValidityInvalidLevelHookFile);
+ }
+ LOGD << "LevelHookFile: " << pElem->GetText() << std::endl;
+
+ pElem = pElem->NextSiblingElement("LevelHookFile");
+ }
+
+ pElem = hRoot.FirstChild( "MenuItem" ).Element();
+ while( pElem ) {
+ MenuItem mi;
+
+ err = mi.title.set(pElem->Attribute("title"));
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElem, kValidityMenuItemTitleTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ PARSE_ERROR(pElem, kValidityMenuItemTitleMissing);
+ }
+
+ err = mi.category.set(pElem->Attribute("category"));
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElem, kValidityMenuItemCategoryTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ PARSE_ERROR(pElem, kValidityMenuItemCategoryMissing);
+ }
+
+ err = mi.thumbnail.set(pElem->Attribute("thumbnail"));
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElem, kValidityMenuItemThumbnailTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ //We accept this not having a value.
+ } //else if( FileExists(AssemblePath(path, AssemblePath("Data",mi.thumbnail)),kAbsPath) == false && FileExists(AssemblePath("Data",mi.thumbnail),kDataPaths) == false ) {
+ // PARSE_ERROR2(pElem, kValidityMenuItemThumbnailInvalid, mi.thumbnail);
+ //}
+
+ err = mi.path.set(pElem->GetText());
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElem, kValidityMenuItemPathTooLong);
+ } else if( err == SOURCE_IS_NULL ){
+ PARSE_ERROR(pElem, kValidityMenuItemPathMissing);
+ }
+
+ main_menu_items.push_back(mi);
+ pElem = pElem->NextSiblingElement("MenuItem");
+ }
+
+ bool requireID = false;
+ {
+ TiXmlElement* campaignElem = hRoot.FirstChild( "Campaign" ).Element();
+ if(campaignElem) {
+ campaignElem = campaignElem->NextSiblingElement( "Campaign" );
+ if(campaignElem)
+ requireID = true;
+ }
+ }
+ pElem = hRoot.FirstChild( "Campaign" ).Element();
+ while( pElem ) {
+ Campaign campaign;
+ err = campaign.id.set(pElem->Attribute("id"));
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElem, kValidityCampaignIDTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ if(requireID)
+ PARSE_ERROR(pElem, kValidityCampaignIDMissing);
+ else
+ campaign.id.set(id);
+ }
+
+ err = campaign.title.set(pElem->Attribute("title"));
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElem, kValidityCampaignTitleTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ PARSE_ERROR(pElem, kValidityCampaignTitleMissing);
+ }
+
+ err = campaign.thumbnail.set(pElem->Attribute("thumbnail"));
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElem, kValidityCampaignThumbnailTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ //We actually accept as being empty.
+ //PARSE_ERROR(pElem, kValidityCampaignThumbnailMissing);
+ campaign.thumbnail.set("Images/thumb_fallback.png");
+ } else if( strmtch(campaign.thumbnail, "") ) {
+ campaign.thumbnail.set("Images/thumb_fallback.png");
+ } //else if( FileExists(AssemblePath(path, AssemblePath("Data",campaign.thumbnail)),kAbsPath) == false && FileExists(AssemblePath("Data",campaign.thumbnail),kDataPaths) == false ) {
+ // PARSE_ERROR2(pElem, kValidityCampaignThumbnailInvalid, campaign.thumbnail);
+ //}
+
+ err = campaign.menu_script.set(pElem->Attribute("menu_script"));
+
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElem, kValidityCampaignMenuScriptTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ campaign.menu_script.set("standard_campaign_menu.as");
+ }
+
+ const char* supports_online = pElem->Attribute("supports_online");
+ campaign.supports_online = false;
+ if (supports_online != NULL) {
+ if (strcmp(supports_online, "true") == 0) {
+ campaign.supports_online = true;
+ }
+ }
+
+ const char* requires_online = pElem->Attribute("requires_online");
+ campaign.requires_online = false;
+ if (requires_online != NULL) {
+ if (strcmp(requires_online, "true") == 0) {
+ campaign.requires_online = true;
+ }
+ }
+
+ err = campaign.main_script.set(pElem->Attribute("main_script"));
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElem, kValidityMainScriptTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ campaign.main_script.set("campaign/overgrowth_campaign.as");
+ }
+
+ campaign.parameter.type.set("empty");
+
+ TiXmlElement* pElemSub = pElem->FirstChildElement();
+ while( pElemSub ) {
+ if( strmtch(pElemSub->Value(),"Parameter") ) {
+ if( pElemSub ) {
+ ParseParameter(&campaign.parameter,pElemSub,"");
+ }
+ } else if( strmtch(pElemSub->Value(),"Level") ) {
+ campaign.levels.resize(campaign.levels.size()+1);
+ ParseLevel(&campaign.levels[campaign.levels.size()-1],pElemSub,(unsigned int)campaign.levels.size());
+ } else if( strmtch(pElemSub->Value(),"Attribute") ) {
+ Attribute attribute;
+
+ err = attribute.id.set(pElemSub->Attribute("id"));
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElemSub, kValidityAttributeIdTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ PARSE_ERROR(pElemSub, kValidityAttributeIdInvalid);
+ }
+
+ err = attribute.value.set(pElemSub->Attribute("value"));
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElemSub, kValidityAttributeValueTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ PARSE_ERROR(pElemSub, kValidityAttributeValueInvalid);
+ }
+
+ campaign.attributes.push_back(attribute);
+ } else {
+ LOGW << "Unknown element name in campaign: " << pElemSub->Value() << " on row " << pElemSub->Row() << std::endl;
+ }
+
+ pElemSub = pElemSub->NextSiblingElement();
+ }
+
+ pElem = pElem->NextSiblingElement("Campaign");
+
+ campaigns.push_back(campaign);
+ }
+
+ pElem = hRoot.FirstChild( "Level" ).Element();
+ while( pElem ) {
+ levels.resize(levels.size()+1);
+ ParseLevel(&levels[levels.size()-1],pElem,(unsigned int)levels.size());
+ pElem = pElem->NextSiblingElement("Level");
+ }
+
+ pElem = hRoot.FirstChild( "NeedsRestart" ).Element();
+ if( pElem ) {
+ if( pElem->GetText() ) {
+ int v = saysTrue(pElem->GetText());
+
+ if( v != -1 ) {
+ needs_restart = (v==1);
+ } else {
+ LOGW << "NeedsRestart contains invalid value, needs to be true or false, is \"" << pElem->GetText() << "\"" << std::endl;
+ needs_restart = true;
+ }
+ } else {
+ PARSE_ERROR(pElem, kValidityInvalidNeedRestart);
+ }
+ } else {
+ needs_restart = true;
+ }
+
+ pElem = hRoot.FirstChild( "SupportsOnline" ).Element();
+ if( pElem ) {
+ if( pElem->GetText() ) {
+ int v = saysTrue(pElem->GetText());
+
+ if( v != -1 ) {
+ supports_online = (v==1);
+ } else {
+ LOGW << "SupportsOnline contains invalid value, needs to be true or false, is \"" << pElem->GetText() << "\"" << std::endl;
+ supports_online = true;
+ }
+ } else {
+ PARSE_ERROR(pElem, kValidityInvalidNeedRestart);
+ }
+ } else {
+ supports_online = false;
+ }
+
+ pElem = hRoot.FirstChild( "ModDependency" ).Element();
+ while( pElem ) {
+ TiXmlElement* pModDepElem;
+
+ ModDependency moddep;
+
+ pModDepElem = pElem->FirstChildElement("Id");
+ if( pModDepElem ) {
+ if( pModDepElem->GetText() ) {
+ moddep.id = pModDepElem->GetText();
+ }
+ }
+
+ pModDepElem = pElem->FirstChildElement("Version");
+ while( pModDepElem ) {
+ if( pModDepElem->GetText() ) {
+ moddep.versions.push_back( pModDepElem->GetText() );
+ }
+
+ pModDepElem = pModDepElem->NextSiblingElement("Version");
+ }
+
+ mod_dependencies.push_back( moddep );
+
+ pElem = pElem->NextSiblingElement("ModDependency");
+ }
+
+ pElem = hRoot.FirstChild( "OverloadFile" ).Element();
+ while( pElem ) {
+ if( pElem->GetText() ) {
+ overload_files.push_back( pElem->GetText() );
+ }
+
+ pElem = pElem->NextSiblingElement("OverloadFile");
+ }
+
+ pElem = hRoot.FirstChild( "Item" ).Element();
+ while( pElem ) {
+ Item item;
+
+ err = item.title.set(pElem->Attribute("title"));
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElem, kValidityItemTitleTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ PARSE_ERROR(pElem, kValidityItemTitleMissing);
+ }
+
+ err = item.category.set(pElem->Attribute("category"));
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElem, kValidityItemCategoryTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ PARSE_ERROR(pElem, kValidityItemCategoryMissing);
+ }
+
+ err = item.path.set(pElem->Attribute("path"));
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElem, kValidityItemPathTooLong);
+ invalid_item_paths.push_back(item.path.str());
+ } else if( err == SOURCE_IS_NULL ) {
+ PARSE_ERROR(pElem, kValidityItemPathMissing);
+ } //else if( FileExists( AssemblePath( path.c_str(), item.path.c_str() ), kAbsPath ) == false && FileExists(item.path, kDataPaths) == false) {
+ // PARSE_ERROR2(pElem, kValidityItemPathFileMissing, item.path);
+ // invalid_item_paths.push_back(item.path.str());
+ //}
+
+ err = item.thumbnail.set(pElem->Attribute("thumbnail"));
+ if( err == SOURCE_TOO_LONG ) {
+ PARSE_ERROR(pElem, kValidityItemThumbnailTooLong);
+ } else if( err == SOURCE_IS_NULL ) {
+ PARSE_ERROR(pElem, kValidityItemThumbnailMissing);
+ } //else if(
+ //strmtch(item.thumbnail, "") == false
+ // && FileExists( AssemblePath( path.c_str(), item.thumbnail.c_str() ), kAbsPath ) == false
+ // && FileExists(item.thumbnail, kDataPaths) == false) {
+ //PARSE_ERROR2(pElem, kValidityItemThumbnailFileMissing, item.thumbnail);
+ //}
+
+ pElem = pElem->NextSiblingElement("Item");
+
+ items.push_back(item);
+ }
+
+ pElem = hRoot.FirstChild("Pose").Element();
+ while (pElem) {
+ Pose pose;
+
+ err = pose.name.set(pElem->Attribute("name"));
+ if (err == SOURCE_TOO_LONG) {
+ PARSE_ERROR(pElem, kValidityPoseNameTooLong);
+ }
+ else if (err == SOURCE_IS_NULL) {
+ PARSE_ERROR(pElem, kValidityPoseNameMissing);
+ }
+
+ err = pose.command.set(pElem->Attribute("command"));
+ if (err == SOURCE_TOO_LONG) {
+ PARSE_ERROR(pElem, kValidityPoseCommandTooLong);
+ }
+ else if (err == SOURCE_IS_NULL) {
+ PARSE_ERROR(pElem, kValidityPoseCommandMissing);
+ }
+
+ err = pose.path.set(pElem->Attribute("path"));
+ if (err == SOURCE_TOO_LONG) {
+ PARSE_ERROR(pElem, kValidityPosePathTooLong);
+ }
+ else if (err == SOURCE_IS_NULL) {
+ PARSE_ERROR(pElem, kValidityPosePathMissing);
+ }
+ Path animation_path = FindFilePath(pose.path, kAnyPath, true);
+ if (animation_path.valid == false) {
+ LOGE << "Path to animation is invalid " << pose.path << ", file is missing." << std::endl;
+ }
+ else {
+ poses.push_back(pose);
+ }
+ pElem = pElem->NextSiblingElement("Pose");
+ }
+
+ pElem = hRoot.FirstChild("Languages").FirstChild("Language").Element();
+ while(pElem) {
+ std::string shortcode = pElem->Attribute("shortcode");
+ std::string name = pElem->Attribute("name");
+ const char* local_name = pElem->Attribute("local_name");
+ std::string local_name_str = local_name ? local_name : "";
+ LanguageData new_data;
+ new_data.language = name;
+ new_data.local_language = local_name_str;
+ language_data[shortcode] = new_data;
+ pElem = pElem->NextSiblingElement("Language");
+ }
+ }
+ } else {
+ LOGE << "Unable to load mod.xml " << modxmlpath << ", missing readable rights." << std::endl;
+ PARSE_ERROR(NULL, kValidityMissingReadRights);
+ }
+ } else {
+ LOGE << "Unable to load mod.xml " << modxmlpath << ", file is missing." << std::endl;
+ PARSE_ERROR(NULL, kValidityMissingXml);
+ }
+
+ //Check if mod has files in root folder we don't like.
+ for( unsigned i = 0; i < manifest.size(); i++ ) {
+ bool is_root = true;
+
+ std::string &str = manifest[i];
+ for( unsigned k = 0; k < str.size(); k++ ) {
+ if( str[k] == '/' ) {
+ is_root = false;
+ }
+ }
+
+ const static size_t LOC_LENGTH = strlen("Localized/");
+ if(memcmp("Localized/", str.c_str(), LOC_LENGTH) == 0) {
+ if(str.size() > 9 && strcmp(str.c_str() + str.size() - 9, "_meta.xml") == 0) {
+ size_t second_slash = str.find("/", LOC_LENGTH);
+
+ std::string shortcode = str.substr(LOC_LENGTH, second_slash - LOC_LENGTH);
+ std::string level = str.substr(second_slash + 1, str.size() - (second_slash + 1));
+ level.replace(level.size() - strlen("_meta.xml"), strlen("_meta.xml"), ".xml");
+
+ std::string xml_path = path + "/" + str;
+ if(fileExist(xml_path.c_str())) {
+ int err = 0;
+
+ if(fileReadable(xml_path.c_str())) {
+ TiXmlDocument doc(xml_path.c_str());
+ doc.LoadFile();
+
+ if( doc.Error() ) {
+ LOGE << "Error when parsing " << xml_path << doc.ErrorDesc() << std::endl;
+ PARSE_ERROR(NULL, kValidityBrokenXml);
+ }
+
+ TiXmlHandle hDoc(&doc);
+ TiXmlElement* pElem;
+ TiXmlHandle hRoot(0);
+
+ pElem = hDoc.FirstChildElement().Element();
+ if( !pElem ) {
+ LOGE << "Unable to load level metadata file " << xml_path << " broken xml file." << std::endl;
+ PARSE_ERROR(NULL, kValidityBrokenXml);
+ } else {
+ hRoot = TiXmlHandle(pElem);
+ pElem = hRoot.FirstChild("LoadTip").Element();
+ if(pElem) {
+ language_data[shortcode].per_level_data[level].loading_screen_tip = pElem->GetText();
+ }
+ pElem = hRoot.FirstChild("Title").Element();
+ if(pElem) {
+ language_data[shortcode].per_level_data[level].name = pElem->GetText();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if( is_root ) {
+ if(strmtch("Data",str.c_str()) || strmtch("mod.xml", str.c_str()) || strmtch("Localized", str.c_str())) {
+
+ } else {
+ LOGW << "Mod " << id << " has files in root besides mod.xml and Data, this is bad form. File is question " << str << " consider moving it to Data." << std::endl;
+ }
+ }
+ }
+
+ std::vector<std::string>::iterator supported_versions_it;
+
+ for( supported_versions_it = supported_versions.begin();
+ supported_versions_it != supported_versions.end();
+ supported_versions_it++ ) {
+
+ std::vector<std::string> split_string;
+ split(*supported_versions_it, '.', split_string);
+
+ if( split_string.size() >= 3 ) {
+ if( split_string[0] == GetVersionMajor() && split_string[1] == GetVersionMinor() && (split_string[2] == GetVersionPatch()) ) {
+ explicit_version_support = true;
+ }
+ }
+
+ if( split_string.size() == 2 ) {
+ if(split_string[0] == GetVersionMajor() && split_string[1] == GetVersionMinor()) {
+ explicit_version_support = true;
+ }
+ }
+
+ //Accept either the short form version or the full name.
+ if( GetShortBuildTag() == *supported_versions_it || std::string(GetBuildVersion()) == *supported_versions_it ) {
+ explicit_version_support = true;
+ }
+ }
+ }
+
+ if( strmtch( "com-wolfire-lugaru-campaign", id ) ) {
+ is_core = true;
+ } else if( strmtch( "com-wolfire-overgrowth-campaign", id ) ) {
+ is_core = true;
+ } else if( strmtch( "com-wolfire-overgrowth-core", id ) ) {
+ is_core = true;
+ } else if( strmtch( "com-wolfire-timbles-therium", id ) ) {
+ is_core = true;
+ } else if( strmtch( "com-wolfire-drika", id ) ) {
+ is_core = true;
+ } else if( strmtch( "com-wolfire-versus", id ) ) {
+ is_core = true;
+ } else if( strmtch( "com-wolfire-versus-online", id ) ) {
+ is_core = true;
+ } else if( strmtch( "com-wolfire-sandbox-levels", id ) ) {
+ is_core = true;
+ } else if( strmtch( "com-wolfire-arena", id ) ) {
+ is_core = true;
+ } else {
+ is_core = false;
+ }
+
+ if( is_core ) {
+ if (!Activate(true)) {
+ LOGW << "FAILED ACTIVATING CORE MOD \"" << id << "\"" << std::endl;
+ }
+ } else if( CanActivate() ) {
+ if(ModLoading::Instance().acp.HasModInstance(id,modsource)) {
+ ActiveModsParser::ModInstance mi = ModLoading::Instance().acp.GetModInstance(id,modsource);
+ Activate(mi.activated);
+ } else { //Fall backon checking the config file and purging the value if found.
+ std::stringstream active_index_ss;
+ if( modsource == ModSourceSteamworks ) {
+ active_index_ss << "mod_activated[" << id << "_" << version << "_steam]";
+ } else {
+ active_index_ss << "mod_activated[" << id << "_" << version << "]";
+ }
+ std::string active_index = active_index_ss.str();
+
+ //If we don't have anything in the state file, pull in from config.
+ //Then remove value from old config.
+ if( config.HasKey( active_index ) ) {
+ Activate(config[active_index].toBool());
+ //Save to file immediately to ensure that the information isn't lost.
+ //Purging the value as it shouldn't be in the config.txt file in the long run.
+ config.RemoveConfig(active_index);
+ ModLoading::Instance().acp.Save(AssemblePath(std::string(GetWritePath(CoreGameModID).c_str()),"modconfig.xml"));
+ } else {
+ switch( modsource ) {
+ case ModSourceLocalModFolder:
+ Activate(false);
+ break;
+ case ModSourceSteamworks:
+ Activate(true);
+ break;
+ case ModSourceUnknown:
+ LOGE << "Unhandled mod source" << std::endl;
+ break;
+ }
+ }
+ }
+ } else {
+ //Don't call Activate because we might not own our id and version, basically we are perpertually deactivated.
+ active = false;
+ }
+}
+
+bool ModInstance::IsValid() const {
+ return GetValidityErrorsArr().empty();
+}
+
+bool ModInstance::CanActivate() const {
+ return (GetValidity() & kValidityActivationBreakingErrorMask).Empty();
+}
+
+bool ModInstance::IsActive() const {
+ return active;
+}
+
+bool ModInstance::IsCore() const {
+ return is_core;
+}
+
+ModID ModInstance::GetSid() const {
+ return sid;
+}
+
+bool ModInstance::Activate(bool value) {
+ bool ret_value = false;
+ bool prev_active = active;
+
+ if( is_core ) {
+ value = true;
+ }
+
+ if( value == active ) {
+ ret_value = true;
+ } else if( value == false ) {
+ RemovePath( path.c_str(), kModPaths );
+ active = false;
+ } else if( CanActivate() ) {
+ if( AddModPath( path.c_str(), GetSid() ) ) {
+ LOGI << "Added mod path to filesystem" << std::endl;
+ active = true;
+ ret_value = false;
+ } else {
+ LOGE << "Can't activate mod, can't add path to filesystem" << std::endl;
+ active = false;
+ ret_value = false;
+ }
+ } else {
+ switch( modsource ) {
+ case ModSourceLocalModFolder:
+ LOGE << "Can't activate invalid local mod " << id << "_" << version << std::endl;
+ break;
+ case ModSourceSteamworks:
+ LOGE << "Can't activate invalid steam mod " << id << "_" << version << std::endl;
+ break;
+ case ModSourceUnknown:
+ LOGE << "Can't activate invalid unknown mod " << id << "_" << version << std::endl;
+ break;
+ }
+ ret_value = false;
+ }
+
+ ModLoading::Instance().acp.SetModInstanceActive(id,modsource,active,version);
+
+ if( active != prev_active ) {
+ ModLoading::Instance().TriggerModActivationCallback(this);
+ ModLoading::Instance().RebuildLocalization();
+ }
+
+ return ret_value;
+}
+
+void ModInstance::SetHasBeenActivated() {
+ has_been_activated = true;
+}
+
+bool ModInstance::HasBeenActivated() const {
+ return has_been_activated;
+}
+
+bool ModInstance::NeedsRestart() const {
+ return needs_restart;
+}
+
+bool ModInstance::SupportsOnline() const {
+ return supports_online;
+}
+
+bool ModInstance::ExplicitVersionSupport() const {
+ return explicit_version_support;
+}
+
+uint32_t ModInstance::GetUploadValidity() {
+ uint32_t status = 0;
+ ModValidity val = GetValidity();
+
+ if( preview_images.size() > 0 ) {
+ Path previewpath = FindFilePath( AssemblePath( path, preview_images[0] ).c_str(), kAbsPath, true );
+ if( previewpath.valid == false) {
+ status |= k_UploadValidityInvalidPreviewImage;
+ }
+
+ //Check that the file isn't larger than 1MB (SI). Steam API limit for all preview files.
+ if( GetFileSize(previewpath) > 1 * 1000 * 1000 ) {
+ status |= k_UploadValidityOversizedPreviewImage;
+ }
+ } else {
+ status |= k_UploadValidityMissingPreview;
+ }
+
+ Path thumbpath = FindFilePath( AssemblePath( path.c_str(), thumbnail.c_str() ).c_str(), kAbsPath, true );
+ if( thumbpath.valid == false) {
+ status |= k_UploadValidityInvalidThumbnail;
+ }
+
+ Path modpath = FindFilePath( path.c_str(), kAbsPath, true );
+
+ if( modpath.valid == false ) {
+ status |= k_UploadValidityInvalidModFolder;
+ }
+
+ if(val.Intersects(kValidityBrokenXml)) {
+ status |= k_UploadValidityBrokenXml;
+ }
+
+ if(val.Intersects(kValidityIDInvalid)) {
+ status |= k_UploadValidityInvalidID;
+ }
+
+ if(val.Intersects(kValidityInvalidVersion)) {
+ status |= k_UploadValidityInvalidVersion;
+ }
+
+ if(val.Intersects(kValidityMissingReadRights)) {
+ status |= k_UploadValidityMissingReadRights;
+ }
+
+ if(val.Intersects(kValidityMissingXml)) {
+ status |= k_UploadValidityMissingXml;
+ }
+
+ ModValidity ignored =
+ kValidityBrokenXml
+ | kValidityIDInvalid
+ | kValidityInvalidVersion
+ | kValidityMissingReadRights
+ | kValidityMissingXml
+ ;
+
+ if( (val & ~ignored).Intersects(kValidityUploadSteamworksBlockingMask) ) {
+ status |= k_UploadValidityGenericValidityError;
+ }
+
+ return status;
+}
+
+#if ENABLE_STEAMWORKS
+SteamworksUGCItem* ModInstance::GetUGCItem() {
+ SteamworksUGC* ugc = Steamworks::Instance()->GetUGC();
+ if(ugc){
+ std::vector<SteamworksUGCItem*>::iterator ugc_it = ugc->GetItem(ugc_id);
+ if( ugc_it != ugc->GetItemEnd() ) {
+ return (*ugc_it);
+ } else {
+ return NULL;
+ }
+ } else {
+ return NULL;
+ }
+}
+#endif
+
+bool ModInstance::IsOwnedByCurrentUser() {
+ if( modsource == ModSourceSteamworks ) {
+#if ENABLE_STEAMWORKS
+ if( SteamUser() && SteamUGC() ) {
+ if( GetUGCItem() ) {
+ CSteamID userid = SteamUser()->GetSteamID();
+ if( GetUGCItem()->owner_id == userid ) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+#else
+ return false;
+#endif
+ } else {
+ return false;
+ }
+}
+
+bool ModInstance::IsSubscribed() {
+ if( modsource == ModSourceSteamworks ) {
+#if ENABLE_STEAMWORKS
+ if( GetUGCItem() ) {
+ return GetUGCItem()->IsSubscribed();
+ }
+#endif
+ return false;
+ } else {
+ return true;
+ }
+}
+
+bool ModInstance::IsInstalled() {
+ if( modsource == ModSourceSteamworks ) {
+#if ENABLE_STEAMWORKS
+ if( GetUGCItem() ) {
+ return GetUGCItem()->IsInstalled();
+ }
+#endif
+ return false;
+ } else {
+ return true;
+ }
+}
+
+bool ModInstance::IsDownloading() {
+ if( modsource == ModSourceSteamworks ) {
+#if ENABLE_STEAMWORKS
+ if( GetUGCItem() ) {
+ return GetUGCItem()->IsDownloading();
+ }
+#endif
+ return false;
+ } else {
+ return false;
+ }
+}
+
+bool ModInstance::IsDownloadPending() {
+ if( modsource == ModSourceSteamworks ) {
+#if ENABLE_STEAMWORKS
+ if( GetUGCItem() ) {
+ return GetUGCItem()->IsDownloadPending();
+ }
+#endif
+ return false;
+ } else {
+ return false;
+ }
+}
+
+bool ModInstance::IsNeedsUpdate() {
+ if( modsource == ModSourceSteamworks ) {
+#if ENABLE_STEAMWORKS
+ if( GetUGCItem() ) {
+ return GetUGCItem()->NeedsUpdate();
+ }
+#endif
+ return false;
+ } else {
+ return false;
+ }
+}
+
+bool ModInstance::IsFavorite() {
+ if( modsource == ModSourceSteamworks ) {
+#if ENABLE_STEAMWORKS
+ if( GetUGCItem() ) {
+ return GetUGCItem()->IsFavorite();
+ }
+#endif
+ return false;
+ } else {
+ return false;
+ }
+
+}
+
+ModInstance::UserVote ModInstance::GetUserVote() {
+#if ENABLE_STEAMWORKS
+ if( GetUGCItem() ) {
+ switch(GetUGCItem()->GetUserVote()){
+ case SteamworksUGCItem::k_VoteUnknown: return k_VoteUnknown;
+ case SteamworksUGCItem::k_VoteNone: return k_VoteNone;
+ case SteamworksUGCItem::k_VoteUp: return k_VoteUp;
+ case SteamworksUGCItem::k_VoteDown: return k_VoteDown;
+ }
+ }
+#endif
+ return k_VoteUnknown;
+}
+
+void ModInstance::RequestUnsubscribe() {
+#if ENABLE_STEAMWORKS
+ if( GetUGCItem() ) {
+ GetUGCItem()->RequestUnsubscribe();
+ }
+#endif
+}
+
+void ModInstance::RequestSubscribe() {
+#if ENABLE_STEAMWORKS
+ if( GetUGCItem() ) {
+ GetUGCItem()->RequestSubscribe();
+ }
+#endif
+}
+
+void ModInstance::RequestVoteSet(bool voteup) {
+#if ENABLE_STEAMWORKS
+ if( GetUGCItem() ) {
+ GetUGCItem()->RequestUserVoteSet(voteup);
+ }
+#endif
+}
+
+void ModInstance::RequestFavoriteSet(bool fav) {
+#if ENABLE_STEAMWORKS
+ if( GetUGCItem() ) {
+ GetUGCItem()->RequestFavoriteSet(fav);
+ }
+#endif
+}
+
+void ModInstance::RequestUpdate( ModID source_sid, const char* update_message, ModVisibilityOptions visibility ) {
+#if ENABLE_STEAMWORKS
+ ERemoteStoragePublishedFileVisibility new_visibility = mod_visibility_map[static_cast<int>(visibility)];
+ if( GetUGCItem() ) {
+ GetUGCItem()->upload_sid_source = source_sid;
+ GetUGCItem()->RequestUpload(update_message, new_visibility, 0U );
+ } else {
+ LOGE << "Missing ugc item for mod as update target" << std::endl;
+ }
+#endif
+}
+
+const char* ModInstance::GetModsourceString() const {
+ switch(modsource) {
+ case ModSourceLocalModFolder:
+ return "Local Mod";
+ break;
+ case ModSourceSteamworks:
+ return "Steam Workshop";
+ break;
+ case ModSourceUnknown:
+ return "Unknown";
+ break;
+ }
+ return "";
+}
+
+/*
+const char* GetStatus() {
+ switch(modsource) {
+ case LocalModFolder:
+ return "Installed";
+ break;
+ case Steamworks;
+ SteamworksUGC* ugc = Steamworks::Instance()->GetUGC();
+ std::vector<SteamworksUGCItem*>::iterator ugc_it = ugc->GetItem(ugc_id);
+ if( ugc_it != ugc->GetItemEnd() ) {
+ return (*ugc_it)->GetStatus();
+ } else {
+ return "Unknown";
+ }
+ break;
+ default:
+ return "Unknown Modsource";
+ break;
+ }
+}
+*/
+
+std::string ModInstance::GetTagsListString() {
+ std::stringstream ss;
+ std::set<std::string>::iterator tag_it = tags.begin();
+
+ while( tag_it != tags.end() ) {
+ if( tag_it == tags.begin() ) {
+ ss << *tag_it;
+ } else {
+ ss << "," << *tag_it;
+ }
+ tag_it++;
+ }
+
+ return ss.str();
+}
+
+std::vector<std::string> ModInstance::GetFileCollisions(const ModInstance* lhs, const ModInstance* rhs) {
+ std::vector<std::string> uncollidable;
+ uncollidable.push_back("mod.xml");
+
+ std::vector<std::string> intersection;
+
+ std::set_intersection( lhs->manifest.begin(),
+ lhs->manifest.end(),
+ rhs->manifest.begin(),
+ rhs->manifest.end(),
+ std::back_inserter(intersection));
+
+ std::vector<std::string> intersection_cleaned;
+
+ std::set_difference( intersection.begin(),
+ intersection.end(),
+ uncollidable.begin(),
+ uncollidable.end(),
+ std::back_inserter(intersection_cleaned));
+
+ return intersection_cleaned;
+}
+
+std::vector<ModID> ModInstance::GetCollisionsWithActiveMods() const {
+ std::vector<ModID> colliding_sids;
+
+ std::vector<ModInstance*>::iterator modit = ModLoading::Instance().mods.begin();
+
+ for(; modit != ModLoading::Instance().mods.end(); modit++) {
+ if( (*modit)->sid != this->sid ) {
+ //Files that don't collide.
+ std::vector<std::string> intersection_cleaned = GetFileCollisions(this, *modit);
+
+ if( intersection_cleaned.size() > 0 && (*modit)->active ) {
+ colliding_sids.push_back( (*modit)->sid );
+
+ // Not logged due to spam when having the dear imgui mods menu open
+ /*LOGW << "Mod " << id << "_" << version << "has colliding files with " << (*modit)->id << "_" << (*modit)->version << std::endl;
+ for( unsigned i = 0; i < intersection_cleaned.size(); i++ ) {
+ LOGW << "Colliding file: " << intersection_cleaned[i] << std::endl;
+ }*/
+ }
+ }
+ }
+
+ return colliding_sids;
+}
+
+struct CharEqual {
+ bool operator()(const char* lhs, const char* rhs) const {
+ return strmtch(lhs, rhs);
+ }
+};
+
+struct CharLess {
+ bool operator()(const char* lhs, const char* rhs) const {
+ return std::strcmp(lhs, rhs) < 0;
+ }
+};
+
+std::vector<ModID> ModInstance::GetIDCollisionsWithActiveMods() const {
+ std::vector<ModID> colliding_sids;
+
+ std::vector<ModInstance*>::iterator modit = ModLoading::Instance().mods.begin();
+
+ std::set<const char*, CharLess> campaign_ids;
+ for(size_t i = 0; i < campaigns.size(); i++) {
+ if(campaign_ids.count(campaigns[i].id) != 0)
+ colliding_sids.push_back(sid);
+ else
+ campaign_ids.insert(campaigns[i].id);
+ }
+
+ for(; modit != ModLoading::Instance().mods.end(); modit++ ) {
+ bool collides = false;
+
+ if( (*modit)->sid != this->sid ) {
+ if( strmtch((*modit)->id, id) && (*modit)->active ) {
+ colliding_sids.push_back( (*modit)->sid );
+ collides = true;
+ }
+
+ if(!collides) {
+ for(size_t i = 0; i < (*modit)->campaigns.size(); i++) {
+ if(campaign_ids.count((*modit)->campaigns[i].id) ) {
+ colliding_sids.push_back( (*modit)->sid );
+ break;
+ }
+ }
+ }
+ }
+ }
+ return colliding_sids;
+}
+
+ModValidity ModInstance::GetValidity() const {
+ ModValidity out = valid;
+
+ if( GetIDCollisionsWithActiveMods().size() > 0 ) {
+ out |= kValidityIDCollision;
+ }
+
+ if( GetCollisionsWithActiveMods().size() > 0 ) {
+ out |= kValidityActiveModCollision;
+ }
+
+ if( !explicit_version_support ) {
+ out |= kValidityUnsupportedOvergrowthVersion;
+ }
+
+ return out;
+}
+
+std::vector<std::string> ModInstance::GetInvalidItemPaths() const {
+ return invalid_item_paths;
+}
+
+std::vector<std::string> ModInstance::GenerateValidityErrorsArr( const ModValidity& val, const ModInstance* instance/*= NULL*/ ) {
+ std::vector<std::string> e;
+
+ if( val.Intersects(kValidityIDCollision) ) {
+ e.push_back("ID Collision");
+ }
+
+ if( val.Intersects(kValidityActiveModCollision) ) {
+ if(instance) {
+ std::vector<ModInstance*>::iterator modit = ModLoading::Instance().mods.begin();
+ for(; modit != ModLoading::Instance().mods.end(); modit++) {
+ if( (*modit)->sid != instance->sid ) {
+ //Files that don't collide.
+ std::vector<std::string> intersection_cleaned = GetFileCollisions(instance, *modit);
+
+ if( intersection_cleaned.size() > 0 && (*modit)->active ) {
+ std::string text = std::string("File collisions with mod \"") + (*modit)->name.c_str() + "\" (" +(*modit)->id.c_str() + "): ";
+
+ for( unsigned i = 0; i < intersection_cleaned.size(); i++ ) {
+ text += intersection_cleaned[i];
+ if(i != intersection_cleaned.size() - 1) {
+ text += ", ";
+ }
+ }
+ e.push_back(text);
+ }
+ }
+ }
+ }
+ }
+
+ if( val.Intersects(kValidityBrokenXml) ) {
+ e.push_back("Mod Xml Is Broken");
+ }
+
+ if( val.Intersects(kValidityInvalidVersion) ) {
+ e.push_back("Version Invalid");
+ }
+
+ if( val.Intersects(kValidityMissingReadRights) ) {
+ e.push_back("MissingReadRights");
+ }
+
+ if( val.Intersects(kValidityMissingXml) ) {
+ e.push_back("Missing Mod Xml");
+ }
+
+ if( val.Intersects(kValidityUnloaded) ) {
+ e.push_back("Unloaded");
+ }
+
+ if( val.Intersects(kValidityUnsupportedOvergrowthVersion) ) {
+ e.push_back("Current version of the game not explicitly supported");
+ }
+
+ if( val.Intersects(kValidityNoDataOnDisk) ) {
+ e.push_back("No Data On Disk");
+ }
+
+ if( val.Intersects(kValiditySteamworksError) ) {
+ e.push_back("Steamworks error");
+ }
+
+ if( val.Intersects(kValidityNotInstalled) ) {
+ e.push_back("Not Installed");
+ }
+
+ if( val.Intersects(kValidityNotSubscribed)) {
+ e.push_back("Not Subscribed");
+ }
+
+ if( val.Intersects(kValidityIDTooLong) ) {
+ e.push_back("ID Is Too Long");
+ }
+
+ if( val.Intersects(kValidityIDMissing) ) {
+ e.push_back("ID Missing");
+ }
+
+ if( val.Intersects(kValidityIDInvalid) ) {
+ e.push_back("ID Invalid, it must only contain lower-case characters, numbers, or dashes");
+ }
+
+ if( val.Intersects(kValidityNameTooLong) ) {
+ e.push_back("Name Too Long");
+ }
+
+ if( val.Intersects(kValidityVersionTooLong) ) {
+ e.push_back("Version Too Long");
+ }
+
+ if( val.Intersects(kValidityAuthorTooLong) ) {
+ e.push_back("Author Too Long");
+ }
+
+ if( val.Intersects(kValidityDescriptionTooLong) ) {
+ e.push_back("Description Too Long");
+ }
+
+ if( val.Intersects(kValidityThumbnailMaxLength) ) {
+ e.push_back("Thumbnail Max Length");
+ }
+
+ if( val.Intersects(kValidityTagsListTooLong) ) {
+ e.push_back("Tags List Too Long");
+ }
+
+ if( val.Intersects(kValidityMenuItemTitleTooLong) ) {
+ e.push_back("Menu Item Title Too Long");
+ }
+
+ if( val.Intersects(kValidityMenuItemTitleMissing) ) {
+ e.push_back("Menu Item Title Missing");
+ }
+
+ if( val.Intersects(kValidityMenuItemPathTooLong) ) {
+ e.push_back("Menu Item Path Too Long");
+ }
+
+ if( val.Intersects(kValidityMenuItemPathMissing) ) {
+ e.push_back("Menu Item Path Missing");
+ }
+
+ if( val.Intersects(kValidityMenuItemPathInvalid) ) {
+ e.push_back("Menu Item Path Invalid");
+ }
+
+ if( val.Intersects(kValidityMenuItemCategoryTooLong) ) {
+ e.push_back("Menu Item Category Too Long");
+ }
+
+ if( val.Intersects(kValidityMenuItemCategoryMissing) ) {
+ e.push_back("Menu Item Category Missing");
+ }
+
+ if( val.Intersects(kValidityInvalidTag) ) {
+ e.push_back("Invalid Tag");
+ }
+
+ if( val.Intersects(kValidityMissingName) ) {
+ e.push_back("Missing Name");
+ }
+
+ if( val.Intersects(kValidityMissingAuthor) ) {
+ e.push_back("Missing Author");
+ }
+
+ if( val.Intersects(kValidityMissingDescription) ) {
+ e.push_back("Missing Description");
+ }
+
+ if( val.Intersects(kValidityMissingThumbnail) ) {
+ e.push_back("Missing Thumbnail");
+ }
+
+ if( val.Intersects(kValidityMissingThumbnailFile) ) {
+ e.push_back("Missing Thumbnail File");
+ }
+
+ if( val.Intersects(kValidityMissingVersion) ) {
+ e.push_back("Missing Version");
+ }
+
+ if( val.Intersects(kValidityCampaignTitleTooLong) ) {
+ e.push_back("Campaign Title Too Long");
+ }
+
+ if( val.Intersects(kValidityCampaignTitleMissing) ) {
+ e.push_back("Campaign Title Missing");
+ }
+
+ if( val.Intersects(kValidityCampaignTypeTooLong) ) {
+ e.push_back("Campaign Type Too Long");
+ }
+
+ if( val.Intersects(kValidityCampaignTypeMissing) ) {
+ e.push_back("Campaign Type Missing");
+ }
+
+ if( val.Intersects(kValidityLevelTitleTooLong) ) {
+ e.push_back("Level Title Too Long");
+ }
+
+ if( val.Intersects(kValidityLevelTitleMissing) ) {
+ e.push_back("Level Title Missing");
+ }
+
+ if( val.Intersects(kValidityLevelPathTooLong) ) {
+ e.push_back("Level Path Too Long");
+ }
+
+ if( val.Intersects(kValidityLevelPathMissing) ) {
+ e.push_back("Level Path Missing");
+ }
+
+ if( val.Intersects(kValidityLevelPathInvalid) ) {
+ e.push_back("Level Path Invalid");
+ }
+
+ if( val.Intersects(kValidityLevelThumbnailTooLong) ) {
+ e.push_back("Level Thumbnail Too Long");
+ }
+
+ if( val.Intersects(kValidityLevelThumbnailMissing) ) {
+ e.push_back("Level Thumbnail Missing");
+ }
+
+ if( val.Intersects(kValidityLevelThumbnailInvalid) ) {
+ e.push_back("Level Thumbnail Invalid");
+ }
+
+ if( val.Intersects(kValidityItemTitleTooLong) ) {
+ e.push_back("Item Title Too Long");
+ }
+
+ if( val.Intersects(kValidityItemTitleMissing) ) {
+ e.push_back("Item Title Missing");
+ }
+
+ if( val.Intersects(kValidityItemCategoryTooLong) ) {
+ e.push_back("Item Category Too Long");
+ }
+
+ if( val.Intersects(kValidityItemCategoryMissing) ) {
+ e.push_back("Item Category Missing");
+ }
+
+ if( val.Intersects(kValidityItemPathTooLong) ) {
+ e.push_back("Item Path Too Long");
+ }
+
+ if( val.Intersects(kValidityItemPathMissing) ) {
+ e.push_back("Item Path Missing");
+ }
+
+ if( val.Intersects(kValidityItemPathFileMissing) ) {
+ e.push_back("Item Path File Missing");
+ }
+
+ if( val.Intersects(kValidityItemThumbnailTooLong) ) {
+ e.push_back("Item Thumbnail Too Long");
+ }
+
+ if( val.Intersects(kValidityItemThumbnailMissing) ) {
+ e.push_back("Item Thumbnail Missing");
+ }
+
+ if( val.Intersects(kValidityItemThumbnailFileMissing) ) {
+ e.push_back("Item Thumbnail File Missing");
+ }
+
+ if( val.Intersects(kValidityCategoryTooLong) ) {
+ e.push_back("Category Too Long");
+ }
+
+ if( val.Intersects(kValidityCategoryMissing) ) {
+ e.push_back("Category Missing");
+ }
+
+ if( val.Intersects(kValidityInvalidPreviewImage) ) {
+ e.push_back("Invalid Preview Image");
+ }
+
+ if( val.Intersects(kValidityInvalidSupportedVersion) ) {
+ e.push_back("Invalid Supported Version");
+ }
+
+ if( val.Intersects(kValidityInvalidLevelHookFile) ) {
+ e.push_back("Invalid Level Hook File");
+ }
+
+ if( val.Intersects(kValidityInvalidNeedRestart) ) {
+ e.push_back("Invalid Need Restart");
+ }
+
+ if( val.Intersects(kValidityCampaignIsLinearInvalidEnum) ) {
+ e.push_back("Invalid is_linear Campaign Attribute");
+ }
+
+ if( val.Intersects(kValidityCampaignThumbnailMissing) ) {
+ e.push_back("Campaign Thumbnail Attribute Missing");
+ }
+
+ if( val.Intersects(kValidityCampaignThumbnailTooLong) ) {
+ e.push_back("Campaign Thumbnail Attribute Too Long");
+ }
+
+ if( val.Intersects(kValidityCampaignThumbnailInvalid) ) {
+ e.push_back("Campaign Thumbnail Attribute Invalid");
+ }
+
+ if( val.Intersects(kValidityMenuItemThumbnailTooLong) ) {
+ e.push_back("Menu Item Thumbnail Attribute Too Long");
+ }
+
+ if( val.Intersects(kValidityMenuItemThumbnailInvalid) ) {
+ e.push_back("Menu Item Thumbnail Attribute Invalid");
+ }
+
+ if( val.Intersects(kValidityCampaignIDTooLong) ) {
+ e.push_back("Campaign ID too long");
+ }
+
+ if( val.Intersects(kValidityCampaignIDMissing) ) {
+ e.push_back("Campaign IDs are missing");
+ }
+
+ return e;
+}
+
+std::vector<std::string> ModInstance::GetValidityErrorsArr() const {
+ return GenerateValidityErrorsArr( GetValidity(), this );
+}
+
+Path ModInstance::GetFullAbsThumbnailPath() const {
+ return FindFilePath( AssemblePath( path.c_str(), thumbnail.c_str() ).c_str(), kAbsPath, true );
+}
+
+std::string ModInstance::GetValidityErrors() const {
+ return GenerateValidityErrors( GetValidity(), this );
+}
+
+std::string ModInstance::GenerateValidityErrors( const ModValidity& v, const ModInstance* instance/*= NULL*/ ) {
+ std::vector<std::string> e = GenerateValidityErrorsArr(v, instance);
+
+ std::stringstream ss;
+ for( size_t i = 0; i < e.size(); i++) {
+ ss << e[i];
+ if( i < e.size()-1 ) {
+ ss << ", ";
+ }
+ }
+
+ return ss.str();
+}
+
+ModLoading::ModLoading() {
+}
+
+std::vector<ModInstance*>::iterator ModLoading::GetMod( const std::string& path ) {
+ std::vector<ModInstance*>::iterator modit = mods.begin();
+
+ for(; modit != mods.end(); modit++ ) {
+ if( (*modit)->path == path ) {
+ break;
+ }
+ }
+
+ return modit;
+}
+
+bool ModLoading::IsCampaignPresent(const std::string& campaign) {
+ for (uint32_t i = 0; i < mods.size(); i++) {
+ if (mods[i]->IsActive()) {
+ for (uint32_t j = 0; j < mods[i]->campaigns.size(); j++) {
+ if (strmtch(mods[i]->campaigns[j].id, campaign)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+bool ModLoading::CampaignHasLevel(const std::string& campaign, const std::string& level_path) {
+ for (uint32_t i = 0; i < mods.size(); i++) {
+ if (mods[i]->IsActive()) {
+ for (uint32_t j = 0; j < mods[i]->campaigns.size(); j++) {
+ if (strmtch(mods[i]->campaigns[j].id, campaign)) {
+ for (const auto& level : mods[i]->campaigns[j].levels){
+ LOGI << "path: " << std::string("Data/Levels/") + level.path.str() << std::endl;
+ LOGI << "Level path: " << level_path << std::endl;
+ if (level.path.str() == level_path) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+}
+
+ModInstance* ModLoading::GetMod( ModID sid ) {
+ std::vector<ModInstance*>::iterator modit = mods.begin();
+
+ for(; modit != mods.end(); modit++ ) {
+ if( (*modit)->GetSid() == sid ) {
+ return (*modit);
+ }
+ }
+ return NULL;
+}
+
+std::string ModLoading::GetModName( ModID sid ) {
+ std::vector<ModInstance*>::iterator modit = mods.begin();
+
+ if( sid == CoreGameModID ) {
+ return "Core Engine";
+ }
+
+ for(; modit != mods.end(); modit++ ) {
+ if( (*modit)->GetSid() == sid ) {
+ return (*modit)->name.str();
+ }
+ }
+
+ return "Unknown";
+}
+
+std::string ModLoading::GetModID( ModID sid ) {
+ if(sid == CoreGameModID ) {
+ return "";
+ }
+
+ std::vector<ModInstance*>::iterator modit = mods.begin();
+ for(; modit != mods.end(); modit++ ) {
+ if( (*modit)->GetSid() == sid ) {
+ return (*modit)->id.str();
+ }
+ }
+
+ LOGW << "Got an invalid mod id, returning core id" << std::endl;
+ return "";
+}
+
+bool ModLoading::IsActive( ModID sid ) {
+ std::vector<ModInstance*>::iterator modit = mods.begin();
+
+ for(; modit != mods.end(); modit++ ) {
+ if( (*modit)->GetSid() == sid ) {
+ if( (*modit)->IsActive() ) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+ return false;
+}
+
+bool ModLoading::IsActive( std::string id ) {
+ std::vector<ModInstance*>::iterator modit = mods.begin();
+
+ for(; modit != mods.end(); modit++ ) {
+ if(strmtch((*modit)->id,id.c_str())) {
+ if( (*modit)->IsActive() ) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+ return false;
+}
+
+ModInstance::Campaign ModLoading::GetCampaign(std::string& campaign_id) {
+ for( uint32_t i = 0; i < mods.size(); i++ ) {
+ if( mods[i]->IsActive() ) {
+ for( uint32_t j = 0; j < mods[i]->campaigns.size(); j++) {
+ if( strmtch( mods[i]->campaigns[j].id, campaign_id ) ) {
+ return mods[i]->campaigns[j];
+ }
+ }
+ }
+ }
+ return ModInstance::Campaign();
+}
+
+ModInstance* ModLoading::GetModInstance(const std::string& campaign_id) {
+ for (uint32_t i = 0; i < mods.size(); i++) {
+ if (mods[i]->IsActive()) {
+ for (uint32_t j = 0; j < mods[i]->campaigns.size(); j++) {
+ if (strmtch(mods[i]->campaigns[j].id, campaign_id)) {
+ return mods[i];
+ }
+ }
+ }
+ }
+ return nullptr;
+}
+
+std::vector<ModInstance::Campaign> ModLoading::GetCampaigns() {
+ std::vector<ModInstance::Campaign> campaigns;
+ for( uint32_t i = 0; i < mods.size(); i++ ) {
+ if( mods[i]->IsActive() ) {
+ for( uint32_t j = 0; j < mods[i]->campaigns.size(); j++ ) {
+ if(strlen( mods[i]->campaigns[j].title ) > 0 ) {
+ campaigns.push_back(mods[i]->campaigns[j]);
+ }
+ }
+ }
+ }
+ return campaigns;
+}
+
+
+std::vector<ModInstance*> ModLoading::GetModsMatchingID(std::string id) {
+ std::vector<ModInstance*> lmods;
+ for( uint32_t i = 0; i < mods.size(); i++ ) {
+ if( strmtch( mods[i]->id, id ) ) {
+ lmods.push_back(mods[i]);
+ }
+ }
+ return lmods;
+}
+
+std::vector<ModInstance*> ModLoading::GetSteamModsMatchingID(std::string id) {
+ std::vector<ModInstance*> lmods;
+ for( uint32_t i = 0; i < mods.size(); i++ ) {
+ if( strmtch( mods[i]->id, id ) && mods[i]->modsource == ModSourceSteamworks) {
+ lmods.push_back(mods[i]);
+ }
+ }
+ return lmods;
+}
+
+std::vector<ModInstance*> ModLoading::GetLocalModsMatchingID(std::string id) {
+ std::vector<ModInstance*> lmods;
+ for( uint32_t i = 0; i < mods.size(); i++ ) {
+ if( strmtch( mods[i]->id, id ) && mods[i]->modsource == ModSourceLocalModFolder) {
+ lmods.push_back(mods[i]);
+ }
+ }
+ return lmods;
+}
+
+std::vector<ModInstance*> ModLoading::GetLocalMods() {
+ std::vector<ModInstance*> lmods;
+ for( uint32_t i = 0; i < mods.size(); i++ ) {
+ if(mods[i]->modsource == ModSourceLocalModFolder) {
+ lmods.push_back(mods[i]);
+ }
+ }
+ return lmods;
+}
+
+std::vector<ModInstance*> ModLoading::GetAllMods() {
+ std::vector<ModInstance*> lmods;
+ for( uint32_t i = 0; i < mods.size(); i++ ) {
+ lmods.push_back(mods[i]);
+ }
+ return lmods;
+}
+
+void ModLoading::AddMod( const std::string& path ) {
+ std::vector<ModInstance*>::iterator modit = GetMod( path );
+ LOGI << "Added mod: " << path << std::endl;
+ if( modit == mods.end() ) {
+ ModInstance* modInstance = new ModInstance(path);
+ LOGD << modInstance << std::endl;
+ mods.push_back(modInstance);
+ }
+}
+
+
+std::string ModLoading::WhichCampaignLevelBelongsTo(const std::string & level_path) {
+ for (uint32_t i = 0; i < mods.size(); i++) {
+ if (mods[i]->IsActive()) {
+ for (uint32_t j = 0; j < mods[i]->campaigns.size(); j++) {
+ for (const auto& level : mods[i]->campaigns[j].levels) {
+ if (std::string("Data/Levels/") + level.path.str() == level_path) {
+ return mods[i]->campaigns[j].id.str();
+ }
+ }
+ }
+ }
+ }
+ return "";
+}
+
+void ModLoading::RemoveMod( const std::string& path ) {
+ std::vector<ModInstance*>::iterator modit = GetMod( path );
+ LOGI << "Removed mod: " << path << std::endl;
+ if( modit != mods.end() ) {
+ delete *modit;
+ mods.erase( modit );
+ }
+}
+
+void ModLoading::ReloadMod( const std::string& path ) {
+ std::vector<ModInstance*>::iterator modit = GetMod( path );
+ LOGI << "Reload mod: " << path << std::endl;
+ if( modit != mods.end() ) {
+ (*modit)->Reload();
+ }
+}
+
+void ModLoading::Reload( ) {
+ DetectMods();
+
+ std::vector<ModInstance*>::iterator modit = mods.begin();
+ for(; modit != mods.end(); modit++ ) {
+ (*modit)->Reload();
+ }
+}
+
+void ModLoading::InitMods() {
+ DetectMods();
+}
+
+void ModLoading::DetectMods() {
+ std::vector<std::string> newMods;
+
+ for(int i=0; i<vanilla_data_paths.num_paths; ++i) {
+ std::stringstream ss;
+ ss << vanilla_data_paths.paths[i];
+ //TODO: Use config var instead.
+ ss << "Data/Mods/";
+
+ std::string path;
+ path = caseCorrect(ss.str());
+ if(CheckFileAccess(path.c_str())) {
+ getSubdirectories(path.c_str(),newMods);
+ }
+ }
+
+ std::stringstream ss;
+ ss << write_path;
+ //TODO: Use config var instead.
+ ss << "Data/Mods/";
+
+ std::string path;
+ path = caseCorrect(ss.str());
+
+ LOGI << "Looking for mods in " << path << std::endl;
+ if(CheckFileAccess(path.c_str())) {
+ getSubdirectories(path.c_str(),newMods);
+ }
+
+ std::vector<std::string> verifiedMods;
+
+ for( unsigned i = 0; i < newMods.size(); i++ ) {
+ bool already_in_list = false;
+ bool has_mod_xml = false;
+
+ if( CheckFileAccess(std::string(newMods[i] + "/mod.xml").c_str() ) ) {
+ has_mod_xml = true;
+ }
+
+ if( has_mod_xml ) {
+ for( unsigned k = 0; k < verifiedMods.size(); k++ ) {
+ if( AreSame( std::string(newMods[i] + "/mod.xml").c_str(), std::string(verifiedMods[k] + "/mod.xml").c_str() ) ) {
+ already_in_list = true;
+ }
+ }
+ if( already_in_list == false ) {
+ verifiedMods.push_back(newMods[i]);
+ }
+ }
+ }
+
+ SetMods( verifiedMods );
+}
+
+#if ENABLE_STEAMWORKS
+ModID ModLoading::CreateSteamworksMod(UGCID ugc_id) {
+ LOGI << "Adding steamworks mod: " << ugc_id << std::endl;
+ ModInstance* modInstance = new ModInstance(ugc_id);
+ mods.push_back(modInstance);
+ return modInstance->GetSid();
+}
+#endif
+
+void ModLoading::SetMods( std::vector<std::string> modpaths ) {
+ std::vector<std::string> currentPaths;
+
+ std::vector<ModInstance*>::iterator modit = mods.begin();
+ for( ;modit != mods.end(); modit++ ) {
+ if( (*modit)->path.empty() == false ) {
+ if( (*modit)->modsource == ModSourceLocalModFolder ) {
+ currentPaths.push_back( (*modit)->path );
+ }
+ }
+ }
+
+ std::sort( currentPaths.begin(), currentPaths.end() );
+ std::sort( modpaths.begin(), modpaths.end() );
+
+ std::vector<std::string> newPaths;
+ std::vector<std::string> missingPaths;
+ std::vector<std::string> stillPaths;
+
+ if( modpaths.size() == 0 || currentPaths.size() == 0 ) {
+ newPaths = modpaths;
+ missingPaths = currentPaths;
+ stillPaths = std::vector<std::string>(); //Empty as intersection is guaranteed to be zero
+ } else {
+ for( unsigned k = 0; k < modpaths.size(); k++ ) {
+ bool found = false;
+ for( unsigned i = 0; i < currentPaths.size(); i++ ) {
+ if( modpaths[k] == currentPaths[i] ) {
+ found = true;
+ }
+ }
+ if( found == false ) {
+ newPaths.push_back( modpaths[k] );
+ }
+ }
+ for( unsigned i = 0; i < currentPaths.size(); i++ ) {
+ bool found = false;
+ for( unsigned k = 0; k < modpaths.size(); k++ ) {
+ if( modpaths[k] == currentPaths[i] ) {
+ found = true;
+ }
+ }
+ if( found == false ) {
+ missingPaths.push_back( currentPaths[i] );
+ }
+ }
+ for( unsigned i = 0; i < currentPaths.size(); i++ ) {
+ bool found = false;
+ for( unsigned k = 0; k < modpaths.size(); k++ ) {
+ if( modpaths[k] == currentPaths[i] ) {
+ found = true;
+ }
+ }
+ if( found ) {
+ stillPaths.push_back( currentPaths[i] );
+ }
+ }
+ }
+
+ std::vector<std::string>::iterator pathit;
+ for( pathit = newPaths.begin(); pathit != newPaths.end(); pathit++ ) {
+ AddMod( *pathit );
+ }
+
+ for( pathit = missingPaths.begin(); pathit != missingPaths.end(); pathit++ ) {
+ RemoveMod( *pathit );
+ }
+
+ for( pathit = stillPaths.begin(); pathit != stillPaths.end(); pathit++ ) {
+ ReloadMod( *pathit );
+ }
+}
+
+void ModLoading::TriggerModActivationCallback( ModInstance* mod ) {
+ std::vector<ModLoadingCallback*>::iterator callbackit;
+
+ for( callbackit = callbacks.begin();
+ callbackit != callbacks.end();
+ callbackit++ ) {
+ (*callbackit)->ModActivationChange(mod);
+ }
+}
+
+const std::vector<ModInstance*>& ModLoading::GetMods() {
+ return mods;
+}
+
+const std::vector<ModID> ModLoading::GetModsSid() {
+ std::vector<ModID> vals;
+ for( unsigned i = 0; i < mods.size(); i++ ) {
+ vals.push_back( mods[i]->GetSid() );
+ }
+ return vals;
+}
+
+void ModLoading::RegisterCallback( ModLoadingCallback * callback ) {
+ callbacks.push_back( callback );
+}
+
+void ModLoading::DeRegisterCallback( ModLoadingCallback * callback ) {
+ std::vector<ModLoadingCallback*>::iterator callbackit = std::find( callbacks.begin(), callbacks.end(), callback );
+
+ if( callbackit != callbacks.end() ) {
+ callbacks.erase( callbackit );
+ }
+}
+
+ModLoading ModLoading::instance;
+
+ModLoading& ModLoading::Instance() {
+ return instance;
+}
+
+void ModLoading::Initialize() {
+ std::string path = AssemblePath(std::string(GetWritePath(CoreGameModID).c_str()),"modconfig.xml");
+ if( isFile(path.c_str()) == false ) {
+ acp.Save(path);
+ } else {
+ acp.Load(path);
+ }
+}
+
+void ModLoading::Dispose() {
+ SaveModConfig();
+}
+
+void ModLoading::SaveModConfig() {
+ acp.Save(AssemblePath(std::string(GetWritePath(CoreGameModID).c_str()),"modconfig.xml"));
+}
+
+std::vector<std::string> ModLoading::ActiveLevelHooks() {
+ std::vector<ModInstance*>::iterator modit = mods.begin();
+ std::vector<std::string> files;
+
+ for( ; modit != mods.end(); modit++ ) {
+ if( (*modit)->IsActive() ) {
+ files.insert(files.end(), (*modit)->level_hook_files.begin(), (*modit)->level_hook_files.end());
+ }
+ }
+
+ return files;
+}
+
+void ModLoading::RebuildLocalization() {
+ std::vector<ModInstance*>::iterator modit = mods.begin();
+ ClearLocale();
+ const std::string current_language = config["language"].str();
+ for( ; modit != mods.end(); modit++ ) {
+ if( (*modit)->IsActive() ) {
+ std::map<std::string, ModInstance::LanguageData>::iterator lditer = (*modit)->language_data.begin();
+ for( ; lditer != (*modit)->language_data.end(); ++lditer ) {
+ AddLocale(lditer->first.c_str(), lditer->second.language.c_str(), lditer->second.local_language.c_str());
+
+ std::map<std::string, ModInstance::LevelLocalizationData>::iterator per_level_iter = lditer->second.per_level_data.begin();
+ for( ; per_level_iter != lditer->second.per_level_data.end(); ++per_level_iter ) {
+ AddLevelName(lditer->first.c_str(), per_level_iter->first.c_str(), per_level_iter->second.name.c_str());
+ AddLevelTip(lditer->first.c_str(), per_level_iter->first.c_str(), per_level_iter->second.loading_screen_tip.c_str());
+ }
+ }
+ }
+ }
+}
+
+std::ostream& operator<<(std::ostream& os, const ModInstance::ModDependency &md ) {
+ os << "[ModDependency]" << std::endl;
+ os << "Id:" << md.id << std::endl;
+
+ os << "-Versions-" << std::endl;
+
+ for( std::vector<std::string>::const_iterator sit = md.versions.begin();
+ sit != md.versions.end();
+ sit++) {
+ os << *sit << std::endl;
+ }
+
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const ModInstance &mi ) {
+ os << "[ModInstance]" << std::endl;
+ os << "Path:"<< mi.path << std::endl;
+ os << "Id:"<< mi.id << std::endl;
+ os << "Name:"<< mi.name << std::endl;
+ os << "Version:"<< mi.version << std::endl;
+ os << "Valid:" << mi.GetValidity() << std::endl;
+ os << "-Supported Versions-" << std::endl;
+
+ for( std::vector<std::string>::const_iterator sit = mi.supported_versions.begin();
+ sit != mi.supported_versions.end();
+ sit++) {
+ os << *sit << std::endl;
+ }
+
+ os << "-Mod Dependencies-" << std::endl;
+
+ for( std::vector<ModInstance::ModDependency>::const_iterator mdit = mi.mod_dependencies.begin();
+ mdit != mi.mod_dependencies.end();
+ mdit++) {
+ os << *mdit << std::endl;
+ }
+
+ os << "-Manifest -" << std::endl;
+
+ for( std::vector<std::string>::const_iterator sit = mi.manifest.begin();
+ sit != mi.manifest.end();
+ sit++) {
+ os << *sit << std::endl;
+ }
+
+ os << "-Overloaded Files-" << std::endl;
+
+ for( std::vector<std::string>::const_iterator sit = mi.overload_files.begin();
+ sit != mi.overload_files.end();
+ sit++) {
+ os << *sit << std::endl;
+ }
+
+ return os;
+}
+
diff --git a/Source/Internal/modloading.h b/Source/Internal/modloading.h
new file mode 100644
index 00000000..a3cf47ef
--- /dev/null
+++ b/Source/Internal/modloading.h
@@ -0,0 +1,485 @@
+//-----------------------------------------------------------------------------
+// Name: modloading.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/modid.h>
+#include <Internal/integer.h>
+#include <Internal/filesystem.h>
+
+#include <XML/Parsers/activemodsparser.h>
+#include <Utility/fixed_string.h>
+#include <Steam/ugc_id.h>
+
+#include <vector>
+#include <string>
+#include <iostream>
+#include <set>
+
+class SteamworksUGCItem;
+class TiXmlElement;
+
+enum class ModVisibilityOptions {
+ Public = 0,
+ Friends = 1,
+ Private = 2,
+};
+
+static const char* mod_visibility_options[] = { "Public", "Friends", "Private" };
+
+#if ENABLE_STEAMWORKS
+const ERemoteStoragePublishedFileVisibility mod_visibility_map[] = { k_ERemoteStoragePublishedFileVisibilityPublic,
+ k_ERemoteStoragePublishedFileVisibilityFriendsOnly,
+ k_ERemoteStoragePublishedFileVisibilityPrivate };
+static char edit_tags_field[k_cchTagListMax] = "";
+#endif
+
+const int update_to_visibilty_options_count = sizeof(mod_visibility_options)/sizeof(mod_visibility_options[0]);
+
+class ModInstance
+{
+public:
+ struct Attribute {
+ fixed_string<MOD_ATTRIBUTE_ID_MAX_LENGTH> id;
+ fixed_string<MOD_ATTRIBUTE_VALUE_MAX_LENGTH> value;
+ };
+
+ struct ModDependency {
+ std::string id;
+ std::vector<std::string> versions;
+ };
+
+ struct MenuItem {
+ fixed_string<MOD_MENU_ITEM_TITLE_MAX_LENGTH> title;
+ fixed_string<MOD_MENU_ITEM_CATEGORY_MAX_LENGTH> category;
+ fixed_string<MOD_MENU_ITEM_PATH_MAX_LENGTH> path;
+ fixed_string<MOD_MENU_ITEM_THUMBNAIL_MAX_LENGTH> thumbnail;
+ };
+
+ struct Parameter {
+ Parameter();
+
+ fixed_string<MOD_LEVEL_PARAMETER_NAME_MAX_LENGTH> name;
+ fixed_string<MOD_LEVEL_PARAMETER_TYPE_MAX_LENGTH> type;
+ fixed_string<MOD_LEVEL_PARAMETER_VALUE_MAX_LENGTH> value;
+
+ std::vector<Parameter> parameters;
+ };
+
+ struct Level {
+ fixed_string<MOD_LEVEL_ID_MAX_LENGTH> id;
+ fixed_string<MOD_LEVEL_TITLE_MAX_LENGTH> title;
+ fixed_string<MOD_LEVEL_THUMBNAIL_MAX_LENGTH> thumbnail;
+ fixed_string<MOD_LEVEL_PATH_MAX_LENGTH> path;
+ bool completion_optional;
+ bool supports_online;
+ bool requires_online;
+ Parameter parameter;
+ };
+
+ struct Item {
+ fixed_string<MOD_ITEM_TITLE_MAX_LENGTH> title;
+ fixed_string<MOD_ITEM_CATEGORY_MAX_LENGTH> category;
+ fixed_string<MOD_ITEM_PATH_MAX_LENGTH> path;
+ fixed_string<MOD_ITEM_PATH_MAX_LENGTH> thumbnail;
+ };
+
+ struct Campaign {
+ fixed_string<MOD_CAMPAIGN_ID_MAX_LENGTH> id;
+ fixed_string<MOD_CAMPAIGN_TITLE_MAX_LENGTH> title;
+ fixed_string<MOD_CAMPAIGN_THUMBNAIL_MAX_LENGTH> thumbnail;
+ fixed_string<MOD_CAMPAIGN_MENU_SCRIPT_PATH_MAX_LENGTH> menu_script;
+ fixed_string<MOD_CAMPAIGN_MAIN_SCRIPT_PATH_MAX_LENGTH> main_script;
+
+ std::vector<ModInstance::Attribute> attributes;
+ std::vector<ModInstance::Level> levels;
+ Parameter parameter;
+ bool supports_online;
+ bool requires_online;
+ };
+
+ struct Pose
+ {
+ fixed_string<MOD_POSE_NAME_MAX_LENGTH> name;
+ fixed_string<MOD_POSE_NAME_MAX_LENGTH> command;
+ fixed_string<MOD_POSE_PATH_MAX_LENGTH> path;
+ };
+
+ static const ModValidity kValidityValid ;
+ static const ModValidity kValidityBrokenXml ;
+ static const ModValidity kValidityInvalidVersion ;
+ static const ModValidity kValidityMissingReadRights ;
+ static const ModValidity kValidityMissingXml ;
+ static const ModValidity kValidityUnloaded ;
+ static const ModValidity kValidityUnsupportedOvergrowthVersion ;
+ static const ModValidity kValidityNoDataOnDisk ;
+ static const ModValidity kValiditySteamworksError ;
+ static const ModValidity kValidityNotInstalled ;
+ static const ModValidity kValidityNotSubscribed ;
+
+ static const ModValidity kValidityIDTooLong ;
+ static const ModValidity kValidityIDMissing ;
+ static const ModValidity kValidityIDInvalid ;
+
+ static const ModValidity kValidityNameTooLong ;
+ static const ModValidity kValidityVersionTooLong ;
+ static const ModValidity kValidityAuthorTooLong ;
+
+ static const ModValidity kValidityDescriptionTooLong ;
+ static const ModValidity kValidityThumbnailMaxLength ;
+ static const ModValidity kValidityTagsListTooLong ;
+
+ static const ModValidity kValidityMenuItemTitleTooLong ;
+ static const ModValidity kValidityMenuItemTitleMissing ;
+
+ static const ModValidity kValidityMenuItemCategoryTooLong ;
+ static const ModValidity kValidityMenuItemCategoryMissing ;
+
+ static const ModValidity kValidityMenuItemPathTooLong ;
+ static const ModValidity kValidityMenuItemPathMissing ;
+ static const ModValidity kValidityMenuItemPathInvalid ;
+
+ static const ModValidity kValidityInvalidTag ;
+
+ static const ModValidity kValidityIDCollision ;
+ static const ModValidity kValidityActiveModCollision ;
+
+ static const ModValidity kValidityMissingName ;
+ static const ModValidity kValidityMissingAuthor ;
+ static const ModValidity kValidityMissingDescription ;
+ static const ModValidity kValidityMissingThumbnail ;
+ static const ModValidity kValidityMissingThumbnailFile ;
+ static const ModValidity kValidityMissingVersion ;
+
+ static const ModValidity kValidityCampaignTitleTooLong ;
+ static const ModValidity kValidityCampaignTitleMissing ;
+
+ static const ModValidity kValidityCampaignTypeTooLong ;
+ static const ModValidity kValidityCampaignTypeMissing ;
+
+ static const ModValidity kValidityCampaignIsLinearInvalidEnum ;
+
+ static const ModValidity kValidityLevelTitleTooLong ;
+ static const ModValidity kValidityLevelTitleMissing ;
+
+ static const ModValidity kValidityLevelPathTooLong ;
+ static const ModValidity kValidityLevelPathMissing ;
+ static const ModValidity kValidityLevelPathInvalid ;
+
+ static const ModValidity kValidityLevelThumbnailTooLong ;
+ static const ModValidity kValidityLevelThumbnailMissing ;
+ static const ModValidity kValidityLevelThumbnailInvalid ;
+ static const ModValidity kValidityInvalidPreviewImage ;
+ static const ModValidity kValidityInvalidSupportedVersion ;
+ static const ModValidity kValidityInvalidLevelHookFile ;
+ static const ModValidity kValidityInvalidNeedRestart ;
+
+ static const ModValidity kValidityItemTitleTooLong ;
+ static const ModValidity kValidityItemTitleMissing ;
+
+ static const ModValidity kValidityItemCategoryTooLong ;
+ static const ModValidity kValidityItemCategoryMissing ;
+
+ static const ModValidity kValidityItemPathTooLong ;
+ static const ModValidity kValidityItemPathMissing ;
+ static const ModValidity kValidityItemPathFileMissing ;
+
+ static const ModValidity kValidityItemThumbnailTooLong ;
+ static const ModValidity kValidityItemThumbnailMissing ;
+ static const ModValidity kValidityItemThumbnailFileMissing ;
+
+ static const ModValidity kValidityCategoryTooLong ;
+ static const ModValidity kValidityCategoryMissing ;
+
+ static const ModValidity kValidityCampaignThumbnailMissing ;
+ static const ModValidity kValidityCampaignThumbnailTooLong ;
+ static const ModValidity kValidityCampaignThumbnailInvalid ;
+
+ static const ModValidity kValidityMenuItemThumbnailTooLong ;
+ static const ModValidity kValidityMenuItemThumbnailInvalid ;
+
+ static const ModValidity kValidityCampaignMenuScriptTooLong ;
+ static const ModValidity kValidityMainScriptTooLong ;
+
+ static const ModValidity kValidityAttributeIdTooLong;
+ static const ModValidity kValidityAttributeIdInvalid;
+
+ static const ModValidity kValidityAttributeValueTooLong;
+ static const ModValidity kValidityAttributeValueInvalid;
+
+ static const ModValidity kValidityLevelIdTooLong;
+
+ static const ModValidity kValidityCampaignIDTooLong;
+ static const ModValidity kValidityCampaignIDMissing;
+
+ static const ModValidity kValidityLevelParameterNameTooLong;
+ static const ModValidity kValidityLevelParameterNameMissing;
+
+ static const ModValidity kValidityLevelParameterValueTooLong;
+ static const ModValidity kValidityLevelParameterValueMissing;
+
+ static const ModValidity kValidityLevelParameterTypeTooLong;
+
+ static const ModValidity kValidityPoseNameTooLong;
+ static const ModValidity kValidityPoseNameMissing;
+ static const ModValidity kValidityPosePathTooLong;
+ static const ModValidity kValidityPosePathMissing;
+ static const ModValidity kValidityPoseCommandTooLong;
+ static const ModValidity kValidityPoseCommandMissing;
+
+ static const uint64_t kValidityFlagCount = 91;
+
+ static const ModValidity kValidityUploadSteamworksBlockingMask ;
+ static const ModValidity kValidityActivationBreakingErrorMask ;
+
+ static const ModValidity kValidityCampaignMenuMusicTooLong ;
+ static const ModValidity kValidityCampaignMenuMusicMissing ;
+
+ static const uint32_t k_UploadValidityOk = 0;
+ static const uint32_t k_UploadValidityGenericValidityError = 1UL << 0; //If any of the standard kValidity values are set, we don't allow an upload.
+ static const uint32_t k_UploadValidityInvalidPreviewImage = 1UL << 1;
+ static const uint32_t k_UploadValidityMissingPreview = 1UL << 2;
+ static const uint32_t k_UploadValidityInvalidModFolder = 1UL << 3;
+ static const uint32_t k_UploadValidityBrokenXml = 1UL << 4;
+ static const uint32_t k_UploadValidityInvalidID = 1UL << 5;
+ static const uint32_t k_UploadValidityInvalidVersion = 1UL << 6;
+ static const uint32_t k_UploadValidityMissingReadRights = 1UL << 7;
+ static const uint32_t k_UploadValidityMissingXml = 1UL << 8;
+ static const uint32_t k_UploadValidityInvalidThumbnail = 1UL << 9;
+ static const uint32_t k_UploadValidityOversizedPreviewImage = 1UL << 10;
+
+ enum UserVote {
+ k_VoteUnknown,
+ k_VoteNone,
+ k_VoteUp,
+ k_VoteDown
+ };
+
+private:
+ ModValidity valid;
+
+ bool is_core;
+ bool active;
+ bool needs_restart;
+ bool supports_online;
+ bool explicit_version_support;
+ ModID sid;
+
+ //Steamworks specific data
+ UGCID ugc_id;
+ //---
+
+ bool has_been_activated;
+
+ static int sid_counter;
+
+public:
+ ModInstance( const std::string &_path );
+ ModInstance( const UGCID _ugc_id );
+
+ void ParseLevel(ModInstance::Level * dest, TiXmlElement* pElemLevel, uint32_t id_fallback);
+ void ParseParameter(ModInstance::Parameter* dest, TiXmlElement* pElemParameter, const char* parent_type);
+
+ void PurgeActiveSettings();
+ void Reload();
+
+ bool IsValid() const;
+ bool CanActivate() const;
+ bool IsActive() const;
+ bool IsCore() const;
+
+ ModID GetSid() const;
+ bool Activate(bool value);
+ void SetHasBeenActivated();
+ bool HasBeenActivated() const;
+ bool NeedsRestart() const;
+ bool SupportsOnline() const;
+ bool ExplicitVersionSupport() const;
+
+ /* Evaluate if this mod could be uploaded to steam or other */
+ uint32_t GetUploadValidity();
+
+ /** These only really applies to a mod from steamworks **/
+ SteamworksUGCItem* GetUGCItem();
+
+ bool IsOwnedByCurrentUser();
+ bool IsSubscribed();
+ bool IsInstalled();
+ bool IsDownloading();
+ bool IsDownloadPending();
+ bool IsNeedsUpdate();
+ bool IsFavorite();
+
+ UserVote GetUserVote();
+
+ void RequestUnsubscribe();
+ void RequestSubscribe();
+ void RequestVoteSet(bool voteup);
+ void RequestFavoriteSet(bool fav);
+
+ void RequestUpdate( ModID source_sid, const char* update_message, ModVisibilityOptions visibility );
+ /** End to steamworks specific **/
+
+ const char* GetModsourceString() const;
+
+ const char* GetStatus();
+
+ std::string GetTagsListString();
+
+ std::vector<ModID> GetCollisionsWithActiveMods() const;
+ std::vector<ModID> GetIDCollisionsWithActiveMods() const;
+
+ static std::vector<std::string> GenerateValidityErrorsArr( const ModValidity& validity, const ModInstance* instance = NULL );
+ static std::string GenerateValidityErrors( const ModValidity& v, const ModInstance* instance = NULL );
+ static std::vector<std::string> GetFileCollisions(const ModInstance* lhs, const ModInstance* rhs);
+
+ Path GetFullAbsThumbnailPath() const;
+
+ std::vector<std::string> GetValidityErrorsArr() const;
+ std::string GetValidityErrors() const;
+ ModValidity GetValidity() const;
+
+ std::vector<std::string> invalid_item_paths;
+ std::vector<std::string> GetInvalidItemPaths() const;
+
+ ModSource modsource;
+
+ std::string path;
+
+ fixed_string<MOD_ID_MAX_LENGTH> id;
+ fixed_string<MOD_NAME_MAX_LENGTH> name;
+ fixed_string<MOD_CATEGORY_MAX_LENGTH> category;
+ fixed_string<MOD_VERSION_MAX_LENGTH> version;
+ fixed_string<MOD_AUTHOR_MAX_LENGTH> author;
+ fixed_string<MOD_DESCRIPTION_MAX_LENGTH> description;
+ fixed_string<MOD_THUMBNAIL_MAX_LENGTH> thumbnail;
+
+ std::vector<std::string> preview_images;
+ std::set<std::string> tags;
+
+ std::vector<std::string> supported_versions;
+ std::vector<std::string> level_hook_files;
+ std::vector<ModDependency> mod_dependencies;
+
+ std::vector<MenuItem> main_menu_items;
+ std::vector<Item> items;
+
+ std::vector<Level> levels;
+
+ std::vector<Campaign> campaigns;
+
+ std::vector<Pose> poses;
+
+ std::vector<std::string> overload_files;
+ std::vector<std::string> manifest; //List of all files in the mod.
+
+ struct LevelLocalizationData {
+ std::string name;
+ std::string loading_screen_tip;
+ };
+ struct LanguageData {
+ std::string language; // Language name in english
+ std::string local_language; // Language name in the language
+ std::map<std::string, LevelLocalizationData> per_level_data;
+ };
+ // Language shortcode
+ std::map<std::string, LanguageData> language_data;
+
+ friend std::ostream& operator<<(std::ostream& os, const ModInstance &mi );
+};
+
+class ModLoadingCallback
+{
+ public:
+ virtual void ModActivationChange( const ModInstance* mod ) = 0;
+
+ virtual ~ModLoadingCallback() {}
+};
+
+class ModLoading
+{
+private:
+ std::vector<ModInstance*> mods;
+ std::vector<ModLoadingCallback*> callbacks;
+
+ static ModLoading instance;
+ ModLoading();
+
+ std::vector<ModInstance*>::iterator GetMod( const std::string& path );
+
+ //void AddMod( const std::string& path );
+ void RemoveMod( const std::string& path );
+ void ReloadMod( const std::string& path );
+
+ void SetMods( std::vector<std::string> modpaths );
+
+ friend class ModInstance;
+ void TriggerModActivationCallback( ModInstance* mod );
+
+ ActiveModsParser acp;
+public:
+ void AddMod(const std::string& path);
+ std::string WhichCampaignLevelBelongsTo(const std::string& level);
+ bool IsCampaignPresent(const std::string& campaign);
+ bool CampaignHasLevel(const std::string& campaign, const std::string& level_path);
+ ModInstance* GetMod( ModID sid );
+ std::string GetModName( ModID sid );
+ std::string GetModID(ModID sid);
+ bool IsActive(ModID sid);
+ bool IsActive(std::string id);
+
+ ModInstance::Campaign GetCampaign(std::string& campaign_id);
+ ModInstance* GetModInstance(const std::string& campaign_id);
+ std::vector<ModInstance::Campaign> GetCampaigns();
+
+ std::vector<ModInstance*> GetModsMatchingID(std::string id);
+ std::vector<ModInstance*> GetSteamModsMatchingID(std::string id);
+ std::vector<ModInstance*> GetLocalModsMatchingID(std::string id);
+ std::vector<ModInstance*> GetLocalMods();
+ std::vector<ModInstance*> GetAllMods();
+
+ const std::vector<ModInstance*>& GetMods();
+ const std::vector<ModID> GetModsSid();
+
+ void InitMods();
+ void DetectMods();
+ void Reload();
+
+#if ENABLE_STEAMWORKS
+ ModID CreateSteamworksMod( UGCID steamworks_id );
+#endif
+
+ void RegisterCallback( ModLoadingCallback * callback );
+ void DeRegisterCallback( ModLoadingCallback * callback );
+
+ static ModLoading& Instance();
+ void Initialize();
+ void Dispose();
+ void SaveModConfig();
+
+ std::vector<std::string> ActiveLevelHooks();
+
+ void RebuildLocalization();
+};
+
+std::ostream& operator<<(std::ostream& os, const ModInstance::ModDependency &md );
+std::ostream& operator<<(std::ostream& os, const ModInstance &mi );
diff --git a/Source/Internal/path.cpp b/Source/Internal/path.cpp
new file mode 100644
index 00000000..2103e20d
--- /dev/null
+++ b/Source/Internal/path.cpp
@@ -0,0 +1,96 @@
+//-----------------------------------------------------------------------------
+// Name: path.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "path.h"
+
+#include <Utility/strings.h>
+
+#include <Compat/compat.h>
+
+#include <iostream>
+#include <string>
+#include <cstdlib>
+#include <cstring>
+
+using std::string;
+using std::ostream;
+using std::endl;
+
+Path::Path()
+{
+ memset( original, '\0', kPathSize * sizeof(char));
+ memset( resolved, '\0', kPathSize * sizeof(char));
+ source = kNoPath;
+ valid = false;
+}
+
+string Path::GetAbsPathStr() const
+{
+ return GetAbsPath(resolved);
+}
+
+string Path::GetFullPathStr() const
+{
+ return string(resolved);
+}
+
+const char* Path::GetFullPath() const
+{
+ return resolved;
+}
+
+string Path::GetOriginalPathStr() const
+{
+ return string(original);
+}
+
+const char* Path::GetOriginalPath() const
+{
+ return original;
+}
+
+ModID Path::GetModsource() const
+{
+ return mod_source;
+}
+
+bool Path::isValid() const
+{
+ return valid;
+}
+
+bool Path::operator==(const Path& rhs) const {
+ return strmtch(resolved,rhs.resolved)
+ && strmtch(original,rhs.original)
+ && source == rhs.source
+ && valid == rhs.valid;
+}
+
+bool Path::operator<(const Path& rhs) const {
+ return strcmp(resolved,rhs.resolved);
+}
+
+ostream& operator<<(ostream& out, const Path& path )
+{
+ out << "\"" << path.GetFullPath() << "\" (" << (path.valid ? "valid" : "invalid") << ")" << endl;
+ return out;
+}
diff --git a/Source/Internal/path.h b/Source/Internal/path.h
new file mode 100644
index 00000000..b0402643
--- /dev/null
+++ b/Source/Internal/path.h
@@ -0,0 +1,68 @@
+//-----------------------------------------------------------------------------
+// Name: path.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/modid.h>
+
+#include <Global/global_config.h>
+
+enum PathFlags {
+ kNoPath = 0,
+ kDataPaths = 1<<0,
+ kWriteDir = 1<<1,
+ kModPaths = 1<<2,
+ kAbsPath = 1<<3,
+ kModWriteDirs = 1<<4,
+ kAnyPath = kDataPaths | kWriteDir | kModPaths | kAbsPath | kModWriteDirs
+};
+
+struct Path
+{
+ Path();
+
+ std::string GetAbsPathStr() const;
+ std::string GetFullPathStr() const;
+ const char* GetFullPath() const;
+ std::string GetOriginalPathStr() const;
+ const char* GetOriginalPath() const;
+ ModID GetModsource() const;
+ bool isValid() const;
+
+ //What mod, if any, this file comes from.
+ //This is valid only if source has the mod flag set.
+ ModID mod_source;
+
+ //The original path request value
+ char original[kPathSize];
+ //The found path
+ char resolved[kPathSize];
+ //Where the file was found
+ PathFlags source;
+ //Is file valid
+ bool valid;
+
+ bool operator==(const Path& rhs) const;
+ bool operator<(const Path& rhs) const;
+};
+
+std::ostream& operator<<( std::ostream& out, const Path& path );
diff --git a/Source/Internal/path_set.cpp b/Source/Internal/path_set.cpp
new file mode 100644
index 00000000..9ac3148c
--- /dev/null
+++ b/Source/Internal/path_set.cpp
@@ -0,0 +1,59 @@
+//-----------------------------------------------------------------------------
+// Name: path_set.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "path_set.h"
+
+#include <Asset/Asset/image_sampler.h>
+#include <Graphics/textures.h>
+#include <Main/engine.h>
+
+void PathSetUtil::GetCachedFiles( PathSet& path_set ) {
+ PathSet new_path_set;
+ for(PathSet::iterator iter = path_set.begin(); iter != path_set.end(); ++iter){
+ const std::string &labeled_path = (*iter);
+ size_t space_pos = labeled_path.find(' ');
+ if(space_pos == std::string::npos){
+ DisplayError("Error", ("No space found in labeled string: "+labeled_path).c_str());
+ continue;
+ }
+ const std::string& type = labeled_path.substr(0, space_pos);
+ const std::string& path = labeled_path.substr(space_pos+1, labeled_path.size() - (space_pos+1));
+ switch(type[0]){
+ case 'i':
+ if(type == "image_sample"){
+ ImageSamplerRef ref = Engine::Instance()->GetAssetManager()->LoadSync<ImageSampler>(path);
+ if( ref.valid() ) {
+ std::string fixed;
+ if(ref->GetCachePath(&fixed)) {
+ new_path_set.insert("image_sample_cache "+fixed);
+ }
+ } else {
+ LOGE << "Failed loading " << path << std::endl;
+ }
+ }
+ break;
+ default:
+ new_path_set.insert(labeled_path);
+ }
+ }
+ path_set = new_path_set;
+}
diff --git a/Source/Internal/path_set.h b/Source/Internal/path_set.h
new file mode 100644
index 00000000..227af345
--- /dev/null
+++ b/Source/Internal/path_set.h
@@ -0,0 +1,32 @@
+//-----------------------------------------------------------------------------
+// Name: path_set.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <set>
+#include <string>
+
+typedef std::set<std::string> PathSet;
+
+namespace PathSetUtil {
+ void GetCachedFiles(PathSet& path_set);
+}
diff --git a/Source/Internal/path_utility.cpp b/Source/Internal/path_utility.cpp
new file mode 100644
index 00000000..8a05e1ae
--- /dev/null
+++ b/Source/Internal/path_utility.cpp
@@ -0,0 +1,67 @@
+//-----------------------------------------------------------------------------
+// Name: pathutility.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "path_utility.h"
+
+#ifdef WIN32
+#define NOMINMAX
+#include <windows.h>
+#include <direct.h>
+#endif
+
+std::string pathUtility::localPathToGlobal(const std::string &path)
+{
+ return localPathToGlobal(path.c_str());
+}
+
+std::string pathUtility::localPathToGlobal(const char *path)
+{
+#ifdef _DEPLOY
+#ifdef WIN32
+ char filepath[4096];
+ GetModuleFileName( NULL, filepath, 4096);
+ std::string dirpath = filepath;
+
+ dirpath = dirpath.substr(0, dirpath.rfind("\\"));
+ dirpath += "\\";
+ dirpath += path;
+
+ return dirpath;
+#endif
+#endif
+
+ return std::string(path);
+}
+
+void pathUtility::changeWorkingDirToAppDir()
+{
+#ifdef _DEPLOY
+#ifdef WIN32
+ char filepath[4096];
+ GetModuleFileName( NULL, filepath, 4096);
+ std::string dirpath = filepath;
+
+ _chdir(dirpath.substr(0, dirpath.rfind("\\")).c_str());
+
+#endif
+#endif
+}
diff --git a/Source/Internal/path_utility.h b/Source/Internal/path_utility.h
new file mode 100644
index 00000000..652046eb
--- /dev/null
+++ b/Source/Internal/path_utility.h
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------------
+// Name: pathutility.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <string>
+#include <sstream>
+
+/**
+ * Utilities for cross system path fixing,
+ * mainly to resolve ifstream issues on windows right now
+ */
+
+class pathUtility
+{
+public:
+
+ // Currently does nothing on non-windows platforms
+ static std::string localPathToGlobal(const std::string &path);
+ static std::string localPathToGlobal(const char *path);
+
+ static void changeWorkingDirToAppDir();
+};
diff --git a/Source/Internal/profiler.cpp b/Source/Internal/profiler.cpp
new file mode 100644
index 00000000..9e208019
--- /dev/null
+++ b/Source/Internal/profiler.cpp
@@ -0,0 +1,207 @@
+//-----------------------------------------------------------------------------
+// Name: profiler.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "profiler.h"
+
+#include <Internal/error.h>
+#include <Memory/stack_allocator.h>
+#include <Logging/logdata.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#if PROFILER_DEBUG
+#include <cassert>
+#include <cstdarg>
+#include <string>
+#include <vector>
+
+
+#define GPUMARKBUFSIZE 512
+
+struct ProfStack {
+ std::vector<std::string> messages;
+};
+
+
+__thread unsigned int stack_depth = 0;
+__thread ProfStack *prof_stack = NULL;
+
+
+class SanityCheck {
+public:
+ SanityCheck() {
+ assert(stack_depth == 0);
+ }
+
+ ~SanityCheck() {
+ assert(stack_depth == 0);
+ }
+} san_check;
+
+
+void profileEnter(ProfilerContext *ctx, const char *msg, va_list args) {
+ if (prof_stack == NULL) {
+ prof_stack = new ProfStack;
+ }
+ assert(prof_stack != NULL);
+
+ char buf[GPUMARKBUFSIZE];
+ int ret = vsnprintf(buf, GPUMARKBUFSIZE, msg, args);
+ // We want to crash if this fails
+ assert(ret >= 0);
+ if (ret >= GPUMARKBUFSIZE) {
+ ret = GPUMARKBUFSIZE - 1;
+ }
+ prof_stack->messages.push_back(std::string(buf, ret));
+
+ stack_depth++;
+}
+
+
+void profileEnter(ProfilerContext *ctx, const char *msg, ...) {
+ va_list args;
+
+ va_start(args, msg);
+ profileEnter(ctx, msg, args);
+ va_end(args);
+}
+
+
+void profileLeave(ProfilerContext *ctx) {
+ assert(prof_stack != NULL);
+
+ prof_stack->messages.pop_back();
+
+ assert(stack_depth > 0);
+ stack_depth--;
+}
+
+
+ProfilerScopedZone::ProfilerScopedZone(ProfilerContext *ctx_, const char *msg, ...)
+: ctx(ctx_)
+, expected_depth(stack_depth)
+{
+ va_list args;
+
+ va_start(args, msg);
+ profileEnter(ctx, msg, args);
+ va_end(args);
+}
+
+
+ProfilerScopedZone::~ProfilerScopedZone() {
+ assert(stack_depth == expected_depth + 1);
+
+ profileLeave(ctx);
+}
+
+
+#elif !defined(NTELEMETRY)
+
+
+void ProfilerContext::Init(StackAllocator* stack_allocator) {
+ memory = stack_allocator->Alloc(ProfilerContext::kMemSize);
+ if(!memory){
+ FatalError("Error",
+ "Memory allocation error in file %s : %d", __FILE__, __LINE__);
+ }
+ tmLoadTelemetry(TM_LOAD_CHECKED_LIBRARY); // Load telemetry dll
+ if(TM_OK != tmStartup()) {
+ FatalError("Error", "Could not start up Telemetry -- "
+ "check if DLL is in correct place");
+ }
+ if(TM_OK != tmInitializeContext(
+ &tm_context, memory, ProfilerContext::kMemSize))
+ {
+ FatalError("Error", "Could not initialize Telemetry context");
+ }
+ if(TM_OK != tmOpen(
+ tm_context, "Overgrowth", __DATE__ " " __TIME__,
+ "localhost", TMCT_TCP, TELEMETRY_DEFAULT_PORT,
+ TMOF_DEFAULT | TMOF_INIT_NETWORKING, 1000 ))
+ {
+ LOGE << "Could not open Telemetry -- check if server is open" << std::endl;
+ }
+ tmThreadName(tm_context, 0, "Main thread");
+}
+
+void ProfilerContext::Dispose(StackAllocator* stack_allocator) {
+ tmClose(tm_context);
+ tmShutdownContext(tm_context);
+ if(TM_OK != tmShutdown()) {
+ FatalError("Error", "Could not shutdown telemetry");
+ }
+ stack_allocator->Free(memory);
+}
+
+
+#elif GPU_MARKERS
+
+#include <Internal/snprintf.h>
+#include <Threading/thread_sanity.h>
+
+#include <opengl.h>
+
+#include <cassert>
+#include <cstdarg>
+
+
+#define GPUMARKBUFSIZE 512
+
+ProfilerScopedGPUZone::ProfilerScopedGPUZone(const char *msg, ...) {
+ AssertMainThread();
+
+ if (GLEW_KHR_debug) {
+ char buf[GPUMARKBUFSIZE];
+ va_list args;
+
+ va_start(args, msg);
+
+ int ret = vsnprintf(buf, GPUMARKBUFSIZE, msg, args);
+ va_end(args);
+
+ // We want to crash if this fails
+ // If we want to continue we'd have to keep track of whether this succeeded to avoid GL errors
+ assert(ret >= 0);
+
+ if (ret < GPUMARKBUFSIZE) {
+ buf[ret] = '\0';
+ } else {
+ buf[GPUMARKBUFSIZE - 1] = '\0';
+ }
+
+ glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0, -1, buf);
+ }
+}
+
+
+ProfilerScopedGPUZone::~ProfilerScopedGPUZone() {
+ AssertMainThread();
+
+ if (GLEW_KHR_debug) {
+ glPopDebugGroup();
+ }
+}
+
+
+#endif //NTELEMETRY
diff --git a/Source/Internal/profiler.h b/Source/Internal/profiler.h
new file mode 100644
index 00000000..438ccef7
--- /dev/null
+++ b/Source/Internal/profiler.h
@@ -0,0 +1,193 @@
+//-----------------------------------------------------------------------------
+// Name: profiler.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+class StackAllocator;
+
+#if defined(OGDA) || defined(OG_WORKER)
+
+#define PROFILER_ENTER(x,y) do{}while(0)
+#define PROFILER_LEAVE(x) do{}while(0)
+#define PROFILER_ZONE(x,y) do{}while(0)
+
+struct ProfilerContext {}; // TODO: handle better?
+
+#else
+
+// for figuring out why profiler leaks zones
+// only tested on Linux...
+#define PROFILER_DEBUG 0
+
+#if PROFILER_DEBUG
+
+ #undef NTELEMETRY
+ #define NTELEMETRY 1
+
+ struct ProfilerContext {
+ void Init(StackAllocator* stack_allocator) {}
+ void Dispose(StackAllocator* stack_allocator) {}
+ };
+
+ struct ProfilerScopedZone {
+ ProfilerContext *ctx;
+ void *pointer;
+ unsigned int expected_depth;
+
+ ProfilerScopedZone(ProfilerContext *ctx_, const char *msg, ...);
+ ~ProfilerScopedZone();
+ };
+
+ void profileEnter(ProfilerContext *ctx, const char *msg, ...);
+ void profileLeave(ProfilerContext *ctx);
+
+
+ #define MERGE_HELPER(a,b) a##b
+ #define MERGE(a,b) MERGE_HELPER(a,b)
+
+ #define PROFILER_ENTER(ctx, ...) profileEnter(ctx, __VA_ARGS__);
+ #define PROFILER_LEAVE(ctx) profileLeave(ctx);
+ #define PROFILER_ZONE(ctx, ...) ProfilerScopedZone MERGE(p, __LINE__) (ctx, __VA_ARGS__);
+ #define PROFILER_ZONE_STALL(ctx, str) ProfilerScopedZone MERGE(p, __LINE__) (ctx, str);
+ #define PROFILER_ZONE_IDLE(ctx, str) ProfilerScopedZone MERGE(p, __LINE__) (ctx, str);
+ #define PROFILER_GPU_ZONE(ctx, ...) ProfilerScopedZone MERGE(p, __LINE__) (ctx, __VA_ARGS__);
+ #define PROFILER_ENTER_STALL_THRESHOLD(ctx, match_id_ptr, u_sec, str) profileEnter(ctx, str);
+ #define PROFILER_LEAVE_THRESHOLD(ctx, match_id) profileLeave(ctx);
+ #define PROFILER_ENTER_DYNAMIC_STRING(ctx, str) profileEnter(ctx, str);
+ #define PROFILER_ZONE_DYNAMIC_STRING(ctx, str) ProfilerScopedZone MERGE(p, __LINE__) (ctx, str);
+ #define PROFILER_NAME_TIMELINE(ctx, str)
+ #define PROFILER_NAME_THREAD(ctx, str)
+ #define PROFILER_TICK(ctx)
+
+ #define PROFILED_TEXTURE_MUTEX_LOCK \
+ texture_mutex.lock();
+
+ #define PROFILED_TEXTURE_MUTEX_UNLOCK \
+ texture_mutex.unlock();
+
+#elif GPU_MARKERS
+
+ #undef NTELEMETRY
+ #define NTELEMETRY 1
+
+ struct ProfilerContext {
+ void Init(StackAllocator* stack_allocator) {}
+ void Dispose(StackAllocator* stack_allocator) {}
+ };
+
+ struct ProfilerScopedGPUZone {
+ ProfilerScopedGPUZone(const char *msg, ...);
+ ~ProfilerScopedGPUZone();
+ };
+
+ #define MERGE_HELPER(a,b) a##b
+ #define MERGE(a,b) MERGE_HELPER(a,b)
+
+ #define PROFILER_ENTER(ctx, ...)
+ #define PROFILER_LEAVE(ctx)
+ #define PROFILER_ZONE(ctx, ...)
+ #define PROFILER_ZONE_STALL(ctx, str)
+ #define PROFILER_ZONE_IDLE(ctx, str)
+ #define PROFILER_GPU_ZONE(ctx, ...) ProfilerScopedGPUZone MERGE(p, __LINE__) (__VA_ARGS__);
+ #define PROFILER_ENTER_STALL_THRESHOLD(ctx, match_id_ptr, u_sec, str)
+ #define PROFILER_LEAVE_THRESHOLD(ctx, match_id)
+ #define PROFILER_ENTER_DYNAMIC_STRING(ctx, str)
+ #define PROFILER_ZONE_DYNAMIC_STRING(ctx, str)
+ #define PROFILER_NAME_TIMELINE(ctx, str)
+ #define PROFILER_NAME_THREAD(ctx, str)
+ #define PROFILER_TICK(ctx)
+
+ #define PROFILED_TEXTURE_MUTEX_LOCK \
+ texture_mutex.lock();
+
+ #define PROFILED_TEXTURE_MUTEX_UNLOCK \
+ texture_mutex.unlock();
+
+#elif !defined(NTELEMETRY)
+ #include <telemetry.h>
+
+ class ProfilerContext {
+ public:
+ static const int kMemSize = 2*1024*1024;
+ HTELEMETRY tm_context;
+ void* memory;
+ const char* err_str;
+
+ void Init(StackAllocator* stack_allocator);
+ void Dispose(StackAllocator* stack_allocator);
+ };
+
+ #define PROFILER_ENTER(ctx, ...) tmEnter((ctx)->tm_context, TMZF_NONE, __VA_ARGS__)
+ #define PROFILER_LEAVE(ctx) tmLeave((ctx)->tm_context)
+ #define PROFILER_ZONE(ctx, ...) tmZone((ctx)->tm_context, TMZF_NONE, __VA_ARGS__)
+ #define PROFILER_ZONE_STALL(ctx, str) tmZone((ctx)->tm_context, TMZF_STALL, (str))
+ #define PROFILER_ZONE_IDLE(ctx, str) tmZone((ctx)->tm_context, TMZF_IDLE, (str))
+ #define PROFILER_GPU_ZONE(ctx, ...) tmZone((ctx)->tm_context, TMZF_NONE, __VA_ARGS__)
+ #define PROFILER_ENTER_STALL_THRESHOLD(ctx, match_id_ptr, u_sec, str) tmEnterEx((ctx)->tm_context, (match_id_ptr), 0, (u_sec), __FILE__, __LINE__, TMZF_STALL, (str))
+ #define PROFILER_LEAVE_THRESHOLD(ctx, match_id) tmLeaveEx( (ctx)->tm_context, (match_id), 0, __FILE__, __LINE__ )
+ #define PROFILER_ENTER_DYNAMIC_STRING(ctx, str) tmEnter((ctx)->tm_context, TMZF_NONE, "%s", tmDynamicString((ctx)->tm_context, (str)))
+ #define PROFILER_ZONE_DYNAMIC_STRING(ctx, str) tmZone((ctx)->tm_context, TMZF_NONE, "%s", tmDynamicString((ctx)->tm_context, (str)))
+ #define PROFILER_NAME_TIMELINE(ctx, str) tmSetTimelineSectionName((ctx)->tm_context, (str))
+ #define PROFILER_NAME_THREAD(ctx, str) tmThreadName((ctx)->tm_context, 0, (str))
+ #define PROFILER_TICK(ctx) tmTick((ctx)->tm_context);
+
+ #define PROFILED_TEXTURE_MUTEX_LOCK \
+ TmU64 matchid;\
+ PROFILER_ENTER_STALL_THRESHOLD(g_profiler_ctx, \
+ &matchid, 500, "texture mutex");\
+ texture_mutex.lock();\
+ PROFILER_LEAVE_THRESHOLD(g_profiler_ctx, matchid);
+
+ #define PROFILED_TEXTURE_MUTEX_UNLOCK \
+ texture_mutex.unlock();
+
+#else
+ struct ProfilerContext {
+ void Init(StackAllocator* stack_allocator) {}
+ void Dispose(StackAllocator* stack_allocator) {}
+ };
+
+ #define PROFILER_ENTER(ctx, ...)
+ #define PROFILER_LEAVE(ctx)
+ #define PROFILER_ZONE(ctx, ...)
+ #define PROFILER_ZONE_STALL(ctx, str)
+ #define PROFILER_ZONE_IDLE(ctx, str)
+ #define PROFILER_GPU_ZONE(ctx, ...)
+ #define PROFILER_ENTER_STALL_THRESHOLD(ctx, match_id_ptr, u_sec, str)
+ #define PROFILER_LEAVE_THRESHOLD(ctx, match_id)
+ #define PROFILER_ENTER_DYNAMIC_STRING(ctx, str)
+ #define PROFILER_ZONE_DYNAMIC_STRING(ctx, str)
+ #define PROFILER_NAME_TIMELINE(ctx, str)
+ #define PROFILER_NAME_THREAD(ctx, str)
+ #define PROFILER_TICK(ctx)
+
+ #define PROFILED_TEXTURE_MUTEX_LOCK \
+ texture_mutex.lock();
+
+ #define PROFILED_TEXTURE_MUTEX_UNLOCK \
+ texture_mutex.unlock();
+
+#endif
+
+#endif
+
+extern ProfilerContext* g_profiler_ctx;
diff --git a/Source/Internal/referencecounter.h b/Source/Internal/referencecounter.h
new file mode 100644
index 00000000..0c2e18e6
--- /dev/null
+++ b/Source/Internal/referencecounter.h
@@ -0,0 +1,148 @@
+//-----------------------------------------------------------------------------
+// Name: referencecounter.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+#ifndef NULL
+#define NULL 0
+#endif
+
+template <typename T>
+class ReferenceCounter
+{
+private:
+ int* ref_counter;
+ T* data;
+public:
+ ReferenceCounter():
+ ref_counter(new int(1)),
+ data(new T())
+ {
+ }
+
+ /*
+ ReferenceCounter(const T& _data):
+ ref_counter(new int(1)),
+ data(new T(_data))
+ {
+ }
+ */
+
+ ReferenceCounter( T* _data ):
+ ref_counter(new int(1)),
+ data(_data)
+ {
+ }
+
+ void Set(T* _data) {
+ *ref_counter -= 1;
+ if( *ref_counter == 0)
+ {
+ delete ref_counter;
+ if( data ) {
+ delete data;
+ }
+ data = NULL;
+ ref_counter = NULL;
+ }
+
+ data = _data;
+ ref_counter = new int(1);
+ }
+
+ ReferenceCounter( const ReferenceCounter<T>& other )
+ {
+ data = other.data;
+ ref_counter = other.ref_counter;
+
+ *ref_counter += 1;
+ }
+
+ ReferenceCounter& operator=(const ReferenceCounter<T>& other)
+ {
+ if (&other == this)
+ {
+ return *this;
+ }
+
+ *ref_counter -= 1;
+ if( *ref_counter == 0)
+ {
+ delete ref_counter;
+ if( data ) {
+ delete data;
+ }
+ data = NULL;
+ ref_counter = NULL;
+ }
+
+ data = other.data;
+ ref_counter = other.ref_counter;
+
+ *ref_counter += 1;
+
+ return *this;
+ }
+
+ T* operator->()
+ {
+ return data;
+ }
+
+ ~ReferenceCounter()
+ {
+ *ref_counter -= 1;
+ if( *ref_counter == 0)
+ {
+ delete ref_counter;
+ delete data;
+ data = NULL;
+ ref_counter = NULL;
+ }
+ }
+
+ const T& GetConst() const
+ {
+ return *data;
+ }
+
+ T* GetPtr() {
+ return data;
+ }
+
+ template<typename H>
+ H* GetPtr() {
+ return static_cast<H*>(data);
+ }
+
+ template<typename H>
+ const H* GetConstPtr() const {
+ return static_cast<H*>(data);
+ }
+
+ const bool Valid() const {
+ return data != NULL;
+ }
+
+ int GetReferenceCount() const {
+ return ref_counter ? *ref_counter : 0;
+ }
+};
diff --git a/Source/Internal/returnpathutil.cpp b/Source/Internal/returnpathutil.cpp
new file mode 100644
index 00000000..cd3f5982
--- /dev/null
+++ b/Source/Internal/returnpathutil.cpp
@@ -0,0 +1,199 @@
+//-----------------------------------------------------------------------------
+// Name: returnpathutil.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "returnpathutil.h"
+
+#include <Asset/Asset/ambientsounds.h>
+#include <Asset/Asset/soundgroup.h>
+#include <Asset/Asset/syncedanimation.h>
+#include <Asset/Asset/attacks.h>
+#include <Asset/Asset/character.h>
+#include <Asset/Asset/item.h>
+#include <Asset/Asset/material.h>
+#include <Asset/Asset/reactions.h>
+#include <Asset/Asset/actorfile.h>
+#include <Asset/Asset/decalfile.h>
+#include <Asset/Asset/objectfile.h>
+#include <Asset/Asset/hotspotfile.h>
+
+#include <Main/engine.h>
+#include <XML/xml_helper.h>
+
+#include <tinyxml.h>
+
+namespace {
+std::string GetTypeFromXML( const std::string &path ){
+ TiXmlDocument doc;
+ LoadXMLRetryable(doc, path, "Unknown");
+
+ if(doc.Error()) {
+ LOGE << "Problem loading xml document " << path << " error: " << doc.ErrorDesc() << std::endl;
+ return "UNKNOWN";
+ }
+
+ TiXmlHandle h_doc(&doc);
+
+ TiXmlElement* root = h_doc.FirstChildElement().ToElement();
+ while( root ) {
+ std::string type = root->Value();
+ if(type == "Type") {
+ return type;
+ }
+ root = root->NextSiblingElement();
+ }
+
+ root = h_doc.FirstChildElement().ToElement();
+ while( root ) {
+ std::string type = root->Value();
+
+ switch(type[0]) {
+ case 'a': if(type == "attack"){return "attack";} break;
+ case 'A': if(type == "Actor"){return "actor";} break;
+ case 'c': if(type == "character"){return "character";} break;
+ case 'D': if(type == "DecalObject"){return "decal";} break;
+ case 'H': if(type == "Hotspot"){return "hotspot";} break;
+ case 'i': if(type == "item"){return "item";} break;
+ case 'L': if(type == "Level"){return "level";} break;
+ case 'm': if(type == "material"){return "material";} break;
+ case 'O': if(type == "Object"){return "envobject";} break;
+ case 'p': if(type == "particle"){return "particle";} break;
+ case 'r': switch(type[1]) {
+ case 'e': if(type == "reaction"){return "reaction";} break;
+ case 'i': if(type == "rig"){return "rig";} break;
+ }
+ break;
+ case 's': switch(type[1]) {
+ case 'a': if(type == "saved"){return "saved";} break;
+ case 'o':
+ switch(type[2]) {
+ case 'n': if(type == "songlist"){return "songlist";} break;
+ case 'u': if(type == "soundgroup"){return "soundgroup";} break;
+ }
+ break;
+ }
+ break;
+ case 'S': switch(type[1]){
+ case 'o': if(type == "Sound"){return "ambientsound";} break;
+ case 'y': if(type == "SyncedAnimationGroup"){return "synced_animation";} break;
+ }
+ break;
+ }
+ root = root->NextSiblingElement();
+ }
+
+ LOGE << "Unable to find a type string for " << path << std::endl;
+ return "UNKONWN";
+}
+} // namespace ""
+
+std::string ReturnPathUtil::GetTypeFromFilePath( const std::string &path )
+{
+ std::string::size_type final_dot = path.rfind('.');
+ if(final_dot == std::string::npos){
+ FatalError("Error", "File has no extension: %s", path.c_str());
+ }
+ ++final_dot;
+ std::string suffix = path.substr(final_dot, path.length() - final_dot);
+ switch(suffix[0]){
+ case 'a': switch(suffix[1]){
+ case 'n': if(suffix == "anm"){ return "animation"; } break;
+ case 's': if(suffix == "as"){ return "script"; } break;
+ } break;
+ case 'd': if(suffix == "dds"){ return "texture"; } break;
+ case 'h': if(suffix == "html"){ return "webpage"; } break;
+ case 'o': switch(suffix[1]){
+ case 'b': if(suffix == "obj"){ return "model"; } break;
+ case 's': if(suffix == "ogg"){ return "song"; } break;
+ } break;
+ case 'p': switch(suffix[1]){
+ case 'h': if(suffix == "png"){ return "texture"; } break;
+ case 'n': if(suffix == "phxbn"){ return "skeleton"; } break;
+ } break;
+ case 't': switch(suffix[1]){
+ case 'g': if(suffix == "tga"){ return "texture"; } break;
+ case 't': if(suffix == "ttf"){ return "font"; } break;
+ } break;
+ case 'w': if(suffix == "wav"){ return "sound"; } break;
+ case 'x': if(suffix == "xml"){ return GetTypeFromXML(path); } break;
+ }
+ FatalError("Error", "File suffix unknown: %s", suffix.c_str());
+ return "";
+}
+
+#define LOAD_SYNC_RETURN_PATHS(string_type,type) \
+ if(path == string_type) {\
+ AssetRef<type> a = Engine::Instance()->GetAssetManager()->LoadSync<type>(path);\
+ if(a.valid()){\
+ a->ReturnPaths(path_set);\
+ } else {\
+ LOGE << "Failed retrieving paths from " #type << path << std::endl;\
+ }\
+ }\
+
+void ReturnPathUtil::ReturnPathsFromPath( const std::string &path, PathSet &path_set ){
+ std::string type = GetTypeFromFilePath(path);
+ if(path_set.find(type + " " + path) != path_set.end()){
+ return;
+ }
+ path_set.insert(type + " " + path);
+ switch(type[0]) {
+ case 'a':
+ switch(type[1]) {
+ case 'c': LOAD_SYNC_RETURN_PATHS("actor",ActorFile); break;
+ case 'm': LOAD_SYNC_RETURN_PATHS("ambientsound",AmbientSound); break;
+ case 't': LOAD_SYNC_RETURN_PATHS("attack",Attack); break;
+ case 'n': LOAD_SYNC_RETURN_PATHS("animation",Animation); break;
+ default: LOGE << "Found no match for the string " << type << std::endl; break;
+ }
+ break;
+ case 'c': LOAD_SYNC_RETURN_PATHS("character",Character); break;
+ case 'd': LOAD_SYNC_RETURN_PATHS("decal",DecalFile); break;
+ case 'e': LOAD_SYNC_RETURN_PATHS("envobject",ObjectFile); break;
+ case 'h': LOAD_SYNC_RETURN_PATHS("hotspot",HotspotFile); break;
+ case 'i': LOAD_SYNC_RETURN_PATHS("item",Item); break;
+ case 'm': LOAD_SYNC_RETURN_PATHS("material",Material); break;
+ case 'p': if(type == "particle") { } break; // TODO: Implement this
+ case 'r':
+ switch(type[1]){
+ case 'e': LOAD_SYNC_RETURN_PATHS("reaction",Reaction); break;
+ case 'i': if(type == "rig"){} break; // TODO: Implement this
+ default: LOGE << "Found no match for the string " << type << std::endl; break;
+ }
+ break;
+ case 's':
+ switch(type[1]){
+ case 'a': if(type == "saved"){} break;
+ case 'o':
+ switch(type[2]){
+ case 'n': if(type == "songlist"){} break; // TODO: Implement this
+ case 'u': LOAD_SYNC_RETURN_PATHS("soundgroup", SoundGroupInfo); break;
+ default: LOGE << "Found no match for the string " << type << std::endl; break;
+ }
+ break;
+ case 'y': LOAD_SYNC_RETURN_PATHS("synced_animation",SyncedAnimationGroup); break;
+ default: LOGE << "Found no match for the string " << type << std::endl; break;
+ }
+ break;
+ case 't': if(type == "texture"){} break;
+ default: LOGE << "Found no match for the string " << type << std::endl; break;
+ }
+}
diff --git a/Source/Internal/returnpathutil.h b/Source/Internal/returnpathutil.h
new file mode 100644
index 00000000..e453f6d0
--- /dev/null
+++ b/Source/Internal/returnpathutil.h
@@ -0,0 +1,30 @@
+//-----------------------------------------------------------------------------
+// Name: returnpathutil.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assets.h>
+
+namespace ReturnPathUtil {
+ std::string GetTypeFromFilePath(const std::string &file_path);
+ void ReturnPathsFromPath( const std::string &path, PathSet &path_set );
+}
diff --git a/Source/Internal/scoped_buffer.h b/Source/Internal/scoped_buffer.h
new file mode 100644
index 00000000..ee7d7589
--- /dev/null
+++ b/Source/Internal/scoped_buffer.h
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------------
+// Name: scoped_buffer.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Memory/allocation.h>
+#include <Internal/error.h>
+
+struct ScopedBuffer {
+ void *ptr;
+
+ ScopedBuffer():ptr(NULL){}
+ ScopedBuffer( int size ) {
+ ptr = (void*)OG_MALLOC(size);
+ if(!ptr){
+ FatalError("Error", "Could not allocate memory");
+ }
+ }
+
+ ~ScopedBuffer(){
+ OG_FREE(ptr);
+ }
+};
diff --git a/Source/Internal/snprintf.c b/Source/Internal/snprintf.c
new file mode 100644
index 00000000..3eea2cc5
--- /dev/null
+++ b/Source/Internal/snprintf.c
@@ -0,0 +1,1028 @@
+/*
+ * snprintf.c - a portable implementation of snprintf
+ *
+ * AUTHOR
+ * Mark Martinec <mark.martinec@ijs.si>, April 1999.
+ *
+ * Copyright 1999, Mark Martinec. All rights reserved.
+ *
+ * TERMS AND CONDITIONS
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the "Frontier Artistic License" which comes
+ * with this Kit.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the Frontier Artistic License for more details.
+ *
+ * You should have received a copy of the Frontier Artistic License
+ * with this Kit in the file named LICENSE.txt .
+ * If not, I'll be glad to provide one.
+ *
+ * FEATURES
+ * - careful adherence to specs regarding flags, field width and precision;
+ * - good performance for large string handling (large format, large
+ * argument or large paddings). Performance is similar to system's sprintf
+ * and in several cases significantly better (make sure you compile with
+ * optimizations turned on, tell the compiler the code is strict ANSI
+ * if necessary to give it more freedom for optimizations);
+ * - return value semantics per ISO/IEC 9899:1999 ("ISO C99");
+ * - written in standard ISO/ANSI C - requires an ANSI C compiler.
+ *
+ * SUPPORTED CONVERSION SPECIFIERS AND DATA TYPES
+ *
+ * This snprintf only supports the following conversion specifiers:
+ * s, c, d, u, o, x, X, p (and synonyms: i, D, U, O - see below)
+ * with flags: '-', '+', ' ', '0' and '#'.
+ * An asterisk is supported for field width as well as precision.
+ *
+ * Length modifiers 'h' (short int), 'l' (long int),
+ * and 'll' (long long int) are supported.
+ * NOTE:
+ * If macro SNPRINTF_LONGLONG_SUPPORT is not defined (default) the
+ * length modifier 'll' is recognized but treated the same as 'l',
+ * which may cause argument value truncation! Defining
+ * SNPRINTF_LONGLONG_SUPPORT requires that your system's sprintf also
+ * handles length modifier 'll'. long long int is a language extension
+ * which may not be portable.
+ *
+ * Conversion of numeric data (conversion specifiers d, u, o, x, X, p)
+ * with length modifiers (none or h, l, ll) is left to the system routine
+ * sprintf, but all handling of flags, field width and precision as well as
+ * c and s conversions is done very carefully by this portable routine.
+ * If a string precision (truncation) is specified (e.g. %.8s) it is
+ * guaranteed the string beyond the specified precision will not be referenced.
+ *
+ * Length modifiers h, l and ll are ignored for c and s conversions (data
+ * types wint_t and wchar_t are not supported).
+ *
+ * The following common synonyms for conversion characters are supported:
+ * - i is a synonym for d
+ * - D is a synonym for ld, explicit length modifiers are ignored
+ * - U is a synonym for lu, explicit length modifiers are ignored
+ * - O is a synonym for lo, explicit length modifiers are ignored
+ * The D, O and U conversion characters are nonstandard, they are supported
+ * for backward compatibility only, and should not be used for new code.
+ *
+ * The following is specifically NOT supported:
+ * - flag ' (thousands' grouping character) is recognized but ignored
+ * - numeric conversion specifiers: f, e, E, g, G and synonym F,
+ * as well as the new a and A conversion specifiers
+ * - length modifier 'L' (long double) and 'q' (quad - use 'll' instead)
+ * - wide character/string conversions: lc, ls, and nonstandard
+ * synonyms C and S
+ * - writeback of converted string length: conversion character n
+ * - the n$ specification for direct reference to n-th argument
+ * - locales
+ *
+ * It is permitted for str_m to be zero, and it is permitted to specify NULL
+ * pointer for resulting string argument if str_m is zero (as per ISO C99).
+ *
+ * The return value is the number of characters which would be generated
+ * for the given input, excluding the trailing null. If this value
+ * is greater or equal to str_m, not all characters from the result
+ * have been stored in str, output bytes beyond the (str_m-1) -th character
+ * are discarded. If str_m is greater than zero it is guaranteed
+ * the resulting string will be null-terminated.
+ *
+ * NOTE that this matches the ISO C99, OpenBSD, and GNU C library 2.1,
+ * but is different from some older and vendor implementations,
+ * and is also different from XPG, XSH5, SUSv2 specifications.
+ * For historical discussion on changes in the semantics and standards
+ * of snprintf see printf(3) man page in the Linux programmers manual.
+ *
+ * Routines asprintf and vasprintf return a pointer (in the ptr argument)
+ * to a buffer sufficiently large to hold the resulting string. This pointer
+ * should be passed to free(3) to release the allocated storage when it is
+ * no longer needed. If sufficient space cannot be allocated, these functions
+ * will return -1 and set ptr to be a NULL pointer. These two routines are a
+ * GNU C library extensions (glibc).
+ *
+ * Routines asnprintf and vasnprintf are similar to asprintf and vasprintf,
+ * yet, like snprintf and vsnprintf counterparts, will write at most str_m-1
+ * characters into the allocated output string, the last character in the
+ * allocated buffer then gets the terminating null. If the formatted string
+ * length (the return value) is greater than or equal to the str_m argument,
+ * the resulting string was truncated and some of the formatted characters
+ * were discarded. These routines present a handy way to limit the amount
+ * of allocated memory to some sane value.
+ *
+ * AVAILABILITY
+ * http://www.ijs.si/software/snprintf/
+ *
+ * REVISION HISTORY
+ * 1999-04 V0.9 Mark Martinec
+ * - initial version, some modifications after comparing printf
+ * man pages for Digital Unix 4.0, Solaris 2.6 and HPUX 10,
+ * and checking how Perl handles sprintf (differently!);
+ * 1999-04-09 V1.0 Mark Martinec <mark.martinec@ijs.si>
+ * - added main test program, fixed remaining inconsistencies,
+ * added optional (long long int) support;
+ * 1999-04-12 V1.1 Mark Martinec <mark.martinec@ijs.si>
+ * - support the 'p' conversion (pointer to void);
+ * - if a string precision is specified
+ * make sure the string beyond the specified precision
+ * will not be referenced (e.g. by strlen);
+ * 1999-04-13 V1.2 Mark Martinec <mark.martinec@ijs.si>
+ * - support synonyms %D=%ld, %U=%lu, %O=%lo;
+ * - speed up the case of long format string with few conversions;
+ * 1999-06-30 V1.3 Mark Martinec <mark.martinec@ijs.si>
+ * - fixed runaway loop (eventually crashing when str_l wraps
+ * beyond 2^31) while copying format string without
+ * conversion specifiers to a buffer that is too short
+ * (thanks to Edwin Young <edwiny@autonomy.com> for
+ * spotting the problem);
+ * - added macros PORTABLE_SNPRINTF_VERSION_(MAJOR|MINOR)
+ * to snprintf.h
+ * 2000-02-14 V2.0 (never released) Mark Martinec <mark.martinec@ijs.si>
+ * - relaxed license terms: The Artistic License now applies.
+ * You may still apply the GNU GENERAL PUBLIC LICENSE
+ * as was distributed with previous versions, if you prefer;
+ * - changed REVISION HISTORY dates to use ISO 8601 date format;
+ * - added vsnprintf (patch also independently proposed by
+ * Caolan McNamara 2000-05-04, and Keith M Willenson 2000-06-01)
+ * 2000-06-27 V2.1 Mark Martinec <mark.martinec@ijs.si>
+ * - removed POSIX check for str_m<1; value 0 for str_m is
+ * allowed by ISO C99 (and GNU C library 2.1) - (pointed out
+ * on 2000-05-04 by Caolan McNamara, caolan@ csn dot ul dot ie).
+ * Besides relaxed license this change in standards adherence
+ * is the main reason to bump up the major version number;
+ * - added nonstandard routines asnprintf, vasnprintf, asprintf,
+ * vasprintf that dynamically allocate storage for the
+ * resulting string; these routines are not compiled by default,
+ * see comments where NEED_V?ASN?PRINTF macros are defined;
+ * - autoconf contributed by Caolan McNamara
+ * 2000-10-06 V2.2 Mark Martinec <mark.martinec@ijs.si>
+ * - BUG FIX: the %c conversion used a temporary variable
+ * that was no longer in scope when referenced,
+ * possibly causing incorrect resulting character;
+ * - BUG FIX: make precision and minimal field width unsigned
+ * to handle huge values (2^31 <= n < 2^32) correctly;
+ * also be more careful in the use of signed/unsigned/size_t
+ * internal variables - probably more careful than many
+ * vendor implementations, but there may still be a case
+ * where huge values of str_m, precision or minimal field
+ * could cause incorrect behaviour;
+ * - use separate variables for signed/unsigned arguments,
+ * and for short/int, long, and long long argument lengths
+ * to avoid possible incompatibilities on certain
+ * computer architectures. Also use separate variable
+ * arg_sign to hold sign of a numeric argument,
+ * to make code more transparent;
+ * - some fiddling with zero padding and "0x" to make it
+ * Linux compatible;
+ * - systematically use macros fast_memcpy and fast_memset
+ * instead of case-by-case hand optimization; determine some
+ * breakeven string lengths for different architectures;
+ * - terminology change: 'format' -> 'conversion specifier',
+ * 'C9x' -> 'ISO/IEC 9899:1999 ("ISO C99")',
+ * 'alternative form' -> 'alternate form',
+ * 'data type modifier' -> 'length modifier';
+ * - several comments rephrased and new ones added;
+ * - make compiler not complain about 'credits' defined but
+ * not used;
+ */
+
+
+/* Define HAVE_SNPRINTF if your system already has snprintf and vsnprintf.
+ *
+ * If HAVE_SNPRINTF is defined this module will not produce code for
+ * snprintf and vsnprintf, unless PREFER_PORTABLE_SNPRINTF is defined as well,
+ * causing this portable version of snprintf to be called portable_snprintf
+ * (and portable_vsnprintf).
+ */
+/* #define HAVE_SNPRINTF */
+
+/* Define PREFER_PORTABLE_SNPRINTF if your system does have snprintf and
+ * vsnprintf but you would prefer to use the portable routine(s) instead.
+ * In this case the portable routine is declared as portable_snprintf
+ * (and portable_vsnprintf) and a macro 'snprintf' (and 'vsnprintf')
+ * is defined to expand to 'portable_v?snprintf' - see file snprintf.h .
+ * Defining this macro is only useful if HAVE_SNPRINTF is also defined,
+ * but does does no harm if defined nevertheless.
+ */
+/* #define PREFER_PORTABLE_SNPRINTF */
+
+/* Define SNPRINTF_LONGLONG_SUPPORT if you want to support
+ * data type (long long int) and length modifier 'll' (e.g. %lld).
+ * If undefined, 'll' is recognized but treated as a single 'l'.
+ *
+ * If the system's sprintf does not handle 'll'
+ * the SNPRINTF_LONGLONG_SUPPORT must not be defined!
+ *
+ * This is off by default as (long long int) is a language extension.
+ */
+/* #define SNPRINTF_LONGLONG_SUPPORT */
+
+/* Define NEED_SNPRINTF_ONLY if you only need snprintf, and not vsnprintf.
+ * If NEED_SNPRINTF_ONLY is defined, the snprintf will be defined directly,
+ * otherwise both snprintf and vsnprintf routines will be defined
+ * and snprintf will be a simple wrapper around vsnprintf, at the expense
+ * of an extra procedure call.
+ */
+/* #define NEED_SNPRINTF_ONLY */
+
+/* Define NEED_V?ASN?PRINTF macros if you need library extension
+ * routines asprintf, vasprintf, asnprintf, vasnprintf respectively,
+ * and your system library does not provide them. They are all small
+ * wrapper routines around portable_vsnprintf. Defining any of the four
+ * NEED_V?ASN?PRINTF macros automatically turns off NEED_SNPRINTF_ONLY
+ * and turns on PREFER_PORTABLE_SNPRINTF.
+ *
+ * Watch for name conflicts with the system library if these routines
+ * are already present there.
+ *
+ * NOTE: vasprintf and vasnprintf routines need va_copy() from stdarg.h, as
+ * specified by C99, to be able to traverse the same list of arguments twice.
+ * I don't know of any other standard and portable way of achieving the same.
+ * With some versions of gcc you may use __va_copy(). You might even get away
+ * with "ap2 = ap", in this case you must not call va_end(ap2) !
+ * #define va_copy(ap2,ap) ap2 = ap
+ */
+/* #define NEED_ASPRINTF */
+/* #define NEED_ASNPRINTF */
+/* #define NEED_VASPRINTF */
+/* #define NEED_VASNPRINTF */
+
+
+/* Define the following macros if desired:
+ * SOLARIS_COMPATIBLE, SOLARIS_BUG_COMPATIBLE,
+ * HPUX_COMPATIBLE, HPUX_BUG_COMPATIBLE, LINUX_COMPATIBLE,
+ * DIGITAL_UNIX_COMPATIBLE, DIGITAL_UNIX_BUG_COMPATIBLE,
+ * PERL_COMPATIBLE, PERL_BUG_COMPATIBLE,
+ *
+ * - For portable applications it is best not to rely on peculiarities
+ * of a given implementation so it may be best not to define any
+ * of the macros that select compatibility and to avoid features
+ * that vary among the systems.
+ *
+ * - Selecting compatibility with more than one operating system
+ * is not strictly forbidden but is not recommended.
+ *
+ * - 'x'_BUG_COMPATIBLE implies 'x'_COMPATIBLE .
+ *
+ * - 'x'_COMPATIBLE refers to (and enables) a behaviour that is
+ * documented in a sprintf man page on a given operating system
+ * and actually adhered to by the system's sprintf (but not on
+ * most other operating systems). It may also refer to and enable
+ * a behaviour that is declared 'undefined' or 'implementation specific'
+ * in the man page but a given implementation behaves predictably
+ * in a certain way.
+ *
+ * - 'x'_BUG_COMPATIBLE refers to (and enables) a behaviour of system's sprintf
+ * that contradicts the sprintf man page on the same operating system.
+ *
+ * - I do not claim that the 'x'_COMPATIBLE and 'x'_BUG_COMPATIBLE
+ * conditionals take into account all idiosyncrasies of a particular
+ * implementation, there may be other incompatibilities.
+ */
+
+
+
+/* ============================================= */
+/* NO USER SERVICABLE PARTS FOLLOWING THIS POINT */
+/* ============================================= */
+#ifndef __GNU_LIBRARY__
+
+#define PORTABLE_SNPRINTF_VERSION_MAJOR 2
+#define PORTABLE_SNPRINTF_VERSION_MINOR 2
+
+#if defined(NEED_ASPRINTF) || defined(NEED_ASNPRINTF) || defined(NEED_VASPRINTF) || defined(NEED_VASNPRINTF)
+# if defined(NEED_SNPRINTF_ONLY)
+# undef NEED_SNPRINTF_ONLY
+# endif
+# if !defined(PREFER_PORTABLE_SNPRINTF)
+# define PREFER_PORTABLE_SNPRINTF
+# endif
+#endif
+
+#if defined(SOLARIS_BUG_COMPATIBLE) && !defined(SOLARIS_COMPATIBLE)
+#define SOLARIS_COMPATIBLE
+#endif
+
+#if defined(HPUX_BUG_COMPATIBLE) && !defined(HPUX_COMPATIBLE)
+#define HPUX_COMPATIBLE
+#endif
+
+#if defined(DIGITAL_UNIX_BUG_COMPATIBLE) && !defined(DIGITAL_UNIX_COMPATIBLE)
+#define DIGITAL_UNIX_COMPATIBLE
+#endif
+
+#if defined(PERL_BUG_COMPATIBLE) && !defined(PERL_COMPATIBLE)
+#define PERL_COMPATIBLE
+#endif
+
+#if defined(LINUX_BUG_COMPATIBLE) && !defined(LINUX_COMPATIBLE)
+#define LINUX_COMPATIBLE
+#endif
+
+#include <sys/types.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <errno.h>
+
+
+#ifdef isdigit
+#undef isdigit
+#endif
+#define isdigit(c) ((c) >= '0' && (c) <= '9')
+
+/* For copying strings longer or equal to 'breakeven_point'
+ * it is more efficient to call memcpy() than to do it inline.
+ * The value depends mostly on the processor architecture,
+ * but also on the compiler and its optimization capabilities.
+ * The value is not critical, some small value greater than zero
+ * will be just fine if you don't care to squeeze every drop
+ * of performance out of the code.
+ *
+ * Small values favor memcpy, large values favor inline code.
+ */
+#if defined(__alpha__) || defined(__alpha)
+# define breakeven_point 2 /* AXP (DEC Alpha) - gcc or cc or egcs */
+#endif
+#if defined(__i386__) || defined(__i386)
+# define breakeven_point 12 /* Intel Pentium/Linux - gcc 2.96 */
+#endif
+#if defined(__hppa)
+# define breakeven_point 10 /* HP-PA - gcc */
+#endif
+#if defined(__sparc__) || defined(__sparc)
+# define breakeven_point 33 /* Sun Sparc 5 - gcc 2.8.1 */
+#endif
+
+/* some other values of possible interest: */
+/* #define breakeven_point 8 */ /* VAX 4000 - vaxc */
+/* #define breakeven_point 19 */ /* VAX 4000 - gcc 2.7.0 */
+
+#ifndef breakeven_point
+# define breakeven_point 6 /* some reasonable one-size-fits-all value */
+#endif
+
+#define fast_memcpy(d,s,n) \
+ { register size_t nn = (size_t)(n); \
+ if (nn >= breakeven_point) memcpy((d), (s), nn); \
+ else if (nn > 0) { /* proc call overhead is worth only for large strings*/\
+ register char *dd; register const char *ss; \
+ for (ss=(s), dd=(d); nn>0; nn--) *dd++ = *ss++; } }
+
+#define fast_memset(d,c,n) \
+ { register size_t nn = (size_t)(n); \
+ if (nn >= breakeven_point) memset((d), (int)(c), nn); \
+ else if (nn > 0) { /* proc call overhead is worth only for large strings*/\
+ register char *dd; register const int cc=(int)(c); \
+ for (dd=(d); nn>0; nn--) *dd++ = cc; } }
+
+/* prototypes */
+
+#if defined(NEED_ASPRINTF)
+int asprintf (char **ptr, const char *fmt, /*args*/ ...);
+#endif
+#if defined(NEED_VASPRINTF)
+int vasprintf (char **ptr, const char *fmt, va_list ap);
+#endif
+#if defined(NEED_ASNPRINTF)
+int asnprintf (char **ptr, size_t str_m, const char *fmt, /*args*/ ...);
+#endif
+#if defined(NEED_VASNPRINTF)
+int vasnprintf (char **ptr, size_t str_m, const char *fmt, va_list ap);
+#endif
+
+#if defined(HAVE_SNPRINTF)
+/* declare our portable snprintf routine under name portable_snprintf */
+/* declare our portable vsnprintf routine under name portable_vsnprintf */
+#else
+/* declare our portable routines under names snprintf and vsnprintf */
+#define portable_snprintf snprintf
+#if !defined(NEED_SNPRINTF_ONLY)
+#define portable_vsnprintf vsnprintf
+#endif
+#endif
+
+#if !defined(HAVE_SNPRINTF) || defined(PREFER_PORTABLE_SNPRINTF)
+int portable_snprintf(char *str, size_t str_m, const char *fmt, /*args*/ ...);
+#if !defined(NEED_SNPRINTF_ONLY)
+int portable_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap);
+#endif
+#endif
+
+/* declarations */
+
+static char credits[] = "\n\
+@(#)snprintf.c, v2.2: Mark Martinec, <mark.martinec@ijs.si>\n\
+@(#)snprintf.c, v2.2: Copyright 1999, Mark Martinec. Frontier Artistic License applies.\n\
+@(#)snprintf.c, v2.2: http://www.ijs.si/software/snprintf/\n";
+
+#if defined(NEED_ASPRINTF)
+int asprintf(char **ptr, const char *fmt, /*args*/ ...) {
+ va_list ap;
+ size_t str_m;
+ int str_l;
+
+ *ptr = NULL;
+ va_start(ap, fmt); /* measure the required size */
+ str_l = portable_vsnprintf(NULL, (size_t)0, fmt, ap);
+ va_end(ap);
+ assert(str_l >= 0); /* possible integer overflow if str_m > INT_MAX */
+ *ptr = (char *) malloc(str_m = (size_t)str_l + 1);
+ if (*ptr == NULL) { errno = ENOMEM; str_l = -1; }
+ else {
+ int str_l2;
+ va_start(ap, fmt);
+ str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap);
+ va_end(ap);
+ assert(str_l2 == str_l);
+ }
+ return str_l;
+}
+#endif
+
+#if defined(NEED_VASPRINTF)
+int vasprintf(char **ptr, const char *fmt, va_list ap) {
+ size_t str_m;
+ int str_l;
+
+ *ptr = NULL;
+ { va_list ap2;
+ va_copy(ap2, ap); /* don't consume the original ap, we'll need it again */
+ str_l = portable_vsnprintf(NULL, (size_t)0, fmt, ap2);/*get required size*/
+ va_end(ap2);
+ }
+ assert(str_l >= 0); /* possible integer overflow if str_m > INT_MAX */
+ *ptr = (char *) malloc(str_m = (size_t)str_l + 1);
+ if (*ptr == NULL) { errno = ENOMEM; str_l = -1; }
+ else {
+ int str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap);
+ assert(str_l2 == str_l);
+ }
+ return str_l;
+}
+#endif
+
+#if defined(NEED_ASNPRINTF)
+int asnprintf (char **ptr, size_t str_m, const char *fmt, /*args*/ ...) {
+ va_list ap;
+ int str_l;
+
+ *ptr = NULL;
+ va_start(ap, fmt); /* measure the required size */
+ str_l = portable_vsnprintf(NULL, (size_t)0, fmt, ap);
+ va_end(ap);
+ assert(str_l >= 0); /* possible integer overflow if str_m > INT_MAX */
+ if ((size_t)str_l + 1 < str_m) str_m = (size_t)str_l + 1; /* truncate */
+ /* if str_m is 0, no buffer is allocated, just set *ptr to NULL */
+ if (str_m == 0) { /* not interested in resulting string, just return size */
+ } else {
+ *ptr = (char *) malloc(str_m);
+ if (*ptr == NULL) { errno = ENOMEM; str_l = -1; }
+ else {
+ int str_l2;
+ va_start(ap, fmt);
+ str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap);
+ va_end(ap);
+ assert(str_l2 == str_l);
+ }
+ }
+ return str_l;
+}
+#endif
+
+#if defined(NEED_VASNPRINTF)
+int vasnprintf (char **ptr, size_t str_m, const char *fmt, va_list ap) {
+ int str_l;
+
+ *ptr = NULL;
+ { va_list ap2;
+ va_copy(ap2, ap); /* don't consume the original ap, we'll need it again */
+ str_l = portable_vsnprintf(NULL, (size_t)0, fmt, ap2);/*get required size*/
+ va_end(ap2);
+ }
+ assert(str_l >= 0); /* possible integer overflow if str_m > INT_MAX */
+ if ((size_t)str_l + 1 < str_m) str_m = (size_t)str_l + 1; /* truncate */
+ /* if str_m is 0, no buffer is allocated, just set *ptr to NULL */
+ if (str_m == 0) { /* not interested in resulting string, just return size */
+ } else {
+ *ptr = (char *) malloc(str_m);
+ if (*ptr == NULL) { errno = ENOMEM; str_l = -1; }
+ else {
+ int str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap);
+ assert(str_l2 == str_l);
+ }
+ }
+ return str_l;
+}
+#endif
+
+/*
+ * If the system does have snprintf and the portable routine is not
+ * specifically required, this module produces no code for snprintf/vsnprintf.
+ */
+#if !defined(HAVE_SNPRINTF) || defined(PREFER_PORTABLE_SNPRINTF)
+
+#if !defined(NEED_SNPRINTF_ONLY)
+int portable_snprintf(char *str, size_t str_m, const char *fmt, /*args*/ ...) {
+ va_list ap;
+ int str_l;
+
+ va_start(ap, fmt);
+ str_l = portable_vsnprintf(str, str_m, fmt, ap);
+ va_end(ap);
+ return str_l;
+}
+#endif
+
+#if defined(NEED_SNPRINTF_ONLY)
+int portable_snprintf(char *str, size_t str_m, const char *fmt, /*args*/ ...) {
+#else
+int portable_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap) {
+#endif
+
+#if defined(NEED_SNPRINTF_ONLY)
+ va_list ap;
+#endif
+ size_t str_l = 0;
+ const char *p = fmt;
+
+/* In contrast with POSIX, the ISO C99 now says
+ * that str can be NULL and str_m can be 0.
+ * This is more useful than the old: if (str_m < 1) return -1; */
+
+#if defined(NEED_SNPRINTF_ONLY)
+ va_start(ap, fmt);
+#endif
+ if (!p) p = "";
+ while (*p) {
+ if (*p != '%') {
+ /* if (str_l < str_m) str[str_l++] = *p++; -- this would be sufficient */
+ /* but the following code achieves better performance for cases
+ * where format string is long and contains few conversions */
+ const char *q = strchr(p+1,'%');
+ size_t n = !q ? strlen(p) : (q-p);
+ if (str_l < str_m) {
+ size_t avail = str_m-str_l;
+ fast_memcpy(str+str_l, p, (n>avail?avail:n));
+ }
+ p += n; str_l += n;
+ } else {
+ const char *starting_p;
+ size_t min_field_width = 0, precision = 0;
+ int zero_padding = 0, precision_specified = 0, justify_left = 0;
+ int alternate_form = 0, force_sign = 0;
+ int space_for_positive = 1; /* If both the ' ' and '+' flags appear,
+ the ' ' flag should be ignored. */
+ char length_modifier = '\0'; /* allowed values: \0, h, l, L */
+ char tmp[32];/* temporary buffer for simple numeric->string conversion */
+
+ const char *str_arg; /* string address in case of string argument */
+ size_t str_arg_l; /* natural field width of arg without padding
+ and sign */
+ unsigned char uchar_arg;
+ /* unsigned char argument value - only defined for c conversion.
+ N.B. standard explicitly states the char argument for
+ the c conversion is unsigned */
+
+ size_t number_of_zeros_to_pad = 0;
+ /* number of zeros to be inserted for numeric conversions
+ as required by the precision or minimal field width */
+
+ size_t zero_padding_insertion_ind = 0;
+ /* index into tmp where zero padding is to be inserted */
+
+ char fmt_spec = '\0';
+ /* current conversion specifier character */
+
+ str_arg = credits;/* just to make compiler happy (defined but not used)*/
+ str_arg = NULL;
+ starting_p = p; p++; /* skip '%' */
+ /* parse flags */
+ while (*p == '0' || *p == '-' || *p == '+' ||
+ *p == ' ' || *p == '#' || *p == '\'') {
+ switch (*p) {
+ case '0': zero_padding = 1; break;
+ case '-': justify_left = 1; break;
+ case '+': force_sign = 1; space_for_positive = 0; break;
+ case ' ': force_sign = 1;
+ /* If both the ' ' and '+' flags appear, the ' ' flag should be ignored */
+#ifdef PERL_COMPATIBLE
+ /* ... but in Perl the last of ' ' and '+' applies */
+ space_for_positive = 1;
+#endif
+ break;
+ case '#': alternate_form = 1; break;
+ case '\'': break;
+ }
+ p++;
+ }
+ /* If the '0' and '-' flags both appear, the '0' flag should be ignored. */
+
+ /* parse field width */
+ if (*p == '*') {
+ int j;
+ p++; j = va_arg(ap, int);
+ if (j >= 0) min_field_width = j;
+ else { min_field_width = -j; justify_left = 1; }
+ } else if (isdigit((int)(*p))) {
+ /* size_t could be wider than unsigned int;
+ make sure we treat argument like common implementations do */
+ unsigned int uj = *p++ - '0';
+ while (isdigit((int)(*p))) uj = 10*uj + (unsigned int)(*p++ - '0');
+ min_field_width = uj;
+ }
+ /* parse precision */
+ if (*p == '.') {
+ p++; precision_specified = 1;
+ if (*p == '*') {
+ int j = va_arg(ap, int);
+ p++;
+ if (j >= 0) precision = j;
+ else {
+ precision_specified = 0; precision = 0;
+ /* NOTE:
+ * Solaris 2.6 man page claims that in this case the precision
+ * should be set to 0. Digital Unix 4.0, HPUX 10 and BSD man page
+ * claim that this case should be treated as unspecified precision,
+ * which is what we do here.
+ */
+ }
+ } else if (isdigit((int)(*p))) {
+ /* size_t could be wider than unsigned int;
+ make sure we treat argument like common implementations do */
+ unsigned int uj = *p++ - '0';
+ while (isdigit((int)(*p))) uj = 10*uj + (unsigned int)(*p++ - '0');
+ precision = uj;
+ }
+ }
+ /* parse 'h', 'l' and 'll' length modifiers */
+ if (*p == 'h' || *p == 'l') {
+ length_modifier = *p; p++;
+ if (length_modifier == 'l' && *p == 'l') { /* double l = long long */
+#ifdef SNPRINTF_LONGLONG_SUPPORT
+ length_modifier = '2'; /* double l encoded as '2' */
+#else
+ length_modifier = 'l'; /* treat it as a single 'l' */
+#endif
+ p++;
+ }
+ }
+ fmt_spec = *p;
+ /* common synonyms: */
+ switch (fmt_spec) {
+ case 'i': fmt_spec = 'd'; break;
+ case 'D': fmt_spec = 'd'; length_modifier = 'l'; break;
+ case 'U': fmt_spec = 'u'; length_modifier = 'l'; break;
+ case 'O': fmt_spec = 'o'; length_modifier = 'l'; break;
+ default: break;
+ }
+ /* get parameter value, do initial processing */
+ switch (fmt_spec) {
+ case '%': /* % behaves similar to 's' regarding flags and field widths */
+ case 'c': /* c behaves similar to 's' regarding flags and field widths */
+ case 's':
+ length_modifier = '\0'; /* wint_t and wchar_t not supported */
+ /* the result of zero padding flag with non-numeric conversion specifier*/
+ /* is undefined. Solaris and HPUX 10 does zero padding in this case, */
+ /* Digital Unix and Linux does not. */
+#if !defined(SOLARIS_COMPATIBLE) && !defined(HPUX_COMPATIBLE)
+ zero_padding = 0; /* turn zero padding off for string conversions */
+#endif
+ str_arg_l = 1;
+ switch (fmt_spec) {
+ case '%':
+ str_arg = p; break;
+ case 'c': {
+ int j = va_arg(ap, int);
+ uchar_arg = (unsigned char) j; /* standard demands unsigned char */
+ str_arg = (const char *) &uchar_arg;
+ break;
+ }
+ case 's':
+ str_arg = va_arg(ap, const char *);
+ if (!str_arg) str_arg_l = 0;
+ /* make sure not to address string beyond the specified precision !!! */
+ else if (!precision_specified) str_arg_l = strlen(str_arg);
+ /* truncate string if necessary as requested by precision */
+ else if (precision == 0) str_arg_l = 0;
+ else {
+ /* memchr on HP does not like n > 2^31 !!! */
+ const char *q = (const char*)memchr(str_arg, '\0',
+ precision <= 0x7fffffff ? precision : 0x7fffffff);
+ str_arg_l = !q ? precision : (q-str_arg);
+ }
+ break;
+ default: break;
+ }
+ break;
+ case 'd': case 'u': case 'o': case 'x': case 'X': case 'p': {
+ /* NOTE: the u, o, x, X and p conversion specifiers imply
+ the value is unsigned; d implies a signed value */
+
+ int arg_sign = 0;
+ /* 0 if numeric argument is zero (or if pointer is NULL for 'p'),
+ +1 if greater than zero (or nonzero for unsigned arguments),
+ -1 if negative (unsigned argument is never negative) */
+
+ int int_arg = 0; unsigned int uint_arg = 0;
+ /* only defined for length modifier h, or for no length modifiers */
+
+ long int long_arg = 0; unsigned long int ulong_arg = 0;
+ /* only defined for length modifier l */
+
+ void *ptr_arg = NULL;
+ /* pointer argument value -only defined for p conversion */
+
+#ifdef SNPRINTF_LONGLONG_SUPPORT
+ long long int long_long_arg = 0;
+ unsigned long long int ulong_long_arg = 0;
+ /* only defined for length modifier ll */
+#endif
+ if (fmt_spec == 'p') {
+ /* HPUX 10: An l, h, ll or L before any other conversion character
+ * (other than d, i, u, o, x, or X) is ignored.
+ * Digital Unix:
+ * not specified, but seems to behave as HPUX does.
+ * Solaris: If an h, l, or L appears before any other conversion
+ * specifier (other than d, i, u, o, x, or X), the behavior
+ * is undefined. (Actually %hp converts only 16-bits of address
+ * and %llp treats address as 64-bit data which is incompatible
+ * with (void *) argument on a 32-bit system).
+ */
+#ifdef SOLARIS_COMPATIBLE
+# ifdef SOLARIS_BUG_COMPATIBLE
+ /* keep length modifiers even if it represents 'll' */
+# else
+ if (length_modifier == '2') length_modifier = '\0';
+# endif
+#else
+ length_modifier = '\0';
+#endif
+ ptr_arg = va_arg(ap, void *);
+ if (ptr_arg != NULL) arg_sign = 1;
+ } else if (fmt_spec == 'd') { /* signed */
+ switch (length_modifier) {
+ case '\0':
+ case 'h':
+ /* It is non-portable to specify a second argument of char or short
+ * to va_arg, because arguments seen by the called function
+ * are not char or short. C converts char and short arguments
+ * to int before passing them to a function.
+ */
+ int_arg = va_arg(ap, int);
+ if (int_arg > 0) arg_sign = 1;
+ else if (int_arg < 0) arg_sign = -1;
+ break;
+ case 'l':
+ long_arg = va_arg(ap, long int);
+ if (long_arg > 0) arg_sign = 1;
+ else if (long_arg < 0) arg_sign = -1;
+ break;
+#ifdef SNPRINTF_LONGLONG_SUPPORT
+ case '2':
+ long_long_arg = va_arg(ap, long long int);
+ if (long_long_arg > 0) arg_sign = 1;
+ else if (long_long_arg < 0) arg_sign = -1;
+ break;
+#endif
+ }
+ } else { /* unsigned */
+ switch (length_modifier) {
+ case '\0':
+ case 'h':
+ uint_arg = va_arg(ap, unsigned int);
+ if (uint_arg) arg_sign = 1;
+ break;
+ case 'l':
+ ulong_arg = va_arg(ap, unsigned long int);
+ if (ulong_arg) arg_sign = 1;
+ break;
+#ifdef SNPRINTF_LONGLONG_SUPPORT
+ case '2':
+ ulong_long_arg = va_arg(ap, unsigned long long int);
+ if (ulong_long_arg) arg_sign = 1;
+ break;
+#endif
+ }
+ }
+ str_arg = tmp; str_arg_l = 0;
+ /* NOTE:
+ * For d, i, u, o, x, and X conversions, if precision is specified,
+ * the '0' flag should be ignored. This is so with Solaris 2.6,
+ * Digital UNIX 4.0, HPUX 10, Linux, FreeBSD, NetBSD; but not with Perl.
+ */
+#ifndef PERL_COMPATIBLE
+ if (precision_specified) zero_padding = 0;
+#endif
+ if (fmt_spec == 'd') {
+ if (force_sign && arg_sign >= 0)
+ tmp[str_arg_l++] = space_for_positive ? ' ' : '+';
+ /* leave negative numbers for sprintf to handle,
+ to avoid handling tricky cases like (short int)(-32768) */
+#ifdef LINUX_COMPATIBLE
+ } else if (fmt_spec == 'p' && force_sign && arg_sign > 0) {
+ tmp[str_arg_l++] = space_for_positive ? ' ' : '+';
+#endif
+ } else if (alternate_form) {
+ if (arg_sign != 0 && (fmt_spec == 'x' || fmt_spec == 'X') )
+ { tmp[str_arg_l++] = '0'; tmp[str_arg_l++] = fmt_spec; }
+ /* alternate form should have no effect for p conversion, but ... */
+#ifdef HPUX_COMPATIBLE
+ else if (fmt_spec == 'p'
+ /* HPUX 10: for an alternate form of p conversion,
+ * a nonzero result is prefixed by 0x. */
+#ifndef HPUX_BUG_COMPATIBLE
+ /* Actually it uses 0x prefix even for a zero value. */
+ && arg_sign != 0
+#endif
+ ) { tmp[str_arg_l++] = '0'; tmp[str_arg_l++] = 'x'; }
+#endif
+ }
+ zero_padding_insertion_ind = str_arg_l;
+ if (!precision_specified) precision = 1; /* default precision is 1 */
+ if (precision == 0 && arg_sign == 0
+#if defined(HPUX_BUG_COMPATIBLE) || defined(LINUX_COMPATIBLE)
+ && fmt_spec != 'p'
+ /* HPUX 10 man page claims: With conversion character p the result of
+ * converting a zero value with a precision of zero is a null string.
+ * Actually HP returns all zeroes, and Linux returns "(nil)". */
+#endif
+ ) {
+ /* converted to null string */
+ /* When zero value is formatted with an explicit precision 0,
+ the resulting formatted string is empty (d, i, u, o, x, X, p). */
+ } else {
+ char f[5]; int f_l = 0;
+ f[f_l++] = '%'; /* construct a simple format string for sprintf */
+ if (!length_modifier) { }
+ else if (length_modifier=='2') { f[f_l++] = 'l'; f[f_l++] = 'l'; }
+ else f[f_l++] = length_modifier;
+ f[f_l++] = fmt_spec; f[f_l++] = '\0';
+ if (fmt_spec == 'p') str_arg_l += sprintf(tmp+str_arg_l, f, ptr_arg);
+ else if (fmt_spec == 'd') { /* signed */
+ switch (length_modifier) {
+ case '\0':
+ case 'h': str_arg_l+=sprintf(tmp+str_arg_l, f, int_arg); break;
+ case 'l': str_arg_l+=sprintf(tmp+str_arg_l, f, long_arg); break;
+#ifdef SNPRINTF_LONGLONG_SUPPORT
+ case '2': str_arg_l+=sprintf(tmp+str_arg_l,f,long_long_arg); break;
+#endif
+ }
+ } else { /* unsigned */
+ switch (length_modifier) {
+ case '\0':
+ case 'h': str_arg_l+=sprintf(tmp+str_arg_l, f, uint_arg); break;
+ case 'l': str_arg_l+=sprintf(tmp+str_arg_l, f, ulong_arg); break;
+#ifdef SNPRINTF_LONGLONG_SUPPORT
+ case '2': str_arg_l+=sprintf(tmp+str_arg_l,f,ulong_long_arg);break;
+#endif
+ }
+ }
+ /* include the optional minus sign and possible "0x"
+ in the region before the zero padding insertion point */
+ if (zero_padding_insertion_ind < str_arg_l &&
+ tmp[zero_padding_insertion_ind] == '-') {
+ zero_padding_insertion_ind++;
+ }
+ if (zero_padding_insertion_ind+1 < str_arg_l &&
+ tmp[zero_padding_insertion_ind] == '0' &&
+ (tmp[zero_padding_insertion_ind+1] == 'x' ||
+ tmp[zero_padding_insertion_ind+1] == 'X') ) {
+ zero_padding_insertion_ind += 2;
+ }
+ }
+ { size_t num_of_digits = str_arg_l - zero_padding_insertion_ind;
+ if (alternate_form && fmt_spec == 'o'
+#ifdef HPUX_COMPATIBLE /* ("%#.o",0) -> "" */
+ && (str_arg_l > 0)
+#endif
+#ifdef DIGITAL_UNIX_BUG_COMPATIBLE /* ("%#o",0) -> "00" */
+#else
+ /* unless zero is already the first character */
+ && !(zero_padding_insertion_ind < str_arg_l
+ && tmp[zero_padding_insertion_ind] == '0')
+#endif
+ ) { /* assure leading zero for alternate-form octal numbers */
+ if (!precision_specified || precision < num_of_digits+1) {
+ /* precision is increased to force the first character to be zero,
+ except if a zero value is formatted with an explicit precision
+ of zero */
+ precision = num_of_digits+1; precision_specified = 1;
+ }
+ }
+ /* zero padding to specified precision? */
+ if (num_of_digits < precision)
+ number_of_zeros_to_pad = precision - num_of_digits;
+ }
+ /* zero padding to specified minimal field width? */
+ if (!justify_left && zero_padding) {
+ int n = min_field_width - (str_arg_l+number_of_zeros_to_pad);
+ if (n > 0) number_of_zeros_to_pad += n;
+ }
+ break;
+ }
+ default: /* unrecognized conversion specifier, keep format string as-is*/
+ zero_padding = 0; /* turn zero padding off for non-numeric convers. */
+#ifndef DIGITAL_UNIX_COMPATIBLE
+ justify_left = 1; min_field_width = 0; /* reset flags */
+#endif
+#if defined(PERL_COMPATIBLE) || defined(LINUX_COMPATIBLE)
+ /* keep the entire format string unchanged */
+ str_arg = starting_p; str_arg_l = p - starting_p;
+ /* well, not exactly so for Linux, which does something inbetween,
+ * and I don't feel an urge to imitate it: "%+++++hy" -> "%+y" */
+#else
+ /* discard the unrecognized conversion, just keep *
+ * the unrecognized conversion character */
+ str_arg = p; str_arg_l = 0;
+#endif
+ if (*p) str_arg_l++; /* include invalid conversion specifier unchanged
+ if not at end-of-string */
+ break;
+ }
+ if (*p) p++; /* step over the just processed conversion specifier */
+ /* insert padding to the left as requested by min_field_width;
+ this does not include the zero padding in case of numerical conversions*/
+ if (!justify_left) { /* left padding with blank or zero */
+ int n = min_field_width - (str_arg_l+number_of_zeros_to_pad);
+ if (n > 0) {
+ if (str_l < str_m) {
+ size_t avail = str_m-str_l;
+ fast_memset(str+str_l, (zero_padding?'0':' '), (n>(int)avail?avail:n));
+ }
+ str_l += n;
+ }
+ }
+ /* zero padding as requested by the precision or by the minimal field width
+ * for numeric conversions required? */
+ if (number_of_zeros_to_pad <= 0) {
+ /* will not copy first part of numeric right now, *
+ * force it to be copied later in its entirety */
+ zero_padding_insertion_ind = 0;
+ } else {
+ /* insert first part of numerics (sign or '0x') before zero padding */
+ int n = zero_padding_insertion_ind;
+ if (n > 0) {
+ if (str_l < str_m) {
+ size_t avail = str_m-str_l;
+ fast_memcpy(str+str_l, str_arg, (n>(int)avail?avail:n));
+ }
+ str_l += n;
+ }
+ /* insert zero padding as requested by the precision or min field width */
+ n = number_of_zeros_to_pad;
+ if (n > 0) {
+ if (str_l < str_m) {
+ size_t avail = str_m-str_l;
+ fast_memset(str+str_l, '0', (n>(int)avail?avail:n));
+ }
+ str_l += n;
+ }
+ }
+ /* insert formatted string
+ * (or as-is conversion specifier for unknown conversions) */
+ { int n = str_arg_l - zero_padding_insertion_ind;
+ if (n > 0) {
+ if (str_l < str_m) {
+ size_t avail = str_m-str_l;
+ fast_memcpy(str+str_l, str_arg+zero_padding_insertion_ind,
+ (n>(int)avail?avail:n));
+ }
+ str_l += n;
+ }
+ }
+ /* insert right padding */
+ if (justify_left) { /* right blank padding to the field width */
+ int n = min_field_width - (str_arg_l+number_of_zeros_to_pad);
+ if (n > 0) {
+ if (str_l < str_m) {
+ size_t avail = str_m-str_l;
+ fast_memset(str+str_l, ' ', (n>(int)avail?avail:n));
+ }
+ str_l += n;
+ }
+ }
+ }
+ }
+#if defined(NEED_SNPRINTF_ONLY)
+ va_end(ap);
+#endif
+ if (str_m > 0) { /* make sure the string is null-terminated
+ even at the expense of overwriting the last character
+ (shouldn't happen, but just in case) */
+ str[str_l <= str_m-1 ? str_l : str_m-1] = '\0';
+ }
+ /* Return the number of characters formatted (excluding trailing null
+ * character), that is, the number of characters that would have been
+ * written to the buffer if it were large enough.
+ *
+ * The value of str_l should be returned, but str_l is of unsigned type
+ * size_t, and snprintf is int, possibly leading to an undetected
+ * integer overflow, resulting in a negative return value, which is illegal.
+ * Both XSH5 and ISO C99 (at least the draft) are silent on this issue.
+ * Should errno be set to EOVERFLOW and EOF returned in this case???
+ */
+ return (int) str_l;
+}
+#endif
+#endif //__GNU_LIBRARY__
diff --git a/Source/Internal/snprintf.h b/Source/Internal/snprintf.h
new file mode 100644
index 00000000..2e13fc7b
--- /dev/null
+++ b/Source/Internal/snprintf.h
@@ -0,0 +1,41 @@
+#ifndef _PORTABLE_SNPRINTF_H_
+#define _PORTABLE_SNPRINTF_H_
+
+#ifdef __GNU_LIBRARY__ //We already have these features in GCC
+#include <stdio.h>
+#else
+
+#define PORTABLE_SNPRINTF_VERSION_MAJOR 2
+#define PORTABLE_SNPRINTF_VERSION_MINOR 2
+
+#ifdef HAVE_SNPRINTF
+#include <stdio.h>
+#else
+extern "C" {
+extern int snprintf(char *, size_t, const char *, /*args*/ ...);
+}
+#ifndef NEED_SNPRINTF_ONLY
+extern "C" {
+extern int vsnprintf(char *, size_t, const char *, va_list);
+}
+#endif
+#endif
+
+#if defined(HAVE_SNPRINTF) && defined(PREFER_PORTABLE_SNPRINTF)
+extern "C" {
+extern int portable_snprintf(char *str, size_t str_m, const char *fmt, /*args*/ ...);
+extern int portable_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap);
+}
+#define snprintf portable_snprintf
+#define vsnprintf portable_vsnprintf
+#endif
+
+extern "C" {
+extern int asprintf (char **ptr, const char *fmt, /*args*/ ...);
+extern int vasprintf (char **ptr, const char *fmt, va_list ap);
+extern int asnprintf (char **ptr, size_t str_m, const char *fmt, /*args*/ ...);
+extern int vasnprintf(char **ptr, size_t str_m, const char *fmt, va_list ap);
+}
+
+#endif
+#endif
diff --git a/Source/Internal/spawneritem.cpp b/Source/Internal/spawneritem.cpp
new file mode 100644
index 00000000..38df6541
--- /dev/null
+++ b/Source/Internal/spawneritem.cpp
@@ -0,0 +1,38 @@
+//-----------------------------------------------------------------------------
+// Name: spawneritem.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "spawneritem.h"
+
+SpawnerItem::SpawnerItem( std::string _mod_source_title, std::string _display_name, std::string _path, std::string _thumbnail ) :
+ mod_source_title(_mod_source_title),
+ display_name(_display_name),
+ path(_path),
+ thumbnail_path(_thumbnail)
+{
+
+}
+
+SpawnerItem::SpawnerItem() { }
+
+bool operator==( const SpawnerItem& a, const SpawnerItem& b ) {
+ return a.display_name == b.display_name && a.path == b.path && a.thumbnail_path == b.thumbnail_path;
+}
diff --git a/Source/Internal/spawneritem.h b/Source/Internal/spawneritem.h
new file mode 100644
index 00000000..dc6f8f73
--- /dev/null
+++ b/Source/Internal/spawneritem.h
@@ -0,0 +1,36 @@
+//-----------------------------------------------------------------------------
+// Name: spawneritem.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <string>
+
+struct SpawnerItem {
+ SpawnerItem( std::string mod_source_title, std::string display_name, std::string path, std::string thumbnail );
+ SpawnerItem();
+ std::string mod_source_title;
+ std::string display_name;
+ std::string path;
+ std::string thumbnail_path;
+};
+
+bool operator==( const SpawnerItem& a, const SpawnerItem& b );
diff --git a/Source/Internal/stdafx.cpp b/Source/Internal/stdafx.cpp
new file mode 100644
index 00000000..edc17d94
--- /dev/null
+++ b/Source/Internal/stdafx.cpp
@@ -0,0 +1,3 @@
+#ifdef _WIN32
+ #include "stdafx.h"
+#endif \ No newline at end of file
diff --git a/Source/Internal/stopwatch.cpp b/Source/Internal/stopwatch.cpp
new file mode 100644
index 00000000..a0a14563
--- /dev/null
+++ b/Source/Internal/stopwatch.cpp
@@ -0,0 +1,75 @@
+//-----------------------------------------------------------------------------
+// Name: stopwatch.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "stopwatch.h"
+
+#include <Compat/time.h>
+#include <Logging/logdata.h>
+#include <Threading/sdl_wrapper.h>
+
+void BusyWaitMilliseconds( uint32_t how_many )
+{
+ Stopwatch watch;
+ while(watch.ReportMilliseconds()<how_many){
+ }
+}
+
+void Stopwatch::Start() {
+ start_time = SDL_TS_GetTicks();
+}
+
+void Stopwatch::Stop(std::string text) {
+ uint32_t time_elapsed = StopAndReport();
+ LOGI << time_elapsed << " " << text << std::endl;
+}
+
+uint32_t Stopwatch::StopAndReport() {
+ uint32_t time = SDL_TS_GetTicks();
+ uint32_t time_elapsed = time - start_time;
+ start_time = time;
+
+ return time_elapsed;
+}
+
+uint32_t Stopwatch::ReportMilliseconds() {
+ uint32_t time = SDL_TS_GetTicks();
+ uint32_t time_elapsed = time - start_time;
+
+ return time_elapsed;
+}
+
+void PrecisionStopwatch::Start() {
+ start_time = GetPrecisionTime();
+}
+
+void PrecisionStopwatch::Stop(std::string text) {
+ uint64_t time_elapsed = StopAndReportNanoseconds();
+ LOGI << time_elapsed << " " << text << std::endl;
+}
+
+uint64_t PrecisionStopwatch::StopAndReportNanoseconds() {
+ uint64_t time = GetPrecisionTime();
+ uint64_t time_elapsed = time - start_time;
+ start_time = time;
+
+ return ToNanoseconds(time_elapsed);
+}
diff --git a/Source/Internal/stopwatch.h b/Source/Internal/stopwatch.h
new file mode 100644
index 00000000..46f22789
--- /dev/null
+++ b/Source/Internal/stopwatch.h
@@ -0,0 +1,56 @@
+//-----------------------------------------------------------------------------
+// Name: stopwatch.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <SDL.h>
+
+#include <string>
+
+void BusyWaitMilliseconds(uint32_t how_many);
+
+class Stopwatch {
+private:
+ uint32_t start_time;
+public:
+ void Start();
+ void Stop(std::string text);
+ uint32_t StopAndReport();
+ uint32_t ReportMilliseconds();
+
+ Stopwatch() {
+ Start();
+ }
+};
+
+class PrecisionStopwatch {
+private:
+ uint64_t start_time;
+public:
+ void Start();
+ void Stop(std::string text);
+ uint64_t StopAndReportNanoseconds();
+
+ PrecisionStopwatch() {
+ Start();
+ }
+};
diff --git a/Source/Internal/textfile.cpp b/Source/Internal/textfile.cpp
new file mode 100644
index 00000000..adbe6313
--- /dev/null
+++ b/Source/Internal/textfile.cpp
@@ -0,0 +1,82 @@
+//-----------------------------------------------------------------------------
+// Name: textfile.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+// textfile.cpp
+//
+// simple reading and writing for text files
+//
+// www.lighthouse3d.com
+//
+// You may use these functions freely.
+// they are provided as is, and no warranties, either implicit,
+// or explicit are given
+//////////////////////////////////////////////////////////////////////
+
+#include <Internal/error.h>
+#include <Compat/fileio.h>
+#include <Compat/compat.h>
+
+#ifdef _WIN32
+#define NOMINMAX
+#include <windows.h>
+#include <malloc.h>
+#include <io.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <string.h>
+
+void textFileRead(const std::string &filename, std::string *dst) {
+ FILE *fp;
+ if (filename.c_str() != NULL) {
+ fp = my_fopen(filename.c_str(),"rb");
+ if(!fp){
+ FatalError("Error", "Could not open file txt \"%s\"", filename.c_str());
+ }
+ fseek(fp, 0, SEEK_END);
+ int count = ftell(fp);
+ fseek(fp, 0, SEEK_SET);
+ if (fp != NULL) {
+ dst->clear();
+ if (count > 0) {
+ dst->resize(count);
+ fread(&dst->at(0),sizeof(char),count,fp);
+ }
+ fclose(fp);
+ }
+ }
+ // Remove trailing '\0' characters, they mess up include file expansion
+ for(int i=(int)dst->length()-1; i>=0; --i){
+ if(dst->at(i) != '\0'){
+ dst->resize(i+1);
+ break;
+ }
+ }
+}
+
+int textFileWrite(char *fn, char *s) {
+ FILE *fp;
+ int status = 0;
+
+ if (fn != NULL) {
+ fp = my_fopen(fn,"w");
+ if (fp != NULL) {
+ if (fwrite(s,sizeof(char),strlen(s),fp) == strlen(s))
+ status = 1;
+ fclose(fp);
+ }
+ }
+ return(status);
+}
+
+
+
+
+
+
+
diff --git a/Source/Internal/textfile.h b/Source/Internal/textfile.h
new file mode 100644
index 00000000..c3cbc2ab
--- /dev/null
+++ b/Source/Internal/textfile.h
@@ -0,0 +1,19 @@
+//-----------------------------------------------------------------------------
+// Name: textfile.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+// textfile.h: interface for reading and writing text files
+// www.lighthouse3d.com
+//
+// You may use these functions freely.
+// they are provided as is, and no warranties, either implicit,
+// or explicit are given
+//////////////////////////////////////////////////////////////////////
+#pragma once
+
+#include <string>
+
+void textFileRead(const std::string &filename, std::string *dst);
+int textFileWrite(char *fn, char *s);
diff --git a/Source/Internal/timer.cpp b/Source/Internal/timer.cpp
new file mode 100644
index 00000000..33fef26d
--- /dev/null
+++ b/Source/Internal/timer.cpp
@@ -0,0 +1,191 @@
+//-----------------------------------------------------------------------------
+// Name: timer.cpp
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: The timer handles fixed or dynamic timeSteps
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include <Internal/timer.h>
+#include <Internal/common.h>
+#include <Internal/datemodified.h>
+#include <Internal/filesystem.h>
+
+#include <Compat/fileio.h>
+#include <Compat/time.h>
+
+#include <Threading/sdl_wrapper.h>
+#include <Math/enginemath.h>
+
+#include <SDL.h>
+
+#include <ctime>
+#include <cstdlib>
+
+//-----------------------------------------------------------------------------
+//Functions
+//-----------------------------------------------------------------------------
+
+static const float _time_inertia = 0.6f;
+
+Timer game_timer;
+Timer ui_timer;
+
+void Timer::UpdateWallTime() {
+ wall_ticks = SDL_TS_GetTicks();
+ wall_time = wall_ticks / 1000.0f;
+}
+
+void Timer::Update() {
+ frame_count++;
+ uint32_t tick = SDL_TS_GetTicks();
+ float min_target_time_scale = target_time_scale;
+ for(std::vector<TimedSlowMotionLayer>::iterator iter = timed_slow_motion_layers.begin();
+ iter != timed_slow_motion_layers.end();)
+ {
+ TimedSlowMotionLayer& layer = (*iter);
+ if(layer.start_time <= tick){
+ min_target_time_scale = min(min_target_time_scale, layer.target_time_scale);
+ }
+ if(layer.end_time <= tick){
+ iter = timed_slow_motion_layers.erase(iter);
+ } else {
+ iter++;
+ }
+ }
+ time_scale = mix(min_target_time_scale, time_scale, _time_inertia);
+ game_time += timestep;
+}
+
+float Timer::GetInterpWeight(){
+ return timestep_error;
+}
+
+float Timer::GetInterpWeightX(int num, int progress){
+ float mult = (1.0f/(float)num);
+ return mult*(float)progress + timestep_error*mult;
+}
+
+
+float Timer::GetRenderTime() {
+ return game_time + timestep_error * timestep;
+}
+
+float Timer::GetAverageFrameTime() {
+ float sum = .0f;
+ for (int i = 0; i < NUM_AVERAGED_FRAMES; i++) {
+ sum += frame[i];
+ }
+
+ return sum / (float)NUM_AVERAGED_FRAMES;
+}
+
+//Set simulations per second
+void Timer::SetStepFrequency(int sims) {
+ simulations_per_second = sims;
+ uint32_t tick = SDL_TS_GetTicks();
+ last_tick = tick;
+ timestep = 1/((float)simulations_per_second);
+ srand(tick);
+}
+
+//Get number of timeSteps to use this display frame
+int Timer::GetStepsNeeded() {
+ uint32_t tick = SDL_TS_GetTicks();
+ int num;
+ num=((int)((tick-last_tick)*simulations_per_second*time_scale)/1000);
+ last_tick+=(int)((num*1000)/(simulations_per_second*time_scale));
+ timestep_error=(tick-last_tick)/1000.0f*simulations_per_second*time_scale;
+ return num;
+}
+
+//We have rendered another frame, so gather fps data
+void Timer::ReportFrameForFPSCount() {
+ for (int i = 0;i<NUM_AVERAGED_FRAMES-1;i++){
+ frame[i]=frame[i+1];
+ }
+ frame[NUM_AVERAGED_FRAMES-1]=GetPrecisionTime();
+}
+
+//Calculate fps
+int Timer::GetFramesPerSecond() {
+ uint64_t frame_diff = ToNanoseconds(frame[NUM_AVERAGED_FRAMES-1] - frame[0]);
+ if (frame_diff != 0) {
+ return (int) (1000*NUM_AVERAGED_FRAMES/(frame_diff * 0.000001));
+ } else {
+ return 0;
+ }
+}
+
+float Timer::GetFrameTime() {
+ uint64_t frame_diff = ToNanoseconds(frame[NUM_AVERAGED_FRAMES-1] - frame[0]);
+ if (frame_diff != 0) {
+ return (frame_diff * 0.000001f)/NUM_AVERAGED_FRAMES;
+ } else {
+ return 0;
+ }
+}
+
+float Timer::GetWallTime() {
+ return wall_time;
+}
+
+uint32_t Timer::GetWallTicks() {
+ return wall_ticks;
+}
+
+float Timer::GetSlowestFrameTime() {
+ uint64_t slowest = frame[1] - frame[0];
+ for(int i = 1; i < NUM_AVERAGED_FRAMES; ++i) {
+ slowest = std::max(slowest, frame[i] - frame[i - 1]);
+ }
+ return ToNanoseconds(slowest) * 0.000001f;
+}
+
+float Timer::GetFastestFrameTime() {
+ int64_t fastest = frame[1] - frame[0];
+ for(int i = 1; i < NUM_AVERAGED_FRAMES; ++i) {
+ int64_t current = frame[i] - frame[i - 1];
+ if(fastest == 0 || current < fastest)
+ fastest = current;
+ }
+ return ToNanoseconds(fastest) * 0.000001f;
+}
+
+void Timer::AddTimedSlowMotionLayer( float target_time_scale, float duration, float delay ) {
+ timed_slow_motion_layers.resize(timed_slow_motion_layers.size()+1);
+ TimedSlowMotionLayer& layer = timed_slow_motion_layers.back();
+ layer.target_time_scale = target_time_scale;
+ layer.start_time = SDL_TS_GetTicks() + (int)(delay * 1000.0f);
+ layer.end_time = layer.start_time + (int)(duration * 1000.0f);
+}
+
+Timer::Timer():
+ frame_count(0),
+ target_time_scale(1.0f),
+ time_scale(1.0f),
+ game_time(0),
+ simulations_per_second(30),
+ last_tick(0),
+ wall_time(0)
+{
+ for(int i=0; i<NUM_AVERAGED_FRAMES; i++){
+ frame[i]=0;
+ }
+}
diff --git a/Source/Internal/timer.h b/Source/Internal/timer.h
new file mode 100644
index 00000000..1683f1b1
--- /dev/null
+++ b/Source/Internal/timer.h
@@ -0,0 +1,72 @@
+//-----------------------------------------------------------------------------
+// Name: timer.h
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: The timer handles fixed or dynamic timeSteps
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <vector>
+#include <cstdint>
+
+struct TimedSlowMotionLayer {
+ uint32_t end_time;
+ uint32_t start_time;
+ float target_time_scale;
+};
+
+class Timer {
+public:
+ Timer();
+
+ float target_time_scale;
+ float time_scale;
+ float timestep;
+ float timestep_error;
+ float game_time;
+ float wall_time;
+ uint32_t wall_ticks;
+ int updates_since_last_frame;
+ int simulations_per_second;
+ uint64_t frame_count;
+
+ void AddTimedSlowMotionLayer( float target_time_scale, float how_long, float delay = 0.0f );
+ void UpdateWallTime();
+ void Update();
+ void SetStepFrequency(int sims);
+ int GetStepsNeeded();
+ void ReportFrameForFPSCount();
+ int GetFramesPerSecond();
+ float GetFrameTime();
+ float GetSlowestFrameTime();
+ float GetFastestFrameTime();
+ float GetInterpWeight();
+ float GetInterpWeightX(int num, int progress);
+ float GetRenderTime();
+ float GetWallTime();
+ uint32_t GetWallTicks();
+ float GetAverageFrameTime();
+
+private:
+ uint32_t last_tick;
+ static const int NUM_AVERAGED_FRAMES = 30;
+ uint64_t frame[NUM_AVERAGED_FRAMES];
+ std::vector<TimedSlowMotionLayer> timed_slow_motion_layers;
+};
diff --git a/Source/Internal/treestructure.cpp b/Source/Internal/treestructure.cpp
new file mode 100644
index 00000000..6e5644c5
--- /dev/null
+++ b/Source/Internal/treestructure.cpp
@@ -0,0 +1,195 @@
+//-----------------------------------------------------------------------------
+// Name: treestructure.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include <queue>
+#include <vector>
+#include <algorithm>
+
+int GetDepth(int which, const std::vector<int> &parents){
+ int depth = 0;
+ int temp = which;
+ while(parents[temp] != -1){
+ temp = parents[temp];
+ ++depth;
+ }
+ return depth;
+}
+
+int GetTotalNumChildren(int parent, const std::vector<int> &parents){
+ int num_children = 0;
+ for(unsigned i=0; i<parents.size(); ++i){
+ if(parents[i] == parent){
+ ++num_children;
+ num_children += GetTotalNumChildren(i, parents);
+ }
+ }
+ return num_children;
+}
+
+int GetNumChildren(int parent, const std::vector<int> &parents){
+ int num_children = 0;
+ for(unsigned i=0; i<parents.size(); ++i){
+ if(parents[i] == parent){
+ ++num_children;
+ }
+ }
+ return num_children;
+}
+
+
+int GetNumConnections(int parent, const std::vector<int> &parents){
+ int num_connections = 0;
+ if(parents[parent] != -1){
+ ++num_connections;
+ }
+ for(unsigned i=0; i<parents.size(); ++i){
+ if(parents[i] == parent){
+ ++num_connections;
+ }
+ }
+ return num_connections;
+}
+
+void GetBalancedTree(std::vector<int> &temp_parent) {
+ // Get root node id
+ int root = 0;
+ for(unsigned i=0; i<temp_parent.size(); ++i){
+ if(temp_parent[i] == -1){
+ root = i;
+ }
+ }
+
+ //int old_imbalance = -1;
+ //int imbalance = -1;
+
+ while (true) {
+ // Get a list of all the children of the root node
+ std::vector<int> root_children;
+ for(unsigned i=0; i<temp_parent.size(); ++i){
+ if(temp_parent[i] == root){
+ root_children.push_back(i);
+ }
+ }
+
+ // Get weight of each child branch
+ std::vector<int> root_weight(root_children.size());
+ for(unsigned i=0; i<root_children.size(); ++i){
+ root_weight[i] = GetTotalNumChildren(root_children[i], temp_parent);
+ }
+
+ // Find heaviest and lightest child branch
+ int heaviest_child = 0;
+ int lightest_child = 0;
+ for(unsigned i=1; i<root_children.size(); ++i){
+ if(root_weight[i] > root_weight[heaviest_child]){
+ heaviest_child = i;
+ }
+ if(root_weight[i] < root_weight[lightest_child]){
+ lightest_child = i;
+ }
+ }
+
+ // Make heaviest branch the new root
+ //old_imbalance = imbalance;
+ /*
+ imbalance = root_weight[heaviest_child] -
+ root_weight[lightest_child];
+ */
+
+ if(root_weight[heaviest_child] + 1 <=
+ (int)temp_parent.size() - root_weight[heaviest_child]){
+ return;
+ }
+
+ temp_parent[root_children[heaviest_child]] = -1;
+ temp_parent[root] = root_children[heaviest_child];
+ root = root_children[heaviest_child];
+ }
+}
+
+std::vector<int> GetChildren( int parent, const std::vector<int> &parents )
+{
+ std::vector<int> children;
+ for(unsigned i=0; i<parents.size(); ++i){
+ if(parents[i] == parent){
+ children.push_back(i);
+ }
+ }
+ return children;
+}
+
+std::vector<int> GetChildrenRecursive( int parent, const std::vector<int> &parents )
+{
+ std::vector<int> children;
+ for(unsigned i=0; i<parents.size(); ++i){
+ if(parents[i] == parent){
+ children.push_back(i);
+ std::vector<int> sub_children = GetChildrenRecursive(i, parents);
+ for(unsigned j=0; j<sub_children.size(); ++j){
+ children.push_back(sub_children[j]);
+ }
+ }
+ }
+ return children;
+}
+
+std::vector<int> GetConnections(int parent, const std::vector<int> &parents){
+ std::vector<int> connections;
+ if(parents[parent] != -1){
+ connections.push_back(parents[parent]);
+ }
+ for(unsigned i=0; i<parents.size(); ++i){
+ if(parents[i] == parent){
+ connections.push_back(i);
+ }
+ }
+ return connections;
+}
+
+std::vector<int> FindTreePath( int a, int b, const std::vector<int> &parents )
+{
+ std::queue<std::vector<int> > queue;
+
+ std::vector<int> start_path;
+ start_path.push_back(a);
+ queue.push(start_path);
+
+ while(!queue.empty()){
+ std::vector<int> path = queue.front();
+ queue.pop();
+
+ std::vector<int> connections = GetConnections(path.back(), parents);
+ for(unsigned i=0; i<connections.size(); ++i){
+ if(find(path.begin(), path.end(),connections[i]) == path.end()){
+ queue.push(path);
+ queue.back().push_back(connections[i]);
+ if(connections[i] == b){
+ return queue.back();
+ }
+ }
+ }
+ }
+
+ std::vector<int> failure;
+ return failure;
+}
diff --git a/Source/Internal/treestructure.h b/Source/Internal/treestructure.h
new file mode 100644
index 00000000..4df8b378
--- /dev/null
+++ b/Source/Internal/treestructure.h
@@ -0,0 +1,36 @@
+//-----------------------------------------------------------------------------
+// Name: treestructure.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <vector>
+
+namespace TreeStructure {
+ void GetBalancedTree(std::vector<int> &temp_parent);
+ int GetTotalNumChildren(int parent, const std::vector<int> &parents);
+ int GetDepth(int which, const std::vector<int> &parents);
+ int GetNumChildren(int parent, const std::vector<int> &parents);
+ std::vector<int> GetChildren(int parent, const std::vector<int> &parents);
+ int GetNumConnections(int parent, const std::vector<int> &parents);
+ std::vector<int> FindTreePath( int a, int b, const std::vector<int> &parents );
+ std::vector<int> GetChildrenRecursive( int parent, const std::vector<int> &parents );
+}
diff --git a/Source/Internal/varstring.cpp b/Source/Internal/varstring.cpp
new file mode 100644
index 00000000..1e29ba62
--- /dev/null
+++ b/Source/Internal/varstring.cpp
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------------
+// Name: varstring.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "varstring.h"
+
+#include <Internal/common.h>
+
+#include <stdio.h>
+
+std::string FloatString(float val, int num_chars){
+ char buf[255];
+ std::string return_string;
+ if(val >= 0.0f){
+ return_string += " ";
+ }
+ FormatString(buf, 255, "%f", val);
+ return_string += buf;
+ return_string = return_string.substr(0, num_chars);
+ return return_string;
+}
diff --git a/Source/Internal/varstring.h b/Source/Internal/varstring.h
new file mode 100644
index 00000000..6b4d42ca
--- /dev/null
+++ b/Source/Internal/varstring.h
@@ -0,0 +1,27 @@
+//-----------------------------------------------------------------------------
+// Name: varstring.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <string>
+
+std::string FloatString(float val, int num_chars);
diff --git a/Source/Internal/win_mem_track.cpp b/Source/Internal/win_mem_track.cpp
new file mode 100644
index 00000000..1f140fe6
--- /dev/null
+++ b/Source/Internal/win_mem_track.cpp
@@ -0,0 +1,127 @@
+//-----------------------------------------------------------------------------
+// Name: win_mem_track.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "win_mem_track.h"
+
+#include <Logging/logdata.h>
+#include <Utility/assert.h>
+
+bool mem_track_enable = false;
+//Memory check functions adapted from Doom 3 source
+#if defined(_DEBUG) && defined(_WIN32)
+#define NOMINMAX
+#include <Windows.h>
+#include <crtdbg.h>
+
+#include <cstdio>
+#include <cassert>
+
+static unsigned int debug_total_alloc = 0;
+static unsigned int debug_total_alloc_count = 0;
+static unsigned int debug_current_alloc = 0;
+static unsigned int debug_current_alloc_count = 0;
+static unsigned int debug_frame_alloc = 0;
+static unsigned int debug_frame_alloc_count = 0;
+
+typedef struct CrtMemBlockHeader
+{
+ struct _CrtMemBlockHeader *pBlockHeaderNext; // Pointer to the block allocated just before this one:
+ struct _CrtMemBlockHeader *pBlockHeaderPrev; // Pointer to the block allocated just after this one
+ char *szFileName; // File name
+ int nLine; // Line number
+ size_t nDataSize; // Size of user block
+ int nBlockUse; // Type of block
+ long lRequest; // Allocation number
+ byte gap[4]; // Buffer just before (lower than) the user's memory:
+} CrtMemBlockHeader;
+
+
+int AllocHook( int nAllocType, void *pvData, size_t nSize, int nBlockUse, long lRequest, const unsigned char * szFileName, int nLine ) {
+ CrtMemBlockHeader *pHead;
+ byte *temp;
+
+ if ( nBlockUse == _CRT_BLOCK )
+ {
+ return( TRUE );
+ }
+
+ // get a pointer to memory block header
+ temp = ( byte * )pvData;
+ temp -= 32;
+ pHead = ( CrtMemBlockHeader * )temp;
+
+ switch( nAllocType ) {
+ case _HOOK_ALLOC:
+ if(mem_track_enable){
+ bool break_here = true;
+ }
+ debug_total_alloc += nSize;
+ debug_current_alloc += nSize;
+ debug_frame_alloc += nSize;
+ debug_total_alloc_count++;
+ debug_current_alloc_count++;
+ debug_frame_alloc_count++;
+ break;
+
+ case _HOOK_FREE:
+ LOG_ASSERT( pHead->gap[0] == 0xfd && pHead->gap[1] == 0xfd && pHead->gap[2] == 0xfd && pHead->gap[3] == 0xfd );
+
+ debug_current_alloc -= pHead->nDataSize;
+ debug_current_alloc_count--;
+ debug_total_alloc_count++;
+ debug_frame_alloc_count++;
+ break;
+
+ case _HOOK_REALLOC:
+ LOG_ASSERT( pHead->gap[0] == 0xfd && pHead->gap[1] == 0xfd && pHead->gap[2] == 0xfd && pHead->gap[3] == 0xfd );
+
+ debug_current_alloc -= pHead->nDataSize;
+ debug_total_alloc += nSize;
+ debug_current_alloc += nSize;
+ debug_frame_alloc += nSize;
+ debug_total_alloc_count++;
+ debug_current_alloc_count--;
+ debug_frame_alloc_count++;
+ break;
+ }
+ return( TRUE );
+}
+
+void WinMemTrack::PrintTotal() {
+ LOGI.Format( "Total allocation %8dk in %d blocks\n", debug_total_alloc / 1024, debug_total_alloc_count );
+ LOGI.Format( "Current allocation %8dk in %d blocks\n", debug_current_alloc / 1024, debug_current_alloc_count );
+}
+
+void WinMemTrack::PrintFrame() {
+ LOGI.Format("Frame: %8dk in %5d blocks\n", debug_frame_alloc / 1024, debug_frame_alloc_count );
+ debug_frame_alloc = 0;
+ debug_frame_alloc_count = 0;
+}
+
+void WinMemTrack::Attach() {
+ _CrtSetAllocHook(AllocHook);
+}
+#else
+void WinMemTrack::PrintTotal() {}
+void WinMemTrack::PrintFrame() {}
+void WinMemTrack::Attach() {}
+#endif
diff --git a/Source/Internal/win_mem_track.h b/Source/Internal/win_mem_track.h
new file mode 100644
index 00000000..4a755b52
--- /dev/null
+++ b/Source/Internal/win_mem_track.h
@@ -0,0 +1,29 @@
+//-----------------------------------------------------------------------------
+// Name: win_mem_track.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+namespace WinMemTrack {
+ void Attach();
+ void PrintFrame();
+ void PrintTotal();
+}
diff --git a/Source/Internal/worker.cpp b/Source/Internal/worker.cpp
new file mode 100644
index 00000000..2924b489
--- /dev/null
+++ b/Source/Internal/worker.cpp
@@ -0,0 +1,82 @@
+//-----------------------------------------------------------------------------
+// Name: worker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include <Logging/consolehandler.h>
+#include <Logging/filehandler.h>
+#include <Logging/logdata.h>
+
+#include <Internal/common.h>
+#include <Internal/error_response.h>
+
+#include <Compat/processpool.h>
+#include <Graphics/converttexture.h>
+#include <Memory/allocation.h>
+
+#include <cstdlib>
+
+using std::endl;
+
+Allocation alloc;
+
+ErrorResponse DisplayError(const char* title, const char* contents, ErrorType type, bool allow_repetition)
+{
+ return _continue;
+}
+
+//Replacement for the main program version of the same fucntion
+void FatalError(const char* title, const char* fmt, ...)
+{
+ static const int kBufSize = 1024;
+ char err_buf[kBufSize];
+ va_list args;
+ va_start(args, fmt);
+ VFormatString(err_buf, kBufSize, fmt, args);
+ va_end(args);
+ LOGF << title << "," << err_buf << endl;
+ exit(10);
+}
+
+int ConvertTexture(int argc, const char* argv[]){
+ ConvertImage(argv[0], argv[1], argv[2], TextureData::Nice);
+ return 0;
+}
+
+int main(int argc, char* argv[]){
+ alloc.Init();
+ ConsoleHandler consoleHandler;
+ LogSystem::RegisterLogHandler(
+ LogSystem::info
+ | LogSystem::warning
+ | LogSystem::error
+ | LogSystem::fatal,
+ &consoleHandler );
+ if(!ProcessPool::AmIAWorkerProcess(argc, argv)){
+ exit(1);
+ }
+ ProcessPool::JobMap jobs;
+ jobs["ConvertTexture"] = &ConvertTexture;
+ int ret = ProcessPool::WorkerProcessMain(jobs);
+ LogSystem::DeregisterLogHandler( &consoleHandler );
+ alloc.Dispose();
+ return ret;
+}
diff --git a/Source/Internal/zip_util.cpp b/Source/Internal/zip_util.cpp
new file mode 100644
index 00000000..3116358b
--- /dev/null
+++ b/Source/Internal/zip_util.cpp
@@ -0,0 +1,483 @@
+//-----------------------------------------------------------------------------
+// Name: zip_util.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "zip_util.h"
+
+#include <Internal/error.h>
+#include <Internal/scoped_buffer.h>
+#include <Internal/filesystem.h>
+
+#include <Memory/allocation.h>
+#include <Logging/logdata.h>
+
+#include <minizip/zip_util_internal.h>
+#include <minizip/zip.h>
+#include <minizip/unzip.h>
+#include <zlib.h>
+
+#if _WIN32
+#include <Compat/fileio.h>
+#include <windows.h>
+#endif
+
+#include <string>
+#include <cstring>
+
+const int size_buf = 16384;
+const int opt_compress_level = Z_DEFAULT_COMPRESSION;
+
+void ClearZFI(zip_fileinfo &zi){
+ zi.tmz_date.tm_sec = zi.tmz_date.tm_min = zi.tmz_date.tm_hour =
+ zi.tmz_date.tm_mday = zi.tmz_date.tm_mon = zi.tmz_date.tm_year = 0;
+ zi.dosDate = 0;
+ zi.internal_fa = 0;
+ zi.external_fa = 0;
+}
+
+class ZipFile{
+ zipFile zf;
+
+ public:
+ ZipFile();
+ ~ZipFile();
+ void Load(const std::string& path, bool append);
+ void AddFile( const std::string& src_file_path, const std::string& in_zip_file_path, const std::string& password = "" );
+};
+
+void ZipFile::Load(const std::string& path, bool append){
+ #ifdef USEWIN32IOAPI
+ zlib_filefunc64_def ffunc;
+ fill_win32_filefunc64A(&ffunc);
+ zf = zipOpen2_64(path.c_str(),append ? 2 : 0,NULL,&ffunc);
+ #else
+ zf = zipOpen64(path.c_str(),append ? 2 : 0);
+ #endif
+
+ if (zf == NULL) {
+ FatalError("Error","Problem opening zipfile %s", path.c_str());
+ }
+}
+
+void ZipFile::AddFile( const std::string& src_file_path,
+ const std::string& in_zip_file_path,
+ const std::string& password )
+{
+ ScopedBuffer scoped_buf(size_buf);
+
+ zip_fileinfo zi;
+ ClearZFI(zi);
+ filetime(src_file_path.c_str(),&zi.tmz_date,&zi.dosDate);
+
+ unsigned long crcFile=0;
+ if (!password.empty()){
+ if(getFileCrc(src_file_path.c_str(),scoped_buf.ptr,size_buf,&crcFile) != ZIP_OK){
+ FatalError("Error", "Problem getting CRC of file: %s", src_file_path.c_str());
+ }
+ }
+
+ //The FSEEKO sometimes casuse segfault on linux debian for no reason, we never have large files anyways.
+ int zip64 = false;//isLargeFile(src_file_path.c_str());
+
+ /* The path name saved, should not include a leading slash.
+ if it did, windows/xp and dynazip couldn't read the zip file. */
+ const char *savefilenameinzip = in_zip_file_path.c_str();
+ while( savefilenameinzip[0] == '\\' || savefilenameinzip[0] == '/' ) {
+ savefilenameinzip++;
+ }
+
+ if(zipOpenNewFileInZip3_64(zf,savefilenameinzip,&zi,
+ NULL,0,NULL,0,NULL /* comment*/,
+ (opt_compress_level != 0) ? Z_DEFLATED : 0,
+ opt_compress_level,0,
+ -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
+ password.empty()?NULL:password.c_str(),crcFile, zip64) != ZIP_OK)
+ {
+ FatalError("Error", "Could not open %s in zipfile", src_file_path.c_str());
+ }
+
+ {
+ FILE * file;
+ file = FOPEN_FUNC(src_file_path.c_str(),"rb");
+ if (file == NULL) {
+ FatalError("Error", "Could not open file: %s", src_file_path.c_str());
+ }
+
+ int size_read = 0;
+ do {
+ size_read = (int)fread(scoped_buf.ptr,1,size_buf,file);
+ if (size_read < size_buf && feof(file)==0) {
+ FatalError("Error", "Problem reading file: %s", src_file_path.c_str());
+ }
+ if (size_read > 0) {
+ if(zipWriteInFileInZip (zf,scoped_buf.ptr,size_read) < 0){
+ FatalError("Error", "Problem writing \"%s\" in the zipfile.", src_file_path.c_str());
+ }
+ }
+ } while(size_read>0);
+ if (file)
+ fclose(file);
+ }
+
+ if (zipCloseFileInZip(zf) != ZIP_OK) {
+ FatalError("Error", "Problem closing %s in zip file.", src_file_path.c_str());
+ }
+}
+
+ZipFile::ZipFile():
+ zf(NULL)
+{}
+
+ZipFile::~ZipFile()
+{
+ if(zf && zipClose(zf,NULL) != ZIP_OK) {
+ DisplayError("Error", "Error closing zip file");
+ }
+}
+
+void Zip(const std::string &src_file_path,
+ const std::string &zip_file_path,
+ const std::string &in_zip_file_path,
+ OverwriteType overwrite)
+{
+ if(overwrite == _NO_OVERWRITE && check_exist_file(zip_file_path.c_str())!=0){
+ return;
+ }
+
+ std::string dir = zip_file_path.substr(0, zip_file_path.find_last_of("\\/") + 1);
+ if(CheckWritePermissions(dir.c_str()) != 0) {
+ FatalError("Error","Couldn't write zip-file to %s, make sure you have write permissions", dir.c_str());
+ return;
+ }
+
+ ZipFile zf;
+ #ifdef _WIN32
+ createfile(zip_file_path.c_str());
+
+ std::wstring w_src_path(GetShortPathNameW(UTF16fromUTF8(src_file_path).c_str(), NULL, NULL), '\0');
+ GetShortPathNameW(UTF16fromUTF8(src_file_path).c_str(), &w_src_path[0], w_src_path.size());
+ std::string short_src = UTF8fromUTF16(w_src_path);
+
+ std::wstring w_short_zip(GetShortPathNameW(UTF16fromUTF8(zip_file_path).c_str(), NULL, NULL), '\0');
+ GetShortPathNameW(UTF16fromUTF8(zip_file_path).c_str(), &w_short_zip[0], w_short_zip.size());
+ std::string short_zip = UTF8fromUTF16(w_short_zip);
+
+ zf.Load(short_zip, (overwrite == _APPEND_OVERWRITE));
+ zf.AddFile(short_src, in_zip_file_path);
+ #else
+ zf.Load(zip_file_path, (overwrite == _APPEND_OVERWRITE));
+ zf.AddFile(src_file_path, in_zip_file_path);
+ #endif
+}
+
+struct ExpandedZipEntry {
+ const char *filename;
+ const char *data;
+ unsigned size;
+};
+
+ExpandedZipFile::ExpandedZipFile():buf(NULL),filename_buf(NULL),entries(NULL){}
+
+ExpandedZipFile::~ExpandedZipFile(){
+ Dispose();
+}
+
+void ExpandedZipFile::Dispose(){
+ OG_FREE(buf);
+ OG_FREE(filename_buf);
+ OG_FREE(entries);
+}
+
+void ExpandedZipFile::ResizeEntries(unsigned _num_entries){
+ num_entries = _num_entries;
+ OG_FREE(entries);
+ entries = (ExpandedZipEntry*)OG_MALLOC(sizeof(ExpandedZipEntry)*num_entries);
+}
+
+void ExpandedZipFile::ResizeFilenameBuffer(unsigned num_chars){
+ OG_FREE(filename_buf);
+ filename_buf = (char*)OG_MALLOC(num_chars);
+}
+
+void ExpandedZipFile::ResizeDataBuffer(unsigned num_bytes){
+ OG_FREE(buf);
+ buf = (char*)OG_MALLOC(num_bytes);
+}
+
+void ExpandedZipFile::SetFilename(unsigned offset, const char* data, unsigned size){
+ memcpy(filename_buf+offset, data, size);
+}
+
+void ExpandedZipFile::SetData(unsigned offset, const char* data, unsigned size){
+ memcpy(buf+offset, data, size);
+}
+
+void ExpandedZipFile::SetEntry(unsigned which, unsigned file_name_offset, unsigned data_offset, unsigned size){
+ ExpandedZipEntry& entry = entries[which];
+ entry.filename = filename_buf+file_name_offset;
+ entry.data = buf+data_offset;
+ entry.size = size;
+}
+
+void ExpandedZipFile::GetEntry( unsigned which, const char* &filename, const char* &data, unsigned &size )
+{
+ ExpandedZipEntry& entry = entries[which];
+ filename = entry.filename;
+ data = entry.data;
+ size = entry.size;
+}
+
+
+class UnZipFile{
+ unzFile uf;
+
+public:
+ UnZipFile();
+ ~UnZipFile();
+ void Load(const std::string& path);
+ void Extract( const std::string & in_zip_file_path, const std::string & dst_file_path, OverwriteType overwrite );
+ void PrintInfo();
+ void ExtractAll( ExpandedZipFile & expanded_zip_file );
+};
+
+void UnZipFile::Load(const std::string& path){
+ #ifdef USEWIN32IOAPI
+ zlib_filefunc64_def ffunc;
+ fill_win32_filefunc64A(&ffunc);
+ uf = unzOpen2_64(path.c_str(),&ffunc);
+ #else
+ uf = unzOpen64(path.c_str());
+ #endif
+
+ if (uf == NULL) {
+ FatalError("Error","Problem opening zipfile %s", path.c_str());
+ }
+}
+
+UnZipFile::UnZipFile():
+ uf(NULL)
+{}
+
+UnZipFile::~UnZipFile()
+{
+ if(uf && unzClose(uf) != ZIP_OK) {
+ DisplayError("Error", "Error closing unzip file");
+ }
+}
+
+void do_extract_currentfile(unzFile uf, const std::string &dst, OverwriteType overwrite, const char* password)
+{
+
+ unz_file_info64 file_info;
+ char filename_inzip[256];
+ if(unzGetCurrentFileInfo64(uf,&file_info,filename_inzip,sizeof(filename_inzip),NULL,0,NULL,0) != UNZ_OK){
+ FatalError("Error", "Problem getting current unzip file");
+ }
+
+ FILE *fout=NULL;
+
+ char* filename_withoutpath = filename_inzip;
+ {
+ char* p = filename_inzip;
+ while ((*p) != '\0')
+ {
+ if (((*p)=='/') || ((*p)=='\\'))
+ filename_withoutpath = p+1;
+ p++;
+
+ }
+ }
+
+ if(unzOpenCurrentFilePassword(uf,password)!=UNZ_OK){
+ FatalError("Error", "Error with unzOpenCurrentFilePassword");
+ }
+
+ fout=FOPEN_FUNC(dst.c_str(),"wb");
+ /* some zipfile don't contain directory alone before file */
+ if (fout==NULL) {
+ char c=*(filename_withoutpath-1);
+ *(filename_withoutpath-1)='\0';
+ makedir(dst.c_str());
+ *(filename_withoutpath-1)=c;
+ fout=FOPEN_FUNC(dst.c_str(),"wb");
+ }
+
+ if (fout==NULL) {
+ FatalError("Error","Error opening %s", dst.c_str());
+ }
+
+ if (fout!=NULL) {
+ LOGI << " extracting: " << dst << std::endl;
+
+ ScopedBuffer scoped_buf(size_buf);
+ int err = 0;
+ do {
+ err = unzReadCurrentFile(uf,scoped_buf.ptr,size_buf);
+ if(err < 0) {
+ FatalError("Error","Error reading from UnZip file");
+ printf("error %d with zipfile in unzReadCurrentFile\n",err);
+ break;
+ } else if (err>0) {
+ if (fwrite(scoped_buf.ptr,err,1,fout)!=1){
+ FatalError("Error","Error in writing extracted file");
+ }
+ }
+ } while (err>0);
+ if (fout) {
+ fclose(fout);
+ }
+ change_file_date(dst.c_str(),file_info.dosDate,file_info.tmu_date);
+ }
+
+ if (unzCloseCurrentFile (uf) != UNZ_OK){
+ FatalError("Error","Error with zipfile in unzCloseCurrentFile\n");
+ }
+}
+
+void UnZipFile::Extract( const std::string & in_zip_file_path, const std::string & dst_file_path, OverwriteType overwrite )
+{
+ if (unzLocateFile(uf,in_zip_file_path.c_str(),0) != UNZ_OK){
+ FatalError("Error", "File \"%s\" not found in zip file.", in_zip_file_path.c_str());
+ }
+
+ do_extract_currentfile(uf, dst_file_path, overwrite, NULL);
+}
+
+void UnZipFile::PrintInfo()
+{
+ unz_global_info global_info;
+ unzGetGlobalInfo (uf, &global_info);
+ LOGI << global_info.number_entry << " files." << std::endl;
+
+ unzGoToFirstFile(uf);
+
+ do {
+ unz_file_info file_info;
+ char name[256];
+ unzGetCurrentFileInfo(uf,&file_info,name,256,NULL,0,NULL,0);
+ LOGI << "\t" << name << std::endl;
+ LOGI << "\t\tdosDate: " << file_info.dosDate << std::endl;
+ LOGI << "\t\tcrc: " << file_info.crc << std::endl;
+ LOGI << "\t\tcompressed_size: " << file_info.compressed_size << std::endl;
+ LOGI << "\t\tuncompressed_size: " << file_info.uncompressed_size << std::endl;
+ tm_unz &date = file_info.tmu_date;
+ LOGI << "\t\tDate: " << date.tm_year << ", "<< date.tm_mon << "/" << date.tm_mday << ", " << date.tm_hour << ":" << date.tm_min << ":" << date.tm_sec << std::endl;
+ } while(unzGoToNextFile(uf) == UNZ_OK);
+}
+
+void UnZipFile::ExtractAll( ExpandedZipFile & expanded_zip_file )
+{
+ expanded_zip_file.Dispose();
+
+ { // Allocate memory for the zip file entries
+ unz_global_info global_info;
+ unzGetGlobalInfo (uf, &global_info);
+ expanded_zip_file.ResizeEntries(global_info.number_entry);
+ }
+
+ { // Get total memory requirements for filenames and data
+ unsigned total_size = 0;
+ unsigned total_filename_size = 0;
+
+ unzGoToFirstFile(uf);
+ do {
+ unz_file_info file_info;
+ unzGetCurrentFileInfo(uf,&file_info,NULL,0,NULL,0,NULL,0);
+ total_size += file_info.uncompressed_size + 1; // Extra room for '\0'
+ total_filename_size += file_info.size_filename + 1;
+ } while(unzGoToNextFile(uf) == UNZ_OK);
+
+ expanded_zip_file.ResizeDataBuffer(total_size);
+ expanded_zip_file.ResizeFilenameBuffer(total_filename_size);
+ }
+
+ { // Load filenames and decompress data
+ unsigned entry_id = 0;
+ unsigned data_offset = 0;
+ unsigned filename_offset = 0;
+ ScopedBuffer scoped_buf(size_buf);
+ unzGoToFirstFile(uf);
+ do {
+ char filename_buf[256];
+ unz_file_info file_info;
+ unzGetCurrentFileInfo(uf,&file_info,filename_buf,256,NULL,0,NULL,0);
+ if(file_info.size_filename > 256){
+ FatalError("Error","Zip file contains filename with length greater than 256");
+ }
+ expanded_zip_file.SetEntry(entry_id, filename_offset, data_offset, file_info.uncompressed_size);
+ ++entry_id;
+ expanded_zip_file.SetFilename(filename_offset, filename_buf, file_info.size_filename + 1);
+ filename_offset += file_info.size_filename + 1;
+
+ unzOpenCurrentFile(uf);
+ int bytes_read = 0;
+ do {
+ bytes_read = unzReadCurrentFile(uf,scoped_buf.ptr,size_buf);
+ if(bytes_read < 0) {
+ FatalError("Error","Error reading from UnZip file");
+ } else if (bytes_read>0) {
+ expanded_zip_file.SetData(data_offset, (char*)scoped_buf.ptr, bytes_read);
+ data_offset += bytes_read;
+ }
+ } while (bytes_read>0);
+ char zero = '\0';
+ expanded_zip_file.SetData(data_offset, &zero, 1);
+ ++data_offset;
+ unzCloseCurrentFile(uf);
+ } while(unzGoToNextFile(uf) == UNZ_OK);
+ }
+}
+
+void UnZip(const std::string &zip_file_path,
+ ExpandedZipFile &expanded_zip_file)
+{
+ UnZipFile uf;
+ #ifdef _WIN32
+ // File exists, so just get the short path
+ std::wstring short_zip_file_path(GetShortPathNameW(UTF16fromUTF8(zip_file_path).c_str(), NULL, NULL), '\0');
+ GetShortPathNameW(UTF16fromUTF8(zip_file_path).c_str(), &short_zip_file_path[0], short_zip_file_path.size());
+ uf.Load(UTF8fromUTF16(short_zip_file_path));
+ #else
+ uf.Load(zip_file_path);
+ #endif
+ uf.ExtractAll(expanded_zip_file);
+}
+
+void UnZipToFile(const std::string &dst_file_path,
+ const std::string &zip_file_path,
+ const std::string &in_zip_file_path,
+ OverwriteType overwrite)
+{
+ if(overwrite == _NO_OVERWRITE && check_exist_file(zip_file_path.c_str())!=0){
+ return;
+ }
+
+ UnZipFile uf;
+ uf.Load(zip_file_path);
+ uf.Extract(in_zip_file_path, dst_file_path, overwrite);
+}
+
+void PrintZipFileInfo(const std::string &zip_file_path){
+ UnZipFile uf;
+ uf.Load(zip_file_path);
+ LOGI << "Zip file \"" << zip_file_path << "\" contains:" << std::endl;
+ uf.PrintInfo();
+}
diff --git a/Source/Internal/zip_util.h b/Source/Internal/zip_util.h
new file mode 100644
index 00000000..20eb938c
--- /dev/null
+++ b/Source/Internal/zip_util.h
@@ -0,0 +1,56 @@
+//-----------------------------------------------------------------------------
+// Name: zip_util.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <string>
+
+enum OverwriteType {
+ _NO_OVERWRITE,
+ _YES_OVERWRITE,
+ _APPEND_OVERWRITE
+};
+
+struct ExpandedZipEntry;
+class ExpandedZipFile {
+ char *buf;
+ char *filename_buf;
+ ExpandedZipEntry *entries;
+ unsigned num_entries;
+
+public:
+ ExpandedZipFile();
+ ~ExpandedZipFile();
+ void Dispose();
+ void ResizeEntries(unsigned num_entries);
+ void ResizeFilenameBuffer(unsigned num_chars);
+ void ResizeDataBuffer(unsigned num_bytes);
+ void SetFilename(unsigned offset, const char* data, unsigned size);
+ void SetData(unsigned offset, const char* data, unsigned size);
+ void SetEntry(unsigned which, unsigned file_name_offset, unsigned data_offset, unsigned size);
+ void GetEntry( unsigned which, const char* &filename, const char* &data, unsigned &size );
+ unsigned GetNumEntries(){ return num_entries; }
+};
+
+void Zip(const std::string &src_file_path, const std::string &zip_file_path, const std::string &in_zip_file_path, OverwriteType overwrite);
+void UnZip(const std::string &zip_file_path, ExpandedZipFile &expanded_zip_file);
+void PrintZipFileInfo(const std::string &zip_file_path);
diff --git a/Source/JSON/json-forwards.h b/Source/JSON/json-forwards.h
new file mode 100644
index 00000000..76dc558b
--- /dev/null
+++ b/Source/JSON/json-forwards.h
@@ -0,0 +1,262 @@
+//-----------------------------------------------------------------------------
+// Name: json-forwards.h
+// Developer: Wolfire Games LLC
+// Description: Some modifications have been done for use in Overgrowth
+// License: Read below
+//-----------------------------------------------------------------------------
+
+/// Json-cpp amalgated forward header (http://jsoncpp.sourceforge.net/).
+/// It is intended to be used with #include "json/json-forwards.h"
+/// This header provides forward declaration for all JsonCpp types.
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: LICENSE
+// //////////////////////////////////////////////////////////////////////
+
+/*
+The JsonCpp library's source code, including accompanying documentation,
+tests and demonstration applications, are licensed under the following
+conditions...
+
+The author (Baptiste Lepilleur) explicitly disclaims copyright in all
+jurisdictions which recognize such a disclaimer. In such jurisdictions,
+this software is released into the Public Domain.
+
+In jurisdictions which do not recognize Public Domain property (e.g. Germany as of
+2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur, and is
+released under the terms of the MIT License (see below).
+
+In jurisdictions which recognize Public Domain property, the user of this
+software may choose to accept it either as 1) Public Domain, 2) under the
+conditions of the MIT License (see below), or 3) under the terms of dual
+Public Domain/MIT License conditions described here, as they choose.
+
+The MIT License is about as close to Public Domain as a license can get, and is
+described in clear, concise terms at:
+
+ http://en.wikipedia.org/wiki/MIT_License
+
+The full text of the MIT License follows:
+
+========================================================================
+Copyright (c) 2007-2010 Baptiste Lepilleur
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use, copy,
+modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+========================================================================
+(END LICENSE TEXT)
+
+The MIT license is compatible with both the GPL and commercial
+software, affording one all of the rights of Public Domain with the
+minor nuisance of being required to keep the above copyright notice
+and license text in the source code. Note also that by accepting the
+Public Domain "license" you can re-license your copy using whatever
+license you like.
+
+*/
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: LICENSE
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+#ifndef JSON_FORWARD_AMALGATED_H_INCLUDED
+# define JSON_FORWARD_AMALGATED_H_INCLUDED
+/// If defined, indicates that the source file is amalgated
+/// to prevent private header inclusion.
+#define JSON_IS_AMALGAMATION
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: include/json/config.h
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2007-2010 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+#ifndef JSON_CONFIG_H_INCLUDED
+#define JSON_CONFIG_H_INCLUDED
+
+/// If defined, indicates that json library is embedded in CppTL library.
+//# define JSON_IN_CPPTL 1
+
+/// If defined, indicates that json may leverage CppTL library
+//# define JSON_USE_CPPTL 1
+/// If defined, indicates that cpptl vector based map should be used instead of
+/// std::map
+/// as Value container.
+//# define JSON_USE_CPPTL_SMALLMAP 1
+
+// If non-zero, the library uses exceptions to report bad input instead of C
+// assertion macros. The default is to use exceptions.
+#ifndef JSON_USE_EXCEPTION
+#define JSON_USE_EXCEPTION 1
+#endif
+
+/// If defined, indicates that the source file is amalgated
+/// to prevent private header inclusion.
+/// Remarks: it is automatically defined in the generated amalgated header.
+// #define JSON_IS_AMALGAMATION
+
+#ifdef JSON_IN_CPPTL
+#include <cpptl/config.h>
+#ifndef JSON_USE_CPPTL
+#define JSON_USE_CPPTL 1
+#endif
+#endif
+
+#ifdef JSON_IN_CPPTL
+#define JSON_API CPPTL_API
+#elif defined(JSON_DLL_BUILD)
+#if defined(_MSC_VER)
+#define JSON_API __declspec(dllexport)
+#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING
+#endif // if defined(_MSC_VER)
+#elif defined(JSON_DLL)
+#if defined(_MSC_VER)
+#define JSON_API __declspec(dllimport)
+#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING
+#endif // if defined(_MSC_VER)
+#endif // ifdef JSON_IN_CPPTL
+#if !defined(JSON_API)
+#define JSON_API
+#endif
+
+// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for
+// integer
+// Storages, and 64 bits integer support is disabled.
+// #define JSON_NO_INT64 1
+
+#if defined(_MSC_VER) && _MSC_VER <= 1200 // MSVC 6
+// Microsoft Visual Studio 6 only support conversion from __int64 to double
+// (no conversion from unsigned __int64).
+#define JSON_USE_INT64_DOUBLE_CONVERSION 1
+// Disable warning 4786 for VS6 caused by STL (identifier was truncated to '255'
+// characters in the debug information)
+// All projects I've ever seen with VS6 were using this globally (not bothering
+// with pragma push/pop).
+#pragma warning(disable : 4786)
+#endif // if defined(_MSC_VER) && _MSC_VER < 1200 // MSVC 6
+
+#if defined(_MSC_VER) && _MSC_VER >= 1500 // MSVC 2008
+/// Indicates that the following function is deprecated.
+#define JSONCPP_DEPRECATED(message) __declspec(deprecated(message))
+#elif defined(__clang__) && defined(__has_feature)
+#if __has_feature(attribute_deprecated_with_message)
+#define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message)))
+#endif
+#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5))
+#define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message)))
+#elif defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))
+#define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__))
+#endif
+
+#if !defined(JSONCPP_DEPRECATED)
+#define JSONCPP_DEPRECATED(message)
+#endif // if !defined(JSONCPP_DEPRECATED)
+
+namespace Json {
+typedef int Int;
+typedef unsigned int UInt;
+#if defined(JSON_NO_INT64)
+typedef int LargestInt;
+typedef unsigned int LargestUInt;
+#undef JSON_HAS_INT64
+#else // if defined(JSON_NO_INT64)
+// For Microsoft Visual use specific types as long long is not supported
+#if defined(_MSC_VER) // Microsoft Visual Studio
+typedef __int64 Int64;
+typedef unsigned __int64 UInt64;
+#else // if defined(_MSC_VER) // Other platforms, use long long
+typedef long long int Int64;
+typedef unsigned long long int UInt64;
+#endif // if defined(_MSC_VER)
+typedef Int64 LargestInt;
+typedef UInt64 LargestUInt;
+#define JSON_HAS_INT64
+#endif // if defined(JSON_NO_INT64)
+} // end namespace Json
+
+#endif // JSON_CONFIG_H_INCLUDED
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: include/json/config.h
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: include/json/forwards.h
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2007-2010 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+#ifndef JSON_FORWARDS_H_INCLUDED
+#define JSON_FORWARDS_H_INCLUDED
+
+#if !defined(JSON_IS_AMALGAMATION)
+#include "config.h"
+#endif // if !defined(JSON_IS_AMALGAMATION)
+
+namespace Json {
+
+// writer.h
+class FastWriter;
+class StyledWriter;
+
+// reader.h
+class Reader;
+
+// features.h
+class Features;
+
+// value.h
+typedef unsigned int ArrayIndex;
+class StaticString;
+class Path;
+class PathArgument;
+class Value;
+class ValueIteratorBase;
+class ValueIterator;
+class ValueConstIterator;
+
+} // namespace Json
+
+#endif // JSON_FORWARDS_H_INCLUDED
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: include/json/forwards.h
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+#endif //ifndef JSON_FORWARD_AMALGATED_H_INCLUDED
diff --git a/Source/JSON/json.h b/Source/JSON/json.h
new file mode 100644
index 00000000..80f28412
--- /dev/null
+++ b/Source/JSON/json.h
@@ -0,0 +1,1975 @@
+//-----------------------------------------------------------------------------
+// Name: json.h
+// Developer: Wolfire Games LLC
+// Description: Some modifications have been done for use in Overgrowth
+// License: Read below
+//-----------------------------------------------------------------------------
+
+/// Json-cpp amalgated header (http://jsoncpp.sourceforge.net/).
+/// It is intended to be used with #include "json/json.h"
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: LICENSE
+// //////////////////////////////////////////////////////////////////////
+
+/*
+The JsonCpp library's source code, including accompanying documentation,
+tests and demonstration applications, are licensed under the following
+conditions...
+
+The author (Baptiste Lepilleur) explicitly disclaims copyright in all
+jurisdictions which recognize such a disclaimer. In such jurisdictions,
+this software is released into the Public Domain.
+
+In jurisdictions which do not recognize Public Domain property (e.g. Germany as of
+2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur, and is
+released under the terms of the MIT License (see below).
+
+In jurisdictions which recognize Public Domain property, the user of this
+software may choose to accept it either as 1) Public Domain, 2) under the
+conditions of the MIT License (see below), or 3) under the terms of dual
+Public Domain/MIT License conditions described here, as they choose.
+
+The MIT License is about as close to Public Domain as a license can get, and is
+described in clear, concise terms at:
+
+ http://en.wikipedia.org/wiki/MIT_License
+
+The full text of the MIT License follows:
+
+========================================================================
+Copyright (c) 2007-2010 Baptiste Lepilleur
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use, copy,
+modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+========================================================================
+(END LICENSE TEXT)
+
+The MIT license is compatible with both the GPL and commercial
+software, affording one all of the rights of Public Domain with the
+minor nuisance of being required to keep the above copyright notice
+and license text in the source code. Note also that by accepting the
+Public Domain "license" you can re-license your copy using whatever
+license you like.
+
+*/
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: LICENSE
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+#ifndef JSON_AMALGATED_H_INCLUDED
+# define JSON_AMALGATED_H_INCLUDED
+/// If defined, indicates that the source file is amalgated
+/// to prevent private header inclusion.
+#define JSON_IS_AMALGAMATION
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: include/json/version.h
+// //////////////////////////////////////////////////////////////////////
+
+// DO NOT EDIT. This file (and "version") is generated by CMake.
+// Run CMake configure step to update it.
+#ifndef JSON_VERSION_H_INCLUDED
+# define JSON_VERSION_H_INCLUDED
+
+# define JSONCPP_VERSION_STRING "0.10.5"
+# define JSONCPP_VERSION_MAJOR 0
+# define JSONCPP_VERSION_MINOR 10
+# define JSONCPP_VERSION_PATCH 5
+# define JSONCPP_VERSION_QUALIFIER
+# define JSONCPP_VERSION_HEXA ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | (JSONCPP_VERSION_PATCH << 8))
+
+#endif // JSON_VERSION_H_INCLUDED
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: include/json/version.h
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: include/json/config.h
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2007-2010 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+#ifndef JSON_CONFIG_H_INCLUDED
+#define JSON_CONFIG_H_INCLUDED
+
+/// If defined, indicates that json library is embedded in CppTL library.
+//# define JSON_IN_CPPTL 1
+
+/// If defined, indicates that json may leverage CppTL library
+//# define JSON_USE_CPPTL 1
+/// If defined, indicates that cpptl vector based map should be used instead of
+/// std::map
+/// as Value container.
+//# define JSON_USE_CPPTL_SMALLMAP 1
+
+// If non-zero, the library uses exceptions to report bad input instead of C
+// assertion macros. The default is to use exceptions.
+#ifndef JSON_USE_EXCEPTION
+#define JSON_USE_EXCEPTION 1
+#endif
+
+/// If defined, indicates that the source file is amalgated
+/// to prevent private header inclusion.
+/// Remarks: it is automatically defined in the generated amalgated header.
+// #define JSON_IS_AMALGAMATION
+
+#ifdef JSON_IN_CPPTL
+#include <cpptl/config.h>
+#ifndef JSON_USE_CPPTL
+#define JSON_USE_CPPTL 1
+#endif
+#endif
+
+#ifdef JSON_IN_CPPTL
+#define JSON_API CPPTL_API
+#elif defined(JSON_DLL_BUILD)
+#if defined(_MSC_VER)
+#define JSON_API __declspec(dllexport)
+#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING
+#endif // if defined(_MSC_VER)
+#elif defined(JSON_DLL)
+#if defined(_MSC_VER)
+#define JSON_API __declspec(dllimport)
+#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING
+#endif // if defined(_MSC_VER)
+#endif // ifdef JSON_IN_CPPTL
+#if !defined(JSON_API)
+#define JSON_API
+#endif
+
+// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for
+// integer
+// Storages, and 64 bits integer support is disabled.
+// #define JSON_NO_INT64 1
+
+#if defined(_MSC_VER) && _MSC_VER <= 1200 // MSVC 6
+// Microsoft Visual Studio 6 only support conversion from __int64 to double
+// (no conversion from unsigned __int64).
+#define JSON_USE_INT64_DOUBLE_CONVERSION 1
+// Disable warning 4786 for VS6 caused by STL (identifier was truncated to '255'
+// characters in the debug information)
+// All projects I've ever seen with VS6 were using this globally (not bothering
+// with pragma push/pop).
+#pragma warning(disable : 4786)
+#endif // if defined(_MSC_VER) && _MSC_VER < 1200 // MSVC 6
+
+#if defined(_MSC_VER) && _MSC_VER >= 1500 // MSVC 2008
+/// Indicates that the following function is deprecated.
+#define JSONCPP_DEPRECATED(message) __declspec(deprecated(message))
+#elif defined(__clang__) && defined(__has_feature)
+#if __has_feature(attribute_deprecated_with_message)
+#define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message)))
+#endif
+#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5))
+#define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message)))
+#elif defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))
+#define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__))
+#endif
+
+#if !defined(JSONCPP_DEPRECATED)
+#define JSONCPP_DEPRECATED(message)
+#endif // if !defined(JSONCPP_DEPRECATED)
+
+namespace Json {
+typedef int Int;
+typedef unsigned int UInt;
+#if defined(JSON_NO_INT64)
+typedef int LargestInt;
+typedef unsigned int LargestUInt;
+#undef JSON_HAS_INT64
+#else // if defined(JSON_NO_INT64)
+// For Microsoft Visual use specific types as long long is not supported
+#if defined(_MSC_VER) // Microsoft Visual Studio
+typedef __int64 Int64;
+typedef unsigned __int64 UInt64;
+#else // if defined(_MSC_VER) // Other platforms, use long long
+typedef long long int Int64;
+typedef unsigned long long int UInt64;
+#endif // if defined(_MSC_VER)
+typedef Int64 LargestInt;
+typedef UInt64 LargestUInt;
+#define JSON_HAS_INT64
+#endif // if defined(JSON_NO_INT64)
+} // end namespace Json
+
+#endif // JSON_CONFIG_H_INCLUDED
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: include/json/config.h
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: include/json/forwards.h
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2007-2010 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+#ifndef JSON_FORWARDS_H_INCLUDED
+#define JSON_FORWARDS_H_INCLUDED
+
+#if !defined(JSON_IS_AMALGAMATION)
+#include "config.h"
+#endif // if !defined(JSON_IS_AMALGAMATION)
+
+namespace Json {
+
+// writer.h
+class FastWriter;
+class StyledWriter;
+
+// reader.h
+class Reader;
+
+// features.h
+class Features;
+
+// value.h
+typedef unsigned int ArrayIndex;
+class StaticString;
+class Path;
+class PathArgument;
+class Value;
+class ValueIteratorBase;
+class ValueIterator;
+class ValueConstIterator;
+
+} // namespace Json
+
+#endif // JSON_FORWARDS_H_INCLUDED
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: include/json/forwards.h
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: include/json/features.h
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2007-2010 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+#ifndef CPPTL_JSON_FEATURES_H_INCLUDED
+#define CPPTL_JSON_FEATURES_H_INCLUDED
+
+#if !defined(JSON_IS_AMALGAMATION)
+#include "forwards.h"
+#endif // if !defined(JSON_IS_AMALGAMATION)
+
+namespace Json {
+
+/** \brief Configuration passed to reader and writer.
+ * This configuration object can be used to force the Reader or Writer
+ * to behave in a standard conforming way.
+ */
+class JSON_API Features {
+public:
+ /** \brief A configuration that allows all features and assumes all strings
+ * are UTF-8.
+ * - C & C++ comments are allowed
+ * - Root object can be any JSON value
+ * - Assumes Value strings are encoded in UTF-8
+ */
+ static Features all();
+
+ /** \brief A configuration that is strictly compatible with the JSON
+ * specification.
+ * - Comments are forbidden.
+ * - Root object must be either an array or an object value.
+ * - Assumes Value strings are encoded in UTF-8
+ */
+ static Features strictMode();
+
+ /** \brief Initialize the configuration like JsonConfig::allFeatures;
+ */
+ Features();
+
+ /// \c true if comments are allowed. Default: \c true.
+ bool allowComments_;
+
+ /// \c true if root must be either an array or an object value. Default: \c
+ /// false.
+ bool strictRoot_;
+};
+
+} // namespace Json
+
+#endif // CPPTL_JSON_FEATURES_H_INCLUDED
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: include/json/features.h
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: include/json/value.h
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2007-2010 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+#ifndef CPPTL_JSON_H_INCLUDED
+#define CPPTL_JSON_H_INCLUDED
+
+#if !defined(JSON_IS_AMALGAMATION)
+#include "forwards.h"
+#endif // if !defined(JSON_IS_AMALGAMATION)
+#include <string>
+#include <vector>
+#include <exception>
+
+#ifndef JSON_USE_CPPTL_SMALLMAP
+#include <map>
+#else
+#include <cpptl/smallmap.h>
+#endif
+#ifdef JSON_USE_CPPTL
+#include <cpptl/forwards.h>
+#endif
+
+// Disable warning C4251: <data member>: <type> needs to have dll-interface to
+// be used by...
+#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
+#pragma warning(push)
+#pragma warning(disable : 4251)
+#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
+
+/** \brief JSON (JavaScript Object Notation).
+ */
+namespace Json {
+
+/** Base class for all exceptions we throw.
+ *
+ * We use nothing but these internally. Of course, STL can throw others.
+ */
+class JSON_API Exception : public std::exception {
+public:
+ Exception(std::string const& msg);
+ virtual ~Exception() throw();
+ virtual char const* what() const throw();
+protected:
+ std::string const msg_;
+};
+
+/** Exceptions which the user cannot easily avoid.
+ *
+ * E.g. out-of-memory (when we use malloc), stack-overflow, malicious input
+ *
+ * \remark derived from Json::Exception
+ */
+class JSON_API RuntimeError : public Exception {
+public:
+ RuntimeError(std::string const& msg);
+};
+
+/** Exceptions thrown by JSON_ASSERT/JSON_FAIL macros.
+ *
+ * These are precondition-violations (user bugs) and internal errors (our bugs).
+ *
+ * \remark derived from Json::Exception
+ */
+class JSON_API LogicError : public Exception {
+public:
+ LogicError(std::string const& msg);
+};
+
+/// used internally
+void throwRuntimeError(std::string const& msg);
+/// used internally
+void throwLogicError(std::string const& msg);
+
+/** \brief Type of the value held by a Value object.
+ */
+enum ValueType {
+ nullValue = 0, ///< 'null' value
+ intValue, ///< signed integer value
+ uintValue, ///< unsigned integer value
+ realValue, ///< double value
+ stringValue, ///< UTF-8 string value
+ booleanValue, ///< bool value
+ arrayValue, ///< array value (ordered list)
+ objectValue ///< object value (collection of name/value pairs).
+};
+
+enum CommentPlacement {
+ commentBefore = 0, ///< a comment placed on the line before a value
+ commentAfterOnSameLine, ///< a comment just after a value on the same line
+ commentAfter, ///< a comment on the line after a value (only make sense for
+ /// root value)
+ numberOfCommentPlacement
+};
+
+//# ifdef JSON_USE_CPPTL
+// typedef CppTL::AnyEnumerator<const char *> EnumMemberNames;
+// typedef CppTL::AnyEnumerator<const Value &> EnumValues;
+//# endif
+
+/** \brief Lightweight wrapper to tag static string.
+ *
+ * Value constructor and objectValue member assignement takes advantage of the
+ * StaticString and avoid the cost of string duplication when storing the
+ * string or the member name.
+ *
+ * Example of usage:
+ * \code
+ * Json::Value aValue( StaticString("some text") );
+ * Json::Value object;
+ * static const StaticString code("code");
+ * object[code] = 1234;
+ * \endcode
+ */
+class JSON_API StaticString {
+public:
+ explicit StaticString(const char* czstring) : c_str_(czstring) {}
+
+ operator const char*() const { return c_str_; }
+
+ const char* c_str() const { return c_str_; }
+
+private:
+ const char* c_str_;
+};
+
+/** \brief Represents a <a HREF="http://www.json.org">JSON</a> value.
+ *
+ * This class is a discriminated union wrapper that can represents a:
+ * - signed integer [range: Value::minInt - Value::maxInt]
+ * - unsigned integer (range: 0 - Value::maxUInt)
+ * - double
+ * - UTF-8 string
+ * - boolean
+ * - 'null'
+ * - an ordered list of Value
+ * - collection of name/value pairs (javascript object)
+ *
+ * The type of the held value is represented by a #ValueType and
+ * can be obtained using type().
+ *
+ * Values of an #objectValue or #arrayValue can be accessed using operator[]()
+ * methods.
+ * Non-const methods will automatically create the a #nullValue element
+ * if it does not exist.
+ * The sequence of an #arrayValue will be automatically resized and initialized
+ * with #nullValue. resize() can be used to enlarge or truncate an #arrayValue.
+ *
+ * The get() methods can be used to obtain default value in the case the
+ * required element does not exist.
+ *
+ * It is possible to iterate over the list of a #objectValue values using
+ * the getMemberNames() method.
+ *
+ * \note #Value string-length fit in size_t, but keys must be < 2^30.
+ * (The reason is an implementation detail.) A #CharReader will raise an
+ * exception if a bound is exceeded to avoid security holes in your app,
+ * but the Value API does *not* check bounds. That is the responsibility
+ * of the caller.
+ */
+class JSON_API Value {
+ friend class ValueIteratorBase;
+public:
+ typedef std::vector<std::string> Members;
+ typedef ValueIterator iterator;
+ typedef ValueConstIterator const_iterator;
+ typedef Json::UInt UInt;
+ typedef Json::Int Int;
+#if defined(JSON_HAS_INT64)
+ typedef Json::UInt64 UInt64;
+ typedef Json::Int64 Int64;
+#endif // defined(JSON_HAS_INT64)
+ typedef Json::LargestInt LargestInt;
+ typedef Json::LargestUInt LargestUInt;
+ typedef Json::ArrayIndex ArrayIndex;
+
+ static const Value& nullRef;
+#if !defined(__ARMEL__)
+ /// \deprecated This exists for binary compatibility only. Use nullRef.
+ static const Value null;
+#endif
+ /// Minimum signed integer value that can be stored in a Json::Value.
+ static const LargestInt minLargestInt;
+ /// Maximum signed integer value that can be stored in a Json::Value.
+ static const LargestInt maxLargestInt;
+ /// Maximum unsigned integer value that can be stored in a Json::Value.
+ static const LargestUInt maxLargestUInt;
+
+ /// Minimum signed int value that can be stored in a Json::Value.
+ static const Int minInt;
+ /// Maximum signed int value that can be stored in a Json::Value.
+ static const Int maxInt;
+ /// Maximum unsigned int value that can be stored in a Json::Value.
+ static const UInt maxUInt;
+
+#if defined(JSON_HAS_INT64)
+ /// Minimum signed 64 bits int value that can be stored in a Json::Value.
+ static const Int64 minInt64;
+ /// Maximum signed 64 bits int value that can be stored in a Json::Value.
+ static const Int64 maxInt64;
+ /// Maximum unsigned 64 bits int value that can be stored in a Json::Value.
+ static const UInt64 maxUInt64;
+#endif // defined(JSON_HAS_INT64)
+
+private:
+#ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION
+ class CZString {
+ public:
+ enum DuplicationPolicy {
+ noDuplication = 0,
+ duplicate,
+ duplicateOnCopy
+ };
+ CZString(ArrayIndex index);
+ CZString(char const* str, unsigned length, DuplicationPolicy allocate);
+ CZString(CZString const& other);
+ ~CZString();
+ CZString& operator=(CZString other);
+ bool operator<(CZString const& other) const;
+ bool operator==(CZString const& other) const;
+ ArrayIndex index() const;
+ //const char* c_str() const; ///< \deprecated
+ char const* data() const;
+ unsigned length() const;
+ bool isStaticString() const;
+
+ private:
+ void swap(CZString& other);
+
+ struct StringStorage {
+ unsigned policy_: 2;
+ unsigned length_: 30; // 1GB max
+ };
+
+ char const* cstr_; // actually, a prefixed string, unless policy is noDup
+ union {
+ ArrayIndex index_;
+ StringStorage storage_;
+ };
+ };
+
+public:
+#ifndef JSON_USE_CPPTL_SMALLMAP
+ typedef std::map<CZString, Value> ObjectValues;
+#else
+ typedef CppTL::SmallMap<CZString, Value> ObjectValues;
+#endif // ifndef JSON_USE_CPPTL_SMALLMAP
+#endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION
+
+public:
+ /** \brief Create a default Value of the given type.
+
+ This is a very useful constructor.
+ To create an empty array, pass arrayValue.
+ To create an empty object, pass objectValue.
+ Another Value can then be set to this one by assignment.
+This is useful since clear() and resize() will not alter types.
+
+ Examples:
+\code
+Json::Value null_value; // null
+Json::Value arr_value(Json::arrayValue); // []
+Json::Value obj_value(Json::objectValue); // {}
+\endcode
+ */
+ Value(ValueType type = nullValue);
+ Value(Int value);
+ Value(UInt value);
+#if defined(JSON_HAS_INT64)
+ Value(Int64 value);
+ Value(UInt64 value);
+#endif // if defined(JSON_HAS_INT64)
+ Value(double value);
+ Value(const char* value); ///< Copy til first 0. (NULL causes to seg-fault.)
+ Value(const char* begin, const char* end); ///< Copy all, incl zeroes.
+ /** \brief Constructs a value from a static string.
+
+ * Like other value string constructor but do not duplicate the string for
+ * internal storage. The given string must remain alive after the call to this
+ * constructor.
+ * \note This works only for null-terminated strings. (We cannot change the
+ * size of this class, so we have nowhere to store the length,
+ * which might be computed later for various operations.)
+ *
+ * Example of usage:
+ * \code
+ * static StaticString foo("some text");
+ * Json::Value aValue(foo);
+ * \endcode
+ */
+ Value(const StaticString& value);
+ Value(const std::string& value); ///< Copy data() til size(). Embedded zeroes too.
+#ifdef JSON_USE_CPPTL
+ Value(const CppTL::ConstString& value);
+#endif
+ Value(bool value);
+ /// Deep copy.
+ Value(const Value& other);
+ ~Value();
+
+ /// Deep copy, then swap(other).
+ /// \note Over-write existing comments. To preserve comments, use #swapPayload().
+ Value &operator=(const Value &other);
+ /// Swap everything.
+ void swap(Value& other);
+ /// Swap values but leave comments and source offsets in place.
+ void swapPayload(Value& other);
+
+ ValueType type() const;
+
+ /// Compare payload only, not comments etc.
+ bool operator<(const Value& other) const;
+ bool operator<=(const Value& other) const;
+ bool operator>=(const Value& other) const;
+ bool operator>(const Value& other) const;
+ bool operator==(const Value& other) const;
+ bool operator!=(const Value& other) const;
+ int compare(const Value& other) const;
+
+ const char* asCString() const; ///< Embedded zeroes could cause you trouble!
+ std::string asString() const; ///< Embedded zeroes are possible.
+ /** Get raw char* of string-value.
+ * \return false if !string. (Seg-fault if str or end are NULL.)
+ */
+ bool getString(
+ char const** begin, char const** end) const;
+#ifdef JSON_USE_CPPTL
+ CppTL::ConstString asConstString() const;
+#endif
+ Int asInt() const;
+ UInt asUInt() const;
+#if defined(JSON_HAS_INT64)
+ Int64 asInt64() const;
+ UInt64 asUInt64() const;
+#endif // if defined(JSON_HAS_INT64)
+ LargestInt asLargestInt() const;
+ LargestUInt asLargestUInt() const;
+ float asFloat() const;
+ double asDouble() const;
+ bool asBool() const;
+
+ bool isNull() const;
+ bool isBool() const;
+ bool isInt() const;
+ bool isInt64() const;
+ bool isUInt() const;
+ bool isUInt64() const;
+ bool isIntegral() const;
+ bool isDouble() const;
+ bool isNumeric() const;
+ bool isString() const;
+ bool isArray() const;
+ bool isObject() const;
+
+ bool isConvertibleTo(ValueType other) const;
+
+ /// Number of values in array or object
+ ArrayIndex size() const;
+
+ /// \brief Return true if empty array, empty object, or null;
+ /// otherwise, false.
+ bool empty() const;
+
+ /// Return isNull()
+ bool operator!() const;
+
+ /// Remove all object members and array elements.
+ /// \pre type() is arrayValue, objectValue, or nullValue
+ /// \post type() is unchanged
+ void clear();
+
+ /// Resize the array to size elements.
+ /// New elements are initialized to null.
+ /// May only be called on nullValue or arrayValue.
+ /// \pre type() is arrayValue or nullValue
+ /// \post type() is arrayValue
+ void resize(ArrayIndex size);
+
+ /// Access an array element (zero based index ).
+ /// If the array contains less than index element, then null value are
+ /// inserted
+ /// in the array so that its size is index+1.
+ /// (You may need to say 'value[0u]' to get your compiler to distinguish
+ /// this from the operator[] which takes a string.)
+ Value& operator[](ArrayIndex index);
+
+ /// Access an array element (zero based index ).
+ /// If the array contains less than index element, then null value are
+ /// inserted
+ /// in the array so that its size is index+1.
+ /// (You may need to say 'value[0u]' to get your compiler to distinguish
+ /// this from the operator[] which takes a string.)
+ Value& operator[](int index);
+
+ /// Access an array element (zero based index )
+ /// (You may need to say 'value[0u]' to get your compiler to distinguish
+ /// this from the operator[] which takes a string.)
+ const Value& operator[](ArrayIndex index) const;
+
+ /// Access an array element (zero based index )
+ /// (You may need to say 'value[0u]' to get your compiler to distinguish
+ /// this from the operator[] which takes a string.)
+ const Value& operator[](int index) const;
+
+ /// If the array contains at least index+1 elements, returns the element
+ /// value,
+ /// otherwise returns defaultValue.
+ Value get(ArrayIndex index, const Value& defaultValue) const;
+ /// Return true if index < size().
+ bool isValidIndex(ArrayIndex index) const;
+ /// \brief Append value to array at the end.
+ ///
+ /// Equivalent to jsonvalue[jsonvalue.size()] = value;
+ Value& append(const Value& value);
+
+ /// Access an object value by name, create a null member if it does not exist.
+ /// \note Because of our implementation, keys are limited to 2^30 -1 chars.
+ /// Exceeding that will cause an exception.
+ Value& operator[](const char* key);
+ /// Access an object value by name, returns null if there is no member with
+ /// that name.
+ const Value& operator[](const char* key) const;
+ /// Access an object value by name, create a null member if it does not exist.
+ /// \param key may contain embedded nulls.
+ Value& operator[](const std::string& key);
+ /// Access an object value by name, returns null if there is no member with
+ /// that name.
+ /// \param key may contain embedded nulls.
+ const Value& operator[](const std::string& key) const;
+ /** \brief Access an object value by name, create a null member if it does not
+ exist.
+
+ * If the object has no entry for that name, then the member name used to store
+ * the new entry is not duplicated.
+ * Example of use:
+ * \code
+ * Json::Value object;
+ * static const StaticString code("code");
+ * object[code] = 1234;
+ * \endcode
+ */
+ Value& operator[](const StaticString& key);
+#ifdef JSON_USE_CPPTL
+ /// Access an object value by name, create a null member if it does not exist.
+ Value& operator[](const CppTL::ConstString& key);
+ /// Access an object value by name, returns null if there is no member with
+ /// that name.
+ const Value& operator[](const CppTL::ConstString& key) const;
+#endif
+ /// Return the member named key if it exist, defaultValue otherwise.
+ /// \note deep copy
+ Value get(const char* key, const Value& defaultValue) const;
+ /// Return the member named key if it exist, defaultValue otherwise.
+ /// \note deep copy
+ /// \note key may contain embedded nulls.
+ Value get(const char* begin, const char* end, const Value& defaultValue) const;
+ /// Return the member named key if it exist, defaultValue otherwise.
+ /// \note deep copy
+ /// \param key may contain embedded nulls.
+ Value get(const std::string& key, const Value& defaultValue) const;
+#ifdef JSON_USE_CPPTL
+ /// Return the member named key if it exist, defaultValue otherwise.
+ /// \note deep copy
+ Value get(const CppTL::ConstString& key, const Value& defaultValue) const;
+#endif
+ /// Most general and efficient version of isMember()const, get()const,
+ /// and operator[]const
+ /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30
+ Value const* find(char const* begin, char const* end) const;
+ /// Most general and efficient version of object-mutators.
+ /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30
+ /// \return non-zero, but JSON_ASSERT if this is neither object nor nullValue.
+ Value const* demand(char const* begin, char const* end);
+ /// \brief Remove and return the named member.
+ ///
+ /// Do nothing if it did not exist.
+ /// \return the removed Value, or null.
+ /// \pre type() is objectValue or nullValue
+ /// \post type() is unchanged
+ /// \deprecated
+ Value removeMember(const char* key);
+ /// Same as removeMember(const char*)
+ /// \param key may contain embedded nulls.
+ /// \deprecated
+ Value removeMember(const std::string& key);
+ /// Same as removeMember(const char* begin, const char* end, Value* removed),
+ /// but 'key' is null-terminated.
+ bool removeMember(const char* key, Value* removed);
+ /** \brief Remove the named map member.
+
+ Update 'removed' iff removed.
+ \param key may contain embedded nulls.
+ \return true iff removed (no exceptions)
+ */
+ bool removeMember(std::string const& key, Value* removed);
+ /// Same as removeMember(std::string const& key, Value* removed)
+ bool removeMember(const char* begin, const char* end, Value* removed);
+ /** \brief Remove the indexed array element.
+
+ O(n) expensive operations.
+ Update 'removed' iff removed.
+ \return true iff removed (no exceptions)
+ */
+ bool removeIndex(ArrayIndex i, Value* removed);
+
+ /// Return true if the object has a member named key.
+ /// \note 'key' must be null-terminated.
+ bool isMember(const char* key) const;
+ /// Return true if the object has a member named key.
+ /// \param key may contain embedded nulls.
+ bool isMember(const std::string& key) const;
+ /// Same as isMember(std::string const& key)const
+ bool isMember(const char* begin, const char* end) const;
+#ifdef JSON_USE_CPPTL
+ /// Return true if the object has a member named key.
+ bool isMember(const CppTL::ConstString& key) const;
+#endif
+
+ /// \brief Return a list of the member names.
+ ///
+ /// If null, return an empty list.
+ /// \pre type() is objectValue or nullValue
+ /// \post if type() was nullValue, it remains nullValue
+ Members getMemberNames() const;
+
+ //# ifdef JSON_USE_CPPTL
+ // EnumMemberNames enumMemberNames() const;
+ // EnumValues enumValues() const;
+ //# endif
+
+ /// \deprecated Always pass len.
+ JSONCPP_DEPRECATED("Use setComment(std::string const&) instead.")
+ void setComment(const char* comment, CommentPlacement placement);
+ /// Comments must be //... or /* ... */
+ void setComment(const char* comment, size_t len, CommentPlacement placement);
+ /// Comments must be //... or /* ... */
+ void setComment(const std::string& comment, CommentPlacement placement);
+ bool hasComment(CommentPlacement placement) const;
+ /// Include delimiters and embedded newlines.
+ std::string getComment(CommentPlacement placement) const;
+
+ std::string toStyledString() const;
+
+ const_iterator begin() const;
+ const_iterator end() const;
+
+ iterator begin();
+ iterator end();
+
+private:
+ void initBasic(ValueType type, bool allocated = false);
+
+ Value& resolveReference(const char* key);
+ Value& resolveReference(const char* key, const char* end);
+
+ struct CommentInfo {
+ CommentInfo();
+ ~CommentInfo();
+
+ void setComment(const char* text, size_t len);
+
+ char* comment_;
+ };
+
+ // struct MemberNamesTransform
+ //{
+ // typedef const char *result_type;
+ // const char *operator()( const CZString &name ) const
+ // {
+ // return name.c_str();
+ // }
+ //};
+
+ union ValueHolder {
+ LargestInt int_;
+ LargestUInt uint_;
+ double real_;
+ bool bool_;
+ char* string_; // actually ptr to unsigned, followed by str, unless !allocated_
+ ObjectValues* map_;
+ } value_;
+ ValueType type_ : 8;
+ unsigned int allocated_ : 1; // Notes: if declared as bool, bitfield is useless.
+ // If not allocated_, string_ must be null-terminated.
+ CommentInfo* comments_;
+};
+
+/** \brief Experimental and untested: represents an element of the "path" to
+ * access a node.
+ */
+class JSON_API PathArgument {
+public:
+ friend class Path;
+
+ PathArgument();
+ PathArgument(ArrayIndex index);
+ PathArgument(const char* key);
+ PathArgument(const std::string& key);
+
+private:
+ enum Kind {
+ kindNone = 0,
+ kindIndex,
+ kindKey
+ };
+ std::string key_;
+ ArrayIndex index_;
+ Kind kind_;
+};
+
+/** \brief Experimental and untested: represents a "path" to access a node.
+ *
+ * Syntax:
+ * - "." => root node
+ * - ".[n]" => elements at index 'n' of root node (an array value)
+ * - ".name" => member named 'name' of root node (an object value)
+ * - ".name1.name2.name3"
+ * - ".[0][1][2].name1[3]"
+ * - ".%" => member name is provided as parameter
+ * - ".[%]" => index is provied as parameter
+ */
+class JSON_API Path {
+public:
+ Path(const std::string& path,
+ const PathArgument& a1 = PathArgument(),
+ const PathArgument& a2 = PathArgument(),
+ const PathArgument& a3 = PathArgument(),
+ const PathArgument& a4 = PathArgument(),
+ const PathArgument& a5 = PathArgument());
+
+ const Value& resolve(const Value& root) const;
+ Value resolve(const Value& root, const Value& defaultValue) const;
+ /// Creates the "path" to access the specified node and returns a reference on
+ /// the node.
+ Value& make(Value& root) const;
+
+private:
+ typedef std::vector<const PathArgument*> InArgs;
+ typedef std::vector<PathArgument> Args;
+
+ void makePath(const std::string& path, const InArgs& in);
+ void addPathInArg(const std::string& path,
+ const InArgs& in,
+ InArgs::const_iterator& itInArg,
+ PathArgument::Kind kind);
+ void invalidPath(const std::string& path, int location);
+
+ Args args_;
+};
+
+/** \brief base class for Value iterators.
+ *
+ */
+class JSON_API ValueIteratorBase {
+public:
+ typedef std::bidirectional_iterator_tag iterator_category;
+ typedef unsigned int size_t;
+ typedef int difference_type;
+ typedef ValueIteratorBase SelfType;
+
+ bool operator==(const SelfType& other) const { return isEqual(other); }
+
+ bool operator!=(const SelfType& other) const { return !isEqual(other); }
+
+ difference_type operator-(const SelfType& other) const {
+ return other.computeDistance(*this);
+ }
+
+ /// Return either the index or the member name of the referenced value as a
+ /// Value.
+ Value key() const;
+
+ /// Return the index of the referenced Value, or -1 if it is not an arrayValue.
+ UInt index() const;
+
+ /// Return the member name of the referenced Value, or "" if it is not an
+ /// objectValue.
+ /// \note Avoid `c_str()` on result, as embedded zeroes are possible.
+ std::string name() const;
+
+ /// Return the member name of the referenced Value. "" if it is not an
+ /// objectValue.
+ /// \deprecated This cannot be used for UTF-8 strings, since there can be embedded nulls.
+ JSONCPP_DEPRECATED("Use `key = name();` instead.")
+ char const* memberName() const;
+ /// Return the member name of the referenced Value, or NULL if it is not an
+ /// objectValue.
+ /// \note Better version than memberName(). Allows embedded nulls.
+ char const* memberName(char const** end) const;
+
+protected:
+ Value& deref() const;
+
+ void increment();
+
+ void decrement();
+
+ difference_type computeDistance(const SelfType& other) const;
+
+ bool isEqual(const SelfType& other) const;
+
+ void copy(const SelfType& other);
+
+private:
+ Value::ObjectValues::iterator current_;
+ // Indicates that iterator is for a null value.
+ bool isNull_;
+
+public:
+ // For some reason, BORLAND needs these at the end, rather
+ // than earlier. No idea why.
+ ValueIteratorBase();
+ explicit ValueIteratorBase(const Value::ObjectValues::iterator& current);
+};
+
+/** \brief const iterator for object and array value.
+ *
+ */
+class JSON_API ValueConstIterator : public ValueIteratorBase {
+ friend class Value;
+
+public:
+ typedef const Value value_type;
+ //typedef unsigned int size_t;
+ //typedef int difference_type;
+ typedef const Value& reference;
+ typedef const Value* pointer;
+ typedef ValueConstIterator SelfType;
+
+ ValueConstIterator();
+
+private:
+/*! \internal Use by Value to create an iterator.
+ */
+ explicit ValueConstIterator(const Value::ObjectValues::iterator& current);
+public:
+ SelfType& operator=(const ValueIteratorBase& other);
+
+ SelfType operator++(int) {
+ SelfType temp(*this);
+ ++*this;
+ return temp;
+ }
+
+ SelfType operator--(int) {
+ SelfType temp(*this);
+ --*this;
+ return temp;
+ }
+
+ SelfType& operator--() {
+ decrement();
+ return *this;
+ }
+
+ SelfType& operator++() {
+ increment();
+ return *this;
+ }
+
+ reference operator*() const { return deref(); }
+
+ pointer operator->() const { return &deref(); }
+};
+
+/** \brief Iterator for object and array value.
+ */
+class JSON_API ValueIterator : public ValueIteratorBase {
+ friend class Value;
+
+public:
+ typedef Value value_type;
+ typedef unsigned int size_t;
+ typedef int difference_type;
+ typedef Value& reference;
+ typedef Value* pointer;
+ typedef ValueIterator SelfType;
+
+ ValueIterator();
+ ValueIterator(const ValueConstIterator& other);
+ ValueIterator(const ValueIterator& other);
+
+private:
+/*! \internal Use by Value to create an iterator.
+ */
+ explicit ValueIterator(const Value::ObjectValues::iterator& current);
+public:
+ SelfType& operator=(const SelfType& other);
+
+ SelfType operator++(int) {
+ SelfType temp(*this);
+ ++*this;
+ return temp;
+ }
+
+ SelfType operator--(int) {
+ SelfType temp(*this);
+ --*this;
+ return temp;
+ }
+
+ SelfType& operator--() {
+ decrement();
+ return *this;
+ }
+
+ SelfType& operator++() {
+ increment();
+ return *this;
+ }
+
+ reference operator*() const { return deref(); }
+
+ pointer operator->() const { return &deref(); }
+};
+
+} // namespace Json
+
+
+namespace std {
+/// Specialize std::swap() for Json::Value.
+template<>
+inline void swap(Json::Value& a, Json::Value& b) { a.swap(b); }
+}
+
+
+#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
+#pragma warning(pop)
+#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
+
+#endif // CPPTL_JSON_H_INCLUDED
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: include/json/value.h
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: include/json/reader.h
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2007-2010 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+#ifndef CPPTL_JSON_READER_H_INCLUDED
+#define CPPTL_JSON_READER_H_INCLUDED
+
+#if !defined(JSON_IS_AMALGAMATION)
+#include "features.h"
+#include "value.h"
+#endif // if !defined(JSON_IS_AMALGAMATION)
+#include <deque>
+#include <iosfwd>
+#include <stack>
+#include <string>
+#include <istream>
+
+// Disable warning C4251: <data member>: <type> needs to have dll-interface to
+// be used by...
+#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
+#pragma warning(push)
+#pragma warning(disable : 4251)
+#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
+
+namespace Json {
+
+/** \brief Unserialize a <a HREF="http://www.json.org">JSON</a> document into a
+ *Value.
+ *
+ * \deprecated Use CharReader and CharReaderBuilder.
+ */
+class JSON_API Reader {
+public:
+ typedef char Char;
+ typedef const Char* Location;
+
+ /** \brief Constructs a Reader allowing all features
+ * for parsing.
+ */
+ Reader();
+
+ /** \brief Constructs a Reader allowing the specified feature set
+ * for parsing.
+ */
+ Reader(const Features& features);
+
+ /** \brief Read a Value from a <a HREF="http://www.json.org">JSON</a>
+ * document.
+ * \param document UTF-8 encoded string containing the document to read.
+ * \param root [out] Contains the root value of the document if it was
+ * successfully parsed.
+ * \param collectComments \c true to collect comment and allow writing them
+ * back during
+ * serialization, \c false to discard comments.
+ * This parameter is ignored if
+ * Features::allowComments_
+ * is \c false.
+ * \return \c true if the document was successfully parsed, \c false if an
+ * error occurred.
+ */
+ bool
+ parse(const std::string& document, Value& root, bool collectComments = true);
+
+ /** \brief Read a Value from a <a HREF="http://www.json.org">JSON</a>
+ document.
+ * \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the
+ document to read.
+ * \param endDoc Pointer on the end of the UTF-8 encoded string of the
+ document to read.
+ * Must be >= beginDoc.
+ * \param root [out] Contains the root value of the document if it was
+ * successfully parsed.
+ * \param collectComments \c true to collect comment and allow writing them
+ back during
+ * serialization, \c false to discard comments.
+ * This parameter is ignored if
+ Features::allowComments_
+ * is \c false.
+ * \return \c true if the document was successfully parsed, \c false if an
+ error occurred.
+ */
+ bool parse(const char* beginDoc,
+ const char* endDoc,
+ Value& root,
+ bool collectComments = true);
+
+ /// \brief Parse from input stream.
+ /// \see Json::operator>>(std::istream&, Json::Value&).
+ bool parse(std::istream& is, Value& root, bool collectComments = true);
+
+ /** \brief Returns a user friendly string that list errors in the parsed
+ * document.
+ * \return Formatted error message with the list of errors with their location
+ * in
+ * the parsed document. An empty string is returned if no error
+ * occurred
+ * during parsing.
+ * \deprecated Use getFormattedErrorMessages() instead (typo fix).
+ */
+ JSONCPP_DEPRECATED("Use getFormattedErrorMessages() instead.")
+ std::string getFormatedErrorMessages() const;
+
+ /** \brief Returns a user friendly string that list errors in the parsed
+ * document.
+ * \return Formatted error message with the list of errors with their location
+ * in
+ * the parsed document. An empty string is returned if no error
+ * occurred
+ * during parsing.
+ */
+ std::string getFormattedErrorMessages() const;
+
+private:
+ enum TokenType {
+ tokenEndOfStream = 0,
+ tokenObjectBegin,
+ tokenObjectEnd,
+ tokenArrayBegin,
+ tokenArrayEnd,
+ tokenString,
+ tokenNumber,
+ tokenTrue,
+ tokenFalse,
+ tokenNull,
+ tokenArraySeparator,
+ tokenMemberSeparator,
+ tokenComment,
+ tokenError
+ };
+
+ class Token {
+ public:
+ TokenType type_;
+ Location start_;
+ Location end_;
+ };
+
+ class ErrorInfo {
+ public:
+ Token token_;
+ std::string message_;
+ Location extra_;
+ };
+
+ typedef std::deque<ErrorInfo> Errors;
+
+ bool readToken(Token& token);
+ void skipSpaces();
+ bool match(Location pattern, int patternLength);
+ bool readComment();
+ bool readCStyleComment();
+ bool readCppStyleComment();
+ bool readString();
+ void readNumber();
+ bool readValue();
+ bool readObject(Token& token);
+ bool readArray(Token& token);
+ bool decodeNumber(Token& token);
+ bool decodeNumber(Token& token, Value& decoded);
+ bool decodeString(Token& token);
+ bool decodeString(Token& token, std::string& decoded);
+ bool decodeDouble(Token& token);
+ bool decodeDouble(Token& token, Value& decoded);
+ bool decodeUnicodeCodePoint(Token& token,
+ Location& current,
+ Location end,
+ unsigned int& unicode);
+ bool decodeUnicodeEscapeSequence(Token& token,
+ Location& current,
+ Location end,
+ unsigned int& unicode);
+ bool addError(const std::string& message, Token& token, Location extra = 0);
+ bool recoverFromError(TokenType skipUntilToken);
+ bool addErrorAndRecover(const std::string& message,
+ Token& token,
+ TokenType skipUntilToken);
+ void skipUntilSpace();
+ Value& currentValue();
+ Char getNextChar();
+ void
+ getLocationLineAndColumn(Location location, int& line, int& column) const;
+ std::string getLocationLineAndColumn(Location location) const;
+ void addComment(Location begin, Location end, CommentPlacement placement);
+ void skipCommentTokens(Token& token);
+
+ typedef std::stack<Value*> Nodes;
+ Nodes nodes_;
+ Errors errors_;
+ std::string document_;
+ Location begin_;
+ Location end_;
+ Location current_;
+ Location lastValueEnd_;
+ Value* lastValue_;
+ std::string commentsBefore_;
+ Features features_;
+ bool collectComments_;
+}; // Reader
+
+/** Interface for reading JSON from a char array.
+ */
+class JSON_API CharReader {
+public:
+ virtual ~CharReader() {}
+ /** \brief Read a Value from a <a HREF="http://www.json.org">JSON</a>
+ document.
+ * The document must be a UTF-8 encoded string containing the document to read.
+ *
+ * \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the
+ document to read.
+ * \param endDoc Pointer on the end of the UTF-8 encoded string of the
+ document to read.
+ * Must be >= beginDoc.
+ * \param root [out] Contains the root value of the document if it was
+ * successfully parsed.
+ * \param errs [out] Formatted error messages (if not NULL)
+ * a user friendly string that lists errors in the parsed
+ * document.
+ * \return \c true if the document was successfully parsed, \c false if an
+ error occurred.
+ */
+ virtual bool parse(
+ char const* beginDoc, char const* endDoc,
+ Value* root, std::string* errs) = 0;
+
+ class Factory {
+ public:
+ virtual ~Factory() {}
+ /** \brief Allocate a CharReader via operator new().
+ * \throw std::exception if something goes wrong (e.g. invalid settings)
+ */
+ virtual CharReader* newCharReader() const = 0;
+ }; // Factory
+}; // CharReader
+
+/** \brief Build a CharReader implementation.
+
+Usage:
+\code
+ using namespace Json;
+ CharReaderBuilder builder;
+ builder["collectComments"] = false;
+ Value value;
+ std::string errs;
+ bool ok = parseFromStream(builder, std::cin, &value, &errs);
+\endcode
+*/
+class JSON_API CharReaderBuilder : public CharReader::Factory {
+public:
+ // Note: We use a Json::Value so that we can add data-members to this class
+ // without a major version bump.
+ /** Configuration of this builder.
+ These are case-sensitive.
+ Available settings (case-sensitive):
+ - `"collectComments": false or true`
+ - true to collect comment and allow writing them
+ back during serialization, false to discard comments.
+ This parameter is ignored if allowComments is false.
+ - `"allowComments": false or true`
+ - true if comments are allowed.
+ - `"strictRoot": false or true`
+ - true if root must be either an array or an object value
+ - `"allowDroppedNullPlaceholders": false or true`
+ - true if dropped null placeholders are allowed. (See StreamWriterBuilder.)
+ - `"allowNumericKeys": false or true`
+ - true if numeric object keys are allowed.
+ - `"allowSingleQuotes": false or true`
+ - true if '' are allowed for strings (both keys and values)
+ - `"stackLimit": integer`
+ - Exceeding stackLimit (recursive depth of `readValue()`) will
+ cause an exception.
+ - This is a security issue (seg-faults caused by deeply nested JSON),
+ so the default is low.
+ - `"failIfExtra": false or true`
+ - If true, `parse()` returns false when extra non-whitespace trails
+ the JSON value in the input string.
+ - `"rejectDupKeys": false or true`
+ - If true, `parse()` returns false when a key is duplicated within an object.
+ - `"allowSpecialFloats": false or true`
+ - If true, special float values (NaNs and infinities) are allowed
+ and their values are lossfree restorable.
+
+ You can examine 'settings_` yourself
+ to see the defaults. You can also write and read them just like any
+ JSON Value.
+ \sa setDefaults()
+ */
+ Json::Value settings_;
+
+ CharReaderBuilder();
+ virtual ~CharReaderBuilder();
+
+ virtual CharReader* newCharReader() const;
+
+ /** \return true if 'settings' are legal and consistent;
+ * otherwise, indicate bad settings via 'invalid'.
+ */
+ bool validate(Json::Value* invalid) const;
+
+ /** A simple way to update a specific setting.
+ */
+ Value& operator[](std::string key);
+
+ /** Called by ctor, but you can use this to reset settings_.
+ * \pre 'settings' != NULL (but Json::null is fine)
+ * \remark Defaults:
+ * \snippet src/lib_json/json_reader.cpp CharReaderBuilderDefaults
+ */
+ static void setDefaults(Json::Value* settings);
+ /** Same as old Features::strictMode().
+ * \pre 'settings' != NULL (but Json::null is fine)
+ * \remark Defaults:
+ * \snippet src/lib_json/json_reader.cpp CharReaderBuilderStrictMode
+ */
+ static void strictMode(Json::Value* settings);
+};
+
+/** Consume entire stream and use its begin/end.
+ * Someday we might have a real StreamReader, but for now this
+ * is convenient.
+ */
+bool JSON_API parseFromStream(
+ CharReader::Factory const&,
+ std::istream&,
+ Value* root, std::string* errs);
+
+/** \brief Read from 'sin' into 'root'.
+
+ Always keep comments from the input JSON.
+
+ This can be used to read a file into a particular sub-object.
+ For example:
+ \code
+ Json::Value root;
+ cin >> root["dir"]["file"];
+ cout << root;
+ \endcode
+ Result:
+ \verbatim
+ {
+ "dir": {
+ "file": {
+ // The input stream JSON would be nested here.
+ }
+ }
+ }
+ \endverbatim
+ \throw std::exception on parse error.
+ \see Json::operator<<()
+*/
+JSON_API std::istream& operator>>(std::istream&, Value&);
+
+} // namespace Json
+
+#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
+#pragma warning(pop)
+#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
+
+#endif // CPPTL_JSON_READER_H_INCLUDED
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: include/json/reader.h
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: include/json/writer.h
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2007-2010 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+#ifndef JSON_WRITER_H_INCLUDED
+#define JSON_WRITER_H_INCLUDED
+
+#if !defined(JSON_IS_AMALGAMATION)
+#include "value.h"
+#endif // if !defined(JSON_IS_AMALGAMATION)
+#include <vector>
+#include <string>
+#include <ostream>
+
+// Disable warning C4251: <data member>: <type> needs to have dll-interface to
+// be used by...
+#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
+#pragma warning(push)
+#pragma warning(disable : 4251)
+#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
+
+namespace Json {
+
+class Value;
+
+/**
+
+Usage:
+\code
+ using namespace Json;
+ void writeToStdout(StreamWriter::Factory const& factory, Value const& value) {
+ std::unique_ptr<StreamWriter> const writer(
+ factory.newStreamWriter());
+ writer->write(value, &std::cout);
+ std::cout << std::endl; // add lf and flush
+ }
+\endcode
+*/
+class JSON_API StreamWriter {
+protected:
+ std::ostream* sout_; // not owned; will not delete
+public:
+ StreamWriter();
+ virtual ~StreamWriter();
+ /** Write Value into document as configured in sub-class.
+ Do not take ownership of sout, but maintain a reference during function.
+ \pre sout != NULL
+ \return zero on success (For now, we always return zero, so check the stream instead.)
+ \throw std::exception possibly, depending on configuration
+ */
+ virtual int write(Value const& root, std::ostream* sout) = 0;
+
+ /** \brief A simple abstract factory.
+ */
+ class JSON_API Factory {
+ public:
+ virtual ~Factory();
+ /** \brief Allocate a CharReader via operator new().
+ * \throw std::exception if something goes wrong (e.g. invalid settings)
+ */
+ virtual StreamWriter* newStreamWriter() const = 0;
+ }; // Factory
+}; // StreamWriter
+
+/** \brief Write into stringstream, then return string, for convenience.
+ * A StreamWriter will be created from the factory, used, and then deleted.
+ */
+std::string JSON_API writeString(StreamWriter::Factory const& factory, Value const& root);
+
+
+/** \brief Build a StreamWriter implementation.
+
+Usage:
+\code
+ using namespace Json;
+ Value value = ...;
+ StreamWriterBuilder builder;
+ builder["commentStyle"] = "None";
+ builder["indentation"] = " "; // or whatever you like
+ std::unique_ptr<Json::StreamWriter> writer(
+ builder.newStreamWriter());
+ writer->write(value, &std::cout);
+ std::cout << std::endl; // add lf and flush
+\endcode
+*/
+class JSON_API StreamWriterBuilder : public StreamWriter::Factory {
+public:
+ // Note: We use a Json::Value so that we can add data-members to this class
+ // without a major version bump.
+ /** Configuration of this builder.
+ Available settings (case-sensitive):
+ - "commentStyle": "None" or "All"
+ - "indentation": "<anything>"
+ - "enableYAMLCompatibility": false or true
+ - slightly change the whitespace around colons
+ - "dropNullPlaceholders": false or true
+ - Drop the "null" string from the writer's output for nullValues.
+ Strictly speaking, this is not valid JSON. But when the output is being
+ fed to a browser's Javascript, it makes for smaller output and the
+ browser can handle the output just fine.
+ - "useSpecialFloats": false or true
+ - If true, outputs non-finite floating point values in the following way:
+ NaN values as "NaN", positive infinity as "Infinity", and negative infinity
+ as "-Infinity".
+
+ You can examine 'settings_` yourself
+ to see the defaults. You can also write and read them just like any
+ JSON Value.
+ \sa setDefaults()
+ */
+ Json::Value settings_;
+
+ StreamWriterBuilder();
+ virtual ~StreamWriterBuilder();
+
+ /**
+ * \throw std::exception if something goes wrong (e.g. invalid settings)
+ */
+ virtual StreamWriter* newStreamWriter() const;
+
+ /** \return true if 'settings' are legal and consistent;
+ * otherwise, indicate bad settings via 'invalid'.
+ */
+ bool validate(Json::Value* invalid) const;
+ /** A simple way to update a specific setting.
+ */
+ Value& operator[](std::string key);
+
+ /** Called by ctor, but you can use this to reset settings_.
+ * \pre 'settings' != NULL (but Json::null is fine)
+ * \remark Defaults:
+ * \snippet src/lib_json/json_writer.cpp StreamWriterBuilderDefaults
+ */
+ static void setDefaults(Json::Value* settings);
+};
+
+/** \brief Abstract class for writers.
+ * \deprecated Use StreamWriter. (And really, this is an implementation detail.)
+ */
+class JSON_API Writer {
+public:
+ virtual ~Writer();
+
+ virtual std::string write(const Value& root) = 0;
+};
+
+/** \brief Outputs a Value in <a HREF="http://www.json.org">JSON</a> format
+ *without formatting (not human friendly).
+ *
+ * The JSON document is written in a single line. It is not intended for 'human'
+ *consumption,
+ * but may be usefull to support feature such as RPC where bandwith is limited.
+ * \sa Reader, Value
+ * \deprecated Use StreamWriterBuilder.
+ */
+class JSON_API FastWriter : public Writer {
+
+public:
+ FastWriter();
+ virtual ~FastWriter() {}
+
+ void enableYAMLCompatibility();
+
+public: // overridden from Writer
+ virtual std::string write(const Value& root);
+
+private:
+ void writeValue(const Value& value);
+
+ std::string document_;
+ bool yamlCompatiblityEnabled_;
+};
+
+/** \brief Writes a Value in <a HREF="http://www.json.org">JSON</a> format in a
+ *human friendly way.
+ *
+ * The rules for line break and indent are as follow:
+ * - Object value:
+ * - if empty then print {} without indent and line break
+ * - if not empty the print '{', line break & indent, print one value per
+ *line
+ * and then unindent and line break and print '}'.
+ * - Array value:
+ * - if empty then print [] without indent and line break
+ * - if the array contains no object value, empty array or some other value
+ *types,
+ * and all the values fit on one lines, then print the array on a single
+ *line.
+ * - otherwise, it the values do not fit on one line, or the array contains
+ * object or non empty array, then print one value per line.
+ *
+ * If the Value have comments then they are outputed according to their
+ *#CommentPlacement.
+ *
+ * \sa Reader, Value, Value::setComment()
+ * \deprecated Use StreamWriterBuilder.
+ */
+class JSON_API StyledWriter : public Writer {
+public:
+ StyledWriter();
+ virtual ~StyledWriter() {}
+
+public: // overridden from Writer
+ /** \brief Serialize a Value in <a HREF="http://www.json.org">JSON</a> format.
+ * \param root Value to serialize.
+ * \return String containing the JSON document that represents the root value.
+ */
+ virtual std::string write(const Value& root);
+
+private:
+ void writeValue(const Value& value);
+ void writeArrayValue(const Value& value);
+ bool isMultineArray(const Value& value);
+ void pushValue(const std::string& value);
+ void writeIndent();
+ void writeWithIndent(const std::string& value);
+ void indent();
+ void unindent();
+ void writeCommentBeforeValue(const Value& root);
+ void writeCommentAfterValueOnSameLine(const Value& root);
+ bool hasCommentForValue(const Value& value);
+ static std::string normalizeEOL(const std::string& text);
+
+ typedef std::vector<std::string> ChildValues;
+
+ ChildValues childValues_;
+ std::string document_;
+ std::string indentString_;
+ int rightMargin_;
+ int indentSize_;
+ bool addChildValues_;
+};
+
+/** \brief Writes a Value in <a HREF="http://www.json.org">JSON</a> format in a
+ human friendly way,
+ to a stream rather than to a string.
+ *
+ * The rules for line break and indent are as follow:
+ * - Object value:
+ * - if empty then print {} without indent and line break
+ * - if not empty the print '{', line break & indent, print one value per
+ line
+ * and then unindent and line break and print '}'.
+ * - Array value:
+ * - if empty then print [] without indent and line break
+ * - if the array contains no object value, empty array or some other value
+ types,
+ * and all the values fit on one lines, then print the array on a single
+ line.
+ * - otherwise, it the values do not fit on one line, or the array contains
+ * object or non empty array, then print one value per line.
+ *
+ * If the Value have comments then they are outputed according to their
+ #CommentPlacement.
+ *
+ * \param indentation Each level will be indented by this amount extra.
+ * \sa Reader, Value, Value::setComment()
+ * \deprecated Use StreamWriterBuilder.
+ */
+class JSON_API StyledStreamWriter {
+public:
+ StyledStreamWriter(std::string indentation = "\t");
+ ~StyledStreamWriter() {}
+
+public:
+ /** \brief Serialize a Value in <a HREF="http://www.json.org">JSON</a> format.
+ * \param out Stream to write to. (Can be ostringstream, e.g.)
+ * \param root Value to serialize.
+ * \note There is no point in deriving from Writer, since write() should not
+ * return a value.
+ */
+ void write(std::ostream& out, const Value& root);
+
+private:
+ void writeValue(const Value& value);
+ void writeArrayValue(const Value& value);
+ bool isMultineArray(const Value& value);
+ void pushValue(const std::string& value);
+ void writeIndent();
+ void writeWithIndent(const std::string& value);
+ void indent();
+ void unindent();
+ void writeCommentBeforeValue(const Value& root);
+ void writeCommentAfterValueOnSameLine(const Value& root);
+ bool hasCommentForValue(const Value& value);
+ static std::string normalizeEOL(const std::string& text);
+
+ typedef std::vector<std::string> ChildValues;
+
+ ChildValues childValues_;
+ std::ostream* document_;
+ std::string indentString_;
+ int rightMargin_;
+ std::string indentation_;
+ bool addChildValues_ : 1;
+ bool indented_ : 1;
+};
+
+#if defined(JSON_HAS_INT64)
+std::string JSON_API valueToString(Int value);
+std::string JSON_API valueToString(UInt value);
+#endif // if defined(JSON_HAS_INT64)
+std::string JSON_API valueToString(LargestInt value);
+std::string JSON_API valueToString(LargestUInt value);
+std::string JSON_API valueToString(double value);
+std::string JSON_API valueToString(bool value);
+std::string JSON_API valueToQuotedString(const char* value);
+
+/// \brief Output using the StyledStreamWriter.
+/// \see Json::operator>>()
+JSON_API std::ostream& operator<<(std::ostream&, const Value& root);
+
+} // namespace Json
+
+#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
+#pragma warning(pop)
+#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
+
+#endif // JSON_WRITER_H_INCLUDED
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: include/json/writer.h
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: include/json/assertions.h
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2007-2010 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+#ifndef CPPTL_JSON_ASSERTIONS_H_INCLUDED
+#define CPPTL_JSON_ASSERTIONS_H_INCLUDED
+
+#include <stdlib.h>
+#include <sstream>
+
+#if !defined(JSON_IS_AMALGAMATION)
+#include "config.h"
+#endif // if !defined(JSON_IS_AMALGAMATION)
+
+/** It should not be possible for a maliciously designed file to
+ * cause an abort() or seg-fault, so these macros are used only
+ * for pre-condition violations and internal logic errors.
+ */
+#if JSON_USE_EXCEPTION
+
+// @todo <= add detail about condition in exception
+# define JSON_ASSERT(condition) \
+ {if (!(condition)) {Json::throwLogicError( "assert json failed" );}}
+
+# define JSON_FAIL_MESSAGE(message) \
+ { \
+ std::ostringstream oss; oss << message; \
+ Json::throwLogicError(oss.str()); \
+ abort(); \
+ }
+
+#else // JSON_USE_EXCEPTION
+
+# define JSON_ASSERT(condition) assert(condition)
+
+// The call to assert() will show the failure message in debug builds. In
+// release builds we abort, for a core-dump or debugger.
+# define JSON_FAIL_MESSAGE(message) \
+ { \
+ std::ostringstream oss; oss << message; \
+ assert(false && oss.str().c_str()); \
+ abort(); \
+ }
+
+
+#endif
+
+#define JSON_ASSERT_MESSAGE(condition, message) \
+ if (!(condition)) { \
+ JSON_FAIL_MESSAGE(message); \
+ }
+
+#endif // CPPTL_JSON_ASSERTIONS_H_INCLUDED
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: include/json/assertions.h
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+#endif //ifndef JSON_AMALGATED_H_INCLUDED
diff --git a/Source/JSON/jsoncpp.cpp b/Source/JSON/jsoncpp.cpp
new file mode 100644
index 00000000..f6d3b92c
--- /dev/null
+++ b/Source/JSON/jsoncpp.cpp
@@ -0,0 +1,4956 @@
+//-----------------------------------------------------------------------------
+// Name: jsoncpp.cpp
+// Developer: Wolfire Games LLC
+// Description: Some modifications have been done for use in Overgrowth
+// License: Read below
+//-----------------------------------------------------------------------------
+
+/// Json-cpp amalgated source (http://jsoncpp.sourceforge.net/).
+/// It is intended to be used with #include "json/json.h"
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: LICENSE
+// //////////////////////////////////////////////////////////////////////
+
+/*
+The JsonCpp library's source code, including accompanying documentation,
+tests and demonstration applications, are licensed under the following
+conditions...
+
+The author (Baptiste Lepilleur) explicitly disclaims copyright in all
+jurisdictions which recognize such a disclaimer. In such jurisdictions,
+this software is released into the Public Domain.
+
+In jurisdictions which do not recognize Public Domain property (e.g. Germany as of
+2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur, and is
+released under the terms of the MIT License (see below).
+
+In jurisdictions which recognize Public Domain property, the user of this
+software may choose to accept it either as 1) Public Domain, 2) under the
+conditions of the MIT License (see below), or 3) under the terms of dual
+Public Domain/MIT License conditions described here, as they choose.
+
+The MIT License is about as close to Public Domain as a license can get, and is
+described in clear, concise terms at:
+
+ http://en.wikipedia.org/wiki/MIT_License
+
+The full text of the MIT License follows:
+
+========================================================================
+Copyright (c) 2007-2010 Baptiste Lepilleur
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use, copy,
+modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+========================================================================
+(END LICENSE TEXT)
+
+The MIT license is compatible with both the GPL and commercial
+software, affording one all of the rights of Public Domain with the
+minor nuisance of being required to keep the above copyright notice
+and license text in the source code. Note also that by accepting the
+Public Domain "license" you can re-license your copy using whatever
+license you like.
+
+*/
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: LICENSE
+// //////////////////////////////////////////////////////////////////////
+
+
+
+#include <Memory/allocation.h>
+
+
+#include "JSON/json.h"
+
+#ifndef JSON_IS_AMALGAMATION
+#error "Compile with -I PATH_TO_JSON_DIRECTORY"
+#endif
+
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: src/lib_json/json_tool.h
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2007-2010 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+#ifndef LIB_JSONCPP_JSON_TOOL_H_INCLUDED
+#define LIB_JSONCPP_JSON_TOOL_H_INCLUDED
+
+/* This header provides common string manipulation support, such as UTF-8,
+ * portable conversion from/to string...
+ *
+ * It is an internal header that must not be exposed.
+ */
+namespace Json {
+
+/// Converts a unicode code-point to UTF-8.
+static inline std::string codePointToUTF8(unsigned int cp) {
+ std::string result;
+
+ // based on description from http://en.wikipedia.org/wiki/UTF-8
+
+ if (cp <= 0x7f) {
+ result.resize(1);
+ result[0] = static_cast<char>(cp);
+ } else if (cp <= 0x7FF) {
+ result.resize(2);
+ result[1] = static_cast<char>(0x80 | (0x3f & cp));
+ result[0] = static_cast<char>(0xC0 | (0x1f & (cp >> 6)));
+ } else if (cp <= 0xFFFF) {
+ result.resize(3);
+ result[2] = static_cast<char>(0x80 | (0x3f & cp));
+ result[1] = static_cast<char>(0x80 | (0x3f & (cp >> 6)));
+ result[0] = static_cast<char>(0xE0 | (0xf & (cp >> 12)));
+ } else if (cp <= 0x10FFFF) {
+ result.resize(4);
+ result[3] = static_cast<char>(0x80 | (0x3f & cp));
+ result[2] = static_cast<char>(0x80 | (0x3f & (cp >> 6)));
+ result[1] = static_cast<char>(0x80 | (0x3f & (cp >> 12)));
+ result[0] = static_cast<char>(0xF0 | (0x7 & (cp >> 18)));
+ }
+
+ return result;
+}
+
+/// Returns true if ch is a control character (in range [1,31]).
+static inline bool isControlCharacter(char ch) { return ch > 0 && ch <= 0x1F; }
+
+enum {
+ /// Constant that specify the size of the buffer that must be passed to
+ /// uintToString.
+ uintToStringBufferSize = 3 * sizeof(LargestUInt) + 1
+};
+
+// Defines a char buffer for use with uintToString().
+typedef char UIntToStringBuffer[uintToStringBufferSize];
+
+/** Converts an unsigned integer to string.
+ * @param value Unsigned interger to convert to string
+ * @param current Input/Output string buffer.
+ * Must have at least uintToStringBufferSize chars free.
+ */
+static inline void uintToString(LargestUInt value, char*& current) {
+ *--current = 0;
+ do {
+ *--current = static_cast<signed char>(value % 10U + static_cast<unsigned>('0'));
+ value /= 10;
+ } while (value != 0);
+}
+
+/** Change ',' to '.' everywhere in buffer.
+ *
+ * We had a sophisticated way, but it did not work in WinCE.
+ * @see https://github.com/open-source-parsers/jsoncpp/pull/9
+ */
+static inline void fixNumericLocale(char* begin, char* end) {
+ while (begin < end) {
+ if (*begin == ',') {
+ *begin = '.';
+ }
+ ++begin;
+ }
+}
+
+} // namespace Json {
+
+#endif // LIB_JSONCPP_JSON_TOOL_H_INCLUDED
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: src/lib_json/json_tool.h
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: src/lib_json/json_reader.cpp
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2007-2011 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+#if !defined(JSON_IS_AMALGAMATION)
+#include <json/assertions.h>
+#include <json/reader.h>
+#include <json/value.h>
+#include "json_tool.h"
+#endif // if !defined(JSON_IS_AMALGAMATION)
+#include <utility>
+#include <cstdio>
+#include <cassert>
+#include <cstring>
+#include <istream>
+#include <sstream>
+#include <memory>
+#include <set>
+#include <limits>
+
+#if defined(_MSC_VER)
+#if !defined(WINCE) && defined(__STDC_SECURE_LIB__) && _MSC_VER >= 1500 // VC++ 9.0 and above
+#define snprintf sprintf_s
+#elif _MSC_VER >= 1900 // VC++ 14.0 and above
+#define snprintf std::snprintf
+#else
+#define snprintf _snprintf
+#endif
+#elif defined(__ANDROID__)
+#define snprintf snprintf
+#elif __cplusplus >= 201103L
+#define snprintf std::snprintf
+#endif
+
+#if defined(_MSC_VER) && _MSC_VER >= 1400 // VC++ 8.0
+// Disable warning about strdup being deprecated.
+#pragma warning(disable : 4996)
+#endif
+
+static int const stackLimit_g = 1000;
+static int stackDepth_g = 0; // see readValue()
+
+namespace Json {
+
+typedef std::auto_ptr<CharReader> CharReaderPtr;
+
+// Implementation of class Features
+// ////////////////////////////////
+
+Features::Features()
+ : allowComments_(true), strictRoot_(false)
+{}
+Features Features::all() { return Features(); }
+
+Features Features::strictMode() {
+ Features features;
+ features.allowComments_ = false;
+ features.strictRoot_ = true;
+ return features;
+}
+
+// Implementation of class Reader
+// ////////////////////////////////
+
+static bool containsNewLine(Reader::Location begin, Reader::Location end) {
+ for (; begin < end; ++begin)
+ if (*begin == '\n' || *begin == '\r')
+ return true;
+ return false;
+}
+
+// Class Reader
+// //////////////////////////////////////////////////////////////////
+
+Reader::Reader()
+ : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(),
+ lastValue_(), commentsBefore_(), features_(Features::all()),
+ collectComments_() {}
+
+Reader::Reader(const Features& features)
+ : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(),
+ lastValue_(), commentsBefore_(), features_(features), collectComments_() {
+}
+
+bool
+Reader::parse(const std::string& document, Value& root, bool collectComments) {
+ document_ = document;
+ const char* begin = document_.c_str();
+ const char* end = begin + document_.length();
+ return parse(begin, end, root, collectComments);
+}
+
+bool Reader::parse(std::istream& sin, Value& root, bool collectComments) {
+ // std::istream_iterator<char> begin(sin);
+ // std::istream_iterator<char> end;
+ // Those would allow streamed input from a file, if parse() were a
+ // template function.
+
+ // Since std::string is reference-counted, this at least does not
+ // create an extra copy.
+ std::string doc;
+ std::getline(sin, doc, (char)EOF);
+ return parse(doc, root, collectComments);
+}
+
+bool Reader::parse(const char* beginDoc,
+ const char* endDoc,
+ Value& root,
+ bool collectComments) {
+ if (!features_.allowComments_) {
+ collectComments = false;
+ }
+
+ begin_ = beginDoc;
+ end_ = endDoc;
+ collectComments_ = collectComments;
+ current_ = begin_;
+ lastValueEnd_ = 0;
+ lastValue_ = 0;
+ commentsBefore_ = "";
+ errors_.clear();
+ while (!nodes_.empty())
+ nodes_.pop();
+ nodes_.push(&root);
+
+ stackDepth_g = 0; // Yes, this is bad coding, but options are limited.
+ bool successful = readValue();
+ Token token;
+ skipCommentTokens(token);
+ if (collectComments_ && !commentsBefore_.empty())
+ root.setComment(commentsBefore_, commentAfter);
+ if (features_.strictRoot_) {
+ if (!root.isArray() && !root.isObject()) {
+ // Set error location to start of doc, ideally should be first token found
+ // in doc
+ token.type_ = tokenError;
+ token.start_ = beginDoc;
+ token.end_ = endDoc;
+ addError(
+ "A valid JSON document must be either an array or an object value.",
+ token);
+ return false;
+ }
+ }
+ return successful;
+}
+
+bool Reader::readValue() {
+ // This is a non-reentrant way to support a stackLimit. Terrible!
+ // But this deprecated class has a security problem: Bad input can
+ // cause a seg-fault. This seems like a fair, binary-compatible way
+ // to prevent the problem.
+ if (stackDepth_g >= stackLimit_g) throwRuntimeError("Exceeded stackLimit in readValue().");
+ ++stackDepth_g;
+
+ Token token;
+ skipCommentTokens(token);
+ bool successful = true;
+
+ if (collectComments_ && !commentsBefore_.empty()) {
+ currentValue().setComment(commentsBefore_, commentBefore);
+ commentsBefore_ = "";
+ }
+
+ switch (token.type_) {
+ case tokenObjectBegin:
+ successful = readObject(token);
+ break;
+ case tokenArrayBegin:
+ successful = readArray(token);
+ break;
+ case tokenNumber:
+ successful = decodeNumber(token);
+ break;
+ case tokenString:
+ successful = decodeString(token);
+ break;
+ case tokenTrue:
+ {
+ Value v(true);
+ currentValue().swapPayload(v);
+ }
+ break;
+ case tokenFalse:
+ {
+ Value v(false);
+ currentValue().swapPayload(v);
+ }
+ break;
+ case tokenNull:
+ {
+ Value v;
+ currentValue().swapPayload(v);
+ }
+ break;
+ // Else, fall through...
+ default:
+ return addError("Syntax error: value, object or array expected.", token);
+ }
+
+ if (collectComments_) {
+ lastValueEnd_ = current_;
+ lastValue_ = &currentValue();
+ }
+
+ --stackDepth_g;
+ return successful;
+}
+
+void Reader::skipCommentTokens(Token& token) {
+ if (features_.allowComments_) {
+ do {
+ readToken(token);
+ } while (token.type_ == tokenComment);
+ } else {
+ readToken(token);
+ }
+}
+
+bool Reader::readToken(Token& token) {
+ skipSpaces();
+ token.start_ = current_;
+ Char c = getNextChar();
+ bool ok = true;
+ switch (c) {
+ case '{':
+ token.type_ = tokenObjectBegin;
+ break;
+ case '}':
+ token.type_ = tokenObjectEnd;
+ break;
+ case '[':
+ token.type_ = tokenArrayBegin;
+ break;
+ case ']':
+ token.type_ = tokenArrayEnd;
+ break;
+ case '"':
+ token.type_ = tokenString;
+ ok = readString();
+ break;
+ case '/':
+ token.type_ = tokenComment;
+ ok = readComment();
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '-':
+ token.type_ = tokenNumber;
+ readNumber();
+ break;
+ case 't':
+ token.type_ = tokenTrue;
+ ok = match("rue", 3);
+ break;
+ case 'f':
+ token.type_ = tokenFalse;
+ ok = match("alse", 4);
+ break;
+ case 'n':
+ token.type_ = tokenNull;
+ ok = match("ull", 3);
+ break;
+ case ',':
+ token.type_ = tokenArraySeparator;
+ break;
+ case ':':
+ token.type_ = tokenMemberSeparator;
+ break;
+ case 0:
+ token.type_ = tokenEndOfStream;
+ break;
+ default:
+ ok = false;
+ break;
+ }
+ if (!ok)
+ token.type_ = tokenError;
+ token.end_ = current_;
+ return true;
+}
+
+void Reader::skipSpaces() {
+ while (current_ != end_) {
+ Char c = *current_;
+ if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
+ ++current_;
+ else
+ break;
+ }
+}
+
+bool Reader::match(Location pattern, int patternLength) {
+ if (end_ - current_ < patternLength)
+ return false;
+ int index = patternLength;
+ while (index--)
+ if (current_[index] != pattern[index])
+ return false;
+ current_ += patternLength;
+ return true;
+}
+
+bool Reader::readComment() {
+ Location commentBegin = current_ - 1;
+ Char c = getNextChar();
+ bool successful = false;
+ if (c == '*')
+ successful = readCStyleComment();
+ else if (c == '/')
+ successful = readCppStyleComment();
+ if (!successful)
+ return false;
+
+ if (collectComments_) {
+ CommentPlacement placement = commentBefore;
+ if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) {
+ if (c != '*' || !containsNewLine(commentBegin, current_))
+ placement = commentAfterOnSameLine;
+ }
+
+ addComment(commentBegin, current_, placement);
+ }
+ return true;
+}
+
+static std::string normalizeEOL(Reader::Location begin, Reader::Location end) {
+ std::string normalized;
+ normalized.reserve(end - begin);
+ Reader::Location current = begin;
+ while (current != end) {
+ char c = *current++;
+ if (c == '\r') {
+ if (current != end && *current == '\n')
+ // convert dos EOL
+ ++current;
+ // convert Mac EOL
+ normalized += '\n';
+ } else {
+ normalized += c;
+ }
+ }
+ return normalized;
+}
+
+void
+Reader::addComment(Location begin, Location end, CommentPlacement placement) {
+ assert(collectComments_);
+ const std::string& normalized = normalizeEOL(begin, end);
+ if (placement == commentAfterOnSameLine) {
+ assert(lastValue_ != 0);
+ lastValue_->setComment(normalized, placement);
+ } else {
+ commentsBefore_ += normalized;
+ }
+}
+
+bool Reader::readCStyleComment() {
+ while (current_ != end_) {
+ Char c = getNextChar();
+ if (c == '*' && *current_ == '/')
+ break;
+ }
+ return getNextChar() == '/';
+}
+
+bool Reader::readCppStyleComment() {
+ while (current_ != end_) {
+ Char c = getNextChar();
+ if (c == '\n')
+ break;
+ if (c == '\r') {
+ // Consume DOS EOL. It will be normalized in addComment.
+ if (current_ != end_ && *current_ == '\n')
+ getNextChar();
+ // Break on Moc OS 9 EOL.
+ break;
+ }
+ }
+ return true;
+}
+
+void Reader::readNumber() {
+ const char *p = current_;
+ char c = '0'; // stopgap for already consumed character
+ // integral part
+ while (c >= '0' && c <= '9')
+ c = (current_ = p) < end_ ? *p++ : 0;
+ // fractional part
+ if (c == '.') {
+ c = (current_ = p) < end_ ? *p++ : 0;
+ while (c >= '0' && c <= '9')
+ c = (current_ = p) < end_ ? *p++ : 0;
+ }
+ // exponential part
+ if (c == 'e' || c == 'E') {
+ c = (current_ = p) < end_ ? *p++ : 0;
+ if (c == '+' || c == '-')
+ c = (current_ = p) < end_ ? *p++ : 0;
+ while (c >= '0' && c <= '9')
+ c = (current_ = p) < end_ ? *p++ : 0;
+ }
+}
+
+bool Reader::readString() {
+ Char c = 0;
+ while (current_ != end_) {
+ c = getNextChar();
+ if (c == '\\')
+ getNextChar();
+ else if (c == '"')
+ break;
+ }
+ return c == '"';
+}
+
+bool Reader::readObject(Token& /*tokenStart*/) {
+ Token tokenName;
+ std::string name;
+ Value init(objectValue);
+ currentValue().swapPayload(init);
+ while (readToken(tokenName)) {
+ bool initialTokenOk = true;
+ while (tokenName.type_ == tokenComment && initialTokenOk)
+ initialTokenOk = readToken(tokenName);
+ if (!initialTokenOk)
+ break;
+ if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object
+ return true;
+ name = "";
+ if (tokenName.type_ == tokenString) {
+ if (!decodeString(tokenName, name))
+ return recoverFromError(tokenObjectEnd);
+ } else {
+ break;
+ }
+
+ Token colon;
+ if (!readToken(colon) || colon.type_ != tokenMemberSeparator) {
+ return addErrorAndRecover(
+ "Missing ':' after object member name", colon, tokenObjectEnd);
+ }
+ Value& value = currentValue()[name];
+ nodes_.push(&value);
+ bool ok = readValue();
+ nodes_.pop();
+ if (!ok) // error already set
+ return recoverFromError(tokenObjectEnd);
+
+ Token comma;
+ if (!readToken(comma) ||
+ (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator &&
+ comma.type_ != tokenComment)) {
+ return addErrorAndRecover(
+ "Missing ',' or '}' in object declaration", comma, tokenObjectEnd);
+ }
+ bool finalizeTokenOk = true;
+ while (comma.type_ == tokenComment && finalizeTokenOk)
+ finalizeTokenOk = readToken(comma);
+ if (comma.type_ == tokenObjectEnd)
+ return true;
+ }
+ return addErrorAndRecover(
+ "Missing '}' or object member name", tokenName, tokenObjectEnd);
+}
+
+bool Reader::readArray(Token& /*tokenStart*/) {
+ Value init(arrayValue);
+ currentValue().swapPayload(init);
+ skipSpaces();
+ if (*current_ == ']') // empty array
+ {
+ Token endArray;
+ readToken(endArray);
+ return true;
+ }
+ int index = 0;
+ for (;;) {
+ Value& value = currentValue()[index++];
+ nodes_.push(&value);
+ bool ok = readValue();
+ nodes_.pop();
+ if (!ok) // error already set
+ return recoverFromError(tokenArrayEnd);
+
+ Token token;
+ // Accept Comment after last item in the array.
+ ok = readToken(token);
+ while (token.type_ == tokenComment && ok) {
+ ok = readToken(token);
+ }
+ bool badTokenType =
+ (token.type_ != tokenArraySeparator && token.type_ != tokenArrayEnd);
+ if (!ok || badTokenType) {
+ return addErrorAndRecover(
+ "Missing ',' or ']' in array declaration", token, tokenArrayEnd);
+ }
+ if (token.type_ == tokenArrayEnd)
+ break;
+ }
+ return true;
+}
+
+bool Reader::decodeNumber(Token& token) {
+ Value decoded;
+ if (!decodeNumber(token, decoded))
+ return false;
+ currentValue().swapPayload(decoded);
+ return true;
+}
+
+bool Reader::decodeNumber(Token& token, Value& decoded) {
+ // Attempts to parse the number as an integer. If the number is
+ // larger than the maximum supported value of an integer then
+ // we decode the number as a double.
+ Location current = token.start_;
+ bool isNegative = *current == '-';
+ if (isNegative)
+ ++current;
+ // TODO: Help the compiler do the div and mod at compile time or get rid of them.
+ Value::LargestUInt maxIntegerValue =
+ isNegative ? Value::LargestUInt(Value::maxLargestInt) + 1
+ : Value::maxLargestUInt;
+ Value::LargestUInt threshold = maxIntegerValue / 10;
+ Value::LargestUInt value = 0;
+ while (current < token.end_) {
+ Char c = *current++;
+ if (c < '0' || c > '9')
+ return decodeDouble(token, decoded);
+ Value::UInt digit(c - '0');
+ if (value >= threshold) {
+ // We've hit or exceeded the max value divided by 10 (rounded down). If
+ // a) we've only just touched the limit, b) this is the last digit, and
+ // c) it's small enough to fit in that rounding delta, we're okay.
+ // Otherwise treat this number as a double to avoid overflow.
+ if (value > threshold || current != token.end_ ||
+ digit > maxIntegerValue % 10) {
+ return decodeDouble(token, decoded);
+ }
+ }
+ value = value * 10 + digit;
+ }
+ if (isNegative && value == maxIntegerValue)
+ decoded = Value::minLargestInt;
+ else if (isNegative)
+ decoded = -Value::LargestInt(value);
+ else if (value <= Value::LargestUInt(Value::maxInt))
+ decoded = Value::LargestInt(value);
+ else
+ decoded = value;
+ return true;
+}
+
+bool Reader::decodeDouble(Token& token) {
+ Value decoded;
+ if (!decodeDouble(token, decoded))
+ return false;
+ currentValue().swapPayload(decoded);
+ return true;
+}
+
+bool Reader::decodeDouble(Token& token, Value& decoded) {
+ double value = 0;
+ std::string buffer(token.start_, token.end_);
+ std::istringstream is(buffer);
+ if (!(is >> value))
+ return addError("'" + std::string(token.start_, token.end_) +
+ "' is not a number.",
+ token);
+ decoded = value;
+ return true;
+}
+
+bool Reader::decodeString(Token& token) {
+ std::string decoded_string;
+ if (!decodeString(token, decoded_string))
+ return false;
+ Value decoded(decoded_string);
+ currentValue().swapPayload(decoded);
+ return true;
+}
+
+bool Reader::decodeString(Token& token, std::string& decoded) {
+ decoded.reserve(token.end_ - token.start_ - 2);
+ Location current = token.start_ + 1; // skip '"'
+ Location end = token.end_ - 1; // do not include '"'
+ while (current != end) {
+ Char c = *current++;
+ if (c == '"')
+ break;
+ else if (c == '\\') {
+ if (current == end)
+ return addError("Empty escape sequence in string", token, current);
+ Char escape = *current++;
+ switch (escape) {
+ case '"':
+ decoded += '"';
+ break;
+ case '/':
+ decoded += '/';
+ break;
+ case '\\':
+ decoded += '\\';
+ break;
+ case 'b':
+ decoded += '\b';
+ break;
+ case 'f':
+ decoded += '\f';
+ break;
+ case 'n':
+ decoded += '\n';
+ break;
+ case 'r':
+ decoded += '\r';
+ break;
+ case 't':
+ decoded += '\t';
+ break;
+ case 'u': {
+ unsigned int unicode;
+ if (!decodeUnicodeCodePoint(token, current, end, unicode))
+ return false;
+ decoded += codePointToUTF8(unicode);
+ } break;
+ default:
+ return addError("Bad escape sequence in string", token, current);
+ }
+ } else {
+ decoded += c;
+ }
+ }
+ return true;
+}
+
+bool Reader::decodeUnicodeCodePoint(Token& token,
+ Location& current,
+ Location end,
+ unsigned int& unicode) {
+
+ if (!decodeUnicodeEscapeSequence(token, current, end, unicode))
+ return false;
+ if (unicode >= 0xD800 && unicode <= 0xDBFF) {
+ // surrogate pairs
+ if (end - current < 6)
+ return addError(
+ "additional six characters expected to parse unicode surrogate pair.",
+ token,
+ current);
+ unsigned int surrogatePair;
+ if (*(current++) == '\\' && *(current++) == 'u') {
+ if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) {
+ unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF);
+ } else
+ return false;
+ } else
+ return addError("expecting another \\u token to begin the second half of "
+ "a unicode surrogate pair",
+ token,
+ current);
+ }
+ return true;
+}
+
+bool Reader::decodeUnicodeEscapeSequence(Token& token,
+ Location& current,
+ Location end,
+ unsigned int& unicode) {
+ if (end - current < 4)
+ return addError(
+ "Bad unicode escape sequence in string: four digits expected.",
+ token,
+ current);
+ unicode = 0;
+ for (int index = 0; index < 4; ++index) {
+ Char c = *current++;
+ unicode *= 16;
+ if (c >= '0' && c <= '9')
+ unicode += c - '0';
+ else if (c >= 'a' && c <= 'f')
+ unicode += c - 'a' + 10;
+ else if (c >= 'A' && c <= 'F')
+ unicode += c - 'A' + 10;
+ else
+ return addError(
+ "Bad unicode escape sequence in string: hexadecimal digit expected.",
+ token,
+ current);
+ }
+ return true;
+}
+
+bool
+Reader::addError(const std::string& message, Token& token, Location extra) {
+ ErrorInfo info;
+ info.token_ = token;
+ info.message_ = message;
+ info.extra_ = extra;
+ errors_.push_back(info);
+ return false;
+}
+
+bool Reader::recoverFromError(TokenType skipUntilToken) {
+ int errorCount = int(errors_.size());
+ Token skip;
+ for (;;) {
+ if (!readToken(skip))
+ errors_.resize(errorCount); // discard errors caused by recovery
+ if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream)
+ break;
+ }
+ errors_.resize(errorCount);
+ return false;
+}
+
+bool Reader::addErrorAndRecover(const std::string& message,
+ Token& token,
+ TokenType skipUntilToken) {
+ addError(message, token);
+ return recoverFromError(skipUntilToken);
+}
+
+Value& Reader::currentValue() { return *(nodes_.top()); }
+
+Reader::Char Reader::getNextChar() {
+ if (current_ == end_)
+ return 0;
+ return *current_++;
+}
+
+void Reader::getLocationLineAndColumn(Location location,
+ int& line,
+ int& column) const {
+ Location current = begin_;
+ Location lastLineStart = current;
+ line = 0;
+ while (current < location && current != end_) {
+ Char c = *current++;
+ if (c == '\r') {
+ if (*current == '\n')
+ ++current;
+ lastLineStart = current;
+ ++line;
+ } else if (c == '\n') {
+ lastLineStart = current;
+ ++line;
+ }
+ }
+ // column & line start at 1
+ column = int(location - lastLineStart) + 1;
+ ++line;
+}
+
+std::string Reader::getLocationLineAndColumn(Location location) const {
+ int line, column;
+ getLocationLineAndColumn(location, line, column);
+ char buffer[18 + 16 + 16 + 1];
+ snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column);
+ return buffer;
+}
+
+// Deprecated. Preserved for backward compatibility
+std::string Reader::getFormatedErrorMessages() const {
+ return getFormattedErrorMessages();
+}
+
+std::string Reader::getFormattedErrorMessages() const {
+ std::string formattedMessage;
+ for (Errors::const_iterator itError = errors_.begin();
+ itError != errors_.end();
+ ++itError) {
+ const ErrorInfo& error = *itError;
+ formattedMessage +=
+ "* " + getLocationLineAndColumn(error.token_.start_) + "\n";
+ formattedMessage += " " + error.message_ + "\n";
+ if (error.extra_)
+ formattedMessage +=
+ "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n";
+ }
+ return formattedMessage;
+}
+
+// Reader
+/////////////////////////
+
+// exact copy of Features
+class OurFeatures {
+public:
+ static OurFeatures all();
+ OurFeatures();
+ bool allowComments_;
+ bool strictRoot_;
+ bool allowDroppedNullPlaceholders_;
+ bool allowNumericKeys_;
+ bool allowSingleQuotes_;
+ bool failIfExtra_;
+ bool rejectDupKeys_;
+ bool allowSpecialFloats_;
+ int stackLimit_;
+}; // OurFeatures
+
+// exact copy of Implementation of class Features
+// ////////////////////////////////
+
+OurFeatures::OurFeatures()
+ : allowComments_(true), strictRoot_(false)
+ , allowDroppedNullPlaceholders_(false), allowNumericKeys_(false)
+ , allowSingleQuotes_(false)
+ , failIfExtra_(false)
+ , allowSpecialFloats_(false)
+{
+}
+
+OurFeatures OurFeatures::all() { return OurFeatures(); }
+
+// Implementation of class Reader
+// ////////////////////////////////
+
+// exact copy of Reader, renamed to OurReader
+class OurReader {
+public:
+ typedef char Char;
+ typedef const Char* Location;
+ struct StructuredError {
+ size_t offset_start;
+ size_t offset_limit;
+ std::string message;
+ };
+
+ OurReader(OurFeatures const& features);
+ bool parse(const char* beginDoc,
+ const char* endDoc,
+ Value& root,
+ bool collectComments = true);
+ std::string getFormattedErrorMessages() const;
+
+private:
+ OurReader(OurReader const&); // no impl
+ void operator=(OurReader const&); // no impl
+
+ enum TokenType {
+ tokenEndOfStream = 0,
+ tokenObjectBegin,
+ tokenObjectEnd,
+ tokenArrayBegin,
+ tokenArrayEnd,
+ tokenString,
+ tokenNumber,
+ tokenTrue,
+ tokenFalse,
+ tokenNull,
+ tokenNaN,
+ tokenPosInf,
+ tokenNegInf,
+ tokenArraySeparator,
+ tokenMemberSeparator,
+ tokenComment,
+ tokenError
+ };
+
+ class Token {
+ public:
+ TokenType type_;
+ Location start_;
+ Location end_;
+ };
+
+ class ErrorInfo {
+ public:
+ Token token_;
+ std::string message_;
+ Location extra_;
+ };
+
+ typedef std::deque<ErrorInfo> Errors;
+
+ bool readToken(Token& token);
+ void skipSpaces();
+ bool match(Location pattern, int patternLength);
+ bool readComment();
+ bool readCStyleComment();
+ bool readCppStyleComment();
+ bool readString();
+ bool readStringSingleQuote();
+ bool readNumber(bool checkInf);
+ bool readValue();
+ bool readObject(Token& token);
+ bool readArray(Token& token);
+ bool decodeNumber(Token& token);
+ bool decodeNumber(Token& token, Value& decoded);
+ bool decodeString(Token& token);
+ bool decodeString(Token& token, std::string& decoded);
+ bool decodeDouble(Token& token);
+ bool decodeDouble(Token& token, Value& decoded);
+ bool decodeUnicodeCodePoint(Token& token,
+ Location& current,
+ Location end,
+ unsigned int& unicode);
+ bool decodeUnicodeEscapeSequence(Token& token,
+ Location& current,
+ Location end,
+ unsigned int& unicode);
+ bool addError(const std::string& message, Token& token, Location extra = 0);
+ bool recoverFromError(TokenType skipUntilToken);
+ bool addErrorAndRecover(const std::string& message,
+ Token& token,
+ TokenType skipUntilToken);
+ void skipUntilSpace();
+ Value& currentValue();
+ Char getNextChar();
+ void
+ getLocationLineAndColumn(Location location, int& line, int& column) const;
+ std::string getLocationLineAndColumn(Location location) const;
+ void addComment(Location begin, Location end, CommentPlacement placement);
+ void skipCommentTokens(Token& token);
+
+ typedef std::stack<Value*> Nodes;
+ Nodes nodes_;
+ Errors errors_;
+ std::string document_;
+ Location begin_;
+ Location end_;
+ Location current_;
+ Location lastValueEnd_;
+ Value* lastValue_;
+ std::string commentsBefore_;
+ int stackDepth_;
+
+ OurFeatures const features_;
+ bool collectComments_;
+}; // OurReader
+
+// complete copy of Read impl, for OurReader
+
+OurReader::OurReader(OurFeatures const& features)
+ : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(),
+ lastValue_(), commentsBefore_(), features_(features), collectComments_() {
+}
+
+bool OurReader::parse(const char* beginDoc,
+ const char* endDoc,
+ Value& root,
+ bool collectComments) {
+ if (!features_.allowComments_) {
+ collectComments = false;
+ }
+
+ begin_ = beginDoc;
+ end_ = endDoc;
+ collectComments_ = collectComments;
+ current_ = begin_;
+ lastValueEnd_ = 0;
+ lastValue_ = 0;
+ commentsBefore_ = "";
+ errors_.clear();
+ while (!nodes_.empty())
+ nodes_.pop();
+ nodes_.push(&root);
+
+ stackDepth_ = 0;
+ bool successful = readValue();
+ Token token;
+ skipCommentTokens(token);
+ if (features_.failIfExtra_) {
+ if (token.type_ != tokenError && token.type_ != tokenEndOfStream) {
+ addError("Extra non-whitespace after JSON value.", token);
+ return false;
+ }
+ }
+ if (collectComments_ && !commentsBefore_.empty())
+ root.setComment(commentsBefore_, commentAfter);
+ if (features_.strictRoot_) {
+ if (!root.isArray() && !root.isObject()) {
+ // Set error location to start of doc, ideally should be first token found
+ // in doc
+ token.type_ = tokenError;
+ token.start_ = beginDoc;
+ token.end_ = endDoc;
+ addError(
+ "A valid JSON document must be either an array or an object value.",
+ token);
+ return false;
+ }
+ }
+ return successful;
+}
+
+bool OurReader::readValue() {
+ if (stackDepth_ >= features_.stackLimit_) throwRuntimeError("Exceeded stackLimit in readValue().");
+ ++stackDepth_;
+ Token token;
+ skipCommentTokens(token);
+ bool successful = true;
+
+ if (collectComments_ && !commentsBefore_.empty()) {
+ currentValue().setComment(commentsBefore_, commentBefore);
+ commentsBefore_ = "";
+ }
+
+ switch (token.type_) {
+ case tokenObjectBegin:
+ successful = readObject(token);
+ break;
+ case tokenArrayBegin:
+ successful = readArray(token);
+ break;
+ case tokenNumber:
+ successful = decodeNumber(token);
+ break;
+ case tokenString:
+ successful = decodeString(token);
+ break;
+ case tokenTrue:
+ {
+ Value v(true);
+ currentValue().swapPayload(v);
+ }
+ break;
+ case tokenFalse:
+ {
+ Value v(false);
+ currentValue().swapPayload(v);
+ }
+ break;
+ case tokenNull:
+ {
+ Value v;
+ currentValue().swapPayload(v);
+ }
+ break;
+ case tokenNaN:
+ {
+ Value v(std::numeric_limits<double>::quiet_NaN());
+ currentValue().swapPayload(v);
+ }
+ break;
+ case tokenPosInf:
+ {
+ Value v(std::numeric_limits<double>::infinity());
+ currentValue().swapPayload(v);
+ }
+ break;
+ case tokenNegInf:
+ {
+ Value v(-std::numeric_limits<double>::infinity());
+ currentValue().swapPayload(v);
+ }
+ break;
+ case tokenArraySeparator:
+ case tokenObjectEnd:
+ case tokenArrayEnd:
+ if (features_.allowDroppedNullPlaceholders_) {
+ // "Un-read" the current token and mark the current value as a null
+ // token.
+ current_--;
+ Value v;
+ currentValue().swapPayload(v);
+ break;
+ } // else, fall through ...
+ default:
+ return addError("Syntax error: value, object or array expected.", token);
+ }
+
+ if (collectComments_) {
+ lastValueEnd_ = current_;
+ lastValue_ = &currentValue();
+ }
+
+ --stackDepth_;
+ return successful;
+}
+
+void OurReader::skipCommentTokens(Token& token) {
+ if (features_.allowComments_) {
+ do {
+ readToken(token);
+ } while (token.type_ == tokenComment);
+ } else {
+ readToken(token);
+ }
+}
+
+bool OurReader::readToken(Token& token) {
+ skipSpaces();
+ token.start_ = current_;
+ Char c = getNextChar();
+ bool ok = true;
+ switch (c) {
+ case '{':
+ token.type_ = tokenObjectBegin;
+ break;
+ case '}':
+ token.type_ = tokenObjectEnd;
+ break;
+ case '[':
+ token.type_ = tokenArrayBegin;
+ break;
+ case ']':
+ token.type_ = tokenArrayEnd;
+ break;
+ case '"':
+ token.type_ = tokenString;
+ ok = readString();
+ break;
+ case '\'':
+ if (features_.allowSingleQuotes_) {
+ token.type_ = tokenString;
+ ok = readStringSingleQuote();
+ break;
+ } // else continue
+ case '/':
+ token.type_ = tokenComment;
+ ok = readComment();
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ token.type_ = tokenNumber;
+ readNumber(false);
+ break;
+ case '-':
+ if (readNumber(true)) {
+ token.type_ = tokenNumber;
+ } else {
+ token.type_ = tokenNegInf;
+ ok = features_.allowSpecialFloats_ && match("nfinity", 7);
+ }
+ break;
+ case 't':
+ token.type_ = tokenTrue;
+ ok = match("rue", 3);
+ break;
+ case 'f':
+ token.type_ = tokenFalse;
+ ok = match("alse", 4);
+ break;
+ case 'n':
+ token.type_ = tokenNull;
+ ok = match("ull", 3);
+ break;
+ case 'N':
+ if (features_.allowSpecialFloats_) {
+ token.type_ = tokenNaN;
+ ok = match("aN", 2);
+ } else {
+ ok = false;
+ }
+ break;
+ case 'I':
+ if (features_.allowSpecialFloats_) {
+ token.type_ = tokenPosInf;
+ ok = match("nfinity", 7);
+ } else {
+ ok = false;
+ }
+ break;
+ case ',':
+ token.type_ = tokenArraySeparator;
+ break;
+ case ':':
+ token.type_ = tokenMemberSeparator;
+ break;
+ case 0:
+ token.type_ = tokenEndOfStream;
+ break;
+ default:
+ ok = false;
+ break;
+ }
+ if (!ok)
+ token.type_ = tokenError;
+ token.end_ = current_;
+ return true;
+}
+
+void OurReader::skipSpaces() {
+ while (current_ != end_) {
+ Char c = *current_;
+ if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
+ ++current_;
+ else
+ break;
+ }
+}
+
+bool OurReader::match(Location pattern, int patternLength) {
+ if (end_ - current_ < patternLength)
+ return false;
+ int index = patternLength;
+ while (index--)
+ if (current_[index] != pattern[index])
+ return false;
+ current_ += patternLength;
+ return true;
+}
+
+bool OurReader::readComment() {
+ Location commentBegin = current_ - 1;
+ Char c = getNextChar();
+ bool successful = false;
+ if (c == '*')
+ successful = readCStyleComment();
+ else if (c == '/')
+ successful = readCppStyleComment();
+ if (!successful)
+ return false;
+
+ if (collectComments_) {
+ CommentPlacement placement = commentBefore;
+ if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) {
+ if (c != '*' || !containsNewLine(commentBegin, current_))
+ placement = commentAfterOnSameLine;
+ }
+
+ addComment(commentBegin, current_, placement);
+ }
+ return true;
+}
+
+void
+OurReader::addComment(Location begin, Location end, CommentPlacement placement) {
+ assert(collectComments_);
+ const std::string& normalized = normalizeEOL(begin, end);
+ if (placement == commentAfterOnSameLine) {
+ assert(lastValue_ != 0);
+ lastValue_->setComment(normalized, placement);
+ } else {
+ commentsBefore_ += normalized;
+ }
+}
+
+bool OurReader::readCStyleComment() {
+ while (current_ != end_) {
+ Char c = getNextChar();
+ if (c == '*' && *current_ == '/')
+ break;
+ }
+ return getNextChar() == '/';
+}
+
+bool OurReader::readCppStyleComment() {
+ while (current_ != end_) {
+ Char c = getNextChar();
+ if (c == '\n')
+ break;
+ if (c == '\r') {
+ // Consume DOS EOL. It will be normalized in addComment.
+ if (current_ != end_ && *current_ == '\n')
+ getNextChar();
+ // Break on Moc OS 9 EOL.
+ break;
+ }
+ }
+ return true;
+}
+
+bool OurReader::readNumber(bool checkInf) {
+ const char *p = current_;
+ if (checkInf && p != end_ && *p == 'I') {
+ current_ = ++p;
+ return false;
+ }
+ char c = '0'; // stopgap for already consumed character
+ // integral part
+ while (c >= '0' && c <= '9')
+ c = (current_ = p) < end_ ? *p++ : 0;
+ // fractional part
+ if (c == '.') {
+ c = (current_ = p) < end_ ? *p++ : 0;
+ while (c >= '0' && c <= '9')
+ c = (current_ = p) < end_ ? *p++ : 0;
+ }
+ // exponential part
+ if (c == 'e' || c == 'E') {
+ c = (current_ = p) < end_ ? *p++ : 0;
+ if (c == '+' || c == '-')
+ c = (current_ = p) < end_ ? *p++ : 0;
+ while (c >= '0' && c <= '9')
+ c = (current_ = p) < end_ ? *p++ : 0;
+ }
+ return true;
+}
+bool OurReader::readString() {
+ Char c = 0;
+ while (current_ != end_) {
+ c = getNextChar();
+ if (c == '\\')
+ getNextChar();
+ else if (c == '"')
+ break;
+ }
+ return c == '"';
+}
+
+
+bool OurReader::readStringSingleQuote() {
+ Char c = 0;
+ while (current_ != end_) {
+ c = getNextChar();
+ if (c == '\\')
+ getNextChar();
+ else if (c == '\'')
+ break;
+ }
+ return c == '\'';
+}
+
+bool OurReader::readObject(Token& /*tokenStart*/) {
+ Token tokenName;
+ std::string name;
+ Value init(objectValue);
+ currentValue().swapPayload(init);
+ while (readToken(tokenName)) {
+ bool initialTokenOk = true;
+ while (tokenName.type_ == tokenComment && initialTokenOk)
+ initialTokenOk = readToken(tokenName);
+ if (!initialTokenOk)
+ break;
+ if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object
+ return true;
+ name = "";
+ if (tokenName.type_ == tokenString) {
+ if (!decodeString(tokenName, name))
+ return recoverFromError(tokenObjectEnd);
+ } else if (tokenName.type_ == tokenNumber && features_.allowNumericKeys_) {
+ Value numberName;
+ if (!decodeNumber(tokenName, numberName))
+ return recoverFromError(tokenObjectEnd);
+ name = numberName.asString();
+ } else {
+ break;
+ }
+
+ Token colon;
+ if (!readToken(colon) || colon.type_ != tokenMemberSeparator) {
+ return addErrorAndRecover(
+ "Missing ':' after object member name", colon, tokenObjectEnd);
+ }
+ if (name.length() >= (1U<<30)) throwRuntimeError("keylength >= 2^30");
+ if (features_.rejectDupKeys_ && currentValue().isMember(name)) {
+ std::string msg = "Duplicate key: '" + name + "'";
+ return addErrorAndRecover(
+ msg, tokenName, tokenObjectEnd);
+ }
+ Value& value = currentValue()[name];
+ nodes_.push(&value);
+ bool ok = readValue();
+ nodes_.pop();
+ if (!ok) // error already set
+ return recoverFromError(tokenObjectEnd);
+
+ Token comma;
+ if (!readToken(comma) ||
+ (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator &&
+ comma.type_ != tokenComment)) {
+ return addErrorAndRecover(
+ "Missing ',' or '}' in object declaration", comma, tokenObjectEnd);
+ }
+ bool finalizeTokenOk = true;
+ while (comma.type_ == tokenComment && finalizeTokenOk)
+ finalizeTokenOk = readToken(comma);
+ if (comma.type_ == tokenObjectEnd)
+ return true;
+ }
+ return addErrorAndRecover(
+ "Missing '}' or object member name", tokenName, tokenObjectEnd);
+}
+
+bool OurReader::readArray(Token& /*tokenStart*/) {
+ Value init(arrayValue);
+ currentValue().swapPayload(init);
+ skipSpaces();
+ if (*current_ == ']') // empty array
+ {
+ Token endArray;
+ readToken(endArray);
+ return true;
+ }
+ int index = 0;
+ for (;;) {
+ Value& value = currentValue()[index++];
+ nodes_.push(&value);
+ bool ok = readValue();
+ nodes_.pop();
+ if (!ok) // error already set
+ return recoverFromError(tokenArrayEnd);
+
+ Token token;
+ // Accept Comment after last item in the array.
+ ok = readToken(token);
+ while (token.type_ == tokenComment && ok) {
+ ok = readToken(token);
+ }
+ bool badTokenType =
+ (token.type_ != tokenArraySeparator && token.type_ != tokenArrayEnd);
+ if (!ok || badTokenType) {
+ return addErrorAndRecover(
+ "Missing ',' or ']' in array declaration", token, tokenArrayEnd);
+ }
+ if (token.type_ == tokenArrayEnd)
+ break;
+ }
+ return true;
+}
+
+bool OurReader::decodeNumber(Token& token) {
+ Value decoded;
+ if (!decodeNumber(token, decoded))
+ return false;
+ currentValue().swapPayload(decoded);
+ return true;
+}
+
+bool OurReader::decodeNumber(Token& token, Value& decoded) {
+ // Attempts to parse the number as an integer. If the number is
+ // larger than the maximum supported value of an integer then
+ // we decode the number as a double.
+ Location current = token.start_;
+ bool isNegative = *current == '-';
+ if (isNegative)
+ ++current;
+ // TODO: Help the compiler do the div and mod at compile time or get rid of them.
+ Value::LargestUInt maxIntegerValue =
+ isNegative ? Value::LargestUInt(-Value::minLargestInt)
+ : Value::maxLargestUInt;
+ Value::LargestUInt threshold = maxIntegerValue / 10;
+ Value::LargestUInt value = 0;
+ while (current < token.end_) {
+ Char c = *current++;
+ if (c < '0' || c > '9')
+ return decodeDouble(token, decoded);
+ Value::UInt digit(c - '0');
+ if (value >= threshold) {
+ // We've hit or exceeded the max value divided by 10 (rounded down). If
+ // a) we've only just touched the limit, b) this is the last digit, and
+ // c) it's small enough to fit in that rounding delta, we're okay.
+ // Otherwise treat this number as a double to avoid overflow.
+ if (value > threshold || current != token.end_ ||
+ digit > maxIntegerValue % 10) {
+ return decodeDouble(token, decoded);
+ }
+ }
+ value = value * 10 + digit;
+ }
+ if (isNegative)
+ decoded = -Value::LargestInt(value);
+ else if (value <= Value::LargestUInt(Value::maxInt))
+ decoded = Value::LargestInt(value);
+ else
+ decoded = value;
+ return true;
+}
+
+bool OurReader::decodeDouble(Token& token) {
+ Value decoded;
+ if (!decodeDouble(token, decoded))
+ return false;
+ currentValue().swapPayload(decoded);
+ return true;
+}
+
+bool OurReader::decodeDouble(Token& token, Value& decoded) {
+ double value = 0;
+ const int bufferSize = 32;
+ int count;
+ int length = int(token.end_ - token.start_);
+
+ // Sanity check to avoid buffer overflow exploits.
+ if (length < 0) {
+ return addError("Unable to parse token length", token);
+ }
+
+ // Avoid using a string constant for the format control string given to
+ // sscanf, as this can cause hard to debug crashes on OS X. See here for more
+ // info:
+ //
+ // http://developer.apple.com/library/mac/#DOCUMENTATION/DeveloperTools/gcc-4.0.1/gcc/Incompatibilities.html
+ char format[] = "%lf";
+
+ if (length <= bufferSize) {
+ Char buffer[bufferSize + 1];
+ memcpy(buffer, token.start_, length);
+ buffer[length] = 0;
+ count = sscanf(buffer, format, &value);
+ } else {
+ std::string buffer(token.start_, token.end_);
+ count = sscanf(buffer.c_str(), format, &value);
+ }
+
+ if (count != 1)
+ return addError("'" + std::string(token.start_, token.end_) +
+ "' is not a number.",
+ token);
+ decoded = value;
+ return true;
+}
+
+bool OurReader::decodeString(Token& token) {
+ std::string decoded_string;
+ if (!decodeString(token, decoded_string))
+ return false;
+ Value decoded(decoded_string);
+ currentValue().swapPayload(decoded);
+ return true;
+}
+
+bool OurReader::decodeString(Token& token, std::string& decoded) {
+ decoded.reserve(token.end_ - token.start_ - 2);
+ Location current = token.start_ + 1; // skip '"'
+ Location end = token.end_ - 1; // do not include '"'
+ while (current != end) {
+ Char c = *current++;
+ if (c == '"')
+ break;
+ else if (c == '\\') {
+ if (current == end)
+ return addError("Empty escape sequence in string", token, current);
+ Char escape = *current++;
+ switch (escape) {
+ case '"':
+ decoded += '"';
+ break;
+ case '/':
+ decoded += '/';
+ break;
+ case '\\':
+ decoded += '\\';
+ break;
+ case 'b':
+ decoded += '\b';
+ break;
+ case 'f':
+ decoded += '\f';
+ break;
+ case 'n':
+ decoded += '\n';
+ break;
+ case 'r':
+ decoded += '\r';
+ break;
+ case 't':
+ decoded += '\t';
+ break;
+ case 'u': {
+ unsigned int unicode;
+ if (!decodeUnicodeCodePoint(token, current, end, unicode))
+ return false;
+ decoded += codePointToUTF8(unicode);
+ } break;
+ default:
+ return addError("Bad escape sequence in string", token, current);
+ }
+ } else {
+ decoded += c;
+ }
+ }
+ return true;
+}
+
+bool OurReader::decodeUnicodeCodePoint(Token& token,
+ Location& current,
+ Location end,
+ unsigned int& unicode) {
+
+ if (!decodeUnicodeEscapeSequence(token, current, end, unicode))
+ return false;
+ if (unicode >= 0xD800 && unicode <= 0xDBFF) {
+ // surrogate pairs
+ if (end - current < 6)
+ return addError(
+ "additional six characters expected to parse unicode surrogate pair.",
+ token,
+ current);
+ unsigned int surrogatePair;
+ if (*(current++) == '\\' && *(current++) == 'u') {
+ if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) {
+ unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF);
+ } else
+ return false;
+ } else
+ return addError("expecting another \\u token to begin the second half of "
+ "a unicode surrogate pair",
+ token,
+ current);
+ }
+ return true;
+}
+
+bool OurReader::decodeUnicodeEscapeSequence(Token& token,
+ Location& current,
+ Location end,
+ unsigned int& unicode) {
+ if (end - current < 4)
+ return addError(
+ "Bad unicode escape sequence in string: four digits expected.",
+ token,
+ current);
+ unicode = 0;
+ for (int index = 0; index < 4; ++index) {
+ Char c = *current++;
+ unicode *= 16;
+ if (c >= '0' && c <= '9')
+ unicode += c - '0';
+ else if (c >= 'a' && c <= 'f')
+ unicode += c - 'a' + 10;
+ else if (c >= 'A' && c <= 'F')
+ unicode += c - 'A' + 10;
+ else
+ return addError(
+ "Bad unicode escape sequence in string: hexadecimal digit expected.",
+ token,
+ current);
+ }
+ return true;
+}
+
+bool
+OurReader::addError(const std::string& message, Token& token, Location extra) {
+ ErrorInfo info;
+ info.token_ = token;
+ info.message_ = message;
+ info.extra_ = extra;
+ errors_.push_back(info);
+ return false;
+}
+
+bool OurReader::recoverFromError(TokenType skipUntilToken) {
+ int errorCount = int(errors_.size());
+ Token skip;
+ for (;;) {
+ if (!readToken(skip))
+ errors_.resize(errorCount); // discard errors caused by recovery
+ if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream)
+ break;
+ }
+ errors_.resize(errorCount);
+ return false;
+}
+
+bool OurReader::addErrorAndRecover(const std::string& message,
+ Token& token,
+ TokenType skipUntilToken) {
+ addError(message, token);
+ return recoverFromError(skipUntilToken);
+}
+
+Value& OurReader::currentValue() { return *(nodes_.top()); }
+
+OurReader::Char OurReader::getNextChar() {
+ if (current_ == end_)
+ return 0;
+ return *current_++;
+}
+
+void OurReader::getLocationLineAndColumn(Location location,
+ int& line,
+ int& column) const {
+ Location current = begin_;
+ Location lastLineStart = current;
+ line = 0;
+ while (current < location && current != end_) {
+ Char c = *current++;
+ if (c == '\r') {
+ if (*current == '\n')
+ ++current;
+ lastLineStart = current;
+ ++line;
+ } else if (c == '\n') {
+ lastLineStart = current;
+ ++line;
+ }
+ }
+ // column & line start at 1
+ column = int(location - lastLineStart) + 1;
+ ++line;
+}
+
+std::string OurReader::getLocationLineAndColumn(Location location) const {
+ int line, column;
+ getLocationLineAndColumn(location, line, column);
+ char buffer[18 + 16 + 16 + 1];
+ snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column);
+ return buffer;
+}
+
+std::string OurReader::getFormattedErrorMessages() const {
+ std::string formattedMessage;
+ for (Errors::const_iterator itError = errors_.begin();
+ itError != errors_.end();
+ ++itError) {
+ const ErrorInfo& error = *itError;
+ formattedMessage +=
+ "* " + getLocationLineAndColumn(error.token_.start_) + "\n";
+ formattedMessage += " " + error.message_ + "\n";
+ if (error.extra_)
+ formattedMessage +=
+ "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n";
+ }
+ return formattedMessage;
+}
+
+
+class OurCharReader : public CharReader {
+ bool const collectComments_;
+ OurReader reader_;
+public:
+ OurCharReader(
+ bool collectComments,
+ OurFeatures const& features)
+ : collectComments_(collectComments)
+ , reader_(features)
+ {}
+ virtual bool parse(
+ char const* beginDoc, char const* endDoc,
+ Value* root, std::string* errs) {
+ bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_);
+ if (errs) {
+ *errs = reader_.getFormattedErrorMessages();
+ }
+ return ok;
+ }
+};
+
+CharReaderBuilder::CharReaderBuilder()
+{
+ setDefaults(&settings_);
+}
+CharReaderBuilder::~CharReaderBuilder()
+{}
+CharReader* CharReaderBuilder::newCharReader() const
+{
+ bool collectComments = settings_["collectComments"].asBool();
+ OurFeatures features = OurFeatures::all();
+ features.allowComments_ = settings_["allowComments"].asBool();
+ features.strictRoot_ = settings_["strictRoot"].asBool();
+ features.allowDroppedNullPlaceholders_ = settings_["allowDroppedNullPlaceholders"].asBool();
+ features.allowNumericKeys_ = settings_["allowNumericKeys"].asBool();
+ features.allowSingleQuotes_ = settings_["allowSingleQuotes"].asBool();
+ features.stackLimit_ = settings_["stackLimit"].asInt();
+ features.failIfExtra_ = settings_["failIfExtra"].asBool();
+ features.rejectDupKeys_ = settings_["rejectDupKeys"].asBool();
+ features.allowSpecialFloats_ = settings_["allowSpecialFloats"].asBool();
+ return new OurCharReader(collectComments, features);
+}
+static void getValidReaderKeys(std::set<std::string>* valid_keys)
+{
+ valid_keys->clear();
+ valid_keys->insert("collectComments");
+ valid_keys->insert("allowComments");
+ valid_keys->insert("strictRoot");
+ valid_keys->insert("allowDroppedNullPlaceholders");
+ valid_keys->insert("allowNumericKeys");
+ valid_keys->insert("allowSingleQuotes");
+ valid_keys->insert("stackLimit");
+ valid_keys->insert("failIfExtra");
+ valid_keys->insert("rejectDupKeys");
+ valid_keys->insert("allowSpecialFloats");
+}
+bool CharReaderBuilder::validate(Json::Value* invalid) const
+{
+ Json::Value my_invalid;
+ if (!invalid) invalid = &my_invalid; // so we do not need to test for NULL
+ Json::Value& inv = *invalid;
+ std::set<std::string> valid_keys;
+ getValidReaderKeys(&valid_keys);
+ Value::Members keys = settings_.getMemberNames();
+ size_t n = keys.size();
+ for (size_t i = 0; i < n; ++i) {
+ std::string const& key = keys[i];
+ if (valid_keys.find(key) == valid_keys.end()) {
+ inv[key] = settings_[key];
+ }
+ }
+ return 0u == inv.size();
+}
+Value& CharReaderBuilder::operator[](std::string key)
+{
+ return settings_[key];
+}
+// static
+void CharReaderBuilder::strictMode(Json::Value* settings)
+{
+//! [CharReaderBuilderStrictMode]
+ (*settings)["allowComments"] = false;
+ (*settings)["strictRoot"] = true;
+ (*settings)["allowDroppedNullPlaceholders"] = false;
+ (*settings)["allowNumericKeys"] = false;
+ (*settings)["allowSingleQuotes"] = false;
+ (*settings)["failIfExtra"] = true;
+ (*settings)["rejectDupKeys"] = true;
+ (*settings)["allowSpecialFloats"] = false;
+//! [CharReaderBuilderStrictMode]
+}
+// static
+void CharReaderBuilder::setDefaults(Json::Value* settings)
+{
+//! [CharReaderBuilderDefaults]
+ (*settings)["collectComments"] = true;
+ (*settings)["allowComments"] = true;
+ (*settings)["strictRoot"] = false;
+ (*settings)["allowDroppedNullPlaceholders"] = false;
+ (*settings)["allowNumericKeys"] = false;
+ (*settings)["allowSingleQuotes"] = false;
+ (*settings)["stackLimit"] = 1000;
+ (*settings)["failIfExtra"] = false;
+ (*settings)["rejectDupKeys"] = false;
+ (*settings)["allowSpecialFloats"] = false;
+//! [CharReaderBuilderDefaults]
+}
+
+//////////////////////////////////
+// global functions
+
+bool parseFromStream(
+ CharReader::Factory const& fact, std::istream& sin,
+ Value* root, std::string* errs)
+{
+ std::ostringstream ssin;
+ ssin << sin.rdbuf();
+ std::string doc = ssin.str();
+ char const* begin = doc.data();
+ char const* end = begin + doc.size();
+ // Note that we do not actually need a null-terminator.
+ CharReaderPtr const reader(fact.newCharReader());
+ return reader->parse(begin, end, root, errs);
+}
+
+std::istream& operator>>(std::istream& sin, Value& root) {
+ CharReaderBuilder b;
+ std::string errs;
+ bool ok = parseFromStream(b, sin, &root, &errs);
+ if (!ok) {
+ fprintf(stderr,
+ "Error from reader: %s",
+ errs.c_str());
+
+ throwRuntimeError("reader error");
+ }
+ return sin;
+}
+
+} // namespace Json
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: src/lib_json/json_reader.cpp
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: src/lib_json/json_valueiterator.inl
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2007-2010 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+// included by json_value.cpp
+
+namespace Json {
+
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// class ValueIteratorBase
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+
+ValueIteratorBase::ValueIteratorBase()
+ : current_(), isNull_(true) {
+}
+
+ValueIteratorBase::ValueIteratorBase(
+ const Value::ObjectValues::iterator& current)
+ : current_(current), isNull_(false) {}
+
+Value& ValueIteratorBase::deref() const {
+ return current_->second;
+}
+
+void ValueIteratorBase::increment() {
+ ++current_;
+}
+
+void ValueIteratorBase::decrement() {
+ --current_;
+}
+
+ValueIteratorBase::difference_type
+ValueIteratorBase::computeDistance(const SelfType& other) const {
+#ifdef JSON_USE_CPPTL_SMALLMAP
+ return other.current_ - current_;
+#else
+ // Iterator for null value are initialized using the default
+ // constructor, which initialize current_ to the default
+ // std::map::iterator. As begin() and end() are two instance
+ // of the default std::map::iterator, they can not be compared.
+ // To allow this, we handle this comparison specifically.
+ if (isNull_ && other.isNull_) {
+ return 0;
+ }
+
+ // Usage of std::distance is not portable (does not compile with Sun Studio 12
+ // RogueWave STL,
+ // which is the one used by default).
+ // Using a portable hand-made version for non random iterator instead:
+ // return difference_type( std::distance( current_, other.current_ ) );
+ difference_type myDistance = 0;
+ for (Value::ObjectValues::iterator it = current_; it != other.current_;
+ ++it) {
+ ++myDistance;
+ }
+ return myDistance;
+#endif
+}
+
+bool ValueIteratorBase::isEqual(const SelfType& other) const {
+ if (isNull_) {
+ return other.isNull_;
+ }
+ return current_ == other.current_;
+}
+
+void ValueIteratorBase::copy(const SelfType& other) {
+ current_ = other.current_;
+ isNull_ = other.isNull_;
+}
+
+Value ValueIteratorBase::key() const {
+ const Value::CZString czstring = (*current_).first;
+ if (czstring.data()) {
+ if (czstring.isStaticString())
+ return Value(StaticString(czstring.data()));
+ return Value(czstring.data(), czstring.data() + czstring.length());
+ }
+ return Value(czstring.index());
+}
+
+UInt ValueIteratorBase::index() const {
+ const Value::CZString czstring = (*current_).first;
+ if (!czstring.data())
+ return czstring.index();
+ return Value::UInt(-1);
+}
+
+std::string ValueIteratorBase::name() const {
+ char const* keey;
+ char const* end;
+ keey = memberName(&end);
+ if (!keey) return std::string();
+ return std::string(keey, end);
+}
+
+char const* ValueIteratorBase::memberName() const {
+ const char* cname = (*current_).first.data();
+ return cname ? cname : "";
+}
+
+char const* ValueIteratorBase::memberName(char const** end) const {
+ const char* cname = (*current_).first.data();
+ if (!cname) {
+ *end = NULL;
+ return NULL;
+ }
+ *end = cname + (*current_).first.length();
+ return cname;
+}
+
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// class ValueConstIterator
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+
+ValueConstIterator::ValueConstIterator() {}
+
+ValueConstIterator::ValueConstIterator(
+ const Value::ObjectValues::iterator& current)
+ : ValueIteratorBase(current) {}
+
+ValueConstIterator& ValueConstIterator::
+operator=(const ValueIteratorBase& other) {
+ copy(other);
+ return *this;
+}
+
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// class ValueIterator
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+
+ValueIterator::ValueIterator() {}
+
+ValueIterator::ValueIterator(const Value::ObjectValues::iterator& current)
+ : ValueIteratorBase(current) {}
+
+ValueIterator::ValueIterator(const ValueConstIterator& other)
+ : ValueIteratorBase(other) {}
+
+ValueIterator::ValueIterator(const ValueIterator& other)
+ : ValueIteratorBase(other) {}
+
+ValueIterator& ValueIterator::operator=(const SelfType& other) {
+ copy(other);
+ return *this;
+}
+
+} // namespace Json
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: src/lib_json/json_valueiterator.inl
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: src/lib_json/json_value.cpp
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2011 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+#if !defined(JSON_IS_AMALGAMATION)
+#include <json/assertions.h>
+#include <json/value.h>
+#include <json/writer.h>
+#endif // if !defined(JSON_IS_AMALGAMATION)
+#include <math.h>
+#include <sstream>
+#include <utility>
+#include <cstring>
+#include <cassert>
+#ifdef JSON_USE_CPPTL
+#include <cpptl/conststring.h>
+#endif
+#include <cstddef> // size_t
+#include <algorithm> // min()
+
+#define JSON_ASSERT_UNREACHABLE assert(false)
+
+namespace Json {
+
+// This is a walkaround to avoid the static initialization of Value::null.
+// kNull must be word-aligned to avoid crashing on ARM. We use an alignment of
+// 8 (instead of 4) as a bit of future-proofing.
+#if defined(__ARMEL__)
+#define ALIGNAS(byte_alignment) __attribute__((aligned(byte_alignment)))
+#else
+// This exists for binary compatibility only. Use nullRef.
+const Value Value::null;
+#define ALIGNAS(byte_alignment)
+#endif
+static const unsigned char ALIGNAS(8) kNull[sizeof(Value)] = { 0 };
+const unsigned char& kNullRef = kNull[0];
+const Value& Value::nullRef = reinterpret_cast<const Value&>(kNullRef);
+
+const Int Value::minInt = Int(~(UInt(-1) / 2));
+const Int Value::maxInt = Int(UInt(-1) / 2);
+const UInt Value::maxUInt = UInt(-1);
+#if defined(JSON_HAS_INT64)
+const Int64 Value::minInt64 = Int64(~(UInt64(-1) / 2));
+const Int64 Value::maxInt64 = Int64(UInt64(-1) / 2);
+const UInt64 Value::maxUInt64 = UInt64(-1);
+// The constant is hard-coded because some compiler have trouble
+// converting Value::maxUInt64 to a double correctly (AIX/xlC).
+// Assumes that UInt64 is a 64 bits integer.
+static const double maxUInt64AsDouble = 18446744073709551615.0;
+#endif // defined(JSON_HAS_INT64)
+const LargestInt Value::minLargestInt = LargestInt(~(LargestUInt(-1) / 2));
+const LargestInt Value::maxLargestInt = LargestInt(LargestUInt(-1) / 2);
+const LargestUInt Value::maxLargestUInt = LargestUInt(-1);
+
+#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION)
+template <typename T, typename U>
+static inline bool InRange(double d, T min, U max) {
+ return d >= min && d <= max;
+}
+#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION)
+static inline double integerToDouble(Json::UInt64 value) {
+ return static_cast<double>(Int64(value / 2)) * 2.0 + Int64(value & 1);
+}
+
+template <typename T> static inline double integerToDouble(T value) {
+ return static_cast<double>(value);
+}
+
+template <typename T, typename U>
+static inline bool InRange(double d, T min, U max) {
+ return d >= integerToDouble(min) && d <= integerToDouble(max);
+}
+#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION)
+
+/** Duplicates the specified string value.
+ * @param value Pointer to the string to duplicate. Must be zero-terminated if
+ * length is "unknown".
+ * @param length Length of the value. if equals to unknown, then it will be
+ * computed using strlen(value).
+ * @return Pointer on the duplicate instance of string.
+ */
+static inline char* duplicateStringValue(const char* value,
+ size_t length) {
+ // Avoid an integer overflow in the call to malloc below by limiting length
+ // to a sane value.
+ if (length >= (size_t)Value::maxInt)
+ length = Value::maxInt - 1;
+
+ char* newString = static_cast<char*>(OG_MALLOC(length + 1));
+ if (newString == NULL) {
+ throwRuntimeError(
+ "in Json::Value::duplicateStringValue(): "
+ "Failed to allocate string value buffer");
+ }
+ memcpy(newString, value, length);
+ newString[length] = 0;
+ return newString;
+}
+
+/* Record the length as a prefix.
+ */
+static inline char* duplicateAndPrefixStringValue(
+ const char* value,
+ unsigned int length)
+{
+ // Avoid an integer overflow in the call to malloc below by limiting length
+ // to a sane value.
+ JSON_ASSERT_MESSAGE(length <= (unsigned)Value::maxInt - sizeof(unsigned) - 1U,
+ "in Json::Value::duplicateAndPrefixStringValue(): "
+ "length too big for prefixing");
+ unsigned actualLength = length + static_cast<unsigned>(sizeof(unsigned)) + 1U;
+ char* newString = static_cast<char*>(OG_MALLOC(actualLength));
+ if (newString == 0) {
+ throwRuntimeError(
+ "in Json::Value::duplicateAndPrefixStringValue(): "
+ "Failed to allocate string value buffer");
+ }
+ *reinterpret_cast<unsigned*>(newString) = length;
+ memcpy(newString + sizeof(unsigned), value, length);
+ newString[actualLength - 1U] = 0; // to avoid buffer over-run accidents by users later
+ return newString;
+}
+inline static void decodePrefixedString(
+ bool isPrefixed, char const* prefixed,
+ unsigned* length, char const** value)
+{
+ if (!isPrefixed) {
+ *length = static_cast<unsigned>(strlen(prefixed));
+ *value = prefixed;
+ } else {
+ *length = *reinterpret_cast<unsigned const*>(prefixed);
+ *value = prefixed + sizeof(unsigned);
+ }
+}
+/** Free the string duplicated by duplicateStringValue()/duplicateAndPrefixStringValue().
+ */
+static inline void releaseStringValue(char* value) { OG_FREE(value); }
+
+} // namespace Json
+
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// ValueInternals...
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+#if !defined(JSON_IS_AMALGAMATION)
+
+#include "json_valueiterator.inl"
+#endif // if !defined(JSON_IS_AMALGAMATION)
+
+namespace Json {
+
+Exception::Exception(std::string const& msg)
+ : msg_(msg)
+{}
+Exception::~Exception() throw()
+{}
+char const* Exception::what() const throw()
+{
+ return msg_.c_str();
+}
+RuntimeError::RuntimeError(std::string const& msg)
+ : Exception(msg)
+{}
+LogicError::LogicError(std::string const& msg)
+ : Exception(msg)
+{}
+void throwRuntimeError(std::string const& msg)
+{
+ throw RuntimeError(msg);
+}
+void throwLogicError(std::string const& msg)
+{
+ throw LogicError(msg);
+}
+
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// class Value::CommentInfo
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+
+Value::CommentInfo::CommentInfo() : comment_(0) {}
+
+Value::CommentInfo::~CommentInfo() {
+ if (comment_)
+ releaseStringValue(comment_);
+}
+
+void Value::CommentInfo::setComment(const char* text, size_t len) {
+ if (comment_) {
+ releaseStringValue(comment_);
+ comment_ = 0;
+ }
+ JSON_ASSERT(text != 0);
+ JSON_ASSERT_MESSAGE(
+ text[0] == '\0' || text[0] == '/',
+ "in Json::Value::setComment(): Comments must start with /");
+ // It seems that /**/ style comments are acceptable as well.
+ comment_ = duplicateStringValue(text, len);
+}
+
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// class Value::CZString
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+
+// Notes: policy_ indicates if the string was allocated when
+// a string is stored.
+
+Value::CZString::CZString(ArrayIndex aindex) : cstr_(0), index_(aindex) {}
+
+Value::CZString::CZString(char const* str, unsigned ulength, DuplicationPolicy allocate)
+ : cstr_(str)
+{
+ // allocate != duplicate
+ storage_.policy_ = allocate & 0x3;
+ storage_.length_ = ulength & 0x3FFFFFFF;
+}
+
+Value::CZString::CZString(const CZString& other)
+ : cstr_(other.storage_.policy_ != noDuplication && other.cstr_ != 0
+ ? duplicateStringValue(other.cstr_, other.storage_.length_)
+ : other.cstr_)
+{
+ storage_.policy_ = (other.cstr_
+ ? (static_cast<DuplicationPolicy>(other.storage_.policy_) == noDuplication
+ ? noDuplication : duplicate)
+ : static_cast<DuplicationPolicy>(other.storage_.policy_));
+ storage_.length_ = other.storage_.length_;
+}
+
+Value::CZString::~CZString() {
+ if (cstr_ && storage_.policy_ == duplicate)
+ releaseStringValue(const_cast<char*>(cstr_));
+}
+
+void Value::CZString::swap(CZString& other) {
+ std::swap(cstr_, other.cstr_);
+ std::swap(index_, other.index_);
+}
+
+Value::CZString& Value::CZString::operator=(CZString other) {
+ swap(other);
+ return *this;
+}
+
+bool Value::CZString::operator<(const CZString& other) const {
+ if (!cstr_) return index_ < other.index_;
+ //return strcmp(cstr_, other.cstr_) < 0;
+ // Assume both are strings.
+ unsigned this_len = this->storage_.length_;
+ unsigned other_len = other.storage_.length_;
+ unsigned min_len = std::min(this_len, other_len);
+ int comp = memcmp(this->cstr_, other.cstr_, min_len);
+ if (comp < 0) return true;
+ if (comp > 0) return false;
+ return (this_len < other_len);
+}
+
+bool Value::CZString::operator==(const CZString& other) const {
+ if (!cstr_) return index_ == other.index_;
+ //return strcmp(cstr_, other.cstr_) == 0;
+ // Assume both are strings.
+ unsigned this_len = this->storage_.length_;
+ unsigned other_len = other.storage_.length_;
+ if (this_len != other_len) return false;
+ int comp = memcmp(this->cstr_, other.cstr_, this_len);
+ return comp == 0;
+}
+
+ArrayIndex Value::CZString::index() const { return index_; }
+
+//const char* Value::CZString::c_str() const { return cstr_; }
+const char* Value::CZString::data() const { return cstr_; }
+unsigned Value::CZString::length() const { return storage_.length_; }
+bool Value::CZString::isStaticString() const { return storage_.policy_ == noDuplication; }
+
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// class Value::Value
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+
+/*! \internal Default constructor initialization must be equivalent to:
+ * memset( this, 0, sizeof(Value) )
+ * This optimization is used in ValueInternalMap fast allocator.
+ */
+Value::Value(ValueType vtype) {
+ initBasic(vtype);
+ switch (vtype) {
+ case nullValue:
+ break;
+ case intValue:
+ case uintValue:
+ value_.int_ = 0;
+ break;
+ case realValue:
+ value_.real_ = 0.0;
+ break;
+ case stringValue:
+ value_.string_ = 0;
+ break;
+ case arrayValue:
+ case objectValue:
+ value_.map_ = new ObjectValues();
+ break;
+ case booleanValue:
+ value_.bool_ = false;
+ break;
+ default:
+ JSON_ASSERT_UNREACHABLE;
+ }
+}
+
+Value::Value(Int value) {
+ initBasic(intValue);
+ value_.int_ = value;
+}
+
+Value::Value(UInt value) {
+ initBasic(uintValue);
+ value_.uint_ = value;
+}
+#if defined(JSON_HAS_INT64)
+Value::Value(Int64 value) {
+ initBasic(intValue);
+ value_.int_ = value;
+}
+Value::Value(UInt64 value) {
+ initBasic(uintValue);
+ value_.uint_ = value;
+}
+#endif // defined(JSON_HAS_INT64)
+
+Value::Value(double value) {
+ initBasic(realValue);
+ value_.real_ = value;
+}
+
+Value::Value(const char* value) {
+ initBasic(stringValue, true);
+ value_.string_ = duplicateAndPrefixStringValue(value, static_cast<unsigned>(strlen(value)));
+}
+
+Value::Value(const char* beginValue, const char* endValue) {
+ initBasic(stringValue, true);
+ value_.string_ =
+ duplicateAndPrefixStringValue(beginValue, static_cast<unsigned>(endValue - beginValue));
+}
+
+Value::Value(const std::string& value) {
+ initBasic(stringValue, true);
+ value_.string_ =
+ duplicateAndPrefixStringValue(value.data(), static_cast<unsigned>(value.length()));
+}
+
+Value::Value(const StaticString& value) {
+ initBasic(stringValue);
+ value_.string_ = const_cast<char*>(value.c_str());
+}
+
+#ifdef JSON_USE_CPPTL
+Value::Value(const CppTL::ConstString& value) {
+ initBasic(stringValue, true);
+ value_.string_ = duplicateAndPrefixStringValue(value, static_cast<unsigned>(value.length()));
+}
+#endif
+
+Value::Value(bool value) {
+ initBasic(booleanValue);
+ value_.bool_ = value;
+}
+
+Value::Value(Value const& other)
+ : type_(other.type_), allocated_(false)
+ ,
+ comments_(0)
+{
+ switch (type_) {
+ case nullValue:
+ case intValue:
+ case uintValue:
+ case realValue:
+ case booleanValue:
+ value_ = other.value_;
+ break;
+ case stringValue:
+ if (other.value_.string_ && other.allocated_) {
+ unsigned len;
+ char const* str;
+ decodePrefixedString(other.allocated_, other.value_.string_,
+ &len, &str);
+ value_.string_ = duplicateAndPrefixStringValue(str, len);
+ allocated_ = true;
+ } else {
+ value_.string_ = other.value_.string_;
+ allocated_ = false;
+ }
+ break;
+ case arrayValue:
+ case objectValue:
+ value_.map_ = new ObjectValues(*other.value_.map_);
+ break;
+ default:
+ JSON_ASSERT_UNREACHABLE;
+ }
+ if (other.comments_) {
+ comments_ = new CommentInfo[numberOfCommentPlacement];
+ for (int comment = 0; comment < numberOfCommentPlacement; ++comment) {
+ const CommentInfo& otherComment = other.comments_[comment];
+ if (otherComment.comment_)
+ comments_[comment].setComment(
+ otherComment.comment_, strlen(otherComment.comment_));
+ }
+ }
+}
+
+Value::~Value() {
+ switch (type_) {
+ case nullValue:
+ case intValue:
+ case uintValue:
+ case realValue:
+ case booleanValue:
+ break;
+ case stringValue:
+ if (allocated_)
+ releaseStringValue(value_.string_);
+ break;
+ case arrayValue:
+ case objectValue:
+ delete value_.map_;
+ break;
+ default:
+ JSON_ASSERT_UNREACHABLE;
+ }
+
+ if (comments_)
+ delete[] comments_;
+}
+
+Value &Value::operator=(const Value &other) {
+ Value temp(other);
+ swap(temp);
+ return *this;
+}
+
+void Value::swapPayload(Value& other) {
+ ValueType temp = type_;
+ type_ = other.type_;
+ other.type_ = temp;
+ std::swap(value_, other.value_);
+ int temp2 = allocated_;
+ allocated_ = other.allocated_;
+ other.allocated_ = temp2 & 0x1;
+}
+
+void Value::swap(Value& other) {
+ swapPayload(other);
+ std::swap(comments_, other.comments_);
+}
+
+ValueType Value::type() const { return type_; }
+
+int Value::compare(const Value& other) const {
+ if (*this < other)
+ return -1;
+ if (*this > other)
+ return 1;
+ return 0;
+}
+
+bool Value::operator<(const Value& other) const {
+ int typeDelta = type_ - other.type_;
+ if (typeDelta)
+ return typeDelta < 0 ? true : false;
+ switch (type_) {
+ case nullValue:
+ return false;
+ case intValue:
+ return value_.int_ < other.value_.int_;
+ case uintValue:
+ return value_.uint_ < other.value_.uint_;
+ case realValue:
+ return value_.real_ < other.value_.real_;
+ case booleanValue:
+ return value_.bool_ < other.value_.bool_;
+ case stringValue:
+ {
+ if ((value_.string_ == 0) || (other.value_.string_ == 0)) {
+ if (other.value_.string_) return true;
+ else return false;
+ }
+ unsigned this_len;
+ unsigned other_len;
+ char const* this_str;
+ char const* other_str;
+ decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str);
+ decodePrefixedString(other.allocated_, other.value_.string_, &other_len, &other_str);
+ unsigned min_len = std::min(this_len, other_len);
+ int comp = memcmp(this_str, other_str, min_len);
+ if (comp < 0) return true;
+ if (comp > 0) return false;
+ return (this_len < other_len);
+ }
+ case arrayValue:
+ case objectValue: {
+ int delta = int(value_.map_->size() - other.value_.map_->size());
+ if (delta)
+ return delta < 0;
+ return (*value_.map_) < (*other.value_.map_);
+ }
+ default:
+ JSON_ASSERT_UNREACHABLE;
+ }
+ return false; // unreachable
+}
+
+bool Value::operator<=(const Value& other) const { return !(other < *this); }
+
+bool Value::operator>=(const Value& other) const { return !(*this < other); }
+
+bool Value::operator>(const Value& other) const { return other < *this; }
+
+bool Value::operator==(const Value& other) const {
+ // if ( type_ != other.type_ )
+ // GCC 2.95.3 says:
+ // attempt to take address of bit-field structure member `Json::Value::type_'
+ // Beats me, but a temp solves the problem.
+ int temp = other.type_;
+ if (type_ != temp)
+ return false;
+ switch (type_) {
+ case nullValue:
+ return true;
+ case intValue:
+ return value_.int_ == other.value_.int_;
+ case uintValue:
+ return value_.uint_ == other.value_.uint_;
+ case realValue:
+ return value_.real_ == other.value_.real_;
+ case booleanValue:
+ return value_.bool_ == other.value_.bool_;
+ case stringValue:
+ {
+ if ((value_.string_ == 0) || (other.value_.string_ == 0)) {
+ return (value_.string_ == other.value_.string_);
+ }
+ unsigned this_len;
+ unsigned other_len;
+ char const* this_str;
+ char const* other_str;
+ decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str);
+ decodePrefixedString(other.allocated_, other.value_.string_, &other_len, &other_str);
+ if (this_len != other_len) return false;
+ int comp = memcmp(this_str, other_str, this_len);
+ return comp == 0;
+ }
+ case arrayValue:
+ case objectValue:
+ return value_.map_->size() == other.value_.map_->size() &&
+ (*value_.map_) == (*other.value_.map_);
+ default:
+ JSON_ASSERT_UNREACHABLE;
+ }
+ return false; // unreachable
+}
+
+bool Value::operator!=(const Value& other) const { return !(*this == other); }
+
+const char* Value::asCString() const {
+ JSON_ASSERT_MESSAGE(type_ == stringValue,
+ "in Json::Value::asCString(): requires stringValue");
+ if (value_.string_ == 0) return 0;
+ unsigned this_len;
+ char const* this_str;
+ decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str);
+ return this_str;
+}
+
+bool Value::getString(char const** str, char const** cend) const {
+ if (type_ != stringValue) return false;
+ if (value_.string_ == 0) return false;
+ unsigned length;
+ decodePrefixedString(this->allocated_, this->value_.string_, &length, str);
+ *cend = *str + length;
+ return true;
+}
+
+std::string Value::asString() const {
+ switch (type_) {
+ case nullValue:
+ return "";
+ case stringValue:
+ {
+ if (value_.string_ == 0) return "";
+ unsigned this_len;
+ char const* this_str;
+ decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str);
+ return std::string(this_str, this_len);
+ }
+ case booleanValue:
+ return value_.bool_ ? "true" : "false";
+ case intValue:
+ return valueToString(value_.int_);
+ case uintValue:
+ return valueToString(value_.uint_);
+ case realValue:
+ return valueToString(value_.real_);
+ default:
+ JSON_FAIL_MESSAGE("Type is not convertible to string");
+ }
+}
+
+#ifdef JSON_USE_CPPTL
+CppTL::ConstString Value::asConstString() const {
+ unsigned len;
+ char const* str;
+ decodePrefixedString(allocated_, value_.string_,
+ &len, &str);
+ return CppTL::ConstString(str, len);
+}
+#endif
+
+Value::Int Value::asInt() const {
+ switch (type_) {
+ case intValue:
+ JSON_ASSERT_MESSAGE(isInt(), "LargestInt out of Int range");
+ return Int(value_.int_);
+ case uintValue:
+ JSON_ASSERT_MESSAGE(isInt(), "LargestUInt out of Int range");
+ return Int(value_.uint_);
+ case realValue:
+ JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt, maxInt),
+ "double out of Int range");
+ return Int(value_.real_);
+ case nullValue:
+ return 0;
+ case booleanValue:
+ return value_.bool_ ? 1 : 0;
+ default:
+ break;
+ }
+ JSON_FAIL_MESSAGE("Value is not convertible to Int.");
+}
+
+Value::UInt Value::asUInt() const {
+ switch (type_) {
+ case intValue:
+ JSON_ASSERT_MESSAGE(isUInt(), "LargestInt out of UInt range");
+ return UInt(value_.int_);
+ case uintValue:
+ JSON_ASSERT_MESSAGE(isUInt(), "LargestUInt out of UInt range");
+ return UInt(value_.uint_);
+ case realValue:
+ JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt),
+ "double out of UInt range");
+ return UInt(value_.real_);
+ case nullValue:
+ return 0;
+ case booleanValue:
+ return value_.bool_ ? 1 : 0;
+ default:
+ break;
+ }
+ JSON_FAIL_MESSAGE("Value is not convertible to UInt.");
+}
+
+#if defined(JSON_HAS_INT64)
+
+Value::Int64 Value::asInt64() const {
+ switch (type_) {
+ case intValue:
+ return Int64(value_.int_);
+ case uintValue:
+ JSON_ASSERT_MESSAGE(isInt64(), "LargestUInt out of Int64 range");
+ return Int64(value_.uint_);
+ case realValue:
+ JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt64, maxInt64),
+ "double out of Int64 range");
+ return Int64(value_.real_);
+ case nullValue:
+ return 0;
+ case booleanValue:
+ return value_.bool_ ? 1 : 0;
+ default:
+ break;
+ }
+ JSON_FAIL_MESSAGE("Value is not convertible to Int64.");
+}
+
+Value::UInt64 Value::asUInt64() const {
+ switch (type_) {
+ case intValue:
+ JSON_ASSERT_MESSAGE(isUInt64(), "LargestInt out of UInt64 range");
+ return UInt64(value_.int_);
+ case uintValue:
+ return UInt64(value_.uint_);
+ case realValue:
+ JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt64),
+ "double out of UInt64 range");
+ return UInt64(value_.real_);
+ case nullValue:
+ return 0;
+ case booleanValue:
+ return value_.bool_ ? 1 : 0;
+ default:
+ break;
+ }
+ JSON_FAIL_MESSAGE("Value is not convertible to UInt64.");
+}
+#endif // if defined(JSON_HAS_INT64)
+
+LargestInt Value::asLargestInt() const {
+#if defined(JSON_NO_INT64)
+ return asInt();
+#else
+ return asInt64();
+#endif
+}
+
+LargestUInt Value::asLargestUInt() const {
+#if defined(JSON_NO_INT64)
+ return asUInt();
+#else
+ return asUInt64();
+#endif
+}
+
+double Value::asDouble() const {
+ switch (type_) {
+ case intValue:
+ return static_cast<double>(value_.int_);
+ case uintValue:
+#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION)
+ return static_cast<double>(value_.uint_);
+#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION)
+ return integerToDouble(value_.uint_);
+#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION)
+ case realValue:
+ return value_.real_;
+ case nullValue:
+ return 0.0;
+ case booleanValue:
+ return value_.bool_ ? 1.0 : 0.0;
+ default:
+ break;
+ }
+ JSON_FAIL_MESSAGE("Value is not convertible to double.");
+}
+
+float Value::asFloat() const {
+ switch (type_) {
+ case intValue:
+ return static_cast<float>(value_.int_);
+ case uintValue:
+#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION)
+ return static_cast<float>(value_.uint_);
+#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION)
+ return integerToDouble(value_.uint_);
+#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION)
+ case realValue:
+ return static_cast<float>(value_.real_);
+ case nullValue:
+ return 0.0;
+ case booleanValue:
+ return value_.bool_ ? 1.0f : 0.0f;
+ default:
+ break;
+ }
+ JSON_FAIL_MESSAGE("Value is not convertible to float.");
+}
+
+bool Value::asBool() const {
+ switch (type_) {
+ case booleanValue:
+ return value_.bool_;
+ case nullValue:
+ return false;
+ case intValue:
+ return value_.int_ ? true : false;
+ case uintValue:
+ return value_.uint_ ? true : false;
+ case realValue:
+ // This is kind of strange. Not recommended.
+ return (value_.real_ != 0.0) ? true : false;
+ default:
+ break;
+ }
+ JSON_FAIL_MESSAGE("Value is not convertible to bool.");
+}
+
+bool Value::isConvertibleTo(ValueType other) const {
+ switch (other) {
+ case nullValue:
+ return (isNumeric() && asDouble() == 0.0) ||
+ (type_ == booleanValue && value_.bool_ == false) ||
+ (type_ == stringValue && asString() == "") ||
+ (type_ == arrayValue && value_.map_->size() == 0) ||
+ (type_ == objectValue && value_.map_->size() == 0) ||
+ type_ == nullValue;
+ case intValue:
+ return isInt() ||
+ (type_ == realValue && InRange(value_.real_, minInt, maxInt)) ||
+ type_ == booleanValue || type_ == nullValue;
+ case uintValue:
+ return isUInt() ||
+ (type_ == realValue && InRange(value_.real_, 0, maxUInt)) ||
+ type_ == booleanValue || type_ == nullValue;
+ case realValue:
+ return isNumeric() || type_ == booleanValue || type_ == nullValue;
+ case booleanValue:
+ return isNumeric() || type_ == booleanValue || type_ == nullValue;
+ case stringValue:
+ return isNumeric() || type_ == booleanValue || type_ == stringValue ||
+ type_ == nullValue;
+ case arrayValue:
+ return type_ == arrayValue || type_ == nullValue;
+ case objectValue:
+ return type_ == objectValue || type_ == nullValue;
+ }
+ JSON_ASSERT_UNREACHABLE;
+ return false;
+}
+
+/// Number of values in array or object
+ArrayIndex Value::size() const {
+ switch (type_) {
+ case nullValue:
+ case intValue:
+ case uintValue:
+ case realValue:
+ case booleanValue:
+ case stringValue:
+ return 0;
+ case arrayValue: // size of the array is highest index + 1
+ if (!value_.map_->empty()) {
+ ObjectValues::const_iterator itLast = value_.map_->end();
+ --itLast;
+ return (*itLast).first.index() + 1;
+ }
+ return 0;
+ case objectValue:
+ return ArrayIndex(value_.map_->size());
+ }
+ JSON_ASSERT_UNREACHABLE;
+ return 0; // unreachable;
+}
+
+bool Value::empty() const {
+ if (isNull() || isArray() || isObject())
+ return size() == 0u;
+ else
+ return false;
+}
+
+bool Value::operator!() const { return isNull(); }
+
+void Value::clear() {
+ JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == arrayValue ||
+ type_ == objectValue,
+ "in Json::Value::clear(): requires complex value");
+ switch (type_) {
+ case arrayValue:
+ case objectValue:
+ value_.map_->clear();
+ break;
+ default:
+ break;
+ }
+}
+
+void Value::resize(ArrayIndex newSize) {
+ JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == arrayValue,
+ "in Json::Value::resize(): requires arrayValue");
+ if (type_ == nullValue)
+ *this = Value(arrayValue);
+ ArrayIndex oldSize = size();
+ if (newSize == 0)
+ clear();
+ else if (newSize > oldSize)
+ (*this)[newSize - 1];
+ else {
+ for (ArrayIndex index = newSize; index < oldSize; ++index) {
+ value_.map_->erase(index);
+ }
+ assert(size() == newSize);
+ }
+}
+
+Value& Value::operator[](ArrayIndex index) {
+ JSON_ASSERT_MESSAGE(
+ type_ == nullValue || type_ == arrayValue,
+ "in Json::Value::operator[](ArrayIndex): requires arrayValue");
+ if (type_ == nullValue)
+ *this = Value(arrayValue);
+ CZString key(index);
+ ObjectValues::iterator it = value_.map_->lower_bound(key);
+ if (it != value_.map_->end() && (*it).first == key)
+ return (*it).second;
+
+ ObjectValues::value_type defaultValue(key, nullRef);
+ it = value_.map_->insert(it, defaultValue);
+ return (*it).second;
+}
+
+Value& Value::operator[](int index) {
+ JSON_ASSERT_MESSAGE(
+ index >= 0,
+ "in Json::Value::operator[](int index): index cannot be negative");
+ return (*this)[ArrayIndex(index)];
+}
+
+const Value& Value::operator[](ArrayIndex index) const {
+ JSON_ASSERT_MESSAGE(
+ type_ == nullValue || type_ == arrayValue,
+ "in Json::Value::operator[](ArrayIndex)const: requires arrayValue");
+ if (type_ == nullValue)
+ return nullRef;
+ CZString key(index);
+ ObjectValues::const_iterator it = value_.map_->find(key);
+ if (it == value_.map_->end())
+ return nullRef;
+ return (*it).second;
+}
+
+const Value& Value::operator[](int index) const {
+ JSON_ASSERT_MESSAGE(
+ index >= 0,
+ "in Json::Value::operator[](int index) const: index cannot be negative");
+ return (*this)[ArrayIndex(index)];
+}
+
+void Value::initBasic(ValueType vtype, bool allocated) {
+ type_ = vtype;
+ allocated_ = allocated;
+ comments_ = 0;
+}
+
+// Access an object value by name, create a null member if it does not exist.
+// @pre Type of '*this' is object or null.
+// @param key is null-terminated.
+Value& Value::resolveReference(const char* key) {
+ JSON_ASSERT_MESSAGE(
+ type_ == nullValue || type_ == objectValue,
+ "in Json::Value::resolveReference(): requires objectValue");
+ if (type_ == nullValue)
+ *this = Value(objectValue);
+ CZString actualKey(
+ key, static_cast<unsigned>(strlen(key)), CZString::noDuplication); // NOTE!
+ ObjectValues::iterator it = value_.map_->lower_bound(actualKey);
+ if (it != value_.map_->end() && (*it).first == actualKey)
+ return (*it).second;
+
+ ObjectValues::value_type defaultValue(actualKey, nullRef);
+ it = value_.map_->insert(it, defaultValue);
+ Value& value = (*it).second;
+ return value;
+}
+
+// @param key is not null-terminated.
+Value& Value::resolveReference(char const* key, char const* cend)
+{
+ JSON_ASSERT_MESSAGE(
+ type_ == nullValue || type_ == objectValue,
+ "in Json::Value::resolveReference(key, end): requires objectValue");
+ if (type_ == nullValue)
+ *this = Value(objectValue);
+ CZString actualKey(
+ key, static_cast<unsigned>(cend-key), CZString::duplicateOnCopy);
+ ObjectValues::iterator it = value_.map_->lower_bound(actualKey);
+ if (it != value_.map_->end() && (*it).first == actualKey)
+ return (*it).second;
+
+ ObjectValues::value_type defaultValue(actualKey, nullRef);
+ it = value_.map_->insert(it, defaultValue);
+ Value& value = (*it).second;
+ return value;
+}
+
+Value Value::get(ArrayIndex index, const Value& defaultValue) const {
+ const Value* value = &((*this)[index]);
+ return value == &nullRef ? defaultValue : *value;
+}
+
+bool Value::isValidIndex(ArrayIndex index) const { return index < size(); }
+
+Value const* Value::find(char const* key, char const* cend) const
+{
+ JSON_ASSERT_MESSAGE(
+ type_ == nullValue || type_ == objectValue,
+ "in Json::Value::find(key, end, found): requires objectValue or nullValue");
+ if (type_ == nullValue) return NULL;
+ CZString actualKey(key, static_cast<unsigned>(cend-key), CZString::noDuplication);
+ ObjectValues::const_iterator it = value_.map_->find(actualKey);
+ if (it == value_.map_->end()) return NULL;
+ return &(*it).second;
+}
+const Value& Value::operator[](const char* key) const
+{
+ Value const* found = find(key, key + strlen(key));
+ if (!found) return nullRef;
+ return *found;
+}
+Value const& Value::operator[](std::string const& key) const
+{
+ Value const* found = find(key.data(), key.data() + key.length());
+ if (!found) return nullRef;
+ return *found;
+}
+
+Value& Value::operator[](const char* key) {
+ return resolveReference(key, key + strlen(key));
+}
+
+Value& Value::operator[](const std::string& key) {
+ return resolveReference(key.data(), key.data() + key.length());
+}
+
+Value& Value::operator[](const StaticString& key) {
+ return resolveReference(key.c_str());
+}
+
+#ifdef JSON_USE_CPPTL
+Value& Value::operator[](const CppTL::ConstString& key) {
+ return resolveReference(key.c_str(), key.end_c_str());
+}
+Value const& Value::operator[](CppTL::ConstString const& key) const
+{
+ Value const* found = find(key.c_str(), key.end_c_str());
+ if (!found) return nullRef;
+ return *found;
+}
+#endif
+
+Value& Value::append(const Value& value) { return (*this)[size()] = value; }
+
+Value Value::get(char const* key, char const* cend, Value const& defaultValue) const
+{
+ Value const* found = find(key, cend);
+ return !found ? defaultValue : *found;
+}
+Value Value::get(char const* key, Value const& defaultValue) const
+{
+ return get(key, key + strlen(key), defaultValue);
+}
+Value Value::get(std::string const& key, Value const& defaultValue) const
+{
+ return get(key.data(), key.data() + key.length(), defaultValue);
+}
+
+
+bool Value::removeMember(const char* key, const char* cend, Value* removed)
+{
+ if (type_ != objectValue) {
+ return false;
+ }
+ CZString actualKey(key, static_cast<unsigned>(cend-key), CZString::noDuplication);
+ ObjectValues::iterator it = value_.map_->find(actualKey);
+ if (it == value_.map_->end())
+ return false;
+ *removed = it->second;
+ value_.map_->erase(it);
+ return true;
+}
+bool Value::removeMember(const char* key, Value* removed)
+{
+ return removeMember(key, key + strlen(key), removed);
+}
+bool Value::removeMember(std::string const& key, Value* removed)
+{
+ return removeMember(key.data(), key.data() + key.length(), removed);
+}
+Value Value::removeMember(const char* key)
+{
+ JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == objectValue,
+ "in Json::Value::removeMember(): requires objectValue");
+ if (type_ == nullValue)
+ return nullRef;
+
+ Value removed; // null
+ removeMember(key, key + strlen(key), &removed);
+ return removed; // still null if removeMember() did nothing
+}
+Value Value::removeMember(const std::string& key)
+{
+ return removeMember(key.c_str());
+}
+
+bool Value::removeIndex(ArrayIndex index, Value* removed) {
+ if (type_ != arrayValue) {
+ return false;
+ }
+ CZString key(index);
+ ObjectValues::iterator it = value_.map_->find(key);
+ if (it == value_.map_->end()) {
+ return false;
+ }
+ *removed = it->second;
+ ArrayIndex oldSize = size();
+ // shift left all items left, into the place of the "removed"
+ for (ArrayIndex i = index; i < (oldSize - 1); ++i){
+ CZString keey(i);
+ (*value_.map_)[keey] = (*this)[i + 1];
+ }
+ // erase the last one ("leftover")
+ CZString keyLast(oldSize - 1);
+ ObjectValues::iterator itLast = value_.map_->find(keyLast);
+ value_.map_->erase(itLast);
+ return true;
+}
+
+#ifdef JSON_USE_CPPTL
+Value Value::get(const CppTL::ConstString& key,
+ const Value& defaultValue) const {
+ return get(key.c_str(), key.end_c_str(), defaultValue);
+}
+#endif
+
+bool Value::isMember(char const* key, char const* cend) const
+{
+ Value const* value = find(key, cend);
+ return NULL != value;
+}
+bool Value::isMember(char const* key) const
+{
+ return isMember(key, key + strlen(key));
+}
+bool Value::isMember(std::string const& key) const
+{
+ return isMember(key.data(), key.data() + key.length());
+}
+
+#ifdef JSON_USE_CPPTL
+bool Value::isMember(const CppTL::ConstString& key) const {
+ return isMember(key.c_str(), key.end_c_str());
+}
+#endif
+
+Value::Members Value::getMemberNames() const {
+ JSON_ASSERT_MESSAGE(
+ type_ == nullValue || type_ == objectValue,
+ "in Json::Value::getMemberNames(), value must be objectValue");
+ if (type_ == nullValue)
+ return Value::Members();
+ Members members;
+ members.reserve(value_.map_->size());
+ ObjectValues::const_iterator it = value_.map_->begin();
+ ObjectValues::const_iterator itEnd = value_.map_->end();
+ for (; it != itEnd; ++it) {
+ members.push_back(std::string((*it).first.data(),
+ (*it).first.length()));
+ }
+ return members;
+}
+//
+//# ifdef JSON_USE_CPPTL
+// EnumMemberNames
+// Value::enumMemberNames() const
+//{
+// if ( type_ == objectValue )
+// {
+// return CppTL::Enum::any( CppTL::Enum::transform(
+// CppTL::Enum::keys( *(value_.map_), CppTL::Type<const CZString &>() ),
+// MemberNamesTransform() ) );
+// }
+// return EnumMemberNames();
+//}
+//
+//
+// EnumValues
+// Value::enumValues() const
+//{
+// if ( type_ == objectValue || type_ == arrayValue )
+// return CppTL::Enum::anyValues( *(value_.map_),
+// CppTL::Type<const Value &>() );
+// return EnumValues();
+//}
+//
+//# endif
+
+static bool IsIntegral(double d) {
+ double integral_part;
+ return modf(d, &integral_part) == 0.0;
+}
+
+bool Value::isNull() const { return type_ == nullValue; }
+
+bool Value::isBool() const { return type_ == booleanValue; }
+
+bool Value::isInt() const {
+ switch (type_) {
+ case intValue:
+ return value_.int_ >= minInt && value_.int_ <= maxInt;
+ case uintValue:
+ return value_.uint_ <= UInt(maxInt);
+ case realValue:
+ return value_.real_ >= minInt && value_.real_ <= maxInt &&
+ IsIntegral(value_.real_);
+ default:
+ break;
+ }
+ return false;
+}
+
+bool Value::isUInt() const {
+ switch (type_) {
+ case intValue:
+ return value_.int_ >= 0 && LargestUInt(value_.int_) <= LargestUInt(maxUInt);
+ case uintValue:
+ return value_.uint_ <= maxUInt;
+ case realValue:
+ return value_.real_ >= 0 && value_.real_ <= maxUInt &&
+ IsIntegral(value_.real_);
+ default:
+ break;
+ }
+ return false;
+}
+
+bool Value::isInt64() const {
+#if defined(JSON_HAS_INT64)
+ switch (type_) {
+ case intValue:
+ return true;
+ case uintValue:
+ return value_.uint_ <= UInt64(maxInt64);
+ case realValue:
+ // Note that maxInt64 (= 2^63 - 1) is not exactly representable as a
+ // double, so double(maxInt64) will be rounded up to 2^63. Therefore we
+ // require the value to be strictly less than the limit.
+ return value_.real_ >= double(minInt64) &&
+ value_.real_ < double(maxInt64) && IsIntegral(value_.real_);
+ default:
+ break;
+ }
+#endif // JSON_HAS_INT64
+ return false;
+}
+
+bool Value::isUInt64() const {
+#if defined(JSON_HAS_INT64)
+ switch (type_) {
+ case intValue:
+ return value_.int_ >= 0;
+ case uintValue:
+ return true;
+ case realValue:
+ // Note that maxUInt64 (= 2^64 - 1) is not exactly representable as a
+ // double, so double(maxUInt64) will be rounded up to 2^64. Therefore we
+ // require the value to be strictly less than the limit.
+ return value_.real_ >= 0 && value_.real_ < maxUInt64AsDouble &&
+ IsIntegral(value_.real_);
+ default:
+ break;
+ }
+#endif // JSON_HAS_INT64
+ return false;
+}
+
+bool Value::isIntegral() const {
+#if defined(JSON_HAS_INT64)
+ return isInt64() || isUInt64();
+#else
+ return isInt() || isUInt();
+#endif
+}
+
+bool Value::isDouble() const { return type_ == realValue || isIntegral(); }
+
+bool Value::isNumeric() const { return isIntegral() || isDouble(); }
+
+bool Value::isString() const { return type_ == stringValue; }
+
+bool Value::isArray() const { return type_ == arrayValue; }
+
+bool Value::isObject() const { return type_ == objectValue; }
+
+void Value::setComment(const char* comment, size_t len, CommentPlacement placement) {
+ if (!comments_)
+ comments_ = new CommentInfo[numberOfCommentPlacement];
+ if ((len > 0) && (comment[len-1] == '\n')) {
+ // Always discard trailing newline, to aid indentation.
+ len -= 1;
+ }
+ comments_[placement].setComment(comment, len);
+}
+
+void Value::setComment(const char* comment, CommentPlacement placement) {
+ setComment(comment, strlen(comment), placement);
+}
+
+void Value::setComment(const std::string& comment, CommentPlacement placement) {
+ setComment(comment.c_str(), comment.length(), placement);
+}
+
+bool Value::hasComment(CommentPlacement placement) const {
+ return comments_ != 0 && comments_[placement].comment_ != 0;
+}
+
+std::string Value::getComment(CommentPlacement placement) const {
+ if (hasComment(placement))
+ return comments_[placement].comment_;
+ return "";
+}
+
+std::string Value::toStyledString() const {
+ StyledWriter writer;
+ return writer.write(*this);
+}
+
+Value::const_iterator Value::begin() const {
+ switch (type_) {
+ case arrayValue:
+ case objectValue:
+ if (value_.map_)
+ return const_iterator(value_.map_->begin());
+ break;
+ default:
+ break;
+ }
+ return const_iterator();
+}
+
+Value::const_iterator Value::end() const {
+ switch (type_) {
+ case arrayValue:
+ case objectValue:
+ if (value_.map_)
+ return const_iterator(value_.map_->end());
+ break;
+ default:
+ break;
+ }
+ return const_iterator();
+}
+
+Value::iterator Value::begin() {
+ switch (type_) {
+ case arrayValue:
+ case objectValue:
+ if (value_.map_)
+ return iterator(value_.map_->begin());
+ break;
+ default:
+ break;
+ }
+ return iterator();
+}
+
+Value::iterator Value::end() {
+ switch (type_) {
+ case arrayValue:
+ case objectValue:
+ if (value_.map_)
+ return iterator(value_.map_->end());
+ break;
+ default:
+ break;
+ }
+ return iterator();
+}
+
+// class PathArgument
+// //////////////////////////////////////////////////////////////////
+
+PathArgument::PathArgument() : key_(), index_(), kind_(kindNone) {}
+
+PathArgument::PathArgument(ArrayIndex index)
+ : key_(), index_(index), kind_(kindIndex) {}
+
+PathArgument::PathArgument(const char* key)
+ : key_(key), index_(), kind_(kindKey) {}
+
+PathArgument::PathArgument(const std::string& key)
+ : key_(key.c_str()), index_(), kind_(kindKey) {}
+
+// class Path
+// //////////////////////////////////////////////////////////////////
+
+Path::Path(const std::string& path,
+ const PathArgument& a1,
+ const PathArgument& a2,
+ const PathArgument& a3,
+ const PathArgument& a4,
+ const PathArgument& a5) {
+ InArgs in;
+ in.push_back(&a1);
+ in.push_back(&a2);
+ in.push_back(&a3);
+ in.push_back(&a4);
+ in.push_back(&a5);
+ makePath(path, in);
+}
+
+void Path::makePath(const std::string& path, const InArgs& in) {
+ const char* current = path.c_str();
+ const char* end = current + path.length();
+ InArgs::const_iterator itInArg = in.begin();
+ while (current != end) {
+ if (*current == '[') {
+ ++current;
+ if (*current == '%')
+ addPathInArg(path, in, itInArg, PathArgument::kindIndex);
+ else {
+ ArrayIndex index = 0;
+ for (; current != end && *current >= '0' && *current <= '9'; ++current)
+ index = index * 10 + ArrayIndex(*current - '0');
+ args_.push_back(index);
+ }
+ if (current == end || *current++ != ']')
+ invalidPath(path, int(current - path.c_str()));
+ } else if (*current == '%') {
+ addPathInArg(path, in, itInArg, PathArgument::kindKey);
+ ++current;
+ } else if (*current == '.') {
+ ++current;
+ } else {
+ const char* beginName = current;
+ while (current != end && !strchr("[.", *current))
+ ++current;
+ args_.push_back(std::string(beginName, current));
+ }
+ }
+}
+
+void Path::addPathInArg(const std::string& /*path*/,
+ const InArgs& in,
+ InArgs::const_iterator& itInArg,
+ PathArgument::Kind kind) {
+ if (itInArg == in.end()) {
+ // Error: missing argument %d
+ } else if ((*itInArg)->kind_ != kind) {
+ // Error: bad argument type
+ } else {
+ args_.push_back(**itInArg);
+ }
+}
+
+void Path::invalidPath(const std::string& /*path*/, int /*location*/) {
+ // Error: invalid path.
+}
+
+const Value& Path::resolve(const Value& root) const {
+ const Value* node = &root;
+ for (Args::const_iterator it = args_.begin(); it != args_.end(); ++it) {
+ const PathArgument& arg = *it;
+ if (arg.kind_ == PathArgument::kindIndex) {
+ if (!node->isArray() || !node->isValidIndex(arg.index_)) {
+ // Error: unable to resolve path (array value expected at position...
+ }
+ node = &((*node)[arg.index_]);
+ } else if (arg.kind_ == PathArgument::kindKey) {
+ if (!node->isObject()) {
+ // Error: unable to resolve path (object value expected at position...)
+ }
+ node = &((*node)[arg.key_]);
+ if (node == &Value::nullRef) {
+ // Error: unable to resolve path (object has no member named '' at
+ // position...)
+ }
+ }
+ }
+ return *node;
+}
+
+Value Path::resolve(const Value& root, const Value& defaultValue) const {
+ const Value* node = &root;
+ for (Args::const_iterator it = args_.begin(); it != args_.end(); ++it) {
+ const PathArgument& arg = *it;
+ if (arg.kind_ == PathArgument::kindIndex) {
+ if (!node->isArray() || !node->isValidIndex(arg.index_))
+ return defaultValue;
+ node = &((*node)[arg.index_]);
+ } else if (arg.kind_ == PathArgument::kindKey) {
+ if (!node->isObject())
+ return defaultValue;
+ node = &((*node)[arg.key_]);
+ if (node == &Value::nullRef)
+ return defaultValue;
+ }
+ }
+ return *node;
+}
+
+Value& Path::make(Value& root) const {
+ Value* node = &root;
+ for (Args::const_iterator it = args_.begin(); it != args_.end(); ++it) {
+ const PathArgument& arg = *it;
+ if (arg.kind_ == PathArgument::kindIndex) {
+ if (!node->isArray()) {
+ // Error: node is not an array at position ...
+ }
+ node = &((*node)[arg.index_]);
+ } else if (arg.kind_ == PathArgument::kindKey) {
+ if (!node->isObject()) {
+ // Error: node is not an object at position...
+ }
+ node = &((*node)[arg.key_]);
+ }
+ }
+ return *node;
+}
+
+} // namespace Json
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: src/lib_json/json_value.cpp
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: src/lib_json/json_writer.cpp
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2011 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+#if !defined(JSON_IS_AMALGAMATION)
+#include <json/writer.h>
+#include "json_tool.h"
+#endif // if !defined(JSON_IS_AMALGAMATION)
+#include <iomanip>
+#include <memory>
+#include <sstream>
+#include <utility>
+#include <set>
+#include <cassert>
+#include <cstring>
+#include <cstdio>
+
+#if defined(_MSC_VER) && _MSC_VER >= 1200 && _MSC_VER < 1800 // Between VC++ 6.0 and VC++ 11.0
+#include <float.h>
+#define isfinite _finite
+#elif defined(__sun) && defined(__SVR4) //Solaris
+#include <ieeefp.h>
+#define isfinite finite
+#else
+#include <cmath>
+#define isfinite std::isfinite
+#endif
+
+#if defined(_MSC_VER)
+#if !defined(WINCE) && defined(__STDC_SECURE_LIB__) && _MSC_VER >= 1500 // VC++ 9.0 and above
+#define snprintf sprintf_s
+#elif _MSC_VER >= 1900 // VC++ 14.0 and above
+#define snprintf std::snprintf
+#else
+#define snprintf _snprintf
+#endif
+#elif defined(__ANDROID__)
+#define snprintf snprintf
+#elif __cplusplus >= 201103L
+#define snprintf std::snprintf
+#endif
+
+#if defined(__BORLANDC__)
+#include <float.h>
+#define isfinite _finite
+#define snprintf _snprintf
+#endif
+
+#if defined(_MSC_VER) && _MSC_VER >= 1400 // VC++ 8.0
+// Disable warning about strdup being deprecated.
+#pragma warning(disable : 4996)
+#endif
+
+namespace Json {
+
+typedef std::auto_ptr<StreamWriter> StreamWriterPtr;
+
+static bool containsControlCharacter(const char* str) {
+ while (*str) {
+ if (isControlCharacter(*(str++)))
+ return true;
+ }
+ return false;
+}
+
+static bool containsControlCharacter0(const char* str, unsigned len) {
+ char const* end = str + len;
+ while (end != str) {
+ if (isControlCharacter(*str) || 0==*str)
+ return true;
+ ++str;
+ }
+ return false;
+}
+
+std::string valueToString(LargestInt value) {
+ UIntToStringBuffer buffer;
+ char* current = buffer + sizeof(buffer);
+ if (value == Value::minLargestInt) {
+ uintToString(LargestUInt(Value::maxLargestInt) + 1, current);
+ *--current = '-';
+ } else if (value < 0) {
+ uintToString(LargestUInt(-value), current);
+ *--current = '-';
+ } else {
+ uintToString(LargestUInt(value), current);
+ }
+ assert(current >= buffer);
+ return current;
+}
+
+std::string valueToString(LargestUInt value) {
+ UIntToStringBuffer buffer;
+ char* current = buffer + sizeof(buffer);
+ uintToString(value, current);
+ assert(current >= buffer);
+ return current;
+}
+
+#if defined(JSON_HAS_INT64)
+
+std::string valueToString(Int value) {
+ return valueToString(LargestInt(value));
+}
+
+std::string valueToString(UInt value) {
+ return valueToString(LargestUInt(value));
+}
+
+#endif // # if defined(JSON_HAS_INT64)
+
+std::string valueToString(double value, bool useSpecialFloats) {
+ // Allocate a buffer that is more than large enough to store the 16 digits of
+ // precision requested below.
+ char buffer[32];
+ int len = -1;
+
+ // Print into the buffer. We need not request the alternative representation
+ // that always has a decimal point because JSON doesn't distingish the
+ // concepts of reals and integers.
+ if (isfinite(value)) {
+ len = snprintf(buffer, sizeof(buffer), "%.17g", value);
+ } else {
+ // IEEE standard states that NaN values will not compare to themselves
+ if (value != value) {
+ len = snprintf(buffer, sizeof(buffer), useSpecialFloats ? "NaN" : "null");
+ } else if (value < 0) {
+ len = snprintf(buffer, sizeof(buffer), useSpecialFloats ? "-Infinity" : "-1e+9999");
+ } else {
+ len = snprintf(buffer, sizeof(buffer), useSpecialFloats ? "Infinity" : "1e+9999");
+ }
+ // For those, we do not need to call fixNumLoc, but it is fast.
+ }
+ assert(len >= 0);
+ fixNumericLocale(buffer, buffer + len);
+ return buffer;
+}
+
+std::string valueToString(double value) { return valueToString(value, false); }
+
+std::string valueToString(bool value) { return value ? "true" : "false"; }
+
+std::string valueToQuotedString(const char* value) {
+ if (value == NULL)
+ return "";
+ // Not sure how to handle unicode...
+ if (strpbrk(value, "\"\\\b\f\n\r\t") == NULL &&
+ !containsControlCharacter(value))
+ return std::string("\"") + value + "\"";
+ // We have to walk value and escape any special characters.
+ // Appending to std::string is not efficient, but this should be rare.
+ // (Note: forward slashes are *not* rare, but I am not escaping them.)
+ std::string::size_type maxsize =
+ strlen(value) * 2 + 3; // allescaped+quotes+NULL
+ std::string result;
+ result.reserve(maxsize); // to avoid lots of mallocs
+ result += "\"";
+ for (const char* c = value; *c != 0; ++c) {
+ switch (*c) {
+ case '\"':
+ result += "\\\"";
+ break;
+ case '\\':
+ result += "\\\\";
+ break;
+ case '\b':
+ result += "\\b";
+ break;
+ case '\f':
+ result += "\\f";
+ break;
+ case '\n':
+ result += "\\n";
+ break;
+ case '\r':
+ result += "\\r";
+ break;
+ case '\t':
+ result += "\\t";
+ break;
+ // case '/':
+ // Even though \/ is considered a legal escape in JSON, a bare
+ // slash is also legal, so I see no reason to escape it.
+ // (I hope I am not misunderstanding something.
+ // blep notes: actually escaping \/ may be useful in javascript to avoid </
+ // sequence.
+ // Should add a flag to allow this compatibility mode and prevent this
+ // sequence from occurring.
+ default:
+ if (isControlCharacter(*c)) {
+ std::ostringstream oss;
+ oss << "\\u" << std::hex << std::uppercase << std::setfill('0')
+ << std::setw(4) << static_cast<int>(*c);
+ result += oss.str();
+ } else {
+ result += *c;
+ }
+ break;
+ }
+ }
+ result += "\"";
+ return result;
+}
+
+// https://github.com/upcaste/upcaste/blob/master/src/upcore/src/cstring/strnpbrk.cpp
+static char const* strnpbrk(char const* s, char const* accept, size_t n) {
+ assert((s || !n) && accept);
+
+ char const* const end = s + n;
+ for (char const* cur = s; cur < end; ++cur) {
+ int const c = *cur;
+ for (char const* a = accept; *a; ++a) {
+ if (*a == c) {
+ return cur;
+ }
+ }
+ }
+ return NULL;
+}
+static std::string valueToQuotedStringN(const char* value, unsigned length) {
+ if (value == NULL)
+ return "";
+ // Not sure how to handle unicode...
+ if (strnpbrk(value, "\"\\\b\f\n\r\t", length) == NULL &&
+ !containsControlCharacter0(value, length))
+ return std::string("\"") + value + "\"";
+ // We have to walk value and escape any special characters.
+ // Appending to std::string is not efficient, but this should be rare.
+ // (Note: forward slashes are *not* rare, but I am not escaping them.)
+ std::string::size_type maxsize =
+ length * 2 + 3; // allescaped+quotes+NULL
+ std::string result;
+ result.reserve(maxsize); // to avoid lots of mallocs
+ result += "\"";
+ char const* end = value + length;
+ for (const char* c = value; c != end; ++c) {
+ switch (*c) {
+ case '\"':
+ result += "\\\"";
+ break;
+ case '\\':
+ result += "\\\\";
+ break;
+ case '\b':
+ result += "\\b";
+ break;
+ case '\f':
+ result += "\\f";
+ break;
+ case '\n':
+ result += "\\n";
+ break;
+ case '\r':
+ result += "\\r";
+ break;
+ case '\t':
+ result += "\\t";
+ break;
+ // case '/':
+ // Even though \/ is considered a legal escape in JSON, a bare
+ // slash is also legal, so I see no reason to escape it.
+ // (I hope I am not misunderstanding something.)
+ // blep notes: actually escaping \/ may be useful in javascript to avoid </
+ // sequence.
+ // Should add a flag to allow this compatibility mode and prevent this
+ // sequence from occurring.
+ default:
+ if ((isControlCharacter(*c)) || (*c == 0)) {
+ std::ostringstream oss;
+ oss << "\\u" << std::hex << std::uppercase << std::setfill('0')
+ << std::setw(4) << static_cast<int>(*c);
+ result += oss.str();
+ } else {
+ result += *c;
+ }
+ break;
+ }
+ }
+ result += "\"";
+ return result;
+}
+
+// Class Writer
+// //////////////////////////////////////////////////////////////////
+Writer::~Writer() {}
+
+// Class FastWriter
+// //////////////////////////////////////////////////////////////////
+
+FastWriter::FastWriter()
+ : yamlCompatiblityEnabled_(false) {}
+
+void FastWriter::enableYAMLCompatibility() { yamlCompatiblityEnabled_ = true; }
+
+std::string FastWriter::write(const Value& root) {
+ document_ = "";
+ writeValue(root);
+ document_ += "\n";
+ return document_;
+}
+
+void FastWriter::writeValue(const Value& value) {
+ switch (value.type()) {
+ case nullValue:
+ document_ += "null";
+ break;
+ case intValue:
+ document_ += valueToString(value.asLargestInt());
+ break;
+ case uintValue:
+ document_ += valueToString(value.asLargestUInt());
+ break;
+ case realValue:
+ document_ += valueToString(value.asDouble());
+ break;
+ case stringValue:
+ {
+ // Is NULL possible for value.string_?
+ char const* str;
+ char const* end;
+ bool ok = value.getString(&str, &end);
+ if (ok) document_ += valueToQuotedStringN(str, static_cast<unsigned>(end-str));
+ break;
+ }
+ case booleanValue:
+ document_ += valueToString(value.asBool());
+ break;
+ case arrayValue: {
+ document_ += '[';
+ int size = value.size();
+ for (int index = 0; index < size; ++index) {
+ if (index > 0)
+ document_ += ',';
+ writeValue(value[index]);
+ }
+ document_ += ']';
+ } break;
+ case objectValue: {
+ Value::Members members(value.getMemberNames());
+ document_ += '{';
+ for (Value::Members::iterator it = members.begin(); it != members.end();
+ ++it) {
+ const std::string& name = *it;
+ if (it != members.begin())
+ document_ += ',';
+ document_ += valueToQuotedStringN(name.data(), static_cast<unsigned>(name.length()));
+ document_ += yamlCompatiblityEnabled_ ? ": " : ":";
+ writeValue(value[name]);
+ }
+ document_ += '}';
+ } break;
+ }
+}
+
+// Class StyledWriter
+// //////////////////////////////////////////////////////////////////
+
+StyledWriter::StyledWriter()
+ : rightMargin_(74), indentSize_(3), addChildValues_() {}
+
+std::string StyledWriter::write(const Value& root) {
+ document_ = "";
+ addChildValues_ = false;
+ indentString_ = "";
+ writeCommentBeforeValue(root);
+ writeValue(root);
+ writeCommentAfterValueOnSameLine(root);
+ document_ += "\n";
+ return document_;
+}
+
+void StyledWriter::writeValue(const Value& value) {
+ switch (value.type()) {
+ case nullValue:
+ pushValue("null");
+ break;
+ case intValue:
+ pushValue(valueToString(value.asLargestInt()));
+ break;
+ case uintValue:
+ pushValue(valueToString(value.asLargestUInt()));
+ break;
+ case realValue:
+ pushValue(valueToString(value.asDouble()));
+ break;
+ case stringValue:
+ {
+ // Is NULL possible for value.string_?
+ char const* str;
+ char const* end;
+ bool ok = value.getString(&str, &end);
+ if (ok) pushValue(valueToQuotedStringN(str, static_cast<unsigned>(end-str)));
+ else pushValue("");
+ break;
+ }
+ case booleanValue:
+ pushValue(valueToString(value.asBool()));
+ break;
+ case arrayValue:
+ writeArrayValue(value);
+ break;
+ case objectValue: {
+ Value::Members members(value.getMemberNames());
+ if (members.empty())
+ pushValue("{}");
+ else {
+ writeWithIndent("{");
+ indent();
+ Value::Members::iterator it = members.begin();
+ for (;;) {
+ const std::string& name = *it;
+ const Value& childValue = value[name];
+ writeCommentBeforeValue(childValue);
+ writeWithIndent(valueToQuotedString(name.c_str()));
+ document_ += " : ";
+ writeValue(childValue);
+ if (++it == members.end()) {
+ writeCommentAfterValueOnSameLine(childValue);
+ break;
+ }
+ document_ += ',';
+ writeCommentAfterValueOnSameLine(childValue);
+ }
+ unindent();
+ writeWithIndent("}");
+ }
+ } break;
+ }
+}
+
+void StyledWriter::writeArrayValue(const Value& value) {
+ unsigned size = value.size();
+ if (size == 0)
+ pushValue("[]");
+ else {
+ bool isArrayMultiLine = isMultineArray(value);
+ if (isArrayMultiLine) {
+ writeWithIndent("[");
+ indent();
+ bool hasChildValue = !childValues_.empty();
+ unsigned index = 0;
+ for (;;) {
+ const Value& childValue = value[index];
+ writeCommentBeforeValue(childValue);
+ if (hasChildValue)
+ writeWithIndent(childValues_[index]);
+ else {
+ writeIndent();
+ writeValue(childValue);
+ }
+ if (++index == size) {
+ writeCommentAfterValueOnSameLine(childValue);
+ break;
+ }
+ document_ += ',';
+ writeCommentAfterValueOnSameLine(childValue);
+ }
+ unindent();
+ writeWithIndent("]");
+ } else // output on a single line
+ {
+ assert(childValues_.size() == size);
+ document_ += "[ ";
+ for (unsigned index = 0; index < size; ++index) {
+ if (index > 0)
+ document_ += ", ";
+ document_ += childValues_[index];
+ }
+ document_ += " ]";
+ }
+ }
+}
+
+bool StyledWriter::isMultineArray(const Value& value) {
+ int size = value.size();
+ bool isMultiLine = size * 3 >= rightMargin_;
+ childValues_.clear();
+ for (int index = 0; index < size && !isMultiLine; ++index) {
+ const Value& childValue = value[index];
+ isMultiLine =
+ isMultiLine || ((childValue.isArray() || childValue.isObject()) &&
+ childValue.size() > 0);
+ }
+ if (!isMultiLine) // check if line length > max line length
+ {
+ childValues_.reserve(size);
+ addChildValues_ = true;
+ int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
+ for (int index = 0; index < size; ++index) {
+ if (hasCommentForValue(value[index])) {
+ isMultiLine = true;
+ }
+ writeValue(value[index]);
+ lineLength += int(childValues_[index].length());
+ }
+ addChildValues_ = false;
+ isMultiLine = isMultiLine || lineLength >= rightMargin_;
+ }
+ return isMultiLine;
+}
+
+void StyledWriter::pushValue(const std::string& value) {
+ if (addChildValues_)
+ childValues_.push_back(value);
+ else
+ document_ += value;
+}
+
+void StyledWriter::writeIndent() {
+ if (!document_.empty()) {
+ char last = document_[document_.length() - 1];
+ if (last == ' ') // already indented
+ return;
+ if (last != '\n') // Comments may add new-line
+ document_ += '\n';
+ }
+ document_ += indentString_;
+}
+
+void StyledWriter::writeWithIndent(const std::string& value) {
+ writeIndent();
+ document_ += value;
+}
+
+void StyledWriter::indent() { indentString_ += std::string(indentSize_, ' '); }
+
+void StyledWriter::unindent() {
+ assert(int(indentString_.size()) >= indentSize_);
+ indentString_.resize(indentString_.size() - indentSize_);
+}
+
+void StyledWriter::writeCommentBeforeValue(const Value& root) {
+ if (!root.hasComment(commentBefore))
+ return;
+
+ document_ += "\n";
+ writeIndent();
+ const std::string& comment = root.getComment(commentBefore);
+ std::string::const_iterator iter = comment.begin();
+ while (iter != comment.end()) {
+ document_ += *iter;
+ if (*iter == '\n' &&
+ (iter != comment.end() && *(iter + 1) == '/'))
+ writeIndent();
+ ++iter;
+ }
+
+ // Comments are stripped of trailing newlines, so add one here
+ document_ += "\n";
+}
+
+void StyledWriter::writeCommentAfterValueOnSameLine(const Value& root) {
+ if (root.hasComment(commentAfterOnSameLine))
+ document_ += " " + root.getComment(commentAfterOnSameLine);
+
+ if (root.hasComment(commentAfter)) {
+ document_ += "\n";
+ document_ += root.getComment(commentAfter);
+ document_ += "\n";
+ }
+}
+
+bool StyledWriter::hasCommentForValue(const Value& value) {
+ return value.hasComment(commentBefore) ||
+ value.hasComment(commentAfterOnSameLine) ||
+ value.hasComment(commentAfter);
+}
+
+// Class StyledStreamWriter
+// //////////////////////////////////////////////////////////////////
+
+StyledStreamWriter::StyledStreamWriter(std::string indentation)
+ : document_(NULL), rightMargin_(74), indentation_(indentation),
+ addChildValues_() {}
+
+void StyledStreamWriter::write(std::ostream& out, const Value& root) {
+ document_ = &out;
+ addChildValues_ = false;
+ indentString_ = "";
+ indented_ = true;
+ writeCommentBeforeValue(root);
+ if (!indented_) writeIndent();
+ indented_ = true;
+ writeValue(root);
+ writeCommentAfterValueOnSameLine(root);
+ *document_ << "\n";
+ document_ = NULL; // Forget the stream, for safety.
+}
+
+void StyledStreamWriter::writeValue(const Value& value) {
+ switch (value.type()) {
+ case nullValue:
+ pushValue("null");
+ break;
+ case intValue:
+ pushValue(valueToString(value.asLargestInt()));
+ break;
+ case uintValue:
+ pushValue(valueToString(value.asLargestUInt()));
+ break;
+ case realValue:
+ pushValue(valueToString(value.asDouble()));
+ break;
+ case stringValue:
+ {
+ // Is NULL possible for value.string_?
+ char const* str;
+ char const* end;
+ bool ok = value.getString(&str, &end);
+ if (ok) pushValue(valueToQuotedStringN(str, static_cast<unsigned>(end-str)));
+ else pushValue("");
+ break;
+ }
+ case booleanValue:
+ pushValue(valueToString(value.asBool()));
+ break;
+ case arrayValue:
+ writeArrayValue(value);
+ break;
+ case objectValue: {
+ Value::Members members(value.getMemberNames());
+ if (members.empty())
+ pushValue("{}");
+ else {
+ writeWithIndent("{");
+ indent();
+ Value::Members::iterator it = members.begin();
+ for (;;) {
+ const std::string& name = *it;
+ const Value& childValue = value[name];
+ writeCommentBeforeValue(childValue);
+ writeWithIndent(valueToQuotedString(name.c_str()));
+ *document_ << " : ";
+ writeValue(childValue);
+ if (++it == members.end()) {
+ writeCommentAfterValueOnSameLine(childValue);
+ break;
+ }
+ *document_ << ",";
+ writeCommentAfterValueOnSameLine(childValue);
+ }
+ unindent();
+ writeWithIndent("}");
+ }
+ } break;
+ }
+}
+
+void StyledStreamWriter::writeArrayValue(const Value& value) {
+ unsigned size = value.size();
+ if (size == 0)
+ pushValue("[]");
+ else {
+ bool isArrayMultiLine = isMultineArray(value);
+ if (isArrayMultiLine) {
+ writeWithIndent("[");
+ indent();
+ bool hasChildValue = !childValues_.empty();
+ unsigned index = 0;
+ for (;;) {
+ const Value& childValue = value[index];
+ writeCommentBeforeValue(childValue);
+ if (hasChildValue)
+ writeWithIndent(childValues_[index]);
+ else {
+ if (!indented_) writeIndent();
+ indented_ = true;
+ writeValue(childValue);
+ indented_ = false;
+ }
+ if (++index == size) {
+ writeCommentAfterValueOnSameLine(childValue);
+ break;
+ }
+ *document_ << ",";
+ writeCommentAfterValueOnSameLine(childValue);
+ }
+ unindent();
+ writeWithIndent("]");
+ } else // output on a single line
+ {
+ assert(childValues_.size() == size);
+ *document_ << "[ ";
+ for (unsigned index = 0; index < size; ++index) {
+ if (index > 0)
+ *document_ << ", ";
+ *document_ << childValues_[index];
+ }
+ *document_ << " ]";
+ }
+ }
+}
+
+bool StyledStreamWriter::isMultineArray(const Value& value) {
+ int size = value.size();
+ bool isMultiLine = size * 3 >= rightMargin_;
+ childValues_.clear();
+ for (int index = 0; index < size && !isMultiLine; ++index) {
+ const Value& childValue = value[index];
+ isMultiLine =
+ isMultiLine || ((childValue.isArray() || childValue.isObject()) &&
+ childValue.size() > 0);
+ }
+ if (!isMultiLine) // check if line length > max line length
+ {
+ childValues_.reserve(size);
+ addChildValues_ = true;
+ int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
+ for (int index = 0; index < size; ++index) {
+ if (hasCommentForValue(value[index])) {
+ isMultiLine = true;
+ }
+ writeValue(value[index]);
+ lineLength += int(childValues_[index].length());
+ }
+ addChildValues_ = false;
+ isMultiLine = isMultiLine || lineLength >= rightMargin_;
+ }
+ return isMultiLine;
+}
+
+void StyledStreamWriter::pushValue(const std::string& value) {
+ if (addChildValues_)
+ childValues_.push_back(value);
+ else
+ *document_ << value;
+}
+
+void StyledStreamWriter::writeIndent() {
+ // blep intended this to look at the so-far-written string
+ // to determine whether we are already indented, but
+ // with a stream we cannot do that. So we rely on some saved state.
+ // The caller checks indented_.
+ *document_ << '\n' << indentString_;
+}
+
+void StyledStreamWriter::writeWithIndent(const std::string& value) {
+ if (!indented_) writeIndent();
+ *document_ << value;
+ indented_ = false;
+}
+
+void StyledStreamWriter::indent() { indentString_ += indentation_; }
+
+void StyledStreamWriter::unindent() {
+ assert(indentString_.size() >= indentation_.size());
+ indentString_.resize(indentString_.size() - indentation_.size());
+}
+
+void StyledStreamWriter::writeCommentBeforeValue(const Value& root) {
+ if (!root.hasComment(commentBefore))
+ return;
+
+ if (!indented_) writeIndent();
+ const std::string& comment = root.getComment(commentBefore);
+ std::string::const_iterator iter = comment.begin();
+ while (iter != comment.end()) {
+ *document_ << *iter;
+ if (*iter == '\n' &&
+ (iter != comment.end() && *(iter + 1) == '/'))
+ // writeIndent(); // would include newline
+ *document_ << indentString_;
+ ++iter;
+ }
+ indented_ = false;
+}
+
+void StyledStreamWriter::writeCommentAfterValueOnSameLine(const Value& root) {
+ if (root.hasComment(commentAfterOnSameLine))
+ *document_ << ' ' << root.getComment(commentAfterOnSameLine);
+
+ if (root.hasComment(commentAfter)) {
+ writeIndent();
+ *document_ << root.getComment(commentAfter);
+ }
+ indented_ = false;
+}
+
+bool StyledStreamWriter::hasCommentForValue(const Value& value) {
+ return value.hasComment(commentBefore) ||
+ value.hasComment(commentAfterOnSameLine) ||
+ value.hasComment(commentAfter);
+}
+
+//////////////////////////
+// BuiltStyledStreamWriter
+
+/// Scoped enums are not available until C++11.
+struct CommentStyle {
+ /// Decide whether to write comments.
+ enum Enum {
+ None, ///< Drop all comments.
+ Most, ///< Recover odd behavior of previous versions (not implemented yet).
+ All ///< Keep all comments.
+ };
+};
+
+struct BuiltStyledStreamWriter : public StreamWriter
+{
+ BuiltStyledStreamWriter(
+ std::string const& indentation,
+ CommentStyle::Enum cs,
+ std::string const& colonSymbol,
+ std::string const& nullSymbol,
+ std::string const& endingLineFeedSymbol,
+ bool useSpecialFloats);
+ virtual int write(Value const& root, std::ostream* sout);
+private:
+ void writeValue(Value const& value);
+ void writeArrayValue(Value const& value);
+ bool isMultineArray(Value const& value);
+ void pushValue(std::string const& value);
+ void writeIndent();
+ void writeWithIndent(std::string const& value);
+ void indent();
+ void unindent();
+ void writeCommentBeforeValue(Value const& root);
+ void writeCommentAfterValueOnSameLine(Value const& root);
+ static bool hasCommentForValue(const Value& value);
+
+ typedef std::vector<std::string> ChildValues;
+
+ ChildValues childValues_;
+ std::string indentString_;
+ int rightMargin_;
+ std::string indentation_;
+ CommentStyle::Enum cs_;
+ std::string colonSymbol_;
+ std::string nullSymbol_;
+ std::string endingLineFeedSymbol_;
+ bool addChildValues_ : 1;
+ bool indented_ : 1;
+ bool useSpecialFloats_ : 1;
+};
+BuiltStyledStreamWriter::BuiltStyledStreamWriter(
+ std::string const& indentation,
+ CommentStyle::Enum cs,
+ std::string const& colonSymbol,
+ std::string const& nullSymbol,
+ std::string const& endingLineFeedSymbol,
+ bool useSpecialFloats)
+ : rightMargin_(74)
+ , indentation_(indentation)
+ , cs_(cs)
+ , colonSymbol_(colonSymbol)
+ , nullSymbol_(nullSymbol)
+ , endingLineFeedSymbol_(endingLineFeedSymbol)
+ , addChildValues_(false)
+ , indented_(false)
+ , useSpecialFloats_(useSpecialFloats)
+{
+}
+int BuiltStyledStreamWriter::write(Value const& root, std::ostream* sout)
+{
+ sout_ = sout;
+ addChildValues_ = false;
+ indented_ = true;
+ indentString_ = "";
+ writeCommentBeforeValue(root);
+ if (!indented_) writeIndent();
+ indented_ = true;
+ writeValue(root);
+ writeCommentAfterValueOnSameLine(root);
+ *sout_ << endingLineFeedSymbol_;
+ sout_ = NULL;
+ return 0;
+}
+void BuiltStyledStreamWriter::writeValue(Value const& value) {
+ switch (value.type()) {
+ case nullValue:
+ pushValue(nullSymbol_);
+ break;
+ case intValue:
+ pushValue(valueToString(value.asLargestInt()));
+ break;
+ case uintValue:
+ pushValue(valueToString(value.asLargestUInt()));
+ break;
+ case realValue:
+ pushValue(valueToString(value.asDouble(), useSpecialFloats_));
+ break;
+ case stringValue:
+ {
+ // Is NULL is possible for value.string_?
+ char const* str;
+ char const* end;
+ bool ok = value.getString(&str, &end);
+ if (ok) pushValue(valueToQuotedStringN(str, static_cast<unsigned>(end-str)));
+ else pushValue("");
+ break;
+ }
+ case booleanValue:
+ pushValue(valueToString(value.asBool()));
+ break;
+ case arrayValue:
+ writeArrayValue(value);
+ break;
+ case objectValue: {
+ Value::Members members(value.getMemberNames());
+ if (members.empty())
+ pushValue("{}");
+ else {
+ writeWithIndent("{");
+ indent();
+ Value::Members::iterator it = members.begin();
+ for (;;) {
+ std::string const& name = *it;
+ Value const& childValue = value[name];
+ writeCommentBeforeValue(childValue);
+ writeWithIndent(valueToQuotedStringN(name.data(), static_cast<unsigned>(name.length())));
+ *sout_ << colonSymbol_;
+ writeValue(childValue);
+ if (++it == members.end()) {
+ writeCommentAfterValueOnSameLine(childValue);
+ break;
+ }
+ *sout_ << ",";
+ writeCommentAfterValueOnSameLine(childValue);
+ }
+ unindent();
+ writeWithIndent("}");
+ }
+ } break;
+ }
+}
+
+void BuiltStyledStreamWriter::writeArrayValue(Value const& value) {
+ unsigned size = value.size();
+ if (size == 0)
+ pushValue("[]");
+ else {
+ bool isMultiLine = (cs_ == CommentStyle::All) || isMultineArray(value);
+ if (isMultiLine) {
+ writeWithIndent("[");
+ indent();
+ bool hasChildValue = !childValues_.empty();
+ unsigned index = 0;
+ for (;;) {
+ Value const& childValue = value[index];
+ writeCommentBeforeValue(childValue);
+ if (hasChildValue)
+ writeWithIndent(childValues_[index]);
+ else {
+ if (!indented_) writeIndent();
+ indented_ = true;
+ writeValue(childValue);
+ indented_ = false;
+ }
+ if (++index == size) {
+ writeCommentAfterValueOnSameLine(childValue);
+ break;
+ }
+ *sout_ << ",";
+ writeCommentAfterValueOnSameLine(childValue);
+ }
+ unindent();
+ writeWithIndent("]");
+ } else // output on a single line
+ {
+ assert(childValues_.size() == size);
+ *sout_ << "[";
+ if (!indentation_.empty()) *sout_ << " ";
+ for (unsigned index = 0; index < size; ++index) {
+ if (index > 0)
+ *sout_ << ", ";
+ *sout_ << childValues_[index];
+ }
+ if (!indentation_.empty()) *sout_ << " ";
+ *sout_ << "]";
+ }
+ }
+}
+
+bool BuiltStyledStreamWriter::isMultineArray(Value const& value) {
+ int size = value.size();
+ bool isMultiLine = size * 3 >= rightMargin_;
+ childValues_.clear();
+ for (int index = 0; index < size && !isMultiLine; ++index) {
+ Value const& childValue = value[index];
+ isMultiLine =
+ isMultiLine || ((childValue.isArray() || childValue.isObject()) &&
+ childValue.size() > 0);
+ }
+ if (!isMultiLine) // check if line length > max line length
+ {
+ childValues_.reserve(size);
+ addChildValues_ = true;
+ int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
+ for (int index = 0; index < size; ++index) {
+ if (hasCommentForValue(value[index])) {
+ isMultiLine = true;
+ }
+ writeValue(value[index]);
+ lineLength += int(childValues_[index].length());
+ }
+ addChildValues_ = false;
+ isMultiLine = isMultiLine || lineLength >= rightMargin_;
+ }
+ return isMultiLine;
+}
+
+void BuiltStyledStreamWriter::pushValue(std::string const& value) {
+ if (addChildValues_)
+ childValues_.push_back(value);
+ else
+ *sout_ << value;
+}
+
+void BuiltStyledStreamWriter::writeIndent() {
+ // blep intended this to look at the so-far-written string
+ // to determine whether we are already indented, but
+ // with a stream we cannot do that. So we rely on some saved state.
+ // The caller checks indented_.
+
+ if (!indentation_.empty()) {
+ // In this case, drop newlines too.
+ *sout_ << '\n' << indentString_;
+ }
+}
+
+void BuiltStyledStreamWriter::writeWithIndent(std::string const& value) {
+ if (!indented_) writeIndent();
+ *sout_ << value;
+ indented_ = false;
+}
+
+void BuiltStyledStreamWriter::indent() { indentString_ += indentation_; }
+
+void BuiltStyledStreamWriter::unindent() {
+ assert(indentString_.size() >= indentation_.size());
+ indentString_.resize(indentString_.size() - indentation_.size());
+}
+
+void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) {
+ if (cs_ == CommentStyle::None) return;
+ if (!root.hasComment(commentBefore))
+ return;
+
+ if (!indented_) writeIndent();
+ const std::string& comment = root.getComment(commentBefore);
+ std::string::const_iterator iter = comment.begin();
+ while (iter != comment.end()) {
+ *sout_ << *iter;
+ if (*iter == '\n' &&
+ (iter != comment.end() && *(iter + 1) == '/'))
+ // writeIndent(); // would write extra newline
+ *sout_ << indentString_;
+ ++iter;
+ }
+ indented_ = false;
+}
+
+void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine(Value const& root) {
+ if (cs_ == CommentStyle::None) return;
+ if (root.hasComment(commentAfterOnSameLine))
+ *sout_ << " " + root.getComment(commentAfterOnSameLine);
+
+ if (root.hasComment(commentAfter)) {
+ writeIndent();
+ *sout_ << root.getComment(commentAfter);
+ }
+}
+
+// static
+bool BuiltStyledStreamWriter::hasCommentForValue(const Value& value) {
+ return value.hasComment(commentBefore) ||
+ value.hasComment(commentAfterOnSameLine) ||
+ value.hasComment(commentAfter);
+}
+
+///////////////
+// StreamWriter
+
+StreamWriter::StreamWriter()
+ : sout_(NULL)
+{
+}
+StreamWriter::~StreamWriter()
+{
+}
+StreamWriter::Factory::~Factory()
+{}
+StreamWriterBuilder::StreamWriterBuilder()
+{
+ setDefaults(&settings_);
+}
+StreamWriterBuilder::~StreamWriterBuilder()
+{}
+StreamWriter* StreamWriterBuilder::newStreamWriter() const
+{
+ std::string indentation = settings_["indentation"].asString();
+ std::string cs_str = settings_["commentStyle"].asString();
+ bool eyc = settings_["enableYAMLCompatibility"].asBool();
+ bool dnp = settings_["dropNullPlaceholders"].asBool();
+ bool usf = settings_["useSpecialFloats"].asBool();
+ CommentStyle::Enum cs = CommentStyle::All;
+ if (cs_str == "All") {
+ cs = CommentStyle::All;
+ } else if (cs_str == "None") {
+ cs = CommentStyle::None;
+ } else {
+ throwRuntimeError("commentStyle must be 'All' or 'None'");
+ }
+ std::string colonSymbol = " : ";
+ if (eyc) {
+ colonSymbol = ": ";
+ } else if (indentation.empty()) {
+ colonSymbol = ":";
+ }
+ std::string nullSymbol = "null";
+ if (dnp) {
+ nullSymbol = "";
+ }
+ std::string endingLineFeedSymbol = "";
+ return new BuiltStyledStreamWriter(
+ indentation, cs,
+ colonSymbol, nullSymbol, endingLineFeedSymbol, usf);
+}
+static void getValidWriterKeys(std::set<std::string>* valid_keys)
+{
+ valid_keys->clear();
+ valid_keys->insert("indentation");
+ valid_keys->insert("commentStyle");
+ valid_keys->insert("enableYAMLCompatibility");
+ valid_keys->insert("dropNullPlaceholders");
+ valid_keys->insert("useSpecialFloats");
+}
+bool StreamWriterBuilder::validate(Json::Value* invalid) const
+{
+ Json::Value my_invalid;
+ if (!invalid) invalid = &my_invalid; // so we do not need to test for NULL
+ Json::Value& inv = *invalid;
+ std::set<std::string> valid_keys;
+ getValidWriterKeys(&valid_keys);
+ Value::Members keys = settings_.getMemberNames();
+ size_t n = keys.size();
+ for (size_t i = 0; i < n; ++i) {
+ std::string const& key = keys[i];
+ if (valid_keys.find(key) == valid_keys.end()) {
+ inv[key] = settings_[key];
+ }
+ }
+ return 0u == inv.size();
+}
+Value& StreamWriterBuilder::operator[](std::string key)
+{
+ return settings_[key];
+}
+// static
+void StreamWriterBuilder::setDefaults(Json::Value* settings)
+{
+ //! [StreamWriterBuilderDefaults]
+ (*settings)["commentStyle"] = "All";
+ (*settings)["indentation"] = "\t";
+ (*settings)["enableYAMLCompatibility"] = false;
+ (*settings)["dropNullPlaceholders"] = false;
+ (*settings)["useSpecialFloats"] = false;
+ //! [StreamWriterBuilderDefaults]
+}
+
+std::string writeString(StreamWriter::Factory const& builder, Value const& root) {
+ std::ostringstream sout;
+ StreamWriterPtr const writer(builder.newStreamWriter());
+ writer->write(root, &sout);
+ return sout.str();
+}
+
+std::ostream& operator<<(std::ostream& sout, Value const& root) {
+ StreamWriterBuilder builder;
+ StreamWriterPtr const writer(builder.newStreamWriter());
+ writer->write(root, &sout);
+ return sout;
+}
+
+} // namespace Json
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: src/lib_json/json_writer.cpp
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
diff --git a/Source/JSON/jsonhelper.cpp b/Source/JSON/jsonhelper.cpp
new file mode 100644
index 00000000..5385c0da
--- /dev/null
+++ b/Source/JSON/jsonhelper.cpp
@@ -0,0 +1,120 @@
+//-----------------------------------------------------------------------------
+// Name: jsonhelper.cpp
+// Developer: Wolfire Games LLC
+// Author: Micah J Best
+// Date: 2015-10-09.
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "jsonhelper.h"
+
+#include <Logging/logdata.h>
+#include <Compat/fileio.h>
+#include <Internal/filesystem.h>
+
+#include <iostream>
+
+void testJSON() {
+
+ std::string testString = "{ \"encoding\" : \"UTF-8\", \"plug-ins\" : [ \"python\", \"c++\", \"ruby\" ],\"indent\" : { \"length\" : 3, \"use_space\": true } }";
+
+ Json::Value root;
+
+ //Json::Value::Members members = root.getMemberNames();
+
+ Json::Reader reader;
+
+ if( reader.parse(testString, root ) ) {
+ std::cout << "Parse ok" << std::endl;
+ }
+ else {
+ std::cout << "Parse NOT ok" << std::endl;
+ }
+
+ Json::ValueType type = root.type();
+
+ std::cout << "Type is: " << type << std::endl;
+
+ //std::cout << root.asString() << std::endl;
+
+ Json::Value::Members members = root.getMemberNames();
+
+ for( Json::Value::Members::iterator itr = members.begin(); itr != members.end(); ++itr )
+ {
+ std::cout << *itr << std::endl;
+ }
+
+
+}
+
+/*******
+ *
+ * SimpleJSONWrapper
+ *
+ */
+bool SimpleJSONWrapper::parseString( std::string& sourceString ) {
+
+ Json::Reader reader;
+
+ return reader.parse( sourceString, root );
+
+}
+
+bool SimpleJSONWrapper::parseFile( std::string& sourceFile ) {
+ char path[kPathSize];
+ if( FindFilePath( sourceFile.c_str(), path, kPathSize, kAnyPath, true ) == -1 )
+ {
+ return false;
+ }
+ else
+ {
+ std::string err;
+ std::ifstream f;
+ my_ifstream_open(f, path, std::ifstream::in | std::ifstream::binary);
+ if( parseIstream(f,err) )
+ {
+ return true;
+ }
+ else
+ {
+ LOGE << "Error parsing json file: " << path << " error: " << err << std::endl;
+ return false;
+ }
+ }
+}
+
+bool SimpleJSONWrapper::parseIstream( std::istream& is, std::string& errs ) {
+ Json::CharReaderBuilder rbuilder;
+
+ return Json::parseFromStream(rbuilder, is, &root, &errs);
+}
+
+
+std::string SimpleJSONWrapper::writeString( bool humanFriendly ) {
+
+ if( humanFriendly ) {
+ Json::StyledWriter writer;
+ return writer.write( root );
+ }
+ else {
+ Json::FastWriter writer;
+ return writer.write( root );
+ }
+}
+
diff --git a/Source/JSON/jsonhelper.h b/Source/JSON/jsonhelper.h
new file mode 100644
index 00000000..42483726
--- /dev/null
+++ b/Source/JSON/jsonhelper.h
@@ -0,0 +1,98 @@
+//-----------------------------------------------------------------------------
+// Name: jsonhelper.h
+// Developer: Wolfire Games LLC
+// Author: Micah J Best
+// Date: 2015-10-09.
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <JSON/json.h>
+
+#include <cstdio>
+#include <string>
+
+/**
+ * Wrap up the common JSON loading/creation - mostly for Angelscript interface
+ **/
+class SimpleJSONWrapper {
+
+ Json::Value root;
+
+public:
+
+ /*******************************************************************************************/
+ /**
+ * @brief Constructor
+ *
+ */
+ SimpleJSONWrapper() :
+ root( Json::objectValue )
+ {}
+
+
+ /*******************************************************************************************/
+ /**
+ * @brief Gets the root of the object
+ *
+ * @returns a reference to the root object
+ *
+ */
+ Json::Value& getRoot() {return root; }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Parses a string containing JSON
+ *
+ * @returns true if parse ok, false otherwise
+ *
+ */
+ bool parseString( std::string& sourceString );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Parses a file, finds the file using the built in FinFilePath routine.
+ *
+ * @returns true if parse ok, false otherwise
+ *
+ */
+ bool parseFile( std::string& sourceFile );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Parses a istream file containing json
+ *
+ * @returns true if parse ok, false otherwise
+ *
+ */
+ bool parseIstream( std::istream& is, std::string& errs );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Outputs this JSON object to a string
+ *
+ * @param humanFriendly If true, do some formatting else just output a more machine friendly version
+ *
+ */
+ std::string writeString( bool humanFriendly = false );
+
+
+
+};
diff --git a/Source/Logging/consolehandler.cpp b/Source/Logging/consolehandler.cpp
new file mode 100644
index 00000000..e1b89520
--- /dev/null
+++ b/Source/Logging/consolehandler.cpp
@@ -0,0 +1,129 @@
+//-----------------------------------------------------------------------------
+// Name: consolehandler.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include <Logging/consolehandler.h>
+
+#include <iostream>
+
+using std::cout;
+using std::cerr;
+using std::flush;
+
+ConsoleHandler::ConsoleHandler()
+{
+}
+
+ConsoleHandler::~ConsoleHandler()
+{
+}
+
+void ConsoleHandler::Log( LogSystem::LogType type, int row, const char* filename, const char* cat, const char* message_prefix, const char* message )
+{
+
+ /*
+ #ifdef _WIN32
+
+ switch( m_type )
+ {
+ case LogSystem::LogType::debug :
+ system( "Color 0A" );
+ break;
+
+ case LogSystem::LogType::error :
+ system( "Color 04" );
+ break;
+
+ case LogSystem::LogType::fatal :
+ system( "Color 0C" );
+ break;
+
+ case LogSystem::LogType::warning :
+ system( "Color 0E" );
+ break;
+
+ }
+ // c fatal
+ // a debug
+ // e warning
+
+ #endif
+ */
+
+#if defined(PLATFORM_LINUX)
+
+#define KNRM "\x1B[0m"
+#define KRED "\x1B[31m"
+#define KGRN "\x1B[32m"
+#define KYEL "\x1B[33m"
+#define KBLU "\x1B[34m"
+#define KMAG "\x1B[35m"
+#define KCYN "\x1B[36m"
+#define KWHT "\x1B[37m"
+ switch( type )
+ {
+ case LogSystem::spam :
+ cout << KNRM;
+ break;
+
+ case LogSystem::debug :
+ break;
+
+ case LogSystem::error :
+ cout << KRED;
+ break;
+
+ case LogSystem::fatal :
+ cout << KRED;
+ break;
+
+ case LogSystem::warning :
+ cout << KYEL;
+ break;
+
+ case LogSystem::info :
+ cout << KGRN;
+ break;
+ }
+
+ cout << message_prefix;
+ cout << message;
+
+ cout << KNRM;
+#elif defined(PLATFORM_UNIX)
+ cout << message_prefix;
+ cout << message;
+#else
+ //We restrict output on windows because slow console.
+ if (type != LogSystem::debug && type != LogSystem::spam )
+ {
+ //Using fprint here because std::err and std::out doesn't function.
+ fprintf( stderr, "%s", message_prefix );
+ fprintf( stderr, "%s", message );
+ }
+#endif
+}
+
+void ConsoleHandler::Flush()
+{
+ flush(cerr);
+}
diff --git a/Source/Logging/consolehandler.h b/Source/Logging/consolehandler.h
new file mode 100644
index 00000000..dd283278
--- /dev/null
+++ b/Source/Logging/consolehandler.h
@@ -0,0 +1,41 @@
+//-----------------------------------------------------------------------------
+// Name: consolehandler.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Logging/loghandler.h>
+
+/*!
+ Potential child to LogHandler and will print messages to std::cout
+*/
+class ConsoleHandler : public LogHandler
+{
+public:
+ ConsoleHandler();
+
+ virtual ~ConsoleHandler();
+
+ /*! \param message will be printed to std::cout */
+ virtual void Log( LogSystem::LogType type, int row, const char* filename, const char* cat, const char* message_prefix, const char* message );
+ virtual void Flush();
+};
+
diff --git a/Source/Logging/filehandler.cpp b/Source/Logging/filehandler.cpp
new file mode 100644
index 00000000..efadec98
--- /dev/null
+++ b/Source/Logging/filehandler.cpp
@@ -0,0 +1,179 @@
+//-----------------------------------------------------------------------------
+// Name: filehandler.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "filehandler.h"
+
+#include <Compat/fileio.h>
+
+#include <ctime>
+#include <iomanip>
+#include <vector>
+#include <string>
+
+using std::string;
+using std::fstream;
+using std::vector;
+using std::streampos;
+using std::getline;
+using std::cerr;
+using std::endl;
+using std::setfill;
+using std::setw;
+using std::flush;
+
+FileHandler::FileHandler( string path, size_t _startup_file_size, size_t _max_file_size ) :
+ m_file(),
+ startup_file_size( _startup_file_size ),
+ max_file_size( _max_file_size )
+{
+ string path_tmp = path + ".tmp";
+ fstream m_temp_file;
+
+ my_fstream_open( m_file, path.c_str(), fstream::in | fstream::binary );
+
+ vector<streampos> line_marker_positions;
+ streampos chosen_start_pos;
+
+ if( m_file.good() )
+ {
+ streampos prevpos = m_file.tellg();
+ line_marker_positions.push_back( prevpos );
+ while(m_file.good())
+ {
+ string line;
+ getline( m_file,line );
+
+ if( strcmp( line.c_str(), "START") == 0 )
+ {
+ line_marker_positions.push_back( prevpos );
+ }
+
+ if( m_file.tellg() >= 0 )
+ {
+ prevpos = m_file.tellg();
+ }
+ }
+
+ line_marker_positions.push_back( prevpos );
+
+
+ chosen_start_pos = line_marker_positions[line_marker_positions.size()-1];
+ for( int i = line_marker_positions.size()-1; i >= 0; i-- )
+ {
+ if((prevpos - line_marker_positions[i]) < (long)startup_file_size )
+ {
+ chosen_start_pos = line_marker_positions[i];
+ }
+ }
+ cerr << "Preserving " << prevpos - chosen_start_pos << " of data from previous log file, starting at pos " << chosen_start_pos << endl;
+ }
+
+ m_file.close();
+
+ my_fstream_open( m_file, path.c_str(), fstream::in | fstream::binary );
+ my_fstream_open( m_temp_file, path_tmp.c_str(), fstream::out | fstream::binary | fstream::trunc );
+
+ if( m_file.good() && m_temp_file.good() )
+ {
+ m_file.seekp( chosen_start_pos );
+ while( m_file.good() )
+ {
+ string line;
+ getline( m_file, line );
+
+ m_temp_file << line << endl;
+ }
+ }
+
+ m_file.close();
+ m_temp_file.close();
+
+ ////////////////////////
+ my_fstream_open( m_temp_file, path_tmp.c_str(), fstream::in );
+ my_fstream_open( m_file, path.c_str(), fstream::out | fstream::trunc );
+ if( !m_file.is_open() )
+ {
+ cerr << "FileHandler cannot open path for log output: " << path << endl;
+ return;
+ }
+
+ while( m_temp_file.good() )
+ {
+ string line;
+ getline( m_temp_file, line );
+
+ if( !line.empty() )
+ m_file << line << endl;
+ }
+
+ m_file << "START" << endl;
+}
+
+
+FileHandler::~FileHandler()
+{
+ if( m_file.is_open() )
+ m_file.close();
+}
+
+void FileHandler::Log( LogSystem::LogType type, int row, const char* filename, const char* cat, const char* message_prefix, const char* message )
+{
+ if( m_file.is_open() )
+ {
+ if( m_file.tellg() > (long)max_file_size )
+ {
+ m_file << "STOPPED WRITING BECAUSE FILE REACHED MAX LIMIT" << endl;
+ m_file.close();
+ cerr << "Shut down writing to log file because max file limit was reached." << endl;
+ }
+ else
+ {
+ time_t now = time(0);
+ struct tm* tm = localtime(&now);
+ m_file << tm->tm_year + 1900 << '/'
+ << setfill('0') << setw(2) << tm->tm_mon + 1 << '/'
+ << setfill('0') << setw(2) << tm->tm_mday
+ << ' '
+ << setfill('0') << setw(2) << tm->tm_hour
+ << ':'
+ << setfill('0') << setw(2) << tm->tm_min
+ << ':'
+ << setfill('0') << setw(2) << tm->tm_sec
+ << " ";
+ m_file << message_prefix;
+ m_file << message;
+ }
+ }
+}
+
+void FileHandler::Flush()
+{
+ if( m_file.is_open() )
+ {
+ flush(m_file);
+ }
+}
+
+void FileHandler::SetMaxWriteLimit( size_t size )
+{
+ max_file_size = size;
+}
diff --git a/Source/Logging/filehandler.h b/Source/Logging/filehandler.h
new file mode 100644
index 00000000..743d95fb
--- /dev/null
+++ b/Source/Logging/filehandler.h
@@ -0,0 +1,56 @@
+//-----------------------------------------------------------------------------
+// Name: filehandler.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Compat/fileio.h>
+#include <Logging/loghandler.h>
+
+/*!
+ Potential child of LogHandler and will print messages to file.
+*/
+class FileHandler : public LogHandler
+{
+public:
+ /*!
+ \param Type is the channel to use this object.
+ \param Path is the relative path to the file.
+ \param Append will cause the messages to be appended to the file.
+ */
+ FileHandler( std::string path, size_t _startup_file_size, size_t _max_file_size );
+
+ virtual ~FileHandler();
+
+ /*!
+ \param message will print message to file if file is open
+ */
+ virtual void Log( LogSystem::LogType type, int row, const char* filename, const char* cat, const char* message_prefix, const char* message );
+
+ virtual void Flush();
+
+ void SetMaxWriteLimit( size_t size );
+private:
+ std::fstream m_file;
+ size_t startup_file_size;
+ size_t max_file_size;
+
+};
diff --git a/Source/Logging/logdata.cpp b/Source/Logging/logdata.cpp
new file mode 100644
index 00000000..3dfb1f3c
--- /dev/null
+++ b/Source/Logging/logdata.cpp
@@ -0,0 +1,399 @@
+//-----------------------------------------------------------------------------
+// Name: logdata.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include <Logging/logdata.h>
+#include <Logging/loghandler.h>
+#include <Logging/consolehandler.h>
+
+#include <Utility/strings.h>
+
+#include <sstream>
+#include <iomanip>
+#include <mutex>
+
+#include <cassert>
+#include <cstdio>
+#include <cstdarg>
+
+using std::mutex;
+using std::stringstream;
+using std::string;
+using std::strcmp;
+using std::strcpy;
+using std::strlen;
+using std::strncpy;
+using std::setw;
+
+LogSystem::HandlerInstance LogSystem::handlers[LOGGER_LIMIT];
+
+char ignoreList[1024*8] = { 0 };
+
+LogTypeMask LogSystem::enabledLogTypes = 0;
+
+static mutex logMutex;
+
+void LogSystem::Mute( const char* prefix )
+{
+ logMutex.lock();
+ {
+ stringstream ss;
+ ss << ignoreList;
+
+ string tt;
+ while( ss.rdbuf()->in_avail() != 0 )
+ {
+ ss >> tt;
+ if( strcmp( prefix, tt.c_str() ) == 0 )
+ {
+ logMutex.unlock();
+ return;
+ }
+ }
+ }
+
+ stringstream ss;
+ ss << prefix << " " << ignoreList;
+
+ string msg = ss.str();
+ strcpy( ignoreList, msg.c_str() );
+
+ logMutex.unlock();
+}
+
+void LogSystem::Unmute( const char* prefix )
+{
+ logMutex.lock();
+
+ stringstream ss;
+ stringstream out;
+ ss << ignoreList;
+
+ string tt;
+ while( ss.rdbuf()->in_avail() != 0 )
+ {
+ ss >> tt;
+ if( strcmp( prefix, tt.c_str() ) != 0 )
+ out << tt;
+ }
+
+ string msg = out.str();
+ strcpy( ignoreList, msg.c_str() );
+
+ logMutex.unlock();
+}
+
+void LogSystem::RegisterLogHandler( LogTypeMask types, LogHandler* newHandler )
+{
+ logMutex.lock();
+
+ bool worked = false;
+
+ //Disable if enabled first.
+ enabledLogTypes = 0;
+ for( int i = 0; i < LOGGER_LIMIT; i++ )
+ {
+ if( handlers[i].handler == newHandler )
+ {
+ handlers[i].types = 0U;
+ handlers[i].handler = NULL;
+ } else if (handlers[i].handler != NULL) {
+ enabledLogTypes |= handlers[i].types;
+ }
+ }
+
+ //Then enable.
+ for( int i = 0; i < LOGGER_LIMIT; i++ )
+ {
+ if( handlers[i].handler == NULL )
+ {
+ handlers[i].types = types;
+ handlers[i].handler = newHandler;
+ worked = true;
+ break;
+ }
+ }
+
+ enabledLogTypes |= types;
+
+ assert( worked );
+
+ logMutex.unlock();
+}
+
+void LogSystem::DeregisterLogHandler( LogHandler* newHandler )
+{
+ logMutex.lock();
+
+ enabledLogTypes = 0;
+ for( int i = 0; i < LOGGER_LIMIT; i++ )
+ {
+ if( handlers[i].handler == newHandler )
+ {
+ handlers[i].types = 0U;
+ handlers[i].handler = NULL;
+ } else if (handlers[i].handler != NULL) {
+ enabledLogTypes |= handlers[i].types;
+ }
+ }
+
+ logMutex.unlock();
+}
+
+void LogSystem::Flush()
+{
+ for( int i = 0; i < LOGGER_LIMIT; i++ )
+ {
+ if( handlers[i].handler != NULL)
+ {
+ handlers[i].handler->Flush();
+ }
+ }
+}
+
+LogSystem::LogData& operator<< ( const LogSystem::LogData& data, StandardEndLine obj )
+{
+ LogSystem::LogData& temp = (LogSystem::LogData&)data; // unix hack, nab compiler...
+
+ if ((temp.m_type & LogSystem::enabledLogTypes) == 0) {
+ return temp;
+ }
+
+ stringstream ss;
+ obj( ss );
+
+ string msg = ss.str();
+
+ if( temp.m_message_pos + msg.size() < MESSAGE_LENGTH )
+ {
+ strcpy( temp.m_message + temp.m_message_pos, msg.c_str() );
+ }
+ else if( temp.m_message_pos < (MESSAGE_LENGTH - 1))
+ {
+ strncpy( temp.m_message + temp.m_message_pos, msg.c_str(), MESSAGE_LENGTH - temp.m_message_pos );
+ }
+ temp.m_message_pos += msg.length();
+
+ return temp;
+}
+
+LogSystem::LogData& operator<< ( const LogSystem::LogData& data, const string& obj )
+{
+ LogSystem::LogData& temp = (LogSystem::LogData&)data; // unix hack, nab compiler...
+
+ if ((temp.m_type & LogSystem::enabledLogTypes) == 0) {
+ return temp;
+ }
+
+ if( temp.m_message_pos + obj.length() < MESSAGE_LENGTH )
+ {
+ strcpy( temp.m_message + temp.m_message_pos, obj.c_str() );
+ }
+ else if( temp.m_message_pos < (MESSAGE_LENGTH - 1))
+ {
+ strncpy( temp.m_message + temp.m_message_pos, obj.c_str(), MESSAGE_LENGTH - temp.m_message_pos );
+ }
+ temp.m_message_pos += obj.length();
+
+ return temp;
+}
+
+LogSystem::LogData& operator<< ( const LogSystem::LogData& data, const char* obj )
+{
+ LogSystem::LogData& temp = (LogSystem::LogData&)data; // unix hack, nab compiler...
+
+ if ((temp.m_type & LogSystem::enabledLogTypes) == 0) {
+ return temp;
+ }
+
+ if( obj == NULL ) {
+ obj = "NULL";
+ }
+
+ size_t objlen = strlen( obj );
+
+ if( temp.m_message_pos + objlen < MESSAGE_LENGTH )
+ {
+ strcpy( temp.m_message + temp.m_message_pos, obj );
+ }
+ else if( temp.m_message_pos < (MESSAGE_LENGTH - 1))
+ {
+ strncpy( temp.m_message + temp.m_message_pos, obj, MESSAGE_LENGTH - temp.m_message_pos );
+ }
+ temp.m_message_pos += objlen;
+
+ return temp;
+}
+
+LogSystem::LogData::LogData(LogType type, const char* prefix, const char* filename, int line )
+{
+ const char *end = filename + strlen( filename );
+
+#ifdef PLATFORM_UNIX
+ while( end != NULL && *(end-1) != '/' && end > filename )
+#else
+ while( end != NULL && *(end-1) != '\\' && end > filename )
+#endif
+ end--;
+
+ strscpy(m_filename, end, FILENAME_LENGTH);
+ strscpy(m_prefix, prefix, PREFIX_LENGTH);
+ m_line = line;
+ m_type = type;
+ m_message[0] = '\0';
+ m_message_pos = 0;
+}
+
+LogSystem::LogData::LogData( const LogData& old )
+{
+ strncpy(m_filename, old.m_filename, FILENAME_LENGTH);
+ strncpy(m_prefix, old.m_prefix, PREFIX_LENGTH);
+ m_line = old.m_line;
+ m_type = old.m_type;
+
+ m_message[0] = '\0';
+ m_message_pos = 0;
+}
+
+LogSystem::LogData::~LogData()
+{
+ if( m_message_pos == 0 )
+ return;
+
+ logMutex.lock();
+
+ if (ignoreList[0] != '\0')
+ {
+ stringstream ss;
+ ss << ignoreList;
+
+ string tt;
+ while( ss.rdbuf()->in_avail() != 0 )
+ {
+ ss >> tt;
+ if( strcmp( m_prefix, tt.c_str() ) == 0 )
+ {
+ logMutex.unlock();
+ return;
+ }
+ }
+ }
+
+ stringstream ss;
+
+ switch( m_type )
+ {
+ case LogSystem::spam:
+ ss << "[s]";
+ break;
+ case LogSystem::debug:
+ ss << "[d]";
+ break;
+ case LogSystem::info:
+ ss << "[i]";
+ break;
+ case LogSystem::warning:
+ ss << "[w]";
+ break;
+ case LogSystem::error:
+ ss << "[e]";
+ break;
+ case LogSystem::fatal:
+ ss << "[f]";
+ break;
+ default:
+ ss << "[u]";
+ break;
+ }
+
+ ss << "[" << setw( 2 ) << m_prefix << "]:";
+
+ if( strlen( m_filename ) > 0 )
+ ss << setw( 15 ) << m_filename << ":";
+
+ if( m_line > 0 )
+ ss << setw( 4 ) << m_line << ": ";
+
+ m_message[MESSAGE_LENGTH-2] = '\n';
+ m_message[MESSAGE_LENGTH-1] = '\0';
+
+ string msg = ss.str();
+
+ for( int i = 0; i < LOGGER_LIMIT; i++ )
+ {
+ if( handlers[i].handler != NULL &&
+ (m_type & handlers[i].types) )
+ {
+ handlers[i].handler->Log( m_type, m_line, m_filename, m_prefix, msg.c_str(), m_message );
+ }
+ }
+
+ logMutex.unlock();
+}
+
+void LogSystem::LogData::Format( const char* fmt, ... )
+{
+ char buf[2048];
+ va_list args;
+ va_start(args, fmt);
+ vsnprintf(buf, 2048, fmt, args);
+ va_end(args);
+
+ *this << buf;
+}
+
+LogSystem::LogVoid& operator<< ( const LogSystem::LogVoid& data, StandardEndLine obj )
+{
+ LogSystem::LogVoid& temp = (LogSystem::LogVoid&)data; // unix hack, nab compiler...
+
+ return temp;
+}
+
+LogSystem::LogVoid& operator<< ( const LogSystem::LogVoid& data, const string& obj )
+{
+ LogSystem::LogVoid& temp = (LogSystem::LogVoid&)data; // unix hack, nab compiler...
+
+ return temp;
+}
+
+LogSystem::LogVoid& operator<< ( const LogSystem::LogVoid& data, const char* obj )
+{
+ LogSystem::LogVoid& temp = (LogSystem::LogVoid&)data; // unix hack, nab compiler...
+
+ return temp;
+}
+
+LogSystem::LogVoid::LogVoid( )
+{
+}
+
+LogSystem::LogVoid::LogVoid( const LogVoid& old )
+{
+}
+
+LogSystem::LogVoid::~LogVoid()
+{
+}
+
+void LogSystem::LogVoid::Format( const char* fmt, ... )
+{
+}
diff --git a/Source/Logging/logdata.h b/Source/Logging/logdata.h
new file mode 100644
index 00000000..9f8aebf4
--- /dev/null
+++ b/Source/Logging/logdata.h
@@ -0,0 +1,250 @@
+//-----------------------------------------------------------------------------
+// Name: logdata.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <iostream>
+#include <ostream>
+#include <sstream>
+#include <cstring>
+
+#include "Utility/compiler_macros.h"
+
+#define LOGGER_LIMIT 4
+
+#define MESSAGE_LENGTH 2048*4
+
+#define FILENAME_LENGTH 64
+#define PREFIX_LENGTH 3
+
+class LogHandler;
+
+/*!
+ Namespace for the logging system and overridden by macro to "logger"
+ Use is "logger::youAction", eg.
+ logger::debug << "this is a debug message" << std::endl;
+*/
+
+typedef unsigned LogTypeMask;
+
+namespace LogSystem
+{
+
+ extern LogTypeMask enabledLogTypes;
+
+ /*!
+ Channels for output by the log system.
+ */
+ enum LogType
+ {
+ spam = 1U << 0,
+ debug = 1U << 1,
+ info = 1U << 2,
+ warning = 1U << 3,
+ error = 1U << 4,
+ fatal = 1U << 5
+ };
+
+ struct HandlerInstance
+ {
+ LogTypeMask types;
+ LogHandler* handler;
+ };
+
+ /*!
+ Container object for one "<<" chain. Should only be used via macros.
+ */
+ class LogData
+ {
+ public:
+ /*!
+ \param type is which channel to use. \param prefix is what prefix to use. Should be set via macro. Messages can be muted via this prefix. */
+ LogData( LogType type, const char* prefix, const char* filename, int line );
+
+ /*!
+ Copy constructor
+ */
+ LogData( const LogData& );
+
+ /*!
+ Once the LogData object is destroyed the printing goes into
+ action using other utility in the LogSystem namespace.
+ */
+ ~LogData();
+
+ // the parameter indexes in the atttribute are strange because
+ // class methods have an implicit this pointer
+ void Format( const char* fmt, ... ) __attribute__((format(printf, 2, 3) ));
+
+ const char* GetPrefix() { return m_prefix; }
+
+ char m_message[MESSAGE_LENGTH];
+ size_t m_message_pos;
+
+ LogType m_type;
+ private:
+ char m_prefix[PREFIX_LENGTH];
+ char m_filename[FILENAME_LENGTH];
+ int m_line;
+ };
+
+ class LogVoid
+ {
+ public:
+ /*!
+ \param type is which channel to use. \param prefix is what prefix to use. Should be set via macro. Messages can be muted via this prefix. */
+ LogVoid();
+
+ /*!
+ Copy constructor
+ */
+ LogVoid( const LogVoid& );
+
+ /*!
+ Once the LogData object is destroyed the printing goes into
+ action using other utility in the LogSystem namespace.
+ */
+ ~LogVoid();
+
+ // the parameter indexes in the atttribute are strange because
+ // class methods have an implicit this pointer
+ void Format( const char* fmt, ... ) __attribute__((format(printf, 2, 3) ));
+
+ const char* GetPrefix() { return ""; }
+ };
+
+ extern HandlerInstance handlers[];
+
+ /*!
+ \param prefix to be muted, same one as stated in the macro function for that channel. eg. "debug"
+ */
+ void Mute( const char* prefix );
+
+ /*!
+ \param prefix to be unmuted, same one as stated in the macro function for that channel. eg. "debug"
+ */
+ void Unmute( const char* prefix );
+
+ /*!
+ Not that you can only register one handler pointer once, but you can do so on all channels
+ */
+ void RegisterLogHandler( LogTypeMask types, LogHandler* newHandler );
+
+ void DeregisterLogHandler( LogHandler* newHandler );
+
+ void Flush();
+}
+
+typedef std::basic_ostream<char, std::char_traits<char> > CoutType;
+typedef CoutType& (*StandardEndLine)(CoutType&);
+
+
+LogSystem::LogData& operator<< ( const LogSystem::LogData& data, StandardEndLine obj );
+LogSystem::LogData& operator<< ( const LogSystem::LogData& data, const std::string& obj );
+LogSystem::LogData& operator<< ( const LogSystem::LogData& data, const char* obj );
+
+template < class T >
+LogSystem::LogData& operator<< ( const LogSystem::LogData& data, const T& obj )
+{
+ LogSystem::LogData& temp = (LogSystem::LogData&)data; // unix hack, nab compiler...
+
+ if ((temp.m_type & LogSystem::enabledLogTypes) == 0) {
+ return temp;
+ }
+
+ std::stringstream ss;
+ ss << obj;
+
+ std::string msg = ss.str();
+
+ if( temp.m_message_pos + msg.length() < MESSAGE_LENGTH )
+ {
+ std::strcpy( temp.m_message + temp.m_message_pos, msg.c_str() );
+ }
+ else if( temp.m_message_pos < (MESSAGE_LENGTH - 1))
+ {
+ std::strncpy( temp.m_message + temp.m_message_pos, msg.c_str(), MESSAGE_LENGTH - temp.m_message_pos );
+ }
+
+ temp.m_message_pos += msg.length();
+
+ return temp;
+}
+
+LogSystem::LogVoid& operator<< ( const LogSystem::LogVoid& data, StandardEndLine obj );
+LogSystem::LogVoid& operator<< ( const LogSystem::LogVoid& data, const std::string& obj );
+LogSystem::LogVoid& operator<< ( const LogSystem::LogVoid& data, const char* obj );
+
+template < class T >
+LogSystem::LogVoid& operator<< ( const LogSystem::LogVoid& data, const T& obj )
+{
+ LogSystem::LogVoid& temp = (LogSystem::LogVoid&)data; // unix hack, nab compiler...
+ return temp;
+}
+
+#define LOGF LogSystem::LogVoid()
+#define LOGE LogSystem::LogVoid()
+#define LOGW LogSystem::LogVoid()
+#define LOGI LogSystem::LogVoid()
+#define LOGD LogSystem::LogVoid()
+#define LOGS LogSystem::LogVoid()
+
+#if LOG_LEVEL > 0
+#undef LOGF
+#define LOGF LogSystem::LogData( LogSystem::fatal, "__",__FILE__,__LINE__)
+#endif
+
+#if LOG_LEVEL > 1
+#undef LOGE
+#define LOGE LogSystem::LogData( LogSystem::error, "__",__FILE__,__LINE__)
+#endif
+
+#if LOG_LEVEL > 2
+#undef LOGW
+#define LOGW LogSystem::LogData( LogSystem::warning, "__",__FILE__,__LINE__)
+#endif
+
+#if LOG_LEVEL > 3
+#undef LOGI
+#define LOGI LogSystem::LogData( LogSystem::info, "__",__FILE__,__LINE__)
+#endif
+
+#if LOG_LEVEL > 4
+#undef LOGD
+#define LOGD LogSystem::LogData( LogSystem::debug, "__",__FILE__,__LINE__)
+#endif
+
+#if LOG_LEVEL > 5
+#undef LOGS
+#define LOGS LogSystem::LogData( LogSystem::spam, "__",__FILE__,__LINE__)
+#endif
+
+#define LOGW_ONCE(message)\
+ do{\
+ static bool once = true;\
+ if( once )\
+ {\
+ LOGW << message << std::endl;\
+ once = false;\
+ }\
+ } while(false)\
+
diff --git a/Source/Logging/loghandler.cpp b/Source/Logging/loghandler.cpp
new file mode 100644
index 00000000..0ab93dcf
--- /dev/null
+++ b/Source/Logging/loghandler.cpp
@@ -0,0 +1,35 @@
+//-----------------------------------------------------------------------------
+// Name: loghandler.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include <Logging/loghandler.h>
+
+#include <ostream>
+#include <iostream>
+
+LogHandler::LogHandler()
+{
+}
+
+LogHandler::~LogHandler()
+{
+}
diff --git a/Source/Logging/loghandler.h b/Source/Logging/loghandler.h
new file mode 100644
index 00000000..9a430ad3
--- /dev/null
+++ b/Source/Logging/loghandler.h
@@ -0,0 +1,43 @@
+//-----------------------------------------------------------------------------
+// Name: loghandler.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <string>
+#include <Logging/logdata.h>
+
+/*!
+ Virutal object for indirection of output options.
+ Parent for ConsoleHandler and FileHandler etc.
+*/
+class LogHandler
+{
+public:
+ LogHandler();
+ virtual ~LogHandler();
+
+ /*!
+ Overloaded by child object and will print message accordingly.
+ */
+ virtual void Log( LogSystem::LogType type, int row, const char* filename, const char* cat, const char* message_prefix, const char* message ) = 0;
+ virtual void Flush() = 0;
+};
diff --git a/Source/Logging/ramhandler.cpp b/Source/Logging/ramhandler.cpp
new file mode 100644
index 00000000..0b18159f
--- /dev/null
+++ b/Source/Logging/ramhandler.cpp
@@ -0,0 +1,111 @@
+//-----------------------------------------------------------------------------
+// Name: ramhandler.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <Logging/ramhandler.h>
+
+#include <iostream>
+#include <algorithm>
+
+#include <cassert>
+
+#include <Utility/strings.h>
+
+RamHandler::RamHandler() : is_locked(false) {
+ data = malloc(64 * 2048);
+ mem.Init(data, 2048, 64);
+ mem.no_log = true;
+}
+
+RamHandler::~RamHandler() {
+ free(data);
+ data = NULL;
+}
+
+void RamHandler::DeleteMessage(uint32_t index) {
+ if( row_instances[index].data != NULL ) {
+ mem.Free(row_instances[index].data);
+ row_instances[index].data = NULL;
+ row_instance_count--;
+ }
+}
+
+void RamHandler::Log( LogSystem::LogType type, int row, const char* filename, const char* cat, const char* message_prefix, const char* message ) {
+ log_mutex_.lock();
+
+ unsigned next_index = (row_instance_pos + 1) % ROW_INSTANCE_SIZE;
+ unsigned delete_index_offset = 0;
+
+ DeleteMessage(next_index);
+
+ size_t msg_size = strlen(message)+1;
+ void* new_mem = NULL;
+
+ while(new_mem == NULL) {
+ new_mem = mem.Alloc(msg_size);
+
+ if(new_mem) {
+ memcpy(new_mem,message,msg_size);
+ row_instances[next_index].data = (uint8_t*)new_mem;
+ row_instances[next_index].type = type;
+ strscpy((char*)row_instances[next_index].prefix,cat,PREFIX_LENGTH);
+ strscpy((char*)row_instances[next_index].filename,filename,FILENAME_LENGTH);
+ row_instances[next_index].row = row;
+
+ row_instance_count++;
+ row_instance_pos = next_index;
+ } else if(row_instance_count > 0){
+ delete_index_offset++;
+ DeleteMessage((next_index + delete_index_offset) % ROW_INSTANCE_SIZE);
+ } else {
+ std::cerr << "Unable to allocate memory for log-line" << std::endl;
+ break;
+ }
+ }
+
+ log_mutex_.unlock();
+}
+
+void RamHandler::Flush() {
+
+}
+
+void RamHandler::Lock() {
+ assert(is_locked == false);
+ is_locked = true;
+ log_mutex_.lock();
+}
+
+void RamHandler::Unlock() {
+ assert(is_locked == true);
+ is_locked = false;
+ log_mutex_.unlock();
+}
+
+RamHandlerLogRow RamHandler::GetLog( unsigned index ) {
+ assert(is_locked);
+ return row_instances[(row_instance_pos - row_instance_count + 1 + index) % ROW_INSTANCE_SIZE];
+}
+
+unsigned RamHandler::GetCount() {
+ assert(is_locked);
+ return row_instance_count;
+}
diff --git a/Source/Logging/ramhandler.h b/Source/Logging/ramhandler.h
new file mode 100644
index 00000000..afbbe9ec
--- /dev/null
+++ b/Source/Logging/ramhandler.h
@@ -0,0 +1,70 @@
+//-----------------------------------------------------------------------------
+// Name: ramhandler.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <mutex>
+
+#include <Logging/loghandler.h>
+#include <Internal/integer.h>
+#include <Memory/block_allocator.h>
+
+struct RamHandlerLogRow {
+ uint8_t* data;
+ uint8_t type;
+ uint8_t prefix[PREFIX_LENGTH];
+ uint8_t filename[FILENAME_LENGTH];
+ uint32_t row;
+};
+/*!
+ Potential child to LogHandler, logging output to an array for later rendering.
+*/
+class RamHandler : public LogHandler
+{
+private:
+ static const unsigned ROW_INSTANCE_SIZE = 256;
+ RamHandlerLogRow row_instances[ROW_INSTANCE_SIZE];
+ unsigned row_instance_pos;
+ unsigned row_instance_count;
+
+ void* data;
+ BlockAllocator mem;
+ bool is_locked;
+
+ std::mutex log_mutex_;
+
+ void DeleteMessage(uint32_t index);
+public:
+ RamHandler();
+
+ virtual ~RamHandler();
+
+ /*! \param message will be printed to std::cout */
+ virtual void Log( LogSystem::LogType type, int row, const char* filename, const char* cat, const char* message_prefix, const char* message );
+ virtual void Flush();
+
+ void Lock();
+ void Unlock();
+ RamHandlerLogRow GetLog( unsigned index );
+ unsigned GetCount();
+};
+
diff --git a/Source/Main/altmain.cpp b/Source/Main/altmain.cpp
new file mode 100644
index 00000000..1fe0a547
--- /dev/null
+++ b/Source/Main/altmain.cpp
@@ -0,0 +1,218 @@
+//-----------------------------------------------------------------------------
+// Name: altmain.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "altmain.h"
+
+#include <Internal/levelxml.h>
+#include <Internal/zip_util.h>
+#include <Internal/casecorrectpath.h>
+#include <Internal/textfile.h>
+#include <Internal/snprintf.h>
+
+#include <Compat/processpool.h>
+#include <Compat/fileio.h>
+#include <Compat/filepath.h>
+#include <Compat/platformsetup.h>
+
+#include <Graphics/shaders.h>
+#include <Graphics/textures.h>
+#include <Graphics/graphics.h>
+#include <Graphics/text.h>
+#include <Graphics/font_renderer.h>
+
+#include <Asset/Asset/soundgroup.h>
+#include <Asset/Asset/fzx_file.h>
+#include <Asset/Asset/animation.h>
+
+#include <Sound/sound.h>
+#include <Editors/actors_editor.h>
+#include <UserInput/mouse.h>
+#include <Images/texture_data.h>
+#include <Logging/logdata.h>
+#include <Main/engine.h>
+#include <Game/EntityDescription.h>
+
+#include <SDL.h>
+#include <utf8/utf8.h>
+#include <tinyxml.h>
+
+void CompressPathSet(PathSet path_set, const std::string &base_folder, const std::string &dst){
+ bool first_path = true;
+ for(PathSet::iterator iter = path_set.begin(); iter != path_set.end(); ++iter){
+ const std::string &full_path = (*iter);
+ const char* truncated_path = &full_path[base_folder.length()];
+ Zip(full_path, dst, truncated_path, first_path?_YES_OVERWRITE:_APPEND_OVERWRITE);
+ first_path = false;
+ }
+}
+
+const char* GetSuffix(const char* path){
+ const char* current = &path[0];
+ while(*current != '\0'){
+ if(*current == '.'){
+ return current+1;
+ }
+ ++current;
+ }
+ FatalError("Error", "Path has no suffix!");
+ return current;
+}
+
+void LoadSoundGroup(const char* path, const char* data) {
+ //Disabled function as there is currently no support for loading data from within zip files, if the feature
+ //should exists in the future, it should be part of the asset manager. /Max Danielsson 2016 September 15
+ assert(false);
+ /*
+ LOGI << "Loading SoundGroupInfo \"" << path << "\"" << std::endl;
+ SoundGroupInfo sgi;
+ sgi.ParseXML(data);
+ */
+}
+
+void LoadSound(const char* path, const char* data){
+ LOGI << "Loading Sound file \"" << path << "\"" << std::endl;
+ SoundPlayInfo spi;
+ spi.path = std::string("Data/Sounds/weapon_foley/cut/")+path;
+ Sound sound("");
+ unsigned long handle = sound.CreateHandle();
+ sound.Play(handle,spi);
+ SDL_Delay(1000);
+}
+
+void LoadSoundGroupZipFile(const std::string& path){
+ ExpandedZipFile ezf;
+ UnZip(path, ezf);
+
+ unsigned num_entries = ezf.GetNumEntries();
+ for(unsigned i=0; i<num_entries; ++i){
+ const char* filename;
+ const char* data;
+ unsigned size;
+ ezf.GetEntry(i, filename, data, size);
+
+ const char* suffix = GetSuffix(filename);
+ switch(suffix[0]){
+ case 'x': //xml
+ LoadSoundGroup(filename, data);
+ break;
+ case 'w': //wav
+ LoadSound(filename, data);
+ break;
+ }
+ }
+}
+
+void CheckCase(const char* path){
+ std::string corrected;
+ if(IsPathCaseCorrect(path, &corrected)){
+ LOGI << "Case is correct: " << corrected << std::endl;
+ } else {
+ LOGI << "Case is not correct: " << path << std::endl;
+ LOGI << "The correct case is: " << corrected << std::endl;
+ }
+}
+
+static const int FZX_VERSION_ = 1;
+
+int TestMain( int argc, char* argv[], const char* overloaded_write_dir, const char* overloaded_working_dir) {
+ SetUpEnvironment(argv[0],overloaded_write_dir, overloaded_working_dir);
+
+ //FZXAssetRef ref = FZXAssets::Instance()->ReturnRef("Data/Models/Characters/IGF_Guard/guard_physics.fzx");
+ FZXAssetRef ref = Engine::Instance()->GetAssetManager()->LoadSync<FZXAsset>("Data/Models/Characters/IGF_Guard/guard_physics.fzx");
+ LOGI << "Objects: " << ref->objects.size() << std::endl;
+ const char* key_words[] = {"left", "capsule", "box", "sphere"};
+ const int num_key_words = sizeof(key_words)/sizeof(key_words[0]);
+ for(size_t i=0, len=ref->objects.size(); i<len; ++i){
+ const FZXObject &object = ref->objects[i];
+ // Parse label string
+ const std::string &label = object.label;
+ size_t last_space_index = 0;
+ for(size_t j=0, len=label.size()+1; j<len; ++j){
+ if(label[j] == ' ' || label[j] == '\0'){
+ std::string sub_str_buf;
+ sub_str_buf.resize(j-last_space_index);
+ int count = 0;
+ for(size_t k=last_space_index; k<j; ++k){
+ char c = label[k];
+ if(c >= 'A' && c <= 'Z'){
+ c -= ('A' - 'a');
+ }
+ sub_str_buf[count] = c;
+ ++count;
+ }
+ bool match = false;
+ for(int k=0; k<num_key_words; ++k){
+ bool word_match = (strcmp(key_words[k], sub_str_buf.c_str())==0);
+ if(word_match){
+ LOGI << "Token found: " << key_words[k] << std::endl;
+ match = true;
+ }
+ }
+ if(!match){
+ LOGE << "Unrecognized token: " << sub_str_buf << std::endl;
+ }
+ last_space_index = j+1;
+ }
+ }
+ LOGI << "Object name: " << label << std::endl;
+ const quaternion &quat = object.rotation;
+ LOGI << "Quaternion: " << quat << std::endl;
+ const vec3 &location = object.location;
+ LOGI << "Location: " << location << std::endl;
+ const vec3 &scale = object.scale;
+ LOGI << "Scale: " << scale << std::endl;
+ }
+
+ printf("Press enter to exit\n");
+ getchar();
+
+ DisposeEnvironment();
+
+ return 0;
+}
+
+
+#include "Graphics/converttexture.h"
+
+int DDSConvertMain( int argc, char* argv[], const char* overloaded_write_dir, const char* overloaded_working_dir ) {
+ SetUpEnvironment(argv[0],overloaded_write_dir,overloaded_working_dir);
+
+ printf("Running DDS converter...\n");
+
+ for(int i=2; i<argc; ++i){
+ printf("Loading path: %s\n", argv[i]);
+ std::string src = argv[i];
+ std::string dst = src + "_converted.dds";
+ std::string temp = GetTempDDSPath(dst, true, 0);
+ ConvertImage(src, dst, temp, TextureData::Nice);
+ printf("Converted.\n");
+ }
+ if(argc <= 2) {
+ printf("No path found.\n");
+ }
+ printf("Press any key to exit...\n");
+ getchar();
+
+ DisposeEnvironment();
+
+ return 0;
+}
diff --git a/Source/Main/altmain.h b/Source/Main/altmain.h
new file mode 100644
index 00000000..5cd97159
--- /dev/null
+++ b/Source/Main/altmain.h
@@ -0,0 +1,26 @@
+//-----------------------------------------------------------------------------
+// Name: altmain.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+int TestMain( int argc, char* argv[], const char* overloaded_write_dir, const char* overloaded_working_dir );
+int DDSConvertMain( int argc, char* argv[], const char* overloaded_write_dir,const char* overloaded_working_dir );
diff --git a/Source/Main/debuglevelload.h b/Source/Main/debuglevelload.h
new file mode 100644
index 00000000..bf86e679
--- /dev/null
+++ b/Source/Main/debuglevelload.h
@@ -0,0 +1,90 @@
+//-----------------------------------------------------------------------------
+// Name: debuglevelload.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/config.h>
+
+#include <string>
+
+extern Config config;
+
+std::string DebugLoadLevel() {
+ //return "ShaleGuard2.xml";
+ //return "TheCave.xml";
+ //return "DesertFort_IGF.xml";
+ //return "TerrainSculpt2_IGF.xml";
+ //return "Project60/10_eroded_plateau.xml";
+ //return "Project60/5_painted_desert.xml";
+ //return "Project60/5_painted_desert_night.xml";
+ //return "Project60/16_red_desert.xml";
+ //return "Project60/16_red_desert_platforming.xml";
+ //return "Project60/16_red_desert_bridge.xml";
+ //return "Project60/16_red_desert_super_empty.xml";
+ //return "LugaruStory/Village.xml";
+ //return "Proto/arrival.xml";
+ //return "Project60/stealth_1.xml";
+ //return "ogLevels/up_high.xml";
+ //return "ogLevels/pillars.xml";
+ //return "terrain/dead_hills_terrain.xml";
+ //return "Project60/16_red_desert_super_empty_script.xml";
+ //return "Project60/16_red_desert_challenge1.xml";
+ //return "Project60/16_red_desert_challenge2.xml;"
+ //return "Project60/16_red_desert_challenge3.xml";
+ //return "Project60/16_red_desert_challenge4.xml";
+ //return "Project60/16_red_desert_challenge5.xml";
+ //return "Project60/16_red_desert_challenge6.xml";
+ //return "Project60/16_red_desert_challenge7.xml";
+ //return "Project60/16_red_desert_challenge8.xml";
+ //return "Project60/16_red_desert_challenge9.xml";
+ //return "Project60/16_red_desert_challenge10.xml";
+ //return "Project60/16_red_desert_challenge11.xml";
+ //return "Project60/16_red_desert_challenge12.xml";
+ //return "Project60/16_red_desert_challenge13.xml";
+ //return "Project60/16_red_desert_challenge14.xml";
+ //return "lugaru_snow.xml";
+ //return "Project60/1_dead_hills.xml";
+ //return "Project60/11_forest_creek.xml";
+ //return "Project60/24_patchy_highlands.xml";
+ //return "forest_hills.xml";
+ //return "PlainsHills_IGF.xml";
+ //return "arenas/construction_temple_arena.xml";
+ //return "arenas/stucco_courtyard_arena.xml";
+ //return "arenas/stucco_courtyard_arena_test.xml";
+
+ return config["debug_load_level"].str();
+
+ //return "nothing.xml";
+ //return "map.xml";
+
+ //return "Project60/2_impressive_mountains_4player.xml";
+ //return "Project60/2_impressive_mountains.xml";
+ //return "Project60/8_dead_volcano.xml";
+ //return "Project60/14_gentle_slopes.xml";
+ //return "Project60/22_grass_beach.xml";
+ //return "Project60/4_red_hills.xml";
+ //return "sea_cliffs.xml";
+ //return "Challenge/tree_canyon_summer.xml";
+ //return "challenge/scrubby_hills.xml";
+ //return "challenge/scrubby_hills_fall.xml";
+ //return "challenge/shielded_stands.xml";
+}
diff --git a/Source/Main/engine.cpp b/Source/Main/engine.cpp
new file mode 100644
index 00000000..362b66d5
--- /dev/null
+++ b/Source/Main/engine.cpp
@@ -0,0 +1,6808 @@
+//-----------------------------------------------------------------------------
+// Name: engine.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "engine.h"
+
+#include <Game/hardcoded_assets.h>
+#include <Game/level.h>
+#include <Game/savefile.h>
+#include <Game/avatar_control_manager.h>
+
+#include <GUI/gui.h>
+#include <GUI/widgetframework.h>
+#include <GUI/scriptable_ui.h>
+#include <GUI/dimgui/dimgui.h>
+#include <GUI/dimgui/modmenu.h>
+#include <GUI/dimgui/imgui_impl_sdl_gl3.h>
+#include <GUI/IMUI/im_element.h>
+#include <GUI/IMUI/im_support.h>
+
+#include <Graphics/cubemap.h>
+#include <Graphics/flares.h>
+#include <Graphics/models.h>
+#include <Graphics/particles.h>
+#include <Graphics/pxdebugdraw.h>
+#include <Graphics/retargetfile.h>
+#include <Graphics/sky.h>
+#include <Graphics/camera.h>
+#include <Graphics/shaders.h>
+#include <Graphics/font_renderer.h>
+#include <Graphics/simplify.hpp>
+#include <Graphics/text.h>
+#include <Graphics/lightprobecollection.hpp>
+#include <Graphics/halfedge.h>
+
+#include <XML/level_loader.h>
+#include <XML/Parsers/manifestparser.h>
+
+#include <Objects/cameraobject.h>
+#include <Objects/hotspot.h>
+#include <Objects/terrainobject.h>
+#include <Objects/movementobject.h>
+#include <Objects/reflectioncaptureobject.h>
+#include <Objects/placeholderobject.h>
+#include <Objects/envobject.h>
+#include <Objects/lightprobeobject.h>
+#include <Objects/lightvolume.h>
+#include <Objects/decalobject.h>
+#include <Objects/itemobject.h>
+#include <Objects/dynamiclightobject.h>
+#include <Objects/prefab.h>
+
+#include <Editors/map_editor.h>
+#include <Editors/sky_editor.h>
+#include <Editors/actors_editor.h>
+
+#include <Internal/hardware_specs.h>
+#include <Internal/config.h>
+#include <Internal/timer.h>
+#include <Internal/common.h>
+#include <Internal/filesystem.h>
+#include <Internal/memwrite.h>
+#include <Internal/locale.h>
+#include <Internal/filesystem.h>
+#include <Internal/modloading.h>
+#include <Internal/detect_settings.h>
+#include <Internal/dialogues.h>
+#include <Internal/stopwatch.h>
+#include <Internal/spawneritem.h>
+#include <Internal/assetpreload.h>
+#include <Internal/referencecounter.h>
+#include <Internal/profiler.h>
+#include <Internal/zip_util.h>
+
+#include <Asset/Asset/levelinfo.h>
+#include <Asset/Asset/levelset.h>
+#include <Asset/Asset/syncedanimation.h>
+#include <Asset/Asset/attacks.h>
+#include <Asset/Asset/reactions.h>
+#include <Asset/Asset/animation.h>
+
+#include <Compat/fileio.h>
+#include <Compat/filepath.h>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <Memory/stack_allocator.h>
+#include <Memory/allocation.h>
+
+#include <Physics/bulletworld.h>
+#include <Physics/physics.h>
+
+#include <Math/vec2math.h>
+#include <Math/vec3math.h>
+
+#include <Logging/logdata.h>
+#include <Logging/ramhandler.h>
+
+#include <Utility/assert.h>
+#include <Utility/strings.h>
+
+#include <Online/online.h>
+#include <Online/online_utility.h>
+
+#include <UserInput/keyTranslator.h>
+#include <UserInput/keycommands.h>
+
+#include <Wrappers/glm.h>
+#include <Images/image_export.hpp>
+#include <Threading/thread_name.h>
+#include <Network/asnetwork.h>
+#include <Version/version.h>
+#include <Steam/steamworks.h>
+#include <JSON/json.h>
+#include <Scripting/scriptfile.h>
+#include <Sound/sound.h>
+#include <Main/debuglevelload.h>
+
+
+#include <FreeImage.h>
+#include <SDL.h>
+
+#include <algorithm>
+#include <limits>
+
+extern char imgui_ini_path[kPathSize];
+extern bool asdebugger_enabled;
+extern bool asprofiler_enabled;
+
+//#define OpenVR
+#ifdef OpenVR
+#include "openvr.h"
+#endif
+
+//#define OculusVR
+#ifdef OculusVR
+#include "OVR_CAPI_GL.h"
+#include "Extras/OVR_Math.h"
+bool g_oculus_vr_activated = false;
+
+#ifndef UNREFERENCED_PARAMETER
+#define UNREFERENCED_PARAMETER(k)
+#endif
+
+namespace OVR {
+
+struct DepthBuffer
+{
+ GLuint texId;
+
+ DepthBuffer(Sizei size, int sampleCount)
+ {
+ UNREFERENCED_PARAMETER(sampleCount);
+
+ assert(sampleCount <= 1); // The code doesn't currently handle MSAA textures.
+
+ glGenTextures(1, &texId);
+ glBindTexture(GL_TEXTURE_2D, texId);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ GLenum internalFormat = GL_DEPTH_COMPONENT24;
+ GLenum type = GL_UNSIGNED_INT;
+ if (GLEW_ARB_depth_buffer_float)
+ {
+ internalFormat = GL_DEPTH_COMPONENT32F;
+ type = GL_FLOAT;
+ }
+
+ glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, size.w, size.h, 0, GL_DEPTH_COMPONENT, type, NULL);
+ }
+ ~DepthBuffer()
+ {
+ if (texId)
+ {
+ glDeleteTextures(1, &texId);
+ texId = 0;
+ }
+ }
+};
+
+struct TextureBuffer
+{
+ ovrSession Session;
+ ovrTextureSwapChain TextureChain;
+ GLuint texId;
+ GLuint fboId;
+ Sizei texSize;
+
+ TextureBuffer(ovrSession session, bool rendertarget, bool displayableOnHmd, Sizei size, int mipLevels, unsigned char * data, int sampleCount) :
+ Session(session),
+ TextureChain(NULL),
+ texId(0),
+ fboId(0),
+ texSize(0, 0)
+ {
+ UNREFERENCED_PARAMETER(sampleCount);
+
+ assert(sampleCount <= 1); // The code doesn't currently handle MSAA textures.
+
+ texSize = size;
+
+ if (displayableOnHmd)
+ {
+ // This texture isn't necessarily going to be a rendertarget, but it usually is.
+ assert(session); // No HMD? A little odd.
+ assert(sampleCount == 1); // ovr_CreateSwapTextureSetD3D11 doesn't support MSAA.
+
+ ovrTextureSwapChainDesc desc = {};
+ desc.Type = ovrTexture_2D;
+ desc.ArraySize = 1;
+ desc.Width = size.w;
+ desc.Height = size.h;
+ desc.MipLevels = 1;
+ desc.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB;
+ desc.SampleCount = 1;
+ desc.StaticImage = ovrFalse;
+
+ ovrResult result = ovr_CreateTextureSwapChainGL(Session, &desc, &TextureChain);
+
+ int length = 0;
+ ovr_GetTextureSwapChainLength(session, TextureChain, &length);
+
+ if(OVR_SUCCESS(result))
+ {
+ for (int i = 0; i < length; ++i)
+ {
+ GLuint chainTexId;
+ ovr_GetTextureSwapChainBufferGL(Session, TextureChain, i, &chainTexId);
+ glBindTexture(GL_TEXTURE_2D, chainTexId);
+
+ if (rendertarget)
+ {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ }
+ else
+ {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ }
+ }
+ }
+ }
+ else
+ {
+ glGenTextures(1, &texId);
+ glBindTexture(GL_TEXTURE_2D, texId);
+
+ if (rendertarget)
+ {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ }
+ else
+ {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ }
+
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, texSize.w, texSize.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
+ }
+
+ if (mipLevels > 1)
+ {
+ glGenerateMipmap(GL_TEXTURE_2D);
+ }
+
+ Graphics::Instance()->genFramebuffers(&fboId, "texture_buffer");
+ }
+
+ ~TextureBuffer()
+ {
+ if (TextureChain)
+ {
+ ovr_DestroyTextureSwapChain(Session, TextureChain);
+ TextureChain = NULL;
+ }
+ if (texId)
+ {
+ glDeleteTextures(1, &texId);
+ texId = 0;
+ }
+ if (fboId)
+ {
+ Graphics::Instance()->deleteFramebuffer(&fboId);
+ fboId = 0;
+ }
+ }
+
+ Sizei GetSize() const
+ {
+ return texSize;
+ }
+
+ void SetAndClearRenderSurface(DepthBuffer* dbuffer)
+ {
+ GLuint curTexId;
+ if (TextureChain)
+ {
+ int curIndex;
+ ovr_GetTextureSwapChainCurrentIndex(Session, TextureChain, &curIndex);
+ ovr_GetTextureSwapChainBufferGL(Session, TextureChain, curIndex, &curTexId);
+ }
+ else
+ {
+ curTexId = texId;
+ }
+
+ glBindFramebuffer(GL_FRAMEBUFFER, fboId);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, curTexId, 0);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, dbuffer->texId, 0);
+
+ glViewport(0, 0, texSize.w, texSize.h);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ glEnable(GL_FRAMEBUFFER_SRGB);
+ }
+
+ void UnsetRenderSurface()
+ {
+ glBindFramebuffer(GL_FRAMEBUFFER, fboId);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
+ }
+
+ void Commit()
+ {
+ if (TextureChain)
+ {
+ ovr_CommitTextureSwapChain(Session, TextureChain);
+ }
+ }
+};
+
+}; //namespace OVR
+
+OVR::TextureBuffer * eyeRenderTexture[2] = { NULL, NULL };
+OVR::DepthBuffer * eyeDepthBuffer[2] = { NULL, NULL };
+ovrMirrorTexture mirrorTexture = NULL;
+GLuint mirrorFBO = 0;
+long long frameIndex = 0;
+ovrSession session;
+ovrHmdDesc hmdDesc;
+
+#endif
+
+extern ASTextContext g_as_text_context;
+
+SceneLight* primary_light = NULL;
+
+//We cannot allow the loading screen to call anything in input unless input is mutex locked
+//this is because the loading loops modify the state of the input class.
+std::queue<SDL_Event> queued_events;
+
+extern Timer game_timer;
+extern Timer ui_timer;
+extern TextAtlas g_text_atlas[kNumTextAtlas];
+extern TextAtlasRenderer g_text_atlas_renderer;
+
+#define THREADED 1
+
+#ifdef _DEBUG
+const int _max_steps_per_frame = 4;
+#else
+const int _max_steps_per_frame = 4;
+#endif
+
+extern const char* new_empty_level_path;
+extern Config config;
+extern Config default_config;
+extern NativeLoadingText native_loading_text;
+extern bool g_simple_water;
+extern bool g_disable_fog;
+extern bool g_simple_shadows;
+extern bool g_level_shadows;
+extern bool g_albedo_only;
+extern bool g_no_reflection_capture;
+extern bool g_no_detailmaps;
+extern bool g_no_decals;
+extern bool g_no_decal_elements;
+extern bool g_single_pass_shadow_cascade;
+extern bool g_draw_vr;
+extern bool g_gamma_correct_final_output;
+bool g_draw_collision = false;
+bool g_make_invisible_visible = false;
+
+std::string script_dir_path;
+static float prev_view_time;
+
+static const float shadow_sizes[] = {20.0f, 60.0f, 250.0f, 1100.0f}; // Dimensions in world space of square for each shadow cascade
+
+static bool runtime_level_load_stress = true;
+
+static bool interrupt_loading;
+
+const char* font_path = "Data/Fonts/Lato-Regular.ttf";
+
+//-----------------------------------------------------------------------------
+//Functions
+//-----------------------------------------------------------------------------
+
+//#define USE_NVTX_PROFILER true
+
+#ifdef OpenVR
+vr::IVRSystem *m_pHMD;
+
+std::string GetTrackedDeviceString( vr::IVRSystem *pHmd, vr::TrackedDeviceIndex_t unDevice, vr::TrackedDeviceProperty prop, vr::TrackedPropertyError *peError = NULL )
+{
+ uint32_t unRequiredBufferLen = pHmd->GetStringTrackedDeviceProperty( unDevice, prop, NULL, 0, peError );
+ if( unRequiredBufferLen == 0 )
+ return "";
+
+ char *pchBuffer = new char[ unRequiredBufferLen ];
+ unRequiredBufferLen = pHmd->GetStringTrackedDeviceProperty( unDevice, prop, pchBuffer, unRequiredBufferLen, peError );
+ std::string sResult = pchBuffer;
+ delete [] pchBuffer;
+ return sResult;
+}
+
+void ProcessVREvent( const vr::VREvent_t & event )
+{
+ switch( event.eventType )
+ {
+ case vr::VREvent_TrackedDeviceActivated:
+ {
+ //SetupRenderModelForTrackedDevice( event.trackedDeviceIndex );
+ LOGI << "Device "<< event.trackedDeviceIndex << " attached. Setting up render model." << std::endl;
+ }
+ break;
+ case vr::VREvent_TrackedDeviceDeactivated:
+ {
+ LOGI << "Device "<< event.trackedDeviceIndex << " detached." << std::endl;
+ }
+ break;
+ case vr::VREvent_TrackedDeviceUpdated:
+ {
+ LOGI << "Device "<< event.trackedDeviceIndex << " updated." << std::endl;
+ }
+ break;
+ }
+}
+#endif
+
+
+VBOContainer quad_vert_vbo;
+VBOContainer quad_index_vbo;
+
+void PushGPUProfileRange(const char* cstr){
+#ifdef USE_NVTX_PROFILER
+ nvtxRangePushA(cstr);
+#endif
+
+#ifdef GLDEBUG
+ if (GLEW_KHR_debug) {
+ glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION , 1, -1, cstr);
+ }
+#endif // GLDEBUG
+}
+
+void PopGPUProfileRange(){
+#ifdef USE_NVTX_PROFILER
+ nvtxRangePop();
+#endif
+
+#ifdef GLDEBUG
+ if (GLEW_KHR_debug) {
+ glPopDebugGroup();
+ }
+#endif // GLDEBUG
+}
+
+int live_update_countdown = 0;
+
+static void RequestLiveUpdate() {
+ live_update_countdown = 10;
+}
+
+static void LiveUpdate(SceneGraph* scenegraph, AssetManager* asset_manager, ScriptableUI* scriptable_ui) {
+ Input* input = Input::Instance();
+ Graphics* graphics = Graphics::Instance();
+ Engine* engine = Engine::Instance();
+
+ if(config.PrimarySourceModified()){
+ config.Load(config.GetPrimaryPath());
+ engine->SetGameSpeed(config["global_time_scale_mult"].toNumber<float>(), true);
+ input->SetFromConfig(config);
+ graphics->SetFromConfig(config, true);
+ ActiveCameras::Get()->SetAutoCamera(config["auto_camera"].toNumber<bool>());
+ graphics->InitScreen();
+ DebugDrawAux::Instance()->visible_sound_spheres = config["visible_sound_spheres"].toNumber<bool>();
+ engine->GetSound()->SetMusicVolume(config["music_volume"].toNumber<float>());
+ engine->GetSound()->SetMasterVolume(config["master_volume"].toNumber<float>());
+ }
+
+ ModLoading::Instance().Reload();
+ AssetPreload::Instance().Reload();
+
+ Textures::Instance()->Reload();
+ if(scenegraph){
+ if(scenegraph->sky){
+ scenegraph->sky->Reload();
+ scenegraph->sky->LightingChanged(scenegraph->terrain_object_ != NULL);
+ }
+ }
+
+ asset_manager->Reload<Attack>();
+ asset_manager->Reload<Reaction>();
+ asset_manager->Reload<SyncedAnimationGroup>();
+ asset_manager->Reload<Animation>();
+ asset_manager->Reload<SoundGroup>();
+ asset_manager->Reload<ParticleType>();
+ asset_manager->Reload<Material>();
+ asset_manager->Reload<Character>();
+ asset_manager->Reload<Animation>();
+ asset_manager->Reload<Item>();
+
+ if(scenegraph){
+ scenegraph->SendMessageToAllObjects(OBJECT_MSG::RELOAD);
+ scenegraph->level->LiveUpdateCheck();
+ scenegraph->particle_system->script_context_.Reload();
+ }
+ if( scriptable_ui ) {
+ scriptable_ui->Reload();
+ }
+ AnimationRetargeter::Instance()->Reload();
+ Shaders::Instance()->Reload();
+
+ ReloadImGui();
+}
+
+const char* CStrEngineStateType( const EngineStateType& state ) {
+ switch( state ) {
+ case kEngineNoState: return "EngineNoState";
+ case kEngineLevelState: return "EngineLevelState";
+ case kEngineEditorLevelState: return "EngineEditorLevelState";
+ case kEngineScriptableUIState: return "EngineScriptableUIState";
+ case kEngineCampaignState: return "EngineCampaignState";
+ }
+ return "Unknown";
+}
+
+EngineState::EngineState(std::string _id, EngineStateType _type, Path _path) : id(_id), type(_type), path(_path), pop_past(false)
+{
+
+}
+
+EngineState::EngineState(std::string _id, EngineStateType _type) : id(_id), type(_type), pop_past(false)
+{
+
+}
+
+EngineState::EngineState(std::string _id, ScriptableCampaign* _campaign, Path _path) : id(_id), type(kEngineCampaignState), path(_path), pop_past(true)
+{
+ campaign.Set(_campaign);
+}
+
+EngineState::EngineState() : type(kEngineNoState), pop_past(true)
+{
+
+}
+
+std::ostream& operator<<( std::ostream& out, const EngineState& in )
+{
+ out << "Type(" << in.type << "," << in.path << ")";
+ return out;
+}
+
+Engine* Engine::instance_ = NULL;
+
+void Engine::Dispose() {
+ ModLoading::Instance().DeRegisterCallback(this);
+ ModLoading::Instance().Dispose();
+
+ config.GetRef("menu_show_state") = show_state;
+ config.GetRef("menu_show_performance") = show_performance;
+ config.GetRef("menu_show_log") = show_log;
+ config.GetRef("menu_show_warnings") = show_warnings;
+ config.GetRef("menu_show_save") = show_save;
+ config.GetRef("menu_show_sound") = show_sound;
+ config.GetRef("menu_show_mod_menu") = show_mod_menu;
+ config.GetRef("menu_show_asdebugger_contexts") = show_asdebugger_contexts;
+ config.GetRef("menu_show_asprofiler") = show_asprofiler;
+ config.GetRef("menu_show_mp_debug") = show_mp_debug;
+ config.GetRef("menu_show_mp_info") = show_mp_info;
+ config.GetRef("menu_show_input_debug") = show_input_debug;
+ config.GetRef("menu_show_mp_settings") = show_mp_settings;
+
+ if(scenegraph_){
+ scenegraph_->map_editor->gui = NULL;
+ scenegraph_->Dispose();
+ delete scenegraph_;
+ scenegraph_ = NULL;
+ }
+ if(scriptable_menu_)
+ delete scriptable_menu_;
+
+ for (int i = 0; i < kNumTextAtlas; i++) {
+ g_text_atlas[i].Dispose();
+ }
+ Models::Instance()->Dispose();
+ ActiveCameras::Get()->SetCameraObject(NULL);
+ particle_types.Dispose();
+ g_text_atlas_renderer.Dispose();
+ Input::Instance()->Dispose();
+ DecalTextures::Dispose();
+ Textures::Instance()->Dispose();
+ Shaders::Instance()->Dispose();
+ DebugDraw::Instance()->Dispose();
+ DisposeTextAtlases();
+ ScriptFileCache::Instance()->Dispose();
+ sound.Dispose();
+ ActiveCameras::Instance()->Dispose();
+ Graphics::Instance()->Dispose();
+ asset_manager.Dispose();
+ IMrefCountTracker.logSanityCheck();
+ Online::Instance()->Dispose();
+#if ENABLE_STEAMWORKS
+ Steamworks::Instance()->Dispose();
+#endif
+ as_network.Dispose();
+ DisposeImGui();
+
+ SDLNet_Quit();
+ SDL_Quit();
+
+ //Save config on shutdown, in case we have some global data stored in the configuration.
+ std::string config_path = GetConfigPath();
+ if( config.HasChangedSinceLastSave() )
+ {
+ LOGI << "Saving the configuration because we're disposing the engine and there are unsaved changes" << std::endl;
+ config.Save(config_path);
+ }
+ Engine::instance_ = NULL;
+
+ AssetPreload::Instance().Dispose();
+
+#ifdef OculusVR
+ if(g_oculus_vr_activated){
+ ovr_Shutdown();
+ }
+#endif
+#ifdef OpenVR
+ if( m_pHMD )
+ {
+ vr::VR_Shutdown();
+ m_pHMD = NULL;
+ }
+#endif
+}
+
+Engine* Engine::Instance()
+{
+ return Engine::instance_;
+}
+
+ThreadedSound* Engine::GetSound()
+{
+ return &sound;
+}
+
+AssetManager* Engine::GetAssetManager()
+{
+ return &asset_manager;
+}
+
+AnimationEffectSystem* Engine::GetAnimationEffectSystem()
+{
+ return &particle_types;
+}
+
+LipSyncSystem* Engine::GetLipSyncSystem()
+{
+ return &lip_sync_system;
+}
+
+ASNetwork* Engine::GetASNetwork() {
+ return &as_network;
+}
+
+SceneGraph* Engine::GetSceneGraph() {
+ return scenegraph_;
+}
+
+void Engine::UpdateControls(float timestep, bool loading_screen) {
+ if(scenegraph_ && scenegraph_->map_editor && scenegraph_->map_editor->state_ != MapEditor::kInGame && !paused) {
+ Input::Instance()->AllowControllerInput(false);
+ } else {
+ Input::Instance()->AllowControllerInput(true);
+ }
+
+ if( loading_screen == true || waiting_for_input_ ) {
+ SDL_Event event;
+ // We want to poll events during load screen to allow SDL to handle basic window drag/resize events
+ while (SDL_PollEvent(&event)) {
+ if( event.type == SDL_QUIT ) {
+ {
+ loading_mutex_.lock();
+ LOGE << "User requested interrupt, shutting down loading" << std::endl;
+ interrupt_loading = true;
+ loading_mutex_.unlock();
+ }
+ }
+
+ if(SDL_GetWindowFlags(Graphics::Instance()->sdl_window_) & SDL_WINDOW_INPUT_FOCUS) {
+ switch(event.type){
+ case SDL_MOUSEBUTTONDOWN:
+ case SDL_KEYDOWN:
+ case SDL_JOYBUTTONDOWN:
+ if (Online::Instance()->IsActive()) {
+ if (Online::Instance()->IsHosting() && Online::Instance()->AllClientsReady()) {
+ last_loading_input_time = SDL_TS_GetTicks();
+ Online::Instance()->SessionStarted(true);
+ }
+ }
+ else {
+ last_loading_input_time = SDL_TS_GetTicks();
+ Online::Instance()->SessionStarted(true);
+ }
+ break;
+ }
+ }
+
+ queued_events.push(event);
+ }
+ } else {
+ Input* input = Input::Instance();
+ Graphics* graphics = Graphics::Instance();
+
+ // reset deltas on userinterfaces
+ input->getMouse().Update();
+
+ if(loading_screen == false){
+ PROFILER_ZONE(g_profiler_ctx, "UpdateKeyboardFocus");
+ input->UpdateKeyboardFocus();
+ }
+
+ input->getKeyboard().clearKeyPresses();
+ KeyCommand::ClearKeyPresses();
+
+ // Process all the events queued during the loading screen if necessary.
+ // We never want to miss events because our keyboard/mouse state would get out of sync
+ if(!queued_events.empty()){
+ PROFILER_ZONE(g_profiler_ctx, "Push queued events");
+ SDL_Event event;
+ while (SDL_PollEvent(&event)) {
+ queued_events.push(event);
+ }
+ while(!queued_events.empty()){
+ SDL_PushEvent(&queued_events.front());
+ queued_events.pop();
+ }
+ }
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Process SDL events");
+ SDL_Event event;
+ while (SDL_PollEvent(&event)) {
+ {
+ PROFILER_ZONE(g_profiler_ctx, "ImGui_ImplSdlGL3_ProcessEvent");
+ ProcessEventImGui(&event);
+ }
+ //Redirect input events to the controller
+ switch( event.type ) {
+ case SDL_WINDOWEVENT:
+ switch(event.window.event){
+ case SDL_WINDOWEVENT_FOCUS_LOST:
+ graphics->SetWindowGrab(false);
+ cursor.SetVisible(true);
+ input->HandleEvent(event);
+ break;
+ case SDL_WINDOWEVENT_FOCUS_GAINED:
+ #ifndef WIN32
+ RequestLiveUpdate();
+ #endif
+ input->HandleEvent(event);
+ break;
+ case SDL_WINDOWEVENT_RESIZED:
+ resize_event_frame_counter = 10;
+ resize_value = ivec2(event.window.data1, event.window.data2);
+ break;
+ }
+ break;
+ case SDL_QUIT:
+ Input::Instance()->RequestQuit();
+ return;
+ case SDL_MOUSEBUTTONDOWN:
+ case SDL_MOUSEWHEEL:
+ if(!WantMouseImGui()){
+ input->HandleEvent(event);
+ }
+ break;
+ case SDL_KEYDOWN:
+ case SDL_TEXTINPUT:
+ if(!WantKeyboardImGui()){
+ input->HandleEvent(event);
+ }
+ break;
+ default:
+ input->HandleEvent(event);
+ }
+ }
+ }
+ input->ignore_mouse_frame = false;
+
+ Input::Instance()->ProcessControllers(timestep);
+ PlayerInput *controller = Input::Instance()->GetController(0);
+ static const std::string kSlow = "slow";
+ if(Input::Instance()->debug_keys && current_engine_state_.type == kEngineEditorLevelState){
+ if(controller != NULL && controller->key_down[kSlow].count==1) {
+ slow_motion = !slow_motion;
+ CommitPause();
+ }
+
+ if( KeyCommand::CheckPressed(input->getKeyboard(), KeyCommand::kPause, KIMF_LEVEL_EDITOR_GENERAL)){
+ user_paused = !user_paused;
+ CommitPause();
+ }
+ }
+
+ bool file_menu_active = current_engine_state_.type == kEngineScriptableUIState ||
+ current_engine_state_.type == kEngineCampaignState ||
+ (current_engine_state_.type == kEngineEditorLevelState && Input::Instance()->debug_keys);
+ if(file_menu_active) {
+ if(KeyCommand::CheckPressed(input->getKeyboard(), KeyCommand::kNewLevel, KIMF_LEVEL_EDITOR_GENERAL)) {
+ NewLevel();
+ }
+ if(KeyCommand::CheckPressed(input->getKeyboard(), KeyCommand::kOpenLevel, KIMF_LEVEL_EDITOR_GENERAL)) {
+ ::LoadLevel(false);
+ }
+ }
+
+ static const std::string kShowTiming = "show_timing";
+ if(controller != NULL && controller->key_down[kShowTiming].count==1) {
+ #ifdef SLIM_TIMING
+ slimTimingEvents->toggleVisualization();
+ #endif //SLIM_TIMING
+ }
+ static const std::string kScreenshot = "screenshot";
+ if(controller != NULL && controller->key_down[kScreenshot].count==1) {
+ graphics->queued_screenshot = 1;
+ graphics->screenshot_mode = Graphics::kGameplay;
+ graphics->pre_screenshot_media_mode_state = graphics->media_mode();
+ graphics->SetMediaMode(true);
+ }
+ static const std::string kTransparentScreenshot = "transparent_screenshot";
+ if(controller != NULL && controller->key_down[kTransparentScreenshot].count==1) {
+ graphics->queued_screenshot = 1;
+ graphics->screenshot_mode = Graphics::kTransparentGameplay;
+ graphics->pre_screenshot_media_mode_state = graphics->media_mode();
+ graphics->SetMediaMode(true);
+ }
+
+ if(KeyCommand::CheckPressed(input->getKeyboard(), KeyCommand::kQuit, KIMF_MENU)) {
+ input->RequestQuit();
+ }
+
+ if(KeyCommand::CheckPressed(input->getKeyboard(), KeyCommand::kBack, KIMF_MENU)) {
+ ScriptableUICallback("back");
+ }
+
+ if(KeyCommand::CheckPressed(input->getKeyboard(), KeyCommand::kPrintObjects, KIMF_MENU | KIMF_LEVEL_EDITOR_GENERAL | KIMF_PLAYING )) {
+ if( scenegraph_ )
+ scenegraph_->PrintCurrentObjects();
+ }
+
+ if(KeyCommand::CheckPressed(input->getKeyboard(), KeyCommand::kToggleLevelLoadStress, KIMF_MENU | KIMF_LEVEL_EDITOR_GENERAL | KIMF_PLAYING )){
+ if( config["level_load_stress"].toNumber<bool>()){
+ if( runtime_level_load_stress ) {
+ LOGI << "Disabling level load stress." << std::endl;
+ runtime_level_load_stress = false;
+ } else {
+ LOGI << "Enabling level load stress." << std::endl;
+ runtime_level_load_stress = true;
+ }
+ } else {
+ LOGI << "level load stress has not been enabled." << std::endl;
+ }
+ }
+
+ if(current_engine_state_.type == kEngineEditorLevelState && KeyCommand::CheckPressed(input->getKeyboard(), KeyCommand::kLoadItemSearch, KIMF_LEVEL_EDITOR_GENERAL)) {
+ show_load_item_search = true;
+ }
+
+ if(current_engine_state_.type == kEngineEditorLevelState && KeyCommand::CheckPressed(input->getKeyboard(), KeyCommand::kOpenCustomEditor, KIMF_LEVEL_EDITOR_GENERAL)) {
+ LOGI << "Launch custom editor(s)" << std::endl;
+ scenegraph_->level->Message("edit_selected_dialogue");
+
+ std::vector<Object*> selected;
+ scenegraph_->ReturnSelected(&selected);
+
+ for(unsigned selected_i = 0; selected_i < selected.size(); ++selected_i) {
+ if(selected[selected_i]->GetType() == _hotspot_object) {
+ Hotspot* hotspot = (Hotspot*)selected[selected_i];
+
+ if(hotspot->HasCustomGUI()) {
+ hotspot->LaunchCustomGUI();
+ }
+ }
+ }
+ }
+
+ if(current_engine_state_.type == kEngineEditorLevelState && KeyCommand::CheckPressed(input->getKeyboard(), KeyCommand::kMakeSelectedCharacterSavedCorpse, KIMF_LEVEL_EDITOR_GENERAL)) {
+ LOGI << "Make currently selected characters into saved corpses" << std::endl;
+ scenegraph_->level->Message("make_selected_character_saved_corpse");
+ }
+
+ if(current_engine_state_.type == kEngineEditorLevelState && KeyCommand::CheckPressed(input->getKeyboard(), KeyCommand::kReviveSelectedCharacterAndUnsaveCorpse, KIMF_LEVEL_EDITOR_GENERAL)) {
+ LOGI << "Revive currently selected character corpses, and remove saved corpse state" << std::endl;
+ scenegraph_->level->Message("revive_selected_character_and_unsave_corpse");
+ }
+
+ if(current_engine_state_.type == kEngineScriptableUIState) {
+ if(KeyCommand::CheckPressed(input->getKeyboard(), KeyCommand::kRefresh, KIMF_ANY)) {
+ LOGI << "Request refresh of current menu script" << std::endl;
+ scriptable_menu_->Reload(true);
+ }
+ }
+
+ if( resize_event_frame_counter >= 0 ) {
+ if( resize_event_frame_counter == 0 ) {
+ Graphics::Instance()->WindowResized(resize_value);
+ if(!loading_screen && scenegraph_){
+ scenegraph_->level->WindowResized(resize_value);
+ ScriptableCampaign* campaign_script = GetCurrentCampaign();
+ if( campaign_script ) {
+ campaign_script->WindowResized(resize_value);
+ }
+ }
+ }
+ resize_event_frame_counter--;
+ }
+ }
+
+ #ifdef OpenVR
+ vr::VREvent_t event;
+ while( m_pHMD->PollNextEvent( &event, sizeof( event ) ) )
+ {
+ ProcessVREvent( event );
+ }
+
+ // Process SteamVR controller state
+ for( vr::TrackedDeviceIndex_t unDevice = 0; unDevice < vr::k_unMaxTrackedDeviceCount; unDevice++ )
+ {
+ vr::VRControllerState_t state;
+ if( m_pHMD->GetControllerState( unDevice, &state, sizeof(state) ) )
+ {
+ //m_rbShowTrackedDevice[ unDevice ] = state.ulButtonPressed == 0;
+ }
+ }
+#endif
+}
+
+void Engine::HandleRabbotToggleControls() {
+ Keyboard &keyboard = Input::Instance()->getKeyboard();
+
+ bool rabbot_mode = scenegraph_->map_editor->state_ == MapEditor::kInGame;
+ if(!rabbot_mode && keyboard.wasScancodePressed(SDL_SCANCODE_8, KIMF_PLAYING | KIMF_LEVEL_EDITOR_GENERAL)) {
+ scenegraph_->map_editor->SendInRabbot();
+ }
+ int menu_player = Input::Instance()->PlayerOpenedMenu();
+ if(menu_player != -1 && (current_menu_player == -1 || current_menu_player == menu_player)) {
+ if(rabbot_mode) {
+ if(current_engine_state_.type == kEngineEditorLevelState){
+ scenegraph_->map_editor->StopRabbot();
+ } else {
+ current_menu_player = menu_player;
+ scenegraph_->level->Message("open_menu");
+ char buffer[48];
+ sprintf(buffer, "menu_player%i", menu_player);
+ scenegraph_->level->Message(buffer);
+ }
+ } else {
+ scenegraph_->level->Message("open_menu");
+ current_menu_player = menu_player;
+ char buffer[48];
+ sprintf(buffer, "menu_player%i", menu_player);
+ scenegraph_->level->Message(buffer);
+ }
+ Graphics::Instance()->SetMediaMode(false);
+ }
+
+ if (Online::Instance()->IsClient() && Online::Instance()->GetHostAllowsClientEditor() == false) {
+ if (current_engine_state_.type == kEngineEditorLevelState) {
+ current_engine_state_.type = kEngineLevelState;
+ scenegraph_->map_editor->SendInRabbot();
+ }
+ }
+
+ if (keyboard.wasScancodePressed(SDL_SCANCODE_F1, KIMF_PLAYING | KIMF_LEVEL_EDITOR_GENERAL)) {
+ if (Online::Instance()->IsClient()) {
+ if (Online::Instance()->GetHostAllowsClientEditor() == false && false) {
+
+ return;
+ }
+ }
+
+
+ if(current_engine_state_.type == kEngineLevelState){
+ current_engine_state_.type = kEngineEditorLevelState;
+ scenegraph_->map_editor->StopRabbot();
+ Shaders::Instance()->create_program_warning = false;
+ } else if(current_engine_state_.type == kEngineEditorLevelState){
+ current_engine_state_.type = kEngineLevelState;
+ scenegraph_->map_editor->SendInRabbot();
+ }
+ }
+}
+
+static void ClipPolyToPlane(std::vector<vec3>* poly_verts, vec3 plane_normal, float plane_distance) {
+ std::vector<vec3> clipped_verts;
+ // Check each line segment against the plane
+ for(size_t i=0, len=poly_verts->size(); i<len; ++i){
+ vec3 start = poly_verts->at(i);
+ vec3 end = poly_verts->at((i+1)%len);
+ float start_dot = dot(start, plane_normal);
+ float end_dot = dot(end, plane_normal);
+ if(start_dot >= plane_distance){
+ // Preserve start point if it is on the correct side of the plane
+ clipped_verts.push_back(start);
+ }
+ if( (start_dot < plane_distance) != (end_dot < plane_distance) ){
+ // Line segment intersects the plane, add intersection point
+ // Solve for t: start_dot + t * (end_dot - start_dot) = plane_distance
+ // We know (end_dot != start_dot) so no risk of divide by zero
+ float t = (plane_distance - start_dot) / (end_dot - start_dot);
+ vec3 intersection_point = start + (t * (end - start));
+ clipped_verts.push_back(intersection_point);
+ }
+ }
+ *poly_verts = clipped_verts;
+}
+
+struct VoxelSpan {
+ int height[2]; // start and end height (0 = top, 1 = bottom)
+};
+
+bool VoxelSpanHeightSort (const VoxelSpan& a, const VoxelSpan& b) {
+ return (a.height[0] > b.height[0]);
+}
+
+typedef std::list < VoxelSpan > VoxelSpanList;
+typedef std::vector< VoxelSpanList > VoxelSpanField;
+
+struct VoxelField {
+ VoxelSpanField spans;
+ int voxel_field_dims[3];
+ vec3 voxel_field_bounds[2];
+ float voxel_size;
+ VoxelField() {
+ for(int i=0; i<3; ++i){
+ voxel_field_dims[i] = 0;
+ }
+ voxel_size = 0.5f;
+ }
+};
+
+static void RasterizeTrisToVoxelField(const std::vector<vec3>& tri_verts, VoxelField& field) {
+ vec3 intersect_bounds[2] = {vec3(FLT_MAX), vec3(-FLT_MAX)};
+ for(size_t tri_index=0, num_tri_verts=tri_verts.size();
+ tri_index < num_tri_verts;
+ ++tri_index)
+ {
+ for(int j=0; j<3; ++j){
+ intersect_bounds[0][j] = min(intersect_bounds[0][j], tri_verts[tri_index][j]);
+ intersect_bounds[1][j] = max(intersect_bounds[1][j], tri_verts[tri_index][j]);
+ }
+ }
+
+ static const bool kDrawBounds = false;
+ if(kDrawBounds){
+ DebugDraw::Instance()->AddWireBox((intersect_bounds[0] + intersect_bounds[1])*0.5f,
+ (intersect_bounds[1] - intersect_bounds[0])*0.5f,
+ vec4(1.0f), _persistent);
+ }
+
+ // Get dimensions of voxel field
+ for(int j=0; j<3; ++j){
+ int voxel_min = (int)floorf(intersect_bounds[0][j]/field.voxel_size);
+ int voxel_max = (int)ceilf(intersect_bounds[1][j]/field.voxel_size);
+ field.voxel_field_bounds[0][j] = voxel_min * field.voxel_size;
+ field.voxel_field_bounds[1][j] = voxel_max * field.voxel_size;
+ field.voxel_field_dims[j] = voxel_max - voxel_min;
+ }
+
+ // Create storage for voxel spans
+ field.spans.clear();
+ field.spans.resize(field.voxel_field_dims[0] * field.voxel_field_dims[2]);
+
+ // Rasterize triangles to voxel field
+ for(size_t tri_index=0, num_tri_verts=tri_verts.size();
+ tri_index < num_tri_verts;
+ tri_index += 3)
+ {
+ // Get bounding box of triangle
+ vec3 bounds_min(FLT_MAX), bounds_max(-FLT_MAX);
+ for(int i=0; i<3; ++i){
+ for(int j=0; j<3; ++j){
+ bounds_min[j] = min(bounds_min[j], tri_verts[tri_index+i][j]);
+ bounds_max[j] = max(bounds_max[j], tri_verts[tri_index+i][j]);
+ }
+ }
+ static const bool kDrawTriBounds = false;
+ if(kDrawTriBounds){
+ DebugDraw::Instance()->AddWireBox((bounds_max + bounds_min)*0.5f, (bounds_max-bounds_min)*0.5f, vec4(1.0f), _persistent);
+ }
+
+ // Get bounding box of triangle in terms of voxels
+ vec3 voxel_bounds_min, voxel_bounds_max;
+ int voxel_size[3];
+ for(int i=0; i<3; ++i){
+ voxel_bounds_min[i] = floorf(bounds_min[i]/field.voxel_size);
+ voxel_bounds_max[i] = ceilf(bounds_max[i]/field.voxel_size);
+ voxel_size[i] = (int)voxel_bounds_max[i] - (int)voxel_bounds_min[i];
+ voxel_bounds_min[i] *= field.voxel_size;
+ voxel_bounds_max[i] *= field.voxel_size;
+ }
+
+ // Clip poly to each voxel slice
+ for(int voxel_x = 0; voxel_x < voxel_size[0]; ++voxel_x){
+ // Start with initial triangle
+ std::vector<vec3> poly_verts;
+ for(int i=0; i<3; ++i){
+ poly_verts.push_back(tri_verts[tri_index+i]);
+ }
+ // Clip to slice bounds
+ ClipPolyToPlane(&poly_verts, vec3(1,0,0), voxel_bounds_min[0] + voxel_x*field.voxel_size);
+ ClipPolyToPlane(&poly_verts, vec3(-1,0,0), -(voxel_bounds_min[0] + (voxel_x+1)*field.voxel_size));
+ // Clip poly along other axis
+ for(int voxel_z = 0; voxel_z < voxel_size[2]; ++voxel_z){
+ // Start with initial triangle
+ std::vector<vec3> new_poly_verts = poly_verts;
+ // Clip to slice bounds
+ ClipPolyToPlane(&new_poly_verts, vec3(0,0,1), voxel_bounds_min[2] + voxel_z*field.voxel_size);
+ ClipPolyToPlane(&new_poly_verts, vec3(0,0,-1), -(voxel_bounds_min[2] + (voxel_z+1)*field.voxel_size));
+ // Rasterize clipped polygon to voxel field
+ if(!new_poly_verts.empty()){
+ vec3 poly_bounds_min(FLT_MAX), poly_bounds_max(-FLT_MAX);
+ for(size_t i=0, len=new_poly_verts.size(); i<len; ++i){
+ for(int j=0; j<3; ++j){
+ poly_bounds_min[j] = min(poly_bounds_min[j], new_poly_verts[i][j]);
+ poly_bounds_max[j] = max(poly_bounds_max[j], new_poly_verts[i][j]);
+ }
+ }
+ int span_top = (int)ceilf(poly_bounds_max[1]/field.voxel_size);
+ int span_bottom = (int)floorf(poly_bounds_min[1]/field.voxel_size);
+ VoxelSpan span;
+ span.height[0] = span_top;
+ span.height[1] = span_bottom;
+ int abs_voxel_x = voxel_x + (int)((voxel_bounds_min[0] - field.voxel_field_bounds[0][0])/field.voxel_size);
+ int abs_voxel_z = voxel_z + (int)((voxel_bounds_min[2] - field.voxel_field_bounds[0][2])/field.voxel_size);
+ field.spans[abs_voxel_x*field.voxel_field_dims[2] + abs_voxel_z].push_back(span);
+
+ static const bool kDrawSpans = false;
+ if(kDrawSpans){
+ vec3 pos = vec3(voxel_bounds_min[0] + (voxel_x+0.5f)*field.voxel_size,
+ (span_top + span_bottom)*0.5f * field.voxel_size,
+ voxel_bounds_min[2] + (voxel_z+0.5f)*field.voxel_size);
+ vec3 box_size = vec3(field.voxel_size*0.5f,
+ (span_top - span_bottom)*0.5f * field.voxel_size,
+ field.voxel_size*0.5f);
+ DebugDraw::Instance()->AddWireBox(pos, box_size, vec4(1.0f), _persistent);
+ }
+ }
+ }
+ }
+ }
+
+ // Merge overlapping voxel spans
+ for(size_t span_index=0, len=field.spans.size(); span_index<len; ++span_index){
+ std::list<VoxelSpan>& span_list = field.spans[span_index];
+ span_list.sort(VoxelSpanHeightSort);
+ for(std::list<VoxelSpan>::iterator iter = span_list.begin();
+ iter != span_list.end();
+ ++iter)
+ {
+ bool stop = false;
+ while(!stop){
+ std::list<VoxelSpan>::iterator next_iter = iter;
+ ++next_iter;
+ if(next_iter != span_list.end()){
+ VoxelSpan& span = *iter;
+ VoxelSpan& next_span = *next_iter;
+ if(span.height[1] <= next_span.height[0]+1 &&
+ span.height[0] >= next_span.height[1]-1)
+ {
+ span.height[0] = max(span.height[0], next_span.height[0]);
+ span.height[1] = min(span.height[1], next_span.height[1]);
+ span_list.erase(next_iter);
+ } else {
+ stop = true;
+ }
+ } else {
+ stop = true;
+ }
+ }
+ }
+ }
+}
+
+VoxelField g_voxel_field;
+
+void GetColumnNeighbors(int num_spans[2], VoxelSpan* spans[2], int span_start[2], std::vector< std::list<int> > *span_neighbors) {
+ if(num_spans[0] == 0 || num_spans[1] == 0){
+ // If either column has no spans than we will find no new neighbors
+ return;
+ }
+ // Start with the top side of the top span of each column
+ int span_id[2] = {0, 0};
+ int span_end[2] = {0, 0};
+ int active_span[2] = {-1, -1};
+ bool done = false;
+ while(!done){
+ int next_height[2];
+ for(int i=0; i<2; ++i){
+ next_height[i] = spans[i][span_id[i]].height[span_end[i]];
+ }
+ if(next_height[0] > next_height[1]){
+ ++span_end[0];
+ } else if(next_height[1] > next_height[0]){
+ ++span_end[1];
+ } else {
+ ++span_end[0];
+ ++span_end[1];
+ }
+ // If we go off the bottom of a span, move to the top of the next span
+ for(int i=0; i<2; ++i){
+ if(span_end[i] > 1){
+ active_span[i] = -1; // We left the span so it is no longer active
+ span_end[i] = 0;
+ ++span_id[i];
+ if(span_id[i] >= num_spans[i]){
+ done = true;
+ }
+ } else if(span_end[i] == 1) {
+ active_span[i] = span_start[i] + span_id[i]; // We entered a new span
+ if(active_span[0] != -1 && active_span[1] != -1){
+ VoxelSpan test_spans[2];
+ test_spans[0] = spans[0][span_id[0]];
+ test_spans[1] = spans[1][span_id[1]];
+ if(test_spans[0].height[0] < test_spans[1].height[1] ||
+ test_spans[1].height[0] < test_spans[0].height[1])
+ {
+ LOGE << "False positive!" << std::endl;
+ }
+ span_neighbors->at(active_span[0]).push_back(active_span[1]);
+ span_neighbors->at(active_span[1]).push_back(active_span[0]);
+ // We found a neighbor pair!
+ }
+ }
+ }
+ }
+}
+
+void PlaceLightProbes(SceneGraph* scenegraph, vec3 translation, quaternion rotation, vec3 scale) {
+ // Rasterize triangles in scene to g_voxel_field
+ {
+ std::vector<vec3> tri_verts;
+ { // Get tris intersecting box
+ btTransform transform;
+ { // Calculate box transform matrix
+ mat4 rotation_mat = Mat4FromQuaternion(rotation);
+ mat4 translation_mat;
+ translation_mat.SetTranslation(translation);
+ mat4 transform_mat = translation_mat * rotation_mat;
+ transform.setFromOpenGLMatrix(transform_mat.entries);
+ }
+ // Set up box collision shape in Bullet
+ btCollisionObject temp_object;
+ btBoxShape shape(btVector3(scale[0], scale[1], scale[2]));
+ temp_object.setCollisionShape(&shape);
+ temp_object.setWorldTransform(transform);
+ // Check for collision with each object
+ for(SceneGraph::object_list::iterator iter = scenegraph->collide_objects_.begin();
+ iter != scenegraph->collide_objects_.end();
+ ++iter)
+ {
+ Object* object = (*iter);
+ EntityType object_type = object->GetType();
+ if(object_type == _env_object || object_type == _terrain_type){
+ // Extract model from object
+ const Model* model = NULL;
+ BulletObject *bullet_object = NULL;
+ switch(object_type){
+ case _env_object:
+ bullet_object = ((EnvObject*)object)->bullet_object_;
+ model = &Models::Instance()->GetModel(((EnvObject*)object)->model_id_);
+ break;
+ case _terrain_type:
+ bullet_object = ((TerrainObject*)object)->bullet_object_;
+ model = &((TerrainObject*)object)->terrain_.GetModel();
+ break;
+ default:
+ break;
+ }
+ if(bullet_object && bullet_object->shape.get() &&
+ (bullet_object->shape->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE || bullet_object->shape->getShapeType() == SCALED_TRIANGLE_MESH_SHAPE_PROXYTYPE))
+ {
+ // Check for collision between box and object, with triangle list
+ TriListResults tlr;
+ TriListCallback cb(tlr);
+ object->scenegraph_->bullet_world_->GetPairCollisions(temp_object, *bullet_object->body, cb);
+ // Add transformed triangles to list
+ const std::vector<int>& tris = tlr[bullet_object];
+ for(unsigned tri_index=0; tri_index<tris.size(); ++tri_index){
+ vec3 vert[3];
+ int face_index = tris[tri_index]*3;
+ for(int j=0; j<3; ++j){
+ int vert_index = model->faces[face_index+j]*3;
+ for(int k=0; k<3; ++k){
+ vert[j][k] = model->vertices[vert_index+k];
+ }
+ tri_verts.push_back(object->GetTransform() * vert[j]);
+ }
+ }
+ }
+ }
+ }
+ } // done getting tris intersecting box
+
+ RasterizeTrisToVoxelField(tri_verts, g_voxel_field);
+ }
+
+ // Create virtual box mesh, and create a voxel field from that
+ VoxelField box_voxel_field;
+ {
+ std::vector<vec3> tri_verts;
+ // Add box faces to voxel field
+ mat4 box_transform;
+ {
+ mat4 rotation_mat = Mat4FromQuaternion(rotation);
+ mat4 translation_mat;
+ translation_mat.SetTranslation(translation);
+ mat4 scale_mat;
+ scale_mat.SetScale(scale);
+ box_transform = translation_mat * rotation_mat * scale_mat;
+ }
+ tri_verts.push_back(box_transform * vec3(-1,-1,-1));
+ tri_verts.push_back(box_transform * vec3( 1,-1,-1));
+ tri_verts.push_back(box_transform * vec3( 1, 1,-1));
+
+ tri_verts.push_back(box_transform * vec3(-1,-1,-1));
+ tri_verts.push_back(box_transform * vec3( 1, 1,-1));
+ tri_verts.push_back(box_transform * vec3(-1, 1,-1));
+
+ tri_verts.push_back(box_transform * vec3(-1,-1, 1));
+ tri_verts.push_back(box_transform * vec3( 1,-1, 1));
+ tri_verts.push_back(box_transform * vec3( 1, 1, 1));
+
+ tri_verts.push_back(box_transform * vec3(-1,-1, 1));
+ tri_verts.push_back(box_transform * vec3( 1, 1, 1));
+ tri_verts.push_back(box_transform * vec3(-1, 1, 1));
+
+ tri_verts.push_back(box_transform * vec3(-1,-1,-1));
+ tri_verts.push_back(box_transform * vec3(-1, 1,-1));
+ tri_verts.push_back(box_transform * vec3(-1, 1, 1));
+
+ tri_verts.push_back(box_transform * vec3(-1,-1,-1));
+ tri_verts.push_back(box_transform * vec3(-1, 1, 1));
+ tri_verts.push_back(box_transform * vec3(-1,-1, 1));
+
+ tri_verts.push_back(box_transform * vec3( 1,-1,-1));
+ tri_verts.push_back(box_transform * vec3( 1, 1,-1));
+ tri_verts.push_back(box_transform * vec3( 1, 1, 1));
+
+ tri_verts.push_back(box_transform * vec3( 1,-1,-1));
+ tri_verts.push_back(box_transform * vec3( 1, 1, 1));
+ tri_verts.push_back(box_transform * vec3( 1,-1, 1));
+
+ tri_verts.push_back(box_transform * vec3(-1,-1,-1));
+ tri_verts.push_back(box_transform * vec3( 1,-1,-1));
+ tri_verts.push_back(box_transform * vec3( 1,-1, 1));
+
+ tri_verts.push_back(box_transform * vec3(-1,-1,-1));
+ tri_verts.push_back(box_transform * vec3( 1,-1, 1));
+ tri_verts.push_back(box_transform * vec3(-1,-1, 1));
+
+ tri_verts.push_back(box_transform * vec3(-1, 1,-1));
+ tri_verts.push_back(box_transform * vec3( 1, 1,-1));
+ tri_verts.push_back(box_transform * vec3( 1, 1, 1));
+
+ tri_verts.push_back(box_transform * vec3(-1, 1,-1));
+ tri_verts.push_back(box_transform * vec3( 1, 1, 1));
+ tri_verts.push_back(box_transform * vec3(-1, 1, 1));
+
+ RasterizeTrisToVoxelField(tri_verts, box_voxel_field);
+ }
+
+ VoxelSpanField empty_box_spans;
+ empty_box_spans.resize(box_voxel_field.spans.size());
+
+ // Get empty spans from box field
+ for(size_t column_index=0, len=box_voxel_field.spans.size(); column_index<len; ++column_index){
+ std::list<VoxelSpan>& span_list = box_voxel_field.spans[column_index];
+ std::list<VoxelSpan>& empty_span_list = empty_box_spans[column_index];
+ span_list.sort(VoxelSpanHeightSort);
+ VoxelSpan* prev_span = NULL;
+ for(std::list<VoxelSpan>::iterator iter = span_list.begin();
+ iter != span_list.end();
+ ++iter)
+ {
+ VoxelSpan& span = *iter;
+ if(prev_span){
+ VoxelSpan empty_span;
+ empty_span.height[0] = prev_span->height[1];
+ empty_span.height[1] = span.height[0];
+ empty_span_list.push_back(empty_span);
+ }
+ prev_span = &span;
+ }
+ }
+
+ int voxel_field_start[2];
+ voxel_field_start[0] = (int)(g_voxel_field.voxel_field_bounds[0][0]/g_voxel_field.voxel_size);
+ voxel_field_start[1] = (int)(g_voxel_field.voxel_field_bounds[0][2]/g_voxel_field.voxel_size);
+ int box_voxel_field_start[2];
+ box_voxel_field_start[0] = (int)(box_voxel_field.voxel_field_bounds[0][0]/box_voxel_field.voxel_size);
+ box_voxel_field_start[1] = (int)(box_voxel_field.voxel_field_bounds[0][2]/box_voxel_field.voxel_size);
+
+ VoxelSpanField cropped_voxel_field_spans;
+ cropped_voxel_field_spans.resize(box_voxel_field.spans.size());
+ // Adapt triangle voxel field to box voxel field
+ for(size_t column_index=0, len=box_voxel_field.spans.size(); column_index<len; ++column_index){
+ int world_coord[2];
+ world_coord[0] = box_voxel_field_start[0];
+ world_coord[0] += (column_index / box_voxel_field.voxel_field_dims[2]);
+ world_coord[1] = box_voxel_field_start[1];
+ world_coord[1] += column_index % box_voxel_field.voxel_field_dims[2];
+ int voxel_field_coord[2];
+ voxel_field_coord[0] = world_coord[0] - voxel_field_start[0];
+ voxel_field_coord[1] = world_coord[1] - voxel_field_start[1];
+ int voxel_field_span_index = voxel_field_coord[0] * g_voxel_field.voxel_field_dims[2] + voxel_field_coord[1];
+ std::list<VoxelSpan>& span_list = cropped_voxel_field_spans[column_index];
+ std::list<VoxelSpan>* box_span_list = NULL;
+ if(voxel_field_coord[0] >= 0 && voxel_field_coord[1] >= 0 &&
+ voxel_field_coord[0] < g_voxel_field.voxel_field_dims[0] &&
+ voxel_field_coord[1] < g_voxel_field.voxel_field_dims[2])
+ {
+ box_span_list = &g_voxel_field.spans[voxel_field_span_index];
+ }
+ if(box_span_list) {
+ span_list = *box_span_list;
+ }
+ }
+
+ // Crop tri spans to empty box field spans
+ for(size_t column_index=0, len=cropped_voxel_field_spans.size(); column_index<len; ++column_index){
+ std::list<VoxelSpan>& span_list = cropped_voxel_field_spans[column_index];
+ std::list<VoxelSpan>* box_span_list = &empty_box_spans[column_index];
+ for(std::list<VoxelSpan>::iterator iter = span_list.begin();
+ iter != span_list.end();)
+ {
+ VoxelSpan& span = *iter;
+ if(!box_span_list || box_span_list->empty()){
+ iter = span_list.erase(iter);
+ } else {
+ LOG_ASSERT(box_span_list->size() == 1);
+ VoxelSpan& empty_span = box_span_list->front();
+ if(span.height[1] > empty_span.height[0] || span.height[0] < empty_span.height[1]){
+ iter = span_list.erase(iter);
+ } else {
+ span.height[0] = min(span.height[0], empty_span.height[0]);
+ span.height[1] = max(span.height[1], empty_span.height[1]);
+ ++iter;
+ }
+ }
+ }
+ }
+
+
+ // Get empty spans from voxel field
+ VoxelSpanField empty_field_spans;
+ empty_field_spans.resize(cropped_voxel_field_spans.size());
+ for(size_t column_index=0, len=cropped_voxel_field_spans.size(); column_index<len; ++column_index){
+ std::list<VoxelSpan>& empty_box_span_list = empty_box_spans[column_index];
+ std::list<VoxelSpan>& span_list = cropped_voxel_field_spans[column_index];
+ std::list<VoxelSpan>& empty_span_list = empty_field_spans[column_index];
+ VoxelSpan* prev_span = NULL;
+ for(std::list<VoxelSpan>::iterator iter = span_list.begin();
+ iter != span_list.end();
+ ++iter)
+ {
+ VoxelSpan& span = *iter;
+ if(prev_span){
+ VoxelSpan empty_span;
+ empty_span.height[0] = prev_span->height[1];
+ empty_span.height[1] = span.height[0];
+ if(empty_span.height[0] != empty_span.height[1]){
+ empty_span_list.push_back(empty_span);
+ }
+ }
+ prev_span = &span;
+ }
+ if(!empty_box_span_list.empty()){
+ if(span_list.empty()){
+ empty_span_list = empty_box_span_list;
+ } else {
+ VoxelSpan empty_span;
+ empty_span.height[0] = empty_box_span_list.front().height[0];
+ empty_span.height[1] = span_list.front().height[0];
+ if(empty_span.height[0] != empty_span.height[1]){
+ empty_span_list.push_front(empty_span);
+ }
+ empty_span.height[0] = span_list.back().height[1];
+ empty_span.height[1] = empty_box_span_list.front().height[1];
+ if(empty_span.height[0] != empty_span.height[1]){
+ empty_span_list.push_back(empty_span);
+ }
+ }
+ }
+ }
+
+
+ // Flatten empty spans to contiguous memory
+ std::vector<VoxelSpan> span_vector;
+ std::vector<int> column_start_index;
+ column_start_index.resize(empty_field_spans.size());
+ for(size_t column_index=0, len=empty_field_spans.size();
+ column_index<len;
+ ++column_index)
+ {
+ column_start_index[column_index] = span_vector.size();
+ std::list<VoxelSpan>& span_list = empty_field_spans[column_index];
+ for(std::list<VoxelSpan>::iterator iter = span_list.begin();
+ iter != span_list.end();
+ ++iter)
+ {
+ span_vector.push_back(*iter);
+ }
+ }
+ // Cap start index list to avoid special case when finding num spans per column
+ column_start_index.push_back((int)span_vector.size());
+
+ // Find neighbors to each span
+ std::vector< std::list<int> > span_neighbors;
+ span_neighbors.resize(span_vector.size());
+ for(size_t column_index=0, len=empty_field_spans.size();
+ column_index<len;
+ ++column_index)
+ {
+ int num_spans[2];
+ num_spans[0] = column_start_index[column_index+1] - column_start_index[column_index];
+ if(num_spans[0] == 0){
+ // No spans in this column, so skip to next
+ continue;
+ }
+ int coord[2];
+ coord[0] = (int) (column_index / box_voxel_field.voxel_field_dims[2]);
+ coord[1] = column_index % box_voxel_field.voxel_field_dims[2];
+ // Check column x+1
+ if(coord[0] < box_voxel_field.voxel_field_dims[0] - 1){
+ size_t check_column_index = column_index + 1;
+ num_spans[1] = column_start_index[check_column_index+1] - column_start_index[check_column_index];
+ if(num_spans[1] > 0){
+ int span_start[2];
+ span_start[0] = column_start_index[column_index];
+ span_start[1] = column_start_index[check_column_index];
+ VoxelSpan* spans[2];
+ spans[0] = &span_vector[span_start[0]];
+ spans[1] = &span_vector[span_start[1]];
+ GetColumnNeighbors(num_spans, spans, span_start, &span_neighbors);
+ }
+ }
+ // Check column z+1
+ if(coord[1] < box_voxel_field.voxel_field_dims[0] - 1){
+ size_t check_column_index = column_index + box_voxel_field.voxel_field_dims[2];
+ num_spans[1] = column_start_index[check_column_index+1] - column_start_index[check_column_index];
+ if(num_spans[1] > 0){
+ int span_start[2];
+ span_start[0] = column_start_index[column_index];
+ span_start[1] = column_start_index[check_column_index];
+ VoxelSpan* spans[2];
+ spans[0] = &span_vector[span_start[0]];
+ spans[1] = &span_vector[span_start[1]];
+ GetColumnNeighbors(num_spans, spans, span_start, &span_neighbors);
+ }
+ }
+ }
+
+ // Fill the top middle span, and flood fill from there
+ std::vector<int> filled;
+ filled.resize(span_vector.size(), 0);
+ std::queue<int> to_fill;
+ to_fill.push( column_start_index [
+ (box_voxel_field.voxel_field_dims[0] / 2 * box_voxel_field.voxel_field_dims[2]) +
+ box_voxel_field.voxel_field_dims[2] / 2 ] );
+ filled[to_fill.front()] = 1;
+ while(!to_fill.empty()){
+ int span = to_fill.front();
+ to_fill.pop();
+ for(std::list<int>::iterator iter = span_neighbors[span].begin();
+ iter != span_neighbors[span].end();
+ ++iter)
+ {
+ int neighbor = *iter;
+ if(!filled[neighbor]){
+ to_fill.push(neighbor);
+ filled[neighbor] = filled[span]+1;
+ }
+ }
+ }
+
+ int kVoxelThinAmount = 16;
+ // Spawn probes in voxels
+ for(size_t column_index=0, len=empty_field_spans.size();
+ column_index<len;
+ ++column_index)
+ {
+ std::list<VoxelSpan>& span_list = empty_field_spans[column_index];
+ for(int i=column_start_index[column_index], end=column_start_index[column_index+1];
+ i<end; ++i)
+ {
+ int voxel_x = (int)(column_index / box_voxel_field.voxel_field_dims[2]);
+ int voxel_z = column_index % box_voxel_field.voxel_field_dims[2];
+ if(voxel_x % kVoxelThinAmount == 0 && voxel_z % kVoxelThinAmount == 0) {
+ float world_x = box_voxel_field.voxel_field_bounds[0][0] + (voxel_x+0.5f)*box_voxel_field.voxel_size;
+ float world_z = box_voxel_field.voxel_field_bounds[0][2] + (voxel_z+0.5f)*box_voxel_field.voxel_size;
+ VoxelSpan& span = span_vector[i];
+ for(int j=span.height[1]; j<span.height[0]; ++j){
+ if(j % kVoxelThinAmount == 0){
+ float world_y = (j+0.5f) * box_voxel_field.voxel_size;
+ vec3 pos(world_x, world_y, world_z);
+ Object* ppo = new LightProbeObject();
+ if(!filled[i]){
+ ((LightProbeObject*)ppo)->GetScriptParams()->ASSetInt("Negative", 1);
+ }
+ ppo->SetTranslation(pos);
+ if( ActorsEditor_AddEntity(scenegraph, ppo) ) {
+
+ } else {
+ LOGE << "Failed at adding probe to scenegraph" << std::endl;
+ delete ppo;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ VoxelSpanField final_field;
+ final_field.resize(empty_field_spans.size());
+ for(size_t column_index=0, len=final_field.size();
+ column_index<len;
+ ++column_index)
+ {
+ std::list<VoxelSpan>& span_list = final_field[column_index];
+ for(int i=column_start_index[column_index], end=column_start_index[column_index+1];
+ i<end; ++i)
+ {
+ if(filled[i] == 0){
+ span_list.push_back(span_vector[i]);
+ }
+ }
+ }
+
+ g_voxel_field = box_voxel_field;
+ g_voxel_field.spans = final_field;
+}
+
+static int shutdown_timer = 100;
+
+static uint64_t level_load_settle_frame_count = 0;
+
+const char* stress_levels[] = {
+ "Data/Levels/og_story/19b_Magma_Arena.xml",
+ "Data/Levels/og_story/lukas-test05.xml",
+ "Data/Levels/og_story/lukas-test13.xml",
+ "Data/Levels/og_story/23a_barracks.xml",
+ "Data/Levels/og_story/23b_Rock_Arena_ffa.xml",
+ "Data/Levels/og_story/27_Sky_Ark.xml",
+ "Data/Levels/og_story/test10.xml",
+ "Data/Levels/og_story/lukas-test23.xml",
+ "Data/Levels/og_story/test13.xml",
+ "Data/Levels/og_story/22c_Waterfall_Arena_1v3.xml",
+ "Data/Levels/og_story/lukas-test36.xml",
+ "Data/Levels/og_story/test26.xml",
+ "Data/Levels/og_story/test25.xml",
+ "Data/Levels/og_story/lukas-test09.xml",
+ "Data/Levels/og_story/22b_Waterfall_Arena_1v4.xml",
+ "Data/Levels/og_story/lukas-test12.xml",
+ "Data/Levels/og_story/19_MagmaBarracks_Jill.xml",
+ "Data/Levels/og_story/lukas-test33.xml",
+ "Data/Levels/og_story/25_Garden_Duel.xml",
+ "Data/Levels/og_story/test06.xml",
+ "Data/Levels/og_story/11_Rebel_Base.xml",
+ "Data/Levels/og_story/test18.xml",
+ "Data/Levels/og_story/19c_Magma_Arena_1v1.xml",
+ "Data/Levels/og_story/01_Slaver_Camp_Landing.xml",
+ "Data/Levels/og_story/lukas-test27.xml",
+ "Data/Levels/og_story/23c_Rock_Arena_Wolves.xml",
+ "Data/Levels/og_story/cave_artPass.xml",
+ "Data/Levels/og_story/lukas-test01.xml",
+ "Data/Levels/og_story/14_The_Rats_Cache.xml",
+ "Data/Levels/og_story/test02.xml",
+ "Data/Levels/og_story/lukas-test32.xml",
+ "Data/Levels/og_story/lukas-test10.xml",
+ "Data/Levels/og_story/lukas-test03.xml",
+ "Data/Levels/og_story/test24.xml",
+ "Data/Levels/og_story/19_Magma_Barracks.xml",
+ "Data/Levels/og_story/test03.xml",
+ "Data/Levels/og_story/18_RudeAwakening_Jill.xml",
+ "Data/Levels/og_story/lukas-test02.xml",
+ "Data/Levels/og_story/18_RudeAwakening.xml",
+ "Data/Levels/og_story/lukas-test24.xml",
+ "Data/Levels/og_story/07_Beach_Boat_Assault.xml",
+ "Data/Levels/og_story/23_Rock_Arena.xml",
+ "Data/Levels/og_story/lukas-test08.xml",
+ "Data/Levels/og_story/27_Sky_Ark_David.xml",
+ "Data/Levels/og_story/test05.xml",
+ "Data/Levels/og_story/test15.xml",
+ "Data/Levels/og_story/22_Waterfall_Arena.xml",
+ "Data/Levels/og_story/test11.xml",
+ "Data/Levels/og_story/16_Tree_Rescue.xml",
+ "Data/Levels/og_story/test23.xml",
+ "Data/Levels/og_story/lukas-test17.xml",
+ "Data/Levels/og_story/12_Canyon_Ambush.xml",
+ "Data/Levels/og_story/test37.xml",
+ "Data/Levels/og_story/test04.xml",
+ "Data/Levels/og_story/lukas-test31.xml",
+ "Data/Levels/og_story/lukas-test26.xml",
+ "Data/Levels/og_story/20_Volcano_Mine.xml",
+ "Data/Levels/og_story/lukas-test16.xml",
+ "Data/Levels/og_story/13_Rat_Slavers.xml",
+ "Data/Levels/og_story/lukas-test15.xml",
+ "Data/Levels/og_story/lukas-test20.xml",
+ "Data/Levels/og_story/test14.xml",
+ "Data/Levels/og_story/test17.xml",
+ "Data/Levels/og_story/test32.xml",
+ "Data/Levels/og_story/lukas-test25.xml",
+ "Data/Levels/og_story/test09.xml",
+ "Data/Levels/og_story/test13_terrain.xml",
+ "Data/Levels/og_story/test19.xml",
+ "Data/Levels/og_story/21_Waterfall_Barracks.xml",
+ "Data/Levels/og_story/02_Slaver_Camp.xml",
+ "Data/Levels/og_story/lukas-test11.xml",
+ "Data/Levels/og_story/test12.xml",
+ "Data/Levels/og_story/test30.xml",
+ "Data/Levels/og_story/test16.xml",
+ "Data/Levels/og_story/lukas-test35.xml",
+ "Data/Levels/og_story/lukas-test04.xml",
+ "Data/Levels/og_story/test20.xml",
+ "Data/Levels/og_story/test01Stucco.xml",
+ "Data/Levels/og_story/test27.xml",
+ "Data/Levels/og_story/19e_Magma_Arena_Wolf.xml",
+ "Data/Levels/og_story/22b_Waterfall_Barracks_return.xml",
+ "Data/Levels/og_story/lukas-test21.xml",
+ "Data/Levels/og_story/lukas-test18.xml",
+ "Data/Levels/og_story/26_Sky_Ark_Cutscene.xml",
+ "Data/Levels/og_story/17_Rat_HQ.xml",
+ "Data/Levels/og_story/lukas-test29.xml",
+ "Data/Levels/og_story/lukas-test19.xml",
+ "Data/Levels/og_story/test35.xml",
+ "Data/Levels/og_story/24_Forced_Fight.xml",
+ "Data/Levels/og_story/13c_Rat_Slavers.xml",
+ "Data/Levels/og_story/test21.xml",
+ "Data/Levels/og_story/lukas-test14.xml",
+ "Data/Levels/og_story/test08.xml",
+ "Data/Levels/og_story/test29.xml",
+ "Data/Levels/og_story/09_Dog_Fort_Rescue.xml",
+ "Data/Levels/og_story/22_Waterfall_Arena_1v1.xml",
+ "Data/Levels/og_story/05_Watchtower_Build_Site_Rebuild.xml",
+ "Data/Levels/og_story/19d_Magma_Arena_ffa.xml",
+ "Data/Levels/og_story/lukas-test30.xml",
+ "Data/Levels/og_story/test22.xml",
+ "Data/Levels/og_story/23_Rock_Arena_2v2.xml",
+ "Data/Levels/og_story/test28.xml",
+ "Data/Levels/og_story/08_Ice_Cliff_Landing.xml",
+ "Data/Levels/og_story/13b_Rat_Slavers.xml",
+ "Data/Levels/og_story/08_Ice_Cliff_Landing_Crete.xml",
+ "Data/Levels/og_story/lukas-test28.xml",
+ "Data/Levels/og_story/test33.xml",
+ "Data/Levels/og_story/lukas-test07.xml",
+ "Data/Levels/og_story/test07.xml",
+ "Data/Levels/og_story/lukas-test34.xml",
+ "Data/Levels/og_story/test31.xml",
+ "Data/Levels/og_story/10_Storm_Troopers.xml",
+ "Data/Levels/og_story/test36.xml",
+ "Data/Levels/og_story/19b_Magma_Arena_2v2.xml",
+ "Data/Levels/og_story/test01.xml",
+ "Data/Levels/og_story/06_Occupied_Farm.xml",
+ "Data/Levels/og_story/lukas-test06.xml",
+ "Data/Levels/og_story/test34.xml",
+ "Data/Levels/og_story/04_Main_Slaver_Camp.xml",
+ "Data/Levels/og_story/05_Watchtower_Build_Site.xml",
+ "Data/Levels/og_story/15_Bayou.xml",
+ NULL
+};
+
+//Update the simulation
+void Engine::Update() {
+ game_timer.UpdateWallTime();
+
+ PROFILER_ZONE(g_profiler_ctx, "Update");
+
+ if(check_save_level_changes_dialog_is_showing) {
+ // Wait for dialog to be complete
+ UpdateControls(ui_timer.timestep, false);
+ return;
+ }
+
+#ifdef WIN32
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Wait for object updates");
+ if(WaitForSingleObject(data_change_notification, 0) == WAIT_OBJECT_0 || WaitForSingleObject(write_change_notification, 0) == WAIT_OBJECT_0)
+ {
+ RequestLiveUpdate();
+ while(WaitForSingleObject(data_change_notification, 0) == WAIT_OBJECT_0){
+ FindNextChangeNotification(data_change_notification);
+ }
+ while(write_change_notification != INVALID_HANDLE_VALUE && WaitForSingleObject(write_change_notification, 0) == WAIT_OBJECT_0){
+ FindNextChangeNotification(write_change_notification);
+ }
+ }
+ }
+#endif
+
+#if ENABLE_STEAMWORKS
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Steam update");
+ Steamworks::Instance()->Update(current_engine_state_.type != kEngineLevelState && current_engine_state_.type != kEngineEditorLevelState);
+ }
+#endif
+
+ while( queued_engine_state_.size() > 0 ) {
+ bool continue_state_change = true;
+
+ EngineState new_engine_state;
+
+ bool found = false;
+ std::deque<EngineState> history_copy = state_history;
+ std::deque<EngineState> final_pop;
+
+ LOGI << "Checking if we actually allow any state changes" << std::endl;
+
+ switch(current_engine_state_.type) {
+ case kEngineLevelState:
+ case kEngineEditorLevelState:
+ if( scenegraph_ ) {
+ if(!check_save_level_changes_dialog_is_finished && !check_save_level_changes_dialog_is_showing) {
+ // Trigger save dialog
+ Input::Instance()->SetGrabMouse(false);
+ check_save_level_changes_dialog_is_showing = true;
+ check_save_level_changes_dialog_quit_if_not_cancelled = false;
+ check_save_level_changes_dialog_is_finished = false;
+ return;
+ }
+
+ continue_state_change = check_save_level_changes_last_result;
+
+ // Clear dialog state
+ check_save_level_changes_dialog_is_showing = false;
+ check_save_level_changes_dialog_quit_if_not_cancelled = false;
+ check_save_level_changes_dialog_is_finished = false;
+ check_save_level_changes_last_result = false;
+ }
+ break;
+ case kEngineScriptableUIState:
+ break;
+ case kEngineCampaignState:
+ break;
+ case kEngineNoState:
+ break;
+ }
+ bool pushed_current_to_history = false;
+
+ if( continue_state_change ) {
+ LOGI << "Performing a state queue request " << std::endl;
+
+ EngineStateAction esa = queued_engine_state_.front();
+ queued_engine_state_.pop_front();
+ std::deque<EngineState> local_popped_past;
+
+ switch( esa.type ) {
+ case kEngineStateActionPopState:
+ while( found == false ) {
+ EngineState candidate;
+ if( !history_copy.empty() ) {
+ candidate = history_copy.front();
+ history_copy.pop_front();
+ if( candidate.pop_past == false ) {
+ found = true;
+ LOGI << "Popping state to " << candidate << std::endl;
+ new_engine_state = candidate;
+ state_history = history_copy;
+ final_pop = local_popped_past;
+ } else {
+ LOGI << "Popping and tossing state " << candidate << std::endl;
+ local_popped_past.push_front(candidate);
+ }
+ } else {
+ if( esa.allow_game_exit ) {
+ Input::Instance()->RequestQuit();
+ }
+ found = true;
+ }
+ }
+ break;
+ case kEngineStateActionPopUntilType:
+ while( found == false ) {
+ EngineState candidate;
+ if( !history_copy.empty() ) {
+ candidate = history_copy.front();
+ history_copy.pop_front();
+
+ if( candidate.type == esa.state.type && candidate.pop_past == false ) {
+ found = true;
+ LOGI << "Popping state to " << candidate << std::endl;
+ new_engine_state = candidate;
+ state_history = history_copy;
+ final_pop = local_popped_past;
+ } else {
+ LOGI << "Popping and tossing state " << candidate << std::endl;
+ local_popped_past.push_front(candidate);
+ }
+ } else {
+ if( esa.allow_game_exit ) {
+ Input::Instance()->RequestQuit();
+ }
+ found = true;
+ }
+ }
+ break;
+ case kEngineStateActionPopUntilID:
+ while( found == false ) {
+ EngineState candidate;
+ if( !history_copy.empty() ) {
+ candidate = history_copy.front();
+ history_copy.pop_front();
+
+ if( candidate.id == esa.state.id && candidate.pop_past == false ) {
+ found = true;
+ LOGI << "Popping state to " << candidate << std::endl;
+ new_engine_state = candidate;
+ state_history = history_copy;
+ final_pop = local_popped_past;
+ } else {
+ LOGI << "Popping and tossing state " << candidate << std::endl;
+ local_popped_past.push_front(candidate);
+ }
+ } else {
+ if( esa.allow_game_exit ) {
+ Input::Instance()->RequestQuit();
+ }
+ found = true;
+ }
+ }
+
+ break;
+ case kEngineStateActionPushState:
+ state_history.push_front( current_engine_state_ );
+ pushed_current_to_history = true;
+ new_engine_state = esa.state;
+ break;
+
+ }
+
+ if(pushed_current_to_history == false) {
+ final_pop.push_front(current_engine_state_);
+ }
+
+ //Trigger final destruction of engine state
+ while( final_pop.size() > 0 ) {
+ EngineState final_state_pop = final_pop.front();
+ LOGI << "There are some final popping to do of " << final_state_pop << std::endl;
+ final_pop.pop_front();
+ switch(final_state_pop.type) {
+ case kEngineLevelState:
+ case kEngineEditorLevelState:
+ Input::Instance()->SetUpForXPlayers(0);
+ current_menu_player = -1;
+ break;
+ case kEngineScriptableUIState:
+ break;
+ case kEngineCampaignState:
+ final_state_pop.campaign->LeaveCampaign();
+ break;
+ case kEngineNoState:
+ LOGE << "No state" << std::endl;
+ break;
+ }
+ }
+
+ //We want to load something new!
+ if( new_engine_state.type != kEngineNoState )
+ {
+ cursor.SetCursor(DEFAULT_CURSOR);
+ LOGI << "Switching game state" << std::endl;
+ ScriptableCampaign* sc = GetCurrentCampaign();
+ //Lets unload what we previously had loaded first.
+ switch(current_engine_state_.type) {
+ case kEngineLevelState:
+ case kEngineEditorLevelState:
+ if( sc ) {
+ sc->LeaveLevel();
+ }
+ ClearLoadedLevel();
+ Input::Instance()->SetGrabMouse(false);
+ break;
+ case kEngineScriptableUIState:
+ if( scriptable_menu_ != NULL ) {
+ delete scriptable_menu_;
+ scriptable_menu_ = NULL;
+ IMElement::DestroyQueuedIMElements();
+ LOGI << "Disposing of scriptable menu" << std::endl;
+ IMrefCountTracker.logSanityCheck();
+ }
+ break;
+ case kEngineCampaignState:
+ break;
+ case kEngineNoState:
+ LOGE << "No state" << std::endl;
+ break;
+ }
+
+ current_engine_state_ = new_engine_state;
+
+ //Now load/initialize what is requested.
+ switch(current_engine_state_.type) {
+ case kEngineLevelState:
+ case kEngineEditorLevelState:
+ Engine::Instance()->GetSound()->TransitionToSong("overgrowth_silence");
+ if( current_engine_state_.path.isValid() ) {
+ QueueLevelToLoad( current_engine_state_.path );
+ } else {
+ ScriptableCampaign *sc = GetCurrentCampaign();
+ if( sc ) {
+ std::string campaign_id = sc->GetCampaignID();
+ ModInstance::Campaign camp = ModLoading::Instance().GetCampaign(campaign_id);
+
+ for( size_t i = 0; i < camp.levels.size(); i++ ) {
+ if( strmtch(camp.levels[i].id, current_engine_state_.id) ) {
+ std::string short_path = std::string("Data/Levels/") + std::string(camp.levels[i].path);
+
+ if( FileExists( short_path, kAnyPath ) )
+ {
+ Path path = FindFilePath(short_path,kAnyPath);
+ QueueLevelToLoad( path );
+ }
+ else
+ {
+ LOGE << "Unable to find level: " << short_path << ". Will not change state" << std::endl;
+ EngineStateAction esa;
+ esa.type = kEngineStateActionPopState;
+ esa.allow_game_exit = true;
+ queued_engine_state_.push_back(esa);
+ }
+ }
+ }
+ } else {
+ LOGE << "Currently no campaign is active, can't load level with id: " << current_engine_state_.id<< std::endl;
+ EngineStateAction esa;
+ esa.type = kEngineStateActionPopState;
+ esa.allow_game_exit = true;
+ queued_engine_state_.push_back(esa);
+ }
+ }
+ Shaders::Instance()->create_program_warning = false;
+ break;
+ case kEngineScriptableUIState: {
+ UIShowCursor(0);
+ scriptable_menu_ = new ScriptableUI((void*)this, &Engine::StaticScriptableUICallback);
+ ASData as_data;
+ as_data.scenegraph = scenegraph_;
+ as_data.gui = &gui;
+ Path script_path = current_engine_state_.path;
+ scriptable_menu_->Initialize(script_path, as_data, break_on_script_change);
+ latest_menu_path_ = script_path;
+
+ // Display popups queued while we didn't have a menu
+ while(!popup_pueue.empty()) {
+ scriptable_menu_->QueueBasicPopup(std::get<0>(popup_pueue.front()), std::get<1>(popup_pueue.front()));
+ popup_pueue.pop_front();
+ }
+ break;}
+ case kEngineCampaignState:
+ current_engine_state_.campaign->EnterCampaign();
+ break;
+ case kEngineNoState:
+ LOGE << "No state" << std::endl;
+ break;
+ }
+
+ }
+ } else {
+ queued_engine_state_.clear();
+ }
+ }
+
+ if( queued_level_.isValid() ) {
+ LoadLevel(queued_level_);
+ queued_level_ = Path();
+ PROFILER_NAME_TIMELINE(g_profiler_ctx, "Gameplay");
+
+ if( level_loaded_ ) {
+ if(scenegraph_) {
+ if(scenegraph_->map_editor->state_ != MapEditor::kInGame){
+ cursor.SetVisible(true);
+ }
+ }
+
+ ScriptableCampaign* sc = GetCurrentCampaign();
+ if( sc ) {
+ sc->EnterLevel();
+ }
+ } else {
+ EngineStateAction esa;
+ esa.type = kEngineStateActionPopState;
+ esa.allow_game_exit = true;
+ queued_engine_state_.push_back(esa);
+ }
+ }
+ int num_ui_timesteps = ui_timer.GetStepsNeeded();
+ num_ui_timesteps = min(num_ui_timesteps,_max_steps_per_frame);
+ ui_timer.updates_since_last_frame = num_ui_timesteps;
+
+ if(Input::Instance()->WasQuitRequested()){
+ if(scenegraph_){
+ if (!check_save_level_changes_dialog_is_finished && !check_save_level_changes_dialog_is_showing) {
+ // Trigger save dialog
+ Input::Instance()->SetGrabMouse(false);
+ check_save_level_changes_dialog_is_showing = true;
+ check_save_level_changes_dialog_quit_if_not_cancelled = true;
+ check_save_level_changes_dialog_is_finished = false;
+ return;
+ }
+
+ quitting_ = check_save_level_changes_last_result;
+
+ // Clear dialog state
+ check_save_level_changes_dialog_is_showing = false;
+ check_save_level_changes_dialog_quit_if_not_cancelled = false;
+ check_save_level_changes_dialog_is_finished = false;
+ check_save_level_changes_last_result = false;
+ } else {
+ quitting_ = true;
+ }
+ Input::Instance()->ClearQuitRequested();
+ }
+ sound.UpdateGameTimestep(game_timer.timestep);
+ sound.Update();
+
+ if (Online::Instance()->IsAwaitingShutdown()) {
+ Online::Instance()->StopMultiplayer();
+ }
+
+ if (Online::Instance()->IsActive()) {
+ Online::Instance()->CheckPendingMessages();
+ }
+
+ if(!level_loaded_ || waiting_for_input_) {
+ GetAssetManager()->SetLoadWarningMode(false,"","");
+ // Main menu loop
+ for(int curr_step=0; curr_step<num_ui_timesteps; curr_step++){
+ UpdateControls(ui_timer.timestep, false);
+ ui_timer.Update();
+ if(scriptable_menu_ && !waiting_for_input_){
+ scriptable_menu_->Update();
+ if(scriptable_menu_->IsDeleteScheduled()){
+
+ delete scriptable_menu_;
+ scriptable_menu_ = NULL;
+ }
+ }
+ }
+ sound.UpdateGameTimescale(1.0f);
+ if(waiting_for_input_){
+ waiting_for_input_ = false;
+ bool skip_loading_pause = config["skip_loading_pause"].toNumber<bool>();
+ bool no_dialogues = config["no_dialogues"].toNumber<bool>();
+ if (last_loading_input_time < finished_loading_time &&
+ (load_screen_tip[0] != '\0' || Online::Instance()->IsActive()) &&
+ !skip_loading_pause &&
+ !no_dialogues &&
+ !interrupt_loading)
+ {
+ waiting_for_input_ = true;
+ }
+
+ // Clients should never be allowed to end the loading screen themselves
+ // Multiplayer->ForceMapStartOnLoad() is the authority on level loading in Multiplayer (for the client)
+ if(Online::Instance()->IsClient()) {
+ waiting_for_input_ = !Online::Instance()->ForceMapStartOnLoad();
+ }
+ }
+ } else {
+ if( scenegraph_->map_editor->state_ == MapEditor::kInGame ) {
+ GetAssetManager()->SetLoadWarningMode(true,"Main Game Mode",scenegraph_->level_path_.GetOriginalPath());
+ } else {
+ GetAssetManager()->SetLoadWarningMode(false,"","");
+ }
+ // Fixed time steps
+ const std::vector<ASContext*>& active_contexts = ASProfiler::GetActiveContexts();
+ if(paused) {
+ for(int curr_step=0; curr_step<num_ui_timesteps; curr_step++){
+ PROFILER_ZONE(g_profiler_ctx, "Timestep");
+ ui_timer.Update();
+ avatar_control_manager.Update();
+ DebugDraw::Instance()->Update(game_timer.timestep);
+ DebugDrawAux::Instance()->Update(game_timer.timestep);
+ UpdateControls(ui_timer.timestep, false);
+ ActiveCameras::Get()->getCameraObject()->Update(ui_timer.timestep);
+ if (scenegraph_->map_editor->state_ != MapEditor::kInGame) {
+ PROFILER_ZONE(g_profiler_ctx, "Map editor update");
+ scenegraph_->map_editor->Update(&cursor);
+ }
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Level update");
+ scenegraph_->level->Update(paused);
+ }
+
+ for(size_t i=0, len=scenegraph_->movement_objects_.size(); i<len; ++i) {
+ MovementObject* mo = (MovementObject*)scenegraph_->movement_objects_[i];
+ if(mo->controlled){
+ mo->UpdatePaused();
+ }
+ }
+ HandleRabbotToggleControls();
+
+ for(size_t i = 0; i < active_contexts.size(); ++i) {
+ active_contexts[i]->profiler.Update();
+ }
+ }
+ } else {
+ int num_game_timesteps = game_timer.GetStepsNeeded();
+ num_game_timesteps = min(num_game_timesteps,_max_steps_per_frame);
+ game_timer.updates_since_last_frame = num_game_timesteps;
+
+ for(int curr_step=0; curr_step<num_ui_timesteps; curr_step++){
+ ui_timer.Update();
+ }
+ for(int curr_step=0; curr_step<num_game_timesteps; curr_step++) {
+ PROFILER_ZONE(g_profiler_ctx, "Timestep");
+ {
+ PROFILER_ZONE(g_profiler_ctx, "UpdateControlledModule");
+ avatar_control_manager.Update();
+ }
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Update debugdraw");
+ DebugDraw::Instance()->Update(game_timer.timestep);
+ DebugDrawAux::Instance()->Update(game_timer.timestep);
+ }
+
+ if(!paused){
+ PROFILER_ZONE(g_profiler_ctx, "Update timer");
+ game_timer.Update();
+ }
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Update controls");
+ UpdateControls(game_timer.timestep, false);
+ }
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Update flares");
+ scenegraph_->flares.Update(game_timer.timestep);
+ }
+
+
+
+ if(!paused){
+ PROFILER_ZONE(g_profiler_ctx, "Scenegraph update");
+ scenegraph_->Update(game_timer.timestep, game_timer.game_time);
+ } else {
+ ActiveCameras::Get()->getCameraObject()->Update(game_timer.timestep);
+ }
+
+ // if host, go over all changes and send over socket.
+ // client, go over all updates for env objects
+ if (Online::Instance()->IsActive()) {
+ Online::Instance()->UpdateObjects(scenegraph_);
+ }
+
+ if (Online::Instance()->IsActive()) {
+ for (uint32_t i = 0; i < scenegraph_->visible_static_meshes_.size(); i++) {
+ EnvObject * eo = scenegraph_->visible_static_meshes_[i];
+
+ eo->Update(game_timer.timestep);
+ }
+ }
+
+
+ if (scenegraph_->map_editor->state_ != MapEditor::kInGame) {
+ PROFILER_ZONE(g_profiler_ctx, "Map editor update");
+ scenegraph_->map_editor->Update(&cursor);
+ }
+
+ if(kLightProbeEnabled){
+ // TODO: move alongside other editor input processing?
+ if (KeyCommand::CheckPressed(Input::Instance()->getKeyboard(), KeyCommand::kBakeGI, KIMF_MENU | KIMF_LEVEL_EDITOR_GENERAL)) {
+ scenegraph_->map_editor->BakeLightProbes(0);
+ }
+ }
+ if(!paused){
+ PROFILER_ZONE(g_profiler_ctx, "Animation effects update");
+ particle_types.Update(game_timer.timestep);
+ //AnimationEffects::Instance()->Update(game_timer.timestep);
+ }
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Level update");
+ scenegraph_->level->Update(paused);
+ }
+ HandleRabbotToggleControls();
+ // Disposing of an object might in turn queue up more items
+ // to delete, so make we loop until we're at the end of
+ // object_ids_to_delete
+ for(size_t i = 0; i < scenegraph_->object_ids_to_delete.size(); ++i){
+ scenegraph_->map_editor->DeleteID(scenegraph_->object_ids_to_delete[i]);
+ }
+ scenegraph_->object_ids_to_delete.clear();
+
+ for(size_t i = 0; i < active_contexts.size(); ++i) {
+ active_contexts[i]->profiler.Update();
+ }
+ }
+ sound.UpdateGameTimescale(powf(game_timer.time_scale / current_global_scale_mult, 0.5f));
+ }
+
+ level_updated_ = min(10,level_updated_+1);
+ }
+
+ ScriptableCampaign *sc = GetCurrentCampaign();
+ if(sc) {
+ sc->Update();
+ }
+
+ if( !IsStateQueued() && config["quit_after_load"].toBool() )
+ {
+ if( shutdown_timer < 0 )
+ {
+ Input::Instance()->RequestQuit();
+ }
+
+ shutdown_timer--;
+ }
+
+ asset_manager.Update();
+ as_network.Update();
+
+
+ UpdateImGui();
+ if (Online::Instance() != nullptr) {
+ Online::Instance()->LateUpdate(GetSceneGraph());
+ }
+
+ static int next_load_stress_level = 0;
+ if(!cache_generation_paths.empty() && level_load_settle_frame_count == 0) {
+ back_to_menu = true;
+
+ EngineState newState("IsThisUsed", kEngineLevelState, cache_generation_paths.front());
+ QueueState(newState);
+
+ cache_generation_paths.erase(cache_generation_paths.begin());
+ level_load_settle_frame_count = 60 * 4;
+ } else if(level_load_settle_frame_count > 0) {
+ --level_load_settle_frame_count;
+ } else if(runtime_level_load_stress) {
+ if(!config["level_load_stress"].toNumber<bool>()) {
+ runtime_level_load_stress = false;
+ } else {
+ if( stress_levels[next_load_stress_level] == NULL ) {
+ next_load_stress_level = 0;
+ }
+ if( stress_levels[next_load_stress_level] != NULL ) {
+ if( queued_engine_state_.size() == 0 ) {
+ Path level = FindFilePath( stress_levels[next_load_stress_level] );
+ if( level.isValid() ) {
+ QueueLevelCacheGeneration(level);
+ }
+ next_load_stress_level++;
+ }
+ }
+ }
+ } else if(back_to_menu) {
+ cache_generation_paths.clear();
+
+ EngineStateAction esa;
+ esa.type = kEngineStateActionPopUntilType;
+ esa.allow_game_exit = false;
+ esa.state.type = kEngineScriptableUIState;
+ QueueState(esa);
+ back_to_menu = false;
+ }
+
+#if MONITOR_MEMORY
+ if((frame_counter % (60)) == 0) {
+ LOGI.Format("malloc Calls This Frame: %d\n", GetAndResetMallocAllocationCount() );
+ LOGI.Format("malloc Current Allocations: %d\n", GetCurrentNumberOfMallocAllocations() );
+ for( int i = 0; i < OG_MALLOC_TYPE_COUNT; i++ ) {
+ uint64_t mem_cb = GetCurrentTotalMallocAllocation(i);
+ if( mem_cb >= 1024 ) {
+ LOGI.Format("malloc Memory Use (%s): %u KiB\n", OgMallocTypeString(i), (unsigned)(mem_cb / 1024) );
+ } else {
+ LOGI.Format("malloc Memory Use (%s): %u B\n", OgMallocTypeString(i), (unsigned)mem_cb );
+ }
+ }
+ }
+#endif
+
+ save_file_.ExecuteQueuedWrite();
+
+ if(current_engine_state_.type != kEngineLevelState) {
+ if(live_update_countdown > 0 && --live_update_countdown == 0) {
+ if( config["enable_live_update"].toBool() && frame_counter > 30) {
+ LiveUpdate(scenegraph_, &asset_manager, scriptable_menu_);
+ }
+ }
+ }
+
+ IMElement::DestroyQueuedIMElements();
+
+ frame_counter++;
+
+ DisplayLastQueuedError();
+
+ if( queued_engine_state_.size() > 0 ) { // Avoid drawing bad frames, e.g. when loading level from dialogue
+ Update();
+ }
+}
+
+std::vector<vec3> sphere_points;
+
+static TextureRef histogram_tex;
+static const int kHistogramBuckets = 256;
+static const int kHistogramRes = 128;
+static const bool kDrawHistogram = false;
+
+struct FramebufferObject {
+ GLuint id;
+ TextureRef bound_tex;
+};
+
+static void CreateHistogram(const TextureRef& color_tex) {
+ Shaders* shaders = Shaders::Instance();
+ Textures* textures = Textures::Instance();
+ Graphics *graphics = Graphics::Instance();
+
+ static FramebufferObject framebuffer;
+ static int shader_id;
+ static int vert_attrib_id;
+ //static int buckets_uniform_id;
+ static VBOContainer pixel_vbo;
+ if(!histogram_tex.valid()){
+ histogram_tex = textures->makeTexture(kHistogramBuckets, 1, GL_RGBA16F, GL_RGBA);
+ textures->SetTextureName(histogram_tex, "Histogram");
+ graphics->genFramebuffers(&framebuffer.id, "histogram");
+ CHECK_GL_ERROR();
+ graphics->PushFramebuffer();
+ graphics->RenderFramebufferToTexture(framebuffer.id, histogram_tex);
+ CHECK_GL_ERROR();
+ framebuffer.bound_tex = histogram_tex;
+ graphics->PopFramebuffer();
+ CHECK_GL_ERROR();
+ shader_id = shaders->returnProgram("histogram_create");
+ shaders->setProgram(shader_id);
+ vert_attrib_id = shaders->returnShaderAttrib("pixel_uv", shader_id);
+ CHECK_GL_ERROR();
+ std::vector<GLfloat> pixel_data;
+ pixel_data.resize(kHistogramRes * kHistogramRes * 2);
+ for(int i=0, index=0; i<kHistogramRes; ++i){
+ for(int j=0; j<kHistogramRes; ++j){
+ pixel_data[index+0] = (j+0.5f)/(float)kHistogramRes;
+ pixel_data[index+1] = (i+0.5f)/(float)kHistogramRes;
+ index += 2;
+ }
+ }
+ pixel_vbo.Fill(kVBOFloat | kVBOStatic, pixel_data.size() * sizeof(GLfloat), &pixel_data[0]);
+ CHECK_GL_ERROR();
+ }
+ CHECK_GL_ERROR();
+ graphics->PushViewport();
+ CHECK_GL_ERROR();
+ graphics->setViewport(0, 0, kHistogramBuckets, 1);
+ GLState gl_state;
+ gl_state.blend= true;
+ gl_state.depth_test = false;
+ gl_state.depth_write = false;
+ gl_state.cull_face = false;
+ gl_state.blend_src = GL_SRC_ALPHA;
+ gl_state.blend_dst = GL_ONE;
+ graphics->setGLState(gl_state);
+ CHECK_GL_ERROR();
+ graphics->PushFramebuffer();
+ CHECK_GL_ERROR();
+ graphics->bindFramebuffer(framebuffer.id);
+ CHECK_GL_ERROR();
+ shaders->setProgram(shader_id);
+ textures->bindTexture(color_tex);
+
+ glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ CHECK_GL_ERROR();
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ CHECK_GL_ERROR();
+ pixel_vbo.Bind();
+ glVertexAttribPointer(vert_attrib_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0);
+ CHECK_GL_ERROR();
+ glPointSize(1);
+ graphics->DrawArrays(GL_POINTS, 0, kHistogramRes*kHistogramRes);
+ graphics->ResetVertexAttribArrays();
+ CHECK_GL_ERROR();
+
+ graphics->BindArrayVBO(0);
+ graphics->PopFramebuffer();
+ graphics->PopViewport();
+}
+
+static void DrawHistogram() {
+ Shaders* shaders = Shaders::Instance();
+ Textures* textures = Textures::Instance();
+ Graphics *graphics = Graphics::Instance();
+
+ static int shader_id = -1;
+ static VBOContainer vbo;
+ if(shader_id == -1){
+ shader_id = shaders->returnProgram("histogram_draw");
+ CHECK_GL_ERROR();
+ std::vector<GLfloat> data;
+ data.resize(kHistogramBuckets * 4);
+ for(int i=0, index=0; i<kHistogramBuckets; ++i){
+ data[index+0] = (float)i;
+ data[index+1] = 0.0f;
+ data[index+2] = (float)i;
+ data[index+3] = 1.0f;
+ index += 4;
+ }
+ vbo.Fill(kVBOFloat | kVBOStatic, data.size() * sizeof(GLfloat), &data[0]);
+ CHECK_GL_ERROR();
+ }
+
+ GLState gl_state;
+ gl_state.blend= true;
+ gl_state.depth_test = false;
+ gl_state.depth_write = false;
+ gl_state.cull_face = false;
+ graphics->setGLState(gl_state);
+ shaders->setProgram(shader_id);
+ int vert_attrib_id = shaders->returnShaderAttrib("lines", shader_id);
+ int uniform_mvp_id = shaders->returnShaderVariable("mvp", shader_id);
+ int uniform_buckets_id = shaders->returnShaderVariable("num_buckets", shader_id);
+ glm::mat4 proj_mat;
+ proj_mat = glm::ortho(0.0f, (float)graphics->viewport_dim[2], 0.0f, (float)graphics->viewport_dim[3]);
+ glm::mat4 mvp_mat = proj_mat;
+ shaders->SetUniformInt(uniform_buckets_id, kHistogramBuckets);
+ shaders->SetUniformMat4(uniform_mvp_id, (const GLfloat*)&mvp_mat);
+ textures->bindTexture(histogram_tex);
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ vbo.Bind();
+ glVertexAttribPointer(vert_attrib_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0);
+ glLineWidth(1);
+ graphics->DrawArrays(GL_LINES, 0, kHistogramBuckets*2);
+ graphics->ResetVertexAttribArrays();
+ graphics->BindArrayVBO(0);
+}
+
+static void DrawQuad(int shader_id) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Engine.cpp DrawQuad");
+ Shaders* shaders = Shaders::Instance();
+ Graphics *graphics = Graphics::Instance();
+ int vert_attrib_id = shaders->returnShaderAttrib("vert_attrib", shader_id);
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ glVertexAttribPointer(vert_attrib_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0);
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ graphics->ResetVertexAttribArrays();
+}
+
+void ApplyPostEffects(std::string post_shader, Engine::DrawingViewport drawing_viewport, vec2 active_screen_start, vec2 active_screen_end, Engine::PostEffectsType post_effects_type, const VBOContainer& quad_vert_vbo, const VBOContainer& quad_index_vbo) {;
+ Shaders* shaders = Shaders::Instance();
+ Textures* textures = Textures::Instance();
+ Graphics *graphics = Graphics::Instance();
+ Camera* camera = ActiveCameras::Get();
+
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Apply post effects");
+ graphics->PopFramebuffer();
+
+ if (graphics->multisample_framebuffer_exists) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Blit MSAA color buffer");
+ graphics->BlitColorBuffer();
+ }
+
+ if(kDrawHistogram){
+ CreateHistogram(graphics->screen_color_tex);
+ }
+
+ GLState gl_state;
+ gl_state.blend = false;
+ gl_state.cull_face = false;
+ gl_state.depth_test = false;
+ gl_state.depth_write = false;
+ graphics->setGLState(gl_state);
+
+ // Blur screen
+ quad_vert_vbo.Bind();
+ quad_index_vbo.Bind();
+ CHECK_GL_ERROR();
+
+ if(post_effects_type == Engine::kStraight){
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw unfiltered screen quad");
+ // Draw unfiltered HDR scene (e.g. for reflection capture)
+ int shader_id = shaders->returnProgram(post_shader);
+ graphics->PushViewport();
+ if(drawing_viewport != Engine::kViewport){
+ graphics->setViewport(0,0,graphics->window_dims[0],graphics->window_dims[1]);
+ } else {
+ glViewport(0,0,graphics->window_dims[0],graphics->window_dims[1]);
+ }
+ shaders->setProgram(shader_id);
+ textures->bindTexture(graphics->screen_color_tex);
+ //glEnable(GL_FRAMEBUFFER_SRGB );
+ DrawQuad(shader_id);
+ //glDisable(GL_FRAMEBUFFER_SRGB );
+ graphics->PopViewport();
+ graphics->PopViewport();
+ } /*else if(post_effects_type == Engine::kFinal && drawing_viewport == Engine::kViewport){
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Tone map only");
+ int shader_id = shaders->returnProgram(post_shader + " #TONE_MAP");
+ graphics->PushViewport();
+ glViewport(0,0,graphics->window_dims[0],graphics->window_dims[1]);
+ shaders->setProgram(shader_id);
+ textures->bindTexture(graphics->screen_color_tex);
+ glEnable(GL_FRAMEBUFFER_SRGB );
+ DrawQuad(shader_id);
+ glDisable(GL_FRAMEBUFFER_SRGB );
+ graphics->PopViewport();
+ graphics->PopViewport();
+ } */else if(post_effects_type == Engine::kFinal) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Full post effects");
+ static const GLbitfield all_buffers = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
+ static const GLenum all_attachments[3] = { GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT };
+ graphics->PushViewport();
+ graphics->PushFramebuffer();
+
+ graphics->setViewport(0,0,graphics->render_dims[0],graphics->render_dims[1]);
+ graphics->bindFramebuffer(graphics->post_effects.post_framebuffer);
+
+ TextureRef final_texture = graphics->screen_color_tex;
+ TextureRef temp_texture = graphics->post_effects.temp_screen_tex;
+
+ graphics->EnableVertexAttribArray(0);
+ glVertexAttribPointer(0, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0);
+
+ float curr_time = game_timer.game_time + game_timer.GetInterpWeight() / 120.0f;
+ if(graphics->config_.motion_blur_amount_ > 0.01f)
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Motion blur");
+ {
+ mat4 temp = camera->GetViewMatrix();
+ if(curr_time != prev_view_time){
+ temp.SetTranslationPart(temp.GetTranslationPart() + camera->GetViewMatrix().GetRotatedvec3(ActiveCameras::Get()->GetCenterVel() * (curr_time - prev_view_time)));
+ float rotation_blur_amount = 0.125f;
+ temp = mix(temp, ActiveCameras::Get()->prev_view_mat, rotation_blur_amount);
+ }
+ int shader_id = shaders->returnProgram(post_shader + " #CALC_MOTION_BLUR");
+ shaders->setProgram(shader_id);
+ shaders->SetUniformInt("screen_width",graphics->render_dims[0]);
+ shaders->SetUniformInt("screen_height",graphics->render_dims[1]);
+ shaders->SetUniformMat4("proj_mat",camera->GetProjMatrix());
+ shaders->SetUniformMat4("prev_view_mat", temp);//ActiveCameras::Get()->prev_view_mat);
+ shaders->SetUniformMat4("view_mat",camera->GetViewMatrix());
+ if(curr_time != prev_view_time){
+ shaders->SetUniformFloat("time_offset",curr_time - prev_view_time);
+ } else {
+ shaders->SetUniformFloat("time_offset", game_timer.timestep);
+ }
+
+ textures->bindTexture(final_texture);
+ textures->bindTexture(graphics->screen_vel_tex, 2);
+ textures->bindTexture(graphics->pure_noise_ref, TEX_PURE_NOISE);
+ textures->bindTexture(graphics->screen_depth_tex, TEX_SCREEN_DEPTH);
+ graphics->framebufferColorTexture2D(temp_texture);
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ }
+
+ { // Find dominant motion blur directions
+ int intermediate_width = graphics->render_dims[0] / 4;
+ int intermediate_height = graphics->render_dims[1] / 4;
+ graphics->setViewport(0,0,intermediate_width,intermediate_height);
+ { // Lower resolution of overbright for bloom
+ int shader_id = shaders->returnProgram(post_shader + " #DOWNSAMPLE");
+ shaders->setProgram(shader_id);
+ shaders->SetUniformInt("screen_width",graphics->render_dims[0]);
+ shaders->SetUniformInt("screen_height",graphics->render_dims[1]);
+ shaders->SetUniformFloat("src_lod",0.0f);
+ textures->bindTexture(temp_texture);
+ graphics->framebufferColorTexture2D(temp_texture, 2);
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ }
+
+ // Downsample bloom again
+ int downsampled_width = graphics->render_dims[0] / 16;
+ int downsampled_height = graphics->render_dims[1] / 16;
+ graphics->setViewport(0,0,downsampled_width,downsampled_height);
+ {
+ int shader_id = shaders->returnProgram(post_shader + " #DOWNSAMPLE");
+ shaders->setProgram(shader_id);
+ shaders->SetUniformInt("screen_width",intermediate_width);
+ shaders->SetUniformInt("screen_height",intermediate_height);
+ shaders->SetUniformFloat("src_lod",2.0f);
+ textures->bindTexture(temp_texture);
+ graphics->framebufferColorTexture2D(temp_texture, 4);
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ }
+
+ // Apply separable guassian blur to further down-sampled overbright image
+ {
+ int shader_id = shaders->returnProgram(post_shader + " #BLUR_HORZ");
+ shaders->setProgram(shader_id);
+ shaders->SetUniformInt("screen_width",downsampled_width);
+ shaders->SetUniformInt("screen_height",downsampled_height);
+ shaders->SetUniformFloat("src_lod",4.0f);
+ textures->bindTexture(temp_texture);
+ graphics->framebufferColorTexture2D(final_texture,4);
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ }
+ {
+ int shader_id = shaders->returnProgram(post_shader + " #BLUR_VERT");
+ shaders->setProgram(shader_id);
+ shaders->SetUniformInt("screen_width",downsampled_width);
+ shaders->SetUniformInt("screen_height",downsampled_height);
+ shaders->SetUniformFloat("src_lod",4.0f);
+ textures->bindTexture(final_texture);
+ graphics->framebufferColorTexture2D(temp_texture,4);
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ }
+ graphics->setViewport(0,0,graphics->render_dims[0],graphics->render_dims[1]);
+ }
+
+ {
+ int shader_id = shaders->returnProgram(post_shader + " #APPLY_MOTION_BLUR");
+ shaders->setProgram(shader_id);
+ shaders->SetUniformInt("screen_width",graphics->render_dims[0]);
+ shaders->SetUniformInt("screen_height",graphics->render_dims[1]);
+ shaders->SetUniformFloat("time_offset",curr_time - prev_view_time);
+ shaders->SetUniformFloat("motion_blur_mult", graphics->config_.motion_blur_amount_);
+
+ textures->bindTexture(final_texture);
+ textures->bindTexture(temp_texture, 2);
+ textures->bindTexture(graphics->pure_noise_ref, TEX_PURE_NOISE);
+ textures->bindTexture(graphics->screen_depth_tex, TEX_SCREEN_DEPTH);
+ graphics->framebufferColorTexture2D(graphics->screen_vel_tex);
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ final_texture = graphics->screen_vel_tex;
+ }
+ }
+
+ // Depth of field
+ if(graphics->config_.depth_of_field() && (camera->far_blur_amount > 0.0f || camera->near_blur_amount > 0.0f))
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Depth of field");
+ int shader_id = graphics->config_.depth_of_field_reduced()
+ ? shaders->returnProgram(post_shader + " #DOF #DOF_LESS")
+ : shaders->returnProgram(post_shader + " #DOF");
+ shaders->setProgram(shader_id);
+ shaders->SetUniformInt("screen_width",graphics->render_dims[0]);
+ shaders->SetUniformInt("screen_height",graphics->render_dims[1]);
+ shaders->SetUniformFloat("time",game_timer.game_time);
+
+ shaders->SetUniformFloat("near_blur_amount",camera->near_blur_amount);
+ shaders->SetUniformFloat("far_blur_amount",camera->far_blur_amount);
+ shaders->SetUniformFloat("near_sharp_dist",camera->near_sharp_dist);
+ shaders->SetUniformFloat("far_sharp_dist",camera->far_sharp_dist);
+ shaders->SetUniformFloat("near_blur_transition_size",camera->near_blur_transition_size);
+ shaders->SetUniformFloat("far_blur_transition_size",camera->far_blur_transition_size);
+
+ textures->bindTexture(final_texture);
+ textures->bindTexture(graphics->pure_noise_ref, TEX_PURE_NOISE);
+ textures->bindTexture(graphics->screen_depth_tex, TEX_SCREEN_DEPTH);
+ graphics->framebufferColorTexture2D(temp_texture);
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+
+ shader_id = shaders->returnProgram(post_shader + " #COPY");
+
+ textures->bindTexture(temp_texture);
+ graphics->framebufferColorTexture2D(final_texture);
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+
+ }
+
+
+ CHECK_GL_ERROR();
+ { // Apply tone mapping -- compress dynamic range
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Tone map");
+ int shader_id = shaders->returnProgram(post_shader + " #TONE_MAP");
+ shaders->setProgram(shader_id);
+ shaders->SetUniformInt("screen_width",graphics->render_dims[0]);
+ shaders->SetUniformInt("screen_height",graphics->render_dims[1]);
+ shaders->SetUniformFloat("saturation",Engine::Instance()->GetSceneGraph()->level->script_params().ASGetFloat("Saturation"));
+ if(g_albedo_only){
+ shaders->SetUniformFloat("black_point",0.0f);
+ shaders->SetUniformFloat("white_point",1.0f);
+ } else {
+ shaders->SetUniformFloat("black_point",graphics->hdr_black_point);
+ shaders->SetUniformFloat("white_point",graphics->hdr_white_point);
+ }
+ shaders->SetUniformVec3("tint",camera->tint);
+ shaders->SetUniformVec3("vignette_tint",camera->vignette_tint);
+ textures->bindTexture(final_texture);
+ graphics->framebufferColorTexture2D(graphics->post_effects.tone_mapped_tex);
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ final_texture = graphics->post_effects.tone_mapped_tex;
+ }
+
+ if(graphics->hdr_bloom_mult > 0.0f)
+ { // This is all bloom
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Bloom");
+ CHECK_GL_ERROR();
+
+ int intermediate_width = graphics->render_dims[0] / 4;
+ int intermediate_height = graphics->render_dims[1] / 4;
+ graphics->setViewport(0,0,intermediate_width,intermediate_height);
+ graphics->bindFramebuffer(graphics->post_effects.post_framebuffer);
+
+ { // Isolate pixels that are brighter than pure white
+ int shader_id = shaders->returnProgram(post_shader + " #DOWNSAMPLE #OVERBRIGHT");
+ shaders->setProgram(shader_id);
+ shaders->SetUniformInt("screen_width",graphics->render_dims[0]);
+ shaders->SetUniformInt("screen_height",graphics->render_dims[1]);
+ if(g_albedo_only){
+ shaders->SetUniformFloat("black_point",0.0f);
+ shaders->SetUniformFloat("white_point",1.0f);
+ } else {
+ shaders->SetUniformFloat("black_point",graphics->hdr_black_point);
+ shaders->SetUniformFloat("white_point",graphics->hdr_white_point);
+ }
+ shaders->SetUniformFloat("bloom_mult",graphics->hdr_bloom_mult);
+ shaders->SetUniformFloat("src_lod",0.0f);
+ textures->bindTexture(final_texture);
+ graphics->framebufferColorTexture2D(temp_texture, 2);
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ }
+
+ {
+ int shader_id = shaders->returnProgram(post_shader + " #BLUR_DIR");
+ shaders->setProgram(shader_id);
+ shaders->SetUniformInt("screen_width",intermediate_width);
+ shaders->SetUniformInt("screen_height",intermediate_width);
+ shaders->SetUniformFloat("src_lod",2.0f);
+
+ shaders->SetUniformInt("horz",1);
+ textures->bindTexture(temp_texture);
+ graphics->framebufferColorTexture2D(final_texture, 2);
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+
+ shaders->SetUniformInt("horz",0);
+ textures->bindTexture(final_texture);
+ graphics->framebufferColorTexture2D(temp_texture, 2);
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ }
+
+ // Downsample bloom again
+ int downsampled_width = graphics->render_dims[0] / 16;
+ int downsampled_height = graphics->render_dims[1] / 16;
+ graphics->setViewport(0,0,downsampled_width,downsampled_height);
+ graphics->bindFramebuffer(graphics->post_effects.post_framebuffer);
+ {
+ int shader_id = shaders->returnProgram(post_shader + " #DOWNSAMPLE");
+ shaders->setProgram(shader_id);
+ shaders->SetUniformInt("screen_width",intermediate_width);
+ shaders->SetUniformInt("screen_height",intermediate_height);
+ shaders->SetUniformFloat("src_lod",2.0f);
+ textures->bindTexture(temp_texture);
+ graphics->framebufferColorTexture2D(temp_texture, 4);
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ }
+
+ // Apply separable guassian blur to further down-sampled overbright image
+ {
+ int shader_id = shaders->returnProgram(post_shader + " #BLUR_DIR");
+ shaders->setProgram(shader_id);
+ shaders->SetUniformInt("screen_width",downsampled_width);
+ shaders->SetUniformInt("screen_height",downsampled_height);
+ shaders->SetUniformFloat("src_lod",4.0f);
+
+ shaders->SetUniformInt("horz",1);
+ textures->bindTexture(temp_texture);
+ graphics->framebufferColorTexture2D(final_texture, 4);
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+
+ shaders->SetUniformInt("horz",0);
+ textures->bindTexture(final_texture);
+ graphics->framebufferColorTexture2D(temp_texture, 4);
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ }
+
+ // Add downsampled and super downsampled bloom to the original tone-mapped image
+ graphics->setViewport(0,0,graphics->render_dims[0],graphics->render_dims[1]);
+ {
+ int shader_id = shaders->returnProgram(post_shader + " #ADD");
+ shaders->setProgram(shader_id);
+ textures->bindTexture(final_texture, TEX_TONE_MAPPED);
+ textures->bindTexture(temp_texture, TEX_INTERMEDIATE);
+ graphics->framebufferColorTexture2D(final_texture);
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ }
+ //final_texture = temp_texture;
+ }
+ const bool kMinZDepth = false;
+ if(kMinZDepth)
+ {
+ glColorMask(false, false, false, false);
+ graphics->setDepthWrite(true);
+ graphics->setDepthTest(true);
+ graphics->setDepthFunc(GL_ALWAYS);
+ int width = graphics->render_dims[0];
+ int height = graphics->render_dims[1];
+ int level = 0;
+ int shader_id = shaders->returnProgram(post_shader + " #DOWNSAMPLE_DEPTH");
+ shaders->setProgram(shader_id);
+ textures->bindTexture(graphics->screen_depth_tex);
+ while(width > 1 && height > 1){
+ width = max(1, width/2);
+ height = max(1, height/2);
+ ++level;
+ graphics->setViewport(0,0,width,height);
+ graphics->bindFramebuffer(graphics->post_effects.post_framebuffer);
+ shaders->SetUniformInt("screen_width",width);
+ shaders->SetUniformInt("screen_height",height);
+ shaders->SetUniformFloat("src_lod",(float)(level-1));
+ graphics->framebufferColorTexture2D(final_texture, level);
+ graphics->framebufferDepthTexture2D(graphics->screen_depth_tex, level);
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ }
+ glColorMask(true, true, true, true);
+ graphics->setDepthWrite(false);
+ graphics->setDepthTest(false);
+ graphics->setDepthFunc(GL_LEQUAL);
+ }
+
+ graphics->PopFramebuffer();
+ graphics->PopViewport();
+
+ //glEnable(GL_SCISSOR_TEST);
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Final pass");
+
+ int shader_id;
+ if(g_gamma_correct_final_output){
+ shader_id = shaders->returnProgram(post_shader + " #BRIGHTNESS #GAMMA_CORRECT_OUTPUT");
+ } else {
+ shader_id = shaders->returnProgram(post_shader + " #BRIGHTNESS");
+ }
+
+ shaders->setProgram(shader_id);
+ graphics->setViewport((GLint)(active_screen_start[0] * graphics->window_dims[0])
+ , (GLint)(active_screen_start[1] * graphics->window_dims[1])
+ , (GLint)(active_screen_end[0] * graphics->window_dims[0])
+ , (GLint)(active_screen_end[1] * graphics->window_dims[1]));
+ int temp_viewport_dims[4];
+ assert(sizeof(temp_viewport_dims) == sizeof(graphics->viewport_dim));
+ memcpy(temp_viewport_dims, graphics->viewport_dim, sizeof(temp_viewport_dims));
+ graphics->setViewport((GLint)0.0f
+ , (GLint)0.0f
+ , (GLint)graphics->window_dims[0]
+ , (GLint)graphics->window_dims[1]);
+ glEnable(GL_SCISSOR_TEST);
+ glScissor(temp_viewport_dims[0], temp_viewport_dims[1], temp_viewport_dims[2], temp_viewport_dims[3]);
+ shaders->SetUniformInt("screen_width",graphics->config_.screen_width());
+ shaders->SetUniformInt("screen_height",graphics->config_.screen_height());
+ if(g_albedo_only){
+ shaders->SetUniformFloat("black_point",0.0f);
+ shaders->SetUniformFloat("white_point",1.0f);
+ } else {
+ shaders->SetUniformFloat("black_point",graphics->hdr_black_point);
+ shaders->SetUniformFloat("white_point",graphics->hdr_white_point);
+ }
+ shaders->SetUniformFloat("hdr_bloom_mult",graphics->hdr_bloom_mult);
+ shaders->SetUniformFloat("time",game_timer.game_time);
+ shaders->SetUniformVec3("tint",camera->tint);
+ shaders->SetUniformFloat("brightness",config["brightness"].toNumber<float>());
+
+ if(sphere_points.empty()){
+ while(sphere_points.size() < 32){
+ vec3 point(RangedRandomFloat(-1.0f,1.0f),
+ RangedRandomFloat(-1.0f,1.0f),
+ RangedRandomFloat(-1.0f,1.0f));
+ if(length_squared(point)<=1.0f){
+ sphere_points.push_back(normalize(point));
+ }
+ }
+ }
+ shaders->SetUniformVec3Array("sphere_points",sphere_points);
+ textures->bindTexture(final_texture);
+ textures->bindTexture(graphics->screen_depth_tex, TEX_SCREEN_DEPTH);
+ textures->bindTexture(graphics->noise_ref, TEX_NOISE);
+ textures->bindTexture(graphics->pure_noise_ref, TEX_PURE_NOISE);
+ //It appears this might trigger a crash on intel6XX,
+ //so for now, we do gamma correction in the shader
+ //via #GAMMA_CORRECT_OUTPUT
+ //glEnable(GL_FRAMEBUFFER_SRGB );
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ //glDisable(GL_FRAMEBUFFER_SRGB );
+ }
+ graphics->PopViewport();
+ graphics->ResetVertexAttribArrays();
+ }
+}
+
+void Engine::DrawScene(DrawingViewport drawing_viewport, Engine::PostEffectsType post_effects_type, SceneGraph::SceneDrawType scene_draw_type) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw scene")
+ Graphics* graphics = Graphics::Instance();
+ PushGPUProfileRange(__FUNCTION__);
+ GLenum buffers[] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT };
+
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Setup Post Effects")
+ graphics->PushFramebuffer();
+ if (graphics->multisample_framebuffer_exists) {
+ graphics->bindFramebuffer(graphics->multisample_framebuffer);
+ } else {
+ graphics->bindFramebuffer(graphics->framebuffer);
+ }
+ graphics->PushViewport();
+ }
+
+ // Prepare for drawing
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Clear screen");
+ glDrawBuffers(2, buffers);
+ glClearColor( 0.0f, 0.0f, 0.0f, 0 );
+ graphics->drawing_shadow=false;
+ graphics->Clear(true);
+ glDrawBuffers(1, buffers);
+ }
+
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Apply viewpoint");
+ ActiveCameras::Get()->applyViewpoint();
+
+ std::vector<Camera>& virt_cam = ActiveCameras::Instance()->GetVirtualCameras();
+
+ for (auto& cam : virt_cam) {
+ cam.applyViewpoint();
+ }
+
+ }
+
+ { // Perform per-frame, per-camera functions, like character LOD and spawning grass
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Pre-draw camera");
+ ActiveCameras::Get()->tint = vec3(1.0);
+ ActiveCameras::Get()->vignette_tint = vec3(1.0);
+ float predraw_time = game_timer.GetRenderTime();
+ for(int i=0; i<(int)scenegraph_->objects_.size(); ++i){
+ Object* obj = scenegraph_->objects_[i];
+ if(!obj->parent){
+ obj->PreDrawCamera(predraw_time);
+ }
+ }
+ }
+
+ if(!g_no_decals){
+ scenegraph_->PrepareLightsAndDecals(active_screen_start, active_screen_end, vec2(graphics->render_dims[0], graphics->render_dims[1]));
+ }
+
+ // Draw everything
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw scenegraph");
+ if(graphics->depth_prepass){
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw depth-only pass");
+ mat4 proj = ActiveCameras::Get()->GetProjMatrix();
+ mat4 view = ActiveCameras::Get()->GetViewMatrix();
+ scenegraph_->DrawDepthMap(proj * view, (const vec4*)ActiveCameras::Get()->frustumPlanes, 6, SceneGraph::kDepthPrePass, scene_draw_type);
+ }
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw color pass");
+ scenegraph_->Draw(scene_draw_type);
+ }
+
+ if (graphics->multisample_framebuffer_exists) {
+ graphics->BlitDepthBuffer();
+ graphics->bindFramebuffer(graphics->multisample_framebuffer);
+ }
+
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw misc");
+ {
+ if(!(graphics->queued_screenshot && graphics->screenshot_mode == 1)) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw flares");
+ scenegraph_->flares.Draw(Flares::kSharp);
+ }
+ }
+ if (scenegraph_->map_editor->state_ != MapEditor::kInGame && ActiveCameras::Instance()->Get()->GetFlags() != Camera::kPreviewCamera) {
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw map editor");
+ scenegraph_->map_editor->Draw();
+ }
+ scenegraph_->light_probe_collection.Draw(*scenegraph_->bullet_world_);
+ scenegraph_->dynamic_light_collection.Draw(*scenegraph_->bullet_world_);
+ }
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw debug geometry");
+ GLenum buffers[] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT };
+ glDrawBuffers(2, buffers);
+ DebugDraw::Instance()->Draw();
+ }
+ }
+
+ float temp_white_point = 0.0f, temp_black_point = 0.0f;
+ if(scenegraph_->IsCollisionNavMeshVisible() || g_draw_collision){
+ temp_white_point = graphics->hdr_white_point;
+ temp_black_point = graphics->hdr_black_point;
+ graphics->hdr_white_point = 1.0f;
+ graphics->hdr_black_point = 0.0f;
+ }
+
+ {
+ std::string post_shader_name = graphics->post_shader_name;
+ if(scenegraph_->level->script_params().HasParam("Custom Shader") && config.GetRef("custom_level_shaders").toNumber<bool>()){
+ const std::string& custom_shader = scenegraph_->level->script_params().GetStringVal("Custom Shader");
+ if(!custom_shader.empty()) {
+ post_shader_name += " "+ custom_shader;
+ }
+ }
+ ApplyPostEffects(post_shader_name, drawing_viewport, active_screen_start, active_screen_end, post_effects_type, quad_vert_vbo, quad_index_vbo);
+ }
+ if(scenegraph_->IsCollisionNavMeshVisible() || g_draw_collision){
+ graphics->hdr_white_point = temp_white_point;
+ graphics->hdr_black_point = temp_black_point;
+ }
+ PopGPUProfileRange();
+
+}
+
+void Engine::LoadScreenLoop(bool loading_in_progress) {
+ {
+ PROFILER_ZONE_IDLE(g_profiler_ctx, "Load screen 60hz wait");
+ SDL_Delay(16); // Sleep for 16 ms, don't need more than 60 fps!
+ }
+ PROFILER_ZONE(g_profiler_ctx, "Load screen loop");
+ int num_time_steps = game_timer.GetStepsNeeded();
+ num_time_steps = min(num_time_steps,_max_steps_per_frame);
+ for(int curr_step=0; curr_step<num_time_steps; curr_step++){
+ PROFILER_ZONE(g_profiler_ctx, "Load screen step");
+ game_timer.game_time += game_timer.timestep;
+ UpdateControls(game_timer.timestep, true);
+ }
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Load screen draw");
+ DrawLoadScreen(loading_in_progress);
+ }
+}
+
+void Engine::DrawLoadScreen(bool loading_in_progress) {
+#ifndef GLDEBUG
+ // don't draw loading screen on gl debug
+ // makes traces more useful
+
+ Graphics* graphics = Graphics::Instance();
+ Textures* textures = Textures::Instance();
+ CHECK_GL_ERROR();
+ graphics->setViewport(0, 0, graphics->window_dims[0], graphics->window_dims[1]);
+
+ glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
+ graphics->Clear(true);
+
+ vec3 base_color = vec3(min(1.0f, (SDL_TS_GetTicks() - started_loading_time)/1000.0f));
+
+ if(!level_has_screenshot && load_screen_tip[0] == '\0') {
+ textures->drawTexture(loading_screen_logo->GetTextureRef(),
+ vec3((float)graphics->window_dims[0]/2.0f,(float)graphics->window_dims[1]/2.0f+sinf(game_timer.game_time)*20.0f,0),
+ 512.0f+(sinf(game_timer.game_time*2.0f)-1.0f)*20);
+ CHECK_GL_ERROR();
+ } else {
+ if(level_has_screenshot) {
+ CHECK_GL_ERROR();
+
+ // no coloring
+ Graphics* graphics = Graphics::Instance();
+ Shaders* shaders = Shaders::Instance();
+
+ int shader_id = shaders->returnProgram("simple_2d #TEXTURE #FLIPPED #LEVEL_SCREENSHOT");
+ shaders->createProgram(shader_id);
+ int shader_attrib_vert_coord = shaders->returnShaderAttrib("vert_coord", shader_id);
+ int shader_attrib_tex_coord = shaders->returnShaderAttrib("tex_coord", shader_id);
+ int uniform_mvp_mat = shaders->returnShaderVariable("mvp_mat", shader_id);
+ int uniform_color = shaders->returnShaderVariable("color", shader_id);
+ shaders->setProgram(shader_id);
+ CHECK_GL_ERROR();
+
+ GLState gl_state;
+ gl_state.blend = true;
+ gl_state.cull_face = false;
+ gl_state.depth_write = false;
+ gl_state.depth_test = false;
+ graphics->setGLState(gl_state);
+ CHECK_GL_ERROR();
+
+
+ glm::mat4 proj_mat;
+ proj_mat = glm::ortho(0.0f, (float)graphics->window_dims[0], 0.0f, (float)graphics->window_dims[1]);
+ glm::mat4 modelview_mat(1.0f);
+ vec2 where;
+ vec2 size(Textures::Instance()->getWidth(level_screenshot->GetTextureRef()),
+ Textures::Instance()->getHeight(level_screenshot->GetTextureRef()));
+ size /= size[1];
+ size *= (float) graphics->window_dims[1];
+ where = vec2(graphics->window_dims[0]*0.5f, graphics->window_dims[1]*0.5f);
+ modelview_mat = glm::translate(modelview_mat, glm::vec3(where[0]-size[0]*0.5f,where[1]-size[1]*0.5f,0));
+ modelview_mat = glm::scale(modelview_mat, glm::vec3(size[0],size[1],1.0f));
+ modelview_mat = glm::translate(modelview_mat, glm::vec3(0.5f,0.5f,0.5f));
+ modelview_mat = glm::translate(modelview_mat, glm::vec3(-0.5f,-0.5f,-0.5f));
+ glm::mat4 mvp_mat = proj_mat * modelview_mat;
+ CHECK_GL_ERROR();
+
+ graphics->EnableVertexAttribArray(shader_attrib_vert_coord);
+ CHECK_GL_ERROR();
+ graphics->EnableVertexAttribArray(shader_attrib_tex_coord);
+ CHECK_GL_ERROR();
+ static const GLfloat verts[] = {
+ 0, 0, 0, 0,
+ 1, 0, 1, 0,
+ 1, 1, 1, 1,
+ 0, 1, 0, 1
+ };
+ static const GLuint indices[] = {
+ 0, 1, 2,
+ 0, 2, 3
+ };
+ static VBOContainer vert_vbo;
+ static VBOContainer index_vbo;
+ static bool vbo_filled = false;
+ if(!vbo_filled) {
+ vert_vbo.Fill(kVBOFloat | kVBOStatic, sizeof(verts), (void*)verts);
+ index_vbo.Fill(kVBOElement | kVBOStatic, sizeof(indices), (void*)indices);
+ vbo_filled = true;
+ }
+ vert_vbo.Bind();
+ index_vbo.Bind();
+
+ glVertexAttribPointer(shader_attrib_vert_coord, 2, GL_FLOAT, false, 4*sizeof(GLfloat), 0);
+ CHECK_GL_ERROR();
+ glVertexAttribPointer(shader_attrib_tex_coord, 2, GL_FLOAT, false, 4*sizeof(GLfloat), (void*)(sizeof(GL_FLOAT)*2));
+ CHECK_GL_ERROR();
+
+ int num_indices = 6;
+
+ glUniformMatrix4fv(uniform_mvp_mat, 1, false, (GLfloat*)&mvp_mat);
+ CHECK_GL_ERROR();
+ vec4 color(base_color, 1.0f);
+ glUniform4fv(uniform_color, 1, &color[0]);
+ CHECK_GL_ERROR();
+
+ Textures::Instance()->bindTexture(level_screenshot, 0);
+ CHECK_GL_ERROR();
+ graphics->DrawElements(GL_TRIANGLES, num_indices, GL_UNSIGNED_INT, 0);
+ CHECK_GL_ERROR();
+
+ graphics->BindElementVBO(0);
+ graphics->BindArrayVBO(0);
+ graphics->ResetVertexAttribArrays();
+ CHECK_GL_ERROR();
+ }
+
+ { // Draw pulsing Overgrowth logo
+ CHECK_GL_ERROR();
+
+ // no coloring
+ Graphics* graphics = Graphics::Instance();
+ Shaders* shaders = Shaders::Instance();
+
+ int shader_id = shaders->returnProgram("simple_2d #TEXTURE #FLIPPED #LOADING_LOGO");
+ shaders->createProgram(shader_id);
+ int shader_attrib_vert_coord = shaders->returnShaderAttrib("vert_coord", shader_id);
+ int shader_attrib_tex_coord = shaders->returnShaderAttrib("tex_coord", shader_id);
+ int uniform_mvp_mat = shaders->returnShaderVariable("mvp_mat", shader_id);
+ int uniform_color = shaders->returnShaderVariable("color", shader_id);
+ int uniform_time = shaders->returnShaderVariable("time", shader_id);
+ shaders->setProgram(shader_id);
+ CHECK_GL_ERROR();
+
+ GLState gl_state;
+ gl_state.blend = true;
+ gl_state.cull_face = false;
+ gl_state.depth_write = false;
+ gl_state.depth_test = false;
+ graphics->setGLState(gl_state);
+ CHECK_GL_ERROR();
+
+
+ glm::mat4 proj_mat;
+ proj_mat = glm::ortho(0.0f, (float)graphics->window_dims[0], 0.0f, (float)graphics->window_dims[1]);
+ glm::mat4 modelview_mat(1.0f);
+ vec2 where;
+ vec2 size(graphics->window_dims[1]*0.1f);
+ where = vec2(graphics->window_dims[0]*0.5f, graphics->window_dims[1]*0.1f);
+ modelview_mat = glm::translate(modelview_mat, glm::vec3(where[0]-size[0]*0.5f,where[1]-size[1]*0.5f,0));
+ modelview_mat = glm::scale(modelview_mat, glm::vec3(size[0],size[1],1.0f));
+ modelview_mat = glm::translate(modelview_mat, glm::vec3(0.5f,0.5f,0.5f));
+ modelview_mat = glm::translate(modelview_mat, glm::vec3(-0.5f,-0.5f,-0.5f));
+ glm::mat4 mvp_mat = proj_mat * modelview_mat;
+ CHECK_GL_ERROR();
+
+ graphics->EnableVertexAttribArray(shader_attrib_vert_coord);
+ CHECK_GL_ERROR();
+ graphics->EnableVertexAttribArray(shader_attrib_tex_coord);
+ CHECK_GL_ERROR();
+ static const GLfloat verts[] = {
+ 0, 0, 0, 0,
+ 1, 0, 1, 0,
+ 1, 1, 1, 1,
+ 0, 1, 0, 1
+ };
+ static const GLuint indices[] = {
+ 0, 1, 2,
+ 0, 2, 3
+ };
+ static VBOContainer vert_vbo;
+ static VBOContainer index_vbo;
+ static bool vbo_filled = false;
+ if(!vbo_filled) {
+ vert_vbo.Fill(kVBOFloat | kVBOStatic, sizeof(verts), (void*)verts);
+ index_vbo.Fill(kVBOElement | kVBOStatic, sizeof(indices), (void*)indices);
+ vbo_filled = true;
+ }
+ vert_vbo.Bind();
+ index_vbo.Bind();
+
+ glVertexAttribPointer(shader_attrib_vert_coord, 2, GL_FLOAT, false, 4*sizeof(GLfloat), 0);
+ CHECK_GL_ERROR();
+ glVertexAttribPointer(shader_attrib_tex_coord, 2, GL_FLOAT, false, 4*sizeof(GLfloat), (void*)(sizeof(GL_FLOAT)*2));
+ CHECK_GL_ERROR();
+
+ int num_indices = 6;
+
+ glUniformMatrix4fv(uniform_mvp_mat, 1, false, (GLfloat*)&mvp_mat);
+ CHECK_GL_ERROR();
+ vec4 color(base_color, 1.0f);
+ glUniform4fv(uniform_color, 1, &color[0]);
+ glUniform1f(uniform_time, SDL_GetTicks()/1000.0f);
+ CHECK_GL_ERROR();
+
+ Textures::Instance()->bindTexture(loading_screen_og_logo->GetTextureRef(), 0);
+ CHECK_GL_ERROR();
+ graphics->DrawElements(GL_TRIANGLES, num_indices, GL_UNSIGNED_INT, 0);
+ CHECK_GL_ERROR();
+
+ graphics->BindElementVBO(0);
+ graphics->BindArrayVBO(0);
+ graphics->ResetVertexAttribArrays();
+ CHECK_GL_ERROR();
+ }
+ }
+
+ // Draw technical loading text
+ if(!level_has_screenshot && load_screen_tip[0] == '\0' || load_screen_tip[0] == '\0' && Online::Instance()->IsActive()) {
+ int font_height = font_renderer.GetFontInfo(0, FontRenderer::INFO_HEIGHT) / 64;
+ for(int i=0; i<NativeLoadingText::kMaxLines; ++i){
+ int pos[] = {20, graphics->window_dims[1] - 20 - font_height*i};
+ g_text_atlas_renderer.num_characters = 0;
+ CHECK_GL_ERROR();
+ g_text_atlas_renderer.AddText(&g_text_atlas[kTextAtlasMono],
+ &native_loading_text.buf[NativeLoadingText::kMaxCharPerLine*i],
+ pos, &font_renderer,
+ UINT32MAX);
+ CHECK_GL_ERROR();
+ g_text_atlas_renderer.Draw(&g_text_atlas[kTextAtlasMono],
+ graphics, TextAtlasRenderer::kTextShadow, vec4(1.0f));
+ CHECK_GL_ERROR();
+ Textures::Instance()->InvalidateBindCache();
+ }
+ }
+
+ { // Draw fun text
+ int font_size = int(max(18, min(Graphics::Instance()->window_dims[1] / 30, Graphics::Instance()->window_dims[0] / 50)));
+ TextMetrics metrics = g_as_text_context.ASGetTextAtlasMetrics( font_path, font_size, 0, load_screen_tip);
+ int pos[] = {graphics->window_dims[0] / 2 - metrics.bounds[2] / 2, graphics->window_dims[1] / 2 - font_size / 2};
+ g_as_text_context.ASDrawTextAtlas(font_path, font_size, 0, load_screen_tip, pos[0], pos[1], vec4(base_color, 1.0f));
+ Textures::Instance()->InvalidateBindCache();
+ }
+
+ CHECK_GL_ERROR();
+ Textures::Instance()->InvalidateBindCache();
+ graphics->SwapToScreen();
+ graphics->ClearGLState();
+ CHECK_GL_ERROR();
+
+#endif // GLDEBUG
+}
+
+void Engine::SetViewportForCamera(int which_cam, int num_screens, Graphics::ScreenType screen_type){
+ Graphics *graphics = Graphics::Instance();
+ switch(which_cam){
+ case 0: // Draw the first screen, which might take up the whole viewport, or just half
+ if(num_screens == 1){
+ active_screen_start = vec2(0.0f, 0.0f);
+ active_screen_end = vec2(1.0f, 1.0f);
+ } else if(num_screens == 2){
+ active_screen_start = vec2(0.0f, 0.0f);
+ active_screen_end = vec2(1.0f, 0.5f);
+ } else if(num_screens == 3 || num_screens == 4){
+ active_screen_start = vec2(0.0f, 0.0f);
+ active_screen_end = vec2(0.5f, 0.5f);
+ }
+ break;
+ case 1: // The second screen always just takes half the viewport
+ if(num_screens == 2){
+ active_screen_start = vec2(0.0f, 0.5f);
+ active_screen_end = vec2(1.0f, 1.0f);
+ } else if(num_screens == 3 || num_screens == 4){
+ active_screen_start = vec2(0.5f, 0.0f);
+ active_screen_end = vec2(1.0f, 0.5f);
+ }
+ break;
+ case 2:
+ active_screen_start = vec2(0.0f, 0.5f);
+ active_screen_end = vec2(0.5f, 1.0f);
+ break;
+ case 3:
+ active_screen_start = vec2(0.5f, 0.5f);
+ active_screen_end = vec2(1.0f, 1.0f);
+ break;
+ }
+
+ graphics->startDraw(active_screen_start, active_screen_end, screen_type);
+}
+
+void Engine::DrawCubeMap(TextureRef cube_map, const vec3 &pos, GLuint cube_map_fbo, SceneGraph::SceneDrawType scene_draw_type) {
+ GLuint cube_map_tex_id = Textures::Instance()->returnTexture(cube_map);
+ Graphics* graphics = Graphics::Instance();
+ CHECK_GL_ERROR();
+ // TODO: Why do we have to render the first one twice? Some state is messed up
+ for(int temp_i=-1; temp_i<6; ++temp_i){
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw cubemap face")
+ int i = max(0,temp_i);
+ graphics->startDraw(vec2(0.0f,0.0f), vec2(128.0f,128.0f), Graphics::kTexture);
+ ActiveCameras::Set(1);
+ CHECK_GL_ERROR();
+ ActiveCameras::Get()->SetFlags(Camera::kPreviewCamera);
+ // Apply camera object settings to camera
+ Camera* camera = ActiveCameras::Instance()->Get();
+ CHECK_GL_ERROR();
+ // Set camera position
+ camera->SetPos(pos);
+ // Set camera euler angles from rotation matrix
+ mat4 rot = GetCubeMapRotation(i);
+ vec3 front = rot * vec3(0,0,1);
+ vec3 up = rot * vec3(0,1,0);
+ vec3 expected_right = normalize(cross(front, vec3(0,1,0)));
+ vec3 expected_up = normalize(cross(expected_right, front));
+ float cam[3];
+ cam[0] = asinf(front[1])*-180.0f/PI_f;
+ cam[1] = atan2f(front[0], front[2])*180.0f/PI_f;
+ cam[2] = atan2f(dot(up,expected_right), dot(up, expected_up))*180.0f/PI_f;
+ // Set rotation twice so we don't have to worry about interpolation
+ camera->SetXRotation(cam[0]);
+ camera->SetXRotation(cam[0]);
+ camera->SetYRotation(cam[1]);
+ camera->SetYRotation(cam[1]);
+ camera->SetZRotation(cam[2]);
+ camera->SetZRotation(cam[2]);
+ // Force true 90 degree fov
+ camera->flexible_fov = false;
+ camera->SetFOV(90.0f);
+ camera->SetDistance(0.0f);
+ // Draw view
+ CHECK_GL_ERROR();
+ DrawScene(kViewport, Engine::kStraight, SceneGraph::kStaticOnly);
+ CHECK_GL_ERROR();
+ scenegraph_->level->Draw();
+
+ graphics->PushFramebuffer();
+ glBindFramebuffer(GL_FRAMEBUFFER, cube_map_fbo);
+ CHECK_GL_ERROR();
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, cube_map_tex_id, 0);
+ CHECK_GL_ERROR();
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, graphics->framebuffer);
+ CHECK_GL_ERROR();
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, cube_map_fbo);
+ CHECK_GL_ERROR();
+ glBlitFramebuffer(0, 0, 128, 128, 0, 0, 128, 128, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+ CHECK_GL_ERROR();
+ graphics->PopFramebuffer();
+
+ camera->flexible_fov = true;
+ }
+ CHECK_GL_ERROR();
+}
+
+
+std::vector<GLfloat> shadow_cache_verts;
+std::vector<GLuint> shadow_cache_indices;
+extern const bool kUseShadowCache = false;
+bool shadow_cache_dirty = true;
+bool shadow_cache_dirty_sun_moved = true;
+bool shadow_cache_dirty_level_loaded = true;
+VBOContainer shadow_cache_verts_vbo;
+VBOContainer shadow_cache_indices_vbo;
+
+static inline vec2 CalculateMulMat4Vec3(const mat4& transform, const vec3& vertex) {
+ return {
+ transform[0 + 4 * 0] * vertex[0] + transform[0 + 4 * 1] * vertex[1] + transform[0 + 4 * 2] * vertex[2] + transform[0 + 4 * 3] * 1.0f,
+ transform[1 + 4 * 0] * vertex[0] + transform[1 + 4 * 1] * vertex[1] + transform[1 + 4 * 2] * vertex[2] + transform[1 + 4 * 3] * 1.0f,
+ };
+}
+
+static void CalculateMinMax(const mat4& model_to_light_transform, const Model& model, float out_min_max_bounds[4]) {
+ float result_min_max_bounds[4] = { FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX, }; // min_x, min_y, max_x, max_y
+
+ vec3 bounding_corners[8] = {
+ model.min_coords,
+ vec3(model.max_coords[0], model.min_coords[1], model.min_coords[2]),
+ vec3(model.min_coords[0], model.max_coords[1], model.min_coords[2]),
+ vec3(model.max_coords[0], model.max_coords[1], model.min_coords[2]),
+ vec3(model.min_coords[0], model.min_coords[1], model.max_coords[2]),
+ vec3(model.max_coords[0], model.min_coords[1], model.max_coords[2]),
+ vec3(model.min_coords[0], model.max_coords[1], model.max_coords[2]),
+ model.max_coords,
+ };
+
+ for(int i = 0; i < 8; ++i) {
+ vec2 light_space_vert = CalculateMulMat4Vec3(model_to_light_transform, bounding_corners[i]);
+ result_min_max_bounds[0] = std::min(light_space_vert[0], result_min_max_bounds[0]);
+ result_min_max_bounds[1] = std::min(light_space_vert[1], result_min_max_bounds[1]);
+ result_min_max_bounds[2] = std::max(light_space_vert[0], result_min_max_bounds[2]);
+ result_min_max_bounds[3] = std::max(light_space_vert[1], result_min_max_bounds[3]);
+ }
+
+ memcpy(out_min_max_bounds, result_min_max_bounds, sizeof(result_min_max_bounds));
+}
+
+void DrawShadowCache(const mat4& proj_view_matrix, const char* special){
+ Graphics* graphics = Graphics::Instance();
+ Shaders* shaders = Shaders::Instance();
+
+ GLState gl_state;
+ gl_state.depth_test = true;
+ gl_state.cull_face = true;
+ gl_state.depth_write = true;
+ gl_state.blend = false;
+
+ graphics->setGLState(gl_state);
+
+ const int kBufSize = 512;
+ char shader_name[kBufSize];
+ FormatString(shader_name, kBufSize, "shadow%s", special);
+ int shader_id = shaders->returnProgram(shader_name);
+ shaders->setProgram(shader_id);
+ shaders->SetUniformMat4("projection_view_mat", proj_view_matrix);
+ int vert_attrib_id = shaders->returnShaderAttrib("vert", shader_id);
+
+ shadow_cache_verts_vbo.Bind();
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, 0, 0);
+ shadow_cache_indices_vbo.Bind();
+ graphics->DrawElements(GL_TRIANGLES, shadow_cache_indices.size(), GL_UNSIGNED_INT, 0);
+ graphics->ResetVertexAttribArrays();
+}
+
+static bool UpdateShadowCache(SceneGraph* scenegraph) {
+ Graphics* graphics = Graphics::Instance();
+ Textures* textures = Textures::Instance();
+ Shaders* shaders = Shaders::Instance();
+ PROFILER_ENTER(g_profiler_ctx, "Get bounds");
+
+ // Get basis from light perspective
+ vec3 light_dir = scenegraph->primary_light.pos;
+ vec3 light_to_world_basis[3];
+ light_to_world_basis[2] = light_dir;
+ light_to_world_basis[0] = normalize(cross(vec3(0,1,0), light_to_world_basis[2]));
+ light_to_world_basis[1] = normalize(cross(light_to_world_basis[0], light_to_world_basis[2]));
+ mat4 light_to_world_transform = {
+ light_to_world_basis[0][0], light_to_world_basis[0][1], light_to_world_basis[0][2], 0.0f,
+ light_to_world_basis[1][0], light_to_world_basis[1][1], light_to_world_basis[1][2], 0.0f,
+ light_to_world_basis[2][0], light_to_world_basis[2][1], light_to_world_basis[2][2], 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f,
+ };
+ mat4 world_to_light_transform = transpose(light_to_world_transform);
+
+ // Get bounds of scene in light space
+ const int kMaxUpdatesPerFrame = 32;
+ bool max_updates_hit = false;
+ int current_update_count = 0;
+ if(shadow_cache_dirty_sun_moved)
+ {
+ // Dirty all objects once, so iterative processing can clean them up
+ for(std::vector<ShadowCacheObjectLightBounds>::iterator it = scenegraph->visible_static_meshes_shadow_cache_bounds_.begin();
+ it != scenegraph->visible_static_meshes_shadow_cache_bounds_.end();
+ ++it)
+ {
+ ShadowCacheObjectLightBounds& lb = *it;
+ if(!lb.is_ignored) {
+ lb.is_calculated = false;
+ }
+ }
+ for(std::vector<ShadowCacheObjectLightBounds>::iterator it = scenegraph->terrain_objects_shadow_cache_bounds_.begin();
+ it != scenegraph->terrain_objects_shadow_cache_bounds_.end();
+ ++it)
+ {
+ ShadowCacheObjectLightBounds& lb = *it;
+ lb.is_calculated = false;
+ }
+ }
+ float bounds[4] = {FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX}; // min_x, min_y, max_x, max_y
+ for(size_t i = 0, len = scenegraph->visible_static_meshes_.size(); i < len && !max_updates_hit; ++i)
+ {
+ EnvObject& eo = *scenegraph->visible_static_meshes_[i];
+ ShadowCacheObjectLightBounds& lb = scenegraph->visible_static_meshes_shadow_cache_bounds_[i];
+ lb.is_ignored = eo.attached_ != NULL;
+ if(!lb.is_ignored) {
+ if(!lb.is_calculated || shadow_cache_dirty_level_loaded) {
+ const Model &env_model = *eo.GetModel();
+ const mat4& model_to_world_transform = eo.GetTransform();
+ CalculateMinMax(world_to_light_transform * model_to_world_transform, env_model, &lb.min_bounds[0]);
+ lb.is_calculated = true;
+ ++current_update_count;
+ }
+ }
+
+ if(!shadow_cache_dirty_level_loaded && current_update_count == kMaxUpdatesPerFrame && i + 1 < len)
+ {
+ max_updates_hit = true;
+ break;
+ }
+ }
+ for(size_t i = 0, len = scenegraph->terrain_objects_.size(); i < len && !max_updates_hit; ++i)
+ {
+ TerrainObject& to = *scenegraph->terrain_objects_[i];
+ ShadowCacheObjectLightBounds& lb = scenegraph->terrain_objects_shadow_cache_bounds_[i];
+ if(!lb.is_calculated || shadow_cache_dirty_level_loaded) {
+ const Model &env_model = *to.GetModel();
+ const mat4& model_to_world_transform = to.GetTransform();
+ CalculateMinMax(world_to_light_transform * model_to_world_transform, env_model, &lb.min_bounds[0]);
+ lb.is_calculated = true;
+ ++current_update_count;
+ }
+
+ if(!shadow_cache_dirty_level_loaded && current_update_count == kMaxUpdatesPerFrame && i + 1 < len)
+ {
+ max_updates_hit = true;
+ break;
+ }
+ }
+ if(shadow_cache_dirty_level_loaded || !max_updates_hit)
+ {
+ for(std::vector<ShadowCacheObjectLightBounds>::iterator it = scenegraph->visible_static_meshes_shadow_cache_bounds_.begin();
+ it != scenegraph->visible_static_meshes_shadow_cache_bounds_.end();
+ ++it)
+ {
+ ShadowCacheObjectLightBounds& lb = *it;
+ if(!lb.is_ignored) {
+ bounds[0] = std::min(lb.min_bounds[0], bounds[0]);
+ bounds[1] = std::min(lb.min_bounds[1], bounds[1]);
+ bounds[2] = std::max(lb.max_bounds[0], bounds[2]);
+ bounds[3] = std::max(lb.max_bounds[1], bounds[3]);
+ }
+ }
+ for(std::vector<ShadowCacheObjectLightBounds>::iterator it = scenegraph->terrain_objects_shadow_cache_bounds_.begin();
+ it != scenegraph->terrain_objects_shadow_cache_bounds_.end();
+ ++it)
+ {
+ ShadowCacheObjectLightBounds& lb = *it;
+ bounds[0] = std::min(lb.min_bounds[0], bounds[0]);
+ bounds[1] = std::min(lb.min_bounds[1], bounds[1]);
+ bounds[2] = std::max(lb.max_bounds[0], bounds[2]);
+ bounds[3] = std::max(lb.max_bounds[1], bounds[3]);
+ }
+ }
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ if(max_updates_hit)
+ {
+ return false;
+ }
+
+ { // Draw depth map of the whole level
+ Camera* camera = ActiveCameras::Get();
+ int num_shadow_view_frustum_planes = 6;
+ vec4 shadow_view_frustum_planes[6];
+ CHECK_GL_ERROR();
+ glColorMask(0,0,0,0);
+ graphics->drawing_shadow=true;
+ graphics->PushFramebuffer();
+ graphics->bindFramebuffer(graphics->static_shadow_fb);
+ graphics->PushViewport();
+ glEnable(GL_SCISSOR_TEST);
+ float shadow_size = max(bounds[2] - bounds[0], bounds[3]-bounds[1]);
+ //float shadow_radius = shadow_size / 1.414213563731f / 2.0f; // Get radius of circle inscribed in square with sides 'shadow_size'
+ vec3 shadow_center = (bounds[0]+bounds[2])*0.5f * light_to_world_basis[0] + (bounds[1]+bounds[3])*0.5f * light_to_world_basis[1];
+ vec3 light_space_center(dot(shadow_center, light_to_world_basis[0]),
+ dot(shadow_center, light_to_world_basis[1]),
+ dot(shadow_center, light_to_world_basis[2])); // Note: Dot of vector vs columns is like transpose(mat) * vec. And transpose(rot_only_mat) == inverse(rot_only_mat).
+ // Snap center to nearest texel to improve frame-to-frame coherency
+ float texel_size = shadow_size * 2.0f / graphics->shadow_res;
+ for(int j=0; j<3; ++j){
+ light_space_center[j] = floor(light_space_center[j]/texel_size)*texel_size;
+ }
+ // Update shadow center to new texel-snapped position
+ shadow_center = light_space_center[0] * light_to_world_basis[0] +
+ light_space_center[1] * light_to_world_basis[1];
+
+ graphics->setViewport(0,0,graphics->cascade_shadow_res,graphics->cascade_shadow_res);
+ graphics->Clear(true);
+ mat4 proj, view;
+ camera->applyShadowViewpoint(light_dir, shadow_center, shadow_size, &proj, &view);
+ mat4 proj_view_matrix = proj * view;
+ graphics->simple_shadow_mat = proj_view_matrix;
+
+ for(int i=0; i<6; ++i){
+ for(int j=0; j<4; ++j){
+ shadow_view_frustum_planes[num_shadow_view_frustum_planes-6+i][j] = camera->frustumPlanes[i][j];
+ }
+ }
+ scenegraph->DrawDepthMap(proj_view_matrix, shadow_view_frustum_planes, num_shadow_view_frustum_planes, SceneGraph::kDepthShadow, SceneGraph::kStaticOnly);
+ glDisable(GL_SCISSOR_TEST);
+ graphics->PopViewport();
+ graphics->PopFramebuffer();
+ graphics->drawing_shadow=false;
+ glColorMask(1,1,1,1);
+ }
+
+ if(false){ // Create shadow cache VBO of only triangles that affect shadows
+ PROFILER_LEAVE(g_profiler_ctx);
+ PROFILER_ENTER(g_profiler_ctx, "Fill VBO");
+ shadow_cache_verts_vbo.Fill(kVBOStatic | kVBOFloat, shadow_cache_verts.size()*sizeof(GLfloat), &shadow_cache_verts[0]);
+ shadow_cache_indices_vbo.Fill(kVBOStatic | kVBOElement, shadow_cache_indices.size()*sizeof(GLuint), &shadow_cache_indices[0]);
+
+ PROFILER_LEAVE(g_profiler_ctx);
+ PROFILER_ENTER(g_profiler_ctx, "Render all cascade tiles");
+ const int render_size = 2048;
+ graphics->PushFramebuffer();
+ graphics->PushViewport();
+ graphics->SetBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_DST_COLOR); // Should be equivalent to Photoshop 'lighten' mode
+ std::vector<bool> visible_gpu(shadow_cache_indices.size()/3, false);
+ int tri_visible_shader_id = shaders->returnProgram("tri_visible");
+ static GLuint cascade_tri_visible_fb = UINT_MAX;
+ static TextureRef cascade_tri_visible_texture_ref;
+ // Make sure framebuffer exists for accumulating visible triangle ids
+ if(cascade_tri_visible_fb == UINT_MAX){
+ graphics->genFramebuffers(&cascade_tri_visible_fb, "cascade_tri_visible_fb");
+ graphics->bindFramebuffer(cascade_tri_visible_fb);
+ cascade_tri_visible_texture_ref = textures->makeTextureColor(render_size, render_size);
+ Textures::Instance()->SetTextureName(cascade_tri_visible_texture_ref, "Cascade Triangle Visible");
+ graphics->framebufferColorTexture2D(cascade_tri_visible_texture_ref);
+ }
+ graphics->bindFramebuffer(cascade_tri_visible_fb);
+ graphics->setViewport(0,0,render_size,render_size);
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ graphics->Clear(true);
+ // Create VBO that just sends one vec2 for each texel in a render_size*render_size image
+ static VBOContainer pixel_vbo;
+ if(!pixel_vbo.valid()){
+ std::vector<GLfloat> pixel_data;
+ pixel_data.resize(render_size * render_size * 2);
+ for(int i=0, index=0; i<render_size; ++i){
+ for(int j=0; j<render_size; ++j){
+ pixel_data[index+0] = (j+0.5f)/(float)render_size;
+ pixel_data[index+1] = (i+0.5f)/(float)render_size;
+ index += 2;
+ }
+ }
+ pixel_vbo.Fill(kVBOFloat | kVBOStatic, pixel_data.size() * sizeof(GLfloat), &pixel_data[0]);
+ }
+ glPointSize(1);
+ CHECK_GL_ERROR();
+ float size[2] = {bounds[2] - bounds[0], bounds[3] - bounds[1]};
+ for(int cascade=0; cascade<1; ++cascade){
+ // Convert light-space bounds to texels
+ int texel_dim[2];
+ for(int j=0; j<2; ++j){
+ texel_dim[j] = (int)ceilf(size[j] / (shadow_sizes[cascade]) * graphics->shadow_res);
+ }
+ // How many tiles we need to render in each direction
+ int render_dim[2];
+ for(int j=0; j<2; ++j){
+ render_dim[j] = (texel_dim[j] + render_size - 1) / render_size;
+ }
+ Camera* camera = ActiveCameras::Get();
+ for(int x=0; x<render_dim[0]; ++x){
+ for(int y=0; y<render_dim[1]; ++y){
+ graphics->bindFramebuffer(graphics->cascade_shadow_color_fb);
+ graphics->setViewport(0,0,render_size,render_size);
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Cascade tile");
+ graphics->Clear(true);
+ vec3 light_space_center = vec3(bounds[0] + (x+0.5f)*shadow_sizes[cascade],
+ bounds[1] + (y+0.5f)*shadow_sizes[cascade],
+ 0.0f);
+ // Snap center to nearest texel to improve frame-to-frame coherency
+ float texel_size = shadow_sizes[cascade] * 2.0f / graphics->shadow_res;
+ for(int j=0; j<2; ++j){
+ light_space_center[j] = floor(light_space_center[j]/texel_size)*texel_size;
+ }
+ // Update shadow center to new texel-snapped position
+ vec3 shadow_center = light_space_center[0] * light_to_world_basis[0] +
+ light_space_center[1] * light_to_world_basis[1];
+ mat4 proj, view;
+ camera->applyShadowViewpoint(light_dir, shadow_center, shadow_sizes[cascade], &proj, &view);
+ mat4 proj_view_matrix = proj * view;
+ // Draw triangle ids as color to RGB channels
+ glColorMask(1,1,1,0);
+ graphics->setDepthFunc(GL_LESS);
+ DrawShadowCache(proj_view_matrix, " #TRI_COLOR");
+ // Draw to alpha channel if there is another fragment behind the front one
+ glColorMask(0,0,0,1);
+ graphics->setDepthFunc(GL_GREATER);
+ DrawShadowCache(proj_view_matrix, "");
+ // Calculate visible tris on GPU
+ glColorMask(1,1,1,1);
+ graphics->setBlend(true);
+ graphics->bindFramebuffer(cascade_tri_visible_fb);
+ graphics->setViewport(0,0,render_size,render_size);
+ shaders->setProgram(tri_visible_shader_id);
+ shaders->SetUniformInt("output_width", render_size);
+ shaders->SetUniformInt("output_height", render_size);
+ textures->bindTexture(graphics->cascade_shadow_color_ref);
+ pixel_vbo.Bind();
+ int vert_attrib_id = shaders->returnShaderAttrib("pixel_uv", tri_visible_shader_id);
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ glVertexAttribPointer(vert_attrib_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0);
+ glDrawArrays(GL_POINTS, 0, render_size * render_size);
+ graphics->ResetVertexAttribArrays();
+ }
+ }
+ {
+ int size = render_size * render_size;
+ unsigned char *pixels = new unsigned char[size * 4];
+ ::memset(pixels, 0, size * 4);
+ {
+ PROFILER_ZONE_STALL(g_profiler_ctx, "glReadPixels");
+ glReadPixels(0, 0, render_size, render_size,
+ GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, (GLvoid *)pixels);
+ }
+ //std::string path = ImageExport::FindEmptySequentialFile("Screenshots/TriVisible",".png");
+ //ImageExport::SavePNGTransparent(path.c_str(), pixels, render_size, render_size);
+ int check_size = min(size, (int)visible_gpu.size());
+ int num_tris = 0;
+ for(int pixel=0, index=0; pixel<check_size; ++pixel, index+=4){
+ //unsigned char* pixel_ptr = &pixels[index];
+ if(pixels[index+0] != 0){
+ ++num_tris;
+ int id = index/4;
+ visible_gpu[id] = true;
+ }
+ }
+ delete [] pixels;
+ LOGI << "NUM GPU TRIS: " << num_tris << std::endl;
+ }
+ glColorMask(1,1,1,1);
+ std::vector<GLuint> new_shadow_cache_indices;
+ for(size_t i=0, index=0, len=visible_gpu.size(); i<len; ++i, index+=3){
+ if(visible_gpu[i]){
+ for(size_t j=0; j<3; ++j){
+ new_shadow_cache_indices.push_back(shadow_cache_indices[index+j]);
+ }
+ }
+ }
+
+ if(false){ // Debug wireframe
+ std::vector<GLuint> line_indices;
+ for(size_t i=0, len=new_shadow_cache_indices.size(); i<len; i+=3){
+ for(size_t j=0; j<3; ++j){
+ line_indices.push_back(new_shadow_cache_indices[i+j]);
+ line_indices.push_back(new_shadow_cache_indices[i+(j+1)%3]);
+ }
+ }
+ DebugDraw::Instance()->AddLines(shadow_cache_verts, line_indices, vec4(1.0), _persistent, _DD_XRAY);
+ }
+ shadow_cache_indices = new_shadow_cache_indices;
+ }
+ PROFILER_LEAVE(g_profiler_ctx);
+ PROFILER_ENTER(g_profiler_ctx, "Finalize");
+ graphics->setDepthFunc(GL_LEQUAL);
+ graphics->PopViewport();
+ graphics->PopFramebuffer();
+
+ if(shadow_cache_indices.empty()){
+ // Add degenerate triangle if shadow cache is empty, so
+ // that we don't need a separate branch to deal with this
+ for(int i=0; i<3; ++i){
+ shadow_cache_indices.push_back(0);
+ }
+ }
+
+ shadow_cache_verts_vbo.Fill(kVBOStatic | kVBOFloat, shadow_cache_verts.size()*sizeof(GLfloat), &shadow_cache_verts[0]);
+ shadow_cache_indices_vbo.Fill(kVBOStatic | kVBOElement, shadow_cache_indices.size()*sizeof(GLuint), &shadow_cache_indices[0]);
+
+ LOGI << "Shadow cache verts: " << shadow_cache_verts.size()/3 << std::endl;
+ LOGI << "Shadow cache tris: " << shadow_cache_indices.size()/3 << std::endl;
+ PROFILER_LEAVE(g_profiler_ctx);
+ }
+
+ return true;
+}
+
+
+#define NUM_SHADOW_CASCADES 4
+
+
+struct uvec4 {
+ union {
+ unsigned int v[4];
+ struct {
+ unsigned int x, y, z, w;
+ };
+ };
+
+ uvec4() {}
+
+ uvec4(unsigned int x_, unsigned int y_, unsigned int z_, unsigned int w_)
+ : x(x_)
+ , y(y_)
+ , z(z_)
+ , w(w_)
+ {
+ }
+};
+
+
+struct ShadowCascadeParams {
+ mat4 proj_view_matrix[NUM_SHADOW_CASCADES];
+ vec4 frustum_planes[NUM_SHADOW_CASCADES][6];
+ uvec4 viewport[NUM_SHADOW_CASCADES];
+};
+
+
+void GetShadowableTris(SceneGraph* scenegraph) {
+ // For each cascade size
+ // Divide scene into tiles
+ // Draw tiles using shader that renders the triangle and object id to a texture
+ // Read the texture and add triangle/object pair to a set
+ // Create model for each tile
+
+ Graphics* graphics = Graphics::Instance();
+ Camera *camera = ActiveCameras::Get();
+
+ vec3 light_dir = scenegraph->primary_light.pos;
+ vec3 basis[3];
+ basis[2] = light_dir;
+ basis[0] = normalize(cross(vec3(0,1,0), basis[2]));
+ basis[1] = normalize(cross(basis[0], basis[2]));
+
+ vec3 camera_pos;
+
+ ShadowCascadeParams shadow_params;
+ for (int i = 0; i < NUM_SHADOW_CASCADES; ++i) {
+ float shadow_size = shadow_sizes[i];
+ float shadow_radius = shadow_size / 1.414213563731f / 2.0f; // Get radius of circle inscribed in square with sides 'shadow_size'
+ vec3 shadow_center = camera_pos;
+ vec3 light_space_center(
+ dot(shadow_center, basis[0]),
+ dot(shadow_center, basis[1]),
+ dot(shadow_center, basis[2]));
+ // Snap center to nearest texel to improve frame-to-frame coherency
+ float texel_size = shadow_size * 2.0f / graphics->shadow_res;
+ for(int j=0; j<3; ++j){
+ light_space_center[j] = floor(light_space_center[j]/texel_size)*texel_size;
+ }
+ // Update shadow center to new texel-snapped position
+ shadow_center = light_space_center[0] * basis[0] +
+ light_space_center[1] * basis[1];
+
+ GLuint res = graphics->cascade_shadow_res / 2;
+ uvec4 &viewport = shadow_params.viewport[i];
+ switch(i){
+ case 0: viewport = uvec4(0 , 0, res , res ); break;
+ case 1: viewport = uvec4(res, 0, res*2 , res ); break;
+ case 2: viewport = uvec4(0 , res, res , res*2); break;
+ case 3: viewport = uvec4(res, res, res*2 , res*2); break;
+ }
+ mat4 proj, view;
+ camera->applyShadowViewpoint(light_dir, shadow_center, shadow_size, &proj, &view);
+ mat4 proj_view_matrix = proj * view;
+ graphics->cascade_shadow_mat[i] = proj_view_matrix;
+ shadow_params.proj_view_matrix[i] = proj_view_matrix;
+ for (int j = 0; j < 6; ++j) {
+ memcpy(&shadow_params.frustum_planes[i][j], &camera->frustumPlanes[j][0], 4 * sizeof(float));
+ }
+ }
+
+ // Make room for the shadow frustum planes for each cascade
+ int num_shadow_view_frustum_planes = 6;
+ vec4 shadow_view_frustum_planes[6];
+ CHECK_GL_ERROR();
+ glColorMask(1,1,1,1);
+ graphics->drawing_shadow=true;
+ graphics->PushFramebuffer();
+ graphics->bindFramebuffer(graphics->cascade_shadow_fb);
+ graphics->PushViewport();
+ graphics->setViewport(0, 0, graphics->cascade_shadow_res, graphics->cascade_shadow_res);
+ glEnable(GL_SCISSOR_TEST);
+
+ for(int i=0; i < NUM_SHADOW_CASCADES; ++i){
+ const uvec4 &viewport = shadow_params.viewport[i];
+ graphics->setViewport(viewport.x, viewport.y, viewport.z, viewport.w);
+ graphics->Clear(true);
+ mat4 proj_view_matrix = shadow_params.proj_view_matrix[i];
+
+ if(kUseShadowCache){
+ DrawShadowCache(proj_view_matrix, "");
+ }
+ for(int i=0; i<6; ++i){
+ for(int j=0; j<4; ++j){
+ shadow_view_frustum_planes[num_shadow_view_frustum_planes-6+i][j] = camera->frustumPlanes[i][j];
+ }
+ }
+ if(i==0){
+ scenegraph->DrawDepthMap(proj_view_matrix, shadow_view_frustum_planes, num_shadow_view_frustum_planes, SceneGraph::kDepthShadow, SceneGraph::kStaticAndDynamic);
+ } else {
+ scenegraph->DrawDepthMap(proj_view_matrix, &shadow_view_frustum_planes[num_shadow_view_frustum_planes-6], 6, SceneGraph::kDepthShadow, SceneGraph::kStaticAndDynamic);
+ }
+ }
+
+ glDisable(GL_SCISSOR_TEST);
+ graphics->PopViewport();
+ graphics->PopFramebuffer();
+ graphics->drawing_shadow=false;
+ glColorMask(1,1,1,1);
+}
+
+float last_shadow_update_time = 0.0f;
+
+static void UpdateShadowCascades(SceneGraph* scenegraph) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw shadow cascades");
+ bool update_all = false;
+ if(last_shadow_update_time > game_timer.game_time - 1.0f){
+ } else {
+ update_all = true;
+ last_shadow_update_time = game_timer.game_time;
+ }
+ Graphics* graphics = Graphics::Instance();
+ Camera *camera = ActiveCameras::Get();
+ CHECK_GL_ERROR();
+ // Get orthographic basis from perspective of directional light source
+ vec3 light_dir = scenegraph->primary_light.pos;
+ vec3 basis[3];
+ basis[2] = light_dir;
+ basis[0] = normalize(cross(vec3(0,1,0), basis[2]));
+ basis[1] = normalize(cross(basis[0], basis[2]));
+ // Get player view camera information
+ camera->applyViewpoint();
+ vec3 camera_facing = camera->GetFacing();
+ vec3 camera_pos = camera->GetPos();
+ mat4 camera_proj_mat = camera->GetProjMatrix();
+ mat4 camera_view_mat = camera->GetViewMatrix();
+ mat4 inv_camera_mat = invert(camera_proj_mat * camera_view_mat);
+ vec3 camera_corner_dir[4];
+ camera_corner_dir[0] = inv_camera_mat * vec3(-1.0f, -1.0f, 1.0f);
+ camera_corner_dir[1] = inv_camera_mat * vec3( 1.0f, -1.0f, 1.0f);
+ camera_corner_dir[2] = inv_camera_mat * vec3( 1.0f, 1.0f, 1.0f);
+ camera_corner_dir[3] = inv_camera_mat * vec3(-1.0f, 1.0f, 1.0f);
+ vec4 view_frustum_planes[6];
+ for(int i=0; i<6; ++i){
+ for(int j=0; j<4; ++j){
+ view_frustum_planes[i][j] = camera->frustumPlanes[i][j];
+ }
+ }
+ int num_shadow_view_frustum_planes = 0;
+ vec4 shadow_view_frustum_planes[30];
+ // Add camera frustum panes if they are facing the direction the light is coming from
+ for(int i=0; i<6; ++i) {
+ if(dot(light_dir, view_frustum_planes[i].xyz()) >= 0.0f){
+ shadow_view_frustum_planes[num_shadow_view_frustum_planes] = view_frustum_planes[i];
+ ++num_shadow_view_frustum_planes;
+ }
+ }
+ // Get convex hull of camera frustum in light space
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Convex hull");
+ vec2 light_space_points[5];
+ //bool points_used[5] = {false};
+ for(int i=0; i<2; ++i){
+ light_space_points[0][i] = dot(camera_pos, basis[i]);
+ }
+ const float kDist = 10000.0f;
+ for(int point=0; point<4; ++point){
+ for(int i=0; i<2; ++i){
+ light_space_points[point+1][i] = dot(camera_pos + normalize(camera_corner_dir[point])*kDist, basis[i]);
+ }
+ }
+ // Get convex hull using 'string wrapping' counter-clockwise
+ int hull_points[5];
+ int num_hull_points=1;
+ // Start with leftmost point
+ float lowest_x = FLT_MAX;
+ for(int i=0; i<5; ++i){
+ if(light_space_points[i][0] < lowest_x){
+ lowest_x = light_space_points[i][0];
+ hull_points[0] = i;
+ }
+ }
+ //points_used[hull_points[0]] = true;
+ float last_angle = -PI_f*0.5f; // Start with 'string' pointing straight down
+ bool done = false;
+ while(!done){
+ float best_angle = 0.0f;
+ int last_point = hull_points[num_hull_points-1];
+ float lowest_diff = FLT_MAX;
+ int best_guess = -1;
+ for(int i=0; i<5; ++i){
+ if(i != last_point){
+ vec2 vec = light_space_points[i] - light_space_points[last_point];
+ float angle = atan2(vec[1], vec[0]);
+ while(angle < last_angle){
+ angle += PI_f * 2.0f;
+ }
+ float diff = angle - last_angle;
+ if(diff < lowest_diff){
+ best_guess = i;
+ lowest_diff = diff;
+ best_angle = angle;
+ }
+ }
+ }
+ if(best_guess == hull_points[0]){
+ done = true;
+ } else {
+ LOG_ASSERT(num_hull_points < 5);
+ hull_points[num_hull_points] = best_guess;
+ ++num_hull_points;
+ last_angle = best_angle;
+ last_point = best_guess;
+ }
+ }
+ // Extrude convex hull lines into 3D planes
+ for(int i=0; i<num_hull_points; ++i){
+ int points[2] = {hull_points[i], hull_points[(i+1)%num_hull_points]};
+ vec2 dir = light_space_points[points[1]] - light_space_points[points[0]];
+ vec2 normal(-dir[1], dir[0]);
+ vec3 world_normal = normalize(basis[0] * normal[0] + basis[1] * normal[1]);
+ vec3 pos = basis[0] * light_space_points[points[0]][0] + basis[1] * light_space_points[points[0]][1];
+ float d = -dot(world_normal, pos);
+ shadow_view_frustum_planes[num_shadow_view_frustum_planes] = vec4(world_normal, d);
+ ++num_shadow_view_frustum_planes;
+ }
+ }
+
+ ShadowCascadeParams shadow_params;
+ for (int i = 0; i < NUM_SHADOW_CASCADES; ++i) {
+ if(i!=0 && !update_all){
+ continue;
+ }
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Get shadow cascade params");
+ float shadow_size = shadow_sizes[i];
+ float shadow_radius = shadow_size / 1.414213563731f / 2.0f; // Get radius of circle inscribed in square with sides 'shadow_size'
+ vec3 shadow_center = camera_pos;
+ if(i==0){
+ shadow_center += camera_facing * shadow_radius;
+ }
+ vec3 light_space_center(dot(shadow_center, basis[0]),
+ dot(shadow_center, basis[1]),
+ dot(shadow_center, basis[2]));
+ // Snap center to nearest texel to improve frame-to-frame coherency
+ float texel_size = shadow_size * 2.0f / graphics->shadow_res;
+ for(int j=0; j<3; ++j){
+ light_space_center[j] = floor(light_space_center[j]/texel_size)*texel_size;
+ }
+ // Update shadow center to new texel-snapped position
+ shadow_center = light_space_center[0] * basis[0] +
+ light_space_center[1] * basis[1];
+
+ GLuint res = graphics->cascade_shadow_res / 2;
+ uvec4 &viewport = shadow_params.viewport[i];
+ switch(i){
+ case 0: viewport = uvec4(0 , 0, res , res ); break;
+ case 1: viewport = uvec4(res, 0, res*2 , res ); break;
+ case 2: viewport = uvec4(0 , res, res , res*2); break;
+ case 3: viewport = uvec4(res, res, res*2 , res*2); break;
+ }
+ mat4 proj, view;
+ camera->applyShadowViewpoint(light_dir, shadow_center, shadow_size, &proj, &view);
+ mat4 proj_view_matrix = proj * view;
+ graphics->cascade_shadow_mat[i] = proj_view_matrix;
+ shadow_params.proj_view_matrix[i] = proj_view_matrix;
+ for (int j = 0; j < 6; ++j) {
+ memcpy(&shadow_params.frustum_planes[i][j], &camera->frustumPlanes[j][0], 4 * sizeof(float));
+ }
+ }
+
+ // Make room for the shadow frustum planes for each cascade
+ num_shadow_view_frustum_planes += 6;
+ CHECK_GL_ERROR();
+ glColorMask(0,0,0,0);
+ graphics->drawing_shadow=true;
+ graphics->PushFramebuffer();
+ graphics->bindFramebuffer(graphics->cascade_shadow_fb);
+ graphics->PushViewport();
+ graphics->setViewport(0, 0, graphics->cascade_shadow_res, graphics->cascade_shadow_res);
+ glEnable(GL_SCISSOR_TEST);
+
+ if (g_single_pass_shadow_cascade) {
+ static GLuint shadow_cascade_ubo_id = 0;
+ if (shadow_cascade_ubo_id == 0) {
+ glGenBuffers(1, &shadow_cascade_ubo_id);
+ }
+ glBindBufferBase(GL_UNIFORM_BUFFER, UBO_SHADOW_CASCADES, shadow_cascade_ubo_id);
+ glBufferData(GL_UNIFORM_BUFFER, sizeof(ShadowCascadeParams), &shadow_params, GL_DYNAMIC_DRAW);
+
+ { // Perform per-frame, per-camera functions, like character LOD and spawning grass
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Pre-draw camera");
+ float predraw_time = game_timer.GetRenderTime();
+ for(int i=0; i<(int)scenegraph->objects_.size(); ++i){
+ Object* obj = scenegraph->objects_[i];
+ if(!obj->parent){
+ obj->PreDrawCamera(predraw_time);
+ }
+ }
+ }
+
+ {
+ const uvec4 &viewport = shadow_params.viewport[0];
+ graphics->setViewport(viewport.x, viewport.y, viewport.z, viewport.w);
+ }
+
+ int viewport_dim[4];
+ for (int i = 1; i < NUM_SHADOW_CASCADES; ++i) {
+ const uvec4 &viewport = shadow_params.viewport[i];
+ viewport_dim[0] = viewport.x;
+ viewport_dim[1] = viewport.y;
+ viewport_dim[2] = viewport.z - viewport.x;
+ viewport_dim[3] = viewport.w - viewport.y;
+ glScissorIndexed(i, viewport_dim[0], viewport_dim[1], viewport_dim[2], viewport_dim[3]);
+ glViewportIndexedf(i, (float) viewport_dim[0], (float) viewport_dim[1], (float) viewport_dim[2], (float) viewport_dim[3]);
+ }
+
+ mat4 proj_view_matrix = shadow_params.proj_view_matrix[0];
+
+ if(kUseShadowCache){
+ DrawShadowCache(proj_view_matrix, "");
+ }
+
+ scenegraph->DrawDepthMap(proj_view_matrix, NULL, 0, SceneGraph::kDepthAllShadowCascades, SceneGraph::kStaticAndDynamic);
+ } else {
+ { // Perform per-frame, per-camera functions, like character LOD and spawning grass
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Pre-draw camera");
+ float predraw_time = game_timer.GetRenderTime();
+ for(int i=0; i<(int)scenegraph->objects_.size(); ++i){
+ Object* obj = scenegraph->objects_[i];
+ if(!obj->parent){
+ obj->PreDrawCamera(predraw_time);
+ }
+ }
+ }
+ for(int i=0; i < NUM_SHADOW_CASCADES; ++i){
+ if(i!=0 && !update_all){
+ continue;
+ }
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw shadow cascade");
+ const uvec4 &viewport = shadow_params.viewport[i];
+ graphics->setViewport(viewport.x, viewport.y, viewport.z, viewport.w);
+ graphics->Clear(true);
+ mat4 proj_view_matrix = shadow_params.proj_view_matrix[i];
+
+ if(kUseShadowCache){
+ DrawShadowCache(proj_view_matrix, "");
+ }
+ int cascade = i;
+ for(int i=0; i<6; ++i){
+ for(int j=0; j<4; ++j){
+ shadow_view_frustum_planes[num_shadow_view_frustum_planes-6+i][j] = shadow_params.frustum_planes[cascade][i][j];
+ }
+ }
+ if(i==0) {
+ scenegraph->DrawDepthMap(proj_view_matrix, shadow_view_frustum_planes, num_shadow_view_frustum_planes, SceneGraph::kDepthShadow, SceneGraph::kStaticAndDynamic);
+ } else { // Don't apply camera frustum to larger cascades unless we're updating it every frame
+ scenegraph->DrawDepthMap(proj_view_matrix, &shadow_view_frustum_planes[num_shadow_view_frustum_planes-6], 6, SceneGraph::kDepthShadow, SceneGraph::kStaticOnly);
+ }
+ }
+ }
+
+ glDisable(GL_SCISSOR_TEST);
+ graphics->PopViewport();
+ graphics->PopFramebuffer();
+ graphics->drawing_shadow=false;
+ glColorMask(1,1,1,1);
+}
+
+void ReadAverageColorsFromCubemap(vec3* avg_color, TextureRef cubemap) {
+ Graphics* graphics = Graphics::Instance();
+ for(int i=0; i<6; ++i){
+ static GLuint cube_map_read_fbo = UINT_MAX;
+ graphics->PushFramebuffer();
+ if(cube_map_read_fbo == UINT_MAX){
+ graphics->genFramebuffers(&cube_map_read_fbo,"cube_map_read_fbo");
+ }
+ graphics->bindFramebuffer(cube_map_read_fbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, Textures::Instance()->returnTexture(cubemap), 5);
+ GLfloat pixels[4*4*3];
+ glReadBuffer(GL_COLOR_ATTACHMENT0);
+ {
+ PROFILER_ZONE_STALL(g_profiler_ctx, "Read pixels")
+ glReadPixels(0,0,4,4,GL_RGB,GL_FLOAT,(GLfloat*)pixels);
+ }
+ if(ISNAN(pixels[0])){
+ LOGE << "Light probe read fail" << std::endl;
+ LOGE << "cube_map_read_fbo: " << cube_map_read_fbo << std::endl;
+ LOGE << "scenegraph_->light_probe_collection.cube_map.id: " << cubemap.id << std::endl;
+ LOGE << "pixels[0]: " << pixels[0] << std::endl;
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, Textures::Instance()->returnTexture(cubemap), 5);
+ glReadBuffer(GL_COLOR_ATTACHMENT0);
+ {
+ PROFILER_ZONE_STALL(g_profiler_ctx, "Read pixels")
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, Textures::Instance()->returnTexture(cubemap), 4);
+ glReadPixels(0,0,4,4,GL_RGB,GL_FLOAT,(GLfloat*)pixels);
+ }
+ LOGE << "pixels[0]: " << pixels[0] << std::endl;
+ }
+ LOG_ASSERT(!ISNAN(pixels[0]));
+ // Get average of centermost pixels
+ avg_color[i] = vec3(0.0f);
+ for(int j=0, index=0; j<16; ++j, index+=3){
+ if(j/4>0 && j/4<3 && j%4>0 && j%4<3){
+ avg_color[i] += vec3(pixels[index+0], pixels[index+1], pixels[index+2]) / 4.0f;
+ }
+ }
+ graphics->PopFramebuffer();
+ }
+}
+
+extern int num_detail_object_draw_calls;
+
+void Engine::NewLevel() {
+ Path p = FindFilePath(new_empty_level_path,kAnyPath);
+ Engine::Instance()->QueueState(EngineState("",kEngineEditorLevelState, p));
+}
+
+bool Engine::GetSplitScreen() const
+{
+ return forced_split_screen_mode == kForcedModeSplit || (forced_split_screen_mode == kForcedModeNone && Graphics::Instance()->config_.split_screen());
+}
+
+static const int kCollisionNormalVersion = 2;
+static const char* kCollisionNormalIdentifier = "overgrowth_level_collision_normals";
+
+void SaveCollisionNormals(const SceneGraph* scenegraph) {
+ std::string path = scenegraph->level_path_.GetOriginalPathStr()+".col_norm";
+ if(scenegraph->level_path_.source != kAbsPath) {
+ if(!config["allow_game_dir_save"].toBool()){
+ path = GetWritePath(scenegraph->level_path_.mod_source)+path;
+ }
+ }
+ std::string temp_path = GetWritePath(CoreGameModID).c_str();
+ temp_path += "temp_collision_normals";
+ FILE *file = my_fopen(temp_path.c_str(), "wb");
+ if(file){
+ fwrite(kCollisionNormalIdentifier, strlen(kCollisionNormalIdentifier), 1, file);
+ fwrite(&kCollisionNormalVersion, sizeof(int), 1, file);
+ int num_objects = 0;
+ for(size_t i=0, len=scenegraph->visible_static_meshes_.size(); i<len; ++i){
+ EnvObject* eo = scenegraph->visible_static_meshes_[i];
+ if(eo->GetCollisionModelID() != -1){
+ ++num_objects;
+ }
+ }
+ fwrite(&num_objects, sizeof(int), 1, file);
+ for(size_t i=0, len=scenegraph->visible_static_meshes_.size(); i<len; ++i){
+ EnvObject* eo = scenegraph->visible_static_meshes_[i];
+ if(eo->GetCollisionModelID() != -1){
+ int id = eo->GetID();
+ fwrite(&id, sizeof(int), 1, file);
+ int num_normals = (int)eo->normal_override_custom.size();
+ fwrite(&num_normals, sizeof(int), 1, file);
+ if(num_normals > 0){
+ fwrite(&eo->normal_override_custom[0], sizeof(vec4) * num_normals, 1, file);
+ }
+ int num_ledge_lines = (int)eo->ledge_lines.size();
+ fwrite(&num_ledge_lines, sizeof(int), 1, file);
+ if(num_ledge_lines > 0){
+ fwrite(&eo->ledge_lines[0], sizeof(int) * num_ledge_lines, 1, file);
+ }
+ }
+ }
+ fclose(file);
+
+ Zip(temp_path, temp_path+".zip", "data", _YES_OVERWRITE);
+ //void UnZip(const std::string &zip_file_path, ExpandedZipFile &expanded_zip_file);
+
+ std::string src = temp_path+".zip";
+ std::string dst = path+".zip";
+ if(movefile(src.c_str(), dst.c_str())){
+ LOGI << "Creating parent dirs before trying to move file again" << std::endl;
+ CreateParentDirs(dst);
+ if(movefile(src.c_str(), dst.c_str())){
+ #ifndef NO_ERR
+ DisplayError("Error",
+ (std::string("Problem moving file \"") + strerror(errno) + "\". From " +
+ src +
+ " to " + dst).c_str());
+ #endif
+ }
+ }
+ }
+}
+
+static int ParseCollisionNormalsFile(SceneGraph* scenegraph, const std::vector<char>& file){
+ PROFILER_ZONE(g_profiler_ctx, "ParseCollisionNormalsFile");
+ char header_buf[64] = {'\0'};
+ int index = 0;
+ memread(header_buf, strlen(kCollisionNormalIdentifier), 1, file, index);
+ if(strcmp(header_buf, kCollisionNormalIdentifier) != 0){
+ const int kBufSize = 512;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "Collision normal header '%s' does not match expected '%s'", header_buf, kCollisionNormalIdentifier);
+ DisplayError("Error", buf);
+ return -1;
+ }
+ int version;
+ memread(&version, sizeof(int), 1, file, index);
+ if(version <= 0 || version > kCollisionNormalVersion){
+ const int kBufSize = 512;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "Collision normal version '%d' does not match expected '%d'", version, kCollisionNormalVersion);
+ DisplayError("Error", buf);
+ return -2;
+ }
+ int num_objects;
+ memread(&num_objects, sizeof(int), 1, file, index);
+ std::vector<vec4> normal_buf;
+ std::vector<int> ledge_line_buf;
+ for(int i=0; i<num_objects; ++i){
+ int id;
+ memread(&id, sizeof(int), 1, file, index);
+ Object* obj = scenegraph->GetObjectFromID(id);
+ int num_normals;
+ memread(&num_normals, sizeof(int), 1, file, index);
+ normal_buf.resize(num_normals);
+ if(num_normals > 0){
+ memread(&normal_buf[0], sizeof(vec4) * num_normals, 1, file, index);
+ }
+ if(version >= 2){
+ int num_ledge_lines;
+ memread(&num_ledge_lines, sizeof(int), 1, file, index);
+ if(num_ledge_lines > 0){
+ ledge_line_buf.resize(num_ledge_lines);
+ memread(&ledge_line_buf[0], sizeof(int) * num_ledge_lines, 1, file, index);
+ }
+ }
+ if(obj && obj->GetType() == _env_object){
+ EnvObject* eo = (EnvObject*)obj;
+ eo->normal_override_custom = normal_buf;
+ eo->normal_override_buffer_dirty = true;
+ if(version >= 2){
+ eo->ledge_lines = ledge_line_buf;
+ }
+ }
+ }
+ return 0;
+}
+
+void LoadCollisionNormals(SceneGraph* scenegraph) {
+ PROFILER_ZONE(g_profiler_ctx, "LoadCollisionNormals");
+ std::string path = scenegraph->level_path_.GetOriginalPathStr()+".col_norm.zip";
+ if(FileExists(path, kAnyPath)){
+ char temp_path[kPathSize];
+ FindFilePath(path.c_str(), temp_path, kPathSize, kAnyPath);
+ ExpandedZipFile expanded_zip_file;
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Unzipping file");
+ UnZip(temp_path, expanded_zip_file);
+ }
+ std::vector<char> buffer;
+ const char* data;
+ const char* filename;
+ unsigned size;
+ expanded_zip_file.GetEntry(0, filename, data, size);
+ buffer.resize(size);
+ memcpy(&buffer[0], data, size);
+ ParseCollisionNormalsFile(scenegraph, buffer);
+ }
+}
+
+//Draw the current frame
+void Engine::Draw() {
+ Graphics* graphics = Graphics::Instance();
+ Keyboard &keyboard = Input::Instance()->getKeyboard();
+
+ ++draw_frame;
+ num_detail_object_draw_calls = 0;
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw frame %d", draw_frame);
+
+ if(user_paused){
+ const int kBufSize = 512;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "Time is currently paused, press %s to unpause", config["bind[pause]"].str().c_str());
+ gui.AddDebugText("paused", buf, 0.0f);
+ }
+
+ if(slow_motion){
+ const int kBufSize = 512;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "Time is currently slowed down, press %s to speed back up", config["key[slow]"].str().c_str());
+ gui.AddDebugText("slow", buf, 0.0f);
+ }
+
+ DrawImGui(graphics, scenegraph_, &gui, GetAssetManager(), Engine::Instance(), cursor.visible);
+
+ if(check_save_level_changes_dialog_is_showing || !scenegraph_){
+ // No level loaded, so draw menu instead
+ graphics->setViewport(0, 0, graphics->window_dims[0], graphics->window_dims[1]);
+ glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
+ graphics->Clear(true);
+ if(scriptable_menu_){
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw scriptable menu", draw_frame);
+ scriptable_menu_->Draw();
+ }
+ RenderImGui();
+ ImGui_ImplSdlGL3_RenderDrawLists(ImGui::GetDrawData());
+ if(!WantMouseImGui()){
+ cursor.Draw();
+ }
+
+ // Limit FPS in menus, even if limit_fps_in_game is not set to true (and thus timing code in GameMain isn't being called)
+ if(!graphics->config_.vSync() && !graphics->config_.limit_fps_in_game()){
+ PROFILER_ZONE_IDLE(g_profiler_ctx, "SDL_Sleep");
+ if(!g_draw_vr){
+ static uint32_t last_time = 0;
+
+ int max_frame_rate = graphics->config_.max_frame_rate();
+ if(max_frame_rate < 15){
+ max_frame_rate = 15;
+ }
+ if(max_frame_rate > 500){
+ max_frame_rate = 500;
+ }
+
+ uint32_t ticks_to_wait = (uint32_t)(1000.0f / max_frame_rate);
+ uint32_t diff = ticks_to_wait - (SDL_TS_GetTicks() - last_time) - 1;
+ if(diff > ticks_to_wait - 1){
+ diff = ticks_to_wait - 1;
+ }
+
+ SDL_Delay(diff);
+ last_time = SDL_TS_GetTicks();
+ }
+ }
+ } else {
+ game_timer.ReportFrameForFPSCount();
+ PushGPUProfileRange(__FUNCTION__);
+
+ // Get number of screens that we need
+ const GraphicsConfig &graphics_config = graphics->config_;
+ scenegraph_->GetPlayerCharacterIDs(&num_avatars, avatar_ids, kMaxAvatars);
+ unsigned num_screens = clamp(num_avatars, 1, 4);
+ if(!Engine::Instance()->GetSplitScreen() || scenegraph_->map_editor->state_ != MapEditor::kInGame){
+ num_screens = 1;
+ }
+ bool old_simple_water = g_simple_water;
+ if(num_screens > 1){
+ g_simple_water = true;
+ }
+
+ { // Perform per-frame calculations (like character shadows or LOD)
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Pre-draw frame");
+ float predraw_time = game_timer.GetRenderTime();
+ for(int i=0; i<(int)scenegraph_->objects_.size(); ++i){
+ Object* obj = scenegraph_->objects_[i];
+ if(!obj->parent){
+ obj->PreDrawFrame(predraw_time);
+ }
+ }
+ }
+
+ {
+ if(scenegraph_->reflection_data_loaded == false ) {
+ scenegraph_->LoadReflectionCaptureCubemaps();
+ }
+
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Updating reflection capture cubemaps");
+ std::vector<TextureRef> textures;
+ for(size_t i=0, len=scenegraph_->objects_.size(); i<len; ++i){
+ Object* obj = scenegraph_->objects_[i];
+ ReflectionCaptureObject* reflection_obj = (ReflectionCaptureObject*)obj;
+ if(obj->GetType() == _reflection_capture_object){
+ if(reflection_obj->cube_map_ref.valid()){
+ if(reflection_obj->GetScriptParams()->ASGetInt("Global") == 1){
+ scenegraph_->sky->SetSpecularCubeMapTexture(reflection_obj->cube_map_ref);
+ }
+ }
+ }
+ }
+ textures.push_back(scenegraph_->sky->GetSpecularCubeMapTexture());
+ scenegraph_->ref_cap_matrix.clear();
+ scenegraph_->ref_cap_matrix_inverse.clear();
+ for(size_t i=0, len=scenegraph_->objects_.size(); i<len; ++i){
+ Object* obj = scenegraph_->objects_[i];
+ ReflectionCaptureObject* reflection_obj = (ReflectionCaptureObject*)obj;
+ if(obj->GetType() == _reflection_capture_object){
+ if(reflection_obj->cube_map_ref.valid()){
+ if(reflection_obj->GetScriptParams()->ASGetInt("Global") != 1){
+ textures.push_back(reflection_obj->cube_map_ref);
+ const mat4 &refmat = obj->GetTransform();
+ scenegraph_->ref_cap_matrix.push_back(refmat);
+ scenegraph_->ref_cap_matrix_inverse.push_back(invert(refmat));
+ }
+ }
+ }
+ }
+
+ if(g_no_reflection_capture == false)
+ {
+ if(textures.size() > 6){
+ textures.resize(6);
+ if(scenegraph_->ref_cap_matrix.size() > 6){
+ scenegraph_->ref_cap_matrix.resize(6);
+ scenegraph_->ref_cap_matrix_inverse.resize(6);
+ }
+ LOGI << "Clamping number of cubemaps to 6" << std::endl;
+ }
+
+ bool different = false;
+ if(textures.size() != scenegraph_->sub_cubemaps.size()){
+ different = true;
+ } else {
+ for(size_t i=0, len=textures.size(); i<len; ++i){
+ if(textures[i] != scenegraph_->sub_cubemaps[i]) {
+ different = true;
+ }
+ }
+ }
+ if(different || scenegraph_->cubemaps_need_refresh){
+ scenegraph_->cubemaps_need_refresh = false;
+ {
+ LOGI << "Creating cubemaps texture" << std::endl;
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Create cubemaps texture");
+ //cubemaps = Textures::Instance()->makeCubemapArrayTexture(2, 128, 128, GL_RGBA16F, GL_RGBA, 0);
+ Textures::Instance()->setFilters(GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR);
+ Textures::Instance()->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE);
+ scenegraph_->cubemaps = Textures::Instance()->makeFlatCubemapArrayTexture(6, 128, 128, GL_RGBA16F, GL_RGBA, 0);
+ Textures::Instance()->SetTextureName(scenegraph_->cubemaps, "Reflection Capture Cubemap Array");
+ Textures::Instance()->DeleteUnusedTextures();
+ }
+
+ Graphics* graphics = Graphics::Instance();
+ graphics->PushFramebuffer();
+ static bool initialized_framebuffers = false;
+ static GLuint framebuffers[2] = {0,0};
+ if(initialized_framebuffers == false){
+ Graphics::Instance()->genFramebuffers(&framebuffers[0], "cubemap_1");
+ Graphics::Instance()->genFramebuffers(&framebuffers[1], "cubemap_2");
+ initialized_framebuffers = true;
+ }
+ glColorMask(true, true, true, true);
+ graphics->setViewport(0, 0, 128*6, 128);
+ for(size_t tex_index=0, num_tex = textures.size(); tex_index<num_tex; ++tex_index){
+ TextureRef tex = textures[tex_index];
+ Textures::Instance()->EnsureInVRAM(tex);
+ CHECK_GL_ERROR();
+ int width = 128, height = 128, level = 0;
+ while(width >= 1 && height >= 1){
+ for(int i=0; i<6; ++i){
+ glBindFramebuffer(GL_FRAMEBUFFER, framebuffers[0]);
+ CHECK_GL_ERROR();
+ int tex_id = Textures::Instance()->returnTexture(tex);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, tex_id, level);
+ CHECK_GL_ERROR();
+ glBindFramebuffer(GL_FRAMEBUFFER, framebuffers[1]);
+ CHECK_GL_ERROR();
+ tex_id = Textures::Instance()->returnTexture(scenegraph_->cubemaps);
+ glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex_id, level, tex_index);
+ CHECK_GL_ERROR();
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffers[0]);
+ CHECK_GL_ERROR();
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffers[1]);
+ CHECK_GL_ERROR();
+ glDisable(GL_SCISSOR_TEST);
+ glBlitFramebuffer(0, 0, width, height, width*i, 0, width*i+width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
+ CHECK_GL_ERROR();
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+ CHECK_GL_ERROR();
+ }
+ ++level;
+ width/=2;
+ height/=2;
+ }
+ }
+ graphics->PopFramebuffer();
+ scenegraph_->sub_cubemaps = textures;
+ }
+ } else {
+ scenegraph_->cubemaps.clear();
+ scenegraph_->cubemaps_need_refresh = true;
+ }
+ }
+
+ if(shadow_cache_dirty){
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Update shadow cache");
+ if(UpdateShadowCache(scenegraph_))
+ {
+ shadow_cache_dirty = false;
+ }
+ shadow_cache_dirty_sun_moved = false; // Always cleared. All the objects get their internal dirty flags set, so iterative processing can handle just a few per frame
+ shadow_cache_dirty_level_loaded = false;
+ }
+
+ if(!g_simple_shadows && g_level_shadows) {
+ UpdateShadowCascades(scenegraph_);
+ }
+
+ // Updated dynamic character cubemap if that is enabled
+ if(num_avatars != 0) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Update dynamic cubemap");
+ MovementObject* player_obj = (MovementObject*)scenegraph_->GetObjectFromID(avatar_ids[0]);
+ if(graphics_config.dynamic_character_cubemap_ ){
+ if(player_obj){
+ bool temp_visible = player_obj->visible;
+ player_obj->visible = false;
+ bool media_mode = graphics->media_mode();
+ graphics->SetMediaMode(true);
+ static TextureRef cube_map;
+ static GLuint cube_map_fbo;
+ if(!cube_map.valid()){
+ CHECK_GL_ERROR();
+ cube_map = Textures::Instance()->makeCubemapTexture(128, 128, GL_RGBA16F, GL_RGBA, Textures::MIPMAPS);
+ Textures::Instance()->SetTextureName(cube_map, "Dynamic Character Cubemap");
+ graphics->genFramebuffers(&cube_map_fbo,"cube_map_fbo");
+ CHECK_GL_ERROR();
+ }
+ player_obj->rigged_object()->cube_map = cube_map;
+ DrawCubeMap(cube_map, player_obj->position, cube_map_fbo, SceneGraph::kStaticAndDynamic);
+ CubemapMipChain(cube_map, Cubemap::SPHERE, NULL);
+ graphics->SetMediaMode(media_mode);
+ player_obj->visible = temp_visible;
+ }
+ } else {
+ player_obj->rigged_object()->cube_map.clear();
+ }
+ }
+
+ if(kLightProbeEnabled && !scenegraph_->light_probe_collection.to_process.empty()){
+ LightProbeUpdateEntry entry = scenegraph_->light_probe_collection.to_process.front();
+ scenegraph_->light_probe_collection.to_process.pop();
+ LightProbe* probe = scenegraph_->light_probe_collection.GetProbeFromID(entry.id);
+
+ if(probe){
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Render light probe cubemap")
+ bool media_mode = graphics->media_mode();
+ graphics->SetMediaMode(true);
+ // Increase light intensity to make shadows pure black
+ float temp_intensity = scenegraph_->primary_light.intensity;
+ if(entry.pass == 0){
+ scenegraph_->primary_light.intensity = 3.0f;
+ } else {
+ scenegraph_->primary_light.intensity = 1.5f;
+ }
+ // Draw scene from light probe perspective
+ DrawCubeMap(scenegraph_->light_probe_collection.cube_map, probe->pos,
+ scenegraph_->light_probe_collection.cube_map_fbo, SceneGraph::kStaticOnly);
+ // Blur cubemap to represent different levels of roughness
+ CubemapMipChain(scenegraph_->light_probe_collection.cube_map, Cubemap::SPHERE, NULL);
+ // Reset the light intensity
+ scenegraph_->primary_light.intensity = temp_intensity;
+ graphics->SetMediaMode(media_mode);
+
+ // Read pixels from roughest level of cubemap to get ambient cube
+ vec3 avg_color[6];
+ ReadAverageColorsFromCubemap(avg_color, scenegraph_->light_probe_collection.cube_map);
+ for(int i=0; i<6; ++i){
+ if(entry.pass == 0){
+ probe->ambient_cube_color[i] = avg_color[i];
+ } else if(entry.pass == 1){
+ probe->ambient_cube_color_buf[i] = avg_color[i];
+ }
+ }
+ }
+ // Finalize second bounce light probes
+ if (kLightProbe2pass && scenegraph_->light_probe_collection.to_process.empty()){
+ for(size_t i=0, len=scenegraph_->light_probe_collection.light_probes.size(); i<len; ++i){
+ for(int j=0; j<6; ++j){
+ scenegraph_->light_probe_collection.light_probes[i].ambient_cube_color[j] =
+ scenegraph_->light_probe_collection.light_probes[i].ambient_cube_color_buf[j];
+ }
+ }
+ kLightProbe2pass = false;
+ }
+
+ scenegraph_->light_probe_collection.UpdateTextureBuffer(*scenegraph_->bullet_world_);
+ }
+
+ // Clear background if we have more than one screen, since the screens might not
+ // completely cover everything
+ if(num_screens > 1 || graphics->render_output_dims[0] != graphics->window_dims[0] || graphics->render_output_dims[1] != graphics->window_dims[1]){
+ graphics->startDraw(vec2(0.0f, 0.0f), vec2(1.0f, 1.0f), Graphics::kWindow);
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f );
+ graphics->Clear(true);
+ }
+
+ // Draw voxel spans
+ static const bool kDrawSpans = false;
+ if(kDrawSpans){
+ for(int span_index=0, len=(int)g_voxel_field.spans.size(); span_index<len; ++span_index){
+ std::list<VoxelSpan>& span_list = g_voxel_field.spans[span_index];
+ for(std::list<VoxelSpan>::iterator iter = span_list.begin();
+ iter != span_list.end();
+ ++iter)
+ {
+ VoxelSpan span = *iter;
+
+ int voxel_x = span_index / g_voxel_field.voxel_field_dims[2];
+ int voxel_z = span_index % g_voxel_field.voxel_field_dims[2];
+ vec3 pos = vec3(
+ g_voxel_field.voxel_field_bounds[0][0] + (voxel_x+0.5f)*g_voxel_field.voxel_size,
+ (span.height[0] + span.height[1])*0.5f * g_voxel_field.voxel_size,
+ g_voxel_field.voxel_field_bounds[0][2] + (voxel_z+0.5f)*g_voxel_field.voxel_size);
+ vec3 box_size = vec3(
+ g_voxel_field.voxel_size*0.5f,
+ (span.height[0] - span.height[1])*0.5f * g_voxel_field.voxel_size,
+ g_voxel_field.voxel_size*0.5f);
+ DebugDraw::Instance()->AddWireBox(pos, box_size, vec4(1.0f), _delete_on_draw);
+ }
+ }
+ }
+
+ if(scenegraph_->light_probe_collection.light_volume_enabled)
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Update light volume objects");
+ LightVolumeObject* lvo = NULL;
+ for(size_t i=0, len=scenegraph_->light_volume_objects_.size(); i<len; ++i){
+ lvo = scenegraph_->light_volume_objects_[i];
+ }
+
+ if(lvo && lvo->dirty && !IsBeingMoved(scenegraph_->map_editor, lvo)){
+ int dims[3] = {600, 100, 100};
+ float* pixels = (float*)alloc.stack.Alloc(dims[0] * dims[1] * dims[2] * 4 * sizeof(float));
+ for(int x=0; x<dims[0]/6; ++x){
+ for(int y=0; y<dims[1]; ++y){
+ for(int z=0; z<dims[2]; ++z){
+ vec3 pos = vec3(x/((float)dims[0] / 6.0f), y/(float)dims[1], z/(float)dims[2]);
+ pos -= vec3(0.5f);
+ pos *= 2.0f;
+ pos = lvo->GetTransform() * pos;
+ vec3 color[6];
+ int tet = scenegraph_->light_probe_collection.GetTetrahedron(pos, color, -1);
+ for(int i=0; i<6; ++i){
+ vec3 avg;
+ float opac = 1.0f;
+ if(tet != -1){
+ avg = color[i];
+ } else {
+ avg = vec3(0.0f,0.0f,0.0f);
+ opac = 0.0f;
+ }
+ //avg = pos * 0.01f;
+ int index = (z * (dims[0] * dims[1]) + y * (dims[0]) + x + i * dims[0]/6) * 4;
+ pixels[index + 0] = avg[2];
+ pixels[index + 1] = avg[1];
+ pixels[index + 2] = avg[0];
+ pixels[index + 3] = opac;
+ }
+ }
+ }
+ }
+ scenegraph_->light_probe_collection.ambient_3d_tex = Textures::Instance()->make3DTexture(dims[0], dims[1], dims[2], GL_RGBA16F, GL_BGRA, false, &(pixels[0]));
+ Textures::Instance()->SetTextureName(scenegraph_->light_probe_collection.ambient_3d_tex, "Ambient Light Volume");
+ alloc.stack.Free(pixels);
+ Textures::Instance()->DeleteUnusedTextures();
+ lvo->dirty = false;
+ }
+ }
+ {
+ if( g_no_reflection_capture == false ) {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Update reflection capture objects");
+ for(size_t i=0, len=scenegraph_->objects_.size(); i<len; ++i){
+ Object* obj = scenegraph_->objects_[i];
+ ReflectionCaptureObject* reflection_obj = (ReflectionCaptureObject*)obj;
+ if(obj->GetType() == _reflection_capture_object && reflection_obj->dirty){
+ bool media_mode = graphics->media_mode();
+ graphics->SetMediaMode(true);
+ // Draw scene from light probe perspective
+ if(!reflection_obj->cube_map_ref.valid()){
+ reflection_obj->cube_map_ref = Textures::Instance()->makeCubemapTexture(128, 128, GL_RGBA16F, GL_RGBA, Textures::MIPMAPS);
+ Textures::Instance()->SetTextureName(reflection_obj->cube_map_ref, "Reflection Capture Cubemap");
+ }
+ DrawCubeMap(reflection_obj->cube_map_ref, scenegraph_->objects_[i]->GetTranslation(),
+ scenegraph_->light_probe_collection.cube_map_fbo, SceneGraph::kStaticOnly);
+ // Blur cubemap to represent different levels of roughness
+ CubemapMipChain(reflection_obj->cube_map_ref, Cubemap::SPHERE, NULL);
+ graphics->SetMediaMode(media_mode);
+ ReadAverageColorsFromCubemap(reflection_obj->avg_color, reflection_obj->cube_map_ref);
+ ((ReflectionCaptureObject*)(scenegraph_->objects_[i]))->dirty = false;
+ scenegraph_->cubemaps_need_refresh = true;
+ }
+ }
+ }
+ }
+
+ CHECK_GL_ERROR();
+ // Iterate through screens and render them
+ for(unsigned i=0; i<num_screens; ++i){
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw screen");
+ PushGPUProfileRange("Draw Screen");
+ SetViewportForCamera(i, num_screens, Graphics::kRender);
+ ActiveCameras::Set(i);
+ ActiveCameras::Get()->SetAutoCamera(config["auto_camera"].toNumber<bool>());
+ if(level_loaded_) { // Draw the in-game scene if level is loaded
+ CHECK_GL_ERROR();
+ DrawScene(kScreen, Engine::kFinal, SceneGraph::kStaticAndDynamic);
+ }
+ PopGPUProfileRange();
+ }
+ CHECK_GL_ERROR();
+ // Draw GUI layer
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw GUI screen");
+ PushGPUProfileRange("Draw GUI Screen");
+ CHECK_GL_ERROR();
+ graphics->setViewport(0, 0, graphics->window_dims[0], graphics->window_dims[1]);
+ CHECK_GL_ERROR();
+ {
+ scenegraph_->level->Draw();
+ CHECK_GL_ERROR();
+ }
+ if(!graphics->media_mode()){
+ CHECK_GL_ERROR();
+ { // Draw alert string
+ if(scenegraph_->map_editor->state_ != MapEditor::kInGame){
+ if(scenegraph_->level_path_.isValid()){
+ gui.AddDebugText("_level",scenegraph_->level_path_.GetOriginalPath(),0.5);
+ }
+ if(graphics->nav_mesh_out_of_date){
+ gui.AddDebugText("_nav_update","Nav mesh needs update",0.5);
+ }
+ }
+ }
+ }
+#ifdef SLIM_TIMING
+ slimTimingEvents->drawVisualization( g_text_atlas_renderer, font_renderer, g_text_atlas[kTextAtlasMono] );
+#endif //SLIM_TIMING
+ if(config["fps_label"].toNumber<bool>() && !graphics->media_mode() || Online::Instance()->IsActive()) {
+ static float fps_updated_time = 0.0f;
+ if(fps_updated_time < ui_timer.game_time - 1.0f) {
+ float current = game_timer.GetFrameTime();
+ float fastest = game_timer.GetFastestFrameTime();
+ float slowest = game_timer.GetSlowestFrameTime();
+
+ int current_fps = (int) (current == 0.0f ? 0 : (1000.0f / current));
+ int fastest_fps = (int) (fastest < 0.00001 ? 0 : (1000.0f / fastest));
+ int slowest_fps = (int) (slowest < 0.00001 ? 0 : (1000.0f / slowest));
+
+ FormatString(fps_label_str, kFPSLabelMaxLen, "FPS: [%d, %d] current: %d", slowest_fps, fastest_fps, current_fps);
+ FormatString(frame_time_label_str, kFPSLabelMaxLen, "Frame time: [%.3f, %.3f] ms current: %.3f ms", fastest, slowest, current);
+ fps_updated_time = ui_timer.game_time;
+ }
+ gui.AddDebugText("_framerate",fps_label_str,0.5);
+ gui.AddDebugText("_frametime",frame_time_label_str,0.5);
+ }
+
+ // Make sure glActiveTexture is synced up -- not sure why this should be necessary but it seems to be
+ // Otherwise character shadows break in game mode
+ Textures::Instance()->InvalidateBindCache();
+
+ if(kDrawHistogram){
+ DrawHistogram();
+ }
+
+ PopGPUProfileRange();
+
+ }
+
+ // Draw camera preview
+ if(level_loaded_) { // Draw the in-game scene if level is loaded
+ DrawImGuiCameraPreview(this,scenegraph_, graphics);
+ }
+
+ if(!graphics->media_mode()){
+ graphics->setViewport(0, 0, graphics->window_dims[0], graphics->window_dims[1]);
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Render Dear ImGui screen");
+ RenderImGui();
+ ImGui_ImplSdlGL3_RenderDrawLists(ImGui::GetDrawData());
+ }
+ if(!WantMouseImGui()){
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw cursor");
+ cursor.Draw();
+ }
+ }
+
+ const float fade_ticks = 300.0f;
+ if(level_has_screenshot && (first_level_drawn == UINT_MAX || SDL_TS_GetTicks() < first_level_drawn + fade_ticks)) {
+ CHECK_GL_ERROR();
+
+ if(first_level_drawn == UINT_MAX && !waiting_for_input_){
+ first_level_drawn = SDL_TS_GetTicks();
+ }
+
+ // no coloring
+ Graphics* graphics = Graphics::Instance();
+ Shaders* shaders = Shaders::Instance();
+
+ int shader_id = shaders->returnProgram("simple_2d #TEXTURE #FLIPPED #LEVEL_SCREENSHOT");
+ shaders->createProgram(shader_id);
+ int shader_attrib_vert_coord = shaders->returnShaderAttrib("vert_coord", shader_id);
+ int shader_attrib_tex_coord = shaders->returnShaderAttrib("tex_coord", shader_id);
+ int uniform_mvp_mat = shaders->returnShaderVariable("mvp_mat", shader_id);
+ int uniform_color = shaders->returnShaderVariable("color", shader_id);
+ shaders->setProgram(shader_id);
+ CHECK_GL_ERROR();
+
+ GLState gl_state;
+ gl_state.blend = true;
+ gl_state.cull_face = false;
+ gl_state.depth_write = false;
+ gl_state.depth_test = false;
+ graphics->setGLState(gl_state);
+ CHECK_GL_ERROR();
+
+
+ glm::mat4 proj_mat;
+ proj_mat = glm::ortho(0.0f, (float)graphics->window_dims[0], 0.0f, (float)graphics->window_dims[1]);
+ glm::mat4 modelview_mat(1.0f);
+ vec2 where;
+ vec2 size(Textures::Instance()->getWidth(level_screenshot->GetTextureRef()),
+ Textures::Instance()->getHeight(level_screenshot->GetTextureRef()));
+ size /= size[1];
+ size *= (float) graphics->window_dims[1];
+ where = vec2(graphics->window_dims[0]*0.5f, graphics->window_dims[1]*0.5f);
+ modelview_mat = glm::translate(modelview_mat, glm::vec3(where[0]-size[0]*0.5f,where[1]-size[1]*0.5f,0));
+ modelview_mat = glm::scale(modelview_mat, glm::vec3(size[0],size[1],1.0f));
+ modelview_mat = glm::translate(modelview_mat, glm::vec3(0.5f,0.5f,0.5f));
+ modelview_mat = glm::translate(modelview_mat, glm::vec3(-0.5f,-0.5f,-0.5f));
+ glm::mat4 mvp_mat = proj_mat * modelview_mat;
+ CHECK_GL_ERROR();
+
+ graphics->EnableVertexAttribArray(shader_attrib_vert_coord);
+ CHECK_GL_ERROR();
+ graphics->EnableVertexAttribArray(shader_attrib_tex_coord);
+ CHECK_GL_ERROR();
+ static const GLfloat verts[] = {
+ 0, 0, 0, 0,
+ 1, 0, 1, 0,
+ 1, 1, 1, 1,
+ 0, 1, 0, 1
+ };
+ static const GLuint indices[] = {
+ 0, 1, 2,
+ 0, 2, 3
+ };
+ static VBOContainer vert_vbo;
+ static VBOContainer index_vbo;
+ static bool vbo_filled = false;
+ if(!vbo_filled) {
+ vert_vbo.Fill(kVBOFloat | kVBOStatic, sizeof(verts), (void*)verts);
+ index_vbo.Fill(kVBOElement | kVBOStatic, sizeof(indices), (void*)indices);
+ vbo_filled = true;
+ }
+ vert_vbo.Bind();
+ index_vbo.Bind();
+
+ glVertexAttribPointer(shader_attrib_vert_coord, 2, GL_FLOAT, false, 4*sizeof(GLfloat), 0);
+ CHECK_GL_ERROR();
+ glVertexAttribPointer(shader_attrib_tex_coord, 2, GL_FLOAT, false, 4*sizeof(GLfloat), (void*)(sizeof(GL_FLOAT)*2));
+ CHECK_GL_ERROR();
+
+ int num_indices = 6;
+
+ glUniformMatrix4fv(uniform_mvp_mat, 1, false, (GLfloat*)&mvp_mat);
+ CHECK_GL_ERROR();
+ vec4 color(1.0f);
+ if(!waiting_for_input_){
+ color[3] = 1.0f - (SDL_TS_GetTicks()-first_level_drawn) / fade_ticks;
+ }
+ glUniform4fv(uniform_color, 1, &color[0]);
+ CHECK_GL_ERROR();
+
+ Textures::Instance()->bindTexture(level_screenshot, 0);
+ CHECK_GL_ERROR();
+ graphics->DrawElements(GL_TRIANGLES, num_indices, GL_UNSIGNED_INT, 0);
+ CHECK_GL_ERROR();
+
+ graphics->BindElementVBO(0);
+ graphics->BindArrayVBO(0);
+ graphics->ResetVertexAttribArrays();
+ CHECK_GL_ERROR();
+
+ if(waiting_for_input_) { // Draw fun text
+ // Draw loading screen tip
+ int font_size = int(max(18, min(Graphics::Instance()->window_dims[1] / 30, Graphics::Instance()->window_dims[0] / 50)));
+ TextMetrics metrics = g_as_text_context.ASGetTextAtlasMetrics( font_path, font_size, 0, load_screen_tip);
+ int pos[] = {graphics->window_dims[0] / 2 - metrics.bounds[2] / 2, graphics->window_dims[1] / 2 - font_size / 2};
+ g_as_text_context.ASDrawTextAtlas(font_path, font_size, 0, load_screen_tip, pos[0], pos[1], vec4(1.0f));
+
+ // Add spacing
+ pos[1] += metrics.bounds[3] + font_size;
+
+ // Draw button prompt text
+ float pulse_while_waiting_for_input = powf(sinf((SDL_TS_GetTicks() - finished_loading_time) / (0.5f * 1000.0f)), 2.0f) / 2.0f + 0.5f;
+ std::string button_prompt_text = "Press any key to continue";
+ if (Online::Instance()->IsActive()) {
+ if (Online::Instance()->IsHosting()) {
+ if (Online::Instance()->GetPlayerCount() > 0) {
+ if (Online::Instance()->AllClientsReady()) {
+ button_prompt_text = "All clients are ready. \nPress any key to continue";
+ } else {
+ button_prompt_text = "Waiting for all clients to load";
+ }
+ } else {
+ button_prompt_text = "Press any key to continue";
+ }
+ } else {
+ button_prompt_text = "Waiting for the host to start the game";
+ }
+ }
+
+ if (load_screen_tip[0] == '\0') {
+ TextMetrics prompt_metrics = g_as_text_context.ASGetTextAtlasMetrics( font_path, font_size, 0, button_prompt_text);
+ pos[0] = graphics->window_dims[0] / 2 - prompt_metrics.bounds[2] / 2;
+ }
+ g_as_text_context.ASDrawTextAtlas(font_path, font_size, 0, button_prompt_text, pos[0], pos[1], vec4(vec3(pulse_while_waiting_for_input), 1.0f));
+
+ Textures::Instance()->InvalidateBindCache();
+ }
+ }
+
+ //scenegraph_->decals->PostDraw();
+ ActiveCameras::Set(0);
+
+ PopGPUProfileRange();
+ g_simple_water = old_simple_water;
+ }
+
+ DecalTextures::Instance()->Draw();
+
+ ActiveCameras::Instance()->UpdatePrevViewMats();
+ prev_view_time = game_timer.game_time + game_timer.GetInterpWeight() / 120.0f;
+
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Error check");
+ if( printed_rendering_error_message == false ) {
+ if( CheckGLError(0,"", "General error in OpenGL rendering context. Please report with logfile to bugs@wolfire.com") ) {
+ printed_rendering_error_message = true;
+ }
+ } else {
+ char err[512];
+ if( CheckGLErrorStr(err,512) ) {
+ LOGI << "OpenGL Error: " << err << std::endl;
+ }
+ }
+ }
+
+ #ifdef OpenVR
+ if ( m_pHMD )
+ {
+ vr::TrackedDevicePose_t m_rTrackedDevicePose[ vr::k_unMaxTrackedDeviceCount ];
+ vr::EVRCompositorError err = vr::VRCompositor()->WaitGetPoses(m_rTrackedDevicePose, vr::k_unMaxTrackedDeviceCount, NULL, 0 );
+ if(err != vr::VRCompositorError_None){
+ LOGE << "VR WaitGetPoses error: " << err << std::endl;
+ }
+ vr::TrackedDevicePose_t* hmd_pose = &m_rTrackedDevicePose[vr::k_unTrackedDeviceIndex_Hmd];
+ if(vr::VRCompositor()->CanRenderScene() && hmd_pose->bPoseIsValid){
+ uint32_t dims[2];
+
+ m_pHMD->GetRecommendedRenderTargetSize( &dims[0], &dims[1] );
+ static TextureRef resolve_tex = Textures::Instance()->makeRectangularTexture(dims[0],dims[1],GL_RGBA,GL_RGBA);
+ {
+ Graphics::Instance()->PushFramebuffer();
+ graphics->bindFramebuffer(graphics->post_effects.post_framebuffer);
+ graphics->framebufferColorTexture2D(graphics->post_effects.temp_screen_tex);
+ graphics->bindFramebuffer(0);
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, graphics->post_effects.post_framebuffer);
+ GLint w = graphics->render_dims[0];
+ GLint h = graphics->render_dims[1];
+ glBlitFramebuffer(0, 0, Graphics::Instance()->window_dims[0], Graphics::Instance()->window_dims[1],
+ 0, 0, w, h,
+ GL_COLOR_BUFFER_BIT, GL_NEAREST);
+ Graphics::Instance()->PopFramebuffer();
+ }
+ //RenderControllerAxes();
+ //RenderStereoTargets();
+ //RenderCompanionWindow();
+
+ for(int eye=0; eye<2; ++eye){
+ vr::EVREye vr_eye = eye ? vr::Eye_Left : vr::Eye_Right;
+ vr::HmdMatrix44_t vr_proj_mat = m_pHMD->GetProjectionMatrix( vr_eye, 0.2f, 20000.0f );
+ vr::HmdMatrix34_t vr_eye_mat = m_pHMD->GetEyeToHeadTransform( vr_eye );
+ vr::HmdMatrix34_t vr_head_mat = hmd_pose->mDeviceToAbsoluteTracking;
+
+ mat4 proj_mat (vr_proj_mat.m[0][0], vr_proj_mat.m[1][0], vr_proj_mat.m[2][0], vr_proj_mat.m[3][0],
+ vr_proj_mat.m[0][1], vr_proj_mat.m[1][1], vr_proj_mat.m[2][1], vr_proj_mat.m[3][1],
+ vr_proj_mat.m[0][2], vr_proj_mat.m[1][2], vr_proj_mat.m[2][2], vr_proj_mat.m[3][2],
+ vr_proj_mat.m[0][3], vr_proj_mat.m[1][3], vr_proj_mat.m[2][3], vr_proj_mat.m[3][3]);
+
+ mat4 eye_mat (vr_eye_mat.m[0][0], vr_eye_mat.m[1][0], vr_eye_mat.m[2][0], 0.0,
+ vr_eye_mat.m[0][1], vr_eye_mat.m[1][1], vr_eye_mat.m[2][1], 0.0,
+ vr_eye_mat.m[0][2], vr_eye_mat.m[1][2], vr_eye_mat.m[2][2], 0.0,
+ vr_eye_mat.m[0][3], vr_eye_mat.m[1][3], vr_eye_mat.m[2][3], 1.0f);
+
+ mat4 head_mat (vr_head_mat.m[0][0], vr_head_mat.m[1][0], vr_head_mat.m[2][0], 0.0,
+ vr_head_mat.m[0][1], vr_head_mat.m[1][1], vr_head_mat.m[2][1], 0.0,
+ vr_head_mat.m[0][2], vr_head_mat.m[1][2], vr_head_mat.m[2][2], 0.0,
+ vr_head_mat.m[0][3], vr_head_mat.m[1][3], vr_head_mat.m[2][3], 1.0f);
+
+ const bool kDrawImaxDisplay = true;
+ if(kDrawImaxDisplay)
+ {
+ Graphics::Instance()->PushFramebuffer();
+ graphics->bindFramebuffer(graphics->post_effects.post_framebuffer);
+ graphics->framebufferColorTexture2D(resolve_tex);
+ graphics->setViewport(0,0,dims[0],dims[1]);
+ graphics->Clear(true);
+
+ Shaders* shaders = Shaders::Instance();
+ int shader_id = Shaders::Instance()->returnProgram("simple_tex_3d #VR_DISPLAY");
+ shaders->setProgram(shader_id);
+ shaders->SetUniformVec4("color", vec4(1.0f));
+ int vert_coord_id = shaders->returnShaderAttrib("vert_coord", shader_id);
+ int tex_coord_id = shaders->returnShaderAttrib("tex_coord", shader_id);
+ graphics->BindArrayVBO(0);
+ CHECK_GL_ERROR();
+
+ GLState temp_gl_state;
+ temp_gl_state.blend = false;
+ temp_gl_state.cull_face = false;
+ temp_gl_state.depth_test = false;
+ temp_gl_state.depth_write = false;
+ graphics->setGLState(temp_gl_state);
+ CHECK_GL_ERROR();
+
+ float aspect = graphics->render_dims[0] / (float)graphics->render_dims[1];
+
+ mat4 translate_screen;
+ mat4 scale;
+ mat4 translate_offset;
+
+ translate_screen.SetTranslation(vec3(0,0,-10.0f));
+ scale.SetScale(vec3(10.0f*aspect, 10.0f, 10.0f));
+ translate_offset.SetTranslation(vec3(-0.5f,-0.5f, 0.0f));
+
+ mat4 model = translate_screen * scale * translate_offset;
+
+ mat4 m_mvp = proj_mat * invert(head_mat) * invert(eye_mat) * model;
+ shaders->SetUniformMat4("mvp_mat", m_mvp);
+ Textures::Instance()->bindTexture(graphics->post_effects.temp_screen_tex);
+
+ graphics->EnableVertexAttribArray(vert_coord_id);
+ graphics->EnableVertexAttribArray(tex_coord_id);
+ quad_vert_vbo.Bind();
+ quad_index_vbo.Bind();
+ glVertexAttribPointer(vert_coord_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0);
+ glVertexAttribPointer(tex_coord_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0);
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ graphics->ResetVertexAttribArrays();
+ CHECK_GL_ERROR();
+ DebugDraw::Instance()->Draw();
+ Graphics::Instance()->PopFramebuffer();
+ }
+
+ vr::Texture_t vr_tex;
+ vr_tex.eType = vr::TextureType_OpenGL;
+ vr_tex.eColorSpace = vr::ColorSpace_Auto;
+ vr_tex.handle = (void*)Textures::Instance()->returnTexture(resolve_tex);
+
+ vr::IVRCompositor* compositor = vr::VRCompositor();
+ err = vr::VRCompositor()->Submit(vr_eye, &vr_tex );
+ if(err != vr::VRCompositorError_None){
+ LOGE << "VR Submit error: " << err << std::endl;
+ }
+ glFlush();
+ }
+ glFlush();
+ }
+ }
+ #endif
+
+ #ifdef OculusVR
+ if(g_oculus_vr_activated){
+ bool old_g_simple_water = g_simple_water;
+ g_simple_water = true;
+ PROFILER_ZONE(g_profiler_ctx, "VR");
+ Graphics::Instance()->PushFramebuffer();
+ ovrSessionStatus sessionStatus;
+ ovr_GetSessionStatus(session, &sessionStatus);
+ if (sessionStatus.ShouldQuit)
+ {
+ Input::Instance()->RequestQuit();
+ }
+ if (sessionStatus.ShouldRecenter)
+ ovr_RecenterTrackingOrigin(session);
+
+
+ if (sessionStatus.IsVisible)
+ {
+ PROFILER_ENTER(g_profiler_ctx, "VR Setup");
+ {
+ graphics->bindFramebuffer(graphics->post_effects.post_framebuffer);
+ graphics->framebufferColorTexture2D(Graphics::Instance()->post_effects.temp_screen_tex);
+ graphics->bindFramebuffer(0);
+ Graphics::Instance()->PushFramebuffer();
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, graphics->post_effects.post_framebuffer);
+ GLint w = graphics->render_dims[0];
+ GLint h = graphics->render_dims[1];
+ glBlitFramebuffer(0, 0, Graphics::Instance()->window_dims[0], Graphics::Instance()->window_dims[1],
+ 0, 0, w, h,
+ GL_COLOR_BUFFER_BIT, GL_NEAREST);
+ Graphics::Instance()->PopFramebuffer();
+ }
+
+ // Call ovr_GetRenderDesc each frame to get the ovrEyeRenderDesc, as the returned values (e.g. HmdToEyeOffset) may change at runtime.
+ ovrEyeRenderDesc eyeRenderDesc[2];
+ eyeRenderDesc[0] = ovr_GetRenderDesc(session, ovrEye_Left, hmdDesc.DefaultEyeFov[0]);
+ eyeRenderDesc[1] = ovr_GetRenderDesc(session, ovrEye_Right, hmdDesc.DefaultEyeFov[1]);
+
+ // Get eye poses, feeding in correct IPD offset
+ ovrPosef EyeRenderPose[2];
+ ovrVector3f HmdToEyeOffset[2] = { eyeRenderDesc[0].HmdToEyeOffset,
+ eyeRenderDesc[1].HmdToEyeOffset };
+
+ double sensorSampleTime; // sensorSampleTime is fed into the layer later
+ ovr_GetEyePoses(session, frameIndex, ovrTrue, HmdToEyeOffset, EyeRenderPose, &sensorSampleTime);
+
+ PROFILER_LEAVE(g_profiler_ctx); // VR SETUP
+
+ ovrInputState touchState;
+ ovr_GetInputState(session, ovrControllerType_Active, &touchState);
+ ovrTrackingState trackingState = ovr_GetTrackingState(session, 0.0, false);
+
+ static std::vector<int> debug_draw_vr;
+ for(int i=0, len=debug_draw_vr.size(); i<len; ++i){
+ DebugDraw::Instance()->Remove(debug_draw_vr[i]);
+ }
+ debug_draw_vr.clear();
+
+ {
+ const int kBufSize = 512;
+ char key[kBufSize], val[kBufSize];
+ FormatString(val, kBufSize, "touchState.Buttons: %u", touchState.Buttons);
+ gui.AddDebugText("Buttons", val, 0.5f);
+ FormatString(val, kBufSize, "touchState.Touches: %u", touchState.Touches);
+ gui.AddDebugText("Touches", val, 0.5f);
+ FormatString(val, kBufSize, "touchState.ControllerType: %#010x", (unsigned)touchState.ControllerType);
+ gui.AddDebugText("ControllerType", val, 0.5f);
+
+ for(int hand=0; hand<2; ++hand){
+ if(trackingState.HandStatusFlags[hand] & ovrStatus_PositionTracked){
+ vec3 pos = *(vec3*)&trackingState.HandPoses[hand].ThePose.Position;
+ FormatString(key, kBufSize, "handpos%d", hand);
+ FormatString(val, kBufSize, "trackingState.HandPoses[%d].ThePose.Position: %f %f %f", hand, pos[0], pos[1], pos[2]);
+ quaternion quat = *(quaternion*)&trackingState.HandPoses[hand].ThePose.Orientation;
+
+ mat4 scale;
+ scale.SetUniformScale(0.05f);
+ mat4 transform = Mat4FromQuaternion(quat) * scale;
+ transform.SetTranslationPart(ActiveCameras::Get()->GetPos() + pos);
+ debug_draw_vr.push_back(
+ DebugDraw::Instance()->AddTransformedWireScaledSphere(transform,
+ vec4(1.0f),
+ _fade));
+ vec3 points[2];
+ points[0] = transform * vec3(0,0,0);
+ points[1] = transform * vec3(0,0,0) + (transform * vec4(0,0,-100000,0)).xyz();
+ debug_draw_vr.push_back(
+ DebugDraw::Instance()->AddLine(points[0], points[1],
+ vec4(1.0f),
+ _fade));
+
+ float dist = -10.0;
+ if(points[1][2] < dist){
+ float t = (dist - points[0][2]) / (points[1][2] - points[0][2]);
+ vec3 pos = points[0] * (1.0-t) + points[1] * t;
+ vec2 pixel ((pos[0])*graphics->window_dims[1]*0.1f+graphics->window_dims[0]*0.5f,
+ (5.0f-pos[1])*graphics->window_dims[1]*0.1f);
+ if(pixel[0] > 0.0f && pixel[1] > 0.0f &&
+ pixel[0] <= graphics->window_dims[0] &&
+ pixel[1] <= graphics->window_dims[1])
+ {
+ debug_draw_vr.push_back(
+ DebugDraw::Instance()->AddWireSphere(pos,
+ 0.1,
+ vec4(1.0f, 0.0f, 0.0f, 1.0f),
+ _fade));
+ SDL_WarpMouseInWindow(Graphics::Instance()->sdl_window_, pixel[0], pixel[1]);
+ if(touchState.IndexTrigger[hand] > 0.5f){
+ SDL_MouseButtonEvent event;
+ event.type = SDL_MOUSEBUTTONDOWN;
+ event.button = 1;
+ event.clicks = 1;
+ queued_events.push(*(SDL_Event*)&event);
+ } else {
+ SDL_MouseButtonEvent event;
+ event.type = SDL_MOUSEBUTTONUP;
+ event.button = 1;
+ queued_events.push(*(SDL_Event*)&event);
+ }
+ }
+ }
+ }
+
+ }
+ }
+
+
+ // Render Scene to Eye Buffers
+ for (int eye = 0; eye < 2; ++eye)
+ {
+ PROFILER_ZONE(g_profiler_ctx, "VR draw eye screen");
+ glDisable(GL_SCISSOR_TEST);
+ // Switch to eye render target
+ graphics->setDepthTest(true);
+ graphics->setDepthWrite(true);
+ glEnable(GL_DEPTH_TEST);
+ eyeRenderTexture[eye]->SetAndClearRenderSurface(eyeDepthBuffer[eye]);
+ float Yaw = 0.0f;
+
+ // Get view and projection matrices
+ OVR::Matrix4f rollPitchYaw = OVR::Matrix4f::RotationY(Yaw);
+ OVR::Matrix4f finalRollPitchYaw = rollPitchYaw * OVR::Matrix4f(EyeRenderPose[eye].Orientation);
+ OVR::Vector3f finalUp = finalRollPitchYaw.Transform(OVR::Vector3f(0, 1, 0));
+ OVR::Vector3f finalForward = finalRollPitchYaw.Transform(OVR::Vector3f(0, 0, -1));
+ OVR::Vector3f shiftedEyePos = rollPitchYaw.Transform(*(OVR::Vector3f*)&((*(vec3*)&EyeRenderPose[eye].Position) + ActiveCameras::Get()->GetPos()));
+
+
+ OVR::Matrix4f view = OVR::Matrix4f::LookAtRH(shiftedEyePos, shiftedEyePos + finalForward, finalUp);
+ OVR::Matrix4f proj = ovrMatrix4f_Projection(hmdDesc.DefaultEyeFov[eye], 0.2f, 20000.0f, ovrProjection_None);
+
+ const bool kDrawImaxDisplay = (!scenegraph_);
+ if(kDrawImaxDisplay)
+ {
+ Shaders* shaders = Shaders::Instance();
+ int shader_id = Shaders::Instance()->returnProgram("simple_tex_3d #VR_DISPLAY");
+ shaders->setProgram(shader_id);
+ shaders->SetUniformVec4("color", vec4(1.0f));
+ int vert_coord_id = shaders->returnShaderAttrib("vert_coord", shader_id);
+ int tex_coord_id = shaders->returnShaderAttrib("tex_coord", shader_id);
+ graphics->BindArrayVBO(0);
+ CHECK_GL_ERROR();
+
+ GLState temp_gl_state;
+ temp_gl_state.blend = false;
+ temp_gl_state.cull_face = false;
+ temp_gl_state.depth_test = true;
+ temp_gl_state.depth_write = true;
+ graphics->setGLState(temp_gl_state);
+ CHECK_GL_ERROR();
+
+ float aspect = graphics->render_dims[0] / (float)graphics->render_dims[1];
+ OVR::Matrix4f model = OVR::Matrix4f::Translation(0,0,-10) * OVR::Matrix4f::Scaling(10.0f*aspect, 10.0f, 10.0f) * OVR::Matrix4f::Translation(-0.5,-0.5,0);
+
+ mat4 m_proj, m_view, m_model;
+ memcpy(&m_proj,& proj, sizeof(mat4));
+ memcpy(&m_view, &view, sizeof(mat4));
+ memcpy(&m_model, &model, sizeof(mat4));
+ mat4 m_mvp = transpose(m_proj) * transpose(m_view) * transpose(m_model);
+ shaders->SetUniformMat4("mvp_mat", m_mvp);
+ Textures::Instance()->bindTexture(graphics->post_effects.temp_screen_tex);
+
+ graphics->EnableVertexAttribArray(vert_coord_id);
+ graphics->EnableVertexAttribArray(tex_coord_id);
+ quad_vert_vbo.Bind();
+ quad_index_vbo.Bind();
+ glVertexAttribPointer(vert_coord_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0);
+ glVertexAttribPointer(tex_coord_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0);
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ graphics->ResetVertexAttribArrays();
+ CHECK_GL_ERROR();
+
+ memcpy(&m_proj, &proj, sizeof(mat4));
+ memcpy(&m_view, &view, sizeof(mat4));
+ m_view = transpose(m_view);
+ m_proj = transpose(m_proj);
+ ActiveCameras::Get()->SetMatrices((float*)&m_view, (float*)&m_proj);
+ DebugDraw::Instance()->Draw();
+ }
+
+ const bool kDrawVRScene = (scenegraph_);
+ if(kDrawVRScene){
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw screen");
+ PushGPUProfileRange("Draw Screen");
+ if(level_loaded_) { // Draw the in-game scene if level is loaded
+ mat4 m_proj, m_view, m_model;
+ memcpy(&m_proj, &proj, sizeof(mat4));
+ memcpy(&m_view, &view, sizeof(mat4));
+ m_view = transpose(m_view);
+ m_proj = transpose(m_proj);
+ auto cam = ActiveCameras::Get();
+ vec3 cam_pos;
+ memcpy(&cam_pos, &shiftedEyePos, sizeof(cam_pos));
+ auto old_interp = cam->interp_pos;
+ cam->interp_pos = cam_pos;
+ cam->SetMatrices((float*)&m_view, (float*)&m_proj);
+
+ graphics->setDepthFunc(GL_LEQUAL);
+ graphics->setDepthTest(true);
+ graphics->setDepthWrite(true);
+
+ cam->calcFrustumPlanes(m_proj, m_view);
+
+ if(!g_no_decals){
+ scenegraph_->PrepareLightsAndDecals(vec2(0, 0), vec2(1, 1), vec2(eyeRenderTexture[eye]->texSize.w, eyeRenderTexture[eye]->texSize.h));
+ }
+ scenegraph_->DrawDepthMap(m_proj * m_view,
+ (const vec4*)ActiveCameras::Get()->frustumPlanes,
+ 6,
+ SceneGraph::kDepthPrePass,
+ SceneGraph::kStaticAndDynamic);
+ scenegraph_->Draw(SceneGraph::kStaticAndDynamic);
+ DebugDraw::Instance()->Draw();
+ cam->interp_pos = old_interp;
+ }
+ PopGPUProfileRange();
+ }
+
+ // Avoids an error when calling SetAndClearRenderSurface during next iteration.
+ // Without this, during the next while loop iteration SetAndClearRenderSurface
+ // would bind a framebuffer with an invalid COLOR_ATTACHMENT0 because the texture ID
+ // associated with COLOR_ATTACHMENT0 had been unlocked by calling wglDXUnlockObjectsNV.
+ eyeRenderTexture[eye]->UnsetRenderSurface();
+
+ // Commit changes to the textures so they get picked up frame
+ eyeRenderTexture[eye]->Commit();
+ }
+
+ // Do distortion rendering, Present and flush/sync
+
+ ovrLayerEyeFov ld;
+ ld.Header.Type = ovrLayerType_EyeFov;
+ ld.Header.Flags = ovrLayerFlag_TextureOriginAtBottomLeft; // Because OpenGL.
+
+ for (int eye = 0; eye < 2; ++eye)
+ {
+ ld.ColorTexture[eye] = eyeRenderTexture[eye]->TextureChain;
+ ld.Viewport[eye] = OVR::Recti(eyeRenderTexture[eye]->GetSize());
+ ld.Fov[eye] = hmdDesc.DefaultEyeFov[eye];
+ ld.RenderPose[eye] = EyeRenderPose[eye];
+ ld.SensorSampleTime = sensorSampleTime;
+ }
+
+ ovrLayerHeader* layers = &ld.Header;
+ {
+ PROFILER_ZONE(g_profiler_ctx, "VR submit frame");
+ ovrResult result = ovr_SubmitFrame(session, frameIndex, NULL, &layers, 1);
+ // exit the rendering loop if submit returns an error, will retry on ovrError_DisplayLost
+ if (!OVR_SUCCESS(result)) {
+ ovrErrorInfo errorInfo;
+ ovr_GetLastErrorInfo(&errorInfo);
+ FatalError("Error", "ovr_SubmitFrame failed: %s", errorInfo.ErrorString);
+ }
+ }
+
+ frameIndex++;
+ }
+
+ // Blit mirror texture to back buffer
+ const bool kMirror = false;
+ if(kMirror){
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, mirrorFBO);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+ GLint w = Graphics::Instance()->window_dims[0];
+ GLint h = Graphics::Instance()->window_dims[1];
+ glBlitFramebuffer(0, h, w, 0,
+ 0, 0, w, h,
+ GL_COLOR_BUFFER_BIT, GL_NEAREST);
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
+ }
+ Graphics::Instance()->PopFramebuffer();
+
+ g_simple_water = old_g_simple_water;
+ }
+ #endif // OculusVR
+}
+
+void Engine::LoadConfigFile() {
+ static const int kBufSize = 512;
+ char defaults_path[kBufSize];
+ if(FindFilePath("Data/defaults.txt", defaults_path, kBufSize, kDataPaths | kModPaths) == -1){
+ FatalError("Error", "Working directory is probably not set correctly -- could not find defaults.txt in Data folder");
+ }
+ default_config.Load(defaults_path);
+
+ std::string config_path = GetConfigPath();
+ if(!config.Load(config_path)){
+ // If no config file, then load defaults file, and create config file from that
+ if(!config.Load(defaults_path)){
+ std::string abs_path = AbsPathFromRel("/Data");
+ FatalError("Error", "Working directory is probably not set correctly -- could not find config.txt or defaults.txt in Data folder: %s", abs_path.c_str());
+ }
+ config.Save(config_path);
+ } else {
+ // If config file is found, then fill in missing values with the defaults
+ Config old_config = config;
+ config.Load(defaults_path, true);
+ if(old_config != config){
+ config.Save(config_path);
+ }
+ }
+
+ config.GetRef("menu_player_config") = 0;
+
+ Graphics::Instance()->SetFromConfig(config);
+ Input::Instance()->SetFromConfig(config);
+ //Textures::Instance()->SetProcessPoolsEnabled(config["background_process_pool"].toNumber<bool>());
+ ActiveCameras::Get()->SetAutoCamera(config["auto_camera"].toNumber<bool>());
+ DebugDrawAux::Instance()->visible_sound_spheres = config["visible_sound_spheres"].toNumber<bool>();
+ g_albedo_only = config["albedo_only"].toNumber<bool>();
+ g_disable_fog = config["disable_fog"].toNumber<int>();
+ g_no_reflection_capture = config["no_reflection_capture"].toNumber<bool>();
+ g_no_decals = config["no_decals"].toNumber<bool>();
+ g_no_decal_elements = config["no_decal_elements"].toNumber<bool>();
+ g_no_detailmaps = config["no_detailmaps"].toNumber<bool>();
+ g_single_pass_shadow_cascade = config["single_pass_shadow_cascade"].toNumber<bool>();
+ sound.SetMusicVolume(config["music_volume"].toNumber<float>());
+ sound.SetMasterVolume(config["master_volume"].toNumber<float>());
+ std::string extra_data_path = config["extra_data_path"].str();
+
+ //Only care about data path if it's specified.
+ if( extra_data_path.length() > 0 )
+ {
+ if( CheckFileAccess(extra_data_path.c_str()) )
+ {
+ AddPath(extra_data_path.c_str(), kDataPaths);
+ }
+ else
+ {
+ std::stringstream ss;
+ ss << "Could not find: " << extra_data_path << std::endl << " Check extra_data_path in config file";
+ DisplayError("Warning", ss.str().c_str(), _ok, false);
+ }
+ }
+ else
+ {
+ LOGE << "No extra_data_path was specified." << std::endl;
+ }
+
+ Shaders::Instance()->shader_dir_path = "Data/GLSL/";
+ script_dir_path = "Data/Scripts/";
+ std::string mod_path = config["mod_path0"].str();
+ if( !mod_path.empty()){
+ if(CheckFileAccess(mod_path.c_str()) ){
+ AddPath(mod_path.c_str(), kModPaths);
+ } else {
+ const int kBufSize = 512;
+ char err[kBufSize];
+ FormatString(err, kBufSize, "Could not open path: %s\n", mod_path.c_str());
+ DisplayError("Error", err);
+ }
+ }
+}
+
+void Engine::PreloadAssets(const Path &level_path) {
+ //Release currently hold preloaded from previous level so they may get retagged.
+ asset_manager.ReleaseAssetHoldLoad(HOLD_LOAD_MASK_PRELOAD);
+
+ std::vector<LevelAssetPreloadParser::Asset> &preload_files = AssetPreload::Instance().GetPreloadFiles();
+ LOGI << "Starting Preloading for: " << level_path << std::endl;
+ for( unsigned i = 0; i < preload_files.size(); i++ ) {
+ if( preload_files[i].all_levels || preload_files[i].level_name == level_path.GetOriginalPathStr() ) {
+ //LOGI << "Preloading: " << preload_files[i].path << " " << preload_files[i].asset_type << " " << preload_files[i].load_flags << std::endl;
+ Path p = FindFilePath( preload_files[i].path, kAnyPath, false );
+ if( p.isValid() ) {
+ GetAssetManager()->LoadSync(preload_files[i].asset_type, preload_files[i].path, preload_files[i].load_flags, HOLD_LOAD_MASK_PRELOAD);
+ }
+ }
+ }
+}
+
+void Engine::LoadLevelData(const Path& level_path) {
+ LOGI << "Load Level: \"" << level_path << "\"" << std::endl;
+#if THREADED
+ PROFILER_NAME_THREAD(g_profiler_ctx, "Level loading thread");
+ NameCurrentThread("Level loading thread");
+#endif
+ PROFILER_ZONE(g_profiler_ctx, "Loading level");
+
+ LOG_ASSERT(scenegraph_->bullet_world_ == NULL);
+ scenegraph_->bullet_world_ = new BulletWorld();
+ scenegraph_->bullet_world_->Init();
+ scenegraph_->bullet_world_->SetGravity(Physics::Instance()->gravity);
+ scenegraph_->bullet_world_->CreatePlane(vec3(0.0f,1.0f,0.0f),-100.0f);
+
+ scenegraph_->abstract_bullet_world_ = new BulletWorld();
+ scenegraph_->abstract_bullet_world_->Init();
+
+ scenegraph_->plant_bullet_world_ = new BulletWorld();
+ scenegraph_->plant_bullet_world_->Init();
+
+ //sound.AttachBulletWorld(scenegraph_->bullet_world_);
+
+ AddLoadingText("Loading level...");
+
+ if( LevelLoader::LoadLevel(level_path, *scenegraph_) == false ) {
+ loading_mutex_.lock();
+ finished_loading_time = (float) SDL_TS_GetTicks();
+ loading_in_progress_ = false;
+ waiting_for_input_ = false;
+ loading_mutex_.unlock();
+ return;
+ }
+
+ AddLoadingText("Preloading assets...");
+
+ PreloadAssets(level_path);
+
+ //Clear all unused assets, after loading new level.
+ while( asset_manager.UnloadUnreferenced(0,0) ){}
+
+ loading_mutex_.lock();
+
+ LoadCollisionNormals(scenegraph_);
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "UpdateControlledModule");
+ avatar_control_manager.Update();
+ }
+
+ finished_loading_time = (float) SDL_TS_GetTicks();
+ loading_in_progress_ = false;
+ waiting_for_input_ = true;
+ scenegraph_->map_editor->QueueSaveHistoryState();
+
+ loading_mutex_.unlock();
+}
+
+
+void Engine::ClearLoadedLevel() {
+ //We want music in menues now, levels have to clear their own sounds.
+ sound.ClearTransient();
+ sound.UpdateGameTimestep(game_timer.timestep);
+ sound.UpdateGameTimescale(1.0f);
+ sound.Update();
+ if(scenegraph_){
+ scenegraph_->Dispose();
+ delete scenegraph_;
+ scenegraph_ = NULL;
+ }
+ Models::Instance()->Dispose();
+ Online::Instance()->ClearIDTranslations();
+
+ asset_manager.Update();
+ asset_manager.DumpLoadWarningData("asset_manager_warnings.xml");
+ ActiveCameras::Get()->SetCameraObject(NULL);
+ level_loaded_ = false;
+ DebugDraw::Instance()->Dispose();
+
+ if( config["full_level_unload"].toBool() ) {
+ LOGI << "Unloading all unreferenced assets after clearing level [full_level_unload: true]..." << std::endl;
+ size_t pre_count = asset_manager.GetLoadedAssetCount();
+ size_t tex_pre_count = asset_manager.GetAssetTypeCount(TEXTURE_ASSET);
+ while( asset_manager.UnloadUnreferenced(0,0) ){}
+ Textures::Instance()->DeleteUnusedTextures();
+ LOGI << (pre_count - asset_manager.GetLoadedAssetCount()) << " Assets were unloaded" << std::endl;
+ LOGI << (tex_pre_count - asset_manager.GetAssetTypeCount(TEXTURE_ASSET)) << " Texture assets were unloaded" << std::endl;
+ } else {
+ LOGE << "Skipping post-clear level unload (Increases peak memory usage)[full_level_unload: false]" << std::endl;
+ }
+}
+
+void Engine::LoadLevel(Path queued_level) {
+ GetAssetManager()->SetLoadWarningMode(false,"","");
+ Shaders::Instance()->level_path = queued_level;
+ Shaders::Instance()->create_program_warning = false;
+
+ PROFILER_ZONE(g_profiler_ctx, "Loading queued level");
+ PROFILER_NAME_TIMELINE(g_profiler_ctx, "Loading level");
+
+ Path level_path = queued_level;
+
+ latest_level_path_ = level_path;
+
+ std::string difficulty = config.GetClosestDifficulty();
+
+ if( difficulty == "Casual" ) {
+ loading_screen_og_logo = loading_screen_og_logo_casual;
+ } else if( difficulty == "Hardcore" ) {
+ loading_screen_og_logo = loading_screen_og_logo_hardcore;
+ } else if( difficulty == "Expert" ) {
+ loading_screen_og_logo = loading_screen_og_logo_expert;
+ }
+
+
+ if( level_path.isValid() ) {
+ LevelInfoAssetRef lia = Engine::Instance()->GetAssetManager()->LoadSync<LevelInfoAsset>(level_path.GetOriginalPath());
+ if( lia.valid() ) {
+ Path shortest_path = FindShortestPath2(level_path.GetFullPath());
+ const char* shortest_path_orig_folder = shortest_path.GetOriginalPath();
+
+ if( beginswith(shortest_path_orig_folder, "Data/Levels/") ) {
+ shortest_path_orig_folder = &shortest_path_orig_folder[strlen("Data/Levels/")];
+ }
+
+ level_has_screenshot = false;
+ level_screenshot.clear();
+ char screenshot_path[kPathSize];
+
+ if( lia->GetLoadingScreenImage().empty() == false ) {
+ strscpy(screenshot_path, lia->GetLoadingScreenImage().c_str(), kPathSize);
+ } else {
+ LOGI << "No loading screen image, loading normal." << std::endl;
+
+ FormatString(screenshot_path, kPathSize, "Data/LevelLoading/%s_loading.jpg", shortest_path_orig_folder);
+ }
+
+ if(FileExists(screenshot_path, kModPaths | kDataPaths)){
+ level_screenshot = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(screenshot_path, PX_NOCONVERT | PX_NOREDUCE | PX_NOMIPMAP, 0x0);
+ } else if (Online::Instance()->IsActive()) {
+ // Multiplayer needs an image for the "waiting for all clients" dialogue!
+ strscpy(screenshot_path, "Data/Images/full_fallback.png", kPathSize);
+ if(FileExists(screenshot_path, kModPaths | kDataPaths)) {
+ level_screenshot = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(screenshot_path, PX_NOCONVERT | PX_NOREDUCE | PX_NOMIPMAP, 0x0);
+ } else {
+ level_screenshot = loading_screen_logo; // Won't render correctly! Still better than nothing!
+ }
+ }
+
+ if( level_screenshot.valid() ) {
+ level_has_screenshot = true;
+ first_level_drawn = UINT_MAX;
+ }
+
+ AddLevelPathToRecentLevels(level_path);
+
+ ClearLoadedLevel();
+ forced_split_screen_mode = kForcedModeNone;
+ paused = false;
+ menu_paused = false;
+ user_paused = false;
+ slow_motion = false;
+ check_save_level_changes_dialog_is_showing = false;
+ check_save_level_changes_dialog_quit_if_not_cancelled = false;
+ check_save_level_changes_dialog_is_finished = false;
+ check_save_level_changes_last_result = false;
+ shadow_cache_dirty = true;
+ shadow_cache_dirty_sun_moved = true;
+ shadow_cache_dirty_level_loaded = true;
+ LOG_ASSERT(scenegraph_ == NULL);
+ scenegraph_ = new SceneGraph();
+ scenegraph_->cubemaps_need_refresh = true;
+ scenegraph_->light_probe_collection.probe_lighting_enabled = config["tet_mesh_lighting"].toBool();
+ scenegraph_->light_probe_collection.light_volume_enabled = config["light_volume_lighting"].toBool();
+ ASData as_data;
+ as_data.scenegraph = scenegraph_;
+ as_data.gui = &gui;
+ scenegraph_->particle_system = new ParticleSystem(as_data);
+ scenegraph_->particle_system->particle_types = &particle_types;
+ scenegraph_->sky = new Sky();
+ scenegraph_->flares.scenegraph = scenegraph_;
+ scenegraph_->map_editor = new MapEditor();
+ scenegraph_->map_editor->gui = &gui;
+ scenegraph_->map_editor->Initialize(scenegraph_);
+ scenegraph_->map_editor->state_ = MapEditor::kIdle;
+ scenegraph_->map_editor->SetTypeEnabled(_light_probe_object, scenegraph_->light_probe_collection.show_probes);
+ scenegraph_->level = new Level();
+ scenegraph_->level->Initialize(scenegraph_, &gui);
+ scenegraph_->level->script_params().SetObjectID(std::numeric_limits<uint32_t>::max());
+ primary_light = &scenegraph_->primary_light;
+
+ AddLoadingText("Starting to load \""+level_path.GetFullPathStr()+"\"");
+
+ Input::Instance()->SetGrabMouse(false);
+ UIShowCursor(true);
+
+ started_loading_time = SDL_TS_GetTicks();
+ load_screen_tip[0] = '\0';
+ LoadScreenLoop(true);
+
+ interrupt_loading = false;
+
+ #if THREADED
+ loading_in_progress_ = true;
+ std::thread load_thread(std::bind(&Engine::LoadLevelData, this, level_path));
+ bool keep_looping = loading_in_progress_;
+ last_loading_input_time = SDL_TS_GetTicks();
+ while(keep_looping){
+ LoadScreenLoop(loading_in_progress_);
+ DisplayLastQueuedError();
+ loading_mutex_.lock();
+ keep_looping = loading_in_progress_;
+ loading_mutex_.unlock();
+ }
+ load_thread.join();
+ #else
+ LoadLevelData(level_path);
+ #endif
+ Graphics::Instance()->setSimpleShadows(config["simple_shadows"].toNumber<bool>());
+
+ if( interrupt_loading ) {
+ ClearLoadedLevel();
+ return;
+ }
+
+ if(g_no_reflection_capture == false )
+ {
+ scenegraph_->LoadReflectionCaptureCubemaps();
+ }
+
+ if( kLightProbeEnabled ){
+ PROFILER_ZONE(g_profiler_ctx, "Init light probe collection");
+ scenegraph_->light_probe_collection.Init();
+ }
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Init dynamic light collection");
+ scenegraph_->dynamic_light_collection.Init();
+ }
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Initialize level");
+ scenegraph_->level->Initialize(scenegraph_, &gui);
+ }
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Send 'added_object' messages");
+ for(size_t i=0, len=scenegraph_->objects_.size(); i<len; ++i){
+ Object* obj = scenegraph_->objects_[i];
+ static const int kBufSize = 256;
+ char msg[kBufSize];
+ FormatString(msg, kBufSize, "added_object %d", obj->GetID());
+ scenegraph_->level->Message(msg);
+ }
+ }
+
+ AddLoadingText("Loading sky...");
+ DrawLoadScreen(true);
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Load sky");
+ scenegraph_->sky->QueueLoadResources();
+ scenegraph_->sky->BakeFirstPass();
+ if(!scenegraph_->terrain_object_){
+ scenegraph_->sky->BakeSecondPass(NULL);
+ }
+ }
+
+ AddLoadingText("Reinitializing prefabs...");
+ scenegraph_->map_editor->ReloadAllPrefabs();
+
+ scenegraph_->map_editor->QueueSaveHistoryState();
+
+ AddLoadingText("Level load completed, initiating game...");
+ AddLoadingText("Preloading shaders...");
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Preload shaders");
+ scenegraph_->PreloadShaders();
+ }
+
+ DrawLoadScreen(true);
+
+ if( kLightProbeEnabled ){
+ PROFILER_ZONE(g_profiler_ctx, "Update light probe texture buffer");
+ scenegraph_->light_probe_collection.UpdateTextureBuffer(*scenegraph_->bullet_world_);
+ }
+
+ if ((config["editor_mode"].toNumber<bool>() == false && !scenegraph_->movement_objects_.empty()) || current_engine_state_.type != kEngineEditorLevelState) {
+ scenegraph_->map_editor->SendInRabbot();
+ }
+
+ native_loading_text.Clear();
+
+ level_loaded_ = true;
+ UIShowCursor(0);
+ level_updated_ = 0;
+
+ if(level_path.GetOriginalPathStr() == new_empty_level_path){
+ scenegraph_->level_path_ = Path();
+ }
+
+ if( level_has_screenshot && scenegraph_->level->loading_screen_.image.empty() ) {
+ scenegraph_->level->loading_screen_.image = screenshot_path;
+ }
+ scenegraph_->map_editor->UpdateEnabledObjects();
+ //level_screenshot.clear();
+ } else {
+ LOGE << "Unable to load LevelInfoAsset for " << level_path << ". Level file is likely corrupt or missing in some way." << std::endl;
+ }
+ }
+
+
+ Online::Instance()->SetLevelLoaded();
+ Shaders::Instance()->create_program_warning = true;
+}
+
+void Engine::AddLevelPathToRecentLevels(const Path& level_path) {
+ std::vector<std::string> level_history;
+ level_history.push_back(level_path.GetFullPath());
+
+ for(int i=0; i < kMaxLevelHistory; ++i) {
+ const int kConfigKeyNameSize = 128;
+ char configKeyName[kConfigKeyNameSize];
+ FormatString(configKeyName, kConfigKeyNameSize, "level_history%d", i + 1);
+
+ if(config.HasKey(configKeyName) && config[configKeyName].str() != level_path.GetFullPathStr() && config[configKeyName].str() != "") {
+ level_history.push_back(config[configKeyName].str());
+ }
+ }
+
+ for(int i = 0; i < kMaxLevelHistory; ++i) {
+ const int kConfigKeyNameSize = 128;
+ char configKeyName[kConfigKeyNameSize];
+ FormatString(configKeyName, kConfigKeyNameSize, "level_history%d", i + 1);
+
+ if(i < (int)level_history.size()) {
+ config.GetRef(configKeyName) = level_history[i];
+ } else {
+ config.GetRef(configKeyName) = "";
+ }
+ }
+
+ config.Save(GetConfigPath());
+}
+
+void Engine::GetAvatarIds(std::vector<ObjectID> &avatars) {
+ scenegraph_->GetPlayerCharacterIDs(&num_avatars, avatar_ids, kMaxAvatars);
+ avatars.clear();
+ for (int i = 0; i < num_avatars; ++i) {
+ avatars.push_back(avatar_ids[i]);
+ }
+}
+
+void Engine::QueueLevelToLoad(const Path& level) {
+ if (Online::Instance()->IsHosting()) {
+ Online::Instance()->PerformLevelChangeCleanUp();
+ Online::Instance()->ChangeLevel(level.GetOriginalPathStr());
+ }
+
+ queued_level_ = level;
+ UIShowCursor(1);
+ cursor.SetVisible(false);
+}
+
+void Engine::QueueLevelCacheGeneration(const Path& path) {
+ cache_generation_paths.push_back(path);
+}
+
+void Engine::GenerateLevelCache(ModInstance* mod_instance) {
+ for(size_t i = 0; i < mod_instance->levels.size(); ++i) {
+ std::string path = "Data/levels/";
+ path += mod_instance->levels[i].path;
+
+ QueueLevelCacheGeneration(FindFilePath(path, kAnyPath));
+ }
+ for(size_t i = 0; i < mod_instance->campaigns.size(); ++i) {
+ for(size_t j = 0; j < mod_instance->campaigns[i].levels.size(); ++j) {
+ std::string path = "Data/levels/";
+ path += mod_instance->campaigns[i].levels[j].path;
+
+ QueueLevelCacheGeneration(FindFilePath(path, kAnyPath));
+ }
+ }
+}
+
+/**
+FreeImage error handler
+@param fif Format / Plugin responsible for the error
+@param message Error message
+*/
+void FreeImageErrorHandler(FREE_IMAGE_FORMAT fif, const char *message) {
+ printf("\n*** ");
+ if(fif != FIF_UNKNOWN) {
+ printf("%s Format\n", FreeImage_GetFormatFromFIF(fif));
+ }
+ printf(message);
+ printf(" ***\n");
+}
+
+
+void Engine::Initialize() {
+ current_menu_player = -1;
+ waiting_for_input_ = false;
+ back_to_menu = false;
+ #ifdef OpenVR
+ // Loading the SteamVR Runtime
+ vr::EVRInitError eError = vr::VRInitError_None;
+ m_pHMD = vr::VR_Init( &eError, vr::VRApplication_Scene );
+
+ if ( eError != vr::VRInitError_None )
+ {
+ m_pHMD = NULL;
+ char buf[1024];
+ sprintf_s( buf, sizeof( buf ), "Unable to init VR runtime: %s", vr::VR_GetVRInitErrorAsEnglishDescription( eError ) );
+ FatalError( "OpenVR Error", buf);
+ return;
+ }
+
+ vr::IVRRenderModels *m_pRenderModels;
+ m_pRenderModels = (vr::IVRRenderModels *)vr::VR_GetGenericInterface( vr::IVRRenderModels_Version, &eError );
+ if( !m_pRenderModels )
+ {
+ m_pHMD = NULL;
+ vr::VR_Shutdown();
+
+ char buf[1024];
+ sprintf_s( buf, sizeof( buf ), "Unable to get render model interface: %s", vr::VR_GetVRInitErrorAsEnglishDescription( eError ) );
+ FatalError( "OpenVR Error", buf);
+ }
+
+ if ( !vr::VRCompositor() )
+ {
+ FatalError( "OpenVR Error", "vr::VRCompositor() failed");
+ }
+
+ std::string m_strDriver;
+ std::string m_strDisplay;
+ m_strDriver = "No Driver";
+ m_strDisplay = "No Display";
+
+ m_strDriver = GetTrackedDeviceString( m_pHMD, vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_TrackingSystemName_String );
+ m_strDisplay = GetTrackedDeviceString( m_pHMD, vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_SerialNumber_String );
+ #endif
+ #ifdef OculusVR
+ ovrInitParams initParams = { ovrInit_RequestVersion, OVR_MINOR_VERSION, NULL, 0, 0 };
+ ovrResult result = ovr_Initialize(&initParams);
+ if(OVR_FAILURE(result)) {
+ ovrErrorInfo errorInfo;
+ ovr_GetLastErrorInfo(&errorInfo);
+ LOGI << "ovr_Initialize failed: " << errorInfo.ErrorString << std::endl;
+ } else {
+ LOGE << "ovr_Initialize succeeded." << std::endl;
+
+ ovrGraphicsLuid luid;
+ result = ovr_Create(&session, &luid);
+ if(OVR_FAILURE(result)) {
+ ovrErrorInfo errorInfo;
+ ovr_GetLastErrorInfo(&errorInfo);
+ LOGI << "ovr_Create failed: " << errorInfo.ErrorString << std::endl;
+ } else {
+ LOGE << "ovr_Create succeeded." << std::endl;
+ g_oculus_vr_activated = true;
+ g_draw_vr = true;
+ hmdDesc = ovr_GetHmdDesc(session);
+ }
+ }
+ #endif
+ AssetPreload::Instance().Initialize();
+#if ENABLE_STEAMWORKS
+ Steamworks::Instance()->Initialize();
+#endif
+ //multiplayer.Initialize();
+ FreeImage_SetOutputMessage(FreeImageErrorHandler);
+
+ if( Engine::instance_ != NULL )
+ {
+ LOGF << "Engine only supports one instance at any time, if this need changes, refactoring will be needed for all situations that refer to Engine::Instance()" << std::endl;
+ }
+ else
+ {
+ Engine::instance_ = this;
+ }
+
+ LOG_ASSERT( Engine::instance_ != NULL );
+ paused = false;
+ scriptable_menu_ = NULL;
+ level_loaded_ = false;
+ quitting_ = false;
+ draw_frame = 0;
+ scenegraph_ = NULL;
+ resize_event_frame_counter = -1;
+ printed_rendering_error_message = false;
+ forced_split_screen_mode = kForcedModeNone;
+
+ active_screen_start = vec2(0.0f, 0.0f);
+ active_screen_end = vec2(1.0f, 1.0f);
+
+ // Check if we can find Data folder
+ char data_path[kPathSize];
+ if(FindFilePath("Data", data_path, kPathSize, kDataPaths)){
+ #ifdef _DEPLOY
+ FatalError("Error", "Could not find Data folder; please make sure Overgrowth is fully extracted and installed.");
+ #else
+ FatalError("Error", "Could not find Data folder; please make sure working directory is set correctly.");
+ #endif
+ }
+
+ LOGI << "Initializing SDL" << std::endl;
+ if(SDL_Init(0) == 0) {
+ LOGI << "SDL Initialized successfully" << std::endl;
+ } else {
+ LOGE << "SDL Initialization failed" << std::endl;
+ }
+
+ if( SDL_InitSubSystem(SDL_INIT_VIDEO) < 0 ) {
+ LOGE << "Failed when initializing SDL video subsystem" << std::endl;
+ }
+ if( SDL_InitSubSystem(SDL_INIT_EVENTS) < 0 ) {
+ LOGE << "Failed when initializing SDL events subsystem" << std::endl;
+ }
+ if( SDL_InitSubSystem(SDL_INIT_TIMER) < 0 ) {
+ LOGE << "Failed when initializing SDL timer subsystem" << std::endl;
+ }
+
+ LOGI << "Initializing SDL_net" << std::endl;
+ SDLNet_Init();
+
+ as_network.Initialize();
+ asset_manager.Initialize();
+
+ KeyCommand::Initialize();
+ ActiveCameras::Set(0);
+ Input::Instance()->Initialize();
+ Input::Instance()->cursor = &cursor;
+ LoadConfigFile();
+ AnimationRetargeter::Instance()->Load("Data/Animations/retarget.xml");
+
+ ModLoading::Instance().Initialize();
+ ModLoading::Instance().InitMods();
+
+ std::string preferred_audio_device = config["preferred_audio_device"].str();
+ sound.Initialize(preferred_audio_device.c_str());
+ sound.EnableLayeredSoundtrackLimiter(config["use_soundtrack_limiter"].toBool());
+ Graphics::Instance()->Initialize();
+ Graphics::Instance()->InitScreen();
+
+ if( config["has_detected_settings"].toBool() == false ) {
+ DetectAndSetSettings();
+ config.GetRef("has_detected_settings") = true;
+ }
+
+ // Set initial default playername
+ if(!config.HasKey("playername") || !OnlineUtility::IsValidPlayerName(config.GetRef("playername").str())) {
+ config.GetRef("playername") = OnlineUtility::GetDefaultPlayerName();
+ }
+
+#ifdef OculusVR
+ if(g_oculus_vr_activated){
+ for (int eye = 0; eye < 2; ++eye)
+ {
+ ovrSizei idealTextureSize = ovr_GetFovTextureSize(session, ovrEyeType(eye), hmdDesc.DefaultEyeFov[eye], 1);
+ eyeRenderTexture[eye] = new OVR::TextureBuffer(session, true, true, idealTextureSize, 1, NULL, 1);
+ eyeDepthBuffer[eye] = new OVR::DepthBuffer(eyeRenderTexture[eye]->GetSize(), 0);
+
+ if (!eyeRenderTexture[eye]->TextureChain)
+ {
+ FatalError("Error", "Could not create eye texture");
+ }
+ }
+
+ ovrMirrorTextureDesc desc;
+ memset(&desc, 0, sizeof(desc));
+ desc.Width = hmdDesc.Resolution.w / 2;
+ desc.Height = hmdDesc.Resolution.h / 2;
+ desc.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB;
+ ovrMirrorTexture mirrorTexture = NULL;
+ result = ovr_CreateMirrorTextureGL(session, &desc, &mirrorTexture);
+ if(OVR_FAILURE(result)) {
+ ovrErrorInfo errorInfo;
+ ovr_GetLastErrorInfo(&errorInfo);
+ LOGI << "ovr_CreateMirrorTextureGL failed: " << errorInfo.ErrorString << std::endl;
+ } else {
+ LOGE << "ovr_CreateMirrorTextureGL succeeded." << std::endl;
+ }
+
+ // Configure the mirror read buffer
+ GLuint texId;
+ ovr_GetMirrorTextureBufferGL(session, mirrorTexture, &texId);
+
+ Graphics::Instance()->genFramebuffers(&mirrorFBO, "mirror_fbo");
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, mirrorFBO);
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texId, 0);
+ glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
+
+ ovr_SetTrackingOriginType(session, ovrTrackingOrigin_FloorLevel);
+ }
+ #endif
+
+ show_state = config["menu_show_state"].toBool();
+ show_performance = config["menu_show_performance"].toBool();
+ show_log = config["menu_show_log"].toBool();
+ show_warnings = config["menu_show_warnings"].toBool();
+ show_save = config["menu_show_save"].toBool();
+ show_sound = config["menu_show_sound"].toBool();
+ show_mod_menu = config["menu_show_mod_menu"].toBool();
+ asdebugger_enabled = config["asdebugger_enabled"].toBool();
+ asprofiler_enabled = config["asprofiler_enabled"].toBool();
+ show_asdebugger_contexts = config["menu_show_asdebugger_contexts"].toBool();
+ show_asprofiler = config["menu_show_asprofiler"].toBool();
+ show_mp_debug = config["menu_show_mp_debug"].toBool();
+ show_input_debug = config["menu_show_input_debug"].toBool();
+ show_mp_info = config["menu_show_mp_info"].toBool();
+ show_mp_settings = config["menu_show_mp_settings"].toBool();
+ break_on_script_change = config["asdebugger_break_on_script_change"].toBool();
+
+ if (!GLEW_VERSION_4_0 && g_single_pass_shadow_cascade) {
+ LOGW << "OpenGL 4.0 not found, disabling single-pass shadow cascade" << std::endl;
+ g_single_pass_shadow_cascade = false;
+ config.GetRef("single_pass_shadow_cascade") = false;
+ }
+
+ game_timer.SetStepFrequency(120);
+ ui_timer.SetStepFrequency(120);
+ {
+ char save[kPathSize];
+ for( int i = SaveFile::kCurrentVersion; i >= 1; i--) {
+ if( i == 1 ) {
+ FormatString(save, kPathSize, "%ssavefile.sav", GetWritePath(CoreGameModID).c_str());
+ } else {
+ FormatString(save, kPathSize, "%ssavefile.sav%d", GetWritePath(CoreGameModID).c_str(),i);
+ }
+
+ if(FileExists(save,kAbsPath)){
+ if( save_file_.ReadFromFile(save) ) {
+ LOGI << "Successfully loaded v" << i << " save file" << std::endl;
+ break;
+ } else {
+ LOGE << "Failed to load v" << i << " save file" << std::endl;
+ }
+ }
+ }
+ FormatString(save, kPathSize, "%ssavefile.sav%d", GetWritePath(CoreGameModID).c_str(),SaveFile::kCurrentVersion);
+ save_file_.SetWriteFile(save);
+ }
+
+ CHECK_GL_ERROR();
+
+ cursor.SetCursor(DEFAULT_CURSOR);
+
+ CHECK_GL_ERROR();
+ PrintSpecs();
+ CHECK_GL_ERROR();
+
+ CHECK_GL_ERROR();
+ FontRenderer::Instance(&font_renderer);
+
+ CHECK_GL_ERROR();
+ if(config["load_all_levels"].toNumber<bool>()) {
+ ManifestXMLParser mp;
+
+ if( !mp.Load( FindFilePath(config["ogda_manifest"].str(), kAnyPath).GetFullPathStr() ) )
+ {
+ LOGF << "Unable to parse ogda manifest for load-all-levels" << std::endl;
+ }
+
+ std::vector<ManifestXMLParser::BuilderResult>::iterator resultit;
+
+ for( resultit = mp.manifest.builder_results.begin();
+ resultit != mp.manifest.builder_results.end();
+ resultit++ )
+ {
+ if( resultit->type == "level" )
+ {
+ LevelInfoAssetRef levelinfo = Engine::Instance()->GetAssetManager()->LoadSync<LevelInfoAsset>(std::string("Data/") + resultit->dest);
+ Path level_path = FindFilePath(std::string("Data/") + resultit->dest, kAnyPath);
+ QueueState(EngineState(levelinfo->GetName(),kEngineEditorLevelState,level_path));
+ }
+ }
+ } else if(config["main_menu"].toNumber<bool>()) {
+ CHECK_GL_ERROR();
+ Path path = FindFilePath(script_dir_path + std::string("mainmenu.as"), kAnyPath);
+ QueueState( EngineState( "main_menu", kEngineScriptableUIState,path ) );
+ CHECK_GL_ERROR();
+ } else {
+ CHECK_GL_ERROR();
+ Path path = FindFilePath(script_dir_path + std::string("mainmenu.as"), kAnyPath);
+ current_engine_state_ = EngineState("main_menu", kEngineScriptableUIState, path); // So that "back" takes us back to the main menu
+
+ if( FileExists( DebugLoadLevel(), kAnyPath ) )
+ {
+ Path path = FindFilePath(DebugLoadLevel(),kAnyPath);
+ LevelInfoAssetRef levelinfo = Engine::Instance()->GetAssetManager()->LoadSync<LevelInfoAsset>(path.GetFullPath());
+ QueueState(EngineState(levelinfo->GetName(),kEngineEditorLevelState, path));
+ }
+ else if( FileExists( std::string("Data/") + DebugLoadLevel(), kAnyPath ) )
+ {
+ Path path = FindFilePath(std::string("Data/") + DebugLoadLevel(),kAnyPath);
+ LevelInfoAssetRef levelinfo = Engine::Instance()->GetAssetManager()->LoadSync<LevelInfoAsset>(path.GetFullPath());
+ QueueState(EngineState(levelinfo->GetName(),kEngineEditorLevelState,path));
+ }
+ else
+ {
+ Path path = FindFilePath(std::string("Data/Levels/") + DebugLoadLevel(),kAnyPath);
+ LevelInfoAssetRef levelinfo = Engine::Instance()->GetAssetManager()->LoadSync<LevelInfoAsset>(path.GetFullPath());
+ QueueState(EngineState(levelinfo->GetName(),kEngineEditorLevelState,path));
+ }
+
+ CHECK_GL_ERROR();
+ }
+
+ if( config["global_time_scale_mult"].toNumber<float>() < 0.1f ) {
+ config.GetRef("global_time_scale_mult") = 0.1f;
+ }
+ game_timer.target_time_scale = config["global_time_scale_mult"].toNumber<float>();
+ current_global_scale_mult = config["global_time_scale_mult"].toNumber<float>();
+
+ CHECK_GL_ERROR();
+ g_text_atlas[kTextAtlasMono].Create(HardcodedPaths::paths[HardcodedPaths::kMonoFontPath], 18, &font_renderer, 0);
+ CHECK_GL_ERROR();
+ g_text_atlas[kTextAtlasDynamic].Create(HardcodedPaths::paths[HardcodedPaths::kDynamicFontPath], 18, &font_renderer, 0);
+ CHECK_GL_ERROR();
+ g_text_atlas_renderer.Init();
+
+ static const GLfloat quad_data[] = {
+ 0.0f, 0.0f,
+ 1.0f, 0.0f,
+ 1.0f, 1.0f,
+ 0.0f, 1.0f
+ };
+ static const GLuint quad_index[] = {
+ 0, 1, 2, 0, 3, 2
+ };
+
+ quad_vert_vbo.Fill(kVBOFloat | kVBOStatic, sizeof(quad_data), (void*)quad_data);
+ quad_index_vbo.Fill(kVBOElement | kVBOStatic, sizeof(quad_index), (void*)quad_index);
+
+ InitImGui();
+
+ ModLoading::Instance().RegisterCallback(this);
+#ifdef WIN32
+ data_change_notification = FindFirstChangeNotificationW(UTF16fromUTF8(data_path).c_str(), true, FILE_NOTIFY_CHANGE_LAST_WRITE);
+ char write_path[kPathSize];
+ FindFilePath("Data/Mods/", write_path, kPathSize, kWriteDir);
+ write_change_notification = FindFirstChangeNotificationW(UTF16fromUTF8(write_path).c_str(), true, FILE_NOTIFY_CHANGE_LAST_WRITE);
+#endif
+
+ loading_screen_logo = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/logo.tga", PX_SRGB | PX_NOREDUCE, 0x0);
+
+ loading_screen_og_logo_casual = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/ui/ogicon_casual.png", PX_SRGB | PX_NOREDUCE, 0x0);
+ loading_screen_og_logo_hardcore = loading_screen_og_logo_casual;//= Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/ui/ogicon_hardcore.png", PX_SRGB | PX_NOREDUCE, 0x0);
+ loading_screen_og_logo_expert = loading_screen_og_logo_casual;//= Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/ui/ogicon_expert.png", PX_SRGB | PX_NOREDUCE, 0x0);
+
+ loading_screen_og_logo = loading_screen_og_logo_casual;
+}
+
+void Engine::GetShaderNames(std::map<std::string, int>& preload_shaders) {
+ std::string post_shader_name = Graphics::Instance()->post_shader_name;
+ if(scenegraph_->level->script_params().HasParam("Custom Shader") && config.GetRef("custom_level_shaders").toNumber<bool>()){
+ const std::string& custom_shader = scenegraph_->level->script_params().GetStringVal("Custom Shader");
+ if(!custom_shader.empty()) {
+ post_shader_name += " "+ custom_shader;
+ }
+ }
+
+ preload_shaders[post_shader_name] = 0;
+ preload_shaders[post_shader_name + " #TONE_MAP"] = 0;
+ preload_shaders[post_shader_name + " #CALC_MOTION_BLUR"] = 0;
+ preload_shaders[post_shader_name + " #DOWNSAMPLE"] = 0;
+ preload_shaders[post_shader_name + " #BLUR_HORZ"] = 0;
+ preload_shaders[post_shader_name + " #BLUR_VERT"] = 0;
+ preload_shaders[post_shader_name + " #APPLY_MOTION_BLUR"] = 0;
+ preload_shaders[post_shader_name + " #DOF"] = 0;
+ preload_shaders[post_shader_name + " #COPY"] = 0;
+ preload_shaders[post_shader_name + " #DOWNSAMPLE #OVERBRIGHT"] = 0;
+ preload_shaders[post_shader_name + " #BLUR_DIR"] = 0;
+ preload_shaders[post_shader_name + " #ADD"] = 0;
+ preload_shaders[post_shader_name + " #DOWNSAMPLE_DEPTH"] = 0;
+ if(g_gamma_correct_final_output){
+ preload_shaders[post_shader_name + " #BRIGHTNESS #GAMMA_CORRECT_OUTPUT"] = 0;
+ } else {
+ preload_shaders[post_shader_name + " #BRIGHTNESS"] = 0;
+ }
+}
+
+
+void Engine::ScriptableUICallback(const std::string &level)
+{
+ LOGW << "Got a callback to switch to: " << level << std::endl;
+
+ if( config["global_time_scale_mult"].toNumber<float>() < 0.1f ) {
+ config.GetRef("global_time_scale_mult") = 0.1f;
+ }
+ game_timer.target_time_scale = config["global_time_scale_mult"].toNumber<float>();
+ current_global_scale_mult = config["global_time_scale_mult"].toNumber<float>();
+
+ if( hasEnding( level, ".as" ) )
+ {
+ std::string short_path = script_dir_path + level;
+ std::string long_path = level;
+
+ //First check if a full path was entered.
+ if( FileExists( long_path, kAnyPath ) )
+ {
+ LOGI << "L " << long_path << std::endl;
+ Path path = FindFilePath(long_path,kAnyPath);
+ QueueState(EngineState("",kEngineScriptableUIState,path));
+ }
+ //Then check if it a path relative to script_dir_path as root.
+ else if( FileExists( short_path, kAnyPath ) )
+ {
+ LOGI << "S " << long_path << std::endl;
+ Path path = FindFilePath(short_path,kAnyPath);
+ QueueState(EngineState("",kEngineScriptableUIState,path));
+ }
+ else
+ {
+ std::string message = "Unable to find script: " + short_path + " or " + long_path + ". Will not change state";
+
+ DisplayError( "Unable to find script", message.c_str(), _ok, true );
+ }
+ }
+ else if( hasEnding( level, ".xml" ) )
+ {
+ std::string short_path = "Data/Levels/" + level;
+ std::string long_path = level;
+
+ EngineStateType target_state_type = kEngineLevelState;
+ if(current_engine_state_.type == kEngineEditorLevelState){
+ target_state_type = kEngineEditorLevelState;
+ }
+
+ Path level_path;
+ //First check if a full path was entered.
+ if( FileExists( long_path, kAnyPath ) )
+ {
+ level_path = FindFilePath(long_path,kAnyPath);
+ }
+ //Then check if it a path relative to Data/Levels as root.
+ else if( FileExists( short_path, kAnyPath ) )
+ {
+ level_path = FindFilePath(short_path,kAnyPath);
+ }
+
+ if( level_path.isValid() ) {
+ LevelInfoAssetRef levelinfo = Engine::Instance()->GetAssetManager()->LoadSync<LevelInfoAsset>(level_path.GetFullPath());
+ std::string level_id;
+ if(levelinfo.valid()){
+ level_id = levelinfo->GetName();
+ }
+
+ ScriptableCampaign *sc = GetCurrentCampaign();
+ if(sc) {
+ std::string campaign_id = sc->GetCampaignID();
+ ModInstance::Campaign campaign = ModLoading::Instance().GetCampaign(campaign_id);
+
+ for( size_t i = 0; i < campaign.levels.size(); i++ ) {
+ if( campaign.levels[i].path == long_path || campaign.levels[i].path == short_path ) {
+ level_id = campaign.levels[i].id;
+ }
+ }
+ }
+ QueueState(EngineState(level_id, target_state_type, level_path));
+ }
+ else
+ {
+ std::string message = "Unable to find level: " + level + ". Will not change state";
+
+ DisplayError( "Unable to find level", message.c_str(), _ok, true );
+ }
+ }
+ else if( hasBeginning( level, "set_campaign" ) )
+ {
+ LOGI << "Setting campaign " << std::endl;
+ LOGI << level << std::endl;
+ if( level.size() > strlen("set_campaign ")) {
+ std::string campaign_id = level.substr(strlen("set_campaign "));
+ if(strlen(ModLoading::Instance().GetCampaign(campaign_id).id) > 0) {
+ ModInstance::Campaign mi_campaign = ModLoading::Instance().GetCampaign(campaign_id);
+ ScriptableCampaign* campaign = new ScriptableCampaign();
+
+ Path campaign_path = FindFilePath(std::string("Data/Scripts/campaign/") + std::string(mi_campaign.main_script));
+ if( campaign_path.isValid() == false ) {
+ if( strlen(mi_campaign.main_script) > 0 ) {
+ LOGE << "Given campaign script path \"" << mi_campaign.main_script << "\"is invalid, using fallback default_campaign.as" << std::endl;
+ }
+ campaign_path = FindFilePath("Data/Scripts/campaign/default_campaign.as");
+ }
+
+ if( campaign_path.isValid() ) {
+ LOGI << "Initializing campaign state with script " << campaign_path << std::endl;
+ campaign->Initialize( campaign_path, campaign_id );
+ LOGI << "Campaign accepted, loading as state" << std::endl;
+ QueueState(EngineState("",campaign,campaign_path));
+ } else {
+ LOGE << "Path to campaign script \"" << campaign_path << "\"" << " for campaign_id: " << campaign_id << std::endl;
+ }
+ } else {
+ LOGE << "Campaign id: " << campaign_id << " does not belong to any existing (or active) campaign " << std::endl;
+ }
+ }
+ }
+ else if( hasBeginning( level, "load_campaign_level" ) ) {
+ std::string level_id = level.substr(strlen("load_campaign_level "));
+ QueueState(EngineState(level_id, kEngineLevelState));
+ }
+ else if( level == "back" )
+ {
+ //These rules should probably be removed from here and moved into scripts.
+ bool perform_exit = false;
+ switch( current_engine_state_.type ) {
+ case kEngineNoState:
+ LOGW << "Current state is no state, invalid state" << std::endl;
+ perform_exit = false;
+ break;
+
+ case kEngineLevelState:
+ case kEngineEditorLevelState:
+ //Ingame, the esc button has very different use,
+ //instead spawning the main menu.
+ perform_exit = false;
+ break;
+
+ case kEngineScriptableUIState:
+ if( scriptable_menu_ ) {
+ perform_exit = scriptable_menu_->CanGoBack();
+ } else {
+ LOGE << "No current scriptable_ui_ when there should be." << std::endl;
+ perform_exit = true;
+ }
+ break;
+ case kEngineCampaignState:
+ perform_exit = true;
+ break;
+ default:
+ LOGE << "Unknown current state in ask pop queue state stack" << std::endl;
+ break;
+ }
+
+ if( perform_exit ) {
+ EngineStateAction esa;
+ esa.type = kEngineStateActionPopState;
+ esa.allow_game_exit = false;
+ queued_engine_state_.push_back(esa);
+ } else {
+ LOGI << "Ignoring request to back" << std::endl;
+ }
+ }
+ else if( level == "back_to_menu" )
+ {
+ EngineStateAction esa;
+ esa.type = kEngineStateActionPopUntilType;
+ esa.allow_game_exit = false;
+ esa.state.type = kEngineScriptableUIState;
+ queued_engine_state_.push_back(esa);
+
+ Online::Instance()->StopMultiplayer();
+ }
+ else if( level == "back_to_main_menu" )
+ {
+ EngineStateAction esa;
+ esa.type = kEngineStateActionPopUntilID;
+ esa.allow_game_exit = false;
+ esa.state.id = "main_menu";
+ queued_engine_state_.push_back(esa);
+
+ Online::Instance()->StopMultiplayer();
+ }
+ else if( level == "exit" )
+ {
+ EngineStateAction esa;
+ esa.type = kEngineStateActionPopState;
+ esa.allow_game_exit = true;
+ queued_engine_state_.push_back(esa);
+ }
+ else if( level == "mods" )
+ {
+ Path path = FindFilePath(script_dir_path + std::string("modmenu/main.as"),kAnyPath);
+ QueueState( EngineState("", kEngineScriptableUIState, path ));
+ }
+ else
+ {
+ LOGE << "Unknown message: " << level << std::endl;
+ }
+}
+
+bool Engine::IsStateQueued()
+{
+ return (queued_engine_state_.size() > 0);
+}
+
+void Engine::QueueState( const EngineState& state )
+{
+ EngineStateAction esa;
+ esa.type = kEngineStateActionPushState;
+ esa.state = state;
+ queued_engine_state_.push_back(esa);
+}
+
+void Engine::QueueState( const EngineStateAction& action )
+{
+ queued_engine_state_.push_back(action);
+}
+
+void Engine::QueueErrorMessage(const std::string& title, const std::string& message)
+{
+ // Send error directly to scriptable menus if possible (Main menu)
+ if(scriptable_menu_ != nullptr) {
+ scriptable_menu_->QueueBasicPopup(title, message);
+ } else { // If we don't have a menu, queue it up instead
+ popup_pueue.push_back(std::tuple<std::string, std::string>(title, message));
+ }
+}
+
+/*
+void Engine::OpenChallengeMenu() {
+ LOG_ASSERT(!scriptable_menu_);
+ scriptable_menu_ = new ScriptableUI((void*)this, &Engine::StaticHandleChallengeLevelSelect);
+ ASData as_data;
+ as_data.scenegraph = scenegraph_;
+ as_data.gui = &gui;
+ scriptable_menu_->Initialize(script_dir_path + "campaignchallengemenu.as", "void SetChallengeListMode()", as_data);
+}
+*/
+
+void Engine::StaticScriptableUICallback( void* instance, const std::string &level ) {
+ ((Engine*)instance)->ScriptableUICallback(level);
+}
+
+/*
+void Engine::OpenCampaignMenu()
+{
+ LOG_ASSERT(!scriptable_menu_);
+ scriptable_menu_ = new ScriptableUI((void*)this, &Engine::StaticHandleChallengeLevelSelect);
+ ASData as_data;
+ as_data.scenegraph = scenegraph_;
+ as_data.gui = &gui;
+ scriptable_menu_->Initialize(script_dir_path + "campaignmenu.as", "void SetCampaignListMode()", as_data);
+}
+
+class NewMenuScenegraph: public SceneGraph{
+ public:
+ NewMenuScenegraph():SceneGraph(){}
+ Sound * sound;
+};
+*/
+
+
+void Engine::ModActivationChange( const ModInstance* mod ) {
+ ReloadImGui();
+}
+
+Path Engine::GetLatestMenuPath() {
+ return latest_menu_path_;
+}
+
+Path Engine::GetLatestLevelPath() {
+ return latest_level_path_;
+}
+
+void Engine::CommitPause() {
+ bool was_paused = paused;
+ if (!Online::Instance()->IsActive()) {
+ paused = user_paused || menu_paused;
+ }
+ else {
+ paused = false;
+ }
+
+ game_timer.target_time_scale = (paused || slow_motion) ? 0.1f : current_global_scale_mult;
+ if (was_paused != paused) {
+ game_timer.time_scale = paused ? current_global_scale_mult : 0.1f;
+ }
+ if (!paused) {
+ Engine::Instance()->current_menu_player = -1;
+ }
+
+}
+
+ScriptableCampaign* Engine::GetCurrentCampaign() {
+ if(current_engine_state_.type == kEngineCampaignState ) {
+ if( current_engine_state_.campaign.Valid() ) {
+ return current_engine_state_.campaign.GetPtr();
+ }
+ } else {
+ for( unsigned int i = 0; i < state_history.size(); i++ ) {
+ if( state_history[i].type == kEngineCampaignState ){
+ if( state_history[i].campaign.Valid() ) {
+ return state_history[i].campaign.GetPtr();
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
+std::string Engine::GetCurrentLevelID() {
+ if(current_engine_state_.type == kEngineLevelState || current_engine_state_.type == kEngineEditorLevelState ) {
+ return current_engine_state_.id;
+ } else {
+ for( unsigned int i = 0; i < state_history.size(); i++ ) {
+ if( state_history[i].type == kEngineLevelState || state_history[i].type == kEngineEditorLevelState){
+ return state_history[i].id;
+ }
+ }
+ }
+ return "";
+}
+
+void Engine::InjectWindowResizeEvent(ivec2 size) {
+ resize_event_frame_counter = 10;
+ resize_value = size;
+}
+
+void DumpScenegraphState() {
+ SceneGraph* s = Engine::Instance()->GetSceneGraph();
+ if( s) {
+ s->DumpState();
+ }
+}
+
+void Engine::SetGameSpeed(float val, bool hard) {
+ if( val >= 0.1f ) {
+ if(hard) {
+ game_timer.time_scale /= current_global_scale_mult;
+ }
+ game_timer.target_time_scale /= current_global_scale_mult;
+ config.GetRef("global_time_scale_mult") = val;
+ current_global_scale_mult = val;
+ game_timer.target_time_scale *= val;
+ if(hard) {
+ game_timer.time_scale *= val;
+ }
+ } else {
+ LOGE << "Disallowing setting gamespeed below 0.1" << std::endl;
+ }
+}
+
+bool Engine::RequestedInterruptLoading() {
+ loading_mutex_.lock();
+ bool val = interrupt_loading;
+ loading_mutex_.unlock();
+ return val;
+}
diff --git a/Source/Main/engine.h b/Source/Main/engine.h
new file mode 100644
index 00000000..ec6eedd2
--- /dev/null
+++ b/Source/Main/engine.h
@@ -0,0 +1,326 @@
+//-----------------------------------------------------------------------------
+// Name: engine.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Main/scenegraph.h>
+#include <Main/scenegraph.h>
+
+#include <UserInput/input.h>
+
+#include <Graphics/Cursor.h>
+#include <Graphics/graphics.h>
+#include <Graphics/text.h>
+#include <Graphics/model.h>
+#include <Graphics/font_renderer.h>
+#include <Graphics/particles.h>
+#include <Graphics/vbocontainer.h>
+#include <Graphics/animationeffectsystem.h>
+#include <Graphics/lipsyncsystem.h>
+
+#include <Sound/sound.h>
+#include <Sound/threaded_sound_wrapper.h>
+#include <Sound/threaded_sound_wrapper.h>
+
+
+#include <Game/avatar_control_manager.h>
+#include <Game/savefile.h>
+#include <Game/scriptablecampaign.h>
+
+#include <GUI/gui.h>
+#include <Asset/assetmanager.h>
+#include <Network/asnetwork.h>
+#include <Internal/modloading.h>
+#include <Online/online.h>
+
+#include <stack>
+#include <iostream>
+#include <mutex>
+
+//-----------------------------------------------------------------------------
+// Class Definition
+//-----------------------------------------------------------------------------
+
+struct ShadowUpdate;
+class ScriptableUI;
+class MovementObject;
+struct SDL_Keysym;
+class MapEditor;
+class Timer;
+class ProfilerContext;
+class Multiplayer;
+
+extern bool shadow_cache_dirty;
+extern bool shadow_cache_dirty_sun_moved;
+
+extern bool g_draw_collision;
+
+static const int kMaxLevelHistory = 10;
+
+enum EngineStateType {
+ kEngineNoState = 0,
+ kEngineLevelState,
+ kEngineEditorLevelState,
+ kEngineScriptableUIState,
+ kEngineCampaignState //Mid stat stack injection to indicate what campaign we are currently in.
+};
+
+const char* CStrEngineStateType( const EngineStateType& state );
+
+void SaveCollisionNormals(const SceneGraph* scenegraph);
+void LoadCollisionNormals(SceneGraph* scenegraph);
+
+void PushGPUProfileRange(const char* cstr);
+void PopGPUProfileRange();
+
+class EngineState
+{
+public:
+ EngineState();
+ EngineState(std::string id, EngineStateType _type);
+ EngineState(std::string id, EngineStateType _type, Path _path);
+ EngineState(std::string id, ScriptableCampaign* campaign, Path _path);
+
+ EngineStateType type;
+
+ bool pop_past;
+ std::string id;
+ Path path;
+ ReferenceCounter<ScriptableCampaign> campaign;
+};
+
+enum EngineStateActionType {
+ kEngineStateActionPushState,
+ kEngineStateActionPopState,
+ kEngineStateActionPopUntilType,
+ kEngineStateActionPopUntilID
+};
+enum ForcedSplitScreenMode {
+ kForcedModeNone,
+ kForcedModeFull,
+ kForcedModeSplit
+};
+
+class EngineStateAction
+{
+public:
+ bool allow_game_exit;
+ EngineState state;
+ EngineStateActionType type;
+};
+
+std::ostream& operator<<( std::ostream& out, const EngineState& in );
+
+class Engine : public ModLoadingCallback {
+ public:
+ enum DrawingViewport { kViewport, kScreen };
+ enum PostEffectsType { kStraight, kFinal };
+
+ void Initialize();
+ void GetShaderNames(std::map<std::string, int>& preload_shaders);
+ void Update();
+ void Draw();
+ void Dispose();
+
+ static Engine* Instance();
+
+ ThreadedSound* GetSound();
+ AssetManager* GetAssetManager();
+ AnimationEffectSystem* GetAnimationEffectSystem();
+ LipSyncSystem* GetLipSyncSystem();
+ ASNetwork* GetASNetwork();
+ SceneGraph* GetSceneGraph();
+
+ bool IsStateQueued();
+ void QueueState(const EngineState& state);
+ void QueueState( const EngineStateAction& action ) ;
+
+ void QueueErrorMessage(const std::string& title, const std::string& message);
+
+ void AddLevelPathToRecentLevels(const Path& level_path); // TODO: Expose some Save state to queue instead?
+
+ void GetAvatarIds(std::vector<ObjectID> &avatars);
+
+ /**
+ * @brief function called when the "back" button is pushed,
+ * commonly escape on keyboard or b on controller.
+ * Depending on the current state this might be ignored
+ * or propagated into the current state's script.
+ */
+ void PopQueueStateStack( bool allow_game_exit );
+private:
+ void LoadLevel(Path queued_level);
+ void PreloadAssets(const Path &level_path);
+ void LoadLevelData(const Path &level_path);
+
+ bool back_to_menu; // Used after cache generation to queue up the state
+ std::vector<Path> cache_generation_paths;
+ void QueueLevelCacheGeneration(const Path& path);
+
+ ForcedSplitScreenMode forced_split_screen_mode;
+public:
+
+ void GenerateLevelCache(ModInstance* mod_instance);
+
+ void HandleRabbotToggleControls();
+
+ void ClearArenaSession();
+
+ void ClearLoadedLevel();
+ static void StaticScriptableUICallback(void* instance, const std::string &level);
+ void ScriptableUICallback(const std::string &level);
+ static void NewLevel();
+
+ void SetForcedSplitScreenMode(ForcedSplitScreenMode mode) { forced_split_screen_mode = mode; }
+ bool GetSplitScreen() const;
+
+ bool quitting_;
+ bool paused;
+ bool user_paused;
+ bool menu_paused;
+ bool slow_motion;
+ bool check_save_level_changes_dialog_is_showing;
+ bool check_save_level_changes_dialog_quit_if_not_cancelled;
+ bool check_save_level_changes_dialog_is_finished;
+ bool check_save_level_changes_last_result;
+ int current_menu_player;
+ std::string current_spawner_thumbnail;
+ TextureAssetRef spawner_thumbnail;
+
+ TextureAssetRef loading_screen_logo;
+ TextureAssetRef loading_screen_og_logo;
+
+ TextureAssetRef loading_screen_og_logo_casual;
+ TextureAssetRef loading_screen_og_logo_hardcore;
+ TextureAssetRef loading_screen_og_logo_expert;
+
+ bool level_has_screenshot;
+ TextureAssetRef level_screenshot;
+ uint32_t first_level_drawn;
+
+ SaveFile save_file_;
+ GUI gui;
+ EngineState current_engine_state_;
+
+ std::map<std::string, std::string> interlevel_data;
+
+ vec2 active_screen_start;
+ vec2 active_screen_end;
+
+ Path GetLatestMenuPath();
+ Path GetLatestLevelPath();
+
+ ScriptableCampaign* GetCurrentCampaign();
+ std::string GetCurrentLevelID();
+ char load_screen_tip[kPathSize];
+ bool waiting_for_input_;
+
+ std::deque<EngineState> state_history;
+
+ void CommitPause();
+
+ uint64_t draw_frame;
+
+ bool loading_in_progress_;
+ private:
+ Path latest_level_path_;
+ Path latest_menu_path_;
+
+ void QueueLevelToLoad(const Path& level);
+
+ static Engine* instance_;
+
+ std::deque<EngineStateAction> queued_engine_state_;
+ std::deque<std::tuple<std::string, std::string>> popup_pueue;
+#ifdef WIN32
+ HANDLE data_change_notification;
+ HANDLE write_change_notification;
+#endif
+ GameCursor cursor;
+ ThreadedSound sound;
+ AssetManager asset_manager;
+ AnimationEffectSystem particle_types;
+ LipSyncSystem lip_sync_system;
+ ASNetwork as_network;
+ FontRenderer font_renderer;
+
+ int started_loading_time;
+ int last_loading_input_time;
+ int level_updated_;
+ SceneGraph *scenegraph_;
+ static const int kFPSLabelMaxLen = 64;
+ char fps_label_str[kFPSLabelMaxLen];
+ char frame_time_label_str[kFPSLabelMaxLen];
+
+ // Used for loading thread to tell main thread if it is done
+ std::mutex loading_mutex_;
+ float finished_loading_time;
+
+ ScriptableUI *scriptable_menu_;
+
+ Path queued_level_;
+ bool level_loaded_;
+
+ // These are just members to avoid mallocs
+ public:
+ static const int kMaxAvatars = 64;
+ int num_avatars;
+ int num_npc_avatars;
+ int avatar_ids[kMaxAvatars];
+ int npc_avatar_ids[kMaxAvatars];
+
+ private:
+
+ //Countdown value used to delay massively frequent resizing requests.
+ int resize_event_frame_counter;
+ ivec2 resize_value;
+
+ float current_global_scale_mult;
+
+ uint64_t frame_counter;
+
+ bool printed_rendering_error_message;
+
+ AvatarControlManager avatar_control_manager;
+
+ public:
+ void DrawScene(DrawingViewport drawing_viewport, PostEffectsType post_effects_type, SceneGraph::SceneDrawType scene_draw_type);
+ private:
+
+ void SetViewportForCamera(int which_cam, int num_screens, Graphics::ScreenType screen_type);
+
+ void LoadScreenLoop(bool loading_in_progress);
+ void DrawLoadScreen(bool loading_in_progress);
+
+ void LoadConfigFile();
+
+ void UpdateControls(float timestep, bool loading_screen);
+
+ void DrawCubeMap(TextureRef cube_map, const vec3 &pos, GLuint cube_map_fbo, SceneGraph::SceneDrawType scene_draw_type);
+
+ void ModActivationChange( const ModInstance* mod );
+public:
+ void InjectWindowResizeEvent(ivec2 size);
+ void SetGameSpeed(float val, bool hard);
+
+ bool RequestedInterruptLoading();
+};
diff --git a/Source/Main/main.cpp b/Source/Main/main.cpp
new file mode 100644
index 00000000..b45b333c
--- /dev/null
+++ b/Source/Main/main.cpp
@@ -0,0 +1,511 @@
+//-----------------------------------------------------------------------------
+// Name: main.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#ifndef __PS4__
+#include <Internal/config.h>
+#include <Internal/crashreport.h>
+#include <Internal/error.h>
+#include <Internal/filesystem.h>
+#include <Internal/profiler.h>
+#include <Internal/dialogues.h>
+
+#include <Logging/consolehandler.h>
+#include <Logging/filehandler.h>
+#include <Logging/logdata.h>
+#include <Logging/ramhandler.h>
+
+#include <Main/engine.h>
+#include <Main/altmain.h>
+
+#include <Threading/rand.h>
+#include <Threading/thread_sanity.h>
+
+#include <Graphics/graphics.h>
+#include <Memory/allocation.h>
+#include <Version/version.h>
+#include <Compat/platformsetup.h>
+#include <Timing/timingevent.h>
+
+#include <SDL.h>
+#include <RecastAlloc.h>
+#include <DetourAlloc.h>
+#include <tclap/CmdLine.h>
+#if ENABLE_FPU_SIGNALS == 1
+#include <fenv.h>
+#endif
+#ifdef UNIT_TESTS
+#include <UnitTests/testmain.h>
+#endif
+#include <sstream>
+
+extern Config config;
+extern bool mem_track_enable;
+extern bool g_draw_vr;
+ProfilerContext* g_profiler_ctx;
+
+static bool debug_output = false;
+static bool spam_output = false;
+static bool clear_log = false;
+static bool quit_after_load = false;
+static bool no_dialogues = false;
+static bool disable_rendering = false;
+static bool load_all_levels = false;
+static bool clear_cache = false;
+static bool clear_cache_dry_run = false;
+static bool level_load_stress = false;
+
+static std::string overloadedWriteDir;
+static std::string overloadedWorkingDir;
+
+Allocation alloc;
+
+RamHandler ram_handler;
+
+static void *rcAllocReplacement(size_t size, rcAllocHint)
+{
+ return og_malloc(size,OG_MALLOC_RC);
+}
+
+static void rcFreeReplacement(void *ptr)
+{
+ og_free(ptr);
+}
+
+static void *dtAllocReplacement(size_t size, dtAllocHint)
+{
+ return og_malloc(size,OG_MALLOC_DT);
+}
+
+static void dtFreeReplacement(void *ptr)
+{
+ og_free(ptr);
+}
+
+#if defined PLATFORM_WINDOWS
+#include <windows.h>
+typedef enum PROCESS_DPI_AWARENESS
+{
+ PROCESS_DPI_UNAWARE = 0,
+ PROCESS_SYSTEM_DPI_AWARE = 1,
+ PROCESS_PER_MONITOR_DPI_AWARE = 2
+} PROCESS_DPI_AWARENESS;
+//typedef BOOL (WINAPI * SETPROCESSDPIAWARE_T)(void);
+typedef HRESULT (WINAPI * SETPROCESSDPIAWARENESS_T)(PROCESS_DPI_AWARENESS);
+#ifdef OG_DEBUG
+#include <DbgHelp.h>
+#endif
+#endif
+
+int GameMain(int argc, char* argv[]) {
+ RegisterMainThreadID();
+
+#ifdef PLATFORM_WINDOWS
+ HMODULE shcore = LoadLibraryA("Shcore.dll");
+ SETPROCESSDPIAWARENESS_T SetProcessDpiAwareness = NULL;
+ if (shcore) {
+ SetProcessDpiAwareness = (SETPROCESSDPIAWARENESS_T) GetProcAddress(shcore, "SetProcessDpiAwareness");
+ }
+ //HMODULE user32 = LoadLibraryA("User32.dll");
+ //SETPROCESSDPIAWARE_T SetProcessDPIAware = NULL;
+ //if (user32) {
+ // SetProcessDPIAware = (SETPROCESSDPIAWARE_T) GetProcAddress(user32, "SetProcessDPIAware");
+ //}
+
+ if (SetProcessDpiAwareness) {
+ if (SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE) != S_OK) {
+ LOGW << "Couldn't set process dpi awareness (per monitor)" << std::endl;
+ }
+ } else if (SetProcessDPIAware != nullptr) {
+ if (!SetProcessDPIAware()) {
+ LOGW << "Couldn't set process dpi awareness (system)" << std::endl;
+ }
+ }
+
+ //if (user32) {
+ // FreeLibrary(user32);
+ //}
+ if (shcore) {
+ FreeLibrary(shcore);
+ }
+#ifdef OG_DEBUG
+ SymSetOptions(SYMOPT_LOAD_LINES);
+ HANDLE handle = GetCurrentProcess();
+ SymInitialize(handle, NULL, TRUE);
+#endif //OG_DEBUG
+#endif
+
+ rand_ts_seed((unsigned int)time(NULL));
+
+ alloc.Init();
+
+ dtAllocSetCustom(dtAllocReplacement,dtFreeReplacement);
+ rcAllocSetCustom(rcAllocReplacement,rcFreeReplacement);
+
+ // Initialize profiler
+ ProfilerContext profiler_context;
+ profiler_context.Init(&alloc.stack);
+ g_profiler_ctx = &profiler_context;
+
+ PROFILER_ENTER(&profiler_context, "SetUpEnvironment");
+ SetUpEnvironment(argv[0],overloadedWriteDir.c_str(),overloadedWorkingDir.c_str());
+ PROFILER_LEAVE(&profiler_context);
+
+ //Have to wait until we SetUpEnvironment before we can get write dir.
+ FileHandler fileLogHandler(GetLogfilePath(), clear_log ? 0 : 10 * 1024 * 1024, 40 * 1024 * 1024);
+
+ STIMING_INIT( GetWritePath(CoreGameModID).c_str() );
+
+ STIMING_ADDVISUALIZATION( STUpdate, vec3( 0.7, 0.3, 0.3 ), //Redish
+ STDraw, vec3( 0.3, 0.7, 0.3 ), //Greenish
+ STDrawSwap, vec3( 0.3, 0.3, 0.7 ) ); //Blueish
+
+ STIMING_SETVISUALIZATIONSCALE(16,32);
+
+ STIMING_INITVISUALIZATION();
+
+ LogTypeMask level =
+ LogSystem::info
+ | LogSystem::warning
+ | LogSystem::error
+ | LogSystem::fatal;
+ if( debug_output ) {
+ level |= LogSystem::debug;
+ }
+ if( spam_output ) {
+ level |= LogSystem::spam;
+ }
+ LogSystem::RegisterLogHandler( level, &fileLogHandler );
+
+ LOGI << "Starting program. Version " << GetBuildVersion() << "_" << GetBuildIDString() << " " << GetBuildTimestamp() << " " << GetArch() << " " << GetPlatform() << std::endl;
+
+#ifdef NDEBUG
+ LOGI << "Deploy (Release)" << std::endl;
+#else
+ LOGI << "Debug" << std::endl;
+#endif
+
+ PROFILER_ENTER(&profiler_context, "Engine initialize");
+ //Engine* engine = (Engine*)alloc.stack.Alloc(sizeof(Engine));
+ //new(engine) Engine;
+ Engine* engine = new Engine();
+ engine->Initialize();
+ Dialog::Initialize();
+ PROFILER_LEAVE(&profiler_context);
+
+ if( clear_cache ) {
+ ClearCache(false);
+ }
+ if( clear_cache_dry_run ) {
+ ClearCache(true);
+ }
+
+ // Main loop
+ while( !engine->quitting_ ) {
+ STIMING_STARTFRAME();
+
+ STIMING_START_COARSE( STUpdate );
+ engine->Update();
+ STIMING_END_COARSE( STUpdate );
+
+ bool time_to_draw = true;
+ static uint32_t last_time = 0;
+
+ if(!engine->quitting_ && disable_rendering == false ){
+ Graphics* graphics = Graphics::Instance();
+ if(!graphics->config_.vSync()&&graphics->config_.limit_fps_in_game()){
+ PROFILER_ZONE_IDLE(g_profiler_ctx, "SDL_Sleep");
+ if(!g_draw_vr){
+ time_to_draw = false;
+
+ int max_frame_rate = graphics->config_.max_frame_rate();
+ if(max_frame_rate < 15){
+ max_frame_rate = 15;
+ }
+ if(max_frame_rate > 500){
+ max_frame_rate = 500;
+ }
+
+ int ticks_to_wait = 1000 / max_frame_rate;
+ int diff = ticks_to_wait - (SDL_TS_GetTicks() - last_time) - 1;
+ if(diff > ticks_to_wait - 1){
+ diff = ticks_to_wait - 1;
+ }
+
+ if(diff > 1){
+ SDL_Delay(1);
+ }
+ else if(diff < 1) {
+ time_to_draw = true;
+ }
+ }
+ }
+
+ if(time_to_draw){
+ STIMING_START_COARSE(STDraw);
+ engine->Draw();
+ last_time = SDL_TS_GetTicks();
+ STIMING_END_COARSE(STDraw);
+ }
+ }
+
+ if(time_to_draw){
+ STIMING_START_COARSE(STDrawSwap);
+ Graphics::Instance()->SwapToScreen();
+ STIMING_END_COARSE(STDrawSwap);
+
+ Graphics::Instance()->ClearGLState();
+ }
+
+ STIMING_ENDFRAME();
+
+ SDL_Delay(0); // Allow other threads to run
+ PROFILER_TICK(g_profiler_ctx);
+ }
+
+ STIMING_FINALIZE();
+
+ LOGI << "Final check if savefile needs to be written..." << std::endl;
+ Engine::Instance()->save_file_.ExecuteQueuedWrite();
+
+ LOGI << "Cleanly disposing of loaded assets..." << std::endl;
+ engine->Dispose();
+ //engine->~Engine();
+ delete engine;
+ //alloc.stack.Free(engine);
+
+ profiler_context.Dispose(&alloc.stack);
+
+ LOGI << "Program terminated successfully." << std::endl;
+ DisposeEnvironment();
+
+ LogSystem::DeregisterLogHandler( &fileLogHandler );
+
+ alloc.Dispose();
+
+#if defined PLATFORM_WINDOWS && defined OG_DEBUG
+ SymCleanup(handle);
+#endif
+
+ LOGE << "Shutting down" << std::endl;
+ LogSystem::Flush();
+ return 0;
+}
+
+int main( int argc, char* argv[] )
+{
+
+#if ENABLE_FPU_SIGNALS == 1
+ feenableexcept(FE_INVALID | FE_OVERFLOW);
+#endif
+
+ //_CrtSetDbgFlag(_CRTDBG_CHECK_ALWAYS_DF);
+ // Check for the command-line arg that XCode automatically adds when starting through the debugger,
+ // and remove it so as not to confuse the parser
+ for( int i = 0; i < argc-1; i++ )
+ {
+ if( strcmp(argv[i], "-NSDocumentRevisionsDebugMode") == 0 )
+ {
+ strcpy( argv[i], "" );
+ strcpy( argv[i+1], "" );
+ }
+ };
+
+ try
+ {
+ std::string full_version = std::string(GetBuildVersion()) + "_" + GetBuildIDString();
+ // Set up command-line-parser
+ TCLAP::CmdLine cmd("Overgrowth", ' ', full_version.c_str() );
+
+ TCLAP::ValueArg<std::string> configurationArg("c","config","Configuration string",false,"","string");
+ cmd.add( configurationArg );
+
+ TCLAP::ValueArg<std::string> levelArg("l","level","Load level on startup",false,"","string");
+ cmd.add( levelArg );
+
+ TCLAP::ValueArg<std::string> writeDirArg("", "write-dir", "Force set write directory to something else than system default.", false,"","string");
+ cmd.add( writeDirArg );
+
+ TCLAP::ValueArg<std::string> workingDirArg("", "working-dir", "Force set the working dir for the application.", false,"","string");
+ cmd.add( workingDirArg );
+
+ TCLAP::ValueArg<std::string> ogdaManifest("", "ogda-manifest", "Ogda generated manifest of game assets.", false, "", "string");
+ cmd.add( ogdaManifest );
+
+ TCLAP::SwitchArg ddsconvertSwitch( "", "ddsconvert", "Start game with DDSConvert.", cmd, false);
+ TCLAP::SwitchArg debugOutput( "d","debug-output", "Start game with debug output", cmd, false);
+ TCLAP::SwitchArg spamOutput( "s","spam-output", "Start game with spammy debug output", cmd, false);
+ TCLAP::SwitchArg quitAfterLoad( "", "quit-after-load", "Turn of the game after level load is performed", cmd, false );
+ TCLAP::SwitchArg noDialogues( "", "no-dialogues", "Skip creating dialogues and instead do automatic responses", cmd, false );
+ TCLAP::SwitchArg clearLog( "", "clear-log", "Empty the log instead of appending to it", cmd, false);
+ TCLAP::SwitchArg disableRendering( "", "disable-rendering", "Disable the draw loop", cmd, false);
+ TCLAP::SwitchArg loadAllLevels( "", "load-all-levels", "Load all levels specified in the ogda build manifest", cmd, false );
+ TCLAP::SwitchArg clearCache( "", "clear-cache", "Clear the write folder of known cache files", cmd, false );
+ TCLAP::SwitchArg clearCacheDryRun( "", "clear-cache-dry-run", "Clear the write folder of known cache files (dry run)", cmd, false );
+ TCLAP::SwitchArg levelLoadStress( "", "level-load-stress", "Load levels in a loop", cmd, false);
+#ifdef UNIT_TESTS
+ TCLAP::SwitchArg runUnitTests( "", "run-unit-tests", "Run all unit tests", cmd, false );
+#endif
+
+ // Actually parse the command line
+ cmd.parse( argc, argv );
+
+ // Extract information from command-line-parser
+ bool runWithDDSConvert = ddsconvertSwitch.getValue();
+ std::string levelname = levelArg.getValue();
+ std::string configuration = configurationArg.getValue();
+ std::string manifest = ogdaManifest.getValue();
+
+ overloadedWriteDir = writeDirArg.getValue();
+ overloadedWorkingDir = workingDirArg.getValue();
+ debug_output = debugOutput.getValue();
+ spam_output = spamOutput.getValue();
+ clear_log = clearLog.getValue();
+ quit_after_load = quitAfterLoad.getValue();
+ no_dialogues = noDialogues.getValue();
+ disable_rendering = disableRendering.getValue();
+ load_all_levels = loadAllLevels.getValue();
+ clear_cache = clearCache.getValue();
+ clear_cache_dry_run = clearCacheDryRun.getValue();
+ level_load_stress = levelLoadStress.getValue();
+
+ std::stringstream configurationStream( configuration );
+ config.Load( configurationStream, false, true );
+
+ // If command line specified a level, skip main menu and jump to loading that level
+ if( !levelname.empty() )
+ {
+ std::stringstream ss;
+ ss << "debug_load_level: " << levelname << std::endl;
+ ss << "main_menu: false" << std::endl;
+ config.Load( ss, false, true );
+ }
+
+ if( !manifest.empty() )
+ {
+ std::stringstream ss;
+ ss << "ogda_manifest: " << manifest << std::endl;
+ config.Load( ss, false, true );
+ }
+
+ if( quit_after_load )
+ {
+ std::stringstream ss;
+ ss << "quit_after_load: true" << std::endl;
+ config.Load( ss, false, true );
+ }
+
+ if( no_dialogues )
+ {
+ std::stringstream ss;
+ ss << "no_dialogues: true" << std::endl;
+ config.Load( ss, false, true );
+ }
+
+ if( load_all_levels )
+ {
+ std::stringstream ss;
+ ss << "load_all_levels: true" << std::endl;
+ config.Load( ss, false, true );
+ }
+
+ if( level_load_stress ) {
+ std::stringstream ss;
+ ss << "level_load_stress: true" << std::endl;
+ config.Load( ss, false, true );
+ }
+
+ /******************************************************/
+ // Register logging handlers
+ ConsoleHandler consoleHandler;
+ LogTypeMask level =
+ LogSystem::info
+ | LogSystem::warning
+ | LogSystem::error
+ | LogSystem::fatal;
+
+ if( debug_output ) {
+ level |= LogSystem::debug;
+ }
+
+ if( spam_output ) {
+ level |= LogSystem::spam;
+ }
+
+ // Choose which main function to run
+ int ret;
+ if( runWithDDSConvert ) {
+ LogSystem::RegisterLogHandler(level, &consoleHandler);
+ ret = DDSConvertMain(argc, argv,overloadedWriteDir.c_str(),overloadedWorkingDir.c_str());
+ LogSystem::DeregisterLogHandler(&consoleHandler);
+ #ifdef UNIT_TESTS
+ } else if( runUnitTests.getValue() ) {
+ ret = RunUnitTests();
+ #endif
+ } else {
+ LogSystem::RegisterLogHandler( level, &consoleHandler );
+ LogSystem::RegisterLogHandler(level, &ram_handler);
+ ret = RunWithCrashReport(argc, argv, &GameMain);
+ LogSystem::DeregisterLogHandler(&ram_handler);
+ LogSystem::DeregisterLogHandler( &consoleHandler );
+ }
+
+ return ret;
+ } catch (TCLAP::ArgException &e) {
+ std::cerr << "error: " << e.error() << " for arg " << e.argId() << std::endl;
+ }
+ return 0;
+}
+
+#else
+#include <cstdio>
+#include <sys/stat.h>
+
+struct PS4Engine {
+ void Initialize();
+};
+
+void PS4Engine::Initialize() {
+ FILE *file = fopen("/app0/Data/","rb");
+}
+
+int main( int argc, char* argv[] ) {
+ ConsoleHandler consoleHandler;
+ LogTypeMask level =
+ LogSystem::info
+ | LogSystem::warning
+ | LogSystem::error
+ | LogSystem::fatal;
+
+ if( debug_output ) {
+ level |= LogSystem::debug;
+ }
+ if( spam_output ) {
+ level |= LogSystem::spam;
+ }
+
+ PS4Engine engine;
+ engine.Initialize();
+
+ LogSystem::DeregisterLogHandler( &consoleHandler );
+ return 0;
+}
+#endif
diff --git a/Source/Main/scenegraph.cpp b/Source/Main/scenegraph.cpp
new file mode 100644
index 00000000..ab4bcafb
--- /dev/null
+++ b/Source/Main/scenegraph.cpp
@@ -0,0 +1,3179 @@
+//-----------------------------------------------------------------------------
+// Name: scenegraph.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "scenegraph.h"
+
+#include <Graphics/decaltextures.h>
+#include <Graphics/flares.h>
+#include <Graphics/graphics.h>
+#include <Graphics/models.h>
+#include <Graphics/sky.h>
+#include <Graphics/shaders.h>
+#include <Graphics/pxdebugdraw.h>
+#include <Graphics/particles.h>
+#include <Graphics/geometry.h>
+
+#include <Objects/envobject.h>
+#include <Objects/cameraobject.h>
+#include <Objects/terrainobject.h>
+#include <Objects/decalobject.h>
+#include <Objects/hotspot.h>
+#include <Objects/group.h>
+#include <Objects/itemobject.h>
+#include <Objects/movementobject.h>
+#include <Objects/navmeshhintobject.h>
+#include <Objects/navmeshregionobject.h>
+#include <Objects/navmeshconnectionobject.h>
+#include <Objects/lightvolume.h>
+#include <Objects/reflectioncaptureobject.h>
+
+#include <Physics/bulletworld.h>
+#include <Physics/bulletcollision.h>
+#include <Physics/bulletobject.h>
+#include <Physics/physics.h>
+
+#include <Math/vec2math.h>
+#include <Math/vec3math.h>
+#include <Math/vec4math.h>
+
+#include <Internal/snprintf.h>
+#include <Internal/common.h>
+#include <Internal/profiler.h>
+#include <Internal/config.h>
+
+#include <Editors/map_editor.h>
+#include <Editors/actors_editor.h>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <Main/engine.h>
+#include <Internal/stopwatch.h>
+#include <Compat/fileio.h>
+#include <AI/navmesh.h>
+#include <Logging/logdata.h>
+#include <UserInput/input.h>
+#include <Sound/sound.h>
+#include <Game/level.h>
+#include <Utility/assert.h>
+#include <GUI/gui.h>
+
+#include <SDL.h>
+
+#include <cassert>
+#include <algorithm>
+#include <cstring>
+
+extern const bool kUseShadowCache;
+static const bool _draw_collision_shapes = false;
+
+extern bool g_draw_vr;
+extern bool g_simple_water;
+extern bool g_disable_fog;
+extern bool g_simple_shadows;
+extern bool g_level_shadows;
+extern bool g_albedo_only;
+extern bool g_no_reflection_capture;
+extern bool g_no_detailmaps;
+extern bool g_no_decals;
+extern bool g_no_decal_elements;
+extern bool g_character_decals_enabled;
+extern bool g_attrib_envobj_intancing_support;
+extern bool g_attrib_envobj_intancing_enabled;
+extern bool g_ubo_batch_multiplier_force_1x;
+
+const int kGlobalShaderSuffixLen = 1024;
+char global_shader_suffix_storage[kGlobalShaderSuffixLen];
+char* global_shader_suffix = &global_shader_suffix_storage[0];
+
+extern bool g_debug_runtime_disable_scene_graph_draw;
+extern bool g_debug_runtime_disable_scene_graph_draw_depth_map;
+extern bool g_debug_runtime_disable_scene_graph_prepare_lights_and_decals;
+
+static UniformRingBuffer uniform_ring_buffer;
+
+using std::max;
+using std::min;
+
+// These MUST match shader EXACTLY, be careful when changing them
+
+// This is ClusterInfo in the shader
+struct ShaderClusterInfo {
+ unsigned int grid_size[3];
+ unsigned int num_decals;
+ unsigned int num_lights;
+ unsigned int light_cluster_data_offset;
+ unsigned int light_data_offset;
+ unsigned int cluster_width;
+ float inv_proj_mat[16];
+ float viewport[4];
+ float z_near;
+ float z_mult;
+ float pad3;
+ float pad4;
+};
+
+
+// This is in decal data texture buffer
+// must be floats only and divisible by 4
+struct ShaderDecal {
+ float decal_scale[3];
+ float decal_spawn_time;
+ float decal_rotation[4]; // quaternion
+ float decal_position[3];
+ float decal_pad1;
+ float decal_tint[4];
+ float decal_uv[4];
+ float decal_normal[4];
+};
+
+
+// This is PointLightData in the shader
+// also must be floats only and divisible by 4
+struct ShaderLight {
+ float pos[3];
+ float radius;
+ float color[3];
+ float padding;
+};
+
+
+struct DetailObjectSurfaceDrawCall {
+ EnvObject* draw_owner;
+ EnvObject** instance_array;
+ int num_instances;
+};
+
+#define NUM_GRID_COMPONENTS 2
+
+
+SceneGraph::SceneGraph()
+ :
+ particle_system(NULL),
+ terrain_object_(NULL),
+ num_update_objects(0),
+ bullet_world_(NULL),
+ abstract_bullet_world_(NULL),
+ plant_bullet_world_(NULL),
+ queued_level_reset_(false),
+ nav_mesh_(NULL),
+ haze_mult(0.0008f),
+ fog_amount(1.0f)
+ , level_has_been_previously_saved_(false)
+ , cluster_size(128)
+ , num_z_clusters(16)
+ , destruction_sanity_insert_position(0)
+ , destruction_memory_insert_position(0)
+ , infreq_update_index(0)
+ , partial_object_loop_counter(0)
+ , reflection_data_loaded(false)
+ , hotspots_modified_(false)
+{
+ memset(destruction_sanity,0,destruction_sanity_size*sizeof(Object*));
+ for( unsigned i = 0; i < destruction_memory_size; i++ ) {
+ destruction_memory_ids[i] = -1;
+ }
+ memset(destruction_memory_strings,'\0',destruction_memory_size*destruction_memory_string_size);
+
+ const unsigned int num_floats = 3 * 4 + 3 * 4;
+ LOG_ASSERT_EQ(num_floats * sizeof(float), sizeof(ShaderDecal));
+ decal_tbo.resize(kMaxDecals * num_floats, 0.0f);
+
+ // this is mainly used to accumulate decal cluster info before copying to GPU
+ // but also here during initialization to fill buffers with 0
+ decal_cluster_buffer.resize(4096, 0);
+ const char *empty = reinterpret_cast<const char *>(&decal_cluster_buffer[0]);
+
+ decal_data_texture = Textures::Instance()->makeBufferTexture(kMaxDecals * sizeof(ShaderDecal), GL_RGBA32F);
+ Textures::Instance()->SetTextureName(decal_data_texture, "Decal Data");
+ // TODO: should use 3D texture instead
+ decal_cluster_texture = Textures::Instance()->makeBufferTexture(decal_cluster_buffer.size() * 4, GL_R32UI, empty);
+ Textures::Instance()->SetTextureName(decal_cluster_texture, "Decal Clusters");
+}
+
+SceneGraph::~SceneGraph() {
+ LOG_ASSERT(bullet_world_ == NULL);
+ LOG_ASSERT(abstract_bullet_world_ == NULL);
+ LOG_ASSERT(plant_bullet_world_ == NULL);
+ LOG_ASSERT(nav_mesh_ == NULL);
+ LOG_ASSERT(particle_system == NULL);
+ LOG_ASSERT(level == NULL);
+ LOG_ASSERT(map_editor == NULL);
+ LOG_ASSERT(sky == NULL);
+ SDL_assert(bullet_world_ == NULL);
+ SDL_assert(abstract_bullet_world_ == NULL);
+ SDL_assert(plant_bullet_world_ == NULL);
+ SDL_assert(nav_mesh_ == NULL);
+ SDL_assert(particle_system == NULL);
+ SDL_assert(level == NULL);
+ SDL_assert(map_editor == NULL);
+ SDL_assert(sky == NULL);
+}
+
+//-----------------------------------------------------------------------------
+//Functions
+//-----------------------------------------------------------------------------
+
+static void SetIdToObjectMapValue(std::vector<Object*>& object_from_id_map_, int id, Object* value) {
+ if(object_from_id_map_.size() <= id)
+ {
+ object_from_id_map_.resize((id + 1) * 2);
+ }
+ object_from_id_map_[id] = value;
+}
+
+static Object* GetIdToObjectMapValue(std::vector<Object*>& object_from_id_map_, int id) {
+ Object* result = NULL;
+ if(id >= 0 && id < object_from_id_map_.size())
+ {
+ result = object_from_id_map_[id];
+ }
+ return result;
+}
+
+void SceneGraph::AssignID(Object* obj) {
+ int temp_id = obj->GetID();
+ bool say_new_id = false;
+
+ //The id 0 is reserved for the terrain.
+ if( temp_id == 0 && obj->GetType() != _terrain_type ) {
+ temp_id = -1;
+ }
+
+ if(temp_id != -1) {
+ Object* obj_with_id = GetObjectFromID(temp_id);
+ if( temp_id < 0 || (obj_with_id != obj && obj_with_id != NULL) ) {
+ if( temp_id != -1 ) {
+ LOGI << "Object has a taken id: " << *obj << " giving it a new id" << std::endl;
+ LOGI << "It collided with: " << *obj_with_id << std::endl;
+ say_new_id = true;
+ }
+ temp_id = -1;
+ }
+ }
+
+ int id;
+ if(temp_id != -1){
+ id = temp_id;
+ } else {
+ id = GetAndReserveID();
+ if( say_new_id) {
+ LOGI << "Gave object id " << id << std::endl;
+ }
+ obj->SetID(id);
+ }
+
+ SetIdToObjectMapValue(object_from_id_map_, id, obj);
+}
+
+bool SceneGraph::addObject(Object* new_object) {
+ LinkObject(new_object);
+ new_object->scenegraph_ = this;
+ if( new_object->Initialize() == false ) {
+ LOGE << "Failed at initializing object on addObject in scenegraph, unlinking. Type: " << CStringFromEntityType(new_object->GetType()) << " name: " << new_object->GetName() << std::endl;
+ UnlinkObject(new_object);
+ return false;
+ }
+ new_object->GetShaderNames(preload_shaders);
+ return true;
+}
+
+void SceneGraph::LinkObject(Object* new_object) {
+ for( size_t i = 0; i < destruction_sanity_size; i++ ) {
+ if( destruction_sanity[i] == new_object ) {
+ destruction_sanity[i] = NULL;
+ }
+ }
+
+ for( size_t i = 0; i < destruction_memory_size; i++ ) {
+ if( destruction_memory_ids[i] == new_object->GetID() ) {
+ destruction_memory_ids[i] = 0;
+ }
+ }
+
+ objects_.push_back(new_object);
+ if(new_object->collidable){
+ collide_objects_.push_back(new_object);
+ }
+ if(new_object->GetType() != _light_probe_object || new_object->GetType() != _dynamic_light_object) {
+ visible_objects_.push_back(new_object);
+ if(new_object->GetType() == _env_object){
+ visible_static_meshes_.push_back((EnvObject*)new_object);
+ ShadowCacheObjectLightBounds env_object_light_bounds = {0};
+ visible_static_meshes_shadow_cache_bounds_.push_back(env_object_light_bounds);
+ visible_static_mesh_indices_.push_back(visible_static_mesh_indices_.size());
+ visible_objects_need_sort = true;
+ }
+ }
+ switch(new_object->GetType()){
+ case _terrain_type:
+ {
+ terrain_objects_.push_back((TerrainObject*)new_object);
+ ShadowCacheObjectLightBounds terrain_light_bounds = {0};
+ terrain_objects_shadow_cache_bounds_.push_back(terrain_light_bounds);
+ break;
+ }
+ case _movement_object:
+ movement_objects_.push_back(new_object); break;
+ case _item_object:
+ item_objects_.push_back(new_object); break;
+ case _decal_object:
+ decal_objects_.push_back(new_object); break;
+ case _hotspot_object:
+ hotspots_.push_back((Hotspot*)(new_object));
+ hotspots_modified_ = true;
+ break;
+ case _navmesh_hint_object:
+ navmesh_hints_.push_back((NavmeshHintObject*)(new_object)); break;
+ case _navmesh_connection_object:
+ navmesh_connections_.push_back((NavmeshConnectionObject*)(new_object)); break;
+ case _path_point_object:
+ path_points_.push_back(new_object); break;
+ case _light_volume_object:
+ light_volume_objects_.push_back((LightVolumeObject*)new_object); break;
+ default:
+ break;
+ }
+ int id = new_object->GetID();
+ if(id != -1){
+ Object* object_at_id = GetIdToObjectMapValue(object_from_id_map_, id);
+
+ if( object_at_id != NULL )
+ {
+ if( new_object != object_at_id )
+ {
+ if( object_at_id != NULL && new_object != NULL )
+ {
+ LOGD << "Overwriting already assigned object id: " << *(object_at_id)
+ << " with the different object: " << *new_object << std::endl;
+ }
+ }
+ else
+ {
+ LOGD << "Rewriting already assigned object id: " << *(object_at_id) << std::endl;
+ }
+ }
+
+ SetIdToObjectMapValue(object_from_id_map_, id, new_object);
+ }
+}
+
+void SceneGraph::QueueLevelReset() {
+ queued_level_reset_ = true;
+}
+
+Collision SceneGraph::lineCheck(const vec3 &start, const vec3 &end) {
+ std::vector<Collision> collisions;
+ LineCheckAll(start, end, &collisions);
+ Collision *closest = NULL;
+ float closest_distance;
+ for(std::vector<Collision>::iterator it = collisions.begin(); it != collisions.end(); ++it) {
+ Collision &c = *it;
+ float dist = distance_squared(start, c.hit_where);
+ if(!closest || dist < closest_distance){
+ closest = &c;
+ closest_distance = dist;
+ }
+ }
+ if(closest){
+ return *closest;
+ } else {
+ return Collision();
+ }
+}
+
+Collision SceneGraph::lineCheckCollidable(const vec3 &start, const vec3 &end, Object* not_hit) {
+ std::vector<Collision> collisions;
+ LineCheckAll(start, end, &collisions);
+ Collision *closest = NULL;
+ float closest_distance;
+ for(std::vector<Collision>::iterator it = collisions.begin(); it != collisions.end(); ++it) {
+ Collision &c = *it;
+ if(c.hit_what->collidable && c.hit_what != not_hit){
+ float dist = distance_squared(start, c.hit_where);
+ if(!closest || dist < closest_distance){
+ closest = &c;
+ closest_distance = dist;
+ }
+ }
+ }
+ if(closest){
+ return *closest;
+ } else {
+ return Collision();
+ }
+}
+
+void SceneGraph::LineCheckAll(const vec3 &start, const vec3 &end, std::vector<Collision> *collisions) {
+ PROFILER_ZONE(g_profiler_ctx, "SceneGraph::LineCheckAll");
+ for(object_list::iterator it = objects_.begin(); it != objects_.end(); ++it) {
+ Object* obj = *it;
+ vec3 point, normal;
+ int collision_tri = obj->lineCheck(start, end, &point, &normal);
+ if(collision_tri != -1) {
+ Collision c;
+ c.hit = true;
+ c.hit_normal = normal;
+ c.hit_what = obj;
+ c.hit_how = collision_tri;
+ c.hit_where = point;
+ collisions->push_back(c);
+ }
+ }
+}
+
+static std::vector<EnvObject*>* g_static_mesh_draw_sort_entries = NULL;
+
+static bool StaticMeshDrawSortIndices(uint16_t a_index, uint16_t b_index){
+ const EnvObject* a = (*g_static_mesh_draw_sort_entries)[a_index];
+ const EnvObject* b = (*g_static_mesh_draw_sort_entries)[b_index];
+ if(a->transparent != b->transparent) {
+ return a->transparent < b->transparent;
+ } else if(a->ofr->shader_name != b->ofr->shader_name){
+ return a->ofr->shader_name < b->ofr->shader_name;
+ } else if(a->ofr->path_ != b->ofr->path_) {
+ return a->ofr->path_ < b->ofr->path_;
+ } else {
+ return a->winding_flip < b->winding_flip;
+ }
+}
+
+static void DrawQuad(int shader_id) {
+ Shaders* shaders = Shaders::Instance();
+ Graphics *graphics = Graphics::Instance();
+ int vert_attrib_id = shaders->returnShaderAttrib("vert_attrib", shader_id);
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ glVertexAttribPointer(vert_attrib_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0);
+ graphics->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ graphics->ResetVertexAttribArrays();
+}
+
+extern VBOContainer quad_vert_vbo;
+extern VBOContainer quad_index_vbo;
+
+static void UpdateShaderSuffix(SceneGraph* scenegraph, Object::DrawType object_draw_type) {
+ const int kShaderStrSize = 1024;
+ char buf[2][kShaderStrSize];
+ char* shader_str[2] = {buf[0], buf[1]};
+ FormatString(shader_str[0], kShaderStrSize, "");
+ if(object_draw_type == Object::kFullDraw){
+ if(g_simple_shadows || !g_level_shadows){
+ FormatString(shader_str[1], kShaderStrSize, "%s #SIMPLE_SHADOW", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(scenegraph->light_probe_collection.probe_lighting_enabled &&
+ scenegraph->light_probe_collection.light_probe_buffer_object_id != -1 &&
+ !scenegraph->light_probe_collection.light_probes.empty())
+ {
+ FormatString(shader_str[1], kShaderStrSize, "%s #CAN_USE_LIGHT_PROBES", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(g_albedo_only){
+ FormatString(shader_str[1], kShaderStrSize, "%s #ALBEDO_ONLY", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(g_simple_water){
+ FormatString(shader_str[1], kShaderStrSize, "%s #SIMPLE_WATER", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(g_disable_fog){
+ FormatString(shader_str[1], kShaderStrSize, "%s #DISABLE_FOG", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(g_no_reflection_capture){
+ FormatString(shader_str[1], kShaderStrSize, "%s #NO_REFLECTION_CAPTURE", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(g_no_detailmaps){
+ FormatString(shader_str[1], kShaderStrSize, "%s #NO_DETAILMAPS", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(g_no_decals){
+ FormatString(shader_str[1], kShaderStrSize, "%s #NO_DECALS", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(config["ssao"].toNumber<bool>()){
+ FormatString(shader_str[1], kShaderStrSize, "%s #SSAO_TEST", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(config["volume_shadows"].toNumber<bool>()){
+ FormatString(shader_str[1], kShaderStrSize, "%s #VOLUME_SHADOWS", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ }
+ if(object_draw_type == Object::kFullDraw || object_draw_type == Object::kDrawDepthOnly || object_draw_type == Object::kDrawDepthNoAA || Object::kDrawAllShadowCascades) {
+ static int ubo_batch_size_multiplier = 1;
+ static GLint max_ubo_size = -1;
+ if(max_ubo_size == -1) {
+ glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &max_ubo_size);
+
+ if(max_ubo_size >= 131072) {
+ ubo_batch_size_multiplier = 8;
+ } else if(max_ubo_size >= 65536) {
+ ubo_batch_size_multiplier = 4;
+ } else if(max_ubo_size >= 32768) {
+ ubo_batch_size_multiplier = 2;
+ }
+ }
+
+ if(!g_ubo_batch_multiplier_force_1x && ubo_batch_size_multiplier >= 8) {
+ FormatString(shader_str[1], kShaderStrSize, "%s #UBO_BATCH_SIZE_8X", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ } else if(!g_ubo_batch_multiplier_force_1x && ubo_batch_size_multiplier >= 4) {
+ FormatString(shader_str[1], kShaderStrSize, "%s #UBO_BATCH_SIZE_4X", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ } else if(!g_ubo_batch_multiplier_force_1x && ubo_batch_size_multiplier >= 2) {
+ FormatString(shader_str[1], kShaderStrSize, "%s #UBO_BATCH_SIZE_2X", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+
+ if(g_attrib_envobj_intancing_support && g_attrib_envobj_intancing_enabled) {
+ FormatString(shader_str[1], kShaderStrSize, "%s #ATTRIB_ENVOBJ_INSTANCING", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ }
+ if(Graphics::Instance()->use_sample_alpha_to_coverage && object_draw_type != Object::kDrawDepthNoAA && object_draw_type != Object::kDrawAllShadowCascades){
+ FormatString(shader_str[1], kShaderStrSize, "%s #ALPHA_TO_COVERAGE", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(object_draw_type != Object::kFullDraw){
+ FormatString(shader_str[1], kShaderStrSize, "%s #DEPTH_ONLY #NO_INSTANCE_ID", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(object_draw_type == Object::kDrawAllShadowCascades){
+ FormatString(shader_str[1], kShaderStrSize, "%s #SHADOW_CASCADE", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(scenegraph->light_probe_collection.light_volume_enabled && scenegraph->light_probe_collection.ambient_3d_tex.valid()){
+ FormatString(shader_str[1], kShaderStrSize, "%s #CAN_USE_3D_TEX", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(!(Graphics::Instance()->config_.motion_blur_amount_ > 0.01f)){
+ FormatString(shader_str[1], kShaderStrSize, "%s #NO_VELOCITY_BUF", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(scenegraph->level->script_params().HasParam("Custom Shader") && config["custom_level_shaders"].toNumber<bool>()){
+ const std::string& custom_shader = scenegraph->level->script_params().GetStringVal("Custom Shader");
+ if(!custom_shader.empty()) {
+ FormatString(shader_str[1], kShaderStrSize, "%s %s", shader_str[0], custom_shader.c_str());
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ }
+ if(Graphics::Instance()->config_.simple_fog()) {
+ FormatString(shader_str[1], kShaderStrSize, "%s %s", shader_str[0], "#SIMPLE_FOG");
+ std::swap(shader_str[0], shader_str[1]);
+ }
+
+ for(int length = strlen(shader_str[0]), i = 0;
+ i < length && shader_str[0][0] == ' ';
+ ++i, shader_str[0]++);
+
+ FormatString(global_shader_suffix_storage, kGlobalShaderSuffixLen, "%s", shader_str[0]);
+}
+
+extern bool last_ofr_is_valid;
+
+//Draw all objects
+void SceneGraph::Draw(SceneGraph::SceneDrawType scene_draw_type) {
+ if (g_debug_runtime_disable_scene_graph_draw) {
+ return;
+ }
+
+ Graphics *graphics = Graphics::Instance();
+ Camera* camera = ActiveCameras::Get();
+
+ UpdateShaderSuffix(this, Object::kFullDraw);
+
+ graphics->setDepthFunc(GL_LEQUAL);
+ if(nav_mesh_ && (nav_mesh_renderer_.IsNavMeshVisible() || nav_mesh_renderer_.IsCollisionMeshVisible())){
+ glClearColor(0.4f,0.4f,0.4f,1.0f);
+ graphics->Clear(true);
+
+ nav_mesh_renderer_.Draw();
+ }
+
+ if(graphics->queued_screenshot && graphics->screenshot_mode == Graphics::kTransparentGameplay){
+ glClearColor(0.0f,0.0f,0.0f,0.0f);
+ graphics->Clear(true);
+ }
+
+ // List static meshes
+ PROFILER_ENTER(g_profiler_ctx, "Draw env object batches");
+ if(visible_objects_need_sort){
+ PROFILER_ENTER(g_profiler_ctx, "Sort visible objects");
+ g_static_mesh_draw_sort_entries = &visible_static_meshes_;
+ std::sort(visible_static_mesh_indices_.begin(), visible_static_mesh_indices_.end(), StaticMeshDrawSortIndices);
+ visible_objects_need_sort = false;
+ PROFILER_LEAVE(g_profiler_ctx);
+ }
+ static std::vector<EnvObject*> static_meshes_to_draw;
+ static_meshes_to_draw.clear();
+ static_meshes_to_draw.reserve(visible_static_meshes_.size());
+ PROFILER_ENTER(g_profiler_ctx, "List visible objects / frustum cull");
+ if( !nav_mesh_renderer_.IsCollisionMeshVisible() )
+ {
+ for(std::vector<uint16_t>::iterator it = visible_static_mesh_indices_.begin(); it != visible_static_mesh_indices_.end(); ++it) {
+ uint16_t index = *it;
+ EnvObject* eo = visible_static_meshes_[index];
+ if(!eo->transparent && eo->enabled_) {
+ if(camera->checkSphereInFrustum(eo->sphere_center_, eo->sphere_radius_)){
+ static_meshes_to_draw.push_back(eo);
+ }
+ }
+ }
+ }
+ PROFILER_LEAVE(g_profiler_ctx);
+ PROFILER_ENTER(g_profiler_ctx, "Issue draw calls");
+ mat4 proj_view_mat = camera->GetProjMatrix() * camera->GetViewMatrix();
+ mat4 prev_proj_view_mat = camera->GetProjMatrix() * camera->prev_view_mat;
+ vec3 cam_pos = camera->GetPos();
+ std::vector<mat4> shadow_matrix;
+ shadow_matrix.resize(4);
+ for(int i=0; i<4; ++i){
+ shadow_matrix[i] = camera->biasMatrix * graphics->cascade_shadow_mat[i];
+ }
+ int batch_start = 0;
+
+ if(!graphics->drawing_shadow && !(graphics->queued_screenshot && graphics->screenshot_mode == 1)){
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw sky");
+ sky->Draw(this);
+ }
+
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw flares");
+ flares.Draw(Flares::kDiffuse);
+ }
+
+ static std::vector<DetailObjectSurfaceDrawCall> detail_objects_surfaces_to_draw;
+ detail_objects_surfaces_to_draw.clear();
+ detail_objects_surfaces_to_draw.reserve(visible_static_meshes_.size());
+
+ last_ofr_is_valid = false;
+ for(int i=1, len=static_meshes_to_draw.size(); i<=len; ++i) {
+ if(i == len || (static_meshes_to_draw[i]->ofr->path_ != static_meshes_to_draw[i-1]->ofr->path_ ||
+ static_meshes_to_draw[i]->winding_flip != static_meshes_to_draw[i-1]->winding_flip))
+ {
+ static_meshes_to_draw[i-1]->DrawInstances(&static_meshes_to_draw[batch_start], i-batch_start, proj_view_mat, prev_proj_view_mat, &shadow_matrix, cam_pos, Object::kFullDraw);
+ if(static_meshes_to_draw[i-1]->HasDetailObjectSurfaces()){
+ detail_objects_surfaces_to_draw.push_back({ static_meshes_to_draw[i-1], &static_meshes_to_draw[batch_start], i-batch_start });
+ }
+ batch_start = i;
+ }
+ }
+ for(int i = 0; i < detail_objects_surfaces_to_draw.size(); ++i) {
+ auto current = detail_objects_surfaces_to_draw[i];
+ current.draw_owner->DrawDetailObjectInstances(current.instance_array, current.num_instances, Object::kFullDraw);
+ }
+ if(g_draw_collision){
+ batch_start = 0;
+ last_ofr_is_valid = false;
+ for(int i=1, len=static_meshes_to_draw.size(); i<=len; ++i) {
+ if(i == len || (static_meshes_to_draw[i]->ofr->path_ != static_meshes_to_draw[i-1]->ofr->path_ ||
+ static_meshes_to_draw[i]->winding_flip != static_meshes_to_draw[i-1]->winding_flip))
+ {
+ if(static_meshes_to_draw[i-1]->ofr->bush_collision){
+ if(static_meshes_to_draw[i-1]->plant_component_.get()){
+ HullCache* cache = static_meshes_to_draw[i-1]->plant_component_->GetHullCache();
+ if(cache){
+ for(int instance = batch_start; instance<i; ++instance){
+ EnvObject* eo = static_meshes_to_draw[instance];
+ for(unsigned face_index=0; face_index < cache->faces.size(); face_index+=3){
+ for(int side=0; side<3; ++side){
+ DebugDraw::Instance()->AddLine(eo->GetTransform() * cache->verts[cache->faces[face_index+side]], eo->GetTransform() * cache->verts[cache->faces[face_index+(side+1)%3]], vec4(0.0f,1.0f,0.0f,1.0f), _delete_on_draw);
+ }
+ }
+ }
+ }
+ }
+ } else {
+ glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+ static_meshes_to_draw[i-1]->DrawInstances(&static_meshes_to_draw[batch_start], i-batch_start, proj_view_mat, prev_proj_view_mat, &shadow_matrix, cam_pos, Object::kWireframe);
+ // Intentionally not drawing detail object surfaces
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+ }
+ batch_start = i;
+ }
+ }
+ }
+ PROFILER_LEAVE(g_profiler_ctx);
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ {
+ // Update whether character decals should be enabled/disabled
+ bool character_decals_enabled = false;
+ if(config["custom_level_shaders"].toNumber<bool>() && level->script_params().HasParam("Custom Shader")){
+ const std::string& custom_shader = level->script_params().GetStringVal("Custom Shader");
+ if(!custom_shader.empty()) {
+ character_decals_enabled = custom_shader.find("#CHARACTER_DECALS") != std::string::npos;
+ }
+ }
+ g_character_decals_enabled = character_decals_enabled;
+ }
+
+ {
+ //First non transparent, then transparent objects.
+ PROFILER_ZONE(g_profiler_ctx, "Draw objects");
+ for(object_list::iterator it = visible_objects_.begin(); it != visible_objects_.end(); ++it) {
+ Object& obj = *(*it);
+ const EntityType& obj_type = obj.GetType();
+ switch(obj_type){
+ case _decal_object:
+ case _env_object:
+ case _hotspot_object:
+ case _group:
+ continue;
+ case _terrain_type:
+ break;
+ default:
+ if(scene_draw_type == kStaticOnly){
+ continue;
+ }
+ }
+ if(obj.enabled_ && !obj.transparent) {
+ PROFILER_GPU_ZONE(g_profiler_ctx,"%s %d draw",CStringFromEntityType(obj.GetType()),obj.GetID());
+ bool output_velocity_buffer = (graphics->config_.motion_blur_amount_ > 0.01f)
+ && ( (obj_type == _movement_object) || (obj_type == _item_object) );
+ if (output_velocity_buffer) {
+ GLenum buffers[] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT };
+ glDrawBuffers(2, buffers);
+ }
+ //We want to hide the terrain if we're rendering the navmesh.
+ if( obj_type == _terrain_type && nav_mesh_renderer_.IsCollisionMeshVisible() ) {
+ continue;
+ }
+ obj.ReceiveObjectMessage(OBJECT_MSG::DRAW);
+ if(output_velocity_buffer){
+ GLenum buffers[] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT };
+ glDrawBuffers(1, buffers);
+ }
+ }
+ }
+
+ bool draw_transparent = false;
+ if(!nav_mesh_renderer_.IsCollisionMeshVisible()){
+ // Iterating backwards because transparent objects are last in the list
+ for(std::vector<uint16_t>::reverse_iterator it = visible_static_mesh_indices_.rbegin(); it != visible_static_mesh_indices_.rend(); ++it) {
+ uint16_t index = *it;
+ EnvObject* eo = visible_static_meshes_[index];
+ if(eo->enabled_){
+ // Whatever the last enabled object in the list is will determine if there are any transparent objects
+ draw_transparent = eo->transparent;
+ break;
+ }
+ }
+ }
+
+ if(draw_transparent) {
+ if(!g_simple_water)
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Update depth/color textures");
+ if (graphics->multisample_framebuffer_exists) {
+ PROFILER_ZONE(g_profiler_ctx, "Blit from MSAA buffers");
+ graphics->PushFramebuffer();
+ graphics->BlitColorBuffer();
+ graphics->BlitDepthBuffer();
+ graphics->PopFramebuffer();
+ }
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Copy screen_color_tex to temp_screen_tex");
+ GLState gl_state;
+ gl_state.blend = false;
+ gl_state.cull_face = false;
+ gl_state.depth_test = false;
+ gl_state.depth_write = false;
+ graphics->setGLState(gl_state);
+
+ Shaders* shaders = Shaders::Instance();
+ Textures* textures = Textures::Instance();
+
+ graphics->PushViewport();
+ graphics->PushFramebuffer();
+
+ graphics->setViewport(0,0,graphics->render_dims[0],graphics->render_dims[1]);
+ graphics->bindFramebuffer(graphics->post_effects.post_framebuffer);
+
+ int shader_id = shaders->returnProgram(graphics->post_shader_name);
+ shaders->setProgram(shader_id);
+ shaders->SetUniformInt("screen_width",graphics->render_dims[0]);
+ shaders->SetUniformInt("screen_height",graphics->render_dims[1]);
+
+ textures->bindTexture(graphics->screen_color_tex);
+ graphics->framebufferColorTexture2D(graphics->post_effects.temp_screen_tex);
+ quad_vert_vbo.Bind();
+ quad_index_vbo.Bind();
+ DrawQuad(shader_id);
+
+ graphics->PopFramebuffer();
+ graphics->PopViewport();
+ }
+ }
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Draw transparent objects");
+ for(std::vector<EnvObject*>::iterator it = visible_static_meshes_.begin(); it != visible_static_meshes_.end(); ++it) {
+ EnvObject& obj = *(*it);
+ if(obj.enabled_ && obj.transparent){
+ PROFILER_ZONE(g_profiler_ctx,"%s %d draw",CStringFromEntityType(((Object*)&obj)->GetType()),obj.GetID());
+ // Avoid calling EnvObject::Draw repeatedly, so matrices etc can be shared instead of reacquired for every draw call
+ // TODO: last_ofr_is_valid is set to false in EnvObject::Draw - is it important?
+ obj.DrawInstances(&(*it), 1, proj_view_mat, prev_proj_view_mat, &shadow_matrix, cam_pos, Object::kFullDraw);
+ obj.DrawDetailObjectInstances(&(*it), 1, Object::kFullDraw);
+ }
+ }
+ }
+ }
+ }
+
+ {
+ graphics->SetBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ if(graphics->use_sample_alpha_to_coverage){
+ glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);
+ }
+ if(!Graphics::Instance()->media_mode() && map_editor->state_ != MapEditor::kInGame){
+ for(object_list::iterator it = decal_objects_.begin(); it != decal_objects_.end(); ++it) {
+ Object &obj = *(*it);
+ if(obj.enabled_ && obj.Selected() && obj.editor_visible) {
+ PROFILER_ZONE(g_profiler_ctx,"%s %d draw",CStringFromEntityType(obj.GetType()),obj.GetID());
+ obj.ReceiveObjectMessage(OBJECT_MSG::DRAW);
+ }
+ }
+ }
+ }
+ if(_draw_collision_shapes){
+ if( bullet_world_ )
+ bullet_world_->Draw(sky->GetSpecularCubeMapTexture());
+ //abstract_bullet_world->Draw();
+ //plant_bullet_world->Draw();
+ }
+
+ // Draw hotspots
+ if(map_editor->state_ == MapEditor::kInGame || map_editor->IsTypeEnabled(_hotspot_object)){
+ PROFILER_ZONE(g_profiler_ctx,"Draw Hotspots");
+ for (object_list::iterator it = hotspots_.begin(); it != hotspots_.end(); ++it) {
+ ((Hotspot*)(*it))->Draw();
+ }
+ }
+ {
+ if(!bullet_world_){
+ FatalError("Error", "No bullet world");
+ }
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw particles");
+ particle_system->Draw(this);
+ if(level->script_params().HasParam("GPU Particle Field") && config["particle_field"].toNumber<bool>()){
+ const std::string& gpu_particle_field = level->script_params().GetStringVal("GPU Particle Field");
+ if(!gpu_particle_field.empty()) {
+ DrawGPUParticleField(this, gpu_particle_field.c_str());
+ }
+ }
+ }
+}
+
+Object* SceneGraph::GetLastSelected() {
+ Object * res = NULL;
+ for( unsigned i = 0; i < objects_.size(); i++ ) {
+ if( res == NULL || objects_[i]->Selected() > res->Selected() ) {
+ res = objects_[i];
+ }
+ }
+ return res;
+}
+
+void SceneGraph::ReturnSelected(std::vector<Object*>* selected) {
+ for( unsigned i = 0; i < objects_.size(); i++ ) {
+ if( objects_[i]->Selected() ) {
+ selected->push_back(objects_[i]);
+ }
+ }
+}
+
+void SceneGraph::UnselectAll() {
+ for( unsigned i = 0; i < objects_.size(); i++ ) {
+ if( objects_[i]->Selected() ) {
+ objects_[i]->Select(false);
+ }
+ }
+}
+
+//Update all objects
+void SceneGraph::Update(float timestep, float curr_game_time) {
+ if(queued_level_reset_){
+ PROFILER_ZONE(g_profiler_ctx, "Resetting level");
+ queued_level_reset_ = false;
+ Level::CollisionPtrMap blank;
+ level->HandleCollisions(blank, *this );
+ SendMessageToAllObjects(OBJECT_MSG::RESET);
+ level->Message("post_reset");
+ }
+
+ //uint64_t start_count = SDL_GetPerformanceCounter();
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Bullet world update");
+ bullet_world_->Update(timestep);
+ }
+ //uint64_t end_count = SDL_GetPerformanceCounter();
+ plant_bullet_world_->Update(timestep);
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Abstract world collisions");
+ level->HandleCollisions(abstract_bullet_world_->GetCollisions(), *this );
+ }
+
+ {
+ //Only updating specific subtypes of objects? -Max
+ PROFILER_ZONE(g_profiler_ctx, "Object updates");
+ for(int i=0; i<num_update_objects; ++i){
+ Object* obj = update_objects_[i];
+ PROFILER_ZONE(g_profiler_ctx, "%s %d update", CStringFromEntityType(obj->GetType()),obj->GetID());
+ obj->ReceiveObjectMessage(OBJECT_MSG::UPDATE, timestep);
+ }
+ /*
+ for(object_list::iterator it = objects_.begin(); it != objects_.end();) {
+ Object* obj = *it;
+ if(obj->active){
+ PROFILER_ZONE(g_profiler_ctx,"%s %d update",CStringFromEntityType(obj->GetType()),obj->GetID());
+ obj->Update();
+ }
+ ++it;
+ if(obj->to_delete){
+ UnlinkObject(obj);
+ delete obj;
+ }
+ }*/
+ }
+
+ //Checking sanity, a couple of objects at a time
+ int partial_loop_countdown = 10;
+ size_t objects_size = objects_.size();
+ while( objects_size > 0 && partial_loop_countdown > 0 ) {
+ if( partial_object_loop_counter >= objects_size ) {
+ partial_object_loop_counter = 0;
+ }
+
+ int i = partial_object_loop_counter;
+
+ ObjectSanityState oss = objects_[i]->GetSanity();
+
+ if( oss.Ok() ) {
+ for( unsigned k = 0; k < kMaxWarnings; k++ ) {
+ if( sanity_list[k].GetID() == oss.GetID() ) {
+ sanity_list[k] = ObjectSanityState();
+ }
+ }
+ } else {
+ int index = -1;
+ for( unsigned k = 0; k < kMaxWarnings; k++ ) {
+ if( sanity_list[k].GetID() == oss.GetID() ) {
+ index = k;
+ } else if( sanity_list[k].GetID() == -1 ) {
+ if( index == -1 ) {
+ index = k;
+ }
+ }
+ }
+
+ if( index != -1 ) {
+ sanity_list[index] = oss;
+ }
+ }
+
+ partial_object_loop_counter++;
+ partial_loop_countdown--;
+ }
+
+ if(nav_mesh_ && (nav_mesh_renderer_.IsNavMeshVisible() || nav_mesh_renderer_.IsCollisionMeshVisible())){
+ PROFILER_ZONE(g_profiler_ctx, "Navmesh update");
+ nav_mesh_->Update();
+ }
+
+ //Update hotspots.
+
+ // If a hotspot adds another hotspot, the iterator risks being invalidated,
+ // so track which objects have been modified, and then find the correct index to keep updating at
+ hotspots_modified_ = false;
+ object_list updated_hotspots;
+ updated_hotspots.reserve(hotspots_.size());
+ for (object_list::iterator it = hotspots_.begin(); it != hotspots_.end(); ++it) {
+ PROFILER_ZONE(g_profiler_ctx, "Hotspot update");
+ updated_hotspots.push_back(*it);
+ (*it)->ReceiveObjectMessage(OBJECT_MSG::UPDATE, timestep);
+
+ if(hotspots_modified_) {
+ for(int i = (int)updated_hotspots.size() - 1; i >= 0; --i) {
+ it = std::find(hotspots_.begin(), hotspots_.end(), updated_hotspots[i]);
+ if(it != hotspots_.end())
+ break;
+ }
+
+ if(it == hotspots_.end())
+ it = hotspots_.begin();
+ hotspots_modified_ = false;
+ }
+ }
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Particle update");
+ particle_system->Update(this, timestep, curr_game_time);
+ }
+
+ if( objects_.size() > 0 ) {
+ for( int c = 0; c < 64; c++ ) {
+ if( infreq_update_index >= objects_.size() ) {
+ infreq_update_index = 0;
+ }
+ objects_[infreq_update_index]->ReceiveObjectMessage(OBJECT_MSG::INFREQUENT_UPDATE);
+ infreq_update_index++;
+ }
+ }
+}
+
+static bool RemoveObjFromList(Object* o, SceneGraph::object_list* list){
+ SceneGraph::object_list::iterator it = std::find(list->begin(), list->end(), o);
+ if (it != list->end()) {
+ list->erase(it);
+ return true;
+ }
+ return false;
+}
+
+template <typename T>
+static bool RemoveDerivedObjFromList(EntityType type, Object* o, std::vector<T*>* list) {
+ if (o->GetType() == type) {
+ typename std::vector<T*>::iterator it = std::find(list->begin(), list->end(), (T*)o);
+ if (it != list->end()) {
+ list->erase(it);
+ return true;
+ }
+ }
+ return false;
+}
+
+template <typename T, typename U>
+static bool RemoveDerivedObjFromList(EntityType type, Object* o, std::vector<T*>* list, std::vector<U>* second_parallel_list) {
+ if (o->GetType() == type) {
+ typename std::vector<T*>::iterator it = std::find(list->begin(), list->end(), (T*)o);
+ if (it != list->end()) {
+ auto distance_from_begin = std::distance(list->begin(), it);
+ list->erase(it);
+ second_parallel_list->erase(second_parallel_list->begin() + distance_from_begin);
+ return true;
+ }
+ }
+ return false;
+}
+
+template <typename T, typename U, typename I>
+static bool RemoveDerivedObjFromListAndIndex(EntityType type, Object* o, std::vector<T*>* list, std::vector<U>* second_parallel_list, std::vector<I>* index_list) {
+ if (o->GetType() == type) {
+ typename std::vector<T*>::iterator it = std::find(list->begin(), list->end(), (T*)o);
+ if (it != list->end()) {
+ auto distance_from_begin = std::distance(list->begin(), it);
+ list->erase(it);
+ second_parallel_list->erase(second_parallel_list->begin() + distance_from_begin);
+ auto index_list_it = std::find(index_list->begin(), index_list->end(), (I)distance_from_begin);
+ index_list->erase(index_list_it);
+ for(int i = 0; i < index_list->size(); ++i){ // Decrement all above the removed index
+ (*index_list)[i] = (*index_list)[i] - ((*index_list)[i] > (I)distance_from_begin ? 1 : 0);
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+void SceneGraph::UnlinkObject(Object *o) {
+ int id = o->GetID();
+ Object* object_at_id = GetIdToObjectMapValue(object_from_id_map_, id);
+
+ LOGD << "Unlinking object: " << *o << std::endl;
+ destruction_sanity_insert_position = (destruction_sanity_insert_position+1)%destruction_sanity_size;
+ destruction_sanity[destruction_sanity_insert_position] = o;
+
+ destruction_memory_insert_position = (destruction_memory_insert_position+1)%destruction_memory_size;
+
+ destruction_memory_ids[destruction_memory_insert_position] = o->GetID();
+ o->GetDisplayName(&destruction_memory_strings[destruction_memory_insert_position*destruction_memory_string_size], destruction_memory_string_size-1);
+
+ if(object_at_id != NULL){
+ if( o != object_at_id)
+ {
+ LOGW << "Object removal mismatch." << std::endl;
+ }
+
+ SetIdToObjectMapValue(object_from_id_map_, id, NULL);
+ }
+
+ if(o == terrain_object_){
+ terrain_object_ = NULL;
+ }
+
+ RemoveObjFromList(o, &visible_objects_);
+ RemoveDerivedObjFromListAndIndex(_env_object, o, &visible_static_meshes_, &visible_static_meshes_shadow_cache_bounds_, &visible_static_mesh_indices_);
+ RemoveDerivedObjFromList(_terrain_type, o, &terrain_objects_, &terrain_objects_shadow_cache_bounds_);
+ RemoveObjFromList(o, &collide_objects_);
+ RemoveObjFromList(o, &movement_objects_);
+ RemoveObjFromList(o, &item_objects_);
+ RemoveObjFromList(o, &decal_objects_);
+ RemoveObjFromList(o, &objects_);
+ if(RemoveObjFromList(o, &hotspots_))
+ hotspots_modified_ = true;
+ RemoveObjFromList(o, &navmesh_hints_);
+ RemoveObjFromList(o, &navmesh_connections_);
+ RemoveObjFromList(o, &path_points_);
+ RemoveDerivedObjFromList(_light_volume_object, o, &light_volume_objects_);
+
+ for (decal_deque::iterator it = dynamic_decals.begin(); it != dynamic_decals.end();) {
+ DecalObject *obj = *it;
+ if(obj == o){
+ it = dynamic_decals.erase(it);
+ } else {
+ ++it;
+ }
+ }
+
+ for( unsigned i = 0; i < kMaxWarnings; i++ ) {
+ if( sanity_list[i].GetID() == id ) {
+ sanity_list[i] = ObjectSanityState();
+ }
+ }
+}
+
+Object* SceneGraph::GetObjectFromID(int object_id) {
+ Object* object_at_id = GetIdToObjectMapValue(object_from_id_map_, object_id);
+ if(object_at_id != NULL){
+ return object_at_id;
+ } else {
+ LOGW << "Requested an object with id " << object_id << " but found none. Last info known of this id is: " << GetDestroyedObjectInfo(object_id) << std::endl;
+ }
+ return NULL;
+}
+
+bool SceneGraph::DoesObjectWithIdExist(int object_id) {
+ Object* object_at_id = GetIdToObjectMapValue(object_from_id_map_, object_id);
+ bool result = object_at_id != NULL;
+ return result;
+}
+
+std::vector<Object*> SceneGraph::GetObjectsOfType(enum EntityType type)
+{
+ std::vector<Object*> ret;
+
+ std::vector<Object*>::iterator objit;
+
+ for( objit = objects_.begin(); objit != objects_.end(); objit++ )
+ {
+ if( (*objit)->GetType() == type )
+ {
+ ret.push_back( *objit );
+ }
+ }
+
+ return ret;
+}
+
+void SceneGraph::CreateNavMesh() {
+ if(map_editor->GetTerrainPreviewMode()) {
+ DisplayMessage("Cannot create navmesh", "It is not possible to create navmesh while previewing terrain");
+ return;
+ }
+
+ PROFILER_ZONE(g_profiler_ctx, "CreateNavMesh");
+ if(nav_mesh_){
+ delete nav_mesh_;
+ }
+ nav_mesh_ = new NavMesh();
+
+ std::vector<Object*> regions = GetObjectsOfType(_navmesh_region_object);
+
+ if( regions.size() > 0 )
+ {
+ if(regions.size() > 1 )
+ {
+ LOGW << "THere is more than one region object, using the first one, please remove all others" << std::endl;
+ }
+
+ nav_mesh_->SetExplicitBounderies(
+ ((NavmeshRegionObject*)regions[0])->GetMinBounds(),
+ ((NavmeshRegionObject*)regions[0])->GetMaxBounds()
+ );
+ }
+
+ AddSceneToNavmesh();
+ nav_mesh_->CalcNavMesh();
+ Graphics::Instance()->nav_mesh_out_of_date = false;
+
+ nav_mesh_renderer_.LoadNavMesh( nav_mesh_ );
+}
+
+void SceneGraph::SaveNavMesh()
+{
+ PROFILER_ZONE(g_profiler_ctx, "SaveNavMesh");
+ if(!nav_mesh_){
+ LOGE << "Unable to save navmesh, none is generated." << std::endl;
+ return;
+ }
+ if(level_path_.isValid() == false){
+ LOGE << "Unable to save navmesh, level has no path." << std::endl;
+ return;
+ }
+ LOGI << "Saving NAVMESH." << std::endl;
+ nav_mesh_->Save(level_name_,level_path_);
+}
+
+bool SceneGraph::LoadNavMesh() {
+ delete nav_mesh_;
+ nav_mesh_ = new NavMesh();
+ if(!nav_mesh_->Load(level_name_,level_path_)){
+ delete nav_mesh_;
+ nav_mesh_ = NULL;
+ Graphics::Instance()->nav_mesh_out_of_date = true;
+ Graphics::Instance()->nav_mesh_out_of_date_chunk = -1;
+ }
+
+ nav_mesh_renderer_.LoadNavMesh( nav_mesh_ );
+
+ return nav_mesh_ != NULL;
+}
+
+void SceneGraph::AddSceneToNavmesh() {
+ nav_mesh_->SetNavMeshParameters(level->nav_mesh_parameters_);
+ object_list::iterator iter = collide_objects_.begin();
+ for(; iter != collide_objects_.end(); ++iter){
+ Object* obj = (*iter);
+ if(obj->GetType() == _env_object && !((EnvObject*)obj)->ofr->no_collision && !((EnvObject*)obj)->no_navmesh){
+ //Check that this object doesn't have a movementobject parent (meaning it is dynamic)
+ EnvObject *env_obj = (EnvObject*)obj;
+ if(env_obj->attached_ == NULL)
+ {
+ int model_id = env_obj->GetCollisionModelID();
+ if(model_id != -1){
+ const Model* model = &Models::Instance()->GetModel(model_id);
+ nav_mesh_->AddMesh(model->vertices, model->faces, env_obj->GetTransform());
+ }
+ }
+ }
+ if(obj->GetType() == _terrain_type){
+ TerrainObject *terrain_obj = (TerrainObject*)obj;
+ const Model* model = terrain_obj->GetModel();
+ mat4 identity;
+ nav_mesh_->AddMesh(model->vertices, model->faces, identity);
+ }
+ }
+
+ {
+ std::vector<float> verts;
+ std::vector<unsigned> faces;
+
+ GetUnitBoxVertArray(verts, faces);
+
+ for(iter = navmesh_hints_.begin(); iter != navmesh_hints_.end(); ++iter){
+ nav_mesh_->AddMesh( verts, faces, (*iter)->GetTransform() );
+ }
+ }
+
+ std::set<std::pair<int,int> > offmesh_connections;
+
+ for(iter = navmesh_connections_.begin(); iter != navmesh_connections_.end(); ++iter){
+ NavmeshConnectionObject* obj = static_cast<NavmeshConnectionObject*>(*iter);
+
+ obj->ResetConnectionOffMeshReference();
+
+ int first_id = obj->GetID();
+ int second_id;
+
+ std::vector<NavMeshConnectionData>::iterator idit = obj->connections.begin();
+ for( ;idit != obj->connections.end(); idit++ )
+ {
+ second_id = idit->other_object_id;
+ //We always add the highest value first to ensure the connections are unique. (two-way assumed)
+ if( first_id > second_id )
+ {
+ offmesh_connections.insert( std::pair<int,int>(first_id, second_id) );
+ }
+ else if( second_id > first_id )
+ {
+ offmesh_connections.insert( std::pair<int,int>(second_id, first_id) );
+ }
+ else
+ {
+ LOGE << "Offmesh connection is self-referential. \"" << first_id << "\"" << std::endl;
+ }
+ }
+ }
+
+ std::set<std::pair<int,int> >::iterator pair_it = offmesh_connections.begin();
+
+ nav_mesh_->getInputGeom().deleteAllOffMeshConnections();
+
+ int id_counter = 1000;
+
+ for(; pair_it != offmesh_connections.end(); pair_it++ )
+ {
+ Object* first = GetObjectFromID( pair_it->first );
+ Object* second = GetObjectFromID( pair_it->second );
+
+ if( first && second )
+ {
+ if( first->GetType() == _navmesh_connection_object )
+ {
+ NavmeshConnectionObject* first_nco = static_cast<NavmeshConnectionObject*>(first);
+ first_nco->UpdatePolyAreas();
+ }
+
+ if( second->GetType() == _navmesh_connection_object )
+ {
+ NavmeshConnectionObject* second_nco = static_cast<NavmeshConnectionObject*>(second);
+ second_nco->UpdatePolyAreas();
+ }
+
+ SamplePolyAreas area = SAMPLE_POLYAREA_DISABLED;
+
+ //TODO: We might want to assert that the poly areas are the same from both connections.
+ if( first->GetType() == _navmesh_connection_object )
+ {
+ NavmeshConnectionObject *nmco = static_cast<NavmeshConnectionObject*>(first);
+ std::vector<NavMeshConnectionData>::iterator other = nmco->GetConnectionTo( pair_it->second );
+ if( other != nmco->connections.end() )
+ {
+ other->offmesh_connection_id = id_counter;
+ area = other->poly_area;
+ }
+ }
+
+ if( second->GetType() == _navmesh_connection_object )
+ {
+ NavmeshConnectionObject *nmco = static_cast<NavmeshConnectionObject*>(second);
+ std::vector<NavMeshConnectionData>::iterator other = nmco->GetConnectionTo( pair_it->first );
+ if( other != nmco->connections.end() )
+ {
+ other->offmesh_connection_id = id_counter;
+ area = other->poly_area;
+ }
+ }
+
+ LOGI << "Adding off mesh connection between " <<
+ first->GetTranslation() << " and " <<
+ second->GetTranslation() << " type " << area << std::endl;
+
+ unsigned short jump_category = SAMPLE_POLYFLAGS_JUMP5;
+
+ if( area == SAMPLE_POLYAREA_JUMP1 )
+ {
+ jump_category = SAMPLE_POLYFLAGS_JUMP1;
+ }
+
+ if( area == SAMPLE_POLYAREA_JUMP2 )
+ {
+ jump_category = SAMPLE_POLYFLAGS_JUMP2;
+ }
+
+ if( area == SAMPLE_POLYAREA_JUMP3 )
+ {
+ jump_category = SAMPLE_POLYFLAGS_JUMP3;
+ }
+
+ if( area == SAMPLE_POLYAREA_JUMP4 )
+ {
+ jump_category = SAMPLE_POLYFLAGS_JUMP4;
+ }
+
+ if( area == SAMPLE_POLYAREA_JUMP5 )
+ {
+ jump_category = SAMPLE_POLYFLAGS_JUMP5;
+ }
+
+ if( area == SAMPLE_POLYAREA_DISABLED )
+ {
+ jump_category = SAMPLE_POLYFLAGS_DISABLED;
+ }
+
+ nav_mesh_->getInputGeom().addOffMeshConnection(
+ first->GetTranslation(),
+ second->GetTranslation(),
+ 1.0f, //rad
+ DT_OFFMESH_CON_BIDIR, //bidir
+ area, //User defined area id
+ jump_category,
+ id_counter++); //User defined flags
+ } else {
+ LOGW << "Invalid offmesh connection between " << pair_it->first << " and " << pair_it->second << std::endl;
+ }
+ }
+}
+
+NavMesh* SceneGraph::GetNavMesh() {
+ return nav_mesh_;
+}
+
+Object* SceneGraph::GetClosestObject(const vec3 &pos, Object* excluded_object, vec3 *hit_pos, int *hit_tri){
+ ContactInfoCallback cb;
+ float size = 0.1f;
+ bool hit_something = false;
+ while(!hit_something && size < 0.5f){
+ bullet_world_->GetSphereCollisions(pos, size, cb);
+ size += 0.1f;
+ for(int i=0; i<cb.contact_info.size();++i){
+ BulletObject* bo = cb.contact_info[i].object;
+ if(bo &&
+ bo->owner_object &&
+ bo->owner_object->GetType() != _movement_object &&
+ bo->owner_object != excluded_object)
+ {
+ hit_something = true;
+ }
+ }
+ }
+ //DebugDraw::Instance()->AddWireSphere(event_pos, size,vec4(1.0f),_persistent);
+ Object* closest_obj = NULL;
+ float closest_dist;
+ vec3 closest_point;
+ int closest_tri;
+ for(int i=0; i<cb.contact_info.size();++i){
+ BulletObject* bo = cb.contact_info[i].object;
+ if(bo &&
+ bo->owner_object &&
+ (bo->owner_object->GetType() == _movement_object ||
+ bo->owner_object == excluded_object))
+ {
+ continue;
+ }
+ const vec3 &point = vec3(cb.contact_info[i].point[0],
+ cb.contact_info[i].point[1],
+ cb.contact_info[i].point[2]);
+ if(closest_obj == NULL ||
+ distance_squared(pos, point) < closest_dist)
+ {
+ closest_dist = distance_squared(pos, point);
+ closest_obj = bo->owner_object;
+ closest_point = point;
+ closest_tri = cb.contact_info[i].tri;
+ }
+ }
+ if(closest_obj != NULL){
+ if(hit_pos){
+ *hit_pos = closest_point;
+ }
+ if(hit_tri){
+ *hit_tri = closest_tri;
+ }
+ }
+ return closest_obj;
+}
+
+const MaterialEvent *SceneGraph::GetMaterialEvent( const std::string &the_event, const vec3 &event_pos )
+{
+ int hit_tri;
+ vec3 hit_pos;
+ Object* closest_obj = GetClosestObject(event_pos, NULL, &hit_pos, &hit_tri);
+ if(!closest_obj){
+ return NULL;
+ }
+ return &closest_obj->GetMaterialEvent(the_event, event_pos, &hit_tri);
+}
+
+const MaterialEvent *SceneGraph::GetMaterialEvent( const std::string &the_event, const vec3 &event_pos, Object* excluded_object )
+{
+ vec3 hit_pos;
+ int hit_tri;
+ Object* closest_obj = GetClosestObject(event_pos, excluded_object, &hit_pos, &hit_tri);
+ if(!closest_obj){
+ return NULL;
+ }
+ return &closest_obj->GetMaterialEvent(the_event, event_pos, &hit_tri);
+}
+
+const MaterialEvent * SceneGraph::GetMaterialEvent( const std::string &the_event, const vec3 &event_pos, const std::string &mod )
+{
+ vec3 hit_pos;
+ int hit_tri;
+ Object* closest_obj = GetClosestObject(event_pos, NULL, &hit_pos, &hit_tri);
+ if(!closest_obj){
+ return NULL;
+ }
+ return &closest_obj->GetMaterialEvent(the_event, event_pos, mod, &hit_tri);
+}
+
+const MaterialEvent * SceneGraph::GetMaterialEvent( const std::string &the_event, const vec3 &event_pos, const std::string &mod, Object* excluded_object ) {
+ vec3 hit_pos;
+ int hit_tri;
+ Object* closest_obj = GetClosestObject(event_pos, excluded_object, &hit_pos, &hit_tri);
+ if(!closest_obj){
+ return NULL;
+ }
+ return &closest_obj->GetMaterialEvent(the_event, event_pos, mod, &hit_tri);
+}
+
+
+const MaterialDecal *SceneGraph::GetMaterialDecal( const std::string &type,
+ const vec3 &pos )
+{
+ vec3 hit_pos;
+ int hit_tri;
+ Object* closest_obj = GetClosestObject(pos, NULL, &hit_pos, &hit_tri);
+ if(!closest_obj){
+ return NULL;
+ }
+ return &closest_obj->GetMaterialDecal(type, pos, &hit_tri);
+}
+
+const MaterialParticle * SceneGraph::GetMaterialParticle( const std::string &type, const vec3 &pos )
+{
+ vec3 hit_pos;
+ int hit_tri;
+ Object* closest_obj = GetClosestObject(pos, NULL, &hit_pos, &hit_tri);
+ if(!closest_obj){
+ return NULL;
+ }
+ return &closest_obj->GetMaterialParticle(type, pos, &hit_tri);
+}
+
+vec3 SceneGraph::GetColorAtPoint( const vec3 &pos )
+{
+ vec3 hit_pos;
+ int hit_tri;
+ Object* closest_obj = GetClosestObject(pos, NULL, &hit_pos, &hit_tri);
+ if(!closest_obj){
+ return vec3(0);
+ }
+ return closest_obj->GetColorAtPoint(pos, &hit_tri);
+}
+
+void SceneGraph::UpdatePhysicsTransforms()
+{
+ for(std::vector<EnvObject*>::iterator it = visible_static_meshes_.begin(); it != visible_static_meshes_.end(); ++it){
+ (*it)->UpdatePhysicsTransform();
+ }
+}
+
+void SceneGraph::GetPlayerCharacterIDs(int* num_avatars, int avatar_ids[], int max_avatars) {
+ *num_avatars = 0;
+ for(unsigned i=0; i < movement_objects_.size(); ++i) {
+ MovementObject* mo = (MovementObject*)movement_objects_[i];
+ if(mo->is_player){
+ if(*num_avatars < max_avatars){
+ avatar_ids[*num_avatars] = mo->GetID();
+ ++*num_avatars;
+ } else {
+ LOG_ASSERT(false);
+ }
+ }
+ }
+}
+
+void SceneGraph::GetNPCCharacterIDs(int * num_avatars, int avatar_ids[], int max_avatars) {
+ *num_avatars = 0;
+ for (unsigned i = 0; i < movement_objects_.size(); ++i) {
+ MovementObject* mo = (MovementObject*)movement_objects_[i];
+ if (!mo->is_player) {
+ if (*num_avatars < max_avatars) {
+ avatar_ids[*num_avatars] = mo->GetID();
+ ++*num_avatars;
+ }
+ else {
+ LOG_ASSERT(false);
+ }
+ }
+ }
+}
+
+void SceneGraph::GetCharacterIDs(int *num_avatars, int avatar_ids[], int max_avatars) {
+ *num_avatars = 0;
+ for (unsigned i = 0; i < movement_objects_.size(); ++i) {
+ MovementObject* mo = (MovementObject*)movement_objects_[i];
+ if (*num_avatars < max_avatars) {
+ avatar_ids[*num_avatars] = mo->GetID();
+ ++*num_avatars;
+ }
+ else {
+ LOG_ASSERT(false);
+ }
+ }
+}
+
+float SceneGraph::GetMaterialHardness( const vec3 &pos, Object* excluded_object )
+{
+ vec3 hit_pos;
+ int hit_tri;
+ Object* closest_obj = GetClosestObject(pos, excluded_object, &hit_pos, &hit_tri);
+ if(!closest_obj){
+ return 1.0f;
+ }
+ return closest_obj->GetMaterial(pos, &hit_tri)->GetHardness();
+}
+
+float SceneGraph::GetMaterialFriction( const vec3 &pos, Object* excluded_object ) {
+ vec3 hit_pos;
+ int hit_tri;
+ Object* closest_obj = GetClosestObject(pos, excluded_object, &hit_pos, &hit_tri);
+ if(!closest_obj){
+ return 1.0f;
+ }
+ //DebugDraw::Instance()->AddWireSphere(hit_pos, 0.1f, vec4(1.0f), _fade);
+ return closest_obj->GetMaterial(pos, &hit_tri)->GetFriction();
+}
+
+float SceneGraph::GetMaterialSharpPenetration( const vec3 &pos, Object* excluded_object ) {
+ vec3 hit_pos;
+ int hit_tri;
+ Object* closest_obj = GetClosestObject(pos, excluded_object, &hit_pos, &hit_tri);
+ if(!closest_obj){
+ return 0.0f;
+ }
+ return closest_obj->GetMaterial(pos, &hit_tri)->GetSharpPenetration();
+}
+
+void SceneGraph::GetSweptSphereCollisionCharacters( const vec3 &pos, const vec3 &pos2, float radius, SphereCollision &as_col ) {
+ vec3 end = pos2;
+ for(unsigned i=0; i<movement_objects_.size(); ++i){
+ MovementObject* mo = (MovementObject*)movement_objects_[i];
+ mo->rigged_object()->skeleton().GetSweptSphereCollisionCharacter(pos, end, radius, as_col);
+ end = as_col.position;
+ }
+}
+
+int SceneGraph::CheckRayCollisionCharacters( const vec3 &start, const vec3 &end, vec3 *point, vec3 *normal, int *bone ) {
+ vec3 new_end = end;
+ vec3 temp_point;
+ vec3 temp_normal;
+ int char_id = -1;
+ for(unsigned i=0; i<movement_objects_.size(); ++i){
+ MovementObject* mo = (MovementObject*)movement_objects_[i];
+ const btCollisionObject* bone_col = mo->rigged_object()->skeleton().CheckRayCollision(start, new_end, &temp_point, &temp_normal);
+ if(bone_col != NULL){
+ if(point){
+ *point = temp_point;
+ }
+ if(normal){
+ *normal = temp_normal;
+ }
+ if(bone){
+ BulletObject* bullet_obj = (BulletObject*)bone_col->getUserPointer();
+ if(bullet_obj){
+ PhysicsBone* pb = (PhysicsBone*)bullet_obj->owner_object;
+ if(pb){
+ *bone = pb->bone;
+ }
+ }
+ }
+ new_end = temp_point;
+ char_id = mo->GetID();
+ }
+ }
+ return char_id;
+}
+
+void SceneGraph::SendMessageToAllObjects( OBJECT_MSG::Type type ) {
+ //This is a simple loop instead of an iterator because sometimes this would crash as an iterator after
+ //passing way past the end. Likely because objects_ is sometimes changed as a result of this call.
+ //This solution means that most objects get called, some might not if they are first destroyed.
+ //New objects will also be called assuming they are added last to the list.
+ for( unsigned int i = 0; i < objects_.size(); i++ ) {
+ Object* obj = objects_[i];
+ if( obj )
+ {
+ obj->ReceiveObjectMessage(type);
+ }
+ else
+ {
+ LOGE << "One of the objects is NULL when trying to SendMessageToAllObjects." << std::endl;
+ }
+ }
+}
+
+void SceneGraph::SendScriptMessageToAllObjects( std::string& msg ) {
+ for( unsigned int i = 0; i < objects_.size(); i++ ) {
+ Object* obj = objects_[i];
+ if( obj )
+ {
+ obj->ReceiveObjectMessage(OBJECT_MSG::SCRIPT, &msg);
+ }
+ else
+ {
+ LOGE << "One of the objects is NULL when trying to SendMessageToAllObjects." << std::endl;
+ }
+ }
+}
+
+std::vector<MovementObject*> SceneGraph::GetControlledMovementObjects() {
+ std::vector<MovementObject*> controlled_objects;
+ for (Object* obj : movement_objects_) {
+ MovementObject* mo = static_cast<MovementObject*>(obj);
+
+ if (mo->controlled) {
+ controlled_objects.push_back(mo);
+ }
+ }
+ return controlled_objects;
+}
+
+std::vector<MovementObject*> SceneGraph::GetControllableMovementObjects() {
+ std::vector<MovementObject*> controllable_objects;
+ for (Object* obj : movement_objects_) {
+ MovementObject* mo = static_cast<MovementObject*>(obj);
+
+ if (mo->is_player) {
+ controllable_objects.push_back(mo);
+ }
+ }
+ return controllable_objects;
+}
+
+bool SceneGraph::VerifySanity() {
+ for( unsigned i = 0; i < kMaxWarnings; i++ ) {
+ ObjectSanityState& sanity = sanity_list[i];
+ if(sanity.Valid() && sanity.Ok() == false) {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+// Adapted from https://fgiesen.wordpress.com/2013/02/08/triangle-rasterization-in-practice/
+float orient2d(const float *a, const float *b, const float *c) {
+ return (b[0]-a[0])*(c[1]-a[1]) - (b[1]-a[1])*(c[0]-a[0]);
+}
+
+void SceneGraph::DrawDepthMap(const mat4& proj_view_matrix, const vec4* cull_planes, int num_cull_planes, SceneGraph::DepthType depth_type, SceneDrawType scene_draw_type) {
+ if (g_debug_runtime_disable_scene_graph_draw_depth_map) {
+ return;
+ }
+
+ Object::DrawType object_draw_type = Object::kUnknown;
+ if(depth_type == SceneGraph::kDepthPrePass){
+ object_draw_type = Object::kDrawDepthOnly;
+ } else if(depth_type == SceneGraph::kDepthShadow){
+ object_draw_type = Object::kDrawDepthNoAA;
+ } else if(depth_type == SceneGraph::kDepthAllShadowCascades){
+ object_draw_type = Object::kDrawAllShadowCascades;
+ }
+
+ UpdateShaderSuffix(this, object_draw_type);
+
+ {
+ static object_list visible_objects_copy;
+ visible_objects_copy = visible_objects_;
+ PROFILER_ZONE(g_profiler_ctx, "Draw dynamic objects");
+ if(scene_draw_type == kStaticAndDynamic){
+ for(object_list::iterator it = visible_objects_copy.begin(); it != visible_objects_copy.end(); ++it) {
+ Object& obj = *(*it);
+ if(obj.enabled_ && obj.GetType() != _decal_object && obj.GetType() != _env_object){
+ obj.DrawDepthMap(proj_view_matrix, cull_planes, num_cull_planes, object_draw_type);
+ }
+ }
+ } else {
+ for(object_list::iterator it = visible_objects_copy.begin(); it != visible_objects_copy.end(); ++it) {
+ Object& obj = *(*it);
+ if(obj.enabled_ && obj.GetType() == _terrain_type){
+ obj.DrawDepthMap(proj_view_matrix, cull_planes, num_cull_planes, object_draw_type);
+ }
+ }
+ }
+ }
+
+ if(!kUseShadowCache){
+ if(visible_objects_need_sort){
+ PROFILER_ENTER(g_profiler_ctx, "Sort visible objects");
+ g_static_mesh_draw_sort_entries = &visible_static_meshes_;
+ std::sort(visible_static_mesh_indices_.begin(), visible_static_mesh_indices_.end(), StaticMeshDrawSortIndices);
+ visible_objects_need_sort = false;
+ PROFILER_LEAVE(g_profiler_ctx);
+ }
+ vec3 cam_pos = ActiveCameras::Get()->GetPos();
+ // List static meshes
+ PROFILER_ENTER(g_profiler_ctx, "Draw env object batches (depth only)");
+ static std::vector<EnvObject*> static_meshes_to_draw;
+ static_meshes_to_draw.clear();
+ static_meshes_to_draw.reserve(visible_static_meshes_.size());
+ PROFILER_ENTER(g_profiler_ctx, "List visible objects / frustum cull");
+ for(std::vector<uint16_t>::iterator it = visible_static_mesh_indices_.begin(); it != visible_static_mesh_indices_.end(); ++it) {
+ uint16_t index = *it;
+ EnvObject* eo = visible_static_meshes_[index];
+ if(!eo->transparent && eo->enabled_) {
+ bool culled = false;
+ for(int plane=0; plane<num_cull_planes; ++plane){
+ if( dot(eo->sphere_center_, cull_planes[plane].xyz()) +
+ cull_planes[plane][3] + eo->sphere_radius_ <= 0.0f )
+ {
+ culled = true;
+ break;
+ }
+ }
+ if(!culled){
+ static_meshes_to_draw.push_back(eo);
+ }
+ }
+ }
+ PROFILER_LEAVE(g_profiler_ctx);
+ PROFILER_ENTER(g_profiler_ctx, "Issue draw calls");
+ int batch_start = 0;
+ last_ofr_is_valid = false;
+ for(int i=1, len=static_meshes_to_draw.size(); i<=len; ++i) {
+ if(i == len || (static_meshes_to_draw[i]->ofr->path_ != static_meshes_to_draw[i-1]->ofr->path_ ||
+ static_meshes_to_draw[i]->winding_flip != static_meshes_to_draw[i-1]->winding_flip))
+ {
+ static_meshes_to_draw[i-1]->DrawInstances(&static_meshes_to_draw[batch_start], i-batch_start, proj_view_matrix, proj_view_matrix, NULL, cam_pos, object_draw_type);
+ batch_start = i;
+ }
+ }
+ if(object_draw_type != Object::kDrawDepthOnly) {
+ // Batch and draw detail objects
+ static std::vector<DetailObjectSurfaceDrawCall> detail_objects_surfaces_to_draw;
+ detail_objects_surfaces_to_draw.clear();
+ detail_objects_surfaces_to_draw.reserve(visible_static_meshes_.size());
+ batch_start = 0;
+ for(int i=1, len=static_meshes_to_draw.size(); i<=len; ++i) {
+ if(i == len || (static_meshes_to_draw[i]->ofr->path_ != static_meshes_to_draw[i-1]->ofr->path_ ||
+ static_meshes_to_draw[i]->winding_flip != static_meshes_to_draw[i-1]->winding_flip))
+ {
+ if(static_meshes_to_draw[i-1]->HasDetailObjectSurfaces()){
+ detail_objects_surfaces_to_draw.push_back({ static_meshes_to_draw[i-1], &static_meshes_to_draw[batch_start], i-batch_start });
+ }
+ batch_start = i;
+ }
+ }
+ for(unsigned int i = 0; i < detail_objects_surfaces_to_draw.size(); ++i) {
+ auto current = detail_objects_surfaces_to_draw[i];
+ current.draw_owner->DrawDetailObjectInstances(current.instance_array, current.num_instances, object_draw_type);
+ }
+ }
+ PROFILER_LEAVE(g_profiler_ctx);
+ PROFILER_LEAVE(g_profiler_ctx);
+ } else {
+ }
+ /*if(depth_type == SceneGraph::kDepthShadow){
+ particle_system->DrawDepth(this, proj_view_matrix);
+ }*/
+}
+
+int SceneGraph::GetAndReserveID() {
+ int curr_id = 0;
+ int last_id = -1;
+ int free_id = -1;
+ for(unsigned int i = 0; i < object_from_id_map_.size(); ++i){
+ if(object_from_id_map_[i] == NULL){
+ free_id = i;
+ break;
+ }
+ }
+ if(free_id == -1){
+ free_id = object_from_id_map_.size();
+ }
+ SetIdToObjectMapValue(object_from_id_map_, free_id, NULL);
+ return free_id;
+}
+
+int SceneGraph::LinkUpdateObject(Object* obj) {
+ if(num_update_objects < kMaxUpdateObjects){
+ int id = num_update_objects++;
+ update_objects_[id] = obj;
+ return id;
+ } else {
+ FatalError("Error", "Too many update objects");
+ return -1;
+ }
+}
+
+void SceneGraph::UnlinkUpdateObject(Object* obj, int entry) {
+ SDL_assert(update_objects_[entry] == obj);
+ if(entry != num_update_objects-1){
+ update_objects_[entry] = update_objects_[num_update_objects-1];
+ update_objects_[entry]->update_list_entry = entry;
+ }
+ --num_update_objects;
+}
+
+void SceneGraph::Dispose() {
+ for(object_list::iterator it = objects_.begin(); it != objects_.end(); ++it) {
+ (*it)->Dispose();
+ }
+ for(object_list::iterator it = objects_.begin(); it != objects_.end(); ++it) {
+ delete (*it);
+ }
+ object_from_id_map_.clear();
+ if(bullet_world_){
+ bullet_world_->Dispose();
+ delete bullet_world_;
+ bullet_world_ = NULL;
+ }
+ if(abstract_bullet_world_){
+ abstract_bullet_world_->Dispose();
+ delete abstract_bullet_world_;
+ abstract_bullet_world_ = NULL;
+ }
+ if(plant_bullet_world_){
+ plant_bullet_world_->Dispose();
+ delete plant_bullet_world_;
+ plant_bullet_world_ = NULL;
+ }
+ if(nav_mesh_){
+ delete nav_mesh_;
+ nav_mesh_ = NULL;
+ nav_mesh_renderer_.LoadNavMesh( nav_mesh_ );
+ }
+ delete particle_system;
+ particle_system = NULL;
+ light_probe_collection.Dispose();
+ dynamic_light_collection.Dispose();
+ level->Dispose();
+ delete level;
+ level = NULL;
+ delete map_editor;
+ map_editor = NULL;
+ sky->Dispose();
+ delete sky;
+ sky = NULL;
+ flares.CleanupFlares();
+ DecalTextures::Instance()->Clear();
+}
+
+void SceneGraph::SetNavMeshVisible( bool v )
+{
+ nav_mesh_renderer_.SetNavMeshVisible(v);
+}
+
+void SceneGraph::SetCollisionNavMeshVisible( bool v )
+{
+ nav_mesh_renderer_.SetCollisionMeshVisible(v);
+}
+
+bool SceneGraph::IsNavMeshVisible()
+{
+ return nav_mesh_renderer_.IsNavMeshVisible();
+}
+
+bool SceneGraph::IsCollisionNavMeshVisible()
+{
+ return nav_mesh_renderer_.IsCollisionMeshVisible();
+}
+
+bool SceneGraph::AddDynamicDecal(DecalObject *decal) {
+ if (dynamic_decals.size() >= kMaxDynamicDecals) {
+ DecalObject *obj = dynamic_decals.front();
+ obj->SetParent(NULL);
+ UnlinkObject(obj);
+ obj->Dispose();
+ delete(obj);
+ }
+ // make sure we don't clump too many decals into one place
+ vec3 my_pos = decal->GetTranslation();
+
+ // find nearest decal which is same type
+ // c++11 auto would be really helpful here
+ float nearest_dist_sq = FLT_MAX;
+
+ Object* nearest_obj = NULL;
+ decal_deque::iterator e = dynamic_decals.end();
+ for (decal_deque::iterator it = dynamic_decals.begin(); it != e; ++it) {
+ DecalObject *obj = *it;
+
+ LOG_ASSERT(obj != NULL);
+ if (obj->decal_file_ref->color_map != "Data/Textures/bloodsplat_c.tga" || obj->obj_file != decal->obj_file) {
+ // not the same kind of decal, don't consider
+ continue;
+ }
+
+ vec3 other_pos = obj->GetTranslation();
+ float dist_sq = length_squared(my_pos - other_pos);
+ if (dist_sq < nearest_dist_sq) {
+ nearest_dist_sq = dist_sq;
+ nearest_obj = obj;
+ }
+ }
+
+ vec3 scale = decal->GetScale();
+ float my_radius = min(scale.x(), min(scale.y(), scale.z()));
+ float my_radius_sq = my_radius * my_radius;
+ float other_radius_sq = 0.0f;
+ if(nearest_obj){
+ scale = nearest_obj->GetScale();
+ float other_radius = min(scale.x(), min(scale.y(), scale.z()));
+ float other_radius_sq = other_radius * other_radius;
+ }
+ if (nearest_dist_sq > my_radius_sq && nearest_dist_sq > other_radius_sq) {
+ if( ActorsEditor_AddEntity(this, decal, NULL, false) ) {
+ dynamic_decals.push_back(decal);
+ } else {
+ LOGE << "Failed to add dynamic decal to scenegraph" << std::endl;
+ return false;
+ }
+ } else {
+ vec3 cur_scale = nearest_obj->GetScale();
+ vec3 add_scale = decal->GetScale();
+ float cur_volume = cur_scale[0] * cur_scale[1];
+ float add_volume = add_scale[0] * add_scale[1];
+ if(cur_scale[0] < cur_scale[1]){
+ float square_vol = cur_scale[1] * cur_scale[1];
+ if(cur_volume + add_volume > square_vol){
+ cur_scale[0] = cur_scale[1];
+ add_volume += cur_volume - square_vol;
+ } else {
+ cur_scale[0] *= (cur_volume + add_volume) / cur_volume;
+ add_volume = 0.0f;
+ }
+ } else if(cur_scale[0] > cur_scale[1]){
+ float square_vol = cur_scale[0] * cur_scale[0];
+ if(cur_volume + add_volume > square_vol){
+ cur_scale[1] = cur_scale[0];
+ add_volume += cur_volume - square_vol;
+ } else {
+ cur_scale[1] *= (cur_volume + add_volume) / cur_volume;
+ add_volume = 0.0f;
+ }
+ }
+ float increase = (cur_volume + add_volume) / cur_volume;
+ nearest_obj->SetScale(nearest_obj->GetScale() * increase);
+ // on top of existing, don't add
+ return false;
+ }
+ return true;
+}
+
+
+#define COUNT_BITS 24u
+#define COUNT_MASK ((1u << (32u - COUNT_BITS)) - 1u)
+#define INDEX_MASK ((1u << (COUNT_BITS)) - 1u)
+
+uint32_t SetCount(uint32_t val, uint32_t count){
+ uint32_t count_shifted = count << COUNT_BITS;
+ LOG_ASSERT(count_shifted >> COUNT_BITS == count);
+ return val | count_shifted;
+}
+
+uint32_t SetIndex(uint32_t val, uint32_t index){
+ LOG_ASSERT(index == (index & INDEX_MASK));
+ return val | index;
+}
+
+
+// - z_near + 1.0f = minimum of 1.0, log of that = 0.0f
+// z_mult already contains num_z_clusters
+float ZCLUSTERFUNC(float val, float z_near, float z_mult) {
+ return log(-1.0f * (val) - z_near + 1.0f) * z_mult;
+}
+
+
+void SceneGraph::PrepareLightsAndDecals(vec2 active_screen_start, vec2 active_screen_end, vec2 screen_dims) {
+ if (g_debug_runtime_disable_scene_graph_prepare_lights_and_decals) {
+ return;
+ }
+
+ PROFILER_GPU_ZONE(g_profiler_ctx, "PrepareLightsAndDecals");
+ // http://www.humus.name/index.php?page=Articles
+ // "Practical Clustered Shading"
+ // this is similar but NOT the same algorithm
+ // 1. different z function
+ // 2. doesn't use plane tests
+
+ // 1. I couldn't get the paper's version to work
+ // somehow it doesn't cluster things correctly
+ // play with ZCLUSTERFUNC if you want to fix it (also in the shader)
+
+ // 2. this code loops through all light/decals and converts them to screen space
+ // the paper build a set of planes aligned with screen x/y/z axes
+ // and tests primitives against those
+ // the naive way was simpler and we don't have plane test primitives yet
+
+ // you need plane-box and plane-sphere tests
+ // with return value of Front, Behind, Intersect
+ // Math/enginemath, Math/overgroth_geometry and Internal/collisiondetection
+ // contain some things but not the things we need
+
+ Graphics *graphics = Graphics::Instance();
+ const unsigned int width = (unsigned int) screen_dims[0];
+ const unsigned int height = (unsigned int) screen_dims[1];
+
+ Camera* camera = ActiveCameras::Get();
+ LOG_ASSERT(camera);
+ mat4 proj_mat = camera->GetProjMatrix();
+ mat4 inv_proj_mat = invert(proj_mat);
+ mat4 view_mat = camera->GetViewMatrix();
+ mat4 proj_view_mat = proj_mat * view_mat;
+ float z_near = camera->GetNearPlane();
+ float z_far = camera->GetFarPlane();
+ mat4 cluster_mat;
+
+ {
+ const vec3 screen_start(active_screen_start, 0.0f);
+ const vec3 screen_end(active_screen_end, 1.0f);
+ const vec3 screen_mult = screen_end - screen_start;
+
+ mat4 scale;
+ mat4 translate;
+ // scale .xy from [-1.0, 1.0] to [0.0, 1.0]
+ scale.SetUniformScale(0.5f);
+ translate.SetTranslation(vec3(0.5f));
+ cluster_mat = translate * scale * proj_mat;
+
+ // account for currently active view
+ scale.SetScale(screen_mult);
+ translate.SetTranslation(screen_start);
+ cluster_mat = translate * scale * cluster_mat;
+
+ // scale to [(0,0), (width, height)]
+ scale.SetScale(vec3((float)width, (float)height, 1.0f));
+ cluster_mat = scale * cluster_mat;
+
+ // scale to cluster space
+ scale.SetScale(vec3(1.0f / cluster_size, 1.0f / cluster_size, 1.0f));
+ cluster_mat = scale * cluster_mat;
+ }
+
+ const unsigned int grid_width = (width + cluster_size - 1) / cluster_size;
+ const unsigned int grid_height = (height + cluster_size - 1) / cluster_size;
+
+ const unsigned int numclusters = grid_width * grid_height * num_z_clusters;
+
+ const vec2 grid_size = vec2(float(grid_width), float(grid_height));
+ const vec3 cluster_min_bound(active_screen_start * grid_size, -z_far);
+ const vec3 cluster_max_bound(active_screen_end * grid_size, 0.0f);
+
+ // z values will be divided by this
+ // the value inside log is maximum we should get in view space
+ // after adding 1.0 so the log works
+ // by dividing with num_z_clusters the final value ends up
+ // multiplied by it so we get [0,num_z_clusters]
+ float z_div = log(z_far - z_near + 1.0f) / num_z_clusters;
+ float z_mult = 1.0f / z_div;
+
+ ShaderClusterInfo clusterInfo;
+ clusterInfo.num_decals = 0;
+ clusterInfo.num_lights = 0;
+ clusterInfo.light_cluster_data_offset = 0;
+ clusterInfo.light_data_offset = 0;
+ clusterInfo.cluster_width = cluster_size;
+
+ clusterInfo.grid_size[0] = grid_width;
+ clusterInfo.grid_size[1] = grid_height;
+ clusterInfo.grid_size[2] = num_z_clusters;
+
+ memcpy(clusterInfo.inv_proj_mat, &inv_proj_mat, 16 * 4);
+ for (unsigned int i = 0; i < 4; i++) {
+ clusterInfo.viewport[i] = (float)graphics->viewport_dim[i];
+ }
+
+ clusterInfo.z_near = z_near;
+ clusterInfo.z_mult = z_mult;
+ clusterInfo.pad3 = 0.0f;
+ clusterInfo.pad4 = 0.0f;
+
+ // resize buffers if necessary
+ if (cluster_decal_counts.size() != numclusters) {
+ cluster_decal_counts.resize(numclusters);
+ cluster_light_counts.resize(numclusters);
+ cluster_list_heads.resize(numclusters);
+ decal_grid_lookup.resize(numclusters);
+ light_grid_lookup.resize(numclusters);
+ }
+
+ LOG_ASSERT_EQ(decal_grid_lookup.size(), cluster_decal_counts.size());
+ LOG_ASSERT_EQ(light_grid_lookup.size(), cluster_decal_counts.size());
+ LOG_ASSERT_EQ(cluster_light_counts.size(), cluster_decal_counts.size());
+
+ // number of decals alive (visible on screen) in current frame
+ unsigned int num_alive_decals = 0;
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Cluster decals");
+
+ int num_decals = decal_objects_.size();
+ LOG_ASSERT_LTEQ(num_decals, (int)kMaxDecals);
+
+ if (decal_tbo.size() < num_decals * sizeof(ShaderDecal) / sizeof(float)) {
+ decal_tbo.resize(num_decals * sizeof(ShaderDecal) / sizeof(float));
+ }
+
+ // LOGI << "num_decals = " << num_decals << std::endl;
+
+ for (unsigned int i = 0; i < numclusters; i++) {
+ cluster_decal_counts[i] = 0;
+ }
+
+ ShaderDecal decal;
+
+ bool decal_normals = config["decal_normals"].toNumber<bool>();
+
+ memset(&cluster_list_heads[0], 0, sizeof(uint32_t) * cluster_list_heads.size());
+ cluster_list_contents.clear();
+ cluster_list_contents.push_back(ClusterData());
+
+ // assign decals into clusters
+ for (int i = 0; i < num_decals; ++i){
+ DecalObject * dec = static_cast<DecalObject*>(decal_objects_[i]);
+ if(dec->enabled_ == false){
+ continue;
+ }
+
+ decal.decal_tint[3] = dec->decal_file_ref->special_type + 0.5f;
+
+ mat4 mv_mat = view_mat * dec->GetTransform();
+
+ // calculate decal bounding box in view space
+ vec3 decal_view_min(FLT_MAX, FLT_MAX, FLT_MAX);
+ vec3 decal_view_max(-FLT_MAX, -FLT_MAX, -FLT_MAX);
+
+ Box decal_box = dec->box_;
+ for (int point = 0; point < Box::NUM_POINTS; point++) {
+ // strange thing: view space z decreases farther away
+ vec3 view_point = (mv_mat * vec4(decal_box.GetPoint(point), 1.0f)).xyz();
+ decal_view_min = components_min(decal_view_min, view_point);
+ decal_view_max = components_max(decal_view_max, view_point);
+ }
+
+ //LOGI << "decal " << i << std::endl;
+
+ // drop it if it's entirely behind the camera
+ if (decal_view_min.z() > -z_near) {
+ continue;
+ }
+ /*
+ LOGI << "decal_view_min: " << decal_view_min << std::endl;
+ LOGI << "decal_view_max: " << decal_view_max << std::endl;
+ */
+
+ // cap at near plane
+ decal_view_max.z() = min(-z_near, decal_view_max.z());
+
+ Box view_box;
+ view_box.center = (decal_view_min + decal_view_max) * 0.5f;
+ view_box.dims = decal_view_max - decal_view_min;
+
+ // calculate projection space bounding box
+ vec2 decal_proj_min(FLT_MAX, FLT_MAX);
+ vec2 decal_proj_max(-FLT_MAX, -FLT_MAX);
+
+ // TODO: we probably don't need to do the whole box, just the
+ // four points on the near face
+ for (int point = 0; point < Box::NUM_POINTS; point++) {
+ vec4 proj_point = cluster_mat * vec4(view_box.GetPoint(point), 1.0f);
+ // we know it's in front of the screen since we capped it at near plane
+ LOG_ASSERT_GTEQ(proj_point.w(), 0.0f);
+ vec2 proj_point2 = proj_point.xy() * (1.0f / proj_point.w());
+
+ decal_proj_min = components_min2(decal_proj_min, proj_point2);
+ decal_proj_max = components_max2(decal_proj_max, proj_point2);
+ }
+
+ /*
+ LOGI << "decal_proj_min: " << decal_proj_min << std::endl;
+ LOGI << "decal_proj_max: " << decal_proj_max << std::endl;
+ */
+
+ // we use projection-space .xy and view-space .z for clustering
+ vec3 decal_cluster_min(decal_proj_min, decal_view_min.z());
+ vec3 decal_cluster_max(decal_proj_max, decal_view_max.z());
+
+ // drop the decal if it's entirely off-screen
+ if (decal_cluster_min.x() > cluster_max_bound.x()
+ || decal_cluster_max.x() < cluster_min_bound.x()
+ || decal_cluster_min.y() > cluster_max_bound.y()
+ || decal_cluster_max.y() < cluster_min_bound.y()) {
+ continue;
+ }
+
+ decal_cluster_min = components_max(decal_cluster_min, cluster_min_bound);
+ decal_cluster_max = components_max(decal_cluster_max, cluster_min_bound);
+ decal_cluster_min = components_min(decal_cluster_min, cluster_max_bound);
+ decal_cluster_max = components_min(decal_cluster_max, cluster_max_bound);
+
+ /*
+ LOGI << "decal_cluster_min: " << decal_cluster_min << std::endl;
+ LOGI << "decal_cluster_max: " << decal_cluster_max << std::endl << std::endl;
+ LOGI << "view_point " << point << ": " << view_point << std::endl;
+ LOGI << "proj_point " << point << ": " << proj_point << std::endl;
+ LOGI << "screen_point " << point << ": " << screen_point << std::endl;
+ */
+
+ if(g_no_decal_elements){
+ continue;
+ }
+
+ unsigned int decal_index = num_alive_decals;
+ num_alive_decals++;
+
+
+ vec3 combined_tint = dec->color_tint_component_.tint_ * (1.0f + dec->color_tint_component_.overbright_ * 0.3f);
+ memcpy(&decal.decal_tint, &combined_tint, 4 * 3);
+
+ vec3 scale = dec->GetScale();
+ decal.decal_scale[0] = scale.x();
+ decal.decal_scale[1] = scale.y();
+ decal.decal_scale[2] = scale.z();
+ decal.decal_spawn_time = dec->spawn_time_;
+ quaternion rotation = dec->GetRotation();
+ memcpy(&decal.decal_rotation[0], &rotation.entries[0], 4 * sizeof(float));
+ vec3 pos = dec->GetTranslation();
+ decal.decal_position[0] = pos.x();
+ decal.decal_position[1] = pos.y();
+ decal.decal_position[2] = pos.z();
+ decal.decal_pad1 = 0.0f;
+
+ decal.decal_uv[0] = dec->texture->color_texture_ref.uv_start.entries[0];
+ decal.decal_uv[1] = dec->texture->color_texture_ref.uv_start.entries[1];
+ decal.decal_uv[2] = dec->texture->color_texture_ref.uv_size.entries[0];
+ decal.decal_uv[3] = dec->texture->color_texture_ref.uv_size.entries[1];
+
+ if (decal_normals){
+ decal.decal_normal[0] = dec->texture->normal_texture_ref.uv_start.entries[0];
+ decal.decal_normal[1] = dec->texture->normal_texture_ref.uv_start.entries[1];
+ decal.decal_normal[2] = dec->texture->normal_texture_ref.uv_size.entries[0];
+ decal.decal_normal[3] = dec->texture->normal_texture_ref.uv_size.entries[1];
+ }
+
+ memcpy(&decal_tbo[sizeof(ShaderDecal) / sizeof(float) * decal_index], &decal, sizeof(ShaderDecal));
+
+ unsigned int x_min = (unsigned int)decal_cluster_min.x();
+ unsigned int x_max = (unsigned int)ceil(decal_cluster_max.x());
+
+ unsigned int y_min = (unsigned int)decal_cluster_min.y();
+ unsigned int y_max = (unsigned int)ceil(decal_cluster_max.y());
+
+ // since z is "reversed" and min is actually the farthest value
+ // we swap min and max
+ unsigned int z_min = (unsigned int)(ZCLUSTERFUNC(decal_cluster_max.z(), z_near, z_mult));
+ unsigned int z_max = (unsigned int)ceil(ZCLUSTERFUNC(decal_cluster_min.z(), z_near, z_mult));
+
+ z_min = max(0u, z_min);
+ z_max = min(z_max, num_z_clusters - 1);
+
+ LOG_ASSERT_LTEQ(x_min, x_max);
+ LOG_ASSERT_LTEQ(y_min, y_max);
+ LOG_ASSERT_LTEQ(z_min, z_max);
+
+ // these can be equal to the max since they're used in a C-style loop
+ LOG_ASSERT_LTEQ(x_max, grid_width);
+ LOG_ASSERT_LTEQ(y_max, grid_height);
+ LOG_ASSERT_LTEQ(z_max, num_z_clusters);
+
+ /*
+ LOGI << "decal " << i << std::endl;
+ LOGI << " min: " << decal_cluster_min << std::endl;
+ LOGI << " max: " << decal_cluster_max << std::endl;
+ LOGI << " z:" << z_min << " - " << z_max << std::endl;
+ LOGI << std::endl;
+ */
+
+ for (unsigned int x = x_min; x < x_max; x++) {
+ for (unsigned int y = y_min; y < y_max; y++) {
+ for (unsigned int z = z_min; z < z_max; z++) {
+ // TODO: we might get better results with something like morton order
+ // it might increase cache locality in the shader
+ unsigned int decal_cluster_index = (y * grid_width + x) * num_z_clusters + z;
+ LOG_ASSERT_LT(decal_cluster_index, numclusters);
+
+ unsigned int count = cluster_decal_counts[decal_cluster_index];
+ cluster_decal_counts[decal_cluster_index]++;
+
+ // write new to 1d buffer and update 3d buffer
+ ClusterData new_data;
+ new_data.item = decal_index;
+ new_data.next = cluster_list_heads[decal_cluster_index];
+ cluster_list_heads[decal_cluster_index] = cluster_list_contents.size();
+ cluster_list_contents.push_back(new_data);
+ }
+ }
+ }
+ }
+
+ // fill lookup buffer
+ // TODO: try to reuse buffer contents
+ cluster_decals.clear();
+ for (int unsigned i = 0; i < numclusters; i++) {
+ unsigned int cluster_num_decals = 0;
+ decal_grid_lookup[i] = SetIndex(0, cluster_decals.size());
+
+ uint32_t index = cluster_list_heads[i];
+ while (index != 0) {
+ const ClusterData &data = cluster_list_contents[index];
+ cluster_decals.push_back(data.item);
+ cluster_num_decals++;
+ index = data.next;
+ }
+
+ decal_grid_lookup[i] = SetCount(decal_grid_lookup[i], cluster_num_decals);
+ }
+
+ // LOGI << "decal buffer size: " << cluster_decals.size() << std::endl;
+ }
+
+ std::vector<DynamicLight> &dynamic_lights = dynamic_light_collection.dynamic_lights;
+ std::vector<float> light_data_buffer;
+ unsigned int num_alive_lights = 0;
+
+ unsigned int num_lights = dynamic_lights.size();
+ LOG_ASSERT_LT(num_lights, 65536);
+
+ mat4 cluster_view_mat = cluster_mat * view_mat;
+
+ LOG_ASSERT_EQ((sizeof(ShaderLight) % sizeof(float)) , 0);
+ const unsigned int light_params = sizeof(ShaderLight) / sizeof(float);
+ light_data_buffer.resize(num_lights * light_params);
+ ShaderLight l;
+ l.padding = 0.0f;
+
+ for (unsigned int i = 0; i < numclusters; i++) {
+ cluster_light_counts[i] = 0;
+ }
+
+ memset(&cluster_list_heads[0], 0, sizeof(uint32_t) * cluster_list_heads.size());
+ cluster_list_contents.clear();
+ cluster_list_contents.push_back(ClusterData());
+
+ for (unsigned int light_index = 0; light_index < num_lights; light_index++) {
+ const DynamicLight &dl = dynamic_lights[light_index];
+ const vec3 world_pos = dl.pos;
+ vec3 view_pos = view_mat * world_pos;
+
+ // TODO: don't use bounding box, there's a sphere algorithm in the paper
+
+ // FIXME: not correct, doesn't account for projection
+ vec3 view_min = view_pos - vec3(dl.radius*0.5f);
+ vec3 view_max = view_pos + vec3(dl.radius*0.5f);
+
+ // drop it if it's entirely behind the camera
+ if (view_min.z() > -z_near) {
+ continue;
+ }
+
+ // cap at near plane
+ view_max.z() = min(-z_near, view_max.z());
+
+ Box view_box;
+ view_box.center = (view_min + view_max) * 0.5f;
+ view_box.dims = view_max - view_min;
+
+ // calculate projection space bounding box
+ vec2 proj_min(FLT_MAX, FLT_MAX);
+ vec2 proj_max(-FLT_MAX, -FLT_MAX);
+
+ // TODO: we probably don't need to do the whole box, just the
+ // four points on the near face
+ for (int point = 0; point < Box::NUM_POINTS; point++) {
+ vec4 proj_point = cluster_mat * vec4(view_box.GetPoint(point), 1.0f);
+ // we know it's in front of the screen since we capped it at near plane
+ LOG_ASSERT_GTEQ(proj_point.w(), 0.0f);
+ vec2 proj_point2 = proj_point.xy() * (1.0f / proj_point.w());
+
+ proj_min = components_min2(proj_min, proj_point2);
+ proj_max = components_max2(proj_max, proj_point2);
+ }
+
+ /*
+ LOGI << "proj_min: " << proj_min << std::endl;
+ LOGI << "proj_max: " << proj_max << std::endl;
+ */
+
+ // we use projection-space .xy and view-space .z for clustering
+ vec3 cluster_min(proj_min, view_min.z());
+ vec3 cluster_max(proj_max, view_max.z());
+
+ // drop the light if it's entirely off-screen
+ if (cluster_min.x() > cluster_max_bound.x()
+ || cluster_max.x() < cluster_min_bound.x()
+ || cluster_min.y() > cluster_max_bound.y()
+ || cluster_max.y() < cluster_min_bound.y()) {
+ continue;
+ }
+
+ cluster_min = components_max(cluster_min, cluster_min_bound);
+ cluster_max = components_max(cluster_max, cluster_min_bound);
+ cluster_min = components_min(cluster_min, cluster_max_bound);
+ cluster_max = components_min(cluster_max, cluster_max_bound);
+
+ if(g_no_decal_elements){
+ continue;
+ }
+ /*
+ LOGI << "cluster_min: " << cluster_min << std::endl;
+ LOGI << "cluster_max: " << cluster_max << std::endl << std::endl;
+ LOGI << "view_point " << point << ": " << view_point << std::endl;
+ LOGI << "proj_point " << point << ": " << proj_point << std::endl;
+ LOGI << "screen_point " << point << ": " << screen_point << std::endl;
+ */
+
+ unsigned int light_buf_index = num_alive_lights;
+ num_alive_lights++;
+
+ memcpy(l.pos, dl.pos.entries, 3 * sizeof(float));
+ // the bounding box and helper mesh have a size of 0.5 instead of 1.0
+ // so we divide the radius by 2 to make it look right
+ l.radius = dl.radius / 2.0f;
+ memcpy(l.color, dl.color.entries, 3 * sizeof(float));
+ memcpy(&light_data_buffer[light_buf_index * light_params], &l, sizeof(ShaderLight));
+
+ unsigned int x_min = (unsigned int)cluster_min.x();
+ unsigned int x_max = (unsigned int)ceil(cluster_max.x());
+
+ unsigned int y_min = (unsigned int)cluster_min.y();
+ unsigned int y_max = (unsigned int)ceil(cluster_max.y());
+
+ // since z is "reversed" and min is actually the farthest value
+ // we swap min and max
+ unsigned int z_min = (unsigned int)(ZCLUSTERFUNC(cluster_max.z(), z_near, z_mult));
+ unsigned int z_max = (unsigned int)ceil(ZCLUSTERFUNC(cluster_min.z(), z_near, z_mult));
+
+ z_min = max(0u, z_min);
+ z_max = min(z_max, num_z_clusters - 1);
+
+ LOG_ASSERT_LTEQ(x_min, x_max);
+ LOG_ASSERT_LTEQ(y_min, y_max);
+ LOG_ASSERT_LTEQ(z_min, z_max);
+
+ // these can be equal to the max since they're used in a C-style loop
+ LOG_ASSERT_LTEQ(x_max, grid_width);
+ LOG_ASSERT_LTEQ(y_max, grid_height);
+ LOG_ASSERT_LTEQ(z_max, num_z_clusters);
+
+ /*
+ LOGI << "decal " << i << std::endl;
+ LOGI << " min: " << cluster_min << std::endl;
+ LOGI << " max: " << cluster_max << std::endl;
+ LOGI << " z:" << z_min << " - " << z_max << std::endl;
+ LOGI << std::endl;
+ */
+
+ for (unsigned int x = x_min; x < x_max; x++) {
+ for (unsigned int y = y_min; y < y_max; y++) {
+ for (unsigned int z = z_min; z < z_max; z++) {
+ unsigned int cluster_index = (y * grid_width + x) * num_z_clusters + z;
+ LOG_ASSERT_LT(cluster_index, numclusters);
+
+ unsigned int count = cluster_light_counts[cluster_index];
+ cluster_light_counts[cluster_index]++;
+
+ // write new to 1d buffer and update 3d buffer
+ ClusterData new_data;
+ new_data.item = light_buf_index;
+ new_data.next = cluster_list_heads[cluster_index];
+ cluster_list_heads[cluster_index] = cluster_list_contents.size();
+ cluster_list_contents.push_back(new_data);
+ }
+ }
+ }
+ }
+
+ // LOGI << "num_alive_lights: " << num_alive_lights << std::endl;
+
+ // fill the lookup buffer with data
+ // TODO: try to reuse buffer contents
+ cluster_lights.clear();
+ for (int unsigned j = 0; j < numclusters; j++) {
+ unsigned int cluster_num_lights = 0;
+ light_grid_lookup[j] = SetIndex(0, cluster_lights.size());
+
+ uint32_t index = cluster_list_heads[j];
+ while (index != 0) {
+ const ClusterData &data = cluster_list_contents[index];
+ cluster_lights.push_back(data.item);
+ cluster_num_lights++;
+ index = data.next;
+ }
+
+ light_grid_lookup[j] = SetCount(light_grid_lookup[j], cluster_num_lights);
+ }
+
+ LOG_ASSERT_EQ(sizeof(ShaderDecal), (12 + 4 + 4 + 4) * 4);
+ LOG_ASSERT_EQ(sizeof(ShaderClusterInfo), (8 * 4) * 4);
+
+ {
+ PROFILER_ENTER(g_profiler_ctx, "Setup batch data");
+
+ clusterInfo.num_decals = num_alive_decals;
+ clusterInfo.num_lights = num_alive_lights;
+
+ unsigned int decal_grid_lookup_offset = 0;
+ unsigned int decal_grid_lookup_size = numclusters * NUM_GRID_COMPONENTS;
+ unsigned int cluster_decals_offset = decal_grid_lookup_size;
+ unsigned int cluster_decals_size = cluster_decals.size();
+ unsigned int light_cluster_data_offset = cluster_decals_offset + cluster_decals_size;
+ unsigned int light_cluster_data_size = cluster_lights.size();
+
+ const bool kBandwidthDisplay = false;
+ if(kBandwidthDisplay){
+ const int kBufSize = 512;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "decal_grid_lookup_size: %u", decal_grid_lookup_size);
+ Engine::Instance()->gui.AddDebugText("decal_grid_lookup_size", buf, 0.5f);
+ FormatString(buf, kBufSize, "cluster_decals_size: %u", cluster_decals_size);
+ Engine::Instance()->gui.AddDebugText("cluster_decals_size", buf, 0.5f);
+ FormatString(buf, kBufSize, "light_cluster_data_size: %u", light_cluster_data_size);
+ Engine::Instance()->gui.AddDebugText("light_cluster_data_size", buf, 0.5f);
+ }
+
+ unsigned int buf_size = decal_grid_lookup_size + cluster_decals.size() + cluster_lights.size();
+ if (buf_size > decal_cluster_buffer.size()) {
+ // resize the buffer
+ decal_cluster_buffer.resize(buf_size, 0);
+ }
+
+ // first copy them into decal_cluster_buffer ...
+ if (decal_grid_lookup_size > 0) {
+ for (unsigned int i = 0; i < numclusters; i++) {
+ unsigned int offs = decal_grid_lookup_offset + (NUM_GRID_COMPONENTS * i);
+ memcpy(&decal_cluster_buffer[offs + 0], &decal_grid_lookup[i], 4);
+ memcpy(&decal_cluster_buffer[offs + 1], &light_grid_lookup[i], 4);
+ }
+ }
+
+ if (!cluster_decals.empty()) {
+ memcpy(&decal_cluster_buffer[cluster_decals_offset], &cluster_decals[0], cluster_decals_size * 4);
+ }
+
+ if (!cluster_lights.empty()) {
+ memcpy(&decal_cluster_buffer[light_cluster_data_offset], &cluster_lights[0], light_cluster_data_size * 4);
+ }
+
+ // light data and decal data in the same buffer, decal data first
+ // light_data_offset is in floats because we're copying to float buffer...
+ unsigned int light_data_offset = num_alive_decals * sizeof(ShaderDecal) / sizeof(float);
+ unsigned int light_data_size = num_alive_lights * sizeof(ShaderLight) / sizeof(float);
+
+ if (light_data_size > 0) {
+ // make sure it fits
+ if (decal_tbo.size() < light_data_offset + light_data_size) {
+ decal_tbo.resize(light_data_offset + light_data_size, 0.0f);
+ }
+
+ // TODO: we could avoid this memcpy by packing the data in decal_tbo buffer
+ // when looping over lights
+ memcpy(&decal_tbo[light_data_offset], &light_data_buffer[0], light_data_size * 4);
+ }
+
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ PROFILER_ZONE(g_profiler_ctx, "Upload buffer data");
+ // ...and then send it to the GPU
+ if(kBandwidthDisplay){
+ const int kBufSize = 512;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "buf_size*4: %u", buf_size*4);
+ Engine::Instance()->gui.AddDebugText("buf_size", buf, 0.5f);
+ FormatString(buf, kBufSize, "decal_tbo.size(): %u", decal_tbo.size());
+ Engine::Instance()->gui.AddDebugText("decal_tbo.size()", buf, 0.5f);
+ FormatString(buf, kBufSize, "sizeof(ShaderClusterInfo): %u", sizeof(ShaderClusterInfo));
+ Engine::Instance()->gui.AddDebugText("sizeof(ShaderClusterInfo)", buf, 0.5f);
+ }
+
+ PrecisionStopwatch stop_watch;
+ stop_watch.Start();
+ Textures::Instance()->updateBufferTexture(decal_cluster_texture, buf_size * 4, &decal_cluster_buffer[0]);
+ if(kBandwidthDisplay){
+ uint64_t time = stop_watch.StopAndReportNanoseconds();
+ const int kBufSize = 512;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "decal_cluster_texture: %u us", time / 1000);
+ Engine::Instance()->gui.AddDebugText("decal_cluster_texture", buf, 0.5f);
+ }
+ Textures::Instance()->updateBufferTexture(decal_data_texture, decal_tbo.size() * sizeof(float), &decal_tbo[0]);
+ if(kBandwidthDisplay){
+ uint64_t time = stop_watch.StopAndReportNanoseconds();
+ const int kBufSize = 512;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "decal_data_texture: %u us", time / 1000);
+ Engine::Instance()->gui.AddDebugText("decal_data_texture", buf, 0.5f);
+ }
+
+ clusterInfo.light_cluster_data_offset = light_cluster_data_offset;
+ // ... but here we divide by 4 since the shader samples from a vec4 buffer
+ LOG_ASSERT_EQ((sizeof(ShaderDecal) % (sizeof(float) * 4)), 0);
+ clusterInfo.light_data_offset = light_data_offset / 4;
+
+
+ stop_watch.Start();
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Cluster info buffer");
+ if(uniform_ring_buffer.gl_id == -1){
+ uniform_ring_buffer.Create(128*1024);
+ }
+ uniform_ring_buffer.Fill(sizeof(ShaderClusterInfo), &clusterInfo);
+ }
+ if(kBandwidthDisplay){
+ uint64_t time = stop_watch.StopAndReportNanoseconds();
+ const int kBufSize = 512;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "clusterInfo: %u us", time / 1000000);
+ Engine::Instance()->gui.AddDebugText("clusterInfo", buf, 0.5f);
+ }
+ if(kBandwidthDisplay){
+ static GLint max_tbo_size = -1;
+ if(max_tbo_size == -1){
+ glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, &max_tbo_size);
+ }
+ static GLint max_ubo_size = -1;
+ if(max_ubo_size == -1){
+ glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &max_ubo_size);
+ }
+ const int kBufSize = 512;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "GL_MAX_TEXTURE_BUFFER_SIZE: %d", max_tbo_size);
+ Engine::Instance()->gui.AddDebugText("GL_MAX_TEXTURE_BUFFER_SIZE", buf, 0.5f);
+ FormatString(buf, kBufSize, "GL_MAX_UNIFORM_BLOCK_SIZE: %d", max_ubo_size);
+ Engine::Instance()->gui.AddDebugText("GL_MAX_UNIFORM_BLOCK_SIZE", buf, 0.5f);
+ }
+ }
+}
+
+
+void SceneGraph::BindDecals(int the_shader)
+{
+ Textures* textures = Textures::Instance();
+ Shaders* shaders = Shaders::Instance();
+
+ int cluster_block_index = shaders->GetUBOBindIndex(the_shader, "ClusterInfo");
+
+ if (cluster_block_index == (int)GL_INVALID_INDEX) {
+ // no decals in this shader, skip the whole thing
+ return;
+ }
+
+ // TODO: pass in decal info so they can be drawn in one pass
+ // Pass in structure of all decals that overlap any instances of this type
+ // Decal transform
+ // Decal texture
+
+ // Pass in number of intersecting decals for each instance
+ // For each instance of envobject
+ // Pass in indices of each decal
+ TextureRef decal_color_texture_ref;
+
+ //Disabling normal texture as we've reached the texture limit.
+ TextureRef decal_normal_texture_ref;
+
+ decal_color_texture_ref = DecalTextures::Instance()->coloratlas->atlas_texture;
+ if (decal_color_texture_ref.valid()){
+ textures->bindTexture(decal_color_texture_ref, TEX_DECAL_COLOR);
+ shaders->SetUniformInt(shaders->GetTexUniform(TEX_DECAL_COLOR), TEX_DECAL_COLOR);
+ }
+
+ if (config["decal_normals"].toNumber<bool>()){
+ decal_normal_texture_ref = DecalTextures::Instance()->normalatlas->atlas_texture;
+ if (decal_normal_texture_ref.valid()){
+ textures->bindTexture(decal_normal_texture_ref, TEX_DECAL_NORMAL);
+ shaders->SetUniformInt(shaders->GetTexUniform(TEX_DECAL_NORMAL), TEX_DECAL_NORMAL);
+ }
+ }
+}
+
+
+void SceneGraph::BindLights(int the_shader) {
+ Textures* textures = Textures::Instance();
+ Shaders* shaders = Shaders::Instance();
+
+
+ int cluster_block_index = shaders->GetUBOBindIndex(the_shader, "ClusterInfo");
+ if (cluster_block_index == (int)GL_INVALID_INDEX) {
+ // no decals in this shader, skip the whole thing
+ return;
+ }
+
+ // Set up cluster parameters UBO
+ {
+ PROFILER_ENTER(g_profiler_ctx, "Setup decal parameters UBO");
+
+ GLint block_size = shaders->returnShaderBlockSize(cluster_block_index, the_shader);
+
+ LOG_ASSERT_LT(block_size, 16384);
+ LOG_ASSERT_EQ(block_size, sizeof(ShaderClusterInfo));
+
+ if(uniform_ring_buffer.gl_id != -1){
+ glBindBufferBase(GL_UNIFORM_BUFFER, UBO_CLUSTER_DATA, uniform_ring_buffer.gl_id);
+ glBindBufferRange( GL_UNIFORM_BUFFER, UBO_CLUSTER_DATA, uniform_ring_buffer.gl_id, uniform_ring_buffer.offset, uniform_ring_buffer.next_offset - uniform_ring_buffer.offset);
+ }
+ glUniformBlockBinding(shaders->programs[the_shader].gl_program, cluster_block_index, UBO_CLUSTER_DATA);
+ PROFILER_LEAVE(g_profiler_ctx);
+ }
+
+ shaders->SetUniformInt(shaders->GetTexUniform(TEX_LIGHT_DECAL_DATA_BUFFER), TEX_LIGHT_DECAL_DATA_BUFFER);
+ shaders->SetUniformInt(shaders->GetTexUniform(TEX_CLUSTER_BUFFER), TEX_CLUSTER_BUFFER);
+
+ textures->bindTexture(decal_data_texture, TEX_LIGHT_DECAL_DATA_BUFFER);
+ textures->bindTexture(decal_cluster_texture, TEX_CLUSTER_BUFFER);
+}
+
+static int HexToVal(char hex){
+ if(hex >= '0' && hex <= '9'){
+ return hex - '0';
+ } else if(hex >= 'A' && hex <= 'F'){
+ return hex - 'A' + 10;
+ } else {
+ return -1;
+ }
+}
+
+vec3 ColorFromString(const char* str) {
+ const char *char_ptr = str;
+ int color_id = 0;
+ int val = 0;
+ vec3 color;
+ char char_val = ' ';
+ while(char_val != '\0'){
+ char_val = *char_ptr;
+ if(char_val >= '0' && char_val <= '9'){
+ val *= 10;
+ val += char_val - '0';
+ } else if(char_val == ',' || char_val == '\0') {
+ color[color_id] = val / 255.0f;
+ ++color_id;
+ val = 0;
+ }
+ ++char_ptr;
+ }
+ return color;
+}
+
+void SceneGraph::ApplyScriptParams(SceneGraph* scenegraph, const ScriptParamMap& spm) {
+ scenegraph->level->SetScriptParams(spm);
+ {
+ ScriptParamMap::const_iterator iter = spm.find("Sky Rotation");
+ if(iter != spm.end()){
+ const ScriptParam& sp = iter->second;
+ float new_sky_rotation = sp.GetFloat();
+ Sky* sky = scenegraph->sky;
+ if(new_sky_rotation != sky->sky_rotation){
+ sky->sky_rotation = sp.GetFloat();
+ sky->LightingChanged(scenegraph->terrain_object_ != NULL);
+ }
+ }
+ }
+ {
+ ScriptParamMap::const_iterator iter = spm.find("Saturation");
+ if(iter == spm.end()){
+ scenegraph->level->script_params().ASAddFloat("Saturation", 1.0f);
+ }
+ }
+ {
+ ScriptParamMap::const_iterator iter = spm.find("Sky Tint");
+ ScriptParamMap::const_iterator iter2 = spm.find("Sky Brightness");
+ if(iter != spm.end() && iter2 != spm.end()){
+ const ScriptParam& sp = iter->second;
+ const std::string& new_sky_tint = sp.GetString();
+ /*if(new_sky_tint.size() == 7 && new_sky_tint[0] == '#'){
+ vec3 color;
+ color[0] = (HexToVal(new_sky_tint[1])*16.0f + HexToVal(new_sky_tint[2])) / 255.0f;
+ color[1] = (HexToVal(new_sky_tint[3])*16.0f + HexToVal(new_sky_tint[4])) / 255.0f;
+ color[2] = (HexToVal(new_sky_tint[5])*16.0f + HexToVal(new_sky_tint[6])) / 255.0f;
+ Sky* sky = scenegraph->sky;
+ if(color != sky->sky_tint){
+ sky->sky_tint = color;
+ sky->LightingChanged(scenegraph->terrain_object_ != NULL);
+ }
+ }*/
+ vec3 color = ColorFromString(new_sky_tint.c_str());
+ color *= iter2->second.GetFloat();
+ Sky* sky = scenegraph->sky;
+ if(color != sky->sky_tint){
+ sky->sky_tint = color;
+ sky->sky_base_tint = color;
+ sky->LightingChanged(scenegraph->terrain_object_ != NULL);
+ }
+ }
+ }
+ {
+ ScriptParamMap::const_iterator iter = spm.find("HDR White point");
+ if(iter != spm.end()){
+ const ScriptParam& sp = iter->second;
+ Graphics::Instance()->hdr_white_point = sp.GetFloat();
+ }
+ }
+ {
+ ScriptParamMap::const_iterator iter = spm.find("HDR Black point");
+ if(iter != spm.end()){
+ const ScriptParam& sp = iter->second;
+ Graphics::Instance()->hdr_black_point = sp.GetFloat();
+ }
+ }
+ {
+ ScriptParamMap::const_iterator iter = spm.find("HDR Bloom multiplier");
+ if(iter != spm.end()){
+ const ScriptParam& sp = iter->second;
+ Graphics::Instance()->hdr_bloom_mult = sp.GetFloat();
+ }
+ }
+ {
+ ScriptParamMap::const_iterator iter = spm.find("Fog amount");
+ if(iter != spm.end()){
+ const ScriptParam& sp = iter->second;
+ scenegraph->fog_amount = sp.GetFloat();
+ scenegraph->haze_mult = 8.0f * (float) pow(10, scenegraph->fog_amount - 5.0f);
+ }
+ }
+}
+
+void SceneGraph::PrintCurrentObjects()
+{
+ object_list::iterator objit = objects_.begin();
+
+ LOGI << "Listing current objects in scene" << std::endl;
+ for(; objit != objects_.end(); objit++ )
+ {
+ LOGI << **objit << std::endl;
+ }
+}
+
+int SceneGraph::CountObjectsWithName(const char* name) {
+ int count = 0;
+ object_list::iterator objit = objects_.begin();
+
+ LOGI << "Listing current objects in scene" << std::endl;
+ for(; objit != objects_.end(); objit++ ) {
+ if( strmtch((*objit)->name,name) ) {
+ count++;
+ }
+ }
+ return count;
+}
+
+bool SceneGraph::IsObjectSane(Object* obj) {
+ if(obj == NULL) return false;
+
+ for( size_t i = 0; i < destruction_sanity_size; i++ ) {
+ if( destruction_sanity[i] == obj ) {
+ return false;
+ }
+ }
+ return true;
+}
+
+const char* SceneGraph::GetDestroyedObjectInfo(int object_id) {
+ if( object_id >= 0 ) {
+ for( unsigned i = 0; i < destruction_memory_size; i++ ) {
+ if( destruction_memory_ids[i] == object_id) {
+ return &destruction_memory_strings[i*destruction_memory_string_size];
+ }
+ }
+ }
+ return "";
+}
+
+void SceneGraph::DumpState() {
+ std::ofstream output;
+ char dump_path[kPathSize];
+ GetScenGraphDumpPath(dump_path, kPathSize);
+
+ my_ofstream_open(output,dump_path);
+
+ if( output.is_open() ) {
+ LOGI << "Dumping all objects in scenegraph into " << dump_path << std::endl;
+ for( size_t i = 0; i < objects_.size(); i++ ) {
+ if( output.good() ) {
+ output << "[" << objects_[i]->GetID() << "]: \"" << CStringFromEntityType(objects_[i]->GetType()) << "\" " << objects_[i]->GetName() << std::endl;
+ }
+ }
+ } else {
+ LOGE << "Failed at opening " << dump_path << " for dumping. " << std::endl;
+ }
+}
+
+void SceneGraph::GetParticleShaderNames(std::map<std::string, int>& preload_shaders) {
+ Path p = FindFilePath("Data/shader_preload.xml", kDataPaths, true);
+ if(!p.isValid()) {
+ LOGW << "Couldn't load " << p.GetOriginalPath() << std::endl;
+ return;
+ }
+
+ if(!CheckFileAccess(p.GetFullPath())) {
+ LOGW << "Couldn't access " << p.GetFullPath() << std::endl;
+ return;
+ }
+
+ TiXmlDocument doc;
+ if(!doc.LoadFile(p.GetFullPath())) {
+ LOGE << "Couldn't load " << p.GetFullPath() << ": " << doc.ErrorDesc() << " on row " << doc.ErrorRow() << std::endl;
+ return;
+ }
+
+ TiXmlElement* root = doc.RootElement();
+ if(root && strcmp(root->Value(), "Shaders") == 0) {
+ root = root->FirstChildElement();
+ } else {
+ LOGE << p.GetFullPath() << " root element null or not \"Shaders\"" << std::endl;
+ return;
+ }
+
+ for(const TiXmlElement* field = root; field; field = field->NextSiblingElement()) {
+ const char* field_value = field->Value();
+
+ if(strcmp(field_value, "Shader") == 0) {
+ const char* attribute = field->Attribute("name");
+ const char* suffix = field->Attribute("suffix");
+ const char* optional = field->Attribute("optional");
+
+ int draw_type = 0;
+ if(suffix) {
+ draw_type = (strcmp(suffix, "true") == 0) ? kFullDraw : 0;
+ }
+ if(optional) {
+ if(strcmp(optional, "none") == 0) {
+ draw_type |= kOptionalNone;
+ } else if(strcmp(optional, "geometry") == 0) {
+ draw_type |= kOptionalGeometry;
+ } else if(strcmp(optional, "tessellation") == 0) {
+ draw_type |= kOptionalTessellation;
+ }
+ }
+
+ if(attribute) {
+ preload_shaders[attribute] = draw_type;
+ } else {
+ LOGE << "No name attribute found when parsing " << p.GetFullPath() << std::endl;
+ }
+ }
+ }
+}
+
+void SceneGraph::PreloadForDrawType(std::map<std::string, int>& preload_shaders, PreloadType type) {
+ Shaders* shaders = Shaders::Instance();
+ Object::DrawType draw_type;
+ switch(type) {
+ case kDrawDepthOnly: draw_type = Object::kDrawDepthOnly; break;
+ case kDrawAllShadowCascades: draw_type = Object::kDrawAllShadowCascades; break;
+ case kDrawDepthNoAA: draw_type = Object::kDrawDepthNoAA; break;
+ case kFullDraw: draw_type = Object::kFullDraw; break;
+ case kWireframe: draw_type = Object::kWireframe; break;
+ case kDecal: draw_type = Object::kDecal; break;
+ default:
+ return;
+ }
+
+ UpdateShaderSuffix(this, draw_type);
+ for(std::map<std::string, int>::iterator iter = preload_shaders.begin(); iter != preload_shaders.end(); ++iter) {
+ if(iter->second & type) {
+ const int kShaderStrSize = 1024;
+ char buf[kShaderStrSize];
+ FormatString(buf, kShaderStrSize, "%s %s", iter->first.c_str(), global_shader_suffix);
+ Shaders::OptionalShaders optional = Shaders::kNone;
+ if(iter->second & kOptionalGeometry)
+ optional = Shaders::kGeometry;
+ else if(iter->second & kOptionalTessellation)
+ optional = Shaders::kTesselation;
+ shaders->createProgram(shaders->returnProgram(buf, optional));
+ }
+ }
+}
+
+void SceneGraph::PreloadShaders() {
+ LOGI << "Preloading shaders..." << std::endl;
+ PROFILER_ZONE(g_profiler_ctx, "Preload shaders");
+ // There is no easy way to call DetailObjectSurface::GetShaderNames, so do it here for now
+ const char* detail_object_names[6] =
+ {
+ "envobject #DETAIL_OBJECT"
+ , "envobject #DETAIL_OBJECT #PLANT"
+ , "envobject #DETAIL_OBJECT #PLANT #LESS_PLANT_MOVEMENT"
+ , "envobject #DETAIL_OBJECT #TERRAIN"
+ , "envobject #DETAIL_OBJECT #TERRAIN #PLANT"
+ , "envobject #DETAIL_OBJECT #TERRAIN #PLANT #LESS_PLANT_MOVEMENT"
+ };
+
+ for(int i = 0; i < 6; ++i) {
+ const int kShaderStrSize = 1024;
+ char buf[2][kShaderStrSize];
+ char* shader_str[2] = {buf[0], buf[1]};
+
+ FormatString(shader_str[0], kShaderStrSize, "%s", detail_object_names[i]);
+ if(!Graphics::Instance()->config_.detail_object_decals()) {
+ FormatString(shader_str[1], kShaderStrSize, "%s %s", shader_str[0], "#NO_DECALS");
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(!Graphics::Instance()->config_.detail_object_shadows()) {
+ FormatString(shader_str[1], kShaderStrSize, "%s %s", shader_str[0], "#NO_DETAIL_OBJECT_SHADOWS");
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ preload_shaders[shader_str[0]] = kFullDraw | kDrawDepthOnly;
+ }
+
+ GetParticleShaderNames(preload_shaders);
+ Engine::Instance()->GetShaderNames(preload_shaders);
+ Graphics::Instance()->GetShaderNames(preload_shaders);
+ sky->GetShaderNames(preload_shaders);
+ flares.GetShaderNames(preload_shaders);
+ const PreloadType preload_types[6] = {
+ kDrawDepthOnly
+ , kDrawAllShadowCascades
+ , kDrawDepthNoAA
+ , kFullDraw
+ , kWireframe
+ , kDecal
+ };
+ for(int i = 0; i < 6; ++i)
+ PreloadForDrawType(preload_shaders, preload_types[i]);
+
+ // Load everything that isn't any of the above types
+ Shaders* shaders = Shaders::Instance();
+ for(std::map<std::string, int>::iterator iter = preload_shaders.begin(); iter != preload_shaders.end(); ++iter) {
+ if((iter->second & kPreloadTypeAll) == 0) {
+ Shaders::OptionalShaders optional = Shaders::kNone;
+ if(iter->second & kOptionalGeometry)
+ optional = Shaders::kGeometry;
+ else if(iter->second & kOptionalTessellation)
+ optional = Shaders::kTesselation;
+ shaders->createProgram(shaders->returnProgram(iter->first, optional));
+ }
+ }
+}
+
+void SceneGraph::LoadReflectionCaptureCubemaps()
+{
+ PROFILER_ZONE(g_profiler_ctx, "Load reflection capture cubemaps");
+ for (object_list::iterator iter = objects_.begin();
+ iter != objects_.end();
+ ++iter)
+ {
+ Object* obj = *iter;
+ if(obj->GetType() == _reflection_capture_object){
+ char save_path[kPathSize];
+ FormatString(save_path, kPathSize, "%s_refl_cap_%d.hdrcube", level_path_.GetOriginalPath(), obj->GetID());
+ char abs_path[kPathSize];
+ if(FindFilePath(save_path, abs_path, kPathSize, kAnyPath, false) != -1){
+ ReflectionCaptureObject* rco = (ReflectionCaptureObject*)obj;
+ rco->cube_map_ref = Textures::LoadCubeMapMipmapsHDR(abs_path); //TODO: if no_reflection_capture is true, only the global capture cube should be loaded.
+ if(rco->cube_map_ref.valid()){
+ rco->dirty = false;
+ if(rco->GetScriptParams()->ASGetInt("Global") == 1){
+ sky->SetSpecularCubeMapTexture(rco->cube_map_ref);
+ }
+ } else {
+ rco->dirty = true;
+ }
+ }
+ }
+ }
+ reflection_data_loaded = true;
+}
+
+//TODO: make a version that only clears the dynamic cubemaps.
+void SceneGraph::UnloadReflectionCaptureCubemaps() {
+ for (object_list::iterator iter = objects_.begin();
+ iter != objects_.end();
+ ++iter)
+ {
+ Object* obj = *iter;
+ if(obj->GetType() == _reflection_capture_object){
+ ReflectionCaptureObject* rco = (ReflectionCaptureObject*)obj;
+ rco->cube_map_ref.clear();
+ rco->dirty = true;
+ }
+ }
+
+ sky->ResetSpecularCubeMapTexture();
+ reflection_data_loaded = false;
+}
diff --git a/Source/Main/scenegraph.h b/Source/Main/scenegraph.h
new file mode 100644
index 00000000..0a5bf178
--- /dev/null
+++ b/Source/Main/scenegraph.h
@@ -0,0 +1,320 @@
+//-----------------------------------------------------------------------------
+// Name: scenegraph.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Graphics/drawbatch.h>
+#include <Graphics/texturepack.h>
+#include <Graphics/lightprobecollection.hpp>
+#include <Graphics/dynamiclightcollection.hpp>
+#include <Graphics/flares.h>
+#include <Graphics/navmeshrenderer.h>
+
+#include <Editors/entity_type.h>
+#include <Editors/object_sanity_state.h>
+
+#include <Math/vec4.h>
+#include <Objects/object_msg.h>
+#include <Asset/Asset/material.h>
+#include <Internal/collisiondetection.h>
+#include <Scripting/scriptparams.h>
+
+#include <list>
+#include <map>
+
+class Sound;
+class Object;
+class TerrainObject;
+class DecalObject;
+class Hotspot;
+class Group;
+struct SphereCollision;
+class BulletWorld;
+class NavMesh;
+class ParticleSystem;
+class MapEditor;
+class Level;
+class Sky;
+class Textures;
+class EnvObject;
+class LightVolumeObject;
+class MovementObject;
+
+struct SceneLight {
+ vec3 pos;
+ vec3 color;
+ float intensity;
+};
+
+struct ShadowCacheObjectLightBounds {
+ float min_bounds[2];
+ float max_bounds[2];
+ bool is_calculated;
+ bool is_ignored;
+};
+
+class SceneGraph {
+ public:
+ enum SceneDrawType { kStaticOnly, kStaticAndDynamic };
+ SceneLight primary_light;
+ MapEditor* map_editor;
+ Level* level;
+ Sky* sky;
+ Flares flares;
+ ParticleSystem *particle_system;
+ TerrainObject* terrain_object_;
+ LightProbeCollection light_probe_collection;
+ DynamicLightCollection dynamic_light_collection;
+
+ std::vector<mat4> ref_cap_matrix;
+ std::vector<mat4> ref_cap_matrix_inverse;
+ TextureRef cubemaps;
+ std::vector<TextureRef> sub_cubemaps;
+ bool cubemaps_need_refresh;
+ bool reflection_data_loaded;
+ float fog_amount;
+ float haze_mult; // derived from fog_amount
+
+ unsigned infreq_update_index;
+
+ Path level_path_;
+ bool level_has_been_previously_saved_;
+ std::string level_name_;
+ std::string level_visible_name_;
+ std::string level_visible_description_;
+
+ // Objects that must be updated every timestep
+ static const int kMaxUpdateObjects = 1024;
+ Object* update_objects_[kMaxUpdateObjects];
+ int num_update_objects;
+
+ typedef std::vector<Object*> object_list;
+ object_list objects_;
+ object_list collide_objects_;
+ object_list visible_objects_; // Objects that have a Draw() function
+ std::vector<EnvObject*> visible_static_meshes_;
+ std::vector<ShadowCacheObjectLightBounds> visible_static_meshes_shadow_cache_bounds_;
+ std::vector<uint16_t> visible_static_mesh_indices_; // Will be used to keep both arrays in sorted order. Should never be more than 65536 visible static meshes
+ std::vector<TerrainObject*> terrain_objects_;
+ std::vector<ShadowCacheObjectLightBounds> terrain_objects_shadow_cache_bounds_;
+ object_list decal_objects_;
+ object_list movement_objects_;
+ object_list item_objects_;
+ object_list hotspots_;
+ object_list navmesh_hints_;
+ object_list navmesh_connections_;
+ object_list path_points_;
+ std::vector<LightVolumeObject*> light_volume_objects_;
+ std::vector<int> object_ids_to_delete;
+
+ bool hotspots_modified_;
+
+ static const size_t destruction_sanity_size = 256;
+ size_t destruction_sanity_insert_position;
+ Object* destruction_sanity[destruction_sanity_size];
+
+ static const size_t destruction_memory_size = 128;
+ static const size_t destruction_memory_string_size = 128;
+ size_t destruction_memory_insert_position;
+ int destruction_memory_ids[destruction_memory_size];
+ char destruction_memory_strings[destruction_memory_size*destruction_memory_string_size];
+
+ static const unsigned int kMaxWarnings = 64;
+ ObjectSanityState sanity_list[kMaxWarnings];
+ uint32_t partial_object_loop_counter;
+
+ // Maximum total number of static and dynamic decals
+ // This could be raised as high as 65535, after that refactoring is required to replace 16-bit uints
+ static const unsigned int kMaxDecals = 20000;
+ // Maximum number of decals added at run time
+ // When count exceeds this older ones are removed
+ static const unsigned int kMaxDynamicDecals = 1000;
+ // Maximum number of decals that can be added via editor
+ static const unsigned int kMaxStaticDecals = kMaxDecals - kMaxDynamicDecals;
+ typedef std::deque<DecalObject*> decal_deque;
+ decal_deque dynamic_decals;
+
+ BulletWorld *bullet_world_;
+ BulletWorld *abstract_bullet_world_;
+ BulletWorld *plant_bullet_world_;
+
+ SceneGraph();
+ ~SceneGraph();
+
+ void UpdatePhysicsTransforms();
+ bool addObject(Object* new_object);
+
+ bool AddDynamicDecal(DecalObject *decal);
+
+ Collision lineCheck(const vec3 &start, const vec3 &end);
+ Collision lineCheckCollidable(const vec3 &start, const vec3 &end, Object* notHit = NULL);
+ void LineCheckAll(const vec3 &start, const vec3 &end, std::vector<Collision> *collisions);
+
+ void Update(float timestep, float curr_game_time);
+ void Draw(SceneGraph::SceneDrawType scene_draw_type);
+
+ Object* GetLastSelected();
+ void ReturnSelected(std::vector<Object*>* selected);
+ void UnselectAll();
+
+ Object* GetObjectFromID(int object_id);
+ bool DoesObjectWithIdExist(int object_id);
+ std::vector<Object*> GetObjectsOfType(enum EntityType type);
+ void UnlinkObject(Object *o);
+ void LinkObject(Object* new_object);
+ void CreateNavMesh();
+ void SaveNavMesh();
+ bool LoadNavMesh();
+ void AddSceneToNavmesh();
+ NavMesh* GetNavMesh();
+ const MaterialEvent *GetMaterialEvent( const std::string &the_event, const vec3 &event_pos );
+ const MaterialEvent *GetMaterialEvent( const std::string &the_event, const vec3 &event_pos, const std::string &mod );
+ const MaterialEvent *GetMaterialEvent( const std::string &the_event, const vec3 &event_pos, Object* excluded_object );
+ const MaterialEvent *GetMaterialEvent( const std::string &the_event, const vec3 &event_pos, const std::string &mod, Object* excluded_object );
+ Object* GetClosestObject(const vec3 &pos, Object* excluded_object = NULL, vec3 *hit_pos = NULL, int *hit_tri = NULL);
+ const MaterialDecal *GetMaterialDecal( const std::string &type, const vec3 &pos );
+ const MaterialParticle *GetMaterialParticle( const std::string &type, const vec3 &pos );
+ float GetMaterialHardness( const vec3 &pos, Object* excluded_object = NULL );
+ float GetMaterialFriction( const vec3 &pos, Object* excluded_object = NULL );
+ float GetMaterialSharpPenetration( const vec3 &pos, Object* excluded_object = NULL );
+ vec3 GetColorAtPoint( const vec3 &pos );
+ void AssignID(Object* obj);
+ void QueueLevelReset();
+ void GetSweptSphereCollisionCharacters( const vec3 &pos, const vec3 &pos2, float radius, SphereCollision &as_col );
+ int CheckRayCollisionCharacters( const vec3 &start, const vec3 &end, vec3 *point, vec3 *normal, int *bone );
+ void GetPlayerCharacterIDs(int* num_avatars, int avatar_ids[], int max_avatars);
+ void GetNPCCharacterIDs(int* num_avatars, int avatar_ids[], int max_avatars);
+ void GetCharacterIDs(int* num_avatars, int avatar_ids[], int max_avatars);
+ void SendMessageToAllObjects( OBJECT_MSG::Type type );
+ void SendScriptMessageToAllObjects( std::string& msg );
+ std::vector<MovementObject*> GetControlledMovementObjects();
+ std::vector<MovementObject*> GetControllableMovementObjects();
+
+ bool VerifySanity();
+
+ enum DepthType {
+ kDepthShadow,
+ kDepthAllShadowCascades,
+ kDepthPrePass
+ };
+ void DrawDepthMap(const mat4& proj_view_matrix, const vec4* cull_planes, int num_cull_planes, DepthType depth_type, SceneDrawType scene_draw_type);
+ int GetAndReserveID();
+ int LinkUpdateObject( Object* obj );
+ void UnlinkUpdateObject( Object* obj, int entry );
+ void Dispose();
+
+ void SetNavMeshVisible( bool v );
+ void SetCollisionNavMeshVisible( bool v );
+
+ bool IsNavMeshVisible();
+ bool IsCollisionNavMeshVisible();
+
+ void PrepareLightsAndDecals(vec2 active_screen_start, vec2 active_screen_end, vec2 screen_dims);
+ void BindDecals(int the_shader);
+ void BindLights(int the_shader);
+
+ static void ApplyScriptParams(SceneGraph* scenegraph, const ScriptParamMap& spm);
+
+ void PrintCurrentObjects();
+ int CountObjectsWithName(const char* name);
+ bool IsObjectSane(Object* obj);
+
+ const char* GetDestroyedObjectInfo(int object_id);
+
+ void DumpState();
+
+ enum PreloadType {
+ kUnknown = (1<<0),
+ kDrawDepthOnly = (1<<1),
+ kDrawAllShadowCascades = (1<<2),
+ kDrawDepthNoAA = (1<<3),
+ kFullDraw = (1<<4),
+ kWireframe = (1<<5),
+ kDecal = (1<<6),
+ kPreloadTypeAll = (1<<7) - 1,
+ kOptionalNone = (1<<7),
+ kOptionalGeometry = (1<<8),
+ kOptionalTessellation = (1<<9)
+ };
+ void GetParticleShaderNames(std::map<std::string, int>& preload_shaders);
+ void PreloadForDrawType(std::map<std::string, int>& preload_shaders, PreloadType type);
+ void PreloadShaders();
+private:
+ bool visible_objects_need_sort;
+ bool queued_level_reset_;
+ typedef std::vector<Object*> IDMap;
+ IDMap object_from_id_map_;
+ NavMesh *nav_mesh_;
+
+ NavMeshRenderer nav_mesh_renderer_;
+
+ // this buffer contains decal and light data
+ std::vector<float> decal_tbo;
+ TextureRef decal_data_texture;
+
+ // this buffer contains
+ // 1. grid lookup (should be in a separate 3D texture)
+ // 2. decal cluster contents (decal indices)
+ // 2. light cluster contents (light indices)
+ std::vector<uint32_t> decal_cluster_buffer;
+ TextureRef decal_cluster_texture;
+
+ // int is a bitmask of Object::DrawType
+ std::map<std::string, int> preload_shaders;
+
+struct ClusterData {
+ uint32_t next;
+ uint16_t item;
+
+ ClusterData()
+ : next(0)
+ , item(0)
+ {
+ }
+};
+
+
+ // in pixels
+ // This value is hardcoded in shaders, so has to be modified there as well.
+ unsigned int cluster_size;
+ unsigned int num_z_clusters;
+
+ // 3D texture of cluster pointers
+ std::vector<uint32_t> cluster_list_heads;
+ // 1D texture of cluster pointers
+ std::vector<ClusterData> cluster_list_contents;
+
+ // number of decals in the cluster, size = number of clusters
+ std::vector<uint16_t> cluster_decal_counts;
+ std::vector<uint32_t> decal_grid_lookup;
+ std::vector<unsigned int> cluster_decals;
+
+ // number of lights in each cluster, size = number of clusters
+ std::vector<unsigned int> cluster_light_counts;
+ std::vector<uint32_t> light_grid_lookup;
+ std::vector<unsigned int> cluster_lights;
+public:
+ void LoadReflectionCaptureCubemaps();
+ void UnloadReflectionCaptureCubemaps();
+};
+
+vec3 ColorFromString(const char* str);
diff --git a/Source/Math/enginemath.cpp b/Source/Math/enginemath.cpp
new file mode 100644
index 00000000..84d7eae0
--- /dev/null
+++ b/Source/Math/enginemath.cpp
@@ -0,0 +1,161 @@
+//-----------------------------------------------------------------------------
+// Name: enginemath.cpp
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: This contains most 3d math functions and classes
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "enginemath.h"
+
+#include <Math/vec3math.h>
+
+#define _USE_MATH_DEFINES
+#include <cmath>
+#include <cstdlib>
+#include <cstdio>
+#include <algorithm>
+
+void PlaneSpace(const vec3 &n, vec3 &p, vec3 &q) {
+ if (fabs(n[2]) > 0.7071067f) {
+ // choose p in y-z plane
+ float a = n[1]*n[1] + n[2]*n[2];
+ float k = 1.0f/sqrtf(a);
+ p[0] = 0;
+ p[1] = -n[2]*k;
+ p[2] = n[1]*k;
+ // set q = n x p
+ q[0] = a*k;
+ q[1] = -n[0]*p[2];
+ q[2] = n[0]*p[1];
+ }
+ else {
+ // choose p in x-y plane
+ float a = n[0]*n[0] + n[1]*n[1];
+ float k = 1.0f/sqrtf(a);
+ p[0] = -n[1]*k;
+ p[1] = n[0]*k;
+ p[2] = 0;
+ // set q = n x p
+ q[0] = -n[2]*p[1];
+ q[1] = n[2]*p[0];
+ q[2] = a*k;
+ }
+}
+float RangedRandomFloat(float min, float max) {
+ if(min == max){
+ return min;
+ }
+ return (((float)abs(rand()))/RAND_MAX*((float)(max)-(float)(min))+(float)(min));
+}
+
+int RangedRandomInt(int min, int max) { // Inclusive
+ return abs(rand())%(max-min+1)+min;
+}
+
+float Range(float val, float min_val, float max_val) {
+ float temp_val = val;
+ temp_val -= min_val;
+ temp_val /= (max_val-min_val);
+ return min(1.0f,max(0.0f,temp_val));
+}
+
+float YAxisRotationFromVector(const vec3 &theVector)
+{
+ vec3 vector(theVector.x(),0,theVector.z());
+ vector = normalize(vector);
+ float new_rotation = acos(vector.z())/3.1415f*180.0f;
+ if(vector.x()<0)new_rotation*=-1;
+ new_rotation+=180;
+ return new_rotation;
+}
+
+vec3 AngleAxisRotation(const vec3 &thePoint, const vec3 &theAxis, const float howmuch){
+ return AngleAxisRotationRadian(thePoint, theAxis, howmuch * deg2radf);
+}
+
+vec3 AngleAxisRotationRadian(const vec3 &thePoint, const vec3 &theAxis, const float howmuch){
+ float costheta=cosf(howmuch);
+ return thePoint*costheta+theAxis*(dot(thePoint,theAxis))*(1-costheta)+cross(thePoint,theAxis)*sinf(howmuch);
+}
+
+vec3 doRotation(const vec3 &thePoint, const float xang, const float yang, const float zang){
+ return doRotationRadian(thePoint, xang*deg2radf, yang*deg2radf, zang*deg2radf);
+}
+
+vec3 doRotationRadian(const vec3 &thePoint, const float xang, const float yang, const float zang){
+ vec3 newpoint;
+ vec3 oldpoint;
+
+ oldpoint=thePoint;
+
+ if(yang!=0){
+ newpoint.z()=oldpoint.z()*cosf(yang)-oldpoint.x()*sinf(yang);
+ newpoint.x()=oldpoint.z()*sinf(yang)+oldpoint.x()*cosf(yang);
+ oldpoint.z()=newpoint.z();
+ oldpoint.x()=newpoint.x();
+ }
+
+ if(zang!=0){
+ newpoint.x()=oldpoint.x()*cosf(zang)-oldpoint.y()*sinf(zang);
+ newpoint.y()=oldpoint.y()*cosf(zang)+oldpoint.x()*sinf(zang);
+ oldpoint.x()=newpoint.x();
+ oldpoint.y()=newpoint.y();
+ }
+
+ if(xang!=0){
+ newpoint.y()=oldpoint.y()*cosf(xang)-oldpoint.z()*sinf(xang);
+ newpoint.z()=oldpoint.y()*sinf(xang)+oldpoint.z()*cosf(xang);
+ oldpoint.z()=newpoint.z();
+ oldpoint.y()=newpoint.y();
+ }
+
+ return oldpoint;
+}
+
+int log2(unsigned int x)
+{
+ int log = -1; // special case for log2(0)
+ while (x != 0) { x >>= 1; log++; }
+ return log;
+}
+
+void GetRotationBetweenVectors( const vec3 &a, const vec3 &b, quaternion &rotate ) {
+ vec3 rotate_axis = normalize(cross(a, b));
+ vec3 up = normalize(a);
+ vec3 right_vec = cross(up, rotate_axis);
+ vec3 ik_dir = normalize(b);
+ float rotate_angle = atan2f(-dot(ik_dir, right_vec), dot(ik_dir, up));
+ rotate = quaternion(vec4(rotate_axis, rotate_angle));
+}
+
+const uint64_t m1 = 0x5555555555555555; //binary: 0101...
+const uint64_t m2 = 0x3333333333333333; //binary: 00110011..
+const uint64_t m4 = 0x0f0f0f0f0f0f0f0f; //binary: 4 zeros, 4 ones ...
+const uint64_t m8 = 0x00ff00ff00ff00ff; //binary: 8 zeros, 8 ones ...
+const uint64_t m16 = 0x0000ffff0000ffff; //binary: 16 zeros, 16 ones ...
+const uint64_t m32 = 0x00000000ffffffff; //binary: 32 zeros, 32 ones
+const uint64_t hff = 0xffffffffffffffff; //binary: all ones
+const uint64_t h01 = 0x0101010101010101; //the sum of 256 to the power of 0,1,2,3...
+
+int popcount(uint64_t x) {
+ x -= (x >> 1) & m1; //put count of each 2 bits into those 2 bits
+ x = (x & m2) + ((x >> 2) & m2); //put count of each 4 bits into those 4 bits
+ x = (x + (x >> 4)) & m4; //put count of each 8 bits into those 8 bits
+ return (x * h01)>>56; //returns left 8 bits of x + (x<<8) + (x<<16) + (x<<24) + ...
+}
diff --git a/Source/Math/enginemath.h b/Source/Math/enginemath.h
new file mode 100644
index 00000000..cb652e64
--- /dev/null
+++ b/Source/Math/enginemath.h
@@ -0,0 +1,123 @@
+//-----------------------------------------------------------------------------
+// Name: enginemath.h
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: This contains most 3d math functions and classes
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/mat4.h>
+#include <Math/mat3.h>
+#include <Math/vec3.h>
+#include <Math/quaternions.h>
+
+#include <Internal/integer.h>
+
+#include <algorithm>
+
+using std::min;
+using std::max;
+
+const double PI = 3.141592653589793238462643383279502884197;
+const float PI_f = (float)PI;
+const double deg2rad = PI/180.0;
+const double rad2deg = 180.0/PI;
+const float deg2radf = (float)deg2rad;
+const float rad2degf = (float)rad2deg;
+
+const float EPSILON = 0.000001f;
+
+#define PI_DEFINED 1
+
+#define ISNAN(x) (x!=x)
+
+float RangedRandomFloat(float min, float max);
+int RangedRandomInt(int min, int max);
+
+//-----------------------------------------------------------------------------
+// Function Prototypes
+//-----------------------------------------------------------------------------
+float YAxisRotationFromVector(const vec3 &theVector);
+vec3 AngleAxisRotation(const vec3 &thePoint, const vec3 &theAxis, const float howmuch);
+vec3 AngleAxisRotationRadian(const vec3 &thePoint, const vec3 &theAxis, const float howmuch);
+vec3 doRotation(const vec3 &thePoint, const float xang, const float yang, const float zang);
+vec3 doRotationRadian(const vec3 &thePoint, const float xang, const float yang, const float zang);
+
+inline float clamp(float val, float floor, float ceil);
+float Range(float val, float min_val, float max_val);
+
+//-----------------------------------------------------------------------------
+// Inline Function Definitions
+//-----------------------------------------------------------------------------
+
+inline float square( float f ) { return (f*f) ;}
+inline int square( int f ) { return (f*f) ;}
+
+template <typename T> inline T mix(T x, T y, float alpha) {
+ return x * (1.0f - alpha) + y * alpha;
+}
+
+template <typename T> inline T mix(T x, T y, double alpha) {
+ return x * (1.0 - alpha) + y * alpha;
+}
+
+template <typename T> T BlendFour(T* values, float* weights) {
+ T value;
+ float total_weight = weights[0] + weights[1];
+ if(total_weight > 0.0f){
+ value = mix(values[0],values[1],weights[1]/total_weight);
+ }
+ total_weight += weights[2];
+ if(total_weight > 0.0f){
+ value = mix(value,values[2],weights[2]/total_weight);
+ }
+ total_weight += weights[3];
+ if(total_weight > 0.0f){
+ value = mix(value,values[3],weights[3]/total_weight);
+ }
+ return value;
+}
+
+inline float clamp(float val, float floor, float ceil) {
+ if(val < floor){
+ return floor;
+ } else if(val > ceil){
+ return ceil;
+ } else {
+ return val;
+ }
+}
+
+inline int clamp(int val, int floor, int ceil) {
+ if(val < floor){
+ return floor;
+ } else if(val > ceil){
+ return ceil;
+ } else {
+ return val;
+ }
+}
+
+
+void PlaneSpace(const vec3 &n, vec3 &p, vec3 &q);
+
+void GetRotationBetweenVectors(const vec3 &a, const vec3 &b, quaternion &rotate);
+
+int popcount(uint64_t x);
diff --git a/Source/Math/ivec2.h b/Source/Math/ivec2.h
new file mode 100644
index 00000000..fb93cd25
--- /dev/null
+++ b/Source/Math/ivec2.h
@@ -0,0 +1,66 @@
+//-----------------------------------------------------------------------------
+// Name: ivec2.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <iostream>
+
+class ivec2{
+public:
+ int entries[2];
+
+ inline ivec2(int val = 0) {
+ entries[0] = val;
+ entries[1] = val;
+ }
+
+ inline ivec2(int newx, int newy) {
+ entries[0] = newx;
+ entries[1] = newy;
+ }
+
+ inline int& operator[](const int which) {
+ return entries[which];
+ }
+
+ inline const int& operator[](const int which) const {
+ return entries[which];
+ }
+
+ inline ivec2& operator=(int param) {
+ entries[0]=param;
+ entries[1]=param;
+ return *this;
+ }
+
+ inline ivec2& operator=(const ivec2 &param) {
+ entries[0]=param.entries[0];
+ entries[1]=param.entries[1];
+ return *this;
+ }
+};
+
+inline std::ostream& operator<<(std::ostream& os, const ivec2& v )
+{
+ os << "ivec2(" << v[0] << "," << v[1] << ")";
+ return os;
+}
diff --git a/Source/Math/ivec2math.h b/Source/Math/ivec2math.h
new file mode 100644
index 00000000..dc6ecf77
--- /dev/null
+++ b/Source/Math/ivec2math.h
@@ -0,0 +1,119 @@
+//-----------------------------------------------------------------------------
+// Name: ivec2math.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/ivec2.h>
+
+inline ivec2& operator+=(ivec2 &vec, const ivec2 & param) {
+ vec.entries[0] += param.entries[0];
+ vec.entries[1] += param.entries[1];
+ return vec;
+}
+
+inline ivec2& operator-=(ivec2 &vec, const ivec2 & param) {
+ vec.entries[0] -= param.entries[0];
+ vec.entries[1] -= param.entries[1];
+ return vec;
+}
+
+inline ivec2& operator*=(ivec2 &vec, int param) {
+ vec.entries[0] *= param;
+ vec.entries[1] *= param;
+ return vec;
+}
+
+inline ivec2& operator*=(ivec2 &vec, const ivec2 & param) {
+ vec.entries[0] *= param.entries[0];
+ vec.entries[1] *= param.entries[1];
+ return vec;
+}
+
+inline ivec2& operator/=(ivec2 &vec, int param) {
+ vec.entries[0] /= param;
+ vec.entries[1] /= param;
+ return vec;
+}
+
+inline bool operator!=(const ivec2 &vec, const ivec2 &param) {
+ return ( vec.entries[0] != param.entries[0] ||
+ vec.entries[1] != param.entries[1]);
+}
+
+inline bool operator!=(const ivec2& vec, int param) {
+ return ( vec.entries[0] != param ||
+ vec.entries[1] != param);
+}
+
+inline ivec2 operator+( const ivec2 &vec_a, const ivec2 &vec_b ) {
+ ivec2 vec(vec_a);
+ vec += vec_b;
+ return vec;
+}
+
+inline ivec2 operator-( const ivec2 &vec_a, const ivec2 &vec_b ) {
+ ivec2 vec(vec_a);
+ vec -= vec_b;
+ return vec;
+}
+
+inline ivec2 operator*( const ivec2 &vec_a, const int param ) {
+ ivec2 vec(vec_a);
+ vec *= param;
+ return vec;
+}
+
+inline ivec2 operator/( const ivec2 &vec_a, const int param ) {
+ ivec2 vec(vec_a);
+ vec /= param;
+ return vec;
+}
+
+inline ivec2 operator/( const ivec2 &vec_a, const ivec2 &vec_b ) {
+ return ivec2(vec_a.entries[0]/vec_b.entries[0],
+ vec_a.entries[1]/vec_b.entries[1]);
+}
+
+
+inline ivec2 operator*(const ivec2 &vec_a, const ivec2 &vec_b) {
+ return ivec2(vec_a[0]*vec_b[0],
+ vec_a[1]*vec_b[1]);
+}
+
+inline ivec2 operator-(const ivec2 &vec) {
+ return vec * 1;
+}
+
+inline ivec2 operator*(int param, const ivec2 &vec_b) {
+ return vec_b*param;
+}
+
+inline bool operator==( const ivec2 &vec_a, const ivec2 &vec_b ) {
+ return (vec_a.entries[0]==vec_b.entries[0] &&
+ vec_a.entries[1]==vec_b.entries[1]);
+}
+inline bool operator<( const ivec2 &a, const ivec2 &b ) {
+ return a.entries[0] < b.entries[0] ||
+ (a.entries[0] == b.entries[0] &&
+ (a.entries[1] < b.entries[1] ||
+ (a.entries[1] == b.entries[1])));
+}
diff --git a/Source/Math/ivec3.h b/Source/Math/ivec3.h
new file mode 100644
index 00000000..a62c0ab5
--- /dev/null
+++ b/Source/Math/ivec3.h
@@ -0,0 +1,98 @@
+//-----------------------------------------------------------------------------
+// Name: ivec3.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/ivec2.h>
+
+#include <cassert>
+#include <iostream>
+
+class ivec3{
+ public:
+ int entries[3];
+
+ inline explicit ivec3(int val = 0.0f) {
+ entries[0] = val;
+ entries[1] = val;
+ entries[2] = val;
+ }
+
+ inline ivec3(int newx, int newy, int newz) {
+ entries[0] = newx;
+ entries[1] = newy;
+ entries[2] = newz;
+ }
+
+ inline ivec3(const ivec2 &vec, int newz) {
+ entries[0] = vec[0];
+ entries[1] = vec[1];
+ entries[2] = newz;
+ }
+
+ inline ivec3& operator=(int param) {
+ entries[0] = param;
+ entries[1] = param;
+ entries[2] = param;
+ return *this;
+ }
+
+ inline ivec3& operator=(const ivec3 &param) {
+ entries[0] = param.entries[0];
+ entries[1] = param.entries[1];
+ entries[2] = param.entries[2];
+ return *this;
+ }
+
+ inline int& operator[](const int which) {
+ assert(which < 3);
+ return entries[which];
+ }
+
+ inline const int& operator[](const int which) const {
+ return entries[which];
+ }
+
+ inline const int& x() const { return entries[0]; }
+ inline const int& y() const { return entries[1]; }
+ inline const int& z() const { return entries[2]; }
+ inline int& x() { return entries[0]; }
+ inline int& y() { return entries[1]; }
+ inline int& z() { return entries[2]; }
+ inline const int& r() const { return entries[0]; }
+ inline const int& g() const { return entries[1]; }
+ inline const int& b() const { return entries[2]; }
+ inline int& r() { return entries[0]; }
+ inline int& g() { return entries[1]; }
+ inline int& b() { return entries[2]; }
+
+ inline ivec2 xy() const {
+ return ivec2(entries[0], entries[1]);
+ }
+
+ inline int *operator*() { return entries; }
+};
+
+inline std::ostream& operator<<(std::ostream &os, const ivec3& vec) {
+ os << "ivec3(" << vec[0] << ", " << vec[1] << ", " << vec[2] << ")";
+ return os;
+}
diff --git a/Source/Math/ivec4.h b/Source/Math/ivec4.h
new file mode 100644
index 00000000..6b4cfc40
--- /dev/null
+++ b/Source/Math/ivec4.h
@@ -0,0 +1,124 @@
+//-----------------------------------------------------------------------------
+// Name: ivec4.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/ivec3.h>
+
+#include <iostream>
+
+class ivec4{
+ public:
+ int entries[4];
+
+ inline int& operator[](const int which) {
+ return entries[which];
+ }
+ inline const int& operator[](const int which) const {
+ return entries[which];
+ }
+
+ inline explicit ivec4() {
+ entries[0] = 0;
+ entries[1] = 0;
+ entries[2] = 0;
+ entries[3] = 1;
+ }
+
+ inline ivec4(int val) {
+ entries[0] = val;
+ entries[1] = val;
+ entries[2] = val;
+ entries[3] = val;
+ }
+
+ inline ivec4(int newx, int newy, int newz, int neww = 1.0f) {
+ entries[0] = newx;
+ entries[1] = newy;
+ entries[2] = newz;
+ entries[3] = neww;
+ }
+
+ inline ivec4(const ivec3 &vec, int val = 1.0f) {
+ entries[0] = vec[0];
+ entries[1] = vec[1];
+ entries[2] = vec[2];
+ entries[3] = val;
+ }
+
+ inline ivec4& operator=(int param){
+ entries[0]=param;
+ entries[1]=param;
+ entries[2]=param;
+ entries[3]=param;
+ return *this;
+ }
+
+ inline ivec4& operator=(const ivec4 &param){
+ entries[0]=param.entries[0];
+ entries[1]=param.entries[1];
+ entries[2]=param.entries[2];
+ entries[3]=param.entries[3];
+ return *this;
+ }
+
+ inline ivec4& operator=(const ivec3 &param){
+ entries[0]=param.entries[0];
+ entries[1]=param.entries[1];
+ entries[2]=param.entries[2];
+ return *this;
+ }
+
+ inline const int& x() const { return entries[0]; }
+ inline const int& y() const { return entries[1]; }
+ inline const int& z() const { return entries[2]; }
+ inline const int& w() const { return entries[3]; }
+ inline int& x() { return entries[0]; }
+ inline int& y() { return entries[1]; }
+ inline int& z() { return entries[2]; }
+ inline int& w() { return entries[3]; }
+
+ inline ivec2 xy() const {
+ return ivec2(entries[0], entries[1]);
+ }
+
+ inline ivec3 xyz() const {
+ return ivec3(entries[0], entries[1], entries[2]);
+ }
+
+ inline void ToArray(int *array) const {
+ array[0] = entries[0];
+ array[1] = entries[1];
+ array[2] = entries[2];
+ array[3] = entries[3];
+ }
+ inline int *operator*()
+ {
+ return entries;
+ }
+};
+
+inline std::ostream& operator<<(std::ostream& os, const ivec4& v )
+{
+ os << "ivec4(" << v[0] << "," << v[1] << "," << v[2] << "," << v[3] << ")";
+ return os;
+}
diff --git a/Source/Math/mat3.cpp b/Source/Math/mat3.cpp
new file mode 100644
index 00000000..214ca783
--- /dev/null
+++ b/Source/Math/mat3.cpp
@@ -0,0 +1,73 @@
+//-----------------------------------------------------------------------------
+// Name: mat3.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "mat3.h"
+
+#include <memory>
+#include <cstring>
+
+mat3::mat3(float val)
+{
+ entries[0] = val;
+ entries[4] = val;
+ entries[8] = val;
+ entries[3] = entries[2] = entries[1] = 0.0f;
+ entries[7] = entries[6] = entries[5] = 0.0f;
+}
+mat3::mat3(float e0, float e1, float e2,
+ float e3, float e4, float e5,
+ float e6, float e7, float e8)
+{
+ entries[0] = e0;
+ entries[1] = e1;
+ entries[2] = e2;
+ entries[3] = e3;
+ entries[4] = e4;
+ entries[5] = e5;
+ entries[6] = e6;
+ entries[7] = e7;
+ entries[8] = e8;
+
+}
+mat3::mat3(const float * rhs)
+{
+ memcpy(entries,rhs,sizeof(entries));
+}
+
+mat3::mat3(const mat4& m)
+{
+ entries[0] = m[0];
+ entries[1] = m[1];
+ entries[2] = m[2];
+ entries[3] = m[4];
+ entries[4] = m[5];
+ entries[5] = m[6];
+ entries[6] = m[8];
+ entries[7] = m[9];
+ entries[8] = m[10];
+}
+
+vec3 mat3::operator*(const vec3 &rhs) const {
+ return vec3(entries[0]*rhs[0] + entries[3]*rhs[1] + entries[6]*rhs[2],
+ entries[1]*rhs[0] + entries[4]*rhs[1] + entries[7]*rhs[2],
+ entries[2]*rhs[0] + entries[5]*rhs[1] + entries[8]*rhs[2]);
+}
diff --git a/Source/Math/mat3.h b/Source/Math/mat3.h
new file mode 100644
index 00000000..69388daf
--- /dev/null
+++ b/Source/Math/mat3.h
@@ -0,0 +1,52 @@
+//-----------------------------------------------------------------------------
+// Name: mat3.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+#include <Math/mat4.h>
+
+class mat3
+{
+public:
+ mat3(float val = 1.0f);
+ mat3(float e0, float e1, float e2,
+ float e3, float e4, float e5,
+ float e6, float e7, float e8);
+ mat3(const float * rhs);
+ mat3(const mat4& m);
+ ~mat3() {} //empty
+
+ //cast to pointer to a (float *) for glGetFloatv etc
+ operator float* () const {return (float*) this;}
+ operator const float* () const {return (const float*) this;}
+
+ inline float& operator()(int i, int j) {return entries[i+j*3];}
+ inline const float& operator()(int i, int j) const {return entries[i+j*3];}
+
+ inline float& operator[](int i) { return entries[i]; }
+ inline const float& operator[](int i) const { return entries[i]; }
+ vec3 operator*(const vec3 &rhs) const;
+
+ //member variables
+ float entries[9];
+};
diff --git a/Source/Math/mat4.cpp b/Source/Math/mat4.cpp
new file mode 100644
index 00000000..903006f0
--- /dev/null
+++ b/Source/Math/mat4.cpp
@@ -0,0 +1,1123 @@
+//-----------------------------------------------------------------------------
+// Name: mat4.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "mat4.h"
+
+#include <Math/enginemath.h>
+#include <Math/vec3math.h>
+
+#include <cmath>
+#include <memory.h>
+
+mat4::mat4(float val) {
+ memset(entries, 0, 16*sizeof(float));
+ entries[0]=val;
+ entries[5]=val;
+ entries[10]=val;
+ entries[15]=val;
+}
+
+mat4::mat4(float e0, float e1, float e2, float e3,
+ float e4, float e5, float e6, float e7,
+ float e8, float e9, float e10, float e11,
+ float e12, float e13, float e14, float e15)
+{
+ entries[0]=e0;
+ entries[1]=e1;
+ entries[2]=e2;
+ entries[3]=e3;
+ entries[4]=e4;
+ entries[5]=e5;
+ entries[6]=e6;
+ entries[7]=e7;
+ entries[8]=e8;
+ entries[9]=e9;
+ entries[10]=e10;
+ entries[11]=e11;
+ entries[12]=e12;
+ entries[13]=e13;
+ entries[14]=e14;
+ entries[15]=e15;
+}
+
+mat4::mat4(const mat4 & rhs)
+{
+ memcpy(entries, rhs.entries, 16*sizeof(float));
+}
+
+mat4::mat4(const float * rhs)
+{
+ memcpy(entries, rhs, 16*sizeof(float));
+}
+
+vec4 mat4::GetRow(int position) const
+{
+ if(position==0)
+ return vec4(entries[0], entries[4], entries[8], entries[12]);
+
+ if(position==1)
+ return vec4(entries[1], entries[5], entries[9], entries[13]);
+
+ if(position==2)
+ return vec4(entries[2], entries[6], entries[10], entries[14]);
+
+ if(position==3)
+ return vec4(entries[3], entries[7], entries[11], entries[15]);
+
+ return vec4(0.0f, 0.0f, 0.0f, 0.0f);
+}
+
+vec3 mat4::GetRowVec3(int position) const
+{
+ if(position==0)
+ return vec3(entries[0], entries[4], entries[8]);
+
+ if(position==1)
+ return vec3(entries[1], entries[5], entries[9]);
+
+ if(position==2)
+ return vec3(entries[2], entries[6], entries[10]);
+
+ if(position==3)
+ return vec3(entries[3], entries[7], entries[11]);
+
+ return vec3(0.0f, 0.0f, 0.0f);
+}
+
+void mat4::SetRow(int position, const vec4 &vec)
+{
+ entries[position] = vec[0];
+ entries[position+4] = vec[1];
+ entries[position+8] = vec[2];
+ entries[position+12] = vec[3];
+}
+
+void mat4::SetRow(int position, const vec3 &vec)
+{
+ entries[position] = vec[0];
+ entries[position+4] = vec[1];
+ entries[position+8] = vec[2];
+}
+
+void mat4::ToVec4(vec4 *vector1, vec4 *vector2, vec4 *vector3, vec4 *vector4) const {
+ vector1->entries[0] = entries[0];
+ vector1->entries[1] = entries[4];
+ vector1->entries[2] = entries[8];
+ vector1->entries[3] = entries[12];
+ vector2->entries[0] = entries[1];
+ vector2->entries[1] = entries[5];
+ vector2->entries[2] = entries[9];
+ vector2->entries[3] = entries[13];
+ vector3->entries[0] = entries[2];
+ vector3->entries[1] = entries[6];
+ vector3->entries[2] = entries[10];
+ vector3->entries[3] = entries[14];
+ vector4->entries[0] = entries[3];
+ vector4->entries[1] = entries[7];
+ vector4->entries[2] = entries[11];
+ vector4->entries[3] = entries[15];
+}
+
+void mat4::FromVec4(const vec4& vector1,
+ const vec4& vector2,
+ const vec4& vector3,
+ const vec4& vector4)
+{
+ entries[0] = vector1.entries[0];
+ entries[4] = vector1.entries[1];
+ entries[8] = vector1.entries[2];
+ entries[12] = vector1.entries[3];
+ entries[1] = vector2.entries[0];
+ entries[5] = vector2.entries[1];
+ entries[9] = vector2.entries[2];
+ entries[13] = vector2.entries[3];
+ entries[2] = vector3.entries[0];
+ entries[6] = vector3.entries[1];
+ entries[10] = vector3.entries[2];
+ entries[14] = vector3.entries[3];
+ entries[3] = vector4.entries[0];
+ entries[7] = vector4.entries[1];
+ entries[11] = vector4.entries[2];
+ entries[15] = vector4.entries[3];
+}
+
+vec4 mat4::GetColumn(int position) const
+{
+ if(position==0)
+ return vec4(entries[0], entries[1], entries[2], entries[3]);
+
+ if(position==1)
+ return vec4(entries[4], entries[5], entries[6], entries[7]);
+
+ if(position==2)
+ return vec4(entries[8], entries[9], entries[10], entries[11]);
+
+ if(position==3)
+ return vec4(entries[12], entries[13], entries[14], entries[15]);
+
+ return vec4(0.0f, 0.0f, 0.0f, 0.0f);
+}
+
+vec3 mat4::GetColumnVec3(int position) const
+{
+ if(position==0)
+ return vec3(entries[0], entries[1], entries[2]);
+
+ if(position==1)
+ return vec3(entries[4], entries[5], entries[6]);
+
+ if(position==2)
+ return vec3(entries[8], entries[9], entries[10]);
+
+ if(position==3)
+ return vec3(entries[12], entries[13], entries[14]);
+
+ return vec3(0.0f, 0.0f, 0.0f);
+}
+
+void mat4::SetColumn(int position, const vec4 &vec)
+{
+ int start = position*4;
+ entries[start] = vec[0];
+ entries[start+1] = vec[1];
+ entries[start+2] = vec[2];
+ entries[start+3] = vec[3];
+}
+
+void mat4::SetColumn(int position, const vec3 &vec)
+{
+ int start = position*4;
+ entries[start] = vec[0];
+ entries[start+1] = vec[1];
+ entries[start+2] = vec[2];
+}
+
+float mat4::GetDeterminant()
+{
+ float det;
+ det = entries[0] * entries[5] * entries[10] -
+ entries[0] * entries[6] * entries[9] -
+ entries[1] * entries[4] * entries[10] +
+ entries[2] * entries[4] * entries[9] +
+ entries[1] * entries[6] * entries[8] -
+ entries[2] * entries[5] * entries[8];
+ return det;
+}
+
+void mat4::LoadIdentity(void)
+{
+ memset(entries, 0, 16*sizeof(float));
+ entries[0]=1.0f;
+ entries[5]=1.0f;
+ entries[10]=1.0f;
+ entries[15]=1.0f;
+}
+
+mat4 mat4::operator+(const mat4 & rhs) const //overloaded operators
+{
+ return mat4( entries[0]+rhs.entries[0],
+ entries[1]+rhs.entries[1],
+ entries[2]+rhs.entries[2],
+ entries[3]+rhs.entries[3],
+ entries[4]+rhs.entries[4],
+ entries[5]+rhs.entries[5],
+ entries[6]+rhs.entries[6],
+ entries[7]+rhs.entries[7],
+ entries[8]+rhs.entries[8],
+ entries[9]+rhs.entries[9],
+ entries[10]+rhs.entries[10],
+ entries[11]+rhs.entries[11],
+ entries[12]+rhs.entries[12],
+ entries[13]+rhs.entries[13],
+ entries[14]+rhs.entries[14],
+ entries[15]+rhs.entries[15]);
+}
+
+mat4 mat4::operator-(const mat4 & rhs) const //overloaded operators
+{
+ return mat4( entries[0]-rhs.entries[0],
+ entries[1]-rhs.entries[1],
+ entries[2]-rhs.entries[2],
+ entries[3]-rhs.entries[3],
+ entries[4]-rhs.entries[4],
+ entries[5]-rhs.entries[5],
+ entries[6]-rhs.entries[6],
+ entries[7]-rhs.entries[7],
+ entries[8]-rhs.entries[8],
+ entries[9]-rhs.entries[9],
+ entries[10]-rhs.entries[10],
+ entries[11]-rhs.entries[11],
+ entries[12]-rhs.entries[12],
+ entries[13]-rhs.entries[13],
+ entries[14]-rhs.entries[14],
+ entries[15]-rhs.entries[15]);
+}
+
+mat4 mat4::operator*(const mat4 & rhs) const
+{
+ //Optimise for matrices in which bottom row is (0, 0, 0, 1) in both matrices
+ if( entries[3]==0.0f && entries[7]==0.0f && entries[11]==0.0f && entries[15]==1.0f &&
+ rhs.entries[3]==0.0f && rhs.entries[7]==0.0f &&
+ rhs.entries[11]==0.0f && rhs.entries[15]==1.0f)
+ {
+ return mat4( entries[0]*rhs.entries[0]+entries[4]*rhs.entries[1]+entries[8]*rhs.entries[2],
+ entries[1]*rhs.entries[0]+entries[5]*rhs.entries[1]+entries[9]*rhs.entries[2],
+ entries[2]*rhs.entries[0]+entries[6]*rhs.entries[1]+entries[10]*rhs.entries[2],
+ 0.0f,
+ entries[0]*rhs.entries[4]+entries[4]*rhs.entries[5]+entries[8]*rhs.entries[6],
+ entries[1]*rhs.entries[4]+entries[5]*rhs.entries[5]+entries[9]*rhs.entries[6],
+ entries[2]*rhs.entries[4]+entries[6]*rhs.entries[5]+entries[10]*rhs.entries[6],
+ 0.0f,
+ entries[0]*rhs.entries[8]+entries[4]*rhs.entries[9]+entries[8]*rhs.entries[10],
+ entries[1]*rhs.entries[8]+entries[5]*rhs.entries[9]+entries[9]*rhs.entries[10],
+ entries[2]*rhs.entries[8]+entries[6]*rhs.entries[9]+entries[10]*rhs.entries[10],
+ 0.0f,
+ entries[0]*rhs.entries[12]+entries[4]*rhs.entries[13]+entries[8]*rhs.entries[14]+entries[12],
+ entries[1]*rhs.entries[12]+entries[5]*rhs.entries[13]+entries[9]*rhs.entries[14]+entries[13],
+ entries[2]*rhs.entries[12]+entries[6]*rhs.entries[13]+entries[10]*rhs.entries[14]+entries[14],
+ 1.0f);
+ }
+
+ //Optimise for when bottom row of 1st matrix is (0, 0, 0, 1)
+ if( entries[3]==0.0f && entries[7]==0.0f && entries[11]==0.0f && entries[15]==1.0f)
+ {
+ return mat4( entries[0]*rhs.entries[0]+entries[4]*rhs.entries[1]+entries[8]*rhs.entries[2]+entries[12]*rhs.entries[3],
+ entries[1]*rhs.entries[0]+entries[5]*rhs.entries[1]+entries[9]*rhs.entries[2]+entries[13]*rhs.entries[3],
+ entries[2]*rhs.entries[0]+entries[6]*rhs.entries[1]+entries[10]*rhs.entries[2]+entries[14]*rhs.entries[3],
+ rhs.entries[3],
+ entries[0]*rhs.entries[4]+entries[4]*rhs.entries[5]+entries[8]*rhs.entries[6]+entries[12]*rhs.entries[7],
+ entries[1]*rhs.entries[4]+entries[5]*rhs.entries[5]+entries[9]*rhs.entries[6]+entries[13]*rhs.entries[7],
+ entries[2]*rhs.entries[4]+entries[6]*rhs.entries[5]+entries[10]*rhs.entries[6]+entries[14]*rhs.entries[7],
+ rhs.entries[7],
+ entries[0]*rhs.entries[8]+entries[4]*rhs.entries[9]+entries[8]*rhs.entries[10]+entries[12]*rhs.entries[11],
+ entries[1]*rhs.entries[8]+entries[5]*rhs.entries[9]+entries[9]*rhs.entries[10]+entries[13]*rhs.entries[11],
+ entries[2]*rhs.entries[8]+entries[6]*rhs.entries[9]+entries[10]*rhs.entries[10]+entries[14]*rhs.entries[11],
+ rhs.entries[11],
+ entries[0]*rhs.entries[12]+entries[4]*rhs.entries[13]+entries[8]*rhs.entries[14]+entries[12]*rhs.entries[15],
+ entries[1]*rhs.entries[12]+entries[5]*rhs.entries[13]+entries[9]*rhs.entries[14]+entries[13]*rhs.entries[15],
+ entries[2]*rhs.entries[12]+entries[6]*rhs.entries[13]+entries[10]*rhs.entries[14]+entries[14]*rhs.entries[15],
+ rhs.entries[15]);
+ }
+
+ //Optimise for when bottom row of 2nd matrix is (0, 0, 0, 1)
+ if( rhs.entries[3]==0.0f && rhs.entries[7]==0.0f &&
+ rhs.entries[11]==0.0f && rhs.entries[15]==1.0f)
+ {
+ return mat4( entries[0]*rhs.entries[0]+entries[4]*rhs.entries[1]+entries[8]*rhs.entries[2],
+ entries[1]*rhs.entries[0]+entries[5]*rhs.entries[1]+entries[9]*rhs.entries[2],
+ entries[2]*rhs.entries[0]+entries[6]*rhs.entries[1]+entries[10]*rhs.entries[2],
+ entries[3]*rhs.entries[0]+entries[7]*rhs.entries[1]+entries[11]*rhs.entries[2],
+ entries[0]*rhs.entries[4]+entries[4]*rhs.entries[5]+entries[8]*rhs.entries[6],
+ entries[1]*rhs.entries[4]+entries[5]*rhs.entries[5]+entries[9]*rhs.entries[6],
+ entries[2]*rhs.entries[4]+entries[6]*rhs.entries[5]+entries[10]*rhs.entries[6],
+ entries[3]*rhs.entries[4]+entries[7]*rhs.entries[5]+entries[11]*rhs.entries[6],
+ entries[0]*rhs.entries[8]+entries[4]*rhs.entries[9]+entries[8]*rhs.entries[10],
+ entries[1]*rhs.entries[8]+entries[5]*rhs.entries[9]+entries[9]*rhs.entries[10],
+ entries[2]*rhs.entries[8]+entries[6]*rhs.entries[9]+entries[10]*rhs.entries[10],
+ entries[3]*rhs.entries[8]+entries[7]*rhs.entries[9]+entries[11]*rhs.entries[10],
+ entries[0]*rhs.entries[12]+entries[4]*rhs.entries[13]+entries[8]*rhs.entries[14]+entries[12],
+ entries[1]*rhs.entries[12]+entries[5]*rhs.entries[13]+entries[9]*rhs.entries[14]+entries[13],
+ entries[2]*rhs.entries[12]+entries[6]*rhs.entries[13]+entries[10]*rhs.entries[14]+entries[14],
+ entries[3]*rhs.entries[12]+entries[7]*rhs.entries[13]+entries[11]*rhs.entries[14]+entries[15]);
+ }
+
+ return mat4( entries[0]*rhs.entries[0]+entries[4]*rhs.entries[1]+entries[8]*rhs.entries[2]+entries[12]*rhs.entries[3],
+ entries[1]*rhs.entries[0]+entries[5]*rhs.entries[1]+entries[9]*rhs.entries[2]+entries[13]*rhs.entries[3],
+ entries[2]*rhs.entries[0]+entries[6]*rhs.entries[1]+entries[10]*rhs.entries[2]+entries[14]*rhs.entries[3],
+ entries[3]*rhs.entries[0]+entries[7]*rhs.entries[1]+entries[11]*rhs.entries[2]+entries[15]*rhs.entries[3],
+ entries[0]*rhs.entries[4]+entries[4]*rhs.entries[5]+entries[8]*rhs.entries[6]+entries[12]*rhs.entries[7],
+ entries[1]*rhs.entries[4]+entries[5]*rhs.entries[5]+entries[9]*rhs.entries[6]+entries[13]*rhs.entries[7],
+ entries[2]*rhs.entries[4]+entries[6]*rhs.entries[5]+entries[10]*rhs.entries[6]+entries[14]*rhs.entries[7],
+ entries[3]*rhs.entries[4]+entries[7]*rhs.entries[5]+entries[11]*rhs.entries[6]+entries[15]*rhs.entries[7],
+ entries[0]*rhs.entries[8]+entries[4]*rhs.entries[9]+entries[8]*rhs.entries[10]+entries[12]*rhs.entries[11],
+ entries[1]*rhs.entries[8]+entries[5]*rhs.entries[9]+entries[9]*rhs.entries[10]+entries[13]*rhs.entries[11],
+ entries[2]*rhs.entries[8]+entries[6]*rhs.entries[9]+entries[10]*rhs.entries[10]+entries[14]*rhs.entries[11],
+ entries[3]*rhs.entries[8]+entries[7]*rhs.entries[9]+entries[11]*rhs.entries[10]+entries[15]*rhs.entries[11],
+ entries[0]*rhs.entries[12]+entries[4]*rhs.entries[13]+entries[8]*rhs.entries[14]+entries[12]*rhs.entries[15],
+ entries[1]*rhs.entries[12]+entries[5]*rhs.entries[13]+entries[9]*rhs.entries[14]+entries[13]*rhs.entries[15],
+ entries[2]*rhs.entries[12]+entries[6]*rhs.entries[13]+entries[10]*rhs.entries[14]+entries[14]*rhs.entries[15],
+ entries[3]*rhs.entries[12]+entries[7]*rhs.entries[13]+entries[11]*rhs.entries[14]+entries[15]*rhs.entries[15]);
+}
+
+mat4 mat4::operator*(const float rhs) const
+{
+ return mat4( entries[0]*rhs,
+ entries[1]*rhs,
+ entries[2]*rhs,
+ entries[3]*rhs,
+ entries[4]*rhs,
+ entries[5]*rhs,
+ entries[6]*rhs,
+ entries[7]*rhs,
+ entries[8]*rhs,
+ entries[9]*rhs,
+ entries[10]*rhs,
+ entries[11]*rhs,
+ entries[12]*rhs,
+ entries[13]*rhs,
+ entries[14]*rhs,
+ entries[15]*rhs);
+}
+
+mat4 mat4::operator/(const float rhs) const
+{
+ if (rhs==0.0f || rhs==1.0f)
+ return (*this);
+
+ float temp=1/rhs;
+
+ return (*this)*temp;
+}
+
+mat4 operator*(float scaleFactor, const mat4 & rhs)
+{
+ return rhs*scaleFactor;
+}
+
+bool mat4::operator==(const mat4 & rhs) const
+{
+ for (int i = 0; i<16; i++)
+ {
+ if(entries[i]!=rhs.entries[i])
+ return false;
+ }
+ return true;
+}
+
+bool mat4::operator!=(const mat4 & rhs) const
+{
+ return !((*this)==rhs);
+}
+
+void mat4::operator+=(const mat4 & rhs)
+{
+ (*this)=(*this)+rhs;
+}
+
+void mat4::operator-=(const mat4 & rhs)
+{
+ (*this)=(*this)-rhs;
+}
+
+void mat4::operator*=(const mat4 & rhs)
+{
+ (*this)=(*this)*rhs;
+}
+
+void mat4::operator*=(const float rhs)
+{
+ (*this)=(*this)*rhs;
+}
+
+void mat4::operator/=(const float rhs)
+{
+ (*this)=(*this)/rhs;
+}
+
+mat4 mat4::operator-(void) const
+{
+ mat4 result(*this);
+
+ for (int i = 0; i<16; i++)
+ result.entries[i]=-result.entries[i];
+
+ return result;
+}
+
+vec4 mat4::operator*(const vec4 &rhs) const
+{
+ //Optimise for matrices in which bottom row is (0, 0, 0, 1)
+ if(entries[3]==0.0f && entries[7]==0.0f && entries[11]==0.0f && entries[15]==1.0f)
+ {
+ return vec4(entries[0]*rhs.x()
+ + entries[4]*rhs.y()
+ + entries[8]*rhs.z()
+ + entries[12]*rhs.w(),
+
+ entries[1]*rhs.x()
+ + entries[5]*rhs.y()
+ + entries[9]*rhs.z()
+ + entries[13]*rhs.w(),
+
+ entries[2]*rhs.x()
+ + entries[6]*rhs.y()
+ + entries[10]*rhs.z()
+ + entries[14]*rhs.w(),
+
+ rhs.w());
+ }
+
+ return vec4( entries[0]*rhs.x()
+ + entries[4]*rhs.y()
+ + entries[8]*rhs.z()
+ + entries[12]*rhs.w(),
+
+ entries[1]*rhs.x()
+ + entries[5]*rhs.y()
+ + entries[9]*rhs.z()
+ + entries[13]*rhs.w(),
+
+ entries[2]*rhs.x()
+ + entries[6]*rhs.y()
+ + entries[10]*rhs.z()
+ + entries[14]*rhs.w(),
+
+ entries[3]*rhs.x()
+ + entries[7]*rhs.y()
+ + entries[11]*rhs.z()
+ + entries[15]*rhs.w());
+}
+
+vec3 mat4::operator*(const vec3 &rhs) const
+{
+ return (*this * vec4(rhs,1.0f)).xyz();
+}
+
+vec3 mat4::GetRotatedvec3(const vec3 & rhs) const
+{
+ return vec3(entries[0]*rhs.x() + entries[4]*rhs.y() + entries[8]*rhs.z(),
+ entries[1]*rhs.x() + entries[5]*rhs.y() + entries[9]*rhs.z(),
+ entries[2]*rhs.x() + entries[6]*rhs.y() + entries[10]*rhs.z());
+}
+
+vec3 mat4::GetInverseRotatedvec3(const vec3 & rhs) const
+{
+ //rotate by transpose:
+ return vec3(entries[0]*rhs.x() + entries[1]*rhs.y() + entries[2]*rhs.z(),
+ entries[4]*rhs.x() + entries[5]*rhs.y() + entries[6]*rhs.z(),
+ entries[8]*rhs.x() + entries[9]*rhs.y() + entries[10]*rhs.z());
+}
+
+vec3 mat4::GetTranslatedvec3(const vec3 & rhs) const
+{
+ return vec3(rhs.x()+entries[12], rhs.y()+entries[13], rhs.z()+entries[14]);
+}
+
+vec3 mat4::GetInverseTranslatedvec3(const vec3 & rhs) const
+{
+ return vec3(rhs.x()-entries[12], rhs.y()-entries[13], rhs.z()-entries[14]);
+}
+
+mat4 invert(const mat4& mat) {
+ return transpose(mat.GetInverseTranspose());
+}
+
+mat4 transpose(const mat4& mat) {
+ return mat4(mat.entries[ 0], mat.entries[ 4], mat.entries[ 8], mat.entries[12],
+ mat.entries[ 1], mat.entries[ 5], mat.entries[ 9], mat.entries[13],
+ mat.entries[ 2], mat.entries[ 6], mat.entries[10], mat.entries[14],
+ mat.entries[ 3], mat.entries[ 7], mat.entries[11], mat.entries[15]);
+}
+
+void mat4::InvertTranspose(void)
+{
+ *this=GetInverseTranspose();
+}
+
+mat4 mat4::GetInverseTranspose(void) const
+{
+ mat4 result;
+
+ float tmp[12]; //temporary pair storage
+ float det; //determinant
+
+ //calculate pairs for first 8 elements (cofactors)
+ tmp[0] = entries[10] * entries[15];
+ tmp[1] = entries[11] * entries[14];
+ tmp[2] = entries[9] * entries[15];
+ tmp[3] = entries[11] * entries[13];
+ tmp[4] = entries[9] * entries[14];
+ tmp[5] = entries[10] * entries[13];
+ tmp[6] = entries[8] * entries[15];
+ tmp[7] = entries[11] * entries[12];
+ tmp[8] = entries[8] * entries[14];
+ tmp[9] = entries[10] * entries[12];
+ tmp[10] = entries[8] * entries[13];
+ tmp[11] = entries[9] * entries[12];
+
+ //calculate first 8 elements (cofactors)
+ result.entries[0] = tmp[0]*entries[5] + tmp[3]*entries[6] + tmp[4]*entries[7]
+ - tmp[1]*entries[5] - tmp[2]*entries[6] - tmp[5]*entries[7];
+
+ result.entries[1] = tmp[1]*entries[4] + tmp[6]*entries[6] + tmp[9]*entries[7]
+ - tmp[0]*entries[4] - tmp[7]*entries[6] - tmp[8]*entries[7];
+
+ result.entries[2] = tmp[2]*entries[4] + tmp[7]*entries[5] + tmp[10]*entries[7]
+ - tmp[3]*entries[4] - tmp[6]*entries[5] - tmp[11]*entries[7];
+
+ result.entries[3] = tmp[5]*entries[4] + tmp[8]*entries[5] + tmp[11]*entries[6]
+ - tmp[4]*entries[4] - tmp[9]*entries[5] - tmp[10]*entries[6];
+
+ result.entries[4] = tmp[1]*entries[1] + tmp[2]*entries[2] + tmp[5]*entries[3]
+ - tmp[0]*entries[1] - tmp[3]*entries[2] - tmp[4]*entries[3];
+
+ result.entries[5] = tmp[0]*entries[0] + tmp[7]*entries[2] + tmp[8]*entries[3]
+ - tmp[1]*entries[0] - tmp[6]*entries[2] - tmp[9]*entries[3];
+
+ result.entries[6] = tmp[3]*entries[0] + tmp[6]*entries[1] + tmp[11]*entries[3]
+ - tmp[2]*entries[0] - tmp[7]*entries[1] - tmp[10]*entries[3];
+
+ result.entries[7] = tmp[4]*entries[0] + tmp[9]*entries[1] + tmp[10]*entries[2]
+ - tmp[5]*entries[0] - tmp[8]*entries[1] - tmp[11]*entries[2];
+
+ //calculate pairs for second 8 elements (cofactors)
+ tmp[0] = entries[2]*entries[7];
+ tmp[1] = entries[3]*entries[6];
+ tmp[2] = entries[1]*entries[7];
+ tmp[3] = entries[3]*entries[5];
+ tmp[4] = entries[1]*entries[6];
+ tmp[5] = entries[2]*entries[5];
+ tmp[6] = entries[0]*entries[7];
+ tmp[7] = entries[3]*entries[4];
+ tmp[8] = entries[0]*entries[6];
+ tmp[9] = entries[2]*entries[4];
+ tmp[10] = entries[0]*entries[5];
+ tmp[11] = entries[1]*entries[4];
+
+ //calculate second 8 elements (cofactors)
+ result.entries[8] = tmp[0]*entries[13] + tmp[3]*entries[14] + tmp[4]*entries[15]
+ - tmp[1]*entries[13] - tmp[2]*entries[14] - tmp[5]*entries[15];
+
+ result.entries[9] = tmp[1]*entries[12] + tmp[6]*entries[14] + tmp[9]*entries[15]
+ - tmp[0]*entries[12] - tmp[7]*entries[14] - tmp[8]*entries[15];
+
+ result.entries[10] = tmp[2]*entries[12] + tmp[7]*entries[13] + tmp[10]*entries[15]
+ - tmp[3]*entries[12] - tmp[6]*entries[13] - tmp[11]*entries[15];
+
+ result.entries[11] = tmp[5]*entries[12] + tmp[8]*entries[13] + tmp[11]*entries[14]
+ - tmp[4]*entries[12] - tmp[9]*entries[13] - tmp[10]*entries[14];
+
+ result.entries[12] = tmp[2]*entries[10] + tmp[5]*entries[11] + tmp[1]*entries[9]
+ - tmp[4]*entries[11] - tmp[0]*entries[9] - tmp[3]*entries[10];
+
+ result.entries[13] = tmp[8]*entries[11] + tmp[0]*entries[8] + tmp[7]*entries[10]
+ - tmp[6]*entries[10] - tmp[9]*entries[11] - tmp[1]*entries[8];
+
+ result.entries[14] = tmp[6]*entries[9] + tmp[11]*entries[11] + tmp[3]*entries[8]
+ - tmp[10]*entries[11] - tmp[2]*entries[8] - tmp[7]*entries[9];
+
+ result.entries[15] = tmp[10]*entries[10] + tmp[4]*entries[8] + tmp[9]*entries[9]
+ - tmp[8]*entries[9] - tmp[11]*entries[10] - tmp[5]*entries[8];
+
+ // calculate determinant
+ det = entries[0]*result.entries[0]
+ +entries[1]*result.entries[1]
+ +entries[2]*result.entries[2]
+ +entries[3]*result.entries[3];
+
+ if(det==0.0f)
+ {
+ mat4 id;
+ return id;
+ }
+
+ result=result/det;
+
+ return result;
+}
+
+//Invert if only composed of rotations & translations
+void mat4::AffineInvert(void)
+{
+ (*this)=GetAffineInverse();
+}
+
+mat4 mat4::GetAffineInverse(void) const
+{
+ //return the transpose of the rotation part
+ //and the negative of the inverse rotated translation part
+ return mat4( entries[0],
+ entries[4],
+ entries[8],
+ 0.0f,
+ entries[1],
+ entries[5],
+ entries[9],
+ 0.0f,
+ entries[2],
+ entries[6],
+ entries[10],
+ 0.0f,
+ -(entries[0]*entries[12]+entries[1]*entries[13]+entries[2]*entries[14]),
+ -(entries[4]*entries[12]+entries[5]*entries[13]+entries[6]*entries[14]),
+ -(entries[8]*entries[12]+entries[9]*entries[13]+entries[10]*entries[14]),
+ 1.0f);
+}
+
+void mat4::AffineInvertTranspose(void)
+{
+ (*this)=GetAffineInverseTranspose();
+}
+
+mat4 mat4::GetAffineInverseTranspose(void) const
+{
+ //return the transpose of the rotation part
+ //and the negative of the inverse rotated translation part
+ //transposed
+ return mat4( entries[0],
+ entries[1],
+ entries[2],
+ -(entries[0]*entries[12]+entries[1]*entries[13]+entries[2]*entries[14]),
+ entries[4],
+ entries[5],
+ entries[6],
+ -(entries[4]*entries[12]+entries[5]*entries[13]+entries[6]*entries[14]),
+ entries[8],
+ entries[9],
+ entries[10],
+ -(entries[8]*entries[12]+entries[9]*entries[13]+entries[10]*entries[14]),
+ 0.0f, 0.0f, 0.0f, 1.0f);
+}
+
+void mat4::SetTranslation(const vec3 & translation)
+{
+ LoadIdentity();
+
+ SetTranslationPart(translation);
+}
+
+void mat4::SetScale(const vec3 & scaleFactor)
+{
+ LoadIdentity();
+
+ entries[0]=scaleFactor.x();
+ entries[5]=scaleFactor.y();
+ entries[10]=scaleFactor.z();
+}
+
+void mat4::SetUniformScale(const float scaleFactor)
+{
+ LoadIdentity();
+
+ entries[0]=entries[5]=entries[10]=scaleFactor;
+}
+
+void mat4::SetRotationAxisDeg(const double angle, const vec3 & axis) {
+ SetRotationAxisRad(PI*angle/180.0f,axis);
+}
+
+void mat4::SetRotationAxisRad(const double angle, const vec3 & axis) {
+ vec3 u = normalize(axis);
+
+ float sinAngle=(float)sin(angle);
+ float cosAngle=(float)cos(angle);
+ float oneMinusCosAngle=1.0f-cosAngle;
+
+ LoadIdentity();
+
+ entries[0]=(u.x())*(u.x()) + cosAngle*(1-(u.x())*(u.x()));
+ entries[4]=(u.x())*(u.y())*(oneMinusCosAngle) - sinAngle*u.z();
+ entries[8]=(u.x())*(u.z())*(oneMinusCosAngle) + sinAngle*u.y();
+
+ entries[1]=(u.x())*(u.y())*(oneMinusCosAngle) + sinAngle*u.z();
+ entries[5]=(u.y())*(u.y()) + cosAngle*(1-(u.y())*(u.y()));
+ entries[9]=(u.y())*(u.z())*(oneMinusCosAngle) - sinAngle*u.x();
+
+ entries[2]=(u.x())*(u.z())*(oneMinusCosAngle) - sinAngle*u.y();
+ entries[6]=(u.y())*(u.z())*(oneMinusCosAngle) + sinAngle*u.x();
+ entries[10]=(u.z())*(u.z()) + cosAngle*(1-(u.z())*(u.z()));
+}
+
+void mat4::SetRotationX(const float angle)
+{
+ LoadIdentity();
+
+ //entries[5]=(float)cos(PI*angle/180);
+ //entries[6]=(float)sin(PI*angle/180);
+
+ entries[5]=cosf(angle);
+ entries[6]=sinf(angle);
+
+ entries[9]=-entries[6];
+ entries[10]=entries[5];
+}
+
+void mat4::SetRotationY(const float angle)
+{
+ LoadIdentity();
+
+ entries[0]=cosf(angle);
+ entries[2]=-sinf(angle);
+
+ //entries[0]=(float)cosf(angle);
+ //entries[2]=-(float)sinf(angle);
+
+ entries[8]=-entries[2];
+ entries[10]=entries[0];
+}
+
+void mat4::SetRotationZ(const float angle)
+{
+ LoadIdentity();
+
+ //entries[0]=(float)cos(PI*angle/180);
+ //entries[1]=(float)sin(PI*angle/180);
+
+ entries[0]=cosf(angle);
+ entries[1]=sinf(angle);
+
+ entries[4]=-entries[1];
+ entries[5]=entries[0];
+}
+
+void mat4::SetRotationEuler(const double angleX, const double angleY, const double angleZ)
+{
+ LoadIdentity();
+
+ SetRotationPartEuler(angleX, angleY, angleZ);
+}
+
+void mat4::SetPerspectiveInfinite( float fovyInDegrees, float aspectRatio, float znear, float zfar ) {
+ float e = 1/tanf(fovyInDegrees*((float)PI/180.0f)*0.5f);
+ float a = aspectRatio;
+ float n = znear;
+ float epsilon = 0.000001f;
+
+ entries[0] = e/a;
+ entries[1] = 0.0f;
+ entries[2] = 0.0f;
+ entries[3] = 0.0f;
+
+ entries[4] = 0.0f;
+ entries[5] = e;
+ entries[6] = 0.0f;
+ entries[7] = 0.0f;
+
+ entries[8] = 0.0f;
+ entries[9] = 0.0f;
+ entries[10] = epsilon-1.0f;
+ entries[11] = -1.0f;
+
+ entries[12] = 0.0f;
+ entries[13] = 0.0f;
+ entries[14] = (epsilon-2.0f)*n;
+ entries[15] = 0.0f;
+}
+
+void mat4::SetPerspective( float fovyInDegrees, float aspectRatio, float znear, float zfar )
+{
+ float e = 1/tanf(fovyInDegrees*((float)PI/180.0f)*0.5f);
+ float a = aspectRatio;
+ float f = zfar;
+ float n = znear;
+
+ entries[0] = e/a;
+ entries[1] = 0.0f;
+ entries[2] = 0.0f;
+ entries[3] = 0.0f;
+
+ entries[4] = 0.0f;
+ entries[5] = e;
+ entries[6] = 0.0f;
+ entries[7] = 0.0f;
+
+ entries[8] = 0.0f;
+ entries[9] = 0.0f;
+ entries[10] = (-f-n)/(f-n);
+ entries[11] = -1.0f;
+
+ entries[12] = 0.0f;
+ entries[13] = 0.0f;
+ entries[14] = (-2.0f*f*n) / (f-n);
+ entries[15] = 0.0f;
+}
+
+void mat4::SetOrtho( float left, float right, float bottom, float top, float _near, float _far )
+{
+ entries[0] = 2.0f / (right - left);
+ entries[1] = 0.0f;
+ entries[2] = 0.0f;
+ entries[3] = 0.0f;
+
+ entries[4] = 0.0f;
+ entries[5] = 2.0f / (top - bottom);
+ entries[6] = 0.0f;
+ entries[7] = 0.0f;
+
+ entries[8] = 0.0f;
+ entries[9] = 0.0f;
+ entries[10] = -2.0f / (_far - _near);
+ entries[11] = 0.0f;
+
+ entries[12] = -(right + left)/(right - left);
+ entries[13] = -(top + bottom)/(top - bottom);
+ entries[14] = -(_far + _near)/(_far - _near);
+ entries[15] = 1.0f;
+}
+
+
+void mat4::SetTranslationPart(const vec3 & translation)
+{
+ SetColumn(3,translation);
+}
+
+void mat4::AddTranslation(const vec3 & translation)
+{
+ SetColumn(3,translation+GetColumnVec3(3));
+}
+
+
+vec3 mat4::GetTranslationPart() const
+{
+ return GetColumn(3).xyz();
+}
+
+
+void mat4::SetRotationPartEuler(const double angleX, const double angleY, const double angleZ)
+{
+ double cr = cos( PI*angleX/180 );
+ double sr = sin( PI*angleX/180 );
+ double cp = cos( PI*angleY/180 );
+ double sp = sin( PI*angleY/180 );
+ double cy = cos( PI*angleZ/180 );
+ double sy = sin( PI*angleZ/180 );
+
+ entries[0] = ( float )( cp*cy );
+ entries[1] = ( float )( cp*sy );
+ entries[2] = ( float )( -sp );
+
+ double srsp = sr*sp;
+ double crsp = cr*sp;
+
+ entries[4] = ( float )( srsp*cy-cr*sy );
+ entries[5] = ( float )( srsp*sy+cr*cy );
+ entries[6] = ( float )( sr*cp );
+
+ entries[8] = ( float )( crsp*cy+sr*sy );
+ entries[9] = ( float )( crsp*sy-sr*cy );
+ entries[10] = ( float )( cr*cp );
+}
+
+mat4 mat4::GetRotationPart() const
+{
+ mat4 temp;
+ temp.entries[0] = entries[0];
+ temp.entries[1] = entries[1];
+ temp.entries[2] = entries[2];
+ temp.entries[4] = entries[4];
+ temp.entries[5] = entries[5];
+ temp.entries[6] = entries[6];
+ temp.entries[8] = entries[8];
+ temp.entries[9] = entries[9];
+ temp.entries[10] = entries[10];
+
+ return temp;
+}
+
+
+void mat4::SetRotationPart( mat4 temp )
+{
+ entries[0] = temp.entries[0];
+ entries[1] = temp.entries[1];
+ entries[2] = temp.entries[2];
+ entries[4] = temp.entries[4];
+ entries[5] = temp.entries[5];
+ entries[6] = temp.entries[6];
+ entries[8] = temp.entries[8];
+ entries[9] = temp.entries[9];
+ entries[10] = temp.entries[10];
+}
+
+void mat4::NormalizeBases()
+{
+ float vec_length;
+ int index = 0;
+ for(int i=0; i<3; i++){
+ vec_length = sqrt(square(entries[index+0])+
+ square(entries[index+1])+
+ square(entries[index+2]));
+ entries[index+0] /= vec_length;
+ entries[index+1] /= vec_length;
+ entries[index+2] /= vec_length;
+ index += 4;
+ }
+}
+
+void mat4::OrthoNormalizeBases()
+{
+ vec3 col0(entries[0],entries[1],entries[2]);
+ vec3 col1(entries[4],entries[5],entries[6]);
+ vec3 col2(entries[8],entries[9],entries[10]);
+
+ col0 = normalize(col0);
+ col1 = normalize(col1-dot(col0,col1)*col0);
+ col2 = normalize(col2-dot(col0,col2)*col0);
+
+ SetColumn(0,col0);
+ SetColumn(1,col1);
+ SetColumn(2,col2);
+}
+
+void mat4::SetLookAt( const vec3& camera, const vec3& target, const vec3 &up )
+{
+ vec3 dir = normalize(target - camera);
+ vec3 right = normalize(cross(dir, up));
+ vec3 new_up = normalize(cross(right, dir));
+
+ mat4 rotation;
+ rotation.SetRow(0, right);
+ rotation.SetRow(1, new_up);
+ rotation.SetRow(2, -dir);
+
+ mat4 translation;
+ translation.SetTranslationPart(-camera);
+
+ (*this) = rotation * translation;
+}
+
+void mat4::AddRotation( const vec3& rotation )
+{
+ float angle = length(rotation);
+ if(angle == 0.0f){
+ return;
+ }
+ const vec3 axis = rotation / angle;
+ vec3 bases[3];
+ bases[0] = GetColumnVec3(0);
+ bases[1] = GetColumnVec3(1);
+ bases[2] = GetColumnVec3(2);
+ bases[0] = AngleAxisRotationRadian(bases[0], axis, angle);
+ bases[1] = AngleAxisRotationRadian(bases[1], axis, angle);
+ bases[2] = AngleAxisRotationRadian(bases[2], axis, angle);
+ SetColumn(0, bases[0]);
+ SetColumn(1, bases[1]);
+ SetColumn(2, bases[2]);
+}
+
+bool operator<( const mat4 &a, const mat4 &b )
+{
+ for(int i=0; i<16; i++){
+ if(a.entries[i]<b.entries[i]){
+ return true;
+ }
+ }
+ return false;
+}
+
+
+// Adapted from http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm
+vec4 AxisAngleFromMat4( const mat4& m )
+{
+ //return vec4(1.0f,0.0f,0.0f,0.0f);
+
+ float angle,x,y,z; // variables for result
+ float epsilon = 0.00001f; // margin to allow for rounding errors
+ float epsilon2 = 0.1f; // margin to distinguish between 0 and 180 degrees
+ // optional check that input is pure rotation, 'isRotationMatrix' is defined at:
+ // http://www.euclideanspace.com/maths/algebra/matrix/orthogonal/rotation/
+ if ((fabs(m(0,1)-m(1,0))< epsilon)
+ && (fabs(m(0,2)-m(2,0))< epsilon)
+ && (fabs(m(1,2)-m(2,1))< epsilon)) {
+ // singularity found
+ // first check for identity matrix which must have +1 for all terms
+ // in leading diagonaland zero in other terms
+ if ((fabs(m(0,1)+m(1,0)) < epsilon2)
+ && (fabs(m(0,2)+m(2,0)) < epsilon2)
+ && (fabs(m(1,2)+m(2,1)) < epsilon2)
+ && (fabs(m(0,0)+m(1,1)+m(2,2)-3) < epsilon2)) {
+ // this singularity is identity matrix so angle = 0
+ return vec4(1.0f,0.0f,0.0f,0.0f); // zero angle, arbitrary axis
+ }
+ // otherwise this singularity is angle = 180
+ angle = (float)PI;
+ float xx = (m(0,0)+1.0f)/2.0f;
+ float yy = (m(1,1)+1.0f)/2.0f;
+ float zz = (m(2,2)+1.0f)/2.0f;
+ float xy = (m(0,1)+m(1,0))/4.0f;
+ float xz = (m(0,2)+m(2,0))/4.0f;
+ float yz = (m(1,2)+m(2,1))/4.0f;
+ if ((xx > yy) && (xx > zz)) { // m(0,0) is the largest diagonal term
+ if (xx< epsilon) {
+ x = 0.0f;
+ y = 0.7071f;
+ z = 0.7071f;
+ } else {
+ x = sqrtf(xx);
+ y = xy/x;
+ z = xz/x;
+ }
+ } else if (yy > zz) { // m(1,1) is the largest diagonal term
+ if (yy< epsilon) {
+ x = 0.7071f;
+ y = 0.0f;
+ z = 0.7071f;
+ } else {
+ y = sqrtf(yy);
+ x = xy/y;
+ z = yz/y;
+ }
+ } else { // m(2,2) is the largest diagonal term so base result on this
+ if (zz< epsilon) {
+ x = 0.7071f;
+ y = 0.7071f;
+ z = 0.0f;
+ } else {
+ z = sqrtf(zz);
+ x = xz/z;
+ y = yz/z;
+ }
+ }
+ return vec4(x,y,z,angle); // return 180 deg rotation
+ }
+ // as we have reached here there are no singularities so we can handle normally
+ float s = sqrtf((m(2,1) - m(1,2))*(m(2,1) - m(1,2))
+ +(m(0,2) - m(2,0))*(m(0,2) - m(2,0))
+ +(m(1,0) - m(0,1))*(m(1,0) - m(0,1))); // used to normalise
+ if (fabs(s) < 0.001) s=1;
+ // prevent divide by zero, should not happen if matrix is orthogonal and should be
+ // caught by singularity test above, but I've left it in just in case
+ float test = ( m(0,0) + m(1,1) + m(2,2) - 1.0f)/2.0f;
+ angle = acosf(test);
+ x = (m(2,1) - m(1,2))/s;
+ y = (m(0,2) - m(2,0))/s;
+ z = (m(1,0) - m(0,1))/s;
+ return vec4(x,y,z,angle);
+}
+
+mat4 orthonormalize( const mat4& mat )
+{
+ vec3 basis1(mat(0,0),mat(0,1),mat(0,2));
+ vec3 basis2(mat(1,0),mat(1,1),mat(1,2));
+ vec3 basis3(mat(2,0),mat(2,1),mat(2,2));
+
+ basis1 = normalize(basis1);
+
+ basis2 -= basis1 * dot(basis1,basis2);
+ basis2 = normalize(basis2);
+
+ basis3 -= basis1 * dot(basis1,basis3);
+ basis3 -= basis2 * dot(basis2,basis3);
+ basis3 = normalize(basis3);
+
+ mat4 to_return = mat;
+ to_return(0,0) = basis1.entries[0];
+ to_return(0,1) = basis1.entries[1];
+ to_return(0,2) = basis1.entries[2];
+ to_return(1,0) = basis2.entries[0];
+ to_return(1,1) = basis2.entries[1];
+ to_return(1,2) = basis2.entries[2];
+ to_return(2,0) = basis3.entries[0];
+ to_return(2,1) = basis3.entries[1];
+ to_return(2,2) = basis3.entries[2];
+
+ return to_return;
+}
diff --git a/Source/Math/mat4.h b/Source/Math/mat4.h
new file mode 100644
index 00000000..6f8289c3
--- /dev/null
+++ b/Source/Math/mat4.h
@@ -0,0 +1,174 @@
+//-----------------------------------------------------------------------------
+// Name: mat4.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec4.h>
+#include <Math/vec3.h>
+
+class mat4
+{
+public:
+ mat4(float val = 1.0f);
+ mat4(float e0, float e1, float e2, float e3,
+ float e4, float e5, float e6, float e7,
+ float e8, float e9, float e10, float e11,
+ float e12, float e13, float e14, float e15);
+ mat4(const float * rhs);
+ mat4(const mat4 & rhs);
+ ~mat4() {} //empty
+
+ vec4 GetRow(int position) const;
+ vec4 GetColumn(int position) const;
+ vec3 GetRowVec3(int position) const;
+ vec3 GetColumnVec3(int position) const;
+
+ void SetRow(int position, const vec4 &vec);
+ void SetRow(int position, const vec3 &vec);
+ void SetColumn(int position, const vec4 &vec);
+ void SetColumn(int position, const vec3 &vec);
+
+ void LoadIdentity(void);
+
+ //binary operators
+ mat4 operator+(const mat4 & rhs) const;
+ mat4 operator-(const mat4 & rhs) const;
+ mat4 operator*(const mat4 & rhs) const;
+ mat4 operator*(const float rhs) const;
+ mat4 operator/(const float rhs) const;
+ friend mat4 operator*(float scaleFactor, const mat4 & rhs);
+
+ bool operator==(const mat4 & rhs) const;
+ bool operator!=(const mat4 & rhs) const;
+
+ //self-add etc
+ void operator+=(const mat4 & rhs);
+ void operator-=(const mat4 & rhs);
+ void operator*=(const mat4 & rhs);
+ void operator*=(const float rhs);
+ void operator/=(const float rhs);
+
+ //unary operators
+ mat4 operator-(void) const;
+ mat4 operator+(void) const {return (*this);}
+
+ void ToVec4(vec4 *vector1, vec4 *vector2, vec4 *vector3, vec4 *vector4) const;
+
+ //multiply a vector by this matrix
+ vec4 operator*(const vec4 &rhs) const;
+ vec3 operator*(const vec3 &rhs) const;
+
+ //rotate a 3d vector by rotation part
+ void Rotatevec3(vec3 & rhs) const
+ {rhs=GetRotatedvec3(rhs);}
+
+ void InverseRotatevec3(vec3 & rhs) const
+ {rhs=GetInverseRotatedvec3(rhs);}
+
+ vec3 GetRotatedvec3(const vec3 & rhs) const;
+ vec3 GetInverseRotatedvec3(const vec3 & rhs) const;
+
+ //translate a 3d vector by translation part
+ void Translatevec3(vec3 & rhs) const
+ {rhs=GetTranslatedvec3(rhs);}
+
+ void InverseTranslatevec3(vec3 & rhs) const
+ {rhs=GetInverseTranslatedvec3(rhs);}
+
+ vec3 GetTranslatedvec3(const vec3 & rhs) const;
+ vec3 GetInverseTranslatedvec3(const vec3 & rhs) const;
+
+ //Other methods
+ float GetDeterminant();
+ void InvertTranspose(void);
+ mat4 GetInverseTranspose(void) const;
+
+ //Inverse of a rotation/translation only matrix
+ void AffineInvert(void);
+ mat4 GetAffineInverse(void) const;
+ void AffineInvertTranspose(void);
+ mat4 GetAffineInverseTranspose(void) const;
+
+ void SetOrtho(float left, float right, float bottom, float top, float near, float far);
+ void SetPerspective( float fovyInDegrees, float aspectRatio, float znear, float zfar );
+ void SetPerspectiveInfinite( float fovyInDegrees, float aspectRatio, float znear, float zfar );
+
+ //set to perform an operation on space - removes other entries
+ void SetTranslation(const vec3 & translation);
+ void SetScale(const vec3 & scaleFactor);
+ void SetUniformScale(const float scaleFactor);
+ void SetRotationAxisDeg(const double angle, const vec3 & axis);
+ void SetRotationAxisRad(const double angle, const vec3 & axis);
+ void SetRotationX(const float angle);
+ void SetRotationY(const float angle);
+ void SetRotationZ(const float angle);
+ void SetRotationEuler(const double angleX, const double angleY, const double angleZ);
+
+ //set parts of the matrix
+ void SetTranslationPart(const vec3 & translation);
+ vec3 GetTranslationPart() const;
+ mat4 GetRotationPart() const;
+ void SetRotationPart(mat4 rot);
+ void SetRotationPartEuler(const double angleX, const double angleY, const double angleZ);
+ void SetRotationPartEuler(const vec3 & rotations)
+ {
+ SetRotationPartEuler((double)rotations.x(), (double)rotations.y(), (double)rotations.z());
+ }
+
+ void NormalizeBases();
+ void OrthoNormalizeBases();
+
+ //cast to pointer to a (float *) for glGetFloatv etc
+ operator float* () const {return (float*) this;}
+ operator const float* () const {return (const float*) this;}
+
+ inline float& operator()(int i, int j) {return entries[i+j*4];}
+ inline const float& operator()(int i, int j) const {return entries[i+j*4];}
+
+ inline float& operator[](int i) { return entries[i]; }
+ inline const float& operator[](int i) const { return entries[i]; }
+ void SetLookAt( const vec3& camera, const vec3& target, const vec3 &up );
+ void AddTranslation(const vec3 & translation);
+ void FromVec4(const vec4& vector1, const vec4& vector2, const vec4& vector3, const vec4& vector4);
+ void AddRotation( const vec3& rotation );
+ //member variables
+ float entries[16];
+};
+
+mat4 invert(const mat4& mat);
+mat4 transpose(const mat4& mat);
+mat4 orthonormalize(const mat4& mat);
+
+vec4 AxisAngleFromMat4(const mat4& mat);
+
+bool operator<(const mat4 &a, const mat4 &b);
+
+inline std::ostream& operator<<(std::ostream& os, const mat4& v )
+{
+ os << "mat4(";
+ os << "[" << v[0] << "," << v[4] << "," << v[8] << "," << v[12] << "]";
+ os << "[" << v[1] << "," << v[5] << "," << v[9] << "," << v[13] << "]";
+ os << "[" << v[2] << "," << v[6] << "," << v[10] << "," << v[14] << "]";
+ os << "[" << v[3] << "," << v[7] << "," << v[11] << "," << v[15] << "]";
+ os << ")";
+ return os;
+}
diff --git a/Source/Math/overgrowth_geometry.cpp b/Source/Math/overgrowth_geometry.cpp
new file mode 100644
index 00000000..d9023abd
--- /dev/null
+++ b/Source/Math/overgrowth_geometry.cpp
@@ -0,0 +1,251 @@
+//-----------------------------------------------------------------------------
+// Name: overgrowth_geometry.cpp
+// Developer: Wolfire Games LLC
+// Author: Phillip Isola
+// Description: Simple geometry primitives and calculations
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "overgrowth_geometry.h"
+
+#include <Math/vec4math.h>
+#include <Math/enginemath.h>
+
+#include <Graphics/camera.h>
+#include <Utility/assert.h>
+
+#include <cmath>
+#include <cfloat>
+#include <cassert>
+
+vec3 Box::GetPlaneNormal(int index) {
+ static const vec3 normals[] = {
+ vec3( 0, 0, 1),
+ vec3( 0, 0,-1),
+ vec3(-1, 0, 0),
+ vec3( 1, 0, 0),
+ vec3( 0,-1, 0),
+ vec3( 0, 1, 0)
+ };
+ LOG_ASSERT(index < Box::NUM_FACES);
+ return normals[index];
+}
+
+vec3 Box::GetPlanePoint(int index) const {
+ LOG_ASSERT(index < Box::NUM_FACES);
+ return center + GetPlaneNormal(index) * dims * 0.5f;
+}
+
+vec3 Box::GetPlaneTangent(int index) {
+ static const vec3 tangents[] = {
+ vec3(-1, 0, 0),
+ vec3( 1, 0, 0),
+ vec3( 0, 0,-1),
+ vec3( 0, 0, 1),
+ vec3(-1, 0, 0),
+ vec3( 1, 0, 0)
+ };
+ LOG_ASSERT(index < Box::NUM_FACES);
+ return tangents[index];
+}
+
+vec3 Box::GetPlaneBitangent(int index) {
+ static const vec3 bitangents[] = {
+ vec3( 0,-1, 0),
+ vec3( 0,-1, 0),
+ vec3( 0,-1, 0),
+ vec3( 0,-1, 0),
+ vec3( 0, 0,-1),
+ vec3( 0, 0,-1)
+ };
+ LOG_ASSERT(index < Box::NUM_FACES);
+ return bitangents[index];
+}
+
+vec3 Box::GetPoint(int index) const {
+ static const vec3 box_points[] = {
+ vec3(-0.5f,-0.5f, 0.5f),
+ vec3( 0.5f,-0.5f, 0.5f),
+ vec3( 0.5f, 0.5f, 0.5f),
+ vec3(-0.5f, 0.5f, 0.5f),
+ vec3(-0.5f,-0.5f,-0.5f),
+ vec3( 0.5f,-0.5f,-0.5f),
+ vec3( 0.5f, 0.5f,-0.5f),
+ vec3(-0.5f, 0.5f,-0.5f)
+ };
+
+ LOG_ASSERT(index < NUM_POINTS);
+ return center + (dims * box_points[index]);
+}
+
+// returns 1 if there is an intersection, -1 otherwise
+int Box::lineCheck(const vec3& start, const vec3& end, vec3* point, vec3* normal) {
+ int hit = -1;
+ float nearest_dist = FLT_MAX;
+ float curr_dist = 0;
+ for (int i = 0; i < NUM_FACES; i++) {
+ vec3 tmp_point, tmp_normal;
+ curr_dist = lineCheckFace(start, end, &tmp_point, &tmp_normal, i);
+ if (curr_dist >= 0.0f && curr_dist < nearest_dist) {
+ hit = 1;
+ nearest_dist = curr_dist;
+ *point = tmp_point;
+ *normal = tmp_normal;
+ }
+ }
+ return hit;
+}
+
+int Box::GetNearestPointIndex(const vec3& point, float& dist) const {
+ //printf("x: %g\ny: %g\nz: %g\n\n", point[0], point[1], point[2]);
+ bool unset = true;
+ float curr_dist, least_dist = FLT_MAX;
+ int index = 0;
+ for (int i = 0; i < NUM_POINTS; i++) {
+ curr_dist = length(GetPoint(i) - point);
+ if (unset || curr_dist < least_dist) {
+ unset = false;
+ least_dist = curr_dist;
+ index = i;
+ }
+ }
+
+ dist = least_dist;
+ //printf("x2: %g\ny2: %g\nz2: %g\n\n", points[index].coords[0], points[index].coords[1], points[index].coords[2]);
+ return index;
+}
+
+const float _hit_face_tolerance = 0.01f;
+int Box::GetHitFaceIndex(const vec3& normal, const vec3& point) const {
+ for (int i = 0; i < NUM_FACES; i++) {
+ const vec3 plane_normal = GetPlaneNormal(i);
+ if (plane_normal[0] + _hit_face_tolerance >= normal[0] && plane_normal[0] - _hit_face_tolerance <= normal[0] &&
+ plane_normal[1] + _hit_face_tolerance >= normal[1] && plane_normal[1] - _hit_face_tolerance <= normal[1] &&
+ plane_normal[2] + _hit_face_tolerance >= normal[2] && plane_normal[2] - _hit_face_tolerance <= normal[2])
+ {
+ return i;
+ }
+ }
+ return -1;
+}
+
+bool Box::IsInFace(const vec3& point, int which_face, float dimensions_shrink) const {
+ vec3 vec_from_center = point-GetPlanePoint(which_face);
+ vec3 tangent = GetPlaneTangent(which_face);
+ vec3 bitangent = GetPlaneBitangent(which_face);
+ return (fabsf(dot(vec_from_center, tangent)) <= fabsf(dot(dims, tangent)) *0.5f * dimensions_shrink &&
+ fabsf(dot(vec_from_center, bitangent)) <= fabsf(dot(dims, bitangent))*0.5f * dimensions_shrink);
+}
+
+float Box::lineCheckFace(const vec3& start, const vec3& end, vec3* point, vec3* normal, int which_face) {
+ vec3 plane_point = GetPlanePoint(which_face);
+ vec3 plane_normal = GetPlaneNormal(which_face);
+ vec3 dir = normalize(end-start);
+ float hit_dist = RayPlaneIntersection(start, dir, plane_point, plane_normal);
+ if (hit_dist > distance(end, start) || hit_dist < 0) {
+ return -1.0f; // Intersection point is outside of line segment
+ }
+ vec3 tangent = GetPlaneTangent(which_face);
+ vec3 bitangent = GetPlaneBitangent(which_face);
+ vec3 intersect_point = start + hit_dist*dir;
+ vec3 center_to_intersect = intersect_point-plane_point;
+ if (fabsf(dot(center_to_intersect, tangent)) > fabsf(dot(dims, tangent)) *0.5f ||
+ fabsf(dot(center_to_intersect, bitangent)) > fabsf(dot(dims, bitangent))*0.5f)
+ {
+ return -1.0f; // No intersection: line intersects plane outside face boundaries
+ }
+ *point = intersect_point;
+ *normal = plane_normal;
+ return hit_dist;
+}
+
+bool Box::operator==( const Box& other ) {
+ for( int i=0; i<3; ++i ){
+ if( center[i] != other.center[i] ){
+ return false;
+ }
+ if( dims[i] != other.dims[i] ){
+ return false;
+ }
+ }
+ return true;
+}
+
+/*
+ Modified from: http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline3d/
+ Calculate the line segment PaPb that is the shortest route between
+ two lines P1P2 and P3P4. Calculate also the values of mua and mub where
+ Pa = P1 + mua (P2 - P1)
+ Pb = P3 + mub (P4 - P3)
+ Return false if no solution exists.
+*/
+bool LineLineIntersect(vec3 line1_p1, vec3 line1_p2, vec3 line2_p1, vec3 line2_p2, vec3* closest_on_line1, vec3* closest_on_line2) {
+ vec3 p13,p43,p21;
+ float d1343,d4321,d1321,d4343,d2121;
+ float numer,denom;
+ float mua,mub;
+
+ p13 = line1_p1 - line2_p1;
+ p43 = line2_p2 - line2_p1;
+ p21 = line1_p2 - line1_p1;
+
+ d1343 = dot(p13,p43);
+ d4321 = dot(p43,p21);
+ d1321 = dot(p13,p21);
+ d4343 = dot(p43,p43);
+ d2121 = dot(p21,p21);
+
+ denom = d2121 * d4343 - d4321 * d4321;
+ if (denom == 0) return false;
+ numer = d1343 * d4321 - d1321 * d4343;
+
+ mua = numer / denom;
+ if (d4343 == 0) return false;
+ mub = (d1343 + d4321 * mua) / d4343;
+
+ if(closest_on_line1){
+ *closest_on_line1 = line1_p1 + (float)mua * p21;
+ }
+ if(closest_on_line2){
+ *closest_on_line2 = line2_p1 + (float)mub * p43;
+ }
+ return true;
+}
+
+// Return distance from ray start to intersection point with plane, or -1.0f if parallel
+float RayPlaneIntersection(const vec3& ray_start, const vec3& ray_dir, const vec3& plane_point, const vec3& plane_normal) {
+ if (dot(ray_dir,plane_normal) == 0.0f) {
+ return -1.0f; // ray is perpendicular to plane
+ } else {
+ float plane_d = dot(plane_point,plane_normal);
+ float ray_d = dot(ray_start,plane_normal);
+ return (plane_d - ray_d) / dot(ray_dir,plane_normal);
+ }
+}
+
+bool RayLineClosestPoint(vec3 ray_start, vec3 ray_dir, vec3 line_point, vec3 line_dir, vec3* closest_point) {
+ return LineLineIntersect(ray_start, ray_start+ray_dir, line_point, line_point+line_dir, NULL, closest_point);
+}
+
+// note: returns smaller of the two angles between (i.e. <= 180 degrees)
+float GetAngleBetween(const vec3& v1, const vec3& v2) {
+ float d = dot(normalize(v1), normalize(v2));
+ if (d < -1) d = -1;
+ if (d > 1) d = 1;
+ return rad2degf*acosf(d);
+}
diff --git a/Source/Math/overgrowth_geometry.h b/Source/Math/overgrowth_geometry.h
new file mode 100644
index 00000000..0f81a0ff
--- /dev/null
+++ b/Source/Math/overgrowth_geometry.h
@@ -0,0 +1,59 @@
+//-----------------------------------------------------------------------------
+// Name: overgrowth_geometry.h
+// Developer: Wolfire Games LLC
+// Author: Phillip Isola
+// Description: Simple geometry primitives and calculations
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+#include <Math/vec4.h>
+
+struct LineSegment {
+ vec3 start;
+ vec3 end;
+};
+
+class Box {
+public:
+ static const int NUM_FACES = 6;
+ static const int NUM_POINTS = 8;
+ vec3 center;
+ vec3 dims;
+
+ int lineCheck(const vec3& start, const vec3& end, vec3* point, vec3* normal);
+ int GetNearestPointIndex(const vec3& point, float& dist) const;
+ int GetHitFaceIndex(const vec3& normal, const vec3& poin) const; // returns -1 if none hit
+ bool IsInFace(const vec3& point, int which_face, float proportion_offset) const;
+ vec3 GetPoint(int index) const;
+ static vec3 GetPlaneNormal(int index);
+ static vec3 GetPlaneTangent(int index);
+ static vec3 GetPlaneBitangent(int index);
+ vec3 GetPlanePoint(int index) const;
+ bool operator==(const Box& other);
+
+private:
+ float lineCheckFace(const vec3& start, const vec3& end, vec3* point, vec3* normal, int which_face);
+};
+
+bool LineLineIntersect(vec3 p1, vec3 p2, vec3 p3, vec3 p4, vec3* pa, vec3* pb);
+float RayPlaneIntersection(const vec3& start, const vec3& dir, const vec3& p, const vec3& n);
+bool RayLineClosestPoint(vec3 start, vec3 dir, vec3 p, vec3 n, vec3* closest_point);
+float GetAngleBetween(const vec3& v1, const vec3& v2);
diff --git a/Source/Math/quaternions.cpp b/Source/Math/quaternions.cpp
new file mode 100644
index 00000000..61854417
--- /dev/null
+++ b/Source/Math/quaternions.cpp
@@ -0,0 +1,579 @@
+//-----------------------------------------------------------------------------
+// Name: quaternions.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "quaternions.h"
+
+#include <Math/enginemath.h>
+#include <Math/vec3math.h>
+
+#include <cmath>
+
+quaternion normalize(const quaternion &quat){
+ float magnitude;
+
+
+ magnitude = sqrtf((quat.entries[0] * quat.entries[0]) +
+ (quat.entries[1] * quat.entries[1]) +
+ (quat.entries[2] * quat.entries[2]) +
+ (quat.entries[3] * quat.entries[3]));
+
+ return quaternion(quat.entries[0]/magnitude,
+ quat.entries[1]/magnitude,
+ quat.entries[2]/magnitude,
+ quat.entries[3]/magnitude);
+}
+
+quaternion Quat_Mult(const quaternion &q1, const quaternion &q2)
+{
+ quaternion QResult;
+ float a, b, c, d, e, f, g, h;
+ a = (q1.entries[3] + q1.entries[0]) * (q2.entries[3] + q2.entries[0]);
+ b = (q1.entries[2] - q1.entries[1]) * (q2.entries[1] - q2.entries[2]);
+ c = (q1.entries[3] - q1.entries[0]) * (q2.entries[1] + q2.entries[2]);
+ d = (q1.entries[1] + q1.entries[2]) * (q2.entries[3] - q2.entries[0]);
+ e = (q1.entries[0] + q1.entries[2]) * (q2.entries[0] + q2.entries[1]);
+ f = (q1.entries[0] - q1.entries[2]) * (q2.entries[0] - q2.entries[1]);
+ g = (q1.entries[3] + q1.entries[1]) * (q2.entries[3] - q2.entries[2]);
+ h = (q1.entries[3] - q1.entries[1]) * (q2.entries[3] + q2.entries[2]);
+ QResult.entries[0] = a - (e + f + g + h) / 2;
+ QResult.entries[1] = c + (e - f + g - h) / 2;
+ QResult.entries[2] = d + (e - f - g + h) / 2;
+ QResult.entries[3] = b + (-e - f + g + h) / 2;
+ return QResult;
+}
+
+quaternion QuaternionMultiply(const quaternion * quat1, const quaternion * quat2) {
+ quaternion result;
+
+ result.entries[0] = (quat1->entries[0]*quat2->entries[3] + quat2->entries[0]*quat1->entries[3] + quat1->entries[1] * quat2->entries[2] - quat1->entries[2] * quat2->entries[1]);
+ result.entries[1] = (quat1->entries[1]*quat2->entries[3] + quat2->entries[1]*quat1->entries[3] + quat1->entries[2] * quat2->entries[0] - quat1->entries[0] * quat2->entries[2]);
+ result.entries[2] = (quat1->entries[2]*quat2->entries[3] + quat2->entries[2]*quat1->entries[3] + quat1->entries[0] * quat2->entries[1] - quat1->entries[1] * quat2->entries[0]);
+ result.entries[3] = (quat1->entries[3]*quat2->entries[3]) - (quat1->entries[0]*quat2->entries[0]+quat1->entries[1]*quat2->entries[1]+quat1->entries[2]*quat2->entries[2]);
+
+ return result;
+}
+
+
+quaternion QuaternionFromAxisAngle(vec3 axis, float angle) {
+ quaternion result;
+
+ float sinAngle;
+
+ angle *= 0.5f;
+ axis = normalize(axis);
+ sinAngle = sinf(angle);
+ result.entries[0] = (axis.entries[0] * sinAngle);
+ result.entries[1] = (axis.entries[1] * sinAngle);
+ result.entries[2] = (axis.entries[2] * sinAngle);
+ result.entries[3] = cosf(angle);
+
+ return result;
+}
+
+void QuaternionNormalize(quaternion * quat) {
+
+ float magnitude;
+
+ magnitude = sqrtf((quat->entries[0] * quat->entries[0]) + (quat->entries[1] * quat->entries[1]) + (quat->entries[2] * quat->entries[2]) + (quat->entries[3] * quat->entries[3]));
+ quat->entries[0] /= magnitude;
+ quat->entries[1] /= magnitude;
+ quat->entries[2] /= magnitude;
+ quat->entries[3] /= magnitude;
+}
+
+void QuaternionToAxisAngle(quaternion quat, vec3 * axis, float * angle) {
+ float sinAngle;
+
+ QuaternionNormalize(&quat);
+ sinAngle = sqrtf(1.0f - (quat.entries[3] * quat.entries[3]));
+ if (fabs(sinAngle) < 0.0005f) sinAngle = 1.0f;
+ axis->entries[0] = (quat.entries[0] / sinAngle);
+ axis->entries[1] = (quat.entries[1] / sinAngle);
+ axis->entries[2] = (quat.entries[2] / sinAngle);
+ *angle = (acosf(quat.entries[3]) * 2.0f);
+}
+
+
+void QuaternionRotate(quaternion * quat, vec3 axis, float angle) {
+ quaternion rotationQuat;
+ rotationQuat = QuaternionFromAxisAngle(axis, angle);
+ *quat = QuaternionMultiply(quat, &rotationQuat);
+}
+
+void QuaternionInvert(quaternion * quat) {
+ float length;
+ length = (1.0f / ((quat->entries[0] * quat->entries[0]) +
+ (quat->entries[1] * quat->entries[1]) +
+ (quat->entries[2] * quat->entries[2]) +
+ (quat->entries[3] * quat->entries[3])));
+ quat->entries[0] *= -length;
+ quat->entries[1] *= -length;
+ quat->entries[2] *= -length;
+ quat->entries[3] *= length;
+}
+
+quaternion invert(const quaternion &quat){
+ quaternion inverted;
+ inverted.entries[0] = -quat.entries[0];
+ inverted.entries[1] = -quat.entries[1];
+ inverted.entries[2] = -quat.entries[2];
+ inverted.entries[3] = quat.entries[3];
+ //QuaternionInvert(&inverted);
+ return inverted;
+}
+
+quaternion invert_by_val(quaternion quat){
+ quaternion inverted;
+ inverted.entries[0] = -quat.entries[0];
+ inverted.entries[1] = -quat.entries[1];
+ inverted.entries[2] = -quat.entries[2];
+ inverted.entries[3] = quat.entries[3];
+ return inverted;
+}
+
+
+void QuaternionMultiply(const vec3 * quat1, const quaternion * quat2, quaternion * result) {
+ result->entries[0] = (quat1->entries[0]*quat2->entries[3] + quat1->entries[1] * quat2->entries[2] - quat1->entries[2] * quat2->entries[1]);
+ result->entries[1] = (quat1->entries[1]*quat2->entries[3] + quat1->entries[2] * quat2->entries[0] - quat1->entries[0] * quat2->entries[2]);
+ result->entries[2] = (quat1->entries[2]*quat2->entries[3] + quat1->entries[0] * quat2->entries[1] - quat1->entries[1] * quat2->entries[0]);
+ result->entries[3] = (quat1->entries[0]*quat2->entries[0]+quat1->entries[1]*quat2->entries[1]+quat1->entries[2]*quat2->entries[2])*-1;
+}
+
+void QuaternionMultiply(const quaternion * quat1, const quaternion * quat2, quaternion * result) {
+ result->entries[0] = (quat1->entries[0]*quat2->entries[3] + quat2->entries[0]*quat1->entries[3] + quat1->entries[1] * quat2->entries[2] - quat1->entries[2] * quat2->entries[1]);
+ result->entries[1] = (quat1->entries[1]*quat2->entries[3] + quat2->entries[1]*quat1->entries[3] + quat1->entries[2] * quat2->entries[0] - quat1->entries[0] * quat2->entries[2]);
+ result->entries[2] = (quat1->entries[2]*quat2->entries[3] + quat2->entries[2]*quat1->entries[3] + quat1->entries[0] * quat2->entries[1] - quat1->entries[1] * quat2->entries[0]);
+ result->entries[3] = (quat1->entries[3] * quat2->entries[3]) - (quat1->entries[0]*quat2->entries[0]+quat1->entries[1]*quat2->entries[1]+quat1->entries[2]*quat2->entries[2]);
+}
+
+
+void QuaternionMultiplyVector(const quaternion * quat, vec3 * vector) {
+ quaternion inverseQuat, resultQuat;
+
+ inverseQuat = *quat;
+ QuaternionInvert(&inverseQuat);
+ QuaternionMultiply(vector, &inverseQuat, &resultQuat);
+ resultQuat = QuaternionMultiply(quat, &resultQuat);
+
+ vector->entries[0] = resultQuat.entries[0];
+ vector->entries[1] = resultQuat.entries[1];
+ vector->entries[2] = resultQuat.entries[2];
+}
+
+void QuaternionMultiplyVector(quaternion * quat, quaternion * inverseQuat, vec3 * vector) {
+ quaternion resultQuat;
+
+ QuaternionMultiply(vector, inverseQuat, &resultQuat);
+ resultQuat = QuaternionMultiply(quat, &resultQuat);
+
+ vector->entries[0] = resultQuat.entries[0];
+ vector->entries[1] = resultQuat.entries[1];
+ vector->entries[2] = resultQuat.entries[2];
+}
+
+void QuaternionMultiplyVector(quaternion * quat, quaternion * inverseQuat, quaternion * resultQuat, vec3 * vector) {
+ QuaternionMultiply(vector, inverseQuat, resultQuat);
+ *resultQuat = QuaternionMultiply(quat, resultQuat);
+
+ vector->entries[0] = resultQuat->entries[0];
+ vector->entries[1] = resultQuat->entries[1];
+ vector->entries[2] = resultQuat->entries[2];
+}
+
+quaternion::quaternion(vec4 Ang_Ax) {
+ // From the Quaternion Powers article on gamedev.net
+ float sin_angle = sinf(Ang_Ax.entries[3] / 2);
+ entries[0] = Ang_Ax.entries[0] * sin_angle;
+ entries[1] = Ang_Ax.entries[1] * sin_angle;
+ entries[2] = Ang_Ax.entries[2] * sin_angle;
+ entries[3] = cosf(Ang_Ax.entries[3] / 2);
+}
+
+float Length2(const quaternion& quat) {
+ return
+ quat.entries[0] * quat.entries[0]
+ + quat.entries[1] * quat.entries[1]
+ + quat.entries[2] * quat.entries[2]
+ + quat.entries[3] * quat.entries[3];
+}
+
+vec4 Quat_2_AA(quaternion Quat)
+{
+ vec4 Ang_Ax;
+ float scale, tw;
+ if(Quat.entries[3] >= 1.0f){
+ Ang_Ax.entries[0]=1.0f; Ang_Ax.entries[1] = 0.0f; Ang_Ax.entries[2] = 0.0f; Ang_Ax.angle() = 0.0f;
+ return Ang_Ax;
+ }
+ tw = (float)acos(Quat.entries[3]) * 2;
+ scale = (float)sin(tw / 2.0f);
+ Ang_Ax.entries[0] = Quat.entries[0] / scale;
+ Ang_Ax.entries[1] = Quat.entries[1] / scale;
+ Ang_Ax.entries[2] = Quat.entries[2] / scale;
+
+ Ang_Ax.angle() = 2.0f * acosf(Quat.entries[3]);///(float)PI*180;
+ return Ang_Ax;
+}
+
+quaternion::quaternion(bool In_Degrees, vec3 Euler)
+{
+ // From the gamasutra quaternion article
+ quaternion Quat;
+ float cr, cp, cy, sr, sp, sy, cpcy, spsy;
+ //If we are in Degree mode, convert to Radians
+ if (In_Degrees) {
+ Euler.entries[0] = Euler.entries[0] * (float)PI / 180;
+ Euler.entries[1] = Euler.entries[1] * (float)PI / 180;
+ Euler.entries[2] = Euler.entries[2] * (float)PI / 180;
+ }
+ //Calculate trig identities
+ //Formerly roll, pitch, yaw
+ cr = float(cos(Euler.entries[0]/2));
+ cp = float(cos(Euler.entries[1]/2));
+ cy = float(cos(Euler.entries[2]/2));
+ sr = float(sin(Euler.entries[0]/2));
+ sp = float(sin(Euler.entries[1]/2));
+ sy = float(sin(Euler.entries[2]/2));
+
+
+ cpcy = cp * cy;
+ spsy = sp * sy;
+ entries[0] = sr * cpcy - cr * spsy;
+ entries[1] = cr * sp * cy + sr * cp * sy;
+ entries[2] = cr * cp * sy - sr * sp * cy;
+ entries[3] = cr * cpcy + sr * spsy;
+}
+
+quaternion::quaternion()
+{
+ entries[0] = 0.0f;
+ entries[1] = 0.0f;
+ entries[2] = 0.0f;
+ entries[3] = 1.0f;
+}
+
+quaternion::quaternion( float x, float y, float z, float w )
+{
+ entries[0] = x;
+ entries[1] = y;
+ entries[2] = z;
+ entries[3] = w;
+}
+
+quaternion::quaternion( const quaternion &other )
+{
+ entries[0] = other.entries[0];
+ entries[1] = other.entries[1];
+ entries[2] = other.entries[2];
+ entries[3] = other.entries[3];
+}
+
+quaternion& quaternion::operator+=( const quaternion &b )
+{
+ (*this) = (*this) + b;
+ return (*this);
+}
+
+quaternion QNormalize(quaternion Quat)
+{
+ float norm;
+ norm = Quat.entries[0] * Quat.entries[0] +
+ Quat.entries[1] * Quat.entries[1] +
+ Quat.entries[2] * Quat.entries[2] +
+ Quat.entries[3] * Quat.entries[3];
+ Quat.entries[0] = float(Quat.entries[0] / norm);
+ Quat.entries[1] = float(Quat.entries[1] / norm);
+ Quat.entries[2] = float(Quat.entries[2] / norm);
+ Quat.entries[3] = float(Quat.entries[3] / norm);
+ return Quat;
+}
+
+vec3 Quat2Vector(quaternion Quat)
+{
+ QNormalize(Quat);
+
+ float fW = Quat.entries[3];
+ float fX = Quat.entries[0];
+ float fY = Quat.entries[1];
+ float fZ = Quat.entries[2];
+
+ vec3 tempvec;
+
+ tempvec.entries[0] = 2.0f*(fX*fZ-fW*fY);
+ tempvec.entries[1] = 2.0f*(fY*fZ+fW*fX);
+ tempvec.entries[2] = 1.0f-2.0f*(fX*fX+fY*fY);
+
+ return tempvec;
+}
+
+#define DELTA 0.01f
+quaternion Slerp(quaternion start, quaternion end, float t) {
+ quaternion res;
+
+ float to1[4];
+ float omega, cosom, sinom, scale0, scale1;
+
+ quaternion *from = &start;
+ quaternion *to = &end;
+
+ // calc cosine
+ cosom = from->entries[0] * to->entries[0] + from->entries[1] * to->entries[1] + from->entries[2] * to->entries[2]
+ + from->entries[3] * to->entries[3];
+
+ // adjust signs (if necessary)
+ if ( cosom <0.0f ){
+ cosom = -cosom;
+ to1[0] = - to->entries[0];
+ to1[1] = - to->entries[1];
+ to1[2] = - to->entries[2];
+ to1[3] = - to->entries[3];
+ } else {
+ to1[0] = to->entries[0];
+ to1[1] = to->entries[1];
+ to1[2] = to->entries[2];
+ to1[3] = to->entries[3];
+ }
+
+ // calculate coefficients
+ if ( (1.0f - cosom) > DELTA ) {
+ // standard case (slerp)
+ omega = acosf(cosom);
+ sinom = sinf(omega);
+ scale0 = sinf((1.0f - t) * omega) / sinom;
+ scale1 = sinf(t * omega) / sinom;
+ } else {
+ // "from" and "to" quaternions are very close
+ // ... so we can do a linear interpolation
+ scale0 = 1.0f - t;
+ scale1 = t;
+ }
+ // calculate final values
+ res.entries[0] = scale0 * from->entries[0] + scale1 * to1[0];
+ res.entries[1] = scale0 * from->entries[1] + scale1 * to1[1];
+ res.entries[2] = scale0 * from->entries[2] + scale1 * to1[2];
+ res.entries[3] = scale0 * from->entries[3] + scale1 * to1[3];
+
+ return res;
+}
+
+quaternion QuatScale(quaternion start, float alpha) {
+ quaternion neutral;
+ neutral.entries[0] = 0.0f;
+ neutral.entries[1] = 0.0f;
+ neutral.entries[2] = 0.0f;
+ neutral.entries[3] = 1.0f;
+
+ return Slerp(neutral,start,alpha);
+}
+
+// Adapted from a paper by ID Software
+// http://cache-www.intel.com/cd/00/00/29/37/293748_293748.pdf
+quaternion QuaternionFromMat4( const mat4 &R )
+{
+ const float *m = R.entries;
+ quaternion quat;
+
+ if ( m[0 * 4 + 0] + m[1 * 4 + 1] + m[2 * 4 + 2] > 0.0f ) {
+ float t = + m[0 * 4 + 0] + m[1 * 4 + 1] + m[2 * 4 + 2] + 1.0f;
+ float s = sqrtf( t ) * 0.5f;
+ quat.entries[3] = s * t;
+ quat.entries[2] = ( m[0 * 4 + 1] - m[1 * 4 + 0] ) * s;
+ quat.entries[1] = ( m[2 * 4 + 0] - m[0 * 4 + 2] ) * s;
+ quat.entries[0] = ( m[1 * 4 + 2] - m[2 * 4 + 1] ) * s;
+ } else if ( m[0 * 4 + 0] > m[1 * 4 + 1] && m[0 * 4 + 0] > m[2 * 4 + 2] ) {
+ float t = + m[0 * 4 + 0] - m[1 * 4 + 1] - m[2 * 4 + 2] + 1.0f;
+ float s = sqrtf( t ) * 0.5f;
+ quat.entries[0] = s * t;
+ quat.entries[1] = ( m[0 * 4 + 1] + m[1 * 4 + 0] ) * s;
+ quat.entries[2] = ( m[2 * 4 + 0] + m[0 * 4 + 2] ) * s;
+ quat.entries[3] = ( m[1 * 4 + 2] - m[2 * 4 + 1] ) * s;
+ } else if ( m[1 * 4 + 1] > m[2 * 4 + 2] ) {
+ float t = - m[0 * 4 + 0] + m[1 * 4 + 1] - m[2 * 4 + 2] + 1.0f;
+ float s = sqrtf( t ) * 0.5f;
+ quat.entries[1] = s * t;
+ quat.entries[0] = ( m[0 * 4 + 1] + m[1 * 4 + 0] ) * s;
+ quat.entries[3] = ( m[2 * 4 + 0] - m[0 * 4 + 2] ) * s;
+ quat.entries[2] = ( m[1 * 4 + 2] + m[2 * 4 + 1] ) * s;
+ } else {
+ float t = - m[0 * 4 + 0] - m[1 * 4 + 1] + m[2 * 4 + 2] + 1.0f;
+ float s = sqrtf( t ) * 0.5f;
+ quat.entries[2] = s * t;
+ quat.entries[3] = ( m[0 * 4 + 1] - m[1 * 4 + 0] ) * s;
+ quat.entries[0] = ( m[2 * 4 + 0] + m[0 * 4 + 2] ) * s;
+ quat.entries[1] = ( m[1 * 4 + 2] + m[2 * 4 + 1] ) * s;
+ }
+
+ QuaternionNormalize(&quat);
+
+ return quat;
+}
+
+mat4 Mat4FromQuaternion( const quaternion &q )
+{
+ mat4 matrix;
+ float *m = matrix.entries;
+ float x2 = q.entries[0] + q.entries[0];
+ float y2 = q.entries[1] + q.entries[1];
+ float z2 = q.entries[2] + q.entries[2];
+ {
+ float xx2 = q.entries[0] * x2;
+ float yy2 = q.entries[1] * y2;
+ float zz2 = q.entries[2] * z2;
+ m[0*4+0] = 1.0f - yy2 - zz2;
+ m[1*4+1] = 1.0f - xx2 - zz2;
+ m[2*4+2] = 1.0f - xx2 - yy2;
+ }
+ {
+ float yz2 = q.entries[1] * z2;
+ float wx2 = q.entries[3] * x2;
+ m[2*4+1] = yz2 - wx2;
+ m[1*4+2] = yz2 + wx2;
+ }
+ {
+ float xy2 = q.entries[0] * y2;
+ float wz2 = q.entries[3] * z2;
+ m[1*4+0] = xy2 - wz2;
+ m[0*4+1] = xy2 + wz2;
+ }
+ {
+ float xz2 = q.entries[0] * z2;
+ float wy2 = q.entries[3] * y2;
+ m[0*4+2] = xz2 - wy2;
+ m[2*4+0] = xz2 + wy2;
+ }
+ return matrix;
+}
+
+const quaternion operator*( const quaternion &a, const quaternion &b )
+{
+ return Quat_Mult(a,b);
+}
+
+const vec3 operator*( const quaternion &a, const vec3 &b )
+{
+ vec3 result = b;
+ QuaternionMultiplyVector(&a, &result);
+ return result;
+}
+
+const quaternion operator*( const quaternion &a, float b )
+{
+ return quaternion(a.entries[0] * b,
+ a.entries[1] * b,
+ a.entries[2] * b,
+ a.entries[3] * b);
+}
+
+vec3 ASMult( quaternion a, vec3 b )
+{
+ vec3 result = b;
+ QuaternionMultiplyVector(&a, &result);
+ return result;
+}
+
+const quaternion operator+( const quaternion &a, const quaternion &b )
+{
+ return quaternion(a.entries[0] + b.entries[0],
+ a.entries[1] + b.entries[1],
+ a.entries[2] + b.entries[2],
+ a.entries[3] + b.entries[3]);
+}
+
+float dot( const quaternion &a, const quaternion &b )
+{
+ return a.entries[0] * b.entries[0] +
+ a.entries[1] * b.entries[1] +
+ a.entries[2] * b.entries[2] +
+ a.entries[3] * b.entries[3];
+}
+
+mat3 Mat3FromQuaternion( const quaternion &quat ) {
+ mat3 matrix;
+ float *m = matrix.entries;
+ const float *q = quat.entries;
+ float x2 = q[0] + q[0];
+ float y2 = q[1] + q[1];
+ float z2 = q[2] + q[2];
+ float xx2 = q[0] * x2;
+ float yy2 = q[1] * y2;
+ float zz2 = q[2] * z2;
+ m[0*3+0] = 1.0f - yy2 - zz2;
+ m[1*3+1] = 1.0f - xx2 - zz2;
+ m[2*3+2] = 1.0f - xx2 - yy2;
+ float yz2 = q[1] * z2;
+ float wx2 = q[3] * x2;
+ m[2*3+1] = yz2 - wx2;
+ m[1*3+2] = yz2 + wx2;
+ float xy2 = q[0] * y2;
+ float wz2 = q[3] * z2;
+ m[1*3+0] = xy2 - wz2;
+ m[0*3+1] = xy2 + wz2;
+ float xz2 = q[0] * z2;
+ float wy2 = q[3] * y2;
+ m[0*3+2] = xz2 - wy2;
+ m[2*3+0] = xz2 + wy2;
+ return matrix;
+}
+
+bool operator!=(const quaternion &a, const quaternion &b) {
+ for(int i=0; i<4; ++i){
+ if(a[i] != b[i]){
+ return true;
+ }
+ }
+ return false;
+}
+
+bool operator==(const quaternion &a, const quaternion &b) {
+ return !(a != b);
+}
+
+// Funcion assumes xyzw order!
+vec3 QuaternionToEuler(const quaternion& quat) {
+ vec3 euler_angles;
+ quaternion q(quat[3], quat[0], quat[1], quat[2]);
+ euler_angles[0] = atan2(2*(q[0]*q[1] + q[2]*q[3]), 1 - 2*(q[1]*q[1] + q[2]*q[2]));
+ float sinval = 2*(q[0]*q[2] - q[3]*q[1]);
+ if(fabs(sinval) >= 1.0f)
+ if(sinval >= 0.0f)
+ euler_angles[1] = 3.14159266f / 2.0f;
+ else
+ euler_angles[1] = 3.14159266f / -2.0f;
+ else
+ euler_angles[1] = asin(2*(q[0]*q[2] - q[3]*q[1]));
+ euler_angles[2] = atan2(2*(q[0]*q[3] + q[1]*q[2]), 1 - 2*(q[2]*q[2] + q[3]*q[3]));
+ return euler_angles;
+}
+
+quaternion EulerToQuaternion(const vec3& euler) {
+ quaternion q;
+ vec3 eu = euler * 0.5f; // makes conversion simpler
+ q[3] = cos(eu[0])*cos(eu[1])*cos(eu[2]) +
+ sin(eu[0])*sin(eu[1])*sin(eu[2]);
+ q[0] = sin(eu[0])*cos(eu[1])*cos(eu[2]) -
+ cos(eu[0])*sin(eu[1])*sin(eu[2]);
+ q[1] = cos(eu[0])*sin(eu[1])*cos(eu[2]) +
+ sin(eu[0])*cos(eu[1])*sin(eu[2]);
+ q[2] = cos(eu[0])*cos(eu[1])*sin(eu[2]) -
+ sin(eu[0])*sin(eu[1])*cos(eu[2]);
+ return q;
+}
diff --git a/Source/Math/quaternions.h b/Source/Math/quaternions.h
new file mode 100644
index 00000000..f0f9d8f7
--- /dev/null
+++ b/Source/Math/quaternions.h
@@ -0,0 +1,107 @@
+//-----------------------------------------------------------------------------
+// Name: quaternions.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3math.h>
+#include <Math/mat4.h>
+#include <Math/mat3.h>
+
+class quaternion {
+public:
+ float entries[4];
+
+ quaternion& operator+=( const quaternion &b );
+
+ quaternion(bool Degree_Flag, vec3 Euler);
+ quaternion(float x, float y, float z, float w);
+ quaternion(vec4 AnAx); // vec4(vec3(axis), float angle [radians])
+ quaternion(const quaternion &other);
+ quaternion();
+ float& operator[](int which){ return entries[which]; }
+ const float& operator[](int which)const{ return entries[which]; }
+};
+
+float Length2(const quaternion& quat);
+vec4 Quat_2_AA(quaternion Quat);
+quaternion normalize(const quaternion &quat);
+quaternion Quat_Mult(const quaternion &q1, const quaternion &q2);
+quaternion QNormalize(quaternion Quat);
+vec3 Quat2Vector(quaternion Quat);
+void QuaternionInvert(quaternion * quat);
+void QuaternionToAxisAngle(quaternion quat, vec3 * axis, float * angle);
+quaternion QuaternionFromAxisAngle(vec3 axis, float angle);
+
+quaternion QuaternionFromMat4(const mat4 &m);
+mat4 Mat4FromQuaternion(const quaternion &Quat);
+mat3 Mat3FromQuaternion(const quaternion &Quat);
+
+quaternion invert(const quaternion &quat);
+const quaternion operator*(const quaternion &a,
+ const quaternion &b);
+bool operator!=(const quaternion &a,
+ const quaternion &b);
+bool operator==(const quaternion &a,
+ const quaternion &b);
+const quaternion operator+(const quaternion &a,
+ const quaternion &b);
+const vec3 operator*(const quaternion &a,
+ const vec3 &b);
+const quaternion operator*(const quaternion &a,
+ float b);
+float dot( const quaternion &a, const quaternion &b );
+
+quaternion Slerp(quaternion start, quaternion end, float t);
+inline quaternion mix( const quaternion &a, const quaternion &b, float alpha )
+{return Slerp(a,b,alpha);}
+
+vec3 ASMult( quaternion a, vec3 b );
+quaternion invert_by_val(quaternion quat);
+void QuaternionMultiplyVector(const quaternion * quat, vec3 * vector);
+
+
+inline vec3 QuaternionMultiplyVectorFast(const quaternion& quat, const vec3& vector) {
+ // Adapted from https://github.com/g-truc/glm/blob/master/glm/detail/type_quat.inl
+ // Also from Fabien Giesen, according to - https://blog.molecular-matters.com/2013/05/24/a-faster-quaternion-vector-multiplication/
+ vec3 quat_vector = *(vec3*)&quat.entries[0];
+ vec3 uv = cross(quat_vector, vector);
+ vec3 uuv = cross(quat_vector, uv);
+ return vector + ((uv * quat[3]) + uuv) * 2.0f;
+}
+
+inline vec3 TransformVec3(const vec3& scale, const quaternion& rotation, const vec3& translation, const vec3& vertex) {
+ vec3 result = vertex;
+ result *= scale;
+ result = QuaternionMultiplyVectorFast(rotation, result);
+ result += translation;
+ return result;
+}
+
+
+vec3 QuaternionToEuler(const quaternion& quat);
+quaternion EulerToQuaternion(const vec3& euler);
+
+inline std::ostream& operator<<(std::ostream& os, const quaternion& v )
+{
+ os << "quat(" << v[0] << "," << v[1] << "," << v[2] << "," << v[3] << ")";
+ return os;
+}
diff --git a/Source/Math/simd_mat4.cpp b/Source/Math/simd_mat4.cpp
new file mode 100644
index 00000000..3defbcd1
--- /dev/null
+++ b/Source/Math/simd_mat4.cpp
@@ -0,0 +1,22 @@
+//-----------------------------------------------------------------------------
+// Name: simd_mat4.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
diff --git a/Source/Math/simd_mat4.h b/Source/Math/simd_mat4.h
new file mode 100644
index 00000000..6db91587
--- /dev/null
+++ b/Source/Math/simd_mat4.h
@@ -0,0 +1,157 @@
+//-----------------------------------------------------------------------------
+// Name: simd_mat4.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <xmmintrin.h>
+
+class simd_mat4
+{
+public:
+ __m128 col[4];
+
+ void assign(const float* entries){
+ col[0] = _mm_loadu_ps(&entries[0]);
+ col[1] = _mm_loadu_ps(&entries[4]);
+ col[2] = _mm_loadu_ps(&entries[8]);
+ col[3] = _mm_loadu_ps(&entries[12]);
+
+ /*__declspec(align(16)) float entries_rearranged[] = {
+ entries[0], entries[4], entries[8], entries[12],
+ entries[1], entries[5], entries[9], entries[13],
+ entries[2], entries[6], entries[10], entries[14],
+ entries[3], entries[7], entries[11], entries[15]};
+ row[0] = _mm_load_ps(&entries_rearranged[0]);
+ row[1] = _mm_load_ps(&entries_rearranged[4]);
+ row[2] = _mm_load_ps(&entries_rearranged[8]);
+ row[3] = _mm_load_ps(&entries_rearranged[12]);*/
+ }
+
+ simd_mat4& operator=(const simd_mat4 &other){
+ col[0] = other.col[0];
+ col[1] = other.col[1];
+ col[2] = other.col[2];
+ col[3] = other.col[3];
+ return *this;
+ }
+
+ simd_mat4 operator*(float val){
+ __m128 _m_val = _mm_load1_ps(&val);
+ simd_mat4 result;
+ result.col[0] = _mm_mul_ps(col[0], _m_val);
+ result.col[1] = _mm_mul_ps(col[1], _m_val);
+ result.col[2] = _mm_mul_ps(col[2], _m_val);
+ result.col[3] = _mm_mul_ps(col[3], _m_val);
+ return result;
+ }
+
+ void operator+=(const simd_mat4 &other){
+ col[0] = _mm_add_ps(col[0], other.col[0]);
+ col[1] = _mm_add_ps(col[1], other.col[1]);
+ col[2] = _mm_add_ps(col[2], other.col[2]);
+ col[3] = _mm_add_ps(col[3], other.col[3]);
+ }
+
+ void AddRotatedVec3(const float *rhs) {
+#ifdef _WIN32
+ __declspec(align(16)) float me[16];
+#else
+ static float me[16] __attribute__((aligned(16)));
+#endif
+
+ _mm_store_ps(&me[0], col[0]);
+ _mm_store_ps(&me[4], col[1]);
+ _mm_store_ps(&me[8], col[2]);
+ _mm_store_ps(&me[12], col[3]);
+
+ me[12] += me[0]*rhs[0] + me[4]*rhs[1] + me[8]*rhs[2];
+ me[13] += me[1]*rhs[0] + me[5]*rhs[1] + me[9]*rhs[2];
+ me[14] += me[2]*rhs[0] + me[6]*rhs[1] + me[10]*rhs[2];
+
+ col[0] = _mm_load_ps(&me[0]);
+ col[1] = _mm_load_ps(&me[4]);
+ col[2] = _mm_load_ps(&me[8]);
+ col[3] = _mm_load_ps(&me[12]);
+ }
+
+ void ToVec4(float *vector1, float *vector2, float *vector3, float *vector4){
+#ifdef _WIN32
+ __declspec(align(16)) float entries[16];
+#else
+ static float entries[16] __attribute__((aligned(16)));
+#endif
+ _mm_store_ps(&entries[0], col[0]);
+ _mm_store_ps(&entries[4], col[1]);
+ _mm_store_ps(&entries[8], col[2]);
+ _mm_store_ps(&entries[12], col[3]);
+
+ vector1[0] = entries[0];
+ vector1[1] = entries[4];
+ vector1[2] = entries[8];
+ vector1[3] = entries[12];
+ vector2[0] = entries[1];
+ vector2[1] = entries[5];
+ vector2[2] = entries[9];
+ vector2[3] = entries[13];
+ vector3[0] = entries[2];
+ vector3[1] = entries[6];
+ vector3[2] = entries[10];
+ vector3[3] = entries[14];
+ vector4[0] = entries[3];
+ vector4[1] = entries[7];
+ vector4[2] = entries[11];
+ vector4[3] = entries[15];
+ }
+
+ void AddRotatedVec3AndSave(const float *rhs, float *vector1, float *vector2, float *vector3, float *vector4) {
+#ifdef _WIN32
+ __declspec(align(16)) float me[16];
+#else
+ static float me[16] __attribute__((aligned(16)));
+#endif
+ _mm_store_ps(&me[0], col[0]);
+ _mm_store_ps(&me[4], col[1]);
+ _mm_store_ps(&me[8], col[2]);
+ _mm_store_ps(&me[12], col[3]);
+
+ me[12] += me[0]*rhs[0] + me[4]*rhs[1] + me[8]*rhs[2];
+ me[13] += me[1]*rhs[0] + me[5]*rhs[1] + me[9]*rhs[2];
+ me[14] += me[2]*rhs[0] + me[6]*rhs[1] + me[10]*rhs[2];
+
+ vector1[0] = me[0];
+ vector1[1] = me[4];
+ vector1[2] = me[8];
+ vector1[3] = me[12];
+ vector2[0] = me[1];
+ vector2[1] = me[5];
+ vector2[2] = me[9];
+ vector2[3] = me[13];
+ vector3[0] = me[2];
+ vector3[1] = me[6];
+ vector3[2] = me[10];
+ vector3[3] = me[14];
+ vector4[0] = me[3];
+ vector4[1] = me[7];
+ vector4[2] = me[11];
+ vector4[3] = me[15];
+ }
+};
diff --git a/Source/Math/triangle.c b/Source/Math/triangle.c
new file mode 100644
index 00000000..05ec1040
--- /dev/null
+++ b/Source/Math/triangle.c
@@ -0,0 +1,16006 @@
+/*****************************************************************************/
+/* */
+/* 888888888 ,o, / 888 */
+/* 888 88o88o " o8888o 88o8888o o88888o 888 o88888o */
+/* 888 888 888 88b 888 888 888 888 888 d888 88b */
+/* 888 888 888 o88^o888 888 888 "88888" 888 8888oo888 */
+/* 888 888 888 C888 888 888 888 / 888 q888 */
+/* 888 888 888 "88o^888 888 888 Cb 888 "88oooo" */
+/* "8oo8D */
+/* */
+/* A Two-Dimensional Quality Mesh Generator and Delaunay Triangulator. */
+/* (triangle.c) */
+/* */
+/* Version 1.6 */
+/* July 28, 2005 */
+/* */
+/* Copyright 1993, 1995, 1997, 1998, 2002, 2005 */
+/* Jonathan Richard Shewchuk */
+/* 2360 Woolsey #H */
+/* Berkeley, California 94705-1927 */
+/* jrs@cs.berkeley.edu */
+/* */
+/* This program may be freely redistributed under the condition that the */
+/* copyright notices (including this entire header and the copyright */
+/* notice printed when the `-h' switch is selected) are not removed, and */
+/* no compensation is received. Private, research, and institutional */
+/* use is free. You may distribute modified versions of this code UNDER */
+/* THE CONDITION THAT THIS CODE AND ANY MODIFICATIONS MADE TO IT IN THE */
+/* SAME FILE REMAIN UNDER COPYRIGHT OF THE ORIGINAL AUTHOR, BOTH SOURCE */
+/* AND OBJECT CODE ARE MADE FREELY AVAILABLE WITHOUT CHARGE, AND CLEAR */
+/* NOTICE IS GIVEN OF THE MODIFICATIONS. Distribution of this code as */
+/* part of a commercial system is permissible ONLY BY DIRECT ARRANGEMENT */
+/* WITH THE AUTHOR. (If you are not directly supplying this code to a */
+/* customer, and you are instead telling them how they can obtain it for */
+/* free, then you are not required to make any arrangement with me.) */
+/* */
+/* Hypertext instructions for Triangle are available on the Web at */
+/* */
+/* http://www.cs.cmu.edu/~quake/triangle.html */
+/* */
+/* Disclaimer: Neither I nor Carnegie Mellon warrant this code in any way */
+/* whatsoever. This code is provided "as-is". Use at your own risk. */
+/* */
+/* Some of the references listed below are marked with an asterisk. [*] */
+/* These references are available for downloading from the Web page */
+/* */
+/* http://www.cs.cmu.edu/~quake/triangle.research.html */
+/* */
+/* Three papers discussing aspects of Triangle are available. A short */
+/* overview appears in "Triangle: Engineering a 2D Quality Mesh */
+/* Generator and Delaunay Triangulator," in Applied Computational */
+/* Geometry: Towards Geometric Engineering, Ming C. Lin and Dinesh */
+/* Manocha, editors, Lecture Notes in Computer Science volume 1148, */
+/* pages 203-222, Springer-Verlag, Berlin, May 1996 (from the First ACM */
+/* Workshop on Applied Computational Geometry). [*] */
+/* */
+/* The algorithms are discussed in the greatest detail in "Delaunay */
+/* Refinement Algorithms for Triangular Mesh Generation," Computational */
+/* Geometry: Theory and Applications 22(1-3):21-74, May 2002. [*] */
+/* */
+/* More detail about the data structures may be found in my dissertation: */
+/* "Delaunay Refinement Mesh Generation," Ph.D. thesis, Technical Report */
+/* CMU-CS-97-137, School of Computer Science, Carnegie Mellon University, */
+/* Pittsburgh, Pennsylvania, 18 May 1997. [*] */
+/* */
+/* Triangle was created as part of the Quake Project in the School of */
+/* Computer Science at Carnegie Mellon University. For further */
+/* information, see Hesheng Bao, Jacobo Bielak, Omar Ghattas, Loukas F. */
+/* Kallivokas, David R. O'Hallaron, Jonathan R. Shewchuk, and Jifeng Xu, */
+/* "Large-scale Simulation of Elastic Wave Propagation in Heterogeneous */
+/* Media on Parallel Computers," Computer Methods in Applied Mechanics */
+/* and Engineering 152(1-2):85-102, 22 January 1998. */
+/* */
+/* Triangle's Delaunay refinement algorithm for quality mesh generation is */
+/* a hybrid of one due to Jim Ruppert, "A Delaunay Refinement Algorithm */
+/* for Quality 2-Dimensional Mesh Generation," Journal of Algorithms */
+/* 18(3):548-585, May 1995 [*], and one due to L. Paul Chew, "Guaranteed- */
+/* Quality Mesh Generation for Curved Surfaces," Proceedings of the Ninth */
+/* Annual Symposium on Computational Geometry (San Diego, California), */
+/* pages 274-280, Association for Computing Machinery, May 1993, */
+/* http://portal.acm.org/citation.cfm?id=161150 . */
+/* */
+/* The Delaunay refinement algorithm has been modified so that it meshes */
+/* domains with small input angles well, as described in Gary L. Miller, */
+/* Steven E. Pav, and Noel J. Walkington, "When and Why Ruppert's */
+/* Algorithm Works," Twelfth International Meshing Roundtable, pages */
+/* 91-102, Sandia National Laboratories, September 2003. [*] */
+/* */
+/* My implementation of the divide-and-conquer and incremental Delaunay */
+/* triangulation algorithms follows closely the presentation of Guibas */
+/* and Stolfi, even though I use a triangle-based data structure instead */
+/* of their quad-edge data structure. (In fact, I originally implemented */
+/* Triangle using the quad-edge data structure, but the switch to a */
+/* triangle-based data structure sped Triangle by a factor of two.) The */
+/* mesh manipulation primitives and the two aforementioned Delaunay */
+/* triangulation algorithms are described by Leonidas J. Guibas and Jorge */
+/* Stolfi, "Primitives for the Manipulation of General Subdivisions and */
+/* the Computation of Voronoi Diagrams," ACM Transactions on Graphics */
+/* 4(2):74-123, April 1985, http://portal.acm.org/citation.cfm?id=282923 .*/
+/* */
+/* Their O(n log n) divide-and-conquer algorithm is adapted from Der-Tsai */
+/* Lee and Bruce J. Schachter, "Two Algorithms for Constructing the */
+/* Delaunay Triangulation," International Journal of Computer and */
+/* Information Science 9(3):219-242, 1980. Triangle's improvement of the */
+/* divide-and-conquer algorithm by alternating between vertical and */
+/* horizontal cuts was introduced by Rex A. Dwyer, "A Faster Divide-and- */
+/* Conquer Algorithm for Constructing Delaunay Triangulations," */
+/* Algorithmica 2(2):137-151, 1987. */
+/* */
+/* The incremental insertion algorithm was first proposed by C. L. Lawson, */
+/* "Software for C1 Surface Interpolation," in Mathematical Software III, */
+/* John R. Rice, editor, Academic Press, New York, pp. 161-194, 1977. */
+/* For point location, I use the algorithm of Ernst P. Mucke, Isaac */
+/* Saias, and Binhai Zhu, "Fast Randomized Point Location Without */
+/* Preprocessing in Two- and Three-Dimensional Delaunay Triangulations," */
+/* Proceedings of the Twelfth Annual Symposium on Computational Geometry, */
+/* ACM, May 1996. [*] If I were to randomize the order of vertex */
+/* insertion (I currently don't bother), their result combined with the */
+/* result of Kenneth L. Clarkson and Peter W. Shor, "Applications of */
+/* Random Sampling in Computational Geometry II," Discrete & */
+/* Computational Geometry 4(1):387-421, 1989, would yield an expected */
+/* O(n^{4/3}) bound on running time. */
+/* */
+/* The O(n log n) sweepline Delaunay triangulation algorithm is taken from */
+/* Steven Fortune, "A Sweepline Algorithm for Voronoi Diagrams", */
+/* Algorithmica 2(2):153-174, 1987. A random sample of edges on the */
+/* boundary of the triangulation are maintained in a splay tree for the */
+/* purpose of point location. Splay trees are described by Daniel */
+/* Dominic Sleator and Robert Endre Tarjan, "Self-Adjusting Binary Search */
+/* Trees," Journal of the ACM 32(3):652-686, July 1985, */
+/* http://portal.acm.org/citation.cfm?id=3835 . */
+/* */
+/* The algorithms for exact computation of the signs of determinants are */
+/* described in Jonathan Richard Shewchuk, "Adaptive Precision Floating- */
+/* Point Arithmetic and Fast Robust Geometric Predicates," Discrete & */
+/* Computational Geometry 18(3):305-363, October 1997. (Also available */
+/* as Technical Report CMU-CS-96-140, School of Computer Science, */
+/* Carnegie Mellon University, Pittsburgh, Pennsylvania, May 1996.) [*] */
+/* An abbreviated version appears as Jonathan Richard Shewchuk, "Robust */
+/* Adaptive Floating-Point Geometric Predicates," Proceedings of the */
+/* Twelfth Annual Symposium on Computational Geometry, ACM, May 1996. [*] */
+/* Many of the ideas for my exact arithmetic routines originate with */
+/* Douglas M. Priest, "Algorithms for Arbitrary Precision Floating Point */
+/* Arithmetic," Tenth Symposium on Computer Arithmetic, pp. 132-143, IEEE */
+/* Computer Society Press, 1991. [*] Many of the ideas for the correct */
+/* evaluation of the signs of determinants are taken from Steven Fortune */
+/* and Christopher J. Van Wyk, "Efficient Exact Arithmetic for Computa- */
+/* tional Geometry," Proceedings of the Ninth Annual Symposium on */
+/* Computational Geometry, ACM, pp. 163-172, May 1993, and from Steven */
+/* Fortune, "Numerical Stability of Algorithms for 2D Delaunay Triangu- */
+/* lations," International Journal of Computational Geometry & Applica- */
+/* tions 5(1-2):193-213, March-June 1995. */
+/* */
+/* The method of inserting new vertices off-center (not precisely at the */
+/* circumcenter of every poor-quality triangle) is from Alper Ungor, */
+/* "Off-centers: A New Type of Steiner Points for Computing Size-Optimal */
+/* Quality-Guaranteed Delaunay Triangulations," Proceedings of LATIN */
+/* 2004 (Buenos Aires, Argentina), April 2004. */
+/* */
+/* For definitions of and results involving Delaunay triangulations, */
+/* constrained and conforming versions thereof, and other aspects of */
+/* triangular mesh generation, see the excellent survey by Marshall Bern */
+/* and David Eppstein, "Mesh Generation and Optimal Triangulation," in */
+/* Computing and Euclidean Geometry, Ding-Zhu Du and Frank Hwang, */
+/* editors, World Scientific, Singapore, pp. 23-90, 1992. [*] */
+/* */
+/* The time for incrementally adding PSLG (planar straight line graph) */
+/* segments to create a constrained Delaunay triangulation is probably */
+/* O(t^2) per segment in the worst case and O(t) per segment in the */
+/* common case, where t is the number of triangles that intersect the */
+/* segment before it is inserted. This doesn't count point location, */
+/* which can be much more expensive. I could improve this to O(d log d) */
+/* time, but d is usually quite small, so it's not worth the bother. */
+/* (This note does not apply when the -s switch is used, invoking a */
+/* different method is used to insert segments.) */
+/* */
+/* The time for deleting a vertex from a Delaunay triangulation is O(d^2) */
+/* in the worst case and O(d) in the common case, where d is the degree */
+/* of the vertex being deleted. I could improve this to O(d log d) time, */
+/* but d is usually quite small, so it's not worth the bother. */
+/* */
+/* Ruppert's Delaunay refinement algorithm typically generates triangles */
+/* at a linear rate (constant time per triangle) after the initial */
+/* triangulation is formed. There may be pathological cases where */
+/* quadratic time is required, but these never arise in practice. */
+/* */
+/* The geometric predicates (circumcenter calculations, segment */
+/* intersection formulae, etc.) appear in my "Lecture Notes on Geometric */
+/* Robustness" at http://www.cs.berkeley.edu/~jrs/mesh . */
+/* */
+/* If you make any improvements to this code, please please please let me */
+/* know, so that I may obtain the improvements. Even if you don't change */
+/* the code, I'd still love to hear what it's being used for. */
+/* */
+/*****************************************************************************/
+
+/* For single precision (which will save some memory and reduce paging), */
+/* define the symbol SINGLE by using the -DSINGLE compiler switch or by */
+/* writing "#define SINGLE" below. */
+/* */
+/* For double precision (which will allow you to refine meshes to a smaller */
+/* edge length), leave SINGLE undefined. */
+/* */
+/* Double precision uses more memory, but improves the resolution of the */
+/* meshes you can generate with Triangle. It also reduces the likelihood */
+/* of a floating exception due to overflow. Finally, it is much faster */
+/* than single precision on 64-bit architectures like the DEC Alpha. I */
+/* recommend double precision unless you want to generate a mesh for which */
+/* you do not have enough memory. */
+
+/* #define SINGLE */
+
+#ifdef SINGLE
+#define tREAL float
+#else /* not SINGLE */
+#define tREAL double
+#endif /* not SINGLE */
+
+/* If yours is not a Unix system, define the NO_TIMER compiler switch to */
+/* remove the Unix-specific timing code. */
+
+ #define NO_TIMER
+
+/* To insert lots of self-checks for internal errors, define the SELF_CHECK */
+/* symbol. This will slow down the program significantly. It is best to */
+/* define the symbol using the -DSELF_CHECK compiler switch, but you could */
+/* write "#define SELF_CHECK" below. If you are modifying this code, I */
+/* recommend you turn self-checks on until your work is debugged. */
+
+/* #define SELF_CHECK */
+
+/* To compile Triangle as a callable object library (triangle.o), define the */
+/* TRILIBRARY symbol. Read the file triangle.h for details on how to call */
+/* the procedure triangulate() that results. */
+
+ #define TRILIBRARY
+
+/* It is possible to generate a smaller version of Triangle using one or */
+/* both of the following symbols. Define the REDUCED symbol to eliminate */
+/* all features that are primarily of research interest; specifically, the */
+/* -i, -F, -s, and -C switches. Define the CDT_ONLY symbol to eliminate */
+/* all meshing algorithms above and beyond constrained Delaunay */
+/* triangulation; specifically, the -r, -q, -a, -u, -D, -S, and -s */
+/* switches. These reductions are most likely to be useful when */
+/* generating an object library (triangle.o) by defining the TRILIBRARY */
+/* symbol. */
+
+/* #define REDUCED */
+/* #define CDT_ONLY */
+
+/* On some machines, my exact arithmetic routines might be defeated by the */
+/* use of internal extended precision floating-point registers. The best */
+/* way to solve this problem is to set the floating-point registers to use */
+/* single or double precision internally. On 80x86 processors, this may */
+/* be accomplished by setting the CPU86 symbol for the Microsoft C */
+/* compiler, or the LINUX symbol for the gcc compiler running on Linux. */
+/* */
+/* An inferior solution is to declare certain values as `volatile', thus */
+/* forcing them to be stored to memory and rounded off. Unfortunately, */
+/* this solution might slow Triangle down quite a bit. To use volatile */
+/* values, write "#define INEXACT volatile" below. Normally, however, */
+/* INEXACT should be defined to be nothing. ("#define INEXACT".) */
+/* */
+/* For more discussion, see http://www.cs.cmu.edu/~quake/robust.pc.html . */
+/* For yet more discussion, see Section 5 of my paper, "Adaptive Precision */
+/* Floating-Point Arithmetic and Fast Robust Geometric Predicates" (also */
+/* available as Section 6.6 of my dissertation). */
+
+/* #define CPU86 */
+/* #define LINUX */
+
+#define INEXACT /* Nothing */
+/* #define INEXACT volatile */
+
+/* Maximum number of characters in a file name (including the null). */
+
+#define FILENAMESIZE 2048
+
+/* Maximum number of characters in a line read from a file (including the */
+/* null). */
+
+#define INPUTLINESIZE 1024
+
+/* For efficiency, a variety of data structures are allocated in bulk. The */
+/* following constants determine how many of each structure is allocated */
+/* at once. */
+
+#define TRIPERBLOCK 4092 /* Number of triangles allocated at once. */
+#define SUBSEGPERBLOCK 508 /* Number of subsegments allocated at once. */
+#define VERTEXPERBLOCK 4092 /* Number of vertices allocated at once. */
+#define VIRUSPERBLOCK 1020 /* Number of virus triangles allocated at once. */
+/* Number of encroached subsegments allocated at once. */
+#define BADSUBSEGPERBLOCK 252
+/* Number of skinny triangles allocated at once. */
+#define BADTRIPERBLOCK 4092
+/* Number of flipped triangles allocated at once. */
+#define FLIPSTACKERPERBLOCK 252
+/* Number of splay tree nodes allocated at once. */
+#define SPLAYNODEPERBLOCK 508
+
+/* The vertex types. A DEADVERTEX has been deleted entirely. An */
+/* UNDEADVERTEX is not part of the mesh, but is written to the output */
+/* .node file and affects the node indexing in the other output files. */
+
+#define INPUTVERTEX 0
+#define SEGMENTVERTEX 1
+#define FREEVERTEX 2
+#define DEADVERTEX -32768
+#define UNDEADVERTEX -32767
+
+/* The next line is used to outsmart some very stupid compilers. If your */
+/* compiler is smarter, feel free to replace the "int" with "void". */
+/* Not that it matters. */
+
+#define VOID int
+
+/* Two constants for algorithms based on random sampling. Both constants */
+/* have been chosen empirically to optimize their respective algorithms. */
+
+/* Used for the point location scheme of Mucke, Saias, and Zhu, to decide */
+/* how large a random sample of triangles to inspect. */
+
+#define SAMPLEFACTOR 11
+
+/* Used in Fortune's sweepline Delaunay algorithm to determine what fraction */
+/* of boundary edges should be maintained in the splay tree for point */
+/* location on the front. */
+
+#define SAMPLERATE 10
+
+/* A number that speaks for itself, every kissable digit. */
+
+#define PI 3.141592653589793238462643383279502884197169399375105820974944592308
+
+/* Another fave. */
+
+#define SQUAREROOTTWO 1.4142135623730950488016887242096980785696718753769480732
+
+/* And here's one for those of you who are intimidated by math. */
+
+#define ONETHIRD 0.333333333333333333333333333333333333333333333333333333333333
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#ifndef NO_TIMER
+#include <sys/time.h>
+#endif /* not NO_TIMER */
+#ifdef CPU86
+#include <float.h>
+#endif /* CPU86 */
+#ifdef LINUX
+#include <fpu_control.h>
+#endif /* LINUX */
+#ifdef TRILIBRARY
+#include "triangle.h"
+#endif /* TRILIBRARY */
+
+/* A few forward declarations. */
+
+#ifndef TRILIBRARY
+char *readline();
+char *findfield();
+#endif /* not TRILIBRARY */
+
+/* Labels that signify the result of point location. The result of a */
+/* search indicates that the point falls in the interior of a triangle, on */
+/* an edge, on a vertex, or outside the mesh. */
+
+enum locateresult {INTRIANGLE, ONEDGE, ONVERTEX, OUTSIDE};
+
+/* Labels that signify the result of vertex insertion. The result indicates */
+/* that the vertex was inserted with complete success, was inserted but */
+/* encroaches upon a subsegment, was not inserted because it lies on a */
+/* segment, or was not inserted because another vertex occupies the same */
+/* location. */
+
+enum insertvertexresult {SUCCESSFULVERTEX, ENCROACHINGVERTEX, VIOLATINGVERTEX,
+ DUPLICATEVERTEX};
+
+/* Labels that signify the result of direction finding. The result */
+/* indicates that a segment connecting the two query points falls within */
+/* the direction triangle, along the left edge of the direction triangle, */
+/* or along the right edge of the direction triangle. */
+
+enum finddirectionresult {WITHIN, LEFTCOLLINEAR, RIGHTCOLLINEAR};
+
+/*****************************************************************************/
+/* */
+/* The basic mesh data structures */
+/* */
+/* There are three: vertices, triangles, and subsegments (abbreviated */
+/* `subseg'). These three data structures, linked by pointers, comprise */
+/* the mesh. A vertex simply represents a mesh vertex and its properties. */
+/* A triangle is a triangle. A subsegment is a special data structure used */
+/* to represent an impenetrable edge of the mesh (perhaps on the outer */
+/* boundary, on the boundary of a hole, or part of an internal boundary */
+/* separating two triangulated regions). Subsegments represent boundaries, */
+/* defined by the user, that triangles may not lie across. */
+/* */
+/* A triangle consists of a list of three vertices, a list of three */
+/* adjoining triangles, a list of three adjoining subsegments (when */
+/* segments exist), an arbitrary number of optional user-defined */
+/* floating-point attributes, and an optional area constraint. The latter */
+/* is an upper bound on the permissible area of each triangle in a region, */
+/* used for mesh refinement. */
+/* */
+/* For a triangle on a boundary of the mesh, some or all of the neighboring */
+/* triangles may not be present. For a triangle in the interior of the */
+/* mesh, often no neighboring subsegments are present. Such absent */
+/* triangles and subsegments are never represented by NULL pointers; they */
+/* are represented by two special records: `dummytri', the triangle that */
+/* fills "outer space", and `dummysub', the omnipresent subsegment. */
+/* `dummytri' and `dummysub' are used for several reasons; for instance, */
+/* they can be dereferenced and their contents examined without violating */
+/* protected memory. */
+/* */
+/* However, it is important to understand that a triangle includes other */
+/* information as well. The pointers to adjoining vertices, triangles, and */
+/* subsegments are ordered in a way that indicates their geometric relation */
+/* to each other. Furthermore, each of these pointers contains orientation */
+/* information. Each pointer to an adjoining triangle indicates which face */
+/* of that triangle is contacted. Similarly, each pointer to an adjoining */
+/* subsegment indicates which side of that subsegment is contacted, and how */
+/* the subsegment is oriented relative to the triangle. */
+/* */
+/* The data structure representing a subsegment may be thought to be */
+/* abutting the edge of one or two triangle data structures: either */
+/* sandwiched between two triangles, or resting against one triangle on an */
+/* exterior boundary or hole boundary. */
+/* */
+/* A subsegment consists of a list of four vertices--the vertices of the */
+/* subsegment, and the vertices of the segment it is a part of--a list of */
+/* two adjoining subsegments, and a list of two adjoining triangles. One */
+/* of the two adjoining triangles may not be present (though there should */
+/* always be one), and neighboring subsegments might not be present. */
+/* Subsegments also store a user-defined integer "boundary marker". */
+/* Typically, this integer is used to indicate what boundary conditions are */
+/* to be applied at that location in a finite element simulation. */
+/* */
+/* Like triangles, subsegments maintain information about the relative */
+/* orientation of neighboring objects. */
+/* */
+/* Vertices are relatively simple. A vertex is a list of floating-point */
+/* numbers, starting with the x, and y coordinates, followed by an */
+/* arbitrary number of optional user-defined floating-point attributes, */
+/* followed by an integer boundary marker. During the segment insertion */
+/* phase, there is also a pointer from each vertex to a triangle that may */
+/* contain it. Each pointer is not always correct, but when one is, it */
+/* speeds up segment insertion. These pointers are = vec3ed values once */
+/* at the beginning of the segment insertion phase, and are not used or */
+/* updated except during this phase. Edge flipping during segment */
+/* insertion will render some of them incorrect. Hence, don't rely upon */
+/* them for anything. */
+/* */
+/* Other than the exception mentioned above, vertices have no information */
+/* about what triangles, subfacets, or subsegments they are linked to. */
+/* */
+/*****************************************************************************/
+
+/*****************************************************************************/
+/* */
+/* Handles */
+/* */
+/* The oriented triangle (`otri') and oriented subsegment (`osub') data */
+/* structures defined below do not themselves store any part of the mesh. */
+/* The mesh itself is made of `triangle's, `subseg's, and `vertex's. */
+/* */
+/* Oriented triangles and oriented subsegments will usually be referred to */
+/* as "handles." A handle is essentially a pointer into the mesh; it */
+/* allows you to "hold" one particular part of the mesh. Handles are used */
+/* to specify the regions in which one is traversing and modifying the mesh.*/
+/* A single `triangle' may be held by many handles, or none at all. (The */
+/* latter case is not a memory leak, because the triangle is still */
+/* connected to other triangles in the mesh.) */
+/* */
+/* An `otri' is a handle that holds a triangle. It holds a specific edge */
+/* of the triangle. An `osub' is a handle that holds a subsegment. It */
+/* holds either the left or right side of the subsegment. */
+/* */
+/* Navigation about the mesh is accomplished through a set of mesh */
+/* manipulation primitives, further below. Many of these primitives take */
+/* a handle and produce a new handle that holds the mesh near the first */
+/* handle. Other primitives take two handles and glue the corresponding */
+/* parts of the mesh together. The orientation of the handles is */
+/* important. For instance, when two triangles are glued together by the */
+/* bond() primitive, they are glued at the edges on which the handles lie. */
+/* */
+/* Because vertices have no information about which triangles they are */
+/* attached to, I commonly represent a vertex by use of a handle whose */
+/* origin is the vertex. A single handle can simultaneously represent a */
+/* triangle, an edge, and a vertex. */
+/* */
+/*****************************************************************************/
+
+/* The triangle data structure. Each triangle contains three pointers to */
+/* adjoining triangles, plus three pointers to vertices, plus three */
+/* pointers to subsegments (declared below; these pointers are usually */
+/* `dummysub'). It may or may not also contain user-defined attributes */
+/* and/or a floating-point "area constraint." It may also contain extra */
+/* pointers for nodes, when the user asks for high-order elements. */
+/* Because the size and structure of a `triangle' is not decided until */
+/* runtime, I haven't simply declared the type `triangle' as a struct. */
+
+typedef tREAL **triangle; /* Really: typedef triangle *triangle */
+
+/* An oriented triangle: includes a pointer to a triangle and orientation. */
+/* The orientation denotes an edge of the triangle. Hence, there are */
+/* three possible orientations. By convention, each edge always points */
+/* counterclockwise about the corresponding triangle. */
+
+struct otri {
+ triangle *tri;
+ int orient; /* Ranges from 0 to 2. */
+};
+
+/* The subsegment data structure. Each subsegment contains two pointers to */
+/* adjoining subsegments, plus four pointers to vertices, plus two */
+/* pointers to adjoining triangles, plus one boundary marker, plus one */
+/* segment number. */
+
+typedef tREAL **subseg; /* Really: typedef subseg *subseg */
+
+/* An oriented subsegment: includes a pointer to a subsegment and an */
+/* orientation. The orientation denotes a side of the edge. Hence, there */
+/* are two possible orientations. By convention, the edge is always */
+/* directed so that the "side" denoted is the right side of the edge. */
+
+struct osub {
+ subseg *ss;
+ int ssorient; /* Ranges from 0 to 1. */
+};
+
+/* The vertex data structure. Each vertex is actually an array of tREALs. */
+/* The number of tREALs is unknown until runtime. An integer boundary */
+/* marker, and sometimes a pointer to a triangle, is appended after the */
+/* tREALs. */
+
+typedef tREAL *vertex;
+
+/* A queue used to store encroached subsegments. Each subsegment's vertices */
+/* are stored so that we can check whether a subsegment is still the same. */
+
+struct badsubseg {
+ subseg encsubseg; /* An encroached subsegment. */
+ vertex subsegorg, subsegdest; /* Its two vertices. */
+};
+
+/* A queue used to store bad triangles. The key is the square of the cosine */
+/* of the smallest angle of the triangle. Each triangle's vertices are */
+/* stored so that one can check whether a triangle is still the same. */
+
+struct badtriang {
+ triangle poortri; /* A skinny or too-large triangle. */
+ tREAL key; /* cos^2 of smallest (apical) angle. */
+ vertex triangorg, triangdest, triangapex; /* Its three vertices. */
+ struct badtriang *nexttriang; /* Pointer to next bad triangle. */
+};
+
+/* A stack of triangles flipped during the most recent vertex insertion. */
+/* The stack is used to undo the vertex insertion if the vertex encroaches */
+/* upon a subsegment. */
+
+struct flipstacker {
+ triangle flippedtri; /* A recently flipped triangle. */
+ struct flipstacker *prevflip; /* Previous flip in the stack. */
+};
+
+/* A node in a heap used to store events for the sweepline Delaunay */
+/* algorithm. Nodes do not point directly to their parents or children in */
+/* the heap. Instead, each node knows its position in the heap, and can */
+/* look up its parent and children in a separate array. The `eventptr' */
+/* points either to a `vertex' or to a triangle (in encoded format, so */
+/* that an orientation is included). In the latter case, the origin of */
+/* the oriented triangle is the apex of a "circle event" of the sweepline */
+/* algorithm. To distinguish site events from circle events, all circle */
+/* events are given an invalid (smaller than `xmin') x-coordinate `xkey'. */
+
+struct event {
+ tREAL xkey, ykey; /* Coordinates of the event. */
+ VOID *eventptr; /* Can be a vertex or the location of a circle event. */
+ int heapposition; /* Marks this event's position in the heap. */
+};
+
+/* A node in the splay tree. Each node holds an oriented ghost triangle */
+/* that represents a boundary edge of the growing triangulation. When a */
+/* circle event covers two boundary edges with a triangle, so that they */
+/* are no longer boundary edges, those edges are not immediately deleted */
+/* from the tree; rather, they are lazily deleted when they are next */
+/* encountered. (Since only a random sample of boundary edges are kept */
+/* in the tree, lazy deletion is faster.) `keydest' is used to verify */
+/* that a triangle is still the same as when it entered the splay tree; if */
+/* it has been rotated (due to a circle event), it no longer represents a */
+/* boundary edge and should be deleted. */
+
+struct splaynode {
+ struct otri keyedge; /* Lprev of an edge on the front. */
+ vertex keydest; /* Used to verify that splay node is still live. */
+ struct splaynode *lchild, *rchild; /* Children in splay tree. */
+};
+
+/* A type used to allocate memory. firstblock is the first block of items. */
+/* nowblock is the block from which items are currently being allocated. */
+/* nextitem points to the next slab of free memory for an item. */
+/* deaditemstack is the head of a linked list (stack) of deallocated items */
+/* that can be recycled. unallocateditems is the number of items that */
+/* remain to be allocated from nowblock. */
+/* */
+/* Traversal is the process of walking through the entire list of items, and */
+/* is separate from allocation. Note that a traversal will visit items on */
+/* the "deaditemstack" stack as well as live items. pathblock points to */
+/* the block currently being traversed. pathitem points to the next item */
+/* to be traversed. pathitemsleft is the number of items that remain to */
+/* be traversed in pathblock. */
+/* */
+/* alignbytes determines how new records should be aligned in memory. */
+/* itembytes is the length of a record in bytes (after rounding up). */
+/* itemsperblock is the number of items allocated at once in a single */
+/* block. itemsfirstblock is the number of items in the first block, */
+/* which can vary from the others. items is the number of currently */
+/* allocated items. maxitems is the maximum number of items that have */
+/* been allocated at once; it is the current number of items plus the */
+/* number of records kept on deaditemstack. */
+
+struct memorypool {
+ VOID **firstblock, **nowblock;
+ VOID *nextitem;
+ VOID *deaditemstack;
+ VOID **pathblock;
+ VOID *pathitem;
+ int alignbytes;
+ int itembytes;
+ int itemsperblock;
+ int itemsfirstblock;
+ long items, maxitems;
+ int unallocateditems;
+ int pathitemsleft;
+};
+
+
+/* Global constants. */
+
+tREAL splitter; /* Used to split tREAL factors for exact multiplication. */
+tREAL epsilon; /* Floating-point machine epsilon. */
+tREAL resulterrbound;
+tREAL ccwerrboundA, ccwerrboundB, ccwerrboundC;
+tREAL iccerrboundA, iccerrboundB, iccerrboundC;
+tREAL o3derrboundA, o3derrboundB, o3derrboundC;
+
+/* Random number seed is not constant, but I've made it global anyway. */
+
+unsigned long randomseed; /* Current random number seed. */
+
+
+/* Mesh data structure. Triangle operates on only one mesh, but the mesh */
+/* structure is used (instead of global variables) to allow reentrancy. */
+
+struct mesh {
+
+/* Variables used to allocate memory for triangles, subsegments, vertices, */
+/* viri (triangles being eaten), encroached segments, bad (skinny or too */
+/* large) triangles, and splay tree nodes. */
+
+ struct memorypool triangles;
+ struct memorypool subsegs;
+ struct memorypool vertices;
+ struct memorypool viri;
+ struct memorypool badsubsegs;
+ struct memorypool badtriangles;
+ struct memorypool flipstackers;
+ struct memorypool splaynodes;
+
+/* Variables that maintain the bad triangle queues. The queues are */
+/* ordered from 4095 (highest priority) to 0 (lowest priority). */
+
+ struct badtriang *queuefront[4096];
+ struct badtriang *queuetail[4096];
+ int nextnonemptyq[4096];
+ int firstnonemptyq;
+
+/* Variable that maintains the stack of recently flipped triangles. */
+
+ struct flipstacker *lastflip;
+
+/* Other variables. */
+
+ tREAL xmin, xmax, ymin, ymax; /* x and y bounds. */
+ tREAL xminextreme; /* Nonexistent x value used as a flag in sweepline. */
+ int invertices; /* Number of input vertices. */
+ int inelements; /* Number of input triangles. */
+ int insegments; /* Number of input segments. */
+ int holes; /* Number of input holes. */
+ int regions; /* Number of input regions. */
+ int undeads; /* Number of input vertices that don't appear in the mesh. */
+ long edges; /* Number of output edges. */
+ int mesh_dim; /* Dimension (ought to be 2). */
+ int nextras; /* Number of attributes per vertex. */
+ int eextras; /* Number of attributes per triangle. */
+ long hullsize; /* Number of edges in convex hull. */
+ int steinerleft; /* Number of Steiner points not yet used. */
+ int vertexmarkindex; /* Index to find boundary marker of a vertex. */
+ int vertex2triindex; /* Index to find a triangle adjacent to a vertex. */
+ int highorderindex; /* Index to find extra nodes for high-order elements. */
+ int elemattribindex; /* Index to find attributes of a triangle. */
+ int areaboundindex; /* Index to find area bound of a triangle. */
+ int checksegments; /* Are there segments in the triangulation yet? */
+ int checkquality; /* Has quality triangulation begun yet? */
+ int readnodefile; /* Has a .node file been read? */
+ long samples; /* Number of random samples for point location. */
+
+ long incirclecount; /* Number of incircle tests performed. */
+ long counterclockcount; /* Number of counterclockwise tests performed. */
+ long orient3dcount; /* Number of 3D orientation tests performed. */
+ long hyperbolacount; /* Number of right-of-hyperbola tests performed. */
+ long circumcentercount; /* Number of circumcenter calculations performed. */
+ long circletopcount; /* Number of circle top calculations performed. */
+
+/* Triangular bounding box vertices. */
+
+ vertex infvertex1, infvertex2, infvertex3;
+
+/* Pointer to the `triangle' that occupies all of "outer space." */
+
+ triangle *dummytri;
+ triangle *dummytribase; /* Keep base address so we can free() it later. */
+
+/* Pointer to the omnipresent subsegment. Referenced by any triangle or */
+/* subsegment that isn't really connected to a subsegment at that */
+/* location. */
+
+ subseg *dummysub;
+ subseg *dummysubbase; /* Keep base address so we can free() it later. */
+
+/* Pointer to a recently visited triangle. Improves point location if */
+/* proximate vertices are inserted sequentially. */
+
+ struct otri recenttri;
+
+}; /* End of `struct mesh'. */
+
+
+/* Data structure for command line switches and file names. This structure */
+/* is used (instead of global variables) to allow reentrancy. */
+
+struct behavior {
+
+/* Switches for the triangulator. */
+/* poly: -p switch. refine: -r switch. */
+/* quality: -q switch. */
+/* minangle: minimum angle bound, specified after -q switch. */
+/* goodangle: cosine squared of minangle. */
+/* offconstant: constant used to place off-center Steiner points. */
+/* vararea: -a switch without number. */
+/* fixedarea: -a switch with number. */
+/* maxarea: maximum area bound, specified after -a switch. */
+/* usertest: -u switch. */
+/* regionattrib: -A switch. convex: -c switch. */
+/* weighted: 1 for -w switch, 2 for -W switch. jettison: -j switch */
+/* firstnumber: inverse of -z switch. All items are numbered starting */
+/* from `firstnumber'. */
+/* edgesout: -e switch. voronoi: -v switch. */
+/* neighbors: -n switch. geomview: -g switch. */
+/* nobound: -B switch. nopolywritten: -P switch. */
+/* nonodewritten: -N switch. noelewritten: -E switch. */
+/* noiterationnum: -I switch. noholes: -O switch. */
+/* noexact: -X switch. */
+/* order: element order, specified after -o switch. */
+/* nobisect: count of how often -Y switch is selected. */
+/* steiner: maximum number of Steiner points, specified after -S switch. */
+/* incremental: -i switch. sweepline: -F switch. */
+/* dwyer: inverse of -l switch. */
+/* splitseg: -s switch. */
+/* conformdel: -D switch. docheck: -C switch. */
+/* quiet: -Q switch. verbose: count of how often -V switch is selected. */
+/* usesegments: -p, -r, -q, or -c switch; determines whether segments are */
+/* used at all. */
+/* */
+/* Read the instructions to find out the meaning of these switches. */
+
+ int poly, refine, quality, vararea, fixedarea, usertest;
+ int regionattrib, convex, weighted, jettison;
+ int firstnumber;
+ int edgesout, voronoi, neighbors, geomview;
+ int nobound, nopolywritten, nonodewritten, noelewritten, noiterationnum;
+ int noholes, noexact, conformdel;
+ int incremental, sweepline, dwyer;
+ int splitseg;
+ int docheck;
+ int quiet, verbose;
+ int usesegments;
+ int order;
+ int nobisect;
+ int steiner;
+ tREAL minangle, goodangle, offconstant;
+ tREAL maxarea;
+
+/* Variables for file names. */
+
+#ifndef TRILIBRARY
+ char innodefilename[FILENAMESIZE];
+ char inelefilename[FILENAMESIZE];
+ char inpolyfilename[FILENAMESIZE];
+ char areafilename[FILENAMESIZE];
+ char outnodefilename[FILENAMESIZE];
+ char outelefilename[FILENAMESIZE];
+ char outpolyfilename[FILENAMESIZE];
+ char edgefilename[FILENAMESIZE];
+ char vnodefilename[FILENAMESIZE];
+ char vedgefilename[FILENAMESIZE];
+ char neighborfilename[FILENAMESIZE];
+ char offfilename[FILENAMESIZE];
+#endif /* not TRILIBRARY */
+
+}; /* End of `struct behavior'. */
+
+
+/*****************************************************************************/
+/* */
+/* Mesh manipulation primitives. Each triangle contains three pointers to */
+/* other triangles, with orientations. Each pointer points not to the */
+/* first byte of a triangle, but to one of the first three bytes of a */
+/* triangle. It is necessary to extract both the triangle itself and the */
+/* orientation. To save memory, I keep both pieces of information in one */
+/* pointer. To make this possible, I assume that all triangles are aligned */
+/* to four-byte boundaries. The decode() routine below decodes a pointer, */
+/* extracting an orientation (in the range 0 to 2) and a pointer to the */
+/* beginning of a triangle. The encode() routine compresses a pointer to a */
+/* triangle and an orientation into a single pointer. My assumptions that */
+/* triangles are four-byte-aligned and that the `unsigned long' type is */
+/* long enough to hold a pointer are two of the few kludges in this program.*/
+/* */
+/* Subsegments are manipulated similarly. A pointer to a subsegment */
+/* carries both an address and an orientation in the range 0 to 1. */
+/* */
+/* The other primitives take an oriented triangle or oriented subsegment, */
+/* and return an oriented triangle or oriented subsegment or vertex; or */
+/* they change the connections in the data structure. */
+/* */
+/* Below, triangles and subsegments are denoted by their vertices. The */
+/* triangle abc has origin (org) a, destination (dest) b, and apex (apex) */
+/* c. These vertices occur in counterclockwise order about the triangle. */
+/* The handle abc may simultaneously denote vertex a, edge ab, and triangle */
+/* abc. */
+/* */
+/* Similarly, the subsegment ab has origin (sorg) a and destination (sdest) */
+/* b. If ab is thought to be directed upward (with b directly above a), */
+/* then the handle ab is thought to grasp the right side of ab, and may */
+/* simultaneously denote vertex a and edge ab. */
+/* */
+/* An asterisk (*) denotes a vertex whose identity is unknown. */
+/* */
+/* Given this notation, a partial list of mesh manipulation primitives */
+/* follows. */
+/* */
+/* */
+/* For triangles: */
+/* */
+/* sym: Find the abutting triangle; same edge. */
+/* sym(abc) -> ba* */
+/* */
+/* lnext: Find the next edge (counterclockwise) of a triangle. */
+/* lnext(abc) -> bca */
+/* */
+/* lprev: Find the previous edge (clockwise) of a triangle. */
+/* lprev(abc) -> cab */
+/* */
+/* onext: Find the next edge counterclockwise with the same origin. */
+/* onext(abc) -> ac* */
+/* */
+/* oprev: Find the next edge clockwise with the same origin. */
+/* oprev(abc) -> a*b */
+/* */
+/* dnext: Find the next edge counterclockwise with the same destination. */
+/* dnext(abc) -> *ba */
+/* */
+/* dprev: Find the next edge clockwise with the same destination. */
+/* dprev(abc) -> cb* */
+/* */
+/* rnext: Find the next edge (counterclockwise) of the adjacent triangle. */
+/* rnext(abc) -> *a* */
+/* */
+/* rprev: Find the previous edge (clockwise) of the adjacent triangle. */
+/* rprev(abc) -> b** */
+/* */
+/* org: Origin dest: Destination apex: Apex */
+/* org(abc) -> a dest(abc) -> b apex(abc) -> c */
+/* */
+/* bond: Bond two triangles together at the resepective handles. */
+/* bond(abc, bad) */
+/* */
+/* */
+/* For subsegments: */
+/* */
+/* ssym: Reverse the orientation of a subsegment. */
+/* ssym(ab) -> ba */
+/* */
+/* spivot: Find adjoining subsegment with the same origin. */
+/* spivot(ab) -> a* */
+/* */
+/* snext: Find next subsegment in sequence. */
+/* snext(ab) -> b* */
+/* */
+/* sorg: Origin sdest: Destination */
+/* sorg(ab) -> a sdest(ab) -> b */
+/* */
+/* sbond: Bond two subsegments together at the respective origins. */
+/* sbond(ab, ac) */
+/* */
+/* */
+/* For interacting tetrahedra and subfacets: */
+/* */
+/* tspivot: Find a subsegment abutting a triangle. */
+/* tspivot(abc) -> ba */
+/* */
+/* stpivot: Find a triangle abutting a subsegment. */
+/* stpivot(ab) -> ba* */
+/* */
+/* tsbond: Bond a triangle to a subsegment. */
+/* tsbond(abc, ba) */
+/* */
+/*****************************************************************************/
+
+/********* Mesh manipulation primitives begin here *********/
+/** **/
+/** **/
+
+/* Fast lookup arrays to speed some of the mesh manipulation primitives. */
+
+int plus1mod3[3] = {1, 2, 0};
+int minus1mod3[3] = {2, 0, 1};
+
+/********* Primitives for triangles *********/
+/* */
+/* */
+
+/* decode() converts a pointer to an oriented triangle. The orientation is */
+/* extracted from the two least significant bits of the pointer. */
+
+#define decode(ptr, otri) \
+ (otri).orient = (int) ((unsigned long) (ptr) & (unsigned long) 3l); \
+ (otri).tri = (triangle *) \
+ ((unsigned long) (ptr) ^ (unsigned long) (otri).orient)
+
+/* encode() compresses an oriented triangle into a single pointer. It */
+/* relies on the assumption that all triangles are aligned to four-byte */
+/* boundaries, so the two least significant bits of (otri).tri are zero. */
+
+#define encode(otri) \
+ (triangle) ((unsigned long) (otri).tri | (unsigned long) (otri).orient)
+
+/* The following handle manipulation primitives are all described by Guibas */
+/* and Stolfi. However, Guibas and Stolfi use an edge-based data */
+/* structure, whereas I use a triangle-based data structure. */
+
+/* sym() finds the abutting triangle, on the same edge. Note that the edge */
+/* direction is necessarily reversed, because the handle specified by an */
+/* oriented triangle is directed counterclockwise around the triangle. */
+
+#define sym(otri1, otri2) \
+ ptr = (otri1).tri[(otri1).orient]; \
+ decode(ptr, otri2);
+
+#define symself(otri) \
+ ptr = (otri).tri[(otri).orient]; \
+ decode(ptr, otri);
+
+/* lnext() finds the next edge (counterclockwise) of a triangle. */
+
+#define lnext(otri1, otri2) \
+ (otri2).tri = (otri1).tri; \
+ (otri2).orient = plus1mod3[(otri1).orient]
+
+#define lnextself(otri) \
+ (otri).orient = plus1mod3[(otri).orient]
+
+/* lprev() finds the previous edge (clockwise) of a triangle. */
+
+#define lprev(otri1, otri2) \
+ (otri2).tri = (otri1).tri; \
+ (otri2).orient = minus1mod3[(otri1).orient]
+
+#define lprevself(otri) \
+ (otri).orient = minus1mod3[(otri).orient]
+
+/* onext() spins counterclockwise around a vertex; that is, it finds the */
+/* next edge with the same origin in the counterclockwise direction. This */
+/* edge is part of a different triangle. */
+
+#define onext(otri1, otri2) \
+ lprev(otri1, otri2); \
+ symself(otri2);
+
+#define onextself(otri) \
+ lprevself(otri); \
+ symself(otri);
+
+/* oprev() spins clockwise around a vertex; that is, it finds the next edge */
+/* with the same origin in the clockwise direction. This edge is part of */
+/* a different triangle. */
+
+#define oprev(otri1, otri2) \
+ sym(otri1, otri2); \
+ lnextself(otri2);
+
+#define oprevself(otri) \
+ symself(otri); \
+ lnextself(otri);
+
+/* dnext() spins counterclockwise around a vertex; that is, it finds the */
+/* next edge with the same destination in the counterclockwise direction. */
+/* This edge is part of a different triangle. */
+
+#define dnext(otri1, otri2) \
+ sym(otri1, otri2); \
+ lprevself(otri2);
+
+#define dnextself(otri) \
+ symself(otri); \
+ lprevself(otri);
+
+/* dprev() spins clockwise around a vertex; that is, it finds the next edge */
+/* with the same destination in the clockwise direction. This edge is */
+/* part of a different triangle. */
+
+#define dprev(otri1, otri2) \
+ lnext(otri1, otri2); \
+ symself(otri2);
+
+#define dprevself(otri) \
+ lnextself(otri); \
+ symself(otri);
+
+/* rnext() moves one edge counterclockwise about the adjacent triangle. */
+/* (It's best understood by reading Guibas and Stolfi. It involves */
+/* changing triangles twice.) */
+
+#define rnext(otri1, otri2) \
+ sym(otri1, otri2); \
+ lnextself(otri2); \
+ symself(otri2);
+
+#define rnextself(otri) \
+ symself(otri); \
+ lnextself(otri); \
+ symself(otri);
+
+/* rprev() moves one edge clockwise about the adjacent triangle. */
+/* (It's best understood by reading Guibas and Stolfi. It involves */
+/* changing triangles twice.) */
+
+#define rprev(otri1, otri2) \
+ sym(otri1, otri2); \
+ lprevself(otri2); \
+ symself(otri2);
+
+#define rprevself(otri) \
+ symself(otri); \
+ lprevself(otri); \
+ symself(otri);
+
+/* These primitives determine or set the origin, destination, or apex of a */
+/* triangle. */
+
+#define org(otri, vertexptr) \
+ vertexptr = (vertex) (otri).tri[plus1mod3[(otri).orient] + 3]
+
+#define dest(otri, vertexptr) \
+ vertexptr = (vertex) (otri).tri[minus1mod3[(otri).orient] + 3]
+
+#define apex(otri, vertexptr) \
+ vertexptr = (vertex) (otri).tri[(otri).orient + 3]
+
+#define setorg(otri, vertexptr) \
+ (otri).tri[plus1mod3[(otri).orient] + 3] = (triangle) vertexptr
+
+#define setdest(otri, vertexptr) \
+ (otri).tri[minus1mod3[(otri).orient] + 3] = (triangle) vertexptr
+
+#define setapex(otri, vertexptr) \
+ (otri).tri[(otri).orient + 3] = (triangle) vertexptr
+
+/* Bond two triangles together. */
+
+#define bond(otri1, otri2) \
+ (otri1).tri[(otri1).orient] = encode(otri2); \
+ (otri2).tri[(otri2).orient] = encode(otri1)
+
+/* Dissolve a bond (from one side). Note that the other triangle will still */
+/* think it's connected to this triangle. Usually, however, the other */
+/* triangle is being deleted entirely, or bonded to another triangle, so */
+/* it doesn't matter. */
+
+#define dissolve(otri) \
+ (otri).tri[(otri).orient] = (triangle) m->dummytri
+
+/* Copy an oriented triangle. */
+
+#define otricopy(otri1, otri2) \
+ (otri2).tri = (otri1).tri; \
+ (otri2).orient = (otri1).orient
+
+/* Test for equality of oriented triangles. */
+
+#define otriequal(otri1, otri2) \
+ (((otri1).tri == (otri2).tri) && \
+ ((otri1).orient == (otri2).orient))
+
+/* Primitives to infect or cure a triangle with the virus. These rely on */
+/* the assumption that all subsegments are aligned to four-byte boundaries.*/
+
+#define infect(otri) \
+ (otri).tri[6] = (triangle) \
+ ((unsigned long) (otri).tri[6] | (unsigned long) 2l)
+
+#define uninfect(otri) \
+ (otri).tri[6] = (triangle) \
+ ((unsigned long) (otri).tri[6] & ~ (unsigned long) 2l)
+
+/* Test a triangle for viral infection. */
+
+#define infected(otri) \
+ (((unsigned long) (otri).tri[6] & (unsigned long) 2l) != 0l)
+
+/* Check or set a triangle's attributes. */
+
+#define elemattribute(otri, attnum) \
+ ((tREAL *) (otri).tri)[m->elemattribindex + (attnum)]
+
+#define setelemattribute(otri, attnum, value) \
+ ((tREAL *) (otri).tri)[m->elemattribindex + (attnum)] = value
+
+/* Check or set a triangle's maximum area bound. */
+
+#define areabound(otri) ((tREAL *) (otri).tri)[m->areaboundindex]
+
+#define setareabound(otri, value) \
+ ((tREAL *) (otri).tri)[m->areaboundindex] = value
+
+/* Check or set a triangle's deallocation. Its second pointer is set to */
+/* NULL to indicate that it is not allocated. (Its first pointer is used */
+/* for the stack of dead items.) Its fourth pointer (its first vertex) */
+/* is set to NULL in case a `badtriang' structure points to it. */
+
+#define deadtri(tria) ((tria)[1] == (triangle) NULL)
+
+#define killtri(tria) \
+ (tria)[1] = (triangle) NULL; \
+ (tria)[3] = (triangle) NULL
+
+/********* Primitives for subsegments *********/
+/* */
+/* */
+
+/* sdecode() converts a pointer to an oriented subsegment. The orientation */
+/* is extracted from the least significant bit of the pointer. The two */
+/* least significant bits (one for orientation, one for viral infection) */
+/* are masked out to produce the real pointer. */
+
+#define sdecode(sptr, osub) \
+ (osub).ssorient = (int) ((unsigned long) (sptr) & (unsigned long) 1l); \
+ (osub).ss = (subseg *) \
+ ((unsigned long) (sptr) & ~ (unsigned long) 3l)
+
+/* sencode() compresses an oriented subsegment into a single pointer. It */
+/* relies on the assumption that all subsegments are aligned to two-byte */
+/* boundaries, so the least significant bit of (osub).ss is zero. */
+
+#define sencode(osub) \
+ (subseg) ((unsigned long) (osub).ss | (unsigned long) (osub).ssorient)
+
+/* ssym() toggles the orientation of a subsegment. */
+
+#define ssym(osub1, osub2) \
+ (osub2).ss = (osub1).ss; \
+ (osub2).ssorient = 1 - (osub1).ssorient
+
+#define ssymself(osub) \
+ (osub).ssorient = 1 - (osub).ssorient
+
+/* spivot() finds the other subsegment (from the same segment) that shares */
+/* the same origin. */
+
+#define spivot(osub1, osub2) \
+ sptr = (osub1).ss[(osub1).ssorient]; \
+ sdecode(sptr, osub2)
+
+#define spivotself(osub) \
+ sptr = (osub).ss[(osub).ssorient]; \
+ sdecode(sptr, osub)
+
+/* snext() finds the next subsegment (from the same segment) in sequence; */
+/* one whose origin is the input subsegment's destination. */
+
+#define snext(osub1, osub2) \
+ sptr = (osub1).ss[1 - (osub1).ssorient]; \
+ sdecode(sptr, osub2)
+
+#define snextself(osub) \
+ sptr = (osub).ss[1 - (osub).ssorient]; \
+ sdecode(sptr, osub)
+
+/* These primitives determine or set the origin or destination of a */
+/* subsegment or the segment that includes it. */
+
+#define sorg(osub, vertexptr) \
+ vertexptr = (vertex) (osub).ss[2 + (osub).ssorient]
+
+#define sdest(osub, vertexptr) \
+ vertexptr = (vertex) (osub).ss[3 - (osub).ssorient]
+
+#define setsorg(osub, vertexptr) \
+ (osub).ss[2 + (osub).ssorient] = (subseg) vertexptr
+
+#define setsdest(osub, vertexptr) \
+ (osub).ss[3 - (osub).ssorient] = (subseg) vertexptr
+
+#define segorg(osub, vertexptr) \
+ vertexptr = (vertex) (osub).ss[4 + (osub).ssorient]
+
+#define segdest(osub, vertexptr) \
+ vertexptr = (vertex) (osub).ss[5 - (osub).ssorient]
+
+#define setsegorg(osub, vertexptr) \
+ (osub).ss[4 + (osub).ssorient] = (subseg) vertexptr
+
+#define setsegdest(osub, vertexptr) \
+ (osub).ss[5 - (osub).ssorient] = (subseg) vertexptr
+
+/* These primitives read or set a boundary marker. Boundary markers are */
+/* used to hold user-defined tags for setting boundary conditions in */
+/* finite element solvers. */
+
+#define mark(osub) (* (int *) ((osub).ss + 8))
+
+#define setmark(osub, value) \
+ * (int *) ((osub).ss + 8) = value
+
+/* Bond two subsegments together. */
+
+#define sbond(osub1, osub2) \
+ (osub1).ss[(osub1).ssorient] = sencode(osub2); \
+ (osub2).ss[(osub2).ssorient] = sencode(osub1)
+
+/* Dissolve a subsegment bond (from one side). Note that the other */
+/* subsegment will still think it's connected to this subsegment. */
+
+#define sdissolve(osub) \
+ (osub).ss[(osub).ssorient] = (subseg) m->dummysub
+
+/* Copy a subsegment. */
+
+#define subsegcopy(osub1, osub2) \
+ (osub2).ss = (osub1).ss; \
+ (osub2).ssorient = (osub1).ssorient
+
+/* Test for equality of subsegments. */
+
+#define subsegequal(osub1, osub2) \
+ (((osub1).ss == (osub2).ss) && \
+ ((osub1).ssorient == (osub2).ssorient))
+
+/* Check or set a subsegment's deallocation. Its second pointer is set to */
+/* NULL to indicate that it is not allocated. (Its first pointer is used */
+/* for the stack of dead items.) Its third pointer (its first vertex) */
+/* is set to NULL in case a `badsubseg' structure points to it. */
+
+#define deadsubseg(sub) ((sub)[1] == (subseg) NULL)
+
+#define killsubseg(sub) \
+ (sub)[1] = (subseg) NULL; \
+ (sub)[2] = (subseg) NULL
+
+/********* Primitives for interacting triangles and subsegments *********/
+/* */
+/* */
+
+/* tspivot() finds a subsegment abutting a triangle. */
+
+#define tspivot(otri, osub) \
+ sptr = (subseg) (otri).tri[6 + (otri).orient]; \
+ sdecode(sptr, osub)
+
+/* stpivot() finds a triangle abutting a subsegment. It requires that the */
+/* variable `ptr' of type `triangle' be defined. */
+
+#define stpivot(osub, otri) \
+ ptr = (triangle) (osub).ss[6 + (osub).ssorient]; \
+ decode(ptr, otri)
+
+/* Bond a triangle to a subsegment. */
+
+#define tsbond(otri, osub) \
+ (otri).tri[6 + (otri).orient] = (triangle) sencode(osub); \
+ (osub).ss[6 + (osub).ssorient] = (subseg) encode(otri)
+
+/* Dissolve a bond (from the triangle side). */
+
+#define tsdissolve(otri) \
+ (otri).tri[6 + (otri).orient] = (triangle) m->dummysub
+
+/* Dissolve a bond (from the subsegment side). */
+
+#define stdissolve(osub) \
+ (osub).ss[6 + (osub).ssorient] = (subseg) m->dummytri
+
+/********* Primitives for vertices *********/
+/* */
+/* */
+
+#define vertexmark(vx) ((int *) (vx))[m->vertexmarkindex]
+
+#define setvertexmark(vx, value) \
+ ((int *) (vx))[m->vertexmarkindex] = value
+
+#define vertextype(vx) ((int *) (vx))[m->vertexmarkindex + 1]
+
+#define setvertextype(vx, value) \
+ ((int *) (vx))[m->vertexmarkindex + 1] = value
+
+#define vertex2tri(vx) ((triangle *) (vx))[m->vertex2triindex]
+
+#define setvertex2tri(vx, value) \
+ ((triangle *) (vx))[m->vertex2triindex] = value
+
+/** **/
+/** **/
+/********* Mesh manipulation primitives end here *********/
+
+/********* User-defined triangle evaluation routine begins here *********/
+/** **/
+/** **/
+
+/*****************************************************************************/
+/* */
+/* triunsuitable() Determine if a triangle is unsuitable, and thus must */
+/* be further refined. */
+/* */
+/* You may write your own procedure that decides whether or not a selected */
+/* triangle is too big (and needs to be refined). There are two ways to do */
+/* this. */
+/* */
+/* (1) Modify the procedure `triunsuitable' below, then recompile */
+/* Triangle. */
+/* */
+/* (2) Define the symbol EXTERNAL_TEST (either by adding the definition */
+/* to this file, or by using the appropriate compiler switch). This way, */
+/* you can compile triangle.c separately from your test. Write your own */
+/* `triunsuitable' procedure in a separate C file (using the same prototype */
+/* as below). Compile it and link the object code with triangle.o. */
+/* */
+/* This procedure returns 1 if the triangle is too large and should be */
+/* refined; 0 otherwise. */
+/* */
+/*****************************************************************************/
+
+#ifdef EXTERNAL_TEST
+
+int triunsuitable();
+
+#else /* not EXTERNAL_TEST */
+
+#ifdef ANSI_DECLARATORS
+int triunsuitable(vertex triorg, vertex tridest, vertex triapex, tREAL area)
+#else /* not ANSI_DECLARATORS */
+int triunsuitable(triorg, tridest, triapex, area)
+vertex triorg; /* The triangle's origin vertex. */
+vertex tridest; /* The triangle's destination vertex. */
+vertex triapex; /* The triangle's apex vertex. */
+tREAL area; /* The area of the triangle. */
+#endif /* not ANSI_DECLARATORS */
+
+{
+ tREAL dxoa, dxda, dxod;
+ tREAL dyoa, dyda, dyod;
+ tREAL oalen, dalen, odlen;
+ tREAL maxlen;
+
+ dxoa = triorg[0] - triapex[0];
+ dyoa = triorg[1] - triapex[1];
+ dxda = tridest[0] - triapex[0];
+ dyda = tridest[1] - triapex[1];
+ dxod = triorg[0] - tridest[0];
+ dyod = triorg[1] - tridest[1];
+ /* Find the squares of the lengths of the triangle's three edges. */
+ oalen = dxoa * dxoa + dyoa * dyoa;
+ dalen = dxda * dxda + dyda * dyda;
+ odlen = dxod * dxod + dyod * dyod;
+ /* Find the square of the length of the longest edge. */
+ maxlen = (dalen > oalen) ? dalen : oalen;
+ maxlen = (odlen > maxlen) ? odlen : maxlen;
+
+ if (maxlen > 0.05 * (triorg[0] * triorg[0] + triorg[1] * triorg[1]) + 0.02f) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+#endif /* not EXTERNAL_TEST */
+
+/** **/
+/** **/
+/********* User-defined triangle evaluation routine ends here *********/
+
+/********* Memory allocation and program exit wrappers begin here *********/
+/** **/
+/** **/
+
+#ifdef ANSI_DECLARATORS
+void triexit(int status)
+#else /* not ANSI_DECLARATORS */
+void triexit(status)
+int status;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ exit(status);
+}
+
+#ifdef ANSI_DECLARATORS
+VOID *trimalloc(int size)
+#else /* not ANSI_DECLARATORS */
+VOID *trimalloc(size)
+int size;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ VOID *memptr;
+
+ memptr = (VOID *) malloc((unsigned int) size);
+ if (memptr == (VOID *) NULL) {
+ printf("Error: Out of memory.\n");
+ triexit(1);
+ }
+ return(memptr);
+}
+
+#ifdef ANSI_DECLARATORS
+void trifree(void *memptr)
+#else /* not ANSI_DECLARATORS */
+void trifree(memptr)
+void *memptr;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ free(memptr);
+}
+
+/** **/
+/** **/
+/********* Memory allocation and program exit wrappers end here *********/
+
+/********* User interaction routines begin here *********/
+/** **/
+/** **/
+
+/*****************************************************************************/
+/* */
+/* syntax() Print list of command line switches. */
+/* */
+/*****************************************************************************/
+
+#ifndef TRILIBRARY
+
+void syntax()
+{
+#ifdef CDT_ONLY
+#ifdef REDUCED
+ printf("triangle [-pAcjevngBPNEIOXzo_lQVh] input_file\n");
+#else /* not REDUCED */
+ printf("triangle [-pAcjevngBPNEIOXzo_iFlCQVh] input_file\n");
+#endif /* not REDUCED */
+#else /* not CDT_ONLY */
+#ifdef REDUCED
+ printf("triangle [-prq__a__uAcDjevngBPNEIOXzo_YS__lQVh] input_file\n");
+#else /* not REDUCED */
+ printf("triangle [-prq__a__uAcDjevngBPNEIOXzo_YS__iFlsCQVh] input_file\n");
+#endif /* not REDUCED */
+#endif /* not CDT_ONLY */
+
+ printf(" -p Triangulates a Planar Straight Line Graph (.poly file).\n");
+#ifndef CDT_ONLY
+ printf(" -r Refines a previously generated mesh.\n");
+ printf(
+ " -q Quality mesh generation. A minimum angle may be specified.\n");
+ printf(" -a Applies a maximum triangle area constraint.\n");
+ printf(" -u Applies a user-defined triangle constraint.\n");
+#endif /* not CDT_ONLY */
+ printf(
+ " -A Applies attributes to identify triangles in certain regions.\n");
+ printf(" -c Encloses the convex hull with segments.\n");
+#ifndef CDT_ONLY
+ printf(" -D Conforming Delaunay: all triangles are truly Delaunay.\n");
+#endif /* not CDT_ONLY */
+/*
+ printf(" -w Weighted Delaunay triangulation.\n");
+ printf(" -W Regular triangulation (lower hull of a height field).\n");
+*/
+ printf(" -j Jettison unused vertices from output .node file.\n");
+ printf(" -e Generates an edge list.\n");
+ printf(" -v Generates a Voronoi diagram.\n");
+ printf(" -n Generates a list of triangle neighbors.\n");
+ printf(" -g Generates an .off file for Geomview.\n");
+ printf(" -B Suppresses output of boundary information.\n");
+ printf(" -P Suppresses output of .poly file.\n");
+ printf(" -N Suppresses output of .node file.\n");
+ printf(" -E Suppresses output of .ele file.\n");
+ printf(" -I Suppresses mesh iteration numbers.\n");
+ printf(" -O Ignores holes in .poly file.\n");
+ printf(" -X Suppresses use of exact arithmetic.\n");
+ printf(" -z Numbers all items starting from zero (rather than one).\n");
+ printf(" -o2 Generates second-order subparametric elements.\n");
+#ifndef CDT_ONLY
+ printf(" -Y Suppresses boundary segment splitting.\n");
+ printf(" -S Specifies maximum number of added Steiner points.\n");
+#endif /* not CDT_ONLY */
+#ifndef REDUCED
+ printf(" -i Uses incremental method, rather than divide-and-conquer.\n");
+ printf(" -F Uses Fortune's sweepline algorithm, rather than d-and-c.\n");
+#endif /* not REDUCED */
+ printf(" -l Uses vertical cuts only, rather than alternating cuts.\n");
+#ifndef REDUCED
+#ifndef CDT_ONLY
+ printf(
+ " -s Force segments into mesh by splitting (instead of using CDT).\n");
+#endif /* not CDT_ONLY */
+ printf(" -C Check consistency of final mesh.\n");
+#endif /* not REDUCED */
+ printf(" -Q Quiet: No terminal output except errors.\n");
+ printf(" -V Verbose: Detailed information on what I'm doing.\n");
+ printf(" -h Help: Detailed instructions for Triangle.\n");
+ triexit(0);
+}
+
+#endif /* not TRILIBRARY */
+
+/*****************************************************************************/
+/* */
+/* info() Print out complete instructions. */
+/* */
+/*****************************************************************************/
+
+#ifndef TRILIBRARY
+
+void info()
+{
+ printf("Triangle\n");
+ printf(
+"A Two-Dimensional Quality Mesh Generator and Delaunay Triangulator.\n");
+ printf("Version 1.6\n\n");
+ printf(
+"Copyright 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk\n");
+ printf("2360 Woolsey #H / Berkeley, California 94705-1927\n");
+ printf("Bugs/comments to jrs@cs.berkeley.edu\n");
+ printf(
+"Created as part of the Quake project (tools for earthquake simulation).\n");
+ printf(
+"Supported in part by NSF Grant CMS-9318163 and an NSERC 1967 Scholarship.\n");
+ printf("There is no warranty whatsoever. Use at your own risk.\n");
+#ifdef SINGLE
+ printf("This executable is compiled for single precision arithmetic.\n\n\n");
+#else /* not SINGLE */
+ printf("This executable is compiled for double precision arithmetic.\n\n\n");
+#endif /* not SINGLE */
+ printf(
+"Triangle generates exact Delaunay triangulations, constrained Delaunay\n");
+ printf(
+"triangulations, conforming Delaunay triangulations, Voronoi diagrams, and\n");
+ printf(
+"high-quality triangular meshes. The latter can be generated with no small\n"
+);
+ printf(
+"or large angles, and are thus suitable for finite element analysis. If no\n"
+);
+ printf(
+"command line switch is specified, your .node input file is read, and the\n");
+ printf(
+"Delaunay triangulation is returned in .node and .ele output files. The\n");
+ printf("command syntax is:\n\n");
+ printf("triangle [-prq__a__uAcDjevngBPNEIOXzo_YS__iFlsCQVh] input_file\n\n");
+ printf(
+"Underscores indicate that numbers may optionally follow certain switches.\n");
+ printf(
+"Do not leave any space between a switch and its numeric parameter.\n");
+ printf(
+"input_file must be a file with extension .node, or extension .poly if the\n");
+ printf(
+"-p switch is used. If -r is used, you must supply .node and .ele files,\n");
+ printf(
+"and possibly a .poly file and an .area file as well. The formats of these\n"
+);
+ printf("files are described below.\n\n");
+ printf("Command Line Switches:\n\n");
+ printf(
+" -p Reads a Planar Straight Line Graph (.poly file), which can specify\n"
+);
+ printf(
+" vertices, segments, holes, regional attributes, and regional area\n");
+ printf(
+" constraints. Generates a constrained Delaunay triangulation (CDT)\n"
+);
+ printf(
+" fitting the input; or, if -s, -q, -a, or -u is used, a conforming\n");
+ printf(
+" constrained Delaunay triangulation (CCDT). If you want a truly\n");
+ printf(
+" Delaunay (not just constrained Delaunay) triangulation, use -D as\n");
+ printf(
+" well. When -p is not used, Triangle reads a .node file by default.\n"
+);
+ printf(
+" -r Refines a previously generated mesh. The mesh is read from a .node\n"
+);
+ printf(
+" file and an .ele file. If -p is also used, a .poly file is read\n");
+ printf(
+" and used to constrain segments in the mesh. If -a is also used\n");
+ printf(
+" (with no number following), an .area file is read and used to\n");
+ printf(
+" impose area constraints on the mesh. Further details on refinement\n"
+);
+ printf(" appear below.\n");
+ printf(
+" -q Quality mesh generation by Delaunay refinement (a hybrid of Paul\n");
+ printf(
+" Chew's and Jim Ruppert's algorithms). Adds vertices to the mesh to\n"
+);
+ printf(
+" ensure that all angles are between 20 and 140 degrees. An\n");
+ printf(
+" alternative bound on the minimum angle, replacing 20 degrees, may\n");
+ printf(
+" be specified after the `q'. The specified angle may include a\n");
+ printf(
+" decimal point, but not exponential notation. Note that a bound of\n"
+);
+ printf(
+" theta degrees on the smallest angle also implies a bound of\n");
+ printf(
+" (180 - 2 theta) on the largest angle. If the minimum angle is 28.6\n"
+);
+ printf(
+" degrees or smaller, Triangle is mathematically guaranteed to\n");
+ printf(
+" terminate (assuming infinite precision arithmetic--Triangle may\n");
+ printf(
+" fail to terminate if you run out of precision). In practice,\n");
+ printf(
+" Triangle often succeeds for minimum angles up to 34 degrees. For\n");
+ printf(
+" some meshes, however, you might need to reduce the minimum angle to\n"
+);
+ printf(
+" avoid problems associated with insufficient floating-point\n");
+ printf(" precision.\n");
+ printf(
+" -a Imposes a maximum triangle area. If a number follows the `a', no\n");
+ printf(
+" triangle is generated whose area is larger than that number. If no\n"
+);
+ printf(
+" number is specified, an .area file (if -r is used) or .poly file\n");
+ printf(
+" (if -r is not used) specifies a set of maximum area constraints.\n");
+ printf(
+" An .area file contains a separate area constraint for each\n");
+ printf(
+" triangle, and is useful for refining a finite element mesh based on\n"
+);
+ printf(
+" a posteriori error estimates. A .poly file can optionally contain\n"
+);
+ printf(
+" an area constraint for each segment-bounded region, thereby\n");
+ printf(
+" controlling triangle densities in a first triangulation of a PSLG.\n"
+);
+ printf(
+" You can impose both a fixed area constraint and a varying area\n");
+ printf(
+" constraint by invoking the -a switch twice, once with and once\n");
+ printf(
+" without a number following. Each area specified may include a\n");
+ printf(" decimal point.\n");
+ printf(
+" -u Imposes a user-defined constraint on triangle size. There are two\n"
+);
+ printf(
+" ways to use this feature. One is to edit the triunsuitable()\n");
+ printf(
+" procedure in triangle.c to encode any constraint you like, then\n");
+ printf(
+" recompile Triangle. The other is to compile triangle.c with the\n");
+ printf(
+" EXTERNAL_TEST symbol set (compiler switch -DEXTERNAL_TEST), then\n");
+ printf(
+" link Triangle with a separate object file that implements\n");
+ printf(
+" triunsuitable(). In either case, the -u switch causes the user-\n");
+ printf(" defined test to be applied to every triangle.\n");
+ printf(
+" -A Assigns an additional floating-point attribute to each triangle\n");
+ printf(
+" that identifies what segment-bounded region each triangle belongs\n");
+ printf(
+" to. Attributes are = vec3ed to regions by the .poly file. If a\n");
+ printf(
+" region is not explicitly marked by the .poly file, triangles in\n");
+ printf(
+" that region are = vec3ed an attribute of zero. The -A switch has\n");
+ printf(
+" an effect only when the -p switch is used and the -r switch is not.\n"
+);
+ printf(
+" -c Creates segments on the convex hull of the triangulation. If you\n");
+ printf(
+" are triangulating a vertex set, this switch causes a .poly file to\n"
+);
+ printf(
+" be written, containing all edges of the convex hull. If you are\n");
+ printf(
+" triangulating a PSLG, this switch specifies that the whole convex\n");
+ printf(
+" hull of the PSLG should be triangulated, regardless of what\n");
+ printf(
+" segments the PSLG has. If you do not use this switch when\n");
+ printf(
+" triangulating a PSLG, Triangle assumes that you have identified the\n"
+);
+ printf(
+" region to be triangulated by surrounding it with segments of the\n");
+ printf(
+" input PSLG. Beware: if you are not careful, this switch can cause\n"
+);
+ printf(
+" the introduction of an extremely thin angle between a PSLG segment\n"
+);
+ printf(
+" and a convex hull segment, which can cause overrefinement (and\n");
+ printf(
+" possibly failure if Triangle runs out of precision). If you are\n");
+ printf(
+" refining a mesh, the -c switch works differently: it causes a\n");
+ printf(
+" .poly file to be written containing the boundary edges of the mesh\n"
+);
+ printf(" (useful if no .poly file was read).\n");
+ printf(
+" -D Conforming Delaunay triangulation: use this switch if you want to\n"
+);
+ printf(
+" ensure that all the triangles in the mesh are Delaunay, and not\n");
+ printf(
+" merely constrained Delaunay; or if you want to ensure that all the\n"
+);
+ printf(
+" Voronoi vertices lie within the triangulation. (Some finite volume\n"
+);
+ printf(
+" methods have this requirement.) This switch invokes Ruppert's\n");
+ printf(
+" original algorithm, which splits every subsegment whose diametral\n");
+ printf(
+" circle is encroached. It usually increases the number of vertices\n"
+);
+ printf(" and triangles.\n");
+ printf(
+" -j Jettisons vertices that are not part of the final triangulation\n");
+ printf(
+" from the output .node file. By default, Triangle copies all\n");
+ printf(
+" vertices in the input .node file to the output .node file, in the\n");
+ printf(
+" same order, so their indices do not change. The -j switch prevents\n"
+);
+ printf(
+" duplicated input vertices, or vertices `eaten' by holes, from\n");
+ printf(
+" appearing in the output .node file. Thus, if two input vertices\n");
+ printf(
+" have exactly the same coordinates, only the first appears in the\n");
+ printf(
+" output. If any vertices are jettisoned, the vertex numbering in\n");
+ printf(
+" the output .node file differs from that of the input .node file.\n");
+ printf(
+" -e Outputs (to an .edge file) a list of edges of the triangulation.\n");
+ printf(
+" -v Outputs the Voronoi diagram associated with the triangulation.\n");
+ printf(
+" Does not attempt to detect degeneracies, so some Voronoi vertices\n");
+ printf(
+" may be duplicated. See the discussion of Voronoi diagrams below.\n");
+ printf(
+" -n Outputs (to a .neigh file) a list of triangles neighboring each\n");
+ printf(" triangle.\n");
+ printf(
+" -g Outputs the mesh to an Object File Format (.off) file, suitable for\n"
+);
+ printf(" viewing with the Geometry Center's Geomview package.\n");
+ printf(
+" -B No boundary markers in the output .node, .poly, and .edge output\n");
+ printf(
+" files. See the detailed discussion of boundary markers below.\n");
+ printf(
+" -P No output .poly file. Saves disk space, but you lose the ability\n");
+ printf(
+" to maintain constraining segments on later refinements of the mesh.\n"
+);
+ printf(" -N No output .node file.\n");
+ printf(" -E No output .ele file.\n");
+ printf(
+" -I No iteration numbers. Suppresses the output of .node and .poly\n");
+ printf(
+" files, so your input files won't be overwritten. (If your input is\n"
+);
+ printf(
+" a .poly file only, a .node file is written.) Cannot be used with\n");
+ printf(
+" the -r switch, because that would overwrite your input .ele file.\n");
+ printf(
+" Shouldn't be used with the -q, -a, -u, or -s switch if you are\n");
+ printf(
+" using a .node file for input, because no .node file is written, so\n"
+);
+ printf(" there is no record of any added Steiner points.\n");
+ printf(" -O No holes. Ignores the holes in the .poly file.\n");
+ printf(
+" -X No exact arithmetic. Normally, Triangle uses exact floating-point\n"
+);
+ printf(
+" arithmetic for certain tests if it thinks the inexact tests are not\n"
+);
+ printf(
+" accurate enough. Exact arithmetic ensures the robustness of the\n");
+ printf(
+" triangulation algorithms, despite floating-point roundoff error.\n");
+ printf(
+" Disabling exact arithmetic with the -X switch causes a small\n");
+ printf(
+" improvement in speed and creates the possibility that Triangle will\n"
+);
+ printf(" fail to produce a valid mesh. Not recommended.\n");
+ printf(
+" -z Numbers all items starting from zero (rather than one). Note that\n"
+);
+ printf(
+" this switch is normally overridden by the value used to number the\n"
+);
+ printf(
+" first vertex of the input .node or .poly file. However, this\n");
+ printf(
+" switch is useful when calling Triangle from another program.\n");
+ printf(
+" -o2 Generates second-order subparametric elements with six nodes each.\n"
+);
+ printf(
+" -Y No new vertices on the boundary. This switch is useful when the\n");
+ printf(
+" mesh boundary must be preserved so that it conforms to some\n");
+ printf(
+" adjacent mesh. Be forewarned that you will probably sacrifice much\n"
+);
+ printf(
+" of the quality of the mesh; Triangle will try, but the resulting\n");
+ printf(
+" mesh may contain poorly shaped triangles. Works well if all the\n");
+ printf(
+" boundary vertices are closely spaced. Specify this switch twice\n");
+ printf(
+" (`-YY') to prevent all segment splitting, including internal\n");
+ printf(" boundaries.\n");
+ printf(
+" -S Specifies the maximum number of Steiner points (vertices that are\n");
+ printf(
+" not in the input, but are added to meet the constraints on minimum\n"
+);
+ printf(
+" angle and maximum area). The default is to allow an unlimited\n");
+ printf(
+" number. If you specify this switch with no number after it,\n");
+ printf(
+" the limit is set to zero. Triangle always adds vertices at segment\n"
+);
+ printf(
+" intersections, even if it needs to use more vertices than the limit\n"
+);
+ printf(
+" you set. When Triangle inserts segments by splitting (-s), it\n");
+ printf(
+" always adds enough vertices to ensure that all the segments of the\n"
+);
+ printf(" PLSG are recovered, ignoring the limit if necessary.\n");
+ printf(
+" -i Uses an incremental rather than a divide-and-conquer algorithm to\n");
+ printf(
+" construct a Delaunay triangulation. Try it if the divide-and-\n");
+ printf(" conquer algorithm fails.\n");
+ printf(
+" -F Uses Steven Fortune's sweepline algorithm to construct a Delaunay\n");
+ printf(
+" triangulation. Warning: does not use exact arithmetic for all\n");
+ printf(" calculations. An exact result is not guaranteed.\n");
+ printf(
+" -l Uses only vertical cuts in the divide-and-conquer algorithm. By\n");
+ printf(
+" default, Triangle alternates between vertical and horizontal cuts,\n"
+);
+ printf(
+" which usually improve the speed except with vertex sets that are\n");
+ printf(
+" small or short and wide. This switch is primarily of theoretical\n");
+ printf(" interest.\n");
+ printf(
+" -s Specifies that segments should be forced into the triangulation by\n"
+);
+ printf(
+" recursively splitting them at their midpoints, rather than by\n");
+ printf(
+" generating a constrained Delaunay triangulation. Segment splitting\n"
+);
+ printf(
+" is true to Ruppert's original algorithm, but can create needlessly\n"
+);
+ printf(
+" small triangles. This switch is primarily of theoretical interest.\n"
+);
+ printf(
+" -C Check the consistency of the final mesh. Uses exact arithmetic for\n"
+);
+ printf(
+" checking, even if the -X switch is used. Useful if you suspect\n");
+ printf(" Triangle is buggy.\n");
+ printf(
+" -Q Quiet: Suppresses all explanation of what Triangle is doing,\n");
+ printf(" unless an error occurs.\n");
+ printf(
+" -V Verbose: Gives detailed information about what Triangle is doing.\n"
+);
+ printf(
+" Add more `V's for increasing amount of detail. `-V' is most\n");
+ printf(
+" useful; itgives information on algorithmic progress and much more\n");
+ printf(
+" detailed statistics. `-VV' gives vertex-by-vertex details, and\n");
+ printf(
+" prints so much that Triangle runs much more slowly. `-VVVV' gives\n"
+);
+ printf(" information only a debugger could love.\n");
+ printf(" -h Help: Displays these instructions.\n");
+ printf("\n");
+ printf("Definitions:\n");
+ printf("\n");
+ printf(
+" A Delaunay triangulation of a vertex set is a triangulation whose\n");
+ printf(
+" vertices are the vertex set, that covers the convex hull of the vertex\n");
+ printf(
+" set. A Delaunay triangulation has the property that no vertex lies\n");
+ printf(
+" inside the circumscribing circle (circle that passes through all three\n");
+ printf(" vertices) of any triangle in the triangulation.\n\n");
+ printf(
+" A Voronoi diagram of a vertex set is a subdivision of the plane into\n");
+ printf(
+" polygonal cells (some of which may be unbounded, meaning infinitely\n");
+ printf(
+" large), where each cell is the set of points in the plane that are closer\n"
+);
+ printf(
+" to some input vertex than to any other input vertex. The Voronoi diagram\n"
+);
+ printf(" is a geometric dual of the Delaunay triangulation.\n\n");
+ printf(
+" A Planar Straight Line Graph (PSLG) is a set of vertices and segments.\n");
+ printf(
+" Segments are simply edges, whose endpoints are all vertices in the PSLG.\n"
+);
+ printf(
+" Segments may intersect each other only at their endpoints. The file\n");
+ printf(" format for PSLGs (.poly files) is described below.\n\n");
+ printf(
+" A constrained Delaunay triangulation (CDT) of a PSLG is similar to a\n");
+ printf(
+" Delaunay triangulation, but each PSLG segment is present as a single edge\n"
+);
+ printf(
+" of the CDT. (A constrained Delaunay triangulation is not truly a\n");
+ printf(
+" Delaunay triangulation, because some of its triangles might not be\n");
+ printf(
+" Delaunay.) By definition, a CDT does not have any vertices other than\n");
+ printf(
+" those specified in the input PSLG. Depending on context, a CDT might\n");
+ printf(
+" cover the convex hull of the PSLG, or it might cover only a segment-\n");
+ printf(" bounded region (e.g. a polygon).\n\n");
+ printf(
+" A conforming Delaunay triangulation of a PSLG is a triangulation in which\n"
+);
+ printf(
+" each triangle is truly Delaunay, and each PSLG segment is represented by\n"
+);
+ printf(
+" a linear contiguous sequence of edges of the triangulation. New vertices\n"
+);
+ printf(
+" (not part of the PSLG) may appear, and each input segment may have been\n");
+ printf(
+" subdivided into shorter edges (subsegments) by these additional vertices.\n"
+);
+ printf(
+" The new vertices are frequently necessary to maintain the Delaunay\n");
+ printf(" property while ensuring that every segment is represented.\n\n");
+ printf(
+" A conforming constrained Delaunay triangulation (CCDT) of a PSLG is a\n");
+ printf(
+" triangulation of a PSLG whose triangles are constrained Delaunay. New\n");
+ printf(" vertices may appear, and input segments may be subdivided into\n");
+ printf(
+" subsegments, but not to guarantee that segments are respected; rather, to\n"
+);
+ printf(
+" improve the quality of the triangles. The high-quality meshes produced\n");
+ printf(
+" by the -q switch are usually CCDTs, but can be made conforming Delaunay\n");
+ printf(" with the -D switch.\n\n");
+ printf("File Formats:\n\n");
+ printf(
+" All files may contain comments prefixed by the character '#'. Vertices,\n"
+);
+ printf(
+" triangles, edges, holes, and maximum area constraints must be numbered\n");
+ printf(
+" consecutively, starting from either 1 or 0. Whichever you choose, all\n");
+ printf(
+" input files must be consistent; if the vertices are numbered from 1, so\n");
+ printf(
+" must be all other objects. Triangle automatically detects your choice\n");
+ printf(
+" while reading the .node (or .poly) file. (When calling Triangle from\n");
+ printf(
+" another program, use the -z switch if you wish to number objects from\n");
+ printf(" zero.) Examples of these file formats are given below.\n\n");
+ printf(" .node files:\n");
+ printf(
+" First line: <# of vertices> <dimension (must be 2)> <# of attributes>\n"
+);
+ printf(
+" <# of boundary markers (0 or 1)>\n"
+);
+ printf(
+" Remaining lines: <vertex #> <x> <y> [attributes] [boundary marker]\n");
+ printf("\n");
+ printf(
+" The attributes, which are typically floating-point values of physical\n");
+ printf(
+" quantities (such as mass or conductivity) associated with the nodes of\n"
+);
+ printf(
+" a finite element mesh, are copied unchanged to the output mesh. If -q,\n"
+);
+ printf(
+" -a, -u, -D, or -s is selected, each new Steiner point added to the mesh\n"
+);
+ printf(" has attributes = vec3ed to it by linear interpolation.\n\n");
+ printf(
+" If the fourth entry of the first line is `1', the last column of the\n");
+ printf(
+" remainder of the file is assumed to contain boundary markers. Boundary\n"
+);
+ printf(
+" markers are used to identify boundary vertices and vertices resting on\n"
+);
+ printf(
+" PSLG segments; a complete description appears in a section below. The\n"
+);
+ printf(
+" .node file produced by Triangle contains boundary markers in the last\n");
+ printf(" column unless they are suppressed by the -B switch.\n\n");
+ printf(" .ele files:\n");
+ printf(
+" First line: <# of triangles> <nodes per triangle> <# of attributes>\n");
+ printf(
+" Remaining lines: <triangle #> <node> <node> <node> ... [attributes]\n");
+ printf("\n");
+ printf(
+" Nodes are indices into the corresponding .node file. The first three\n");
+ printf(
+" nodes are the corner vertices, and are listed in counterclockwise order\n"
+);
+ printf(
+" around each triangle. (The remaining nodes, if any, depend on the type\n"
+);
+ printf(" of finite element used.)\n\n");
+ printf(
+" The attributes are just like those of .node files. Because there is no\n"
+);
+ printf(
+" simple mapping from input to output triangles, Triangle attempts to\n");
+ printf(
+" interpolate attributes, and may cause a lot of diffusion of attributes\n"
+);
+ printf(
+" among nearby triangles as the triangulation is refined. Attributes do\n"
+);
+ printf(" not diffuse across segments, so attributes used to identify\n");
+ printf(" segment-bounded regions remain intact.\n\n");
+ printf(
+" In .ele files produced by Triangle, each triangular element has three\n");
+ printf(
+" nodes (vertices) unless the -o2 switch is used, in which case\n");
+ printf(
+" subparametric quadratic elements with six nodes each are generated.\n");
+ printf(
+" The first three nodes are the corners in counterclockwise order, and\n");
+ printf(
+" the fourth, fifth, and sixth nodes lie on the midpoints of the edges\n");
+ printf(
+" opposite the first, second, and third vertices, respectively.\n");
+ printf("\n");
+ printf(" .poly files:\n");
+ printf(
+" First line: <# of vertices> <dimension (must be 2)> <# of attributes>\n"
+);
+ printf(
+" <# of boundary markers (0 or 1)>\n"
+);
+ printf(
+" Following lines: <vertex #> <x> <y> [attributes] [boundary marker]\n");
+ printf(" One line: <# of segments> <# of boundary markers (0 or 1)>\n");
+ printf(
+" Following lines: <segment #> <endpoint> <endpoint> [boundary marker]\n");
+ printf(" One line: <# of holes>\n");
+ printf(" Following lines: <hole #> <x> <y>\n");
+ printf(
+" Optional line: <# of regional attributes and/or area constraints>\n");
+ printf(
+" Optional following lines: <region #> <x> <y> <attribute> <max area>\n");
+ printf("\n");
+ printf(
+" A .poly file represents a PSLG, as well as some additional information.\n"
+);
+ printf(
+" The first section lists all the vertices, and is identical to the\n");
+ printf(
+" format of .node files. <# of vertices> may be set to zero to indicate\n"
+);
+ printf(
+" that the vertices are listed in a separate .node file; .poly files\n");
+ printf(
+" produced by Triangle always have this format. A vertex set represented\n"
+);
+ printf(
+" this way has the advantage that it may easily be triangulated with or\n");
+ printf(
+" without segments (depending on whether the -p switch is invoked).\n");
+ printf("\n");
+ printf(
+" The second section lists the segments. Segments are edges whose\n");
+ printf(
+" presence in the triangulation is enforced. (Depending on the choice of\n"
+);
+ printf(
+" switches, segment might be subdivided into smaller edges). Each\n");
+ printf(
+" segment is specified by listing the indices of its two endpoints. This\n"
+);
+ printf(
+" means that you must include its endpoints in the vertex list. Each\n");
+ printf(" segment, like each point, may have a boundary marker.\n\n");
+ printf(
+" If -q, -a, -u, and -s are not selected, Triangle produces a constrained\n"
+);
+ printf(
+" Delaunay triangulation (CDT), in which each segment appears as a single\n"
+);
+ printf(
+" edge in the triangulation. If -q, -a, -u, or -s is selected, Triangle\n"
+);
+ printf(
+" produces a conforming constrained Delaunay triangulation (CCDT), in\n");
+ printf(
+" which segments may be subdivided into smaller edges. If -D is\n");
+ printf(
+" selected, Triangle produces a conforming Delaunay triangulation, so\n");
+ printf(
+" that every triangle is Delaunay, and not just constrained Delaunay.\n");
+ printf("\n");
+ printf(
+" The third section lists holes (and concavities, if -c is selected) in\n");
+ printf(
+" the triangulation. Holes are specified by identifying a point inside\n");
+ printf(
+" each hole. After the triangulation is formed, Triangle creates holes\n");
+ printf(
+" by eating triangles, spreading out from each hole point until its\n");
+ printf(
+" progress is blocked by segments in the PSLG. You must be careful to\n");
+ printf(
+" enclose each hole in segments, or your whole triangulation might be\n");
+ printf(
+" eaten away. If the two triangles abutting a segment are eaten, the\n");
+ printf(
+" segment itself is also eaten. Do not place a hole directly on a\n");
+ printf(" segment; if you do, Triangle chooses one side of the segment\n");
+ printf(" arbitrarily.\n\n");
+ printf(
+" The optional fourth section lists regional attributes (to be = vec3ed\n");
+ printf(
+" to all triangles in a region) and regional constraints on the maximum\n");
+ printf(
+" triangle area. Triangle reads this section only if the -A switch is\n");
+ printf(
+" used or the -a switch is used without a number following it, and the -r\n"
+);
+ printf(
+" switch is not used. Regional attributes and area constraints are\n");
+ printf(
+" propagated in the same manner as holes: you specify a point for each\n");
+ printf(
+" attribute and/or constraint, and the attribute and/or constraint\n");
+ printf(
+" affects the whole region (bounded by segments) containing the point.\n");
+ printf(
+" If two values are written on a line after the x and y coordinate, the\n");
+ printf(
+" first such value is assumed to be a regional attribute (but is only\n");
+ printf(
+" applied if the -A switch is selected), and the second value is assumed\n"
+);
+ printf(
+" to be a regional area constraint (but is only applied if the -a switch\n"
+);
+ printf(
+" is selected). You may specify just one value after the coordinates,\n");
+ printf(
+" which can serve as both an attribute and an area constraint, depending\n"
+);
+ printf(
+" on the choice of switches. If you are using the -A and -a switches\n");
+ printf(
+" simultaneously and wish to = vec3 an attribute to some region without\n");
+ printf(" imposing an area constraint, use a negative maximum area.\n\n");
+ printf(
+" When a triangulation is created from a .poly file, you must either\n");
+ printf(
+" enclose the entire region to be triangulated in PSLG segments, or\n");
+ printf(
+" use the -c switch, which automatically creates extra segments that\n");
+ printf(
+" enclose the convex hull of the PSLG. If you do not use the -c switch,\n"
+);
+ printf(
+" Triangle eats all triangles that are not enclosed by segments; if you\n");
+ printf(
+" are not careful, your whole triangulation may be eaten away. If you do\n"
+);
+ printf(
+" use the -c switch, you can still produce concavities by the appropriate\n"
+);
+ printf(
+" placement of holes just inside the boundary of the convex hull.\n");
+ printf("\n");
+ printf(
+" An ideal PSLG has no intersecting segments, nor any vertices that lie\n");
+ printf(
+" upon segments (except, of course, the endpoints of each segment). You\n"
+);
+ printf(
+" aren't required to make your .poly files ideal, but you should be aware\n"
+);
+ printf(
+" of what can go wrong. Segment intersections are relatively safe--\n");
+ printf(
+" Triangle calculates the intersection points for you and adds them to\n");
+ printf(
+" the triangulation--as long as your machine's floating-point precision\n");
+ printf(
+" doesn't become a problem. You are tempting the fates if you have three\n"
+);
+ printf(
+" segments that cross at the same location, and expect Triangle to figure\n"
+);
+ printf(
+" out where the intersection point is. Thanks to floating-point roundoff\n"
+);
+ printf(
+" error, Triangle will probably decide that the three segments intersect\n"
+);
+ printf(
+" at three different points, and you will find a minuscule triangle in\n");
+ printf(
+" your output--unless Triangle tries to refine the tiny triangle, uses\n");
+ printf(
+" up the last bit of machine precision, and fails to terminate at all.\n");
+ printf(
+" You're better off putting the intersection point in the input files,\n");
+ printf(
+" and manually breaking up each segment into two. Similarly, if you\n");
+ printf(
+" place a vertex at the middle of a segment, and hope that Triangle will\n"
+);
+ printf(
+" break up the segment at that vertex, you might get lucky. On the other\n"
+);
+ printf(
+" hand, Triangle might decide that the vertex doesn't lie precisely on\n");
+ printf(
+" the segment, and you'll have a needle-sharp triangle in your output--or\n"
+);
+ printf(" a lot of tiny triangles if you're generating a quality mesh.\n");
+ printf("\n");
+ printf(
+" When Triangle reads a .poly file, it also writes a .poly file, which\n");
+ printf(
+" includes all the subsegments--the edges that are parts of input\n");
+ printf(
+" segments. If the -c switch is used, the output .poly file also\n");
+ printf(
+" includes all of the edges on the convex hull. Hence, the output .poly\n"
+);
+ printf(
+" file is useful for finding edges associated with input segments and for\n"
+);
+ printf(
+" setting boundary conditions in finite element simulations. Moreover,\n");
+ printf(
+" you will need the output .poly file if you plan to refine the output\n");
+ printf(
+" mesh, and don't want segments to be missing in later triangulations.\n");
+ printf("\n");
+ printf(" .area files:\n");
+ printf(" First line: <# of triangles>\n");
+ printf(" Following lines: <triangle #> <maximum area>\n");
+ printf("\n");
+ printf(
+" An .area file associates with each triangle a maximum area that is used\n"
+);
+ printf(
+" for mesh refinement. As with other file formats, every triangle must\n");
+ printf(
+" be represented, and the triangles must be numbered consecutively. A\n");
+ printf(
+" triangle may be left unconstrained by = vec3ing it a negative maximum\n");
+ printf(" area.\n\n");
+ printf(" .edge files:\n");
+ printf(" First line: <# of edges> <# of boundary markers (0 or 1)>\n");
+ printf(
+" Following lines: <edge #> <endpoint> <endpoint> [boundary marker]\n");
+ printf("\n");
+ printf(
+" Endpoints are indices into the corresponding .node file. Triangle can\n"
+);
+ printf(
+" produce .edge files (use the -e switch), but cannot read them. The\n");
+ printf(
+" optional column of boundary markers is suppressed by the -B switch.\n");
+ printf("\n");
+ printf(
+" In Voronoi diagrams, one also finds a special kind of edge that is an\n");
+ printf(
+" infinite ray with only one endpoint. For these edges, a different\n");
+ printf(" format is used:\n\n");
+ printf(" <edge #> <endpoint> -1 <direction x> <direction y>\n\n");
+ printf(
+" The `direction' is a floating-point vector that indicates the direction\n"
+);
+ printf(" of the infinite ray.\n\n");
+ printf(" .neigh files:\n");
+ printf(
+" First line: <# of triangles> <# of neighbors per triangle (always 3)>\n"
+);
+ printf(
+" Following lines: <triangle #> <neighbor> <neighbor> <neighbor>\n");
+ printf("\n");
+ printf(
+" Neighbors are indices into the corresponding .ele file. An index of -1\n"
+);
+ printf(
+" indicates no neighbor (because the triangle is on an exterior\n");
+ printf(
+" boundary). The first neighbor of triangle i is opposite the first\n");
+ printf(" corner of triangle i, and so on.\n\n");
+ printf(
+" Triangle can produce .neigh files (use the -n switch), but cannot read\n"
+);
+ printf(" them.\n\n");
+ printf("Boundary Markers:\n\n");
+ printf(
+" Boundary markers are tags used mainly to identify which output vertices\n");
+ printf(
+" and edges are associated with which PSLG segment, and to identify which\n");
+ printf(
+" vertices and edges occur on a boundary of the triangulation. A common\n");
+ printf(
+" use is to determine where boundary conditions should be applied to a\n");
+ printf(
+" finite element mesh. You can prevent boundary markers from being written\n"
+);
+ printf(" into files produced by Triangle by using the -B switch.\n\n");
+ printf(
+" The boundary marker associated with each segment in an output .poly file\n"
+);
+ printf(" and each edge in an output .edge file is chosen as follows:\n");
+ printf(
+" - If an output edge is part or all of a PSLG segment with a nonzero\n");
+ printf(
+" boundary marker, then the edge is = vec3ed the same marker.\n");
+ printf(
+" - Otherwise, if the edge lies on a boundary of the triangulation\n");
+ printf(
+" (even the boundary of a hole), then the edge is = vec3ed the marker\n");
+ printf(" one (1).\n");
+ printf(" - Otherwise, the edge is = vec3ed the marker zero (0).\n");
+ printf(
+" The boundary marker associated with each vertex in an output .node file\n");
+ printf(" is chosen as follows:\n");
+ printf(
+" - If a vertex is = vec3ed a nonzero boundary marker in the input file,\n"
+);
+ printf(
+" then it is = vec3ed the same marker in the output .node file.\n");
+ printf(
+" - Otherwise, if the vertex lies on a PSLG segment (even if it is an\n");
+ printf(
+" endpoint of the segment) with a nonzero boundary marker, then the\n");
+ printf(
+" vertex is = vec3ed the same marker. If the vertex lies on several\n");
+ printf(" such segments, one of the markers is chosen arbitrarily.\n");
+ printf(
+" - Otherwise, if the vertex occurs on a boundary of the triangulation,\n");
+ printf(" then the vertex is = vec3ed the marker one (1).\n");
+ printf(" - Otherwise, the vertex is = vec3ed the marker zero (0).\n");
+ printf("\n");
+ printf(
+" If you want Triangle to determine for you which vertices and edges are on\n"
+);
+ printf(
+" the boundary, = vec3 them the boundary marker zero (or use no markers at\n"
+);
+ printf(
+" all) in your input files. In the output files, all boundary vertices,\n");
+ printf(" edges, and segments will be = vec3ed the value one.\n\n");
+ printf("Triangulation Iteration Numbers:\n\n");
+ printf(
+" Because Triangle can read and refine its own triangulations, input\n");
+ printf(
+" and output files have iteration numbers. For instance, Triangle might\n");
+ printf(
+" read the files mesh.3.node, mesh.3.ele, and mesh.3.poly, refine the\n");
+ printf(
+" triangulation, and output the files mesh.4.node, mesh.4.ele, and\n");
+ printf(" mesh.4.poly. Files with no iteration number are treated as if\n");
+ printf(
+" their iteration number is zero; hence, Triangle might read the file\n");
+ printf(
+" points.node, triangulate it, and produce the files points.1.node and\n");
+ printf(" points.1.ele.\n\n");
+ printf(
+" Iteration numbers allow you to create a sequence of successively finer\n");
+ printf(
+" meshes suitable for multigrid methods. They also allow you to produce a\n"
+);
+ printf(
+" sequence of meshes using error estimate-driven mesh refinement.\n");
+ printf("\n");
+ printf(
+" If you're not using refinement or quality meshing, and you don't like\n");
+ printf(
+" iteration numbers, use the -I switch to disable them. This switch also\n");
+ printf(
+" disables output of .node and .poly files to prevent your input files from\n"
+);
+ printf(
+" being overwritten. (If the input is a .poly file that contains its own\n");
+ printf(
+" points, a .node file is written. This can be quite convenient for\n");
+ printf(" computing CDTs or quality meshes.)\n\n");
+ printf("Examples of How to Use Triangle:\n\n");
+ printf(
+" `triangle dots' reads vertices from dots.node, and writes their Delaunay\n"
+);
+ printf(
+" triangulation to dots.1.node and dots.1.ele. (dots.1.node is identical\n");
+ printf(
+" to dots.node.) `triangle -I dots' writes the triangulation to dots.ele\n");
+ printf(
+" instead. (No additional .node file is needed, so none is written.)\n");
+ printf("\n");
+ printf(
+" `triangle -pe object.1' reads a PSLG from object.1.poly (and possibly\n");
+ printf(
+" object.1.node, if the vertices are omitted from object.1.poly) and writes\n"
+);
+ printf(
+" its constrained Delaunay triangulation to object.2.node and object.2.ele.\n"
+);
+ printf(
+" The segments are copied to object.2.poly, and all edges are written to\n");
+ printf(" object.2.edge.\n\n");
+ printf(
+" `triangle -pq31.5a.1 object' reads a PSLG from object.poly (and possibly\n"
+);
+ printf(
+" object.node), generates a mesh whose angles are all between 31.5 and 117\n"
+);
+ printf(
+" degrees and whose triangles all have areas of 0.1 or less, and writes the\n"
+);
+ printf(
+" mesh to object.1.node and object.1.ele. Each segment may be broken up\n");
+ printf(" into multiple subsegments; these are written to object.1.poly.\n");
+ printf("\n");
+ printf(
+" Here is a sample file `box.poly' describing a square with a square hole:\n"
+);
+ printf("\n");
+ printf(
+" # A box with eight vertices in 2D, no attributes, one boundary marker.\n"
+);
+ printf(" 8 2 0 1\n");
+ printf(" # Outer box has these vertices:\n");
+ printf(" 1 0 0 0\n");
+ printf(" 2 0 3 0\n");
+ printf(" 3 3 0 0\n");
+ printf(" 4 3 3 33 # A special marker for this vertex.\n");
+ printf(" # Inner square has these vertices:\n");
+ printf(" 5 1 1 0\n");
+ printf(" 6 1 2 0\n");
+ printf(" 7 2 1 0\n");
+ printf(" 8 2 2 0\n");
+ printf(" # Five segments with boundary markers.\n");
+ printf(" 5 1\n");
+ printf(" 1 1 2 5 # Left side of outer box.\n");
+ printf(" # Square hole has these segments:\n");
+ printf(" 2 5 7 0\n");
+ printf(" 3 7 8 0\n");
+ printf(" 4 8 6 10\n");
+ printf(" 5 6 5 0\n");
+ printf(" # One hole in the middle of the inner square.\n");
+ printf(" 1\n");
+ printf(" 1 1.5 1.5\n");
+ printf("\n");
+ printf(
+" Note that some segments are missing from the outer square, so you must\n");
+ printf(
+" use the `-c' switch. After `triangle -pqc box.poly', here is the output\n"
+);
+ printf(
+" file `box.1.node', with twelve vertices. The last four vertices were\n");
+ printf(
+" added to meet the angle constraint. Vertices 1, 2, and 9 have markers\n");
+ printf(
+" from segment 1. Vertices 6 and 8 have markers from segment 4. All the\n");
+ printf(
+" other vertices but 4 have been marked to indicate that they lie on a\n");
+ printf(" boundary.\n\n");
+ printf(" 12 2 0 1\n");
+ printf(" 1 0 0 5\n");
+ printf(" 2 0 3 5\n");
+ printf(" 3 3 0 1\n");
+ printf(" 4 3 3 33\n");
+ printf(" 5 1 1 1\n");
+ printf(" 6 1 2 10\n");
+ printf(" 7 2 1 1\n");
+ printf(" 8 2 2 10\n");
+ printf(" 9 0 1.5 5\n");
+ printf(" 10 1.5 0 1\n");
+ printf(" 11 3 1.5 1\n");
+ printf(" 12 1.5 3 1\n");
+ printf(" # Generated by triangle -pqc box.poly\n");
+ printf("\n");
+ printf(" Here is the output file `box.1.ele', with twelve triangles.\n");
+ printf("\n");
+ printf(" 12 3 0\n");
+ printf(" 1 5 6 9\n");
+ printf(" 2 10 3 7\n");
+ printf(" 3 6 8 12\n");
+ printf(" 4 9 1 5\n");
+ printf(" 5 6 2 9\n");
+ printf(" 6 7 3 11\n");
+ printf(" 7 11 4 8\n");
+ printf(" 8 7 5 10\n");
+ printf(" 9 12 2 6\n");
+ printf(" 10 8 7 11\n");
+ printf(" 11 5 1 10\n");
+ printf(" 12 8 4 12\n");
+ printf(" # Generated by triangle -pqc box.poly\n\n");
+ printf(
+" Here is the output file `box.1.poly'. Note that segments have been added\n"
+);
+ printf(
+" to represent the convex hull, and some segments have been subdivided by\n");
+ printf(
+" newly added vertices. Note also that <# of vertices> is set to zero to\n");
+ printf(" indicate that the vertices should be read from the .node file.\n");
+ printf("\n");
+ printf(" 0 2 0 1\n");
+ printf(" 12 1\n");
+ printf(" 1 1 9 5\n");
+ printf(" 2 5 7 1\n");
+ printf(" 3 8 7 1\n");
+ printf(" 4 6 8 10\n");
+ printf(" 5 5 6 1\n");
+ printf(" 6 3 10 1\n");
+ printf(" 7 4 11 1\n");
+ printf(" 8 2 12 1\n");
+ printf(" 9 9 2 5\n");
+ printf(" 10 10 1 1\n");
+ printf(" 11 11 3 1\n");
+ printf(" 12 12 4 1\n");
+ printf(" 1\n");
+ printf(" 1 1.5 1.5\n");
+ printf(" # Generated by triangle -pqc box.poly\n");
+ printf("\n");
+ printf("Refinement and Area Constraints:\n");
+ printf("\n");
+ printf(
+" The -r switch causes a mesh (.node and .ele files) to be read and\n");
+ printf(
+" refined. If the -p switch is also used, a .poly file is read and used to\n"
+);
+ printf(
+" specify edges that are constrained and cannot be eliminated (although\n");
+ printf(
+" they can be subdivided into smaller edges) by the refinement process.\n");
+ printf("\n");
+ printf(
+" When you refine a mesh, you generally want to impose tighter constraints.\n"
+);
+ printf(
+" One way to accomplish this is to use -q with a larger angle, or -a\n");
+ printf(
+" followed by a smaller area than you used to generate the mesh you are\n");
+ printf(
+" refining. Another way to do this is to create an .area file, which\n");
+ printf(
+" specifies a maximum area for each triangle, and use the -a switch\n");
+ printf(
+" (without a number following). Each triangle's area constraint is applied\n"
+);
+ printf(
+" to that triangle. Area constraints tend to diffuse as the mesh is\n");
+ printf(
+" refined, so if there are large variations in area constraint between\n");
+ printf(
+" adjacent triangles, you may not get the results you want. In that case,\n"
+);
+ printf(
+" consider instead using the -u switch and writing a C procedure that\n");
+ printf(" determines which triangles are too large.\n\n");
+ printf(
+" If you are refining a mesh composed of linear (three-node) elements, the\n"
+);
+ printf(
+" output mesh contains all the nodes present in the input mesh, in the same\n"
+);
+ printf(
+" order, with new nodes added at the end of the .node file. However, the\n");
+ printf(
+" refinement is not hierarchical: there is no guarantee that each output\n");
+ printf(
+" element is contained in a single input element. Often, an output element\n"
+);
+ printf(
+" can overlap two or three input elements, and some input edges are not\n");
+ printf(
+" present in the output mesh. Hence, a sequence of refined meshes forms a\n"
+);
+ printf(
+" hierarchy of nodes, but not a hierarchy of elements. If you refine a\n");
+ printf(
+" mesh of higher-order elements, the hierarchical property applies only to\n"
+);
+ printf(
+" the nodes at the corners of an element; the midpoint nodes on each edge\n");
+ printf(" are discarded before the mesh is refined.\n\n");
+ printf(
+" Maximum area constraints in .poly files operate differently from those in\n"
+);
+ printf(
+" .area files. A maximum area in a .poly file applies to the whole\n");
+ printf(
+" (segment-bounded) region in which a point falls, whereas a maximum area\n");
+ printf(
+" in an .area file applies to only one triangle. Area constraints in .poly\n"
+);
+ printf(
+" files are used only when a mesh is first generated, whereas area\n");
+ printf(
+" constraints in .area files are used only to refine an existing mesh, and\n"
+);
+ printf(
+" are typically based on a posteriori error estimates resulting from a\n");
+ printf(" finite element simulation on that mesh.\n\n");
+ printf(
+" `triangle -rq25 object.1' reads object.1.node and object.1.ele, then\n");
+ printf(
+" refines the triangulation to enforce a 25 degree minimum angle, and then\n"
+);
+ printf(
+" writes the refined triangulation to object.2.node and object.2.ele.\n");
+ printf("\n");
+ printf(
+" `triangle -rpaa6.2 z.3' reads z.3.node, z.3.ele, z.3.poly, and z.3.area.\n"
+);
+ printf(
+" After reconstructing the mesh and its subsegments, Triangle refines the\n");
+ printf(
+" mesh so that no triangle has area greater than 6.2f, and furthermore the\n");
+ printf(
+" triangles satisfy the maximum area constraints in z.3.area. No angle\n");
+ printf(
+" bound is imposed at all. The output is written to z.4.node, z.4.ele, and\n"
+);
+ printf(" z.4.poly.\n\n");
+ printf(
+" The sequence `triangle -qa1 x', `triangle -rqa.3 x.1', `triangle -rqa.1\n");
+ printf(
+" x.2' creates a sequence of successively finer meshes x.1f, x.2f, and x.3f,\n");
+ printf(" suitable for multigrid.\n\n");
+ printf("Convex Hulls and Mesh Boundaries:\n\n");
+ printf(
+" If the input is a vertex set (not a PSLG), Triangle produces its convex\n");
+ printf(
+" hull as a by-product in the output .poly file if you use the -c switch.\n");
+ printf(
+" There are faster algorithms for finding a two-dimensional convex hull\n");
+ printf(" than triangulation, of course, but this one comes for free.\n\n");
+ printf(
+" If the input is an unconstrained mesh (you are using the -r switch but\n");
+ printf(
+" not the -p switch), Triangle produces a list of its boundary edges\n");
+ printf(
+" (including hole boundaries) as a by-product when you use the -c switch.\n");
+ printf(
+" If you also use the -p switch, the output .poly file contains all the\n");
+ printf(" segments from the input .poly file as well.\n\n");
+ printf("Voronoi Diagrams:\n\n");
+ printf(
+" The -v switch produces a Voronoi diagram, in files suffixed .v.node and\n");
+ printf(
+" .v.edge. For example, `triangle -v points' reads points.node, produces\n");
+ printf(
+" its Delaunay triangulation in points.1.node and points.1.ele, and\n");
+ printf(
+" produces its Voronoi diagram in points.1.v.node and points.1.v.edge. The\n"
+);
+ printf(
+" .v.node file contains a list of all Voronoi vertices, and the .v.edge\n");
+ printf(
+" file contains a list of all Voronoi edges, some of which may be infinite\n"
+);
+ printf(
+" rays. (The choice of filenames makes it easy to run the set of Voronoi\n");
+ printf(" vertices through Triangle, if so desired.)\n\n");
+ printf(
+" This implementation does not use exact arithmetic to compute the Voronoi\n"
+);
+ printf(
+" vertices, and does not check whether neighboring vertices are identical.\n"
+);
+ printf(
+" Be forewarned that if the Delaunay triangulation is degenerate or\n");
+ printf(
+" near-degenerate, the Voronoi diagram may have duplicate vertices or\n");
+ printf(" crossing edges.\n\n");
+ printf(
+" The result is a valid Voronoi diagram only if Triangle's output is a true\n"
+);
+ printf(
+" Delaunay triangulation. The Voronoi output is usually meaningless (and\n");
+ printf(
+" may contain crossing edges and other pathology) if the output is a CDT or\n"
+);
+ printf(
+" CCDT, or if it has holes or concavities. If the triangulated domain is\n");
+ printf(
+" convex and has no holes, you can use -D switch to force Triangle to\n");
+ printf(
+" construct a conforming Delaunay triangulation instead of a CCDT, so the\n");
+ printf(" Voronoi diagram will be valid.\n\n");
+ printf("Mesh Topology:\n\n");
+ printf(
+" You may wish to know which triangles are adjacent to a certain Delaunay\n");
+ printf(
+" edge in an .edge file, which Voronoi cells are adjacent to a certain\n");
+ printf(
+" Voronoi edge in a .v.edge file, or which Voronoi cells are adjacent to\n");
+ printf(
+" each other. All of this information can be found by cross-referencing\n");
+ printf(
+" output files with the recollection that the Delaunay triangulation and\n");
+ printf(" the Voronoi diagram are planar duals.\n\n");
+ printf(
+" Specifically, edge i of an .edge file is the dual of Voronoi edge i of\n");
+ printf(
+" the corresponding .v.edge file, and is rotated 90 degrees counterclock-\n");
+ printf(
+" wise from the Voronoi edge. Triangle j of an .ele file is the dual of\n");
+ printf(
+" vertex j of the corresponding .v.node file. Voronoi cell k is the dual\n");
+ printf(" of vertex k of the corresponding .node file.\n\n");
+ printf(
+" Hence, to find the triangles adjacent to a Delaunay edge, look at the\n");
+ printf(
+" vertices of the corresponding Voronoi edge. If the endpoints of a\n");
+ printf(
+" Voronoi edge are Voronoi vertices 2 and 6 respectively, then triangles 2\n"
+);
+ printf(
+" and 6 adjoin the left and right sides of the corresponding Delaunay edge,\n"
+);
+ printf(
+" respectively. To find the Voronoi cells adjacent to a Voronoi edge, look\n"
+);
+ printf(
+" at the endpoints of the corresponding Delaunay edge. If the endpoints of\n"
+);
+ printf(
+" a Delaunay edge are input vertices 7 and 12, then Voronoi cells 7 and 12\n"
+);
+ printf(
+" adjoin the right and left sides of the corresponding Voronoi edge,\n");
+ printf(
+" respectively. To find which Voronoi cells are adjacent to each other,\n");
+ printf(" just read the list of Delaunay edges.\n\n");
+ printf(
+" Triangle does not write a list of the edges adjoining each Voronoi cell,\n"
+);
+ printf(
+" but you can reconstructed it straightforwardly. For instance, to find\n");
+ printf(
+" all the edges of Voronoi cell 1, search the output .edge file for every\n");
+ printf(
+" edge that has input vertex 1 as an endpoint. The corresponding dual\n");
+ printf(
+" edges in the output .v.edge file form the boundary of Voronoi cell 1.\n");
+ printf("\n");
+ printf(
+" For each Voronoi vertex, the .neigh file gives a list of the three\n");
+ printf(
+" Voronoi vertices attached to it. You might find this more convenient\n");
+ printf(" than the .v.edge file.\n\n");
+ printf("Quadratic Elements:\n\n");
+ printf(
+" Triangle generates meshes with subparametric quadratic elements if the\n");
+ printf(
+" -o2 switch is specified. Quadratic elements have six nodes per element,\n"
+);
+ printf(
+" rather than three. `Subparametric' means that the edges of the triangles\n"
+);
+ printf(
+" are always straight, so that subparametric quadratic elements are\n");
+ printf(
+" geometrically identical to linear elements, even though they can be used\n"
+);
+ printf(
+" with quadratic interpolating functions. The three extra nodes of an\n");
+ printf(
+" element fall at the midpoints of the three edges, with the fourth, fifth,\n"
+);
+ printf(
+" and sixth nodes appearing opposite the first, second, and third corners\n");
+ printf(" respectively.\n\n");
+ printf("Domains with Small Angles:\n\n");
+ printf(
+" If two input segments adjoin each other at a small angle, clearly the -q\n"
+);
+ printf(
+" switch cannot remove the small angle. Moreover, Triangle may have no\n");
+ printf(
+" choice but to generate additional triangles whose smallest angles are\n");
+ printf(
+" smaller than the specified bound. However, these triangles only appear\n");
+ printf(
+" between input segments separated by small angles. Moreover, if you\n");
+ printf(
+" request a minimum angle of theta degrees, Triangle will generally produce\n"
+);
+ printf(
+" no angle larger than 180 - 2 theta, even if it is forced to compromise on\n"
+);
+ printf(" the minimum angle.\n\n");
+ printf("Statistics:\n\n");
+ printf(
+" After generating a mesh, Triangle prints a count of entities in the\n");
+ printf(
+" output mesh, including the number of vertices, triangles, edges, exterior\n"
+);
+ printf(
+" boundary edges (i.e. subsegments on the boundary of the triangulation,\n");
+ printf(
+" including hole boundaries), interior boundary edges (i.e. subsegments of\n"
+);
+ printf(
+" input segments not on the boundary), and total subsegments. If you've\n");
+ printf(
+" forgotten the statistics for an existing mesh, run Triangle on that mesh\n"
+);
+ printf(
+" with the -rNEP switches to read the mesh and print the statistics without\n"
+);
+ printf(
+" writing any files. Use -rpNEP if you've got a .poly file for the mesh.\n");
+ printf("\n");
+ printf(
+" The -V switch produces extended statistics, including a rough estimate\n");
+ printf(
+" of memory use, the number of calls to geometric predicates, and\n");
+ printf(
+" histograms of the angles and the aspect ratios of the triangles in the\n");
+ printf(" mesh.\n\n");
+ printf("Exact Arithmetic:\n\n");
+ printf(
+" Triangle uses adaptive exact arithmetic to perform what computational\n");
+ printf(
+" geometers call the `orientation' and `incircle' tests. If the floating-\n"
+);
+ printf(
+" point arithmetic of your machine conforms to the IEEE 754 standard (as\n");
+ printf(
+" most workstations do), and does not use extended precision internal\n");
+ printf(
+" floating-point registers, then your output is guaranteed to be an\n");
+ printf(
+" absolutely true Delaunay or constrained Delaunay triangulation, roundoff\n"
+);
+ printf(
+" error notwithstanding. The word `adaptive' implies that these arithmetic\n"
+);
+ printf(
+" routines compute the result only to the precision necessary to guarantee\n"
+);
+ printf(
+" correctness, so they are usually nearly as fast as their approximate\n");
+ printf(" counterparts.\n\n");
+ printf(
+" May CPUs, including Intel x86 processors, have extended precision\n");
+ printf(
+" floating-point registers. These must be reconfigured so their precision\n"
+);
+ printf(
+" is reduced to memory precision. Triangle does this if it is compiled\n");
+ printf(" correctly. See the makefile for details.\n\n");
+ printf(
+" The exact tests can be disabled with the -X switch. On most inputs, this\n"
+);
+ printf(
+" switch reduces the computation time by about eight percent--it's not\n");
+ printf(
+" worth the risk. There are rare difficult inputs (having many collinear\n");
+ printf(
+" and cocircular vertices), however, for which the difference in speed\n");
+ printf(
+" could be a factor of two. Be forewarned that these are precisely the\n");
+ printf(
+" inputs most likely to cause errors if you use the -X switch. Hence, the\n"
+);
+ printf(" -X switch is not recommended.\n\n");
+ printf(
+" Unfortunately, the exact tests don't solve every numerical problem.\n");
+ printf(
+" Exact arithmetic is not used to compute the positions of new vertices,\n");
+ printf(
+" because the bit complexity of vertex coordinates would grow without\n");
+ printf(
+" bound. Hence, segment intersections aren't computed exactly; in very\n");
+ printf(
+" unusual cases, roundoff error in computing an intersection point might\n");
+ printf(
+" actually lead to an inverted triangle and an invalid triangulation.\n");
+ printf(
+" (This is one reason to specify your own intersection points in your .poly\n"
+);
+ printf(
+" files.) Similarly, exact arithmetic is not used to compute the vertices\n"
+);
+ printf(" of the Voronoi diagram.\n\n");
+ printf(
+" Another pair of problems not solved by the exact arithmetic routines is\n");
+ printf(
+" underflow and overflow. If Triangle is compiled for double precision\n");
+ printf(
+" arithmetic, I believe that Triangle's geometric predicates work correctly\n"
+);
+ printf(
+" if the exponent of every input coordinate falls in the range [-148, 201].\n"
+);
+ printf(
+" Underflow can silently prevent the orientation and incircle tests from\n");
+ printf(
+" being performed exactly, while overflow typically causes a floating\n");
+ printf(" exception.\n\n");
+ printf("Calling Triangle from Another Program:\n\n");
+ printf(" Read the file triangle.h for details.\n\n");
+ printf("Troubleshooting:\n\n");
+ printf(" Please read this section before mailing me bugs.\n\n");
+ printf(" `My output mesh has no triangles!'\n\n");
+ printf(
+" If you're using a PSLG, you've probably failed to specify a proper set\n"
+);
+ printf(
+" of bounding segments, or forgotten to use the -c switch. Or you may\n");
+ printf(
+" have placed a hole badly, thereby eating all your triangles. To test\n");
+ printf(" these possibilities, try again with the -c and -O switches.\n");
+ printf(
+" Alternatively, all your input vertices may be collinear, in which case\n"
+);
+ printf(" you can hardly expect to triangulate them.\n\n");
+ printf(" `Triangle doesn't terminate, or just crashes.'\n\n");
+ printf(
+" Bad things can happen when triangles get so small that the distance\n");
+ printf(
+" between their vertices isn't much larger than the precision of your\n");
+ printf(
+" machine's arithmetic. If you've compiled Triangle for single-precision\n"
+);
+ printf(
+" arithmetic, you might do better by recompiling it for double-precision.\n"
+);
+ printf(
+" Then again, you might just have to settle for more lenient constraints\n"
+);
+ printf(
+" on the minimum angle and the maximum area than you had planned.\n");
+ printf("\n");
+ printf(
+" You can minimize precision problems by ensuring that the origin lies\n");
+ printf(
+" inside your vertex set, or even inside the densest part of your\n");
+ printf(
+" mesh. If you're triangulating an object whose x-coordinates all fall\n");
+ printf(
+" between 6247133 and 6247134, you're not leaving much floating-point\n");
+ printf(" precision for Triangle to work with.\n\n");
+ printf(
+" Precision problems can occur covertly if the input PSLG contains two\n");
+ printf(
+" segments that meet (or intersect) at an extremely small angle, or if\n");
+ printf(
+" such an angle is introduced by the -c switch. If you don't realize\n");
+ printf(
+" that a tiny angle is being formed, you might never discover why\n");
+ printf(
+" Triangle is crashing. To check for this possibility, use the -S switch\n"
+);
+ printf(
+" (with an appropriate limit on the number of Steiner points, found by\n");
+ printf(
+" trial-and-error) to stop Triangle early, and view the output .poly file\n"
+);
+ printf(
+" with Show Me (described below). Look carefully for regions where dense\n"
+);
+ printf(
+" clusters of vertices are forming and for small angles between segments.\n"
+);
+ printf(
+" Zoom in closely, as such segments might look like a single segment from\n"
+);
+ printf(" a distance.\n\n");
+ printf(
+" If some of the input values are too large, Triangle may suffer a\n");
+ printf(
+" floating exception due to overflow when attempting to perform an\n");
+ printf(
+" orientation or incircle test. (Read the section on exact arithmetic\n");
+ printf(
+" above.) Again, I recommend compiling Triangle for double (rather\n");
+ printf(" than single) precision arithmetic.\n\n");
+ printf(
+" Unexpected problems can arise if you use quality meshing (-q, -a, or\n");
+ printf(
+" -u) with an input that is not segment-bounded--that is, if your input\n");
+ printf(
+" is a vertex set, or you're using the -c switch. If the convex hull of\n"
+);
+ printf(
+" your input vertices has collinear vertices on its boundary, an input\n");
+ printf(
+" vertex that you think lies on the convex hull might actually lie just\n");
+ printf(
+" inside the convex hull. If so, the vertex and the nearby convex hull\n");
+ printf(
+" edge form an extremely thin triangle. When Triangle tries to refine\n");
+ printf(
+" the mesh to enforce angle and area constraints, Triangle might generate\n"
+);
+ printf(
+" extremely tiny triangles, or it might fail because of insufficient\n");
+ printf(" floating-point precision.\n\n");
+ printf(
+" `The numbering of the output vertices doesn't match the input vertices.'\n"
+);
+ printf("\n");
+ printf(
+" You may have had duplicate input vertices, or you may have eaten some\n");
+ printf(
+" of your input vertices with a hole, or by placing them outside the area\n"
+);
+ printf(
+" enclosed by segments. In any case, you can solve the problem by not\n");
+ printf(" using the -j switch.\n\n");
+ printf(
+" `Triangle executes without incident, but when I look at the resulting\n");
+ printf(
+" mesh, it has overlapping triangles or other geometric inconsistencies.'\n");
+ printf("\n");
+ printf(
+" If you select the -X switch, Triangle occasionally makes mistakes due\n");
+ printf(
+" to floating-point roundoff error. Although these errors are rare,\n");
+ printf(
+" don't use the -X switch. If you still have problems, please report the\n"
+);
+ printf(" bug.\n\n");
+ printf(
+" `Triangle executes without incident, but when I look at the resulting\n");
+ printf(" Voronoi diagram, it has overlapping edges or other geometric\n");
+ printf(" inconsistencies.'\n");
+ printf("\n");
+ printf(
+" If your input is a PSLG (-p), you can only expect a meaningful Voronoi\n"
+);
+ printf(
+" diagram if the domain you are triangulating is convex and free of\n");
+ printf(
+" holes, and you use the -D switch to construct a conforming Delaunay\n");
+ printf(" triangulation (instead of a CDT or CCDT).\n\n");
+ printf(
+" Strange things can happen if you've taken liberties with your PSLG. Do\n");
+ printf(
+" you have a vertex lying in the middle of a segment? Triangle sometimes\n");
+ printf(
+" copes poorly with that sort of thing. Do you want to lay out a collinear\n"
+);
+ printf(
+" row of evenly spaced, segment-connected vertices? Have you simply\n");
+ printf(
+" defined one long segment connecting the leftmost vertex to the rightmost\n"
+);
+ printf(
+" vertex, and a bunch of vertices lying along it? This method occasionally\n"
+);
+ printf(
+" works, especially with horizontal and vertical lines, but often it\n");
+ printf(
+" doesn't, and you'll have to connect each adjacent pair of vertices with a\n"
+);
+ printf(" separate segment. If you don't like it, tough.\n\n");
+ printf(
+" Furthermore, if you have segments that intersect other than at their\n");
+ printf(
+" endpoints, try not to let the intersections fall extremely close to PSLG\n"
+);
+ printf(" vertices or each other.\n\n");
+ printf(
+" If you have problems refining a triangulation not produced by Triangle:\n");
+ printf(
+" Are you sure the triangulation is geometrically valid? Is it formatted\n");
+ printf(
+" correctly for Triangle? Are the triangles all listed so the first three\n"
+);
+ printf(
+" vertices are their corners in counterclockwise order? Are all of the\n");
+ printf(
+" triangles constrained Delaunay? Triangle's Delaunay refinement algorithm\n"
+);
+ printf(" assumes that it starts with a CDT.\n\n");
+ printf("Show Me:\n\n");
+ printf(
+" Triangle comes with a separate program named `Show Me', whose primary\n");
+ printf(
+" purpose is to draw meshes on your screen or in PostScript. Its secondary\n"
+);
+ printf(
+" purpose is to check the validity of your input files, and do so more\n");
+ printf(
+" thoroughly than Triangle does. Unlike Triangle, Show Me requires that\n");
+ printf(
+" you have the X Windows system. Sorry, Microsoft Windows users.\n");
+ printf("\n");
+ printf("Triangle on the Web:\n");
+ printf("\n");
+ printf(" To see an illustrated version of these instructions, check out\n");
+ printf("\n");
+ printf(" http://www.cs.cmu.edu/~quake/triangle.html\n");
+ printf("\n");
+ printf("A Brief Plea:\n");
+ printf("\n");
+ printf(
+" If you use Triangle, and especially if you use it to accomplish real\n");
+ printf(
+" work, I would like very much to hear from you. A short letter or email\n");
+ printf(
+" (to jrs@cs.berkeley.edu) describing how you use Triangle will mean a lot\n"
+);
+ printf(
+" to me. The more people I know are using this program, the more easily I\n"
+);
+ printf(
+" can justify spending time on improvements, which in turn will benefit\n");
+ printf(
+" you. Also, I can put you on a list to receive email whenever a new\n");
+ printf(" version of Triangle is available.\n\n");
+ printf(
+" If you use a mesh generated by Triangle in a publication, please include\n"
+);
+ printf(
+" an acknowledgment as well. And please spell Triangle with a capital `T'!\n"
+);
+ printf(
+" If you want to include a citation, use `Jonathan Richard Shewchuk,\n");
+ printf(
+" ``Triangle: Engineering a 2D Quality Mesh Generator and Delaunay\n");
+ printf(
+" Triangulator,'' in Applied Computational Geometry: Towards Geometric\n");
+ printf(
+" Engineering (Ming C. Lin and Dinesh Manocha, editors), volume 1148 of\n");
+ printf(
+" Lecture Notes in Computer Science, pages 203-222, Springer-Verlag,\n");
+ printf(
+" Berlin, May 1996. (From the First ACM Workshop on Applied Computational\n"
+);
+ printf(" Geometry.)'\n\n");
+ printf("Research credit:\n\n");
+ printf(
+" Of course, I can take credit for only a fraction of the ideas that made\n");
+ printf(
+" this mesh generator possible. Triangle owes its existence to the efforts\n"
+);
+ printf(
+" of many fine computational geometers and other researchers, including\n");
+ printf(
+" Marshall Bern, L. Paul Chew, Kenneth L. Clarkson, Boris Delaunay, Rex A.\n"
+);
+ printf(
+" Dwyer, David Eppstein, Steven Fortune, Leonidas J. Guibas, Donald E.\n");
+ printf(
+" Knuth, Charles L. Lawson, Der-Tsai Lee, Gary L. Miller, Ernst P. Mucke,\n");
+ printf(
+" Steven E. Pav, Douglas M. Priest, Jim Ruppert, Isaac Saias, Bruce J.\n");
+ printf(
+" Schachter, Micha Sharir, Peter W. Shor, Daniel D. Sleator, Jorge Stolfi,\n"
+);
+ printf(" Robert E. Tarjan, Alper Ungor, Christopher J. Van Wyk, Noel J.\n");
+ printf(
+" Walkington, and Binhai Zhu. See the comments at the beginning of the\n");
+ printf(" source code for references.\n\n");
+ triexit(0);
+}
+
+#endif /* not TRILIBRARY */
+
+/*****************************************************************************/
+/* */
+/* internalerror() Ask the user to send me the defective product. Exit. */
+/* */
+/*****************************************************************************/
+
+void internalerror()
+{
+ printf(" Please report this bug to jrs@cs.berkeley.edu\n");
+ printf(" Include the message above, your input data set, and the exact\n");
+ printf(" command line you used to run Triangle.\n");
+ triexit(1);
+}
+
+/*****************************************************************************/
+/* */
+/* parsecommandline() Read the command line, identify switches, and set */
+/* up options and file names. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void parsecommandline(int argc, const char **argv, struct behavior *b)
+#else /* not ANSI_DECLARATORS */
+void parsecommandline(argc, argv, b)
+int argc;
+char **argv;
+struct behavior *b;
+#endif /* not ANSI_DECLARATORS */
+
+{
+#ifdef TRILIBRARY
+#define STARTINDEX 0
+#else /* not TRILIBRARY */
+#define STARTINDEX 1
+ int increment;
+ int meshnumber;
+#endif /* not TRILIBRARY */
+ int i, j, k;
+ char workstring[FILENAMESIZE];
+
+ b->poly = b->refine = b->quality = 0;
+ b->vararea = b->fixedarea = b->usertest = 0;
+ b->regionattrib = b->convex = b->weighted = b->jettison = 0;
+ b->firstnumber = 1;
+ b->edgesout = b->voronoi = b->neighbors = b->geomview = 0;
+ b->nobound = b->nopolywritten = b->nonodewritten = b->noelewritten = 0;
+ b->noiterationnum = 0;
+ b->noholes = b->noexact = 0;
+ b->incremental = b->sweepline = 0;
+ b->dwyer = 1;
+ b->splitseg = 0;
+ b->docheck = 0;
+ b->nobisect = 0;
+ b->conformdel = 0;
+ b->steiner = -1;
+ b->order = 1;
+ b->minangle = 0.0f;
+ b->maxarea = -1.0f;
+ b->quiet = b->verbose = 0;
+#ifndef TRILIBRARY
+ b->innodefilename[0] = '\0';
+#endif /* not TRILIBRARY */
+
+ for (i = STARTINDEX; i < argc; i++) {
+#ifndef TRILIBRARY
+ if (argv[i][0] == '-') {
+#endif /* not TRILIBRARY */
+ for (j = STARTINDEX; argv[i][j] != '\0'; j++) {
+ if (argv[i][j] == 'p') {
+ b->poly = 1;
+ }
+#ifndef CDT_ONLY
+ if (argv[i][j] == 'r') {
+ b->refine = 1;
+ }
+ if (argv[i][j] == 'q') {
+ b->quality = 1;
+ if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) ||
+ (argv[i][j + 1] == '.')) {
+ k = 0;
+ while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) ||
+ (argv[i][j + 1] == '.')) {
+ j++;
+ workstring[k] = argv[i][j];
+ k++;
+ }
+ workstring[k] = '\0';
+ b->minangle = (tREAL) strtod(workstring, (char **) NULL);
+ } else {
+ b->minangle = 20.0f;
+ }
+ }
+ if (argv[i][j] == 'a') {
+ b->quality = 1;
+ if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) ||
+ (argv[i][j + 1] == '.')) {
+ b->fixedarea = 1;
+ k = 0;
+ while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) ||
+ (argv[i][j + 1] == '.')) {
+ j++;
+ workstring[k] = argv[i][j];
+ k++;
+ }
+ workstring[k] = '\0';
+ b->maxarea = (tREAL) strtod(workstring, (char **) NULL);
+ if (b->maxarea <= 0.0f) {
+ printf("Error: Maximum area must be greater than zero.\n");
+ triexit(1);
+ }
+ } else {
+ b->vararea = 1;
+ }
+ }
+ if (argv[i][j] == 'u') {
+ b->quality = 1;
+ b->usertest = 1;
+ }
+#endif /* not CDT_ONLY */
+ if (argv[i][j] == 'A') {
+ b->regionattrib = 1;
+ }
+ if (argv[i][j] == 'c') {
+ b->convex = 1;
+ }
+ if (argv[i][j] == 'w') {
+ b->weighted = 1;
+ }
+ if (argv[i][j] == 'W') {
+ b->weighted = 2;
+ }
+ if (argv[i][j] == 'j') {
+ b->jettison = 1;
+ }
+ if (argv[i][j] == 'z') {
+ b->firstnumber = 0;
+ }
+ if (argv[i][j] == 'e') {
+ b->edgesout = 1;
+ }
+ if (argv[i][j] == 'v') {
+ b->voronoi = 1;
+ }
+ if (argv[i][j] == 'n') {
+ b->neighbors = 1;
+ }
+ if (argv[i][j] == 'g') {
+ b->geomview = 1;
+ }
+ if (argv[i][j] == 'B') {
+ b->nobound = 1;
+ }
+ if (argv[i][j] == 'P') {
+ b->nopolywritten = 1;
+ }
+ if (argv[i][j] == 'N') {
+ b->nonodewritten = 1;
+ }
+ if (argv[i][j] == 'E') {
+ b->noelewritten = 1;
+ }
+#ifndef TRILIBRARY
+ if (argv[i][j] == 'I') {
+ b->noiterationnum = 1;
+ }
+#endif /* not TRILIBRARY */
+ if (argv[i][j] == 'O') {
+ b->noholes = 1;
+ }
+ if (argv[i][j] == 'X') {
+ b->noexact = 1;
+ }
+ if (argv[i][j] == 'o') {
+ if (argv[i][j + 1] == '2') {
+ j++;
+ b->order = 2;
+ }
+ }
+#ifndef CDT_ONLY
+ if (argv[i][j] == 'Y') {
+ b->nobisect++;
+ }
+ if (argv[i][j] == 'S') {
+ b->steiner = 0;
+ while ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) {
+ j++;
+ b->steiner = b->steiner * 10 + (int) (argv[i][j] - '0');
+ }
+ }
+#endif /* not CDT_ONLY */
+#ifndef REDUCED
+ if (argv[i][j] == 'i') {
+ b->incremental = 1;
+ }
+ if (argv[i][j] == 'F') {
+ b->sweepline = 1;
+ }
+#endif /* not REDUCED */
+ if (argv[i][j] == 'l') {
+ b->dwyer = 0;
+ }
+#ifndef REDUCED
+#ifndef CDT_ONLY
+ if (argv[i][j] == 's') {
+ b->splitseg = 1;
+ }
+ if ((argv[i][j] == 'D') || (argv[i][j] == 'L')) {
+ b->quality = 1;
+ b->conformdel = 1;
+ }
+#endif /* not CDT_ONLY */
+ if (argv[i][j] == 'C') {
+ b->docheck = 1;
+ }
+#endif /* not REDUCED */
+ if (argv[i][j] == 'Q') {
+ b->quiet = 1;
+ }
+ if (argv[i][j] == 'V') {
+ b->verbose++;
+ }
+#ifndef TRILIBRARY
+ if ((argv[i][j] == 'h') || (argv[i][j] == 'H') ||
+ (argv[i][j] == '?')) {
+ info();
+ }
+#endif /* not TRILIBRARY */
+ }
+#ifndef TRILIBRARY
+ } else {
+ strncpy(b->innodefilename, argv[i], FILENAMESIZE - 1);
+ b->innodefilename[FILENAMESIZE - 1] = '\0';
+ }
+#endif /* not TRILIBRARY */
+ }
+#ifndef TRILIBRARY
+ if (b->innodefilename[0] == '\0') {
+ syntax();
+ }
+ if (!strcmp(&b->innodefilename[strlen(b->innodefilename) - 5], ".node")) {
+ b->innodefilename[strlen(b->innodefilename) - 5] = '\0';
+ }
+ if (!strcmp(&b->innodefilename[strlen(b->innodefilename) - 5], ".poly")) {
+ b->innodefilename[strlen(b->innodefilename) - 5] = '\0';
+ b->poly = 1;
+ }
+#ifndef CDT_ONLY
+ if (!strcmp(&b->innodefilename[strlen(b->innodefilename) - 4], ".ele")) {
+ b->innodefilename[strlen(b->innodefilename) - 4] = '\0';
+ b->refine = 1;
+ }
+ if (!strcmp(&b->innodefilename[strlen(b->innodefilename) - 5], ".area")) {
+ b->innodefilename[strlen(b->innodefilename) - 5] = '\0';
+ b->refine = 1;
+ b->quality = 1;
+ b->vararea = 1;
+ }
+#endif /* not CDT_ONLY */
+#endif /* not TRILIBRARY */
+ b->usesegments = b->poly || b->refine || b->quality || b->convex;
+ b->goodangle = cos(b->minangle * PI / 180.0f);
+ if (b->goodangle == 1.0f) {
+ b->offconstant = 0.0f;
+ } else {
+ b->offconstant = 0.475 * sqrt((1.0 + b->goodangle) / (1.0 - b->goodangle));
+ }
+ b->goodangle *= b->goodangle;
+ if (b->refine && b->noiterationnum) {
+ printf(
+ "Error: You cannot use the -I switch when refining a triangulation.\n");
+ triexit(1);
+ }
+ /* Be careful not to allocate space for element area constraints that */
+ /* will never be = vec3ed any value (other than the default -1.0f). */
+ if (!b->refine && !b->poly) {
+ b->vararea = 0;
+ }
+ /* Be careful not to add an extra attribute to each element unless the */
+ /* input supports it (PSLG in, but not refining a preexisting mesh). */
+ if (b->refine || !b->poly) {
+ b->regionattrib = 0;
+ }
+ /* Regular/weighted triangulations are incompatible with PSLGs */
+ /* and meshing. */
+ if (b->weighted && (b->poly || b->quality)) {
+ b->weighted = 0;
+ if (!b->quiet) {
+ printf("Warning: weighted triangulations (-w, -W) are incompatible\n");
+ printf(" with PSLGs (-p) and meshing (-q, -a, -u). Weights ignored.\n"
+ );
+ }
+ }
+ if (b->jettison && b->nonodewritten && !b->quiet) {
+ printf("Warning: -j and -N switches are somewhat incompatible.\n");
+ printf(" If any vertices are jettisoned, you will need the output\n");
+ printf(" .node file to reconstruct the new node indices.");
+ }
+
+#ifndef TRILIBRARY
+ strcpy(b->inpolyfilename, b->innodefilename);
+ strcpy(b->inelefilename, b->innodefilename);
+ strcpy(b->areafilename, b->innodefilename);
+ increment = 0;
+ strcpy(workstring, b->innodefilename);
+ j = 1;
+ while (workstring[j] != '\0') {
+ if ((workstring[j] == '.') && (workstring[j + 1] != '\0')) {
+ increment = j + 1;
+ }
+ j++;
+ }
+ meshnumber = 0;
+ if (increment > 0) {
+ j = increment;
+ do {
+ if ((workstring[j] >= '0') && (workstring[j] <= '9')) {
+ meshnumber = meshnumber * 10 + (int) (workstring[j] - '0');
+ } else {
+ increment = 0;
+ }
+ j++;
+ } while (workstring[j] != '\0');
+ }
+ if (b->noiterationnum) {
+ strcpy(b->outnodefilename, b->innodefilename);
+ strcpy(b->outelefilename, b->innodefilename);
+ strcpy(b->edgefilename, b->innodefilename);
+ strcpy(b->vnodefilename, b->innodefilename);
+ strcpy(b->vedgefilename, b->innodefilename);
+ strcpy(b->neighborfilename, b->innodefilename);
+ strcpy(b->offfilename, b->innodefilename);
+ strcat(b->outnodefilename, ".node");
+ strcat(b->outelefilename, ".ele");
+ strcat(b->edgefilename, ".edge");
+ strcat(b->vnodefilename, ".v.node");
+ strcat(b->vedgefilename, ".v.edge");
+ strcat(b->neighborfilename, ".neigh");
+ strcat(b->offfilename, ".off");
+ } else if (increment == 0) {
+ strcpy(b->outnodefilename, b->innodefilename);
+ strcpy(b->outpolyfilename, b->innodefilename);
+ strcpy(b->outelefilename, b->innodefilename);
+ strcpy(b->edgefilename, b->innodefilename);
+ strcpy(b->vnodefilename, b->innodefilename);
+ strcpy(b->vedgefilename, b->innodefilename);
+ strcpy(b->neighborfilename, b->innodefilename);
+ strcpy(b->offfilename, b->innodefilename);
+ strcat(b->outnodefilename, ".1.node");
+ strcat(b->outpolyfilename, ".1.poly");
+ strcat(b->outelefilename, ".1.ele");
+ strcat(b->edgefilename, ".1.edge");
+ strcat(b->vnodefilename, ".1.v.node");
+ strcat(b->vedgefilename, ".1.v.edge");
+ strcat(b->neighborfilename, ".1.neigh");
+ strcat(b->offfilename, ".1.off");
+ } else {
+ workstring[increment] = '%';
+ workstring[increment + 1] = 'd';
+ workstring[increment + 2] = '\0';
+ sprintf(b->outnodefilename, workstring, meshnumber + 1);
+ strcpy(b->outpolyfilename, b->outnodefilename);
+ strcpy(b->outelefilename, b->outnodefilename);
+ strcpy(b->edgefilename, b->outnodefilename);
+ strcpy(b->vnodefilename, b->outnodefilename);
+ strcpy(b->vedgefilename, b->outnodefilename);
+ strcpy(b->neighborfilename, b->outnodefilename);
+ strcpy(b->offfilename, b->outnodefilename);
+ strcat(b->outnodefilename, ".node");
+ strcat(b->outpolyfilename, ".poly");
+ strcat(b->outelefilename, ".ele");
+ strcat(b->edgefilename, ".edge");
+ strcat(b->vnodefilename, ".v.node");
+ strcat(b->vedgefilename, ".v.edge");
+ strcat(b->neighborfilename, ".neigh");
+ strcat(b->offfilename, ".off");
+ }
+ strcat(b->innodefilename, ".node");
+ strcat(b->inpolyfilename, ".poly");
+ strcat(b->inelefilename, ".ele");
+ strcat(b->areafilename, ".area");
+#endif /* not TRILIBRARY */
+}
+
+/** **/
+/** **/
+/********* User interaction routines begin here *********/
+
+/********* Debugging routines begin here *********/
+/** **/
+/** **/
+
+/*****************************************************************************/
+/* */
+/* printtriangle() Print out the details of an oriented triangle. */
+/* */
+/* I originally wrote this procedure to simplify debugging; it can be */
+/* called directly from the debugger, and presents information about an */
+/* oriented triangle in digestible form. It's also used when the */
+/* highest level of verbosity (`-VVV') is specified. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void printtriangle(struct mesh *m, struct behavior *b, struct otri *t)
+#else /* not ANSI_DECLARATORS */
+void printtriangle(m, b, t)
+struct mesh *m;
+struct behavior *b;
+struct otri *t;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri printtri;
+ struct osub printsh;
+ vertex printvertex;
+
+ printf("triangle x%lx with orientation %d:\n", (unsigned long) t->tri,
+ t->orient);
+ decode(t->tri[0], printtri);
+ if (printtri.tri == m->dummytri) {
+ printf(" [0] = Outer space\n");
+ } else {
+ printf(" [0] = x%lx %d\n", (unsigned long) printtri.tri,
+ printtri.orient);
+ }
+ decode(t->tri[1], printtri);
+ if (printtri.tri == m->dummytri) {
+ printf(" [1] = Outer space\n");
+ } else {
+ printf(" [1] = x%lx %d\n", (unsigned long) printtri.tri,
+ printtri.orient);
+ }
+ decode(t->tri[2], printtri);
+ if (printtri.tri == m->dummytri) {
+ printf(" [2] = Outer space\n");
+ } else {
+ printf(" [2] = x%lx %d\n", (unsigned long) printtri.tri,
+ printtri.orient);
+ }
+
+ org(*t, printvertex);
+ if (printvertex == (vertex) NULL)
+ printf(" Origin[%d] = NULL\n", (t->orient + 1) % 3 + 3);
+ else
+ printf(" Origin[%d] = x%lx (%.12g, %.12g)\n",
+ (t->orient + 1) % 3 + 3, (unsigned long) printvertex,
+ printvertex[0], printvertex[1]);
+ dest(*t, printvertex);
+ if (printvertex == (vertex) NULL)
+ printf(" Dest [%d] = NULL\n", (t->orient + 2) % 3 + 3);
+ else
+ printf(" Dest [%d] = x%lx (%.12g, %.12g)\n",
+ (t->orient + 2) % 3 + 3, (unsigned long) printvertex,
+ printvertex[0], printvertex[1]);
+ apex(*t, printvertex);
+ if (printvertex == (vertex) NULL)
+ printf(" Apex [%d] = NULL\n", t->orient + 3);
+ else
+ printf(" Apex [%d] = x%lx (%.12g, %.12g)\n",
+ t->orient + 3, (unsigned long) printvertex,
+ printvertex[0], printvertex[1]);
+
+ if (b->usesegments) {
+ sdecode(t->tri[6], printsh);
+ if (printsh.ss != m->dummysub) {
+ printf(" [6] = x%lx %d\n", (unsigned long) printsh.ss,
+ printsh.ssorient);
+ }
+ sdecode(t->tri[7], printsh);
+ if (printsh.ss != m->dummysub) {
+ printf(" [7] = x%lx %d\n", (unsigned long) printsh.ss,
+ printsh.ssorient);
+ }
+ sdecode(t->tri[8], printsh);
+ if (printsh.ss != m->dummysub) {
+ printf(" [8] = x%lx %d\n", (unsigned long) printsh.ss,
+ printsh.ssorient);
+ }
+ }
+
+ if (b->vararea) {
+ printf(" Area constraint: %.4g\n", areabound(*t));
+ }
+}
+
+/*****************************************************************************/
+/* */
+/* printsubseg() Print out the details of an oriented subsegment. */
+/* */
+/* I originally wrote this procedure to simplify debugging; it can be */
+/* called directly from the debugger, and presents information about an */
+/* oriented subsegment in digestible form. It's also used when the highest */
+/* level of verbosity (`-VVV') is specified. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void printsubseg(struct mesh *m, struct behavior *b, struct osub *s)
+#else /* not ANSI_DECLARATORS */
+void printsubseg(m, b, s)
+struct mesh *m;
+struct behavior *b;
+struct osub *s;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct osub printsh;
+ struct otri printtri;
+ vertex printvertex;
+
+ printf("subsegment x%lx with orientation %d and mark %d:\n",
+ (unsigned long) s->ss, s->ssorient, mark(*s));
+ sdecode(s->ss[0], printsh);
+ if (printsh.ss == m->dummysub) {
+ printf(" [0] = No subsegment\n");
+ } else {
+ printf(" [0] = x%lx %d\n", (unsigned long) printsh.ss,
+ printsh.ssorient);
+ }
+ sdecode(s->ss[1], printsh);
+ if (printsh.ss == m->dummysub) {
+ printf(" [1] = No subsegment\n");
+ } else {
+ printf(" [1] = x%lx %d\n", (unsigned long) printsh.ss,
+ printsh.ssorient);
+ }
+
+ sorg(*s, printvertex);
+ if (printvertex == (vertex) NULL)
+ printf(" Origin[%d] = NULL\n", 2 + s->ssorient);
+ else
+ printf(" Origin[%d] = x%lx (%.12g, %.12g)\n",
+ 2 + s->ssorient, (unsigned long) printvertex,
+ printvertex[0], printvertex[1]);
+ sdest(*s, printvertex);
+ if (printvertex == (vertex) NULL)
+ printf(" Dest [%d] = NULL\n", 3 - s->ssorient);
+ else
+ printf(" Dest [%d] = x%lx (%.12g, %.12g)\n",
+ 3 - s->ssorient, (unsigned long) printvertex,
+ printvertex[0], printvertex[1]);
+
+ decode(s->ss[6], printtri);
+ if (printtri.tri == m->dummytri) {
+ printf(" [6] = Outer space\n");
+ } else {
+ printf(" [6] = x%lx %d\n", (unsigned long) printtri.tri,
+ printtri.orient);
+ }
+ decode(s->ss[7], printtri);
+ if (printtri.tri == m->dummytri) {
+ printf(" [7] = Outer space\n");
+ } else {
+ printf(" [7] = x%lx %d\n", (unsigned long) printtri.tri,
+ printtri.orient);
+ }
+
+ segorg(*s, printvertex);
+ if (printvertex == (vertex) NULL)
+ printf(" Segment origin[%d] = NULL\n", 4 + s->ssorient);
+ else
+ printf(" Segment origin[%d] = x%lx (%.12g, %.12g)\n",
+ 4 + s->ssorient, (unsigned long) printvertex,
+ printvertex[0], printvertex[1]);
+ segdest(*s, printvertex);
+ if (printvertex == (vertex) NULL)
+ printf(" Segment dest [%d] = NULL\n", 5 - s->ssorient);
+ else
+ printf(" Segment dest [%d] = x%lx (%.12g, %.12g)\n",
+ 5 - s->ssorient, (unsigned long) printvertex,
+ printvertex[0], printvertex[1]);
+}
+
+/** **/
+/** **/
+/********* Debugging routines end here *********/
+
+/********* Memory management routines begin here *********/
+/** **/
+/** **/
+
+/*****************************************************************************/
+/* */
+/* poolzero() Set all of a pool's fields to zero. */
+/* */
+/* This procedure should never be called on a pool that has any memory */
+/* allocated to it, as that memory would leak. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void poolzero(struct memorypool *pool)
+#else /* not ANSI_DECLARATORS */
+void poolzero(pool)
+struct memorypool *pool;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ pool->firstblock = (VOID **) NULL;
+ pool->nowblock = (VOID **) NULL;
+ pool->nextitem = (VOID *) NULL;
+ pool->deaditemstack = (VOID *) NULL;
+ pool->pathblock = (VOID **) NULL;
+ pool->pathitem = (VOID *) NULL;
+ pool->alignbytes = 0;
+ pool->itembytes = 0;
+ pool->itemsperblock = 0;
+ pool->itemsfirstblock = 0;
+ pool->items = 0;
+ pool->maxitems = 0;
+ pool->unallocateditems = 0;
+ pool->pathitemsleft = 0;
+}
+
+/*****************************************************************************/
+/* */
+/* poolrestart() Deallocate all items in a pool. */
+/* */
+/* The pool is returned to its starting state, except that no memory is */
+/* freed to the operating system. Rather, the previously allocated blocks */
+/* are ready to be reused. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void poolrestart(struct memorypool *pool)
+#else /* not ANSI_DECLARATORS */
+void poolrestart(pool)
+struct memorypool *pool;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ unsigned long alignptr;
+
+ pool->items = 0;
+ pool->maxitems = 0;
+
+ /* Set the currently active block. */
+ pool->nowblock = pool->firstblock;
+ /* Find the first item in the pool. Increment by the size of (VOID *). */
+ alignptr = (unsigned long) (pool->nowblock + 1);
+ /* Align the item on an `alignbytes'-byte boundary. */
+ pool->nextitem = (VOID *)
+ (alignptr + (unsigned long) pool->alignbytes -
+ (alignptr % (unsigned long) pool->alignbytes));
+ /* There are lots of unallocated items left in this block. */
+ pool->unallocateditems = pool->itemsfirstblock;
+ /* The stack of deallocated items is empty. */
+ pool->deaditemstack = (VOID *) NULL;
+}
+
+/*****************************************************************************/
+/* */
+/* poolinit() Initialize a pool of memory for allocation of items. */
+/* */
+/* This routine initializes the machinery for allocating items. A `pool' */
+/* is created whose records have size at least `bytecount'. Items will be */
+/* allocated in `itemcount'-item blocks. Each item is assumed to be a */
+/* collection of words, and either pointers or floating-point values are */
+/* assumed to be the "primary" word type. (The "primary" word type is used */
+/* to determine alignment of items.) If `alignment' isn't zero, all items */
+/* will be `alignment'-byte aligned in memory. `alignment' must be either */
+/* a multiple or a factor of the primary word size; powers of two are safe. */
+/* `alignment' is normally used to create a few unused bits at the bottom */
+/* of each item's pointer, in which information may be stored. */
+/* */
+/* Don't change this routine unless you understand it. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void poolinit(struct memorypool *pool, int bytecount, int itemcount,
+ int firstitemcount, int alignment)
+#else /* not ANSI_DECLARATORS */
+void poolinit(pool, bytecount, itemcount, firstitemcount, alignment)
+struct memorypool *pool;
+int bytecount;
+int itemcount;
+int firstitemcount;
+int alignment;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ /* Find the proper alignment, which must be at least as large as: */
+ /* - The parameter `alignment'. */
+ /* - sizeof(VOID *), so the stack of dead items can be maintained */
+ /* without unaligned accesses. */
+ if (alignment > (int)(sizeof(VOID *))) {
+ pool->alignbytes = alignment;
+ } else {
+ pool->alignbytes = sizeof(VOID *);
+ }
+ pool->itembytes = ((bytecount - 1) / pool->alignbytes + 1) *
+ pool->alignbytes;
+ pool->itemsperblock = itemcount;
+ if (firstitemcount == 0) {
+ pool->itemsfirstblock = itemcount;
+ } else {
+ pool->itemsfirstblock = firstitemcount;
+ }
+
+ /* Allocate a block of items. Space for `itemsfirstblock' items and one */
+ /* pointer (to point to the next block) are allocated, as well as space */
+ /* to ensure alignment of the items. */
+ pool->firstblock = (VOID **)
+ trimalloc(pool->itemsfirstblock * pool->itembytes + (int) sizeof(VOID *) +
+ pool->alignbytes);
+ /* Set the next block pointer to NULL. */
+ *(pool->firstblock) = (VOID *) NULL;
+ poolrestart(pool);
+}
+
+/*****************************************************************************/
+/* */
+/* pooldeinit() Free to the operating system all memory taken by a pool. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void pooldeinit(struct memorypool *pool)
+#else /* not ANSI_DECLARATORS */
+void pooldeinit(pool)
+struct memorypool *pool;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ while (pool->firstblock != (VOID **) NULL) {
+ pool->nowblock = (VOID **) *(pool->firstblock);
+ trifree((VOID *) pool->firstblock);
+ pool->firstblock = pool->nowblock;
+ }
+}
+
+/*****************************************************************************/
+/* */
+/* poolalloc() Allocate space for an item. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+VOID *poolalloc(struct memorypool *pool)
+#else /* not ANSI_DECLARATORS */
+VOID *poolalloc(pool)
+struct memorypool *pool;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ VOID *newitem;
+ VOID **newblock;
+ unsigned long alignptr;
+
+ /* First check the linked list of dead items. If the list is not */
+ /* empty, allocate an item from the list rather than a fresh one. */
+ if (pool->deaditemstack != (VOID *) NULL) {
+ newitem = pool->deaditemstack; /* Take first item in list. */
+ pool->deaditemstack = * (VOID **) pool->deaditemstack;
+ } else {
+ /* Check if there are any free items left in the current block. */
+ if (pool->unallocateditems == 0) {
+ /* Check if another block must be allocated. */
+ if (*(pool->nowblock) == (VOID *) NULL) {
+ /* Allocate a new block of items, pointed to by the previous block. */
+ newblock = (VOID **) trimalloc(pool->itemsperblock * pool->itembytes +
+ (int) sizeof(VOID *) +
+ pool->alignbytes);
+ *(pool->nowblock) = (VOID *) newblock;
+ /* The next block pointer is NULL. */
+ *newblock = (VOID *) NULL;
+ }
+
+ /* Move to the new block. */
+ pool->nowblock = (VOID **) *(pool->nowblock);
+ /* Find the first item in the block. */
+ /* Increment by the size of (VOID *). */
+ alignptr = (unsigned long) (pool->nowblock + 1);
+ /* Align the item on an `alignbytes'-byte boundary. */
+ pool->nextitem = (VOID *)
+ (alignptr + (unsigned long) pool->alignbytes -
+ (alignptr % (unsigned long) pool->alignbytes));
+ /* There are lots of unallocated items left in this block. */
+ pool->unallocateditems = pool->itemsperblock;
+ }
+
+ /* Allocate a new item. */
+ newitem = pool->nextitem;
+ /* Advance `nextitem' pointer to next free item in block. */
+ pool->nextitem = (VOID *) ((char *) pool->nextitem + pool->itembytes);
+ pool->unallocateditems--;
+ pool->maxitems++;
+ }
+ pool->items++;
+ return newitem;
+}
+
+/*****************************************************************************/
+/* */
+/* pooldealloc() Deallocate space for an item. */
+/* */
+/* The deallocated space is stored in a queue for later reuse. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void pooldealloc(struct memorypool *pool, VOID *dyingitem)
+#else /* not ANSI_DECLARATORS */
+void pooldealloc(pool, dyingitem)
+struct memorypool *pool;
+VOID *dyingitem;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ /* Push freshly killed item onto stack. */
+ *((VOID **) dyingitem) = pool->deaditemstack;
+ pool->deaditemstack = dyingitem;
+ pool->items--;
+}
+
+/*****************************************************************************/
+/* */
+/* traversalinit() Prepare to traverse the entire list of items. */
+/* */
+/* This routine is used in conjunction with traverse(). */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void traversalinit(struct memorypool *pool)
+#else /* not ANSI_DECLARATORS */
+void traversalinit(pool)
+struct memorypool *pool;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ unsigned long alignptr;
+
+ /* Begin the traversal in the first block. */
+ pool->pathblock = pool->firstblock;
+ /* Find the first item in the block. Increment by the size of (VOID *). */
+ alignptr = (unsigned long) (pool->pathblock + 1);
+ /* Align with item on an `alignbytes'-byte boundary. */
+ pool->pathitem = (VOID *)
+ (alignptr + (unsigned long) pool->alignbytes -
+ (alignptr % (unsigned long) pool->alignbytes));
+ /* Set the number of items left in the current block. */
+ pool->pathitemsleft = pool->itemsfirstblock;
+}
+
+/*****************************************************************************/
+/* */
+/* traverse() Find the next item in the list. */
+/* */
+/* This routine is used in conjunction with traversalinit(). Be forewarned */
+/* that this routine successively returns all items in the list, including */
+/* deallocated ones on the deaditemqueue. It's up to you to figure out */
+/* which ones are actually dead. Why? I don't want to allocate extra */
+/* space just to demarcate dead items. It can usually be done more */
+/* space-efficiently by a routine that knows something about the structure */
+/* of the item. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+VOID *traverse(struct memorypool *pool)
+#else /* not ANSI_DECLARATORS */
+VOID *traverse(pool)
+struct memorypool *pool;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ VOID *newitem;
+ unsigned long alignptr;
+
+ /* Stop upon exhausting the list of items. */
+ if (pool->pathitem == pool->nextitem) {
+ return (VOID *) NULL;
+ }
+
+ /* Check whether any untraversed items remain in the current block. */
+ if (pool->pathitemsleft == 0) {
+ /* Find the next block. */
+ pool->pathblock = (VOID **) *(pool->pathblock);
+ /* Find the first item in the block. Increment by the size of (VOID *). */
+ alignptr = (unsigned long) (pool->pathblock + 1);
+ /* Align with item on an `alignbytes'-byte boundary. */
+ pool->pathitem = (VOID *)
+ (alignptr + (unsigned long) pool->alignbytes -
+ (alignptr % (unsigned long) pool->alignbytes));
+ /* Set the number of items left in the current block. */
+ pool->pathitemsleft = pool->itemsperblock;
+ }
+
+ newitem = pool->pathitem;
+ /* Find the next item in the block. */
+ pool->pathitem = (VOID *) ((char *) pool->pathitem + pool->itembytes);
+ pool->pathitemsleft--;
+ return newitem;
+}
+
+/*****************************************************************************/
+/* */
+/* dummyinit() Initialize the triangle that fills "outer space" and the */
+/* omnipresent subsegment. */
+/* */
+/* The triangle that fills "outer space," called `dummytri', is pointed to */
+/* by every triangle and subsegment on a boundary (be it outer or inner) of */
+/* the triangulation. Also, `dummytri' points to one of the triangles on */
+/* the convex hull (until the holes and concavities are carved), making it */
+/* possible to find a starting triangle for point location. */
+/* */
+/* The omnipresent subsegment, `dummysub', is pointed to by every triangle */
+/* or subsegment that doesn't have a full complement of real subsegments */
+/* to point to. */
+/* */
+/* `dummytri' and `dummysub' are generally required to fulfill only a few */
+/* invariants: their vertices must remain NULL and `dummytri' must always */
+/* be bonded (at offset zero) to some triangle on the convex hull of the */
+/* mesh, via a boundary edge. Otherwise, the connections of `dummytri' and */
+/* `dummysub' may change willy-nilly. This makes it possible to avoid */
+/* writing a good deal of special-case code (in the edge flip, for example) */
+/* for dealing with the boundary of the mesh, places where no subsegment is */
+/* present, and so forth. Other entities are frequently bonded to */
+/* `dummytri' and `dummysub' as if they were real mesh entities, with no */
+/* harm done. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void dummyinit(struct mesh *m, struct behavior *b, int trianglebytes,
+ int subsegbytes)
+#else /* not ANSI_DECLARATORS */
+void dummyinit(m, b, trianglebytes, subsegbytes)
+struct mesh *m;
+struct behavior *b;
+int trianglebytes;
+int subsegbytes;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ unsigned long alignptr;
+
+ /* Set up `dummytri', the `triangle' that occupies "outer space." */
+ m->dummytribase = (triangle *) trimalloc(trianglebytes +
+ m->triangles.alignbytes);
+ /* Align `dummytri' on a `triangles.alignbytes'-byte boundary. */
+ alignptr = (unsigned long) m->dummytribase;
+ m->dummytri = (triangle *)
+ (alignptr + (unsigned long) m->triangles.alignbytes -
+ (alignptr % (unsigned long) m->triangles.alignbytes));
+ /* Initialize the three adjoining triangles to be "outer space." These */
+ /* will eventually be changed by various bonding operations, but their */
+ /* values don't really matter, as long as they can legally be */
+ /* dereferenced. */
+ m->dummytri[0] = (triangle) m->dummytri;
+ m->dummytri[1] = (triangle) m->dummytri;
+ m->dummytri[2] = (triangle) m->dummytri;
+ /* Three NULL vertices. */
+ m->dummytri[3] = (triangle) NULL;
+ m->dummytri[4] = (triangle) NULL;
+ m->dummytri[5] = (triangle) NULL;
+
+ if (b->usesegments) {
+ /* Set up `dummysub', the omnipresent subsegment pointed to by any */
+ /* triangle side or subsegment end that isn't attached to a real */
+ /* subsegment. */
+ m->dummysubbase = (subseg *) trimalloc(subsegbytes +
+ m->subsegs.alignbytes);
+ /* Align `dummysub' on a `subsegs.alignbytes'-byte boundary. */
+ alignptr = (unsigned long) m->dummysubbase;
+ m->dummysub = (subseg *)
+ (alignptr + (unsigned long) m->subsegs.alignbytes -
+ (alignptr % (unsigned long) m->subsegs.alignbytes));
+ /* Initialize the two adjoining subsegments to be the omnipresent */
+ /* subsegment. These will eventually be changed by various bonding */
+ /* operations, but their values don't really matter, as long as they */
+ /* can legally be dereferenced. */
+ m->dummysub[0] = (subseg) m->dummysub;
+ m->dummysub[1] = (subseg) m->dummysub;
+ /* Four NULL vertices. */
+ m->dummysub[2] = (subseg) NULL;
+ m->dummysub[3] = (subseg) NULL;
+ m->dummysub[4] = (subseg) NULL;
+ m->dummysub[5] = (subseg) NULL;
+ /* Initialize the two adjoining triangles to be "outer space." */
+ m->dummysub[6] = (subseg) m->dummytri;
+ m->dummysub[7] = (subseg) m->dummytri;
+ /* Set the boundary marker to zero. */
+ * (int *) (m->dummysub + 8) = 0;
+
+ /* Initialize the three adjoining subsegments of `dummytri' to be */
+ /* the omnipresent subsegment. */
+ m->dummytri[6] = (triangle) m->dummysub;
+ m->dummytri[7] = (triangle) m->dummysub;
+ m->dummytri[8] = (triangle) m->dummysub;
+ }
+}
+
+/*****************************************************************************/
+/* */
+/* initializevertexpool() Calculate the size of the vertex data structure */
+/* and initialize its memory pool. */
+/* */
+/* This routine also computes the `vertexmarkindex' and `vertex2triindex' */
+/* indices used to find values within each vertex. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void initializevertexpool(struct mesh *m, struct behavior *b)
+#else /* not ANSI_DECLARATORS */
+void initializevertexpool(m, b)
+struct mesh *m;
+struct behavior *b;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ int vertexsize;
+
+ /* The index within each vertex at which the boundary marker is found, */
+ /* followed by the vertex type. Ensure the vertex marker is aligned to */
+ /* a sizeof(int)-byte address. */
+ m->vertexmarkindex = ((m->mesh_dim + m->nextras) * sizeof(tREAL) +
+ sizeof(int) - 1) /
+ sizeof(int);
+ vertexsize = (m->vertexmarkindex + 2) * sizeof(int);
+ if (b->poly) {
+ /* The index within each vertex at which a triangle pointer is found. */
+ /* Ensure the pointer is aligned to a sizeof(triangle)-byte address. */
+ m->vertex2triindex = (vertexsize + sizeof(triangle) - 1) /
+ sizeof(triangle);
+ vertexsize = (m->vertex2triindex + 1) * sizeof(triangle);
+ }
+
+ /* Initialize the pool of vertices. */
+ poolinit(&m->vertices, vertexsize, VERTEXPERBLOCK,
+ m->invertices > VERTEXPERBLOCK ? m->invertices : VERTEXPERBLOCK,
+ sizeof(tREAL));
+}
+
+/*****************************************************************************/
+/* */
+/* initializetrisubpools() Calculate the sizes of the triangle and */
+/* subsegment data structures and initialize */
+/* their memory pools. */
+/* */
+/* This routine also computes the `highorderindex', `elemattribindex', and */
+/* `areaboundindex' indices used to find values within each triangle. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void initializetrisubpools(struct mesh *m, struct behavior *b)
+#else /* not ANSI_DECLARATORS */
+void initializetrisubpools(m, b)
+struct mesh *m;
+struct behavior *b;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ unsigned int trisize;
+
+ /* The index within each triangle at which the extra nodes (above three) */
+ /* associated with high order elements are found. There are three */
+ /* pointers to other triangles, three pointers to corners, and possibly */
+ /* three pointers to subsegments before the extra nodes. */
+ m->highorderindex = 6 + (b->usesegments * 3);
+ /* The number of bytes occupied by a triangle. */
+ trisize = ((b->order + 1) * (b->order + 2) / 2 + (m->highorderindex - 3)) *
+ sizeof(triangle);
+ /* The index within each triangle at which its attributes are found, */
+ /* where the index is measured in tREALs. */
+ m->elemattribindex = (trisize + sizeof(tREAL) - 1) / sizeof(tREAL);
+ /* The index within each triangle at which the maximum area constraint */
+ /* is found, where the index is measured in tREALs. Note that if the */
+ /* `regionattrib' flag is set, an additional attribute will be added. */
+ m->areaboundindex = m->elemattribindex + m->eextras + b->regionattrib;
+ /* If triangle attributes or an area bound are needed, increase the number */
+ /* of bytes occupied by a triangle. */
+ if (b->vararea) {
+ trisize = (m->areaboundindex + 1) * sizeof(tREAL);
+ } else if (m->eextras + b->regionattrib > 0) {
+ trisize = m->areaboundindex * sizeof(tREAL);
+ }
+ /* If a Voronoi diagram or triangle neighbor graph is requested, make */
+ /* sure there's room to store an integer index in each triangle. This */
+ /* integer index can occupy the same space as the subsegment pointers */
+ /* or attributes or area constraint or extra nodes. */
+ if ((b->voronoi || b->neighbors) &&
+ (trisize < 6 * sizeof(triangle) + sizeof(int))) {
+ trisize = 6 * sizeof(triangle) + sizeof(int);
+ }
+
+ /* Having determined the memory size of a triangle, initialize the pool. */
+ poolinit(&m->triangles, trisize, TRIPERBLOCK,
+ (2 * m->invertices - 2) > TRIPERBLOCK ? (2 * m->invertices - 2) :
+ TRIPERBLOCK, 4);
+
+ if (b->usesegments) {
+ /* Initialize the pool of subsegments. Take into account all eight */
+ /* pointers and one boundary marker. */
+ poolinit(&m->subsegs, 8 * sizeof(triangle) + sizeof(int),
+ SUBSEGPERBLOCK, SUBSEGPERBLOCK, 4);
+
+ /* Initialize the "outer space" triangle and omnipresent subsegment. */
+ dummyinit(m, b, m->triangles.itembytes, m->subsegs.itembytes);
+ } else {
+ /* Initialize the "outer space" triangle. */
+ dummyinit(m, b, m->triangles.itembytes, 0);
+ }
+}
+
+/*****************************************************************************/
+/* */
+/* triangledealloc() Deallocate space for a triangle, marking it dead. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void triangledealloc(struct mesh *m, triangle *dyingtriangle)
+#else /* not ANSI_DECLARATORS */
+void triangledealloc(m, dyingtriangle)
+struct mesh *m;
+triangle *dyingtriangle;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ /* Mark the triangle as dead. This makes it possible to detect dead */
+ /* triangles when traversing the list of all triangles. */
+ killtri(dyingtriangle);
+ pooldealloc(&m->triangles, (VOID *) dyingtriangle);
+}
+
+/*****************************************************************************/
+/* */
+/* triangletraverse() Traverse the triangles, skipping dead ones. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+triangle *triangletraverse(struct mesh *m)
+#else /* not ANSI_DECLARATORS */
+triangle *triangletraverse(m)
+struct mesh *m;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ triangle *newtriangle;
+
+ do {
+ newtriangle = (triangle *) traverse(&m->triangles);
+ if (newtriangle == (triangle *) NULL) {
+ return (triangle *) NULL;
+ }
+ } while (deadtri(newtriangle)); /* Skip dead ones. */
+ return newtriangle;
+}
+
+/*****************************************************************************/
+/* */
+/* subsegdealloc() Deallocate space for a subsegment, marking it dead. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void subsegdealloc(struct mesh *m, subseg *dyingsubseg)
+#else /* not ANSI_DECLARATORS */
+void subsegdealloc(m, dyingsubseg)
+struct mesh *m;
+subseg *dyingsubseg;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ /* Mark the subsegment as dead. This makes it possible to detect dead */
+ /* subsegments when traversing the list of all subsegments. */
+ killsubseg(dyingsubseg);
+ pooldealloc(&m->subsegs, (VOID *) dyingsubseg);
+}
+
+/*****************************************************************************/
+/* */
+/* subsegtraverse() Traverse the subsegments, skipping dead ones. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+subseg *subsegtraverse(struct mesh *m)
+#else /* not ANSI_DECLARATORS */
+subseg *subsegtraverse(m)
+struct mesh *m;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ subseg *newsubseg;
+
+ do {
+ newsubseg = (subseg *) traverse(&m->subsegs);
+ if (newsubseg == (subseg *) NULL) {
+ return (subseg *) NULL;
+ }
+ } while (deadsubseg(newsubseg)); /* Skip dead ones. */
+ return newsubseg;
+}
+
+/*****************************************************************************/
+/* */
+/* vertexdealloc() Deallocate space for a vertex, marking it dead. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void vertexdealloc(struct mesh *m, vertex dyingvertex)
+#else /* not ANSI_DECLARATORS */
+void vertexdealloc(m, dyingvertex)
+struct mesh *m;
+vertex dyingvertex;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ /* Mark the vertex as dead. This makes it possible to detect dead */
+ /* vertices when traversing the list of all vertices. */
+ setvertextype(dyingvertex, DEADVERTEX);
+ pooldealloc(&m->vertices, (VOID *) dyingvertex);
+}
+
+/*****************************************************************************/
+/* */
+/* vertextraverse() Traverse the vertices, skipping dead ones. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+vertex vertextraverse(struct mesh *m)
+#else /* not ANSI_DECLARATORS */
+vertex vertextraverse(m)
+struct mesh *m;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ vertex newvertex;
+
+ do {
+ newvertex = (vertex) traverse(&m->vertices);
+ if (newvertex == (vertex) NULL) {
+ return (vertex) NULL;
+ }
+ } while (vertextype(newvertex) == DEADVERTEX); /* Skip dead ones. */
+ return newvertex;
+}
+
+/*****************************************************************************/
+/* */
+/* badsubsegdealloc() Deallocate space for a bad subsegment, marking it */
+/* dead. */
+/* */
+/*****************************************************************************/
+
+#ifndef CDT_ONLY
+
+#ifdef ANSI_DECLARATORS
+void badsubsegdealloc(struct mesh *m, struct badsubseg *dyingseg)
+#else /* not ANSI_DECLARATORS */
+void badsubsegdealloc(m, dyingseg)
+struct mesh *m;
+struct badsubseg *dyingseg;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ /* Set subsegment's origin to NULL. This makes it possible to detect dead */
+ /* badsubsegs when traversing the list of all badsubsegs . */
+ dyingseg->subsegorg = (vertex) NULL;
+ pooldealloc(&m->badsubsegs, (VOID *) dyingseg);
+}
+
+#endif /* not CDT_ONLY */
+
+/*****************************************************************************/
+/* */
+/* badsubsegtraverse() Traverse the bad subsegments, skipping dead ones. */
+/* */
+/*****************************************************************************/
+
+#ifndef CDT_ONLY
+
+#ifdef ANSI_DECLARATORS
+struct badsubseg *badsubsegtraverse(struct mesh *m)
+#else /* not ANSI_DECLARATORS */
+struct badsubseg *badsubsegtraverse(m)
+struct mesh *m;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct badsubseg *newseg;
+
+ do {
+ newseg = (struct badsubseg *) traverse(&m->badsubsegs);
+ if (newseg == (struct badsubseg *) NULL) {
+ return (struct badsubseg *) NULL;
+ }
+ } while (newseg->subsegorg == (vertex) NULL); /* Skip dead ones. */
+ return newseg;
+}
+
+#endif /* not CDT_ONLY */
+
+/*****************************************************************************/
+/* */
+/* getvertex() Get a specific vertex, by number, from the list. */
+/* */
+/* The first vertex is number 'firstnumber'. */
+/* */
+/* Note that this takes O(n) time (with a small constant, if VERTEXPERBLOCK */
+/* is large). I don't care to take the trouble to make it work in constant */
+/* time. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+vertex getvertex(struct mesh *m, struct behavior *b, int number)
+#else /* not ANSI_DECLARATORS */
+vertex getvertex(m, b, number)
+struct mesh *m;
+struct behavior *b;
+int number;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ VOID **getblock;
+ char *foundvertex;
+ unsigned long alignptr;
+ int current;
+
+ getblock = m->vertices.firstblock;
+ current = b->firstnumber;
+
+ /* Find the right block. */
+ if (current + m->vertices.itemsfirstblock <= number) {
+ getblock = (VOID **) *getblock;
+ current += m->vertices.itemsfirstblock;
+ while (current + m->vertices.itemsperblock <= number) {
+ getblock = (VOID **) *getblock;
+ current += m->vertices.itemsperblock;
+ }
+ }
+
+ /* Now find the right vertex. */
+ alignptr = (unsigned long) (getblock + 1);
+ foundvertex = (char *) (alignptr + (unsigned long) m->vertices.alignbytes -
+ (alignptr % (unsigned long) m->vertices.alignbytes));
+ return (vertex) (foundvertex + m->vertices.itembytes * (number - current));
+}
+
+/*****************************************************************************/
+/* */
+/* triangledeinit() Free all remaining allocated memory. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void triangledeinit(struct mesh *m, struct behavior *b)
+#else /* not ANSI_DECLARATORS */
+void triangledeinit(m, b)
+struct mesh *m;
+struct behavior *b;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ pooldeinit(&m->triangles);
+ trifree((VOID *) m->dummytribase);
+ if (b->usesegments) {
+ pooldeinit(&m->subsegs);
+ trifree((VOID *) m->dummysubbase);
+ }
+ pooldeinit(&m->vertices);
+#ifndef CDT_ONLY
+ if (b->quality) {
+ pooldeinit(&m->badsubsegs);
+ if ((b->minangle > 0.0f) || b->vararea || b->fixedarea || b->usertest) {
+ pooldeinit(&m->badtriangles);
+ pooldeinit(&m->flipstackers);
+ }
+ }
+#endif /* not CDT_ONLY */
+}
+
+/** **/
+/** **/
+/********* Memory management routines end here *********/
+
+/********* Constructors begin here *********/
+/** **/
+/** **/
+
+/*****************************************************************************/
+/* */
+/* maketriangle() Create a new triangle with orientation zero. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void maketriangle(struct mesh *m, struct behavior *b, struct otri *newotri)
+#else /* not ANSI_DECLARATORS */
+void maketriangle(m, b, newotri)
+struct mesh *m;
+struct behavior *b;
+struct otri *newotri;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ int i;
+
+ newotri->tri = (triangle *) poolalloc(&m->triangles);
+ /* Initialize the three adjoining triangles to be "outer space". */
+ newotri->tri[0] = (triangle) m->dummytri;
+ newotri->tri[1] = (triangle) m->dummytri;
+ newotri->tri[2] = (triangle) m->dummytri;
+ /* Three NULL vertices. */
+ newotri->tri[3] = (triangle) NULL;
+ newotri->tri[4] = (triangle) NULL;
+ newotri->tri[5] = (triangle) NULL;
+ if (b->usesegments) {
+ /* Initialize the three adjoining subsegments to be the omnipresent */
+ /* subsegment. */
+ newotri->tri[6] = (triangle) m->dummysub;
+ newotri->tri[7] = (triangle) m->dummysub;
+ newotri->tri[8] = (triangle) m->dummysub;
+ }
+ for (i = 0; i < m->eextras; i++) {
+ setelemattribute(*newotri, i, 0.0f);
+ }
+ if (b->vararea) {
+ setareabound(*newotri, -1.0f);
+ }
+
+ newotri->orient = 0;
+}
+
+/*****************************************************************************/
+/* */
+/* makesubseg() Create a new subsegment with orientation zero. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void makesubseg(struct mesh *m, struct osub *newsubseg)
+#else /* not ANSI_DECLARATORS */
+void makesubseg(m, newsubseg)
+struct mesh *m;
+struct osub *newsubseg;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ newsubseg->ss = (subseg *) poolalloc(&m->subsegs);
+ /* Initialize the two adjoining subsegments to be the omnipresent */
+ /* subsegment. */
+ newsubseg->ss[0] = (subseg) m->dummysub;
+ newsubseg->ss[1] = (subseg) m->dummysub;
+ /* Four NULL vertices. */
+ newsubseg->ss[2] = (subseg) NULL;
+ newsubseg->ss[3] = (subseg) NULL;
+ newsubseg->ss[4] = (subseg) NULL;
+ newsubseg->ss[5] = (subseg) NULL;
+ /* Initialize the two adjoining triangles to be "outer space." */
+ newsubseg->ss[6] = (subseg) m->dummytri;
+ newsubseg->ss[7] = (subseg) m->dummytri;
+ /* Set the boundary marker to zero. */
+ setmark(*newsubseg, 0);
+
+ newsubseg->ssorient = 0;
+}
+
+/** **/
+/** **/
+/********* Constructors end here *********/
+
+/********* Geometric primitives begin here *********/
+/** **/
+/** **/
+
+/* The adaptive exact arithmetic geometric predicates implemented herein are */
+/* described in detail in my paper, "Adaptive Precision Floating-Point */
+/* Arithmetic and Fast Robust Geometric Predicates." See the header for a */
+/* full citation. */
+
+/* Which of the following two methods of finding the absolute values is */
+/* fastest is compiler-dependent. A few compilers can inline and optimize */
+/* the fabs() call; but most will incur the overhead of a function call, */
+/* which is disastrously slow. A faster way on IEEE machines might be to */
+/* mask the appropriate bit, but that's difficult to do in C without */
+/* forcing the value to be stored to memory (rather than be kept in the */
+/* register to which the optimizer = vec3ed it). */
+
+#define Absolute(a) ((a) >= 0.0 ? (a) : -(a))
+/* #define Absolute(a) fabs(a) */
+
+/* Many of the operations are broken up into two pieces, a main part that */
+/* performs an approximate operation, and a "tail" that computes the */
+/* roundoff error of that operation. */
+/* */
+/* The operations Fast_Two_Sum(), Fast_Two_Diff(), Two_Sum(), Two_Diff(), */
+/* Split(), and Two_Product() are all implemented as described in the */
+/* reference. Each of these macros requires certain variables to be */
+/* defined in the calling routine. The variables `bvirt', `c', `abig', */
+/* `_i', `_j', `_k', `_l', `_m', and `_n' are declared `INEXACT' because */
+/* they store the result of an operation that may incur roundoff error. */
+/* The input parameter `x' (or the highest numbered `x_' parameter) must */
+/* also be declared `INEXACT'. */
+
+#define Fast_Two_Sum_Tail(a, b, x, y) \
+ bvirt = x - a; \
+ y = b - bvirt
+
+#define Fast_Two_Sum(a, b, x, y) \
+ x = (tREAL) (a + b); \
+ Fast_Two_Sum_Tail(a, b, x, y)
+
+#define Two_Sum_Tail(a, b, x, y) \
+ bvirt = (tREAL) (x - a); \
+ avirt = x - bvirt; \
+ bround = b - bvirt; \
+ around = a - avirt; \
+ y = around + bround
+
+#define Two_Sum(a, b, x, y) \
+ x = (tREAL) (a + b); \
+ Two_Sum_Tail(a, b, x, y)
+
+#define Two_Diff_Tail(a, b, x, y) \
+ bvirt = (tREAL) (a - x); \
+ avirt = x + bvirt; \
+ bround = bvirt - b; \
+ around = a - avirt; \
+ y = around + bround
+
+#define Two_Diff(a, b, x, y) \
+ x = (tREAL) (a - b); \
+ Two_Diff_Tail(a, b, x, y)
+
+#define Split(a, ahi, alo) \
+ c = (tREAL) (splitter * a); \
+ abig = (tREAL) (c - a); \
+ ahi = c - abig; \
+ alo = a - ahi
+
+#define Two_Product_Tail(a, b, x, y) \
+ Split(a, ahi, alo); \
+ Split(b, bhi, blo); \
+ err1 = x - (ahi * bhi); \
+ err2 = err1 - (alo * bhi); \
+ err3 = err2 - (ahi * blo); \
+ y = (alo * blo) - err3
+
+#define Two_Product(a, b, x, y) \
+ x = (tREAL) (a * b); \
+ Two_Product_Tail(a, b, x, y)
+
+/* Two_Product_Presplit() is Two_Product() where one of the inputs has */
+/* already been split. Avoids redundant splitting. */
+
+#define Two_Product_Presplit(a, b, bhi, blo, x, y) \
+ x = (tREAL) (a * b); \
+ Split(a, ahi, alo); \
+ err1 = x - (ahi * bhi); \
+ err2 = err1 - (alo * bhi); \
+ err3 = err2 - (ahi * blo); \
+ y = (alo * blo) - err3
+
+/* Square() can be done more quickly than Two_Product(). */
+
+#define Square_Tail(a, x, y) \
+ Split(a, ahi, alo); \
+ err1 = x - (ahi * ahi); \
+ err3 = err1 - ((ahi + ahi) * alo); \
+ y = (alo * alo) - err3
+
+#define Square(a, x, y) \
+ x = (tREAL) (a * a); \
+ Square_Tail(a, x, y)
+
+/* Macros for summing expansions of various fixed lengths. These are all */
+/* unrolled versions of Expansion_Sum(). */
+
+#define Two_One_Sum(a1, a0, b, x2, x1, x0) \
+ Two_Sum(a0, b , _i, x0); \
+ Two_Sum(a1, _i, x2, x1)
+
+#define Two_One_Diff(a1, a0, b, x2, x1, x0) \
+ Two_Diff(a0, b , _i, x0); \
+ Two_Sum( a1, _i, x2, x1)
+
+#define Two_Two_Sum(a1, a0, b1, b0, x3, x2, x1, x0) \
+ Two_One_Sum(a1, a0, b0, _j, _0, x0); \
+ Two_One_Sum(_j, _0, b1, x3, x2, x1)
+
+#define Two_Two_Diff(a1, a0, b1, b0, x3, x2, x1, x0) \
+ Two_One_Diff(a1, a0, b0, _j, _0, x0); \
+ Two_One_Diff(_j, _0, b1, x3, x2, x1)
+
+/* Macro for multiplying a two-component expansion by a single component. */
+
+#define Two_One_Product(a1, a0, b, x3, x2, x1, x0) \
+ Split(b, bhi, blo); \
+ Two_Product_Presplit(a0, b, bhi, blo, _i, x0); \
+ Two_Product_Presplit(a1, b, bhi, blo, _j, _0); \
+ Two_Sum(_i, _0, _k, x1); \
+ Fast_Two_Sum(_j, _k, x3, x2)
+
+/*****************************************************************************/
+/* */
+/* exactinit() Initialize the variables used for exact arithmetic. */
+/* */
+/* `epsilon' is the largest power of two such that 1.0 + epsilon = 1.0 in */
+/* floating-point arithmetic. `epsilon' bounds the relative roundoff */
+/* error. It is used for floating-point error analysis. */
+/* */
+/* `splitter' is used to split floating-point numbers into two half- */
+/* length significands for exact multiplication. */
+/* */
+/* I imagine that a highly optimizing compiler might be too smart for its */
+/* own good, and somehow cause this routine to fail, if it pretends that */
+/* floating-point arithmetic is too much like real arithmetic. */
+/* */
+/* Don't change this routine unless you fully understand it. */
+/* */
+/*****************************************************************************/
+
+void exactinit()
+{
+ tREAL half;
+ tREAL check, lastcheck;
+ int every_other;
+#ifdef LINUX
+ int cword;
+#endif /* LINUX */
+
+#ifdef CPU86
+#ifdef SINGLE
+ _control87(_PC_24, _MCW_PC); /* Set FPU control word for single precision. */
+#else /* not SINGLE */
+ _control87(_PC_53, _MCW_PC); /* Set FPU control word for double precision. */
+#endif /* not SINGLE */
+#endif /* CPU86 */
+#ifdef LINUX
+#ifdef SINGLE
+ /* cword = 4223; */
+ cword = 4210; /* set FPU control word for single precision */
+#else /* not SINGLE */
+ /* cword = 4735; */
+ cword = 4722; /* set FPU control word for double precision */
+#endif /* not SINGLE */
+ _FPU_SETCW(cword);
+#endif /* LINUX */
+
+ every_other = 1;
+ half = 0.5f;
+ epsilon = 1.0f;
+ splitter = 1.0f;
+ check = 1.0f;
+ /* Repeatedly divide `epsilon' by two until it is too small to add to */
+ /* one without causing roundoff. (Also check if the sum is equal to */
+ /* the previous sum, for machines that round up instead of using exact */
+ /* rounding. Not that these routines will work on such machines.) */
+ do {
+ lastcheck = check;
+ epsilon *= half;
+ if (every_other) {
+ splitter *= 2.0f;
+ }
+ every_other = !every_other;
+ check = 1.0 + epsilon;
+ } while ((check != 1.0f) && (check != lastcheck));
+ splitter += 1.0f;
+ /* Error bounds for orientation and incircle tests. */
+ resulterrbound = (3.0 + 8.0 * epsilon) * epsilon;
+ ccwerrboundA = (3.0 + 16.0 * epsilon) * epsilon;
+ ccwerrboundB = (2.0 + 12.0 * epsilon) * epsilon;
+ ccwerrboundC = (9.0 + 64.0 * epsilon) * epsilon * epsilon;
+ iccerrboundA = (10.0 + 96.0 * epsilon) * epsilon;
+ iccerrboundB = (4.0 + 48.0 * epsilon) * epsilon;
+ iccerrboundC = (44.0 + 576.0 * epsilon) * epsilon * epsilon;
+ o3derrboundA = (7.0 + 56.0 * epsilon) * epsilon;
+ o3derrboundB = (3.0 + 28.0 * epsilon) * epsilon;
+ o3derrboundC = (26.0 + 288.0 * epsilon) * epsilon * epsilon;
+}
+
+/*****************************************************************************/
+/* */
+/* fast_expansion_sum_zeroelim() Sum two expansions, eliminating zero */
+/* components from the output expansion. */
+/* */
+/* Sets h = e + f. See my Robust Predicates paper for details. */
+/* */
+/* If round-to-even is used (as with IEEE 754), maintains the strongly */
+/* nonoverlapping property. (That is, if e is strongly nonoverlapping, h */
+/* will be also.) Does NOT maintain the nonoverlapping or nonadjacent */
+/* properties. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+int fast_expansion_sum_zeroelim(int elen, tREAL *e, int flen, tREAL *f, tREAL *h)
+#else /* not ANSI_DECLARATORS */
+int fast_expansion_sum_zeroelim(elen, e, flen, f, h) /* h cannot be e or f. */
+int elen;
+tREAL *e;
+int flen;
+tREAL *f;
+tREAL *h;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ tREAL Q;
+ INEXACT tREAL Qnew;
+ INEXACT tREAL hh;
+ INEXACT tREAL bvirt;
+ tREAL avirt, bround, around;
+ int eindex, findex, hindex;
+ tREAL enow, fnow;
+
+ enow = e[0];
+ fnow = f[0];
+ eindex = findex = 0;
+ if ((fnow > enow) == (fnow > -enow)) {
+ Q = enow;
+ enow = e[++eindex];
+ } else {
+ Q = fnow;
+ fnow = f[++findex];
+ }
+ hindex = 0;
+ if ((eindex < elen) && (findex < flen)) {
+ if ((fnow > enow) == (fnow > -enow)) {
+ Fast_Two_Sum(enow, Q, Qnew, hh);
+ enow = e[++eindex];
+ } else {
+ Fast_Two_Sum(fnow, Q, Qnew, hh);
+ fnow = f[++findex];
+ }
+ Q = Qnew;
+ if (hh != 0.0f) {
+ h[hindex++] = hh;
+ }
+ while ((eindex < elen) && (findex < flen)) {
+ if ((fnow > enow) == (fnow > -enow)) {
+ Two_Sum(Q, enow, Qnew, hh);
+ enow = e[++eindex];
+ } else {
+ Two_Sum(Q, fnow, Qnew, hh);
+ fnow = f[++findex];
+ }
+ Q = Qnew;
+ if (hh != 0.0f) {
+ h[hindex++] = hh;
+ }
+ }
+ }
+ while (eindex < elen) {
+ Two_Sum(Q, enow, Qnew, hh);
+ enow = e[++eindex];
+ Q = Qnew;
+ if (hh != 0.0f) {
+ h[hindex++] = hh;
+ }
+ }
+ while (findex < flen) {
+ Two_Sum(Q, fnow, Qnew, hh);
+ fnow = f[++findex];
+ Q = Qnew;
+ if (hh != 0.0f) {
+ h[hindex++] = hh;
+ }
+ }
+ if ((Q != 0.0f) || (hindex == 0)) {
+ h[hindex++] = Q;
+ }
+ return hindex;
+}
+
+/*****************************************************************************/
+/* */
+/* scale_expansion_zeroelim() Multiply an expansion by a scalar, */
+/* eliminating zero components from the */
+/* output expansion. */
+/* */
+/* Sets h = be. See my Robust Predicates paper for details. */
+/* */
+/* Maintains the nonoverlapping property. If round-to-even is used (as */
+/* with IEEE 754), maintains the strongly nonoverlapping and nonadjacent */
+/* properties as well. (That is, if e has one of these properties, so */
+/* will h.) */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+int scale_expansion_zeroelim(int elen, tREAL *e, tREAL b, tREAL *h)
+#else /* not ANSI_DECLARATORS */
+int scale_expansion_zeroelim(elen, e, b, h) /* e and h cannot be the same. */
+int elen;
+tREAL *e;
+tREAL b;
+tREAL *h;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ INEXACT tREAL Q, sum;
+ tREAL hh;
+ INEXACT tREAL product1;
+ tREAL product0;
+ int eindex, hindex;
+ tREAL enow;
+ INEXACT tREAL bvirt;
+ tREAL avirt, bround, around;
+ INEXACT tREAL c;
+ INEXACT tREAL abig;
+ tREAL ahi, alo, bhi, blo;
+ tREAL err1, err2, err3;
+
+ Split(b, bhi, blo);
+ Two_Product_Presplit(e[0], b, bhi, blo, Q, hh);
+ hindex = 0;
+ if (hh != 0) {
+ h[hindex++] = hh;
+ }
+ for (eindex = 1; eindex < elen; eindex++) {
+ enow = e[eindex];
+ Two_Product_Presplit(enow, b, bhi, blo, product1, product0);
+ Two_Sum(Q, product0, sum, hh);
+ if (hh != 0) {
+ h[hindex++] = hh;
+ }
+ Fast_Two_Sum(product1, sum, Q, hh);
+ if (hh != 0) {
+ h[hindex++] = hh;
+ }
+ }
+ if ((Q != 0.0f) || (hindex == 0)) {
+ h[hindex++] = Q;
+ }
+ return hindex;
+}
+
+/*****************************************************************************/
+/* */
+/* estimate() Produce a one-word estimate of an expansion's value. */
+/* */
+/* See my Robust Predicates paper for details. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+tREAL estimate(int elen, tREAL *e)
+#else /* not ANSI_DECLARATORS */
+tREAL estimate(elen, e)
+int elen;
+tREAL *e;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ tREAL Q;
+ int eindex;
+
+ Q = e[0];
+ for (eindex = 1; eindex < elen; eindex++) {
+ Q += e[eindex];
+ }
+ return Q;
+}
+
+/*****************************************************************************/
+/* */
+/* counterclockwise() Return a positive value if the points pa, pb, and */
+/* pc occur in counterclockwise order; a negative */
+/* value if they occur in clockwise order; and zero */
+/* if they are collinear. The result is also a rough */
+/* approximation of twice the signed area of the */
+/* triangle defined by the three points. */
+/* */
+/* Uses exact arithmetic if necessary to ensure a correct answer. The */
+/* result returned is the determinant of a matrix. This determinant is */
+/* computed adaptively, in the sense that exact arithmetic is used only to */
+/* the degree it is needed to ensure that the returned value has the */
+/* correct sign. Hence, this function is usually quite fast, but will run */
+/* more slowly when the input points are collinear or nearly so. */
+/* */
+/* See my Robust Predicates paper for details. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+tREAL counterclockwiseadapt(vertex pa, vertex pb, vertex pc, tREAL detsum)
+#else /* not ANSI_DECLARATORS */
+tREAL counterclockwiseadapt(pa, pb, pc, detsum)
+vertex pa;
+vertex pb;
+vertex pc;
+tREAL detsum;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ INEXACT tREAL acx, acy, bcx, bcy;
+ tREAL acxtail, acytail, bcxtail, bcytail;
+ INEXACT tREAL detleft, detright;
+ tREAL detlefttail, detrighttail;
+ tREAL det, errbound;
+ tREAL B[4], C1[8], C2[12], D[16];
+ INEXACT tREAL B3;
+ int C1length, C2length, Dlength;
+ tREAL u[4];
+ INEXACT tREAL u3;
+ INEXACT tREAL s1, t1;
+ tREAL s0, t0;
+
+ INEXACT tREAL bvirt;
+ tREAL avirt, bround, around;
+ INEXACT tREAL c;
+ INEXACT tREAL abig;
+ tREAL ahi, alo, bhi, blo;
+ tREAL err1, err2, err3;
+ INEXACT tREAL _i, _j;
+ tREAL _0;
+
+ acx = (tREAL) (pa[0] - pc[0]);
+ bcx = (tREAL) (pb[0] - pc[0]);
+ acy = (tREAL) (pa[1] - pc[1]);
+ bcy = (tREAL) (pb[1] - pc[1]);
+
+ Two_Product(acx, bcy, detleft, detlefttail);
+ Two_Product(acy, bcx, detright, detrighttail);
+
+ Two_Two_Diff(detleft, detlefttail, detright, detrighttail,
+ B3, B[2], B[1], B[0]);
+ B[3] = B3;
+
+ det = estimate(4, B);
+ errbound = ccwerrboundB * detsum;
+ if ((det >= errbound) || (-det >= errbound)) {
+ return det;
+ }
+
+ Two_Diff_Tail(pa[0], pc[0], acx, acxtail);
+ Two_Diff_Tail(pb[0], pc[0], bcx, bcxtail);
+ Two_Diff_Tail(pa[1], pc[1], acy, acytail);
+ Two_Diff_Tail(pb[1], pc[1], bcy, bcytail);
+
+ if ((acxtail == 0.0f) && (acytail == 0.0f)
+ && (bcxtail == 0.0f) && (bcytail == 0.0f)) {
+ return det;
+ }
+
+ errbound = ccwerrboundC * detsum + resulterrbound * Absolute(det);
+ det += (acx * bcytail + bcy * acxtail)
+ - (acy * bcxtail + bcx * acytail);
+ if ((det >= errbound) || (-det >= errbound)) {
+ return det;
+ }
+
+ Two_Product(acxtail, bcy, s1, s0);
+ Two_Product(acytail, bcx, t1, t0);
+ Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ C1length = fast_expansion_sum_zeroelim(4, B, 4, u, C1);
+
+ Two_Product(acx, bcytail, s1, s0);
+ Two_Product(acy, bcxtail, t1, t0);
+ Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ C2length = fast_expansion_sum_zeroelim(C1length, C1, 4, u, C2);
+
+ Two_Product(acxtail, bcytail, s1, s0);
+ Two_Product(acytail, bcxtail, t1, t0);
+ Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ Dlength = fast_expansion_sum_zeroelim(C2length, C2, 4, u, D);
+
+ return(D[Dlength - 1]);
+}
+
+#ifdef ANSI_DECLARATORS
+tREAL counterclockwise(struct mesh *m, struct behavior *b,
+ vertex pa, vertex pb, vertex pc)
+#else /* not ANSI_DECLARATORS */
+tREAL counterclockwise(m, b, pa, pb, pc)
+struct mesh *m;
+struct behavior *b;
+vertex pa;
+vertex pb;
+vertex pc;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ tREAL detleft, detright, det;
+ tREAL detsum, errbound;
+
+ m->counterclockcount++;
+
+ detleft = (pa[0] - pc[0]) * (pb[1] - pc[1]);
+ detright = (pa[1] - pc[1]) * (pb[0] - pc[0]);
+ det = detleft - detright;
+
+ if (b->noexact) {
+ return det;
+ }
+
+ if (detleft > 0.0f) {
+ if (detright <= 0.0f) {
+ return det;
+ } else {
+ detsum = detleft + detright;
+ }
+ } else if (detleft < 0.0f) {
+ if (detright >= 0.0f) {
+ return det;
+ } else {
+ detsum = -detleft - detright;
+ }
+ } else {
+ return det;
+ }
+
+ errbound = ccwerrboundA * detsum;
+ if ((det >= errbound) || (-det >= errbound)) {
+ return det;
+ }
+
+ return counterclockwiseadapt(pa, pb, pc, detsum);
+}
+
+/*****************************************************************************/
+/* */
+/* incircle() Return a positive value if the point pd lies inside the */
+/* circle passing through pa, pb, and pc; a negative value if */
+/* it lies outside; and zero if the four points are cocircular.*/
+/* The points pa, pb, and pc must be in counterclockwise */
+/* order, or the sign of the result will be reversed. */
+/* */
+/* Uses exact arithmetic if necessary to ensure a correct answer. The */
+/* result returned is the determinant of a matrix. This determinant is */
+/* computed adaptively, in the sense that exact arithmetic is used only to */
+/* the degree it is needed to ensure that the returned value has the */
+/* correct sign. Hence, this function is usually quite fast, but will run */
+/* more slowly when the input points are cocircular or nearly so. */
+/* */
+/* See my Robust Predicates paper for details. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+tREAL incircleadapt(vertex pa, vertex pb, vertex pc, vertex pd, tREAL permanent)
+#else /* not ANSI_DECLARATORS */
+tREAL incircleadapt(pa, pb, pc, pd, permanent)
+vertex pa;
+vertex pb;
+vertex pc;
+vertex pd;
+tREAL permanent;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ INEXACT tREAL adx, bdx, cdx, ady, bdy, cdy;
+ tREAL det, errbound;
+
+ INEXACT tREAL bdxcdy1, cdxbdy1, cdxady1, adxcdy1, adxbdy1, bdxady1;
+ tREAL bdxcdy0, cdxbdy0, cdxady0, adxcdy0, adxbdy0, bdxady0;
+ tREAL bc[4], ca[4], ab[4];
+ INEXACT tREAL bc3, ca3, ab3;
+ tREAL axbc[8], axxbc[16], aybc[8], ayybc[16], adet[32];
+ int axbclen, axxbclen, aybclen, ayybclen, alen;
+ tREAL bxca[8], bxxca[16], byca[8], byyca[16], bdet[32];
+ int bxcalen, bxxcalen, bycalen, byycalen, blen;
+ tREAL cxab[8], cxxab[16], cyab[8], cyyab[16], cdet[32];
+ int cxablen, cxxablen, cyablen, cyyablen, clen;
+ tREAL abdet[64];
+ int ablen;
+ tREAL fin1[1152], fin2[1152];
+ tREAL *finnow, *finother, *finswap;
+ int finlength;
+
+ tREAL adxtail, bdxtail, cdxtail, adytail, bdytail, cdytail;
+ INEXACT tREAL adxadx1, adyady1, bdxbdx1, bdybdy1, cdxcdx1, cdycdy1;
+ tREAL adxadx0, adyady0, bdxbdx0, bdybdy0, cdxcdx0, cdycdy0;
+ tREAL aa[4], bb[4], cc[4];
+ INEXACT tREAL aa3, bb3, cc3;
+ INEXACT tREAL ti1, tj1;
+ tREAL ti0, tj0;
+ tREAL u[4], v[4];
+ INEXACT tREAL u3, v3;
+ tREAL temp8[8], temp16a[16], temp16b[16], temp16c[16];
+ tREAL temp32a[32], temp32b[32], temp48[48], temp64[64];
+ int temp8len, temp16alen, temp16blen, temp16clen;
+ int temp32alen, temp32blen, temp48len, temp64len;
+ tREAL axtbb[8], axtcc[8], aytbb[8], aytcc[8];
+ int axtbblen, axtcclen, aytbblen, aytcclen;
+ tREAL bxtaa[8], bxtcc[8], bytaa[8], bytcc[8];
+ int bxtaalen, bxtcclen, bytaalen, bytcclen;
+ tREAL cxtaa[8], cxtbb[8], cytaa[8], cytbb[8];
+ int cxtaalen, cxtbblen, cytaalen, cytbblen;
+ tREAL axtbc[8], aytbc[8], bxtca[8], bytca[8], cxtab[8], cytab[8];
+ int axtbclen, aytbclen, bxtcalen, bytcalen, cxtablen, cytablen;
+ tREAL axtbct[16], aytbct[16], bxtcat[16], bytcat[16], cxtabt[16], cytabt[16];
+ int axtbctlen, aytbctlen, bxtcatlen, bytcatlen, cxtabtlen, cytabtlen;
+ tREAL axtbctt[8], aytbctt[8], bxtcatt[8];
+ tREAL bytcatt[8], cxtabtt[8], cytabtt[8];
+ int axtbcttlen, aytbcttlen, bxtcattlen, bytcattlen, cxtabttlen, cytabttlen;
+ tREAL abt[8], bct[8], cat[8];
+ int abtlen, bctlen, catlen;
+ tREAL abtt[4], bctt[4], catt[4];
+ int abttlen, bcttlen, cattlen;
+ INEXACT tREAL abtt3, bctt3, catt3;
+ tREAL negate;
+
+ INEXACT tREAL bvirt;
+ tREAL avirt, bround, around;
+ INEXACT tREAL c;
+ INEXACT tREAL abig;
+ tREAL ahi, alo, bhi, blo;
+ tREAL err1, err2, err3;
+ INEXACT tREAL _i, _j;
+ tREAL _0;
+
+ adx = (tREAL) (pa[0] - pd[0]);
+ bdx = (tREAL) (pb[0] - pd[0]);
+ cdx = (tREAL) (pc[0] - pd[0]);
+ ady = (tREAL) (pa[1] - pd[1]);
+ bdy = (tREAL) (pb[1] - pd[1]);
+ cdy = (tREAL) (pc[1] - pd[1]);
+
+ Two_Product(bdx, cdy, bdxcdy1, bdxcdy0);
+ Two_Product(cdx, bdy, cdxbdy1, cdxbdy0);
+ Two_Two_Diff(bdxcdy1, bdxcdy0, cdxbdy1, cdxbdy0, bc3, bc[2], bc[1], bc[0]);
+ bc[3] = bc3;
+ axbclen = scale_expansion_zeroelim(4, bc, adx, axbc);
+ axxbclen = scale_expansion_zeroelim(axbclen, axbc, adx, axxbc);
+ aybclen = scale_expansion_zeroelim(4, bc, ady, aybc);
+ ayybclen = scale_expansion_zeroelim(aybclen, aybc, ady, ayybc);
+ alen = fast_expansion_sum_zeroelim(axxbclen, axxbc, ayybclen, ayybc, adet);
+
+ Two_Product(cdx, ady, cdxady1, cdxady0);
+ Two_Product(adx, cdy, adxcdy1, adxcdy0);
+ Two_Two_Diff(cdxady1, cdxady0, adxcdy1, adxcdy0, ca3, ca[2], ca[1], ca[0]);
+ ca[3] = ca3;
+ bxcalen = scale_expansion_zeroelim(4, ca, bdx, bxca);
+ bxxcalen = scale_expansion_zeroelim(bxcalen, bxca, bdx, bxxca);
+ bycalen = scale_expansion_zeroelim(4, ca, bdy, byca);
+ byycalen = scale_expansion_zeroelim(bycalen, byca, bdy, byyca);
+ blen = fast_expansion_sum_zeroelim(bxxcalen, bxxca, byycalen, byyca, bdet);
+
+ Two_Product(adx, bdy, adxbdy1, adxbdy0);
+ Two_Product(bdx, ady, bdxady1, bdxady0);
+ Two_Two_Diff(adxbdy1, adxbdy0, bdxady1, bdxady0, ab3, ab[2], ab[1], ab[0]);
+ ab[3] = ab3;
+ cxablen = scale_expansion_zeroelim(4, ab, cdx, cxab);
+ cxxablen = scale_expansion_zeroelim(cxablen, cxab, cdx, cxxab);
+ cyablen = scale_expansion_zeroelim(4, ab, cdy, cyab);
+ cyyablen = scale_expansion_zeroelim(cyablen, cyab, cdy, cyyab);
+ clen = fast_expansion_sum_zeroelim(cxxablen, cxxab, cyyablen, cyyab, cdet);
+
+ ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet);
+ finlength = fast_expansion_sum_zeroelim(ablen, abdet, clen, cdet, fin1);
+
+ det = estimate(finlength, fin1);
+ errbound = iccerrboundB * permanent;
+ if ((det >= errbound) || (-det >= errbound)) {
+ return det;
+ }
+
+ Two_Diff_Tail(pa[0], pd[0], adx, adxtail);
+ Two_Diff_Tail(pa[1], pd[1], ady, adytail);
+ Two_Diff_Tail(pb[0], pd[0], bdx, bdxtail);
+ Two_Diff_Tail(pb[1], pd[1], bdy, bdytail);
+ Two_Diff_Tail(pc[0], pd[0], cdx, cdxtail);
+ Two_Diff_Tail(pc[1], pd[1], cdy, cdytail);
+ if ((adxtail == 0.0f) && (bdxtail == 0.0f) && (cdxtail == 0.0f)
+ && (adytail == 0.0f) && (bdytail == 0.0f) && (cdytail == 0.0f)) {
+ return det;
+ }
+
+ errbound = iccerrboundC * permanent + resulterrbound * Absolute(det);
+ det += ((adx * adx + ady * ady) * ((bdx * cdytail + cdy * bdxtail)
+ - (bdy * cdxtail + cdx * bdytail))
+ + 2.0 * (adx * adxtail + ady * adytail) * (bdx * cdy - bdy * cdx))
+ + ((bdx * bdx + bdy * bdy) * ((cdx * adytail + ady * cdxtail)
+ - (cdy * adxtail + adx * cdytail))
+ + 2.0 * (bdx * bdxtail + bdy * bdytail) * (cdx * ady - cdy * adx))
+ + ((cdx * cdx + cdy * cdy) * ((adx * bdytail + bdy * adxtail)
+ - (ady * bdxtail + bdx * adytail))
+ + 2.0 * (cdx * cdxtail + cdy * cdytail) * (adx * bdy - ady * bdx));
+ if ((det >= errbound) || (-det >= errbound)) {
+ return det;
+ }
+
+ finnow = fin1;
+ finother = fin2;
+
+ if ((bdxtail != 0.0f) || (bdytail != 0.0f)
+ || (cdxtail != 0.0f) || (cdytail != 0.0f)) {
+ Square(adx, adxadx1, adxadx0);
+ Square(ady, adyady1, adyady0);
+ Two_Two_Sum(adxadx1, adxadx0, adyady1, adyady0, aa3, aa[2], aa[1], aa[0]);
+ aa[3] = aa3;
+ }
+ if ((cdxtail != 0.0f) || (cdytail != 0.0f)
+ || (adxtail != 0.0f) || (adytail != 0.0f)) {
+ Square(bdx, bdxbdx1, bdxbdx0);
+ Square(bdy, bdybdy1, bdybdy0);
+ Two_Two_Sum(bdxbdx1, bdxbdx0, bdybdy1, bdybdy0, bb3, bb[2], bb[1], bb[0]);
+ bb[3] = bb3;
+ }
+ if ((adxtail != 0.0f) || (adytail != 0.0f)
+ || (bdxtail != 0.0f) || (bdytail != 0.0f)) {
+ Square(cdx, cdxcdx1, cdxcdx0);
+ Square(cdy, cdycdy1, cdycdy0);
+ Two_Two_Sum(cdxcdx1, cdxcdx0, cdycdy1, cdycdy0, cc3, cc[2], cc[1], cc[0]);
+ cc[3] = cc3;
+ }
+
+ if (adxtail != 0.0f) {
+ axtbclen = scale_expansion_zeroelim(4, bc, adxtail, axtbc);
+ temp16alen = scale_expansion_zeroelim(axtbclen, axtbc, 2.0 * adx,
+ temp16a);
+
+ axtcclen = scale_expansion_zeroelim(4, cc, adxtail, axtcc);
+ temp16blen = scale_expansion_zeroelim(axtcclen, axtcc, bdy, temp16b);
+
+ axtbblen = scale_expansion_zeroelim(4, bb, adxtail, axtbb);
+ temp16clen = scale_expansion_zeroelim(axtbblen, axtbb, -cdy, temp16c);
+
+ temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a,
+ temp16blen, temp16b, temp32a);
+ temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c,
+ temp32alen, temp32a, temp48);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len,
+ temp48, finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+ if (adytail != 0.0f) {
+ aytbclen = scale_expansion_zeroelim(4, bc, adytail, aytbc);
+ temp16alen = scale_expansion_zeroelim(aytbclen, aytbc, 2.0 * ady,
+ temp16a);
+
+ aytbblen = scale_expansion_zeroelim(4, bb, adytail, aytbb);
+ temp16blen = scale_expansion_zeroelim(aytbblen, aytbb, cdx, temp16b);
+
+ aytcclen = scale_expansion_zeroelim(4, cc, adytail, aytcc);
+ temp16clen = scale_expansion_zeroelim(aytcclen, aytcc, -bdx, temp16c);
+
+ temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a,
+ temp16blen, temp16b, temp32a);
+ temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c,
+ temp32alen, temp32a, temp48);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len,
+ temp48, finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+ if (bdxtail != 0.0f) {
+ bxtcalen = scale_expansion_zeroelim(4, ca, bdxtail, bxtca);
+ temp16alen = scale_expansion_zeroelim(bxtcalen, bxtca, 2.0 * bdx,
+ temp16a);
+
+ bxtaalen = scale_expansion_zeroelim(4, aa, bdxtail, bxtaa);
+ temp16blen = scale_expansion_zeroelim(bxtaalen, bxtaa, cdy, temp16b);
+
+ bxtcclen = scale_expansion_zeroelim(4, cc, bdxtail, bxtcc);
+ temp16clen = scale_expansion_zeroelim(bxtcclen, bxtcc, -ady, temp16c);
+
+ temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a,
+ temp16blen, temp16b, temp32a);
+ temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c,
+ temp32alen, temp32a, temp48);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len,
+ temp48, finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+ if (bdytail != 0.0f) {
+ bytcalen = scale_expansion_zeroelim(4, ca, bdytail, bytca);
+ temp16alen = scale_expansion_zeroelim(bytcalen, bytca, 2.0 * bdy,
+ temp16a);
+
+ bytcclen = scale_expansion_zeroelim(4, cc, bdytail, bytcc);
+ temp16blen = scale_expansion_zeroelim(bytcclen, bytcc, adx, temp16b);
+
+ bytaalen = scale_expansion_zeroelim(4, aa, bdytail, bytaa);
+ temp16clen = scale_expansion_zeroelim(bytaalen, bytaa, -cdx, temp16c);
+
+ temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a,
+ temp16blen, temp16b, temp32a);
+ temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c,
+ temp32alen, temp32a, temp48);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len,
+ temp48, finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+ if (cdxtail != 0.0f) {
+ cxtablen = scale_expansion_zeroelim(4, ab, cdxtail, cxtab);
+ temp16alen = scale_expansion_zeroelim(cxtablen, cxtab, 2.0 * cdx,
+ temp16a);
+
+ cxtbblen = scale_expansion_zeroelim(4, bb, cdxtail, cxtbb);
+ temp16blen = scale_expansion_zeroelim(cxtbblen, cxtbb, ady, temp16b);
+
+ cxtaalen = scale_expansion_zeroelim(4, aa, cdxtail, cxtaa);
+ temp16clen = scale_expansion_zeroelim(cxtaalen, cxtaa, -bdy, temp16c);
+
+ temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a,
+ temp16blen, temp16b, temp32a);
+ temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c,
+ temp32alen, temp32a, temp48);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len,
+ temp48, finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+ if (cdytail != 0.0f) {
+ cytablen = scale_expansion_zeroelim(4, ab, cdytail, cytab);
+ temp16alen = scale_expansion_zeroelim(cytablen, cytab, 2.0 * cdy,
+ temp16a);
+
+ cytaalen = scale_expansion_zeroelim(4, aa, cdytail, cytaa);
+ temp16blen = scale_expansion_zeroelim(cytaalen, cytaa, bdx, temp16b);
+
+ cytbblen = scale_expansion_zeroelim(4, bb, cdytail, cytbb);
+ temp16clen = scale_expansion_zeroelim(cytbblen, cytbb, -adx, temp16c);
+
+ temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a,
+ temp16blen, temp16b, temp32a);
+ temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c,
+ temp32alen, temp32a, temp48);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len,
+ temp48, finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+
+ if ((adxtail != 0.0f) || (adytail != 0.0f)) {
+ if ((bdxtail != 0.0f) || (bdytail != 0.0f)
+ || (cdxtail != 0.0f) || (cdytail != 0.0f)) {
+ Two_Product(bdxtail, cdy, ti1, ti0);
+ Two_Product(bdx, cdytail, tj1, tj0);
+ Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ negate = -bdy;
+ Two_Product(cdxtail, negate, ti1, ti0);
+ negate = -bdytail;
+ Two_Product(cdx, negate, tj1, tj0);
+ Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]);
+ v[3] = v3;
+ bctlen = fast_expansion_sum_zeroelim(4, u, 4, v, bct);
+
+ Two_Product(bdxtail, cdytail, ti1, ti0);
+ Two_Product(cdxtail, bdytail, tj1, tj0);
+ Two_Two_Diff(ti1, ti0, tj1, tj0, bctt3, bctt[2], bctt[1], bctt[0]);
+ bctt[3] = bctt3;
+ bcttlen = 4;
+ } else {
+ bct[0] = 0.0f;
+ bctlen = 1;
+ bctt[0] = 0.0f;
+ bcttlen = 1;
+ }
+
+ if (adxtail != 0.0f) {
+ temp16alen = scale_expansion_zeroelim(axtbclen, axtbc, adxtail, temp16a);
+ axtbctlen = scale_expansion_zeroelim(bctlen, bct, adxtail, axtbct);
+ temp32alen = scale_expansion_zeroelim(axtbctlen, axtbct, 2.0 * adx,
+ temp32a);
+ temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a,
+ temp32alen, temp32a, temp48);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len,
+ temp48, finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ if (bdytail != 0.0f) {
+ temp8len = scale_expansion_zeroelim(4, cc, adxtail, temp8);
+ temp16alen = scale_expansion_zeroelim(temp8len, temp8, bdytail,
+ temp16a);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen,
+ temp16a, finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+ if (cdytail != 0.0f) {
+ temp8len = scale_expansion_zeroelim(4, bb, -adxtail, temp8);
+ temp16alen = scale_expansion_zeroelim(temp8len, temp8, cdytail,
+ temp16a);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen,
+ temp16a, finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+
+ temp32alen = scale_expansion_zeroelim(axtbctlen, axtbct, adxtail,
+ temp32a);
+ axtbcttlen = scale_expansion_zeroelim(bcttlen, bctt, adxtail, axtbctt);
+ temp16alen = scale_expansion_zeroelim(axtbcttlen, axtbctt, 2.0 * adx,
+ temp16a);
+ temp16blen = scale_expansion_zeroelim(axtbcttlen, axtbctt, adxtail,
+ temp16b);
+ temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a,
+ temp16blen, temp16b, temp32b);
+ temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a,
+ temp32blen, temp32b, temp64);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len,
+ temp64, finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+ if (adytail != 0.0f) {
+ temp16alen = scale_expansion_zeroelim(aytbclen, aytbc, adytail, temp16a);
+ aytbctlen = scale_expansion_zeroelim(bctlen, bct, adytail, aytbct);
+ temp32alen = scale_expansion_zeroelim(aytbctlen, aytbct, 2.0 * ady,
+ temp32a);
+ temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a,
+ temp32alen, temp32a, temp48);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len,
+ temp48, finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+
+
+ temp32alen = scale_expansion_zeroelim(aytbctlen, aytbct, adytail,
+ temp32a);
+ aytbcttlen = scale_expansion_zeroelim(bcttlen, bctt, adytail, aytbctt);
+ temp16alen = scale_expansion_zeroelim(aytbcttlen, aytbctt, 2.0 * ady,
+ temp16a);
+ temp16blen = scale_expansion_zeroelim(aytbcttlen, aytbctt, adytail,
+ temp16b);
+ temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a,
+ temp16blen, temp16b, temp32b);
+ temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a,
+ temp32blen, temp32b, temp64);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len,
+ temp64, finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+ }
+ if ((bdxtail != 0.0f) || (bdytail != 0.0f)) {
+ if ((cdxtail != 0.0f) || (cdytail != 0.0f)
+ || (adxtail != 0.0f) || (adytail != 0.0f)) {
+ Two_Product(cdxtail, ady, ti1, ti0);
+ Two_Product(cdx, adytail, tj1, tj0);
+ Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ negate = -cdy;
+ Two_Product(adxtail, negate, ti1, ti0);
+ negate = -cdytail;
+ Two_Product(adx, negate, tj1, tj0);
+ Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]);
+ v[3] = v3;
+ catlen = fast_expansion_sum_zeroelim(4, u, 4, v, cat);
+
+ Two_Product(cdxtail, adytail, ti1, ti0);
+ Two_Product(adxtail, cdytail, tj1, tj0);
+ Two_Two_Diff(ti1, ti0, tj1, tj0, catt3, catt[2], catt[1], catt[0]);
+ catt[3] = catt3;
+ cattlen = 4;
+ } else {
+ cat[0] = 0.0f;
+ catlen = 1;
+ catt[0] = 0.0f;
+ cattlen = 1;
+ }
+
+ if (bdxtail != 0.0f) {
+ temp16alen = scale_expansion_zeroelim(bxtcalen, bxtca, bdxtail, temp16a);
+ bxtcatlen = scale_expansion_zeroelim(catlen, cat, bdxtail, bxtcat);
+ temp32alen = scale_expansion_zeroelim(bxtcatlen, bxtcat, 2.0 * bdx,
+ temp32a);
+ temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a,
+ temp32alen, temp32a, temp48);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len,
+ temp48, finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ if (cdytail != 0.0f) {
+ temp8len = scale_expansion_zeroelim(4, aa, bdxtail, temp8);
+ temp16alen = scale_expansion_zeroelim(temp8len, temp8, cdytail,
+ temp16a);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen,
+ temp16a, finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+ if (adytail != 0.0f) {
+ temp8len = scale_expansion_zeroelim(4, cc, -bdxtail, temp8);
+ temp16alen = scale_expansion_zeroelim(temp8len, temp8, adytail,
+ temp16a);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen,
+ temp16a, finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+
+ temp32alen = scale_expansion_zeroelim(bxtcatlen, bxtcat, bdxtail,
+ temp32a);
+ bxtcattlen = scale_expansion_zeroelim(cattlen, catt, bdxtail, bxtcatt);
+ temp16alen = scale_expansion_zeroelim(bxtcattlen, bxtcatt, 2.0 * bdx,
+ temp16a);
+ temp16blen = scale_expansion_zeroelim(bxtcattlen, bxtcatt, bdxtail,
+ temp16b);
+ temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a,
+ temp16blen, temp16b, temp32b);
+ temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a,
+ temp32blen, temp32b, temp64);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len,
+ temp64, finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+ if (bdytail != 0.0f) {
+ temp16alen = scale_expansion_zeroelim(bytcalen, bytca, bdytail, temp16a);
+ bytcatlen = scale_expansion_zeroelim(catlen, cat, bdytail, bytcat);
+ temp32alen = scale_expansion_zeroelim(bytcatlen, bytcat, 2.0 * bdy,
+ temp32a);
+ temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a,
+ temp32alen, temp32a, temp48);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len,
+ temp48, finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+
+
+ temp32alen = scale_expansion_zeroelim(bytcatlen, bytcat, bdytail,
+ temp32a);
+ bytcattlen = scale_expansion_zeroelim(cattlen, catt, bdytail, bytcatt);
+ temp16alen = scale_expansion_zeroelim(bytcattlen, bytcatt, 2.0 * bdy,
+ temp16a);
+ temp16blen = scale_expansion_zeroelim(bytcattlen, bytcatt, bdytail,
+ temp16b);
+ temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a,
+ temp16blen, temp16b, temp32b);
+ temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a,
+ temp32blen, temp32b, temp64);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len,
+ temp64, finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+ }
+ if ((cdxtail != 0.0f) || (cdytail != 0.0f)) {
+ if ((adxtail != 0.0f) || (adytail != 0.0f)
+ || (bdxtail != 0.0f) || (bdytail != 0.0f)) {
+ Two_Product(adxtail, bdy, ti1, ti0);
+ Two_Product(adx, bdytail, tj1, tj0);
+ Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ negate = -ady;
+ Two_Product(bdxtail, negate, ti1, ti0);
+ negate = -adytail;
+ Two_Product(bdx, negate, tj1, tj0);
+ Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]);
+ v[3] = v3;
+ abtlen = fast_expansion_sum_zeroelim(4, u, 4, v, abt);
+
+ Two_Product(adxtail, bdytail, ti1, ti0);
+ Two_Product(bdxtail, adytail, tj1, tj0);
+ Two_Two_Diff(ti1, ti0, tj1, tj0, abtt3, abtt[2], abtt[1], abtt[0]);
+ abtt[3] = abtt3;
+ abttlen = 4;
+ } else {
+ abt[0] = 0.0f;
+ abtlen = 1;
+ abtt[0] = 0.0f;
+ abttlen = 1;
+ }
+
+ if (cdxtail != 0.0f) {
+ temp16alen = scale_expansion_zeroelim(cxtablen, cxtab, cdxtail, temp16a);
+ cxtabtlen = scale_expansion_zeroelim(abtlen, abt, cdxtail, cxtabt);
+ temp32alen = scale_expansion_zeroelim(cxtabtlen, cxtabt, 2.0 * cdx,
+ temp32a);
+ temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a,
+ temp32alen, temp32a, temp48);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len,
+ temp48, finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ if (adytail != 0.0f) {
+ temp8len = scale_expansion_zeroelim(4, bb, cdxtail, temp8);
+ temp16alen = scale_expansion_zeroelim(temp8len, temp8, adytail,
+ temp16a);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen,
+ temp16a, finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+ if (bdytail != 0.0f) {
+ temp8len = scale_expansion_zeroelim(4, aa, -cdxtail, temp8);
+ temp16alen = scale_expansion_zeroelim(temp8len, temp8, bdytail,
+ temp16a);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen,
+ temp16a, finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+
+ temp32alen = scale_expansion_zeroelim(cxtabtlen, cxtabt, cdxtail,
+ temp32a);
+ cxtabttlen = scale_expansion_zeroelim(abttlen, abtt, cdxtail, cxtabtt);
+ temp16alen = scale_expansion_zeroelim(cxtabttlen, cxtabtt, 2.0 * cdx,
+ temp16a);
+ temp16blen = scale_expansion_zeroelim(cxtabttlen, cxtabtt, cdxtail,
+ temp16b);
+ temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a,
+ temp16blen, temp16b, temp32b);
+ temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a,
+ temp32blen, temp32b, temp64);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len,
+ temp64, finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+ if (cdytail != 0.0f) {
+ temp16alen = scale_expansion_zeroelim(cytablen, cytab, cdytail, temp16a);
+ cytabtlen = scale_expansion_zeroelim(abtlen, abt, cdytail, cytabt);
+ temp32alen = scale_expansion_zeroelim(cytabtlen, cytabt, 2.0 * cdy,
+ temp32a);
+ temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a,
+ temp32alen, temp32a, temp48);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len,
+ temp48, finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+
+
+ temp32alen = scale_expansion_zeroelim(cytabtlen, cytabt, cdytail,
+ temp32a);
+ cytabttlen = scale_expansion_zeroelim(abttlen, abtt, cdytail, cytabtt);
+ temp16alen = scale_expansion_zeroelim(cytabttlen, cytabtt, 2.0 * cdy,
+ temp16a);
+ temp16blen = scale_expansion_zeroelim(cytabttlen, cytabtt, cdytail,
+ temp16b);
+ temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a,
+ temp16blen, temp16b, temp32b);
+ temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a,
+ temp32blen, temp32b, temp64);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len,
+ temp64, finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+ }
+
+ return finnow[finlength - 1];
+}
+
+#ifdef ANSI_DECLARATORS
+tREAL incircle(struct mesh *m, struct behavior *b,
+ vertex pa, vertex pb, vertex pc, vertex pd)
+#else /* not ANSI_DECLARATORS */
+tREAL incircle(m, b, pa, pb, pc, pd)
+struct mesh *m;
+struct behavior *b;
+vertex pa;
+vertex pb;
+vertex pc;
+vertex pd;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ tREAL adx, bdx, cdx, ady, bdy, cdy;
+ tREAL bdxcdy, cdxbdy, cdxady, adxcdy, adxbdy, bdxady;
+ tREAL alift, blift, clift;
+ tREAL det;
+ tREAL permanent, errbound;
+
+ m->incirclecount++;
+
+ adx = pa[0] - pd[0];
+ bdx = pb[0] - pd[0];
+ cdx = pc[0] - pd[0];
+ ady = pa[1] - pd[1];
+ bdy = pb[1] - pd[1];
+ cdy = pc[1] - pd[1];
+
+ bdxcdy = bdx * cdy;
+ cdxbdy = cdx * bdy;
+ alift = adx * adx + ady * ady;
+
+ cdxady = cdx * ady;
+ adxcdy = adx * cdy;
+ blift = bdx * bdx + bdy * bdy;
+
+ adxbdy = adx * bdy;
+ bdxady = bdx * ady;
+ clift = cdx * cdx + cdy * cdy;
+
+ det = alift * (bdxcdy - cdxbdy)
+ + blift * (cdxady - adxcdy)
+ + clift * (adxbdy - bdxady);
+
+ if (b->noexact) {
+ return det;
+ }
+
+ permanent = (Absolute(bdxcdy) + Absolute(cdxbdy)) * alift
+ + (Absolute(cdxady) + Absolute(adxcdy)) * blift
+ + (Absolute(adxbdy) + Absolute(bdxady)) * clift;
+ errbound = iccerrboundA * permanent;
+ if ((det > errbound) || (-det > errbound)) {
+ return det;
+ }
+
+ return incircleadapt(pa, pb, pc, pd, permanent);
+}
+
+/*****************************************************************************/
+/* */
+/* orient3d() Return a positive value if the point pd lies below the */
+/* plane passing through pa, pb, and pc; "below" is defined so */
+/* that pa, pb, and pc appear in counterclockwise order when */
+/* viewed from above the plane. Returns a negative value if */
+/* pd lies above the plane. Returns zero if the points are */
+/* coplanar. The result is also a rough approximation of six */
+/* times the signed volume of the tetrahedron defined by the */
+/* four points. */
+/* */
+/* Uses exact arithmetic if necessary to ensure a correct answer. The */
+/* result returned is the determinant of a matrix. This determinant is */
+/* computed adaptively, in the sense that exact arithmetic is used only to */
+/* the degree it is needed to ensure that the returned value has the */
+/* correct sign. Hence, this function is usually quite fast, but will run */
+/* more slowly when the input points are coplanar or nearly so. */
+/* */
+/* See my Robust Predicates paper for details. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+tREAL orient3dadapt(vertex pa, vertex pb, vertex pc, vertex pd,
+ tREAL aheight, tREAL bheight, tREAL cheight, tREAL dheight,
+ tREAL permanent)
+#else /* not ANSI_DECLARATORS */
+tREAL orient3dadapt(pa, pb, pc, pd,
+ aheight, bheight, cheight, dheight, permanent)
+vertex pa;
+vertex pb;
+vertex pc;
+vertex pd;
+tREAL aheight;
+tREAL bheight;
+tREAL cheight;
+tREAL dheight;
+tREAL permanent;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ INEXACT tREAL adx, bdx, cdx, ady, bdy, cdy, adheight, bdheight, cdheight;
+ tREAL det, errbound;
+
+ INEXACT tREAL bdxcdy1, cdxbdy1, cdxady1, adxcdy1, adxbdy1, bdxady1;
+ tREAL bdxcdy0, cdxbdy0, cdxady0, adxcdy0, adxbdy0, bdxady0;
+ tREAL bc[4], ca[4], ab[4];
+ INEXACT tREAL bc3, ca3, ab3;
+ tREAL adet[8], bdet[8], cdet[8];
+ int alen, blen, clen;
+ tREAL abdet[16];
+ int ablen;
+ tREAL *finnow, *finother, *finswap;
+ tREAL fin1[192], fin2[192];
+ int finlength;
+
+ tREAL adxtail, bdxtail, cdxtail;
+ tREAL adytail, bdytail, cdytail;
+ tREAL adheighttail, bdheighttail, cdheighttail;
+ INEXACT tREAL at_blarge, at_clarge;
+ INEXACT tREAL bt_clarge, bt_alarge;
+ INEXACT tREAL ct_alarge, ct_blarge;
+ tREAL at_b[4], at_c[4], bt_c[4], bt_a[4], ct_a[4], ct_b[4];
+ int at_blen, at_clen, bt_clen, bt_alen, ct_alen, ct_blen;
+ INEXACT tREAL bdxt_cdy1, cdxt_bdy1, cdxt_ady1;
+ INEXACT tREAL adxt_cdy1, adxt_bdy1, bdxt_ady1;
+ tREAL bdxt_cdy0, cdxt_bdy0, cdxt_ady0;
+ tREAL adxt_cdy0, adxt_bdy0, bdxt_ady0;
+ INEXACT tREAL bdyt_cdx1, cdyt_bdx1, cdyt_adx1;
+ INEXACT tREAL adyt_cdx1, adyt_bdx1, bdyt_adx1;
+ tREAL bdyt_cdx0, cdyt_bdx0, cdyt_adx0;
+ tREAL adyt_cdx0, adyt_bdx0, bdyt_adx0;
+ tREAL bct[8], cat[8], abt[8];
+ int bctlen, catlen, abtlen;
+ INEXACT tREAL bdxt_cdyt1, cdxt_bdyt1, cdxt_adyt1;
+ INEXACT tREAL adxt_cdyt1, adxt_bdyt1, bdxt_adyt1;
+ tREAL bdxt_cdyt0, cdxt_bdyt0, cdxt_adyt0;
+ tREAL adxt_cdyt0, adxt_bdyt0, bdxt_adyt0;
+ tREAL u[4], v[12], w[16];
+ INEXACT tREAL u3;
+ int vlength, wlength;
+ tREAL negate;
+
+ INEXACT tREAL bvirt;
+ tREAL avirt, bround, around;
+ INEXACT tREAL c;
+ INEXACT tREAL abig;
+ tREAL ahi, alo, bhi, blo;
+ tREAL err1, err2, err3;
+ INEXACT tREAL _i, _j, _k;
+ tREAL _0;
+
+ adx = (tREAL) (pa[0] - pd[0]);
+ bdx = (tREAL) (pb[0] - pd[0]);
+ cdx = (tREAL) (pc[0] - pd[0]);
+ ady = (tREAL) (pa[1] - pd[1]);
+ bdy = (tREAL) (pb[1] - pd[1]);
+ cdy = (tREAL) (pc[1] - pd[1]);
+ adheight = (tREAL) (aheight - dheight);
+ bdheight = (tREAL) (bheight - dheight);
+ cdheight = (tREAL) (cheight - dheight);
+
+ Two_Product(bdx, cdy, bdxcdy1, bdxcdy0);
+ Two_Product(cdx, bdy, cdxbdy1, cdxbdy0);
+ Two_Two_Diff(bdxcdy1, bdxcdy0, cdxbdy1, cdxbdy0, bc3, bc[2], bc[1], bc[0]);
+ bc[3] = bc3;
+ alen = scale_expansion_zeroelim(4, bc, adheight, adet);
+
+ Two_Product(cdx, ady, cdxady1, cdxady0);
+ Two_Product(adx, cdy, adxcdy1, adxcdy0);
+ Two_Two_Diff(cdxady1, cdxady0, adxcdy1, adxcdy0, ca3, ca[2], ca[1], ca[0]);
+ ca[3] = ca3;
+ blen = scale_expansion_zeroelim(4, ca, bdheight, bdet);
+
+ Two_Product(adx, bdy, adxbdy1, adxbdy0);
+ Two_Product(bdx, ady, bdxady1, bdxady0);
+ Two_Two_Diff(adxbdy1, adxbdy0, bdxady1, bdxady0, ab3, ab[2], ab[1], ab[0]);
+ ab[3] = ab3;
+ clen = scale_expansion_zeroelim(4, ab, cdheight, cdet);
+
+ ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet);
+ finlength = fast_expansion_sum_zeroelim(ablen, abdet, clen, cdet, fin1);
+
+ det = estimate(finlength, fin1);
+ errbound = o3derrboundB * permanent;
+ if ((det >= errbound) || (-det >= errbound)) {
+ return det;
+ }
+
+ Two_Diff_Tail(pa[0], pd[0], adx, adxtail);
+ Two_Diff_Tail(pb[0], pd[0], bdx, bdxtail);
+ Two_Diff_Tail(pc[0], pd[0], cdx, cdxtail);
+ Two_Diff_Tail(pa[1], pd[1], ady, adytail);
+ Two_Diff_Tail(pb[1], pd[1], bdy, bdytail);
+ Two_Diff_Tail(pc[1], pd[1], cdy, cdytail);
+ Two_Diff_Tail(aheight, dheight, adheight, adheighttail);
+ Two_Diff_Tail(bheight, dheight, bdheight, bdheighttail);
+ Two_Diff_Tail(cheight, dheight, cdheight, cdheighttail);
+
+ if ((adxtail == 0.0f) && (bdxtail == 0.0f) && (cdxtail == 0.0f) &&
+ (adytail == 0.0f) && (bdytail == 0.0f) && (cdytail == 0.0f) &&
+ (adheighttail == 0.0f) &&
+ (bdheighttail == 0.0f) &&
+ (cdheighttail == 0.0f)) {
+ return det;
+ }
+
+ errbound = o3derrboundC * permanent + resulterrbound * Absolute(det);
+ det += (adheight * ((bdx * cdytail + cdy * bdxtail) -
+ (bdy * cdxtail + cdx * bdytail)) +
+ adheighttail * (bdx * cdy - bdy * cdx)) +
+ (bdheight * ((cdx * adytail + ady * cdxtail) -
+ (cdy * adxtail + adx * cdytail)) +
+ bdheighttail * (cdx * ady - cdy * adx)) +
+ (cdheight * ((adx * bdytail + bdy * adxtail) -
+ (ady * bdxtail + bdx * adytail)) +
+ cdheighttail * (adx * bdy - ady * bdx));
+ if ((det >= errbound) || (-det >= errbound)) {
+ return det;
+ }
+
+ finnow = fin1;
+ finother = fin2;
+
+ if (adxtail == 0.0f) {
+ if (adytail == 0.0f) {
+ at_b[0] = 0.0f;
+ at_blen = 1;
+ at_c[0] = 0.0f;
+ at_clen = 1;
+ } else {
+ negate = -adytail;
+ Two_Product(negate, bdx, at_blarge, at_b[0]);
+ at_b[1] = at_blarge;
+ at_blen = 2;
+ Two_Product(adytail, cdx, at_clarge, at_c[0]);
+ at_c[1] = at_clarge;
+ at_clen = 2;
+ }
+ } else {
+ if (adytail == 0.0f) {
+ Two_Product(adxtail, bdy, at_blarge, at_b[0]);
+ at_b[1] = at_blarge;
+ at_blen = 2;
+ negate = -adxtail;
+ Two_Product(negate, cdy, at_clarge, at_c[0]);
+ at_c[1] = at_clarge;
+ at_clen = 2;
+ } else {
+ Two_Product(adxtail, bdy, adxt_bdy1, adxt_bdy0);
+ Two_Product(adytail, bdx, adyt_bdx1, adyt_bdx0);
+ Two_Two_Diff(adxt_bdy1, adxt_bdy0, adyt_bdx1, adyt_bdx0,
+ at_blarge, at_b[2], at_b[1], at_b[0]);
+ at_b[3] = at_blarge;
+ at_blen = 4;
+ Two_Product(adytail, cdx, adyt_cdx1, adyt_cdx0);
+ Two_Product(adxtail, cdy, adxt_cdy1, adxt_cdy0);
+ Two_Two_Diff(adyt_cdx1, adyt_cdx0, adxt_cdy1, adxt_cdy0,
+ at_clarge, at_c[2], at_c[1], at_c[0]);
+ at_c[3] = at_clarge;
+ at_clen = 4;
+ }
+ }
+ if (bdxtail == 0.0f) {
+ if (bdytail == 0.0f) {
+ bt_c[0] = 0.0f;
+ bt_clen = 1;
+ bt_a[0] = 0.0f;
+ bt_alen = 1;
+ } else {
+ negate = -bdytail;
+ Two_Product(negate, cdx, bt_clarge, bt_c[0]);
+ bt_c[1] = bt_clarge;
+ bt_clen = 2;
+ Two_Product(bdytail, adx, bt_alarge, bt_a[0]);
+ bt_a[1] = bt_alarge;
+ bt_alen = 2;
+ }
+ } else {
+ if (bdytail == 0.0f) {
+ Two_Product(bdxtail, cdy, bt_clarge, bt_c[0]);
+ bt_c[1] = bt_clarge;
+ bt_clen = 2;
+ negate = -bdxtail;
+ Two_Product(negate, ady, bt_alarge, bt_a[0]);
+ bt_a[1] = bt_alarge;
+ bt_alen = 2;
+ } else {
+ Two_Product(bdxtail, cdy, bdxt_cdy1, bdxt_cdy0);
+ Two_Product(bdytail, cdx, bdyt_cdx1, bdyt_cdx0);
+ Two_Two_Diff(bdxt_cdy1, bdxt_cdy0, bdyt_cdx1, bdyt_cdx0,
+ bt_clarge, bt_c[2], bt_c[1], bt_c[0]);
+ bt_c[3] = bt_clarge;
+ bt_clen = 4;
+ Two_Product(bdytail, adx, bdyt_adx1, bdyt_adx0);
+ Two_Product(bdxtail, ady, bdxt_ady1, bdxt_ady0);
+ Two_Two_Diff(bdyt_adx1, bdyt_adx0, bdxt_ady1, bdxt_ady0,
+ bt_alarge, bt_a[2], bt_a[1], bt_a[0]);
+ bt_a[3] = bt_alarge;
+ bt_alen = 4;
+ }
+ }
+ if (cdxtail == 0.0f) {
+ if (cdytail == 0.0f) {
+ ct_a[0] = 0.0f;
+ ct_alen = 1;
+ ct_b[0] = 0.0f;
+ ct_blen = 1;
+ } else {
+ negate = -cdytail;
+ Two_Product(negate, adx, ct_alarge, ct_a[0]);
+ ct_a[1] = ct_alarge;
+ ct_alen = 2;
+ Two_Product(cdytail, bdx, ct_blarge, ct_b[0]);
+ ct_b[1] = ct_blarge;
+ ct_blen = 2;
+ }
+ } else {
+ if (cdytail == 0.0f) {
+ Two_Product(cdxtail, ady, ct_alarge, ct_a[0]);
+ ct_a[1] = ct_alarge;
+ ct_alen = 2;
+ negate = -cdxtail;
+ Two_Product(negate, bdy, ct_blarge, ct_b[0]);
+ ct_b[1] = ct_blarge;
+ ct_blen = 2;
+ } else {
+ Two_Product(cdxtail, ady, cdxt_ady1, cdxt_ady0);
+ Two_Product(cdytail, adx, cdyt_adx1, cdyt_adx0);
+ Two_Two_Diff(cdxt_ady1, cdxt_ady0, cdyt_adx1, cdyt_adx0,
+ ct_alarge, ct_a[2], ct_a[1], ct_a[0]);
+ ct_a[3] = ct_alarge;
+ ct_alen = 4;
+ Two_Product(cdytail, bdx, cdyt_bdx1, cdyt_bdx0);
+ Two_Product(cdxtail, bdy, cdxt_bdy1, cdxt_bdy0);
+ Two_Two_Diff(cdyt_bdx1, cdyt_bdx0, cdxt_bdy1, cdxt_bdy0,
+ ct_blarge, ct_b[2], ct_b[1], ct_b[0]);
+ ct_b[3] = ct_blarge;
+ ct_blen = 4;
+ }
+ }
+
+ bctlen = fast_expansion_sum_zeroelim(bt_clen, bt_c, ct_blen, ct_b, bct);
+ wlength = scale_expansion_zeroelim(bctlen, bct, adheight, w);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w,
+ finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+
+ catlen = fast_expansion_sum_zeroelim(ct_alen, ct_a, at_clen, at_c, cat);
+ wlength = scale_expansion_zeroelim(catlen, cat, bdheight, w);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w,
+ finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+
+ abtlen = fast_expansion_sum_zeroelim(at_blen, at_b, bt_alen, bt_a, abt);
+ wlength = scale_expansion_zeroelim(abtlen, abt, cdheight, w);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w,
+ finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+
+ if (adheighttail != 0.0f) {
+ vlength = scale_expansion_zeroelim(4, bc, adheighttail, v);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, vlength, v,
+ finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+ if (bdheighttail != 0.0f) {
+ vlength = scale_expansion_zeroelim(4, ca, bdheighttail, v);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, vlength, v,
+ finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+ if (cdheighttail != 0.0f) {
+ vlength = scale_expansion_zeroelim(4, ab, cdheighttail, v);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, vlength, v,
+ finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+
+ if (adxtail != 0.0f) {
+ if (bdytail != 0.0f) {
+ Two_Product(adxtail, bdytail, adxt_bdyt1, adxt_bdyt0);
+ Two_One_Product(adxt_bdyt1, adxt_bdyt0, cdheight, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u,
+ finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ if (cdheighttail != 0.0f) {
+ Two_One_Product(adxt_bdyt1, adxt_bdyt0, cdheighttail,
+ u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u,
+ finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+ }
+ if (cdytail != 0.0f) {
+ negate = -adxtail;
+ Two_Product(negate, cdytail, adxt_cdyt1, adxt_cdyt0);
+ Two_One_Product(adxt_cdyt1, adxt_cdyt0, bdheight, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u,
+ finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ if (bdheighttail != 0.0f) {
+ Two_One_Product(adxt_cdyt1, adxt_cdyt0, bdheighttail,
+ u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u,
+ finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+ }
+ }
+ if (bdxtail != 0.0f) {
+ if (cdytail != 0.0f) {
+ Two_Product(bdxtail, cdytail, bdxt_cdyt1, bdxt_cdyt0);
+ Two_One_Product(bdxt_cdyt1, bdxt_cdyt0, adheight, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u,
+ finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ if (adheighttail != 0.0f) {
+ Two_One_Product(bdxt_cdyt1, bdxt_cdyt0, adheighttail,
+ u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u,
+ finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+ }
+ if (adytail != 0.0f) {
+ negate = -bdxtail;
+ Two_Product(negate, adytail, bdxt_adyt1, bdxt_adyt0);
+ Two_One_Product(bdxt_adyt1, bdxt_adyt0, cdheight, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u,
+ finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ if (cdheighttail != 0.0f) {
+ Two_One_Product(bdxt_adyt1, bdxt_adyt0, cdheighttail,
+ u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u,
+ finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+ }
+ }
+ if (cdxtail != 0.0f) {
+ if (adytail != 0.0f) {
+ Two_Product(cdxtail, adytail, cdxt_adyt1, cdxt_adyt0);
+ Two_One_Product(cdxt_adyt1, cdxt_adyt0, bdheight, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u,
+ finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ if (bdheighttail != 0.0f) {
+ Two_One_Product(cdxt_adyt1, cdxt_adyt0, bdheighttail,
+ u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u,
+ finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+ }
+ if (bdytail != 0.0f) {
+ negate = -cdxtail;
+ Two_Product(negate, bdytail, cdxt_bdyt1, cdxt_bdyt0);
+ Two_One_Product(cdxt_bdyt1, cdxt_bdyt0, adheight, u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u,
+ finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ if (adheighttail != 0.0f) {
+ Two_One_Product(cdxt_bdyt1, cdxt_bdyt0, adheighttail,
+ u3, u[2], u[1], u[0]);
+ u[3] = u3;
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, 4, u,
+ finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+ }
+ }
+
+ if (adheighttail != 0.0f) {
+ wlength = scale_expansion_zeroelim(bctlen, bct, adheighttail, w);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w,
+ finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+ if (bdheighttail != 0.0f) {
+ wlength = scale_expansion_zeroelim(catlen, cat, bdheighttail, w);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w,
+ finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+ if (cdheighttail != 0.0f) {
+ wlength = scale_expansion_zeroelim(abtlen, abt, cdheighttail, w);
+ finlength = fast_expansion_sum_zeroelim(finlength, finnow, wlength, w,
+ finother);
+ finswap = finnow; finnow = finother; finother = finswap;
+ }
+
+ return finnow[finlength - 1];
+}
+
+#ifdef ANSI_DECLARATORS
+tREAL orient3d(struct mesh *m, struct behavior *b,
+ vertex pa, vertex pb, vertex pc, vertex pd,
+ tREAL aheight, tREAL bheight, tREAL cheight, tREAL dheight)
+#else /* not ANSI_DECLARATORS */
+tREAL orient3d(m, b, pa, pb, pc, pd, aheight, bheight, cheight, dheight)
+struct mesh *m;
+struct behavior *b;
+vertex pa;
+vertex pb;
+vertex pc;
+vertex pd;
+tREAL aheight;
+tREAL bheight;
+tREAL cheight;
+tREAL dheight;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ tREAL adx, bdx, cdx, ady, bdy, cdy, adheight, bdheight, cdheight;
+ tREAL bdxcdy, cdxbdy, cdxady, adxcdy, adxbdy, bdxady;
+ tREAL det;
+ tREAL permanent, errbound;
+
+ m->orient3dcount++;
+
+ adx = pa[0] - pd[0];
+ bdx = pb[0] - pd[0];
+ cdx = pc[0] - pd[0];
+ ady = pa[1] - pd[1];
+ bdy = pb[1] - pd[1];
+ cdy = pc[1] - pd[1];
+ adheight = aheight - dheight;
+ bdheight = bheight - dheight;
+ cdheight = cheight - dheight;
+
+ bdxcdy = bdx * cdy;
+ cdxbdy = cdx * bdy;
+
+ cdxady = cdx * ady;
+ adxcdy = adx * cdy;
+
+ adxbdy = adx * bdy;
+ bdxady = bdx * ady;
+
+ det = adheight * (bdxcdy - cdxbdy)
+ + bdheight * (cdxady - adxcdy)
+ + cdheight * (adxbdy - bdxady);
+
+ if (b->noexact) {
+ return det;
+ }
+
+ permanent = (Absolute(bdxcdy) + Absolute(cdxbdy)) * Absolute(adheight)
+ + (Absolute(cdxady) + Absolute(adxcdy)) * Absolute(bdheight)
+ + (Absolute(adxbdy) + Absolute(bdxady)) * Absolute(cdheight);
+ errbound = o3derrboundA * permanent;
+ if ((det > errbound) || (-det > errbound)) {
+ return det;
+ }
+
+ return orient3dadapt(pa, pb, pc, pd, aheight, bheight, cheight, dheight,
+ permanent);
+}
+
+/*****************************************************************************/
+/* */
+/* nonregular() Return a positive value if the point pd is incompatible */
+/* with the circle or plane passing through pa, pb, and pc */
+/* (meaning that pd is inside the circle or below the */
+/* plane); a negative value if it is compatible; and zero if */
+/* the four points are cocircular/coplanar. The points pa, */
+/* pb, and pc must be in counterclockwise order, or the sign */
+/* of the result will be reversed. */
+/* */
+/* If the -w switch is used, the points are lifted onto the parabolic */
+/* lifting map, then they are dropped according to their weights, then the */
+/* 3D orientation test is applied. If the -W switch is used, the points' */
+/* heights are already provided, so the 3D orientation test is applied */
+/* directly. If neither switch is used, the incircle test is applied. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+tREAL nonregular(struct mesh *m, struct behavior *b,
+ vertex pa, vertex pb, vertex pc, vertex pd)
+#else /* not ANSI_DECLARATORS */
+tREAL nonregular(m, b, pa, pb, pc, pd)
+struct mesh *m;
+struct behavior *b;
+vertex pa;
+vertex pb;
+vertex pc;
+vertex pd;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ if (b->weighted == 0) {
+ return incircle(m, b, pa, pb, pc, pd);
+ } else if (b->weighted == 1) {
+ return orient3d(m, b, pa, pb, pc, pd,
+ pa[0] * pa[0] + pa[1] * pa[1] - pa[2],
+ pb[0] * pb[0] + pb[1] * pb[1] - pb[2],
+ pc[0] * pc[0] + pc[1] * pc[1] - pc[2],
+ pd[0] * pd[0] + pd[1] * pd[1] - pd[2]);
+ } else {
+ return orient3d(m, b, pa, pb, pc, pd, pa[2], pb[2], pc[2], pd[2]);
+ }
+}
+
+/*****************************************************************************/
+/* */
+/* findcircumcenter() Find the circumcenter of a triangle. */
+/* */
+/* The result is returned both in terms of x-y coordinates and xi-eta */
+/* (barycentric) coordinates. The xi-eta coordinate system is defined in */
+/* terms of the triangle: the origin of the triangle is the origin of the */
+/* coordinate system; the destination of the triangle is one unit along the */
+/* xi axis; and the apex of the triangle is one unit along the eta axis. */
+/* This procedure also returns the square of the length of the triangle's */
+/* shortest edge. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void findcircumcenter(struct mesh *m, struct behavior *b,
+ vertex torg, vertex tdest, vertex tapex,
+ vertex circumcenter, tREAL *xi, tREAL *eta, int offcenter)
+#else /* not ANSI_DECLARATORS */
+void findcircumcenter(m, b, torg, tdest, tapex, circumcenter, xi, eta,
+ offcenter)
+struct mesh *m;
+struct behavior *b;
+vertex torg;
+vertex tdest;
+vertex tapex;
+vertex circumcenter;
+tREAL *xi;
+tREAL *eta;
+int offcenter;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ tREAL xdo, ydo, xao, yao;
+ tREAL dodist, aodist, dadist;
+ tREAL denominator;
+ tREAL dx, dy, dxoff, dyoff;
+
+ m->circumcentercount++;
+
+ /* Compute the circumcenter of the triangle. */
+ xdo = tdest[0] - torg[0];
+ ydo = tdest[1] - torg[1];
+ xao = tapex[0] - torg[0];
+ yao = tapex[1] - torg[1];
+ dodist = xdo * xdo + ydo * ydo;
+ aodist = xao * xao + yao * yao;
+ dadist = (tdest[0] - tapex[0]) * (tdest[0] - tapex[0]) +
+ (tdest[1] - tapex[1]) * (tdest[1] - tapex[1]);
+ if (b->noexact) {
+ denominator = 0.5 / (xdo * yao - xao * ydo);
+ } else {
+ /* Use the counterclockwise() routine to ensure a positive (and */
+ /* reasonably accurate) result, avoiding any possibility of */
+ /* division by zero. */
+ denominator = 0.5 / counterclockwise(m, b, tdest, tapex, torg);
+ /* Don't count the above as an orientation test. */
+ m->counterclockcount--;
+ }
+ dx = (yao * dodist - ydo * aodist) * denominator;
+ dy = (xdo * aodist - xao * dodist) * denominator;
+
+ /* Find the (squared) length of the triangle's shortest edge. This */
+ /* serves as a conservative estimate of the insertion radius of the */
+ /* circumcenter's parent. The estimate is used to ensure that */
+ /* the algorithm terminates even if very small angles appear in */
+ /* the input PSLG. */
+ if ((dodist < aodist) && (dodist < dadist)) {
+ if (offcenter && (b->offconstant > 0.0f)) {
+ /* Find the position of the off-center, as described by Alper Ungor. */
+ dxoff = 0.5 * xdo - b->offconstant * ydo;
+ dyoff = 0.5 * ydo + b->offconstant * xdo;
+ /* If the off-center is closer to the origin than the */
+ /* circumcenter, use the off-center instead. */
+ if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) {
+ dx = dxoff;
+ dy = dyoff;
+ }
+ }
+ } else if (aodist < dadist) {
+ if (offcenter && (b->offconstant > 0.0f)) {
+ dxoff = 0.5 * xao + b->offconstant * yao;
+ dyoff = 0.5 * yao - b->offconstant * xao;
+ /* If the off-center is closer to the origin than the */
+ /* circumcenter, use the off-center instead. */
+ if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) {
+ dx = dxoff;
+ dy = dyoff;
+ }
+ }
+ } else {
+ if (offcenter && (b->offconstant > 0.0f)) {
+ dxoff = 0.5 * (tapex[0] - tdest[0]) -
+ b->offconstant * (tapex[1] - tdest[1]);
+ dyoff = 0.5 * (tapex[1] - tdest[1]) +
+ b->offconstant * (tapex[0] - tdest[0]);
+ /* If the off-center is closer to the destination than the */
+ /* circumcenter, use the off-center instead. */
+ if (dxoff * dxoff + dyoff * dyoff <
+ (dx - xdo) * (dx - xdo) + (dy - ydo) * (dy - ydo)) {
+ dx = xdo + dxoff;
+ dy = ydo + dyoff;
+ }
+ }
+ }
+
+ circumcenter[0] = torg[0] + dx;
+ circumcenter[1] = torg[1] + dy;
+
+ /* To interpolate vertex attributes for the new vertex inserted at */
+ /* the circumcenter, define a coordinate system with a xi-axis, */
+ /* directed from the triangle's origin to its destination, and */
+ /* an eta-axis, directed from its origin to its apex. */
+ /* Calculate the xi and eta coordinates of the circumcenter. */
+ *xi = (yao * dx - xao * dy) * (2.0 * denominator);
+ *eta = (xdo * dy - ydo * dx) * (2.0 * denominator);
+}
+
+/** **/
+/** **/
+/********* Geometric primitives end here *********/
+
+/*****************************************************************************/
+/* */
+/* triangleinit() Initialize some variables. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void triangleinit(struct mesh *m)
+#else /* not ANSI_DECLARATORS */
+void triangleinit(m)
+struct mesh *m;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ poolzero(&m->vertices);
+ poolzero(&m->triangles);
+ poolzero(&m->subsegs);
+ poolzero(&m->viri);
+ poolzero(&m->badsubsegs);
+ poolzero(&m->badtriangles);
+ poolzero(&m->flipstackers);
+ poolzero(&m->splaynodes);
+
+ m->recenttri.tri = (triangle *) NULL; /* No triangle has been visited yet. */
+ m->undeads = 0; /* No eliminated input vertices yet. */
+ m->samples = 1; /* Point location should take at least one sample. */
+ m->checksegments = 0; /* There are no segments in the triangulation yet. */
+ m->checkquality = 0; /* The quality triangulation stage has not begun. */
+ m->incirclecount = m->counterclockcount = m->orient3dcount = 0;
+ m->hyperbolacount = m->circletopcount = m->circumcentercount = 0;
+ randomseed = 1;
+
+ exactinit(); /* Initialize exact arithmetic constants. */
+}
+
+/*****************************************************************************/
+/* */
+/* randomnation() Generate a random number between 0 and `choices' - 1. */
+/* */
+/* This is a simple linear congruential random number generator. Hence, it */
+/* is a bad random number generator, but good enough for most randomized */
+/* geometric algorithms. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+unsigned long randomnation(unsigned int choices)
+#else /* not ANSI_DECLARATORS */
+unsigned long randomnation(choices)
+unsigned int choices;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ randomseed = (randomseed * 1366l + 150889l) % 714025l;
+ return randomseed / (714025l / choices + 1);
+}
+
+/********* Mesh quality testing routines begin here *********/
+/** **/
+/** **/
+
+/*****************************************************************************/
+/* */
+/* checkmesh() Test the mesh for topological consistency. */
+/* */
+/*****************************************************************************/
+
+#ifndef REDUCED
+
+#ifdef ANSI_DECLARATORS
+void checkmesh(struct mesh *m, struct behavior *b)
+#else /* not ANSI_DECLARATORS */
+void checkmesh(m, b)
+struct mesh *m;
+struct behavior *b;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri triangleloop;
+ struct otri oppotri, oppooppotri;
+ vertex triorg, tridest, triapex;
+ vertex oppoorg, oppodest;
+ int horrors;
+ int saveexact;
+ triangle ptr; /* Temporary variable used by sym(). */
+
+ /* Temporarily turn on exact arithmetic if it's off. */
+ saveexact = b->noexact;
+ b->noexact = 0;
+ if (!b->quiet) {
+ printf(" Checking consistency of mesh...\n");
+ }
+ horrors = 0;
+ /* Run through the list of triangles, checking each one. */
+ traversalinit(&m->triangles);
+ triangleloop.tri = triangletraverse(m);
+ while (triangleloop.tri != (triangle *) NULL) {
+ /* Check all three edges of the triangle. */
+ for (triangleloop.orient = 0; triangleloop.orient < 3;
+ triangleloop.orient++) {
+ org(triangleloop, triorg);
+ dest(triangleloop, tridest);
+ if (triangleloop.orient == 0) { /* Only test for inversion once. */
+ /* Test if the triangle is flat or inverted. */
+ apex(triangleloop, triapex);
+ if (counterclockwise(m, b, triorg, tridest, triapex) <= 0.0f) {
+ printf(" !! !! Inverted ");
+ printtriangle(m, b, &triangleloop);
+ horrors++;
+ }
+ }
+ /* Find the neighboring triangle on this edge. */
+ sym(triangleloop, oppotri);
+ if (oppotri.tri != m->dummytri) {
+ /* Check that the triangle's neighbor knows it's a neighbor. */
+ sym(oppotri, oppooppotri);
+ if ((triangleloop.tri != oppooppotri.tri)
+ || (triangleloop.orient != oppooppotri.orient)) {
+ printf(" !! !! Asymmetric triangle-triangle bond:\n");
+ if (triangleloop.tri == oppooppotri.tri) {
+ printf(" (Right triangle, wrong orientation)\n");
+ }
+ printf(" First ");
+ printtriangle(m, b, &triangleloop);
+ printf(" Second (nonreciprocating) ");
+ printtriangle(m, b, &oppotri);
+ horrors++;
+ }
+ /* Check that both triangles agree on the identities */
+ /* of their shared vertices. */
+ org(oppotri, oppoorg);
+ dest(oppotri, oppodest);
+ if ((triorg != oppodest) || (tridest != oppoorg)) {
+ printf(" !! !! Mismatched edge coordinates between two triangles:\n"
+ );
+ printf(" First mismatched ");
+ printtriangle(m, b, &triangleloop);
+ printf(" Second mismatched ");
+ printtriangle(m, b, &oppotri);
+ horrors++;
+ }
+ }
+ }
+ triangleloop.tri = triangletraverse(m);
+ }
+ if (horrors == 0) {
+ if (!b->quiet) {
+ printf(" In my studied opinion, the mesh appears to be consistent.\n");
+ }
+ } else if (horrors == 1) {
+ printf(" !! !! !! !! Precisely one festering wound discovered.\n");
+ } else {
+ printf(" !! !! !! !! %d abominations witnessed.\n", horrors);
+ }
+ /* Restore the status of exact arithmetic. */
+ b->noexact = saveexact;
+}
+
+#endif /* not REDUCED */
+
+/*****************************************************************************/
+/* */
+/* checkdelaunay() Ensure that the mesh is (constrained) Delaunay. */
+/* */
+/*****************************************************************************/
+
+#ifndef REDUCED
+
+#ifdef ANSI_DECLARATORS
+void checkdelaunay(struct mesh *m, struct behavior *b)
+#else /* not ANSI_DECLARATORS */
+void checkdelaunay(m, b)
+struct mesh *m;
+struct behavior *b;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri triangleloop;
+ struct otri oppotri;
+ struct osub opposubseg;
+ vertex triorg, tridest, triapex;
+ vertex oppoapex;
+ int shouldbedelaunay;
+ int horrors;
+ int saveexact;
+ triangle ptr; /* Temporary variable used by sym(). */
+ subseg sptr; /* Temporary variable used by tspivot(). */
+
+ /* Temporarily turn on exact arithmetic if it's off. */
+ saveexact = b->noexact;
+ b->noexact = 0;
+ if (!b->quiet) {
+ printf(" Checking Delaunay property of mesh...\n");
+ }
+ horrors = 0;
+ /* Run through the list of triangles, checking each one. */
+ traversalinit(&m->triangles);
+ triangleloop.tri = triangletraverse(m);
+ while (triangleloop.tri != (triangle *) NULL) {
+ /* Check all three edges of the triangle. */
+ for (triangleloop.orient = 0; triangleloop.orient < 3;
+ triangleloop.orient++) {
+ org(triangleloop, triorg);
+ dest(triangleloop, tridest);
+ apex(triangleloop, triapex);
+ sym(triangleloop, oppotri);
+ apex(oppotri, oppoapex);
+ /* Only test that the edge is locally Delaunay if there is an */
+ /* adjoining triangle whose pointer is larger (to ensure that */
+ /* each pair isn't tested twice). */
+ shouldbedelaunay = (oppotri.tri != m->dummytri) &&
+ !deadtri(oppotri.tri) && (triangleloop.tri < oppotri.tri) &&
+ (triorg != m->infvertex1) && (triorg != m->infvertex2) &&
+ (triorg != m->infvertex3) &&
+ (tridest != m->infvertex1) && (tridest != m->infvertex2) &&
+ (tridest != m->infvertex3) &&
+ (triapex != m->infvertex1) && (triapex != m->infvertex2) &&
+ (triapex != m->infvertex3) &&
+ (oppoapex != m->infvertex1) && (oppoapex != m->infvertex2) &&
+ (oppoapex != m->infvertex3);
+ if (m->checksegments && shouldbedelaunay) {
+ /* If a subsegment separates the triangles, then the edge is */
+ /* constrained, so no local Delaunay test should be done. */
+ tspivot(triangleloop, opposubseg);
+ if (opposubseg.ss != m->dummysub){
+ shouldbedelaunay = 0;
+ }
+ }
+ if (shouldbedelaunay) {
+ if (nonregular(m, b, triorg, tridest, triapex, oppoapex) > 0.0f) {
+ if (!b->weighted) {
+ printf(" !! !! Non-Delaunay pair of triangles:\n");
+ printf(" First non-Delaunay ");
+ printtriangle(m, b, &triangleloop);
+ printf(" Second non-Delaunay ");
+ } else {
+ printf(" !! !! Non-regular pair of triangles:\n");
+ printf(" First non-regular ");
+ printtriangle(m, b, &triangleloop);
+ printf(" Second non-regular ");
+ }
+ printtriangle(m, b, &oppotri);
+ horrors++;
+ }
+ }
+ }
+ triangleloop.tri = triangletraverse(m);
+ }
+ if (horrors == 0) {
+ if (!b->quiet) {
+ printf(
+ " By virtue of my perceptive intelligence, I declare the mesh Delaunay.\n");
+ }
+ } else if (horrors == 1) {
+ printf(
+ " !! !! !! !! Precisely one terrifying transgression identified.\n");
+ } else {
+ printf(" !! !! !! !! %d obscenities viewed with horror.\n", horrors);
+ }
+ /* Restore the status of exact arithmetic. */
+ b->noexact = saveexact;
+}
+
+#endif /* not REDUCED */
+
+/*****************************************************************************/
+/* */
+/* enqueuebadtriang() Add a bad triangle data structure to the end of a */
+/* queue. */
+/* */
+/* The queue is actually a set of 4096 queues. I use multiple queues to */
+/* give priority to smaller angles. I originally implemented a heap, but */
+/* the queues are faster by a larger margin than I'd suspected. */
+/* */
+/*****************************************************************************/
+
+#ifndef CDT_ONLY
+
+#ifdef ANSI_DECLARATORS
+void enqueuebadtriang(struct mesh *m, struct behavior *b,
+ struct badtriang *badtri)
+#else /* not ANSI_DECLARATORS */
+void enqueuebadtriang(m, b, badtri)
+struct mesh *m;
+struct behavior *b;
+struct badtriang *badtri;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ tREAL length, multiplier;
+ int exponent, expincrement;
+ int queuenumber;
+ int posexponent;
+ int i;
+
+ if (b->verbose > 2) {
+ printf(" Queueing bad triangle:\n");
+ printf(" (%.12g, %.12g) (%.12g, %.12g) (%.12g, %.12g)\n",
+ badtri->triangorg[0], badtri->triangorg[1],
+ badtri->triangdest[0], badtri->triangdest[1],
+ badtri->triangapex[0], badtri->triangapex[1]);
+ }
+
+ /* Determine the appropriate queue to put the bad triangle into. */
+ /* Recall that the key is the square of its shortest edge length. */
+ if (badtri->key >= 1.0f) {
+ length = badtri->key;
+ posexponent = 1;
+ } else {
+ /* `badtri->key' is 2.0 to a negative exponent, so we'll record that */
+ /* fact and use the reciprocal of `badtri->key', which is > 1.0. */
+ length = 1.0 / badtri->key;
+ posexponent = 0;
+ }
+ /* `length' is approximately 2.0 to what exponent? The following code */
+ /* determines the answer in time logarithmic in the exponent. */
+ exponent = 0;
+ while (length > 2.0f) {
+ /* Find an approximation by repeated squaring of two. */
+ expincrement = 1;
+ multiplier = 0.5f;
+ while (length * multiplier * multiplier > 1.0f) {
+ expincrement *= 2;
+ multiplier *= multiplier;
+ }
+ /* Reduce the value of `length', then iterate if necessary. */
+ exponent += expincrement;
+ length *= multiplier;
+ }
+ /* `length' is approximately squareroot(2.0f) to what exponent? */
+ exponent = (int)(2.0f * exponent + (length > SQUAREROOTTWO));
+ /* `exponent' is now in the range 0...2047 for IEEE double precision. */
+ /* Choose a queue in the range 0...4095. The shortest edges have the */
+ /* highest priority (queue 4095). */
+ if (posexponent) {
+ queuenumber = 2047 - exponent;
+ } else {
+ queuenumber = 2048 + exponent;
+ }
+
+ /* Are we inserting into an empty queue? */
+ if (m->queuefront[queuenumber] == (struct badtriang *) NULL) {
+ /* Yes, we are inserting into an empty queue. */
+ /* Will this become the highest-priority queue? */
+ if (queuenumber > m->firstnonemptyq) {
+ /* Yes, this is the highest-priority queue. */
+ m->nextnonemptyq[queuenumber] = m->firstnonemptyq;
+ m->firstnonemptyq = queuenumber;
+ } else {
+ /* No, this is not the highest-priority queue. */
+ /* Find the queue with next higher priority. */
+ i = queuenumber + 1;
+ while (m->queuefront[i] == (struct badtriang *) NULL) {
+ i++;
+ }
+ /* Mark the newly nonempty queue as following a higher-priority queue. */
+ m->nextnonemptyq[queuenumber] = m->nextnonemptyq[i];
+ m->nextnonemptyq[i] = queuenumber;
+ }
+ /* Put the bad triangle at the beginning of the (empty) queue. */
+ m->queuefront[queuenumber] = badtri;
+ } else {
+ /* Add the bad triangle to the end of an already nonempty queue. */
+ m->queuetail[queuenumber]->nexttriang = badtri;
+ }
+ /* Maintain a pointer to the last triangle of the queue. */
+ m->queuetail[queuenumber] = badtri;
+ /* Newly enqueued bad triangle has no successor in the queue. */
+ badtri->nexttriang = (struct badtriang *) NULL;
+}
+
+#endif /* not CDT_ONLY */
+
+/*****************************************************************************/
+/* */
+/* enqueuebadtri() Add a bad triangle to the end of a queue. */
+/* */
+/* Allocates a badtriang data structure for the triangle, then passes it to */
+/* enqueuebadtriang(). */
+/* */
+/*****************************************************************************/
+
+#ifndef CDT_ONLY
+
+#ifdef ANSI_DECLARATORS
+void enqueuebadtri(struct mesh *m, struct behavior *b, struct otri *enqtri,
+ tREAL minedge, vertex enqapex, vertex enqorg, vertex enqdest)
+#else /* not ANSI_DECLARATORS */
+void enqueuebadtri(m, b, enqtri, minedge, enqapex, enqorg, enqdest)
+struct mesh *m;
+struct behavior *b;
+struct otri *enqtri;
+tREAL minedge;
+vertex enqapex;
+vertex enqorg;
+vertex enqdest;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct badtriang *newbad;
+
+ /* Allocate space for the bad triangle. */
+ newbad = (struct badtriang *) poolalloc(&m->badtriangles);
+ newbad->poortri = encode(*enqtri);
+ newbad->key = minedge;
+ newbad->triangapex = enqapex;
+ newbad->triangorg = enqorg;
+ newbad->triangdest = enqdest;
+ enqueuebadtriang(m, b, newbad);
+}
+
+#endif /* not CDT_ONLY */
+
+/*****************************************************************************/
+/* */
+/* dequeuebadtriang() Remove a triangle from the front of the queue. */
+/* */
+/*****************************************************************************/
+
+#ifndef CDT_ONLY
+
+#ifdef ANSI_DECLARATORS
+struct badtriang *dequeuebadtriang(struct mesh *m)
+#else /* not ANSI_DECLARATORS */
+struct badtriang *dequeuebadtriang(m)
+struct mesh *m;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct badtriang *result;
+
+ /* If no queues are nonempty, return NULL. */
+ if (m->firstnonemptyq < 0) {
+ return (struct badtriang *) NULL;
+ }
+ /* Find the first triangle of the highest-priority queue. */
+ result = m->queuefront[m->firstnonemptyq];
+ /* Remove the triangle from the queue. */
+ m->queuefront[m->firstnonemptyq] = result->nexttriang;
+ /* If this queue is now empty, note the new highest-priority */
+ /* nonempty queue. */
+ if (result == m->queuetail[m->firstnonemptyq]) {
+ m->firstnonemptyq = m->nextnonemptyq[m->firstnonemptyq];
+ }
+ return result;
+}
+
+#endif /* not CDT_ONLY */
+
+/*****************************************************************************/
+/* */
+/* checkseg4encroach() Check a subsegment to see if it is encroached; add */
+/* it to the list if it is. */
+/* */
+/* A subsegment is encroached if there is a vertex in its diametral lens. */
+/* For Ruppert's algorithm (-D switch), the "diametral lens" is the */
+/* diametral circle. For Chew's algorithm (default), the diametral lens is */
+/* just big enough to enclose two isosceles triangles whose bases are the */
+/* subsegment. Each of the two isosceles triangles has two angles equal */
+/* to `b->minangle'. */
+/* */
+/* Chew's algorithm does not require diametral lenses at all--but they save */
+/* time. Any vertex inside a subsegment's diametral lens implies that the */
+/* triangle adjoining the subsegment will be too skinny, so it's only a */
+/* matter of time before the encroaching vertex is deleted by Chew's */
+/* algorithm. It's faster to simply not insert the doomed vertex in the */
+/* first place, which is why I use diametral lenses with Chew's algorithm. */
+/* */
+/* Returns a nonzero value if the subsegment is encroached. */
+/* */
+/*****************************************************************************/
+
+#ifndef CDT_ONLY
+
+#ifdef ANSI_DECLARATORS
+int checkseg4encroach(struct mesh *m, struct behavior *b,
+ struct osub *testsubseg)
+#else /* not ANSI_DECLARATORS */
+int checkseg4encroach(m, b, testsubseg)
+struct mesh *m;
+struct behavior *b;
+struct osub *testsubseg;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri neighbortri;
+ struct osub testsym;
+ struct badsubseg *encroachedseg;
+ tREAL dotproduct;
+ int encroached;
+ int sides;
+ vertex eorg, edest, eapex;
+ triangle ptr; /* Temporary variable used by stpivot(). */
+
+ encroached = 0;
+ sides = 0;
+
+ sorg(*testsubseg, eorg);
+ sdest(*testsubseg, edest);
+ /* Check one neighbor of the subsegment. */
+ stpivot(*testsubseg, neighbortri);
+ /* Does the neighbor exist, or is this a boundary edge? */
+ if (neighbortri.tri != m->dummytri) {
+ sides++;
+ /* Find a vertex opposite this subsegment. */
+ apex(neighbortri, eapex);
+ /* Check whether the apex is in the diametral lens of the subsegment */
+ /* (the diametral circle if `conformdel' is set). A dot product */
+ /* of two sides of the triangle is used to check whether the angle */
+ /* at the apex is greater than (180 - 2 `minangle') degrees (for */
+ /* lenses; 90 degrees for diametral circles). */
+ dotproduct = (eorg[0] - eapex[0]) * (edest[0] - eapex[0]) +
+ (eorg[1] - eapex[1]) * (edest[1] - eapex[1]);
+ if (dotproduct < 0.0f) {
+ if (b->conformdel ||
+ (dotproduct * dotproduct >=
+ (2.0 * b->goodangle - 1.0f) * (2.0 * b->goodangle - 1.0f) *
+ ((eorg[0] - eapex[0]) * (eorg[0] - eapex[0]) +
+ (eorg[1] - eapex[1]) * (eorg[1] - eapex[1])) *
+ ((edest[0] - eapex[0]) * (edest[0] - eapex[0]) +
+ (edest[1] - eapex[1]) * (edest[1] - eapex[1])))) {
+ encroached = 1;
+ }
+ }
+ }
+ /* Check the other neighbor of the subsegment. */
+ ssym(*testsubseg, testsym);
+ stpivot(testsym, neighbortri);
+ /* Does the neighbor exist, or is this a boundary edge? */
+ if (neighbortri.tri != m->dummytri) {
+ sides++;
+ /* Find the other vertex opposite this subsegment. */
+ apex(neighbortri, eapex);
+ /* Check whether the apex is in the diametral lens of the subsegment */
+ /* (or the diametral circle, if `conformdel' is set). */
+ dotproduct = (eorg[0] - eapex[0]) * (edest[0] - eapex[0]) +
+ (eorg[1] - eapex[1]) * (edest[1] - eapex[1]);
+ if (dotproduct < 0.0f) {
+ if (b->conformdel ||
+ (dotproduct * dotproduct >=
+ (2.0 * b->goodangle - 1.0f) * (2.0 * b->goodangle - 1.0f) *
+ ((eorg[0] - eapex[0]) * (eorg[0] - eapex[0]) +
+ (eorg[1] - eapex[1]) * (eorg[1] - eapex[1])) *
+ ((edest[0] - eapex[0]) * (edest[0] - eapex[0]) +
+ (edest[1] - eapex[1]) * (edest[1] - eapex[1])))) {
+ encroached += 2;
+ }
+ }
+ }
+
+ if (encroached && (!b->nobisect || ((b->nobisect == 1) && (sides == 2)))) {
+ if (b->verbose > 2) {
+ printf(
+ " Queueing encroached subsegment (%.12g, %.12g) (%.12g, %.12g).\n",
+ eorg[0], eorg[1], edest[0], edest[1]);
+ }
+ /* Add the subsegment to the list of encroached subsegments. */
+ /* Be sure to get the orientation right. */
+ encroachedseg = (struct badsubseg *) poolalloc(&m->badsubsegs);
+ if (encroached == 1) {
+ encroachedseg->encsubseg = sencode(*testsubseg);
+ encroachedseg->subsegorg = eorg;
+ encroachedseg->subsegdest = edest;
+ } else {
+ encroachedseg->encsubseg = sencode(testsym);
+ encroachedseg->subsegorg = edest;
+ encroachedseg->subsegdest = eorg;
+ }
+ }
+
+ return encroached;
+}
+
+#endif /* not CDT_ONLY */
+
+/*****************************************************************************/
+/* */
+/* testtriangle() Test a triangle for quality and size. */
+/* */
+/* Tests a triangle to see if it satisfies the minimum angle condition and */
+/* the maximum area condition. Triangles that aren't up to spec are added */
+/* to the bad triangle queue. */
+/* */
+/*****************************************************************************/
+
+#ifndef CDT_ONLY
+
+#ifdef ANSI_DECLARATORS
+void testtriangle(struct mesh *m, struct behavior *b, struct otri *testtri)
+#else /* not ANSI_DECLARATORS */
+void testtriangle(m, b, testtri)
+struct mesh *m;
+struct behavior *b;
+struct otri *testtri;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri tri1, tri2;
+ struct osub testsub;
+ vertex torg, tdest, tapex;
+ vertex base1, base2;
+ vertex org1, dest1, org2, dest2;
+ vertex joinvertex;
+ tREAL dxod, dyod, dxda, dyda, dxao, dyao;
+ tREAL dxod2, dyod2, dxda2, dyda2, dxao2, dyao2;
+ tREAL apexlen, orglen, destlen, minedge;
+ tREAL angle;
+ tREAL area;
+ tREAL dist1, dist2;
+ subseg sptr; /* Temporary variable used by tspivot(). */
+ triangle ptr; /* Temporary variable used by oprev() and dnext(). */
+
+ org(*testtri, torg);
+ dest(*testtri, tdest);
+ apex(*testtri, tapex);
+ dxod = torg[0] - tdest[0];
+ dyod = torg[1] - tdest[1];
+ dxda = tdest[0] - tapex[0];
+ dyda = tdest[1] - tapex[1];
+ dxao = tapex[0] - torg[0];
+ dyao = tapex[1] - torg[1];
+ dxod2 = dxod * dxod;
+ dyod2 = dyod * dyod;
+ dxda2 = dxda * dxda;
+ dyda2 = dyda * dyda;
+ dxao2 = dxao * dxao;
+ dyao2 = dyao * dyao;
+ /* Find the lengths of the triangle's three edges. */
+ apexlen = dxod2 + dyod2;
+ orglen = dxda2 + dyda2;
+ destlen = dxao2 + dyao2;
+
+ if ((apexlen < orglen) && (apexlen < destlen)) {
+ /* The edge opposite the apex is shortest. */
+ minedge = apexlen;
+ /* Find the square of the cosine of the angle at the apex. */
+ angle = dxda * dxao + dyda * dyao;
+ angle = angle * angle / (orglen * destlen);
+ base1 = torg;
+ base2 = tdest;
+ otricopy(*testtri, tri1);
+ } else if (orglen < destlen) {
+ /* The edge opposite the origin is shortest. */
+ minedge = orglen;
+ /* Find the square of the cosine of the angle at the origin. */
+ angle = dxod * dxao + dyod * dyao;
+ angle = angle * angle / (apexlen * destlen);
+ base1 = tdest;
+ base2 = tapex;
+ lnext(*testtri, tri1);
+ } else {
+ /* The edge opposite the destination is shortest. */
+ minedge = destlen;
+ /* Find the square of the cosine of the angle at the destination. */
+ angle = dxod * dxda + dyod * dyda;
+ angle = angle * angle / (apexlen * orglen);
+ base1 = tapex;
+ base2 = torg;
+ lprev(*testtri, tri1);
+ }
+
+ if (b->vararea || b->fixedarea || b->usertest) {
+ /* Check whether the area is larger than permitted. */
+ area = 0.5 * (dxod * dyda - dyod * dxda);
+ if (b->fixedarea && (area > b->maxarea)) {
+ /* Add this triangle to the list of bad triangles. */
+ enqueuebadtri(m, b, testtri, minedge, tapex, torg, tdest);
+ return;
+ }
+
+ /* Nonpositive area constraints are treated as unconstrained. */
+ if ((b->vararea) && (area > areabound(*testtri)) &&
+ (areabound(*testtri) > 0.0f)) {
+ /* Add this triangle to the list of bad triangles. */
+ enqueuebadtri(m, b, testtri, minedge, tapex, torg, tdest);
+ return;
+ }
+
+ if (b->usertest) {
+ /* Check whether the user thinks this triangle is too large. */
+ if (triunsuitable(torg, tdest, tapex, area)) {
+ enqueuebadtri(m, b, testtri, minedge, tapex, torg, tdest);
+ return;
+ }
+ }
+ }
+
+ /* Check whether the angle is smaller than permitted. */
+ if (angle > b->goodangle) {
+ /* Use the rules of Miller, Pav, and Walkington to decide that certain */
+ /* triangles should not be split, even if they have bad angles. */
+ /* A skinny triangle is not split if its shortest edge subtends a */
+ /* small input angle, and both endpoints of the edge lie on a */
+ /* concentric circular shell. For convenience, I make a small */
+ /* adjustment to that rule: I check if the endpoints of the edge */
+ /* both lie in segment interiors, equidistant from the apex where */
+ /* the two segments meet. */
+ /* First, check if both points lie in segment interiors. */
+ if ((vertextype(base1) == SEGMENTVERTEX) &&
+ (vertextype(base2) == SEGMENTVERTEX)) {
+ /* Check if both points lie in a common segment. If they do, the */
+ /* skinny triangle is enqueued to be split as usual. */
+ tspivot(tri1, testsub);
+ if (testsub.ss == m->dummysub) {
+ /* No common segment. Find a subsegment that contains `torg'. */
+ otricopy(tri1, tri2);
+ do {
+ oprevself(tri1);
+ tspivot(tri1, testsub);
+ } while (testsub.ss == m->dummysub);
+ /* Find the endpoints of the containing segment. */
+ segorg(testsub, org1);
+ segdest(testsub, dest1);
+ /* Find a subsegment that contains `tdest'. */
+ do {
+ dnextself(tri2);
+ tspivot(tri2, testsub);
+ } while (testsub.ss == m->dummysub);
+ /* Find the endpoints of the containing segment. */
+ segorg(testsub, org2);
+ segdest(testsub, dest2);
+ /* Check if the two containing segments have an endpoint in common. */
+ joinvertex = (vertex) NULL;
+ if ((dest1[0] == org2[0]) && (dest1[1] == org2[1])) {
+ joinvertex = dest1;
+ } else if ((org1[0] == dest2[0]) && (org1[1] == dest2[1])) {
+ joinvertex = org1;
+ }
+ if (joinvertex != (vertex) NULL) {
+ /* Compute the distance from the common endpoint (of the two */
+ /* segments) to each of the endpoints of the shortest edge. */
+ dist1 = ((base1[0] - joinvertex[0]) * (base1[0] - joinvertex[0]) +
+ (base1[1] - joinvertex[1]) * (base1[1] - joinvertex[1]));
+ dist2 = ((base2[0] - joinvertex[0]) * (base2[0] - joinvertex[0]) +
+ (base2[1] - joinvertex[1]) * (base2[1] - joinvertex[1]));
+ /* If the two distances are equal, don't split the triangle. */
+ if ((dist1 < 1.001 * dist2) && (dist1 > 0.999 * dist2)) {
+ /* Return now to avoid enqueueing the bad triangle. */
+ return;
+ }
+ }
+ }
+ }
+
+ /* Add this triangle to the list of bad triangles. */
+ enqueuebadtri(m, b, testtri, minedge, tapex, torg, tdest);
+ }
+}
+
+#endif /* not CDT_ONLY */
+
+/** **/
+/** **/
+/********* Mesh quality testing routines end here *********/
+
+/********* Point location routines begin here *********/
+/** **/
+/** **/
+
+/*****************************************************************************/
+/* */
+/* makevertexmap() Construct a mapping from vertices to triangles to */
+/* improve the speed of point location for segment */
+/* insertion. */
+/* */
+/* Traverses all the triangles, and provides each corner of each triangle */
+/* with a pointer to that triangle. Of course, pointers will be */
+/* overwritten by other pointers because (almost) each vertex is a corner */
+/* of several triangles, but in the end every vertex will point to some */
+/* triangle that contains it. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void makevertexmap(struct mesh *m, struct behavior *b)
+#else /* not ANSI_DECLARATORS */
+void makevertexmap(m, b)
+struct mesh *m;
+struct behavior *b;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri triangleloop;
+ vertex triorg;
+
+ if (b->verbose) {
+ printf(" Constructing mapping from vertices to triangles.\n");
+ }
+ traversalinit(&m->triangles);
+ triangleloop.tri = triangletraverse(m);
+ while (triangleloop.tri != (triangle *) NULL) {
+ /* Check all three vertices of the triangle. */
+ for (triangleloop.orient = 0; triangleloop.orient < 3;
+ triangleloop.orient++) {
+ org(triangleloop, triorg);
+ setvertex2tri(triorg, encode(triangleloop));
+ }
+ triangleloop.tri = triangletraverse(m);
+ }
+}
+
+/*****************************************************************************/
+/* */
+/* preciselocate() Find a triangle or edge containing a given point. */
+/* */
+/* Begins its search from `searchtri'. It is important that `searchtri' */
+/* be a handle with the property that `searchpoint' is strictly to the left */
+/* of the edge denoted by `searchtri', or is collinear with that edge and */
+/* does not intersect that edge. (In particular, `searchpoint' should not */
+/* be the origin or destination of that edge.) */
+/* */
+/* These conditions are imposed because preciselocate() is normally used in */
+/* one of two situations: */
+/* */
+/* (1) To try to find the location to insert a new point. Normally, we */
+/* know an edge that the point is strictly to the left of. In the */
+/* incremental Delaunay algorithm, that edge is a bounding box edge. */
+/* In Ruppert's Delaunay refinement algorithm for quality meshing, */
+/* that edge is the shortest edge of the triangle whose circumcenter */
+/* is being inserted. */
+/* */
+/* (2) To try to find an existing point. In this case, any edge on the */
+/* convex hull is a good starting edge. You must screen out the */
+/* possibility that the vertex sought is an endpoint of the starting */
+/* edge before you call preciselocate(). */
+/* */
+/* On completion, `searchtri' is a triangle that contains `searchpoint'. */
+/* */
+/* This implementation differs from that given by Guibas and Stolfi. It */
+/* walks from triangle to triangle, crossing an edge only if `searchpoint' */
+/* is on the other side of the line containing that edge. After entering */
+/* a triangle, there are two edges by which one can leave that triangle. */
+/* If both edges are valid (`searchpoint' is on the other side of both */
+/* edges), one of the two is chosen by drawing a line perpendicular to */
+/* the entry edge (whose endpoints are `forg' and `fdest') passing through */
+/* `fapex'. Depending on which side of this perpendicular `searchpoint' */
+/* falls on, an exit edge is chosen. */
+/* */
+/* This implementation is empirically faster than the Guibas and Stolfi */
+/* point location routine (which I originally used), which tends to spiral */
+/* in toward its target. */
+/* */
+/* Returns ONVERTEX if the point lies on an existing vertex. `searchtri' */
+/* is a handle whose origin is the existing vertex. */
+/* */
+/* Returns ONEDGE if the point lies on a mesh edge. `searchtri' is a */
+/* handle whose primary edge is the edge on which the point lies. */
+/* */
+/* Returns INTRIANGLE if the point lies strictly within a triangle. */
+/* `searchtri' is a handle on the triangle that contains the point. */
+/* */
+/* Returns OUTSIDE if the point lies outside the mesh. `searchtri' is a */
+/* handle whose primary edge the point is to the right of. This might */
+/* occur when the circumcenter of a triangle falls just slightly outside */
+/* the mesh due to floating-point roundoff error. It also occurs when */
+/* seeking a hole or region point that a foolish user has placed outside */
+/* the mesh. */
+/* */
+/* If `stopatsubsegment' is nonzero, the search will stop if it tries to */
+/* walk through a subsegment, and will return OUTSIDE. */
+/* */
+/* WARNING: This routine is designed for convex triangulations, and will */
+/* not generally work after the holes and concavities have been carved. */
+/* However, it can still be used to find the circumcenter of a triangle, as */
+/* long as the search is begun from the triangle in question. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+enum locateresult preciselocate(struct mesh *m, struct behavior *b,
+ vertex searchpoint, struct otri *searchtri,
+ int stopatsubsegment)
+#else /* not ANSI_DECLARATORS */
+enum locateresult preciselocate(m, b, searchpoint, searchtri, stopatsubsegment)
+struct mesh *m;
+struct behavior *b;
+vertex searchpoint;
+struct otri *searchtri;
+int stopatsubsegment;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri backtracktri;
+ struct osub checkedge;
+ vertex forg, fdest, fapex;
+ tREAL orgorient, destorient;
+ int moveleft;
+ triangle ptr; /* Temporary variable used by sym(). */
+ subseg sptr; /* Temporary variable used by tspivot(). */
+
+ if (b->verbose > 2) {
+ printf(" Searching for point (%.12g, %.12g).\n",
+ searchpoint[0], searchpoint[1]);
+ }
+ /* Where are we? */
+ org(*searchtri, forg);
+ dest(*searchtri, fdest);
+ apex(*searchtri, fapex);
+ while (1) {
+ if (b->verbose > 2) {
+ printf(" At (%.12g, %.12g) (%.12g, %.12g) (%.12g, %.12g)\n",
+ forg[0], forg[1], fdest[0], fdest[1], fapex[0], fapex[1]);
+ }
+ /* Check whether the apex is the point we seek. */
+ if ((fapex[0] == searchpoint[0]) && (fapex[1] == searchpoint[1])) {
+ lprevself(*searchtri);
+ return ONVERTEX;
+ }
+ /* Does the point lie on the other side of the line defined by the */
+ /* triangle edge opposite the triangle's destination? */
+ destorient = counterclockwise(m, b, forg, fapex, searchpoint);
+ /* Does the point lie on the other side of the line defined by the */
+ /* triangle edge opposite the triangle's origin? */
+ orgorient = counterclockwise(m, b, fapex, fdest, searchpoint);
+ if (destorient > 0.0f) {
+ if (orgorient > 0.0f) {
+ /* Move left if the inner product of (fapex - searchpoint) and */
+ /* (fdest - forg) is positive. This is equivalent to drawing */
+ /* a line perpendicular to the line (forg, fdest) and passing */
+ /* through `fapex', and determining which side of this line */
+ /* `searchpoint' falls on. */
+ moveleft = (fapex[0] - searchpoint[0]) * (fdest[0] - forg[0]) +
+ (fapex[1] - searchpoint[1]) * (fdest[1] - forg[1]) > 0.0f;
+ } else {
+ moveleft = 1;
+ }
+ } else {
+ if (orgorient > 0.0f) {
+ moveleft = 0;
+ } else {
+ /* The point we seek must be on the boundary of or inside this */
+ /* triangle. */
+ if (destorient == 0.0f) {
+ lprevself(*searchtri);
+ return ONEDGE;
+ }
+ if (orgorient == 0.0f) {
+ lnextself(*searchtri);
+ return ONEDGE;
+ }
+ return INTRIANGLE;
+ }
+ }
+
+ /* Move to another triangle. Leave a trace `backtracktri' in case */
+ /* floating-point roundoff or some such bogey causes us to walk */
+ /* off a boundary of the triangulation. */
+ if (moveleft) {
+ lprev(*searchtri, backtracktri);
+ fdest = fapex;
+ } else {
+ lnext(*searchtri, backtracktri);
+ forg = fapex;
+ }
+ sym(backtracktri, *searchtri);
+
+ if (m->checksegments && stopatsubsegment) {
+ /* Check for walking through a subsegment. */
+ tspivot(backtracktri, checkedge);
+ if (checkedge.ss != m->dummysub) {
+ /* Go back to the last triangle. */
+ otricopy(backtracktri, *searchtri);
+ return OUTSIDE;
+ }
+ }
+ /* Check for walking right out of the triangulation. */
+ if (searchtri->tri == m->dummytri) {
+ /* Go back to the last triangle. */
+ otricopy(backtracktri, *searchtri);
+ return OUTSIDE;
+ }
+
+ apex(*searchtri, fapex);
+ }
+}
+
+/*****************************************************************************/
+/* */
+/* locate() Find a triangle or edge containing a given point. */
+/* */
+/* Searching begins from one of: the input `searchtri', a recently */
+/* encountered triangle `recenttri', or from a triangle chosen from a */
+/* random sample. The choice is made by determining which triangle's */
+/* origin is closest to the point we are searching for. Normally, */
+/* `searchtri' should be a handle on the convex hull of the triangulation. */
+/* */
+/* Details on the random sampling method can be found in the Mucke, Saias, */
+/* and Zhu paper cited in the header of this code. */
+/* */
+/* On completion, `searchtri' is a triangle that contains `searchpoint'. */
+/* */
+/* Returns ONVERTEX if the point lies on an existing vertex. `searchtri' */
+/* is a handle whose origin is the existing vertex. */
+/* */
+/* Returns ONEDGE if the point lies on a mesh edge. `searchtri' is a */
+/* handle whose primary edge is the edge on which the point lies. */
+/* */
+/* Returns INTRIANGLE if the point lies strictly within a triangle. */
+/* `searchtri' is a handle on the triangle that contains the point. */
+/* */
+/* Returns OUTSIDE if the point lies outside the mesh. `searchtri' is a */
+/* handle whose primary edge the point is to the right of. This might */
+/* occur when the circumcenter of a triangle falls just slightly outside */
+/* the mesh due to floating-point roundoff error. It also occurs when */
+/* seeking a hole or region point that a foolish user has placed outside */
+/* the mesh. */
+/* */
+/* WARNING: This routine is designed for convex triangulations, and will */
+/* not generally work after the holes and concavities have been carved. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+enum locateresult locate(struct mesh *m, struct behavior *b,
+ vertex searchpoint, struct otri *searchtri)
+#else /* not ANSI_DECLARATORS */
+enum locateresult locate(m, b, searchpoint, searchtri)
+struct mesh *m;
+struct behavior *b;
+vertex searchpoint;
+struct otri *searchtri;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ VOID **sampleblock;
+ char *firsttri;
+ struct otri sampletri;
+ vertex torg, tdest;
+ unsigned long alignptr;
+ tREAL searchdist, dist;
+ tREAL ahead;
+ long samplesperblock, totalsamplesleft, samplesleft;
+ long population, totalpopulation;
+ triangle ptr; /* Temporary variable used by sym(). */
+
+ if (b->verbose > 2) {
+ printf(" Randomly sampling for a triangle near point (%.12g, %.12g).\n",
+ searchpoint[0], searchpoint[1]);
+ }
+ /* Record the distance from the suggested starting triangle to the */
+ /* point we seek. */
+ org(*searchtri, torg);
+ searchdist = (searchpoint[0] - torg[0]) * (searchpoint[0] - torg[0]) +
+ (searchpoint[1] - torg[1]) * (searchpoint[1] - torg[1]);
+ if (b->verbose > 2) {
+ printf(" Boundary triangle has origin (%.12g, %.12g).\n",
+ torg[0], torg[1]);
+ }
+
+ /* If a recently encountered triangle has been recorded and has not been */
+ /* deallocated, test it as a good starting point. */
+ if (m->recenttri.tri != (triangle *) NULL) {
+ if (!deadtri(m->recenttri.tri)) {
+ org(m->recenttri, torg);
+ if ((torg[0] == searchpoint[0]) && (torg[1] == searchpoint[1])) {
+ otricopy(m->recenttri, *searchtri);
+ return ONVERTEX;
+ }
+ dist = (searchpoint[0] - torg[0]) * (searchpoint[0] - torg[0]) +
+ (searchpoint[1] - torg[1]) * (searchpoint[1] - torg[1]);
+ if (dist < searchdist) {
+ otricopy(m->recenttri, *searchtri);
+ searchdist = dist;
+ if (b->verbose > 2) {
+ printf(" Choosing recent triangle with origin (%.12g, %.12g).\n",
+ torg[0], torg[1]);
+ }
+ }
+ }
+ }
+
+ /* The number of random samples taken is proportional to the cube root of */
+ /* the number of triangles in the mesh. The next bit of code assumes */
+ /* that the number of triangles increases monotonically (or at least */
+ /* doesn't decrease enough to matter). */
+ while (SAMPLEFACTOR * m->samples * m->samples * m->samples <
+ m->triangles.items) {
+ m->samples++;
+ }
+
+ /* We'll draw ceiling(samples * TRIPERBLOCK / maxitems) random samples */
+ /* from each block of triangles (except the first)--until we meet the */
+ /* sample quota. The ceiling means that blocks at the end might be */
+ /* neglected, but I don't care. */
+ samplesperblock = (m->samples * TRIPERBLOCK - 1) / m->triangles.maxitems + 1;
+ /* We'll draw ceiling(samples * itemsfirstblock / maxitems) random samples */
+ /* from the first block of triangles. */
+ samplesleft = (m->samples * m->triangles.itemsfirstblock - 1) /
+ m->triangles.maxitems + 1;
+ totalsamplesleft = m->samples;
+ population = m->triangles.itemsfirstblock;
+ totalpopulation = m->triangles.maxitems;
+ sampleblock = m->triangles.firstblock;
+ sampletri.orient = 0;
+ while (totalsamplesleft > 0) {
+ /* If we're in the last block, `population' needs to be corrected. */
+ if (population > totalpopulation) {
+ population = totalpopulation;
+ }
+ /* Find a pointer to the first triangle in the block. */
+ alignptr = (unsigned long) (sampleblock + 1);
+ firsttri = (char *) (alignptr +
+ (unsigned long) m->triangles.alignbytes -
+ (alignptr %
+ (unsigned long) m->triangles.alignbytes));
+
+ /* Choose `samplesleft' randomly sampled triangles in this block. */
+ do {
+ sampletri.tri = (triangle *) (firsttri +
+ (randomnation((unsigned int) population) *
+ m->triangles.itembytes));
+ if (!deadtri(sampletri.tri)) {
+ org(sampletri, torg);
+ dist = (searchpoint[0] - torg[0]) * (searchpoint[0] - torg[0]) +
+ (searchpoint[1] - torg[1]) * (searchpoint[1] - torg[1]);
+ if (dist < searchdist) {
+ otricopy(sampletri, *searchtri);
+ searchdist = dist;
+ if (b->verbose > 2) {
+ printf(" Choosing triangle with origin (%.12g, %.12g).\n",
+ torg[0], torg[1]);
+ }
+ }
+ }
+
+ samplesleft--;
+ totalsamplesleft--;
+ } while ((samplesleft > 0) && (totalsamplesleft > 0));
+
+ if (totalsamplesleft > 0) {
+ sampleblock = (VOID **) *sampleblock;
+ samplesleft = samplesperblock;
+ totalpopulation -= population;
+ population = TRIPERBLOCK;
+ }
+ }
+
+ /* Where are we? */
+ org(*searchtri, torg);
+ dest(*searchtri, tdest);
+ /* Check the starting triangle's vertices. */
+ if ((torg[0] == searchpoint[0]) && (torg[1] == searchpoint[1])) {
+ return ONVERTEX;
+ }
+ if ((tdest[0] == searchpoint[0]) && (tdest[1] == searchpoint[1])) {
+ lnextself(*searchtri);
+ return ONVERTEX;
+ }
+ /* Orient `searchtri' to fit the preconditions of calling preciselocate(). */
+ ahead = counterclockwise(m, b, torg, tdest, searchpoint);
+ if (ahead < 0.0f) {
+ /* Turn around so that `searchpoint' is to the left of the */
+ /* edge specified by `searchtri'. */
+ symself(*searchtri);
+ } else if (ahead == 0.0f) {
+ /* Check if `searchpoint' is between `torg' and `tdest'. */
+ if (((torg[0] < searchpoint[0]) == (searchpoint[0] < tdest[0])) &&
+ ((torg[1] < searchpoint[1]) == (searchpoint[1] < tdest[1]))) {
+ return ONEDGE;
+ }
+ }
+ return preciselocate(m, b, searchpoint, searchtri, 0);
+}
+
+/** **/
+/** **/
+/********* Point location routines end here *********/
+
+/********* Mesh transformation routines begin here *********/
+/** **/
+/** **/
+
+/*****************************************************************************/
+/* */
+/* insertsubseg() Create a new subsegment and insert it between two */
+/* triangles. */
+/* */
+/* The new subsegment is inserted at the edge described by the handle */
+/* `tri'. Its vertices are properly initialized. The marker `subsegmark' */
+/* is applied to the subsegment and, if appropriate, its vertices. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void insertsubseg(struct mesh *m, struct behavior *b, struct otri *tri,
+ int subsegmark)
+#else /* not ANSI_DECLARATORS */
+void insertsubseg(m, b, tri, subsegmark)
+struct mesh *m;
+struct behavior *b;
+struct otri *tri; /* Edge at which to insert the new subsegment. */
+int subsegmark; /* Marker for the new subsegment. */
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri oppotri;
+ struct osub newsubseg;
+ vertex triorg, tridest;
+ triangle ptr; /* Temporary variable used by sym(). */
+ subseg sptr; /* Temporary variable used by tspivot(). */
+
+ org(*tri, triorg);
+ dest(*tri, tridest);
+ /* Mark vertices if possible. */
+ if (vertexmark(triorg) == 0) {
+ setvertexmark(triorg, subsegmark);
+ }
+ if (vertexmark(tridest) == 0) {
+ setvertexmark(tridest, subsegmark);
+ }
+ /* Check if there's already a subsegment here. */
+ tspivot(*tri, newsubseg);
+ if (newsubseg.ss == m->dummysub) {
+ /* Make new subsegment and initialize its vertices. */
+ makesubseg(m, &newsubseg);
+ setsorg(newsubseg, tridest);
+ setsdest(newsubseg, triorg);
+ setsegorg(newsubseg, tridest);
+ setsegdest(newsubseg, triorg);
+ /* Bond new subsegment to the two triangles it is sandwiched between. */
+ /* Note that the facing triangle `oppotri' might be equal to */
+ /* `dummytri' (outer space), but the new subsegment is bonded to it */
+ /* all the same. */
+ tsbond(*tri, newsubseg);
+ sym(*tri, oppotri);
+ ssymself(newsubseg);
+ tsbond(oppotri, newsubseg);
+ setmark(newsubseg, subsegmark);
+ if (b->verbose > 2) {
+ printf(" Inserting new ");
+ printsubseg(m, b, &newsubseg);
+ }
+ } else {
+ if (mark(newsubseg) == 0) {
+ setmark(newsubseg, subsegmark);
+ }
+ }
+}
+
+/*****************************************************************************/
+/* */
+/* Terminology */
+/* */
+/* A "local transformation" replaces a small set of triangles with another */
+/* set of triangles. This may or may not involve inserting or deleting a */
+/* vertex. */
+/* */
+/* The term "casing" is used to describe the set of triangles that are */
+/* attached to the triangles being transformed, but are not transformed */
+/* themselves. Think of the casing as a fixed hollow structure inside */
+/* which all the action happens. A "casing" is only defined relative to */
+/* a single transformation; each occurrence of a transformation will */
+/* involve a different casing. */
+/* */
+/*****************************************************************************/
+
+/*****************************************************************************/
+/* */
+/* flip() Transform two triangles to two different triangles by flipping */
+/* an edge counterclockwise within a quadrilateral. */
+/* */
+/* Imagine the original triangles, abc and bad, oriented so that the */
+/* shared edge ab lies in a horizontal plane, with the vertex b on the left */
+/* and the vertex a on the right. The vertex c lies below the edge, and */
+/* the vertex d lies above the edge. The `flipedge' handle holds the edge */
+/* ab of triangle abc, and is directed left, from vertex a to vertex b. */
+/* */
+/* The triangles abc and bad are deleted and replaced by the triangles cdb */
+/* and dca. The triangles that represent abc and bad are NOT deallocated; */
+/* they are reused for dca and cdb, respectively. Hence, any handles that */
+/* may have held the original triangles are still valid, although not */
+/* directed as they were before. */
+/* */
+/* Upon completion of this routine, the `flipedge' handle holds the edge */
+/* dc of triangle dca, and is directed down, from vertex d to vertex c. */
+/* (Hence, the two triangles have rotated counterclockwise.) */
+/* */
+/* WARNING: This transformation is geometrically valid only if the */
+/* quadrilateral adbc is convex. Furthermore, this transformation is */
+/* valid only if there is not a subsegment between the triangles abc and */
+/* bad. This routine does not check either of these preconditions, and */
+/* it is the responsibility of the calling routine to ensure that they are */
+/* met. If they are not, the streets shall be filled with wailing and */
+/* gnashing of teeth. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void flip(struct mesh *m, struct behavior *b, struct otri *flipedge)
+#else /* not ANSI_DECLARATORS */
+void flip(m, b, flipedge)
+struct mesh *m;
+struct behavior *b;
+struct otri *flipedge; /* Handle for the triangle abc. */
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri botleft, botright;
+ struct otri topleft, topright;
+ struct otri top;
+ struct otri botlcasing, botrcasing;
+ struct otri toplcasing, toprcasing;
+ struct osub botlsubseg, botrsubseg;
+ struct osub toplsubseg, toprsubseg;
+ vertex leftvertex, rightvertex, botvertex;
+ vertex farvertex;
+ triangle ptr; /* Temporary variable used by sym(). */
+ subseg sptr; /* Temporary variable used by tspivot(). */
+
+ /* Identify the vertices of the quadrilateral. */
+ org(*flipedge, rightvertex);
+ dest(*flipedge, leftvertex);
+ apex(*flipedge, botvertex);
+ sym(*flipedge, top);
+#ifdef SELF_CHECK
+ if (top.tri == m->dummytri) {
+ printf("Internal error in flip(): Attempt to flip on boundary.\n");
+ lnextself(*flipedge);
+ return;
+ }
+ if (m->checksegments) {
+ tspivot(*flipedge, toplsubseg);
+ if (toplsubseg.ss != m->dummysub) {
+ printf("Internal error in flip(): Attempt to flip a segment.\n");
+ lnextself(*flipedge);
+ return;
+ }
+ }
+#endif /* SELF_CHECK */
+ apex(top, farvertex);
+
+ /* Identify the casing of the quadrilateral. */
+ lprev(top, topleft);
+ sym(topleft, toplcasing);
+ lnext(top, topright);
+ sym(topright, toprcasing);
+ lnext(*flipedge, botleft);
+ sym(botleft, botlcasing);
+ lprev(*flipedge, botright);
+ sym(botright, botrcasing);
+ /* Rotate the quadrilateral one-quarter turn counterclockwise. */
+ bond(topleft, botlcasing);
+ bond(botleft, botrcasing);
+ bond(botright, toprcasing);
+ bond(topright, toplcasing);
+
+ if (m->checksegments) {
+ /* Check for subsegments and rebond them to the quadrilateral. */
+ tspivot(topleft, toplsubseg);
+ tspivot(botleft, botlsubseg);
+ tspivot(botright, botrsubseg);
+ tspivot(topright, toprsubseg);
+ if (toplsubseg.ss == m->dummysub) {
+ tsdissolve(topright);
+ } else {
+ tsbond(topright, toplsubseg);
+ }
+ if (botlsubseg.ss == m->dummysub) {
+ tsdissolve(topleft);
+ } else {
+ tsbond(topleft, botlsubseg);
+ }
+ if (botrsubseg.ss == m->dummysub) {
+ tsdissolve(botleft);
+ } else {
+ tsbond(botleft, botrsubseg);
+ }
+ if (toprsubseg.ss == m->dummysub) {
+ tsdissolve(botright);
+ } else {
+ tsbond(botright, toprsubseg);
+ }
+ }
+
+ /* New vertex = vec3ments for the rotated quadrilateral. */
+ setorg(*flipedge, farvertex);
+ setdest(*flipedge, botvertex);
+ setapex(*flipedge, rightvertex);
+ setorg(top, botvertex);
+ setdest(top, farvertex);
+ setapex(top, leftvertex);
+ if (b->verbose > 2) {
+ printf(" Edge flip results in left ");
+ printtriangle(m, b, &top);
+ printf(" and right ");
+ printtriangle(m, b, flipedge);
+ }
+}
+
+/*****************************************************************************/
+/* */
+/* unflip() Transform two triangles to two different triangles by */
+/* flipping an edge clockwise within a quadrilateral. Reverses */
+/* the flip() operation so that the data structures representing */
+/* the triangles are back where they were before the flip(). */
+/* */
+/* Imagine the original triangles, abc and bad, oriented so that the */
+/* shared edge ab lies in a horizontal plane, with the vertex b on the left */
+/* and the vertex a on the right. The vertex c lies below the edge, and */
+/* the vertex d lies above the edge. The `flipedge' handle holds the edge */
+/* ab of triangle abc, and is directed left, from vertex a to vertex b. */
+/* */
+/* The triangles abc and bad are deleted and replaced by the triangles cdb */
+/* and dca. The triangles that represent abc and bad are NOT deallocated; */
+/* they are reused for cdb and dca, respectively. Hence, any handles that */
+/* may have held the original triangles are still valid, although not */
+/* directed as they were before. */
+/* */
+/* Upon completion of this routine, the `flipedge' handle holds the edge */
+/* cd of triangle cdb, and is directed up, from vertex c to vertex d. */
+/* (Hence, the two triangles have rotated clockwise.) */
+/* */
+/* WARNING: This transformation is geometrically valid only if the */
+/* quadrilateral adbc is convex. Furthermore, this transformation is */
+/* valid only if there is not a subsegment between the triangles abc and */
+/* bad. This routine does not check either of these preconditions, and */
+/* it is the responsibility of the calling routine to ensure that they are */
+/* met. If they are not, the streets shall be filled with wailing and */
+/* gnashing of teeth. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void unflip(struct mesh *m, struct behavior *b, struct otri *flipedge)
+#else /* not ANSI_DECLARATORS */
+void unflip(m, b, flipedge)
+struct mesh *m;
+struct behavior *b;
+struct otri *flipedge; /* Handle for the triangle abc. */
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri botleft, botright;
+ struct otri topleft, topright;
+ struct otri top;
+ struct otri botlcasing, botrcasing;
+ struct otri toplcasing, toprcasing;
+ struct osub botlsubseg, botrsubseg;
+ struct osub toplsubseg, toprsubseg;
+ vertex leftvertex, rightvertex, botvertex;
+ vertex farvertex;
+ triangle ptr; /* Temporary variable used by sym(). */
+ subseg sptr; /* Temporary variable used by tspivot(). */
+
+ /* Identify the vertices of the quadrilateral. */
+ org(*flipedge, rightvertex);
+ dest(*flipedge, leftvertex);
+ apex(*flipedge, botvertex);
+ sym(*flipedge, top);
+#ifdef SELF_CHECK
+ if (top.tri == m->dummytri) {
+ printf("Internal error in unflip(): Attempt to flip on boundary.\n");
+ lnextself(*flipedge);
+ return;
+ }
+ if (m->checksegments) {
+ tspivot(*flipedge, toplsubseg);
+ if (toplsubseg.ss != m->dummysub) {
+ printf("Internal error in unflip(): Attempt to flip a subsegment.\n");
+ lnextself(*flipedge);
+ return;
+ }
+ }
+#endif /* SELF_CHECK */
+ apex(top, farvertex);
+
+ /* Identify the casing of the quadrilateral. */
+ lprev(top, topleft);
+ sym(topleft, toplcasing);
+ lnext(top, topright);
+ sym(topright, toprcasing);
+ lnext(*flipedge, botleft);
+ sym(botleft, botlcasing);
+ lprev(*flipedge, botright);
+ sym(botright, botrcasing);
+ /* Rotate the quadrilateral one-quarter turn clockwise. */
+ bond(topleft, toprcasing);
+ bond(botleft, toplcasing);
+ bond(botright, botlcasing);
+ bond(topright, botrcasing);
+
+ if (m->checksegments) {
+ /* Check for subsegments and rebond them to the quadrilateral. */
+ tspivot(topleft, toplsubseg);
+ tspivot(botleft, botlsubseg);
+ tspivot(botright, botrsubseg);
+ tspivot(topright, toprsubseg);
+ if (toplsubseg.ss == m->dummysub) {
+ tsdissolve(botleft);
+ } else {
+ tsbond(botleft, toplsubseg);
+ }
+ if (botlsubseg.ss == m->dummysub) {
+ tsdissolve(botright);
+ } else {
+ tsbond(botright, botlsubseg);
+ }
+ if (botrsubseg.ss == m->dummysub) {
+ tsdissolve(topright);
+ } else {
+ tsbond(topright, botrsubseg);
+ }
+ if (toprsubseg.ss == m->dummysub) {
+ tsdissolve(topleft);
+ } else {
+ tsbond(topleft, toprsubseg);
+ }
+ }
+
+ /* New vertex = vec3ments for the rotated quadrilateral. */
+ setorg(*flipedge, botvertex);
+ setdest(*flipedge, farvertex);
+ setapex(*flipedge, leftvertex);
+ setorg(top, farvertex);
+ setdest(top, botvertex);
+ setapex(top, rightvertex);
+ if (b->verbose > 2) {
+ printf(" Edge unflip results in left ");
+ printtriangle(m, b, flipedge);
+ printf(" and right ");
+ printtriangle(m, b, &top);
+ }
+}
+
+/*****************************************************************************/
+/* */
+/* insertvertex() Insert a vertex into a Delaunay triangulation, */
+/* performing flips as necessary to maintain the Delaunay */
+/* property. */
+/* */
+/* The point `insertvertex' is located. If `searchtri.tri' is not NULL, */
+/* the search for the containing triangle begins from `searchtri'. If */
+/* `searchtri.tri' is NULL, a full point location procedure is called. */
+/* If `insertvertex' is found inside a triangle, the triangle is split into */
+/* three; if `insertvertex' lies on an edge, the edge is split in two, */
+/* thereby splitting the two adjacent triangles into four. Edge flips are */
+/* used to restore the Delaunay property. If `insertvertex' lies on an */
+/* existing vertex, no action is taken, and the value DUPLICATEVERTEX is */
+/* returned. On return, `searchtri' is set to a handle whose origin is the */
+/* existing vertex. */
+/* */
+/* Normally, the parameter `splitseg' is set to NULL, implying that no */
+/* subsegment should be split. In this case, if `insertvertex' is found to */
+/* lie on a segment, no action is taken, and the value VIOLATINGVERTEX is */
+/* returned. On return, `searchtri' is set to a handle whose primary edge */
+/* is the violated subsegment. */
+/* */
+/* If the calling routine wishes to split a subsegment by inserting a */
+/* vertex in it, the parameter `splitseg' should be that subsegment. In */
+/* this case, `searchtri' MUST be the triangle handle reached by pivoting */
+/* from that subsegment; no point location is done. */
+/* */
+/* `segmentflaws' and `triflaws' are flags that indicate whether or not */
+/* there should be checks for the creation of encroached subsegments or bad */
+/* quality triangles. If a newly inserted vertex encroaches upon */
+/* subsegments, these subsegments are added to the list of subsegments to */
+/* be split if `segmentflaws' is set. If bad triangles are created, these */
+/* are added to the queue if `triflaws' is set. */
+/* */
+/* If a duplicate vertex or violated segment does not prevent the vertex */
+/* from being inserted, the return value will be ENCROACHINGVERTEX if the */
+/* vertex encroaches upon a subsegment (and checking is enabled), or */
+/* SUCCESSFULVERTEX otherwise. In either case, `searchtri' is set to a */
+/* handle whose origin is the newly inserted vertex. */
+/* */
+/* insertvertex() does not use flip() for reasons of speed; some */
+/* information can be reused from edge flip to edge flip, like the */
+/* locations of subsegments. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+enum insertvertexresult insertvertex(struct mesh *m, struct behavior *b,
+ vertex newvertex, struct otri *searchtri,
+ struct osub *splitseg,
+ int segmentflaws, int triflaws)
+#else /* not ANSI_DECLARATORS */
+enum insertvertexresult insertvertex(m, b, newvertex, searchtri, splitseg,
+ segmentflaws, triflaws)
+struct mesh *m;
+struct behavior *b;
+vertex newvertex;
+struct otri *searchtri;
+struct osub *splitseg;
+int segmentflaws;
+int triflaws;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri horiz;
+ struct otri top;
+ struct otri botleft, botright;
+ struct otri topleft, topright;
+ struct otri newbotleft, newbotright;
+ struct otri newtopright;
+ struct otri botlcasing, botrcasing;
+ struct otri toplcasing, toprcasing;
+ struct otri testtri;
+ struct osub botlsubseg, botrsubseg;
+ struct osub toplsubseg, toprsubseg;
+ struct osub brokensubseg;
+ struct osub checksubseg;
+ struct osub rightsubseg;
+ struct osub newsubseg;
+ struct badsubseg *encroached;
+ struct flipstacker *newflip;
+ vertex first;
+ vertex leftvertex, rightvertex, botvertex, topvertex, farvertex;
+ vertex segmentorg, segmentdest;
+ tREAL attrib;
+ tREAL area;
+ enum insertvertexresult success;
+ enum locateresult intersect;
+ int doflip;
+ int mirrorflag;
+ int enq;
+ int i;
+ triangle ptr; /* Temporary variable used by sym(). */
+ subseg sptr; /* Temporary variable used by spivot() and tspivot(). */
+
+ if (b->verbose > 1) {
+ printf(" Inserting (%.12g, %.12g).\n", newvertex[0], newvertex[1]);
+ }
+
+ if (splitseg == (struct osub *) NULL) {
+ /* Find the location of the vertex to be inserted. Check if a good */
+ /* starting triangle has already been provided by the caller. */
+ if (searchtri->tri == m->dummytri) {
+ /* Find a boundary triangle. */
+ horiz.tri = m->dummytri;
+ horiz.orient = 0;
+ symself(horiz);
+ /* Search for a triangle containing `newvertex'. */
+ intersect = locate(m, b, newvertex, &horiz);
+ } else {
+ /* Start searching from the triangle provided by the caller. */
+ otricopy(*searchtri, horiz);
+ intersect = preciselocate(m, b, newvertex, &horiz, 1);
+ }
+ } else {
+ /* The calling routine provides the subsegment in which */
+ /* the vertex is inserted. */
+ otricopy(*searchtri, horiz);
+ intersect = ONEDGE;
+ }
+
+ if (intersect == ONVERTEX) {
+ /* There's already a vertex there. Return in `searchtri' a triangle */
+ /* whose origin is the existing vertex. */
+ otricopy(horiz, *searchtri);
+ otricopy(horiz, m->recenttri);
+ return DUPLICATEVERTEX;
+ }
+ if ((intersect == ONEDGE) || (intersect == OUTSIDE)) {
+ /* The vertex falls on an edge or boundary. */
+ if (m->checksegments && (splitseg == (struct osub *) NULL)) {
+ /* Check whether the vertex falls on a subsegment. */
+ tspivot(horiz, brokensubseg);
+ if (brokensubseg.ss != m->dummysub) {
+ /* The vertex falls on a subsegment, and hence will not be inserted. */
+ if (segmentflaws) {
+ enq = b->nobisect != 2;
+ if (enq && (b->nobisect == 1)) {
+ /* This subsegment may be split only if it is an */
+ /* internal boundary. */
+ sym(horiz, testtri);
+ enq = testtri.tri != m->dummytri;
+ }
+ if (enq) {
+ /* Add the subsegment to the list of encroached subsegments. */
+ encroached = (struct badsubseg *) poolalloc(&m->badsubsegs);
+ encroached->encsubseg = sencode(brokensubseg);
+ sorg(brokensubseg, encroached->subsegorg);
+ sdest(brokensubseg, encroached->subsegdest);
+ if (b->verbose > 2) {
+ printf(
+ " Queueing encroached subsegment (%.12g, %.12g) (%.12g, %.12g).\n",
+ encroached->subsegorg[0], encroached->subsegorg[1],
+ encroached->subsegdest[0], encroached->subsegdest[1]);
+ }
+ }
+ }
+ /* Return a handle whose primary edge contains the vertex, */
+ /* which has not been inserted. */
+ otricopy(horiz, *searchtri);
+ otricopy(horiz, m->recenttri);
+ return VIOLATINGVERTEX;
+ }
+ }
+
+ /* Insert the vertex on an edge, dividing one triangle into two (if */
+ /* the edge lies on a boundary) or two triangles into four. */
+ lprev(horiz, botright);
+ sym(botright, botrcasing);
+ sym(horiz, topright);
+ /* Is there a second triangle? (Or does this edge lie on a boundary?) */
+ mirrorflag = topright.tri != m->dummytri;
+ if (mirrorflag) {
+ lnextself(topright);
+ sym(topright, toprcasing);
+ maketriangle(m, b, &newtopright);
+ } else {
+ /* Splitting a boundary edge increases the number of boundary edges. */
+ m->hullsize++;
+ }
+ maketriangle(m, b, &newbotright);
+
+ /* Set the vertices of changed and new triangles. */
+ org(horiz, rightvertex);
+ dest(horiz, leftvertex);
+ apex(horiz, botvertex);
+ setorg(newbotright, botvertex);
+ setdest(newbotright, rightvertex);
+ setapex(newbotright, newvertex);
+ setorg(horiz, newvertex);
+ for (i = 0; i < m->eextras; i++) {
+ /* Set the element attributes of a new triangle. */
+ setelemattribute(newbotright, i, elemattribute(botright, i));
+ }
+ if (b->vararea) {
+ /* Set the area constraint of a new triangle. */
+ setareabound(newbotright, areabound(botright));
+ }
+ if (mirrorflag) {
+ dest(topright, topvertex);
+ setorg(newtopright, rightvertex);
+ setdest(newtopright, topvertex);
+ setapex(newtopright, newvertex);
+ setorg(topright, newvertex);
+ for (i = 0; i < m->eextras; i++) {
+ /* Set the element attributes of another new triangle. */
+ setelemattribute(newtopright, i, elemattribute(topright, i));
+ }
+ if (b->vararea) {
+ /* Set the area constraint of another new triangle. */
+ setareabound(newtopright, areabound(topright));
+ }
+ }
+
+ /* There may be subsegments that need to be bonded */
+ /* to the new triangle(s). */
+ if (m->checksegments) {
+ tspivot(botright, botrsubseg);
+ if (botrsubseg.ss != m->dummysub) {
+ tsdissolve(botright);
+ tsbond(newbotright, botrsubseg);
+ }
+ if (mirrorflag) {
+ tspivot(topright, toprsubseg);
+ if (toprsubseg.ss != m->dummysub) {
+ tsdissolve(topright);
+ tsbond(newtopright, toprsubseg);
+ }
+ }
+ }
+
+ /* Bond the new triangle(s) to the surrounding triangles. */
+ bond(newbotright, botrcasing);
+ lprevself(newbotright);
+ bond(newbotright, botright);
+ lprevself(newbotright);
+ if (mirrorflag) {
+ bond(newtopright, toprcasing);
+ lnextself(newtopright);
+ bond(newtopright, topright);
+ lnextself(newtopright);
+ bond(newtopright, newbotright);
+ }
+
+ if (splitseg != (struct osub *) NULL) {
+ /* Split the subsegment into two. */
+ setsdest(*splitseg, newvertex);
+ segorg(*splitseg, segmentorg);
+ segdest(*splitseg, segmentdest);
+ ssymself(*splitseg);
+ spivot(*splitseg, rightsubseg);
+ insertsubseg(m, b, &newbotright, mark(*splitseg));
+ tspivot(newbotright, newsubseg);
+ setsegorg(newsubseg, segmentorg);
+ setsegdest(newsubseg, segmentdest);
+ sbond(*splitseg, newsubseg);
+ ssymself(newsubseg);
+ sbond(newsubseg, rightsubseg);
+ ssymself(*splitseg);
+ /* Transfer the subsegment's boundary marker to the vertex */
+ /* if required. */
+ if (vertexmark(newvertex) == 0) {
+ setvertexmark(newvertex, mark(*splitseg));
+ }
+ }
+
+ if (m->checkquality) {
+ poolrestart(&m->flipstackers);
+ m->lastflip = (struct flipstacker *) poolalloc(&m->flipstackers);
+ m->lastflip->flippedtri = encode(horiz);
+ m->lastflip->prevflip = (struct flipstacker *) &insertvertex;
+ }
+
+#ifdef SELF_CHECK
+ if (counterclockwise(m, b, rightvertex, leftvertex, botvertex) < 0.0f) {
+ printf("Internal error in insertvertex():\n");
+ printf(
+ " Clockwise triangle prior to edge vertex insertion (bottom).\n");
+ }
+ if (mirrorflag) {
+ if (counterclockwise(m, b, leftvertex, rightvertex, topvertex) < 0.0f) {
+ printf("Internal error in insertvertex():\n");
+ printf(" Clockwise triangle prior to edge vertex insertion (top).\n");
+ }
+ if (counterclockwise(m, b, rightvertex, topvertex, newvertex) < 0.0f) {
+ printf("Internal error in insertvertex():\n");
+ printf(
+ " Clockwise triangle after edge vertex insertion (top right).\n");
+ }
+ if (counterclockwise(m, b, topvertex, leftvertex, newvertex) < 0.0f) {
+ printf("Internal error in insertvertex():\n");
+ printf(
+ " Clockwise triangle after edge vertex insertion (top left).\n");
+ }
+ }
+ if (counterclockwise(m, b, leftvertex, botvertex, newvertex) < 0.0f) {
+ printf("Internal error in insertvertex():\n");
+ printf(
+ " Clockwise triangle after edge vertex insertion (bottom left).\n");
+ }
+ if (counterclockwise(m, b, botvertex, rightvertex, newvertex) < 0.0f) {
+ printf("Internal error in insertvertex():\n");
+ printf(
+ " Clockwise triangle after edge vertex insertion (bottom right).\n");
+ }
+#endif /* SELF_CHECK */
+ if (b->verbose > 2) {
+ printf(" Updating bottom left ");
+ printtriangle(m, b, &botright);
+ if (mirrorflag) {
+ printf(" Updating top left ");
+ printtriangle(m, b, &topright);
+ printf(" Creating top right ");
+ printtriangle(m, b, &newtopright);
+ }
+ printf(" Creating bottom right ");
+ printtriangle(m, b, &newbotright);
+ }
+
+ /* Position `horiz' on the first edge to check for */
+ /* the Delaunay property. */
+ lnextself(horiz);
+ } else {
+ /* Insert the vertex in a triangle, splitting it into three. */
+ lnext(horiz, botleft);
+ lprev(horiz, botright);
+ sym(botleft, botlcasing);
+ sym(botright, botrcasing);
+ maketriangle(m, b, &newbotleft);
+ maketriangle(m, b, &newbotright);
+
+ /* Set the vertices of changed and new triangles. */
+ org(horiz, rightvertex);
+ dest(horiz, leftvertex);
+ apex(horiz, botvertex);
+ setorg(newbotleft, leftvertex);
+ setdest(newbotleft, botvertex);
+ setapex(newbotleft, newvertex);
+ setorg(newbotright, botvertex);
+ setdest(newbotright, rightvertex);
+ setapex(newbotright, newvertex);
+ setapex(horiz, newvertex);
+ for (i = 0; i < m->eextras; i++) {
+ /* Set the element attributes of the new triangles. */
+ attrib = elemattribute(horiz, i);
+ setelemattribute(newbotleft, i, attrib);
+ setelemattribute(newbotright, i, attrib);
+ }
+ if (b->vararea) {
+ /* Set the area constraint of the new triangles. */
+ area = areabound(horiz);
+ setareabound(newbotleft, area);
+ setareabound(newbotright, area);
+ }
+
+ /* There may be subsegments that need to be bonded */
+ /* to the new triangles. */
+ if (m->checksegments) {
+ tspivot(botleft, botlsubseg);
+ if (botlsubseg.ss != m->dummysub) {
+ tsdissolve(botleft);
+ tsbond(newbotleft, botlsubseg);
+ }
+ tspivot(botright, botrsubseg);
+ if (botrsubseg.ss != m->dummysub) {
+ tsdissolve(botright);
+ tsbond(newbotright, botrsubseg);
+ }
+ }
+
+ /* Bond the new triangles to the surrounding triangles. */
+ bond(newbotleft, botlcasing);
+ bond(newbotright, botrcasing);
+ lnextself(newbotleft);
+ lprevself(newbotright);
+ bond(newbotleft, newbotright);
+ lnextself(newbotleft);
+ bond(botleft, newbotleft);
+ lprevself(newbotright);
+ bond(botright, newbotright);
+
+ if (m->checkquality) {
+ poolrestart(&m->flipstackers);
+ m->lastflip = (struct flipstacker *) poolalloc(&m->flipstackers);
+ m->lastflip->flippedtri = encode(horiz);
+ m->lastflip->prevflip = (struct flipstacker *) NULL;
+ }
+
+#ifdef SELF_CHECK
+ if (counterclockwise(m, b, rightvertex, leftvertex, botvertex) < 0.0f) {
+ printf("Internal error in insertvertex():\n");
+ printf(" Clockwise triangle prior to vertex insertion.\n");
+ }
+ if (counterclockwise(m, b, rightvertex, leftvertex, newvertex) < 0.0f) {
+ printf("Internal error in insertvertex():\n");
+ printf(" Clockwise triangle after vertex insertion (top).\n");
+ }
+ if (counterclockwise(m, b, leftvertex, botvertex, newvertex) < 0.0f) {
+ printf("Internal error in insertvertex():\n");
+ printf(" Clockwise triangle after vertex insertion (left).\n");
+ }
+ if (counterclockwise(m, b, botvertex, rightvertex, newvertex) < 0.0f) {
+ printf("Internal error in insertvertex():\n");
+ printf(" Clockwise triangle after vertex insertion (right).\n");
+ }
+#endif /* SELF_CHECK */
+ if (b->verbose > 2) {
+ printf(" Updating top ");
+ printtriangle(m, b, &horiz);
+ printf(" Creating left ");
+ printtriangle(m, b, &newbotleft);
+ printf(" Creating right ");
+ printtriangle(m, b, &newbotright);
+ }
+ }
+
+ /* The insertion is successful by default, unless an encroached */
+ /* subsegment is found. */
+ success = SUCCESSFULVERTEX;
+ /* Circle around the newly inserted vertex, checking each edge opposite */
+ /* it for the Delaunay property. Non-Delaunay edges are flipped. */
+ /* `horiz' is always the edge being checked. `first' marks where to */
+ /* stop circling. */
+ org(horiz, first);
+ rightvertex = first;
+ dest(horiz, leftvertex);
+ /* Circle until finished. */
+ while (1) {
+ /* By default, the edge will be flipped. */
+ doflip = 1;
+
+ if (m->checksegments) {
+ /* Check for a subsegment, which cannot be flipped. */
+ tspivot(horiz, checksubseg);
+ if (checksubseg.ss != m->dummysub) {
+ /* The edge is a subsegment and cannot be flipped. */
+ doflip = 0;
+#ifndef CDT_ONLY
+ if (segmentflaws) {
+ /* Does the new vertex encroach upon this subsegment? */
+ if (checkseg4encroach(m, b, &checksubseg)) {
+ success = ENCROACHINGVERTEX;
+ }
+ }
+#endif /* not CDT_ONLY */
+ }
+ }
+
+ if (doflip) {
+ /* Check if the edge is a boundary edge. */
+ sym(horiz, top);
+ if (top.tri == m->dummytri) {
+ /* The edge is a boundary edge and cannot be flipped. */
+ doflip = 0;
+ } else {
+ /* Find the vertex on the other side of the edge. */
+ apex(top, farvertex);
+ /* In the incremental Delaunay triangulation algorithm, any of */
+ /* `leftvertex', `rightvertex', and `farvertex' could be vertices */
+ /* of the triangular bounding box. These vertices must be */
+ /* treated as if they are infinitely distant, even though their */
+ /* "coordinates" are not. */
+ if ((leftvertex == m->infvertex1) || (leftvertex == m->infvertex2) ||
+ (leftvertex == m->infvertex3)) {
+ /* `leftvertex' is infinitely distant. Check the convexity of */
+ /* the boundary of the triangulation. 'farvertex' might be */
+ /* infinite as well, but trust me, this same condition should */
+ /* be applied. */
+ doflip = counterclockwise(m, b, newvertex, rightvertex, farvertex)
+ > 0.0f;
+ } else if ((rightvertex == m->infvertex1) ||
+ (rightvertex == m->infvertex2) ||
+ (rightvertex == m->infvertex3)) {
+ /* `rightvertex' is infinitely distant. Check the convexity of */
+ /* the boundary of the triangulation. 'farvertex' might be */
+ /* infinite as well, but trust me, this same condition should */
+ /* be applied. */
+ doflip = counterclockwise(m, b, farvertex, leftvertex, newvertex)
+ > 0.0f;
+ } else if ((farvertex == m->infvertex1) ||
+ (farvertex == m->infvertex2) ||
+ (farvertex == m->infvertex3)) {
+ /* `farvertex' is infinitely distant and cannot be inside */
+ /* the circumcircle of the triangle `horiz'. */
+ doflip = 0;
+ } else {
+ /* Test whether the edge is locally Delaunay. */
+ doflip = incircle(m, b, leftvertex, newvertex, rightvertex,
+ farvertex) > 0.0f;
+ }
+ if (doflip) {
+ /* We made it! Flip the edge `horiz' by rotating its containing */
+ /* quadrilateral (the two triangles adjacent to `horiz'). */
+ /* Identify the casing of the quadrilateral. */
+ lprev(top, topleft);
+ sym(topleft, toplcasing);
+ lnext(top, topright);
+ sym(topright, toprcasing);
+ lnext(horiz, botleft);
+ sym(botleft, botlcasing);
+ lprev(horiz, botright);
+ sym(botright, botrcasing);
+ /* Rotate the quadrilateral one-quarter turn counterclockwise. */
+ bond(topleft, botlcasing);
+ bond(botleft, botrcasing);
+ bond(botright, toprcasing);
+ bond(topright, toplcasing);
+ if (m->checksegments) {
+ /* Check for subsegments and rebond them to the quadrilateral. */
+ tspivot(topleft, toplsubseg);
+ tspivot(botleft, botlsubseg);
+ tspivot(botright, botrsubseg);
+ tspivot(topright, toprsubseg);
+ if (toplsubseg.ss == m->dummysub) {
+ tsdissolve(topright);
+ } else {
+ tsbond(topright, toplsubseg);
+ }
+ if (botlsubseg.ss == m->dummysub) {
+ tsdissolve(topleft);
+ } else {
+ tsbond(topleft, botlsubseg);
+ }
+ if (botrsubseg.ss == m->dummysub) {
+ tsdissolve(botleft);
+ } else {
+ tsbond(botleft, botrsubseg);
+ }
+ if (toprsubseg.ss == m->dummysub) {
+ tsdissolve(botright);
+ } else {
+ tsbond(botright, toprsubseg);
+ }
+ }
+ /* New vertex = vec3ments for the rotated quadrilateral. */
+ setorg(horiz, farvertex);
+ setdest(horiz, newvertex);
+ setapex(horiz, rightvertex);
+ setorg(top, newvertex);
+ setdest(top, farvertex);
+ setapex(top, leftvertex);
+ for (i = 0; i < m->eextras; i++) {
+ /* Take the average of the two triangles' attributes. */
+ attrib = 0.5 * (elemattribute(top, i) + elemattribute(horiz, i));
+ setelemattribute(top, i, attrib);
+ setelemattribute(horiz, i, attrib);
+ }
+ if (b->vararea) {
+ if ((areabound(top) <= 0.0f) || (areabound(horiz) <= 0.0f)) {
+ area = -1.0f;
+ } else {
+ /* Take the average of the two triangles' area constraints. */
+ /* This prevents small area constraints from migrating a */
+ /* long, long way from their original location due to flips. */
+ area = 0.5 * (areabound(top) + areabound(horiz));
+ }
+ setareabound(top, area);
+ setareabound(horiz, area);
+ }
+
+ if (m->checkquality) {
+ newflip = (struct flipstacker *) poolalloc(&m->flipstackers);
+ newflip->flippedtri = encode(horiz);
+ newflip->prevflip = m->lastflip;
+ m->lastflip = newflip;
+ }
+
+#ifdef SELF_CHECK
+ if (newvertex != (vertex) NULL) {
+ if (counterclockwise(m, b, leftvertex, newvertex, rightvertex) <
+ 0.0f) {
+ printf("Internal error in insertvertex():\n");
+ printf(" Clockwise triangle prior to edge flip (bottom).\n");
+ }
+ /* The following test has been removed because constrainededge() */
+ /* sometimes generates inverted triangles that insertvertex() */
+ /* removes. */
+/*
+ if (counterclockwise(m, b, rightvertex, farvertex, leftvertex) <
+ 0.0f) {
+ printf("Internal error in insertvertex():\n");
+ printf(" Clockwise triangle prior to edge flip (top).\n");
+ }
+*/
+ if (counterclockwise(m, b, farvertex, leftvertex, newvertex) <
+ 0.0f) {
+ printf("Internal error in insertvertex():\n");
+ printf(" Clockwise triangle after edge flip (left).\n");
+ }
+ if (counterclockwise(m, b, newvertex, rightvertex, farvertex) <
+ 0.0f) {
+ printf("Internal error in insertvertex():\n");
+ printf(" Clockwise triangle after edge flip (right).\n");
+ }
+ }
+#endif /* SELF_CHECK */
+ if (b->verbose > 2) {
+ printf(" Edge flip results in left ");
+ lnextself(topleft);
+ printtriangle(m, b, &topleft);
+ printf(" and right ");
+ printtriangle(m, b, &horiz);
+ }
+ /* On the next iterations, consider the two edges that were */
+ /* exposed (this is, are now visible to the newly inserted */
+ /* vertex) by the edge flip. */
+ lprevself(horiz);
+ leftvertex = farvertex;
+ }
+ }
+ }
+ if (!doflip) {
+ /* The handle `horiz' is accepted as locally Delaunay. */
+#ifndef CDT_ONLY
+ if (triflaws) {
+ /* Check the triangle `horiz' for quality. */
+ testtriangle(m, b, &horiz);
+ }
+#endif /* not CDT_ONLY */
+ /* Look for the next edge around the newly inserted vertex. */
+ lnextself(horiz);
+ sym(horiz, testtri);
+ /* Check for finishing a complete revolution about the new vertex, or */
+ /* falling outside of the triangulation. The latter will happen */
+ /* when a vertex is inserted at a boundary. */
+ if ((leftvertex == first) || (testtri.tri == m->dummytri)) {
+ /* We're done. Return a triangle whose origin is the new vertex. */
+ lnext(horiz, *searchtri);
+ lnext(horiz, m->recenttri);
+ return success;
+ }
+ /* Finish finding the next edge around the newly inserted vertex. */
+ lnext(testtri, horiz);
+ rightvertex = leftvertex;
+ dest(horiz, leftvertex);
+ }
+ }
+}
+
+/*****************************************************************************/
+/* */
+/* triangulatepolygon() Find the Delaunay triangulation of a polygon that */
+/* has a certain "nice" shape. This includes the */
+/* polygons that result from deletion of a vertex or */
+/* insertion of a segment. */
+/* */
+/* This is a conceptually difficult routine. The starting assumption is */
+/* that we have a polygon with n sides. n - 1 of these sides are currently */
+/* represented as edges in the mesh. One side, called the "base", need not */
+/* be. */
+/* */
+/* Inside the polygon is a structure I call a "fan", consisting of n - 1 */
+/* triangles that share a common origin. For each of these triangles, the */
+/* edge opposite the origin is one of the sides of the polygon. The */
+/* primary edge of each triangle is the edge directed from the origin to */
+/* the destination; note that this is not the same edge that is a side of */
+/* the polygon. `firstedge' is the primary edge of the first triangle. */
+/* From there, the triangles follow in counterclockwise order about the */
+/* polygon, until `lastedge', the primary edge of the last triangle. */
+/* `firstedge' and `lastedge' are probably connected to other triangles */
+/* beyond the extremes of the fan, but their identity is not important, as */
+/* long as the fan remains connected to them. */
+/* */
+/* Imagine the polygon oriented so that its base is at the bottom. This */
+/* puts `firstedge' on the far right, and `lastedge' on the far left. */
+/* The right vertex of the base is the destination of `firstedge', and the */
+/* left vertex of the base is the apex of `lastedge'. */
+/* */
+/* The challenge now is to find the right sequence of edge flips to */
+/* transform the fan into a Delaunay triangulation of the polygon. Each */
+/* edge flip effectively removes one triangle from the fan, committing it */
+/* to the polygon. The resulting polygon has one fewer edge. If `doflip' */
+/* is set, the final flip will be performed, resulting in a fan of one */
+/* (useless?) triangle. If `doflip' is not set, the final flip is not */
+/* performed, resulting in a fan of two triangles, and an unfinished */
+/* triangular polygon that is not yet filled out with a single triangle. */
+/* On completion of the routine, `lastedge' is the last remaining triangle, */
+/* or the leftmost of the last two. */
+/* */
+/* Although the flips are performed in the order described above, the */
+/* decisions about what flips to perform are made in precisely the reverse */
+/* order. The recursive triangulatepolygon() procedure makes a decision, */
+/* uses up to two recursive calls to triangulate the "subproblems" */
+/* (polygons with fewer edges), and then performs an edge flip. */
+/* */
+/* The "decision" it makes is which vertex of the polygon should be */
+/* connected to the base. This decision is made by testing every possible */
+/* vertex. Once the best vertex is found, the two edges that connect this */
+/* vertex to the base become the bases for two smaller polygons. These */
+/* are triangulated recursively. Unfortunately, this approach can take */
+/* O(n^2) time not only in the worst case, but in many common cases. It's */
+/* rarely a big deal for vertex deletion, where n is rarely larger than */
+/* ten, but it could be a big deal for segment insertion, especially if */
+/* there's a lot of long segments that each cut many triangles. I ought to */
+/* code a faster algorithm some day. */
+/* */
+/* The `edgecount' parameter is the number of sides of the polygon, */
+/* including its base. `triflaws' is a flag that determines whether the */
+/* new triangles should be tested for quality, and enqueued if they are */
+/* bad. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void triangulatepolygon(struct mesh *m, struct behavior *b,
+ struct otri *firstedge, struct otri *lastedge,
+ int edgecount, int doflip, int triflaws)
+#else /* not ANSI_DECLARATORS */
+void triangulatepolygon(m, b, firstedge, lastedge, edgecount, doflip, triflaws)
+struct mesh *m;
+struct behavior *b;
+struct otri *firstedge;
+struct otri *lastedge;
+int edgecount;
+int doflip;
+int triflaws;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri testtri;
+ struct otri besttri;
+ struct otri tempedge;
+ vertex leftbasevertex, rightbasevertex;
+ vertex testvertex;
+ vertex bestvertex;
+ int bestnumber;
+ int i;
+ triangle ptr; /* Temporary variable used by sym(), onext(), and oprev(). */
+
+ /* Identify the base vertices. */
+ apex(*lastedge, leftbasevertex);
+ dest(*firstedge, rightbasevertex);
+ if (b->verbose > 2) {
+ printf(" Triangulating interior polygon at edge\n");
+ printf(" (%.12g, %.12g) (%.12g, %.12g)\n", leftbasevertex[0],
+ leftbasevertex[1], rightbasevertex[0], rightbasevertex[1]);
+ }
+ /* Find the best vertex to connect the base to. */
+ onext(*firstedge, besttri);
+ dest(besttri, bestvertex);
+ otricopy(besttri, testtri);
+ bestnumber = 1;
+ for (i = 2; i <= edgecount - 2; i++) {
+ onextself(testtri);
+ dest(testtri, testvertex);
+ /* Is this a better vertex? */
+ if (incircle(m, b, leftbasevertex, rightbasevertex, bestvertex,
+ testvertex) > 0.0f) {
+ otricopy(testtri, besttri);
+ bestvertex = testvertex;
+ bestnumber = i;
+ }
+ }
+ if (b->verbose > 2) {
+ printf(" Connecting edge to (%.12g, %.12g)\n", bestvertex[0],
+ bestvertex[1]);
+ }
+ if (bestnumber > 1) {
+ /* Recursively triangulate the smaller polygon on the right. */
+ oprev(besttri, tempedge);
+ triangulatepolygon(m, b, firstedge, &tempedge, bestnumber + 1, 1,
+ triflaws);
+ }
+ if (bestnumber < edgecount - 2) {
+ /* Recursively triangulate the smaller polygon on the left. */
+ sym(besttri, tempedge);
+ triangulatepolygon(m, b, &besttri, lastedge, edgecount - bestnumber, 1,
+ triflaws);
+ /* Find `besttri' again; it may have been lost to edge flips. */
+ sym(tempedge, besttri);
+ }
+ if (doflip) {
+ /* Do one final edge flip. */
+ flip(m, b, &besttri);
+#ifndef CDT_ONLY
+ if (triflaws) {
+ /* Check the quality of the newly committed triangle. */
+ sym(besttri, testtri);
+ testtriangle(m, b, &testtri);
+ }
+#endif /* not CDT_ONLY */
+ }
+ /* Return the base triangle. */
+ otricopy(besttri, *lastedge);
+}
+
+/*****************************************************************************/
+/* */
+/* deletevertex() Delete a vertex from a Delaunay triangulation, ensuring */
+/* that the triangulation remains Delaunay. */
+/* */
+/* The origin of `deltri' is deleted. The union of the triangles adjacent */
+/* to this vertex is a polygon, for which the Delaunay triangulation is */
+/* found. Two triangles are removed from the mesh. */
+/* */
+/* Only interior vertices that do not lie on segments or boundaries may be */
+/* deleted. */
+/* */
+/*****************************************************************************/
+
+#ifndef CDT_ONLY
+
+#ifdef ANSI_DECLARATORS
+void deletevertex(struct mesh *m, struct behavior *b, struct otri *deltri)
+#else /* not ANSI_DECLARATORS */
+void deletevertex(m, b, deltri)
+struct mesh *m;
+struct behavior *b;
+struct otri *deltri;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri countingtri;
+ struct otri firstedge, lastedge;
+ struct otri deltriright;
+ struct otri lefttri, righttri;
+ struct otri leftcasing, rightcasing;
+ struct osub leftsubseg, rightsubseg;
+ vertex delvertex;
+ vertex neworg;
+ int edgecount;
+ triangle ptr; /* Temporary variable used by sym(), onext(), and oprev(). */
+ subseg sptr; /* Temporary variable used by tspivot(). */
+
+ org(*deltri, delvertex);
+ if (b->verbose > 1) {
+ printf(" Deleting (%.12g, %.12g).\n", delvertex[0], delvertex[1]);
+ }
+ vertexdealloc(m, delvertex);
+
+ /* Count the degree of the vertex being deleted. */
+ onext(*deltri, countingtri);
+ edgecount = 1;
+ while (!otriequal(*deltri, countingtri)) {
+#ifdef SELF_CHECK
+ if (countingtri.tri == m->dummytri) {
+ printf("Internal error in deletevertex():\n");
+ printf(" Attempt to delete boundary vertex.\n");
+ internalerror();
+ }
+#endif /* SELF_CHECK */
+ edgecount++;
+ onextself(countingtri);
+ }
+
+#ifdef SELF_CHECK
+ if (edgecount < 3) {
+ printf("Internal error in deletevertex():\n Vertex has degree %d.\n",
+ edgecount);
+ internalerror();
+ }
+#endif /* SELF_CHECK */
+ if (edgecount > 3) {
+ /* Triangulate the polygon defined by the union of all triangles */
+ /* adjacent to the vertex being deleted. Check the quality of */
+ /* the resulting triangles. */
+ onext(*deltri, firstedge);
+ oprev(*deltri, lastedge);
+ triangulatepolygon(m, b, &firstedge, &lastedge, edgecount, 0,
+ !b->nobisect);
+ }
+ /* Splice out two triangles. */
+ lprev(*deltri, deltriright);
+ dnext(*deltri, lefttri);
+ sym(lefttri, leftcasing);
+ oprev(deltriright, righttri);
+ sym(righttri, rightcasing);
+ bond(*deltri, leftcasing);
+ bond(deltriright, rightcasing);
+ tspivot(lefttri, leftsubseg);
+ if (leftsubseg.ss != m->dummysub) {
+ tsbond(*deltri, leftsubseg);
+ }
+ tspivot(righttri, rightsubseg);
+ if (rightsubseg.ss != m->dummysub) {
+ tsbond(deltriright, rightsubseg);
+ }
+
+ /* Set the new origin of `deltri' and check its quality. */
+ org(lefttri, neworg);
+ setorg(*deltri, neworg);
+ if (!b->nobisect) {
+ testtriangle(m, b, deltri);
+ }
+
+ /* Delete the two spliced-out triangles. */
+ triangledealloc(m, lefttri.tri);
+ triangledealloc(m, righttri.tri);
+}
+
+#endif /* not CDT_ONLY */
+
+/*****************************************************************************/
+/* */
+/* undovertex() Undo the most recent vertex insertion. */
+/* */
+/* Walks through the list of transformations (flips and a vertex insertion) */
+/* in the reverse of the order in which they were done, and undoes them. */
+/* The inserted vertex is removed from the triangulation and deallocated. */
+/* Two triangles (possibly just one) are also deallocated. */
+/* */
+/*****************************************************************************/
+
+#ifndef CDT_ONLY
+
+#ifdef ANSI_DECLARATORS
+void undovertex(struct mesh *m, struct behavior *b)
+#else /* not ANSI_DECLARATORS */
+void undovertex(m, b)
+struct mesh *m;
+struct behavior *b;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri fliptri;
+ struct otri botleft, botright, topright;
+ struct otri botlcasing, botrcasing, toprcasing;
+ struct otri gluetri;
+ struct osub botlsubseg, botrsubseg, toprsubseg;
+ vertex botvertex, rightvertex;
+ triangle ptr; /* Temporary variable used by sym(). */
+ subseg sptr; /* Temporary variable used by tspivot(). */
+
+ /* Walk through the list of transformations (flips and a vertex insertion) */
+ /* in the reverse of the order in which they were done, and undo them. */
+ while (m->lastflip != (struct flipstacker *) NULL) {
+ /* Find a triangle involved in the last unreversed transformation. */
+ decode(m->lastflip->flippedtri, fliptri);
+
+ /* We are reversing one of three transformations: a trisection of one */
+ /* triangle into three (by inserting a vertex in the triangle), a */
+ /* bisection of two triangles into four (by inserting a vertex in an */
+ /* edge), or an edge flip. */
+ if (m->lastflip->prevflip == (struct flipstacker *) NULL) {
+ /* Restore a triangle that was split into three triangles, */
+ /* so it is again one triangle. */
+ dprev(fliptri, botleft);
+ lnextself(botleft);
+ onext(fliptri, botright);
+ lprevself(botright);
+ sym(botleft, botlcasing);
+ sym(botright, botrcasing);
+ dest(botleft, botvertex);
+
+ setapex(fliptri, botvertex);
+ lnextself(fliptri);
+ bond(fliptri, botlcasing);
+ tspivot(botleft, botlsubseg);
+ tsbond(fliptri, botlsubseg);
+ lnextself(fliptri);
+ bond(fliptri, botrcasing);
+ tspivot(botright, botrsubseg);
+ tsbond(fliptri, botrsubseg);
+
+ /* Delete the two spliced-out triangles. */
+ triangledealloc(m, botleft.tri);
+ triangledealloc(m, botright.tri);
+ } else if (m->lastflip->prevflip == (struct flipstacker *) &insertvertex) {
+ /* Restore two triangles that were split into four triangles, */
+ /* so they are again two triangles. */
+ lprev(fliptri, gluetri);
+ sym(gluetri, botright);
+ lnextself(botright);
+ sym(botright, botrcasing);
+ dest(botright, rightvertex);
+
+ setorg(fliptri, rightvertex);
+ bond(gluetri, botrcasing);
+ tspivot(botright, botrsubseg);
+ tsbond(gluetri, botrsubseg);
+
+ /* Delete the spliced-out triangle. */
+ triangledealloc(m, botright.tri);
+
+ sym(fliptri, gluetri);
+ if (gluetri.tri != m->dummytri) {
+ lnextself(gluetri);
+ dnext(gluetri, topright);
+ sym(topright, toprcasing);
+
+ setorg(gluetri, rightvertex);
+ bond(gluetri, toprcasing);
+ tspivot(topright, toprsubseg);
+ tsbond(gluetri, toprsubseg);
+
+ /* Delete the spliced-out triangle. */
+ triangledealloc(m, topright.tri);
+ }
+
+ /* This is the end of the list, sneakily encoded. */
+ m->lastflip->prevflip = (struct flipstacker *) NULL;
+ } else {
+ /* Undo an edge flip. */
+ unflip(m, b, &fliptri);
+ }
+
+ /* Go on and process the next transformation. */
+ m->lastflip = m->lastflip->prevflip;
+ }
+}
+
+#endif /* not CDT_ONLY */
+
+/** **/
+/** **/
+/********* Mesh transformation routines end here *********/
+
+/********* Divide-and-conquer Delaunay triangulation begins here *********/
+/** **/
+/** **/
+
+/*****************************************************************************/
+/* */
+/* The divide-and-conquer bounding box */
+/* */
+/* I originally implemented the divide-and-conquer and incremental Delaunay */
+/* triangulations using the edge-based data structure presented by Guibas */
+/* and Stolfi. Switching to a triangle-based data structure doubled the */
+/* speed. However, I had to think of a few extra tricks to maintain the */
+/* elegance of the original algorithms. */
+/* */
+/* The "bounding box" used by my variant of the divide-and-conquer */
+/* algorithm uses one triangle for each edge of the convex hull of the */
+/* triangulation. These bounding triangles all share a common apical */
+/* vertex, which is represented by NULL and which represents nothing. */
+/* The bounding triangles are linked in a circular fan about this NULL */
+/* vertex, and the edges on the convex hull of the triangulation appear */
+/* opposite the NULL vertex. You might find it easiest to imagine that */
+/* the NULL vertex is a point in 3D space behind the center of the */
+/* triangulation, and that the bounding triangles form a sort of cone. */
+/* */
+/* This bounding box makes it easy to represent degenerate cases. For */
+/* instance, the triangulation of two vertices is a single edge. This edge */
+/* is represented by two bounding box triangles, one on each "side" of the */
+/* edge. These triangles are also linked together in a fan about the NULL */
+/* vertex. */
+/* */
+/* The bounding box also makes it easy to traverse the convex hull, as the */
+/* divide-and-conquer algorithm needs to do. */
+/* */
+/*****************************************************************************/
+
+/*****************************************************************************/
+/* */
+/* vertexsort() Sort an array of vertices by x-coordinate, using the */
+/* y-coordinate as a secondary key. */
+/* */
+/* Uses quicksort. Randomized O(n log n) time. No, I did not make any of */
+/* the usual quicksort mistakes. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void vertexsort(vertex *sortarray, int arraysize)
+#else /* not ANSI_DECLARATORS */
+void vertexsort(sortarray, arraysize)
+vertex *sortarray;
+int arraysize;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ int left, right;
+ int pivot;
+ tREAL pivotx, pivoty;
+ vertex temp;
+
+ if (arraysize == 2) {
+ /* Recursive base case. */
+ if ((sortarray[0][0] > sortarray[1][0]) ||
+ ((sortarray[0][0] == sortarray[1][0]) &&
+ (sortarray[0][1] > sortarray[1][1]))) {
+ temp = sortarray[1];
+ sortarray[1] = sortarray[0];
+ sortarray[0] = temp;
+ }
+ return;
+ }
+ /* Choose a random pivot to split the array. */
+ pivot = (int) randomnation((unsigned int) arraysize);
+ pivotx = sortarray[pivot][0];
+ pivoty = sortarray[pivot][1];
+ /* Split the array. */
+ left = -1;
+ right = arraysize;
+ while (left < right) {
+ /* Search for a vertex whose x-coordinate is too large for the left. */
+ do {
+ left++;
+ } while ((left <= right) && ((sortarray[left][0] < pivotx) ||
+ ((sortarray[left][0] == pivotx) &&
+ (sortarray[left][1] < pivoty))));
+ /* Search for a vertex whose x-coordinate is too small for the right. */
+ do {
+ right--;
+ } while ((left <= right) && ((sortarray[right][0] > pivotx) ||
+ ((sortarray[right][0] == pivotx) &&
+ (sortarray[right][1] > pivoty))));
+ if (left < right) {
+ /* Swap the left and right vertices. */
+ temp = sortarray[left];
+ sortarray[left] = sortarray[right];
+ sortarray[right] = temp;
+ }
+ }
+ if (left > 1) {
+ /* Recursively sort the left subset. */
+ vertexsort(sortarray, left);
+ }
+ if (right < arraysize - 2) {
+ /* Recursively sort the right subset. */
+ vertexsort(&sortarray[right + 1], arraysize - right - 1);
+ }
+}
+
+/*****************************************************************************/
+/* */
+/* vertexmedian() An order statistic algorithm, almost. Shuffles an */
+/* array of vertices so that the first `median' vertices */
+/* occur lexicographically before the remaining vertices. */
+/* */
+/* Uses the x-coordinate as the primary key if axis == 0; the y-coordinate */
+/* if axis == 1. Very similar to the vertexsort() procedure, but runs in */
+/* randomized linear time. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void vertexmedian(vertex *sortarray, int arraysize, int median, int axis)
+#else /* not ANSI_DECLARATORS */
+void vertexmedian(sortarray, arraysize, median, axis)
+vertex *sortarray;
+int arraysize;
+int median;
+int axis;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ int left, right;
+ int pivot;
+ tREAL pivot1, pivot2;
+ vertex temp;
+
+ if (arraysize == 2) {
+ /* Recursive base case. */
+ if ((sortarray[0][axis] > sortarray[1][axis]) ||
+ ((sortarray[0][axis] == sortarray[1][axis]) &&
+ (sortarray[0][1 - axis] > sortarray[1][1 - axis]))) {
+ temp = sortarray[1];
+ sortarray[1] = sortarray[0];
+ sortarray[0] = temp;
+ }
+ return;
+ }
+ /* Choose a random pivot to split the array. */
+ pivot = (int) randomnation((unsigned int) arraysize);
+ pivot1 = sortarray[pivot][axis];
+ pivot2 = sortarray[pivot][1 - axis];
+ /* Split the array. */
+ left = -1;
+ right = arraysize;
+ while (left < right) {
+ /* Search for a vertex whose x-coordinate is too large for the left. */
+ do {
+ left++;
+ } while ((left <= right) && ((sortarray[left][axis] < pivot1) ||
+ ((sortarray[left][axis] == pivot1) &&
+ (sortarray[left][1 - axis] < pivot2))));
+ /* Search for a vertex whose x-coordinate is too small for the right. */
+ do {
+ right--;
+ } while ((left <= right) && ((sortarray[right][axis] > pivot1) ||
+ ((sortarray[right][axis] == pivot1) &&
+ (sortarray[right][1 - axis] > pivot2))));
+ if (left < right) {
+ /* Swap the left and right vertices. */
+ temp = sortarray[left];
+ sortarray[left] = sortarray[right];
+ sortarray[right] = temp;
+ }
+ }
+ /* Unlike in vertexsort(), at most one of the following */
+ /* conditionals is true. */
+ if (left > median) {
+ /* Recursively shuffle the left subset. */
+ vertexmedian(sortarray, left, median, axis);
+ }
+ if (right < median - 1) {
+ /* Recursively shuffle the right subset. */
+ vertexmedian(&sortarray[right + 1], arraysize - right - 1,
+ median - right - 1, axis);
+ }
+}
+
+/*****************************************************************************/
+/* */
+/* alternateaxes() Sorts the vertices as appropriate for the divide-and- */
+/* conquer algorithm with alternating cuts. */
+/* */
+/* Partitions by x-coordinate if axis == 0; by y-coordinate if axis == 1. */
+/* For the base case, subsets containing only two or three vertices are */
+/* always sorted by x-coordinate. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void alternateaxes(vertex *sortarray, int arraysize, int axis)
+#else /* not ANSI_DECLARATORS */
+void alternateaxes(sortarray, arraysize, axis)
+vertex *sortarray;
+int arraysize;
+int axis;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ int divider;
+
+ divider = arraysize >> 1;
+ if (arraysize <= 3) {
+ /* Recursive base case: subsets of two or three vertices will be */
+ /* handled specially, and should always be sorted by x-coordinate. */
+ axis = 0;
+ }
+ /* Partition with a horizontal or vertical cut. */
+ vertexmedian(sortarray, arraysize, divider, axis);
+ /* Recursively partition the subsets with a cross cut. */
+ if (arraysize - divider >= 2) {
+ if (divider >= 2) {
+ alternateaxes(sortarray, divider, 1 - axis);
+ }
+ alternateaxes(&sortarray[divider], arraysize - divider, 1 - axis);
+ }
+}
+
+/*****************************************************************************/
+/* */
+/* mergehulls() Merge two adjacent Delaunay triangulations into a */
+/* single Delaunay triangulation. */
+/* */
+/* This is similar to the algorithm given by Guibas and Stolfi, but uses */
+/* a triangle-based, rather than edge-based, data structure. */
+/* */
+/* The algorithm walks up the gap between the two triangulations, knitting */
+/* them together. As they are merged, some of their bounding triangles */
+/* are converted into real triangles of the triangulation. The procedure */
+/* pulls each hull's bounding triangles apart, then knits them together */
+/* like the teeth of two gears. The Delaunay property determines, at each */
+/* step, whether the next "tooth" is a bounding triangle of the left hull */
+/* or the right. When a bounding triangle becomes real, its apex is */
+/* changed from NULL to a real vertex. */
+/* */
+/* Only two new triangles need to be allocated. These become new bounding */
+/* triangles at the top and bottom of the seam. They are used to connect */
+/* the remaining bounding triangles (those that have not been converted */
+/* into real triangles) into a single fan. */
+/* */
+/* On entry, `farleft' and `innerleft' are bounding triangles of the left */
+/* triangulation. The origin of `farleft' is the leftmost vertex, and */
+/* the destination of `innerleft' is the rightmost vertex of the */
+/* triangulation. Similarly, `innerright' and `farright' are bounding */
+/* triangles of the right triangulation. The origin of `innerright' and */
+/* destination of `farright' are the leftmost and rightmost vertices. */
+/* */
+/* On completion, the origin of `farleft' is the leftmost vertex of the */
+/* merged triangulation, and the destination of `farright' is the rightmost */
+/* vertex. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void mergehulls(struct mesh *m, struct behavior *b, struct otri *farleft,
+ struct otri *innerleft, struct otri *innerright,
+ struct otri *farright, int axis)
+#else /* not ANSI_DECLARATORS */
+void mergehulls(m, b, farleft, innerleft, innerright, farright, axis)
+struct mesh *m;
+struct behavior *b;
+struct otri *farleft;
+struct otri *innerleft;
+struct otri *innerright;
+struct otri *farright;
+int axis;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri leftcand, rightcand;
+ struct otri baseedge;
+ struct otri nextedge;
+ struct otri sidecasing, topcasing, outercasing;
+ struct otri checkedge;
+ vertex innerleftdest;
+ vertex innerrightorg;
+ vertex innerleftapex, innerrightapex;
+ vertex farleftpt, farrightpt;
+ vertex farleftapex, farrightapex;
+ vertex lowerleft, lowerright;
+ vertex upperleft, upperright;
+ vertex nextapex;
+ vertex checkvertex;
+ int changemade;
+ int badedge;
+ int leftfinished, rightfinished;
+ triangle ptr; /* Temporary variable used by sym(). */
+
+ dest(*innerleft, innerleftdest);
+ apex(*innerleft, innerleftapex);
+ org(*innerright, innerrightorg);
+ apex(*innerright, innerrightapex);
+ /* Special treatment for horizontal cuts. */
+ if (b->dwyer && (axis == 1)) {
+ org(*farleft, farleftpt);
+ apex(*farleft, farleftapex);
+ dest(*farright, farrightpt);
+ apex(*farright, farrightapex);
+ /* The pointers to the extremal vertices are shifted to point to the */
+ /* topmost and bottommost vertex of each hull, rather than the */
+ /* leftmost and rightmost vertices. */
+ while (farleftapex[1] < farleftpt[1]) {
+ lnextself(*farleft);
+ symself(*farleft);
+ farleftpt = farleftapex;
+ apex(*farleft, farleftapex);
+ }
+ sym(*innerleft, checkedge);
+ apex(checkedge, checkvertex);
+ while (checkvertex[1] > innerleftdest[1]) {
+ lnext(checkedge, *innerleft);
+ innerleftapex = innerleftdest;
+ innerleftdest = checkvertex;
+ sym(*innerleft, checkedge);
+ apex(checkedge, checkvertex);
+ }
+ while (innerrightapex[1] < innerrightorg[1]) {
+ lnextself(*innerright);
+ symself(*innerright);
+ innerrightorg = innerrightapex;
+ apex(*innerright, innerrightapex);
+ }
+ sym(*farright, checkedge);
+ apex(checkedge, checkvertex);
+ while (checkvertex[1] > farrightpt[1]) {
+ lnext(checkedge, *farright);
+ farrightapex = farrightpt;
+ farrightpt = checkvertex;
+ sym(*farright, checkedge);
+ apex(checkedge, checkvertex);
+ }
+ }
+ /* Find a line tangent to and below both hulls. */
+ do {
+ changemade = 0;
+ /* Make innerleftdest the "bottommost" vertex of the left hull. */
+ if (counterclockwise(m, b, innerleftdest, innerleftapex, innerrightorg) >
+ 0.0f) {
+ lprevself(*innerleft);
+ symself(*innerleft);
+ innerleftdest = innerleftapex;
+ apex(*innerleft, innerleftapex);
+ changemade = 1;
+ }
+ /* Make innerrightorg the "bottommost" vertex of the right hull. */
+ if (counterclockwise(m, b, innerrightapex, innerrightorg, innerleftdest) >
+ 0.0f) {
+ lnextself(*innerright);
+ symself(*innerright);
+ innerrightorg = innerrightapex;
+ apex(*innerright, innerrightapex);
+ changemade = 1;
+ }
+ } while (changemade);
+ /* Find the two candidates to be the next "gear tooth." */
+ sym(*innerleft, leftcand);
+ sym(*innerright, rightcand);
+ /* Create the bottom new bounding triangle. */
+ maketriangle(m, b, &baseedge);
+ /* Connect it to the bounding boxes of the left and right triangulations. */
+ bond(baseedge, *innerleft);
+ lnextself(baseedge);
+ bond(baseedge, *innerright);
+ lnextself(baseedge);
+ setorg(baseedge, innerrightorg);
+ setdest(baseedge, innerleftdest);
+ /* Apex is intentionally left NULL. */
+ if (b->verbose > 2) {
+ printf(" Creating base bounding ");
+ printtriangle(m, b, &baseedge);
+ }
+ /* Fix the extreme triangles if necessary. */
+ org(*farleft, farleftpt);
+ if (innerleftdest == farleftpt) {
+ lnext(baseedge, *farleft);
+ }
+ dest(*farright, farrightpt);
+ if (innerrightorg == farrightpt) {
+ lprev(baseedge, *farright);
+ }
+ /* The vertices of the current knitting edge. */
+ lowerleft = innerleftdest;
+ lowerright = innerrightorg;
+ /* The candidate vertices for knitting. */
+ apex(leftcand, upperleft);
+ apex(rightcand, upperright);
+ /* Walk up the gap between the two triangulations, knitting them together. */
+ while (1) {
+ /* Have we reached the top? (This isn't quite the right question, */
+ /* because even though the left triangulation might seem finished now, */
+ /* moving up on the right triangulation might reveal a new vertex of */
+ /* the left triangulation. And vice-versa.) */
+ leftfinished = counterclockwise(m, b, upperleft, lowerleft, lowerright) <=
+ 0.0f;
+ rightfinished = counterclockwise(m, b, upperright, lowerleft, lowerright)
+ <= 0.0f;
+ if (leftfinished && rightfinished) {
+ /* Create the top new bounding triangle. */
+ maketriangle(m, b, &nextedge);
+ setorg(nextedge, lowerleft);
+ setdest(nextedge, lowerright);
+ /* Apex is intentionally left NULL. */
+ /* Connect it to the bounding boxes of the two triangulations. */
+ bond(nextedge, baseedge);
+ lnextself(nextedge);
+ bond(nextedge, rightcand);
+ lnextself(nextedge);
+ bond(nextedge, leftcand);
+ if (b->verbose > 2) {
+ printf(" Creating top bounding ");
+ printtriangle(m, b, &nextedge);
+ }
+ /* Special treatment for horizontal cuts. */
+ if (b->dwyer && (axis == 1)) {
+ org(*farleft, farleftpt);
+ apex(*farleft, farleftapex);
+ dest(*farright, farrightpt);
+ apex(*farright, farrightapex);
+ sym(*farleft, checkedge);
+ apex(checkedge, checkvertex);
+ /* The pointers to the extremal vertices are restored to the */
+ /* leftmost and rightmost vertices (rather than topmost and */
+ /* bottommost). */
+ while (checkvertex[0] < farleftpt[0]) {
+ lprev(checkedge, *farleft);
+ farleftapex = farleftpt;
+ farleftpt = checkvertex;
+ sym(*farleft, checkedge);
+ apex(checkedge, checkvertex);
+ }
+ while (farrightapex[0] > farrightpt[0]) {
+ lprevself(*farright);
+ symself(*farright);
+ farrightpt = farrightapex;
+ apex(*farright, farrightapex);
+ }
+ }
+ return;
+ }
+ /* Consider eliminating edges from the left triangulation. */
+ if (!leftfinished) {
+ /* What vertex would be exposed if an edge were deleted? */
+ lprev(leftcand, nextedge);
+ symself(nextedge);
+ apex(nextedge, nextapex);
+ /* If nextapex is NULL, then no vertex would be exposed; the */
+ /* triangulation would have been eaten right through. */
+ if (nextapex != (vertex) NULL) {
+ /* Check whether the edge is Delaunay. */
+ badedge = incircle(m, b, lowerleft, lowerright, upperleft, nextapex) >
+ 0.0f;
+ while (badedge) {
+ /* Eliminate the edge with an edge flip. As a result, the */
+ /* left triangulation will have one more boundary triangle. */
+ lnextself(nextedge);
+ sym(nextedge, topcasing);
+ lnextself(nextedge);
+ sym(nextedge, sidecasing);
+ bond(nextedge, topcasing);
+ bond(leftcand, sidecasing);
+ lnextself(leftcand);
+ sym(leftcand, outercasing);
+ lprevself(nextedge);
+ bond(nextedge, outercasing);
+ /* Correct the vertices to reflect the edge flip. */
+ setorg(leftcand, lowerleft);
+ setdest(leftcand, NULL);
+ setapex(leftcand, nextapex);
+ setorg(nextedge, NULL);
+ setdest(nextedge, upperleft);
+ setapex(nextedge, nextapex);
+ /* Consider the newly exposed vertex. */
+ upperleft = nextapex;
+ /* What vertex would be exposed if another edge were deleted? */
+ otricopy(sidecasing, nextedge);
+ apex(nextedge, nextapex);
+ if (nextapex != (vertex) NULL) {
+ /* Check whether the edge is Delaunay. */
+ badedge = incircle(m, b, lowerleft, lowerright, upperleft,
+ nextapex) > 0.0f;
+ } else {
+ /* Avoid eating right through the triangulation. */
+ badedge = 0;
+ }
+ }
+ }
+ }
+ /* Consider eliminating edges from the right triangulation. */
+ if (!rightfinished) {
+ /* What vertex would be exposed if an edge were deleted? */
+ lnext(rightcand, nextedge);
+ symself(nextedge);
+ apex(nextedge, nextapex);
+ /* If nextapex is NULL, then no vertex would be exposed; the */
+ /* triangulation would have been eaten right through. */
+ if (nextapex != (vertex) NULL) {
+ /* Check whether the edge is Delaunay. */
+ badedge = incircle(m, b, lowerleft, lowerright, upperright, nextapex) >
+ 0.0f;
+ while (badedge) {
+ /* Eliminate the edge with an edge flip. As a result, the */
+ /* right triangulation will have one more boundary triangle. */
+ lprevself(nextedge);
+ sym(nextedge, topcasing);
+ lprevself(nextedge);
+ sym(nextedge, sidecasing);
+ bond(nextedge, topcasing);
+ bond(rightcand, sidecasing);
+ lprevself(rightcand);
+ sym(rightcand, outercasing);
+ lnextself(nextedge);
+ bond(nextedge, outercasing);
+ /* Correct the vertices to reflect the edge flip. */
+ setorg(rightcand, NULL);
+ setdest(rightcand, lowerright);
+ setapex(rightcand, nextapex);
+ setorg(nextedge, upperright);
+ setdest(nextedge, NULL);
+ setapex(nextedge, nextapex);
+ /* Consider the newly exposed vertex. */
+ upperright = nextapex;
+ /* What vertex would be exposed if another edge were deleted? */
+ otricopy(sidecasing, nextedge);
+ apex(nextedge, nextapex);
+ if (nextapex != (vertex) NULL) {
+ /* Check whether the edge is Delaunay. */
+ badedge = incircle(m, b, lowerleft, lowerright, upperright,
+ nextapex) > 0.0f;
+ } else {
+ /* Avoid eating right through the triangulation. */
+ badedge = 0;
+ }
+ }
+ }
+ }
+ if (leftfinished || (!rightfinished &&
+ (incircle(m, b, upperleft, lowerleft, lowerright, upperright) >
+ 0.0f))) {
+ /* Knit the triangulations, adding an edge from `lowerleft' */
+ /* to `upperright'. */
+ bond(baseedge, rightcand);
+ lprev(rightcand, baseedge);
+ setdest(baseedge, lowerleft);
+ lowerright = upperright;
+ sym(baseedge, rightcand);
+ apex(rightcand, upperright);
+ } else {
+ /* Knit the triangulations, adding an edge from `upperleft' */
+ /* to `lowerright'. */
+ bond(baseedge, leftcand);
+ lnext(leftcand, baseedge);
+ setorg(baseedge, lowerright);
+ lowerleft = upperleft;
+ sym(baseedge, leftcand);
+ apex(leftcand, upperleft);
+ }
+ if (b->verbose > 2) {
+ printf(" Connecting ");
+ printtriangle(m, b, &baseedge);
+ }
+ }
+}
+
+/*****************************************************************************/
+/* */
+/* divconqrecurse() Recursively form a Delaunay triangulation by the */
+/* divide-and-conquer method. */
+/* */
+/* Recursively breaks down the problem into smaller pieces, which are */
+/* knitted together by mergehulls(). The base cases (problems of two or */
+/* three vertices) are handled specially here. */
+/* */
+/* On completion, `farleft' and `farright' are bounding triangles such that */
+/* the origin of `farleft' is the leftmost vertex (breaking ties by */
+/* choosing the highest leftmost vertex), and the destination of */
+/* `farright' is the rightmost vertex (breaking ties by choosing the */
+/* lowest rightmost vertex). */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void divconqrecurse(struct mesh *m, struct behavior *b, vertex *sortarray,
+ int vertices, int axis,
+ struct otri *farleft, struct otri *farright)
+#else /* not ANSI_DECLARATORS */
+void divconqrecurse(m, b, sortarray, vertices, axis, farleft, farright)
+struct mesh *m;
+struct behavior *b;
+vertex *sortarray;
+int vertices;
+int axis;
+struct otri *farleft;
+struct otri *farright;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri midtri, tri1, tri2, tri3;
+ struct otri innerleft, innerright;
+ tREAL area;
+ int divider;
+
+ if (b->verbose > 2) {
+ printf(" Triangulating %d vertices.\n", vertices);
+ }
+ if (vertices == 2) {
+ /* The triangulation of two vertices is an edge. An edge is */
+ /* represented by two bounding triangles. */
+ maketriangle(m, b, farleft);
+ setorg(*farleft, sortarray[0]);
+ setdest(*farleft, sortarray[1]);
+ /* The apex is intentionally left NULL. */
+ maketriangle(m, b, farright);
+ setorg(*farright, sortarray[1]);
+ setdest(*farright, sortarray[0]);
+ /* The apex is intentionally left NULL. */
+ bond(*farleft, *farright);
+ lprevself(*farleft);
+ lnextself(*farright);
+ bond(*farleft, *farright);
+ lprevself(*farleft);
+ lnextself(*farright);
+ bond(*farleft, *farright);
+ if (b->verbose > 2) {
+ printf(" Creating ");
+ printtriangle(m, b, farleft);
+ printf(" Creating ");
+ printtriangle(m, b, farright);
+ }
+ /* Ensure that the origin of `farleft' is sortarray[0]. */
+ lprev(*farright, *farleft);
+ return;
+ } else if (vertices == 3) {
+ /* The triangulation of three vertices is either a triangle (with */
+ /* three bounding triangles) or two edges (with four bounding */
+ /* triangles). In either case, four triangles are created. */
+ maketriangle(m, b, &midtri);
+ maketriangle(m, b, &tri1);
+ maketriangle(m, b, &tri2);
+ maketriangle(m, b, &tri3);
+ area = counterclockwise(m, b, sortarray[0], sortarray[1], sortarray[2]);
+ if (area == 0.0f) {
+ /* Three collinear vertices; the triangulation is two edges. */
+ setorg(midtri, sortarray[0]);
+ setdest(midtri, sortarray[1]);
+ setorg(tri1, sortarray[1]);
+ setdest(tri1, sortarray[0]);
+ setorg(tri2, sortarray[2]);
+ setdest(tri2, sortarray[1]);
+ setorg(tri3, sortarray[1]);
+ setdest(tri3, sortarray[2]);
+ /* All apices are intentionally left NULL. */
+ bond(midtri, tri1);
+ bond(tri2, tri3);
+ lnextself(midtri);
+ lprevself(tri1);
+ lnextself(tri2);
+ lprevself(tri3);
+ bond(midtri, tri3);
+ bond(tri1, tri2);
+ lnextself(midtri);
+ lprevself(tri1);
+ lnextself(tri2);
+ lprevself(tri3);
+ bond(midtri, tri1);
+ bond(tri2, tri3);
+ /* Ensure that the origin of `farleft' is sortarray[0]. */
+ otricopy(tri1, *farleft);
+ /* Ensure that the destination of `farright' is sortarray[2]. */
+ otricopy(tri2, *farright);
+ } else {
+ /* The three vertices are not collinear; the triangulation is one */
+ /* triangle, namely `midtri'. */
+ setorg(midtri, sortarray[0]);
+ setdest(tri1, sortarray[0]);
+ setorg(tri3, sortarray[0]);
+ /* Apices of tri1, tri2, and tri3 are left NULL. */
+ if (area > 0.0f) {
+ /* The vertices are in counterclockwise order. */
+ setdest(midtri, sortarray[1]);
+ setorg(tri1, sortarray[1]);
+ setdest(tri2, sortarray[1]);
+ setapex(midtri, sortarray[2]);
+ setorg(tri2, sortarray[2]);
+ setdest(tri3, sortarray[2]);
+ } else {
+ /* The vertices are in clockwise order. */
+ setdest(midtri, sortarray[2]);
+ setorg(tri1, sortarray[2]);
+ setdest(tri2, sortarray[2]);
+ setapex(midtri, sortarray[1]);
+ setorg(tri2, sortarray[1]);
+ setdest(tri3, sortarray[1]);
+ }
+ /* The topology does not depend on how the vertices are ordered. */
+ bond(midtri, tri1);
+ lnextself(midtri);
+ bond(midtri, tri2);
+ lnextself(midtri);
+ bond(midtri, tri3);
+ lprevself(tri1);
+ lnextself(tri2);
+ bond(tri1, tri2);
+ lprevself(tri1);
+ lprevself(tri3);
+ bond(tri1, tri3);
+ lnextself(tri2);
+ lprevself(tri3);
+ bond(tri2, tri3);
+ /* Ensure that the origin of `farleft' is sortarray[0]. */
+ otricopy(tri1, *farleft);
+ /* Ensure that the destination of `farright' is sortarray[2]. */
+ if (area > 0.0f) {
+ otricopy(tri2, *farright);
+ } else {
+ lnext(*farleft, *farright);
+ }
+ }
+ if (b->verbose > 2) {
+ printf(" Creating ");
+ printtriangle(m, b, &midtri);
+ printf(" Creating ");
+ printtriangle(m, b, &tri1);
+ printf(" Creating ");
+ printtriangle(m, b, &tri2);
+ printf(" Creating ");
+ printtriangle(m, b, &tri3);
+ }
+ return;
+ } else {
+ /* Split the vertices in half. */
+ divider = vertices >> 1;
+ /* Recursively triangulate each half. */
+ divconqrecurse(m, b, sortarray, divider, 1 - axis, farleft, &innerleft);
+ divconqrecurse(m, b, &sortarray[divider], vertices - divider, 1 - axis,
+ &innerright, farright);
+ if (b->verbose > 1) {
+ printf(" Joining triangulations with %d and %d vertices.\n", divider,
+ vertices - divider);
+ }
+ /* Merge the two triangulations into one. */
+ mergehulls(m, b, farleft, &innerleft, &innerright, farright, axis);
+ }
+}
+
+#ifdef ANSI_DECLARATORS
+long removeghosts(struct mesh *m, struct behavior *b, struct otri *startghost)
+#else /* not ANSI_DECLARATORS */
+long removeghosts(m, b, startghost)
+struct mesh *m;
+struct behavior *b;
+struct otri *startghost;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri searchedge;
+ struct otri dissolveedge;
+ struct otri deadtriangle;
+ vertex markorg;
+ long hullsize;
+ triangle ptr; /* Temporary variable used by sym(). */
+
+ if (b->verbose) {
+ printf(" Removing ghost triangles.\n");
+ }
+ /* Find an edge on the convex hull to start point location from. */
+ lprev(*startghost, searchedge);
+ symself(searchedge);
+ m->dummytri[0] = encode(searchedge);
+ /* Remove the bounding box and count the convex hull edges. */
+ otricopy(*startghost, dissolveedge);
+ hullsize = 0;
+ do {
+ hullsize++;
+ lnext(dissolveedge, deadtriangle);
+ lprevself(dissolveedge);
+ symself(dissolveedge);
+ /* If no PSLG is involved, set the boundary markers of all the vertices */
+ /* on the convex hull. If a PSLG is used, this step is done later. */
+ if (!b->poly) {
+ /* Watch out for the case where all the input vertices are collinear. */
+ if (dissolveedge.tri != m->dummytri) {
+ org(dissolveedge, markorg);
+ if (vertexmark(markorg) == 0) {
+ setvertexmark(markorg, 1);
+ }
+ }
+ }
+ /* Remove a bounding triangle from a convex hull triangle. */
+ dissolve(dissolveedge);
+ /* Find the next bounding triangle. */
+ sym(deadtriangle, dissolveedge);
+ /* Delete the bounding triangle. */
+ triangledealloc(m, deadtriangle.tri);
+ } while (!otriequal(dissolveedge, *startghost));
+ return hullsize;
+}
+
+/*****************************************************************************/
+/* */
+/* divconqdelaunay() Form a Delaunay triangulation by the divide-and- */
+/* conquer method. */
+/* */
+/* Sorts the vertices, calls a recursive procedure to triangulate them, and */
+/* removes the bounding box, setting boundary markers as appropriate. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+long divconqdelaunay(struct mesh *m, struct behavior *b)
+#else /* not ANSI_DECLARATORS */
+long divconqdelaunay(m, b)
+struct mesh *m;
+struct behavior *b;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ vertex *sortarray;
+ struct otri hullleft, hullright;
+ int divider;
+ int i, j;
+
+ if (b->verbose) {
+ printf(" Sorting vertices.\n");
+ }
+
+ /* Allocate an array of pointers to vertices for sorting. */
+ sortarray = (vertex *) trimalloc(m->invertices * (int) sizeof(vertex));
+ traversalinit(&m->vertices);
+ for (i = 0; i < m->invertices; i++) {
+ sortarray[i] = vertextraverse(m);
+ }
+ /* Sort the vertices. */
+ vertexsort(sortarray, m->invertices);
+ /* Discard duplicate vertices, which can really mess up the algorithm. */
+ i = 0;
+ for (j = 1; j < m->invertices; j++) {
+ if ((sortarray[i][0] == sortarray[j][0])
+ && (sortarray[i][1] == sortarray[j][1])) {
+ if (!b->quiet) {
+ printf(
+"Warning: A duplicate vertex at (%.12g, %.12g) appeared and was ignored.\n",
+ sortarray[j][0], sortarray[j][1]);
+ }
+ setvertextype(sortarray[j], UNDEADVERTEX);
+ m->undeads++;
+ } else {
+ i++;
+ sortarray[i] = sortarray[j];
+ }
+ }
+ i++;
+ if (b->dwyer) {
+ /* Re-sort the array of vertices to accommodate alternating cuts. */
+ divider = i >> 1;
+ if (i - divider >= 2) {
+ if (divider >= 2) {
+ alternateaxes(sortarray, divider, 1);
+ }
+ alternateaxes(&sortarray[divider], i - divider, 1);
+ }
+ }
+
+ if (b->verbose) {
+ printf(" Forming triangulation.\n");
+ }
+
+ /* Form the Delaunay triangulation. */
+ divconqrecurse(m, b, sortarray, i, 0, &hullleft, &hullright);
+ trifree((VOID *) sortarray);
+
+ return removeghosts(m, b, &hullleft);
+}
+
+/** **/
+/** **/
+/********* Divide-and-conquer Delaunay triangulation ends here *********/
+
+/********* Incremental Delaunay triangulation begins here *********/
+/** **/
+/** **/
+
+/*****************************************************************************/
+/* */
+/* boundingbox() Form an "infinite" bounding triangle to insert vertices */
+/* into. */
+/* */
+/* The vertices at "infinity" are = vec3ed finite coordinates, which are */
+/* used by the point location routines, but (mostly) ignored by the */
+/* Delaunay edge flip routines. */
+/* */
+/*****************************************************************************/
+
+#ifndef REDUCED
+
+#ifdef ANSI_DECLARATORS
+void boundingbox(struct mesh *m, struct behavior *b)
+#else /* not ANSI_DECLARATORS */
+void boundingbox(m, b)
+struct mesh *m;
+struct behavior *b;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri inftri; /* Handle for the triangular bounding box. */
+ tREAL width;
+
+ if (b->verbose) {
+ printf(" Creating triangular bounding box.\n");
+ }
+ /* Find the width (or height, whichever is larger) of the triangulation. */
+ width = m->xmax - m->xmin;
+ if (m->ymax - m->ymin > width) {
+ width = m->ymax - m->ymin;
+ }
+ if (width == 0.0f) {
+ width = 1.0f;
+ }
+ /* Create the vertices of the bounding box. */
+ m->infvertex1 = (vertex) trimalloc(m->vertices.itembytes);
+ m->infvertex2 = (vertex) trimalloc(m->vertices.itembytes);
+ m->infvertex3 = (vertex) trimalloc(m->vertices.itembytes);
+ m->infvertex1[0] = m->xmin - 50.0 * width;
+ m->infvertex1[1] = m->ymin - 40.0 * width;
+ m->infvertex2[0] = m->xmax + 50.0 * width;
+ m->infvertex2[1] = m->ymin - 40.0 * width;
+ m->infvertex3[0] = 0.5 * (m->xmin + m->xmax);
+ m->infvertex3[1] = m->ymax + 60.0 * width;
+
+ /* Create the bounding box. */
+ maketriangle(m, b, &inftri);
+ setorg(inftri, m->infvertex1);
+ setdest(inftri, m->infvertex2);
+ setapex(inftri, m->infvertex3);
+ /* Link dummytri to the bounding box so we can always find an */
+ /* edge to begin searching (point location) from. */
+ m->dummytri[0] = (triangle) inftri.tri;
+ if (b->verbose > 2) {
+ printf(" Creating ");
+ printtriangle(m, b, &inftri);
+ }
+}
+
+#endif /* not REDUCED */
+
+/*****************************************************************************/
+/* */
+/* removebox() Remove the "infinite" bounding triangle, setting boundary */
+/* markers as appropriate. */
+/* */
+/* The triangular bounding box has three boundary triangles (one for each */
+/* side of the bounding box), and a bunch of triangles fanning out from */
+/* the three bounding box vertices (one triangle for each edge of the */
+/* convex hull of the inner mesh). This routine removes these triangles. */
+/* */
+/* Returns the number of edges on the convex hull of the triangulation. */
+/* */
+/*****************************************************************************/
+
+#ifndef REDUCED
+
+#ifdef ANSI_DECLARATORS
+long removebox(struct mesh *m, struct behavior *b)
+#else /* not ANSI_DECLARATORS */
+long removebox(m, b)
+struct mesh *m;
+struct behavior *b;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri deadtriangle;
+ struct otri searchedge;
+ struct otri checkedge;
+ struct otri nextedge, finaledge, dissolveedge;
+ vertex markorg;
+ long hullsize;
+ triangle ptr; /* Temporary variable used by sym(). */
+
+ if (b->verbose) {
+ printf(" Removing triangular bounding box.\n");
+ }
+ /* Find a boundary triangle. */
+ nextedge.tri = m->dummytri;
+ nextedge.orient = 0;
+ symself(nextedge);
+ /* Mark a place to stop. */
+ lprev(nextedge, finaledge);
+ lnextself(nextedge);
+ symself(nextedge);
+ /* Find a triangle (on the boundary of the vertex set) that isn't */
+ /* a bounding box triangle. */
+ lprev(nextedge, searchedge);
+ symself(searchedge);
+ /* Check whether nextedge is another boundary triangle */
+ /* adjacent to the first one. */
+ lnext(nextedge, checkedge);
+ symself(checkedge);
+ if (checkedge.tri == m->dummytri) {
+ /* Go on to the next triangle. There are only three boundary */
+ /* triangles, and this next triangle cannot be the third one, */
+ /* so it's safe to stop here. */
+ lprevself(searchedge);
+ symself(searchedge);
+ }
+ /* Find a new boundary edge to search from, as the current search */
+ /* edge lies on a bounding box triangle and will be deleted. */
+ m->dummytri[0] = encode(searchedge);
+ hullsize = -2l;
+ while (!otriequal(nextedge, finaledge)) {
+ hullsize++;
+ lprev(nextedge, dissolveedge);
+ symself(dissolveedge);
+ /* If not using a PSLG, the vertices should be marked now. */
+ /* (If using a PSLG, markhull() will do the job.) */
+ if (!b->poly) {
+ /* Be careful! One must check for the case where all the input */
+ /* vertices are collinear, and thus all the triangles are part of */
+ /* the bounding box. Otherwise, the setvertexmark() call below */
+ /* will cause a bad pointer reference. */
+ if (dissolveedge.tri != m->dummytri) {
+ org(dissolveedge, markorg);
+ if (vertexmark(markorg) == 0) {
+ setvertexmark(markorg, 1);
+ }
+ }
+ }
+ /* Disconnect the bounding box triangle from the mesh triangle. */
+ dissolve(dissolveedge);
+ lnext(nextedge, deadtriangle);
+ sym(deadtriangle, nextedge);
+ /* Get rid of the bounding box triangle. */
+ triangledealloc(m, deadtriangle.tri);
+ /* Do we need to turn the corner? */
+ if (nextedge.tri == m->dummytri) {
+ /* Turn the corner. */
+ otricopy(dissolveedge, nextedge);
+ }
+ }
+ triangledealloc(m, finaledge.tri);
+
+ trifree((VOID *) m->infvertex1); /* Deallocate the bounding box vertices. */
+ trifree((VOID *) m->infvertex2);
+ trifree((VOID *) m->infvertex3);
+
+ return hullsize;
+}
+
+#endif /* not REDUCED */
+
+/*****************************************************************************/
+/* */
+/* incrementaldelaunay() Form a Delaunay triangulation by incrementally */
+/* inserting vertices. */
+/* */
+/* Returns the number of edges on the convex hull of the triangulation. */
+/* */
+/*****************************************************************************/
+
+#ifndef REDUCED
+
+#ifdef ANSI_DECLARATORS
+long incrementaldelaunay(struct mesh *m, struct behavior *b)
+#else /* not ANSI_DECLARATORS */
+long incrementaldelaunay(m, b)
+struct mesh *m;
+struct behavior *b;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri starttri;
+ vertex vertexloop;
+
+ /* Create a triangular bounding box. */
+ boundingbox(m, b);
+ if (b->verbose) {
+ printf(" Incrementally inserting vertices.\n");
+ }
+ traversalinit(&m->vertices);
+ vertexloop = vertextraverse(m);
+ while (vertexloop != (vertex) NULL) {
+ starttri.tri = m->dummytri;
+ if (insertvertex(m, b, vertexloop, &starttri, (struct osub *) NULL, 0, 0)
+ == DUPLICATEVERTEX) {
+ if (!b->quiet) {
+ printf(
+"Warning: A duplicate vertex at (%.12g, %.12g) appeared and was ignored.\n",
+ vertexloop[0], vertexloop[1]);
+ }
+ setvertextype(vertexloop, UNDEADVERTEX);
+ m->undeads++;
+ }
+ vertexloop = vertextraverse(m);
+ }
+ /* Remove the bounding box. */
+ return removebox(m, b);
+}
+
+#endif /* not REDUCED */
+
+/** **/
+/** **/
+/********* Incremental Delaunay triangulation ends here *********/
+
+/********* Sweepline Delaunay triangulation begins here *********/
+/** **/
+/** **/
+
+#ifndef REDUCED
+
+#ifdef ANSI_DECLARATORS
+void eventheapinsert(struct event **heap, int heapsize, struct event *newevent)
+#else /* not ANSI_DECLARATORS */
+void eventheapinsert(heap, heapsize, newevent)
+struct event **heap;
+int heapsize;
+struct event *newevent;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ tREAL eventx, eventy;
+ int eventnum;
+ int parent;
+ int notdone;
+
+ eventx = newevent->xkey;
+ eventy = newevent->ykey;
+ eventnum = heapsize;
+ notdone = eventnum > 0;
+ while (notdone) {
+ parent = (eventnum - 1) >> 1;
+ if ((heap[parent]->ykey < eventy) ||
+ ((heap[parent]->ykey == eventy)
+ && (heap[parent]->xkey <= eventx))) {
+ notdone = 0;
+ } else {
+ heap[eventnum] = heap[parent];
+ heap[eventnum]->heapposition = eventnum;
+
+ eventnum = parent;
+ notdone = eventnum > 0;
+ }
+ }
+ heap[eventnum] = newevent;
+ newevent->heapposition = eventnum;
+}
+
+#endif /* not REDUCED */
+
+#ifndef REDUCED
+
+#ifdef ANSI_DECLARATORS
+void eventheapify(struct event **heap, int heapsize, int eventnum)
+#else /* not ANSI_DECLARATORS */
+void eventheapify(heap, heapsize, eventnum)
+struct event **heap;
+int heapsize;
+int eventnum;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct event *thisevent;
+ tREAL eventx, eventy;
+ int leftchild, rightchild;
+ int smallest;
+ int notdone;
+
+ thisevent = heap[eventnum];
+ eventx = thisevent->xkey;
+ eventy = thisevent->ykey;
+ leftchild = 2 * eventnum + 1;
+ notdone = leftchild < heapsize;
+ while (notdone) {
+ if ((heap[leftchild]->ykey < eventy) ||
+ ((heap[leftchild]->ykey == eventy)
+ && (heap[leftchild]->xkey < eventx))) {
+ smallest = leftchild;
+ } else {
+ smallest = eventnum;
+ }
+ rightchild = leftchild + 1;
+ if (rightchild < heapsize) {
+ if ((heap[rightchild]->ykey < heap[smallest]->ykey) ||
+ ((heap[rightchild]->ykey == heap[smallest]->ykey)
+ && (heap[rightchild]->xkey < heap[smallest]->xkey))) {
+ smallest = rightchild;
+ }
+ }
+ if (smallest == eventnum) {
+ notdone = 0;
+ } else {
+ heap[eventnum] = heap[smallest];
+ heap[eventnum]->heapposition = eventnum;
+ heap[smallest] = thisevent;
+ thisevent->heapposition = smallest;
+
+ eventnum = smallest;
+ leftchild = 2 * eventnum + 1;
+ notdone = leftchild < heapsize;
+ }
+ }
+}
+
+#endif /* not REDUCED */
+
+#ifndef REDUCED
+
+#ifdef ANSI_DECLARATORS
+void eventheapdelete(struct event **heap, int heapsize, int eventnum)
+#else /* not ANSI_DECLARATORS */
+void eventheapdelete(heap, heapsize, eventnum)
+struct event **heap;
+int heapsize;
+int eventnum;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct event *moveevent;
+ tREAL eventx, eventy;
+ int parent;
+ int notdone;
+
+ moveevent = heap[heapsize - 1];
+ if (eventnum > 0) {
+ eventx = moveevent->xkey;
+ eventy = moveevent->ykey;
+ do {
+ parent = (eventnum - 1) >> 1;
+ if ((heap[parent]->ykey < eventy) ||
+ ((heap[parent]->ykey == eventy)
+ && (heap[parent]->xkey <= eventx))) {
+ notdone = 0;
+ } else {
+ heap[eventnum] = heap[parent];
+ heap[eventnum]->heapposition = eventnum;
+
+ eventnum = parent;
+ notdone = eventnum > 0;
+ }
+ } while (notdone);
+ }
+ heap[eventnum] = moveevent;
+ moveevent->heapposition = eventnum;
+ eventheapify(heap, heapsize - 1, eventnum);
+}
+
+#endif /* not REDUCED */
+
+#ifndef REDUCED
+
+#ifdef ANSI_DECLARATORS
+void createeventheap(struct mesh *m, struct event ***eventheap,
+ struct event **events, struct event **freeevents)
+#else /* not ANSI_DECLARATORS */
+void createeventheap(m, eventheap, events, freeevents)
+struct mesh *m;
+struct event ***eventheap;
+struct event **events;
+struct event **freeevents;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ vertex thisvertex;
+ int maxevents;
+ int i;
+
+ maxevents = (3 * m->invertices) / 2;
+ *eventheap = (struct event **) trimalloc(maxevents *
+ (int) sizeof(struct event *));
+ *events = (struct event *) trimalloc(maxevents * (int) sizeof(struct event));
+ traversalinit(&m->vertices);
+ for (i = 0; i < m->invertices; i++) {
+ thisvertex = vertextraverse(m);
+ (*events)[i].eventptr = (VOID *) thisvertex;
+ (*events)[i].xkey = thisvertex[0];
+ (*events)[i].ykey = thisvertex[1];
+ eventheapinsert(*eventheap, i, *events + i);
+ }
+ *freeevents = (struct event *) NULL;
+ for (i = maxevents - 1; i >= m->invertices; i--) {
+ (*events)[i].eventptr = (VOID *) *freeevents;
+ *freeevents = *events + i;
+ }
+}
+
+#endif /* not REDUCED */
+
+#ifndef REDUCED
+
+#ifdef ANSI_DECLARATORS
+int rightofhyperbola(struct mesh *m, struct otri *fronttri, vertex newsite)
+#else /* not ANSI_DECLARATORS */
+int rightofhyperbola(m, fronttri, newsite)
+struct mesh *m;
+struct otri *fronttri;
+vertex newsite;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ vertex leftvertex, rightvertex;
+ tREAL dxa, dya, dxb, dyb;
+
+ m->hyperbolacount++;
+
+ dest(*fronttri, leftvertex);
+ apex(*fronttri, rightvertex);
+ if ((leftvertex[1] < rightvertex[1]) ||
+ ((leftvertex[1] == rightvertex[1]) &&
+ (leftvertex[0] < rightvertex[0]))) {
+ if (newsite[0] >= rightvertex[0]) {
+ return 1;
+ }
+ } else {
+ if (newsite[0] <= leftvertex[0]) {
+ return 0;
+ }
+ }
+ dxa = leftvertex[0] - newsite[0];
+ dya = leftvertex[1] - newsite[1];
+ dxb = rightvertex[0] - newsite[0];
+ dyb = rightvertex[1] - newsite[1];
+ return dya * (dxb * dxb + dyb * dyb) > dyb * (dxa * dxa + dya * dya);
+}
+
+#endif /* not REDUCED */
+
+#ifndef REDUCED
+
+#ifdef ANSI_DECLARATORS
+tREAL circletop(struct mesh *m, vertex pa, vertex pb, vertex pc, tREAL ccwabc)
+#else /* not ANSI_DECLARATORS */
+tREAL circletop(m, pa, pb, pc, ccwabc)
+struct mesh *m;
+vertex pa;
+vertex pb;
+vertex pc;
+tREAL ccwabc;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ tREAL xac, yac, xbc, ybc, xab, yab;
+ tREAL aclen2, bclen2, ablen2;
+
+ m->circletopcount++;
+
+ xac = pa[0] - pc[0];
+ yac = pa[1] - pc[1];
+ xbc = pb[0] - pc[0];
+ ybc = pb[1] - pc[1];
+ xab = pa[0] - pb[0];
+ yab = pa[1] - pb[1];
+ aclen2 = xac * xac + yac * yac;
+ bclen2 = xbc * xbc + ybc * ybc;
+ ablen2 = xab * xab + yab * yab;
+ return pc[1] + (xac * bclen2 - xbc * aclen2 + sqrt(aclen2 * bclen2 * ablen2))
+ / (2.0 * ccwabc);
+}
+
+#endif /* not REDUCED */
+
+#ifndef REDUCED
+
+#ifdef ANSI_DECLARATORS
+void check4deadevent(struct otri *checktri, struct event **freeevents,
+ struct event **eventheap, int *heapsize)
+#else /* not ANSI_DECLARATORS */
+void check4deadevent(checktri, freeevents, eventheap, heapsize)
+struct otri *checktri;
+struct event **freeevents;
+struct event **eventheap;
+int *heapsize;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct event *deadevent;
+ vertex eventvertex;
+ int eventnum;
+
+ org(*checktri, eventvertex);
+ if (eventvertex != (vertex) NULL) {
+ deadevent = (struct event *) eventvertex;
+ eventnum = deadevent->heapposition;
+ deadevent->eventptr = (VOID *) *freeevents;
+ *freeevents = deadevent;
+ eventheapdelete(eventheap, *heapsize, eventnum);
+ (*heapsize)--;
+ setorg(*checktri, NULL);
+ }
+}
+
+#endif /* not REDUCED */
+
+#ifndef REDUCED
+
+#ifdef ANSI_DECLARATORS
+struct splaynode *splay(struct mesh *m, struct splaynode *splaytree,
+ vertex searchpoint, struct otri *searchtri)
+#else /* not ANSI_DECLARATORS */
+struct splaynode *splay(m, splaytree, searchpoint, searchtri)
+struct mesh *m;
+struct splaynode *splaytree;
+vertex searchpoint;
+struct otri *searchtri;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct splaynode *child, *grandchild;
+ struct splaynode *lefttree, *righttree;
+ struct splaynode *leftright;
+ vertex checkvertex;
+ int rightofroot, rightofchild;
+
+ if (splaytree == (struct splaynode *) NULL) {
+ return (struct splaynode *) NULL;
+ }
+ dest(splaytree->keyedge, checkvertex);
+ if (checkvertex == splaytree->keydest) {
+ rightofroot = rightofhyperbola(m, &splaytree->keyedge, searchpoint);
+ if (rightofroot) {
+ otricopy(splaytree->keyedge, *searchtri);
+ child = splaytree->rchild;
+ } else {
+ child = splaytree->lchild;
+ }
+ if (child == (struct splaynode *) NULL) {
+ return splaytree;
+ }
+ dest(child->keyedge, checkvertex);
+ if (checkvertex != child->keydest) {
+ child = splay(m, child, searchpoint, searchtri);
+ if (child == (struct splaynode *) NULL) {
+ if (rightofroot) {
+ splaytree->rchild = (struct splaynode *) NULL;
+ } else {
+ splaytree->lchild = (struct splaynode *) NULL;
+ }
+ return splaytree;
+ }
+ }
+ rightofchild = rightofhyperbola(m, &child->keyedge, searchpoint);
+ if (rightofchild) {
+ otricopy(child->keyedge, *searchtri);
+ grandchild = splay(m, child->rchild, searchpoint, searchtri);
+ child->rchild = grandchild;
+ } else {
+ grandchild = splay(m, child->lchild, searchpoint, searchtri);
+ child->lchild = grandchild;
+ }
+ if (grandchild == (struct splaynode *) NULL) {
+ if (rightofroot) {
+ splaytree->rchild = child->lchild;
+ child->lchild = splaytree;
+ } else {
+ splaytree->lchild = child->rchild;
+ child->rchild = splaytree;
+ }
+ return child;
+ }
+ if (rightofchild) {
+ if (rightofroot) {
+ splaytree->rchild = child->lchild;
+ child->lchild = splaytree;
+ } else {
+ splaytree->lchild = grandchild->rchild;
+ grandchild->rchild = splaytree;
+ }
+ child->rchild = grandchild->lchild;
+ grandchild->lchild = child;
+ } else {
+ if (rightofroot) {
+ splaytree->rchild = grandchild->lchild;
+ grandchild->lchild = splaytree;
+ } else {
+ splaytree->lchild = child->rchild;
+ child->rchild = splaytree;
+ }
+ child->lchild = grandchild->rchild;
+ grandchild->rchild = child;
+ }
+ return grandchild;
+ } else {
+ lefttree = splay(m, splaytree->lchild, searchpoint, searchtri);
+ righttree = splay(m, splaytree->rchild, searchpoint, searchtri);
+
+ pooldealloc(&m->splaynodes, (VOID *) splaytree);
+ if (lefttree == (struct splaynode *) NULL) {
+ return righttree;
+ } else if (righttree == (struct splaynode *) NULL) {
+ return lefttree;
+ } else if (lefttree->rchild == (struct splaynode *) NULL) {
+ lefttree->rchild = righttree->lchild;
+ righttree->lchild = lefttree;
+ return righttree;
+ } else if (righttree->lchild == (struct splaynode *) NULL) {
+ righttree->lchild = lefttree->rchild;
+ lefttree->rchild = righttree;
+ return lefttree;
+ } else {
+/* printf("Holy Toledo!!!\n"); */
+ leftright = lefttree->rchild;
+ while (leftright->rchild != (struct splaynode *) NULL) {
+ leftright = leftright->rchild;
+ }
+ leftright->rchild = righttree;
+ return lefttree;
+ }
+ }
+}
+
+#endif /* not REDUCED */
+
+#ifndef REDUCED
+
+#ifdef ANSI_DECLARATORS
+struct splaynode *splayinsert(struct mesh *m, struct splaynode *splayroot,
+ struct otri *newkey, vertex searchpoint)
+#else /* not ANSI_DECLARATORS */
+struct splaynode *splayinsert(m, splayroot, newkey, searchpoint)
+struct mesh *m;
+struct splaynode *splayroot;
+struct otri *newkey;
+vertex searchpoint;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct splaynode *newsplaynode;
+
+ newsplaynode = (struct splaynode *) poolalloc(&m->splaynodes);
+ otricopy(*newkey, newsplaynode->keyedge);
+ dest(*newkey, newsplaynode->keydest);
+ if (splayroot == (struct splaynode *) NULL) {
+ newsplaynode->lchild = (struct splaynode *) NULL;
+ newsplaynode->rchild = (struct splaynode *) NULL;
+ } else if (rightofhyperbola(m, &splayroot->keyedge, searchpoint)) {
+ newsplaynode->lchild = splayroot;
+ newsplaynode->rchild = splayroot->rchild;
+ splayroot->rchild = (struct splaynode *) NULL;
+ } else {
+ newsplaynode->lchild = splayroot->lchild;
+ newsplaynode->rchild = splayroot;
+ splayroot->lchild = (struct splaynode *) NULL;
+ }
+ return newsplaynode;
+}
+
+#endif /* not REDUCED */
+
+#ifndef REDUCED
+
+#ifdef ANSI_DECLARATORS
+struct splaynode *circletopinsert(struct mesh *m, struct behavior *b,
+ struct splaynode *splayroot,
+ struct otri *newkey,
+ vertex pa, vertex pb, vertex pc, tREAL topy)
+#else /* not ANSI_DECLARATORS */
+struct splaynode *circletopinsert(m, b, splayroot, newkey, pa, pb, pc, topy)
+struct mesh *m;
+struct behavior *b;
+struct splaynode *splayroot;
+struct otri *newkey;
+vertex pa;
+vertex pb;
+vertex pc;
+tREAL topy;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ tREAL ccwabc;
+ tREAL xac, yac, xbc, ybc;
+ tREAL aclen2, bclen2;
+ tREAL searchpoint[2];
+ struct otri dummytri;
+
+ ccwabc = counterclockwise(m, b, pa, pb, pc);
+ xac = pa[0] - pc[0];
+ yac = pa[1] - pc[1];
+ xbc = pb[0] - pc[0];
+ ybc = pb[1] - pc[1];
+ aclen2 = xac * xac + yac * yac;
+ bclen2 = xbc * xbc + ybc * ybc;
+ searchpoint[0] = pc[0] - (yac * bclen2 - ybc * aclen2) / (2.0 * ccwabc);
+ searchpoint[1] = topy;
+ return splayinsert(m, splay(m, splayroot, (vertex) searchpoint, &dummytri),
+ newkey, (vertex) searchpoint);
+}
+
+#endif /* not REDUCED */
+
+#ifndef REDUCED
+
+#ifdef ANSI_DECLARATORS
+struct splaynode *frontlocate(struct mesh *m, struct splaynode *splayroot,
+ struct otri *bottommost, vertex searchvertex,
+ struct otri *searchtri, int *farright)
+#else /* not ANSI_DECLARATORS */
+struct splaynode *frontlocate(m, splayroot, bottommost, searchvertex,
+ searchtri, farright)
+struct mesh *m;
+struct splaynode *splayroot;
+struct otri *bottommost;
+vertex searchvertex;
+struct otri *searchtri;
+int *farright;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ int farrightflag;
+ triangle ptr; /* Temporary variable used by onext(). */
+
+ otricopy(*bottommost, *searchtri);
+ splayroot = splay(m, splayroot, searchvertex, searchtri);
+
+ farrightflag = 0;
+ while (!farrightflag && rightofhyperbola(m, searchtri, searchvertex)) {
+ onextself(*searchtri);
+ farrightflag = otriequal(*searchtri, *bottommost);
+ }
+ *farright = farrightflag;
+ return splayroot;
+}
+
+#endif /* not REDUCED */
+
+#ifndef REDUCED
+
+#ifdef ANSI_DECLARATORS
+long sweeplinedelaunay(struct mesh *m, struct behavior *b)
+#else /* not ANSI_DECLARATORS */
+long sweeplinedelaunay(m, b)
+struct mesh *m;
+struct behavior *b;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct event **eventheap;
+ struct event *events;
+ struct event *freeevents;
+ struct event *nextevent;
+ struct event *newevent;
+ struct splaynode *splayroot;
+ struct otri bottommost;
+ struct otri searchtri;
+ struct otri fliptri;
+ struct otri lefttri, righttri, farlefttri, farrighttri;
+ struct otri inserttri;
+ vertex firstvertex, secondvertex;
+ vertex nextvertex, lastvertex;
+ vertex connectvertex;
+ vertex leftvertex, midvertex, rightvertex;
+ tREAL lefttest, righttest;
+ int heapsize;
+ int check4events, farrightflag;
+ triangle ptr; /* Temporary variable used by sym(), onext(), and oprev(). */
+
+ poolinit(&m->splaynodes, sizeof(struct splaynode), SPLAYNODEPERBLOCK,
+ SPLAYNODEPERBLOCK, 0);
+ splayroot = (struct splaynode *) NULL;
+
+ if (b->verbose) {
+ printf(" Placing vertices in event heap.\n");
+ }
+ createeventheap(m, &eventheap, &events, &freeevents);
+ heapsize = m->invertices;
+
+ if (b->verbose) {
+ printf(" Forming triangulation.\n");
+ }
+ maketriangle(m, b, &lefttri);
+ maketriangle(m, b, &righttri);
+ bond(lefttri, righttri);
+ lnextself(lefttri);
+ lprevself(righttri);
+ bond(lefttri, righttri);
+ lnextself(lefttri);
+ lprevself(righttri);
+ bond(lefttri, righttri);
+ firstvertex = (vertex) eventheap[0]->eventptr;
+ eventheap[0]->eventptr = (VOID *) freeevents;
+ freeevents = eventheap[0];
+ eventheapdelete(eventheap, heapsize, 0);
+ heapsize--;
+ do {
+ if (heapsize == 0) {
+ printf("Error: Input vertices are all identical.\n");
+ triexit(1);
+ }
+ secondvertex = (vertex) eventheap[0]->eventptr;
+ eventheap[0]->eventptr = (VOID *) freeevents;
+ freeevents = eventheap[0];
+ eventheapdelete(eventheap, heapsize, 0);
+ heapsize--;
+ if ((firstvertex[0] == secondvertex[0]) &&
+ (firstvertex[1] == secondvertex[1])) {
+ if (!b->quiet) {
+ printf(
+"Warning: A duplicate vertex at (%.12g, %.12g) appeared and was ignored.\n",
+ secondvertex[0], secondvertex[1]);
+ }
+ setvertextype(secondvertex, UNDEADVERTEX);
+ m->undeads++;
+ }
+ } while ((firstvertex[0] == secondvertex[0]) &&
+ (firstvertex[1] == secondvertex[1]));
+ setorg(lefttri, firstvertex);
+ setdest(lefttri, secondvertex);
+ setorg(righttri, secondvertex);
+ setdest(righttri, firstvertex);
+ lprev(lefttri, bottommost);
+ lastvertex = secondvertex;
+ while (heapsize > 0) {
+ nextevent = eventheap[0];
+ eventheapdelete(eventheap, heapsize, 0);
+ heapsize--;
+ check4events = 1;
+ if (nextevent->xkey < m->xmin) {
+ decode(nextevent->eventptr, fliptri);
+ oprev(fliptri, farlefttri);
+ check4deadevent(&farlefttri, &freeevents, eventheap, &heapsize);
+ onext(fliptri, farrighttri);
+ check4deadevent(&farrighttri, &freeevents, eventheap, &heapsize);
+
+ if (otriequal(farlefttri, bottommost)) {
+ lprev(fliptri, bottommost);
+ }
+ flip(m, b, &fliptri);
+ setapex(fliptri, NULL);
+ lprev(fliptri, lefttri);
+ lnext(fliptri, righttri);
+ sym(lefttri, farlefttri);
+
+ if (randomnation(SAMPLERATE) == 0) {
+ symself(fliptri);
+ dest(fliptri, leftvertex);
+ apex(fliptri, midvertex);
+ org(fliptri, rightvertex);
+ splayroot = circletopinsert(m, b, splayroot, &lefttri, leftvertex,
+ midvertex, rightvertex, nextevent->ykey);
+ }
+ } else {
+ nextvertex = (vertex) nextevent->eventptr;
+ if ((nextvertex[0] == lastvertex[0]) &&
+ (nextvertex[1] == lastvertex[1])) {
+ if (!b->quiet) {
+ printf(
+"Warning: A duplicate vertex at (%.12g, %.12g) appeared and was ignored.\n",
+ nextvertex[0], nextvertex[1]);
+ }
+ setvertextype(nextvertex, UNDEADVERTEX);
+ m->undeads++;
+ check4events = 0;
+ } else {
+ lastvertex = nextvertex;
+
+ splayroot = frontlocate(m, splayroot, &bottommost, nextvertex,
+ &searchtri, &farrightflag);
+/*
+ otricopy(bottommost, searchtri);
+ farrightflag = 0;
+ while (!farrightflag && rightofhyperbola(m, &searchtri, nextvertex)) {
+ onextself(searchtri);
+ farrightflag = otriequal(searchtri, bottommost);
+ }
+*/
+
+ check4deadevent(&searchtri, &freeevents, eventheap, &heapsize);
+
+ otricopy(searchtri, farrighttri);
+ sym(searchtri, farlefttri);
+ maketriangle(m, b, &lefttri);
+ maketriangle(m, b, &righttri);
+ dest(farrighttri, connectvertex);
+ setorg(lefttri, connectvertex);
+ setdest(lefttri, nextvertex);
+ setorg(righttri, nextvertex);
+ setdest(righttri, connectvertex);
+ bond(lefttri, righttri);
+ lnextself(lefttri);
+ lprevself(righttri);
+ bond(lefttri, righttri);
+ lnextself(lefttri);
+ lprevself(righttri);
+ bond(lefttri, farlefttri);
+ bond(righttri, farrighttri);
+ if (!farrightflag && otriequal(farrighttri, bottommost)) {
+ otricopy(lefttri, bottommost);
+ }
+
+ if (randomnation(SAMPLERATE) == 0) {
+ splayroot = splayinsert(m, splayroot, &lefttri, nextvertex);
+ } else if (randomnation(SAMPLERATE) == 0) {
+ lnext(righttri, inserttri);
+ splayroot = splayinsert(m, splayroot, &inserttri, nextvertex);
+ }
+ }
+ }
+ nextevent->eventptr = (VOID *) freeevents;
+ freeevents = nextevent;
+
+ if (check4events) {
+ apex(farlefttri, leftvertex);
+ dest(lefttri, midvertex);
+ apex(lefttri, rightvertex);
+ lefttest = counterclockwise(m, b, leftvertex, midvertex, rightvertex);
+ if (lefttest > 0.0f) {
+ newevent = freeevents;
+ freeevents = (struct event *) freeevents->eventptr;
+ newevent->xkey = m->xminextreme;
+ newevent->ykey = circletop(m, leftvertex, midvertex, rightvertex,
+ lefttest);
+ newevent->eventptr = (VOID *) encode(lefttri);
+ eventheapinsert(eventheap, heapsize, newevent);
+ heapsize++;
+ setorg(lefttri, newevent);
+ }
+ apex(righttri, leftvertex);
+ org(righttri, midvertex);
+ apex(farrighttri, rightvertex);
+ righttest = counterclockwise(m, b, leftvertex, midvertex, rightvertex);
+ if (righttest > 0.0f) {
+ newevent = freeevents;
+ freeevents = (struct event *) freeevents->eventptr;
+ newevent->xkey = m->xminextreme;
+ newevent->ykey = circletop(m, leftvertex, midvertex, rightvertex,
+ righttest);
+ newevent->eventptr = (VOID *) encode(farrighttri);
+ eventheapinsert(eventheap, heapsize, newevent);
+ heapsize++;
+ setorg(farrighttri, newevent);
+ }
+ }
+ }
+
+ pooldeinit(&m->splaynodes);
+ lprevself(bottommost);
+ return removeghosts(m, b, &bottommost);
+}
+
+#endif /* not REDUCED */
+
+/** **/
+/** **/
+/********* Sweepline Delaunay triangulation ends here *********/
+
+/********* General mesh construction routines begin here *********/
+/** **/
+/** **/
+
+/*****************************************************************************/
+/* */
+/* delaunay() Form a Delaunay triangulation. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+long delaunay(struct mesh *m, struct behavior *b)
+#else /* not ANSI_DECLARATORS */
+long delaunay(m, b)
+struct mesh *m;
+struct behavior *b;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ long hulledges;
+
+ m->eextras = 0;
+ initializetrisubpools(m, b);
+
+#ifdef REDUCED
+ if (!b->quiet) {
+ printf(
+ "Constructing Delaunay triangulation by divide-and-conquer method.\n");
+ }
+ hulledges = divconqdelaunay(m, b);
+#else /* not REDUCED */
+ if (!b->quiet) {
+ printf("Constructing Delaunay triangulation ");
+ if (b->incremental) {
+ printf("by incremental method.\n");
+ } else if (b->sweepline) {
+ printf("by sweepline method.\n");
+ } else {
+ printf("by divide-and-conquer method.\n");
+ }
+ }
+ if (b->incremental) {
+ hulledges = incrementaldelaunay(m, b);
+ } else if (b->sweepline) {
+ hulledges = sweeplinedelaunay(m, b);
+ } else {
+ hulledges = divconqdelaunay(m, b);
+ }
+#endif /* not REDUCED */
+
+ if (m->triangles.items == 0) {
+ /* The input vertices were all collinear, so there are no triangles. */
+ return 0l;
+ } else {
+ return hulledges;
+ }
+}
+
+/*****************************************************************************/
+/* */
+/* reconstruct() Reconstruct a triangulation from its .ele (and possibly */
+/* .poly) file. Used when the -r switch is used. */
+/* */
+/* Reads an .ele file and reconstructs the original mesh. If the -p switch */
+/* is used, this procedure will also read a .poly file and reconstruct the */
+/* subsegments of the original mesh. If the -a switch is used, this */
+/* procedure will also read an .area file and set a maximum area constraint */
+/* on each triangle. */
+/* */
+/* Vertices that are not corners of triangles, such as nodes on edges of */
+/* subparametric elements, are discarded. */
+/* */
+/* This routine finds the adjacencies between triangles (and subsegments) */
+/* by forming one stack of triangles for each vertex. Each triangle is on */
+/* three different stacks simultaneously. Each triangle's subsegment */
+/* pointers are used to link the items in each stack. This memory-saving */
+/* feature makes the code harder to read. The most important thing to keep */
+/* in mind is that each triangle is removed from a stack precisely when */
+/* the corresponding pointer is adjusted to refer to a subsegment rather */
+/* than the next triangle of the stack. */
+/* */
+/*****************************************************************************/
+
+#ifndef CDT_ONLY
+
+#ifdef TRILIBRARY
+
+#ifdef ANSI_DECLARATORS
+int reconstruct(struct mesh *m, struct behavior *b, int *trianglelist,
+ tREAL *triangleattriblist, tREAL *trianglearealist,
+ int elements, int corners, int attribs,
+ int *segmentlist,int *segmentmarkerlist, int numberofsegments)
+#else /* not ANSI_DECLARATORS */
+int reconstruct(m, b, trianglelist, triangleattriblist, trianglearealist,
+ elements, corners, attribs, segmentlist, segmentmarkerlist,
+ numberofsegments)
+struct mesh *m;
+struct behavior *b;
+int *trianglelist;
+tREAL *triangleattriblist;
+tREAL *trianglearealist;
+int elements;
+int corners;
+int attribs;
+int *segmentlist;
+int *segmentmarkerlist;
+int numberofsegments;
+#endif /* not ANSI_DECLARATORS */
+
+#else /* not TRILIBRARY */
+
+#ifdef ANSI_DECLARATORS
+long reconstruct(struct mesh *m, struct behavior *b, char *elefilename,
+ char *areafilename, char *polyfilename, FILE *polyfile)
+#else /* not ANSI_DECLARATORS */
+long reconstruct(m, b, elefilename, areafilename, polyfilename, polyfile)
+struct mesh *m;
+struct behavior *b;
+char *elefilename;
+char *areafilename;
+char *polyfilename;
+FILE *polyfile;
+#endif /* not ANSI_DECLARATORS */
+
+#endif /* not TRILIBRARY */
+
+{
+#ifdef TRILIBRARY
+ int vertexindex;
+ int attribindex;
+#else /* not TRILIBRARY */
+ FILE *elefile;
+ FILE *areafile;
+ char inputline[INPUTLINESIZE];
+ char *stringptr;
+ int areaelements;
+#endif /* not TRILIBRARY */
+ struct otri triangleloop;
+ struct otri triangleleft;
+ struct otri checktri;
+ struct otri checkleft;
+ struct otri checkneighbor;
+ struct osub subsegloop;
+ triangle *vertexarray;
+ triangle *prevlink;
+ triangle nexttri;
+ vertex tdest, tapex;
+ vertex checkdest, checkapex;
+ vertex shorg;
+ vertex killvertex;
+ vertex segmentorg, segmentdest;
+ tREAL area;
+ int corner[3];
+ int end[2];
+ int killvertexindex;
+ int incorners;
+ int segmentmarkers;
+ int boundmarker;
+ int aroundvertex;
+ long hullsize;
+ int notfound;
+ long elementnumber, segmentnumber;
+ int i, j;
+ triangle ptr; /* Temporary variable used by sym(). */
+
+#ifdef TRILIBRARY
+ m->inelements = elements;
+ incorners = corners;
+ if (incorners < 3) {
+ printf("Error: Triangles must have at least 3 vertices.\n");
+ triexit(1);
+ }
+ m->eextras = attribs;
+#else /* not TRILIBRARY */
+ /* Read the triangles from an .ele file. */
+ if (!b->quiet) {
+ printf("Opening %s.\n", elefilename);
+ }
+ elefile = fopen(elefilename, "r");
+ if (elefile == (FILE *) NULL) {
+ printf(" Error: Cannot access file %s.\n", elefilename);
+ triexit(1);
+ }
+ /* Read number of triangles, number of vertices per triangle, and */
+ /* number of triangle attributes from .ele file. */
+ stringptr = readline(inputline, elefile, elefilename);
+ m->inelements = (int) strtol(stringptr, &stringptr, 0);
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ incorners = 3;
+ } else {
+ incorners = (int) strtol(stringptr, &stringptr, 0);
+ if (incorners < 3) {
+ printf("Error: Triangles in %s must have at least 3 vertices.\n",
+ elefilename);
+ triexit(1);
+ }
+ }
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ m->eextras = 0;
+ } else {
+ m->eextras = (int) strtol(stringptr, &stringptr, 0);
+ }
+#endif /* not TRILIBRARY */
+
+ initializetrisubpools(m, b);
+
+ /* Create the triangles. */
+ for (elementnumber = 1; elementnumber <= m->inelements; elementnumber++) {
+ maketriangle(m, b, &triangleloop);
+ /* Mark the triangle as living. */
+ triangleloop.tri[3] = (triangle) triangleloop.tri;
+ }
+
+ segmentmarkers = 0;
+ if (b->poly) {
+#ifdef TRILIBRARY
+ m->insegments = numberofsegments;
+ segmentmarkers = segmentmarkerlist != (int *) NULL;
+#else /* not TRILIBRARY */
+ /* Read number of segments and number of segment */
+ /* boundary markers from .poly file. */
+ stringptr = readline(inputline, polyfile, b->inpolyfilename);
+ m->insegments = (int) strtol(stringptr, &stringptr, 0);
+ stringptr = findfield(stringptr);
+ if (*stringptr != '\0') {
+ segmentmarkers = (int) strtol(stringptr, &stringptr, 0);
+ }
+#endif /* not TRILIBRARY */
+
+ /* Create the subsegments. */
+ for (segmentnumber = 1; segmentnumber <= m->insegments; segmentnumber++) {
+ makesubseg(m, &subsegloop);
+ /* Mark the subsegment as living. */
+ subsegloop.ss[2] = (subseg) subsegloop.ss;
+ }
+ }
+
+#ifdef TRILIBRARY
+ vertexindex = 0;
+ attribindex = 0;
+#else /* not TRILIBRARY */
+ if (b->vararea) {
+ /* Open an .area file, check for consistency with the .ele file. */
+ if (!b->quiet) {
+ printf("Opening %s.\n", areafilename);
+ }
+ areafile = fopen(areafilename, "r");
+ if (areafile == (FILE *) NULL) {
+ printf(" Error: Cannot access file %s.\n", areafilename);
+ triexit(1);
+ }
+ stringptr = readline(inputline, areafile, areafilename);
+ areaelements = (int) strtol(stringptr, &stringptr, 0);
+ if (areaelements != m->inelements) {
+ printf("Error: %s and %s disagree on number of triangles.\n",
+ elefilename, areafilename);
+ triexit(1);
+ }
+ }
+#endif /* not TRILIBRARY */
+
+ if (!b->quiet) {
+ printf("Reconstructing mesh.\n");
+ }
+ /* Allocate a temporary array that maps each vertex to some adjacent */
+ /* triangle. I took care to allocate all the permanent memory for */
+ /* triangles and subsegments first. */
+ vertexarray = (triangle *) trimalloc(m->vertices.items *
+ (int) sizeof(triangle));
+ /* Each vertex is initially unrepresented. */
+ for (i = 0; i < m->vertices.items; i++) {
+ vertexarray[i] = (triangle) m->dummytri;
+ }
+
+ if (b->verbose) {
+ printf(" Assembling triangles.\n");
+ }
+ /* Read the triangles from the .ele file, and link */
+ /* together those that share an edge. */
+ traversalinit(&m->triangles);
+ triangleloop.tri = triangletraverse(m);
+ elementnumber = b->firstnumber;
+ while (triangleloop.tri != (triangle *) NULL) {
+#ifdef TRILIBRARY
+ /* Copy the triangle's three corners. */
+ for (j = 0; j < 3; j++) {
+ corner[j] = trianglelist[vertexindex++];
+ if ((corner[j] < b->firstnumber) ||
+ (corner[j] >= b->firstnumber + m->invertices)) {
+ printf("Error: Triangle %ld has an invalid vertex index.\n",
+ elementnumber);
+ triexit(1);
+ }
+ }
+#else /* not TRILIBRARY */
+ /* Read triangle number and the triangle's three corners. */
+ stringptr = readline(inputline, elefile, elefilename);
+ for (j = 0; j < 3; j++) {
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ printf("Error: Triangle %ld is missing vertex %d in %s.\n",
+ elementnumber, j + 1, elefilename);
+ triexit(1);
+ } else {
+ corner[j] = (int) strtol(stringptr, &stringptr, 0);
+ if ((corner[j] < b->firstnumber) ||
+ (corner[j] >= b->firstnumber + m->invertices)) {
+ printf("Error: Triangle %ld has an invalid vertex index.\n",
+ elementnumber);
+ triexit(1);
+ }
+ }
+ }
+#endif /* not TRILIBRARY */
+
+ /* Find out about (and throw away) extra nodes. */
+ for (j = 3; j < incorners; j++) {
+#ifdef TRILIBRARY
+ killvertexindex = trianglelist[vertexindex++];
+#else /* not TRILIBRARY */
+ stringptr = findfield(stringptr);
+ if (*stringptr != '\0') {
+ killvertexindex = (int) strtol(stringptr, &stringptr, 0);
+#endif /* not TRILIBRARY */
+ if ((killvertexindex >= b->firstnumber) &&
+ (killvertexindex < b->firstnumber + m->invertices)) {
+ /* Delete the non-corner vertex if it's not already deleted. */
+ killvertex = getvertex(m, b, killvertexindex);
+ if (vertextype(killvertex) != DEADVERTEX) {
+ vertexdealloc(m, killvertex);
+ }
+ }
+#ifndef TRILIBRARY
+ }
+#endif /* not TRILIBRARY */
+ }
+
+ /* Read the triangle's attributes. */
+ for (j = 0; j < m->eextras; j++) {
+#ifdef TRILIBRARY
+ setelemattribute(triangleloop, j, triangleattriblist[attribindex++]);
+#else /* not TRILIBRARY */
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ setelemattribute(triangleloop, j, 0);
+ } else {
+ setelemattribute(triangleloop, j,
+ (tREAL) strtod(stringptr, &stringptr));
+ }
+#endif /* not TRILIBRARY */
+ }
+
+ if (b->vararea) {
+#ifdef TRILIBRARY
+ area = trianglearealist[elementnumber - b->firstnumber];
+#else /* not TRILIBRARY */
+ /* Read an area constraint from the .area file. */
+ stringptr = readline(inputline, areafile, areafilename);
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ area = -1.0f; /* No constraint on this triangle. */
+ } else {
+ area = (tREAL) strtod(stringptr, &stringptr);
+ }
+#endif /* not TRILIBRARY */
+ setareabound(triangleloop, area);
+ }
+
+ /* Set the triangle's vertices. */
+ triangleloop.orient = 0;
+ setorg(triangleloop, getvertex(m, b, corner[0]));
+ setdest(triangleloop, getvertex(m, b, corner[1]));
+ setapex(triangleloop, getvertex(m, b, corner[2]));
+ /* Try linking the triangle to others that share these vertices. */
+ for (triangleloop.orient = 0; triangleloop.orient < 3;
+ triangleloop.orient++) {
+ /* Take the number for the origin of triangleloop. */
+ aroundvertex = corner[triangleloop.orient];
+ /* Look for other triangles having this vertex. */
+ nexttri = vertexarray[aroundvertex - b->firstnumber];
+ /* Link the current triangle to the next one in the stack. */
+ triangleloop.tri[6 + triangleloop.orient] = nexttri;
+ /* Push the current triangle onto the stack. */
+ vertexarray[aroundvertex - b->firstnumber] = encode(triangleloop);
+ decode(nexttri, checktri);
+ if (checktri.tri != m->dummytri) {
+ dest(triangleloop, tdest);
+ apex(triangleloop, tapex);
+ /* Look for other triangles that share an edge. */
+ do {
+ dest(checktri, checkdest);
+ apex(checktri, checkapex);
+ if (tapex == checkdest) {
+ /* The two triangles share an edge; bond them together. */
+ lprev(triangleloop, triangleleft);
+ bond(triangleleft, checktri);
+ }
+ if (tdest == checkapex) {
+ /* The two triangles share an edge; bond them together. */
+ lprev(checktri, checkleft);
+ bond(triangleloop, checkleft);
+ }
+ /* Find the next triangle in the stack. */
+ nexttri = checktri.tri[6 + checktri.orient];
+ decode(nexttri, checktri);
+ } while (checktri.tri != m->dummytri);
+ }
+ }
+ triangleloop.tri = triangletraverse(m);
+ elementnumber++;
+ }
+
+#ifdef TRILIBRARY
+ vertexindex = 0;
+#else /* not TRILIBRARY */
+ fclose(elefile);
+ if (b->vararea) {
+ fclose(areafile);
+ }
+#endif /* not TRILIBRARY */
+
+ hullsize = 0; /* Prepare to count the boundary edges. */
+ if (b->poly) {
+ if (b->verbose) {
+ printf(" Marking segments in triangulation.\n");
+ }
+ /* Read the segments from the .poly file, and link them */
+ /* to their neighboring triangles. */
+ boundmarker = 0;
+ traversalinit(&m->subsegs);
+ subsegloop.ss = subsegtraverse(m);
+ segmentnumber = b->firstnumber;
+ while (subsegloop.ss != (subseg *) NULL) {
+#ifdef TRILIBRARY
+ end[0] = segmentlist[vertexindex++];
+ end[1] = segmentlist[vertexindex++];
+ if (segmentmarkers) {
+ boundmarker = segmentmarkerlist[segmentnumber - b->firstnumber];
+ }
+#else /* not TRILIBRARY */
+ /* Read the endpoints of each segment, and possibly a boundary marker. */
+ stringptr = readline(inputline, polyfile, b->inpolyfilename);
+ /* Skip the first (segment number) field. */
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ printf("Error: Segment %ld has no endpoints in %s.\n", segmentnumber,
+ polyfilename);
+ triexit(1);
+ } else {
+ end[0] = (int) strtol(stringptr, &stringptr, 0);
+ }
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ printf("Error: Segment %ld is missing its second endpoint in %s.\n",
+ segmentnumber, polyfilename);
+ triexit(1);
+ } else {
+ end[1] = (int) strtol(stringptr, &stringptr, 0);
+ }
+ if (segmentmarkers) {
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ boundmarker = 0;
+ } else {
+ boundmarker = (int) strtol(stringptr, &stringptr, 0);
+ }
+ }
+#endif /* not TRILIBRARY */
+ for (j = 0; j < 2; j++) {
+ if ((end[j] < b->firstnumber) ||
+ (end[j] >= b->firstnumber + m->invertices)) {
+ printf("Error: Segment %ld has an invalid vertex index.\n",
+ segmentnumber);
+ triexit(1);
+ }
+ }
+
+ /* set the subsegment's vertices. */
+ subsegloop.ssorient = 0;
+ segmentorg = getvertex(m, b, end[0]);
+ segmentdest = getvertex(m, b, end[1]);
+ setsorg(subsegloop, segmentorg);
+ setsdest(subsegloop, segmentdest);
+ setsegorg(subsegloop, segmentorg);
+ setsegdest(subsegloop, segmentdest);
+ setmark(subsegloop, boundmarker);
+ /* Try linking the subsegment to triangles that share these vertices. */
+ for (subsegloop.ssorient = 0; subsegloop.ssorient < 2;
+ subsegloop.ssorient++) {
+ /* Take the number for the destination of subsegloop. */
+ aroundvertex = end[1 - subsegloop.ssorient];
+ /* Look for triangles having this vertex. */
+ prevlink = &vertexarray[aroundvertex - b->firstnumber];
+ nexttri = vertexarray[aroundvertex - b->firstnumber];
+ decode(nexttri, checktri);
+ sorg(subsegloop, shorg);
+ notfound = 1;
+ /* Look for triangles having this edge. Note that I'm only */
+ /* comparing each triangle's destination with the subsegment; */
+ /* each triangle's apex is handled through a different vertex. */
+ /* Because each triangle appears on three vertices' lists, each */
+ /* occurrence of a triangle on a list can (and does) represent */
+ /* an edge. In this way, most edges are represented twice, and */
+ /* every triangle-subsegment bond is represented once. */
+ while (notfound && (checktri.tri != m->dummytri)) {
+ dest(checktri, checkdest);
+ if (shorg == checkdest) {
+ /* We have a match. Remove this triangle from the list. */
+ *prevlink = checktri.tri[6 + checktri.orient];
+ /* Bond the subsegment to the triangle. */
+ tsbond(checktri, subsegloop);
+ /* Check if this is a boundary edge. */
+ sym(checktri, checkneighbor);
+ if (checkneighbor.tri == m->dummytri) {
+ /* The next line doesn't insert a subsegment (because there's */
+ /* already one there), but it sets the boundary markers of */
+ /* the existing subsegment and its vertices. */
+ insertsubseg(m, b, &checktri, 1);
+ hullsize++;
+ }
+ notfound = 0;
+ }
+ /* Find the next triangle in the stack. */
+ prevlink = &checktri.tri[6 + checktri.orient];
+ nexttri = checktri.tri[6 + checktri.orient];
+ decode(nexttri, checktri);
+ }
+ }
+ subsegloop.ss = subsegtraverse(m);
+ segmentnumber++;
+ }
+ }
+
+ /* Mark the remaining edges as not being attached to any subsegment. */
+ /* Also, count the (yet uncounted) boundary edges. */
+ for (i = 0; i < m->vertices.items; i++) {
+ /* Search the stack of triangles adjacent to a vertex. */
+ nexttri = vertexarray[i];
+ decode(nexttri, checktri);
+ while (checktri.tri != m->dummytri) {
+ /* Find the next triangle in the stack before this */
+ /* information gets overwritten. */
+ nexttri = checktri.tri[6 + checktri.orient];
+ /* No adjacent subsegment. (This overwrites the stack info.) */
+ tsdissolve(checktri);
+ sym(checktri, checkneighbor);
+ if (checkneighbor.tri == m->dummytri) {
+ insertsubseg(m, b, &checktri, 1);
+ hullsize++;
+ }
+ decode(nexttri, checktri);
+ }
+ }
+
+ trifree((VOID *) vertexarray);
+ return hullsize;
+}
+
+#endif /* not CDT_ONLY */
+
+/** **/
+/** **/
+/********* General mesh construction routines end here *********/
+
+/********* Segment insertion begins here *********/
+/** **/
+/** **/
+
+/*****************************************************************************/
+/* */
+/* finddirection() Find the first triangle on the path from one point */
+/* to another. */
+/* */
+/* Finds the triangle that intersects a line segment drawn from the */
+/* origin of `searchtri' to the point `searchpoint', and returns the result */
+/* in `searchtri'. The origin of `searchtri' does not change, even though */
+/* the triangle returned may differ from the one passed in. This routine */
+/* is used to find the direction to move in to get from one point to */
+/* another. */
+/* */
+/* The return value notes whether the destination or apex of the found */
+/* triangle is collinear with the two points in question. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+enum finddirectionresult finddirection(struct mesh *m, struct behavior *b,
+ struct otri *searchtri,
+ vertex searchpoint)
+#else /* not ANSI_DECLARATORS */
+enum finddirectionresult finddirection(m, b, searchtri, searchpoint)
+struct mesh *m;
+struct behavior *b;
+struct otri *searchtri;
+vertex searchpoint;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri checktri;
+ vertex startvertex;
+ vertex leftvertex, rightvertex;
+ tREAL leftccw, rightccw;
+ int leftflag, rightflag;
+ triangle ptr; /* Temporary variable used by onext() and oprev(). */
+
+ org(*searchtri, startvertex);
+ dest(*searchtri, rightvertex);
+ apex(*searchtri, leftvertex);
+ /* Is `searchpoint' to the left? */
+ leftccw = counterclockwise(m, b, searchpoint, startvertex, leftvertex);
+ leftflag = leftccw > 0.0f;
+ /* Is `searchpoint' to the right? */
+ rightccw = counterclockwise(m, b, startvertex, searchpoint, rightvertex);
+ rightflag = rightccw > 0.0f;
+ if (leftflag && rightflag) {
+ /* `searchtri' faces directly away from `searchpoint'. We could go left */
+ /* or right. Ask whether it's a triangle or a boundary on the left. */
+ onext(*searchtri, checktri);
+ if (checktri.tri == m->dummytri) {
+ leftflag = 0;
+ } else {
+ rightflag = 0;
+ }
+ }
+ while (leftflag) {
+ /* Turn left until satisfied. */
+ onextself(*searchtri);
+ if (searchtri->tri == m->dummytri) {
+ printf("Internal error in finddirection(): Unable to find a\n");
+ printf(" triangle leading from (%.12g, %.12g) to", startvertex[0],
+ startvertex[1]);
+ printf(" (%.12g, %.12g).\n", searchpoint[0], searchpoint[1]);
+ internalerror();
+ }
+ apex(*searchtri, leftvertex);
+ rightccw = leftccw;
+ leftccw = counterclockwise(m, b, searchpoint, startvertex, leftvertex);
+ leftflag = leftccw > 0.0f;
+ }
+ while (rightflag) {
+ /* Turn right until satisfied. */
+ oprevself(*searchtri);
+ if (searchtri->tri == m->dummytri) {
+ printf("Internal error in finddirection(): Unable to find a\n");
+ printf(" triangle leading from (%.12g, %.12g) to", startvertex[0],
+ startvertex[1]);
+ printf(" (%.12g, %.12g).\n", searchpoint[0], searchpoint[1]);
+ internalerror();
+ }
+ dest(*searchtri, rightvertex);
+ leftccw = rightccw;
+ rightccw = counterclockwise(m, b, startvertex, searchpoint, rightvertex);
+ rightflag = rightccw > 0.0f;
+ }
+ if (leftccw == 0.0f) {
+ return LEFTCOLLINEAR;
+ } else if (rightccw == 0.0f) {
+ return RIGHTCOLLINEAR;
+ } else {
+ return WITHIN;
+ }
+}
+
+/*****************************************************************************/
+/* */
+/* segmentintersection() Find the intersection of an existing segment */
+/* and a segment that is being inserted. Insert */
+/* a vertex at the intersection, splitting an */
+/* existing subsegment. */
+/* */
+/* The segment being inserted connects the apex of splittri to endpoint2. */
+/* splitsubseg is the subsegment being split, and MUST adjoin splittri. */
+/* Hence, endpoints of the subsegment being split are the origin and */
+/* destination of splittri. */
+/* */
+/* On completion, splittri is a handle having the newly inserted */
+/* intersection point as its origin, and endpoint1 as its destination. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void segmentintersection(struct mesh *m, struct behavior *b,
+ struct otri *splittri, struct osub *splitsubseg,
+ vertex endpoint2)
+#else /* not ANSI_DECLARATORS */
+void segmentintersection(m, b, splittri, splitsubseg, endpoint2)
+struct mesh *m;
+struct behavior *b;
+struct otri *splittri;
+struct osub *splitsubseg;
+vertex endpoint2;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct osub opposubseg;
+ vertex endpoint1;
+ vertex torg, tdest;
+ vertex leftvertex, rightvertex;
+ vertex newvertex;
+ enum insertvertexresult success;
+ enum finddirectionresult collinear;
+ tREAL ex, ey;
+ tREAL tx, ty;
+ tREAL etx, ety;
+ tREAL split, denom;
+ int i;
+ triangle ptr; /* Temporary variable used by onext(). */
+ subseg sptr; /* Temporary variable used by snext(). */
+
+ /* Find the other three segment endpoints. */
+ apex(*splittri, endpoint1);
+ org(*splittri, torg);
+ dest(*splittri, tdest);
+ /* Segment intersection formulae; see the Antonio reference. */
+ tx = tdest[0] - torg[0];
+ ty = tdest[1] - torg[1];
+ ex = endpoint2[0] - endpoint1[0];
+ ey = endpoint2[1] - endpoint1[1];
+ etx = torg[0] - endpoint2[0];
+ ety = torg[1] - endpoint2[1];
+ denom = ty * ex - tx * ey;
+ if (denom == 0.0f) {
+ printf("Internal error in segmentintersection():");
+ printf(" Attempt to find intersection of parallel segments.\n");
+ internalerror();
+ }
+ split = (ey * etx - ex * ety) / denom;
+ /* Create the new vertex. */
+ newvertex = (vertex) poolalloc(&m->vertices);
+ /* Interpolate its coordinate and attributes. */
+ for (i = 0; i < 2 + m->nextras; i++) {
+ newvertex[i] = torg[i] + split * (tdest[i] - torg[i]);
+ }
+ setvertexmark(newvertex, mark(*splitsubseg));
+ setvertextype(newvertex, INPUTVERTEX);
+ if (b->verbose > 1) {
+ printf(
+ " Splitting subsegment (%.12g, %.12g) (%.12g, %.12g) at (%.12g, %.12g).\n",
+ torg[0], torg[1], tdest[0], tdest[1], newvertex[0], newvertex[1]);
+ }
+ /* Insert the intersection vertex. This should always succeed. */
+ success = insertvertex(m, b, newvertex, splittri, splitsubseg, 0, 0);
+ if (success != SUCCESSFULVERTEX) {
+ printf("Internal error in segmentintersection():\n");
+ printf(" Failure to split a segment.\n");
+ internalerror();
+ }
+ /* Record a triangle whose origin is the new vertex. */
+ setvertex2tri(newvertex, encode(*splittri));
+ if (m->steinerleft > 0) {
+ m->steinerleft--;
+ }
+
+ /* Divide the segment into two, and correct the segment endpoints. */
+ ssymself(*splitsubseg);
+ spivot(*splitsubseg, opposubseg);
+ sdissolve(*splitsubseg);
+ sdissolve(opposubseg);
+ do {
+ setsegorg(*splitsubseg, newvertex);
+ snextself(*splitsubseg);
+ } while (splitsubseg->ss != m->dummysub);
+ do {
+ setsegorg(opposubseg, newvertex);
+ snextself(opposubseg);
+ } while (opposubseg.ss != m->dummysub);
+
+ /* Inserting the vertex may have caused edge flips. We wish to rediscover */
+ /* the edge connecting endpoint1 to the new intersection vertex. */
+ collinear = finddirection(m, b, splittri, endpoint1);
+ dest(*splittri, rightvertex);
+ apex(*splittri, leftvertex);
+ if ((leftvertex[0] == endpoint1[0]) && (leftvertex[1] == endpoint1[1])) {
+ onextself(*splittri);
+ } else if ((rightvertex[0] != endpoint1[0]) ||
+ (rightvertex[1] != endpoint1[1])) {
+ printf("Internal error in segmentintersection():\n");
+ printf(" Topological inconsistency after splitting a segment.\n");
+ internalerror();
+ }
+ /* `splittri' should have destination endpoint1. */
+}
+
+/*****************************************************************************/
+/* */
+/* scoutsegment() Scout the first triangle on the path from one endpoint */
+/* to another, and check for completion (reaching the */
+/* second endpoint), a collinear vertex, or the */
+/* intersection of two segments. */
+/* */
+/* Returns one if the entire segment is successfully inserted, and zero if */
+/* the job must be finished by conformingedge() or constrainededge(). */
+/* */
+/* If the first triangle on the path has the second endpoint as its */
+/* destination or apex, a subsegment is inserted and the job is done. */
+/* */
+/* If the first triangle on the path has a destination or apex that lies on */
+/* the segment, a subsegment is inserted connecting the first endpoint to */
+/* the collinear vertex, and the search is continued from the collinear */
+/* vertex. */
+/* */
+/* If the first triangle on the path has a subsegment opposite its origin, */
+/* then there is a segment that intersects the segment being inserted. */
+/* Their intersection vertex is inserted, splitting the subsegment. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+int scoutsegment(struct mesh *m, struct behavior *b, struct otri *searchtri,
+ vertex endpoint2, int newmark)
+#else /* not ANSI_DECLARATORS */
+int scoutsegment(m, b, searchtri, endpoint2, newmark)
+struct mesh *m;
+struct behavior *b;
+struct otri *searchtri;
+vertex endpoint2;
+int newmark;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri crosstri;
+ struct osub crosssubseg;
+ vertex leftvertex, rightvertex;
+ enum finddirectionresult collinear;
+ subseg sptr; /* Temporary variable used by tspivot(). */
+
+ collinear = finddirection(m, b, searchtri, endpoint2);
+ dest(*searchtri, rightvertex);
+ apex(*searchtri, leftvertex);
+ if (((leftvertex[0] == endpoint2[0]) && (leftvertex[1] == endpoint2[1])) ||
+ ((rightvertex[0] == endpoint2[0]) && (rightvertex[1] == endpoint2[1]))) {
+ /* The segment is already an edge in the mesh. */
+ if ((leftvertex[0] == endpoint2[0]) && (leftvertex[1] == endpoint2[1])) {
+ lprevself(*searchtri);
+ }
+ /* Insert a subsegment, if there isn't already one there. */
+ insertsubseg(m, b, searchtri, newmark);
+ return 1;
+ } else if (collinear == LEFTCOLLINEAR) {
+ /* We've collided with a vertex between the segment's endpoints. */
+ /* Make the collinear vertex be the triangle's origin. */
+ lprevself(*searchtri);
+ insertsubseg(m, b, searchtri, newmark);
+ /* Insert the remainder of the segment. */
+ return scoutsegment(m, b, searchtri, endpoint2, newmark);
+ } else if (collinear == RIGHTCOLLINEAR) {
+ /* We've collided with a vertex between the segment's endpoints. */
+ insertsubseg(m, b, searchtri, newmark);
+ /* Make the collinear vertex be the triangle's origin. */
+ lnextself(*searchtri);
+ /* Insert the remainder of the segment. */
+ return scoutsegment(m, b, searchtri, endpoint2, newmark);
+ } else {
+ lnext(*searchtri, crosstri);
+ tspivot(crosstri, crosssubseg);
+ /* Check for a crossing segment. */
+ if (crosssubseg.ss == m->dummysub) {
+ return 0;
+ } else {
+ /* Insert a vertex at the intersection. */
+ segmentintersection(m, b, &crosstri, &crosssubseg, endpoint2);
+ otricopy(crosstri, *searchtri);
+ insertsubseg(m, b, searchtri, newmark);
+ /* Insert the remainder of the segment. */
+ return scoutsegment(m, b, searchtri, endpoint2, newmark);
+ }
+ }
+}
+
+/*****************************************************************************/
+/* */
+/* conformingedge() Force a segment into a conforming Delaunay */
+/* triangulation by inserting a vertex at its midpoint, */
+/* and recursively forcing in the two half-segments if */
+/* necessary. */
+/* */
+/* Generates a sequence of subsegments connecting `endpoint1' to */
+/* `endpoint2'. `newmark' is the boundary marker of the segment, = vec3ed */
+/* to each new splitting vertex and subsegment. */
+/* */
+/* Note that conformingedge() does not always maintain the conforming */
+/* Delaunay property. Once inserted, segments are locked into place; */
+/* vertices inserted later (to force other segments in) may render these */
+/* fixed segments non-Delaunay. The conforming Delaunay property will be */
+/* restored by enforcequality() by splitting encroached subsegments. */
+/* */
+/*****************************************************************************/
+
+#ifndef REDUCED
+#ifndef CDT_ONLY
+
+#ifdef ANSI_DECLARATORS
+void conformingedge(struct mesh *m, struct behavior *b,
+ vertex endpoint1, vertex endpoint2, int newmark)
+#else /* not ANSI_DECLARATORS */
+void conformingedge(m, b, endpoint1, endpoint2, newmark)
+struct mesh *m;
+struct behavior *b;
+vertex endpoint1;
+vertex endpoint2;
+int newmark;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri searchtri1, searchtri2;
+ struct osub brokensubseg;
+ vertex newvertex;
+ vertex midvertex1, midvertex2;
+ enum insertvertexresult success;
+ int i;
+ subseg sptr; /* Temporary variable used by tspivot(). */
+
+ if (b->verbose > 2) {
+ printf("Forcing segment into triangulation by recursive splitting:\n");
+ printf(" (%.12g, %.12g) (%.12g, %.12g)\n", endpoint1[0], endpoint1[1],
+ endpoint2[0], endpoint2[1]);
+ }
+ /* Create a new vertex to insert in the middle of the segment. */
+ newvertex = (vertex) poolalloc(&m->vertices);
+ /* Interpolate coordinates and attributes. */
+ for (i = 0; i < 2 + m->nextras; i++) {
+ newvertex[i] = 0.5 * (endpoint1[i] + endpoint2[i]);
+ }
+ setvertexmark(newvertex, newmark);
+ setvertextype(newvertex, SEGMENTVERTEX);
+ /* No known triangle to search from. */
+ searchtri1.tri = m->dummytri;
+ /* Attempt to insert the new vertex. */
+ success = insertvertex(m, b, newvertex, &searchtri1, (struct osub *) NULL,
+ 0, 0);
+ if (success == DUPLICATEVERTEX) {
+ if (b->verbose > 2) {
+ printf(" Segment intersects existing vertex (%.12g, %.12g).\n",
+ newvertex[0], newvertex[1]);
+ }
+ /* Use the vertex that's already there. */
+ vertexdealloc(m, newvertex);
+ org(searchtri1, newvertex);
+ } else {
+ if (success == VIOLATINGVERTEX) {
+ if (b->verbose > 2) {
+ printf(" Two segments intersect at (%.12g, %.12g).\n",
+ newvertex[0], newvertex[1]);
+ }
+ /* By fluke, we've landed right on another segment. Split it. */
+ tspivot(searchtri1, brokensubseg);
+ success = insertvertex(m, b, newvertex, &searchtri1, &brokensubseg,
+ 0, 0);
+ if (success != SUCCESSFULVERTEX) {
+ printf("Internal error in conformingedge():\n");
+ printf(" Failure to split a segment.\n");
+ internalerror();
+ }
+ }
+ /* The vertex has been inserted successfully. */
+ if (m->steinerleft > 0) {
+ m->steinerleft--;
+ }
+ }
+ otricopy(searchtri1, searchtri2);
+ /* `searchtri1' and `searchtri2' are fastened at their origins to */
+ /* `newvertex', and will be directed toward `endpoint1' and `endpoint2' */
+ /* respectively. First, we must get `searchtri2' out of the way so it */
+ /* won't be invalidated during the insertion of the first half of the */
+ /* segment. */
+ finddirection(m, b, &searchtri2, endpoint2);
+ if (!scoutsegment(m, b, &searchtri1, endpoint1, newmark)) {
+ /* The origin of searchtri1 may have changed if a collision with an */
+ /* intervening vertex on the segment occurred. */
+ org(searchtri1, midvertex1);
+ conformingedge(m, b, midvertex1, endpoint1, newmark);
+ }
+ if (!scoutsegment(m, b, &searchtri2, endpoint2, newmark)) {
+ /* The origin of searchtri2 may have changed if a collision with an */
+ /* intervening vertex on the segment occurred. */
+ org(searchtri2, midvertex2);
+ conformingedge(m, b, midvertex2, endpoint2, newmark);
+ }
+}
+
+#endif /* not CDT_ONLY */
+#endif /* not REDUCED */
+
+/*****************************************************************************/
+/* */
+/* delaunayfixup() Enforce the Delaunay condition at an edge, fanning out */
+/* recursively from an existing vertex. Pay special */
+/* attention to stacking inverted triangles. */
+/* */
+/* This is a support routine for inserting segments into a constrained */
+/* Delaunay triangulation. */
+/* */
+/* The origin of fixuptri is treated as if it has just been inserted, and */
+/* the local Delaunay condition needs to be enforced. It is only enforced */
+/* in one sector, however, that being the angular range defined by */
+/* fixuptri. */
+/* */
+/* This routine also needs to make decisions regarding the "stacking" of */
+/* triangles. (Read the description of constrainededge() below before */
+/* reading on here, so you understand the algorithm.) If the position of */
+/* the new vertex (the origin of fixuptri) indicates that the vertex before */
+/* it on the polygon is a reflex vertex, then "stack" the triangle by */
+/* doing nothing. (fixuptri is an inverted triangle, which is how stacked */
+/* triangles are identified.) */
+/* */
+/* Otherwise, check whether the vertex before that was a reflex vertex. */
+/* If so, perform an edge flip, thereby eliminating an inverted triangle */
+/* (popping it off the stack). The edge flip may result in the creation */
+/* of a new inverted triangle, depending on whether or not the new vertex */
+/* is visible to the vertex three edges behind on the polygon. */
+/* */
+/* If neither of the two vertices behind the new vertex are reflex */
+/* vertices, fixuptri and fartri, the triangle opposite it, are not */
+/* inverted; hence, ensure that the edge between them is locally Delaunay. */
+/* */
+/* `leftside' indicates whether or not fixuptri is to the left of the */
+/* segment being inserted. (Imagine that the segment is pointing up from */
+/* endpoint1 to endpoint2.) */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void delaunayfixup(struct mesh *m, struct behavior *b,
+ struct otri *fixuptri, int leftside)
+#else /* not ANSI_DECLARATORS */
+void delaunayfixup(m, b, fixuptri, leftside)
+struct mesh *m;
+struct behavior *b;
+struct otri *fixuptri;
+int leftside;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri neartri;
+ struct otri fartri;
+ struct osub faredge;
+ vertex nearvertex, leftvertex, rightvertex, farvertex;
+ triangle ptr; /* Temporary variable used by sym(). */
+ subseg sptr; /* Temporary variable used by tspivot(). */
+
+ lnext(*fixuptri, neartri);
+ sym(neartri, fartri);
+ /* Check if the edge opposite the origin of fixuptri can be flipped. */
+ if (fartri.tri == m->dummytri) {
+ return;
+ }
+ tspivot(neartri, faredge);
+ if (faredge.ss != m->dummysub) {
+ return;
+ }
+ /* Find all the relevant vertices. */
+ apex(neartri, nearvertex);
+ org(neartri, leftvertex);
+ dest(neartri, rightvertex);
+ apex(fartri, farvertex);
+ /* Check whether the previous polygon vertex is a reflex vertex. */
+ if (leftside) {
+ if (counterclockwise(m, b, nearvertex, leftvertex, farvertex) <= 0.0f) {
+ /* leftvertex is a reflex vertex too. Nothing can */
+ /* be done until a convex section is found. */
+ return;
+ }
+ } else {
+ if (counterclockwise(m, b, farvertex, rightvertex, nearvertex) <= 0.0f) {
+ /* rightvertex is a reflex vertex too. Nothing can */
+ /* be done until a convex section is found. */
+ return;
+ }
+ }
+ if (counterclockwise(m, b, rightvertex, leftvertex, farvertex) > 0.0f) {
+ /* fartri is not an inverted triangle, and farvertex is not a reflex */
+ /* vertex. As there are no reflex vertices, fixuptri isn't an */
+ /* inverted triangle, either. Hence, test the edge between the */
+ /* triangles to ensure it is locally Delaunay. */
+ if (incircle(m, b, leftvertex, farvertex, rightvertex, nearvertex) <=
+ 0.0f) {
+ return;
+ }
+ /* Not locally Delaunay; go on to an edge flip. */
+ } /* else fartri is inverted; remove it from the stack by flipping. */
+ flip(m, b, &neartri);
+ lprevself(*fixuptri); /* Restore the origin of fixuptri after the flip. */
+ /* Recursively process the two triangles that result from the flip. */
+ delaunayfixup(m, b, fixuptri, leftside);
+ delaunayfixup(m, b, &fartri, leftside);
+}
+
+/*****************************************************************************/
+/* */
+/* constrainededge() Force a segment into a constrained Delaunay */
+/* triangulation by deleting the triangles it */
+/* intersects, and triangulating the polygons that */
+/* form on each side of it. */
+/* */
+/* Generates a single subsegment connecting `endpoint1' to `endpoint2'. */
+/* The triangle `starttri' has `endpoint1' as its origin. `newmark' is the */
+/* boundary marker of the segment. */
+/* */
+/* To insert a segment, every triangle whose interior intersects the */
+/* segment is deleted. The union of these deleted triangles is a polygon */
+/* (which is not necessarily monotone, but is close enough), which is */
+/* divided into two polygons by the new segment. This routine's task is */
+/* to generate the Delaunay triangulation of these two polygons. */
+/* */
+/* You might think of this routine's behavior as a two-step process. The */
+/* first step is to walk from endpoint1 to endpoint2, flipping each edge */
+/* encountered. This step creates a fan of edges connected to endpoint1, */
+/* including the desired edge to endpoint2. The second step enforces the */
+/* Delaunay condition on each side of the segment in an incremental manner: */
+/* proceeding along the polygon from endpoint1 to endpoint2 (this is done */
+/* independently on each side of the segment), each vertex is "enforced" */
+/* as if it had just been inserted, but affecting only the previous */
+/* vertices. The result is the same as if the vertices had been inserted */
+/* in the order they appear on the polygon, so the result is Delaunay. */
+/* */
+/* In truth, constrainededge() interleaves these two steps. The procedure */
+/* walks from endpoint1 to endpoint2, and each time an edge is encountered */
+/* and flipped, the newly exposed vertex (at the far end of the flipped */
+/* edge) is "enforced" upon the previously flipped edges, usually affecting */
+/* only one side of the polygon (depending upon which side of the segment */
+/* the vertex falls on). */
+/* */
+/* The algorithm is complicated by the need to handle polygons that are not */
+/* convex. Although the polygon is not necessarily monotone, it can be */
+/* triangulated in a manner similar to the stack-based algorithms for */
+/* monotone polygons. For each reflex vertex (local concavity) of the */
+/* polygon, there will be an inverted triangle formed by one of the edge */
+/* flips. (An inverted triangle is one with negative area - that is, its */
+/* vertices are arranged in clockwise order - and is best thought of as a */
+/* wrinkle in the fabric of the mesh.) Each inverted triangle can be */
+/* thought of as a reflex vertex pushed on the stack, waiting to be fixed */
+/* later. */
+/* */
+/* A reflex vertex is popped from the stack when a vertex is inserted that */
+/* is visible to the reflex vertex. (However, if the vertex behind the */
+/* reflex vertex is not visible to the reflex vertex, a new inverted */
+/* triangle will take its place on the stack.) These details are handled */
+/* by the delaunayfixup() routine above. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void constrainededge(struct mesh *m, struct behavior *b,
+ struct otri *starttri, vertex endpoint2, int newmark)
+#else /* not ANSI_DECLARATORS */
+void constrainededge(m, b, starttri, endpoint2, newmark)
+struct mesh *m;
+struct behavior *b;
+struct otri *starttri;
+vertex endpoint2;
+int newmark;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri fixuptri, fixuptri2;
+ struct osub crosssubseg;
+ vertex endpoint1;
+ vertex farvertex;
+ tREAL area;
+ int collision;
+ int done;
+ triangle ptr; /* Temporary variable used by sym() and oprev(). */
+ subseg sptr; /* Temporary variable used by tspivot(). */
+
+ org(*starttri, endpoint1);
+ lnext(*starttri, fixuptri);
+ flip(m, b, &fixuptri);
+ /* `collision' indicates whether we have found a vertex directly */
+ /* between endpoint1 and endpoint2. */
+ collision = 0;
+ done = 0;
+ do {
+ org(fixuptri, farvertex);
+ /* `farvertex' is the extreme point of the polygon we are "digging" */
+ /* to get from endpoint1 to endpoint2. */
+ if ((farvertex[0] == endpoint2[0]) && (farvertex[1] == endpoint2[1])) {
+ oprev(fixuptri, fixuptri2);
+ /* Enforce the Delaunay condition around endpoint2. */
+ delaunayfixup(m, b, &fixuptri, 0);
+ delaunayfixup(m, b, &fixuptri2, 1);
+ done = 1;
+ } else {
+ /* Check whether farvertex is to the left or right of the segment */
+ /* being inserted, to decide which edge of fixuptri to dig */
+ /* through next. */
+ area = counterclockwise(m, b, endpoint1, endpoint2, farvertex);
+ if (area == 0.0f) {
+ /* We've collided with a vertex between endpoint1 and endpoint2. */
+ collision = 1;
+ oprev(fixuptri, fixuptri2);
+ /* Enforce the Delaunay condition around farvertex. */
+ delaunayfixup(m, b, &fixuptri, 0);
+ delaunayfixup(m, b, &fixuptri2, 1);
+ done = 1;
+ } else {
+ if (area > 0.0f) { /* farvertex is to the left of the segment. */
+ oprev(fixuptri, fixuptri2);
+ /* Enforce the Delaunay condition around farvertex, on the */
+ /* left side of the segment only. */
+ delaunayfixup(m, b, &fixuptri2, 1);
+ /* Flip the edge that crosses the segment. After the edge is */
+ /* flipped, one of its endpoints is the fan vertex, and the */
+ /* destination of fixuptri is the fan vertex. */
+ lprevself(fixuptri);
+ } else { /* farvertex is to the right of the segment. */
+ delaunayfixup(m, b, &fixuptri, 0);
+ /* Flip the edge that crosses the segment. After the edge is */
+ /* flipped, one of its endpoints is the fan vertex, and the */
+ /* destination of fixuptri is the fan vertex. */
+ oprevself(fixuptri);
+ }
+ /* Check for two intersecting segments. */
+ tspivot(fixuptri, crosssubseg);
+ if (crosssubseg.ss == m->dummysub) {
+ flip(m, b, &fixuptri); /* May create inverted triangle at left. */
+ } else {
+ /* We've collided with a segment between endpoint1 and endpoint2. */
+ collision = 1;
+ /* Insert a vertex at the intersection. */
+ segmentintersection(m, b, &fixuptri, &crosssubseg, endpoint2);
+ done = 1;
+ }
+ }
+ }
+ } while (!done);
+ /* Insert a subsegment to make the segment permanent. */
+ insertsubseg(m, b, &fixuptri, newmark);
+ /* If there was a collision with an interceding vertex, install another */
+ /* segment connecting that vertex with endpoint2. */
+ if (collision) {
+ /* Insert the remainder of the segment. */
+ if (!scoutsegment(m, b, &fixuptri, endpoint2, newmark)) {
+ constrainededge(m, b, &fixuptri, endpoint2, newmark);
+ }
+ }
+}
+
+/*****************************************************************************/
+/* */
+/* insertsegment() Insert a PSLG segment into a triangulation. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void insertsegment(struct mesh *m, struct behavior *b,
+ vertex endpoint1, vertex endpoint2, int newmark)
+#else /* not ANSI_DECLARATORS */
+void insertsegment(m, b, endpoint1, endpoint2, newmark)
+struct mesh *m;
+struct behavior *b;
+vertex endpoint1;
+vertex endpoint2;
+int newmark;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri searchtri1, searchtri2;
+ triangle encodedtri;
+ vertex checkvertex;
+ triangle ptr; /* Temporary variable used by sym(). */
+
+ if (b->verbose > 1) {
+ printf(" Connecting (%.12g, %.12g) to (%.12g, %.12g).\n",
+ endpoint1[0], endpoint1[1], endpoint2[0], endpoint2[1]);
+ }
+
+ /* Find a triangle whose origin is the segment's first endpoint. */
+ checkvertex = (vertex) NULL;
+ encodedtri = vertex2tri(endpoint1);
+ if (encodedtri != (triangle) NULL) {
+ decode(encodedtri, searchtri1);
+ org(searchtri1, checkvertex);
+ }
+ if (checkvertex != endpoint1) {
+ /* Find a boundary triangle to search from. */
+ searchtri1.tri = m->dummytri;
+ searchtri1.orient = 0;
+ symself(searchtri1);
+ /* Search for the segment's first endpoint by point location. */
+ if (locate(m, b, endpoint1, &searchtri1) != ONVERTEX) {
+ printf(
+ "Internal error in insertsegment(): Unable to locate PSLG vertex\n");
+ printf(" (%.12g, %.12g) in triangulation.\n",
+ endpoint1[0], endpoint1[1]);
+ internalerror();
+ }
+ }
+ /* Remember this triangle to improve subsequent point location. */
+ otricopy(searchtri1, m->recenttri);
+ /* Scout the beginnings of a path from the first endpoint */
+ /* toward the second. */
+ if (scoutsegment(m, b, &searchtri1, endpoint2, newmark)) {
+ /* The segment was easily inserted. */
+ return;
+ }
+ /* The first endpoint may have changed if a collision with an intervening */
+ /* vertex on the segment occurred. */
+ org(searchtri1, endpoint1);
+
+ /* Find a triangle whose origin is the segment's second endpoint. */
+ checkvertex = (vertex) NULL;
+ encodedtri = vertex2tri(endpoint2);
+ if (encodedtri != (triangle) NULL) {
+ decode(encodedtri, searchtri2);
+ org(searchtri2, checkvertex);
+ }
+ if (checkvertex != endpoint2) {
+ /* Find a boundary triangle to search from. */
+ searchtri2.tri = m->dummytri;
+ searchtri2.orient = 0;
+ symself(searchtri2);
+ /* Search for the segment's second endpoint by point location. */
+ if (locate(m, b, endpoint2, &searchtri2) != ONVERTEX) {
+ printf(
+ "Internal error in insertsegment(): Unable to locate PSLG vertex\n");
+ printf(" (%.12g, %.12g) in triangulation.\n",
+ endpoint2[0], endpoint2[1]);
+ internalerror();
+ }
+ }
+ /* Remember this triangle to improve subsequent point location. */
+ otricopy(searchtri2, m->recenttri);
+ /* Scout the beginnings of a path from the second endpoint */
+ /* toward the first. */
+ if (scoutsegment(m, b, &searchtri2, endpoint1, newmark)) {
+ /* The segment was easily inserted. */
+ return;
+ }
+ /* The second endpoint may have changed if a collision with an intervening */
+ /* vertex on the segment occurred. */
+ org(searchtri2, endpoint2);
+
+#ifndef REDUCED
+#ifndef CDT_ONLY
+ if (b->splitseg) {
+ /* Insert vertices to force the segment into the triangulation. */
+ conformingedge(m, b, endpoint1, endpoint2, newmark);
+ } else {
+#endif /* not CDT_ONLY */
+#endif /* not REDUCED */
+ /* Insert the segment directly into the triangulation. */
+ constrainededge(m, b, &searchtri1, endpoint2, newmark);
+#ifndef REDUCED
+#ifndef CDT_ONLY
+ }
+#endif /* not CDT_ONLY */
+#endif /* not REDUCED */
+}
+
+/*****************************************************************************/
+/* */
+/* markhull() Cover the convex hull of a triangulation with subsegments. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void markhull(struct mesh *m, struct behavior *b)
+#else /* not ANSI_DECLARATORS */
+void markhull(m, b)
+struct mesh *m;
+struct behavior *b;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri hulltri;
+ struct otri nexttri;
+ struct otri starttri;
+ triangle ptr; /* Temporary variable used by sym() and oprev(). */
+
+ /* Find a triangle handle on the hull. */
+ hulltri.tri = m->dummytri;
+ hulltri.orient = 0;
+ symself(hulltri);
+ /* Remember where we started so we know when to stop. */
+ otricopy(hulltri, starttri);
+ /* Go once counterclockwise around the convex hull. */
+ do {
+ /* Create a subsegment if there isn't already one here. */
+ insertsubseg(m, b, &hulltri, 1);
+ /* To find the next hull edge, go clockwise around the next vertex. */
+ lnextself(hulltri);
+ oprev(hulltri, nexttri);
+ while (nexttri.tri != m->dummytri) {
+ otricopy(nexttri, hulltri);
+ oprev(hulltri, nexttri);
+ }
+ } while (!otriequal(hulltri, starttri));
+}
+
+/*****************************************************************************/
+/* */
+/* formskeleton() Create the segments of a triangulation, including PSLG */
+/* segments and edges on the convex hull. */
+/* */
+/* The PSLG segments are read from a .poly file. The return value is the */
+/* number of segments in the file. */
+/* */
+/*****************************************************************************/
+
+#ifdef TRILIBRARY
+
+#ifdef ANSI_DECLARATORS
+void formskeleton(struct mesh *m, struct behavior *b, int *segmentlist,
+ int *segmentmarkerlist, int numberofsegments)
+#else /* not ANSI_DECLARATORS */
+void formskeleton(m, b, segmentlist, segmentmarkerlist, numberofsegments)
+struct mesh *m;
+struct behavior *b;
+int *segmentlist;
+int *segmentmarkerlist;
+int numberofsegments;
+#endif /* not ANSI_DECLARATORS */
+
+#else /* not TRILIBRARY */
+
+#ifdef ANSI_DECLARATORS
+void formskeleton(struct mesh *m, struct behavior *b,
+ FILE *polyfile, char *polyfilename)
+#else /* not ANSI_DECLARATORS */
+void formskeleton(m, b, polyfile, polyfilename)
+struct mesh *m;
+struct behavior *b;
+FILE *polyfile;
+char *polyfilename;
+#endif /* not ANSI_DECLARATORS */
+
+#endif /* not TRILIBRARY */
+
+{
+#ifdef TRILIBRARY
+ char polyfilename[6];
+ int index;
+#else /* not TRILIBRARY */
+ char inputline[INPUTLINESIZE];
+ char *stringptr;
+#endif /* not TRILIBRARY */
+ vertex endpoint1, endpoint2;
+ int segmentmarkers;
+ int end1, end2;
+ int boundmarker;
+ int i;
+
+ if (b->poly) {
+ if (!b->quiet) {
+ printf("Recovering segments in Delaunay triangulation.\n");
+ }
+#ifdef TRILIBRARY
+ strcpy(polyfilename, "input");
+ m->insegments = numberofsegments;
+ segmentmarkers = segmentmarkerlist != (int *) NULL;
+ index = 0;
+#else /* not TRILIBRARY */
+ /* Read the segments from a .poly file. */
+ /* Read number of segments and number of boundary markers. */
+ stringptr = readline(inputline, polyfile, polyfilename);
+ m->insegments = (int) strtol(stringptr, &stringptr, 0);
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ segmentmarkers = 0;
+ } else {
+ segmentmarkers = (int) strtol(stringptr, &stringptr, 0);
+ }
+#endif /* not TRILIBRARY */
+ /* If the input vertices are collinear, there is no triangulation, */
+ /* so don't try to insert segments. */
+ if (m->triangles.items == 0) {
+ return;
+ }
+
+ /* If segments are to be inserted, compute a mapping */
+ /* from vertices to triangles. */
+ if (m->insegments > 0) {
+ makevertexmap(m, b);
+ if (b->verbose) {
+ printf(" Recovering PSLG segments.\n");
+ }
+ }
+
+ boundmarker = 0;
+ /* Read and insert the segments. */
+ for (i = 0; i < m->insegments; i++) {
+#ifdef TRILIBRARY
+ end1 = segmentlist[index++];
+ end2 = segmentlist[index++];
+ if (segmentmarkers) {
+ boundmarker = segmentmarkerlist[i];
+ }
+#else /* not TRILIBRARY */
+ stringptr = readline(inputline, polyfile, b->inpolyfilename);
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ printf("Error: Segment %d has no endpoints in %s.\n",
+ b->firstnumber + i, polyfilename);
+ triexit(1);
+ } else {
+ end1 = (int) strtol(stringptr, &stringptr, 0);
+ }
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ printf("Error: Segment %d is missing its second endpoint in %s.\n",
+ b->firstnumber + i, polyfilename);
+ triexit(1);
+ } else {
+ end2 = (int) strtol(stringptr, &stringptr, 0);
+ }
+ if (segmentmarkers) {
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ boundmarker = 0;
+ } else {
+ boundmarker = (int) strtol(stringptr, &stringptr, 0);
+ }
+ }
+#endif /* not TRILIBRARY */
+ if ((end1 < b->firstnumber) ||
+ (end1 >= b->firstnumber + m->invertices)) {
+ if (!b->quiet) {
+ printf("Warning: Invalid first endpoint of segment %d in %s.\n",
+ b->firstnumber + i, polyfilename);
+ }
+ } else if ((end2 < b->firstnumber) ||
+ (end2 >= b->firstnumber + m->invertices)) {
+ if (!b->quiet) {
+ printf("Warning: Invalid second endpoint of segment %d in %s.\n",
+ b->firstnumber + i, polyfilename);
+ }
+ } else {
+ /* Find the vertices numbered `end1' and `end2'. */
+ endpoint1 = getvertex(m, b, end1);
+ endpoint2 = getvertex(m, b, end2);
+ if ((endpoint1[0] == endpoint2[0]) && (endpoint1[1] == endpoint2[1])) {
+ if (!b->quiet) {
+ printf("Warning: Endpoints of segment %d are coincident in %s.\n",
+ b->firstnumber + i, polyfilename);
+ }
+ } else {
+ insertsegment(m, b, endpoint1, endpoint2, boundmarker);
+ }
+ }
+ }
+ } else {
+ m->insegments = 0;
+ }
+ if (b->convex || !b->poly) {
+ /* Enclose the convex hull with subsegments. */
+ if (b->verbose) {
+ printf(" Enclosing convex hull with segments.\n");
+ }
+ markhull(m, b);
+ }
+}
+
+/** **/
+/** **/
+/********* Segment insertion ends here *********/
+
+/********* Carving out holes and concavities begins here *********/
+/** **/
+/** **/
+
+/*****************************************************************************/
+/* */
+/* infecthull() Virally infect all of the triangles of the convex hull */
+/* that are not protected by subsegments. Where there are */
+/* subsegments, set boundary markers as appropriate. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void infecthull(struct mesh *m, struct behavior *b)
+#else /* not ANSI_DECLARATORS */
+void infecthull(m, b)
+struct mesh *m;
+struct behavior *b;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri hulltri;
+ struct otri nexttri;
+ struct otri starttri;
+ struct osub hullsubseg;
+ triangle **deadtriangle;
+ vertex horg, hdest;
+ triangle ptr; /* Temporary variable used by sym(). */
+ subseg sptr; /* Temporary variable used by tspivot(). */
+
+ if (b->verbose) {
+ printf(" Marking concavities (external triangles) for elimination.\n");
+ }
+ /* Find a triangle handle on the hull. */
+ hulltri.tri = m->dummytri;
+ hulltri.orient = 0;
+ symself(hulltri);
+ /* Remember where we started so we know when to stop. */
+ otricopy(hulltri, starttri);
+ /* Go once counterclockwise around the convex hull. */
+ do {
+ /* Ignore triangles that are already infected. */
+ if (!infected(hulltri)) {
+ /* Is the triangle protected by a subsegment? */
+ tspivot(hulltri, hullsubseg);
+ if (hullsubseg.ss == m->dummysub) {
+ /* The triangle is not protected; infect it. */
+ if (!infected(hulltri)) {
+ infect(hulltri);
+ deadtriangle = (triangle **) poolalloc(&m->viri);
+ *deadtriangle = hulltri.tri;
+ }
+ } else {
+ /* The triangle is protected; set boundary markers if appropriate. */
+ if (mark(hullsubseg) == 0) {
+ setmark(hullsubseg, 1);
+ org(hulltri, horg);
+ dest(hulltri, hdest);
+ if (vertexmark(horg) == 0) {
+ setvertexmark(horg, 1);
+ }
+ if (vertexmark(hdest) == 0) {
+ setvertexmark(hdest, 1);
+ }
+ }
+ }
+ }
+ /* To find the next hull edge, go clockwise around the next vertex. */
+ lnextself(hulltri);
+ oprev(hulltri, nexttri);
+ while (nexttri.tri != m->dummytri) {
+ otricopy(nexttri, hulltri);
+ oprev(hulltri, nexttri);
+ }
+ } while (!otriequal(hulltri, starttri));
+}
+
+/*****************************************************************************/
+/* */
+/* plague() Spread the virus from all infected triangles to any neighbors */
+/* not protected by subsegments. Delete all infected triangles. */
+/* */
+/* This is the procedure that actually creates holes and concavities. */
+/* */
+/* This procedure operates in two phases. The first phase identifies all */
+/* the triangles that will die, and marks them as infected. They are */
+/* marked to ensure that each triangle is added to the virus pool only */
+/* once, so the procedure will terminate. */
+/* */
+/* The second phase actually eliminates the infected triangles. It also */
+/* eliminates orphaned vertices. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void plague(struct mesh *m, struct behavior *b)
+#else /* not ANSI_DECLARATORS */
+void plague(m, b)
+struct mesh *m;
+struct behavior *b;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri testtri;
+ struct otri neighbor;
+ triangle **virusloop;
+ triangle **deadtriangle;
+ struct osub neighborsubseg;
+ vertex testvertex;
+ vertex norg, ndest;
+ vertex deadorg, deaddest, deadapex;
+ int killorg;
+ triangle ptr; /* Temporary variable used by sym() and onext(). */
+ subseg sptr; /* Temporary variable used by tspivot(). */
+
+ if (b->verbose) {
+ printf(" Marking neighbors of marked triangles.\n");
+ }
+ /* Loop through all the infected triangles, spreading the virus to */
+ /* their neighbors, then to their neighbors' neighbors. */
+ traversalinit(&m->viri);
+ virusloop = (triangle **) traverse(&m->viri);
+ while (virusloop != (triangle **) NULL) {
+ testtri.tri = *virusloop;
+ /* A triangle is marked as infected by messing with one of its pointers */
+ /* to subsegments, setting it to an illegal value. Hence, we have to */
+ /* temporarily uninfect this triangle so that we can examine its */
+ /* adjacent subsegments. */
+ uninfect(testtri);
+ if (b->verbose > 2) {
+ /* Assign the triangle an orientation for convenience in */
+ /* checking its vertices. */
+ testtri.orient = 0;
+ org(testtri, deadorg);
+ dest(testtri, deaddest);
+ apex(testtri, deadapex);
+ printf(" Checking (%.12g, %.12g) (%.12g, %.12g) (%.12g, %.12g)\n",
+ deadorg[0], deadorg[1], deaddest[0], deaddest[1],
+ deadapex[0], deadapex[1]);
+ }
+ /* Check each of the triangle's three neighbors. */
+ for (testtri.orient = 0; testtri.orient < 3; testtri.orient++) {
+ /* Find the neighbor. */
+ sym(testtri, neighbor);
+ /* Check for a subsegment between the triangle and its neighbor. */
+ tspivot(testtri, neighborsubseg);
+ /* Check if the neighbor is nonexistent or already infected. */
+ if ((neighbor.tri == m->dummytri) || infected(neighbor)) {
+ if (neighborsubseg.ss != m->dummysub) {
+ /* There is a subsegment separating the triangle from its */
+ /* neighbor, but both triangles are dying, so the subsegment */
+ /* dies too. */
+ subsegdealloc(m, neighborsubseg.ss);
+ if (neighbor.tri != m->dummytri) {
+ /* Make sure the subsegment doesn't get deallocated again */
+ /* later when the infected neighbor is visited. */
+ uninfect(neighbor);
+ tsdissolve(neighbor);
+ infect(neighbor);
+ }
+ }
+ } else { /* The neighbor exists and is not infected. */
+ if (neighborsubseg.ss == m->dummysub) {
+ /* There is no subsegment protecting the neighbor, so */
+ /* the neighbor becomes infected. */
+ if (b->verbose > 2) {
+ org(neighbor, deadorg);
+ dest(neighbor, deaddest);
+ apex(neighbor, deadapex);
+ printf(
+ " Marking (%.12g, %.12g) (%.12g, %.12g) (%.12g, %.12g)\n",
+ deadorg[0], deadorg[1], deaddest[0], deaddest[1],
+ deadapex[0], deadapex[1]);
+ }
+ infect(neighbor);
+ /* Ensure that the neighbor's neighbors will be infected. */
+ deadtriangle = (triangle **) poolalloc(&m->viri);
+ *deadtriangle = neighbor.tri;
+ } else { /* The neighbor is protected by a subsegment. */
+ /* Remove this triangle from the subsegment. */
+ stdissolve(neighborsubseg);
+ /* The subsegment becomes a boundary. Set markers accordingly. */
+ if (mark(neighborsubseg) == 0) {
+ setmark(neighborsubseg, 1);
+ }
+ org(neighbor, norg);
+ dest(neighbor, ndest);
+ if (vertexmark(norg) == 0) {
+ setvertexmark(norg, 1);
+ }
+ if (vertexmark(ndest) == 0) {
+ setvertexmark(ndest, 1);
+ }
+ }
+ }
+ }
+ /* Remark the triangle as infected, so it doesn't get added to the */
+ /* virus pool again. */
+ infect(testtri);
+ virusloop = (triangle **) traverse(&m->viri);
+ }
+
+ if (b->verbose) {
+ printf(" Deleting marked triangles.\n");
+ }
+
+ traversalinit(&m->viri);
+ virusloop = (triangle **) traverse(&m->viri);
+ while (virusloop != (triangle **) NULL) {
+ testtri.tri = *virusloop;
+
+ /* Check each of the three corners of the triangle for elimination. */
+ /* This is done by walking around each vertex, checking if it is */
+ /* still connected to at least one live triangle. */
+ for (testtri.orient = 0; testtri.orient < 3; testtri.orient++) {
+ org(testtri, testvertex);
+ /* Check if the vertex has already been tested. */
+ if (testvertex != (vertex) NULL) {
+ killorg = 1;
+ /* Mark the corner of the triangle as having been tested. */
+ setorg(testtri, NULL);
+ /* Walk counterclockwise about the vertex. */
+ onext(testtri, neighbor);
+ /* Stop upon reaching a boundary or the starting triangle. */
+ while ((neighbor.tri != m->dummytri) &&
+ (!otriequal(neighbor, testtri))) {
+ if (infected(neighbor)) {
+ /* Mark the corner of this triangle as having been tested. */
+ setorg(neighbor, NULL);
+ } else {
+ /* A live triangle. The vertex survives. */
+ killorg = 0;
+ }
+ /* Walk counterclockwise about the vertex. */
+ onextself(neighbor);
+ }
+ /* If we reached a boundary, we must walk clockwise as well. */
+ if (neighbor.tri == m->dummytri) {
+ /* Walk clockwise about the vertex. */
+ oprev(testtri, neighbor);
+ /* Stop upon reaching a boundary. */
+ while (neighbor.tri != m->dummytri) {
+ if (infected(neighbor)) {
+ /* Mark the corner of this triangle as having been tested. */
+ setorg(neighbor, NULL);
+ } else {
+ /* A live triangle. The vertex survives. */
+ killorg = 0;
+ }
+ /* Walk clockwise about the vertex. */
+ oprevself(neighbor);
+ }
+ }
+ if (killorg) {
+ if (b->verbose > 1) {
+ printf(" Deleting vertex (%.12g, %.12g)\n",
+ testvertex[0], testvertex[1]);
+ }
+ setvertextype(testvertex, UNDEADVERTEX);
+ m->undeads++;
+ }
+ }
+ }
+
+ /* Record changes in the number of boundary edges, and disconnect */
+ /* dead triangles from their neighbors. */
+ for (testtri.orient = 0; testtri.orient < 3; testtri.orient++) {
+ sym(testtri, neighbor);
+ if (neighbor.tri == m->dummytri) {
+ /* There is no neighboring triangle on this edge, so this edge */
+ /* is a boundary edge. This triangle is being deleted, so this */
+ /* boundary edge is deleted. */
+ m->hullsize--;
+ } else {
+ /* Disconnect the triangle from its neighbor. */
+ dissolve(neighbor);
+ /* There is a neighboring triangle on this edge, so this edge */
+ /* becomes a boundary edge when this triangle is deleted. */
+ m->hullsize++;
+ }
+ }
+ /* Return the dead triangle to the pool of triangles. */
+ triangledealloc(m, testtri.tri);
+ virusloop = (triangle **) traverse(&m->viri);
+ }
+ /* Empty the virus pool. */
+ poolrestart(&m->viri);
+}
+
+/*****************************************************************************/
+/* */
+/* regionplague() Spread regional attributes and/or area constraints */
+/* (from a .poly file) throughout the mesh. */
+/* */
+/* This procedure operates in two phases. The first phase spreads an */
+/* attribute and/or an area constraint through a (segment-bounded) region. */
+/* The triangles are marked to ensure that each triangle is added to the */
+/* virus pool only once, so the procedure will terminate. */
+/* */
+/* The second phase uninfects all infected triangles, returning them to */
+/* normal. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void regionplague(struct mesh *m, struct behavior *b,
+ tREAL attribute, tREAL area)
+#else /* not ANSI_DECLARATORS */
+void regionplague(m, b, attribute, area)
+struct mesh *m;
+struct behavior *b;
+tREAL attribute;
+tREAL area;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri testtri;
+ struct otri neighbor;
+ triangle **virusloop;
+ triangle **regiontri;
+ struct osub neighborsubseg;
+ vertex regionorg, regiondest, regionapex;
+ triangle ptr; /* Temporary variable used by sym() and onext(). */
+ subseg sptr; /* Temporary variable used by tspivot(). */
+
+ if (b->verbose > 1) {
+ printf(" Marking neighbors of marked triangles.\n");
+ }
+ /* Loop through all the infected triangles, spreading the attribute */
+ /* and/or area constraint to their neighbors, then to their neighbors' */
+ /* neighbors. */
+ traversalinit(&m->viri);
+ virusloop = (triangle **) traverse(&m->viri);
+ while (virusloop != (triangle **) NULL) {
+ testtri.tri = *virusloop;
+ /* A triangle is marked as infected by messing with one of its pointers */
+ /* to subsegments, setting it to an illegal value. Hence, we have to */
+ /* temporarily uninfect this triangle so that we can examine its */
+ /* adjacent subsegments. */
+ uninfect(testtri);
+ if (b->regionattrib) {
+ /* Set an attribute. */
+ setelemattribute(testtri, m->eextras, attribute);
+ }
+ if (b->vararea) {
+ /* Set an area constraint. */
+ setareabound(testtri, area);
+ }
+ if (b->verbose > 2) {
+ /* Assign the triangle an orientation for convenience in */
+ /* checking its vertices. */
+ testtri.orient = 0;
+ org(testtri, regionorg);
+ dest(testtri, regiondest);
+ apex(testtri, regionapex);
+ printf(" Checking (%.12g, %.12g) (%.12g, %.12g) (%.12g, %.12g)\n",
+ regionorg[0], regionorg[1], regiondest[0], regiondest[1],
+ regionapex[0], regionapex[1]);
+ }
+ /* Check each of the triangle's three neighbors. */
+ for (testtri.orient = 0; testtri.orient < 3; testtri.orient++) {
+ /* Find the neighbor. */
+ sym(testtri, neighbor);
+ /* Check for a subsegment between the triangle and its neighbor. */
+ tspivot(testtri, neighborsubseg);
+ /* Make sure the neighbor exists, is not already infected, and */
+ /* isn't protected by a subsegment. */
+ if ((neighbor.tri != m->dummytri) && !infected(neighbor)
+ && (neighborsubseg.ss == m->dummysub)) {
+ if (b->verbose > 2) {
+ org(neighbor, regionorg);
+ dest(neighbor, regiondest);
+ apex(neighbor, regionapex);
+ printf(" Marking (%.12g, %.12g) (%.12g, %.12g) (%.12g, %.12g)\n",
+ regionorg[0], regionorg[1], regiondest[0], regiondest[1],
+ regionapex[0], regionapex[1]);
+ }
+ /* Infect the neighbor. */
+ infect(neighbor);
+ /* Ensure that the neighbor's neighbors will be infected. */
+ regiontri = (triangle **) poolalloc(&m->viri);
+ *regiontri = neighbor.tri;
+ }
+ }
+ /* Remark the triangle as infected, so it doesn't get added to the */
+ /* virus pool again. */
+ infect(testtri);
+ virusloop = (triangle **) traverse(&m->viri);
+ }
+
+ /* Uninfect all triangles. */
+ if (b->verbose > 1) {
+ printf(" Unmarking marked triangles.\n");
+ }
+ traversalinit(&m->viri);
+ virusloop = (triangle **) traverse(&m->viri);
+ while (virusloop != (triangle **) NULL) {
+ testtri.tri = *virusloop;
+ uninfect(testtri);
+ virusloop = (triangle **) traverse(&m->viri);
+ }
+ /* Empty the virus pool. */
+ poolrestart(&m->viri);
+}
+
+/*****************************************************************************/
+/* */
+/* carveholes() Find the holes and infect them. Find the area */
+/* constraints and infect them. Infect the convex hull. */
+/* Spread the infection and kill triangles. Spread the */
+/* area constraints. */
+/* */
+/* This routine mainly calls other routines to carry out all these */
+/* functions. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void carveholes(struct mesh *m, struct behavior *b, tREAL *holelist, int holes,
+ tREAL *regionlist, int regions)
+#else /* not ANSI_DECLARATORS */
+void carveholes(m, b, holelist, holes, regionlist, regions)
+struct mesh *m;
+struct behavior *b;
+tREAL *holelist;
+int holes;
+tREAL *regionlist;
+int regions;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri searchtri;
+ struct otri triangleloop;
+ struct otri *regiontris;
+ triangle **holetri;
+ triangle **regiontri;
+ vertex searchorg, searchdest;
+ enum locateresult intersect;
+ int i;
+ triangle ptr; /* Temporary variable used by sym(). */
+
+ if (!(b->quiet || (b->noholes && b->convex))) {
+ printf("Removing unwanted triangles.\n");
+ if (b->verbose && (holes > 0)) {
+ printf(" Marking holes for elimination.\n");
+ }
+ }
+
+ if (regions > 0) {
+ /* Allocate storage for the triangles in which region points fall. */
+ regiontris = (struct otri *) trimalloc(regions *
+ (int) sizeof(struct otri));
+ } else {
+ regiontris = (struct otri *) NULL;
+ }
+
+ if (((holes > 0) && !b->noholes) || !b->convex || (regions > 0)) {
+ /* Initialize a pool of viri to be used for holes, concavities, */
+ /* regional attributes, and/or regional area constraints. */
+ poolinit(&m->viri, sizeof(triangle *), VIRUSPERBLOCK, VIRUSPERBLOCK, 0);
+ }
+
+ if (!b->convex) {
+ /* Mark as infected any unprotected triangles on the boundary. */
+ /* This is one way by which concavities are created. */
+ infecthull(m, b);
+ }
+
+ if ((holes > 0) && !b->noholes) {
+ /* Infect each triangle in which a hole lies. */
+ for (i = 0; i < 2 * holes; i += 2) {
+ /* Ignore holes that aren't within the bounds of the mesh. */
+ if ((holelist[i] >= m->xmin) && (holelist[i] <= m->xmax)
+ && (holelist[i + 1] >= m->ymin) && (holelist[i + 1] <= m->ymax)) {
+ /* Start searching from some triangle on the outer boundary. */
+ searchtri.tri = m->dummytri;
+ searchtri.orient = 0;
+ symself(searchtri);
+ /* Ensure that the hole is to the left of this boundary edge; */
+ /* otherwise, locate() will falsely report that the hole */
+ /* falls within the starting triangle. */
+ org(searchtri, searchorg);
+ dest(searchtri, searchdest);
+ if (counterclockwise(m, b, searchorg, searchdest, &holelist[i]) >
+ 0.0f) {
+ /* Find a triangle that contains the hole. */
+ intersect = locate(m, b, &holelist[i], &searchtri);
+ if ((intersect != OUTSIDE) && (!infected(searchtri))) {
+ /* Infect the triangle. This is done by marking the triangle */
+ /* as infected and including the triangle in the virus pool. */
+ infect(searchtri);
+ holetri = (triangle **) poolalloc(&m->viri);
+ *holetri = searchtri.tri;
+ }
+ }
+ }
+ }
+ }
+
+ /* Now, we have to find all the regions BEFORE we carve the holes, because */
+ /* locate() won't work when the triangulation is no longer convex. */
+ /* (Incidentally, this is the reason why regional attributes and area */
+ /* constraints can't be used when refining a preexisting mesh, which */
+ /* might not be convex; they can only be used with a freshly */
+ /* triangulated PSLG.) */
+ if (regions > 0) {
+ /* Find the starting triangle for each region. */
+ for (i = 0; i < regions; i++) {
+ regiontris[i].tri = m->dummytri;
+ /* Ignore region points that aren't within the bounds of the mesh. */
+ if ((regionlist[4 * i] >= m->xmin) && (regionlist[4 * i] <= m->xmax) &&
+ (regionlist[4 * i + 1] >= m->ymin) &&
+ (regionlist[4 * i + 1] <= m->ymax)) {
+ /* Start searching from some triangle on the outer boundary. */
+ searchtri.tri = m->dummytri;
+ searchtri.orient = 0;
+ symself(searchtri);
+ /* Ensure that the region point is to the left of this boundary */
+ /* edge; otherwise, locate() will falsely report that the */
+ /* region point falls within the starting triangle. */
+ org(searchtri, searchorg);
+ dest(searchtri, searchdest);
+ if (counterclockwise(m, b, searchorg, searchdest, &regionlist[4 * i]) >
+ 0.0f) {
+ /* Find a triangle that contains the region point. */
+ intersect = locate(m, b, &regionlist[4 * i], &searchtri);
+ if ((intersect != OUTSIDE) && (!infected(searchtri))) {
+ /* Record the triangle for processing after the */
+ /* holes have been carved. */
+ otricopy(searchtri, regiontris[i]);
+ }
+ }
+ }
+ }
+ }
+
+ if (m->viri.items > 0) {
+ /* Carve the holes and concavities. */
+ plague(m, b);
+ }
+ /* The virus pool should be empty now. */
+
+ if (regions > 0) {
+ if (!b->quiet) {
+ if (b->regionattrib) {
+ if (b->vararea) {
+ printf("Spreading regional attributes and area constraints.\n");
+ } else {
+ printf("Spreading regional attributes.\n");
+ }
+ } else {
+ printf("Spreading regional area constraints.\n");
+ }
+ }
+ if (b->regionattrib && !b->refine) {
+ /* Assign every triangle a regional attribute of zero. */
+ traversalinit(&m->triangles);
+ triangleloop.orient = 0;
+ triangleloop.tri = triangletraverse(m);
+ while (triangleloop.tri != (triangle *) NULL) {
+ setelemattribute(triangleloop, m->eextras, 0.0f);
+ triangleloop.tri = triangletraverse(m);
+ }
+ }
+ for (i = 0; i < regions; i++) {
+ if (regiontris[i].tri != m->dummytri) {
+ /* Make sure the triangle under consideration still exists. */
+ /* It may have been eaten by the virus. */
+ if (!deadtri(regiontris[i].tri)) {
+ /* Put one triangle in the virus pool. */
+ infect(regiontris[i]);
+ regiontri = (triangle **) poolalloc(&m->viri);
+ *regiontri = regiontris[i].tri;
+ /* Apply one region's attribute and/or area constraint. */
+ regionplague(m, b, regionlist[4 * i + 2], regionlist[4 * i + 3]);
+ /* The virus pool should be empty now. */
+ }
+ }
+ }
+ if (b->regionattrib && !b->refine) {
+ /* Note the fact that each triangle has an additional attribute. */
+ m->eextras++;
+ }
+ }
+
+ /* Free up memory. */
+ if (((holes > 0) && !b->noholes) || !b->convex || (regions > 0)) {
+ pooldeinit(&m->viri);
+ }
+ if (regions > 0) {
+ trifree((VOID *) regiontris);
+ }
+}
+
+/** **/
+/** **/
+/********* Carving out holes and concavities ends here *********/
+
+/********* Mesh quality maintenance begins here *********/
+/** **/
+/** **/
+
+/*****************************************************************************/
+/* */
+/* tallyencs() Traverse the entire list of subsegments, and check each */
+/* to see if it is encroached. If so, add it to the list. */
+/* */
+/*****************************************************************************/
+
+#ifndef CDT_ONLY
+
+#ifdef ANSI_DECLARATORS
+void tallyencs(struct mesh *m, struct behavior *b)
+#else /* not ANSI_DECLARATORS */
+void tallyencs(m, b)
+struct mesh *m;
+struct behavior *b;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct osub subsegloop;
+ int dummy;
+
+ traversalinit(&m->subsegs);
+ subsegloop.ssorient = 0;
+ subsegloop.ss = subsegtraverse(m);
+ while (subsegloop.ss != (subseg *) NULL) {
+ /* If the segment is encroached, add it to the list. */
+ dummy = checkseg4encroach(m, b, &subsegloop);
+ subsegloop.ss = subsegtraverse(m);
+ }
+}
+
+#endif /* not CDT_ONLY */
+
+/*****************************************************************************/
+/* */
+/* precisionerror() Print an error message for precision problems. */
+/* */
+/*****************************************************************************/
+
+#ifndef CDT_ONLY
+
+void precisionerror()
+{
+ printf("Try increasing the area criterion and/or reducing the minimum\n");
+ printf(" allowable angle so that tiny triangles are not created.\n");
+#ifdef SINGLE
+ printf("Alternatively, try recompiling me with double precision\n");
+ printf(" arithmetic (by removing \"#define SINGLE\" from the\n");
+ printf(" source file or \"-DSINGLE\" from the makefile).\n");
+#endif /* SINGLE */
+}
+
+#endif /* not CDT_ONLY */
+
+/*****************************************************************************/
+/* */
+/* splitencsegs() Split all the encroached subsegments. */
+/* */
+/* Each encroached subsegment is repaired by splitting it - inserting a */
+/* vertex at or near its midpoint. Newly inserted vertices may encroach */
+/* upon other subsegments; these are also repaired. */
+/* */
+/* `triflaws' is a flag that specifies whether one should take note of new */
+/* bad triangles that result from inserting vertices to repair encroached */
+/* subsegments. */
+/* */
+/*****************************************************************************/
+
+#ifndef CDT_ONLY
+
+#ifdef ANSI_DECLARATORS
+void splitencsegs(struct mesh *m, struct behavior *b, int triflaws)
+#else /* not ANSI_DECLARATORS */
+void splitencsegs(m, b, triflaws)
+struct mesh *m;
+struct behavior *b;
+int triflaws;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri enctri;
+ struct otri testtri;
+ struct osub testsh;
+ struct osub currentenc;
+ struct badsubseg *encloop;
+ vertex eorg, edest, eapex;
+ vertex newvertex;
+ enum insertvertexresult success;
+ tREAL segmentlength, nearestpoweroftwo;
+ tREAL split;
+ tREAL multiplier, divisor;
+ int acuteorg, acuteorg2, acutedest, acutedest2;
+ int dummy;
+ int i;
+ triangle ptr; /* Temporary variable used by stpivot(). */
+ subseg sptr; /* Temporary variable used by snext(). */
+
+ /* Note that steinerleft == -1 if an unlimited number */
+ /* of Steiner points is allowed. */
+ while ((m->badsubsegs.items > 0) && (m->steinerleft != 0)) {
+ traversalinit(&m->badsubsegs);
+ encloop = badsubsegtraverse(m);
+ while ((encloop != (struct badsubseg *) NULL) && (m->steinerleft != 0)) {
+ sdecode(encloop->encsubseg, currentenc);
+ sorg(currentenc, eorg);
+ sdest(currentenc, edest);
+ /* Make sure that this segment is still the same segment it was */
+ /* when it was determined to be encroached. If the segment was */
+ /* enqueued multiple times (because several newly inserted */
+ /* vertices encroached it), it may have already been split. */
+ if (!deadsubseg(currentenc.ss) &&
+ (eorg == encloop->subsegorg) && (edest == encloop->subsegdest)) {
+ /* To decide where to split a segment, we need to know if the */
+ /* segment shares an endpoint with an adjacent segment. */
+ /* The concern is that, if we simply split every encroached */
+ /* segment in its center, two adjacent segments with a small */
+ /* angle between them might lead to an infinite loop; each */
+ /* vertex added to split one segment will encroach upon the */
+ /* other segment, which must then be split with a vertex that */
+ /* will encroach upon the first segment, and so on forever. */
+ /* To avoid this, imagine a set of concentric circles, whose */
+ /* radii are powers of two, about each segment endpoint. */
+ /* These concentric circles determine where the segment is */
+ /* split. (If both endpoints are shared with adjacent */
+ /* segments, split the segment in the middle, and apply the */
+ /* concentric circles for later splittings.) */
+
+ /* Is the origin shared with another segment? */
+ stpivot(currentenc, enctri);
+ lnext(enctri, testtri);
+ tspivot(testtri, testsh);
+ acuteorg = testsh.ss != m->dummysub;
+ /* Is the destination shared with another segment? */
+ lnextself(testtri);
+ tspivot(testtri, testsh);
+ acutedest = testsh.ss != m->dummysub;
+
+ /* If we're using Chew's algorithm (rather than Ruppert's) */
+ /* to define encroachment, delete free vertices from the */
+ /* subsegment's diametral circle. */
+ if (!b->conformdel && !acuteorg && !acutedest) {
+ apex(enctri, eapex);
+ while ((vertextype(eapex) == FREEVERTEX) &&
+ ((eorg[0] - eapex[0]) * (edest[0] - eapex[0]) +
+ (eorg[1] - eapex[1]) * (edest[1] - eapex[1]) < 0.0f)) {
+ deletevertex(m, b, &testtri);
+ stpivot(currentenc, enctri);
+ apex(enctri, eapex);
+ lprev(enctri, testtri);
+ }
+ }
+
+ /* Now, check the other side of the segment, if there's a triangle */
+ /* there. */
+ sym(enctri, testtri);
+ if (testtri.tri != m->dummytri) {
+ /* Is the destination shared with another segment? */
+ lnextself(testtri);
+ tspivot(testtri, testsh);
+ acutedest2 = testsh.ss != m->dummysub;
+ acutedest = acutedest || acutedest2;
+ /* Is the origin shared with another segment? */
+ lnextself(testtri);
+ tspivot(testtri, testsh);
+ acuteorg2 = testsh.ss != m->dummysub;
+ acuteorg = acuteorg || acuteorg2;
+
+ /* Delete free vertices from the subsegment's diametral circle. */
+ if (!b->conformdel && !acuteorg2 && !acutedest2) {
+ org(testtri, eapex);
+ while ((vertextype(eapex) == FREEVERTEX) &&
+ ((eorg[0] - eapex[0]) * (edest[0] - eapex[0]) +
+ (eorg[1] - eapex[1]) * (edest[1] - eapex[1]) < 0.0f)) {
+ deletevertex(m, b, &testtri);
+ sym(enctri, testtri);
+ apex(testtri, eapex);
+ lprevself(testtri);
+ }
+ }
+ }
+
+ /* Use the concentric circles if exactly one endpoint is shared */
+ /* with another adjacent segment. */
+ if (acuteorg || acutedest) {
+ segmentlength = sqrt((edest[0] - eorg[0]) * (edest[0] - eorg[0]) +
+ (edest[1] - eorg[1]) * (edest[1] - eorg[1]));
+ /* Find the power of two that most evenly splits the segment. */
+ /* The worst case is a 2:1 ratio between subsegment lengths. */
+ nearestpoweroftwo = 1.0f;
+ while (segmentlength > 3.0 * nearestpoweroftwo) {
+ nearestpoweroftwo *= 2.0f;
+ }
+ while (segmentlength < 1.5 * nearestpoweroftwo) {
+ nearestpoweroftwo *= 0.5f;
+ }
+ /* Where do we split the segment? */
+ split = nearestpoweroftwo / segmentlength;
+ if (acutedest) {
+ split = 1.0 - split;
+ }
+ } else {
+ /* If we're not worried about adjacent segments, split */
+ /* this segment in the middle. */
+ split = 0.5f;
+ }
+
+ /* Create the new vertex. */
+ newvertex = (vertex) poolalloc(&m->vertices);
+ /* Interpolate its coordinate and attributes. */
+ for (i = 0; i < 2 + m->nextras; i++) {
+ newvertex[i] = eorg[i] + split * (edest[i] - eorg[i]);
+ }
+
+ if (!b->noexact) {
+ /* Roundoff in the above calculation may yield a `newvertex' */
+ /* that is not precisely collinear with `eorg' and `edest'. */
+ /* Improve collinearity by one step of iterative refinement. */
+ multiplier = counterclockwise(m, b, eorg, edest, newvertex);
+ divisor = ((eorg[0] - edest[0]) * (eorg[0] - edest[0]) +
+ (eorg[1] - edest[1]) * (eorg[1] - edest[1]));
+ if ((multiplier != 0.0f) && (divisor != 0.0f)) {
+ multiplier = multiplier / divisor;
+ /* Watch out for NANs. */
+ if (multiplier == multiplier) {
+ newvertex[0] += multiplier * (edest[1] - eorg[1]);
+ newvertex[1] += multiplier * (eorg[0] - edest[0]);
+ }
+ }
+ }
+
+ setvertexmark(newvertex, mark(currentenc));
+ setvertextype(newvertex, SEGMENTVERTEX);
+ if (b->verbose > 1) {
+ printf(
+ " Splitting subsegment (%.12g, %.12g) (%.12g, %.12g) at (%.12g, %.12g).\n",
+ eorg[0], eorg[1], edest[0], edest[1],
+ newvertex[0], newvertex[1]);
+ }
+ /* Check whether the new vertex lies on an endpoint. */
+ if (((newvertex[0] == eorg[0]) && (newvertex[1] == eorg[1])) ||
+ ((newvertex[0] == edest[0]) && (newvertex[1] == edest[1]))) {
+ printf("Error: Ran out of precision at (%.12g, %.12g).\n",
+ newvertex[0], newvertex[1]);
+ printf("I attempted to split a segment to a smaller size than\n");
+ printf(" can be accommodated by the finite precision of\n");
+ printf(" floating point arithmetic.\n");
+ precisionerror();
+ triexit(1);
+ }
+ /* Insert the splitting vertex. This should always succeed. */
+ success = insertvertex(m, b, newvertex, &enctri, &currentenc,
+ 1, triflaws);
+ if ((success != SUCCESSFULVERTEX) && (success != ENCROACHINGVERTEX)) {
+ printf("Internal error in splitencsegs():\n");
+ printf(" Failure to split a segment.\n");
+ internalerror();
+ }
+ if (m->steinerleft > 0) {
+ m->steinerleft--;
+ }
+ /* Check the two new subsegments to see if they're encroached. */
+ dummy = checkseg4encroach(m, b, &currentenc);
+ snextself(currentenc);
+ dummy = checkseg4encroach(m, b, &currentenc);
+ }
+
+ badsubsegdealloc(m, encloop);
+ encloop = badsubsegtraverse(m);
+ }
+ }
+}
+
+#endif /* not CDT_ONLY */
+
+/*****************************************************************************/
+/* */
+/* tallyfaces() Test every triangle in the mesh for quality measures. */
+/* */
+/*****************************************************************************/
+
+#ifndef CDT_ONLY
+
+#ifdef ANSI_DECLARATORS
+void tallyfaces(struct mesh *m, struct behavior *b)
+#else /* not ANSI_DECLARATORS */
+void tallyfaces(m, b)
+struct mesh *m;
+struct behavior *b;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri triangleloop;
+
+ if (b->verbose) {
+ printf(" Making a list of bad triangles.\n");
+ }
+ traversalinit(&m->triangles);
+ triangleloop.orient = 0;
+ triangleloop.tri = triangletraverse(m);
+ while (triangleloop.tri != (triangle *) NULL) {
+ /* If the triangle is bad, enqueue it. */
+ testtriangle(m, b, &triangleloop);
+ triangleloop.tri = triangletraverse(m);
+ }
+}
+
+#endif /* not CDT_ONLY */
+
+/*****************************************************************************/
+/* */
+/* splittriangle() Inserts a vertex at the circumcenter of a triangle. */
+/* Deletes the newly inserted vertex if it encroaches */
+/* upon a segment. */
+/* */
+/*****************************************************************************/
+
+#ifndef CDT_ONLY
+
+#ifdef ANSI_DECLARATORS
+void splittriangle(struct mesh *m, struct behavior *b,
+ struct badtriang *badtri)
+#else /* not ANSI_DECLARATORS */
+void splittriangle(m, b, badtri)
+struct mesh *m;
+struct behavior *b;
+struct badtriang *badtri;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri badotri;
+ vertex borg, bdest, bapex;
+ vertex newvertex;
+ tREAL xi, eta;
+ enum insertvertexresult success;
+ int errorflag;
+ int i;
+
+ decode(badtri->poortri, badotri);
+ org(badotri, borg);
+ dest(badotri, bdest);
+ apex(badotri, bapex);
+ /* Make sure that this triangle is still the same triangle it was */
+ /* when it was tested and determined to be of bad quality. */
+ /* Subsequent transformations may have made it a different triangle. */
+ if (!deadtri(badotri.tri) && (borg == badtri->triangorg) &&
+ (bdest == badtri->triangdest) && (bapex == badtri->triangapex)) {
+ if (b->verbose > 1) {
+ printf(" Splitting this triangle at its circumcenter:\n");
+ printf(" (%.12g, %.12g) (%.12g, %.12g) (%.12g, %.12g)\n", borg[0],
+ borg[1], bdest[0], bdest[1], bapex[0], bapex[1]);
+ }
+
+ errorflag = 0;
+ /* Create a new vertex at the triangle's circumcenter. */
+ newvertex = (vertex) poolalloc(&m->vertices);
+ findcircumcenter(m, b, borg, bdest, bapex, newvertex, &xi, &eta, 1);
+
+ /* Check whether the new vertex lies on a triangle vertex. */
+ if (((newvertex[0] == borg[0]) && (newvertex[1] == borg[1])) ||
+ ((newvertex[0] == bdest[0]) && (newvertex[1] == bdest[1])) ||
+ ((newvertex[0] == bapex[0]) && (newvertex[1] == bapex[1]))) {
+ if (!b->quiet) {
+ printf(
+ "Warning: New vertex (%.12g, %.12g) falls on existing vertex.\n",
+ newvertex[0], newvertex[1]);
+ errorflag = 1;
+ }
+ vertexdealloc(m, newvertex);
+ } else {
+ for (i = 2; i < 2 + m->nextras; i++) {
+ /* Interpolate the vertex attributes at the circumcenter. */
+ newvertex[i] = borg[i] + xi * (bdest[i] - borg[i])
+ + eta * (bapex[i] - borg[i]);
+ }
+ /* The new vertex must be in the interior, and therefore is a */
+ /* free vertex with a marker of zero. */
+ setvertexmark(newvertex, 0);
+ setvertextype(newvertex, FREEVERTEX);
+
+ /* Ensure that the handle `badotri' does not represent the longest */
+ /* edge of the triangle. This ensures that the circumcenter must */
+ /* fall to the left of this edge, so point location will work. */
+ /* (If the angle org-apex-dest exceeds 90 degrees, then the */
+ /* circumcenter lies outside the org-dest edge, and eta is */
+ /* negative. Roundoff error might prevent eta from being */
+ /* negative when it should be, so I test eta against xi.) */
+ if (eta < xi) {
+ lprevself(badotri);
+ }
+
+ /* Insert the circumcenter, searching from the edge of the triangle, */
+ /* and maintain the Delaunay property of the triangulation. */
+ success = insertvertex(m, b, newvertex, &badotri, (struct osub *) NULL,
+ 1, 1);
+ if (success == SUCCESSFULVERTEX) {
+ if (m->steinerleft > 0) {
+ m->steinerleft--;
+ }
+ } else if (success == ENCROACHINGVERTEX) {
+ /* If the newly inserted vertex encroaches upon a subsegment, */
+ /* delete the new vertex. */
+ undovertex(m, b);
+ if (b->verbose > 1) {
+ printf(" Rejecting (%.12g, %.12g).\n", newvertex[0], newvertex[1]);
+ }
+ vertexdealloc(m, newvertex);
+ } else if (success == VIOLATINGVERTEX) {
+ /* Failed to insert the new vertex, but some subsegment was */
+ /* marked as being encroached. */
+ vertexdealloc(m, newvertex);
+ } else { /* success == DUPLICATEVERTEX */
+ /* Couldn't insert the new vertex because a vertex is already there. */
+ if (!b->quiet) {
+ printf(
+ "Warning: New vertex (%.12g, %.12g) falls on existing vertex.\n",
+ newvertex[0], newvertex[1]);
+ errorflag = 1;
+ }
+ vertexdealloc(m, newvertex);
+ }
+ }
+ if (errorflag) {
+ if (b->verbose) {
+ printf(" The new vertex is at the circumcenter of triangle\n");
+ printf(" (%.12g, %.12g) (%.12g, %.12g) (%.12g, %.12g)\n",
+ borg[0], borg[1], bdest[0], bdest[1], bapex[0], bapex[1]);
+ }
+ printf("This probably means that I am trying to refine triangles\n");
+ printf(" to a smaller size than can be accommodated by the finite\n");
+ printf(" precision of floating point arithmetic. (You can be\n");
+ printf(" sure of this if I fail to terminate.)\n");
+ precisionerror();
+ }
+ }
+}
+
+#endif /* not CDT_ONLY */
+
+/*****************************************************************************/
+/* */
+/* enforcequality() Remove all the encroached subsegments and bad */
+/* triangles from the triangulation. */
+/* */
+/*****************************************************************************/
+
+#ifndef CDT_ONLY
+
+#ifdef ANSI_DECLARATORS
+void enforcequality(struct mesh *m, struct behavior *b)
+#else /* not ANSI_DECLARATORS */
+void enforcequality(m, b)
+struct mesh *m;
+struct behavior *b;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct badtriang *badtri;
+ int i;
+
+ if (!b->quiet) {
+ printf("Adding Steiner points to enforce quality.\n");
+ }
+ /* Initialize the pool of encroached subsegments. */
+ poolinit(&m->badsubsegs, sizeof(struct badsubseg), BADSUBSEGPERBLOCK,
+ BADSUBSEGPERBLOCK, 0);
+ if (b->verbose) {
+ printf(" Looking for encroached subsegments.\n");
+ }
+ /* Test all segments to see if they're encroached. */
+ tallyencs(m, b);
+ if (b->verbose && (m->badsubsegs.items > 0)) {
+ printf(" Splitting encroached subsegments.\n");
+ }
+ /* Fix encroached subsegments without noting bad triangles. */
+ splitencsegs(m, b, 0);
+ /* At this point, if we haven't run out of Steiner points, the */
+ /* triangulation should be (conforming) Delaunay. */
+
+ /* Next, we worry about enforcing triangle quality. */
+ if ((b->minangle > 0.0f) || b->vararea || b->fixedarea || b->usertest) {
+ /* Initialize the pool of bad triangles. */
+ poolinit(&m->badtriangles, sizeof(struct badtriang), BADTRIPERBLOCK,
+ BADTRIPERBLOCK, 0);
+ /* Initialize the queues of bad triangles. */
+ for (i = 0; i < 4096; i++) {
+ m->queuefront[i] = (struct badtriang *) NULL;
+ }
+ m->firstnonemptyq = -1;
+ /* Test all triangles to see if they're bad. */
+ tallyfaces(m, b);
+ /* Initialize the pool of recently flipped triangles. */
+ poolinit(&m->flipstackers, sizeof(struct flipstacker), FLIPSTACKERPERBLOCK,
+ FLIPSTACKERPERBLOCK, 0);
+ m->checkquality = 1;
+ if (b->verbose) {
+ printf(" Splitting bad triangles.\n");
+ }
+ while ((m->badtriangles.items > 0) && (m->steinerleft != 0)) {
+ /* Fix one bad triangle by inserting a vertex at its circumcenter. */
+ badtri = dequeuebadtriang(m);
+ splittriangle(m, b, badtri);
+ if (m->badsubsegs.items > 0) {
+ /* Put bad triangle back in queue for another try later. */
+ enqueuebadtriang(m, b, badtri);
+ /* Fix any encroached subsegments that resulted. */
+ /* Record any new bad triangles that result. */
+ splitencsegs(m, b, 1);
+ } else {
+ /* Return the bad triangle to the pool. */
+ pooldealloc(&m->badtriangles, (VOID *) badtri);
+ }
+ }
+ }
+ /* At this point, if the "-D" switch was selected and we haven't run out */
+ /* of Steiner points, the triangulation should be (conforming) Delaunay */
+ /* and have no low-quality triangles. */
+
+ /* Might we have run out of Steiner points too soon? */
+ if (!b->quiet && b->conformdel && (m->badsubsegs.items > 0) &&
+ (m->steinerleft == 0)) {
+ printf("\nWarning: I ran out of Steiner points, but the mesh has\n");
+ if (m->badsubsegs.items == 1) {
+ printf(" one encroached subsegment, and therefore might not be truly\n"
+ );
+ } else {
+ printf(" %ld encroached subsegments, and therefore might not be truly\n"
+ , m->badsubsegs.items);
+ }
+ printf(" Delaunay. If the Delaunay property is important to you,\n");
+ printf(" try increasing the number of Steiner points (controlled by\n");
+ printf(" the -S switch) slightly and try again.\n\n");
+ }
+}
+
+#endif /* not CDT_ONLY */
+
+/** **/
+/** **/
+/********* Mesh quality maintenance ends here *********/
+
+/*****************************************************************************/
+/* */
+/* highorder() Create extra nodes for quadratic subparametric elements. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void highorder(struct mesh *m, struct behavior *b)
+#else /* not ANSI_DECLARATORS */
+void highorder(m, b)
+struct mesh *m;
+struct behavior *b;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri triangleloop, trisym;
+ struct osub checkmark;
+ vertex newvertex;
+ vertex torg, tdest;
+ int i;
+ triangle ptr; /* Temporary variable used by sym(). */
+ subseg sptr; /* Temporary variable used by tspivot(). */
+
+ if (!b->quiet) {
+ printf("Adding vertices for second-order triangles.\n");
+ }
+ /* The following line ensures that dead items in the pool of nodes */
+ /* cannot be allocated for the extra nodes associated with high */
+ /* order elements. This ensures that the primary nodes (at the */
+ /* corners of elements) will occur earlier in the output files, and */
+ /* have lower indices, than the extra nodes. */
+ m->vertices.deaditemstack = (VOID *) NULL;
+
+ traversalinit(&m->triangles);
+ triangleloop.tri = triangletraverse(m);
+ /* To loop over the set of edges, loop over all triangles, and look at */
+ /* the three edges of each triangle. If there isn't another triangle */
+ /* adjacent to the edge, operate on the edge. If there is another */
+ /* adjacent triangle, operate on the edge only if the current triangle */
+ /* has a smaller pointer than its neighbor. This way, each edge is */
+ /* considered only once. */
+ while (triangleloop.tri != (triangle *) NULL) {
+ for (triangleloop.orient = 0; triangleloop.orient < 3;
+ triangleloop.orient++) {
+ sym(triangleloop, trisym);
+ if ((triangleloop.tri < trisym.tri) || (trisym.tri == m->dummytri)) {
+ org(triangleloop, torg);
+ dest(triangleloop, tdest);
+ /* Create a new node in the middle of the edge. Interpolate */
+ /* its attributes. */
+ newvertex = (vertex) poolalloc(&m->vertices);
+ for (i = 0; i < 2 + m->nextras; i++) {
+ newvertex[i] = 0.5 * (torg[i] + tdest[i]);
+ }
+ /* Set the new node's marker to zero or one, depending on */
+ /* whether it lies on a boundary. */
+ setvertexmark(newvertex, trisym.tri == m->dummytri);
+ setvertextype(newvertex,
+ trisym.tri == m->dummytri ? FREEVERTEX : SEGMENTVERTEX);
+ if (b->usesegments) {
+ tspivot(triangleloop, checkmark);
+ /* If this edge is a segment, transfer the marker to the new node. */
+ if (checkmark.ss != m->dummysub) {
+ setvertexmark(newvertex, mark(checkmark));
+ setvertextype(newvertex, SEGMENTVERTEX);
+ }
+ }
+ if (b->verbose > 1) {
+ printf(" Creating (%.12g, %.12g).\n", newvertex[0], newvertex[1]);
+ }
+ /* Record the new node in the (one or two) adjacent elements. */
+ triangleloop.tri[m->highorderindex + triangleloop.orient] =
+ (triangle) newvertex;
+ if (trisym.tri != m->dummytri) {
+ trisym.tri[m->highorderindex + trisym.orient] = (triangle) newvertex;
+ }
+ }
+ }
+ triangleloop.tri = triangletraverse(m);
+ }
+}
+
+/********* File I/O routines begin here *********/
+/** **/
+/** **/
+
+/*****************************************************************************/
+/* */
+/* readline() Read a nonempty line from a file. */
+/* */
+/* A line is considered "nonempty" if it contains something that looks like */
+/* a number. Comments (prefaced by `#') are ignored. */
+/* */
+/*****************************************************************************/
+
+#ifndef TRILIBRARY
+
+#ifdef ANSI_DECLARATORS
+char *readline(char *string, FILE *infile, char *infilename)
+#else /* not ANSI_DECLARATORS */
+char *readline(string, infile, infilename)
+char *string;
+FILE *infile;
+char *infilename;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ char *result;
+
+ /* Search for something that looks like a number. */
+ do {
+ result = fgets(string, INPUTLINESIZE, infile);
+ if (result == (char *) NULL) {
+ printf(" Error: Unexpected end of file in %s.\n", infilename);
+ triexit(1);
+ }
+ /* Skip anything that doesn't look like a number, a comment, */
+ /* or the end of a line. */
+ while ((*result != '\0') && (*result != '#')
+ && (*result != '.') && (*result != '+') && (*result != '-')
+ && ((*result < '0') || (*result > '9'))) {
+ result++;
+ }
+ /* If it's a comment or end of line, read another line and try again. */
+ } while ((*result == '#') || (*result == '\0'));
+ return result;
+}
+
+#endif /* not TRILIBRARY */
+
+/*****************************************************************************/
+/* */
+/* findfield() Find the next field of a string. */
+/* */
+/* Jumps past the current field by searching for whitespace, then jumps */
+/* past the whitespace to find the next field. */
+/* */
+/*****************************************************************************/
+
+#ifndef TRILIBRARY
+
+#ifdef ANSI_DECLARATORS
+char *findfield(char *string)
+#else /* not ANSI_DECLARATORS */
+char *findfield(string)
+char *string;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ char *result;
+
+ result = string;
+ /* Skip the current field. Stop upon reaching whitespace. */
+ while ((*result != '\0') && (*result != '#')
+ && (*result != ' ') && (*result != '\t')) {
+ result++;
+ }
+ /* Now skip the whitespace and anything else that doesn't look like a */
+ /* number, a comment, or the end of a line. */
+ while ((*result != '\0') && (*result != '#')
+ && (*result != '.') && (*result != '+') && (*result != '-')
+ && ((*result < '0') || (*result > '9'))) {
+ result++;
+ }
+ /* Check for a comment (prefixed with `#'). */
+ if (*result == '#') {
+ *result = '\0';
+ }
+ return result;
+}
+
+#endif /* not TRILIBRARY */
+
+/*****************************************************************************/
+/* */
+/* readnodes() Read the vertices from a file, which may be a .node or */
+/* .poly file. */
+/* */
+/*****************************************************************************/
+
+#ifndef TRILIBRARY
+
+#ifdef ANSI_DECLARATORS
+void readnodes(struct mesh *m, struct behavior *b, char *nodefilename,
+ char *polyfilename, FILE **polyfile)
+#else /* not ANSI_DECLARATORS */
+void readnodes(m, b, nodefilename, polyfilename, polyfile)
+struct mesh *m;
+struct behavior *b;
+char *nodefilename;
+char *polyfilename;
+FILE **polyfile;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ FILE *infile;
+ vertex vertexloop;
+ char inputline[INPUTLINESIZE];
+ char *stringptr;
+ char *infilename;
+ tREAL x, y;
+ int firstnode;
+ int nodemarkers;
+ int currentmarker;
+ int i, j;
+
+ if (b->poly) {
+ /* Read the vertices from a .poly file. */
+ if (!b->quiet) {
+ printf("Opening %s.\n", polyfilename);
+ }
+ *polyfile = fopen(polyfilename, "r");
+ if (*polyfile == (FILE *) NULL) {
+ printf(" Error: Cannot access file %s.\n", polyfilename);
+ triexit(1);
+ }
+ /* Read number of vertices, number of dimensions, number of vertex */
+ /* attributes, and number of boundary markers. */
+ stringptr = readline(inputline, *polyfile, polyfilename);
+ m->invertices = (int) strtol(stringptr, &stringptr, 0);
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ m->mesh_dim = 2;
+ } else {
+ m->mesh_dim = (int) strtol(stringptr, &stringptr, 0);
+ }
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ m->nextras = 0;
+ } else {
+ m->nextras = (int) strtol(stringptr, &stringptr, 0);
+ }
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ nodemarkers = 0;
+ } else {
+ nodemarkers = (int) strtol(stringptr, &stringptr, 0);
+ }
+ if (m->invertices > 0) {
+ infile = *polyfile;
+ infilename = polyfilename;
+ m->readnodefile = 0;
+ } else {
+ /* If the .poly file claims there are zero vertices, that means that */
+ /* the vertices should be read from a separate .node file. */
+ m->readnodefile = 1;
+ infilename = nodefilename;
+ }
+ } else {
+ m->readnodefile = 1;
+ infilename = nodefilename;
+ *polyfile = (FILE *) NULL;
+ }
+
+ if (m->readnodefile) {
+ /* Read the vertices from a .node file. */
+ if (!b->quiet) {
+ printf("Opening %s.\n", nodefilename);
+ }
+ infile = fopen(nodefilename, "r");
+ if (infile == (FILE *) NULL) {
+ printf(" Error: Cannot access file %s.\n", nodefilename);
+ triexit(1);
+ }
+ /* Read number of vertices, number of dimensions, number of vertex */
+ /* attributes, and number of boundary markers. */
+ stringptr = readline(inputline, infile, nodefilename);
+ m->invertices = (int) strtol(stringptr, &stringptr, 0);
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ m->mesh_dim = 2;
+ } else {
+ m->mesh_dim = (int) strtol(stringptr, &stringptr, 0);
+ }
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ m->nextras = 0;
+ } else {
+ m->nextras = (int) strtol(stringptr, &stringptr, 0);
+ }
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ nodemarkers = 0;
+ } else {
+ nodemarkers = (int) strtol(stringptr, &stringptr, 0);
+ }
+ }
+
+ if (m->invertices < 3) {
+ printf("Error: Input must have at least three input vertices.\n");
+ triexit(1);
+ }
+ if (m->mesh_dim != 2) {
+ printf("Error: Triangle only works with two-dimensional meshes.\n");
+ triexit(1);
+ }
+ if (m->nextras == 0) {
+ b->weighted = 0;
+ }
+
+ initializevertexpool(m, b);
+
+ /* Read the vertices. */
+ for (i = 0; i < m->invertices; i++) {
+ vertexloop = (vertex) poolalloc(&m->vertices);
+ stringptr = readline(inputline, infile, infilename);
+ if (i == 0) {
+ firstnode = (int) strtol(stringptr, &stringptr, 0);
+ if ((firstnode == 0) || (firstnode == 1)) {
+ b->firstnumber = firstnode;
+ }
+ }
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ printf("Error: Vertex %d has no x coordinate.\n", b->firstnumber + i);
+ triexit(1);
+ }
+ x = (tREAL) strtod(stringptr, &stringptr);
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ printf("Error: Vertex %d has no y coordinate.\n", b->firstnumber + i);
+ triexit(1);
+ }
+ y = (tREAL) strtod(stringptr, &stringptr);
+ vertexloop[0] = x;
+ vertexloop[1] = y;
+ /* Read the vertex attributes. */
+ for (j = 2; j < 2 + m->nextras; j++) {
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ vertexloop[j] = 0.0f;
+ } else {
+ vertexloop[j] = (tREAL) strtod(stringptr, &stringptr);
+ }
+ }
+ if (nodemarkers) {
+ /* Read a vertex marker. */
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ setvertexmark(vertexloop, 0);
+ } else {
+ currentmarker = (int) strtol(stringptr, &stringptr, 0);
+ setvertexmark(vertexloop, currentmarker);
+ }
+ } else {
+ /* If no markers are specified in the file, they default to zero. */
+ setvertexmark(vertexloop, 0);
+ }
+ setvertextype(vertexloop, INPUTVERTEX);
+ /* Determine the smallest and largest x and y coordinates. */
+ if (i == 0) {
+ m->xmin = m->xmax = x;
+ m->ymin = m->ymax = y;
+ } else {
+ m->xmin = (x < m->xmin) ? x : m->xmin;
+ m->xmax = (x > m->xmax) ? x : m->xmax;
+ m->ymin = (y < m->ymin) ? y : m->ymin;
+ m->ymax = (y > m->ymax) ? y : m->ymax;
+ }
+ }
+ if (m->readnodefile) {
+ fclose(infile);
+ }
+
+ /* Nonexistent x value used as a flag to mark circle events in sweepline */
+ /* Delaunay algorithm. */
+ m->xminextreme = 10 * m->xmin - 9 * m->xmax;
+}
+
+#endif /* not TRILIBRARY */
+
+/*****************************************************************************/
+/* */
+/* transfernodes() Read the vertices from memory. */
+/* */
+/*****************************************************************************/
+
+#ifdef TRILIBRARY
+
+#ifdef ANSI_DECLARATORS
+void transfernodes(struct mesh *m, struct behavior *b, tREAL *pointlist,
+ tREAL *pointattriblist, int *pointmarkerlist,
+ int numberofpoints, int numberofpointattribs)
+#else /* not ANSI_DECLARATORS */
+void transfernodes(m, b, pointlist, pointattriblist, pointmarkerlist,
+ numberofpoints, numberofpointattribs)
+struct mesh *m;
+struct behavior *b;
+tREAL *pointlist;
+tREAL *pointattriblist;
+int *pointmarkerlist;
+int numberofpoints;
+int numberofpointattribs;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ vertex vertexloop;
+ tREAL x, y;
+ int i, j;
+ int coordindex;
+ int attribindex;
+
+ m->invertices = numberofpoints;
+ m->mesh_dim = 2;
+ m->nextras = numberofpointattribs;
+ m->readnodefile = 0;
+ if (m->invertices < 3) {
+ printf("Error: Input must have at least three input vertices.\n");
+ triexit(1);
+ }
+ if (m->nextras == 0) {
+ b->weighted = 0;
+ }
+
+ initializevertexpool(m, b);
+
+ /* Read the vertices. */
+ coordindex = 0;
+ attribindex = 0;
+ for (i = 0; i < m->invertices; i++) {
+ vertexloop = (vertex) poolalloc(&m->vertices);
+ /* Read the vertex coordinates. */
+ x = vertexloop[0] = pointlist[coordindex++];
+ y = vertexloop[1] = pointlist[coordindex++];
+ /* Read the vertex attributes. */
+ for (j = 0; j < numberofpointattribs; j++) {
+ vertexloop[2 + j] = pointattriblist[attribindex++];
+ }
+ if (pointmarkerlist != (int *) NULL) {
+ /* Read a vertex marker. */
+ setvertexmark(vertexloop, pointmarkerlist[i]);
+ } else {
+ /* If no markers are specified, they default to zero. */
+ setvertexmark(vertexloop, 0);
+ }
+ setvertextype(vertexloop, INPUTVERTEX);
+ /* Determine the smallest and largest x and y coordinates. */
+ if (i == 0) {
+ m->xmin = m->xmax = x;
+ m->ymin = m->ymax = y;
+ } else {
+ m->xmin = (x < m->xmin) ? x : m->xmin;
+ m->xmax = (x > m->xmax) ? x : m->xmax;
+ m->ymin = (y < m->ymin) ? y : m->ymin;
+ m->ymax = (y > m->ymax) ? y : m->ymax;
+ }
+ }
+
+ /* Nonexistent x value used as a flag to mark circle events in sweepline */
+ /* Delaunay algorithm. */
+ m->xminextreme = 10 * m->xmin - 9 * m->xmax;
+}
+
+#endif /* TRILIBRARY */
+
+/*****************************************************************************/
+/* */
+/* readholes() Read the holes, and possibly regional attributes and area */
+/* constraints, from a .poly file. */
+/* */
+/*****************************************************************************/
+
+#ifndef TRILIBRARY
+
+#ifdef ANSI_DECLARATORS
+void readholes(struct mesh *m, struct behavior *b,
+ FILE *polyfile, char *polyfilename, tREAL **hlist, int *holes,
+ tREAL **rlist, int *regions)
+#else /* not ANSI_DECLARATORS */
+void readholes(m, b, polyfile, polyfilename, hlist, holes, rlist, regions)
+struct mesh *m;
+struct behavior *b;
+FILE *polyfile;
+char *polyfilename;
+tREAL **hlist;
+int *holes;
+tREAL **rlist;
+int *regions;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ tREAL *holelist;
+ tREAL *regionlist;
+ char inputline[INPUTLINESIZE];
+ char *stringptr;
+ int index;
+ int i;
+
+ /* Read the holes. */
+ stringptr = readline(inputline, polyfile, polyfilename);
+ *holes = (int) strtol(stringptr, &stringptr, 0);
+ if (*holes > 0) {
+ holelist = (tREAL *) trimalloc(2 * *holes * (int) sizeof(tREAL));
+ *hlist = holelist;
+ for (i = 0; i < 2 * *holes; i += 2) {
+ stringptr = readline(inputline, polyfile, polyfilename);
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ printf("Error: Hole %d has no x coordinate.\n",
+ b->firstnumber + (i >> 1));
+ triexit(1);
+ } else {
+ holelist[i] = (tREAL) strtod(stringptr, &stringptr);
+ }
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ printf("Error: Hole %d has no y coordinate.\n",
+ b->firstnumber + (i >> 1));
+ triexit(1);
+ } else {
+ holelist[i + 1] = (tREAL) strtod(stringptr, &stringptr);
+ }
+ }
+ } else {
+ *hlist = (tREAL *) NULL;
+ }
+
+#ifndef CDT_ONLY
+ if ((b->regionattrib || b->vararea) && !b->refine) {
+ /* Read the area constraints. */
+ stringptr = readline(inputline, polyfile, polyfilename);
+ *regions = (int) strtol(stringptr, &stringptr, 0);
+ if (*regions > 0) {
+ regionlist = (tREAL *) trimalloc(4 * *regions * (int) sizeof(tREAL));
+ *rlist = regionlist;
+ index = 0;
+ for (i = 0; i < *regions; i++) {
+ stringptr = readline(inputline, polyfile, polyfilename);
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ printf("Error: Region %d has no x coordinate.\n",
+ b->firstnumber + i);
+ triexit(1);
+ } else {
+ regionlist[index++] = (tREAL) strtod(stringptr, &stringptr);
+ }
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ printf("Error: Region %d has no y coordinate.\n",
+ b->firstnumber + i);
+ triexit(1);
+ } else {
+ regionlist[index++] = (tREAL) strtod(stringptr, &stringptr);
+ }
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ printf(
+ "Error: Region %d has no region attribute or area constraint.\n",
+ b->firstnumber + i);
+ triexit(1);
+ } else {
+ regionlist[index++] = (tREAL) strtod(stringptr, &stringptr);
+ }
+ stringptr = findfield(stringptr);
+ if (*stringptr == '\0') {
+ regionlist[index] = regionlist[index - 1];
+ } else {
+ regionlist[index] = (tREAL) strtod(stringptr, &stringptr);
+ }
+ index++;
+ }
+ }
+ } else {
+ /* Set `*regions' to zero to avoid an accidental free() later. */
+ *regions = 0;
+ *rlist = (tREAL *) NULL;
+ }
+#endif /* not CDT_ONLY */
+
+ fclose(polyfile);
+}
+
+#endif /* not TRILIBRARY */
+
+/*****************************************************************************/
+/* */
+/* finishfile() Write the command line to the output file so the user */
+/* can remember how the file was generated. Close the file. */
+/* */
+/*****************************************************************************/
+
+#ifndef TRILIBRARY
+
+#ifdef ANSI_DECLARATORS
+void finishfile(FILE *outfile, int argc, char **argv)
+#else /* not ANSI_DECLARATORS */
+void finishfile(outfile, argc, argv)
+FILE *outfile;
+int argc;
+char **argv;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ int i;
+
+ fprintf(outfile, "# Generated by");
+ for (i = 0; i < argc; i++) {
+ fprintf(outfile, " ");
+ fputs(argv[i], outfile);
+ }
+ fprintf(outfile, "\n");
+ fclose(outfile);
+}
+
+#endif /* not TRILIBRARY */
+
+/*****************************************************************************/
+/* */
+/* writenodes() Number the vertices and write them to a .node file. */
+/* */
+/* To save memory, the vertex numbers are written over the boundary markers */
+/* after the vertices are written to a file. */
+/* */
+/*****************************************************************************/
+
+#ifdef TRILIBRARY
+
+#ifdef ANSI_DECLARATORS
+void writenodes(struct mesh *m, struct behavior *b, tREAL **pointlist,
+ tREAL **pointattriblist, int **pointmarkerlist)
+#else /* not ANSI_DECLARATORS */
+void writenodes(m, b, pointlist, pointattriblist, pointmarkerlist)
+struct mesh *m;
+struct behavior *b;
+tREAL **pointlist;
+tREAL **pointattriblist;
+int **pointmarkerlist;
+#endif /* not ANSI_DECLARATORS */
+
+#else /* not TRILIBRARY */
+
+#ifdef ANSI_DECLARATORS
+void writenodes(struct mesh *m, struct behavior *b, char *nodefilename,
+ int argc, char **argv)
+#else /* not ANSI_DECLARATORS */
+void writenodes(m, b, nodefilename, argc, argv)
+struct mesh *m;
+struct behavior *b;
+char *nodefilename;
+int argc;
+char **argv;
+#endif /* not ANSI_DECLARATORS */
+
+#endif /* not TRILIBRARY */
+
+{
+#ifdef TRILIBRARY
+ tREAL *plist;
+ tREAL *palist;
+ int *pmlist;
+ int coordindex;
+ int attribindex;
+#else /* not TRILIBRARY */
+ FILE *outfile;
+#endif /* not TRILIBRARY */
+ vertex vertexloop;
+ long outvertices;
+ int vertexnumber;
+ int i;
+
+ if (b->jettison) {
+ outvertices = m->vertices.items - m->undeads;
+ } else {
+ outvertices = m->vertices.items;
+ }
+
+#ifdef TRILIBRARY
+ if (!b->quiet) {
+ printf("Writing vertices.\n");
+ }
+ /* Allocate memory for output vertices if necessary. */
+ if (*pointlist == (tREAL *) NULL) {
+ *pointlist = (tREAL *) trimalloc((int) (outvertices * 2 * sizeof(tREAL)));
+ }
+ /* Allocate memory for output vertex attributes if necessary. */
+ if ((m->nextras > 0) && (*pointattriblist == (tREAL *) NULL)) {
+ *pointattriblist = (tREAL *) trimalloc((int) (outvertices * m->nextras *
+ sizeof(tREAL)));
+ }
+ /* Allocate memory for output vertex markers if necessary. */
+ if (!b->nobound && (*pointmarkerlist == (int *) NULL)) {
+ *pointmarkerlist = (int *) trimalloc((int) (outvertices * sizeof(int)));
+ }
+ plist = *pointlist;
+ palist = *pointattriblist;
+ pmlist = *pointmarkerlist;
+ coordindex = 0;
+ attribindex = 0;
+#else /* not TRILIBRARY */
+ if (!b->quiet) {
+ printf("Writing %s.\n", nodefilename);
+ }
+ outfile = fopen(nodefilename, "w");
+ if (outfile == (FILE *) NULL) {
+ printf(" Error: Cannot create file %s.\n", nodefilename);
+ triexit(1);
+ }
+ /* Number of vertices, number of dimensions, number of vertex attributes, */
+ /* and number of boundary markers (zero or one). */
+ fprintf(outfile, "%ld %d %d %d\n", outvertices, m->mesh_dim,
+ m->nextras, 1 - b->nobound);
+#endif /* not TRILIBRARY */
+
+ traversalinit(&m->vertices);
+ vertexnumber = b->firstnumber;
+ vertexloop = vertextraverse(m);
+ while (vertexloop != (vertex) NULL) {
+ if (!b->jettison || (vertextype(vertexloop) != UNDEADVERTEX)) {
+#ifdef TRILIBRARY
+ /* X and y coordinates. */
+ plist[coordindex++] = vertexloop[0];
+ plist[coordindex++] = vertexloop[1];
+ /* Vertex attributes. */
+ for (i = 0; i < m->nextras; i++) {
+ palist[attribindex++] = vertexloop[2 + i];
+ }
+ if (!b->nobound) {
+ /* Copy the boundary marker. */
+ pmlist[vertexnumber - b->firstnumber] = vertexmark(vertexloop);
+ }
+#else /* not TRILIBRARY */
+ /* Vertex number, x and y coordinates. */
+ fprintf(outfile, "%4d %.17g %.17g", vertexnumber, vertexloop[0],
+ vertexloop[1]);
+ for (i = 0; i < m->nextras; i++) {
+ /* Write an attribute. */
+ fprintf(outfile, " %.17g", vertexloop[i + 2]);
+ }
+ if (b->nobound) {
+ fprintf(outfile, "\n");
+ } else {
+ /* Write the boundary marker. */
+ fprintf(outfile, " %d\n", vertexmark(vertexloop));
+ }
+#endif /* not TRILIBRARY */
+
+ setvertexmark(vertexloop, vertexnumber);
+ vertexnumber++;
+ }
+ vertexloop = vertextraverse(m);
+ }
+
+#ifndef TRILIBRARY
+ finishfile(outfile, argc, argv);
+#endif /* not TRILIBRARY */
+}
+
+/*****************************************************************************/
+/* */
+/* numbernodes() Number the vertices. */
+/* */
+/* Each vertex is = vec3ed a marker equal to its number. */
+/* */
+/* Used when writenodes() is not called because no .node file is written. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void numbernodes(struct mesh *m, struct behavior *b)
+#else /* not ANSI_DECLARATORS */
+void numbernodes(m, b)
+struct mesh *m;
+struct behavior *b;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ vertex vertexloop;
+ int vertexnumber;
+
+ traversalinit(&m->vertices);
+ vertexnumber = b->firstnumber;
+ vertexloop = vertextraverse(m);
+ while (vertexloop != (vertex) NULL) {
+ setvertexmark(vertexloop, vertexnumber);
+ if (!b->jettison || (vertextype(vertexloop) != UNDEADVERTEX)) {
+ vertexnumber++;
+ }
+ vertexloop = vertextraverse(m);
+ }
+}
+
+/*****************************************************************************/
+/* */
+/* writeelements() Write the triangles to an .ele file. */
+/* */
+/*****************************************************************************/
+
+#ifdef TRILIBRARY
+
+#ifdef ANSI_DECLARATORS
+void writeelements(struct mesh *m, struct behavior *b,
+ int **trianglelist, tREAL **triangleattriblist)
+#else /* not ANSI_DECLARATORS */
+void writeelements(m, b, trianglelist, triangleattriblist)
+struct mesh *m;
+struct behavior *b;
+int **trianglelist;
+tREAL **triangleattriblist;
+#endif /* not ANSI_DECLARATORS */
+
+#else /* not TRILIBRARY */
+
+#ifdef ANSI_DECLARATORS
+void writeelements(struct mesh *m, struct behavior *b, char *elefilename,
+ int argc, char **argv)
+#else /* not ANSI_DECLARATORS */
+void writeelements(m, b, elefilename, argc, argv)
+struct mesh *m;
+struct behavior *b;
+char *elefilename;
+int argc;
+char **argv;
+#endif /* not ANSI_DECLARATORS */
+
+#endif /* not TRILIBRARY */
+
+{
+#ifdef TRILIBRARY
+ int *tlist;
+ tREAL *talist;
+ int vertexindex;
+ int attribindex;
+#else /* not TRILIBRARY */
+ FILE *outfile;
+#endif /* not TRILIBRARY */
+ struct otri triangleloop;
+ vertex p1, p2, p3;
+ vertex mid1, mid2, mid3;
+ long elementnumber;
+ int i;
+
+#ifdef TRILIBRARY
+ if (!b->quiet) {
+ printf("Writing triangles.\n");
+ }
+ /* Allocate memory for output triangles if necessary. */
+ if (*trianglelist == (int *) NULL) {
+ *trianglelist = (int *) trimalloc((int) (m->triangles.items *
+ ((b->order + 1) * (b->order + 2) /
+ 2) * sizeof(int)));
+ }
+ /* Allocate memory for output triangle attributes if necessary. */
+ if ((m->eextras > 0) && (*triangleattriblist == (tREAL *) NULL)) {
+ *triangleattriblist = (tREAL *) trimalloc((int) (m->triangles.items *
+ m->eextras *
+ sizeof(tREAL)));
+ }
+ tlist = *trianglelist;
+ talist = *triangleattriblist;
+ vertexindex = 0;
+ attribindex = 0;
+#else /* not TRILIBRARY */
+ if (!b->quiet) {
+ printf("Writing %s.\n", elefilename);
+ }
+ outfile = fopen(elefilename, "w");
+ if (outfile == (FILE *) NULL) {
+ printf(" Error: Cannot create file %s.\n", elefilename);
+ triexit(1);
+ }
+ /* Number of triangles, vertices per triangle, attributes per triangle. */
+ fprintf(outfile, "%ld %d %d\n", m->triangles.items,
+ (b->order + 1) * (b->order + 2) / 2, m->eextras);
+#endif /* not TRILIBRARY */
+
+ traversalinit(&m->triangles);
+ triangleloop.tri = triangletraverse(m);
+ triangleloop.orient = 0;
+ elementnumber = b->firstnumber;
+ while (triangleloop.tri != (triangle *) NULL) {
+ org(triangleloop, p1);
+ dest(triangleloop, p2);
+ apex(triangleloop, p3);
+ if (b->order == 1) {
+#ifdef TRILIBRARY
+ tlist[vertexindex++] = vertexmark(p1);
+ tlist[vertexindex++] = vertexmark(p2);
+ tlist[vertexindex++] = vertexmark(p3);
+#else /* not TRILIBRARY */
+ /* Triangle number, indices for three vertices. */
+ fprintf(outfile, "%4ld %4d %4d %4d", elementnumber,
+ vertexmark(p1), vertexmark(p2), vertexmark(p3));
+#endif /* not TRILIBRARY */
+ } else {
+ mid1 = (vertex) triangleloop.tri[m->highorderindex + 1];
+ mid2 = (vertex) triangleloop.tri[m->highorderindex + 2];
+ mid3 = (vertex) triangleloop.tri[m->highorderindex];
+#ifdef TRILIBRARY
+ tlist[vertexindex++] = vertexmark(p1);
+ tlist[vertexindex++] = vertexmark(p2);
+ tlist[vertexindex++] = vertexmark(p3);
+ tlist[vertexindex++] = vertexmark(mid1);
+ tlist[vertexindex++] = vertexmark(mid2);
+ tlist[vertexindex++] = vertexmark(mid3);
+#else /* not TRILIBRARY */
+ /* Triangle number, indices for six vertices. */
+ fprintf(outfile, "%4ld %4d %4d %4d %4d %4d %4d", elementnumber,
+ vertexmark(p1), vertexmark(p2), vertexmark(p3), vertexmark(mid1),
+ vertexmark(mid2), vertexmark(mid3));
+#endif /* not TRILIBRARY */
+ }
+
+#ifdef TRILIBRARY
+ for (i = 0; i < m->eextras; i++) {
+ talist[attribindex++] = elemattribute(triangleloop, i);
+ }
+#else /* not TRILIBRARY */
+ for (i = 0; i < m->eextras; i++) {
+ fprintf(outfile, " %.17g", elemattribute(triangleloop, i));
+ }
+ fprintf(outfile, "\n");
+#endif /* not TRILIBRARY */
+
+ triangleloop.tri = triangletraverse(m);
+ elementnumber++;
+ }
+
+#ifndef TRILIBRARY
+ finishfile(outfile, argc, argv);
+#endif /* not TRILIBRARY */
+}
+
+/*****************************************************************************/
+/* */
+/* writepoly() Write the segments and holes to a .poly file. */
+/* */
+/*****************************************************************************/
+
+#ifdef TRILIBRARY
+
+#ifdef ANSI_DECLARATORS
+void writepoly(struct mesh *m, struct behavior *b,
+ int **segmentlist, int **segmentmarkerlist)
+#else /* not ANSI_DECLARATORS */
+void writepoly(m, b, segmentlist, segmentmarkerlist)
+struct mesh *m;
+struct behavior *b;
+int **segmentlist;
+int **segmentmarkerlist;
+#endif /* not ANSI_DECLARATORS */
+
+#else /* not TRILIBRARY */
+
+#ifdef ANSI_DECLARATORS
+void writepoly(struct mesh *m, struct behavior *b, char *polyfilename,
+ tREAL *holelist, int holes, tREAL *regionlist, int regions,
+ int argc, char **argv)
+#else /* not ANSI_DECLARATORS */
+void writepoly(m, b, polyfilename, holelist, holes, regionlist, regions,
+ argc, argv)
+struct mesh *m;
+struct behavior *b;
+char *polyfilename;
+tREAL *holelist;
+int holes;
+tREAL *regionlist;
+int regions;
+int argc;
+char **argv;
+#endif /* not ANSI_DECLARATORS */
+
+#endif /* not TRILIBRARY */
+
+{
+#ifdef TRILIBRARY
+ int *slist;
+ int *smlist;
+ int index;
+#else /* not TRILIBRARY */
+ FILE *outfile;
+ long holenumber, regionnumber;
+#endif /* not TRILIBRARY */
+ struct osub subsegloop;
+ vertex endpoint1, endpoint2;
+ long subsegnumber;
+
+#ifdef TRILIBRARY
+ if (!b->quiet) {
+ printf("Writing segments.\n");
+ }
+ /* Allocate memory for output segments if necessary. */
+ if (*segmentlist == (int *) NULL) {
+ *segmentlist = (int *) trimalloc((int) (m->subsegs.items * 2 *
+ sizeof(int)));
+ }
+ /* Allocate memory for output segment markers if necessary. */
+ if (!b->nobound && (*segmentmarkerlist == (int *) NULL)) {
+ *segmentmarkerlist = (int *) trimalloc((int) (m->subsegs.items *
+ sizeof(int)));
+ }
+ slist = *segmentlist;
+ smlist = *segmentmarkerlist;
+ index = 0;
+#else /* not TRILIBRARY */
+ if (!b->quiet) {
+ printf("Writing %s.\n", polyfilename);
+ }
+ outfile = fopen(polyfilename, "w");
+ if (outfile == (FILE *) NULL) {
+ printf(" Error: Cannot create file %s.\n", polyfilename);
+ triexit(1);
+ }
+ /* The zero indicates that the vertices are in a separate .node file. */
+ /* Followed by number of dimensions, number of vertex attributes, */
+ /* and number of boundary markers (zero or one). */
+ fprintf(outfile, "%d %d %d %d\n", 0, m->mesh_dim, m->nextras,
+ 1 - b->nobound);
+ /* Number of segments, number of boundary markers (zero or one). */
+ fprintf(outfile, "%ld %d\n", m->subsegs.items, 1 - b->nobound);
+#endif /* not TRILIBRARY */
+
+ traversalinit(&m->subsegs);
+ subsegloop.ss = subsegtraverse(m);
+ subsegloop.ssorient = 0;
+ subsegnumber = b->firstnumber;
+ while (subsegloop.ss != (subseg *) NULL) {
+ sorg(subsegloop, endpoint1);
+ sdest(subsegloop, endpoint2);
+#ifdef TRILIBRARY
+ /* Copy indices of the segment's two endpoints. */
+ slist[index++] = vertexmark(endpoint1);
+ slist[index++] = vertexmark(endpoint2);
+ if (!b->nobound) {
+ /* Copy the boundary marker. */
+ smlist[subsegnumber - b->firstnumber] = mark(subsegloop);
+ }
+#else /* not TRILIBRARY */
+ /* Segment number, indices of its two endpoints, and possibly a marker. */
+ if (b->nobound) {
+ fprintf(outfile, "%4ld %4d %4d\n", subsegnumber,
+ vertexmark(endpoint1), vertexmark(endpoint2));
+ } else {
+ fprintf(outfile, "%4ld %4d %4d %4d\n", subsegnumber,
+ vertexmark(endpoint1), vertexmark(endpoint2), mark(subsegloop));
+ }
+#endif /* not TRILIBRARY */
+
+ subsegloop.ss = subsegtraverse(m);
+ subsegnumber++;
+ }
+
+#ifndef TRILIBRARY
+#ifndef CDT_ONLY
+ fprintf(outfile, "%d\n", holes);
+ if (holes > 0) {
+ for (holenumber = 0; holenumber < holes; holenumber++) {
+ /* Hole number, x and y coordinates. */
+ fprintf(outfile, "%4ld %.17g %.17g\n", b->firstnumber + holenumber,
+ holelist[2 * holenumber], holelist[2 * holenumber + 1]);
+ }
+ }
+ if (regions > 0) {
+ fprintf(outfile, "%d\n", regions);
+ for (regionnumber = 0; regionnumber < regions; regionnumber++) {
+ /* Region number, x and y coordinates, attribute, maximum area. */
+ fprintf(outfile, "%4ld %.17g %.17g %.17g %.17g\n",
+ b->firstnumber + regionnumber,
+ regionlist[4 * regionnumber], regionlist[4 * regionnumber + 1],
+ regionlist[4 * regionnumber + 2],
+ regionlist[4 * regionnumber + 3]);
+ }
+ }
+#endif /* not CDT_ONLY */
+
+ finishfile(outfile, argc, argv);
+#endif /* not TRILIBRARY */
+}
+
+/*****************************************************************************/
+/* */
+/* writeedges() Write the edges to an .edge file. */
+/* */
+/*****************************************************************************/
+
+#ifdef TRILIBRARY
+
+#ifdef ANSI_DECLARATORS
+void writeedges(struct mesh *m, struct behavior *b,
+ int **edgelist, int **edgemarkerlist)
+#else /* not ANSI_DECLARATORS */
+void writeedges(m, b, edgelist, edgemarkerlist)
+struct mesh *m;
+struct behavior *b;
+int **edgelist;
+int **edgemarkerlist;
+#endif /* not ANSI_DECLARATORS */
+
+#else /* not TRILIBRARY */
+
+#ifdef ANSI_DECLARATORS
+void writeedges(struct mesh *m, struct behavior *b, char *edgefilename,
+ int argc, char **argv)
+#else /* not ANSI_DECLARATORS */
+void writeedges(m, b, edgefilename, argc, argv)
+struct mesh *m;
+struct behavior *b;
+char *edgefilename;
+int argc;
+char **argv;
+#endif /* not ANSI_DECLARATORS */
+
+#endif /* not TRILIBRARY */
+
+{
+#ifdef TRILIBRARY
+ int *elist;
+ int *emlist;
+ int index;
+#else /* not TRILIBRARY */
+ FILE *outfile;
+#endif /* not TRILIBRARY */
+ struct otri triangleloop, trisym;
+ struct osub checkmark;
+ vertex p1, p2;
+ long edgenumber;
+ triangle ptr; /* Temporary variable used by sym(). */
+ subseg sptr; /* Temporary variable used by tspivot(). */
+
+#ifdef TRILIBRARY
+ if (!b->quiet) {
+ printf("Writing edges.\n");
+ }
+ /* Allocate memory for edges if necessary. */
+ if (*edgelist == (int *) NULL) {
+ *edgelist = (int *) trimalloc((int) (m->edges * 2 * sizeof(int)));
+ }
+ /* Allocate memory for edge markers if necessary. */
+ if (!b->nobound && (*edgemarkerlist == (int *) NULL)) {
+ *edgemarkerlist = (int *) trimalloc((int) (m->edges * sizeof(int)));
+ }
+ elist = *edgelist;
+ emlist = *edgemarkerlist;
+ index = 0;
+#else /* not TRILIBRARY */
+ if (!b->quiet) {
+ printf("Writing %s.\n", edgefilename);
+ }
+ outfile = fopen(edgefilename, "w");
+ if (outfile == (FILE *) NULL) {
+ printf(" Error: Cannot create file %s.\n", edgefilename);
+ triexit(1);
+ }
+ /* Number of edges, number of boundary markers (zero or one). */
+ fprintf(outfile, "%ld %d\n", m->edges, 1 - b->nobound);
+#endif /* not TRILIBRARY */
+
+ traversalinit(&m->triangles);
+ triangleloop.tri = triangletraverse(m);
+ edgenumber = b->firstnumber;
+ /* To loop over the set of edges, loop over all triangles, and look at */
+ /* the three edges of each triangle. If there isn't another triangle */
+ /* adjacent to the edge, operate on the edge. If there is another */
+ /* adjacent triangle, operate on the edge only if the current triangle */
+ /* has a smaller pointer than its neighbor. This way, each edge is */
+ /* considered only once. */
+ while (triangleloop.tri != (triangle *) NULL) {
+ for (triangleloop.orient = 0; triangleloop.orient < 3;
+ triangleloop.orient++) {
+ sym(triangleloop, trisym);
+ if ((triangleloop.tri < trisym.tri) || (trisym.tri == m->dummytri)) {
+ org(triangleloop, p1);
+ dest(triangleloop, p2);
+#ifdef TRILIBRARY
+ elist[index++] = vertexmark(p1);
+ elist[index++] = vertexmark(p2);
+#endif /* TRILIBRARY */
+ if (b->nobound) {
+#ifndef TRILIBRARY
+ /* Edge number, indices of two endpoints. */
+ fprintf(outfile, "%4ld %d %d\n", edgenumber,
+ vertexmark(p1), vertexmark(p2));
+#endif /* not TRILIBRARY */
+ } else {
+ /* Edge number, indices of two endpoints, and a boundary marker. */
+ /* If there's no subsegment, the boundary marker is zero. */
+ if (b->usesegments) {
+ tspivot(triangleloop, checkmark);
+ if (checkmark.ss == m->dummysub) {
+#ifdef TRILIBRARY
+ emlist[edgenumber - b->firstnumber] = 0;
+#else /* not TRILIBRARY */
+ fprintf(outfile, "%4ld %d %d %d\n", edgenumber,
+ vertexmark(p1), vertexmark(p2), 0);
+#endif /* not TRILIBRARY */
+ } else {
+#ifdef TRILIBRARY
+ emlist[edgenumber - b->firstnumber] = mark(checkmark);
+#else /* not TRILIBRARY */
+ fprintf(outfile, "%4ld %d %d %d\n", edgenumber,
+ vertexmark(p1), vertexmark(p2), mark(checkmark));
+#endif /* not TRILIBRARY */
+ }
+ } else {
+#ifdef TRILIBRARY
+ emlist[edgenumber - b->firstnumber] = trisym.tri == m->dummytri;
+#else /* not TRILIBRARY */
+ fprintf(outfile, "%4ld %d %d %d\n", edgenumber,
+ vertexmark(p1), vertexmark(p2), trisym.tri == m->dummytri);
+#endif /* not TRILIBRARY */
+ }
+ }
+ edgenumber++;
+ }
+ }
+ triangleloop.tri = triangletraverse(m);
+ }
+
+#ifndef TRILIBRARY
+ finishfile(outfile, argc, argv);
+#endif /* not TRILIBRARY */
+}
+
+/*****************************************************************************/
+/* */
+/* writevoronoi() Write the Voronoi diagram to a .v.node and .v.edge */
+/* file. */
+/* */
+/* The Voronoi diagram is the geometric dual of the Delaunay triangulation. */
+/* Hence, the Voronoi vertices are listed by traversing the Delaunay */
+/* triangles, and the Voronoi edges are listed by traversing the Delaunay */
+/* edges. */
+/* */
+/* WARNING: In order to = vec3 numbers to the Voronoi vertices, this */
+/* procedure messes up the subsegments or the extra nodes of every */
+/* element. Hence, you should call this procedure last. */
+/* */
+/*****************************************************************************/
+
+#ifdef TRILIBRARY
+
+#ifdef ANSI_DECLARATORS
+void writevoronoi(struct mesh *m, struct behavior *b, tREAL **vpointlist,
+ tREAL **vpointattriblist, int **vpointmarkerlist,
+ int **vedgelist, int **vedgemarkerlist, tREAL **vnormlist)
+#else /* not ANSI_DECLARATORS */
+void writevoronoi(m, b, vpointlist, vpointattriblist, vpointmarkerlist,
+ vedgelist, vedgemarkerlist, vnormlist)
+struct mesh *m;
+struct behavior *b;
+tREAL **vpointlist;
+tREAL **vpointattriblist;
+int **vpointmarkerlist;
+int **vedgelist;
+int **vedgemarkerlist;
+tREAL **vnormlist;
+#endif /* not ANSI_DECLARATORS */
+
+#else /* not TRILIBRARY */
+
+#ifdef ANSI_DECLARATORS
+void writevoronoi(struct mesh *m, struct behavior *b, char *vnodefilename,
+ char *vedgefilename, int argc, char **argv)
+#else /* not ANSI_DECLARATORS */
+void writevoronoi(m, b, vnodefilename, vedgefilename, argc, argv)
+struct mesh *m;
+struct behavior *b;
+char *vnodefilename;
+char *vedgefilename;
+int argc;
+char **argv;
+#endif /* not ANSI_DECLARATORS */
+
+#endif /* not TRILIBRARY */
+
+{
+#ifdef TRILIBRARY
+ tREAL *plist;
+ tREAL *palist;
+ int *elist;
+ tREAL *normlist;
+ int coordindex;
+ int attribindex;
+#else /* not TRILIBRARY */
+ FILE *outfile;
+#endif /* not TRILIBRARY */
+ struct otri triangleloop, trisym;
+ vertex torg, tdest, tapex;
+ tREAL circumcenter[2];
+ tREAL xi, eta;
+ long vnodenumber, vedgenumber;
+ int p1, p2;
+ int i;
+ triangle ptr; /* Temporary variable used by sym(). */
+
+#ifdef TRILIBRARY
+ if (!b->quiet) {
+ printf("Writing Voronoi vertices.\n");
+ }
+ /* Allocate memory for Voronoi vertices if necessary. */
+ if (*vpointlist == (tREAL *) NULL) {
+ *vpointlist = (tREAL *) trimalloc((int) (m->triangles.items * 2 *
+ sizeof(tREAL)));
+ }
+ /* Allocate memory for Voronoi vertex attributes if necessary. */
+ if (*vpointattriblist == (tREAL *) NULL) {
+ *vpointattriblist = (tREAL *) trimalloc((int) (m->triangles.items *
+ m->nextras * sizeof(tREAL)));
+ }
+ *vpointmarkerlist = (int *) NULL;
+ plist = *vpointlist;
+ palist = *vpointattriblist;
+ coordindex = 0;
+ attribindex = 0;
+#else /* not TRILIBRARY */
+ if (!b->quiet) {
+ printf("Writing %s.\n", vnodefilename);
+ }
+ outfile = fopen(vnodefilename, "w");
+ if (outfile == (FILE *) NULL) {
+ printf(" Error: Cannot create file %s.\n", vnodefilename);
+ triexit(1);
+ }
+ /* Number of triangles, two dimensions, number of vertex attributes, */
+ /* no markers. */
+ fprintf(outfile, "%ld %d %d %d\n", m->triangles.items, 2, m->nextras, 0);
+#endif /* not TRILIBRARY */
+
+ traversalinit(&m->triangles);
+ triangleloop.tri = triangletraverse(m);
+ triangleloop.orient = 0;
+ vnodenumber = b->firstnumber;
+ while (triangleloop.tri != (triangle *) NULL) {
+ org(triangleloop, torg);
+ dest(triangleloop, tdest);
+ apex(triangleloop, tapex);
+ findcircumcenter(m, b, torg, tdest, tapex, circumcenter, &xi, &eta, 0);
+#ifdef TRILIBRARY
+ /* X and y coordinates. */
+ plist[coordindex++] = circumcenter[0];
+ plist[coordindex++] = circumcenter[1];
+ for (i = 2; i < 2 + m->nextras; i++) {
+ /* Interpolate the vertex attributes at the circumcenter. */
+ palist[attribindex++] = torg[i] + xi * (tdest[i] - torg[i])
+ + eta * (tapex[i] - torg[i]);
+ }
+#else /* not TRILIBRARY */
+ /* Voronoi vertex number, x and y coordinates. */
+ fprintf(outfile, "%4ld %.17g %.17g", vnodenumber, circumcenter[0],
+ circumcenter[1]);
+ for (i = 2; i < 2 + m->nextras; i++) {
+ /* Interpolate the vertex attributes at the circumcenter. */
+ fprintf(outfile, " %.17g", torg[i] + xi * (tdest[i] - torg[i])
+ + eta * (tapex[i] - torg[i]));
+ }
+ fprintf(outfile, "\n");
+#endif /* not TRILIBRARY */
+
+ * (int *) (triangleloop.tri + 6) = (int) vnodenumber;
+ triangleloop.tri = triangletraverse(m);
+ vnodenumber++;
+ }
+
+#ifndef TRILIBRARY
+ finishfile(outfile, argc, argv);
+#endif /* not TRILIBRARY */
+
+#ifdef TRILIBRARY
+ if (!b->quiet) {
+ printf("Writing Voronoi edges.\n");
+ }
+ /* Allocate memory for output Voronoi edges if necessary. */
+ if (*vedgelist == (int *) NULL) {
+ *vedgelist = (int *) trimalloc((int) (m->edges * 2 * sizeof(int)));
+ }
+ *vedgemarkerlist = (int *) NULL;
+ /* Allocate memory for output Voronoi norms if necessary. */
+ if (*vnormlist == (tREAL *) NULL) {
+ *vnormlist = (tREAL *) trimalloc((int) (m->edges * 2 * sizeof(tREAL)));
+ }
+ elist = *vedgelist;
+ normlist = *vnormlist;
+ coordindex = 0;
+#else /* not TRILIBRARY */
+ if (!b->quiet) {
+ printf("Writing %s.\n", vedgefilename);
+ }
+ outfile = fopen(vedgefilename, "w");
+ if (outfile == (FILE *) NULL) {
+ printf(" Error: Cannot create file %s.\n", vedgefilename);
+ triexit(1);
+ }
+ /* Number of edges, zero boundary markers. */
+ fprintf(outfile, "%ld %d\n", m->edges, 0);
+#endif /* not TRILIBRARY */
+
+ traversalinit(&m->triangles);
+ triangleloop.tri = triangletraverse(m);
+ vedgenumber = b->firstnumber;
+ /* To loop over the set of edges, loop over all triangles, and look at */
+ /* the three edges of each triangle. If there isn't another triangle */
+ /* adjacent to the edge, operate on the edge. If there is another */
+ /* adjacent triangle, operate on the edge only if the current triangle */
+ /* has a smaller pointer than its neighbor. This way, each edge is */
+ /* considered only once. */
+ while (triangleloop.tri != (triangle *) NULL) {
+ for (triangleloop.orient = 0; triangleloop.orient < 3;
+ triangleloop.orient++) {
+ sym(triangleloop, trisym);
+ if ((triangleloop.tri < trisym.tri) || (trisym.tri == m->dummytri)) {
+ /* Find the number of this triangle (and Voronoi vertex). */
+ p1 = * (int *) (triangleloop.tri + 6);
+ if (trisym.tri == m->dummytri) {
+ org(triangleloop, torg);
+ dest(triangleloop, tdest);
+#ifdef TRILIBRARY
+ /* Copy an infinite ray. Index of one endpoint, and -1. */
+ elist[coordindex] = p1;
+ normlist[coordindex++] = tdest[1] - torg[1];
+ elist[coordindex] = -1;
+ normlist[coordindex++] = torg[0] - tdest[0];
+#else /* not TRILIBRARY */
+ /* Write an infinite ray. Edge number, index of one endpoint, -1, */
+ /* and x and y coordinates of a vector representing the */
+ /* direction of the ray. */
+ fprintf(outfile, "%4ld %d %d %.17g %.17g\n", vedgenumber,
+ p1, -1, tdest[1] - torg[1], torg[0] - tdest[0]);
+#endif /* not TRILIBRARY */
+ } else {
+ /* Find the number of the adjacent triangle (and Voronoi vertex). */
+ p2 = * (int *) (trisym.tri + 6);
+ /* Finite edge. Write indices of two endpoints. */
+#ifdef TRILIBRARY
+ elist[coordindex] = p1;
+ normlist[coordindex++] = 0.0f;
+ elist[coordindex] = p2;
+ normlist[coordindex++] = 0.0f;
+#else /* not TRILIBRARY */
+ fprintf(outfile, "%4ld %d %d\n", vedgenumber, p1, p2);
+#endif /* not TRILIBRARY */
+ }
+ vedgenumber++;
+ }
+ }
+ triangleloop.tri = triangletraverse(m);
+ }
+
+#ifndef TRILIBRARY
+ finishfile(outfile, argc, argv);
+#endif /* not TRILIBRARY */
+}
+
+#ifdef TRILIBRARY
+
+#ifdef ANSI_DECLARATORS
+void writeneighbors(struct mesh *m, struct behavior *b, int **neighborlist)
+#else /* not ANSI_DECLARATORS */
+void writeneighbors(m, b, neighborlist)
+struct mesh *m;
+struct behavior *b;
+int **neighborlist;
+#endif /* not ANSI_DECLARATORS */
+
+#else /* not TRILIBRARY */
+
+#ifdef ANSI_DECLARATORS
+void writeneighbors(struct mesh *m, struct behavior *b, char *neighborfilename,
+ int argc, char **argv)
+#else /* not ANSI_DECLARATORS */
+void writeneighbors(m, b, neighborfilename, argc, argv)
+struct mesh *m;
+struct behavior *b;
+char *neighborfilename;
+int argc;
+char **argv;
+#endif /* not ANSI_DECLARATORS */
+
+#endif /* not TRILIBRARY */
+
+{
+#ifdef TRILIBRARY
+ int *nlist;
+ int index;
+#else /* not TRILIBRARY */
+ FILE *outfile;
+#endif /* not TRILIBRARY */
+ struct otri triangleloop, trisym;
+ long elementnumber;
+ int neighbor1, neighbor2, neighbor3;
+ triangle ptr; /* Temporary variable used by sym(). */
+
+#ifdef TRILIBRARY
+ if (!b->quiet) {
+ printf("Writing neighbors.\n");
+ }
+ /* Allocate memory for neighbors if necessary. */
+ if (*neighborlist == (int *) NULL) {
+ *neighborlist = (int *) trimalloc((int) (m->triangles.items * 3 *
+ sizeof(int)));
+ }
+ nlist = *neighborlist;
+ index = 0;
+#else /* not TRILIBRARY */
+ if (!b->quiet) {
+ printf("Writing %s.\n", neighborfilename);
+ }
+ outfile = fopen(neighborfilename, "w");
+ if (outfile == (FILE *) NULL) {
+ printf(" Error: Cannot create file %s.\n", neighborfilename);
+ triexit(1);
+ }
+ /* Number of triangles, three neighbors per triangle. */
+ fprintf(outfile, "%ld %d\n", m->triangles.items, 3);
+#endif /* not TRILIBRARY */
+
+ traversalinit(&m->triangles);
+ triangleloop.tri = triangletraverse(m);
+ triangleloop.orient = 0;
+ elementnumber = b->firstnumber;
+ while (triangleloop.tri != (triangle *) NULL) {
+ * (int *) (triangleloop.tri + 6) = (int) elementnumber;
+ triangleloop.tri = triangletraverse(m);
+ elementnumber++;
+ }
+ * (int *) (m->dummytri + 6) = -1;
+
+ traversalinit(&m->triangles);
+ triangleloop.tri = triangletraverse(m);
+ elementnumber = b->firstnumber;
+ while (triangleloop.tri != (triangle *) NULL) {
+ triangleloop.orient = 1;
+ sym(triangleloop, trisym);
+ neighbor1 = * (int *) (trisym.tri + 6);
+ triangleloop.orient = 2;
+ sym(triangleloop, trisym);
+ neighbor2 = * (int *) (trisym.tri + 6);
+ triangleloop.orient = 0;
+ sym(triangleloop, trisym);
+ neighbor3 = * (int *) (trisym.tri + 6);
+#ifdef TRILIBRARY
+ nlist[index++] = neighbor1;
+ nlist[index++] = neighbor2;
+ nlist[index++] = neighbor3;
+#else /* not TRILIBRARY */
+ /* Triangle number, neighboring triangle numbers. */
+ fprintf(outfile, "%4ld %d %d %d\n", elementnumber,
+ neighbor1, neighbor2, neighbor3);
+#endif /* not TRILIBRARY */
+
+ triangleloop.tri = triangletraverse(m);
+ elementnumber++;
+ }
+
+#ifndef TRILIBRARY
+ finishfile(outfile, argc, argv);
+#endif /* not TRILIBRARY */
+}
+
+/*****************************************************************************/
+/* */
+/* writeoff() Write the triangulation to an .off file. */
+/* */
+/* OFF stands for the Object File Format, a format used by the Geometry */
+/* Center's Geomview package. */
+/* */
+/*****************************************************************************/
+
+#ifndef TRILIBRARY
+
+#ifdef ANSI_DECLARATORS
+void writeoff(struct mesh *m, struct behavior *b, char *offfilename,
+ int argc, char **argv)
+#else /* not ANSI_DECLARATORS */
+void writeoff(m, b, offfilename, argc, argv)
+struct mesh *m;
+struct behavior *b;
+char *offfilename;
+int argc;
+char **argv;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ FILE *outfile;
+ struct otri triangleloop;
+ vertex vertexloop;
+ vertex p1, p2, p3;
+ long outvertices;
+
+ if (!b->quiet) {
+ printf("Writing %s.\n", offfilename);
+ }
+
+ if (b->jettison) {
+ outvertices = m->vertices.items - m->undeads;
+ } else {
+ outvertices = m->vertices.items;
+ }
+
+ outfile = fopen(offfilename, "w");
+ if (outfile == (FILE *) NULL) {
+ printf(" Error: Cannot create file %s.\n", offfilename);
+ triexit(1);
+ }
+ /* Number of vertices, triangles, and edges. */
+ fprintf(outfile, "OFF\n%ld %ld %ld\n", outvertices, m->triangles.items,
+ m->edges);
+
+ /* Write the vertices. */
+ traversalinit(&m->vertices);
+ vertexloop = vertextraverse(m);
+ while (vertexloop != (vertex) NULL) {
+ if (!b->jettison || (vertextype(vertexloop) != UNDEADVERTEX)) {
+ /* The "0.0" is here because the OFF format uses 3D coordinates. */
+ fprintf(outfile, " %.17g %.17g %.17g\n", vertexloop[0], vertexloop[1],
+ 0.0f);
+ }
+ vertexloop = vertextraverse(m);
+ }
+
+ /* Write the triangles. */
+ traversalinit(&m->triangles);
+ triangleloop.tri = triangletraverse(m);
+ triangleloop.orient = 0;
+ while (triangleloop.tri != (triangle *) NULL) {
+ org(triangleloop, p1);
+ dest(triangleloop, p2);
+ apex(triangleloop, p3);
+ /* The "3" means a three-vertex polygon. */
+ fprintf(outfile, " 3 %4d %4d %4d\n", vertexmark(p1) - b->firstnumber,
+ vertexmark(p2) - b->firstnumber, vertexmark(p3) - b->firstnumber);
+ triangleloop.tri = triangletraverse(m);
+ }
+ finishfile(outfile, argc, argv);
+}
+
+#endif /* not TRILIBRARY */
+
+/** **/
+/** **/
+/********* File I/O routines end here *********/
+
+/*****************************************************************************/
+/* */
+/* quality_statistics() Print statistics about the quality of the mesh. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void quality_statistics(struct mesh *m, struct behavior *b)
+#else /* not ANSI_DECLARATORS */
+void quality_statistics(m, b)
+struct mesh *m;
+struct behavior *b;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ struct otri triangleloop;
+ vertex p[3];
+ tREAL cossquaretable[8];
+ tREAL ratiotable[16];
+ tREAL dx[3], dy[3];
+ tREAL edgelength[3];
+ tREAL dotproduct;
+ tREAL cossquare;
+ tREAL triarea;
+ tREAL shortest, longest;
+ tREAL trilongest2;
+ tREAL smallestarea, biggestarea;
+ tREAL triminaltitude2;
+ tREAL minaltitude;
+ tREAL triaspect2;
+ tREAL worstaspect;
+ tREAL smallestangle, biggestangle;
+ tREAL radconst, degconst;
+ int angletable[18];
+ int aspecttable[16];
+ int aspectindex;
+ int tendegree;
+ int acutebiggest;
+ int i, ii, j, k;
+
+ printf("Mesh quality statistics:\n\n");
+ radconst = PI / 18.0f;
+ degconst = 180.0 / PI;
+ for (i = 0; i < 8; i++) {
+ cossquaretable[i] = cos(radconst * (tREAL) (i + 1));
+ cossquaretable[i] = cossquaretable[i] * cossquaretable[i];
+ }
+ for (i = 0; i < 18; i++) {
+ angletable[i] = 0;
+ }
+
+ ratiotable[0] = 1.5f; ratiotable[1] = 2.0f;
+ ratiotable[2] = 2.5f; ratiotable[3] = 3.0f;
+ ratiotable[4] = 4.0f; ratiotable[5] = 6.0f;
+ ratiotable[6] = 10.0f; ratiotable[7] = 15.0f;
+ ratiotable[8] = 25.0f; ratiotable[9] = 50.0f;
+ ratiotable[10] = 100.0f; ratiotable[11] = 300.0f;
+ ratiotable[12] = 1000.0f; ratiotable[13] = 10000.0f;
+ ratiotable[14] = 100000.0f; ratiotable[15] = 0.0f;
+ for (i = 0; i < 16; i++) {
+ aspecttable[i] = 0;
+ }
+
+ worstaspect = 0.0f;
+ minaltitude = m->xmax - m->xmin + m->ymax - m->ymin;
+ minaltitude = minaltitude * minaltitude;
+ shortest = minaltitude;
+ longest = 0.0f;
+ smallestarea = minaltitude;
+ biggestarea = 0.0f;
+ worstaspect = 0.0f;
+ smallestangle = 0.0f;
+ biggestangle = 2.0f;
+ acutebiggest = 1;
+
+ traversalinit(&m->triangles);
+ triangleloop.tri = triangletraverse(m);
+ triangleloop.orient = 0;
+ while (triangleloop.tri != (triangle *) NULL) {
+ org(triangleloop, p[0]);
+ dest(triangleloop, p[1]);
+ apex(triangleloop, p[2]);
+ trilongest2 = 0.0f;
+
+ for (i = 0; i < 3; i++) {
+ j = plus1mod3[i];
+ k = minus1mod3[i];
+ dx[i] = p[j][0] - p[k][0];
+ dy[i] = p[j][1] - p[k][1];
+ edgelength[i] = dx[i] * dx[i] + dy[i] * dy[i];
+ if (edgelength[i] > trilongest2) {
+ trilongest2 = edgelength[i];
+ }
+ if (edgelength[i] > longest) {
+ longest = edgelength[i];
+ }
+ if (edgelength[i] < shortest) {
+ shortest = edgelength[i];
+ }
+ }
+
+ triarea = counterclockwise(m, b, p[0], p[1], p[2]);
+ if (triarea < smallestarea) {
+ smallestarea = triarea;
+ }
+ if (triarea > biggestarea) {
+ biggestarea = triarea;
+ }
+ triminaltitude2 = triarea * triarea / trilongest2;
+ if (triminaltitude2 < minaltitude) {
+ minaltitude = triminaltitude2;
+ }
+ triaspect2 = trilongest2 / triminaltitude2;
+ if (triaspect2 > worstaspect) {
+ worstaspect = triaspect2;
+ }
+ aspectindex = 0;
+ while ((triaspect2 > ratiotable[aspectindex] * ratiotable[aspectindex])
+ && (aspectindex < 15)) {
+ aspectindex++;
+ }
+ aspecttable[aspectindex]++;
+
+ for (i = 0; i < 3; i++) {
+ j = plus1mod3[i];
+ k = minus1mod3[i];
+ dotproduct = dx[j] * dx[k] + dy[j] * dy[k];
+ cossquare = dotproduct * dotproduct / (edgelength[j] * edgelength[k]);
+ tendegree = 8;
+ for (ii = 7; ii >= 0; ii--) {
+ if (cossquare > cossquaretable[ii]) {
+ tendegree = ii;
+ }
+ }
+ if (dotproduct <= 0.0f) {
+ angletable[tendegree]++;
+ if (cossquare > smallestangle) {
+ smallestangle = cossquare;
+ }
+ if (acutebiggest && (cossquare < biggestangle)) {
+ biggestangle = cossquare;
+ }
+ } else {
+ angletable[17 - tendegree]++;
+ if (acutebiggest || (cossquare > biggestangle)) {
+ biggestangle = cossquare;
+ acutebiggest = 0;
+ }
+ }
+ }
+ triangleloop.tri = triangletraverse(m);
+ }
+
+ shortest = sqrt(shortest);
+ longest = sqrt(longest);
+ minaltitude = sqrt(minaltitude);
+ worstaspect = sqrt(worstaspect);
+ smallestarea *= 0.5f;
+ biggestarea *= 0.5f;
+ if (smallestangle >= 1.0f) {
+ smallestangle = 0.0f;
+ } else {
+ smallestangle = degconst * acos(sqrt(smallestangle));
+ }
+ if (biggestangle >= 1.0f) {
+ biggestangle = 180.0f;
+ } else {
+ if (acutebiggest) {
+ biggestangle = degconst * acos(sqrt(biggestangle));
+ } else {
+ biggestangle = 180.0 - degconst * acos(sqrt(biggestangle));
+ }
+ }
+
+ printf(" Smallest area: %16.5g | Largest area: %16.5g\n",
+ smallestarea, biggestarea);
+ printf(" Shortest edge: %16.5g | Longest edge: %16.5g\n",
+ shortest, longest);
+ printf(" Shortest altitude: %12.5g | Largest aspect ratio: %8.5g\n\n",
+ minaltitude, worstaspect);
+
+ printf(" Triangle aspect ratio histogram:\n");
+ printf(" 1.1547 - %-6.6g : %8d | %6.6g - %-6.6g : %8d\n",
+ ratiotable[0], aspecttable[0], ratiotable[7], ratiotable[8],
+ aspecttable[8]);
+ for (i = 1; i < 7; i++) {
+ printf(" %6.6g - %-6.6g : %8d | %6.6g - %-6.6g : %8d\n",
+ ratiotable[i - 1], ratiotable[i], aspecttable[i],
+ ratiotable[i + 7], ratiotable[i + 8], aspecttable[i + 8]);
+ }
+ printf(" %6.6g - %-6.6g : %8d | %6.6g - : %8d\n",
+ ratiotable[6], ratiotable[7], aspecttable[7], ratiotable[14],
+ aspecttable[15]);
+ printf(" (Aspect ratio is longest edge divided by shortest altitude)\n\n");
+
+ printf(" Smallest angle: %15.5g | Largest angle: %15.5g\n\n",
+ smallestangle, biggestangle);
+
+ printf(" Angle histogram:\n");
+ for (i = 0; i < 9; i++) {
+ printf(" %3d - %3d degrees: %8d | %3d - %3d degrees: %8d\n",
+ i * 10, i * 10 + 10, angletable[i],
+ i * 10 + 90, i * 10 + 100, angletable[i + 9]);
+ }
+ printf("\n");
+}
+
+/*****************************************************************************/
+/* */
+/* statistics() Print all sorts of cool facts. */
+/* */
+/*****************************************************************************/
+
+#ifdef ANSI_DECLARATORS
+void statistics(struct mesh *m, struct behavior *b)
+#else /* not ANSI_DECLARATORS */
+void statistics(m, b)
+struct mesh *m;
+struct behavior *b;
+#endif /* not ANSI_DECLARATORS */
+
+{
+ printf("\nStatistics:\n\n");
+ printf(" Input vertices: %d\n", m->invertices);
+ if (b->refine) {
+ printf(" Input triangles: %d\n", m->inelements);
+ }
+ if (b->poly) {
+ printf(" Input segments: %d\n", m->insegments);
+ if (!b->refine) {
+ printf(" Input holes: %d\n", m->holes);
+ }
+ }
+
+ printf("\n Mesh vertices: %ld\n", m->vertices.items - m->undeads);
+ printf(" Mesh triangles: %ld\n", m->triangles.items);
+ printf(" Mesh edges: %ld\n", m->edges);
+ printf(" Mesh exterior boundary edges: %ld\n", m->hullsize);
+ if (b->poly || b->refine) {
+ printf(" Mesh interior boundary edges: %ld\n",
+ m->subsegs.items - m->hullsize);
+ printf(" Mesh subsegments (constrained edges): %ld\n",
+ m->subsegs.items);
+ }
+ printf("\n");
+
+ if (b->verbose) {
+ quality_statistics(m, b);
+ printf("Memory allocation statistics:\n\n");
+ printf(" Maximum number of vertices: %ld\n", m->vertices.maxitems);
+ printf(" Maximum number of triangles: %ld\n", m->triangles.maxitems);
+ if (m->subsegs.maxitems > 0) {
+ printf(" Maximum number of subsegments: %ld\n", m->subsegs.maxitems);
+ }
+ if (m->viri.maxitems > 0) {
+ printf(" Maximum number of viri: %ld\n", m->viri.maxitems);
+ }
+ if (m->badsubsegs.maxitems > 0) {
+ printf(" Maximum number of encroached subsegments: %ld\n",
+ m->badsubsegs.maxitems);
+ }
+ if (m->badtriangles.maxitems > 0) {
+ printf(" Maximum number of bad triangles: %ld\n",
+ m->badtriangles.maxitems);
+ }
+ if (m->flipstackers.maxitems > 0) {
+ printf(" Maximum number of stacked triangle flips: %ld\n",
+ m->flipstackers.maxitems);
+ }
+ if (m->splaynodes.maxitems > 0) {
+ printf(" Maximum number of splay tree nodes: %ld\n",
+ m->splaynodes.maxitems);
+ }
+ printf(" Approximate heap memory use (bytes): %ld\n\n",
+ m->vertices.maxitems * m->vertices.itembytes +
+ m->triangles.maxitems * m->triangles.itembytes +
+ m->subsegs.maxitems * m->subsegs.itembytes +
+ m->viri.maxitems * m->viri.itembytes +
+ m->badsubsegs.maxitems * m->badsubsegs.itembytes +
+ m->badtriangles.maxitems * m->badtriangles.itembytes +
+ m->flipstackers.maxitems * m->flipstackers.itembytes +
+ m->splaynodes.maxitems * m->splaynodes.itembytes);
+
+ printf("Algorithmic statistics:\n\n");
+ if (!b->weighted) {
+ printf(" Number of incircle tests: %ld\n", m->incirclecount);
+ } else {
+ printf(" Number of 3D orientation tests: %ld\n", m->orient3dcount);
+ }
+ printf(" Number of 2D orientation tests: %ld\n", m->counterclockcount);
+ if (m->hyperbolacount > 0) {
+ printf(" Number of right-of-hyperbola tests: %ld\n",
+ m->hyperbolacount);
+ }
+ if (m->circletopcount > 0) {
+ printf(" Number of circle top computations: %ld\n",
+ m->circletopcount);
+ }
+ if (m->circumcentercount > 0) {
+ printf(" Number of triangle circumcenter computations: %ld\n",
+ m->circumcentercount);
+ }
+ printf("\n");
+ }
+}
+
+/*****************************************************************************/
+/* */
+/* main() or triangulate() Gosh, do everything. */
+/* */
+/* The sequence is roughly as follows. Many of these steps can be skipped, */
+/* depending on the command line switches. */
+/* */
+/* - Initialize constants and parse the command line. */
+/* - Read the vertices from a file and either */
+/* - triangulate them (no -r), or */
+/* - read an old mesh from files and reconstruct it (-r). */
+/* - Insert the PSLG segments (-p), and possibly segments on the convex */
+/* hull (-c). */
+/* - Read the holes (-p), regional attributes (-pA), and regional area */
+/* constraints (-pa). Carve the holes and concavities, and spread the */
+/* regional attributes and area constraints. */
+/* - Enforce the constraints on minimum angle (-q) and maximum area (-a). */
+/* Also enforce the conforming Delaunay property (-q and -a). */
+/* - Compute the number of edges in the resulting mesh. */
+/* - Promote the mesh's linear triangles to higher order elements (-o). */
+/* - Write the output files and print the statistics. */
+/* - Check the consistency and Delaunay property of the mesh (-C). */
+/* */
+/*****************************************************************************/
+
+#ifdef TRILIBRARY
+
+#ifdef ANSI_DECLARATORS
+void triangulate(const char *triswitches, struct triangulateio *in,
+ struct triangulateio *out, struct triangulateio *vorout)
+#else /* not ANSI_DECLARATORS */
+void triangulate(triswitches, in, out, vorout)
+const char *triswitches;
+struct triangulateio *in;
+struct triangulateio *out;
+struct triangulateio *vorout;
+#endif /* not ANSI_DECLARATORS */
+
+#else /* not TRILIBRARY */
+
+#ifdef ANSI_DECLARATORS
+int main(int argc, char **argv)
+#else /* not ANSI_DECLARATORS */
+int main(argc, argv)
+int argc;
+char **argv;
+#endif /* not ANSI_DECLARATORS */
+
+#endif /* not TRILIBRARY */
+
+{
+ struct mesh m;
+ struct behavior b;
+ tREAL *holearray; /* Array of holes. */
+ tREAL *regionarray; /* Array of regional attributes and area constraints. */
+#ifndef TRILIBRARY
+ FILE *polyfile;
+#endif /* not TRILIBRARY */
+#ifndef NO_TIMER
+ /* Variables for timing the performance of Triangle. The types are */
+ /* defined in sys/time.h. */
+ struct timeval tv0, tv1, tv2, tv3, tv4, tv5, tv6;
+ struct timezone tz;
+#endif /* not NO_TIMER */
+
+#ifndef NO_TIMER
+ gettimeofday(&tv0, &tz);
+#endif /* not NO_TIMER */
+
+ triangleinit(&m);
+#ifdef TRILIBRARY
+ parsecommandline(1, &triswitches, &b);
+#else /* not TRILIBRARY */
+ parsecommandline(argc, argv, &b);
+#endif /* not TRILIBRARY */
+ m.steinerleft = b.steiner;
+
+#ifdef TRILIBRARY
+ transfernodes(&m, &b, in->pointlist, in->pointattributelist,
+ in->pointmarkerlist, in->numberofpoints,
+ in->numberofpointattributes);
+#else /* not TRILIBRARY */
+ readnodes(&m, &b, b.innodefilename, b.inpolyfilename, &polyfile);
+#endif /* not TRILIBRARY */
+
+#ifndef NO_TIMER
+ if (!b.quiet) {
+ gettimeofday(&tv1, &tz);
+ }
+#endif /* not NO_TIMER */
+
+#ifdef CDT_ONLY
+ m.hullsize = delaunay(&m, &b); /* Triangulate the vertices. */
+#else /* not CDT_ONLY */
+ if (b.refine) {
+ /* Read and reconstruct a mesh. */
+#ifdef TRILIBRARY
+ m.hullsize = reconstruct(&m, &b, in->trianglelist,
+ in->triangleattributelist, in->trianglearealist,
+ in->numberoftriangles, in->numberofcorners,
+ in->numberoftriangleattributes,
+ in->segmentlist, in->segmentmarkerlist,
+ in->numberofsegments);
+#else /* not TRILIBRARY */
+ m.hullsize = reconstruct(&m, &b, b.inelefilename, b.areafilename,
+ b.inpolyfilename, polyfile);
+#endif /* not TRILIBRARY */
+ } else {
+ m.hullsize = delaunay(&m, &b); /* Triangulate the vertices. */
+ }
+#endif /* not CDT_ONLY */
+
+#ifndef NO_TIMER
+ if (!b.quiet) {
+ gettimeofday(&tv2, &tz);
+ if (b.refine) {
+ printf("Mesh reconstruction");
+ } else {
+ printf("Delaunay");
+ }
+ printf(" milliseconds: %ld\n", 1000l * (tv2.tv_sec - tv1.tv_sec) +
+ (tv2.tv_usec - tv1.tv_usec) / 1000l);
+ }
+#endif /* not NO_TIMER */
+
+ /* Ensure that no vertex can be mistaken for a triangular bounding */
+ /* box vertex in insertvertex(). */
+ m.infvertex1 = (vertex) NULL;
+ m.infvertex2 = (vertex) NULL;
+ m.infvertex3 = (vertex) NULL;
+
+ if (b.usesegments) {
+ m.checksegments = 1; /* Segments will be introduced next. */
+ if (!b.refine) {
+ /* Insert PSLG segments and/or convex hull segments. */
+#ifdef TRILIBRARY
+ formskeleton(&m, &b, in->segmentlist,
+ in->segmentmarkerlist, in->numberofsegments);
+#else /* not TRILIBRARY */
+ formskeleton(&m, &b, polyfile, b.inpolyfilename);
+#endif /* not TRILIBRARY */
+ }
+ }
+
+#ifndef NO_TIMER
+ if (!b.quiet) {
+ gettimeofday(&tv3, &tz);
+ if (b.usesegments && !b.refine) {
+ printf("Segment milliseconds: %ld\n",
+ 1000l * (tv3.tv_sec - tv2.tv_sec) +
+ (tv3.tv_usec - tv2.tv_usec) / 1000l);
+ }
+ }
+#endif /* not NO_TIMER */
+
+ if (b.poly && (m.triangles.items > 0)) {
+#ifdef TRILIBRARY
+ holearray = in->holelist;
+ m.holes = in->numberofholes;
+ regionarray = in->regionlist;
+ m.regions = in->numberofregions;
+#else /* not TRILIBRARY */
+ readholes(&m, &b, polyfile, b.inpolyfilename, &holearray, &m.holes,
+ &regionarray, &m.regions);
+#endif /* not TRILIBRARY */
+ if (!b.refine) {
+ /* Carve out holes and concavities. */
+ carveholes(&m, &b, holearray, m.holes, regionarray, m.regions);
+ }
+ } else {
+ /* Without a PSLG, there can be no holes or regional attributes */
+ /* or area constraints. The following are set to zero to avoid */
+ /* an accidental free() later. */
+ m.holes = 0;
+ m.regions = 0;
+ }
+
+#ifndef NO_TIMER
+ if (!b.quiet) {
+ gettimeofday(&tv4, &tz);
+ if (b.poly && !b.refine) {
+ printf("Hole milliseconds: %ld\n", 1000l * (tv4.tv_sec - tv3.tv_sec) +
+ (tv4.tv_usec - tv3.tv_usec) / 1000l);
+ }
+ }
+#endif /* not NO_TIMER */
+
+#ifndef CDT_ONLY
+ if (b.quality && (m.triangles.items > 0)) {
+ enforcequality(&m, &b); /* Enforce angle and area constraints. */
+ }
+#endif /* not CDT_ONLY */
+
+#ifndef NO_TIMER
+ if (!b.quiet) {
+ gettimeofday(&tv5, &tz);
+#ifndef CDT_ONLY
+ if (b.quality) {
+ printf("Quality milliseconds: %ld\n",
+ 1000l * (tv5.tv_sec - tv4.tv_sec) +
+ (tv5.tv_usec - tv4.tv_usec) / 1000l);
+ }
+#endif /* not CDT_ONLY */
+ }
+#endif /* not NO_TIMER */
+
+ /* Calculate the number of edges. */
+ m.edges = (3l * m.triangles.items + m.hullsize) / 2l;
+
+ if (b.order > 1) {
+ highorder(&m, &b); /* Promote elements to higher polynomial order. */
+ }
+ if (!b.quiet) {
+ printf("\n");
+ }
+
+#ifdef TRILIBRARY
+ if (b.jettison) {
+ out->numberofpoints = m.vertices.items - m.undeads;
+ } else {
+ out->numberofpoints = m.vertices.items;
+ }
+ out->numberofpointattributes = m.nextras;
+ out->numberoftriangles = m.triangles.items;
+ out->numberofcorners = (b.order + 1) * (b.order + 2) / 2;
+ out->numberoftriangleattributes = m.eextras;
+ out->numberofedges = m.edges;
+ if (b.usesegments) {
+ out->numberofsegments = m.subsegs.items;
+ } else {
+ out->numberofsegments = m.hullsize;
+ }
+ if (vorout != (struct triangulateio *) NULL) {
+ vorout->numberofpoints = m.triangles.items;
+ vorout->numberofpointattributes = m.nextras;
+ vorout->numberofedges = m.edges;
+ }
+#endif /* TRILIBRARY */
+ /* If not using iteration numbers, don't write a .node file if one was */
+ /* read, because the original one would be overwritten! */
+ if (b.nonodewritten || (b.noiterationnum && m.readnodefile)) {
+ if (!b.quiet) {
+#ifdef TRILIBRARY
+ printf("NOT writing vertices.\n");
+#else /* not TRILIBRARY */
+ printf("NOT writing a .node file.\n");
+#endif /* not TRILIBRARY */
+ }
+ numbernodes(&m, &b); /* We must remember to number the vertices. */
+ } else {
+ /* writenodes() numbers the vertices too. */
+#ifdef TRILIBRARY
+ writenodes(&m, &b, &out->pointlist, &out->pointattributelist,
+ &out->pointmarkerlist);
+#else /* not TRILIBRARY */
+ writenodes(&m, &b, b.outnodefilename, argc, argv);
+#endif /* TRILIBRARY */
+ }
+ if (b.noelewritten) {
+ if (!b.quiet) {
+#ifdef TRILIBRARY
+ printf("NOT writing triangles.\n");
+#else /* not TRILIBRARY */
+ printf("NOT writing an .ele file.\n");
+#endif /* not TRILIBRARY */
+ }
+ } else {
+#ifdef TRILIBRARY
+ writeelements(&m, &b, &out->trianglelist, &out->triangleattributelist);
+#else /* not TRILIBRARY */
+ writeelements(&m, &b, b.outelefilename, argc, argv);
+#endif /* not TRILIBRARY */
+ }
+ /* The -c switch (convex switch) causes a PSLG to be written */
+ /* even if none was read. */
+ if (b.poly || b.convex) {
+ /* If not using iteration numbers, don't overwrite the .poly file. */
+ if (b.nopolywritten || b.noiterationnum) {
+ if (!b.quiet) {
+#ifdef TRILIBRARY
+ printf("NOT writing segments.\n");
+#else /* not TRILIBRARY */
+ printf("NOT writing a .poly file.\n");
+#endif /* not TRILIBRARY */
+ }
+ } else {
+#ifdef TRILIBRARY
+ writepoly(&m, &b, &out->segmentlist, &out->segmentmarkerlist);
+ out->numberofholes = m.holes;
+ out->numberofregions = m.regions;
+ if (b.poly) {
+ out->holelist = in->holelist;
+ out->regionlist = in->regionlist;
+ } else {
+ out->holelist = (tREAL *) NULL;
+ out->regionlist = (tREAL *) NULL;
+ }
+#else /* not TRILIBRARY */
+ writepoly(&m, &b, b.outpolyfilename, holearray, m.holes, regionarray,
+ m.regions, argc, argv);
+#endif /* not TRILIBRARY */
+ }
+ }
+#ifndef TRILIBRARY
+#ifndef CDT_ONLY
+ if (m.regions > 0) {
+ trifree((VOID *) regionarray);
+ }
+#endif /* not CDT_ONLY */
+ if (m.holes > 0) {
+ trifree((VOID *) holearray);
+ }
+ if (b.geomview) {
+ writeoff(&m, &b, b.offfilename, argc, argv);
+ }
+#endif /* not TRILIBRARY */
+ if (b.edgesout) {
+#ifdef TRILIBRARY
+ writeedges(&m, &b, &out->edgelist, &out->edgemarkerlist);
+#else /* not TRILIBRARY */
+ writeedges(&m, &b, b.edgefilename, argc, argv);
+#endif /* not TRILIBRARY */
+ }
+ if (b.voronoi) {
+#ifdef TRILIBRARY
+ writevoronoi(&m, &b, &vorout->pointlist, &vorout->pointattributelist,
+ &vorout->pointmarkerlist, &vorout->edgelist,
+ &vorout->edgemarkerlist, &vorout->normlist);
+#else /* not TRILIBRARY */
+ writevoronoi(&m, &b, b.vnodefilename, b.vedgefilename, argc, argv);
+#endif /* not TRILIBRARY */
+ }
+ if (b.neighbors) {
+#ifdef TRILIBRARY
+ writeneighbors(&m, &b, &out->neighborlist);
+#else /* not TRILIBRARY */
+ writeneighbors(&m, &b, b.neighborfilename, argc, argv);
+#endif /* not TRILIBRARY */
+ }
+
+ if (!b.quiet) {
+#ifndef NO_TIMER
+ gettimeofday(&tv6, &tz);
+ printf("\nOutput milliseconds: %ld\n",
+ 1000l * (tv6.tv_sec - tv5.tv_sec) +
+ (tv6.tv_usec - tv5.tv_usec) / 1000l);
+ printf("Total running milliseconds: %ld\n",
+ 1000l * (tv6.tv_sec - tv0.tv_sec) +
+ (tv6.tv_usec - tv0.tv_usec) / 1000l);
+#endif /* not NO_TIMER */
+
+ statistics(&m, &b);
+ }
+
+#ifndef REDUCED
+ if (b.docheck) {
+ checkmesh(&m, &b);
+ checkdelaunay(&m, &b);
+ }
+#endif /* not REDUCED */
+
+ triangledeinit(&m, &b);
+#ifndef TRILIBRARY
+ return 0;
+#endif /* not TRILIBRARY */
+}
diff --git a/Source/Math/triangle.h b/Source/Math/triangle.h
new file mode 100644
index 00000000..b43b2bd2
--- /dev/null
+++ b/Source/Math/triangle.h
@@ -0,0 +1,309 @@
+//-----------------------------------------------------------------------------
+// Name: triangle.h
+// Developer: Wolfire Games LLC
+// Description: Third party file, modified for use in Overgrowth
+// License: Read below
+//-----------------------------------------------------------------------------
+/*****************************************************************************/
+/* */
+/* (triangle.h) */
+/* */
+/* Include file for programs that call Triangle. */
+/* */
+/* Accompanies Triangle Version 1.6 */
+/* July 28, 2005 */
+/* */
+/* Copyright 1996, 2005 */
+/* Jonathan Richard Shewchuk */
+/* 2360 Woolsey #H */
+/* Berkeley, California 94705-1927 */
+/* jrs@cs.berkeley.edu */
+/* */
+/*****************************************************************************/
+/*****************************************************************************/
+/* */
+/* How to call Triangle from another program */
+/* */
+/* */
+/* If you haven't read Triangle's instructions (run "triangle -h" to read */
+/* them), you won't understand what follows. */
+/* */
+/* Triangle must be compiled into an object file (triangle.o) with the */
+/* TRILIBRARY symbol defined (generally by using the -DTRILIBRARY compiler */
+/* switch). The makefile included with Triangle will do this for you if */
+/* you run "make trilibrary". The resulting object file can be called via */
+/* the procedure triangulate(). */
+/* */
+/* If the size of the object file is important to you, you may wish to */
+/* generate a reduced version of triangle.o. The REDUCED symbol gets rid */
+/* of all features that are primarily of research interest. Specifically, */
+/* the -DREDUCED switch eliminates Triangle's -i, -F, -s, and -C switches. */
+/* The CDT_ONLY symbol gets rid of all meshing algorithms above and beyond */
+/* constrained Delaunay triangulation. Specifically, the -DCDT_ONLY switch */
+/* eliminates Triangle's -r, -q, -a, -u, -D, -Y, -S, and -s switches. */
+/* */
+/* IMPORTANT: These definitions (TRILIBRARY, REDUCED, CDT_ONLY) must be */
+/* made in the makefile or in triangle.c itself. Putting these definitions */
+/* in this file (triangle.h) will not create the desired effect. */
+/* */
+/* */
+/* The calling convention for triangulate() follows. */
+/* */
+/* void triangulate(triswitches, in, out, vorout) */
+/* char *triswitches; */
+/* struct triangulateio *in; */
+/* struct triangulateio *out; */
+/* struct triangulateio *vorout; */
+/* */
+/* `triswitches' is a string containing the command line switches you wish */
+/* to invoke. No initial dash is required. Some suggestions: */
+/* */
+/* - You'll probably find it convenient to use the `z' switch so that */
+/* points (and other items) are numbered from zero. This simplifies */
+/* indexing, because the first item of any type always starts at index */
+/* [0] of the corresponding array, whether that item's number is zero or */
+/* one. */
+/* - You'll probably want to use the `Q' (quiet) switch in your final code, */
+/* but you can take advantage of Triangle's printed output (including the */
+/* `V' switch) while debugging. */
+/* - If you are not using the `q', `a', `u', `D', `j', or `s' switches, */
+/* then the output points will be identical to the input points, except */
+/* possibly for the boundary markers. If you don't need the boundary */
+/* markers, you should use the `N' (no nodes output) switch to save */
+/* memory. (If you do need boundary markers, but need to save memory, a */
+/* good nasty trick is to set out->pointlist equal to in->pointlist */
+/* before calling triangulate(), so that Triangle overwrites the input */
+/* points with identical copies.) */
+/* - The `I' (no iteration numbers) and `g' (.off file output) switches */
+/* have no effect when Triangle is compiled with TRILIBRARY defined. */
+/* */
+/* `in', `out', and `vorout' are descriptions of the input, the output, */
+/* and the Voronoi output. If the `v' (Voronoi output) switch is not used, */
+/* `vorout' may be NULL. `in' and `out' may never be NULL. */
+/* */
+/* Certain fields of the input and output structures must be initialized, */
+/* as described below. */
+/* */
+/*****************************************************************************/
+/*****************************************************************************/
+/* */
+/* The `triangulateio' structure. */
+/* */
+/* Used to pass data into and out of the triangulate() procedure. */
+/* */
+/* */
+/* Arrays are used to store points, triangles, markers, and so forth. In */
+/* all cases, the first item in any array is stored starting at index [0]. */
+/* However, that item is item number `1' unless the `z' switch is used, in */
+/* which case it is item number `0'. Hence, you may find it easier to */
+/* index points (and triangles in the neighbor list) if you use the `z' */
+/* switch. Unless, of course, you're calling Triangle from a Fortran */
+/* program. */
+/* */
+/* Description of fields (except the `numberof' fields, which are obvious): */
+/* */
+/* `pointlist': An array of point coordinates. The first point's x */
+/* coordinate is at index [0] and its y coordinate at index [1], followed */
+/* by the coordinates of the remaining points. Each point occupies two */
+/* tREALs. */
+/* `pointattributelist': An array of point attributes. Each point's */
+/* attributes occupy `numberofpointattributes' tREALs. */
+/* `pointmarkerlist': An array of point markers; one int per point. */
+/* */
+/* `trianglelist': An array of triangle corners. The first triangle's */
+/* first corner is at index [0], followed by its other two corners in */
+/* counterclockwise order, followed by any other nodes if the triangle */
+/* represents a nonlinear element. Each triangle occupies */
+/* `numberofcorners' ints. */
+/* `triangleattributelist': An array of triangle attributes. Each */
+/* triangle's attributes occupy `numberoftriangleattributes' tREALs. */
+/* `trianglearealist': An array of triangle area constraints; one tREAL per */
+/* triangle. Input only. */
+/* `neighborlist': An array of triangle neighbors; three ints per */
+/* triangle. Output only. */
+/* */
+/* `segmentlist': An array of segment endpoints. The first segment's */
+/* endpoints are at indices [0] and [1], followed by the remaining */
+/* segments. Two ints per segment. */
+/* `segmentmarkerlist': An array of segment markers; one int per segment. */
+/* */
+/* `holelist': An array of holes. The first hole's x and y coordinates */
+/* are at indices [0] and [1], followed by the remaining holes. Two */
+/* tREALs per hole. Input only, although the pointer is copied to the */
+/* output structure for your convenience. */
+/* */
+/* `regionlist': An array of regional attributes and area constraints. */
+/* The first constraint's x and y coordinates are at indices [0] and [1], */
+/* followed by the regional attribute at index [2], followed by the */
+/* maximum area at index [3], followed by the remaining area constraints. */
+/* Four tREALs per area constraint. Note that each regional attribute is */
+/* used only if you select the `A' switch, and each area constraint is */
+/* used only if you select the `a' switch (with no number following), but */
+/* omitting one of these switches does not change the memory layout. */
+/* Input only, although the pointer is copied to the output structure for */
+/* your convenience. */
+/* */
+/* `edgelist': An array of edge endpoints. The first edge's endpoints are */
+/* at indices [0] and [1], followed by the remaining edges. Two ints per */
+/* edge. Output only. */
+/* `edgemarkerlist': An array of edge markers; one int per edge. Output */
+/* only. */
+/* `normlist': An array of normal vectors, used for infinite rays in */
+/* Voronoi diagrams. The first normal vector's x and y magnitudes are */
+/* at indices [0] and [1], followed by the remaining vectors. For each */
+/* finite edge in a Voronoi diagram, the normal vector written is the */
+/* zero vector. Two tREALs per edge. Output only. */
+/* */
+/* */
+/* Any input fields that Triangle will examine must be initialized. */
+/* Furthermore, for each output array that Triangle will write to, you */
+/* must either provide space by setting the appropriate pointer to point */
+/* to the space you want the data written to, or you must initialize the */
+/* pointer to NULL, which tells Triangle to allocate space for the results. */
+/* The latter option is preferable, because Triangle always knows exactly */
+/* how much space to allocate. The former option is provided mainly for */
+/* people who need to call Triangle from Fortran code, though it also makes */
+/* possible some nasty space-saving tricks, like writing the output to the */
+/* same arrays as the input. */
+/* */
+/* Triangle will not free() any input or output arrays, including those it */
+/* allocates itself; that's up to you. You should free arrays allocated by */
+/* Triangle by calling the trifree() procedure defined below. (By default, */
+/* trifree() just calls the standard free() library procedure, but */
+/* applications that call triangulate() may replace trimalloc() and */
+/* trifree() in triangle.c to use specialized memory allocators.) */
+/* */
+/* Here's a guide to help you decide which fields you must initialize */
+/* before you call triangulate(). */
+/* */
+/* `in': */
+/* */
+/* - `pointlist' must always point to a list of points; `numberofpoints' */
+/* and `numberofpointattributes' must be properly set. */
+/* `pointmarkerlist' must either be set to NULL (in which case all */
+/* markers default to zero), or must point to a list of markers. If */
+/* `numberofpointattributes' is not zero, `pointattributelist' must */
+/* point to a list of point attributes. */
+/* - If the `r' switch is used, `trianglelist' must point to a list of */
+/* triangles, and `numberoftriangles', `numberofcorners', and */
+/* `numberoftriangleattributes' must be properly set. If */
+/* `numberoftriangleattributes' is not zero, `triangleattributelist' */
+/* must point to a list of triangle attributes. If the `a' switch is */
+/* used (with no number following), `trianglearealist' must point to a */
+/* list of triangle area constraints. `neighborlist' may be ignored. */
+/* - If the `p' switch is used, `segmentlist' must point to a list of */
+/* segments, `numberofsegments' must be properly set, and */
+/* `segmentmarkerlist' must either be set to NULL (in which case all */
+/* markers default to zero), or must point to a list of markers. */
+/* - If the `p' switch is used without the `r' switch, then */
+/* `numberofholes' and `numberofregions' must be properly set. If */
+/* `numberofholes' is not zero, `holelist' must point to a list of */
+/* holes. If `numberofregions' is not zero, `regionlist' must point to */
+/* a list of region constraints. */
+/* - If the `p' switch is used, `holelist', `numberofholes', */
+/* `regionlist', and `numberofregions' is copied to `out'. (You can */
+/* nonetheless get away with not initializing them if the `r' switch is */
+/* used.) */
+/* - `edgelist', `edgemarkerlist', `normlist', and `numberofedges' may be */
+/* ignored. */
+/* */
+/* `out': */
+/* */
+/* - `pointlist' must be initialized (NULL or pointing to memory) unless */
+/* the `N' switch is used. `pointmarkerlist' must be initialized */
+/* unless the `N' or `B' switch is used. If `N' is not used and */
+/* `in->numberofpointattributes' is not zero, `pointattributelist' must */
+/* be initialized. */
+/* - `trianglelist' must be initialized unless the `E' switch is used. */
+/* `neighborlist' must be initialized if the `n' switch is used. If */
+/* the `E' switch is not used and (`in->numberofelementattributes' is */
+/* not zero or the `A' switch is used), `elementattributelist' must be */
+/* initialized. `trianglearealist' may be ignored. */
+/* - `segmentlist' must be initialized if the `p' or `c' switch is used, */
+/* and the `P' switch is not used. `segmentmarkerlist' must also be */
+/* initialized under these circumstances unless the `B' switch is used. */
+/* - `edgelist' must be initialized if the `e' switch is used. */
+/* `edgemarkerlist' must be initialized if the `e' switch is used and */
+/* the `B' switch is not. */
+/* - `holelist', `regionlist', `normlist', and all scalars may be ignored.*/
+/* */
+/* `vorout' (only needed if `v' switch is used): */
+/* */
+/* - `pointlist' must be initialized. If `in->numberofpointattributes' */
+/* is not zero, `pointattributelist' must be initialized. */
+/* `pointmarkerlist' may be ignored. */
+/* - `edgelist' and `normlist' must both be initialized. */
+/* `edgemarkerlist' may be ignored. */
+/* - Everything else may be ignored. */
+/* */
+/* After a call to triangulate(), the valid fields of `out' and `vorout' */
+/* will depend, in an obvious way, on the choice of switches used. Note */
+/* that when the `p' switch is used, the pointers `holelist' and */
+/* `regionlist' are copied from `in' to `out', but no new space is */
+/* allocated; be careful that you don't free() the same array twice. On */
+/* the other hand, Triangle will never copy the `pointlist' pointer (or any */
+/* others); new space is allocated for `out->pointlist', or if the `N' */
+/* switch is used, `out->pointlist' remains uninitialized. */
+/* */
+/* All of the meaningful `numberof' fields will be properly set; for */
+/* instance, `numberofedges' will represent the number of edges in the */
+/* triangulation whether or not the edges were written. If segments are */
+/* not used, `numberofsegments' will indicate the number of boundary edges. */
+/* */
+/*****************************************************************************/
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef SINGLE
+#define tREAL float
+#else /* not SINGLE */
+#define tREAL double
+#endif /* not SINGLE */
+
+struct triangulateio {
+ tREAL *pointlist; /* In / out */
+ tREAL *pointattributelist; /* In / out */
+ int *pointmarkerlist; /* In / out */
+ int numberofpoints; /* In / out */
+ int numberofpointattributes; /* In / out */
+
+ int *trianglelist; /* In / out */
+ tREAL *triangleattributelist; /* In / out */
+ tREAL *trianglearealist; /* In only */
+ int *neighborlist; /* Out only */
+ int numberoftriangles; /* In / out */
+ int numberofcorners; /* In / out */
+ int numberoftriangleattributes; /* In / out */
+
+ int *segmentlist; /* In / out */
+ int *segmentmarkerlist; /* In / out */
+ int numberofsegments; /* In / out */
+
+ tREAL *holelist; /* In / pointer to array copied out */
+ int numberofholes; /* In / copied out */
+
+ tREAL *regionlist; /* In / pointer to array copied out */
+ int numberofregions; /* In / copied out */
+
+ int *edgelist; /* Out only */
+ int *edgemarkerlist; /* Not used with Voronoi diagram; out only */
+ tREAL *normlist; /* Used only with Voronoi diagram; out only */
+ int numberofedges; /* Out only */
+};
+
+#define ANSI_DECLARATORS
+#ifdef ANSI_DECLARATORS
+void triangulate(const char *triswitches, struct triangulateio *in,
+ struct triangulateio *out, struct triangulateio *vorout);
+void triOG_FREE(void *memptr);
+#else /* not ANSI_DECLARATORS */
+void triangulate();
+void triOG_FREE();
+#endif /* not ANSI_DECLARATORS */
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/Source/Math/vec2.h b/Source/Math/vec2.h
new file mode 100644
index 00000000..6bb1e9a9
--- /dev/null
+++ b/Source/Math/vec2.h
@@ -0,0 +1,106 @@
+//-----------------------------------------------------------------------------
+// Name: vec2.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/ivec2.h>
+
+#include <iostream>
+
+class vec2{
+ public:
+ float entries[2];
+
+ inline explicit vec2(float val = 0.0f) {
+ entries[0] = val;
+ entries[1] = val;
+ }
+
+ inline vec2(float newx, float newy) {
+ entries[0] = newx;
+ entries[1] = newy;
+ }
+
+ inline vec2(int newx, int newy) {
+ entries[0] = (float)newx;
+ entries[1] = (float)newy;
+ }
+
+ inline vec2( const ivec2& d )
+ {
+ entries[0] = (float)d[0];
+ entries[1] = (float)d[1];
+ }
+
+ inline float& operator[](const int which) {
+ return entries[which];
+ }
+
+ inline const float& operator[](const int which) const {
+ return entries[which];
+ }
+
+ inline vec2& operator=(float param) {
+ entries[0]=param;
+ entries[1]=param;
+ return *this;
+ }
+
+ inline vec2& operator=(const vec2 &param) {
+ entries[0]=param.entries[0];
+ entries[1]=param.entries[1];
+ return *this;
+ }
+
+ inline vec2 operator/(const vec2 &param) {
+ vec2 v;
+ v[0] = entries[0]/param.entries[0];
+ v[1] = entries[1]/param.entries[1];
+ return v;
+ }
+
+ inline vec2 operator+(const vec2 &param) {
+ vec2 v;
+ v[0] = entries[0] + param.entries[0];
+ v[1] = entries[1] + param.entries[1];
+ return v;
+ }
+
+ inline vec2 operator-(const vec2 &param) {
+ vec2 v;
+ v[0] = entries[0] - param.entries[0];
+ v[1] = entries[1] - param.entries[1];
+ return v;
+ }
+
+ inline const float& x() const { return entries[0]; }
+ inline const float& y() const { return entries[1]; }
+ inline float& x() { return entries[0]; }
+ inline float& y() { return entries[1]; }
+};
+
+inline std::ostream& operator<<(std::ostream& os, const vec2& v )
+{
+ os << "vec2(" << v[0] << "," << v[1] << ")";
+ return os;
+}
+
diff --git a/Source/Math/vec2math.cpp b/Source/Math/vec2math.cpp
new file mode 100644
index 00000000..1c9218c8
--- /dev/null
+++ b/Source/Math/vec2math.cpp
@@ -0,0 +1,51 @@
+//-----------------------------------------------------------------------------
+// Name: vec2math.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "vec2math.h"
+
+#include <cmath>
+
+float length(const vec2 &vec) {
+ return sqrtf(length_squared(vec));
+}
+
+
+vec2 components_min2(const vec2 &a, const vec2 &b) {
+ vec2 result;
+
+ result.x() = (a.x() < b.x()) ? a.x() : b.x();
+ result.y() = (a.y() < b.y()) ? a.y() : b.y();
+
+ return result;
+}
+
+
+vec2 components_max2(const vec2 &a, const vec2 &b) {
+ vec2 result;
+
+ result.x() = (a.x() > b.x()) ? a.x() : b.x();
+ result.y() = (a.y() > b.y()) ? a.y() : b.y();
+
+ return result;
+}
+
+
diff --git a/Source/Math/vec2math.h b/Source/Math/vec2math.h
new file mode 100644
index 00000000..d06c975d
--- /dev/null
+++ b/Source/Math/vec2math.h
@@ -0,0 +1,132 @@
+//-----------------------------------------------------------------------------
+// Name: vec2math.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec2.h>
+
+inline vec2 operator+(const vec2 &vec, const vec2 & param) {
+ return vec2(vec.entries[0] + param.entries[0],
+ vec.entries[1] + param.entries[1]);
+}
+
+inline vec2 operator-(const vec2 &vec, const vec2 & param) {
+ return vec2(vec.entries[0] - param.entries[0],
+ vec.entries[1] - param.entries[1]);
+}
+
+inline vec2 operator*(const vec2 &vec, float param) {
+ return vec2(vec.entries[0] * param,
+ vec.entries[1] * param);
+}
+
+inline vec2 operator/(const vec2 &vec, float param) {
+ return vec2(vec.entries[0] / param,
+ vec.entries[1] / param);
+}
+
+inline vec2& operator+=(vec2 &vec, const vec2 & param) {
+ vec.entries[0] += param.entries[0];
+ vec.entries[1] += param.entries[1];
+ return vec;
+}
+
+inline vec2& operator-=(vec2 &vec, const vec2 & param){
+ vec.entries[0] -= param.entries[0];
+ vec.entries[1] -= param.entries[1];
+ return vec;
+}
+
+inline vec2& operator*=(vec2 &vec, float param){
+ vec.entries[0] *= param;
+ vec.entries[1] *= param;
+ return vec;
+}
+
+inline vec2& operator*=(vec2 &vec, const vec2 & param){
+ vec.entries[0] *= param.entries[0];
+ vec.entries[1] *= param.entries[1];
+ return vec;
+}
+
+inline vec2& operator/=(vec2 &vec, float param){
+ vec.entries[0] /= param;
+ vec.entries[1] /= param;
+ return vec;
+}
+
+inline bool operator==(const vec2 &vec, const vec2 &param) {
+ return (vec.entries[0] == param.entries[0] &&
+ vec.entries[1] == param.entries[1]);
+}
+
+inline bool operator!=(const vec2 &vec, const vec2 &param) {
+ return(vec.entries[0] != param.entries[0] ||
+ vec.entries[1] != param.entries[1]);
+}
+
+float length(const vec2 &vec);
+
+// TODO: these should be templated
+vec2 components_min2(const vec2 &a, const vec2 &b);
+vec2 components_max2(const vec2 &a, const vec2 &b);
+
+inline float length_squared(const vec2 &vec) {
+ return vec[0]*vec[0] + vec[1]*vec[1];
+}
+
+inline float dot(const vec2 &vec_a, const vec2 &vec_b) {
+ return vec_a[0]*vec_b[0] + vec_a[1]*vec_b[1];
+}
+
+inline float distance(const vec2 &vec_a, const vec2 &vec_b) {
+ return length(vec_a-vec_b);
+}
+
+inline float distance_squared(const vec2 &vec_a, const vec2 &vec_b) {
+ return length_squared(vec_a-vec_b);
+}
+
+inline vec2 normalize(const vec2 &vec) {
+ if(vec[0]==0.0f && vec[1]==0.0f && vec[2]==0.0f){
+ return vec2(0.0);
+ }
+ return vec/length(vec);
+}
+
+inline vec2 operator*(const vec2 &vec_a, const vec2 &vec_b) {
+ return vec2(vec_a[0]*vec_b[0],
+ vec_a[1]*vec_b[1]);
+}
+
+inline vec2 reflect(const vec2 &vec, const vec2 &normal) {
+ return vec - normal*(2.0f*dot(vec, normal));
+}
+
+inline vec2 operator-(const vec2 &vec) {
+ return vec * -1.0f;
+}
+
+inline vec2 operator*(float param, const vec2 &vec_b) {
+ return vec_b*param;
+}
+
diff --git a/Source/Math/vec3.h b/Source/Math/vec3.h
new file mode 100644
index 00000000..8b1b33c2
--- /dev/null
+++ b/Source/Math/vec3.h
@@ -0,0 +1,98 @@
+//-----------------------------------------------------------------------------
+// Name: vec3.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec2.h>
+
+#include <cassert>
+#include <iostream>
+
+class vec3{
+ public:
+ float entries[3];
+
+ inline explicit vec3(float val = 0.0f) {
+ entries[0] = val;
+ entries[1] = val;
+ entries[2] = val;
+ }
+
+ inline vec3(float newx, float newy, float newz) {
+ entries[0] = newx;
+ entries[1] = newy;
+ entries[2] = newz;
+ }
+
+ inline vec3(const vec2 &vec, float newz) {
+ entries[0] = vec[0];
+ entries[1] = vec[1];
+ entries[2] = newz;
+ }
+
+ inline vec3& operator=(float param) {
+ entries[0] = param;
+ entries[1] = param;
+ entries[2] = param;
+ return *this;
+ }
+
+ inline vec3& operator=(const vec3 &param) {
+ entries[0] = param.entries[0];
+ entries[1] = param.entries[1];
+ entries[2] = param.entries[2];
+ return *this;
+ }
+
+ inline float& operator[](const int which) {
+ assert(which < 3);
+ return entries[which];
+ }
+
+ inline const float& operator[](const int which) const {
+ return entries[which];
+ }
+
+ inline const float& x() const { return entries[0]; }
+ inline const float& y() const { return entries[1]; }
+ inline const float& z() const { return entries[2]; }
+ inline float& x() { return entries[0]; }
+ inline float& y() { return entries[1]; }
+ inline float& z() { return entries[2]; }
+ inline const float& r() const { return entries[0]; }
+ inline const float& g() const { return entries[1]; }
+ inline const float& b() const { return entries[2]; }
+ inline float& r() { return entries[0]; }
+ inline float& g() { return entries[1]; }
+ inline float& b() { return entries[2]; }
+
+ inline vec2 xy() const {
+ return vec2(entries[0], entries[1]);
+ }
+
+ inline float *operator*() { return entries; }
+};
+
+inline std::ostream& operator<<(std::ostream &os, const vec3& vec) {
+ os << "vec3(" << vec[0] << ", " << vec[1] << ", " << vec[2] << ")";
+ return os;
+}
diff --git a/Source/Math/vec3math.cpp b/Source/Math/vec3math.cpp
new file mode 100644
index 00000000..12544ca5
--- /dev/null
+++ b/Source/Math/vec3math.cpp
@@ -0,0 +1,43 @@
+//-----------------------------------------------------------------------------
+// Name: vec3math.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "vec3math.h"
+
+#include <Math/vec3.h>
+
+#include <cmath>
+
+float length( const vec3 &vec ) {
+ return sqrtf(length_squared(vec));
+}
+
+float xz_length( const vec3 &vec ) {
+ return sqrtf(xz_length_squared(vec));
+}
+
+vec3 normalize(const vec3 &vec) {
+ float length_squared_val = length_squared(vec);
+ if(length_squared_val == 0.0f){
+ return vec3(0.0f);
+ }
+ return vec/sqrtf(length_squared_val);
+}
diff --git a/Source/Math/vec3math.h b/Source/Math/vec3math.h
new file mode 100644
index 00000000..cc27eed8
--- /dev/null
+++ b/Source/Math/vec3math.h
@@ -0,0 +1,204 @@
+//-----------------------------------------------------------------------------
+// Name: vec3math.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+
+inline vec3& operator+=(vec3 &vec, const vec3 & param) {
+ vec.entries[0] += param.entries[0];
+ vec.entries[1] += param.entries[1];
+ vec.entries[2] += param.entries[2];
+ return vec;
+}
+
+inline vec3& operator-=(vec3 &vec, const vec3 & param) {
+ vec.entries[0] -= param.entries[0];
+ vec.entries[1] -= param.entries[1];
+ vec.entries[2] -= param.entries[2];
+ return vec;
+}
+
+inline vec3& operator*=(vec3 &vec, float param) {
+ vec.entries[0] *= param;
+ vec.entries[1] *= param;
+ vec.entries[2] *= param;
+ return vec;
+}
+
+inline vec3& operator*=(vec3 &vec, const vec3 & param) {
+ vec.entries[0] *= param.entries[0];
+ vec.entries[1] *= param.entries[1];
+ vec.entries[2] *= param.entries[2];
+ return vec;
+}
+
+inline vec3& operator/=(vec3 &vec, float param) {
+ vec.entries[0] /= param;
+ vec.entries[1] /= param;
+ vec.entries[2] /= param;
+ return vec;
+}
+
+inline bool operator!=(const vec3 &vec, const vec3 &param) {
+ return ( vec.entries[0] != param.entries[0] ||
+ vec.entries[1] != param.entries[1] ||
+ vec.entries[2] != param.entries[2] );
+}
+
+inline bool operator!=(const vec3& vec, float param) {
+ return ( vec.entries[0] != param ||
+ vec.entries[1] != param ||
+ vec.entries[2] != param );
+}
+
+inline vec3 operator+( const vec3 &vec_a, const vec3 &vec_b ) {
+ vec3 vec(vec_a);
+ vec += vec_b;
+ return vec;
+}
+
+inline vec3 operator-( const vec3 &vec_a, const vec3 &vec_b ) {
+ vec3 vec(vec_a);
+ vec -= vec_b;
+ return vec;
+}
+
+inline vec3 operator*( const vec3 &vec_a, const float param ) {
+ vec3 vec(vec_a);
+ vec *= param;
+ return vec;
+}
+
+inline vec3 operator/( const vec3 &vec_a, const float param ) {
+ vec3 vec(vec_a);
+ vec /= param;
+ return vec;
+}
+
+inline vec3 operator/( const vec3 &vec_a, const vec3 &vec_b ) {
+ return vec3(vec_a.entries[0]/vec_b.entries[0],
+ vec_a.entries[1]/vec_b.entries[1],
+ vec_a.entries[2]/vec_b.entries[2]);
+}
+
+inline float length_squared(const vec3 &vec) {
+ return vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2];
+}
+
+float length(const vec3 &vec);
+
+inline float xz_length_squared(const vec3 &vec) {
+ return vec[0]*vec[0] + vec[2]*vec[2];
+}
+
+float xz_length(const vec3 &vec);
+
+inline float dot(const vec3 &vec_a, const vec3 &vec_b) {
+ return vec_a[0]*vec_b[0] + vec_a[1]*vec_b[1] + vec_a[2]*vec_b[2];
+}
+
+inline float distance(const vec3 &vec_a, const vec3 &vec_b) {
+ return length(vec_a-vec_b);
+}
+
+inline float distance_squared(const vec3 &vec_a, const vec3 &vec_b) {
+ return length_squared(vec_a-vec_b);
+}
+
+inline float xz_distance(const vec3 &vec_a, const vec3 &vec_b) {
+ return xz_length(vec_a-vec_b);
+}
+
+inline float xz_distance_squared(const vec3 &vec_a, const vec3 &vec_b) {
+ return xz_length_squared(vec_a-vec_b);
+}
+
+vec3 normalize(const vec3 &vec);
+
+
+// TODO: these should be templated
+inline vec3 components_min(const vec3 &a, const vec3 &b) {
+ vec3 result;
+
+ result.x() = (a.x() < b.x()) ? a.x() : b.x();
+ result.y() = (a.y() < b.y()) ? a.y() : b.y();
+ result.z() = (a.z() < b.z()) ? a.z() : b.z();
+
+ return result;
+}
+
+
+inline vec3 components_max(const vec3 &a, const vec3 &b) {
+ vec3 result;
+
+ result.x() = (a.x() > b.x()) ? a.x() : b.x();
+ result.y() = (a.y() > b.y()) ? a.y() : b.y();
+ result.z() = (a.z() > b.z()) ? a.z() : b.z();
+
+ return result;
+}
+
+
+inline vec3 cross(const vec3 &vec_a, const vec3 &vec_b) {
+ vec3 result;
+ result[0] = vec_a[1] * vec_b[2] - vec_a[2] * vec_b[1];
+ result[1] = vec_a[2] * vec_b[0] - vec_a[0] * vec_b[2];
+ result[2] = vec_a[0] * vec_b[1] - vec_a[1] * vec_b[0];
+ return result;
+}
+
+inline vec3 operator*(const vec3 &vec_a, const vec3 &vec_b) {
+ return vec3(vec_a[0]*vec_b[0],
+ vec_a[1]*vec_b[1],
+ vec_a[2]*vec_b[2]);
+}
+
+inline vec3 reflect(const vec3 &vec, const vec3 &normal) {
+ return vec - normal*(2.0f*dot(vec, normal));
+}
+
+inline vec3 operator-(const vec3 &vec) {
+ return vec * -1.0f;
+}
+
+inline vec3 operator*(float param, const vec3 &vec_b) {
+ return vec_b*param;
+}
+
+inline bool operator==( const vec3 &vec_a, const vec3 &vec_b ) {
+ return (vec_a.entries[0]==vec_b.entries[0] &&
+ vec_a.entries[1]==vec_b.entries[1] &&
+ vec_a.entries[2]==vec_b.entries[2]);
+}
+inline bool operator<( const vec3 &a, const vec3 &b ) {
+ return a.entries[0] < b.entries[0] ||
+ (a.entries[0] == b.entries[0] &&
+ (a.entries[1] < b.entries[1] ||
+ (a.entries[1] == b.entries[1] &&
+ a.entries[2] < b.entries[2])));
+}
+
+inline vec3 lerp(vec3 v0, vec3 v1, float t) {
+ return (1-t)*v0 + t*v1;
+}
+
diff --git a/Source/Math/vec4.h b/Source/Math/vec4.h
new file mode 100644
index 00000000..fae5a41e
--- /dev/null
+++ b/Source/Math/vec4.h
@@ -0,0 +1,134 @@
+//-----------------------------------------------------------------------------
+// Name: vec4.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+
+#include <iostream>
+
+class vec4{
+ public:
+ float entries[4];
+
+ inline float& operator[](const int which) {
+ return entries[which];
+ }
+ inline const float& operator[](const int which) const {
+ return entries[which];
+ }
+
+ inline explicit vec4() {
+ entries[0] = 0.0f;
+ entries[1] = 0.0f;
+ entries[2] = 0.0f;
+ entries[3] = 1.0f;
+ }
+
+ inline vec4(float val) {
+ entries[0] = val;
+ entries[1] = val;
+ entries[2] = val;
+ entries[3] = val;
+ }
+
+ inline vec4(float newx, float newy, float newz, float neww = 1.0f) {
+ entries[0] = newx;
+ entries[1] = newy;
+ entries[2] = newz;
+ entries[3] = neww;
+ }
+
+ inline vec4(const vec3 &vec, float val = 1.0f) {
+ entries[0] = vec[0];
+ entries[1] = vec[1];
+ entries[2] = vec[2];
+ entries[3] = val;
+ }
+
+ inline vec4& operator=(float param){
+ entries[0]=param;
+ entries[1]=param;
+ entries[2]=param;
+ entries[3]=param;
+ return *this;
+ }
+
+ inline vec4& operator=(const vec4 &param){
+ entries[0]=param.entries[0];
+ entries[1]=param.entries[1];
+ entries[2]=param.entries[2];
+ entries[3]=param.entries[3];
+ return *this;
+ }
+
+ inline vec4& operator=(const vec3 &param){
+ entries[0]=param.entries[0];
+ entries[1]=param.entries[1];
+ entries[2]=param.entries[2];
+ return *this;
+ }
+
+ inline const float& x() const { return entries[0]; }
+ inline const float& y() const { return entries[1]; }
+ inline const float& z() const { return entries[2]; }
+ inline const float& w() const { return entries[3]; }
+ inline const float& r() const { return entries[0]; }
+ inline const float& g() const { return entries[1]; }
+ inline const float& b() const { return entries[2]; }
+ inline const float& a() const { return entries[3]; }
+ inline const float& angle() const { return entries[3]; }
+ inline float& x() { return entries[0]; }
+ inline float& y() { return entries[1]; }
+ inline float& z() { return entries[2]; }
+ inline float& w() { return entries[3]; }
+ inline float& r() { return entries[0]; }
+ inline float& g() { return entries[1]; }
+ inline float& b() { return entries[2]; }
+ inline float& a() { return entries[3]; }
+ inline float& angle() { return entries[3]; }
+
+ inline vec2 xy() const {
+ return vec2(entries[0], entries[1]);
+ }
+
+ inline vec3 xyz() const {
+ return vec3(entries[0], entries[1], entries[2]);
+ }
+
+ inline void ToArray(float *array) const {
+ array[0] = entries[0];
+ array[1] = entries[1];
+ array[2] = entries[2];
+ array[3] = entries[3];
+ }
+ inline float *operator*()
+ {
+ return entries;
+ }
+};
+
+inline std::ostream& operator<<(std::ostream& os, const vec4& v )
+{
+ os << "vec4(" << v[0] << "," << v[1] << "," << v[2] << "," << v[3] << ")";
+ return os;
+}
diff --git a/Source/Math/vec4math.cpp b/Source/Math/vec4math.cpp
new file mode 100644
index 00000000..2239dbc0
--- /dev/null
+++ b/Source/Math/vec4math.cpp
@@ -0,0 +1,31 @@
+//-----------------------------------------------------------------------------
+// Name: vec4math.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "vec4math.h"
+
+#include <Math/vec4.h>
+
+#include <cmath>
+
+float length( const vec4 &vec ) {
+ return sqrtf(length_squared(vec.xyz()));
+}
diff --git a/Source/Math/vec4math.h b/Source/Math/vec4math.h
new file mode 100644
index 00000000..b33a5a77
--- /dev/null
+++ b/Source/Math/vec4math.h
@@ -0,0 +1,144 @@
+//-----------------------------------------------------------------------------
+// Name: vec4math.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec4.h>
+#include <Math/vec3.h>
+#include <Math/vec3math.h>
+
+inline vec4 operator+(const vec4& vec, const vec4 & param) {
+ return vec4(vec.entries[0] + param.entries[0],
+ vec.entries[1] + param.entries[1],
+ vec.entries[2] + param.entries[2],
+ vec.entries[3]);
+}
+
+inline vec4 operator-(const vec4& vec, const vec4 & param) {
+ return vec4(vec.entries[0] - param.entries[0],
+ vec.entries[1] - param.entries[1],
+ vec.entries[2] - param.entries[2],
+ vec.entries[3]);
+}
+
+inline vec4 operator*(const vec4& vec, float param) {
+ return vec4(vec.entries[0] * param,
+ vec.entries[1] * param,
+ vec.entries[2] * param,
+ vec.entries[3]);
+}
+inline vec4 operator/(const vec4& vec, float param) {
+ return vec4(vec.entries[0] / param,
+ vec.entries[1] / param,
+ vec.entries[2] / param,
+ vec.entries[3]);
+}
+
+inline void operator+=(vec4& vec, const vec4 & param) {
+ vec.entries[0] += param.entries[0];
+ vec.entries[1] += param.entries[1];
+ vec.entries[2] += param.entries[2];
+}
+
+inline void operator-=(vec4& vec, const vec4 & param) {
+ vec.entries[0] -= param.entries[0];
+ vec.entries[1] -= param.entries[1];
+ vec.entries[2] -= param.entries[2];
+}
+
+inline void operator*=(vec4& vec, float param) {
+ vec.entries[0] *= param;
+ vec.entries[1] *= param;
+ vec.entries[2] *= param;
+}
+
+inline void operator*=(vec4& vec, const vec4 & param) {
+ vec.entries[0] *= param.entries[0];
+ vec.entries[1] *= param.entries[1];
+ vec.entries[2] *= param.entries[2];
+}
+
+inline void operator/=(vec4& vec, float param) {
+ vec.entries[0] /= param;
+ vec.entries[1] /= param;
+ vec.entries[2] /= param;
+}
+
+inline bool operator==(const vec4& vec, const vec4 &param) {
+ return (vec.entries[0] == param.entries[0] &&
+ vec.entries[1] == param.entries[1] &&
+ vec.entries[2] == param.entries[2] &&
+ vec.entries[3] == param.entries[3]);
+}
+
+inline bool operator!=(const vec4& vec, const vec4 &param) {
+ return(vec.entries[0] != param.entries[0] ||
+ vec.entries[1] != param.entries[1] ||
+ vec.entries[2] != param.entries[2] ||
+ vec.entries[3] != param.entries[3]);
+}
+
+float length(const vec4 &vec);
+
+inline float length_squared(const vec4 &vec) {
+ return vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2];
+}
+
+inline float dot(const vec4 &vec_a, const vec4 &vec_b) {
+ return vec_a[0]*vec_b[0] + vec_a[1]*vec_b[1] + vec_a[2]*vec_b[2];
+}
+
+inline float distance(const vec4 &vec_a, const vec4 &vec_b) {
+ return length(vec_a-vec_b);
+}
+
+inline float distance_squared(const vec4 &vec_a, const vec4 &vec_b) {
+ return length_squared(vec_a-vec_b);
+}
+
+inline vec4 normalize(const vec4 &vec) {
+ return vec4(normalize(vec.xyz()),vec[3]);
+}
+
+inline vec4 cross(const vec4 &vec_a, const vec4 &vec_b) {
+ vec4 result;
+ result[0] = vec_a[1] * vec_b[2] - vec_a[2] * vec_b[1];
+ result[1] = vec_a[2] * vec_b[0] - vec_a[0] * vec_b[2];
+ result[2] = vec_a[0] * vec_b[1] - vec_a[1] * vec_b[0];
+ return result;
+}
+
+inline vec4 reflect(const vec4 &vec, const vec4 &normal) {
+ return vec - normal*(2.0f*dot(vec, normal));
+}
+
+inline vec4 operator-(const vec4 &vec) {
+ return vec * -1.0f;
+}
+
+inline vec4 operator*(float param, const vec4 &vec_b) {
+ return vec_b*param;
+}
+
+inline vec4 lerp(vec4 v0, vec4 v1, float t) {
+ return (1-t)*v0 + t*v1;
+}
diff --git a/Source/Memory/allocation.cpp b/Source/Memory/allocation.cpp
new file mode 100644
index 00000000..140b52ed
--- /dev/null
+++ b/Source/Memory/allocation.cpp
@@ -0,0 +1,200 @@
+//-----------------------------------------------------------------------------
+// Name: allocation.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "allocation.h"
+
+#include <Internal/error.h>
+
+#include <cstdlib>
+#include <map>
+#include <cassert>
+
+#if MONITOR_MEMORY
+//POSIX variant, win version necessary.
+#include <pthread.h>
+static pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;
+
+static uint64_t total_memory_allocations[OG_MALLOC_TYPE_COUNT] = {0};
+static size_t allocations_count = 0;
+static uint32_t allocations_this_frame = 0;
+
+
+struct Allocations {
+ void* ptr;
+ size_t size;
+ uint8_t source_id;
+};
+
+const int MAX_ALLOCATIONS = 1024*16;
+
+Allocations allocations[MAX_ALLOCATIONS] = {NULL,0};
+
+const char* OgMallocTypeString(uint8_t type) {
+ switch( type ) {
+ case OG_MALLOC_GEN: return "Generic";
+ case OG_MALLOC_NEW: return "new";
+ case OG_MALLOC_NEW_ARR: return "new []";
+ case OG_MALLOC_RC: return "Recast";
+ case OG_MALLOC_DT: return "Detour";
+ default: return "Unknown";
+ }
+}
+
+void* og_malloc(size_t size, uint8_t source_id) {
+ if(source_id >= OG_MALLOC_TYPE_COUNT) {
+ source_id = 0;
+ }
+
+ if( size > 100 * 1024 * 1024 ) {
+ printf( "Allocating more than 100 MiB of memory\n" );
+ }
+ void* ptr = malloc(size);
+
+ pthread_mutex_lock( &fastmutex );
+
+ if( allocations_count < MAX_ALLOCATIONS ) {
+ int64_t alloc_index;
+ alloc_index = allocations_count;
+
+ while( allocations[alloc_index].ptr != NULL && alloc_index >= 0) {
+ alloc_index--;
+ }
+
+ if( alloc_index >= 0 && alloc_index < MAX_ALLOCATIONS ) {
+ allocations[alloc_index].ptr = ptr;
+ allocations[alloc_index].size = size;
+ allocations[alloc_index].source_id = source_id;
+ allocations_count++;
+
+ total_memory_allocations[source_id] += size;
+ }
+ }
+
+ allocations_this_frame += 1;
+ pthread_mutex_unlock( &fastmutex );
+
+ return ptr;
+}
+
+void og_free(void* ptr) {
+ pthread_mutex_lock( &fastmutex );
+ for( uint32_t i = 0; i < MAX_ALLOCATIONS; i++ ) {
+ if( allocations[i].ptr == ptr ) {
+ total_memory_allocations[allocations[i].source_id] -= allocations[i].size;
+ allocations[i].size = 0;
+ allocations[i].ptr = NULL;
+ allocations_count--;
+ break;
+ }
+ }
+ pthread_mutex_unlock( &fastmutex );
+
+ free(ptr);
+}
+
+void* operator new ( size_t size ) { return og_malloc( size, OG_MALLOC_NEW ); }
+void* operator new[] ( size_t size ) { return og_malloc( size, OG_MALLOC_NEW_ARR ); }
+void operator delete ( void* ptr ) { og_free( ptr ); }
+void operator delete[]( void* ptr ) { og_free( ptr ); }
+
+uint32_t GetAndResetMallocAllocationCount() {
+ uint32_t v;
+ pthread_mutex_lock( &fastmutex );
+ v = allocations_this_frame;
+ allocations_this_frame = 0;
+ pthread_mutex_unlock( &fastmutex );
+ return v;
+}
+
+uint64_t GetCurrentTotalMallocAllocation(int index) {
+ return total_memory_allocations[index];
+}
+
+size_t GetCurrentNumberOfMallocAllocations() {
+ return allocations_count;
+}
+#else
+void* og_malloc(size_t size, uint8_t source_id) {
+ return malloc(size);
+}
+
+void og_free(void* ptr) {
+ free(ptr);
+}
+#endif
+
+Allocation::Allocation()
+{
+
+}
+
+void Allocation::Init()
+{
+ int mem_size = 150 * 1024 * 1024;
+
+ mem_block_stack_allocator = malloc(mem_size);
+
+
+#ifndef NO_ERR
+ if(!mem_block_stack_allocator){
+ FatalError("Error", "Could not allocate initial memory block for stack allocator");
+ }
+#endif
+
+ stack.Init(mem_block_stack_allocator, mem_size);
+
+ /*
+ int block_alloc_blocksize = 1024;
+ int block_alloc_blockcount = 128 * 1024;
+ int mem_size_block = block_alloc_blockcount * block_alloc_blocksize;
+
+ mem_block_block_allocator = malloc(mem_size_block);
+
+#ifndef NO_ERR
+ if(!mem_block_block_allocator) {
+ FatalError("Error", "Could not allocate initial memory block for block allocator");
+ }
+#endif
+
+ block.Init(mem_block_block_allocator, block_alloc_blockcount, block_alloc_blocksize);
+*/
+}
+
+void Allocation::Dispose() {
+ stack.Dispose();
+
+ free(mem_block_stack_allocator);
+ mem_block_stack_allocator = NULL;
+// free(mem_block_block_allocator);
+}
+
+StackMem::StackMem(void *ptr) : v(ptr){
+
+}
+
+StackMem::~StackMem() {
+ alloc.stack.Free(v);
+}
+
+void* StackMem::ptr() {
+ return v;
+}
diff --git a/Source/Memory/allocation.h b/Source/Memory/allocation.h
new file mode 100644
index 00000000..1e2ff5c0
--- /dev/null
+++ b/Source/Memory/allocation.h
@@ -0,0 +1,79 @@
+//-----------------------------------------------------------------------------
+// Name: allocation.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Memory/stack_allocator.h>
+#include <Memory/block_allocator.h>
+
+#include <Internal/integer.h>
+
+#include <cstdlib>
+
+#define OG_MALLOC_GEN 0
+#define OG_MALLOC_NEW 1
+#define OG_MALLOC_NEW_ARR 2
+#define OG_MALLOC_RC 3
+#define OG_MALLOC_DT 4
+
+const char* OgMallocTypeString(uint8_t type);
+
+#define OG_MALLOC_TYPE_COUNT 5
+
+#define OG_MALLOC(size) og_malloc(size,OG_MALLOC_GEN)
+#define OG_FREE(pointer) og_free(pointer)
+
+void* og_malloc(size_t size, uint8_t source_id );
+void og_free(void* ptr);
+
+#if MONITOR_MEMORY
+uint32_t GetAndResetMallocAllocationCount();
+uint64_t GetCurrentTotalMallocAllocation(int index);
+size_t GetCurrentNumberOfMallocAllocations();
+#endif
+
+class Allocation {
+public:
+ Allocation();
+
+ void Init();
+ void Dispose();
+
+ StackAllocator stack;
+ //BlockAllocator block;
+ //ThreadSafeBlockAllocator ts_block;
+private:
+ void* mem_block_stack_allocator;
+ //void* mem_block_block_allocator;
+};
+
+class StackMem {
+private:
+ void* v;
+ StackMem(StackMem& other);
+public:
+ StackMem(void *ptr);
+ ~StackMem();
+ void* ptr();
+};
+
+extern Allocation alloc;
diff --git a/Source/Memory/bitarray.cpp b/Source/Memory/bitarray.cpp
new file mode 100644
index 00000000..3fa1fff9
--- /dev/null
+++ b/Source/Memory/bitarray.cpp
@@ -0,0 +1,133 @@
+//-----------------------------------------------------------------------------
+// Name: bitarray.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "bitarray.h"
+
+#include <Memory/allocation.h>
+#include <Internal/filesystem.h>
+#include <Logging/logdata.h>
+
+#include <cstdlib>
+
+using std::endl;
+
+Bitarray::Bitarray(size_t _size) : size(_size), arr(NULL)
+{
+ ResizeAndReset(size);
+}
+
+Bitarray::~Bitarray()
+{
+ if( arr != NULL )
+ {
+ OG_FREE(arr);
+ arr = NULL;
+ }
+}
+
+void Bitarray::ResizeAndReset(size_t _size)
+{
+ if( arr )
+ {
+ OG_FREE(arr);
+ }
+
+ size = _size;
+
+ if( size > 0 )
+ {
+ size_t arr_size = size/64+(size%64?1:0);
+ arr = (uint64_t*)OG_MALLOC( sizeof(uint64_t) * arr_size);
+ FreeBits(0,size);
+ if( arr == NULL )
+ {
+ LOGF << "Unable to allocate memory for Bitarray" << endl;
+ }
+ }
+ else
+ {
+ arr = NULL;
+ }
+}
+
+bool Bitarray::GetBit(size_t index)
+{
+ size_t p = index/64;
+ size_t i = index%64;
+
+ return (arr[p] & (1UL << i));
+}
+
+void Bitarray::SetBit( size_t index )
+{
+ size_t p = index/64;
+ size_t i = index%64;
+
+ arr[p] |= (1UL << i);
+}
+
+void Bitarray::FreeBit( size_t index )
+{
+ size_t p = index/64;
+ size_t i = index%64;
+
+ arr[p] &= ~(1UL << i);
+}
+
+void Bitarray::SetBits( size_t index, size_t count )
+{
+ for( size_t off = 0; off < count; off++ )
+ {
+ SetBit(index+off);
+ }
+}
+
+void Bitarray::FreeBits( size_t index, size_t count )
+{
+ for( size_t off = 0; off < count; off++ )
+ {
+ FreeBit(index+off);
+ }
+}
+
+int Bitarray::GetFirstFreeSlot(size_t req_size)
+{
+ size_t countdown = req_size;
+ for( size_t i = 0; i < size; i++ )
+ {
+ if( GetBit(i) == false )
+ {
+ countdown--;
+ }
+ else
+ {
+ countdown = req_size;
+ }
+
+ if( countdown == 0 )
+ {
+ return i-(req_size-1);
+ }
+ }
+
+ return -1;
+}
diff --git a/Source/Memory/bitarray.h b/Source/Memory/bitarray.h
new file mode 100644
index 00000000..86b514f8
--- /dev/null
+++ b/Source/Memory/bitarray.h
@@ -0,0 +1,50 @@
+//-----------------------------------------------------------------------------
+// Name: bitarray.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/integer.h>
+#include <Utility/disallow_copy_and_assign.h>
+
+#include <cstring>
+
+class Bitarray {
+public:
+ Bitarray(size_t _size);
+ ~Bitarray();
+private:
+ DISALLOW_COPY_AND_ASSIGN(Bitarray);
+ size_t size;
+ uint64_t *arr;
+public:
+ void ResizeAndReset(size_t new_size);
+
+ bool GetBit(size_t index);
+
+ void SetBit(size_t index);
+ void FreeBit(size_t index);
+
+ void SetBits( size_t index, size_t count );
+ void FreeBits( size_t index, size_t count );
+
+ int GetFirstFreeSlot(size_t size);
+};
diff --git a/Source/Memory/block_allocator.cpp b/Source/Memory/block_allocator.cpp
new file mode 100644
index 00000000..35cfa758
--- /dev/null
+++ b/Source/Memory/block_allocator.cpp
@@ -0,0 +1,150 @@
+//-----------------------------------------------------------------------------
+// Name: block_allocator.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "block_allocator.h"
+
+#include <Memory/allocation.h>
+#include <Internal/integer.h>
+#include <Logging/logdata.h>
+#include <Threading/thread_sanity.h>
+
+#include <cstring>
+#include <cstdlib>
+#include <cmath>
+#include <climits>
+
+using std::endl;
+using std::vector;
+
+BlockAllocator::BlockAllocator() : base_mem(NULL), block_count(0), blocksize(0), allocations(NULL), allocations_placements(0), no_log(false)
+{
+
+}
+
+void BlockAllocator::Init( void* mem, size_t _blocks, size_t _blocksize )
+{
+ base_mem = mem;
+ block_count = _blocks;
+ blocksize = _blocksize;
+
+ allocations_placements.ResizeAndReset( block_count );
+ allocations = new BlockAllocation[_blocks];
+}
+
+BlockAllocator::~BlockAllocator()
+{
+ base_mem = NULL;
+ delete[] allocations;
+ allocations = NULL;
+}
+
+void* BlockAllocator::Alloc(size_t size)
+{
+ //AssertMainThread();
+ size_t slots = size/blocksize+(size%blocksize?1:0);
+ int start_pos = allocations_placements.GetFirstFreeSlot(slots);
+
+ if( start_pos >= 0 )
+ {
+ allocations_placements.SetBits(start_pos,slots);
+ void* ptr = ((char*)base_mem) + (size_t)(blocksize*start_pos);
+ for( size_t i = 0; i < block_count; i++ )
+ {
+ if( allocations[i].IsValid() == false )
+ {
+ allocations[i] = BlockAllocation(start_pos,slots,ptr);
+ return ptr;
+ }
+ }
+
+ if(!no_log){
+ LOGW << "Out of block allocator memory, returning NULL." << endl;
+ }
+ return NULL;
+ }
+ else
+ {
+ if(!no_log){
+ LOGW << "Out of block allocator memory slots (" << start_pos << "), falling back to normal heap." << endl;
+ }
+
+ void * ptr = OG_MALLOC( size );
+ if( ptr != NULL )
+ {
+ backup.push_back(ptr);
+ return ptr;
+ }
+ else
+ {
+ if(!no_log){
+ LOGE << "Unable to allocate with OG_MALLOC()" << endl;
+ }
+ }
+
+ return NULL;
+ }
+}
+
+bool BlockAllocator::CanAlloc(size_t size)
+{
+ //AssertMainThread();
+ size_t slots = size/blocksize+(size%blocksize?1:0);
+ int start_pos = allocations_placements.GetFirstFreeSlot(slots);
+
+ if( start_pos >= 0 )
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void BlockAllocator::Free(void* ptr)
+{
+ //AssertMainThread();
+ for( size_t i = 0; i < block_count; i++ )
+ {
+ if( allocations[i].ptr == ptr )
+ {
+ allocations_placements.FreeBits(allocations[i].block_index, allocations[i].block_count);
+ allocations[i] = BlockAllocation();
+ return;
+ }
+ }
+
+ vector<void*>::iterator backupit;
+ for( backupit = backup.begin(); backupit != backup.end(); backupit++ )
+ {
+ if( *backupit == ptr )
+ {
+ OG_FREE( ptr );
+ backup.erase(backupit);
+ return;
+ }
+ }
+
+ if(!no_log){
+ LOGF << "Could not find an allocation for " << ptr << " unable to free." << endl;
+ }
+}
diff --git a/Source/Memory/block_allocator.h b/Source/Memory/block_allocator.h
new file mode 100644
index 00000000..d42a8acd
--- /dev/null
+++ b/Source/Memory/block_allocator.h
@@ -0,0 +1,57 @@
+//-----------------------------------------------------------------------------
+// Name: block_allocator.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Memory/blockallocation.h>
+#include <Memory/bitarray.h>
+
+#include <Utility/disallow_copy_and_assign.h>
+#include <Internal/integer.h>
+
+#include <cstring>
+#include <vector>
+
+class BlockAllocator {
+public:
+ BlockAllocator();
+ ~BlockAllocator();
+
+ void Init(void* mem, size_t _blocks, size_t _blocksize);
+ void* Alloc(size_t size);
+ bool CanAlloc(size_t size);
+ void Free(void* ptr);
+
+ bool no_log;
+
+private:
+ DISALLOW_COPY_AND_ASSIGN(BlockAllocator);
+ void *base_mem;
+
+ size_t block_count;
+ size_t blocksize;
+
+ std::vector<void*> backup; //Normal malloc allocations that are either too big or don't fit to ensure continued execution.
+
+ BlockAllocation* allocations;
+ Bitarray allocations_placements;
+};
diff --git a/Source/Memory/blockallocation.cpp b/Source/Memory/blockallocation.cpp
new file mode 100644
index 00000000..3e32999d
--- /dev/null
+++ b/Source/Memory/blockallocation.cpp
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------------
+// Name: blockallocation.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "blockallocation.h"
+
+BlockAllocation::BlockAllocation( )
+: block_index(0), block_count(0), ptr(NULL)
+{
+}
+
+BlockAllocation::BlockAllocation( size_t _block_index, size_t _block_count, void* _ptr )
+: block_index(_block_index), block_count(_block_count), ptr(_ptr)
+{
+}
+
+
+bool BlockAllocation::IsValid()
+{
+ return block_count != 0;
+}
diff --git a/Source/Memory/blockallocation.h b/Source/Memory/blockallocation.h
new file mode 100644
index 00000000..c1b0bc10
--- /dev/null
+++ b/Source/Memory/blockallocation.h
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------------
+// Name: blockallocation.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/integer.h>
+
+#include <cstring>
+
+class BlockAllocation {
+public:
+ BlockAllocation(size_t _block_index, size_t _block_count, void* ptr);
+ BlockAllocation();
+
+ bool IsValid();
+
+ size_t block_index;
+ uint16_t block_count;
+ void* ptr;
+};
diff --git a/Source/Memory/stack_allocator.cpp b/Source/Memory/stack_allocator.cpp
new file mode 100644
index 00000000..6e5b9142
--- /dev/null
+++ b/Source/Memory/stack_allocator.cpp
@@ -0,0 +1,133 @@
+//-----------------------------------------------------------------------------
+// Name: stack_allocator.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "stack_allocator.h"
+
+#include <Utility/assert.h>
+#include <Utility/stacktrace.h>
+
+#include <Logging/logdata.h>
+#include <Threading/thread_sanity.h>
+#include <Memory/allocation.h>
+
+#ifndef NO_ERR
+#include <Internal/error.h>
+#endif
+
+#include <cstdio>
+#include <cstdlib>
+#include <stdint.h>
+
+using std::endl;
+
+void* StackAllocator::Alloc(size_t requested_size) {
+ current_allocation_count++;
+ frame_allocation_count++;
+ //AssertMainThread();
+#ifdef DEBUG_DISABLE_STACK_ALLOCATOR
+ return OG_MALLOC(requested_size);
+#else // DEBUG_DISABLE_STACK_ALLOCATOR
+ if(stack_block_pts[stack_blocks] + requested_size < size && stack_blocks < kMaxBlocks-2){
+ ++stack_blocks;
+ stack_block_pts[stack_blocks] = stack_block_pts[stack_blocks-1] + requested_size;
+ void* ptr = (void*)((uintptr_t)mem + stack_block_pts[stack_blocks-1]);
+ return ptr;
+ } else {
+ if( stack_block_pts[stack_blocks] + requested_size >= size )
+ {
+ LOGF << "Unable to stack allocate memory size:" << requested_size << ". Out of memory." << endl;
+ }
+ else
+ {
+ LOGF << "Unable to stack allocate memory size:" << requested_size << ". Out of blocks." << endl;
+ }
+
+ LOGF << GenerateStacktrace() << endl;
+ return NULL;
+ }
+#endif // DEBUG_DISABLE_STACK_ALLOCATOR
+}
+
+void StackAllocator::Free(void* ptr) {
+ current_allocation_count--;
+ //AssertMainThread();
+#ifdef DEBUG_DISABLE_STACK_ALLOCATOR
+ OG_FREE(ptr);
+#else // DEBUG_DISABLE_STACK_ALLOCATOR
+ if(stack_blocks){
+ --stack_blocks;
+ LOG_ASSERT(ptr == (void*)((uintptr_t)mem + stack_block_pts[stack_blocks]));
+ if( ptr != (void*)((uintptr_t)mem + stack_block_pts[stack_blocks]) )
+ {
+ LOGE << "Called fre on something that isn't at the top of the stack" << endl;
+ LOGE << "Stacktrace:" << endl << GenerateStacktrace() << endl;
+ }
+
+ } else {
+#ifndef NO_ERR
+ FatalError("Memory stack underflow", "Calling Free() on StackMemoryBlock with no stack elements");
+#endif
+ }
+#endif // DEBUG_DISABLE_STACK_ALLOCATOR
+}
+
+
+uintptr_t StackAllocator::TopBlockSize() {
+ switch(stack_blocks){
+ case 0:
+ return 0;
+ default:
+ return stack_block_pts[stack_blocks] - stack_block_pts[stack_blocks-1];
+ }
+}
+
+void StackAllocator::Init(void* p_mem, size_t p_size) {
+ for (int i = 0; i < kMaxBlocks; i++) {
+ stack_block_pts[i] = 0;
+ }
+ mem = p_mem;
+ size = p_size;
+ stack_blocks = 0;
+}
+
+void StackAllocator::Dispose() {
+ for (int i = 0; i < kMaxBlocks; i++) {
+ stack_block_pts[i] = 0;
+ }
+ mem = 0;
+ size = 0;
+ stack_blocks = 0;
+}
+
+uint64_t StackAllocator::AllocatedAmountMemory() {
+ return stack_block_pts[stack_blocks];
+}
+
+uint32_t StackAllocator::GetCurrentAllocations() {
+ return current_allocation_count;
+}
+
+uint32_t StackAllocator::GetAndResetAllocationCount() {
+ uint32_t v = frame_allocation_count;
+ frame_allocation_count = 0;
+ return v;
+}
diff --git a/Source/Memory/stack_allocator.h b/Source/Memory/stack_allocator.h
new file mode 100644
index 00000000..85b62f42
--- /dev/null
+++ b/Source/Memory/stack_allocator.h
@@ -0,0 +1,48 @@
+//-----------------------------------------------------------------------------
+// Name: stack_allocator.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+#include <Internal/integer.h>
+
+#include <cstdlib>
+
+class StackAllocator {
+public:
+ void Init(void* mem, size_t size);
+ void Dispose();
+ void* Alloc(size_t size);
+ void Free(void* ptr);
+ size_t TopBlockSize();
+ uint64_t AllocatedAmountMemory();
+ void* mem;
+
+ uint32_t GetCurrentAllocations();
+ uint32_t GetAndResetAllocationCount();
+
+private:
+ uint32_t frame_allocation_count;
+ uint32_t current_allocation_count;
+ static const int kMaxBlocks = 100;
+ uintptr_t stack_block_pts[kMaxBlocks];
+ int stack_blocks;
+ size_t size;
+};
diff --git a/Source/Memory/ts_block_allocator.cpp b/Source/Memory/ts_block_allocator.cpp
new file mode 100644
index 00000000..cb51753a
--- /dev/null
+++ b/Source/Memory/ts_block_allocator.cpp
@@ -0,0 +1,22 @@
+//-----------------------------------------------------------------------------
+// Name: ts_block_allocator.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
diff --git a/Source/Memory/ts_block_allocator.h b/Source/Memory/ts_block_allocator.h
new file mode 100644
index 00000000..4f4d64ae
--- /dev/null
+++ b/Source/Memory/ts_block_allocator.h
@@ -0,0 +1,22 @@
+//-----------------------------------------------------------------------------
+// Name: ts_block_allocator.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
diff --git a/Source/Network/Basic/basic_net_framework.cpp b/Source/Network/Basic/basic_net_framework.cpp
new file mode 100644
index 00000000..27e928fd
--- /dev/null
+++ b/Source/Network/Basic/basic_net_framework.cpp
@@ -0,0 +1,344 @@
+//-----------------------------------------------------------------------------
+// Name: basic_net_framework.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "basic_net_framework.h"
+
+#ifdef USE_BASIC_NETWORK_FRAMEWORK
+#include <Network/net_framework_common.h>
+#include <Logging/logdata.h>
+#include <Online/online.h>
+
+#include <SDL_endian.h>
+
+#include <cassert>
+#include <vector>
+#include <iostream>
+
+using std::vector;
+using std::endl;
+
+static const uint16_t default_port = 25743;
+
+static const uint16_t MESSAGE_HEADER_SIZE = 4;
+
+NetworkingMessage::NetworkingMessage() : data(nullptr), size(0) {
+}
+
+const void* NetworkingMessage::GetData() const {
+ return data;
+}
+
+uint64_t NetworkingMessage::GetSize() const {
+ return size;
+}
+
+void NetworkingMessage::Release() {
+ delete data;
+ data = nullptr;
+ size = 0;
+}
+
+NetConnectionStatus::NetConnectionStatus() : count(0) {
+ buf.resize(1024 * 4);
+}
+
+BasicNetFramework::BasicNetFramework(Online* online_callback) : online(online_callback), socket_set(nullptr) {
+
+}
+
+NetConnectionID BasicNetFramework::GetFreeConnectionID() {
+ for(int i = 1; i < 255; i++) {
+ if(connection_id_map.find(i) == connection_id_map.end()) {
+ return i;
+ }
+ }
+ LOGF << "Ran out of id's for connections, this is a fatal issue." << endl;
+ return 0;
+}
+
+NetListenSocketID BasicNetFramework::GetFreeListenSocketID() {
+ for(int i = 1; i < 255; i++) {
+ if(listen_socket_map.find(i) == listen_socket_map.end()) {
+ return i;
+ }
+ }
+ LOGF << "Ran out of id's for listening sockets, this is a fatal issue." << endl;
+ return 0;
+}
+
+
+void BasicNetFramework::Initialize() {
+ socket_set = SDLNet_AllocSocketSet(255);
+}
+
+void BasicNetFramework::Dispose() {
+ SDLNet_FreeSocketSet(socket_set);
+}
+
+void BasicNetFramework::Update() {
+ //See if there are any new incoming connections.
+ for(auto it : listen_socket_map) {
+ TCPsocket new_socket = SDLNet_TCP_Accept(it.second);
+
+ if(new_socket != nullptr) {
+ NetConnectionID new_connection_id = GetFreeConnectionID();
+ if(new_connection_id > 0) {
+ SDLNet_AddSocket(socket_set, (SDLNet_GenericSocket)new_socket);
+ connection_id_map[new_connection_id] = new_socket;
+ connection_id_map_reverse[new_socket] = new_connection_id;
+ connection_status.emplace(new_connection_id, NetConnectionStatus());
+
+ NetFrameworkConnectionStatusChanged status_changed;
+ status_changed.conn_id = new_connection_id;
+ status_changed.conn_info.connection_state = NetFrameworkConnectionState::Connecting;
+
+ //This call must result in the called either calling AcceptConnection or CloseConnection on the given socket.
+ online->OnConnectionChange(&status_changed);
+ } else {
+ LOGE << "Got an incoming connection, but there are too many already, dropping it" << std::endl;
+ SDLNet_TCP_Close(new_socket);
+ }
+ }
+ }
+
+ //Check for, get and buffer data from sockets.
+ int nr_sockets = SDLNet_CheckSockets(socket_set, 0);
+ if(nr_sockets > 0) {
+ for(auto it : connection_id_map) {
+ if(SDLNet_SocketReady(it.second)) {
+ auto conn_status = connection_status.find(it.first);
+ if(conn_status != connection_status.end()) {
+ NetConnectionStatus* ncs = &conn_status->second;
+ //If we don't have a reasonable amount of buffer space left, grow it.
+ if(ncs->buf.size() - ncs->count < 1024) {
+ ncs->buf.resize(ncs->buf.size() + 1024);
+ }
+
+ int recv_size = SDLNet_TCP_Recv(it.second, ncs->buf.data() + ncs->count, ncs->buf.size() - ncs->count);
+
+ if(recv_size > 0) {
+ /*
+ char* data_as_string = new char[recv_size+1];
+ data_as_string[recv_size] = '\0';
+ memcpy(data_as_string, ncs->buf.data() + ncs->count, recv_size);
+
+ LOGW << "Received Data: \"" << data_as_string << "\"" << endl;
+ */
+
+ ncs->count += recv_size;
+ } else {
+ LOGF << "Got an error on socket, something is broken, this should trigger a disconnect" << std::endl;
+ //TODO, set a flag to trigger a disconnect.
+ }
+
+ if(ncs->count > MESSAGE_HEADER_SIZE) {
+
+ uint32_t message_size = SDL_SwapBE16(((uint16_t*)ncs->buf.data())[0]);
+ uint32_t block_size = message_size + MESSAGE_HEADER_SIZE;
+ uint32_t remainder_size = ncs->count - block_size;
+
+ if(ncs->buf[2] != 0 || ncs->buf[3] != 0) {
+ LOGE << "Corrupt package detected, header bytes that are supposed to be zero aren't." << std::endl;
+ }
+
+ //Check if we've buffered up enough data for the next incoming message.
+ if(ncs->count >= block_size) {
+ NetworkingMessage msg;
+ msg.data = new uint8_t[message_size];
+ msg.size = message_size;
+
+ //Copy out the message data from the block.
+ memcpy(msg.data, ncs->buf.data() + MESSAGE_HEADER_SIZE, message_size);
+
+ //Shift down the next upcoming block, if there's any additional data.
+ if(remainder_size > 0) {
+ memmove(ncs->buf.data(), ncs->buf.data() + block_size, remainder_size);
+ }
+ ncs->count = remainder_size;
+ assert(remainder_size >= 0);
+
+ ncs->messages.push_back(msg);
+ }
+ }
+ } else {
+ LOGF << "Missing connection status structure." << std::endl;
+ }
+ }
+ }
+ }
+}
+
+bool BasicNetFramework::HasFriendInviteOverlay() const {
+ return false;
+}
+
+bool BasicNetFramework::ConnectByIPAddress(const string& address, NetConnectionID* id) {
+ *id = GetFreeConnectionID();
+ if(*id != 0) {
+ IPaddress addr;
+
+ int ret = SDLNet_ResolveHost(&addr, address.c_str(), default_port);
+ if(ret == 0) {
+ TCPsocket socket;
+
+ socket = SDLNet_TCP_Open(&addr);
+ if(socket != nullptr) {
+ SDLNet_AddSocket(socket_set, (SDLNet_GenericSocket)socket);
+ connection_id_map[*id] = socket;
+ connection_id_map_reverse[socket] = *id;
+ connection_status.emplace(*id, NetConnectionStatus());
+
+ NetFrameworkConnectionStatusChanged status_changed;
+ status_changed.conn_id = *id;
+ status_changed.conn_info.connection_state = NetFrameworkConnectionState::Connected;
+
+ online->OnConnectionChange(&status_changed);
+
+ return true;
+ } else {
+ LOGE << "Unable to open socket to resolved address: " << address << ":" << default_port << std::endl;
+ LOGE << "Error: " << SDLNet_GetError() << std::endl;
+ }
+ } else {
+ LOGE << "Unable to resolve host " << address << ":" << default_port << std::endl;
+ }
+ } else {
+ LOGE << "Ran out of NetConnectionID's" << std::endl;
+ }
+ *id = 0;
+ return false;
+}
+
+NetListenSocketID BasicNetFramework::CreateListenSocketIP(const string &local_address) {
+ NetListenSocketID socket_id = GetFreeListenSocketID();
+
+ if(socket_id != 0) {
+ IPaddress addr;
+ SDLNet_ResolveHost(&addr, NULL, default_port);
+
+ TCPsocket socket;
+ socket = SDLNet_TCP_Open(&addr);
+ if(socket != nullptr) {
+ listen_socket_map[socket_id] = socket;
+ listen_socket_map_reverse[socket] = socket_id;
+ return socket_id;
+ } else {
+ LOGE << "Unable to open a listen socket on port " << default_port << "(" << SDLNet_GetError() << ")" << std::endl;
+ }
+ } else {
+ LOGE << "Ran out of NetListenSocketID's" << std::endl;
+ }
+
+ return 0;
+}
+
+void BasicNetFramework::CloseListenSocket(NetListenSocketID listen_socket_id) {
+ auto socket_it = listen_socket_map.find(listen_socket_id);
+
+ if(socket_it != listen_socket_map.end()) {
+ SDLNet_TCP_Close(socket_it->second);
+ listen_socket_map_reverse.erase(socket_it->second);
+ listen_socket_map.erase(socket_it->first);
+ }
+}
+
+void BasicNetFramework::CloseConnection(NetConnectionID conn_id, ConnectionClosedReason reason) {
+ auto socket_it = connection_id_map.find(conn_id);
+ //TODO: Send the close reason before closing the socket
+
+ if(socket_it != connection_id_map.end()) {
+ SDLNet_DelSocket(socket_set, (SDLNet_GenericSocket)socket_it->second);
+ SDLNet_TCP_Close(socket_it->second);
+ connection_id_map_reverse.erase(socket_it->second);
+ connection_status.erase(socket_it->first);
+ connection_id_map.erase(socket_it->first);
+ }
+}
+
+void BasicNetFramework::GetConnectionStatus(NetConnectionID conn_id, ConnectionStatus* status) const {
+ status->ms_ping = 0;
+ status->connection_quality_local = 0;
+ status->connection_quality_remote = 0;
+ status->out_packets_per_sec = 0;
+ status->out_bytes_per_sec = 0;
+ status->in_packets_per_sec = 0;
+ status->in_bytes_per_sec = 0;
+ status->send_rate_bytes_per_second = 0;
+ status->pending_unreliable = 0;
+ status->pending_reliable = 0;
+ status->sent_unacked_reliable = 0;
+ status->usec_queue_time = 0;
+}
+
+int BasicNetFramework::ReceiveMessageOnConnection(NetConnectionID conn_id, NetworkingMessage* msg) {
+ auto status = connection_status.find(conn_id);
+
+ if(status != connection_status.end()) {
+ auto first_message = status->second.messages.begin();
+ if(first_message != status->second.messages.end()) {
+ if(msg != nullptr) {
+ *msg = *first_message;
+ }
+ status->second.messages.erase(first_message);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+bool BasicNetFramework::AcceptConnection(NetConnectionID conn_id, int* result) {
+ *result = 0;
+
+ NetFrameworkConnectionStatusChanged status_changed;
+ status_changed.conn_id = conn_id;
+ status_changed.conn_info.connection_state = NetFrameworkConnectionState::Connected;
+
+ online->OnConnectionChange(&status_changed);
+ return true;
+}
+
+void BasicNetFramework::SendMessageToConnection(NetConnectionID conn_id, char* buffer, uint32_t bytes, bool reliable, bool flush) {
+ auto conn = connection_id_map.find(conn_id);
+ uint16_t header[MESSAGE_HEADER_SIZE/2];
+ header[0] = SDL_SwapBE16(bytes);
+ header[1] = 0;
+
+ if(conn != connection_id_map.end()) {
+ int ret_count = SDLNet_TCP_Send(conn->second, header, MESSAGE_HEADER_SIZE);
+ if(ret_count != MESSAGE_HEADER_SIZE) {
+ LOGE << "ERROR ON CONNECTION" << std::endl;
+ return;
+ }
+
+ ret_count = SDLNet_TCP_Send(conn->second, buffer, bytes);
+
+ if(ret_count != bytes) {
+ LOGE << "Error when sending data to connection" << std::endl;
+ }
+ }
+}
+
+string BasicNetFramework::GetResultString(int result) {
+ return string("Unknown result code");
+}
+#endif
diff --git a/Source/Network/Basic/basic_net_framework.h b/Source/Network/Basic/basic_net_framework.h
new file mode 100644
index 00000000..f6628a3c
--- /dev/null
+++ b/Source/Network/Basic/basic_net_framework.h
@@ -0,0 +1,123 @@
+//-----------------------------------------------------------------------------
+// Name: basic_net_framework.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#ifdef USE_BASIC_NETWORK_FRAMEWORK
+#include <Game/connection_closed_reason.h>
+#include <Network/net_framework_common.h>
+
+#include <SDL_net.h>
+
+#include <map>
+#include <string>
+#include <vector>
+#include <list>
+
+using std::map;
+using std::string;
+using std::vector;
+using std::list;
+
+typedef uint8_t NetConnectionID;
+typedef uint8_t NetListenSocketID;
+
+class Online;
+
+class NetworkingMessage {
+private:
+ friend class BasicNetFramework;
+
+ uint8_t* data;
+ uint16_t size;
+
+public:
+
+ NetworkingMessage();
+ const void* GetData() const;
+ uint64_t GetSize() const;
+ void Release();
+};
+
+struct NetConnectionInfo {
+ ConnectionClosedReason end_reason;
+ NetFrameworkConnectionState connection_state;
+};
+
+struct NetFrameworkConnectionStatusChanged {
+ NetConnectionID conn_id;
+ NetConnectionInfo conn_info;
+};
+
+struct NetConnectionStatus {
+ NetConnectionStatus();
+
+ list<NetworkingMessage> messages;
+
+ vector<uint8_t> buf;
+ uint32_t count;
+};
+
+class BasicNetFramework {
+ Online* online;
+
+private:
+ SDLNet_SocketSet socket_set;
+
+ map<NetConnectionID, TCPsocket> connection_id_map;
+ map<TCPsocket, NetConnectionID> connection_id_map_reverse;
+ map<NetConnectionID, NetConnectionStatus> connection_status;
+
+
+ map<NetListenSocketID, TCPsocket> listen_socket_map;
+ map<TCPsocket, NetListenSocketID> listen_socket_map_reverse;
+
+ NetConnectionID GetFreeConnectionID();
+ NetListenSocketID GetFreeListenSocketID();
+
+public:
+ BasicNetFramework(Online* online_callback);
+
+ void Initialize();
+ void Dispose();
+ void Update();
+
+ bool HasFriendInviteOverlay() const;
+
+ bool ConnectByIPAddress(const string& address, NetConnectionID* id);
+
+ NetListenSocketID CreateListenSocketIP(const string &local_address);
+ void CloseListenSocket(NetListenSocketID listen_socket_id);
+
+ void GetConnectionStatus(NetConnectionID conn_id, ConnectionStatus* status) const;
+
+ void CloseConnection(NetConnectionID conn_id, ConnectionClosedReason reason);
+
+ int ReceiveMessageOnConnection(NetConnectionID conn_id, NetworkingMessage* msg);
+
+ bool AcceptConnection(NetConnectionID conn_id, int* result);
+
+ string GetResultString(int result);
+
+ void SendMessageToConnection(NetConnectionID conn_id, char* buffer, uint32_t bytes, bool reliable, bool flush);
+};
+#endif
diff --git a/Source/Network/Steam/steam_net_framework.cpp b/Source/Network/Steam/steam_net_framework.cpp
new file mode 100644
index 00000000..c6610589
--- /dev/null
+++ b/Source/Network/Steam/steam_net_framework.cpp
@@ -0,0 +1,346 @@
+//-----------------------------------------------------------------------------
+// Name: steam_net_framework.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "steam_net_framework.h"
+
+#ifdef USE_STEAM_NETWORK_FRAMEWORK
+#include <Network/net_framework_common.h>
+#include <Logging/logdata.h>
+#include <Online/online.h>
+#include <Steam/steamworks_util.h>
+
+#include <cassert>
+#include <vector>
+#include <iostream>
+#include <map>
+
+
+using std::vector;
+using std::endl;
+using std::map;
+
+static const uint16_t default_port = 25742;
+
+static const map<ESteamNetworkingConnectionState, NetFrameworkConnectionState> connection_state_map = {
+ {ESteamNetworkingConnectionState::k_ESteamNetworkingConnectionState_Connecting, NetFrameworkConnectionState::Connecting},
+ {ESteamNetworkingConnectionState::k_ESteamNetworkingConnectionState_ClosedByPeer, NetFrameworkConnectionState::ClosedByPeer},
+ {ESteamNetworkingConnectionState::k_ESteamNetworkingConnectionState_Connected, NetFrameworkConnectionState::Connected},
+ {ESteamNetworkingConnectionState::k_ESteamNetworkingConnectionState_FindingRoute, NetFrameworkConnectionState::FindingRoute},
+ {ESteamNetworkingConnectionState::k_ESteamNetworkingConnectionState_ProblemDetectedLocally, NetFrameworkConnectionState::ProblemDetectedLocally}
+};
+
+static void SteamDebugOutputFunction(ESteamNetworkingSocketsDebugOutputType nType, const char * pszMsg) {
+ LOGI << pszMsg << endl;
+}
+
+NetworkingMessage::NetworkingMessage() : msg(nullptr){
+
+}
+
+NetworkingMessage::NetworkingMessage(SteamNetworkingMessage_t* msg) : msg(msg) {
+}
+
+const void* NetworkingMessage::GetData() const {
+ return msg->GetData();
+}
+
+uint64_t NetworkingMessage::GetSize() const {
+ return msg->GetSize();
+}
+
+void NetworkingMessage::Release() {
+ msg->Release();
+}
+
+SteamNetFramework::SteamNetFramework(Online* online_callback) : online(online_callback), isns(nullptr), utils(nullptr) {
+
+}
+
+NetConnectionID SteamNetFramework::GetFreeConnectionID() {
+ for(int i = 1; i < 255; i++) {
+ if(connection_id_map.find(i) == connection_id_map.end()) {
+ return i;
+ }
+ }
+ LOGF << "Ran out of id's for connections, this is a fatal issue." << endl;
+ return 0;
+}
+
+NetListenSocketID SteamNetFramework::GetFreeListenSocketID() {
+ for(int i = 1; i < 255; i++) {
+ if(listen_socket_map.find(i) == listen_socket_map.end()) {
+ return i;
+ }
+ }
+ LOGF << "Ran out of id's for listening sockets, this is a fatal issue." << endl;
+ return 0;
+}
+
+
+void SteamNetFramework::Initialize() {
+ isns = SteamNetworkingSockets();
+ utils = SteamNetworkingUtils();
+
+ utils->SetDebugOutputFunction(ESteamNetworkingSocketsDebugOutputType::k_ESteamNetworkingSocketsDebugOutputType_Error,
+ SteamDebugOutputFunction);
+
+ utils->InitRelayNetworkAccess();
+
+ // this is some random numbers that just seems to solve a bug in SteamSDK defaulting into too small values.
+ int mtuPacketsize = 1200;
+ int outgoingbuffersize = 4096 * 60;
+ SteamNetworkingConfigValue_t config_1[4];
+
+ config_1[0].SetInt32(ESteamNetworkingConfigValue::k_ESteamNetworkingConfig_MTU_PacketSize, mtuPacketsize + 100);
+
+ config_1[1].SetInt32(ESteamNetworkingConfigValue::k_ESteamNetworkingConfig_SendBufferSize, outgoingbuffersize);
+
+ config_1[2].SetInt32(ESteamNetworkingConfigValue::k_ESteamNetworkingConfig_SendRateMin, 1024 * 1024 * 9);
+
+ config_1[3].SetInt32(ESteamNetworkingConfigValue::k_ESteamNetworkingConfig_SendRateMax, 1024 * 1024 * 10);
+
+
+ utils->SetConfigValueStruct(config_1[0], ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Global, 0);
+ utils->SetConfigValueStruct(config_1[1], ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Global, 0);
+
+ utils->SetConfigValueStruct(config_1[2], ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Global, 0);
+ utils->SetConfigValueStruct(config_1[3], ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Global, 0);
+}
+
+void SteamNetFramework::Dispose() {
+
+}
+
+void SteamNetFramework::Update() {
+}
+
+bool SteamNetFramework::HasFriendInviteOverlay() const {
+ return SteamUtils()->IsOverlayEnabled();
+}
+
+bool SteamNetFramework::ConnectByIPAddress(const string& address, NetConnectionID* id) {
+ assert(isns);
+
+ SteamNetworkingIPAddr ipAddress;
+ ipAddress.Clear();
+
+ bool ok = ipAddress.ParseString(address.c_str());
+ if (!ok) {
+ LOGE << "Invalid host IP address " << address << endl;
+ return false;
+ } else {
+ LOGI << "Connecting to adress: " << address << endl;
+ }
+
+ if (ipAddress.m_port == 0) {
+ ipAddress.m_port = default_port;
+ }
+
+ HSteamNetConnection connection = isns->ConnectByIPAddress(ipAddress, 0, nullptr);
+ NetConnectionID connection_id = GetFreeConnectionID();
+ vector<char> temp(1024, 0);
+ int ret = isns->GetDetailedConnectionStatus(connection, &temp[0], temp.size());
+ if (ret >= 0) {
+ LOGI << "connection status: " << &temp[0] << endl;
+ } else {
+ LOGE << "GetDetailedConnectionStatus failed" << endl;
+ }
+
+ connection_id_map[connection_id] = connection;
+ connection_id_map_reverse[connection] = connection_id;
+
+ *id = connection_id;
+
+ return true;
+}
+
+NetListenSocketID SteamNetFramework::CreateListenSocketIP(const string &local_address) {
+ assert(isns);
+
+ SteamNetworkingIPAddr ipAddress;
+ ipAddress.Clear();
+
+ bool ok = ipAddress.ParseString(local_address.c_str());
+ if (ok) {
+ LOGI << "Attempting to create IP listen socket at " << local_address << endl;
+ } else {
+ LOGI << "Attempting to create IP listen socket at default port" << endl;
+ ipAddress.m_port = default_port;
+ }
+
+ HSteamListenSocket listen = isns->CreateListenSocketIP(ipAddress, 0, nullptr);
+ NetListenSocketID socket_id = GetFreeListenSocketID();
+
+ listen_socket_map[socket_id] = listen;
+ listen_socket_map_reverse[listen] = socket_id;
+
+ bool data = isns->GetListenSocketAddress(listen, &ipAddress);
+ // REWRITE: It will fail if port is busy.
+ if (data) {
+ vector<char> temp(1024, 0);
+ ipAddress.ToString(&temp[0], temp.size(), true);
+ LOGI << "IP listen socket address: " << &temp[0] << endl;
+
+
+ } else {
+ LOGE << "IP listen socket has no address" << endl;
+ }
+
+ return socket_id;
+}
+
+void SteamNetFramework::CloseListenSocket(NetListenSocketID listen_socket_id) {
+ auto steam_socket = listen_socket_map.find(listen_socket_id);
+ assert(isns);
+
+ if(steam_socket != listen_socket_map.end()) {
+ isns->CloseListenSocket(steam_socket->second);
+ listen_socket_map_reverse.erase(listen_socket_map_reverse.find(steam_socket->second));
+ listen_socket_map.erase(steam_socket);
+ }
+}
+
+void SteamNetFramework::CloseConnection(NetConnectionID conn_id, ConnectionClosedReason reason) {
+ auto map_ref = connection_id_map.find(conn_id);
+
+ if(map_ref != connection_id_map.end()) {
+ isns->CloseConnection(map_ref->second, static_cast<int>(reason), nullptr, false);
+ connection_id_map_reverse.erase(connection_id_map_reverse.find(map_ref->second));
+ connection_id_map.erase(map_ref);
+ }
+}
+
+void SteamNetFramework::GetConnectionStatus(NetConnectionID conn_id, ConnectionStatus* status) const {
+ SteamNetworkingQuickConnectionStatus steam_status;
+ isns->GetQuickConnectionStatus(connection_id_map.at(conn_id), &steam_status);
+
+ status->ms_ping = steam_status.m_nPing;
+ status->connection_quality_local = steam_status.m_flConnectionQualityLocal;
+ status->connection_quality_remote = steam_status.m_flConnectionQualityRemote;
+ status->out_packets_per_sec = steam_status.m_flOutPacketsPerSec;
+ status->out_bytes_per_sec = steam_status.m_flOutBytesPerSec;
+ status->in_packets_per_sec = steam_status.m_flInPacketsPerSec;
+ status->in_bytes_per_sec = steam_status.m_flInBytesPerSec;
+ status->send_rate_bytes_per_second = steam_status.m_nSendRateBytesPerSecond;
+ status->pending_unreliable = steam_status.m_cbPendingUnreliable;
+ status->pending_reliable = steam_status.m_cbPendingReliable;
+ status->sent_unacked_reliable = steam_status.m_cbSentUnackedReliable;
+ status->usec_queue_time = steam_status.m_usecQueueTime;
+}
+
+void SteamNetFramework::OnConnectionChange(SteamNetConnectionStatusChangedCallback_t *data) {
+ NetFrameworkConnectionStatusChanged status_changed;
+
+ LOGI << "Online::OnConnectionChange " << data->m_hConn
+ << " old:" << SteamworksMatchmaking::StatusToString(data->m_eOldState)
+ << " new: " << SteamworksMatchmaking::StatusToString(data->m_info.m_eState)
+ << endl;
+
+ if(data->m_info.m_eState == k_ESteamNetworkingConnectionState_None) {
+ LOGW << "We've gotten an unexpected connection change, this is an error state in the Steam sockets. We likely told steam to do something with this connection after a disconnection" << std::endl;
+ return;
+ }
+
+ if(data->m_info.m_eState == k_ESteamNetworkingConnectionState_Connecting) {
+ NetConnectionID connection_id = GetFreeConnectionID();
+ connection_id_map[connection_id] = data->m_hConn;
+ connection_id_map_reverse[data->m_hConn] = connection_id;
+ }
+
+ status_changed.conn_id = connection_id_map_reverse.at(data->m_hConn);
+ status_changed.conn_info.end_reason = static_cast<ConnectionClosedReason>(data->m_info.m_eEndReason);
+
+ auto connection_state_it = connection_state_map.find(data->m_info.m_eState);
+ if(connection_state_it != connection_state_map.end()) {
+ status_changed.conn_info.connection_state = connection_state_it->second;
+ } else {
+ status_changed.conn_info.connection_state = NetFrameworkConnectionState::Unknown;
+ }
+
+ online->OnConnectionChange(&status_changed);
+}
+
+int SteamNetFramework::ReceiveMessageOnConnection(NetConnectionID conn_id, NetworkingMessage* networking_message) {
+ SteamNetworkingMessage_t* msg;
+ int ret = isns->ReceiveMessagesOnConnection(connection_id_map[conn_id], &msg, 1);
+ *networking_message = NetworkingMessage(msg);
+ return ret;
+}
+
+bool SteamNetFramework::AcceptConnection(NetConnectionID conn_id, int* result) {
+ EResult r = isns->AcceptConnection(connection_id_map[conn_id]);
+ *result = (int)r;
+ return r == EResult::k_EResultOK;
+}
+
+string SteamNetFramework::GetResultString(int result) {
+ EResult r = (EResult)result;
+ return string(GetEResultString(r));
+}
+
+void SteamNetFramework::SendMessageToConnection(NetConnectionID conn_id, char* buffer, uint32_t bytes, bool reliable, bool flush) {
+
+ int send_flags = k_nSteamNetworkingSend_UseCurrentThread | k_nSteamNetworkingSend_AutoRestartBrokenSession;
+
+ if(reliable) {
+ send_flags |= k_nSteamNetworkingSend_Reliable;
+ } else {
+ send_flags |= k_nSteamNetworkingSend_Unreliable | k_nSteamNetworkingSend_NoDelay;
+ }
+
+ if(flush) {
+ send_flags |= k_nSteamNetworkingSend_NoNagle;
+ }
+
+ EResult result = isns->SendMessageToConnection(connection_id_map[conn_id], buffer,
+ bytes,
+ send_flags,
+ 0);
+
+ if (k_EResultOK != result) {
+ switch (result) {
+ LOGE << "Failed to send message: Error " << result << endl;
+ case k_EResultInvalidParam:
+ LOGE << "Invalid connection handle!" << endl;
+ break;
+
+ case k_EResultInvalidState:
+ LOGE << "Connection is in an invalid state!" << endl;
+ break;
+
+ case k_EResultNoConnection:
+ LOGE << "Connection has ended!" << endl;
+ break;
+
+ case k_EResultIgnored:
+ LOGE << "We weren't (yet) connected, so this operation has no effect.!" << endl;
+ break;
+
+ case k_EResultLimitExceeded: {
+ LOGE << "Internal outgoing buffer full" << endl;
+ break;
+ }
+ }
+ }
+}
+
+#endif
diff --git a/Source/Network/Steam/steam_net_framework.h b/Source/Network/Steam/steam_net_framework.h
new file mode 100644
index 00000000..6ec782ad
--- /dev/null
+++ b/Source/Network/Steam/steam_net_framework.h
@@ -0,0 +1,116 @@
+//-----------------------------------------------------------------------------
+// Name: steam_net_framework.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#ifdef USE_STEAM_NETWORK_FRAMEWORK
+#include <Game/connection_closed_reason.h>
+
+#include <isteamnetworkingsockets.h>
+#include <isteamnetworkingutils.h>
+
+#include <Steam/steamworks_matchmaking.h>
+#include <Network/net_framework_common.h>
+
+#include <map>
+#include <string>
+
+using std::map;
+using std::string;
+
+typedef uint8_t NetConnectionID;
+typedef uint8_t NetListenSocketID;
+
+class Online;
+
+class NetworkingMessage {
+private:
+ friend class SteamNetFramework;
+
+ SteamNetworkingMessage_t *msg;
+
+ NetworkingMessage(SteamNetworkingMessage_t* msg);
+public:
+
+ NetworkingMessage();
+ const void* GetData() const;
+ uint64_t GetSize() const;
+ void Release();
+};
+
+struct NetConnectionInfo {
+ ConnectionClosedReason end_reason;
+ NetFrameworkConnectionState connection_state;
+};
+
+struct NetFrameworkConnectionStatusChanged {
+ NetConnectionID conn_id;
+ NetConnectionInfo conn_info;
+};
+
+class SteamNetFramework {
+ map<NetConnectionID, HSteamNetConnection> connection_id_map;
+ map<HSteamNetConnection, NetConnectionID> connection_id_map_reverse;
+
+ map<NetListenSocketID, HSteamListenSocket> listen_socket_map;
+ map<HSteamListenSocket, NetListenSocketID> listen_socket_map_reverse;
+
+
+ ISteamNetworkingSockets* isns;
+ ISteamNetworkingUtils* utils;
+
+ Online* online;
+
+private:
+
+ NetConnectionID GetFreeConnectionID();
+ NetListenSocketID GetFreeListenSocketID();
+
+public:
+ SteamNetFramework(Online* online_callback);
+
+ void Initialize();
+ void Dispose();
+ void Update();
+
+ bool HasFriendInviteOverlay() const;
+
+ bool ConnectByIPAddress(const string& address, NetConnectionID* id);
+
+ NetListenSocketID CreateListenSocketIP(const string &local_address);
+ void CloseListenSocket(NetListenSocketID listen_socket_id);
+
+ void GetConnectionStatus(NetConnectionID conn_id, ConnectionStatus* status) const;
+
+ void CloseConnection(NetConnectionID conn_id, ConnectionClosedReason reason);
+
+ STEAM_CALLBACK(SteamNetFramework, OnConnectionChange, SteamNetConnectionStatusChangedCallback_t);
+
+ int ReceiveMessageOnConnection(NetConnectionID conn_id, NetworkingMessage* msg);
+
+ bool AcceptConnection(NetConnectionID conn_id, int* result);
+
+ string GetResultString(int result);
+
+ void SendMessageToConnection(NetConnectionID conn_id, char* buffer, uint32_t bytes, bool reliable, bool flush);
+};
+#endif
diff --git a/Source/Network/asnetwork.cpp b/Source/Network/asnetwork.cpp
new file mode 100644
index 00000000..55a45273
--- /dev/null
+++ b/Source/Network/asnetwork.cpp
@@ -0,0 +1,190 @@
+//-----------------------------------------------------------------------------
+// Name: asnetwork.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "asnetwork.h"
+
+#include <Logging/logdata.h>
+
+#include <SDL_net.h>
+
+#include <algorithm>
+
+const int ASNetwork::MAX_SOCKETS = 16;
+
+void ASNetwork::Initialize() {
+ socketset = SDLNet_AllocSocketSet(MAX_SOCKETS);
+ socket_id_counter = 1;
+}
+
+void ASNetwork::Dispose() {
+ SDLNet_FreeSocketSet(socketset);
+}
+
+void ASNetwork::Update() {
+ if( sockets.size() > 0 ) {
+ int check_counter = SocketData::BUF_SIZE;
+ while( check_counter > 0 ) {
+ int nr_sockets = SDLNet_CheckSockets(socketset,0);
+ if( nr_sockets > 0 ) {
+ std::map<SocketID, SocketData>::iterator socketit = sockets.begin();
+ for(; socketit != sockets.end(); socketit++ ) {
+ SocketData &sd = socketit->second;
+ if( SDLNet_SocketReady( sd.socket ) && sd.used < SocketData::BUF_SIZE ) {
+ int recv_size = SDLNet_TCP_Recv(sd.socket, sd.buf + sd.used, 1);
+ if( recv_size == 1 ) {
+ sd.used++;
+ } else if( recv_size <= 0 ) {
+ sd.valid = false;
+ LOGE << "Connection Recv error on " << socketit->first << " : " << SDLNet_GetError() << std::endl;
+ }
+ }
+ }
+ check_counter--;
+ } else if( nr_sockets == -1 ) {
+ LOGE << "Error using select() on socket" << std::endl;
+ check_counter = 0;
+ } else {
+ check_counter = 0;
+ }
+ }
+
+ //Offset the calls to allow calls to DestroyTCPSocket.
+ int id_count = 0;
+ uint8_t* data[MAX_SOCKETS];
+ size_t size[MAX_SOCKETS];
+ SocketID id[MAX_SOCKETS];
+
+ std::map<SocketID, SocketData>::iterator socketit = sockets.begin();
+ for(; socketit != sockets.end(); socketit++ ) {
+ SocketData &sd = socketit->second;
+ if( sd.used > 0 ) {
+ data[id_count] = sd.buf;
+ size[id_count] = sd.used;
+ id[id_count] = socketit->first;
+ id_count++;
+ } else if( sd.valid == false ) { //Note that we use NULL on data as a destroy marker.
+ data[id_count] = NULL;
+ size[id_count] = 0;
+ id[id_count] = socketit->first;
+ id_count++;
+ }
+ sd.used = 0;
+ }
+
+ for( int i = 0; i < id_count; i++ ) {
+ if( data[i] == NULL ) {
+ DestroySocketTCP(id[i]);
+ } else {
+ std::vector<ASNetworkCallback*>::iterator cbit = callbacks.begin();
+ for(; cbit != callbacks.end(); cbit++ ) {
+ (*cbit)->IncomingTCPData(id[i], data[i], size[i] );
+ }
+ }
+ }
+ }
+}
+
+SocketID ASNetwork::CreateSocketTCP(std::string host, uint16_t port) {
+ if( sockets.size() < (unsigned)MAX_SOCKETS ) {
+ IPaddress adr;
+ int ret = SDLNet_ResolveHost(&adr, host.c_str(), port);
+ if( ret == 0 ) {
+ TCPsocket tcp_socket;
+ tcp_socket = SDLNet_TCP_Open(&adr);
+ if( tcp_socket ) {
+ SDLNet_AddSocket( socketset, (SDLNet_GenericSocket)tcp_socket );
+
+ SocketData sd;
+ sd.used = 0;
+ sd.socket = tcp_socket;
+ sd.valid = true;
+
+ SocketID socket_id = socket_id_counter++;
+
+ sockets[socket_id] = sd;
+
+ return socket_id;
+ }
+ else
+ {
+ LOGE << "Unable to open socket to resolved address: " << host << ":" << port << std::endl;
+ LOGE << "Error: " << SDLNet_GetError() << std::endl;
+ }
+ }
+ else
+ {
+ LOGE << "Unable to resolve host " << host << ":" << port << std::endl;
+ }
+ }
+ else
+ {
+ LOGE << "Unable to create another socket connection, reached max of: " << MAX_SOCKETS << std::endl;
+ }
+
+ return SOCKET_ID_INVALID;
+}
+
+void ASNetwork::DestroySocketTCP(SocketID sock) {
+ std::map<SocketID, SocketData>::iterator datait = sockets.find(sock);
+ if( datait != sockets.end() ) {
+ SDLNet_DelSocket(socketset, (SDLNet_GenericSocket)datait->second.socket);
+ SDLNet_TCP_Close(datait->second.socket);
+ sockets.erase(datait);
+ }
+}
+
+int ASNetwork::SocketTCPSend(SocketID socket, const uint8_t* buf, size_t len) {
+ std::map<SocketID, SocketData>::iterator datait = sockets.find(socket);
+ if( datait != sockets.end() ) {
+ SocketData &sd = datait->second;
+ int ret_data_size = SDLNet_TCP_Send( sd.socket, buf, len );
+ if( ret_data_size < (int)len ) {
+ sd.valid = false;
+ DestroySocketTCP(socket);
+ LOGE << "Error when trying to send data on socket, error: \"" << SDLNet_GetError() << "\", shutting it down." << std::endl;
+ }
+ return ret_data_size;
+ } else {
+ LOGE << "unable to send data, no such socket " << socket << std::endl;
+ return -1;
+ }
+}
+
+bool ASNetwork::IsValidSocketTCP(SocketID socket) {
+ std::map<SocketID, SocketData>::iterator datait = sockets.find(socket);
+ if( datait != sockets.end() ) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void ASNetwork::RegisterASNetworkCallback( ASNetworkCallback* cb ) {
+ callbacks.push_back(cb);
+}
+
+void ASNetwork::DeRegisterASNetworkCallback( ASNetworkCallback* cb ) {
+ std::vector<ASNetworkCallback*>::iterator callit = std::find(callbacks.begin(), callbacks.end(),cb);
+ if( callit != callbacks.end() ) {
+ callbacks.erase(callit);
+ }
+}
diff --git a/Source/Network/asnetwork.h b/Source/Network/asnetwork.h
new file mode 100644
index 00000000..8ab06520
--- /dev/null
+++ b/Source/Network/asnetwork.h
@@ -0,0 +1,75 @@
+//-----------------------------------------------------------------------------
+// Name: asnetwork.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Threading/threadsafequeue.h>
+
+#include <SDL_net.h>
+
+#include <map>
+
+
+#define SOCKET_ID_INVALID 0xFFFFFFFF
+
+typedef uint32_t SocketID;
+
+class SocketData {
+public:
+ TCPsocket socket;
+
+ static const unsigned BUF_SIZE = 128;
+ bool valid;
+ size_t used;
+ uint8_t buf[BUF_SIZE];
+};
+
+class ASNetworkCallback {
+public:
+ virtual void IncomingTCPData(SocketID socket, uint8_t* data, size_t len) = 0;
+};
+
+class ASNetwork {
+private:
+ std::vector<ASNetworkCallback*> callbacks;
+
+ static const int MAX_SOCKETS;
+ uint32_t socket_id_counter;
+ std::map<SocketID, SocketData> sockets;
+
+ SDLNet_SocketSet socketset;
+public:
+ void Initialize();
+ void Dispose();
+
+ void Update();
+
+ SocketID CreateSocketTCP(std::string host, uint16_t port);
+ void DestroySocketTCP(SocketID sock);
+
+ int SocketTCPSend(SocketID socket, const uint8_t* buf, size_t len);
+
+ bool IsValidSocketTCP(SocketID socket);
+
+ void RegisterASNetworkCallback( ASNetworkCallback* cb );
+ void DeRegisterASNetworkCallback( ASNetworkCallback* cb );
+};
diff --git a/Source/Network/net_framework.cpp b/Source/Network/net_framework.cpp
new file mode 100644
index 00000000..ce12ddfb
--- /dev/null
+++ b/Source/Network/net_framework.cpp
@@ -0,0 +1,22 @@
+//-----------------------------------------------------------------------------
+// Name: net_framework.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
diff --git a/Source/Network/net_framework.h b/Source/Network/net_framework.h
new file mode 100644
index 00000000..e3b60ad0
--- /dev/null
+++ b/Source/Network/net_framework.h
@@ -0,0 +1,38 @@
+//-----------------------------------------------------------------------------
+// Name: net_framework.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#if defined(USE_STEAM_NETWORK_FRAMEWORK)
+#include <Network/Steam/steam_net_framework.h>
+
+typedef SteamNetFramework NetFramework;
+
+#elif defined(USE_BASIC_NETWORK_FRAMEWORK)
+
+#include <Network/Basic/basic_net_framework.h>
+
+typedef BasicNetFramework NetFramework;
+#endif
+
+
+
diff --git a/Source/Network/net_framework_common.cpp b/Source/Network/net_framework_common.cpp
new file mode 100644
index 00000000..030a1455
--- /dev/null
+++ b/Source/Network/net_framework_common.cpp
@@ -0,0 +1,40 @@
+//-----------------------------------------------------------------------------
+// Name: net_framework_common.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "net_framework_common.h"
+
+string to_string(const NetFrameworkConnectionState& state) {
+ switch(state) {
+ case NetFrameworkConnectionState::Unknown: return "Unknown";
+ case NetFrameworkConnectionState::Connecting: return "Connecting";
+ case NetFrameworkConnectionState::ClosedByPeer: return "ClosedByPeer";
+ case NetFrameworkConnectionState::ProblemDetectedLocally: return "ProblemDetectedLocally";
+ case NetFrameworkConnectionState::FindingRoute: return "FindingRoute";
+ case NetFrameworkConnectionState::Connected: return "Connected";
+ default: return "Unknown Default";
+ }
+}
+
+std::ostream& operator<<( std::ostream& out, const NetFrameworkConnectionState& in ) {
+ out << to_string(in);
+ return out;
+}
diff --git a/Source/Network/net_framework_common.h b/Source/Network/net_framework_common.h
new file mode 100644
index 00000000..b35ac7d4
--- /dev/null
+++ b/Source/Network/net_framework_common.h
@@ -0,0 +1,57 @@
+//-----------------------------------------------------------------------------
+// Name: net_framework_common.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <cstdint>
+#include <string>
+#include <iostream>
+
+using std::string;
+using std::ostream;
+
+struct ConnectionStatus {
+ int ms_ping;
+ float connection_quality_local;
+ float connection_quality_remote;
+ float out_packets_per_sec;
+ float out_bytes_per_sec;
+ float in_packets_per_sec;
+ float in_bytes_per_sec;
+ int send_rate_bytes_per_second;
+ int pending_unreliable;
+ int pending_reliable;
+ int sent_unacked_reliable;
+ int64_t usec_queue_time;
+};
+
+enum class NetFrameworkConnectionState {
+ Unknown,
+ Connecting,
+ ClosedByPeer,
+ ProblemDetectedLocally,
+ FindingRoute,
+ Connected,
+};
+
+string to_string(const NetFrameworkConnectionState& state);
+ostream& operator<<(ostream& out, const NetFrameworkConnectionState& in);
diff --git a/Source/Network/net_socket_connection.cpp b/Source/Network/net_socket_connection.cpp
new file mode 100644
index 00000000..5bfabfc0
--- /dev/null
+++ b/Source/Network/net_socket_connection.cpp
@@ -0,0 +1,22 @@
+//-----------------------------------------------------------------------------
+// Name: net_socket_connection.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
diff --git a/Source/Network/net_socket_connection.h b/Source/Network/net_socket_connection.h
new file mode 100644
index 00000000..28610062
--- /dev/null
+++ b/Source/Network/net_socket_connection.h
@@ -0,0 +1,22 @@
+//-----------------------------------------------------------------------------
+// Name: net_socket_connection.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
diff --git a/Source/Objects/ambientsoundobject.cpp b/Source/Objects/ambientsoundobject.cpp
new file mode 100644
index 00000000..99dd6539
--- /dev/null
+++ b/Source/Objects/ambientsoundobject.cpp
@@ -0,0 +1,145 @@
+//-----------------------------------------------------------------------------
+// Name: ambientsoundobject.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "ambientsoundobject.h"
+
+#include <Math/vec3math.h>
+#include <Main/engine.h>
+
+#include <Graphics/pxdebugdraw.h>
+#include <Graphics/camera.h>
+
+#include <Internal/memwrite.h>
+#include <Internal/timer.h>
+
+#include <Objects/group.h>
+
+#include <Sound/sound.h>
+#include <Asset/Asset//ambientsounds.h>
+#include <Main/scenegraph.h>
+
+#include <tinyxml.h>
+
+//-----------------------------------------------------------------------------
+//Functions
+//-----------------------------------------------------------------------------
+
+bool AmbientSoundObject::Initialize() {
+ if(as_ref->GetSoundType() == _continuous){
+ SoundPlayInfo spi;
+ spi.path = as_ref->GetPath();
+ spi.looping = true;
+ spi.position = GetTranslation();
+ spi.occlusion_position = spi.position;
+ sound_handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->Play(sound_handle,spi);
+ }
+ return true;
+}
+
+AmbientSoundObject::AmbientSoundObject():
+ sound_handle(0)
+{
+ box_.dims = vec3(1.0f);
+}
+
+AmbientSoundObject::~AmbientSoundObject() {
+}
+
+bool AmbientSoundObject::SetFromDesc( const EntityDescription& desc ){
+ bool ret = Object::SetFromDesc(desc);
+ if( ret ) {
+ for(unsigned i=0; i<desc.fields.size(); ++i){
+ const EntityDescriptionField& field = desc.fields[i];
+ switch(field.type){
+ case EDF_FILE_PATH: {
+ std::string type_file;
+ field.ReadString(&type_file);
+ if(!as_ref.valid() || as_ref->path_ != obj_file){
+ //as_ref = AmbientSounds::Instance()->ReturnRef(type_file);
+ as_ref = Engine::Instance()->GetAssetManager()->LoadSync<AmbientSound>(type_file);
+ if(as_ref->GetSoundType() == _occasional){
+ delay = as_ref->GetDelayNoLower();
+ }
+ }
+ break;}
+ }
+ }
+ }
+ return ret;
+}
+
+void AmbientSoundObject::Moved(Object::MoveType type) {
+ Object::Moved(type);
+ if(type & kTranslate && scenegraph_){
+ Engine::Instance()->GetSound()->SetPosition(sound_handle, GetTranslation());
+ }
+}
+
+bool AmbientSoundObject::InCameraRange() {
+ const float _threshold_distance = 10.0f;
+ const float _threshold_distance_squared = square(_threshold_distance);
+
+ const float dist_sqrd =
+ distance_squared(ActiveCameras::Get()->GetPos(), GetTranslation());
+ return dist_sqrd < _threshold_distance_squared;
+}
+
+void AmbientSoundObject::Update(float timestep){
+ if(as_ref->GetSoundType() == _occasional && InCameraRange()){
+ delay -= timestep;
+ if(delay <= 0.0f){
+ //SoundGroupRef sgr = SoundGroups::Instance()->ReturnRef(as_ref->GetPath());
+ SoundGroupRef sgr = Engine::Instance()->GetAssetManager()->LoadSync<SoundGroup>(as_ref->GetPath());
+ SoundGroupPlayInfo sgpi(*sgr, GetTranslation());
+ unsigned long handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->PlayGroup(handle, sgpi);
+ delay = as_ref->GetDelay();
+ }
+ }
+}
+
+void AmbientSoundObject::Draw()
+{
+ /*ppolist::iterator iter = connections.begin();
+ for(;iter != connections.end(); ++iter){
+ DebugDraw::Instance()->AddLine(position,
+ (*iter)->position,
+ vec4(1.0f),
+ _delete_on_draw);
+ }*/
+}
+
+void AmbientSoundObject::Copied()
+{
+ sound_handle = 0;
+}
+
+void AmbientSoundObject::GetDesc(EntityDescription &desc) const {
+ Object::GetDesc(desc);
+ desc.AddString(EDF_FILE_PATH, as_ref->path_);
+}
+
+const std::string & AmbientSoundObject::GetPath()
+{
+ return as_ref->path_;
+}
diff --git a/Source/Objects/ambientsoundobject.h b/Source/Objects/ambientsoundobject.h
new file mode 100644
index 00000000..68b18cdf
--- /dev/null
+++ b/Source/Objects/ambientsoundobject.h
@@ -0,0 +1,59 @@
+//-----------------------------------------------------------------------------
+// Name: ambientsoundobject.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Objects/object.h>
+#include <Asset/Asset/ambientsounds.h>
+
+#include <vector>
+#include <string>
+#include <list>
+
+//-----------------------------------------------------------------------------
+// Class Definition
+//-----------------------------------------------------------------------------
+
+class AmbientSoundObject: public Object
+{
+public:
+ virtual EntityType GetType() const { return _ambient_sound_object; }
+ AmbientSoundObject();
+ virtual ~AmbientSoundObject();
+
+ bool Initialize();
+
+ void Update(float timestep);
+ void Draw();
+ void Copied();
+
+ virtual void Moved(Object::MoveType type);
+ const std::string & GetPath();
+ bool InCameraRange();
+ virtual void GetDesc(EntityDescription &desc) const;
+ virtual bool SetFromDesc( const EntityDescription& desc );
+
+private:
+ float delay;
+ AmbientSoundRef as_ref;
+ int sound_handle;
+};
diff --git a/Source/Objects/cameraobject.cpp b/Source/Objects/cameraobject.cpp
new file mode 100644
index 00000000..842cfbf5
--- /dev/null
+++ b/Source/Objects/cameraobject.cpp
@@ -0,0 +1,149 @@
+//-----------------------------------------------------------------------------
+// Name: cameraobject.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include <Internal/common.h>
+#include <Internal/profiler.h>
+#include <Internal/timer.h>
+
+#include <Graphics/camera.h>
+#include <Graphics/graphics.h>
+
+#include <Scripting/angelscript/ascontext.h>
+#include <Scripting/angelscript/asfuncs.h>
+#include <Scripting/angelscript/ascollisions.h>
+
+#include <Sound/sound.h>
+#include <UserInput/input.h>
+#include <Objects/cameraobject.h>
+#include <Game/level.h>
+#include <Editors/map_editor.h>
+#include <Physics/bulletworld.h>
+#include <Online/online.h>
+#include <Main/scenegraph.h>
+
+#include <SDL_assert.h>
+
+extern std::string script_dir_path;
+
+//Camera cannot see itself
+void CameraObject::Draw() {
+}
+
+void CameraObject::GetDisplayName(char* buf, int buf_size) {
+ if( GetName().empty() ) {
+ FormatString(buf, buf_size, "%d, Camera", GetID());
+ } else {
+ FormatString(buf, buf_size, "%s, Camera", GetName().c_str());
+ }
+}
+
+void CameraObject::saveStateToFile(FILE *){
+}
+
+//Give camera fps controls
+void CameraObject::Update(float timestep) {
+ static const std::string kUpdateStr = "void Update()";
+ as_context->CallScriptFunction(kUpdateStr);
+}
+
+void CameraObject::Reload() {
+ as_context->Reload();
+}
+
+bool CameraObject::Initialize() {
+ ASData as_data;
+ as_data.scenegraph = scenegraph_;
+ as_data.gui = scenegraph_->map_editor->gui;
+ as_context = new ASContext("camera_object",as_data);
+ AttachUIQueries(as_context);
+ AttachActiveCamera(as_context);
+ AttachScreenWidth(as_context);
+ AttachPhysics(as_context);
+ AttachEngine(as_context);
+ AttachScenegraph(as_context, scenegraph_);
+ AttachTextCanvasTextureToASContext(as_context);
+ AttachLevel(as_context);
+ AttachInterlevelData(as_context);
+ AttachMessages(as_context);
+ AttachTokenIterator(as_context);
+ AttachStringConvert(as_context);
+ AttachOnline(as_context);
+
+ as_collisions = new ASCollisions(scenegraph_);
+ as_collisions->AttachToContext(as_context);
+
+ as_context->RegisterObjectType("CameraObject", 0, asOBJ_REF | asOBJ_NOHANDLE);
+ as_context->RegisterObjectProperty("CameraObject","vec3 velocity",asOFFSET(CameraObject,velocity));
+ as_context->RegisterObjectProperty("CameraObject","bool controlled",asOFFSET(CameraObject,controlled));
+ as_context->RegisterObjectProperty("CameraObject","bool frozen",asOFFSET(CameraObject,frozen));
+ as_context->RegisterObjectProperty("CameraObject","bool ignore_mouse_input",asOFFSET(CameraObject,ignore_mouse_input));
+ as_context->RegisterObjectProperty("CameraObject","bool has_position_initialized",asOFFSET(CameraObject,has_position_initialized));
+ as_context->RegisterObjectMethod("CameraObject","const vec3& GetTranslation()",asMETHOD(CameraObject,GetTranslation), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("CameraObject","const quaternion& GetRotation()",asMETHOD(CameraObject,GetRotation), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("CameraObject","void SetTranslation(const vec3 &in vec)",asMETHOD(CameraObject,SetTranslation), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("CameraObject","void SetRotation(const quaternion &in quat)",asMETHOD(CameraObject,SetRotation), asCALL_THISCALL);
+ as_context->DocsCloseBrace();
+ as_context->RegisterGlobalProperty("CameraObject co", this);
+
+ as_funcs.init = as_context->RegisterExpectedFunction("void Init()",true);
+ as_funcs.frame_selection = as_context->RegisterExpectedFunction("void FrameSelection(bool)",true);
+
+ PROFILER_ENTER(g_profiler_ctx, "Exporting docs");
+ char path[kPathSize];
+ FormatString(path, kPathSize, "%sascameraobject_docs.h", GetWritePath(CoreGameModID).c_str());
+ as_context->ExportDocs(path);
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ Path script_path = FindFilePath(script_dir_path+"cam.as", kDataPaths | kModPaths);
+
+ if( script_path.isValid() ) {
+ if( as_context->LoadScript(script_path)) {
+ as_context->CallScriptFunction(as_funcs.init);
+
+ SDL_assert(update_list_entry == -1);
+ update_list_entry = scenegraph_->LinkUpdateObject(this);
+ return true;
+ } else {
+ FatalError("Error", "We don't handle the case of an invalid camera, fix the camera script");
+ return false;
+ }
+ } else {
+ FatalError("Error", "We don't handle the case of an invalid camera, missing camera script file.");
+ return false;
+
+ }
+}
+
+void CameraObject::FrameSelection(bool v) {
+ if(as_context) {
+ ASArglist args;
+ args.Add(v);
+ as_context->CallScriptFunction(as_funcs.frame_selection, &args);
+ }
+}
+
+CameraObject::~CameraObject() {
+ delete as_context;
+ delete as_collisions;
+}
+
diff --git a/Source/Objects/cameraobject.h b/Source/Objects/cameraobject.h
new file mode 100644
index 00000000..0e2ee6bb
--- /dev/null
+++ b/Source/Objects/cameraobject.h
@@ -0,0 +1,93 @@
+//-----------------------------------------------------------------------------
+// Name: cameraobject.h
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: The camera object is an entity representing the camera (viewer)
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Scripting/angelscript/asmodule.h>
+#include <Scripting/angelscript/asarglist.h>
+#include <Scripting/angelscript/ascontext.h>
+
+#include <Objects/object.h>
+
+//-----------------------------------------------------------------------------
+// Class Definition
+//-----------------------------------------------------------------------------
+class ASContext;
+class ASCollisions;
+
+class CameraObject: public Object {
+ public:
+ bool controlled;
+ ASContext* as_context;
+ ASCollisions *as_collisions;
+ float speed;
+ float rotation, rotation2;
+ float target_rotation, target_rotation2;
+ bool frozen;
+ vec3 velocity;
+ float smooth_speed;
+ bool ignore_mouse_input;
+
+ bool has_position_initialized;
+
+ struct {
+ ASFunctionHandle init;
+ ASFunctionHandle frame_selection;
+ } as_funcs;
+
+ CameraObject() {
+ has_position_initialized = true;
+ frozen=false;
+ speed=5;
+ rotation=0;
+ rotation2=0;
+ target_rotation=0;
+ target_rotation2=0;
+ velocity = vec3(0.0f);
+ smooth_speed = 0;
+ ignore_mouse_input = false;
+ as_context = NULL;
+ as_collisions = NULL;
+ controlled = false;
+ permission_flags = 0;
+ exclude_from_undo = true;
+ }
+
+ virtual ~CameraObject();
+
+ virtual EntityType GetType() const {return _camera_type;}
+
+ void saveStateToFile(FILE *);
+ void Update(float timestep);
+ virtual void Reload();
+ void Draw();
+ virtual void GetDisplayName(char* buf, int buf_size);
+
+ virtual void IgnoreInput(bool val) {}
+ virtual void IgnoreMouseInput(bool val) {}
+ bool Initialize();
+ vec3 GetMouseRay();
+
+ void FrameSelection(bool v);
+
+};
diff --git a/Source/Objects/decalobject.cpp b/Source/Objects/decalobject.cpp
new file mode 100644
index 00000000..256aedac
--- /dev/null
+++ b/Source/Objects/decalobject.cpp
@@ -0,0 +1,182 @@
+//-----------------------------------------------------------------------------
+// Name: decalobject.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "decalobject.h"
+
+#include <Objects/envobject.h>
+#include <Objects/group.h>
+
+#include <Graphics/graphics.h>
+#include <Graphics/shaders.h>
+#include <Graphics/camera.h>
+#include <Graphics/pxdebugdraw.h>
+
+#include <Internal/stopwatch.h>
+#include <Internal/memwrite.h>
+#include <Internal/profiler.h>
+#include <Internal/common.h>
+
+#include <Math/vec3math.h>
+#include <Math/vec4math.h>
+
+#include <Main/scenegraph.h>
+#include <Main/engine.h>
+
+#include <Editors/map_editor.h>
+#include <UserInput/input.h>
+#include <Logging/logdata.h>
+#include <Internal/timer.h>
+
+#include <tinyxml.h>
+
+extern Timer game_timer;
+
+std::map<std::string,RC_DecalTexture> DecalObject::textureCache;
+
+extern bool g_debug_runtime_disable_decal_object_draw;
+extern bool g_debug_runtime_disable_decal_object_pre_draw_frame;
+
+DecalObject::DecalObject()
+{
+ collidable = false;
+ box_.dims = vec3(1.0f);
+ spawn_time_ = game_timer.game_time;
+}
+
+bool DecalObject::Initialize() {
+ return true;
+}
+
+void DecalObject::Dispose() {
+ texture.Set(NULL);
+ const std::map<std::string,RC_DecalTexture>::iterator& texit = textureCache.find(obj_file);
+ if(texit != textureCache.end()) {
+ if(texit->second.GetReferenceCount() == 1) { // Owned only by texture cache
+ textureCache.erase(texit);
+ }
+ }
+}
+
+void DecalObject::GetDisplayName(char* buf, int buf_size) {
+ if( GetName().empty() ) {
+ FormatString(buf, buf_size, "%d, Decal: %s", GetID(), obj_file.c_str());
+ } else {
+ FormatString(buf, buf_size, "%s, Decal: %s", GetName().c_str(), obj_file.c_str());
+ }
+}
+
+void DecalObject::Draw() {
+ if (g_debug_runtime_disable_decal_object_draw) {
+ return;
+ }
+
+ // Add fading line in the direction of the decal projector
+ DebugDraw::Instance()->AddLine(
+ GetTransform() * vec3(0.0f, 0.5f, 0.0f),
+ GetTransform() * vec3(0.0f, -0.5f, 0.0f),
+ vec4(0.0f, 0.0f, 0.0f, 1.0f),
+ vec4(0.0f, 0.0f, 0.0f, 0.0f),
+ _delete_on_draw);
+ // Add cross on the side from which the decal is projected
+ DebugDraw::Instance()->AddLine(
+ GetTransform() * vec3(-0.5f, 0.5f, -0.5f),
+ GetTransform() * vec3( 0.5f, 0.5f, 0.5f),
+ vec4(0.0f, 0.0f, 0.0f, 1.0f),
+ vec4(0.0f, 0.0f, 0.0f, 1.0f),
+ _delete_on_draw);
+ DebugDraw::Instance()->AddLine(
+ GetTransform() * vec3(-0.5f, 0.5f, 0.5f),
+ GetTransform() * vec3( 0.5f, 0.5f, -0.5f),
+ vec4(0.0f, 0.0f, 0.0f, 1.0f),
+ vec4(0.0f, 0.0f, 0.0f, 1.0f),
+ _delete_on_draw);
+}
+
+void DecalObject::ReceiveObjectMessageVAList( OBJECT_MSG::Type type, va_list args ) {
+ switch(type){
+ case OBJECT_MSG::SET_COLOR:
+ case OBJECT_MSG::SET_OVERBRIGHT: {
+ //vec3 old_color = color_tint_component_.temp_tint();
+ color_tint_component_.ReceiveObjectMessageVAList(type, args);
+ break;}
+ default:
+ Object::ReceiveObjectMessageVAList(type, args);
+ break;
+ }
+}
+
+void DecalObject::PreDrawFrame(float curr_game_time){
+ if (g_debug_runtime_disable_decal_object_pre_draw_frame) {
+ return;
+ }
+
+ if(decal_file_ref->special_type == 6 && (curr_game_time - spawn_time_ > 2.0f)){ // Water drops fade after 2 seconds
+ scenegraph_->map_editor->RemoveObject(this, scenegraph_, true);
+ }
+}
+
+void DecalObject::GetDesc(EntityDescription &desc) const {
+ Object::GetDesc(desc);
+ desc.AddString(EDF_FILE_PATH, obj_file);
+ color_tint_component_.AddToDescription(desc);
+}
+
+bool DecalObject::SetFromDesc( const EntityDescription& desc ) {
+ bool ret = Object::SetFromDesc(desc);
+ if( ret ) {
+ for(unsigned i=0; i<desc.fields.size(); ++i){
+ const EntityDescriptionField& field = desc.fields[i];
+ switch(field.type){
+ case EDF_FILE_PATH: {
+ std::string type_file;
+ field.ReadString(&type_file);
+ if(type_file != obj_file){
+ Load(type_file);
+ }
+ break;}
+ }
+ }
+ color_tint_component_.SetFromDescription(desc);
+ }
+ return ret;
+}
+
+void DecalObject::Load( const std::string& type_file ) {
+ obj_file = type_file;
+
+ //decal_file_ref = DecalFiles::Instance()->ReturnRef(type_file);
+ decal_file_ref = Engine::Instance()->GetAssetManager()->LoadSync<DecalFile>(type_file);
+
+ std::map<std::string,RC_DecalTexture>::iterator texit = textureCache.find(type_file);
+
+ if( texit == textureCache.end() )
+ {
+ texture = DecalTextures::Instance()->allocateTexture( decal_file_ref->color_map, decal_file_ref->normal_map );
+
+ textureCache[type_file] = texture;
+ }
+ else
+ {
+ LOGS << "Loading decal from texture cache rather than reallocating space for it" << std::endl;
+ texture = textureCache[type_file];
+ }
+}
diff --git a/Source/Objects/decalobject.h b/Source/Objects/decalobject.h
new file mode 100644
index 00000000..63d73db4
--- /dev/null
+++ b/Source/Objects/decalobject.h
@@ -0,0 +1,61 @@
+//-----------------------------------------------------------------------------
+// Name: decalobject.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Game/EntityDescription.h>
+#include <Game/color_tint_component.h>
+
+#include <Internal/hardware_specs.h>
+#include <Graphics/decaltextures.h>
+
+#include <Asset/Asset/decalfile.h>
+#include <Objects/object.h>
+
+#include <cmath>
+
+class EnvObject;
+class DecalEditor;
+
+class DecalObject : public Object {
+public:
+ virtual EntityType GetType() const { return _decal_object; }
+ DecalObject();
+ bool Initialize();
+ virtual void Dispose();
+ virtual void GetDisplayName(char* buf, int buf_size);
+ virtual void Draw();
+ virtual bool SetFromDesc( const EntityDescription& desc );
+ virtual void GetDesc(EntityDescription &desc) const;
+ void Load( const std::string& type_file );
+ void ReceiveObjectMessageVAList( OBJECT_MSG::Type type, va_list args );
+
+ virtual void PreDrawFrame(float curr_game_time);
+ ColorTintComponent color_tint_component_;
+ float spawn_time_;
+
+ RC_DecalTexture texture;
+ DecalFileRef decal_file_ref;
+protected:
+
+ static std::map<std::string,RC_DecalTexture> textureCache;
+};
diff --git a/Source/Objects/dynamiclightobject.cpp b/Source/Objects/dynamiclightobject.cpp
new file mode 100644
index 00000000..5c384ab9
--- /dev/null
+++ b/Source/Objects/dynamiclightobject.cpp
@@ -0,0 +1,183 @@
+//-----------------------------------------------------------------------------
+// Name: dynamiclightobject.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "dynamiclightobject.h"
+
+#include <Graphics/pxdebugdraw.h>
+#include <Graphics/graphics.h>
+#include <Game/hardcoded_assets.h>
+#include <Game/EntityDescription.h>
+
+#include <Objects/dynamiclightobject.h>
+#include <Main/scenegraph.h>
+#include <Utility/assert.h>
+#include <Internal/memwrite.h>
+#include <Math/vec3math.h>
+#include <Main/scenegraph.h>
+#include <Editors/map_editor.h>
+
+extern bool g_debug_runtime_disable_dynamic_light_object_draw;
+
+float RadiusFromScale(vec3 scale) {
+ return std::min(fabs(scale.x()), std::min(fabs(scale.y()), fabs(scale.z())));
+}
+
+void DynamicLightObject::Draw() {
+ if (g_debug_runtime_disable_dynamic_light_object_draw) {
+ return;
+ }
+
+ if(!Graphics::Instance()->media_mode() && light_id_ != -1 && scenegraph_ && scenegraph_->map_editor->state_ != MapEditor::kInGame){
+ const DynamicLight *light = scenegraph_->dynamic_light_collection.GetLightFromID(light_id_);
+ if(light){
+ mat4 transform;
+ if(selected_){
+ transform.SetScale(vec3(RadiusFromScale(scale_)));
+ transform.SetTranslationPart(translation_);
+ DebugDraw::Instance()->AddWireMesh(HardcodedPaths::paths[HardcodedPaths::kPointLight], transform, vec4(light->color, 0.025f), _delete_on_draw);
+ }
+ transform.SetScale(vec3(0.1f));
+ transform.SetTranslationPart(translation_);
+ DebugDraw::Instance()->AddWireMesh(HardcodedPaths::paths[HardcodedPaths::kPointLight], transform, vec4(light->color, 0.25f), _delete_on_draw);
+ }
+ }
+}
+
+bool DynamicLightObject::Initialize() {
+ LOG_ASSERT(light_id_ == -1);
+ light_id_ = scenegraph_->dynamic_light_collection.AddLight(GetTranslation(), color * (1.0f + overbright * 0.3f), RadiusFromScale(scale_));
+ return true;
+}
+
+void DynamicLightObject::Moved(Object::MoveType type) {
+ Object::Moved(type);
+ if(scenegraph_ && light_id_ != -1){
+ DynamicLight *light = scenegraph_->dynamic_light_collection.GetLightFromID(light_id_);
+ LOG_ASSERT(light != NULL);
+ if(light){
+ light->radius = RadiusFromScale(scale_);
+ light->pos = translation_;
+ }
+ }
+}
+
+void DynamicLightObject::Dispose() {
+ Object::Dispose();
+ if(scenegraph_ && light_id_ != -1){
+ bool success = scenegraph_->dynamic_light_collection.DeleteLight(light_id_);
+ LOG_ASSERT(success);
+ light_id_ = -1;
+ }
+}
+
+DynamicLightObject::DynamicLightObject() {
+ box_.dims = vec3(1.0f);
+ light_id_ = -1;
+ color = vec3(1.0f, 1.0f, 1.0f);
+ overbright = 0.0f;
+}
+
+DynamicLightObject::~DynamicLightObject() {
+ LOG_ASSERT(light_id_ == -1);
+}
+
+void DynamicLightObject::GetDesc(EntityDescription &desc) const {
+ /*
+ LOG_ASSERT(light_id_ != -1);
+ LOG_ASSERT(scenegraph_);
+ const DynamicLight *light = scenegraph_->dynamic_light_collection.GetLightFromID(light_id_);
+ LOG_ASSERT(light != NULL);
+ */
+ Object::GetDesc(desc);
+ desc.AddVec3(EDF_COLOR, color);
+ desc.AddFloat(EDF_OVERBRIGHT, overbright);
+}
+
+bool DynamicLightObject::SetFromDesc( const EntityDescription& desc ) {
+ LOG_ASSERT(light_id_ == -1);
+ bool ret = Object::SetFromDesc(desc);
+ if(ret) {
+ for(unsigned i=0; i<desc.fields.size(); ++i){
+ const EntityDescriptionField& field = desc.fields[i];
+ switch(field.type){
+ case EDF_COLOR: {
+ memread(color.entries, sizeof(float), 3, field.data);
+ break;}
+ case EDF_OVERBRIGHT: {
+ memread(&overbright, sizeof(float), 1, field.data);
+ break;}
+ }
+ }
+ }
+ return ret;
+}
+
+vec3 DynamicLightObject::GetTint() const {
+ return color;
+ /*
+ LOG_ASSERT(light_id_ != -1);
+ DynamicLight *light = scenegraph_->dynamic_light_collection.GetLightFromID(light_id_);
+ LOG_ASSERT(light != NULL);
+ return light->color;
+ */
+}
+
+float DynamicLightObject::GetOverbright() const {
+ return overbright;
+}
+
+void DynamicLightObject::ReceiveObjectMessageVAList( OBJECT_MSG::Type type, va_list args ) {
+ DynamicLight *light;
+ switch(type){
+ case OBJECT_MSG::SET_COLOR:
+ if(light_id_ != -1){
+ color = *va_arg(args, vec3*);
+ light = scenegraph_->dynamic_light_collection.GetLightFromID(light_id_);
+ if(light){
+ light->color = color * (1.0f + overbright * 0.3f);
+ }
+ }
+ break;
+ case OBJECT_MSG::SET_OVERBRIGHT:
+ if(light_id_ != -1){
+ overbright = *va_arg(args, float*);
+ light = scenegraph_->dynamic_light_collection.GetLightFromID(light_id_);
+ if(light){
+ light->color = color * (1.0f + overbright * 0.3f);
+ }
+ }
+ break;
+ default:
+ Object::ReceiveObjectMessageVAList(type, args);
+ break;
+ }
+}
+
+void DynamicLightObject::SetEnabled(bool val) {
+ if(!enabled_ && val){
+ light_id_ = scenegraph_->dynamic_light_collection.AddLight(GetTranslation(), color * (1.0f + overbright * 0.3f), RadiusFromScale(scale_));
+ } else if(enabled_ && !val){
+ scenegraph_->dynamic_light_collection.DeleteLight(light_id_);
+ light_id_ = -1;
+ }
+ Object::SetEnabled(val);
+}
diff --git a/Source/Objects/dynamiclightobject.h b/Source/Objects/dynamiclightobject.h
new file mode 100644
index 00000000..a6f4c4ce
--- /dev/null
+++ b/Source/Objects/dynamiclightobject.h
@@ -0,0 +1,54 @@
+//-----------------------------------------------------------------------------
+// Name: dynamiclightobject.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Objects/object.h>
+
+//-----------------------------------------------------------------------------
+// Class Definition
+//-----------------------------------------------------------------------------
+
+class DynamicLightObject: public Object
+{
+public:
+ virtual EntityType GetType() const { return _dynamic_light_object; }
+ virtual void Draw();
+ virtual bool Initialize();
+
+ virtual void Moved(Object::MoveType type);
+ virtual void Dispose();
+ virtual void GetDesc(EntityDescription &desc) const;
+ virtual bool SetFromDesc( const EntityDescription& desc );
+ vec3 GetTint() const;
+ float GetOverbright() const;
+ DynamicLightObject();
+ virtual ~DynamicLightObject();
+ void ReceiveObjectMessageVAList( OBJECT_MSG::Type type, va_list args );
+ virtual void SetEnabled(bool val);
+
+private:
+ int light_id_;
+
+ vec3 color;
+ float overbright;
+};
diff --git a/Source/Objects/editorcameraobject.cpp b/Source/Objects/editorcameraobject.cpp
new file mode 100644
index 00000000..076cf59a
--- /dev/null
+++ b/Source/Objects/editorcameraobject.cpp
@@ -0,0 +1,50 @@
+//-----------------------------------------------------------------------------
+// Name: editorcameraobject.h
+// Developer: Wolfire Games LLC
+// Author: Phillip Isola
+// Description: Camera object for the editors
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include <Objects/editorcameraobject.h>
+#include <Logging/logdata.h>
+#include <Internal/common.h>
+
+void EditorCameraObject::IgnoreInput(bool val) {
+ //ignore_input = val;
+ frozen = val;
+#ifdef _DEBUG
+ if (val) LOGI << "Ignoring camera input." << std::endl;
+ else LOGI << "No longer ignoring camera input." << std::endl;
+#endif
+}
+
+void EditorCameraObject::IgnoreMouseInput(bool val) {
+ //ignore_input = val;
+ ignore_mouse_input = val;
+}
+
+void EditorCameraObject::GetDisplayName(char* buf, int buf_size) {
+ if( GetName().empty() ) {
+ FormatString(buf, buf_size, "%d, Editor Camera", GetID());
+ } else {
+ FormatString(buf, buf_size, "%s, Editor Camera", GetName().c_str());
+ }
+}
+
diff --git a/Source/Objects/editorcameraobject.h b/Source/Objects/editorcameraobject.h
new file mode 100644
index 00000000..6364220d
--- /dev/null
+++ b/Source/Objects/editorcameraobject.h
@@ -0,0 +1,52 @@
+//-----------------------------------------------------------------------------
+// Name: editorcameraobject.h
+// Developer: Wolfire Games LLC
+// Author: Phillip Isola
+// Description: Camera object for the editors
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Objects/cameraobject.h>
+
+#ifdef _DEBUG
+#include <stdio.h>
+#endif
+
+class EditorCameraObject: public CameraObject {
+public:
+ virtual EntityType GetType() const { return _camera_type; }
+ EditorCameraObject() {
+ frozen=false;
+ speed=12;
+ rotation=0;
+ rotation2=0;
+ };
+
+ virtual ~EditorCameraObject()
+ {
+#ifdef _DEBUG
+ printf("editor camera destroyed\n");
+#endif
+ }
+
+ virtual void IgnoreInput(bool val);
+ void IgnoreMouseInput(bool val);
+ virtual void GetDisplayName(char* buf, int buf_size);
+};
diff --git a/Source/Objects/envobject.cpp b/Source/Objects/envobject.cpp
new file mode 100644
index 00000000..83a5f6a5
--- /dev/null
+++ b/Source/Objects/envobject.cpp
@@ -0,0 +1,1945 @@
+//-----------------------------------------------------------------------------
+// Name: envobject.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include <Physics/bulletobject.h>
+#include <Physics/bulletworld.h>
+#include <Physics/physics.h>
+
+#include <Utility/assert.h>
+#include <Utility/compiler_macros.h>
+
+#include <Graphics/graphics.h>
+#include <Graphics/particles.h>
+#include <Graphics/camera.h>
+#include <Graphics/shaders.h>
+#include <Graphics/textures.h>
+#include <Graphics/models.h>
+#include <Graphics/particles.h>
+#include <Graphics/sky.h>
+#include <Graphics/pxdebugdraw.h>
+#include <Graphics/detailobjectsurface.h>
+
+#include <Objects/movementobject.h>
+#include <Objects/riggedobject.h>
+#include <Objects/decalobject.h>
+#include <Objects/lightvolume.h>
+#include <Objects/envobject.h>
+#include <Objects/group.h>
+
+#include <Internal/datemodified.h>
+#include <Internal/collisiondetection.h>
+#include <Internal/timer.h>
+#include <Internal/filesystem.h>
+#include <Internal/memwrite.h>
+#include <Internal/profiler.h>
+#include <Internal/common.h>
+#include <Internal/config.h>
+#include <Internal/message.h>
+
+#include <Math/vec2math.h>
+#include <Math/vec3math.h>
+#include <Math/vec4math.h>
+#include <Math/quaternions.h>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <Asset/Asset/material.h>
+#include <Asset/Asset/image_sampler.h>
+#include <Asset/Asset/averagecolorasset.h>
+
+#include <Main/scenegraph.h>
+#include <Main/engine.h>
+
+#include <Sound/sound.h>
+#include <Editors/map_editor.h>
+#include <Compat/fileio.h>
+#include <Scripting/angelscript/ascontext.h>
+#include <Online/online.h>
+
+#include <SDL.h>
+#include <tinyxml.h>
+
+#include <cmath>
+#include <sstream>
+
+extern Timer game_timer;
+extern char* global_shader_suffix;
+extern bool g_simple_shadows;
+extern bool g_level_shadows;
+extern bool g_simple_water;
+extern bool g_no_decals;
+const int _shadow_update_delay = 50;
+extern bool shadow_cache_dirty;
+extern bool g_draw_collision;
+extern bool g_no_reflection_capture;
+extern bool g_make_invisible_visible;
+extern bool g_attrib_envobj_intancing_support;
+extern bool g_attrib_envobj_intancing_enabled;
+extern bool g_ubo_batch_multiplier_force_1x;
+
+extern bool g_debug_runtime_disable_env_object_draw;
+extern bool g_debug_runtime_disable_env_object_draw_depth_map;
+extern bool g_debug_runtime_disable_env_object_draw_detail_object_instances;
+extern bool g_debug_runtime_disable_env_object_draw_instances;
+extern bool g_debug_runtime_disable_env_object_draw_instances_transparent;
+extern bool g_debug_runtime_disable_env_object_pre_draw_camera;
+
+bool last_ofr_is_valid = false;
+std::string last_ofr_shader_name;
+int last_shader;
+
+struct EnvObjectGLState {
+ GLState gl_state;
+ EnvObjectGLState() {
+ gl_state.depth_test = true;
+ gl_state.cull_face = true;
+ gl_state.depth_write = true;
+ gl_state.blend = false;
+ }
+};
+
+static const EnvObjectGLState env_object_gl_state;
+
+static void UpdateDetailObjectSurfaces(EnvObject::DOSList *detail_object_surfaces,
+ const ObjectFileRef& ofr,
+ const TextureAssetRef& base_color,
+ const TextureAssetRef& base_normal,
+ const mat4 transform,
+ int model_id)
+{
+ //TODO: Make sure we aren't duplicating code from Terrain.cpp
+ for(int i=0, len=detail_object_surfaces->size(); i<len; ++i){
+ delete detail_object_surfaces->at(i);
+ }
+ detail_object_surfaces->clear();
+ detail_object_surfaces->resize(ofr->m_detail_object_layers.size());
+ int counter = 0;
+ for(EnvObject::DOSList::iterator iter = detail_object_surfaces->begin();
+ iter != detail_object_surfaces->end(); ++iter)
+ {
+ DetailObjectSurface*& dos = (*iter);
+ dos = new DetailObjectSurface();
+ DetailObjectLayer& dol = ofr->m_detail_object_layers[counter];
+ dos->AttachTo(Models::Instance()->GetModel(model_id), transform);
+ dos->GetTrisInPatches(transform);
+ dos->LoadDetailModel(dol.obj_path);
+ dos->LoadWeightMap(dol.weight_path);
+ dos->SetDensity(dol.density);
+ dos->SetNormalConform(dol.normal_conform);
+ dos->SetMinEmbed(dol.min_embed);
+ dos->tint_weight = dol.tint_weight;
+ dos->SetMaxEmbed(dol.max_embed);
+ dos->SetMinScale(dol.min_scale);
+ dos->SetMaxScale(dol.max_scale);
+ dos->SetViewDist(dol.view_dist);
+ dos->SetJitterDegrees(dol.jitter_degrees);
+ dos->SetOverbright(dol.overbright);
+ dos->SetCollisionType(dol.collision_type);
+ dos->SetBaseTextures(base_color, base_normal);
+ ++counter;
+ }
+}
+
+EnvObject::EnvObject():
+ bullet_object_(NULL),
+ csg_modified_(false),
+ model_id_(-1),
+ attached_(NULL),
+ placeholder_(false),
+ base_color_tint(1.0f),
+ normal_override_buffer_dirty(true),
+ no_navmesh(false)
+{
+ added_to_physics_scene_ = false;
+ collidable = true;
+}
+
+void EnvObject::UpdateParentHierarchy() {
+ bool found_movement_object = false;
+ Object* obj = this;
+ while(obj->parent){
+ obj = obj->parent;
+ if(obj->GetType() == _movement_object){
+ found_movement_object = true;
+ break;
+ }
+ }
+ if(found_movement_object && !attached_){
+ // object was not attached to a character and now is
+ RemovePhysicsShape();
+ attached_ = (MovementObject*)obj;
+ } else if(!found_movement_object && attached_){
+ // object was attached to a character and now is not
+ CreatePhysicsShape();
+ attached_ = NULL;
+ }
+}
+
+void EnvObject::HandleMaterialEvent( const std::string &the_event, const vec3 &event_pos ) {
+ MaterialRef material = ofr_material;
+ material->HandleEvent(the_event, event_pos);
+}
+
+const MaterialEvent& EnvObject::GetMaterialEvent( const std::string &the_event, const vec3 &event_pos, int *tri ) {
+ MaterialRef material = ofr_material;
+ return material->GetEvent(the_event);
+}
+
+const MaterialEvent& EnvObject::GetMaterialEvent( const std::string &the_event, const vec3 &event_pos, const std::string &mod, int *tri) {
+ MaterialRef material = ofr_material;
+ return material->GetEvent(the_event, mod);
+}
+
+EnvObject::~EnvObject() {
+ RemovePhysicsShape();
+ for(int i=0, len=detail_object_surfaces.size(); i<len; ++i){
+ delete detail_object_surfaces[i];
+ }
+}
+
+void EnvObject::Draw() {
+ if (g_debug_runtime_disable_env_object_draw) {
+ return;
+ }
+
+ EnvObject* instances[1];
+ instances[0] = this;
+ Camera* camera = ActiveCameras::Get();
+ Graphics* graphics = Graphics::Instance();
+ mat4 proj_view_mat = camera->GetProjMatrix() * camera->GetViewMatrix();
+ mat4 prev_proj_view_mat = camera->GetProjMatrix() * camera->prev_view_mat;
+ vec3 cam_pos = camera->GetPos();
+ std::vector<mat4> shadow_matrix;
+ shadow_matrix.resize(4);
+ for(int i=0; i<4; ++i){
+ shadow_matrix[i] = camera->biasMatrix * graphics->cascade_shadow_mat[i];
+ }
+ last_ofr_is_valid = false;
+ DrawInstances(instances, 1, proj_view_mat, prev_proj_view_mat, &shadow_matrix, cam_pos, Object::kFullDraw);
+ DrawDetailObjectInstances(instances, 1, Object::kFullDraw);
+}
+
+static void UpdateNormalOverride(EnvObject* obj, Model* model){
+ PROFILER_ZONE(g_profiler_ctx, "UpdateNormalOverride");
+ obj->normal_override.resize(model->faces.size()/3);
+ obj->normal_override_custom.resize(model->faces.size()/3, vec4(0.0f));
+ for(int i=0, len=obj->normal_override.size(); i<len; ++i){
+ vec3 vert[3];
+ for(int vert_index=0; vert_index<3; ++vert_index){
+ int start = model->faces[i*3+vert_index]*3;
+ for(int axis=0; axis<3; ++axis){
+ vert[vert_index][axis] = model->vertices[start+axis];
+ }
+ vert[vert_index] = obj->GetTransform() * vert[vert_index];
+ }
+ vec3 normal = normalize(cross(vert[1] - vert[0], vert[2] - vert[0]));
+ obj->normal_override[i] = mix(normal, obj->normal_override_custom[i].xyz(), obj->normal_override_custom[i][3]);//model->face_normals[i];
+ }
+}
+
+const size_t kAttribIdCountVboInstancing = 11;
+const size_t kAttribIdCountUboInstancing = 7;
+static int attrib_ids[kAttribIdCountVboInstancing];
+
+static void SetupAttribPointers(bool shader_changed, Model* model, VBORingContainer& env_object_model_translation_instance_vbo, VBORingContainer& env_object_model_scale_instance_vbo, VBORingContainer& env_object_model_rotation_quat_instance_vbo, VBORingContainer& env_object_color_tint_instance_vbo, VBORingContainer& env_object_detail_scale_instance_vbo, Shaders* shaders, int the_shader, Graphics* graphics) {
+ bool attrib_envobj_instancing = g_attrib_envobj_intancing_support && g_attrib_envobj_intancing_enabled;
+ int attrib_count = attrib_envobj_instancing ? kAttribIdCountVboInstancing : kAttribIdCountUboInstancing;
+ if(shader_changed){
+ for(int i=0; i<attrib_count; ++i){
+ const char* attrib_str;
+ int num_el;
+ VBOContainer* vbo = NULL;
+ VBORingContainer* vboRing = NULL;
+ bool instanced;
+ switch(i){
+ case 0:
+ attrib_str = "vertex_attrib";
+ num_el = 3;
+ vbo = &model->VBO_vertices;
+ instanced = false;
+ break;
+ case 1:
+ attrib_str = "tangent_attrib";
+ num_el = 3;
+ vbo = &model->VBO_tangents;
+ instanced = false;
+ break;
+ case 2:
+ attrib_str = "bitangent_attrib";
+ num_el = 3;
+ vbo = &model->VBO_bitangents;
+ instanced = false;
+ break;
+ case 3:
+ attrib_str = "normal_attrib";
+ num_el = 3;
+ vbo = &model->VBO_normals;
+ instanced = false;
+ break;
+ case 4:
+ attrib_str = "tex_coord_attrib";
+ num_el = 2;
+ vbo = &model->VBO_tex_coords;
+ instanced = false;
+ break;
+ case 5:
+ attrib_str = "plant_stability_attrib";
+ num_el = 3;
+ vbo = &model->VBO_aux;
+ instanced = false;
+ break;
+ case 6:
+ attrib_str = "model_translation_attrib";
+ num_el = 3;
+ vboRing = &env_object_model_translation_instance_vbo;
+ instanced = true;
+ break;
+ case 7:
+ attrib_str = "model_scale_attrib";
+ num_el = 3;
+ vboRing = &env_object_model_scale_instance_vbo;
+ instanced = true;
+ break;
+ case 8:
+ attrib_str = "model_rotation_quat_attrib";
+ num_el = 4;
+ vboRing = &env_object_model_rotation_quat_instance_vbo;
+ instanced = true;
+ break;
+ case 9:
+ attrib_str = "color_tint_attrib";
+ num_el = 4;
+ vboRing = &env_object_color_tint_instance_vbo;
+ instanced = true;
+ break;
+ case 10:
+ attrib_str = "detail_scale_attrib";
+ num_el = 4;
+ vboRing = &env_object_detail_scale_instance_vbo;
+ instanced = true;
+ break;
+ default:
+ __builtin_unreachable();
+ break;
+ }
+ CHECK_GL_ERROR();
+ attrib_ids[i] = shaders->returnShaderAttrib(attrib_str, the_shader);
+ CHECK_GL_ERROR();
+ if(attrib_ids[i] != -1 && ((vbo && vbo->valid()) || (vboRing && vboRing->valid()))){
+ uintptr_t buffer_offset = 0;
+ if(vbo) { vbo->Bind(); buffer_offset = 0; }
+ if(vboRing) { vboRing->Bind(); buffer_offset = vboRing->offset(); }
+ graphics->EnableVertexAttribArray(attrib_ids[i]);
+ CHECK_GL_ERROR();
+ glVertexAttribPointer(attrib_ids[i], num_el, GL_FLOAT, false, num_el*sizeof(GLfloat), (void*)buffer_offset);
+ CHECK_GL_ERROR();
+ glVertexAttribDivisorARB(attrib_ids[i], instanced ? 1 : 0);
+ CHECK_GL_ERROR();
+ }
+ }
+ } else {
+ for(int i=0; i<attrib_count; ++i){
+ if(attrib_ids[i] != -1){
+ int num_el;
+ VBOContainer* vbo = NULL;
+ VBORingContainer* vboRing = NULL;
+ bool instanced;
+ switch(i){
+ case 0:
+ num_el = 3;
+ vbo = &model->VBO_vertices;
+ instanced = false;
+ break;
+ case 1:
+ num_el = 3;
+ vbo = &model->VBO_tangents;
+ instanced = false;
+ break;
+ case 2:
+ num_el = 3;
+ vbo = &model->VBO_bitangents;
+ instanced = false;
+ break;
+ case 3:
+ num_el = 3;
+ vbo = &model->VBO_normals;
+ instanced = false;
+ break;
+ case 4:
+ num_el = 2;
+ vbo = &model->VBO_tex_coords;
+ instanced = false;
+ break;
+ case 5:
+ num_el = 3;
+ vbo = &model->VBO_aux;
+ instanced = false;
+ break;
+ case 6:
+ num_el = 3;
+ vboRing = &env_object_model_translation_instance_vbo;
+ instanced = true;
+ break;
+ case 7:
+ num_el = 3;
+ vboRing = &env_object_model_scale_instance_vbo;
+ instanced = true;
+ break;
+ case 8:
+ num_el = 4;
+ vboRing = &env_object_model_rotation_quat_instance_vbo;
+ instanced = true;
+ break;
+ case 9:
+ num_el = 4;
+ vboRing = &env_object_color_tint_instance_vbo;
+ instanced = true;
+ break;
+ case 10:
+ num_el = 4;
+ vboRing = &env_object_detail_scale_instance_vbo;
+ instanced = true;
+ break;
+ default:
+ __builtin_unreachable();
+ break;
+ }
+ if((vbo && vbo->valid()) || (vboRing && vboRing->valid())){
+ uintptr_t buffer_offset = 0;
+ if(vbo) { vbo->Bind(); buffer_offset = 0; }
+ if(vboRing) { vboRing->Bind(); buffer_offset = vboRing->offset(); }
+ graphics->EnableVertexAttribArray(attrib_ids[i]);
+ glVertexAttribPointer(attrib_ids[i], num_el, GL_FLOAT, false, num_el*sizeof(GLfloat), (void*)buffer_offset);
+ glVertexAttribDivisorARB(attrib_ids[i], instanced ? 1 : 0);
+ }
+ }
+ }
+ }
+}
+
+void EnvObject::DrawInstances(EnvObject** instance_array, int num_instances, const mat4& proj_view_matrix, const mat4& prev_proj_view_matrix, const std::vector<mat4>* shadow_matrix, const vec3& cam_pos, Object::DrawType type) {
+ if (g_debug_runtime_disable_env_object_draw_instances) {
+ return;
+ }
+
+ if (g_debug_runtime_disable_env_object_draw_instances_transparent && transparent) {
+ return;
+ }
+
+ if (type == Object::DrawType::kDrawDepthOnly && transparent) {
+ return;
+ }
+
+ PROFILER_GPU_ZONE(g_profiler_ctx, "EnvObject::DrawInstances");
+
+ if(g_draw_collision){
+ if(GetCollisionModelID() == -1 || ofr->no_collision){
+ return;
+ }
+ }
+ PROFILER_ENTER(g_profiler_ctx, "Setup");
+
+ // Tessellation is ignored when preloading shaders right now because the use-case isn't obvious.
+ // Is it set once? Is it per-object? Where would it be set?
+ bool use_tesselation = false;// GLEW_ARB_tessellation_shader;
+ Shaders* shaders = Shaders::Instance();
+ Models* models = Models::Instance();
+ Graphics* graphics = Graphics::Instance();
+ Textures* textures = Textures::Instance();
+ Timer* timer = &game_timer;
+ Model* model = &models->GetModel(model_id_);
+ if(g_draw_collision){
+ model = &models->GetModel(GetCollisionModelID());
+ }
+ Camera* cam = ActiveCameras::Get();
+
+ PROFILER_ENTER(g_profiler_ctx, "GL State");
+ GLState gl_state;
+ gl_state.cull_face = !ofr->double_sided;;
+ gl_state.depth_test = true;
+ if(type == Object::kDecal){
+ gl_state.blend = true;
+ gl_state.depth_write = false;
+ } else if(transparent) {
+ gl_state.blend = false;
+ gl_state.depth_write = false;
+ if(g_simple_water){
+ gl_state.depth_write = true;
+ }
+ } else {
+ gl_state.blend = false;
+ gl_state.depth_write = true;
+ }
+ graphics->setGLState(gl_state);
+
+ if(graphics->use_sample_alpha_to_coverage && !transparent){
+ glEnable( GL_SAMPLE_ALPHA_TO_COVERAGE );
+ }
+ PROFILER_LEAVE(g_profiler_ctx); // GL State
+
+ static int ubo_batch_size_multiplier = 1;
+ static GLint max_ubo_size = -1;
+ if(max_ubo_size == -1) {
+ glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &max_ubo_size);
+
+ if(max_ubo_size >= 131072) {
+ ubo_batch_size_multiplier = 8;
+ }
+ else if(max_ubo_size >= 65536) {
+ ubo_batch_size_multiplier = 4;
+ }
+ else if(max_ubo_size >= 32768) {
+ ubo_batch_size_multiplier = 2;
+ }
+ }
+
+ int the_shader = last_shader;
+
+ bool use_textures = true;
+ if(type == Object::kDrawDepthOnly || type == Object::kDrawDepthNoAA || type == Object::kDrawAllShadowCascades){
+ use_textures = false;
+ }
+
+ bool shader_changed = true;
+ if(last_ofr_is_valid && ofr->shader_name == last_ofr_shader_name){
+ shader_changed = false;
+ }
+
+ if(shader_changed){
+ PROFILER_ENTER(g_profiler_ctx, "Shader");
+
+ PROFILER_ENTER(g_profiler_ctx, "Create shader string");
+ const int kShaderStrSize = 1024;
+ char buf[2][kShaderStrSize];
+ char* shader_str[2] = {buf[0], buf[1]};
+ if(g_make_invisible_visible && ofr->shader_name.find("#INVISIBLE") != ofr->shader_name.npos && type == Object::kFullDraw) {
+ size_t index = ofr->shader_name.find("#INVISIBLE");
+ std::string name = ofr->shader_name.substr(0, index);
+ name += ofr->shader_name.substr(index + strlen("#INVISIBLE"));
+
+ strcpy(shader_str[0], name.c_str());
+ placeholder_ = true;
+ } else {
+ strcpy(shader_str[0], ofr->shader_name.c_str());
+ }
+ if(type == Object::kDrawDepthOnly || type == Object::kDrawDepthNoAA || type == Object::kDrawAllShadowCascades || type == Object::kWireframe){
+ } else if(type == Object::kDecal){
+ FormatString(shader_str[1], kShaderStrSize, "%s #DECAL", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if (config["decal_normals"].toNumber<bool>()){
+ FormatString(shader_str[1], kShaderStrSize, "%s #DECAL_NORMALS", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if (g_draw_collision){
+ FormatString(shader_str[1], kShaderStrSize, "%s #COLLISION", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if (placeholder_){
+ FormatString(shader_str[1], kShaderStrSize, "%s #HALFTONE_STIPPLE", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if (type == Object::kWireframe){
+ FormatString(shader_str[1], kShaderStrSize, "%s #WIREFRAME", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+
+ FormatString(shader_str[1], kShaderStrSize, "%s %s", shader_str[0], global_shader_suffix);
+ PROFILER_LEAVE(g_profiler_ctx); // Create shader string
+ PROFILER_ENTER(g_profiler_ctx, "returnProgram");
+ the_shader = shaders->returnProgram(shader_str[1], use_tesselation?Shaders::kTesselation:Shaders::kNone);
+ last_shader = the_shader;
+ PROFILER_LEAVE(g_profiler_ctx); // returnProgram
+
+ PROFILER_ENTER(g_profiler_ctx, "setProgram");
+ shaders->setProgram(the_shader);
+ PROFILER_LEAVE(g_profiler_ctx); // setProgram
+ PROFILER_ENTER(g_profiler_ctx, "Misc uniforms");
+ shaders->SetUniformMat4("projection_view_mat", proj_view_matrix);
+ shaders->SetUniformMat4("prev_projection_view_mat", prev_proj_view_matrix);
+ shaders->SetUniformFloat("time",timer->GetRenderTime());
+ shaders->SetUniformVec3("cam_pos", cam_pos);
+ if(type == Object::kFullDraw){
+ if(shadow_matrix){
+ std::vector<mat4> temp_shadow_matrix = *shadow_matrix;
+ if(g_simple_shadows || !g_level_shadows){
+ temp_shadow_matrix[3] = cam->biasMatrix * graphics->simple_shadow_mat;
+ }
+ shaders->SetUniformMat4Array("shadow_matrix", temp_shadow_matrix);
+ }
+ shaders->SetUniformVec3("ws_light", scenegraph_->primary_light.pos);
+ shaders->SetUniformVec4("primary_light_color",vec4(scenegraph_->primary_light.color,
+ scenegraph_->primary_light.intensity));
+ }
+ PROFILER_LEAVE(g_profiler_ctx); // Misc uniforms
+
+ PROFILER_LEAVE(g_profiler_ctx); // Shader
+ }
+ PROFILER_ENTER(g_profiler_ctx, "Textures");
+ textures->bindTexture(texture_ref_[0], TEX_COLOR);
+ if (use_textures) {
+ textures->bindTexture(normal_texture_ref_[0], TEX_NORMAL);
+ if(!translucency_texture_ref_.empty()){
+ textures->bindTexture(translucency_texture_ref_[0], TEX_TRANSLUCENCY);
+ }
+ if(weight_map_ref_.valid()){
+ textures->bindTexture(weight_map_ref_, 5);
+
+ vec4 temp(0.0f, 0.0f, 0.0f, 0.0f);
+ for (unsigned int i = 0; i < detail_texture_color_indices_.size(); i++) {
+ temp[i] = (float)detail_texture_color_indices_[i];
+ }
+ shaders->SetUniformVec4("detail_color_indices", temp);
+
+ for (unsigned int i = 0; i < detail_texture_normal_indices_.size(); i++) {
+ temp[i] = (float)detail_texture_normal_indices_[i];
+ }
+ shaders->SetUniformVec4("detail_normal_indices", temp);
+
+ for(unsigned i=0; i<detail_texture_color_srgb_.size(); ++i){
+ const int kBufSize = 256;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "avg_color%d",i);
+ shaders->SetUniformVec4(buf, detail_texture_color_srgb_[i]);
+ }
+ }
+ if(shader_changed){
+ textures->bindTexture(scenegraph_->sky->GetSpecularCubeMapTexture(), TEX_SPEC_CUBEMAP);
+ if(g_simple_shadows || !g_level_shadows){
+ textures->bindTexture(graphics->static_shadow_depth_ref, TEX_SHADOW);
+ } else {
+ textures->bindTexture(graphics->cascade_shadow_depth_ref, TEX_SHADOW);
+ }
+ if(scenegraph_->light_probe_collection.light_volume_enabled && scenegraph_->light_probe_collection.ambient_3d_tex.valid()){
+ textures->bindTexture(scenegraph_->light_probe_collection.ambient_3d_tex, 16);
+ }
+ if(weight_map_ref_.valid()){
+ textures->bindTexture(textures->getDetailColorArray(), 6);
+ textures->bindTexture(textures->getDetailNormalArray(), 7);
+ }
+ }
+ }
+ PROFILER_LEAVE(g_profiler_ctx); // Textures
+
+ if((type == Object::kFullDraw || type == Object::kWireframe) && shader_changed){
+ PROFILER_ZONE(g_profiler_ctx, "kFullDraw uniforms");
+ shaders->SetUniformFloat("haze_mult", scenegraph_->haze_mult);
+ shaders->SetUniformInt("reflection_capture_num", scenegraph_->ref_cap_matrix.size());
+ if(!scenegraph_->ref_cap_matrix.empty()){
+ assert(!scenegraph_->ref_cap_matrix_inverse.empty());
+ shaders->SetUniformMat4Array("reflection_capture_matrix", scenegraph_->ref_cap_matrix);
+ shaders->SetUniformMat4Array("reflection_capture_matrix_inverse", scenegraph_->ref_cap_matrix_inverse);
+ }
+
+ if(scenegraph_->light_probe_collection.ShaderNumLightProbes() == 0){
+ shaders->SetUniformInt("light_volume_num", 0);
+ shaders->SetUniformInt("num_tetrahedra", 0);
+ shaders->SetUniformInt("num_light_probes", 0);
+ } else {
+ std::vector<mat4> light_volume_matrix;
+ std::vector<mat4> light_volume_matrix_inverse;
+ for(int i=0, len=scenegraph_->light_volume_objects_.size(); i<len; ++i){
+ Object* obj = scenegraph_->light_volume_objects_[i];
+ const mat4 &mat = obj->GetTransform();
+ light_volume_matrix.push_back(mat);
+ light_volume_matrix_inverse.push_back(invert(mat));
+ }
+ shaders->SetUniformInt("light_volume_num", light_volume_matrix.size());
+ if(!light_volume_matrix.empty()){
+ assert(!light_volume_matrix_inverse.empty());
+ shaders->SetUniformMat4Array("light_volume_matrix", light_volume_matrix);
+ shaders->SetUniformMat4Array("light_volume_matrix_inverse", light_volume_matrix_inverse);
+ }
+
+ shaders->SetUniformInt("num_tetrahedra", scenegraph_->light_probe_collection.ShaderNumTetrahedra());
+ shaders->SetUniformInt("num_light_probes", scenegraph_->light_probe_collection.ShaderNumLightProbes());
+ shaders->SetUniformVec3("grid_bounds_min", scenegraph_->light_probe_collection.grid_lookup.bounds[0]);
+ shaders->SetUniformVec3("grid_bounds_max", scenegraph_->light_probe_collection.grid_lookup.bounds[1]);
+ shaders->SetUniformInt("subdivisions_x", scenegraph_->light_probe_collection.grid_lookup.subdivisions[0]);
+ shaders->SetUniformInt("subdivisions_y", scenegraph_->light_probe_collection.grid_lookup.subdivisions[1]);
+ shaders->SetUniformInt("subdivisions_z", scenegraph_->light_probe_collection.grid_lookup.subdivisions[2]);
+ shaders->SetUniformInt(shaders->GetTexUniform(TEX_AMBIENT_COLOR_BUFFER), TEX_AMBIENT_COLOR_BUFFER);
+ shaders->SetUniformInt(shaders->GetTexUniform(TEX_AMBIENT_GRID_DATA), TEX_AMBIENT_GRID_DATA);
+ if(scenegraph_->light_probe_collection.light_probe_buffer_object_id != -1){
+ glBindBuffer(GL_TEXTURE_BUFFER, scenegraph_->light_probe_collection.light_probe_buffer_object_id);
+ }
+ }
+
+ if( g_no_reflection_capture == false) {
+ textures->bindTexture(scenegraph_->cubemaps, 19);
+ }
+
+ if(use_textures){
+ PROFILER_ZONE(g_profiler_ctx, "Decals and lights");
+ scenegraph_->BindDecals(the_shader);
+ scenegraph_->BindLights(the_shader);
+ }
+ //textures->bindTexture(graphics->screen_color_tex, 17);
+ textures->bindTexture(graphics->post_effects.temp_screen_tex, 17);
+ textures->bindTexture(graphics->screen_depth_tex, 18);
+ }
+
+ PROFILER_ENTER(g_profiler_ctx, "Attribs");
+ if(!model->vbo_loaded){
+ model->createVBO();
+ }
+ model->VBO_faces.Bind();
+
+ int kBatchSize = 256 * (!g_ubo_batch_multiplier_force_1x ? ubo_batch_size_multiplier : 1);
+ const bool ignore_multiplier = true;
+ static VBORingContainer env_object_model_translation_instance_vbo(sizeof(vec3) * kBatchSize * 16, kVBOFloat | kVBOStream, ignore_multiplier);
+ static VBORingContainer env_object_model_scale_instance_vbo(sizeof(vec3) * kBatchSize * 15, kVBOFloat | kVBOStream, ignore_multiplier);
+ static VBORingContainer env_object_model_rotation_quat_instance_vbo(sizeof(vec4) * kBatchSize * 17, kVBOFloat | kVBOStream, ignore_multiplier);
+ static VBORingContainer env_object_color_tint_instance_vbo(sizeof(vec4) * kBatchSize * 14, kVBOFloat | kVBOStream, ignore_multiplier);
+ static VBORingContainer env_object_detail_scale_instance_vbo(sizeof(vec4) * kBatchSize * 18, kVBOFloat | kVBOStream, ignore_multiplier);
+
+ PROFILER_LEAVE(g_profiler_ctx); // Attribs
+ PROFILER_LEAVE(g_profiler_ctx); // Setup
+
+ bool attrib_envobj_instancing = g_attrib_envobj_intancing_support && g_attrib_envobj_intancing_enabled;
+
+ int instance_block_index = shaders->GetUBOBindIndex(the_shader, "InstanceInfo");
+ if (attrib_envobj_instancing || (unsigned)instance_block_index != GL_INVALID_INDEX)
+ {
+ PROFILER_ENTER(g_profiler_ctx, "Setup uniform block");
+ GLint block_size = !attrib_envobj_instancing ? shaders->returnShaderBlockSize(instance_block_index, the_shader) : -1;
+
+ static UniformRingBuffer env_object_instance_buffer;
+ if(!attrib_envobj_instancing && env_object_instance_buffer.gl_id == -1){
+ env_object_instance_buffer.Create(2 * 1024 * 1024);
+ }
+ PROFILER_LEAVE(g_profiler_ctx); // Setup uniform block
+
+ if(g_draw_collision){
+ kBatchSize = 1;
+ }
+
+ static GLubyte blockBuffer[131072]; // Big enough for 8x the OpenGL guaranteed minimum size. Max supported by shader flags currently. 16x or higher could be added if new platforms end up having > 128k frequently
+
+ static std::vector<vec3> model_translation_buffer;
+ model_translation_buffer.resize(kBatchSize);
+
+ static std::vector<vec3> model_scale_buffer;
+ static std::vector<vec4> model_rotation_quat_buffer;
+ static std::vector<vec4> color_tint_buffer;
+ static std::vector<vec4> detail_scale_buffer;
+
+ if(attrib_envobj_instancing) {
+ model_scale_buffer.resize(kBatchSize);
+ model_rotation_quat_buffer.resize(kBatchSize);
+ color_tint_buffer.resize(kBatchSize);
+ detail_scale_buffer.resize(kBatchSize);
+ }
+
+ for(int i=0; i<num_instances; i+=kBatchSize){
+ vec3* model_translation_attrib = &model_translation_buffer[0];
+ vec3* model_scale = attrib_envobj_instancing ? &model_scale_buffer[0] : (vec3*)((uintptr_t)blockBuffer + 0*sizeof(float)*4);
+ vec4* model_rotation_quat = attrib_envobj_instancing ? &model_rotation_quat_buffer[0] : (vec4*)((uintptr_t)blockBuffer + 1*sizeof(float)*4);
+ vec4* color_tint = attrib_envobj_instancing ? &color_tint_buffer[0] : (vec4*)((uintptr_t)blockBuffer + 2*sizeof(float)*4);
+ vec4* detail_scale = attrib_envobj_instancing ? &detail_scale_buffer[0] : (vec4*)((uintptr_t)blockBuffer + 3*sizeof(float)*4);
+ if(!attrib_envobj_instancing) {
+ glUniformBlockBinding(shaders->programs[the_shader].gl_program, instance_block_index, 0);
+ }
+ int to_draw = min(kBatchSize, num_instances-i);
+ PROFILER_ENTER(g_profiler_ctx, "Setup batch data");
+ for(int j=0; j<to_draw; ++j){
+ EnvObject* obj = instance_array[i+j];
+ PlantComponent* plant_component = obj->plant_component_.get();
+ {
+ *model_scale = obj->scale_;
+ *model_rotation_quat = *(vec4*)&obj->GetRotation();
+ *model_translation_attrib = obj->translation_;
+ if(plant_component && plant_component->IsActive()){
+ // TODO: This used to calculate a pivot. Is that important anymore, or can it just rotate around its origin?
+ //if(!plant_component->IsPivotCalculated()){
+ // plant_component->SetPivot(*scenegraph_->bullet_world_,
+ // obj->sphere_center_, obj->sphere_radius_);
+ //}
+ quaternion plant_rotation = plant_component->GetQuaternion(obj->sphere_radius_) * obj->GetRotation();
+ *model_rotation_quat = *(vec4*)&plant_rotation;
+ }
+ }
+ {
+ *color_tint = obj->GetDisplayTint();
+ if(plant_component){
+ (*color_tint)[3] = plant_component->GetPlantShake(obj->sphere_radius_);
+ } else {
+ (*color_tint)[3] = 0.0f;
+ }
+ if(attached_ != NULL){
+ (*color_tint)[3] = -1.0f;
+ }
+ }
+ {
+ vec4 scale = obj->GetScale();
+ float scaled_up = max(scale[0],max(scale[1],scale[2]));
+ float texel_scale = scaled_up/model->texel_density * 0.15f;
+ vec4 scale_vec(1.0f);
+ for(int k=0; k<(int)ofr->m_detail_map_scale.size(); ++k){
+ scale_vec[k] = texel_scale * ofr->m_detail_map_scale[k];
+ }
+ *detail_scale = scale_vec;
+ }
+
+ ++model_translation_attrib;
+ if(attrib_envobj_instancing) {
+ ++model_scale;
+ ++model_rotation_quat;
+ ++color_tint;
+ ++detail_scale;
+ } else {
+ model_scale = (vec3*)((uintptr_t)model_scale+64);
+ model_rotation_quat = (vec4*)((uintptr_t)model_rotation_quat+64);
+ color_tint = (vec4*)((uintptr_t)color_tint+64);
+ detail_scale = (vec4*)((uintptr_t)detail_scale+64);
+ }
+
+ if(g_draw_collision){
+ if(obj->normal_override_buffer_dirty){
+ if(!obj->normal_override_buffer.valid()){
+ obj->normal_override_buffer = Textures::Instance()->makeBufferTexture(obj->normal_override.size()*4*3, GL_RGB32F);
+ }
+ UpdateNormalOverride(obj, model);
+ PROFILER_ZONE(g_profiler_ctx, "UpdateNormalOverride buffer texture");
+ Textures::Instance()->updateBufferTexture(obj->normal_override_buffer, obj->normal_override.size()*4*3, &obj->normal_override[0]);
+ obj->normal_override_buffer_dirty = false;
+ }
+ textures->bindTexture(obj->normal_override_buffer, TEX_LIGHT_DECAL_DATA_BUFFER);
+ }
+ }
+ PROFILER_LEAVE(g_profiler_ctx); // Setup batch data
+ {
+ env_object_model_translation_instance_vbo.Fill(sizeof(vec3) * to_draw, &model_translation_buffer[0]);
+
+ if(attrib_envobj_instancing) {
+ env_object_model_scale_instance_vbo.Fill(sizeof(vec3) * to_draw, &model_scale_buffer[0]);
+ env_object_model_rotation_quat_instance_vbo.Fill(sizeof(vec4) * to_draw, &model_rotation_quat_buffer[0]);
+ env_object_color_tint_instance_vbo.Fill(sizeof(vec4) * to_draw, &color_tint_buffer[0]);
+ env_object_detail_scale_instance_vbo.Fill(sizeof(vec4) * to_draw, &detail_scale_buffer[0]);
+ }
+
+ SetupAttribPointers(shader_changed, model, env_object_model_translation_instance_vbo, env_object_model_scale_instance_vbo, env_object_model_rotation_quat_instance_vbo, env_object_color_tint_instance_vbo, env_object_detail_scale_instance_vbo, shaders, the_shader, graphics);
+ shader_changed = false;
+
+ if(!attrib_envobj_instancing) {
+ env_object_instance_buffer.Fill(block_size / kBatchSize * to_draw, blockBuffer);
+ glBindBufferRange( GL_UNIFORM_BUFFER, 0, env_object_instance_buffer.gl_id, env_object_instance_buffer.offset, env_object_instance_buffer.next_offset - env_object_instance_buffer.offset);
+ }
+ }
+ CHECK_GL_ERROR();
+ {
+ PROFILER_ZONE(g_profiler_ctx, "glDrawElementsInstanced");
+ graphics->DrawElementsInstanced(use_tesselation?GL_PATCHES:GL_TRIANGLES, model->faces.size(), GL_UNSIGNED_INT, 0, to_draw);
+ }
+ CHECK_GL_ERROR();
+ }
+ }
+
+ graphics->ResetVertexAttribArrays();
+ graphics->BindArrayVBO(0);
+ graphics->BindElementVBO(0);
+
+ int attrib_count = attrib_envobj_instancing ? kAttribIdCountVboInstancing : kAttribIdCountUboInstancing;
+ for(int i=0; i<attrib_count; ++i){
+ if(attrib_ids[i] != -1){
+ glVertexAttribDivisorARB(attrib_ids[i], 0);
+ }
+ }
+
+ if(graphics->use_sample_alpha_to_coverage){
+ glDisable( GL_SAMPLE_ALPHA_TO_COVERAGE );
+ }
+
+ last_ofr_is_valid = ofr.valid();
+ if(last_ofr_is_valid) {
+ last_ofr_shader_name = ofr->shader_name;
+ }
+}
+
+void EnvObject::DrawDetailObjectInstances(EnvObject** instance_array, int num_instances, Object::DrawType type) {
+ if (g_debug_runtime_disable_env_object_draw_detail_object_instances) {
+ return;
+ }
+
+ // Note: Should never get called with Object::kDrawDepthOnly. When this comment was written, that was true
+
+ if(!detail_object_surfaces.empty() && type == Object::kFullDraw){
+ for(int i=0; i<num_instances; ++i){
+ EnvObject* obj = instance_array[i];
+ for(DOSList::iterator iter = obj->detail_object_surfaces.begin();
+ iter != obj->detail_object_surfaces.end(); ++iter)
+ {
+ (*iter)->Draw(obj->GetTransform(), DetailObjectSurface::ENVOBJECT, obj->GetDisplayTint(), scenegraph_->sky->GetSpecularCubeMapTexture(), &scenegraph_->light_probe_collection,scenegraph_);
+ }
+ }
+ }
+}
+
+void EnvObject::PreDrawCamera(float curr_game_time) {
+ if (g_debug_runtime_disable_env_object_pre_draw_camera) {
+ return;
+ }
+
+ for(DOSList::iterator iter = detail_object_surfaces.begin();
+ iter != detail_object_surfaces.end(); ++iter)
+ {
+ (*iter)->PreDrawCamera(transform_);
+ }
+}
+
+void EnvObject::UpdateDetailScale() {
+ if(!detail_texture_color_.empty()){
+ vec4 scale = GetScale();
+ float scaled_up = max(scale[0],max(scale[1],scale[2]));
+ Model* model = &Models::Instance()->GetModel(model_id_);
+ float texel_scale = scaled_up/model->texel_density * 0.15f;
+ vec4 scale_vec(1.0f);
+ for(int i=0; i<(int)ofr->m_detail_map_scale.size(); ++i){
+ scale_vec[i] = texel_scale * ofr->m_detail_map_scale[i];
+ }
+ }
+}
+
+void EnvObject::GetObj2World(float *obj2world) {
+ const float *gl_matrix = transform_;
+ memcpy(obj2world,gl_matrix,sizeof(float) * 16);
+}
+
+
+
+void EnvObject::EnvInterpolate(uint16_t pending_updates) {
+ /*delta_time_between_interpolation_frames = (next_time - current_time) / 1000.0;
+
+ interpolation_step += (1.0 / (delta_time_between_interpolation_frames / game_timer.GetFrameTime()));
+ double catchup = (pending_updates > 2) * pending_updates * pending_updates / 100.0;
+ interpolation_step += catchup;
+
+ if (interpolation_step > 1.0 || interpolation_time > delta_time_between_interpolation_frames) {
+ interpolation_step = 0.0;
+ interpolation_time = 0.0;
+ interp = false;
+ }
+ else {
+ vec3 new_scale = lerp(curr_scale, next_scale, interpolation_step);
+ vec3 new_translation = lerp(curr_translation, next_translation, interpolation_step);
+ quaternion new_rot = Slerp(curr_rot, next_rot, interpolation_step);
+
+ SetTranslation(new_translation);
+ SetScale(new_scale);
+ SetRotation(new_rot);
+ }
+
+
+ interpolation_time += game_timer.GetFrameTime();
+ */
+
+ //LOGI << "first_frame" << first_frame << " Interp_step: " << interpolation_step << " delta: " << delta << " Frame time: " << game_timer.GetFrameTime() << " Interp time: " << interpolation_time << " pending transformations:" << std::endl;
+
+}
+
+void EnvObject::DrawDepthMap(const mat4& proj_view_matrix, const vec4* cull_planes, int num_cull_planes, Object::DrawType draw_type) {
+ if (g_debug_runtime_disable_env_object_draw_depth_map) {
+ return;
+ }
+
+ EnvObject* instances[1];
+ instances[0] = this;
+ Camera* camera = ActiveCameras::Get();
+ Graphics* graphics = Graphics::Instance();
+ vec3 cam_pos = camera->GetPos();
+ std::vector<mat4> shadow_matrix;
+ shadow_matrix.resize(4);
+ for(int i=0; i<4; ++i){
+ shadow_matrix[i] = camera->biasMatrix * graphics->cascade_shadow_mat[i];
+ }
+ last_ofr_is_valid = false;
+ DrawInstances(instances, 1, proj_view_matrix, proj_view_matrix, &shadow_matrix, cam_pos, draw_type);
+}
+
+void EnvObject::SetEnabled(bool val) {
+ if(val != enabled_){
+ Object::SetEnabled(val);
+ if(!enabled_){
+ RemovePhysicsShape();
+ } else {
+ if(!attached_) {
+ CreatePhysicsShape();
+ }
+ }
+ shadow_cache_dirty = true;
+ }
+}
+
+void EnvObject::SetCollisionEnabled(bool val) {
+ if(val && !bullet_object_) {
+ if(!attached_) {
+ CreatePhysicsShape();
+ }
+ } else if(!val && bullet_object_) {
+ RemovePhysicsShape();
+ }
+}
+
+struct DistanceSorter {
+ float distance;
+ int id;
+};
+
+class DistanceSorterCompare {
+public:
+ bool operator()(const DistanceSorter &a, const DistanceSorter &b) {
+ return a.distance < b.distance;
+ }
+};
+
+void CreatePointCloud(std::vector<vec3> &points, const Model &model, const ImageSamplerRef &image) {
+ float total_triangle_area = 0.0f;;
+ std::vector<float> triangle_area(model.faces.size()/3);
+ {
+ vec3 verts[3];
+ int face_index = 0;
+ int vert_index;
+ for(unsigned i=0; i<triangle_area.size(); ++i){
+ for(unsigned j=0; j<3; ++j){
+ vert_index = model.faces[face_index+j]*3;
+ verts[j] = vec3(model.vertices[vert_index+0],
+ model.vertices[vert_index+1],
+ model.vertices[vert_index+2]);
+ }
+ triangle_area[i] = length(cross(verts[2]-verts[0], verts[1]-verts[0]))*0.5f;
+ total_triangle_area += triangle_area[i];
+ face_index += 3;
+ }
+ }
+
+ float density = 100.0f;
+ if(total_triangle_area * density > 10000.0f){
+ density = 10000.0f / total_triangle_area;
+ }
+ int vert_index;
+ int tex_index;
+ vec3 verts[3];
+ vec2 tex_coords[3];
+ std::vector<vec3> temp_points;
+ for(unsigned face_index=0; face_index<model.faces.size(); face_index+=3){
+ for(unsigned j=0; j<3; ++j){
+ vert_index = model.faces[face_index+j]*3;
+ tex_index = model.faces[face_index+j]*2;
+ verts[j] = vec3(model.vertices[vert_index+0],
+ model.vertices[vert_index+1],
+ model.vertices[vert_index+2]);
+ tex_coords[j] = vec2(model.tex_coords[tex_index+0],
+ model.tex_coords[tex_index+1]);
+ }
+ unsigned num_dots = max((unsigned)5,(unsigned)(triangle_area[face_index/3]*density));
+ for(unsigned j=0; j<num_dots; ++j){
+ vec2 coord(RangedRandomFloat(0.0f,1.0f),
+ RangedRandomFloat(0.0f,1.0f));
+ if(coord[0] + coord[1] > 1.0f){
+ std::swap(coord[0], coord[1]);
+ coord[0] = 1.0f - coord[0];
+ coord[1] = 1.0f - coord[1];
+ }
+ vec3 pos = (verts[0] + (verts[1]-verts[0])*coord[0] + (verts[2]-verts[0])*coord[1]);
+ vec2 tex_coord;
+ tex_coord = (tex_coords[0] + (tex_coords[1]-tex_coords[0])*coord[0] + (tex_coords[2]-tex_coords[0])*coord[1]);
+ if(image->GetInterpolatedColorUV(tex_coord[0], tex_coord[1]).a()>0.0f){
+ temp_points.push_back(pos);
+ }
+ }
+ }
+
+ vec3 center;
+ for(unsigned i=0; i<temp_points.size(); ++i){
+ center += temp_points[i];
+ }
+ center /= (float)temp_points.size();
+
+ std::vector<DistanceSorter> ds(temp_points.size());
+ for(unsigned i=0; i<temp_points.size(); ++i){
+ ds[i].id = i;
+ ds[i].distance = distance_squared(temp_points[i], center);
+ }
+ std::sort(ds.begin(), ds.end(), DistanceSorterCompare());
+ //ds.resize(ds.size()*0.9f);
+ points.resize(ds.size());
+ for(unsigned i=0; i<points.size(); ++i){
+ points[i] = temp_points[ds[i].id];
+ }
+}
+
+BulletWorld* EnvObject::GetBulletWorld() {
+ return ofr->bush_collision ? scenegraph_->plant_bullet_world_ : scenegraph_->bullet_world_;
+}
+
+void ShortName(const std::string &input, std::string &output){
+ int slash = input.rfind('/');
+ int dot = input.rfind('.');
+ output = input.substr(slash+1, dot-slash-1);
+}
+
+typedef std::map<std::string, bool> HullExistsCache;
+static HullExistsCache hull_exists_cache;
+
+bool DoesHullFileExist(const std::string& hull_path){
+ HullExistsCache::const_iterator iter = hull_exists_cache.find(hull_path);
+ bool hull_file_exists = false;
+ if(iter != hull_exists_cache.end()){
+ hull_file_exists = iter->second;
+ } else {
+ hull_file_exists = FileExists(hull_path.c_str(), kDataPaths | kModPaths);
+ hull_exists_cache[hull_path] = hull_file_exists;
+ }
+ return hull_file_exists;
+}
+
+int EnvObject::GetCollisionModelID() {
+ if(ofr->bush_collision){
+ return -1;
+ } else {
+ std::string collision_model_path = ofr->model_name.substr(0, ofr->model_name.size()-4)+"_col.obj";
+ if(DoesHullFileExist(collision_model_path)){
+ int collision_model_id = Models::Instance()->loadModel(collision_model_path.c_str());
+ Model& collision_model = Models::Instance()->GetModel(collision_model_id);
+ if(collision_model.old_center == collision_model.center_coords){
+ Model& model = Models::Instance()->GetModel(model_id_);
+ collision_model.center_coords = model.old_center;
+ collision_model.CenterModel();
+ }
+ return collision_model_id;
+ } else {
+ return model_id_;
+ }
+ }
+}
+
+void EnvObject::CreatePhysicsShape() {
+ BWFlags flags = BW_NO_FLAGS;
+ if(ofr->no_collision){
+ flags |= BW_DECALS_ONLY;
+ }
+ BulletWorld* bw = GetBulletWorld();
+ if(ofr->bush_collision){
+ CreateBushPhysicsShape();
+ } else {
+ int id = GetCollisionModelID();
+ Model* model = &Models::Instance()->GetModel(id);
+ normal_override_buffer_dirty = true;
+ bullet_object_ = bw->CreateStaticMesh(model, id, flags);
+ }
+ if(bullet_object_){
+ bullet_object_->owner_object = this;
+ bullet_object_->SetTransform(GetTranslation(), Mat4FromQuaternion(GetRotation()), GetScale());
+ bullet_object_->UpdateTransform();
+ bullet_object_->UpdateTransform();
+ bw->UpdateSingleAABB(bullet_object_);
+ }
+}
+
+void EnvObject::RemovePhysicsShape() {
+ if(bullet_object_){
+ GetBulletWorld()->RemoveStaticObject(&bullet_object_);
+ }
+}
+
+bool EnvObject::Initialize() {
+ //int test_shader_id = Shaders::Instance()->returnProgram(ofr->shader_name);
+ //transparent = Shaders::Instance()->IsProgramTransparent(test_shader_id);
+ transparent = false;
+ if(ofr->transparent) {
+ transparent = true;
+ }
+ if(ofr->terrain_fixed){
+ SetTranslation(Models::Instance()->GetModel(model_id_).old_center);
+ permission_flags &= ~(Object::CAN_ROTATE |
+ Object::CAN_SCALE | Object::CAN_TRANSLATE);
+ }
+ if(scenegraph_ && !added_to_physics_scene_ && !attached_){
+ if(!ofr->bush_collision) {
+ int id = GetCollisionModelID();
+ Model* model = &Models::Instance()->GetModel(id);
+ if(model->faces.empty()) {
+ DisplayError("Couldn't find model", "The given envobject doesn't have a valid collision mesh. Make sure the model/collision mesh exists. The object will not be spawned", ErrorType::_ok);
+ return false;
+ }
+ }
+
+ CreatePhysicsShape();
+ added_to_physics_scene_ = true;
+ UpdatePhysicsTransform();
+ }
+ UpdateBoundingSphere();
+ m_transform_starting_sphere_center = sphere_center_;
+ m_transform_starting_sphere_radius = sphere_radius_;
+ return true;
+}
+
+void EnvObject::GetShaderNames(std::map<std::string, int>& shaders) {
+ shaders[ofr->shader_name] = SceneGraph::kPreloadTypeAll;
+}
+
+void EnvObject::Update(float timestep) {
+ Online* online = Online::Instance();
+
+ if(plant_component_.get()){
+ if(plant_component_->IsActive()){
+ plant_component_->Update(timestep);
+ } else {
+ if(scenegraph_ && update_list_entry != -1){
+ scenegraph_->UnlinkUpdateObject(this, update_list_entry);
+ update_list_entry = -1;
+ }
+ }
+ }
+
+ if (online->IsClient()) {
+ list<OnlineMessageRef>& frames = incoming_online_env_update;
+ if(frames.size() > 0) {
+ EnvObjectUpdate* eou = static_cast<EnvObjectUpdate*>(frames.begin()->GetData());
+ if(eou != nullptr) {
+ SetTransformationMatrix(eou->transform);
+ }
+ frames.pop_front();
+ }
+ }
+}
+
+void EnvObject::UpdateBoundingSphere() {
+ vec3 radius_vec = box_.dims*0.5f;
+ sphere_radius_ = length(transform_.GetRotatedvec3(radius_vec));
+ sphere_center_ = GetTranslation();
+}
+
+int EnvObject::lineCheck(const vec3 &start, const vec3 &end, vec3 *point, vec3 *normal) {
+ if(selected_){
+ if(!sphere_line_intersection( start, end, sphere_center_, sphere_radius_ ) ){ // Much faster than the OBB check, and most objects probably won't fall under cursor
+ return -1;
+ }
+ return LineCheckEditorCube(start, end, point, normal);
+ }
+ if(bullet_object_){
+ return GetBulletWorld()->CheckRayCollisionObj(start, end, *bullet_object_, point, normal);
+ }
+ if (model_id_ == -1 ||
+ !sphere_line_intersection( start, end, sphere_center_, sphere_radius_ ) )
+ {
+ return -1;
+ }
+ const Model& model = Models::Instance()->GetModel(model_id_);
+ int intersecting = model.lineCheckNoBackface(invert(transform_)*start,
+ invert(transform_)*end,
+ point,
+ normal);
+ if(intersecting!=-1){
+ if(point){
+ *point = transform_ * *point;
+ }
+ if(normal){
+ *normal = GetRotation() * *normal;
+ }
+ }
+ return intersecting;
+}
+
+void EnvObject::Moved(Object::MoveType type) {
+ Object::Moved(type);
+ if(type & Object::kScale) {
+ UpdateDetailScale();
+ }
+
+ UpdateDetailScale();
+ UpdateBoundingSphere();
+
+ LoadModel();
+ if(plant_component_.get()){
+ plant_component_->ClearPivot();
+ }
+ Graphics::Instance()->nav_mesh_out_of_date = true;
+
+ if(ofr.valid()){
+ UpdateDetailObjectSurfaces(&detail_object_surfaces, ofr, texture_ref_[0],
+ normal_texture_ref_[0], transform_, model_id_);
+ if(!ofr->dynamic && !attached_ && !placeholder_){
+ shadow_cache_dirty = true;
+ if(scenegraph_) {
+ auto it = std::find(scenegraph_->visible_static_meshes_.begin(), scenegraph_->visible_static_meshes_.end(), this);
+ // Dirty shadow cache bounds for current object
+ if(it != scenegraph_->visible_static_meshes_.end()){
+ auto& shadow_cache_bounds = *(scenegraph_->visible_static_meshes_shadow_cache_bounds_.begin() + std::distance(scenegraph_->visible_static_meshes_.begin(), it));
+ shadow_cache_bounds.is_calculated = false;
+ }
+ }
+ normal_override_buffer_dirty = true;
+ }
+ }
+}
+
+// returns true iff an update occured
+bool EnvObject::UpdatePhysicsTransform() {
+ if (!bullet_object_) {
+ return false;
+ }
+
+ bullet_object_->SetTransform(GetTranslation(), Mat4FromQuaternion(GetRotation()), GetScale());
+ bullet_object_->UpdateTransform();
+ bullet_object_->UpdateTransform();
+ GetBulletWorld()->UpdateSingleAABB(bullet_object_);
+ return true;
+}
+
+void EnvObject::GetDisplayName(char* buf, int buf_size) {
+ if( GetName().empty() ) {
+ FormatString(buf, buf_size, "%d, Static Mesh from %s", GetID(), obj_file.c_str());
+ } else {
+ FormatString(buf, buf_size, "%s, Static Mesh from %s", GetName().c_str(), obj_file.c_str());
+ }
+}
+
+const Model * EnvObject::GetModel() const {
+ return &Models::Instance()->GetModel(model_id_);
+}
+
+const MaterialDecal& EnvObject::GetMaterialDecal( const std::string &type, const vec3 &pos ) {
+ MaterialRef material = ofr_material;
+ return material->GetDecal(type);
+}
+
+const MaterialParticle& EnvObject::GetMaterialParticle( const std::string &type, const vec3 &pos ) {
+ MaterialRef material = ofr_material;
+ return material->GetParticle(type);
+}
+
+bool EnvObject::Load( const std::string& type_file ) {
+ bool ret = true;
+ Textures* textures = Textures::Instance();
+ textures->setWrap(GL_REPEAT);
+
+ ofr = Engine::Instance()->GetAssetManager()->LoadSync<ObjectFile>(type_file);
+
+ if( ofr.valid() ) {
+ modsource_ = ofr->modsource_;
+
+ ofr_material = Engine::Instance()->GetAssetManager()->LoadSync<Material>(ofr->material_path);
+
+ if(ofr_material.valid()) {
+ PROFILER_ENTER(g_profiler_ctx, "Loading detail/weight texture refs");
+ if(!ofr->weight_map.empty()){
+ weight_map_ref_ = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(ofr->weight_map);
+ if( weight_map_ref_.valid() == false ) {
+ LOGE << "Failed at loading weight_map " << ofr->weight_map << " for envobject " << type_file << std::endl;
+ ret = false;
+ }
+ }
+
+ TextureRef detail_color_map_ref_ = textures->getDetailColorArray();
+ detail_texture_color_indices_.resize(ofr->m_detail_color_maps.size());
+ for(unsigned i=0; i<ofr->m_detail_color_maps.size(); ++i){
+ detail_texture_color_indices_[i] = textures->loadArraySlice(detail_color_map_ref_, ofr->m_detail_color_maps[i]);
+ }
+
+ detail_texture_normal_indices_.resize(ofr->m_detail_normal_maps.size());
+ TextureRef detail_normal_map_ref_ = textures->getDetailNormalArray();
+ for(unsigned i=0; i<ofr->m_detail_normal_maps.size(); ++i){
+ detail_texture_normal_indices_[i] = textures->loadArraySlice(detail_normal_map_ref_, ofr->m_detail_normal_maps[i]);
+ }
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ PROFILER_ENTER(g_profiler_ctx, "Getting average colors");
+ detail_texture_color_.resize(ofr->m_detail_color_maps.size());
+ detail_texture_color_srgb_.resize(ofr->m_detail_color_maps.size());
+ for(unsigned i=0; i<ofr->m_detail_color_maps.size(); i++){
+ vec4 &dtc = detail_texture_color_[i];
+ vec4 &dtc_srgb = detail_texture_color_srgb_[i];
+ //dtc = AverageColors::Instance()->ReturnRef(ofr->m_detail_color_maps[i])->color();
+
+ AverageColorRef color_ref = Engine::Instance()->GetAssetManager()->LoadSync<AverageColor>(ofr->m_detail_color_maps[i]);
+ if( color_ref.valid() ) {
+ average_color_refs.insert(color_ref);
+ dtc = color_ref->color();
+
+ dtc_srgb[0] = pow(dtc[0], 2.2f);
+ dtc_srgb[1] = pow(dtc[1], 2.2f);
+ dtc_srgb[2] = pow(dtc[2], 2.2f);
+ dtc_srgb[3] = dtc[3];
+ } else {
+ LOGE << "Failed at loading AverageColor " << ofr->m_detail_color_maps[i] << " for EnvObject " << type_file << std::endl;
+ ret = false;
+ }
+ }
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ PROFILER_ENTER(g_profiler_ctx, "Getting main textures");
+ obj_file = type_file;
+
+ if(ofr->clamp_texture){
+ Textures::Instance()->setWrap(GL_CLAMP_TO_EDGE);
+ } else {
+ Textures::Instance()->setWrap(GL_REPEAT);
+ }
+ texture_ref_.clear();
+
+ TextureAssetRef tex = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(ofr->color_map.c_str(),PX_SRGB,0x0);
+
+ if( tex.valid() ) {
+ texture_ref_.push_back(tex);
+ } else {
+ LOGE << "Failed at loading color map TextureAsset " << ofr->color_map.c_str() << " for EnvObject " << type_file << std::endl;
+ ret = false;
+ }
+
+ normal_texture_ref_.clear();
+
+ tex = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(ofr->normal_map.c_str());
+
+ if( tex.valid() ) {
+ normal_texture_ref_.push_back(tex);
+ } else {
+ LOGE << "Failed at loading normal map TextureAsset " << ofr->normal_map.c_str() << " for EnvObject " << type_file << std::endl;
+ ret = false;
+ }
+
+
+ translucency_texture_ref_.clear();
+ if(!ofr->translucency_map.empty()) {
+ tex = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(ofr->translucency_map.c_str(),PX_SRGB,0x0);
+ if( tex.valid() ) {
+ translucency_texture_ref_.push_back(tex);
+ } else {
+ LOGE << "Failed at loading translucency map TextureAsset " << ofr->translucency_map.c_str() << " for EnvObject " << type_file << std::endl;
+ ret = false;
+ }
+ }
+ PROFILER_LEAVE(g_profiler_ctx);
+
+
+ if(ofr->shader_name == "cubemap"){
+ ofr->shader_name = "envobject #TANGENT";
+ } else if(ofr->shader_name == "cubemapobj" ||
+ ofr->shader_name == "cubemapobjchar" ||
+ ofr->shader_name == "cubemapobjitem")
+ {
+ ofr->shader_name = "envobject";
+ } else if(ofr->shader_name == "cubemapalpha"){
+ ofr->shader_name = "envobject #TANGENT #ALPHA";
+ } else if(ofr->shader_name == "plant" || ofr->shader_name == "plant_less_movement"){
+ ofr->shader_name = "envobject #TANGENT #ALPHA #PLANT";
+ } else if(ofr->shader_name == "plant_foliage") {
+ ofr->shader_name = "envobject #TANGENT #ALPHA #PLANT #NO_DECALS";
+ } else if(ofr->shader_name == "detailmap4"){
+ ofr->shader_name = "envobject #DETAILMAP4 #TANGENT";
+ } else if(ofr->shader_name == "detailmap4tangent"){
+ ofr->shader_name = "envobject #DETAILMAP4 #TANGENT #BASE_TANGENT";
+ } else if(ofr->shader_name == "MagmaFloor"){
+ ofr->shader_name = "envobject #MAGMA_FLOOR";
+ } else if(ofr->shader_name == "MagmaFlow"){
+ ofr->shader_name = "envobject #MAGMA_FLOW";
+ }
+
+ if(ofr->shader_name.find("#ALPHA") != std::string::npos){
+ transparent = true;
+ } else {
+ transparent = ofr->transparent;
+ }
+
+ LoadModel();
+
+ if(!ofr->wind_map.empty()) {
+ if( Models::Instance()->GetModel(model_id_).LoadAuxFromImage(ofr->wind_map) == false ) {
+ LOGE << "Failed to acceptably load AuxFromImage " << ofr->wind_map << " for envobject " << type_file << std::endl;
+ ret = false;
+ }
+ }
+
+ base_color_tint = ofr->color_tint;
+
+ if(ofr->bush_collision){
+ plant_component_.reset(new PlantComponent());
+ }
+
+ UpdateDetailObjectSurfaces(&detail_object_surfaces, ofr, texture_ref_[0],
+ normal_texture_ref_[0], transform_, model_id_);
+ } else {
+ LOGE << "Failure loading Material " << ofr->material_path << " for envobject " << type_file << std::endl;
+ ret = false;
+ }
+ } else {
+ LOGE << "Failure loading envobject when loading its base ObjectFile " << type_file << std::endl;
+ ret = false;
+ }
+ return ret;
+}
+
+static bool NeedsWindingFlip(const vec3 &scale) {
+ int needs_flip = 0;
+ for(int i=0; i<3; ++i){
+ if (scale[i] < 0) {
+ needs_flip++;
+ }
+ }
+ return (needs_flip%2 == 1);
+}
+
+void EnvObject::LoadModel() {
+ if(!ofr.valid()){
+ return;
+ }
+ bool use_tangent = true;
+ char flags = 0;
+ if(use_tangent){
+ flags = flags | _MDL_USE_TANGENT;
+ }
+ flags = flags | _MDL_CENTER;
+
+ bool physics_recalc = false;
+ if(!csg_modified_){
+ int old_model_id = model_id_;
+ if(NeedsWindingFlip(GetScale())){
+ model_id_ = Models::Instance()->loadFlippedModel(ofr->model_name.c_str(), flags);
+ winding_flip = true;
+ } else {
+ model_id_ = Models::Instance()->loadModel(ofr->model_name.c_str(), flags);
+ winding_flip = false;
+ }
+ if(old_model_id != -1 && old_model_id != model_id_) {
+ physics_recalc = true;
+ }
+ }
+ if(scenegraph_ && physics_recalc){
+ RemovePhysicsShape();
+
+ if(!attached_) {
+ CreatePhysicsShape();
+ }
+ }
+}
+
+void EnvObject::SetCSGModified() {
+ csg_modified_ = true;
+ RemovePhysicsShape();
+
+ if(!attached_) {
+ CreatePhysicsShape();
+ }
+}
+
+void EnvObject::Reload() {
+ if(ofr->color_tint != base_color_tint){
+ base_color_tint = ofr->color_tint;
+ }
+}
+
+std::string EnvObject::GetLabel() {
+ return ofr->label;
+}
+
+const vec3 & EnvObject::GetColorTint() {
+ return color_tint_component_.tint_;
+}
+
+const float& EnvObject::GetOverbright() {
+ return color_tint_component_.overbright_;
+}
+
+void EnvObject::GetDesc(EntityDescription &desc) const {
+ if(!attached_ || (attached_ && parent && parent->IsGroupDerived())){
+ // Save everything if part of a group
+ Object::GetDesc(desc);
+ } else {
+ // Skip transform info if object is attached to character
+ desc.AddString( EDF_NAME, GetName());
+ desc.AddVec3( EDF_SCALE, GetScale());
+ desc.AddEntityType( EDF_ENTITY_TYPE, GetType());
+ desc.AddInt( EDF_ID, GetID());
+ desc.AddScriptParams(EDF_SCRIPT_PARAMS, sp.GetParameterMap());
+ }
+ color_tint_component_.AddToDescription(desc);
+ desc.AddString(EDF_FILE_PATH, obj_file);
+ desc.AddBool(EDF_NO_NAVMESH, no_navmesh);
+}
+
+MaterialRef EnvObject::GetMaterial( const vec3 &pos, int* tri ) {
+ //return (*Materials::Instance()->ReturnRef(ofr->material_path));
+ //TODO: Make sure that the reference is sent up instead, so that the asset isn't dropped.
+ return ofr_material;
+}
+
+vec3 EnvObject::GetBoundingBoxSize() {
+ return box_.dims;
+}
+
+HullCacheMap hull_cache;
+
+void EnvObject::CreateBushPhysicsShape() {
+ BulletWorld* bw = GetBulletWorld();
+ Model &model = Models::Instance()->GetModel(model_id_);
+ std::string cache_path;
+ {
+ std::string texture_short, model_short;
+ ShortName(ofr->color_map, texture_short);
+ ShortName(ofr->model_name, model_short);
+ cache_path = ofr->model_name.substr(0,ofr->model_name.rfind('/')+1)+
+ model_short+"_" + texture_short + ".hull";
+ }
+
+ HullCache *hc = NULL;
+ HullCacheMap::iterator iter = hull_cache.find(cache_path);
+ if(iter != hull_cache.end()){
+ hc = &(iter->second);
+ }
+
+ if(!hc){
+ char abs_path[kPathSize];
+ bool found_cache = (FindFilePath(cache_path.c_str(), abs_path, kPathSize, kDataPaths|kModPaths|kWriteDir|kModWriteDirs, false) != -1);
+ if(found_cache){
+ FILE *file = my_fopen(abs_path,"rb");
+ char version;
+ fread(&version, sizeof(char), 1, file);
+ if(version != _hull_cache_version){
+ found_cache = false;
+ } else {
+ //printf("Loading hull cache file: %s\n", cache_path.c_str());
+ hc = &hull_cache[cache_path];
+ std::vector<vec3> &verts = hc->verts;
+ std::vector<int> &faces = hc->faces;
+ std::vector<vec3> &point_cloud = hc->point_cloud;
+ {
+ char num_verts;
+ fread(&num_verts, sizeof(char), 1, file);
+ verts.resize(num_verts);
+ fread(&(verts[0]), sizeof(vec3), num_verts, file);
+ }{
+ unsigned char num_face_indices;
+ fread(&num_face_indices, sizeof(unsigned char), 1, file);
+ faces.resize(num_face_indices);
+ fread(&(faces[0]), sizeof(int), num_face_indices, file);
+ }{
+ int num_points;
+ fread(&num_points, sizeof(int), 1, file);
+ point_cloud.resize(num_points);
+ fread(&(point_cloud[0]), sizeof(vec3), num_points, file);
+ }
+ bullet_object_ = bw->CreateConvexObject(verts, faces, true);
+ }
+ fclose(file);
+ }
+ if(!found_cache){
+ //ImageSamplerRef image = ImageSamplers::Instance()->ReturnRef(ofr->color_map);
+ ImageSamplerRef image = Engine::Instance()->GetAssetManager()->LoadSync<ImageSampler>(ofr->color_map);
+
+ hc = &hull_cache[cache_path];
+ std::vector<vec3> &points = hc->point_cloud;
+ std::vector<vec3> &verts = hc->verts;
+ std::vector<int> &faces = hc->faces;
+ CreatePointCloud(points, model, image);
+ GetSimplifiedHull(points, verts, faces);
+
+ bullet_object_ = bw->CreateConvexObject(verts, faces, true);
+
+ char write_path[kPathSize];
+ FormatString(write_path, kPathSize, "%s%s", GetWritePath(modsource_).c_str(), cache_path.c_str());
+ FILE *file = my_fopen(write_path, "wb");
+ fwrite(&_hull_cache_version, sizeof(char), 1, file);
+ {
+ char num_verts = verts.size();
+ fwrite(&num_verts, sizeof(char), 1, file);
+ fwrite(&(verts[0]), sizeof(vec3), num_verts, file);
+ }{
+ unsigned char num_face_indices = faces.size();
+ fwrite(&num_face_indices, sizeof(unsigned char), 1, file);
+ fwrite(&(faces[0]), sizeof(int), num_face_indices, file);
+ }{
+ int num_points = points.size();
+ fwrite(&num_points, sizeof(int), 1, file);
+ fwrite(&(points[0]), sizeof(vec3), num_points, file);
+ }
+ fclose(file);
+ }
+ } else {
+ //printf("Instancing loaded hull cache: %s\n", cache_path.c_str());
+ bullet_object_ = bw->CreateConvexObject(hc->verts, hc->faces, true);
+ }
+ if(plant_component_.get()){
+ plant_component_->SetHullCache(hc);
+ }
+}
+
+void EnvObject::ReceiveASVec3Message( int type, const vec3 &vec_a, const vec3 &vec_b ) {
+ if(type == _plant_movement_msg && plant_component_.get()){
+ const vec3 &pos = vec_a;
+ const vec3 &vel = vec_b;
+ plant_component_->HandleCollision(pos - GetTranslation(), vel);
+ if(update_list_entry == -1){
+ update_list_entry = scenegraph_->LinkUpdateObject(this);
+ }
+ }
+}
+
+void PlantComponent::Update(float timestep) {
+ angle += ang_vel * timestep;
+ if(length(angle) > 0.5f){
+ angle = normalize(angle)*0.5f;
+ }
+ ang_vel -= angle * timestep * 20.0f;
+ ang_vel *= 0.99f;
+ plant_shake *= 0.99f;
+}
+
+PlantComponent::PlantComponent():
+ plant_shake(0.0f),
+ was_active(false),
+ needs_set_inactive(false)
+{}
+
+void PlantComponent::HandleCollision( const vec3 &position, const vec3 &velocity ) {
+ vec3 impulse = cross(normalize(position), velocity) * 0.01f;
+ ang_vel = impulse + ang_vel;
+ plant_shake = min(1.0f, plant_shake + length(impulse)*1.0f);
+}
+
+mat4 PlantComponent::GetTransform( float scale ) const {
+ mat4 rotation;
+ float angle_len2 = length_squared(angle);
+ if(angle_len2 > 0.00001f){
+ float angle_len = sqrtf(angle_len2);
+ rotation.SetRotationAxisRad(angle_len / max(1.0f,scale*0.6f), angle/angle_len);
+ }
+ return rotation;
+}
+
+quaternion PlantComponent::GetQuaternion( float scale ) const {
+ quaternion result;
+ float angle_len2 = length_squared(angle);
+ if(angle_len2 > 0.00001f){
+ float angle_len = sqrtf(angle_len2);
+ result = QuaternionFromAxisAngle(angle / angle_len, angle_len / std::max(1.0f, scale * 0.6f));
+ }
+ return result;
+}
+
+float PlantComponent::GetPlantShake( float scale ) const {
+ return plant_shake * scale * 0.03f;
+}
+
+vec3 PlantComponent::GetPivot() const {
+ return pivot;
+}
+
+void PlantComponent::SetPivot( BulletWorld &bw, const vec3 &pos, float radius ) {
+ pivot = pos;
+ const float _interval = 1.25f;
+ float temp_rad = radius / pow(_interval,20.0f);
+ vec3 offset = vec3(0.0f);
+ while(offset == vec3(0.0f) && temp_rad <= radius){
+ temp_rad *= _interval;
+ offset = bw.CheckSphereCollisionSlide(pos, temp_rad) - pos;
+ }
+ if(offset != vec3(0.0f)){
+ pivot = pos - normalize(offset) * temp_rad + offset;
+ }
+}
+
+bool PlantComponent::IsPivotCalculated() const {
+ return pivot != vec3(0.0f);
+}
+
+void PlantComponent::ClearPivot() {
+ pivot = vec3(0.0f);
+}
+
+bool PlantComponent::IsActive() {
+ static const float _angle_threshold = 0.0001f;
+ static const float _plant_shake_threshold = 0.0001f;
+ bool active = plant_shake > _plant_shake_threshold ||
+ length_squared(angle) > _angle_threshold;
+ if(was_active && !active){
+ needs_set_inactive = true;
+ plant_shake = 0.0f;
+ }
+ was_active = active;
+ return active;
+}
+
+bool PlantComponent::NeedsSetInactive() {
+ bool val = needs_set_inactive;
+ needs_set_inactive = false;
+ return val;
+}
+
+HullCache * PlantComponent::GetHullCache() {
+ return hull_cache;
+}
+
+void PlantComponent::SetHullCache( HullCache * hc ) {
+ hull_cache = hc;
+}
+
+void EnvObject::CreateLeaf(vec3 pos, vec3 vel, int iterations) {
+ if(!plant_component_.get()){
+ return;
+ }
+ const mat4 &transform = transform_;
+ HullCache* hc = plant_component_->GetHullCache();
+ float closest_dist;
+ vec3 closest_point;
+ for(int i=0; i<iterations; ++i){
+ vec3 point = transform * hc->point_cloud[rand()%(hc->point_cloud.size())];
+ float dist = distance_squared(point, pos);
+ if(i==0 || dist < closest_dist){
+ closest_point = point;
+ closest_dist = dist;
+ }
+ }
+ scenegraph_->particle_system->MakeParticle(
+ scenegraph_, "Data/Particles/leaf.xml", closest_point, vel, vec3(1.0f));
+}
+
+bool EnvObject::SetFromDesc( const EntityDescription& desc ) {
+ bool ret = Object::SetFromDesc(desc);
+ if( ret ) {
+ for(unsigned i=0; i<desc.fields.size(); ++i){
+ const EntityDescriptionField& field = desc.fields[i];
+ switch(field.type){
+ case EDF_FILE_PATH: {
+ std::string type_file;
+ field.ReadString(&type_file);
+ if(!ofr.valid() || ofr->path_ != type_file){
+ if( Load(type_file) == false ) {
+ LOGE << "Failed to load data for EnvObject" << std::endl;
+ ret = false;
+ }
+ }
+ break;}
+ case EDF_NO_NAVMESH: {
+ field.ReadBool(&no_navmesh);
+ break;}
+ }
+ }
+
+ if( ret ) {
+ color_tint_component_.SetFromDescription(desc);
+ ReceiveObjectMessage(OBJECT_MSG::SET_COLOR, &color_tint_component_.tint_);
+ ReceiveObjectMessage(OBJECT_MSG::SET_OVERBRIGHT, &color_tint_component_.overbright_);
+
+ const Model& model = Models::Instance()->GetModel(model_id_);
+ vec3 min = model.min_coords;
+ vec3 max = model.max_coords;
+ box_.center = model.center_coords;
+ box_.dims = max-min;
+ }
+ }
+ return ret;
+}
+
+void EnvObject::ReceiveObjectMessageVAList( OBJECT_MSG::Type type, va_list args ) {
+ switch(type){
+ case OBJECT_MSG::SET_COLOR: {
+ //vec3 old_color = color_tint_component_.temp_tint();
+ color_tint_component_.ReceiveObjectMessageVAList(type, args);
+ for(int i=0, len=detail_object_surfaces.size(); i<len; ++i){
+ detail_object_surfaces[i]->SetColorTint(GetDisplayTint());
+ }
+ CalculateDisplayTint_();
+ break;}
+ case OBJECT_MSG::SET_OVERBRIGHT: {
+ color_tint_component_.ReceiveObjectMessageVAList(type, args);
+ for(int i=0, len=detail_object_surfaces.size(); i<len; ++i){
+ detail_object_surfaces[i]->SetOverbright(GetOverbright());
+ }
+ CalculateDisplayTint_();
+ break;}
+ default:
+ Object::ReceiveObjectMessageVAList(type, args);
+ break;
+ }
+}
+
+void EnvObject::CalculateDisplayTint_()
+{
+ cached_combined_tint_ = color_tint_component_.tint_ * (1.0f + color_tint_component_.overbright_ * 0.3f) * base_color_tint;
+}
+
+vec3 EnvObject::GetDisplayTint() {
+ return cached_combined_tint_;
+}
+
+static int ASGetCollisionFace(EnvObject* eo, int index) {
+ LOG_ASSERT( index >= 0 );
+ if(eo->GetCollisionModelID() == -1 || eo->ofr->no_collision){
+ return -1;
+ } else {
+ Model* model = &Models::Instance()->GetModel(eo->GetCollisionModelID());
+ if(!model || (int)model->faces.size() <= index){
+ return -1;
+ } else {
+ return model->faces[index];
+ }
+ }
+}
+
+static vec3 ASGetCollisionVertex(EnvObject* eo, int index) {
+ LOG_ASSERT( index >= 0 );
+ if(eo->GetCollisionModelID() == -1 || eo->ofr->no_collision){
+ return vec3(0.0);
+ } else {
+ Model* model = &Models::Instance()->GetModel(eo->GetCollisionModelID());
+ if(!model || (int)model->faces.size() <= index){
+ return vec3(0.0);
+ } else {
+ vec3 vec;
+ memcpy (&vec, &model->vertices[index*3], sizeof(vec3));
+ return vec;
+ }
+ }
+}
+
+static int ASGetLedgeLine(EnvObject* eo, int index) {
+ return eo->ledge_lines[index];
+}
+
+static int ASGetNumLedgeLines(EnvObject* eo) {
+ return eo->ledge_lines.size();
+}
+
+void DefineEnvObjectTypePublic(ASContext* as_context) {
+ as_context->RegisterObjectType("EnvObject", 0, asOBJ_REF | asOBJ_NOCOUNT);
+ as_context->RegisterObjectMethod("EnvObject",
+ "int GetID()",
+ asMETHOD(EnvObject, GetID), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("EnvObject",
+ "void CreateLeaf(vec3 target_position, vec3 velocity, int iterations)",
+ asMETHOD(EnvObject, CreateLeaf), asCALL_THISCALL, "Spawn leaf particles from random point on object's surface; more iterations = closer to target_position");
+ as_context->RegisterObjectMethod("EnvObject",
+ "int GetCollisionFace(int index)",
+ asFUNCTION(ASGetCollisionFace), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("EnvObject",
+ "vec3 GetCollisionVertex(int index)",
+ asFUNCTION(ASGetCollisionVertex), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("EnvObject",
+ "int GetNumLedgeLines()",
+ asFUNCTION(ASGetNumLedgeLines), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("EnvObject",
+ "int GetLedgeLine(int index)",
+ asFUNCTION(ASGetLedgeLine), asCALL_CDECL_OBJFIRST);
+ as_context->DocsCloseBrace();
+}
diff --git a/Source/Objects/envobject.h b/Source/Objects/envobject.h
new file mode 100644
index 00000000..906d1bc5
--- /dev/null
+++ b/Source/Objects/envobject.h
@@ -0,0 +1,217 @@
+//-----------------------------------------------------------------------------
+// Name: envobject.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Online/online_datastructures.h>
+#include <Online/time_interpolator.h>
+
+#include <Asset/Asset/objectfile.h>
+#include <Asset/Asset/averagecolorasset.h>
+
+#include <Editors/editor_types.h>
+#include <Editors/editor_utilities.h>
+
+#include <Objects/object.h>
+#include <Graphics/textureref.h>
+
+#include <Math/overgrowth_geometry.h>
+#include <Game/color_tint_component.h>
+
+#include <memory>
+#include <list>
+
+class DetailObjectSurface;
+class ObjectEditor;
+class ObjectsEditor;
+class DecalObject;
+class Collision;
+class BulletObject;
+class BulletWorld;
+class quaternion;
+
+using std::list;
+
+const char _hull_cache_version = 2;
+struct HullCache {
+ std::vector<vec3> point_cloud;
+ std::vector<vec3> verts;
+ std::vector<int> faces;
+};
+typedef std::map<std::string, HullCache> HullCacheMap;
+
+class PlantComponent {
+ float plant_shake;
+ vec3 angle;
+ vec3 ang_vel;
+ vec3 pivot;
+ bool was_active;
+ bool needs_set_inactive;
+ HullCache *hull_cache;
+public:
+ PlantComponent();
+ bool IsPivotCalculated() const;
+ mat4 GetTransform(float scale) const;
+ quaternion GetQuaternion(float scale) const;
+ float GetPlantShake( float scale ) const;
+ vec3 GetPivot() const;
+
+ bool IsActive();
+ bool NeedsSetInactive();
+ void SetPivot(BulletWorld &bw, const vec3 &pos, float radius);
+ void Update(float timestep);
+ void HandleCollision( const vec3 &position, const vec3 &velocity );
+ void ClearPivot();
+ HullCache *GetHullCache();
+ void SetHullCache( HullCache * hc );
+};
+
+class Model;
+class MovementObject;
+struct MaterialEvent;
+struct MaterialParticle;
+
+class EnvObject: public Object {
+ public:
+ float timestamp_for_sendoff = 0.0f;
+ std::auto_ptr<PlantComponent> plant_component_;
+ ObjectFileRef ofr;
+
+ BulletObject* bullet_object_;
+
+ float sphere_radius_;
+ vec3 sphere_center_;
+
+ TextureAssetRef ambient_cube_ref_[6];
+
+ bool no_navmesh;
+
+ ModID modsource_;
+
+ std::vector<TextureAssetRef> texture_ref_;
+ std::vector<TextureAssetRef> normal_texture_ref_;
+ std::vector<TextureAssetRef> translucency_texture_ref_;
+
+ TextureAssetRef weight_map_ref_;
+ std::vector<unsigned int> detail_texture_color_indices_;
+ std::vector<unsigned int> detail_texture_normal_indices_;
+ std::vector<vec4> detail_texture_color_;
+ std::vector<vec4> detail_texture_color_srgb_;
+
+ std::vector<vec3> normal_override;
+ std::vector<vec4> normal_override_custom;
+ std::vector<int> ledge_lines;
+
+ TextureRef normal_override_buffer;
+ bool normal_override_buffer_dirty;
+
+ bool csg_modified_;
+
+ int model_id_;
+ bool added_to_physics_scene_;
+ bool winding_flip;
+ MovementObject* attached_;
+ bool placeholder_;
+
+ list<OnlineMessageRef> incoming_online_env_update;
+
+ EnvObject();
+ virtual ~EnvObject();
+
+ virtual bool Initialize();
+ virtual void GetShaderNames(std::map<std::string, int>& shaders);
+ virtual void Update(float timestep);
+ void UpdateBoundingSphere();
+
+ std::string GetLabel();
+
+ // Drawing
+ virtual void Draw();
+ void DrawInstances(EnvObject** instance_array, int num_instances, const mat4& proj_view_matrix, const mat4& prev_proj_view_matrix, const std::vector<mat4>* shadow_matrix, const vec3& cam_pos, Object::DrawType type);
+ bool HasDetailObjectSurfaces() const { return !detail_object_surfaces.empty(); }
+ void DrawDetailObjectInstances(EnvObject** instance_array, int num_instances, Object::DrawType type);
+ virtual void PreDrawCamera(float curr_game_time);
+ void DrawDepthMap(const mat4& proj_view_matrix, const vec4* cull_planes, int num_cull_planes, Object::DrawType draw_type);
+ virtual void SetEnabled(bool val);
+ virtual void SetCollisionEnabled(bool val);
+ virtual void ReceiveObjectMessageVAList(OBJECT_MSG::Type type, va_list args);
+
+ void GetObj2World(float *obj2world);
+
+ void EnvInterpolate(uint16_t pending_updates);
+
+ // Line hits
+ virtual int lineCheck(const vec3 &start, const vec3 &end, vec3 *point, vec3 *normal=0);
+
+ bool UpdatePhysicsTransform();
+ virtual void GetDisplayName(char* buf, int buf_size);
+
+ void CreatePhysicsShape();
+
+ virtual void Moved(Object::MoveType type);
+ void RemovePhysicsShape();
+ const Model* GetModel() const;
+ vec3 GetBoundingBoxSize();
+ void HandleMaterialEvent( const std::string &the_event, const vec3 &event_pos );
+ const MaterialEvent& GetMaterialEvent( const std::string &the_event, const vec3 &event_pos, int *tri );
+ const MaterialEvent& GetMaterialEvent( const std::string &the_event, const vec3 &event_pos, const std::string &mod, int *tri);
+ const MaterialDecal& GetMaterialDecal( const std::string &type, const vec3 &pos );
+ const MaterialParticle& GetMaterialParticle( const std::string &type, const vec3 &pos );
+ MaterialRef GetMaterial( const vec3 &pos, int* tri = NULL );
+ void UpdateDetailScale();
+ bool Load( const std::string& type_file );
+ void Reload();
+ const vec3 &GetColorTint();
+ const float& GetOverbright();
+ void LoadModel();
+ BulletWorld* GetBulletWorld();
+ int GetCollisionModelID();
+ void CreateBushPhysicsShape();
+ void ReceiveASVec3Message( int type, const vec3 &vec_a, const vec3 &vec_b );
+ void CreateLeaf(vec3 pos, vec3 vel, int iterations);
+ virtual bool SetFromDesc( const EntityDescription& desc );
+ virtual void GetDesc(EntityDescription &desc) const;
+ virtual void UpdateParentHierarchy();
+ void SetCSGModified();
+ typedef std::vector<DetailObjectSurface*> DOSList;
+ protected:
+ virtual EntityType GetType() const { return _env_object; }
+ DOSList detail_object_surfaces;
+
+ vec3 GetDisplayTint();
+ ColorTintComponent color_tint_component_;
+ vec3 base_color_tint;
+ vec3 cached_combined_tint_;
+
+ vec3 m_transform_starting_sphere_center;
+ float m_transform_starting_sphere_radius;
+
+ std::set<AverageColorRef> average_color_refs;
+ MaterialRef ofr_material;
+
+ TimeInterpolator network_time_interpolator;
+
+ private:
+ void CalculateDisplayTint_();
+};
+
+void DefineEnvObjectTypePublic(ASContext* as_context);
diff --git a/Source/Objects/envobjectattach.cpp b/Source/Objects/envobjectattach.cpp
new file mode 100644
index 00000000..fa32f75c
--- /dev/null
+++ b/Source/Objects/envobjectattach.cpp
@@ -0,0 +1,60 @@
+//-----------------------------------------------------------------------------
+// Name: envobjectattach.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <Objects/envobjectattach.h>
+#include <Internal/memwrite.h>
+
+void Serialize(const std::vector<AttachedEnvObject>& aeo, std::vector<char> &data) {
+ data.clear();
+ int num_attach = (int)aeo.size();
+ memwrite(&num_attach, sizeof(num_attach), 1, data);
+ for(int i=0; i<num_attach; ++i){
+ const AttachedEnvObject &attached_env_object = aeo[i];
+ memwrite(&attached_env_object.direct_ptr, sizeof(attached_env_object.direct_ptr), 1, data);
+ memwrite(&attached_env_object.legacy_obj_id, sizeof(attached_env_object.legacy_obj_id), 1, data);
+ for(unsigned j=0; j<kMaxBoneConnects; ++j){
+ const BoneConnect& bone_connect = attached_env_object.bone_connects[j];
+ memwrite(&bone_connect.bone_id, sizeof(bone_connect.bone_id), 1, data);
+ memwrite(&bone_connect.num_connections, sizeof(bone_connect.num_connections), 1, data);
+ memwrite(&bone_connect.rel_mat, sizeof(bone_connect.rel_mat), 1, data);
+ }
+ }
+}
+
+void Deserialize(const std::vector<char> &data, std::vector<AttachedEnvObject>& aeo) {
+ int index = 0;
+ int num_attach;
+ memread(&num_attach, sizeof(num_attach), 1, data, index);
+ aeo.resize(num_attach);
+ for(int i=0; i<num_attach; ++i){
+ AttachedEnvObject &attached_env_object = aeo[i];
+ attached_env_object.bone_connection_dirty = false;
+ memread(&attached_env_object.direct_ptr, sizeof(attached_env_object.direct_ptr), 1, data, index);
+ memread(&attached_env_object.legacy_obj_id, sizeof(attached_env_object.legacy_obj_id), 1, data, index);
+ for(unsigned j=0; j<kMaxBoneConnects; ++j){
+ const BoneConnect& bone_connect = attached_env_object.bone_connects[j];
+ memread((void*)&bone_connect.bone_id, sizeof(bone_connect.bone_id), 1, data, index);
+ memread((void*)&bone_connect.num_connections, sizeof(bone_connect.num_connections), 1, data, index);
+ memread((void*)&bone_connect.rel_mat, sizeof(bone_connect.rel_mat), 1, data, index);
+ }
+ }
+}
diff --git a/Source/Objects/envobjectattach.h b/Source/Objects/envobjectattach.h
new file mode 100644
index 00000000..91357dc9
--- /dev/null
+++ b/Source/Objects/envobjectattach.h
@@ -0,0 +1,47 @@
+//-----------------------------------------------------------------------------
+// Name: envobjectattach.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/mat4.h>
+
+#include <vector>
+
+class Object;
+
+const unsigned kMaxBoneConnects = 4;
+
+struct BoneConnect {
+ int bone_id;
+ int num_connections;
+ mat4 rel_mat;
+};
+
+struct AttachedEnvObject {
+ bool bone_connection_dirty; // Used to refresh rel_mats if obj is moved in editor
+ Object* direct_ptr;
+ int legacy_obj_id; // used for opening levels made before A208.2
+ BoneConnect bone_connects[kMaxBoneConnects];
+};
+
+void Serialize(const std::vector<AttachedEnvObject>& aeo, std::vector<char> &data);
+void Deserialize(const std::vector<char> &data, std::vector<AttachedEnvObject>& aeo);
diff --git a/Source/Objects/envobjecttype.h b/Source/Objects/envobjecttype.h
new file mode 100644
index 00000000..2b4b2734
--- /dev/null
+++ b/Source/Objects/envobjecttype.h
@@ -0,0 +1,26 @@
+//-----------------------------------------------------------------------------
+// Name: envobjecttype.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Graphics/textureref.h>
+
diff --git a/Source/Objects/group.cpp b/Source/Objects/group.cpp
new file mode 100644
index 00000000..120fe644
--- /dev/null
+++ b/Source/Objects/group.cpp
@@ -0,0 +1,417 @@
+//-----------------------------------------------------------------------------
+// Name: group.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <Math/vec3.h>
+#include <Math/vec3math.h>
+
+#include <Internal/snprintf.h>
+#include <Internal/common.h>
+#include <Internal/error.h>
+
+#include <Objects/group.h>
+#include <Editors/actors_editor.h>
+
+#include <cfloat>
+#include <cmath>
+#include <algorithm>
+
+extern bool g_debug_runtime_disable_group_pre_draw_camera;
+extern bool g_debug_runtime_disable_group_pre_draw_frame;
+
+Group::Group():
+ child_transforms_need_update(false),
+ child_moved(false)
+{
+ box_.dims = vec3(1.0f);
+}
+
+Group::~Group() {
+
+}
+
+bool Group::Initialize() {
+ return true;
+}
+
+int Group::lineCheck(const vec3 &start, const vec3 &end, vec3 *point, vec3 *normal/*=0*/) {
+ if(selected_){
+ return LineCheckEditorCube(start, end, point, normal);
+ }
+ return -1;
+}
+
+void Group::GetChildren(std::vector<Object*>* ret_children) {
+ ret_children->resize(children.size());
+ for(int i=0, len=children.size(); i<len; ++i){
+ ret_children->at(i) = children[i].direct_ptr;
+ }
+}
+
+void Group::GetBottomUpCompleteChildren(std::vector<Object*>* ret_children) {
+ for(int i=0, len=children.size(); i<len; ++i){
+ Object *obj = children[i].direct_ptr;
+ obj->GetBottomUpCompleteChildren(ret_children);
+ ret_children->push_back(obj);
+ }
+}
+
+void Group::GetTopDownCompleteChildren(std::vector<Object*>* ret_children) {
+ for(int i=0, len=children.size(); i<len; ++i){
+ Object *obj = children[i].direct_ptr;
+ ret_children->push_back(obj);
+ }
+
+ for(int i=0, len=children.size(); i<len; ++i){
+ Object *obj = children[i].direct_ptr;
+ obj->GetTopDownCompleteChildren(ret_children);
+ }
+}
+
+void Group::ChildLost(Object *obj){
+ for(int i=0, len=children.size(); i<len; ++i){
+ if(children[i].direct_ptr == obj){
+ children[i].direct_ptr = children.back().direct_ptr;
+ children.resize(children.size()-1);
+ break;
+ }
+ }
+ child_moved = true;
+}
+
+void Group::FinalizeLoadedConnections() {
+ Object::FinalizeLoadedConnections();
+ for(int i=0, len=children.size(); i<len; ++i ){
+ children[i].direct_ptr->ReceiveObjectMessage(OBJECT_MSG::FINALIZE_LOADED_CONNECTIONS);
+ }
+}
+
+bool Group::SetFromDesc( const EntityDescription &desc ) {
+ bool ret = Object::SetFromDesc(desc);
+ if( ret ) {
+ for (int i=0, len=desc.children.size();
+ i<len;
+ ++i)
+ {
+ EntityDescription new_desc = desc.children[i];
+ Object* obj = CreateObjectFromDesc(new_desc);
+ if(obj){
+ obj->SetParent(this);
+ children.resize(children.size()+1);
+ children.back().direct_ptr = obj;
+ } else {
+ LOGE << "Failed constructing child to Group" << std::endl;
+ }
+ }
+ const EntityDescriptionField* version_edf = desc.GetField(EDF_VERSION);
+ if(!version_edf){
+ InitShape();
+ }
+
+ InitRelMats();
+ }
+ return ret;
+}
+
+void Group::GetDesc(EntityDescription &desc) const {
+ Object::GetDesc(desc);
+ desc.AddInt(EDF_VERSION, 1);
+ for(int i=0, len=children.size(); i<len; ++i){
+ Object* obj = children[i].direct_ptr;
+ if(obj){
+ if(obj == this){
+ FatalError("Error", "Group %d: GetDesc() including copy of itself", GetID());
+ }
+ desc.children.resize(desc.children.size()+1);
+ obj->GetDesc(desc.children.back());
+ }
+ }
+}
+
+void Group::InitRelMats() {
+ for(int i=0, len=children.size(); i<len; ++i){
+ Object* obj = children[i].direct_ptr;
+ if(obj && (obj->GetType() == _group || obj->GetType() == _prefab) && !obj->Selected()){
+ Group* child_group = (Group*)obj;
+ child_group->InitRelMats();
+ }
+ }
+
+ // Get child transforms relative to group transform
+ for(int i=0, len=children.size(); i<len; ++i){
+ Child& child = children[i];
+ Object* obj = children[i].direct_ptr;
+ if(obj){
+ child.rel_rotation = invert(GetRotation()) * obj->GetRotation();
+ child.rel_translation = (invert(GetRotation()) * ((obj->GetTranslation() - translation_))/scale_);
+ child.rel_scale = child.rel_rotation * (obj->GetScale() / scale_);
+ }
+ }
+}
+
+void Group::InitShape() {
+ if(children.empty()){
+ return;
+ }
+
+ for(int i=0, len=children.size(); i<len; ++i){
+ Object* obj = children[i].direct_ptr;
+ if(obj && (obj->GetType() == _group || obj->GetType() == _prefab) && !obj->Selected()){
+ Group* child_group = (Group*)obj;
+ child_group->InitShape();
+ }
+ }
+
+ // Get bounding box points of children
+ static const int MAX_POINTS = 800;
+ int num_points = 0;
+ vec3 points[MAX_POINTS];
+ for(int i=0, len=children.size(); i<len; ++i){
+ if(num_points <= MAX_POINTS-8){
+ Object* obj = children[i].direct_ptr;
+ if(obj){
+ //int index = 0;
+ for(int j=0; j<8; ++j){
+ points[num_points+j] = obj->GetTransform() * obj->box_.GetPoint(j);
+ }
+ num_points += 8;
+ }
+ }
+ }
+
+ // Get axis-aligned bounds of child bbox points
+ vec3 min(FLT_MAX), max(-FLT_MAX);
+ for(int i=0; i<num_points; ++i){
+ for(int j=0; j<3; ++j){
+ min[j] = std::min(min[j], points[i][j]);
+ max[j] = std::max(max[j], points[i][j]);
+ }
+ }
+
+ // Set group shape to axis-aligned bbox
+ SetRotation(quaternion());
+ SetTranslation((min+max)*0.5f);
+ vec3 scale = max-min;
+ // Inflate scale so nested groups are within each other instead of overlapping
+ // (if InitShape() is called in order from child to parent)
+ // Also avoids scale[i] == 0.0 which can cause divide-by-zero elsewhere
+ static const float SCALE_INFLATE = 0.01f;
+ for(int i=0; i<3; ++i){
+ scale[i] += SCALE_INFLATE * ((scale[i]<0.0f)?-1.0f:1.0f);
+ }
+ SetScale(scale);
+}
+
+void Group::PreDrawFrame(float curr_game_time) {
+ if (g_debug_runtime_disable_group_pre_draw_frame) {
+ return;
+ }
+
+ if(child_moved){
+ InitShape();
+ InitRelMats();
+ child_moved = false;
+ }
+
+ if(child_transforms_need_update){
+ PropagateTransformsDown(false);
+ }
+
+ for(int i=0, len=children.size(); i<len; ++i){
+ Child& child = children[i];
+ Object* obj = child.direct_ptr;
+ if(obj){
+ obj->PreDrawFrame(curr_game_time);
+ }
+ }
+}
+
+
+void Group::SetTranslationRotationFast(const vec3& trans, const quaternion& rotation) {
+ Object::SetTranslationRotationFast(trans, rotation);
+ PropagateTransformsDownFast(false);
+}
+
+void Group::PropagateTransformsDownFast(bool deep) {
+ for(int i=0, len=children.size(); i<len; ++i){
+ Child& child = children[i];
+ Object* obj = child.direct_ptr;
+ if(obj){
+ obj->SetTranslationRotationFast(translation_ + (GetRotation() * (scale_ * child.rel_translation)), GetRotation() * child.rel_rotation);
+ if( deep && obj->IsGroupDerived() ) {
+ Group* group = static_cast<Group*>(obj);
+ group->PropagateTransformsDownFast(deep);
+ }
+ }
+ }
+}
+
+void Group::PropagateTransformsDown(bool deep) {
+ for(int i=0, len=children.size(); i<len; ++i){
+ Child& child = children[i];
+ Object* obj = child.direct_ptr;
+ if(obj){
+ obj->SetScale((invert(child.rel_rotation) * child.rel_scale) * scale_);
+ obj->SetRotation(GetRotation() * child.rel_rotation);
+ obj->SetTranslation(translation_ + (GetRotation() * (scale_ * child.rel_translation)));
+
+ if( deep && obj->IsGroupDerived() ) {
+ Group* group = static_cast<Group*>(obj);
+ group->PropagateTransformsDown(deep);
+ }
+ }
+ }
+ child_transforms_need_update = false;
+ child_moved = false;
+}
+
+void Group::PreDrawCamera(float curr_game_time) {
+ if (g_debug_runtime_disable_group_pre_draw_camera) {
+ return;
+ }
+
+ for(int i=0, len=children.size(); i<len; ++i){
+ Child& child = children[i];
+ Object* obj = child.direct_ptr;
+ if(obj){
+ obj->PreDrawCamera(curr_game_time);
+ }
+ }
+}
+
+void Group::Moved(Object::MoveType type) {
+ Object::Moved(type);
+ child_transforms_need_update = true;
+}
+
+void Group::ChildMoved(Object::MoveType type) {
+ child_moved = true;
+}
+
+void Group::UpdateParentHierarchy() {
+ for(int i=0, len=children.size(); i<len; ++i){
+ children[i].direct_ptr->UpdateParentHierarchy();
+ }
+}
+
+void Group::GetDisplayName(char* buf, int buf_size) {
+ if( GetName().empty() ) {
+ FormatString(buf, buf_size, "%d, Group with %d children", GetID(), children.size());
+ } else {
+ FormatString(buf, buf_size, "%s, Group with %d children", GetName().c_str(), children.size());
+ }
+}
+
+void Group::SetEnabled(bool val) {
+ enabled_ = val;
+ for(int i=0, len=children.size(); i<len; ++i){
+ Child& child = children[i];
+ Object* obj = child.direct_ptr;
+ if(obj){
+ obj->SetEnabled(val);
+ }
+ }
+
+}
+
+void Group::HandleTransformationOccured() {
+ for(int i=0, len=children.size(); i<len; ++i){
+ Child& child = children[i];
+ Object* obj = child.direct_ptr;
+ if(obj){
+ obj->HandleTransformationOccurred();
+ }
+ }
+}
+
+void Group::ReceiveObjectMessageVAList( OBJECT_MSG::Type type, va_list args ) {
+ switch(type){
+ case OBJECT_MSG::SET_COLOR: {
+ vec3 temp = *va_arg(args, vec3*);
+
+ std::vector<Child>::iterator cit = children.begin();
+ for( ; cit != children.end(); cit++ )
+ {
+ cit->direct_ptr->ReceiveObjectMessage(OBJECT_MSG::SET_COLOR,&temp);
+ }
+
+ break;}
+ default:
+ Object::ReceiveObjectMessageVAList(type, args);
+ break;
+ }
+}
+
+void Group::RemapReferences(std::map<int,int> id_map) {
+ for(int i=0, len=children.size(); i<len; ++i){
+ children[i].direct_ptr->RemapReferences(id_map);
+ }
+}
+
+ObjectSanityState Group::GetSanity() {
+ uint32_t sanity_flags = 0;
+
+ if( children.size() == 0 ) {
+ sanity_flags |= kObjectSanity_G_Empty;
+ }
+
+ for( uint32_t i = 0; i < children.size(); i++ ) {
+ if( children[i].direct_ptr ) {
+ } else {
+ sanity_flags |= kObjectSanity_G_NullChild;
+ }
+ }
+
+ std::vector<Object*> all_children;
+ GetTopDownCompleteChildren(&all_children);
+
+ for( uint32_t i = 0; i < all_children.size(); i++ ) {
+ Object* obj = all_children[i];
+ if(obj) {
+ if(obj->GetSanity().Ok() == false) {
+ sanity_flags |= kObjectSanity_G_ChildHasSanityIssue;
+ }
+
+ std::vector<int> connections;
+ obj->GetConnectionIDs(&connections);
+
+ for( uint32_t k = 0; k < connections.size(); k++ ) {
+ bool internally_connected = false;
+ for( uint32_t t = 0; t < all_children.size(); t++ ) {
+ if(all_children[i] && all_children[t]->GetID() == connections[k] ) {
+ internally_connected = true;
+ }
+ }
+
+ if( internally_connected == false ) {
+ //TODO: Specify what is externally connected
+ sanity_flags |= kObjectSanity_G_ChildHasExternalConnection;
+ }
+ }
+ } else {
+ sanity_flags |= kObjectSanity_G_NullChild;
+ }
+ }
+
+ return ObjectSanityState(GetType(),GetID(),sanity_flags);
+}
+
+
diff --git a/Source/Objects/group.h b/Source/Objects/group.h
new file mode 100644
index 00000000..a7d70f02
--- /dev/null
+++ b/Source/Objects/group.h
@@ -0,0 +1,75 @@
+//-----------------------------------------------------------------------------
+// Name: group.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/mat4.h>
+#include <Math/quaternions.h>
+
+#include <Game/EntityDescription.h>
+#include <Objects/object.h>
+
+class Group : public Object {
+public:
+ struct Child {
+ Object* direct_ptr;
+ vec3 rel_translation;
+ quaternion rel_rotation;
+ vec3 rel_scale;
+ };
+ std::vector<Child> children;
+ bool child_transforms_need_update;
+ bool child_moved;
+
+ Group();
+ bool Initialize();
+ virtual ~Group();
+ virtual int lineCheck(const vec3 &start, const vec3 &end, vec3 *point, vec3 *normal=0);
+ virtual EntityType GetType() const { return _group; }
+ virtual bool SetFromDesc( const EntityDescription &desc );
+ virtual void PreDrawCamera(float curr_game_time);
+ virtual void PreDrawFrame(float curr_game_time);
+ virtual void Moved(Object::MoveType type);
+ virtual void GetDesc(EntityDescription &desc) const;
+ virtual void ChildMoved(Object::MoveType type);
+ virtual void ChildLost(Object *obj);
+ virtual void UpdateParentHierarchy();
+ virtual void PropagateTransformsDown(bool deep);
+ virtual void GetDisplayName(char* buf, int buf_size);
+ virtual void SetEnabled(bool val);
+ virtual void InitShape();
+ virtual void InitRelMats();
+ virtual void FinalizeLoadedConnections();
+ virtual void GetChildren(std::vector<Object*>* ret_children);
+ virtual void GetBottomUpCompleteChildren(std::vector<Object*>* ret_children);
+ virtual void GetTopDownCompleteChildren(std::vector<Object*>* ret_children);
+ virtual void HandleTransformationOccured();
+ virtual void ReceiveObjectMessageVAList(OBJECT_MSG::Type type, va_list args);
+ virtual void Update(float timestep){}
+ virtual void RemapReferences(std::map<int,int> id_map);
+ virtual void SetTranslationRotationFast(const vec3& trans, const quaternion& rotation);
+ void PropagateTransformsDownFast(bool deep);
+
+ virtual bool IsGroupDerived() const {return true;}
+
+ virtual ObjectSanityState GetSanity();
+};
diff --git a/Source/Objects/hotspot.cpp b/Source/Objects/hotspot.cpp
new file mode 100644
index 00000000..630baa4d
--- /dev/null
+++ b/Source/Objects/hotspot.cpp
@@ -0,0 +1,815 @@
+//-----------------------------------------------------------------------------
+// Name: hotspot.cpp
+// Developer: Wolfire Games LLC
+// Author: Phillip Isola
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "hotspot.h"
+
+#include <Graphics/graphics.h>
+#include <Graphics/shaders.h>
+#include <Graphics/pxdebugdraw.h>
+
+#include <Internal/filesystem.h>
+#include <Internal/memwrite.h>
+#include <Internal/common.h>
+#include <Internal/profiler.h>
+
+#include <Scripting/angelscript/asfuncs.h>
+#include <Scripting/angelscript/add_on/scripthandle/scripthandle.h>
+#include <Scripting/angelscript/add_on/scriptarray/scriptarray.h>
+
+#include <Math/vec3math.h>
+#include <Math/vec4math.h>
+
+#include <Game/level.h>
+#include <Game/savefile_script.h>
+
+#include <Main/scenegraph.h>
+#include <Objects/hotspot.h>
+#include <Physics/bulletworld.h>
+#include <Game/EntityDescription.h>
+#include <Editors/map_editor.h>
+#include <Logging/logdata.h>
+#include <Asset/Asset/hotspotfile.h>
+
+#include <imgui.h>
+
+extern std::string script_dir_path;
+extern std::string preview_script_name;
+extern bool show_script;
+
+extern bool g_debug_runtime_disable_hotspot_draw;
+extern bool g_debug_runtime_disable_hotspot_pre_draw_frame;
+
+Hotspot::Hotspot():
+ as_context(NULL),
+ collision_object(NULL),
+ abstract_collision(true)
+{
+ collidable = false;
+ box_.dims = vec3(4.0f);
+}
+
+Hotspot::~Hotspot() {
+ if(as_context){
+ delete as_context;
+ as_context = NULL;
+ }
+ if(collision_object){
+ scenegraph_->abstract_bullet_world_->RemoveObject(&collision_object);
+ collision_object = NULL;
+ }
+}
+
+void Hotspot::UpdateCollisionShape() {
+ if(collision_object){
+ scenegraph_->abstract_bullet_world_->RemoveObject(&collision_object);
+ collision_object = NULL;
+ }
+ if(abstract_collision){
+ vec3 scale = GetScale();
+ scale[0] = fabs(scale[0]);
+ scale[1] = fabs(scale[1]);
+ scale[2] = fabs(scale[2]);
+ collision_object = scenegraph_->abstract_bullet_world_->CreateBox(
+ vec3(0.0f), scale * 4.0f, BW_STATIC);
+ //mat4 mat = m_transform.blGetMatrix();
+ //mat4 no_scale_mat = mat.GetRotationPart();
+ //no_scale_mat.SetTranslationPart(GetCenter());
+ //col_sphere->SetTransform(no_scale_mat);
+ collision_object->SetTransform(GetTranslation(), Mat4FromQuaternion(GetRotation()), vec3(1.0f));//GetScale());
+ collision_object->body->setInterpolationWorldTransform(collision_object->body->getWorldTransform());
+ collision_object->body->setCollisionFlags(collision_object->body->getCollisionFlags() |
+ btCollisionObject::CF_NO_CONTACT_RESPONSE);
+ collision_object->SetVisibility(true);
+ collision_object->FixDiscontinuity();
+ collision_object->Sleep();
+ scenegraph_->abstract_bullet_world_->dynamics_world_->updateSingleAabb(collision_object->body);
+ collision_object->owner_object = this;
+ }
+}
+
+void Hotspot::Dispose() {
+ as_context->CallScriptFunction(as_funcs.dispose);
+}
+
+void Hotspot::GetDisplayName(char* buf, int buf_size) {
+ if( GetName().empty() ) {
+ FormatString(buf, buf_size, "%d: Hotspot: %s", GetID(), obj_file.c_str());
+ } else {
+ FormatString(buf, buf_size, "%s: Hotspot: %s", GetName().c_str(), obj_file.c_str());
+ }
+}
+
+void Hotspot::DrawImGuiEditor() {
+ ImGui::Text("Script: %s", m_script_file.c_str());
+ ImGui::SameLine();
+ if (ImGui::Button("Preview script")) {
+ show_script = preview_script_name == m_script_file ? !show_script : true;
+ preview_script_name = m_script_file;
+ }
+}
+
+bool Hotspot::Initialize() {
+ UpdateCollisionShape();
+
+ std::string script_file_path = script_dir_path+m_script_file;
+ if(!m_script_file.empty() && FileExists(script_file_path.c_str(), kDataPaths|kModPaths)){
+ ASData as_data;
+ as_data.scenegraph = scenegraph_;
+ as_data.gui = scenegraph_->map_editor->gui;
+ as_context = new ASContext("hotspot", as_data);
+ AttachUIQueries(as_context);
+ AttachActiveCamera(as_context);
+ AttachScreenWidth(as_context);
+ AttachPhysics(as_context);
+ AttachEngine(as_context);
+ AttachScenegraph(as_context, scenegraph_);
+ AttachTextCanvasTextureToASContext(as_context);
+ AttachLevel(as_context);
+ AttachInterlevelData(as_context);
+ AttachPlaceholderObject(as_context);
+ AttachTokenIterator(as_context);
+ AttachStringConvert(as_context);
+ AttachMessages(as_context);
+ AttachSaveFile(as_context, &Engine::Instance()->save_file_);
+ AttachIMGUI(as_context);
+ AttachOnline(as_context);
+ hud_images.AttachToContext(as_context);
+
+ as_collisions.reset(new ASCollisions(scenegraph_));
+ as_collisions->AttachToContext(as_context);
+
+ ScriptParams::RegisterScriptType(as_context);
+ sp.RegisterScriptInstance(as_context);
+
+ as_context->RegisterGlobalProperty("Hotspot hotspot", this);
+
+ as_funcs.init = as_context->RegisterExpectedFunction("void Init()", false);
+ as_funcs.set_parameters = as_context->RegisterExpectedFunction("void SetParameters()", false);
+ as_funcs.receive_message = as_context->RegisterExpectedFunction("void ReceiveMessage(string)", false);
+ as_funcs.update = as_context->RegisterExpectedFunction("void Update()", false);
+ as_funcs.pre_draw = as_context->RegisterExpectedFunction("void PreDraw(float curr_game_time)", false);
+ as_funcs.draw = as_context->RegisterExpectedFunction("void Draw()", false);
+ as_funcs.draw_editor = as_context->RegisterExpectedFunction("void DrawEditor()", false);
+ as_funcs.reset = as_context->RegisterExpectedFunction("void Reset()", false);
+ as_funcs.set_enabled = as_context->RegisterExpectedFunction("void SetEnabled(bool val)", false);
+ as_funcs.handle_event = as_context->RegisterExpectedFunction("void HandleEvent(string event, MovementObject @mo)", false);
+ as_funcs.handle_event_item = as_context->RegisterExpectedFunction("void HandleEventItem(string event, ItemObject @obj)", false);
+ as_funcs.get_type_string = as_context->RegisterExpectedFunction("string GetTypeString()", false);
+ as_funcs.dispose = as_context->RegisterExpectedFunction("void Dispose()", false);
+ as_funcs.pre_script_reload = as_context->RegisterExpectedFunction("void PreScriptReload()", false);
+ as_funcs.post_script_reload = as_context->RegisterExpectedFunction("void PostScriptReload()", false);
+ as_funcs.connect_to = as_context->RegisterExpectedFunction("bool ConnectTo(Object @other)", false);
+ as_funcs.disconnect = as_context->RegisterExpectedFunction("bool Disconnect(Object @other)", false);
+ as_funcs.connected_from = as_context->RegisterExpectedFunction("void ConnectedFrom(Object @other)", false);
+ as_funcs.disconnected_from = as_context->RegisterExpectedFunction("void DisconnectedFrom(Object @other)", false);
+ as_funcs.accept_connections_from = as_context->RegisterExpectedFunction("bool AcceptConnectionsFrom(ConnectionType type)", false);
+ as_funcs.accept_connections_from_obj = as_context->RegisterExpectedFunction("bool AcceptConnectionsFrom(Object @other)", false);
+ as_funcs.accept_connections_to_obj = as_context->RegisterExpectedFunction("bool AcceptConnectionsTo(Object @other)", false);
+ as_funcs.launch_custom_gui = as_context->RegisterExpectedFunction("void LaunchCustomGUI()", false);
+ as_funcs.object_inspector_read_only = as_context->RegisterExpectedFunction("bool ObjectInspectorReadOnly()", false);
+
+ PROFILER_ENTER(g_profiler_ctx, "Exporting docs");
+ char path[kPathSize];
+ FormatString(path, kPathSize, "%sashotspot_docs.h", GetWritePath(CoreGameModID).c_str());
+ as_context->ExportDocs(path);
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ Path script_path = FindFilePath(script_file_path, kDataPaths | kModPaths);
+ as_context->LoadScript(script_path);
+ as_context->CallScriptFunction(as_funcs.init);
+ as_context->CallScriptFunction(as_funcs.set_parameters);
+ return true;
+ } else {
+ LOGE << "Hotspot script \"" << script_file_path << "\" not found" << std::endl;
+ return false;
+ }
+}
+
+void Hotspot::ReceiveObjectMessageVAList( OBJECT_MSG::Type type, va_list args ) {
+ switch(type){
+ case OBJECT_MSG::SCRIPT: {
+ const std::string &str = *va_arg(args, std::string*);
+ ASArglist args;
+ args.AddObject((void*)&str);
+ as_context->CallScriptFunction(as_funcs.receive_message, &args);
+ break;}
+ case OBJECT_MSG::QUEUE_SCRIPT: {
+ const std::string &str = *va_arg(args, std::string*);
+ message_queue.push(str);
+ break;}
+ case OBJECT_MSG::RESET:{
+ Reset();
+ std::string str = "reset";
+ ReceiveObjectMessage(OBJECT_MSG::SCRIPT, &str);
+ break;}
+ default:
+ Object::ReceiveObjectMessageVAList(type, args);
+ break;
+ }
+}
+
+void Hotspot::Update(float timestep) {
+ if(as_context){
+ while(message_queue.empty() == false) {
+ ASArglist args;
+ const std::string str = message_queue.front();
+ args.AddObject((void*)&str);
+ as_context->CallScriptFunction(as_funcs.receive_message, &args);
+ message_queue.pop();
+ }
+ as_context->CallScriptFunction(as_funcs.update);
+ }
+}
+
+void Hotspot::PreDrawFrame(float curr_game_time) {
+ if (g_debug_runtime_disable_hotspot_pre_draw_frame) {
+ return;
+ }
+
+ PROFILER_ZONE(g_profiler_ctx, "Hotspot::PreDrawFrame");
+ ASArglist args;
+ args.Add(curr_game_time);
+ as_context->CallScriptFunction(as_funcs.pre_draw, &args);
+}
+
+void Hotspot::Draw() {
+ if (g_debug_runtime_disable_hotspot_draw) {
+ return;
+ }
+
+ as_context->CallScriptFunction(as_funcs.draw);
+ if(scenegraph_->map_editor->state_ != MapEditor::kInGame){
+ as_context->CallScriptFunction(as_funcs.draw_editor);
+ }
+ if(!Graphics::Instance()->media_mode() && billboard_texture_ref_.valid()){
+ DrawBillboard(billboard_texture_ref_->GetTextureRef(), GetTranslation() + GetScale() * vec3(0.0f,0.5f,0.0f), 2.0f, vec4(1.0f), kStraight);
+ }
+ hud_images.Draw();
+}
+
+void Hotspot::Reset() {
+ if(as_context){
+ as_context->CallScriptFunction(as_funcs.reset);
+ }
+}
+
+void Hotspot::SetEnabled(bool val)
+{
+ Object::SetEnabled(val);
+ PROFILER_ZONE(g_profiler_ctx, "Hotspot::SetEnabled");
+ ASArglist args;
+ args.Add(val);
+ as_context->CallScriptFunction(as_funcs.set_enabled, &args);
+}
+
+int Hotspot::lineCheck(const vec3 &start, const vec3 &end, vec3 *point, vec3 *normal) {
+ return LineCheckEditorCube(start,end,point,normal);
+}
+
+void Hotspot::Moved(Object::MoveType type) {
+ Object::Moved(type);
+ if(collision_object){
+ UpdateCollisionShape();
+ }
+}
+
+void Hotspot::GetDesc(EntityDescription &desc) const {
+ Object::GetDesc(desc);
+ desc.AddString(EDF_FILE_PATH, obj_file);
+ desc.AddIntVec(EDF_CONNECTIONS, connected_to);
+ desc.AddIntVec(EDF_CONNECTIONS_FROM, connected_from);
+}
+
+//Just handle everything in hotspots
+
+void Hotspot::NotifyDeleted(Object* o) {
+ std::vector<int>::iterator iter = std::find(connected_to.begin(), connected_to.end(), o->GetID());
+ if(iter != connected_to.end()) {
+ Disconnect(*scenegraph_->GetObjectFromID(*iter));
+ }
+ Object::NotifyDeleted(o);
+}
+
+void Hotspot::HandleEvent( const std::string &event, MovementObject* mo ) {
+ if(!as_context){
+ return;
+ }
+ ASArglist args;
+ args.AddObject((void*)&event);
+ args.AddObject(mo);
+ as_context->CallScriptFunction(as_funcs.handle_event, &args);
+}
+
+void Hotspot::HandleEventItem( const std::string &event, ItemObject* obj ) {
+ if(!as_context){
+ return;
+ }
+ ASArglist args;
+ args.AddObject((void*)&event);
+ args.AddObject(obj);
+ as_context->CallScriptFunction(as_funcs.handle_event_item, &args);
+}
+
+void Hotspot::SetScriptParams( const ScriptParamMap& spm ) {
+ Object::SetScriptParams(spm);
+}
+
+void Hotspot::UpdateScriptParams() {
+ if(as_context){
+ as_context->CallScriptFunction(as_funcs.set_parameters);
+ }
+}
+
+std::string Hotspot::GetTypeString() {
+ if(!as_context){
+ return "unknown - no script";
+ }
+ ASArglist args;
+ ASArg ret_val;
+ ret_val.type = _as_object;
+ bool success = as_context->CallScriptFunction(as_funcs.get_type_string, &args, &ret_val);
+ if(success){
+ return *((std::string*)ret_val.data);
+ } else {
+ return "unknown - hotspot has no 'GetTypeString()' function";
+ }
+}
+
+bool Hotspot::ASHasVar( const std::string &name ) {
+ return as_context->GetVarPtr(name.c_str()) != NULL;
+}
+
+int Hotspot::ASGetIntVar( const std::string &name ) {
+ return *((int*)as_context->GetVarPtr(name.c_str()));
+}
+
+int Hotspot::ASGetArrayIntVar( const std::string &name, int index ) {
+ return *((int*)as_context->GetArrayVarPtr(name, index));
+}
+
+float Hotspot::ASGetFloatVar( const std::string &name ) {
+ return *((float*)as_context->GetVarPtr(name.c_str()));
+}
+
+bool Hotspot::ASGetBoolVar( const std::string &name ) {
+ return *((bool*)as_context->GetVarPtr(name.c_str()));
+}
+
+void Hotspot::ASSetIntVar( const std::string &name, int value ) {
+ *(int*)as_context->GetVarPtr(name.c_str()) = value;
+}
+
+void Hotspot::ASSetArrayIntVar( const std::string &name, int index, int value ) {
+ *(int*)as_context->GetArrayVarPtr(name, index) = value;
+}
+
+void Hotspot::ASSetFloatVar( const std::string &name, float value ) {
+ *(float*)as_context->GetVarPtr(name.c_str()) = value;
+}
+
+void Hotspot::ASSetBoolVar( const std::string &name, bool value ) {
+ *(bool*)as_context->GetVarPtr(name.c_str()) = value;
+}
+
+bool Hotspot::SetFromDesc( const EntityDescription& desc ) {
+ bool ret = Object::SetFromDesc(desc);
+ if( ret ) {
+ for(unsigned i=0; i<desc.fields.size(); ++i){
+ const EntityDescriptionField& field = desc.fields[i];
+ switch(field.type){
+ case EDF_FILE_PATH: {
+ std::string type_file;
+ field.ReadString(&type_file);
+ if(obj_file != type_file){
+ obj_file = type_file;
+ //HotspotFileRef hfr = HotspotFiles::Instance()->ReturnRef(type_file);
+ HotspotFileRef hfr = Engine::Instance()->GetAssetManager()->LoadSync<HotspotFile>(type_file);
+ if( hfr.valid() ) {
+ if(hfr->billboard_color_map != "Data/UI/spawner/thumbs/Hotspot/empty.png"){
+ SetBillboardColorMap(hfr->billboard_color_map);
+ }
+ SetScriptFile(hfr->script);
+ } else {
+ ret = false;
+ }
+ }
+ break;}
+ case EDF_CONNECTIONS: {
+ field.ReadIntVec(&unfinalized_connected_to);
+ break;}
+ case EDF_CONNECTIONS_FROM: {
+ field.ReadIntVec(&unfinalized_connected_from);
+ break;}
+ }
+ }
+ }
+ return ret;
+}
+
+void Hotspot::SetBillboardColorMap(const std::string& color_map) {
+ billboard_texture_ref_ = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(color_map, PX_SRGB, 0x0);
+ if( billboard_texture_ref_.valid() == false ) {
+ LOGE << "Failed to load Hotspot Billboard texture: " << color_map << " for " << this << std::endl;
+ }
+}
+
+static vec3 GetWind(vec3 check_where, float curr_game_time, float change_rate) {
+ vec3 wind_vel;
+ check_where[0] += curr_game_time*0.7f*change_rate;
+ check_where[1] += curr_game_time*0.3f*change_rate;
+ check_where[2] += curr_game_time*0.5f*change_rate;
+ wind_vel[0] = sin(check_where[0])+cos(check_where[1]*1.3f)+sin(check_where[2]*3.0f);
+ wind_vel[1] = sin(check_where[0]*1.2f)+cos(check_where[1]*1.8f)+sin(check_where[2]*0.8f);
+ wind_vel[2] = sin(check_where[0]*1.6f)+cos(check_where[1]*0.5f)+sin(check_where[2]*1.2f);
+
+ return wind_vel;
+}
+
+void Hotspot::Reload() {
+ if(as_context){
+ as_context->CallScriptFunction(as_funcs.pre_script_reload);
+ as_context->Reload();
+ as_context->CallScriptFunction(as_funcs.post_script_reload);
+ }
+}
+
+static void CFireRibbonUpdate(asIScriptObject* obj, float delta_time, float curr_game_time, const vec3& fire_pos) {
+ CScriptArray& particles = *(CScriptArray*)obj->GetAddressOfProperty(0);
+ vec3& rel_pos = *(vec3*)obj->GetAddressOfProperty(1);
+ vec3& pos = *(vec3*)obj->GetAddressOfProperty(2);
+ float& base_rand = *(float*)obj->GetAddressOfProperty(3);
+ float& spawn_new_particle_delay = *(float*)obj->GetAddressOfProperty(4);
+
+ spawn_new_particle_delay -= delta_time;
+ if(spawn_new_particle_delay <= 0.0f){
+ particles.Resize(particles.GetSize() + 1);
+ asIScriptObject* particle = (asIScriptObject*)particles.At(particles.GetSize()-1);
+ *(vec3*)particle->GetAddressOfProperty(0) = pos;
+ *(vec3*)particle->GetAddressOfProperty(1) = vec3(0.0, 0.0, 0.0);
+ *(float*)particle->GetAddressOfProperty(2) = RangedRandomFloat(0.5,1.5);
+ *(float*)particle->GetAddressOfProperty(3) = curr_game_time;
+
+ while(spawn_new_particle_delay <= 0.0f){
+ spawn_new_particle_delay += 0.1f;
+ }
+ }
+
+ int max_particles = 5;
+ if((int)particles.GetSize() > max_particles) {
+ for(int i=0; i<max_particles; ++i){
+ asIScriptObject* dst_particle = (asIScriptObject*)particles.At(i);
+ asIScriptObject* src_particle = (asIScriptObject*)particles.At(particles.GetSize()-max_particles+i);
+ dst_particle->CopyFrom(src_particle);
+ }
+ particles.Resize(max_particles);
+ }
+ for(int i=0, len=(int)particles.GetSize(); i<len; ++i){
+ asIScriptObject* particle = (asIScriptObject*)particles.At(i);
+ vec3& particle_pos = *(vec3*)particle->GetAddressOfProperty(0);
+ vec3& particle_vel = *(vec3*)particle->GetAddressOfProperty(1);
+ float& particle_heat = *(float*)particle->GetAddressOfProperty(2);
+ particle_vel *= pow(0.2f, delta_time);
+ particle_pos += particle_vel * delta_time;
+ particle_vel += GetWind(particle_pos * 5.0f, curr_game_time, 10.0f) * delta_time * 1.0f;
+ particle_vel += GetWind(particle_pos * 30.0f, curr_game_time, 10.0f) * delta_time * 2.0f;
+ vec3 rel = particle_pos - fire_pos;
+ rel[1] = 0.0;
+ particle_heat -= delta_time * (2.0f + min(1.0f, powf(dot(rel,rel), 2.0)*64.0f)) * 2.0f;
+ if(dot(rel,rel) > 1.0){
+ rel = normalize(rel);
+ }
+
+ particle_vel += rel * delta_time * -3.0f * 6.0f;
+ particle_vel[1] += delta_time * 12.0f;
+ }
+}
+
+
+void CFireRibbonPreDraw(asIScriptObject* obj, float curr_game_time) {
+ CScriptArray& particles = *(CScriptArray*)obj->GetAddressOfProperty(0);
+ float& base_rand = *(float*)obj->GetAddressOfProperty(3);
+
+ DebugDraw* debug_draw = DebugDraw::Instance();
+ int ribbon_id = debug_draw->AddRibbon(_delete_on_draw);
+ DebugDrawElement* element = DebugDraw::Instance()->GetElement(ribbon_id);
+ DebugDrawRibbon* ribbon = (DebugDrawRibbon*) element;
+ const float flame_width = 0.12f;
+ for(int i=0, len=particles.GetSize(); i<len; ++i){
+ asIScriptObject* particle = (asIScriptObject*)particles.At(i);
+ vec3& particle_pos = *(vec3*)particle->GetAddressOfProperty(0);
+ vec3& particle_vel = *(vec3*)particle->GetAddressOfProperty(1);
+ float& particle_heat = *(float*)particle->GetAddressOfProperty(2);
+ float& particle_spawn_time = *(float*)particle->GetAddressOfProperty(3);
+ ribbon->AddPoint(particle_pos, vec4(particle_heat, particle_spawn_time + base_rand, curr_game_time + base_rand, 0.0), flame_width);
+ }
+}
+
+static void ASSetCollisionEnabled(Hotspot* hotspot, bool val) {
+ if(val != hotspot->abstract_collision){
+ hotspot->abstract_collision = val;
+ hotspot->UpdateCollisionShape();
+ }
+}
+
+bool Hotspot::AcceptConnectionsFromImplemented() {
+ if(!as_context)
+ return false;
+ return as_context->HasFunction(as_funcs.accept_connections_from_obj) || as_context->HasFunction(as_funcs.accept_connections_from);
+}
+
+bool Hotspot::AcceptConnectionsFrom(Object::ConnectionType type, Object& other) {
+ bool return_value = false;
+ if(as_context) {
+ ASArglist args;
+
+ ASArg ret_val;
+ ret_val.type = _as_bool;
+ ret_val.data = &return_value;
+ args.AddObject(&other);
+ if(!as_context->CallScriptFunction(as_funcs.accept_connections_from_obj, &args, &ret_val)) {
+ args.clear();
+ args.Add((int)type);
+ as_context->CallScriptFunction(as_funcs.accept_connections_from, &args, &ret_val);
+ }
+ }
+
+ return return_value;
+}
+
+bool Hotspot::AcceptConnectionsTo(Object& other) {
+ bool return_value = false;
+ if(as_context) {
+ ASArglist args;
+
+ ASArg ret_val;
+ ret_val.type = _as_bool;
+ ret_val.data = &return_value;
+ args.AddObject(&other);
+ as_context->CallScriptFunction(as_funcs.accept_connections_to_obj, &args, &ret_val);
+ }
+
+ return return_value;
+}
+
+bool Hotspot::ConnectTo(Object& other, bool checking_other/*= false*/) {
+ if(std::find(connected_to.begin(), connected_to.end(), other.GetID()) != connected_to.end())
+ return false;
+
+ bool return_value = false;
+ if(as_context) {
+ ASArglist args;
+ args.AddObject(&other);
+
+ ASArg ret_val;
+ ret_val.type = _as_bool;
+ ret_val.data = &return_value;
+
+ as_context->CallScriptFunction(as_funcs.connect_to, &args, &ret_val);
+ }
+
+ if(return_value) {
+ connected_to.push_back(other.GetID());
+ other.ConnectedFrom(*this);
+ }
+
+ return return_value;
+}
+
+bool Hotspot::Disconnect(Object& other, bool checking_other/* = false*/) {
+ if(std::find(connected_to.begin(), connected_to.end(), other.GetID()) == connected_to.end())
+ return false;
+
+ bool return_value = false;
+ if(as_context) {
+ ASArglist args;
+ args.AddObject(&other);
+
+ ASArg ret_val;
+ ret_val.type = _as_bool;
+ ret_val.data = &return_value;
+
+ as_context->CallScriptFunction(as_funcs.disconnect, &args, &ret_val);
+ }
+
+ if(return_value) {
+ connected_to.erase(std::remove(connected_to.begin(), connected_to.end(), other.GetID()), connected_to.end());
+ other.DisconnectedFrom(*this);
+ }
+
+ return return_value;
+}
+
+void Hotspot::ConnectedFrom(Object& other) {
+ if(as_context) {
+ ASArglist args;
+ args.AddObject(&other);
+ as_context->CallScriptFunction(as_funcs.connected_from, &args);
+ }
+ connected_from.push_back(other.GetID());
+}
+
+void Hotspot::DisconnectedFrom(Object& other) {
+ if(as_context) {
+ ASArglist args;
+ args.AddObject(&other);
+ as_context->CallScriptFunction(as_funcs.disconnected_from, &args);
+ }
+ connected_from.erase(std::remove(connected_from.begin(), connected_from.end(), other.GetID()), connected_from.end());
+}
+
+CScriptArray* Hotspot::ASGetConnectedObjects() {
+ asIScriptEngine* engine = as_context->GetEngine();
+ asITypeInfo* arrayType = engine->GetTypeInfoByDecl("array<int>");
+ CScriptArray* array = CScriptArray::Create(arrayType);
+
+ array->Reserve(connected_to.size());
+ for(size_t i = 0; i < connected_to.size(); ++i) {
+ array->InsertLast(&connected_to[i]);
+ }
+ return array;
+}
+
+bool Hotspot::HasCustomGUI() {
+ return as_context && as_context->HasFunction(as_funcs.launch_custom_gui);
+}
+
+void Hotspot::LaunchCustomGUI() {
+ if(as_context) {
+ as_context->CallScriptFunction(as_funcs.launch_custom_gui);
+ }
+}
+
+bool Hotspot::ObjectInspectorReadOnly() {
+ bool return_value = false;
+ if(as_context) {
+ ASArg ret_val;
+ ret_val.type = _as_bool;
+ ret_val.data = &return_value;
+ as_context->CallScriptFunction(as_funcs.object_inspector_read_only, NULL, &ret_val);
+ }
+ return return_value;
+}
+
+bool Hotspot::HasFunction(const std::string& function_definition) {
+ bool result = false;
+
+ if(as_context) {
+ result = as_context->HasFunction(function_definition);
+ }
+
+ return result;
+}
+
+int Hotspot::QueryIntFunction(const std::string& function) {
+ if(as_context) {
+ ASArglist args;
+ int val;
+ ASArg return_val;
+ return_val.type = _as_int;
+ return_val.data = &val;
+ as_context->CallScriptFunction(function, &args, &return_val);
+ return val;
+ } else {
+ return -1;
+ }
+}
+
+bool Hotspot::QueryBoolFunction(const std::string& function) {
+ if(as_context) {
+ ASArglist args;
+ bool val;
+ ASArg return_val;
+ return_val.type = _as_bool;
+ return_val.data = &val;
+ as_context->CallScriptFunction(function, &args, &return_val);
+ return val;
+ } else {
+ return false;
+ }
+}
+
+float Hotspot::QueryFloatFunction(const std::string& function) {
+ if(as_context) {
+ ASArglist args;
+ float val;
+ ASArg return_val;
+ return_val.type = _as_float;
+ return_val.data = &val;
+ as_context->CallScriptFunction(function, &args, &return_val);
+ return val;
+ } else {
+ return -1.0f;
+ }
+}
+
+std::string Hotspot::QueryStringFunction(const std::string& function) {
+ if(as_context) {
+ ASArglist args;
+ std::string val;
+ ASArg return_val;
+ return_val.type = _as_string;
+ as_context->CallScriptFunction(function, &args, &return_val);
+ return return_val.strData;
+ } else {
+ return "";
+ }
+}
+
+static bool ConnectTo(Hotspot* obj, Object* other) {
+ if(!other)
+ return false;
+ return obj->ConnectTo(*other);
+}
+static bool Disconnect(Hotspot* obj, Object* other) {
+ if(!other)
+ return false;
+ return obj->Disconnect(*other);
+}
+
+void DefineHotspotTypePublic(ASContext* as_context) {
+ as_context->RegisterObjectType("Hotspot", 0, asOBJ_REF | asOBJ_NOCOUNT);
+ as_context->GetEngine()->RegisterInterface("C_ACCEL");
+ as_context->RegisterObjectMethod("Hotspot",
+ "int GetID()",
+ asMETHOD(Hotspot, GetID), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Hotspot",
+ "string GetTypeString()",
+ asMETHOD(Hotspot, GetTypeString), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Hotspot",
+ "bool HasVar(string& in name)",
+ asMETHOD(Hotspot, ASHasVar), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Hotspot",
+ "bool GetBoolVar(string& in name)",
+ asMETHOD(Hotspot, ASGetBoolVar), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Hotspot",
+ "int GetIntVar(string& in name)",
+ asMETHOD(Hotspot, ASGetIntVar), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Hotspot",
+ "int GetArrayIntVar(const string &in name, int index)",
+ asMETHOD(Hotspot, ASGetArrayIntVar), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Hotspot",
+ "float GetFloatVar(string& in name)",
+ asMETHOD(Hotspot, ASGetFloatVar), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Hotspot",
+ "void SetIntVar(string& in name, int value)",
+ asMETHOD(Hotspot, ASSetIntVar), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Hotspot",
+ "void SetArrayIntVar(const string &in name, int index, int value)",
+ asMETHOD(Hotspot, ASSetArrayIntVar), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Hotspot",
+ "void SetFloatVar(string& in name, float value)",
+ asMETHOD(Hotspot, ASSetFloatVar), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Hotspot",
+ "void SetBoolVar(string& in name, bool value)",
+ asMETHOD(Hotspot, ASSetBoolVar), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Hotspot",
+ "array<int> @GetConnectedObjects()",
+ asMETHOD(Hotspot, ASGetConnectedObjects), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Hotspot",
+ "bool HasFunction(const string &in function_definition)",
+ asMETHOD(Hotspot, HasFunction), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Hotspot",
+ "int QueryIntFunction(const string &in function)",
+ asMETHOD(Hotspot, QueryIntFunction), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Hotspot",
+ "bool QueryBoolFunction(const string &in function)",
+ asMETHOD(Hotspot, QueryBoolFunction), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Hotspot",
+ "float QueryFloatFunction(const string &in function)",
+ asMETHOD(Hotspot, QueryFloatFunction), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Hotspot",
+ "string QueryStringFunction(const string &in function)",
+ asMETHOD(Hotspot, QueryStringFunction), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("Hotspot",
+ "void SetCollisionEnabled(bool)",
+ asFUNCTION(ASSetCollisionEnabled), asCALL_CDECL_OBJFIRST);
+ // Inherited from Object
+ as_context->RegisterObjectMethod("Hotspot", "bool ConnectTo(Object@)", asFUNCTION(ConnectTo), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("Hotspot", "bool Disconnect(Object@)", asFUNCTION(Disconnect), asCALL_CDECL_OBJFIRST);
+
+ as_context->RegisterGlobalFunction("void CFireRibbonUpdate(C_ACCEL @, float delta_time, float curr_game_time, vec3 &in pos)",
+ asFUNCTION(CFireRibbonUpdate), asCALL_CDECL);
+ as_context->RegisterGlobalFunction("void CFireRibbonPreDraw(C_ACCEL @, float curr_game_time)",
+ asFUNCTION(CFireRibbonPreDraw), asCALL_CDECL);
+ as_context->DocsCloseBrace();
+}
diff --git a/Source/Objects/hotspot.h b/Source/Objects/hotspot.h
new file mode 100644
index 00000000..5a20412d
--- /dev/null
+++ b/Source/Objects/hotspot.h
@@ -0,0 +1,152 @@
+//-----------------------------------------------------------------------------
+// Name: hotspot.h
+// Developer: Wolfire Games LLC
+// Author: Phillip Isola
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Objects/object.h>
+#include <Scripting/scriptparams.h>
+
+#include <Graphics/Billboard.h>
+#include <Graphics/hudimage.h>
+#include <Graphics/textureref.h>
+
+#include <Scripting/angelscript/ascollisions.h>
+#include <Scripting/angelscript/ascontext.h>
+#include <Scripting/angelscript/add_on/scriptarray/scriptarray.h>
+
+#include <iostream>
+#include <memory>
+#include <queue>
+
+class MovementObject;
+class ItemObject;
+class HotspotEditor;
+
+class Hotspot : public Object {
+public:
+ Hotspot();
+ virtual ~Hotspot();
+
+ virtual bool Initialize();
+ virtual void ReceiveObjectMessageVAList( OBJECT_MSG::Type type, va_list args );
+ virtual void Update(float timestep);
+ virtual void PreDrawFrame(float curr_game_time);
+ virtual void Draw();
+ virtual void Dispose();
+ virtual void GetDisplayName(char* buf, int buf_size);
+ virtual void DrawImGuiEditor();
+ void GetDesc(EntityDescription &desc) const;
+ virtual void NotifyDeleted(Object* o);
+ virtual EntityType GetType() const { return _hotspot_object; }
+
+ std::auto_ptr<ASCollisions> as_collisions;
+
+ void Reset();
+ virtual void SetEnabled(bool val);
+
+ virtual int lineCheck(const vec3 &start, const vec3 &end, vec3 *point, vec3 *normal);
+
+ inline const std::string& GetScriptFile() const { return m_script_file; }
+ inline void SetScriptFile(const std::string& script_file) { m_script_file = script_file; }
+
+ void SetBillboardColorMap(const std::string& color_map);
+ virtual void Reload();
+ void Moved(Object::MoveType type);
+ void HandleEvent( const std::string &event, MovementObject* mo );
+ void HandleEventItem( const std::string &event, ItemObject* obj );
+ virtual void SetScriptParams( const ScriptParamMap& spm );
+ virtual void UpdateScriptParams();
+ std::string GetTypeString();
+ bool ASHasVar( const std::string &name );
+ int ASGetIntVar( const std::string &name );
+ int ASGetArrayIntVar( const std::string &name, int index );
+ float ASGetFloatVar( const std::string &name );
+ bool ASGetBoolVar( const std::string &name );
+ void ASSetIntVar( const std::string &name, int value );
+ void ASSetArrayIntVar( const std::string &name, int index, int value );
+ void ASSetFloatVar( const std::string &name, float value );
+ void ASSetBoolVar( const std::string &name, bool value );
+ virtual bool SetFromDesc( const EntityDescription& desc );
+ void UpdateCollisionShape();
+
+ bool AcceptConnectionsFromImplemented();
+
+ virtual bool AcceptConnectionsFrom(ConnectionType type, Object& object);
+ virtual bool AcceptConnectionsTo(Object& object);
+ virtual bool ConnectTo(Object& other, bool checking_other = false);
+ virtual bool Disconnect(Object& other, bool checking_other = false);
+
+ virtual void ConnectedFrom(Object& other);
+ virtual void DisconnectedFrom(Object& other);
+
+ CScriptArray* ASGetConnectedObjects();
+
+ bool HasCustomGUI();
+ void LaunchCustomGUI();
+ bool ObjectInspectorReadOnly();
+
+ bool HasFunction(const std::string& function_definition);
+ int QueryIntFunction(const std::string& function);
+ bool QueryBoolFunction(const std::string& function);
+ float QueryFloatFunction(const std::string& function);
+ std::string QueryStringFunction(const std::string& function);
+
+ bool abstract_collision;
+private:
+ struct {
+ ASFunctionHandle init;
+ ASFunctionHandle set_parameters;
+
+ ASFunctionHandle receive_message;
+ ASFunctionHandle update;
+ ASFunctionHandle pre_draw;
+ ASFunctionHandle draw;
+ ASFunctionHandle draw_editor;
+ ASFunctionHandle reset;
+ ASFunctionHandle set_enabled;
+ ASFunctionHandle handle_event;
+ ASFunctionHandle handle_event_item;
+ ASFunctionHandle get_type_string;
+ ASFunctionHandle dispose;
+ ASFunctionHandle pre_script_reload;
+ ASFunctionHandle post_script_reload;
+ ASFunctionHandle connect_to;
+ ASFunctionHandle disconnect;
+ ASFunctionHandle connected_from;
+ ASFunctionHandle disconnected_from;
+ ASFunctionHandle accept_connections_from;
+ ASFunctionHandle accept_connections_from_obj;
+ ASFunctionHandle accept_connections_to_obj;
+ ASFunctionHandle launch_custom_gui;
+ ASFunctionHandle object_inspector_read_only;
+ } as_funcs;
+ HUDImages hud_images;
+ ASContext *as_context;
+ BulletObject *collision_object;
+
+ std::string m_script_file;
+ TextureAssetRef billboard_texture_ref_;
+ std::queue<std::string> message_queue;
+};
+
+void DefineHotspotTypePublic(ASContext* as_context);
diff --git a/Source/Objects/itemobject.cpp b/Source/Objects/itemobject.cpp
new file mode 100644
index 00000000..50b1a3f2
--- /dev/null
+++ b/Source/Objects/itemobject.cpp
@@ -0,0 +1,1705 @@
+//-----------------------------------------------------------------------------
+// Name: itemobject.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "itemobject.h"
+
+#include <Graphics/shaders.h>
+#include <Graphics/models.h>
+#include <Graphics/sky.h>
+#include <Graphics/particles.h>
+#include <Graphics/camera.h>
+#include <Graphics/pxdebugdraw.h>
+#include <Graphics/flares.h>
+
+#include <Objects/itemobjectscriptreader.h>
+#include <Objects/movementobject.h>
+#include <Objects/lightvolume.h>
+#include <Objects/group.h>
+
+#include <Physics/bulletworld.h>
+#include <Physics/bulletobject.h>
+
+#include <Internal/timer.h>
+#include <Internal/profiler.h>
+#include <Internal/common.h>
+
+#include <Math/vec4math.h>
+#include <Math/vec3math.h>
+
+#include <Scripting/angelscript/ascontext.h>
+#include <Main/scenegraph.h>
+#include <Main/engine.h>
+#include <XML/level_loader.h>
+#include <Asset/Asset/material.h>
+#include <Sound/sound.h>
+#include <Utility/compiler_macros.h>
+#include <Logging/logdata.h>
+#include <Threading/sdl_wrapper.h>
+#include <Online/online.h>
+
+#include <btBulletDynamicsCommon.h>
+//-----------------------------------------------------------------------------
+//Functions
+//-----------------------------------------------------------------------------
+
+extern bool g_simple_shadows;
+extern bool g_level_shadows;
+extern char* global_shader_suffix;
+extern bool g_no_decals;
+extern Timer game_timer;
+extern bool g_no_reflection_capture;
+
+extern bool g_debug_runtime_disable_item_object_draw;
+extern bool g_debug_runtime_disable_item_object_draw_depth_map;
+extern bool g_debug_runtime_disable_item_object_pre_draw_frame;
+
+namespace {
+ const float kHeavyThreshold = 3.0f;
+ const float kMediumThreshold = 1.0f;
+
+ /*
+ static bool ShouldUseBlur(const BulletObject* bullet_object_, float time_scale){
+ const float _blur_threshold = 1.5f * square(time_scale);
+ bool use_blur = true;
+ if(use_blur &&
+ length_squared(bullet_object_->GetAngularVelocity()) + min(
+ length_squared(bullet_object_->GetLinearVelocity()-ActiveCameras::Get()->GetVelocity()),
+ length_squared(bullet_object_->GetLinearVelocity())) < _blur_threshold)
+ {
+ use_blur = false;
+ }
+ if(bullet_object_->transform_history.empty()){
+ use_blur = false;
+ }
+ return use_blur;
+ }
+ */
+}
+
+void ItemObject::WakeUpPhysics() {
+ bullet_object_->Activate();
+}
+
+const ItemRef& ItemObject::item_ref() const {
+ return item_ref_;
+}
+
+void ItemObject::Collided( const vec3& pos, float impulse, const CollideInfo &collide_info, BulletObject* object ) {
+ if(stuck_in_environment_){
+ return;
+ }
+ if(thrown_ == kThrown || thrown_ == kThrownStraight){
+ thrown_ = kBounced;
+ }
+ if(scenegraph_->GetMaterialSharpPenetration(pos, this) > 0.0f && !IsConstrained()){
+ int vert = GetClosestVert(pos);
+ float sharpness = GetVertSharpness(vert);
+ vec3 local_pos = GetVertPos(vert) - display_phys_offset_;
+ //vec3 local_vel = old_lin_vel_ + cross(old_ang_vel_, local_pos);
+ vec3 world_vert = bullet_object_->GetTransform() * local_pos;
+ vec3 vert_dir = normalize(world_vert - bullet_object_->GetPosition());
+ //DebugDraw::Instance()->AddLine(pos, pos+collide_info.normal, vec4(0.0f,1.0f,0.5f,1.0f), _persistent);
+ //DebugDraw::Instance()->AddLine(pos, pos-collide_info.normal, vec4(1.0f,0.0f,0.0f,1.0f), _persistent);
+ if(sharpness > 0.3f && dot(vert_dir, normalize(old_lin_vel_)) > 0.7f &&
+ dot(vert_dir, collide_info.normal) < -0.5f){
+ StickingCollisionOccured(pos, vert, collide_info);
+ PlayImpactSound(pos, collide_info.normal, bullet_object_->GetMass());
+ return;
+ }
+ }
+ if(!collide_info.true_impact){
+ return;
+ }
+ if(length_squared(object->GetLinearVelocity())<0.1f){
+ return;
+ }
+ if(impact_sound_delay_ <= 0.0f && impulse > 0.1f){
+ PlayImpactSound(pos, collide_info.normal, impulse);
+ }
+ const MaterialParticle *mp = scenegraph_->GetMaterialParticle("skid", pos);
+ if(!mp || mp->particle_path.empty()){
+ return;
+ }
+
+ vec3 particle_pos = pos;
+ vec3 vel = vec3(0.0f,1.0f,0.0f);
+ vec3 tint =scenegraph_->GetColorAtPoint(pos);
+ scenegraph_->particle_system->MakeParticle(
+ scenegraph_, mp->particle_path, particle_pos, vel, tint);
+}
+
+vec3 ItemObject::GetLinearVelocity() {
+ return bullet_object_->GetLinearVelocity();
+}
+
+vec3 ItemObject::GetAngularVelocity() {
+ return bullet_object_->GetAngularVelocity();
+}
+
+void ItemObject::SetAngularVelocity(vec3 vel) {
+ bullet_object_->SetDamping(0.0f,0.0f);
+ bullet_object_->SetAngularVelocity(vel);
+}
+
+void ItemObject::HandleMaterialEvent(const std::string &the_event, const vec3& normal, const vec3 &event_pos, float gain, float pitch_shift) {
+ const MaterialEvent* me = scenegraph_->GetMaterialEvent(the_event, event_pos, item_ref_->GetSoundModifier(), this);
+ if(me && !me->soundgroup.empty()){
+ float dist = distance(event_pos, ActiveCameras::Get()->GetPos());
+ float dist_filter = dist * 0.1f - 0.4f;
+ //printf("Dist filter: %f\n", dist_filter);
+ const float _speed_of_sound = 340.29f; //meters per second
+
+ dist_filter = min(1.0f, max(0.0f, dist_filter));
+ //SoundGroupRef sgr = SoundGroups::Instance()->ReturnRef(me->soundgroup);
+ SoundGroupRef sgr = Engine::Instance()->GetAssetManager()->LoadSync<SoundGroup>(me->soundgroup);
+ SoundGroupPlayInfo sgpi(*sgr, event_pos);
+ sgpi.gain = gain;
+ sgpi.pitch_shift = pitch_shift;
+ sgpi.play_past_tick = SDL_TS_GetTicks() + 1000 * (unsigned int)(dist / _speed_of_sound);
+ sgpi.occlusion_position = event_pos + normal*0.3f;
+ if(dist_filter < 1.0f){
+ sgpi.max_gain = 1.0f - dist_filter;
+ unsigned long sound_handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->PlayGroup(sound_handle, sgpi);
+ if(me->attached){
+ attached_sounds_.push_back(sound_handle);
+ }
+ }
+
+ if(dist_filter > 0.0f){
+ sgpi.filter_info.path = "Data/Sounds/filters/dist_lowpass.wav";
+ sgpi.filter_info.type = _simple_filter;
+ sgpi.max_gain = dist_filter;
+ unsigned long sound_handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->PlayGroup(sound_handle, sgpi);
+ if(me->attached){
+ attached_sounds_.push_back(sound_handle);
+ }
+ }
+ }
+}
+
+void ItemObject::MakeBulletObject() {
+ if(!bullet_object_ && scenegraph_){
+ bullet_object_ = scenegraph_->bullet_world_->CreateConvexModel(
+ vec3(0.0f),
+ ofc_->model_name,
+ &display_phys_offset_);
+ bullet_object_->keep_history = true;
+ Model& model = Models::Instance()->GetModel(model_id_);
+ phys_offset_ = model.old_center + display_phys_offset_;
+ bullet_object_->owner_object = this;
+ vec3 new_pos = GetTranslation() +
+ GetRotation() * display_phys_offset_;
+ bullet_object_->SetTransform(new_pos, Mat4FromQuaternion(GetRotation()), vec4(1.0f));
+ bullet_object_->SetMass(item_ref_->GetMass());
+ bullet_object_->body->setCcdMotionThreshold(0.000001f);
+ bullet_object_->body->setCcdSweptSphereRadius(0.05f);
+
+ abstract_bullet_object_ = scenegraph_->abstract_bullet_world_->CreateConvexModel(
+ vec3(0.0f),
+ ofc_->model_name,
+ &display_phys_offset_,
+ BW_ABSTRACT_ITEM);
+ abstract_bullet_object_->body->setCollisionFlags(abstract_bullet_object_->body->getCollisionFlags() |
+ btCollisionObject::CF_NO_CONTACT_RESPONSE);
+ abstract_bullet_object_->keep_history = true;
+ abstract_bullet_object_->owner_object = this;
+ abstract_bullet_object_->SetTransform(new_pos, Mat4FromQuaternion(GetRotation()), vec4(1.0f));
+ abstract_bullet_object_->body->setInterpolationWorldTransform(abstract_bullet_object_->body->getWorldTransform());
+ abstract_bullet_object_->Activate();
+ }
+}
+
+bool ItemObject::Initialize() {
+ update_list_entry = scenegraph_->LinkUpdateObject(this);
+ MakeBulletObject();
+ flare_ = scenegraph_->flares.MakeFlare(GetTranslation(), 1.0f, true);
+ Moved(Object::kAll);
+ bullet_object_->UpdateTransform();
+
+ const Model& item_model = Models::Instance()->GetModel(model_id_);
+ vec3 min = item_model.min_coords;
+ vec3 max = item_model.max_coords;
+ box_.center = (max+min)*0.5f;
+ box_.dims = max-min;
+
+ return true;
+}
+
+vec3 ItemObject::GetTransformedVertex(int vert_id) {
+ Model *model = &Models::Instance()->GetModel(model_id_);
+ const mat4 &transform = bullet_object_->GetTransform();
+ vec3 vec = transform * vec3(model->vertices[vert_id*3+0],
+ model->vertices[vert_id*3+1],
+ model->vertices[vert_id*3+2]);
+ return vec;
+}
+
+ItemObject::ItemObject():
+ thrown_(kSafe),
+ active_time_(0.0f),
+ sun_ray_clear_(-1.0f),
+ new_clear_(0.0f),
+ shadow_group_id_(-1),
+ whoosh_sound_handle_(0),
+ impact_sound_delay_(0.0f),
+ whoosh_sound_delay_(0.0f),
+ whoosh_volume_(0.0f),
+ whoosh_pitch_(0.0f),
+ using_physics_(true),
+ bullet_object_(NULL),
+ interp_count_(1),
+ interp_period_(1),
+ stuck_in_environment_(false),
+ sticking_collision_occured_(false),
+ flare_(NULL),
+ flashing_(false),
+ flash_delay_(0.0f),
+ last_held_char_id_(-1),
+ char_impact_delay_(0.0f),
+ blood_surface_getter_(this),
+ blood_surface_(&blood_surface_getter_),
+ abstract_bullet_object_(NULL)
+{
+ permission_flags &= ~Object::CAN_SCALE;
+
+ batch_.gl_state.depth_test = true;
+ batch_.gl_state.cull_face = true;
+ batch_.gl_state.depth_write = true;
+ batch_.gl_state.blend = false;
+
+ depth_batch_.gl_state = batch_.gl_state;
+
+ batch_.AddUniformFloat("fade",0.0f);
+
+ batch_.use_cam_pos = true;
+ batch_.use_light = true;
+
+ Textures* ti = Textures::Instance();
+
+ batch_.texture_ref[4] = ti->GetBlankTextureRef();
+ batch_.texture_ref[5] = ti->GetBlankTextureRef();
+
+ texture_spatterdecal_ = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/spatterdecal.tga");
+}
+
+ItemObject::~ItemObject() {
+ InvalidateReaders();
+ scenegraph_->bullet_world_->RemoveObject(&bullet_object_);
+ if(abstract_bullet_object_){
+ scenegraph_->abstract_bullet_world_->RemoveObject(&abstract_bullet_object_);
+ abstract_bullet_object_ = NULL;
+ }
+ if(flare_){
+ scenegraph_->flares.DeleteFlare(flare_);
+ }
+
+ if(Engine::Instance()->GetSound()->IsHandleValid(whoosh_sound_handle_)){
+ Engine::Instance()->GetSound()->Stop(whoosh_sound_handle_);
+ }
+ blood_surface_.Dispose();
+}
+
+void ItemObject::Moved(Object::MoveType type) {
+ Object::Moved(type);
+ if(bullet_object_){
+ vec3 new_pos = GetTranslation() +
+ GetRotation() * display_phys_offset_;
+ bullet_object_->SetTransform(new_pos, Mat4FromQuaternion(GetRotation()), vec4(1.0f));
+ bullet_object_->SetLinearVelocity(vec3(0.0f));
+ bullet_object_->SetAngularVelocity(vec3(0.0f));
+ bullet_object_->UpdateTransform();
+ bullet_object_->Activate();
+ thrown_ = kThrown;
+ if(stuck_in_environment_){
+ scenegraph_->bullet_world_->LinkObject(bullet_object_);
+ }
+ stuck_in_environment_ = false;
+ sticking_collision_occured_ = false;
+ }
+}
+
+void ItemObject::Reset() {
+ Moved(Object::kAll);
+ blood_surface_.CleanBlood();
+}
+
+void ItemObject::Dispose() {
+ Object::Dispose();
+ InvalidateReaders();
+}
+
+void ItemObject::CleanBlood() {
+ blood_surface_.CleanBlood();
+}
+
+void ItemObject::SetLinearVelocity(vec3 vel) {
+ bullet_object_->SetLinearVelocity(vel);
+}
+
+void ItemObject::Update(float timestep) {
+ Online* online = Online::Instance();
+ bool fetchedData = false;
+ if (online->IsClient()) {
+ std::list<ItemObjectFrame>& frames = incoming_online_item_frames;
+ while(frames.size() > 1) {
+ network_time_interpolator.timestamps.Clear();
+ for(const ItemObjectFrame& frame : frames) {
+ network_time_interpolator.timestamps.PushValue(frame.host_walltime);
+ }
+
+ int interpolator_result = network_time_interpolator.Update();
+
+ if(interpolator_result == 1) {
+ continue;
+ } else if(interpolator_result == 2) {
+ frames.pop_front();
+ continue;
+ } else if(interpolator_result == 3) {
+ break;
+ }
+
+ mat4& current_transform = frames.begin()->transform;
+ mat4& next_transform = std::next(frames.begin())->transform;
+
+ vec3 trans_current = current_transform.GetTranslationPart();
+ vec3 trans_next = next_transform.GetTranslationPart();
+
+ quaternion quat_from_current = QuaternionFromMat4(current_transform.GetRotationPart());
+ quaternion quat_from_next = QuaternionFromMat4(next_transform.GetRotationPart());
+
+ vec3 translation = lerp(trans_current, trans_next, network_time_interpolator.interpolation_step);
+ quaternion rotation = Slerp(quat_from_current, quat_from_next, network_time_interpolator.interpolation_step);
+
+ transform.SetTranslationPart(translation);
+ transform.SetRotationPart(Mat4FromQuaternion(rotation));
+
+ break;
+ }
+
+ // Temp: Disable all updates for clients in regards to items, as we are not really sure what it means to run all of this code here.
+ return;
+ }
+
+ impact_sound_delay_ = max(0.0f,impact_sound_delay_-timestep);
+ whoosh_sound_delay_ = max(0.0f,whoosh_sound_delay_-timestep);
+
+
+ if(bullet_object_->IsActive()){
+ float val = length(bullet_object_->GetLinearVelocity())*0.02f-0.02f;
+ if(StuckInWhom() != -1){
+ val = 0.0f;
+ }
+ bullet_object_->SetMargin(max(0.0f,min(0.05f,val)));
+ vec3 ang_vel = bullet_object_->GetAngularVelocity();
+ const float max_ang_vel = 15.0f;
+ if((thrown_ != kThrown && thrown_ != kThrownStraight) && length_squared(ang_vel)>max_ang_vel*max_ang_vel){
+ bullet_object_->SetAngularVelocity(normalize(ang_vel)*max_ang_vel);
+ }
+ active_time_ += timestep;
+ if(!Engine::Instance()->GetSound()->IsHandleValid(whoosh_sound_handle_)){
+ SoundPlayInfo spi;
+ spi.looping = true;
+ spi.path = "Data/Sounds/windmono.wav";
+ spi.position = bullet_object_->GetPosition();
+ spi.occlusion_position = spi.position;
+
+ // Disable whoosh handle
+ //whoosh_sound_handle_ = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ //Engine::Instance()->GetSound()->Play(whoosh_sound_handle_, spi);
+ }
+ } else {
+ active_time_ = 0.0f;
+ if(Engine::Instance()->GetSound()->IsHandleValid(whoosh_sound_handle_)){
+ Engine::Instance()->GetSound()->Stop(whoosh_sound_handle_);
+ }
+ }
+
+ //printf("Active time: %f\n Owner: %d\n", active_time, last_held_char_id);
+
+ // Orient spear to face in the direction that it is flying
+ vec3 target_dir = normalize(bullet_object_->GetLinearVelocity());
+ if(length_squared(target_dir) != 0.0f && thrown_ == kThrownStraight && bullet_object_->IsActive()){
+ mat4 rot = bullet_object_->GetRotation(); // Get the current rotation of the object
+ vec3 dir = rot * normalize(item_ref_->GetLineEnd(0) - item_ref_->GetLineStart(0)); // Get the direction it is currently pointing
+ vec3 perp = cross(dir, target_dir); // Find a vector perpendicular to both the current direction and the target direction
+ float angle = acosf(dot(dir, target_dir)); // Get angle needed to rotate current direction to target
+ quaternion quat(vec4(perp, angle)); // Create angle-axis rotation
+ mat4 quat_mat = Mat4FromQuaternion(quat);
+ bullet_object_->SetRotation(quat_mat*rot);
+ bullet_object_->SetAngularVelocity(vec3(0.0f));
+ }
+
+ const Model &model = Models::Instance()->GetModel(model_id_);
+ vec3 tip_vec;
+ vec3 dims = model.max_coords - model.min_coords;
+ if(dims[0] > dims[1] && dims[0] > dims[2]){
+ tip_vec = vec3(dims[0] * 0.5f, 0.0f, 0.0f);
+ if(display_phys_offset_[0] > 0.0f){
+ tip_vec *= -1.0f;
+ }
+ } else if(dims[1] > dims[2]){
+ tip_vec = vec3(0.0f, dims[1] * 0.5f, 0.0f);
+ if(display_phys_offset_[1] > 0.0f){
+ tip_vec *= -1.0f;
+ }
+ } else {
+ tip_vec = vec3(0.0f, 0.0f, dims[2] * 0.5f);
+ if(display_phys_offset_[2] > 0.0f){
+ tip_vec *= -1.0f;
+ }
+ }
+
+ tip_vec -= display_phys_offset_;
+ //vec3 local_tip_vec = tip_vec;
+ vec3 rotated_tip_pos = bullet_object_->GetRotation() * tip_vec;
+
+ vec3 tip_pos = bullet_object_->GetTransform() * tip_vec;
+ /*DebugDraw::Instance()->AddLine(bullet_object->GetPosition(),
+ tip_pos,
+ vec4(1.0f),
+ _delete_on_update,
+ _DD_XRAY);*/
+ Engine::Instance()->GetSound()->SetPosition(whoosh_sound_handle_, tip_pos );
+ Engine::Instance()->GetSound()->SetOcclusionPosition(whoosh_sound_handle_, tip_pos );
+ vec3 ang_lin_vel = cross(bullet_object_->GetAngularVelocity(), rotated_tip_pos);
+ vec3 tip_vel = ang_lin_vel + bullet_object_->GetLinearVelocity();
+ /*DebugDraw::Instance()->AddLine(tip_pos,
+ tip_pos + tip_vel,
+ vec4(1.0f),
+ _delete_on_update,
+ _DD_XRAY);*/
+ Engine::Instance()->GetSound()->SetVelocity(whoosh_sound_handle_, tip_vel*2.0f);
+ float speed = min(length(tip_vel),
+ length(tip_vel - ActiveCameras::Get()->GetVelocity()));
+ float pitch = 0.1f/(bullet_object_->GetMass()) * (1.0f + speed * 5.0f);
+ const float _whoosh_volume_inertia = 0.9f;
+ const float _whoosh_pitch_inertia = 0.9f;
+ whoosh_pitch_ = mix(pitch, whoosh_pitch_, _whoosh_pitch_inertia);
+ whoosh_pitch_ = min(20.0f, whoosh_pitch_);
+ Engine::Instance()->GetSound()->SetPitch(whoosh_sound_handle_, whoosh_pitch_);
+ float volume = speed * 0.05f;
+ if(!bullet_object_->IsActive()){
+ volume *= 0.12f;
+ }
+ whoosh_volume_ = mix(volume, whoosh_volume_, _whoosh_volume_inertia);
+ Engine::Instance()->GetSound()->SetVolume(whoosh_sound_handle_, whoosh_volume_);
+
+ /*if(whoosh_sound_delay <= 0.0f){
+ vec3 ang_vel = bullet_object->GetAngularVelocity();
+ const float ang_vel_threshold = 10.0f;
+ if(length_squared(ang_vel)>ang_vel_threshold*ang_vel_threshold){
+ scenegraph_->sound->PlayGroup(
+ "Data/Sounds/sword/light_weapon_swoosh.xml",
+ bullet_object->GetPosition(),
+ length(ang_vel)/ang_vel_threshold,
+ length(ang_vel)/ang_vel_threshold);
+ whoosh_sound_delay = 0.2f;
+ }
+ }*/
+
+ /*mat4 transform = bullet_object->GetInterpRotationX(interp_period,interp_period-interp_count);
+ vec3 pos = bullet_object->GetInterpPositionX(interp_period,interp_period-interp_count);
+ transform.SetTranslationPart(pos);
+ vec3 temp = transform * vec3(0.0f);
+ DebugDraw::Instance()->AddLine(temp-vec3(0.0f,0.1f,0.0f),
+ temp+vec3(0.0f,0.1f,0.0f),
+ vec4(1.0f),
+ _delete_on_update);
+ DebugDraw::Instance()->AddLine(temp-vec3(0.1f,0.0f,0.0f),
+ temp+vec3(0.1f,0.0f,0.0f),
+ vec4(1.0f),
+ _delete_on_update);
+ DebugDraw::Instance()->AddLine(temp-vec3(0.0f,0.0f,0.1f),
+ temp+vec3(0.0f,0.0f,0.1f),
+ vec4(1.0f),
+ _delete_on_update);*/
+ old_lin_vel_ = bullet_object_->GetLinearVelocity();
+ old_ang_vel_ = bullet_object_->GetAngularVelocity();
+
+ if(sticking_collision_occured_){
+ StickToEnvironment(stuck_pos_, stuck_vert_, stuck_collide_info_);
+ }
+ if(!enabled_ && flare_) {
+ // TODO: This is a hack.
+ // This Update should not be called if the item object is not enabled.
+ // Need to implement a SetEnabled override to make that work.
+ // Would also disable the physics object, etc, inside that function. Maybe the flare too?
+ flare_->visible_mult = 0.0f;
+ } else if(flare_) {
+ const float _flash_threshold = 0.0f;//4.0f;
+ if(!flashing_){
+ flare_->visible_mult = 0.0f;
+ flash_delay_ -= timestep;
+ if(!IsHeld() &&
+ flash_delay_ <= 0.0f &&
+ rand()%200 == 0 &&
+ distance_squared(ActiveCameras::Get()->GetPos(), bullet_object_->GetPosition()) > square(_flash_threshold))
+ {
+ /*bool movement_flash = false;
+ if(length_squared(bullet_object_->GetLinearVelocity())>1.0f ||
+ length_squared(bullet_object_->GetAngularVelocity())>1.0f ||
+ length_squared(ActiveCameras::Get()->GetVelocity())>1.0f){
+ movement_flash =true;
+ }
+ if(movement_flash){*/
+ flashing_ = true;
+ flash_progress_ = 0.0f;
+ //}
+ }
+ } else {
+ flash_progress_ += timestep * 8.0f;
+ if(flash_progress_ > 1.0f){
+ flashing_ = false;
+ flash_delay_ = 4.0f;
+ flare_->visible_mult = 0.0f;
+ }
+ }
+
+ if(flashing_){
+ const Model& model = Models::Instance()->GetModel(model_id_);
+ int longest_axis = -1;
+ float longest_length = 0.0f;
+ for(unsigned i=0; i<3; ++i){
+ float ax_len = model.max_coords[i] - model.min_coords[i];
+ if(longest_axis == -1 || ax_len > longest_length){
+ longest_length = ax_len;
+ longest_axis = i;
+ }
+ }
+ vec3 axis_vec;
+ axis_vec[longest_axis] = 1.0f;
+ flare_->position = bullet_object_->GetTransform()*
+ (axis_vec * mix(model.min_coords[longest_axis], model.max_coords[longest_axis], flash_progress_)
+ -display_phys_offset_);
+ flare_->position += normalize(ActiveCameras::Get()->GetPos() - flare_->position)*0.05f;
+ //flare->size_mult = 1.0f/max(1.0f,distance(ActiveCameras::Get()->GetPos(), flare->position));
+ //flare->size_mult = sqrtf(flare->size_mult);
+ flare_->size_mult = 0.1f;
+ flare_->visible_mult = sinf(flash_progress_ * 3.1415f);
+
+ flare_->distant = false;
+ flare_->color = vec3(0.8f,0.8f,1.0f);
+ }
+ }
+ if(stuck_in_environment_){
+ //bullet_object->SetTransform(stuck_transform);
+ bullet_object_->UpdateTransform();
+ }
+ //blood_surface_.Update(scenegraph_, timestep);
+
+ mat4 cur_transform = bullet_object_->GetTransform();
+ if(bullet_object_->IsActive()){
+ // Check thrown collisions
+ mat4 cur_transform = bullet_object_->GetTransform();
+ char_impact_delay_ = max(0.0f, char_impact_delay_ - timestep);
+ for(unsigned j=0; j<=8; ++j){
+ if(char_impact_delay_ == 0.0f && length_squared(bullet_object_->GetLinearVelocity()) > 1.0f){
+ float offset = j*0.125f;
+ mat4 transform = mix(old_transform_, cur_transform, offset);
+
+ size_t num_lines = item_ref_->GetNumLines();
+ for(size_t i=0; i<num_lines; ++i){
+ CheckThrownCollisionLine((int)i, cur_transform, transform);
+ }
+ }
+ }
+ } else {
+ char_impact_delay_ = 0.0f;
+ }
+ old_transform_ = cur_transform;
+
+ if(abstract_bullet_object_){
+ abstract_bullet_object_->SetTransform(bullet_object_->GetTransform());
+ abstract_bullet_object_->body->setInterpolationWorldTransform(abstract_bullet_object_->body->getWorldTransform());
+ abstract_bullet_object_->UpdateTransform();
+ }
+}
+
+void ItemObject::SetThrown(){
+ thrown_ = kThrown;
+}
+
+void ItemObject::SetThrownStraight(){
+ thrown_ = kThrownStraight;
+}
+
+static void DrawItemModel(Model& model, Graphics* graphics, Shaders* shaders, int shader){
+ PROFILER_GPU_ZONE(g_profiler_ctx, "DrawItemModel");
+ if(!model.vbo_loaded){
+ model.createVBO();
+ }
+ model.VBO_faces.Bind();
+ static const int kNumAttribs = 3;
+ int attrib_ids[kNumAttribs];
+ for(int i=0; i<kNumAttribs; ++i){
+ const char* attrib_str;
+ int num_el;
+ VBOContainer* vbo;
+ switch(i){
+ case 0:
+ attrib_str = "vertex_attrib";
+ num_el = 3;
+ vbo = &model.VBO_vertices;
+ break;
+ case 1:
+ attrib_str = "tex_coord_attrib";
+ num_el = 2;
+ vbo = &model.VBO_tex_coords;
+ break;
+ case 2:
+ attrib_str = "normal_attrib";
+ num_el = 3;
+ vbo = &model.VBO_normals;
+ break;
+ default:
+ __builtin_unreachable();
+ break;
+ }
+ CHECK_GL_ERROR();
+ attrib_ids[i] = shaders->returnShaderAttrib(attrib_str, shader);
+ CHECK_GL_ERROR();
+ if(attrib_ids[i] != -1){
+ vbo->Bind();
+ graphics->EnableVertexAttribArray(attrib_ids[i]);
+ CHECK_GL_ERROR();
+ glVertexAttribPointer(attrib_ids[i], num_el, GL_FLOAT, false, num_el*sizeof(GLfloat), 0);
+ CHECK_GL_ERROR();
+ }
+ }
+ graphics->DrawElements(GL_TRIANGLES, model.faces.size(), GL_UNSIGNED_INT, 0);
+ graphics->ResetVertexAttribArrays();
+ graphics->BindArrayVBO(0);
+ graphics->BindElementVBO(0);
+}
+
+void ItemObject::Draw() {
+ if (g_debug_runtime_disable_item_object_draw) {
+ return;
+ }
+
+ Camera* cam = ActiveCameras::Get();
+ mat4 proj_view_matrix = cam->GetProjMatrix() * cam->GetViewMatrix();
+ DrawItem(proj_view_matrix, Object::kFullDraw);
+}
+
+void ItemObject::PreDrawFrame(float curr_game_time){
+ if (g_debug_runtime_disable_item_object_pre_draw_frame) {
+ return;
+ }
+
+ PROFILER_GPU_ZONE(g_profiler_ctx, "ItemObject::PreDrawFrame");
+ batch_.texture_ref[2] = scenegraph_->sky->GetSpecularCubeMapTexture();
+
+ shadow_group_id_ = -1;
+ blood_surface_.PreDrawFrame(Textures::Instance()->getWidth(batch_.texture_ref[0])/4,
+ Textures::Instance()->getHeight(batch_.texture_ref[0])/4);
+
+ vec3 avg_pos = GetPhysicsPosition();
+ static const float _sun_check_threshold = 0.5f;
+ if(distance_squared(avg_pos, last_sun_checked_) > _sun_check_threshold){
+ new_clear_ = (scenegraph_->bullet_world_->CheckRayCollision(
+ avg_pos, avg_pos+scenegraph_->primary_light.pos*1000) == NULL)?1.0f:0.0f;
+ last_sun_checked_ = avg_pos;
+ }
+ if(sun_ray_clear_ >= 0.0f){
+ sun_ray_clear_ = mix(new_clear_, sun_ray_clear_, pow(0.95f, game_timer.updates_since_last_frame));
+ } else {
+ sun_ray_clear_ = new_clear_;
+ }
+}
+
+void ItemObject::DrawDepthMap(const mat4& proj_view_matrix, const vec4* cull_planes, int num_cull_planes, Object::DrawType draw_type) {
+ if (g_debug_runtime_disable_item_object_draw_depth_map) {
+ return;
+ }
+
+ DrawItem(proj_view_matrix, draw_type);
+}
+
+int ItemObject::model_id() const
+{
+ return model_id_;
+}
+
+void ItemObject::Copied()
+{
+ bullet_object_ = NULL;
+ readers_.clear();
+ shadow_group_id_ = -1;
+}
+
+void ItemObject::AddReader( ItemObjectScriptReader* reader )
+{
+ if(reader->holding){
+ std::list<ItemObjectScriptReader*> to_invalidate;
+ for(std::list<ItemObjectScriptReader*>::iterator iter = readers_.begin();
+ iter != readers_.end(); ++iter)
+ {
+ if((*iter)->stuck || (*iter)->holding){
+ to_invalidate.push_back(*iter);
+ }
+ }
+ for(std::list<ItemObjectScriptReader*>::iterator iter = to_invalidate.begin();
+ iter != to_invalidate.end(); ++iter)
+ {
+ (*iter)->Invalidate();
+ }
+ }
+ readers_.push_back(reader);
+}
+
+void ItemObject::RemoveReader( ItemObjectScriptReader* reader )
+{
+ std::list<ItemObjectScriptReader*>::iterator iter =
+ std::find(readers_.begin(), readers_.end(), reader);
+ if(iter != readers_.end()){
+ readers_.erase(iter);
+ }
+}
+
+void ItemObject::SetPosition(const vec3 & pos)
+{
+ if (bullet_object_) {
+ bullet_object_->SetPosition(pos);
+ }
+}
+
+vec3 ItemObject::GetPhysicsPosition()
+{
+ if(bullet_object_){
+ return bullet_object_->GetPosition();
+ }
+ return vec3(0.0f);
+}
+
+
+void ItemObject::SetPhysicsTransform( const mat4 &transform ) {
+ using_physics_ = false;
+ if(bullet_object_){
+ vec3 new_pos = transform.GetTranslationPart() +
+ transform.GetRotatedvec3(phys_offset_);
+ bullet_object_->SetRotationAndVel(transform, interp_period_);
+ bullet_object_->SetPositionAndVel(new_pos, interp_period_);
+ bullet_object_->SetRotation(transform);
+ bullet_object_->SetPosition(new_pos);
+ bullet_object_->Sleep();
+ bullet_object_->UpdateTransform();
+ scenegraph_->bullet_world_->UnlinkObject(bullet_object_);
+ if(stuck_in_environment_){
+ scenegraph_->bullet_world_->LinkObject(bullet_object_);
+ }
+ stuck_in_environment_ = false;
+ sticking_collision_occured_ = false;
+ }
+}
+
+void ItemObject::SetVelocities( const vec3 &linear_vel, const vec3 &angular_vel ) {
+ if(bullet_object_){
+ bullet_object_->SetLinearVelocity(linear_vel);
+ bullet_object_->SetAngularVelocity(angular_vel);
+ }
+}
+
+
+void ItemObject::ActivatePhysics( )
+{
+ if(using_physics_){
+ return;
+ }
+ using_physics_ = true;
+ if(bullet_object_){
+ thrown_ = kSafe;
+ bullet_object_->Activate();
+ interp_period_ = 1;
+ interp_count_ = 1;
+ scenegraph_->bullet_world_->LinkObject(bullet_object_);
+ old_transform_ = bullet_object_->GetTransform();
+ }
+}
+
+void ItemObject::SetInterpolation( int count, int period ) {
+ interp_count_ = count;
+ interp_period_ = period;
+}
+
+void ItemObject::GetDesc(EntityDescription &desc) const {
+ Object::GetDesc(desc);
+ desc.AddString(EDF_FILE_PATH, obj_file);
+ color_tint_component_.AddToDescription(desc);
+}
+
+void ItemObject::StickingCollisionOccured(const vec3 &pos, int vert, const CollideInfo &collide_info){
+ sticking_collision_occured_ = true;
+ stuck_pos_ = pos;
+ stuck_vert_ = vert;
+ stuck_collide_info_ = collide_info;
+}
+
+void ItemObject::StickToEnvironment(const vec3 &pos, int vert, const CollideInfo &collide_info)
+{
+ sticking_collision_occured_ = false;
+ stuck_in_environment_ = true;
+ vec3 vert_pos = ModelToWorldPos(GetVertPos(vert)) - bullet_object_->GetPosition();
+ bullet_object_->SetPosition(pos - vert_pos - collide_info.normal * 0.15f * pow(item_ref_->GetMass(),0.5f));
+ bullet_object_->SetLinearVelocity(vec3(0.0f));
+ bullet_object_->SetAngularVelocity(vec3(0.0f));
+ bullet_object_->SetMargin(0.0f);
+ scenegraph_->bullet_world_->UnlinkObject(bullet_object_);
+ stuck_transform_ = bullet_object_->GetTransform();
+}
+
+vec3 ItemObject::WorldToModelPos(const vec3 &pos){
+ return invert(bullet_object_->GetTransform()) * pos + display_phys_offset_;
+}
+
+vec3 ItemObject::ModelToWorldPos(const vec3 &pos){
+ return bullet_object_->GetTransform() * (pos - display_phys_offset_);
+}
+
+int ItemObject::GetClosestVert(const vec3& pos) {
+ const Model& model = Models::Instance()->GetModel(model_id_);
+ vec3 local_pos = WorldToModelPos(pos);
+ int closest_vert = -1;
+ float closest_dist;
+ unsigned index = 0;
+ for(int i=0, len=model.vertices.size()/3; i<len; ++i){
+ float dist_squared =
+ square(model.vertices[index+0]-local_pos[0]) +
+ square(model.vertices[index+1]-local_pos[1]) +
+ square(model.vertices[index+2]-local_pos[2]);
+ if(closest_vert == -1 || dist_squared < closest_dist){
+ closest_dist = dist_squared;
+ closest_vert = i;
+ }
+ index+=3;
+ }
+ return closest_vert;
+}
+
+vec3 ItemObject::GetVertPos(int vert){
+ const Model& model = Models::Instance()->GetModel(model_id_);
+ return vec3(model.vertices[vert*3+0],
+ model.vertices[vert*3+1],
+ model.vertices[vert*3+2]);
+}
+
+float ItemObject::GetVertSharpness( int vert ) {
+ const Model& model = Models::Instance()->GetModel(model_id_);
+ if(model.aux.empty()){
+ return 0.0f;
+ }
+ return model.aux[vert*3+0];
+}
+
+ItemObject::WeightClass ItemObject::GetWeightClass() {
+ WeightClass weight_class = kLightWeight;
+ float mass = bullet_object_->GetMass();
+ if(mass >= kHeavyThreshold){
+ weight_class = kHeavyWeight;
+ } else if(mass >= kMediumThreshold){
+ weight_class = kMediumWeight;
+ }
+ return weight_class;
+}
+
+void ItemObject::PlayImpactSound(const vec3& pos, const vec3& normal, float impulse) {
+ WeightClass weight_class = GetWeightClass();
+
+ Online* online = Online::Instance();
+
+ if (online->IsHosting()) {
+ online->Send<OnlineMessages::AudioPlaySoundImpactItemMessage>(GetID(), pos, normal, impulse);
+ }
+
+ switch(weight_class){
+ case kLightWeight:
+ HandleMaterialEvent("weapon_drop_light", normal, pos, impulse);
+ break;
+ case kMediumWeight:
+ HandleMaterialEvent("weapon_drop_medium", normal, pos, impulse);
+ break;
+ case kHeavyWeight:
+ HandleMaterialEvent("weapon_drop_heavy", normal, pos, impulse);
+ break;
+ }
+ { // Notify nearby characters
+ const int kBufSize = 512;
+ char msg[kBufSize];
+ FormatString(msg, kBufSize, "nearby_sound %f %f %f %f %d %d", pos[0], pos[1], pos[2], 10.0f, last_held_char_id_, -1);
+
+ ContactInfoCallback cb;
+ scenegraph_->abstract_bullet_world_->GetSphereCollisions(pos, 10.0f, cb);
+ for(int i=0; i<cb.contact_info.size();++i){
+ BulletObject* bo = cb.contact_info[i].object;
+ if(bo && bo->owner_object && scenegraph_->IsObjectSane(bo->owner_object) && bo->owner_object->GetType() == _movement_object) {
+ int id = bo->owner_object->GetID();
+ ((MovementObject*)bo->owner_object)->ReceiveScriptMessage(msg);
+ }
+ }
+ }
+
+ impact_sound_delay_ = 0.2f;
+}
+
+mat4 ItemObject::GetPhysicsTransform() {
+ mat4 transform = bullet_object_->GetTransform();
+ //transform.AddTranslation(transform.GetRotatedvec3(phys_offset)*-1.0f);
+ return transform;
+}
+
+mat4 ItemObject::GetPhysicsTransformIncludeOffset() {
+ mat4 transform = bullet_object_->GetTransform();
+ transform.AddTranslation(transform.GetRotatedvec3(phys_offset_)*-1.0f);
+ return transform;
+}
+
+void ItemObject::GetPhysicsVel( vec3 & linear_vel, vec3 & angular_vel )
+{
+ linear_vel = bullet_object_->GetLinearVelocity();
+ angular_vel = bullet_object_->GetAngularVelocity();
+}
+
+bool ItemObject::IsHeld() {
+ bool held = false;
+ for(std::list<ItemObjectScriptReader*>::iterator iter = readers_.begin();
+ iter != readers_.end(); ++iter)
+ {
+ if((*iter)->holding){
+ held = true;
+ }
+ }
+ return held;
+}
+
+bool ItemObject::IsConstrained() {
+ bool constrained = false;
+ for(std::list<ItemObjectScriptReader*>::iterator iter = readers_.begin();
+ iter != readers_.end(); ++iter)
+ {
+ if((*iter)->constraint){
+ constrained = true;
+ }
+ }
+ return constrained;
+}
+
+bool ItemObject::IsStuckInCharacter() {
+ bool stuck = false;
+ for(std::list<ItemObjectScriptReader*>::iterator iter = readers_.begin();
+ iter != readers_.end(); ++iter)
+ {
+ if((*iter)->stuck){
+ stuck = true;
+ }
+ }
+ return stuck;
+}
+
+int ItemObject::StuckInWhom() {
+ int stuck_id = -1;
+ for(std::list<ItemObjectScriptReader*>::iterator iter = readers_.begin();
+ iter != readers_.end(); ++iter)
+ {
+ if((*iter)->stuck && (*iter)->char_id != -1){
+ stuck_id = (*iter)->char_id;
+ }
+ }
+ return stuck_id;
+}
+
+int ItemObject::HeldByWhom() {
+ int held_id = -1;
+ for(std::list<ItemObjectScriptReader*>::iterator iter = readers_.begin();
+ iter != readers_.end(); ++iter)
+ {
+ if((*iter)->holding && (*iter)->char_id != -1){
+ held_id = (*iter)->char_id;
+ }
+ }
+ return held_id;
+}
+
+void ItemObject::InvalidateReaders()
+{
+ std::list<ItemObjectScriptReader*> temp_readers = readers_;
+ for(std::list<ItemObjectScriptReader*>::iterator iter = temp_readers.begin();
+ iter != temp_readers.end();)
+ {
+ ItemObjectScriptReader* reader = (*iter);
+ reader->Invalidate();
+ ++iter;
+ }
+ readers_.clear();
+}
+
+void ItemObject::AddBloodDecal( vec3 pos, vec3 dir, float size ) {
+ if(Graphics::Instance()->config_.blood() == BloodLevel::kNone){
+ return;
+ }
+ pos -= bullet_object_->GetRotation() * phys_offset_;
+ Model& model = Models::Instance()->GetModel(model_id_);
+ std::vector<int> hit_list;
+ hit_list.resize(model.faces.size()/3);
+ for(unsigned i=0; i<hit_list.size(); ++i){
+ hit_list[i] = i;
+ }
+ blood_surface_.AddDecalToTriangles(hit_list, pos, dir, size, texture_spatterdecal_);
+}
+
+bool ItemObject::ConnectTo( Object& other, bool checking_other /*= false*/ ) {
+ if(other.GetType() == _movement_object){
+ return other.ConnectTo(*this, true);
+ } else if(other.GetType() == _hotspot_object) {
+ return Object::ConnectTo(other, checking_other);
+ } else {
+ return other.GetType() == _hotspot_object;
+ }
+}
+
+bool ItemObject::Disconnect( Object& other, bool checking_other /*= false*/ ) {
+ if(other.GetType() == _movement_object){
+ return other.Disconnect(*this, true);
+ } else {
+ return Object::Disconnect(other, checking_other);
+ }
+}
+
+void ItemObject::SetHolderID( int char_id ) {
+ last_held_char_id_ = char_id;
+ //printf("Item %d now held by %d\n", GetID(), char_id);
+}
+
+ItemObject::ItemState ItemObject::state() const {
+ return state_;
+}
+
+void ItemObject::SetState( ItemState _state ) {
+ state_ = _state;
+}
+
+const bool _debug_draw_collision = false;
+
+bool ItemObject::CheckThrownSafe() const {
+ return thrown_ == kSafe;
+}
+
+
+void ItemObject::CheckThrownCollisionLine( int line_id, const mat4 &cur_transform, const mat4 &transform ) {
+ vec3 point, normal, start, end;
+ start = transform * item_ref_->GetLineStart(line_id);
+ end = transform * item_ref_->GetLineEnd(line_id);
+ int bone = -1;
+ int char_id = scenegraph_->CheckRayCollisionCharacters(start,
+ end,
+ &point,
+ &normal,
+ &bone);
+ if(char_id == last_held_char_id_){// && active_time < 0.1f){
+ return;
+ }
+ vec4 color(1.0f);
+ if(item_ref_->GetLineMaterial(line_id) == "wood"){
+ color = vec4(105/255.0f,77/255.0f,50/255.0f,1.0f);
+ }
+ if(char_id != -1 && !IsHeld() && !IsStuckInCharacter()){
+ if(_debug_draw_collision){
+ DebugDraw::Instance()->AddWireSphere(point, 0.2f, color, _fade);
+ }
+ Object* obj = scenegraph_->GetObjectFromID(char_id);
+ MovementObject *mo = (MovementObject*)obj;
+
+ if(thrown_ != kSafe){
+ int accept_hit = 1;
+ {
+ accept_hit = mo->AboutToBeHitByItem(GetID());
+ }
+ if(accept_hit == 1){
+ bool will_cut = false;
+ int vert = GetClosestVert(point + normalize(GetLinearVelocity()));
+ float sharpness = GetVertSharpness(vert);
+ if(sharpness > 0.3f && item_ref_->GetLineMaterial(line_id) == "metal"){
+ will_cut = true;
+ }
+ bool will_stick = false;
+ if(will_cut){
+ mo->rigged_object()->Stab(point, normalize(end-start), _heavy, 0);
+ AddBloodDecal(cur_transform*invert(transform)*point, normalize(end-start), 0.2f);
+
+ if(item_ref_->GetLabel() == "spear"){
+ will_stick = true;
+ }
+ if(fabs(dot(normalize(end-start), normal)) > 0.4f){
+ will_stick = true;
+ }
+ if(will_stick){
+ mat4 stick_transform = transform;
+ stick_transform.AddTranslation(point - end);
+ stick_transform.AddTranslation(transform.GetRotatedvec3(phys_offset_*-1.0f));
+ float stick_depth = 0.4f;
+ stick_transform.AddTranslation(normalize(end-start)*stick_depth);
+
+ if(bone != -1){
+ //stick_transform = mo->rigged_object()->skeleton_.physics_bones[bone].bullet_object->GetTransform();
+ mo->rigged_object()->StickItem(GetID(), start, end, stick_transform);
+ }
+
+ std::string path = "Data/Sounds/hit/hit_";
+ WeightClass weight_class = GetWeightClass();
+ switch(weight_class){
+ //case _light_weight: path += "light"; break;
+ case kLightWeight: path = "Data/Sounds/weapon_foley/impact/weapon_knife_hit"; break;
+ case kMediumWeight: path += "medium"; break;
+ case kHeavyWeight: path += "hard"; break;
+ }
+ path += ".xml";
+ if(item_ref_->GetLabel() == "spear"){
+ path = "Data/Sounds/hit/spear_hit_flesh.xml";
+ }
+ //SoundGroupRef sgr = SoundGroups::Instance()->ReturnRef(path);
+ SoundGroupRef sgr = Engine::Instance()->GetAssetManager()->LoadSync<SoundGroup>(path);
+ SoundGroupPlayInfo sgpi(*sgr, point);
+ unsigned long handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->PlayGroup(handle,sgpi);
+ } else {
+ std::string path = "Data/Sounds/hit/hit_";
+ WeightClass weight_class = GetWeightClass();
+ switch(weight_class){
+ //case _light_weight: path += "light"; break;
+ case kLightWeight: path = "Data/Sounds/weapon_foley/impact/weapon_knife_hit_neck"; break;
+ case kMediumWeight: path += "medium"; break;
+ case kHeavyWeight: path += "hard"; break;
+ }
+ path += ".xml";
+ //SoundGroupRef sgr = SoundGroups::Instance()->ReturnRef(path);
+ SoundGroupRef sgr = Engine::Instance()->GetAssetManager()->LoadSync<SoundGroup>(path);
+ SoundGroupPlayInfo sgpi(*sgr, point);
+
+ unsigned long handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->PlayGroup(handle,sgpi);
+ }
+ } else {
+ //printf("Will not stick!\n");
+ }
+ int type = 0;
+ if(will_stick){
+ type = 2;
+ } else if(will_cut) {
+ type = 1;
+ }
+ mo->HitByItem(GetID(), point, item_ref_->GetLineMaterial(line_id), type);
+ }
+ }
+ thrown_ = kSafe;
+ vec3 rand_vec;
+ do {
+ rand_vec = vec3(RangedRandomFloat(-1.0f, 1.0f),
+ RangedRandomFloat(-1.0f, 1.0f),
+ RangedRandomFloat(-1.0f, 1.0f));
+ } while (length_squared(rand_vec) > 1.0f);
+ rand_vec = normalize(rand_vec);
+ vec3 vel = bullet_object_->GetLinearVelocity();
+ vec3 reflect_norm = normalize(vel)*-1.0f;
+ reflect_norm[1] *= 0.2f;
+ reflect_norm = normalize(mix(rand_vec, reflect_norm, 0.6f));
+ vel = reflect(vel, reflect_norm);
+ vel *= RangedRandomFloat(0.1f,0.25f);
+ bullet_object_->SetLinearVelocity(vel);
+ //bullet_object->SetAngularVelocity(bullet_object->GetAngularVelocity()*-0.5f);
+ char_impact_delay_ = 0.4f;
+ std::string path = "Data/Sounds/weapon_foley/impact/weapon_drop_";
+ WeightClass weight_class = GetWeightClass();
+ switch(weight_class){
+ //case _light_weight: path += "light_dirt.xml"; break;
+ case kLightWeight: path = "Data/Sounds/weapon_foley/impact/weapon_knife_hilt.xml"; break;
+ case kMediumWeight: path += "medium_dirt.xml"; break;
+ case kHeavyWeight: path += "heavy_dirt.xml"; break;
+ }
+ //SoundGroupRef sgr = SoundGroups::Instance()->ReturnRef(path);
+ SoundGroupRef sgr = Engine::Instance()->GetAssetManager()->LoadSync<SoundGroup>(path);
+ SoundGroupPlayInfo sgpi(*sgr, point);
+
+ unsigned long handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->PlayGroup(handle,sgpi);
+ if(_debug_draw_collision){
+ DebugDraw::Instance()->AddLine(obj->GetTranslation(), point, color, _fade);
+ }
+ }
+ if(_debug_draw_collision){
+ DebugDraw::Instance()->AddLine(start, end, color, _fade);
+ }
+}
+
+void ItemObject::DrawItem(const mat4& proj_view_matrix, Object::DrawType type) {
+ PROFILER_ZONE(g_profiler_ctx, "ItemObject::DrawItem()");
+ Shaders* shaders = Shaders::Instance();
+ Graphics* graphics = Graphics::Instance();
+ Camera* cam = ActiveCameras::Get();
+ Model* model = &Models::Instance()->GetModel(model_id_);
+ Textures* textures = Textures::Instance();
+ Online* online = Online::Instance();
+ //float time_scale = Timer::Instance()->time_scale;
+ //float timestep = Timer::Instance()->timestep;
+
+ CHECK_GL_ERROR();
+ batch_.texture_ref[2] = scenegraph_->sky->GetSpecularCubeMapTexture();
+
+ const int kShaderStrSize = 1024;
+ char buf[2][kShaderStrSize];
+ char* shader_str[2] = {buf[0], buf[1]};
+ if(use_tangent_){
+ FormatString(shader_str[0], kShaderStrSize, "envobject #ITEM #TANGENT");
+ } else {
+ FormatString(shader_str[0], kShaderStrSize, "envobject #ITEM");
+ }
+ FormatString(shader_str[1], kShaderStrSize, "%s %s", shader_str[0], global_shader_suffix);
+ std::swap(shader_str[0], shader_str[1]);
+ if(g_simple_shadows || !g_level_shadows){
+ batch_.texture_ref[4] = graphics->static_shadow_depth_ref;
+ } else {
+ batch_.texture_ref[4] = graphics->cascade_shadow_depth_ref;
+ }
+
+ int the_shader = shaders->returnProgram(shader_str[0]);
+ batch_.shader_id = the_shader;
+
+ batch_.SetStartState();
+
+ if(type == Object::kFullDraw) {
+ TextureRef blood_tex = blood_surface_.blood_tex;
+ if(blood_tex.valid()){
+ Textures::Instance()->bindTexture(blood_tex, 6);
+ }
+ scenegraph_->BindLights(the_shader);
+ shaders->SetUniformFloat("in_light",sun_ray_clear_);
+ //vec3 display_tint = color_tint_component_.temp_tint();
+ shaders->SetUniformVec3("color_tint", color_tint_component_.tint_);
+ shaders->SetUniformVec3("blood_tint", graphics->config_.blood_color());
+ shaders->SetUniformFloat("haze_mult", scenegraph_->haze_mult);
+
+
+ std::vector<mat4> shadow_matrix;
+ shadow_matrix.resize(4);
+ for(int i=0; i<4; ++i){
+ shadow_matrix[i] = cam->biasMatrix * graphics->cascade_shadow_mat[i];
+ }
+ if(g_simple_shadows || !g_level_shadows){
+ shadow_matrix[3] = cam->biasMatrix * graphics->simple_shadow_mat;
+ }
+ shaders->SetUniformMat4Array("shadow_matrix",shadow_matrix);
+
+ if(scenegraph_->light_probe_collection.light_probe_buffer_object_id != -1){
+ shaders->SetUniformInt("num_tetrahedra", scenegraph_->light_probe_collection.ShaderNumTetrahedra());
+ shaders->SetUniformInt("num_light_probes", scenegraph_->light_probe_collection.ShaderNumLightProbes());
+ shaders->SetUniformVec3("grid_bounds_min", scenegraph_->light_probe_collection.grid_lookup.bounds[0]);
+ shaders->SetUniformVec3("grid_bounds_max", scenegraph_->light_probe_collection.grid_lookup.bounds[1]);
+ shaders->SetUniformInt("subdivisions_x", scenegraph_->light_probe_collection.grid_lookup.subdivisions[0]);
+ shaders->SetUniformInt("subdivisions_y", scenegraph_->light_probe_collection.grid_lookup.subdivisions[1]);
+ shaders->SetUniformInt("subdivisions_z", scenegraph_->light_probe_collection.grid_lookup.subdivisions[2]);
+ shaders->SetUniformInt(shaders->GetTexUniform(TEX_AMBIENT_COLOR_BUFFER), TEX_AMBIENT_COLOR_BUFFER);
+ shaders->SetUniformInt(shaders->GetTexUniform(TEX_AMBIENT_GRID_DATA), TEX_AMBIENT_GRID_DATA);
+ glBindBuffer(GL_TEXTURE_BUFFER, scenegraph_->light_probe_collection.light_probe_buffer_object_id);
+ }
+ shaders->SetUniformInt("reflection_capture_num", scenegraph_->ref_cap_matrix.size());
+ if(!scenegraph_->ref_cap_matrix.empty()){
+ assert(!scenegraph_->ref_cap_matrix_inverse.empty());
+ shaders->SetUniformMat4Array("reflection_capture_matrix", scenegraph_->ref_cap_matrix);
+ shaders->SetUniformMat4Array("reflection_capture_matrix_inverse", scenegraph_->ref_cap_matrix_inverse);
+ }
+
+ std::vector<mat4> light_volume_matrix;
+ std::vector<mat4> light_volume_matrix_inverse;
+ for(int i=0, len=scenegraph_->light_volume_objects_.size(); i<len; ++i){
+ Object* obj = scenegraph_->light_volume_objects_[i];
+ const mat4 &mat = obj->GetTransform();
+ light_volume_matrix.push_back(mat);
+ light_volume_matrix_inverse.push_back(invert(mat));
+ }
+ shaders->SetUniformInt("light_volume_num", light_volume_matrix.size());
+ if(!light_volume_matrix.empty()){
+ assert(!light_volume_matrix_inverse.empty());
+ shaders->SetUniformMat4Array("light_volume_matrix", light_volume_matrix);
+ shaders->SetUniformMat4Array("light_volume_matrix_inverse", light_volume_matrix_inverse);
+ }
+
+ if(scenegraph_->light_probe_collection.light_volume_enabled && type == Object::kFullDraw && scenegraph_->light_probe_collection.ambient_3d_tex.valid()){
+ textures->bindTexture(scenegraph_->light_probe_collection.ambient_3d_tex, 16);
+ }
+
+ if( g_no_reflection_capture == false) {
+ textures->bindTexture(scenegraph_->cubemaps, 19);
+ }
+ }
+
+ if (!online->IsClient()) {
+ transform = bullet_object_->GetInterpRotationX(interp_period_, interp_period_ - interp_count_);
+ vec3 pos = bullet_object_->GetInterpPositionX(interp_period_, interp_period_ - interp_count_);
+ transform.SetTranslationPart(pos - transform * display_phys_offset_);
+ }
+
+ shaders->SetUniformMat4("projection_view_mat", proj_view_matrix);
+ shaders->SetUniformMat4("model_mat", transform);
+ shaders->SetUniformMat4("new_vel_mat", bullet_object_->transform);
+ shaders->SetUniformMat4("old_vel_mat", bullet_object_->old_transform);
+ shaders->SetUniformMat3("model_rotation_mat", mat3(transform));
+ shaders->SetUniformFloat("time", game_timer.game_time);
+ DrawItemModel(*model, graphics, shaders, the_shader);
+ CHECK_GL_ERROR();
+
+ batch_.SetEndState();
+}
+
+btTypedConstraint* ItemObject::AddConstraint( BulletObject* _bullet_object ) {
+ return scenegraph_->bullet_world_->AddFixedJoint(bullet_object_, _bullet_object, (bullet_object_->GetPosition() + _bullet_object->GetPosition())*0.5f);
+}
+
+void ItemObject::RemoveConstraint( btTypedConstraint** constraint )
+{
+ scenegraph_->bullet_world_->RemoveJoint(constraint);
+}
+
+mat4 ItemObject::GetTransform() const
+{
+ return transform;
+}
+
+
+void ItemObject::SleepPhysics()
+{
+ bullet_object_->Sleep();
+}
+
+ScriptParams* ItemObject::ASGetScriptParams() {
+ return &sp;
+}
+
+const vec3& ItemObject::phys_offset() const {
+ return phys_offset_;
+}
+
+void ItemObject::InvalidateHeldReaders() {
+ std::list<ItemObjectScriptReader*> temp_readers = readers_; // Use this copy because Invalidate() will mess with readers_ through the callback
+ for(std::list<ItemObjectScriptReader*>::iterator iter = temp_readers.begin();
+ iter != temp_readers.end();)
+ {
+ ItemObjectScriptReader* reader = (*iter);
+ if(reader->holding){
+ reader->Invalidate();
+ }
+ ++iter;
+ }
+}
+
+bool ItemObject::SetFromDesc( const EntityDescription& desc ) {
+ bool ret = Object::SetFromDesc(desc);
+ if( ret ) {
+ for(unsigned i=0; i<desc.fields.size(); ++i){
+ const EntityDescriptionField& field = desc.fields[i];
+ switch(field.type){
+ case EDF_FILE_PATH: {
+ std::string type_file;
+ field.ReadString(&type_file);
+ if(!ofc_.valid() || ofc_->path_ != type_file){
+ Load(type_file);
+ }
+ break;}
+ }
+ }
+
+ color_tint_component_.SetFromDescription(desc);
+ }
+
+ return ret;
+}
+
+const vec3 & ItemObject::GetColorTint()
+{
+ return color_tint_component_.tint_;
+}
+
+const float& ItemObject::GetOverbright()
+{
+ return color_tint_component_.overbright_;
+}
+
+void ItemObject::Load( const std::string &item_path ) {
+ Textures* ti = Textures::Instance();
+
+ textures_.clear();
+
+ obj_file = item_path;
+ //item_ref_ = Items::Instance()->ReturnRef(item_path);
+ item_ref_ = Engine::Instance()->GetAssetManager()->LoadSync<Item>(item_path);
+ //ofc_ = ObjectFiles::Instance()->ReturnRef(item_ref_->GetObjPath());
+ ofc_ = Engine::Instance()->GetAssetManager()->LoadSync<ObjectFile>(item_ref_->GetObjPath());
+
+ if(ofc_->shader_name == "cubemap"){
+ use_tangent_ = true;
+ } else {
+ use_tangent_ = false;
+ }
+
+ char flags = _MDL_CENTER;
+ if(use_tangent_){
+ flags |= _MDL_USE_TANGENT;
+ }
+ model_id_ = Models::Instance()->loadModel(ofc_->model_name.c_str(), flags);
+ blood_surface_.AttachToModel(&Models::Instance()->GetModel(model_id_));
+
+ ti->setWrap(GL_REPEAT);
+ if(!ofc_->color_map.empty()){
+ TextureAssetRef tex = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(ofc_->color_map, PX_SRGB, 0x0);
+ batch_.texture_ref[0] = tex->GetTextureRef();
+ textures_.push_back(tex);
+
+ }
+ if(!ofc_->normal_map.empty()){
+ TextureAssetRef tex = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(ofc_->normal_map);
+ batch_.texture_ref[1] = tex->GetTextureRef();
+ textures_.push_back(tex);
+ }
+ if(!ofc_->sharpness_map.empty()) {
+ Models::Instance()->GetModel(model_id_).LoadAuxFromImage(ofc_->sharpness_map);
+ }
+ batch_.texture_ref[5] = ti->GetBlankTextureRef();
+}
+
+void ItemObject::GetShaderNames(std::map<std::string, int>& preload_shaders) {
+ if(use_tangent_)
+ shader = "envobject #ITEM #TANGENT";
+ else
+ shader = "envobject #ITEM";
+
+ preload_shaders[shader] = SceneGraph::kPreloadTypeAll;
+}
+
+void ItemObject::ReceiveObjectMessageVAList( OBJECT_MSG::Type type, va_list args ) {
+ switch(type){
+ case OBJECT_MSG::SET_COLOR:
+ case OBJECT_MSG::SET_OVERBRIGHT:
+ color_tint_component_.ReceiveObjectMessageVAList(type, args);
+ break;
+ default:
+ Object::ReceiveObjectMessageVAList(type, args);
+ break;
+ }
+}
+
+void ItemObject::SetSafe() {
+ thrown_ = kSafe;
+}
+
+
+namespace {
+ static const int _AS_collectable = _collectable;
+ static const int _AS_misc = _misc;
+ static const int _AS_weapon = _weapon;
+ static const int _AS_item_no_type = _item_no_type;
+
+ int GetNumHands(ItemObject* io){
+ return io->item_ref()->GetNumHands();
+ }
+
+ int GetType(ItemObject* io){
+ return io->item_ref()->GetItemType();
+ }
+
+ bool HasSheatheAttachment(ItemObject* io){
+ return io->item_ref()->HasAttachment(_at_sheathe);
+ }
+
+ vec3 GetPoint(ItemObject* io, std::string name){
+ return io->item_ref()->GetPoint(name);
+ }
+
+ /*
+ const std::string & GetPath(ItemObject* io) {
+ return io->item_ref()->path_;
+ }
+ */
+
+ const std::string& GetSoundModifier(ItemObject* io) {
+ return io->item_ref()->GetSoundModifier();
+ }
+
+ /*
+ bool HasAnimBlend(ItemObject* io, const std::string &anim) {
+ return io->item_ref()->HasAnimBlend(anim);
+ }
+
+ const std::string &GetAnimBlend(ItemObject* io, const std::string &anim)
+ {
+ return io->item_ref()->GetAnimBlend(anim);
+ }
+ */
+
+ float GetMass(ItemObject* io){
+ return io->item_ref()->GetMass();
+ }
+
+ float GetRangeExtender(ItemObject* io) {
+ return io->item_ref()->GetRangeExtender();
+ }
+
+ float GetRangeMultiplier(ItemObject* io) {
+ return io->item_ref()->GetRangeMultiplier();
+ }
+
+ int GetNumLines(ItemObject* io) {
+ return (int) io->item_ref()->GetNumLines();
+ }
+
+ vec3 GetLineStart(ItemObject* io, int which) {
+ return io->item_ref()->GetLineStart(which) - io->phys_offset();
+ }
+
+ vec3 GetLineEnd(ItemObject* io, int which) {
+ return io->item_ref()->GetLineEnd(which) - io->phys_offset();
+ }
+
+ std::string GetLineMaterial(ItemObject* io, int which) {
+ return io->item_ref()->GetLineMaterial(which);
+ }
+
+ const std::string &GetLabel(ItemObject* io) {
+ return io->item_ref()->GetLabel();
+ }
+}
+
+void DefineItemObjectTypePublic(ASContext* as_context)
+{
+ ScriptParams::RegisterScriptType(as_context);
+ as_context->RegisterObjectType("ItemObject", 0, asOBJ_REF | asOBJ_NOCOUNT);
+
+ as_context->RegisterObjectMethod("ItemObject",
+ "vec3 GetPhysicsPosition()",
+ asMETHOD(ItemObject, GetPhysicsPosition), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ItemObject",
+ "vec3 GetLinearVelocity()",
+ asMETHOD(ItemObject, GetLinearVelocity), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ItemObject",
+ "vec3 GetAngularVelocity()",
+ asMETHOD(ItemObject, GetAngularVelocity), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ItemObject",
+ "void SetAngularVelocity(vec3)",
+ asMETHOD(ItemObject, SetAngularVelocity), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ItemObject",
+ "void AddBloodDecal(vec3 position, vec3 direction, float size)",
+ asMETHOD(ItemObject, AddBloodDecal), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ItemObject",
+ "void CleanBlood()",
+ asMETHOD(ItemObject, CleanBlood), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ItemObject",
+ "mat4 GetPhysicsTransform()",
+ asMETHOD(ItemObject, GetPhysicsTransform), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ItemObject",
+ "mat4 GetPhysicsTransformIncludeOffset()",
+ asMETHOD(ItemObject, GetPhysicsTransformIncludeOffset), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ItemObject",
+ "void SetPhysicsTransform(const mat4 &in)",
+ asMETHOD(ItemObject, SetPhysicsTransform), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ItemObject",
+ "void ActivatePhysics()",
+ asMETHOD(ItemObject, ActivatePhysics), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ItemObject",
+ "bool IsHeld()",
+ asMETHOD(ItemObject, IsHeld), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ItemObject",
+ "int StuckInWhom()",
+ asMETHOD(ItemObject, StuckInWhom), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ItemObject",
+ "int HeldByWhom()",
+ asMETHOD(ItemObject, HeldByWhom), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ItemObject",
+ "int GetID()",
+ asMETHOD(ItemObject, GetID), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ItemObject",
+ "void SetLinearVelocity(vec3)",
+ asMETHOD(ItemObject, SetLinearVelocity), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ItemObject",
+ "const string& GetLabel()",
+ asFUNCTION(GetLabel), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("ItemObject",
+ "void SetThrown()",
+ asMETHOD(ItemObject, SetThrown), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ItemObject",
+ "void SetThrownStraight()",
+ asMETHOD(ItemObject, SetThrownStraight), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ItemObject",
+ "float GetMass()",
+ asFUNCTION(GetMass), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("ItemObject",
+ "bool HasSheatheAttachment()",
+ asFUNCTION(HasSheatheAttachment), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("ItemObject",
+ "int GetNumHands()",
+ asFUNCTION(GetNumHands), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("ItemObject",
+ "int GetType()",
+ asFUNCTION(GetType), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("ItemObject",
+ "vec3 GetPoint(string label)",
+ asFUNCTION(GetPoint), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("ItemObject",
+ "int GetNumLines()",
+ asFUNCTION(GetNumLines), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("ItemObject",
+ "vec3 GetLineStart(int line_index)",
+ asFUNCTION(GetLineStart), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("ItemObject",
+ "vec3 GetLineEnd(int line_index)",
+ asFUNCTION(GetLineEnd), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("ItemObject",
+ "string GetLineMaterial(int line_index)",
+ asFUNCTION(GetLineMaterial), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("ItemObject",
+ "const string &GetSoundModifier()",
+ asFUNCTION(GetSoundModifier), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("ItemObject",
+ "float GetRangeExtender()",
+ asFUNCTION(GetRangeExtender), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("ItemObject",
+ "float GetRangeMultiplier()",
+ asFUNCTION(GetRangeMultiplier), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("ItemObject",
+ "bool CheckThrownSafe()",
+ asMETHOD(ItemObject, CheckThrownSafe), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ItemObject",
+ "void SetSafe()",
+ asMETHOD(ItemObject, SetSafe), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ItemObject", "ScriptParams@ GetScriptParams()", asMETHOD(ItemObject,ASGetScriptParams), asCALL_THISCALL);
+ as_context->RegisterObjectProperty("ItemObject", "int last_held_char_id_", asOFFSET(ItemObject, last_held_char_id_));
+ as_context->DocsCloseBrace();
+ as_context->RegisterGlobalProperty("const int _collectable",
+ (void*)&_AS_collectable);
+ as_context->RegisterGlobalProperty("const int _misc",
+ (void*)&_AS_misc);
+ as_context->RegisterGlobalProperty("const int _weapon",
+ (void*)&_AS_weapon);
+ as_context->RegisterGlobalProperty("const int _item_no_type",
+ (void*)&_AS_item_no_type);
+}
+
+vec3 ItemObjectBloodSurfaceTransformedVertexGetter::GetTransformedVertex(int val) {
+ return item_object->GetTransformedVertex(val);
+}
diff --git a/Source/Objects/itemobject.h b/Source/Objects/itemobject.h
new file mode 100644
index 00000000..5dd9c848
--- /dev/null
+++ b/Source/Objects/itemobject.h
@@ -0,0 +1,235 @@
+//-----------------------------------------------------------------------------
+// Name: itemobject.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Online/time_interpolator.h>
+#include <Online/online_datastructures.h>
+
+#include <Graphics/drawbatch.h>
+#include <Graphics/bloodsurface.h>
+
+#include <Asset/Asset/item.h>
+#include <Asset/Asset/objectfile.h>
+
+#include <Objects/object.h>
+#include <XML/level_loader.h>
+#include <Physics/bulletworld.h>
+#include <Game/color_tint_component.h>
+
+#include <vector>
+#include <string>
+#include <list>
+
+//-----------------------------------------------------------------------------
+// Class Definition
+//-----------------------------------------------------------------------------
+
+class BulletObject;
+class ItemObjectScriptReader;
+class ASContext;
+struct Flare;
+class ItemObject;
+
+class ItemObjectBloodSurfaceTransformedVertexGetter : public BloodSurface::TransformedVertexGetter {
+public:
+ ItemObjectBloodSurfaceTransformedVertexGetter(ItemObject* p_item_object):
+ item_object(p_item_object) {}
+ virtual vec3 GetTransformedVertex(int val);
+private:
+ ItemObject* item_object;
+};
+
+class ItemObject: public Object {
+public:
+ virtual EntityType GetType() const { return _item_object; }
+ enum ItemState {
+ kWielded, kSheathed, kFree
+ };
+
+ ItemObject();
+ virtual ~ItemObject();
+
+ void Load(const std::string &item_path);
+ void GetShaderNames(std::map<std::string, int>& preload_shaders);
+
+ virtual bool Initialize();
+ virtual void Update(float timestep);
+ virtual void Draw();
+ virtual void PreDrawFrame(float curr_game_time);
+ virtual bool ConnectTo(Object& other, bool checking_other = false);
+ virtual bool Disconnect(Object& other, bool checking_other = false);
+ virtual void Moved(Object::MoveType type);
+
+ int model_id() const;
+ const ItemRef& item_ref() const;
+ const vec3& phys_offset() const;
+ ItemState state() const;
+
+ void Copied();
+
+ void AddReader(ItemObjectScriptReader* reader);
+ void RemoveReader(ItemObjectScriptReader* reader);
+ void InvalidateReaders();
+ void InvalidateHeldReaders();
+ bool IsHeld();
+ int StuckInWhom();
+
+ void SetPosition(const vec3& pos);
+ vec3 GetPhysicsPosition();
+ void SetPhysicsTransform( const mat4 &transform );
+ void SetVelocities( const vec3 &linear_vel, const vec3 &angular_vel );
+ void SetInterpolation( int count, int period );
+ void ActivatePhysics( );
+ vec3 GetAngularVelocity();
+ void SetAngularVelocity(vec3 vel);
+ mat4 GetPhysicsTransform();
+ mat4 GetPhysicsTransformIncludeOffset();
+ void GetPhysicsVel( vec3 & linear_vel, vec3 & angular_vel );
+ void SetLinearVelocity(vec3 vel);
+ vec3 GetLinearVelocity();
+ void WakeUpPhysics();
+ void SleepPhysics();
+ btTypedConstraint* AddConstraint( BulletObject* _bullet_object );
+ void RemoveConstraint( btTypedConstraint** constraint );
+
+ mat4 GetTransform() const; // Returns final transform matix, used in multiplayer
+
+ void Collided( const vec3& pos, float impulse, const CollideInfo &collide_info, BulletObject* object );
+ void GetDesc(EntityDescription &desc) const;
+ void AddBloodDecal( vec3 pos, vec3 dir, float size );
+ void CleanBlood();
+ void Reset();
+ virtual void Dispose();
+ void SetHolderID( int char_id );
+ void SetThrown();
+ void SetThrownStraight();
+ void SetState(ItemState state);
+ ScriptParams* ASGetScriptParams();
+ int HeldByWhom();
+ virtual void ReceiveObjectMessageVAList(OBJECT_MSG::Type type, va_list args);
+ virtual bool SetFromDesc( const EntityDescription& desc );
+ const vec3 & GetColorTint();
+ const float& GetOverbright();
+ bool CheckThrownSafe() const;
+ void SetSafe();
+ vec3 GetTransformedVertex(int which);
+ int last_held_char_id_;
+
+ std::list<ItemObjectFrame> incoming_online_item_frames;
+
+private:
+ enum ThrownState {
+ kSafe, kThrown, kThrownStraight, kBounced
+ };
+ enum WeightClass {
+ kLightWeight, kMediumWeight, kHeavyWeight
+ };
+
+ uint64_t last_frame = 0;
+
+ // final model matrix. Stored as member variable to use for network interpolation
+ mat4 transform;
+ bool use_tangent_;
+ ColorTintComponent color_tint_component_;
+ ItemState state_;
+ ThrownState thrown_;
+ float active_time_;
+
+ float sun_ray_clear_;
+ float new_clear_;
+ vec3 last_sun_checked_;
+
+ ItemObjectBloodSurfaceTransformedVertexGetter blood_surface_getter_;
+ BloodSurface blood_surface_;
+ int shadow_group_id_;
+ std::vector<TextureAssetRef> textures_;
+ DrawBatch batch_;
+ DrawBatch depth_batch_;
+ int shader_id_;
+ int depth_shader_id_;
+ int shadow_catch_shader_id_;
+ int model_id_;
+ ObjectFileRef ofc_;
+ ItemRef item_ref_;
+
+ unsigned long whoosh_sound_handle_;
+ std::vector<int> attached_sounds_;
+ float impact_sound_delay_;
+ float whoosh_sound_delay_;
+ float whoosh_volume_;
+ float whoosh_pitch_;
+
+ bool using_physics_;
+ BulletObject* bullet_object_;
+ BulletObject* abstract_bullet_object_;
+ vec3 phys_offset_;
+ vec3 display_phys_offset_;
+ int interp_count_;
+ int interp_period_;
+ mat4 old_transform_;
+ vec3 old_lin_vel_;
+ vec3 old_ang_vel_;
+
+ bool stuck_in_environment_;
+ bool sticking_collision_occured_;
+ vec3 stuck_pos_;
+ int stuck_vert_;
+ mat4 stuck_transform_;
+ CollideInfo stuck_collide_info_;
+
+ Flare* flare_;
+ bool flashing_;
+ float flash_progress_;
+ float flash_delay_;
+
+ std::list<ItemObjectScriptReader*> readers_;
+ float char_impact_delay_;
+
+ TextureAssetRef texture_spatterdecal_;
+ std::string shader;
+
+ TimeInterpolator network_time_interpolator;
+
+ virtual void DrawDepthMap(const mat4& proj_view_matrix, const vec4* cull_planes, int num_cull_planes, Object::DrawType draw_type);
+ virtual void HandleMaterialEvent(const std::string &the_event, const vec3& normal, const vec3 &event_pos, float gain = 1.0f, float pitch_shift = 1.0f);
+
+ void MakeBulletObject();
+ void StickingCollisionOccured(const vec3 &pos, int vert, const CollideInfo &collide_info);
+ void StickToEnvironment(const vec3 &pos, int vert, const CollideInfo &collide_info);
+ vec3 WorldToModelPos(const vec3 &pos);
+ vec3 ModelToWorldPos(const vec3 &pos);
+ float GetVertSharpness( int vert );
+ int GetClosestVert(const vec3& pos);
+ vec3 GetVertPos(int vert);
+ WeightClass GetWeightClass();
+ bool IsStuckInCharacter();
+ bool IsConstrained();
+ void HandleThrownImpact(const vec3 &start, const vec3 &end, int char_id, int line_id, const mat4 &cur_transform, const mat4 &transform);
+ void CheckThrownCollisionLine( int line_id, const mat4 &cur_transform, const mat4 &transform );
+ void DrawItem(const mat4& proj_view_matrix, DrawType type);
+
+public:
+ void PlayImpactSound(const vec3& pos, const vec3& normal, float impulse);
+};
+
+void DefineItemObjectTypePublic(ASContext* as_context);
diff --git a/Source/Objects/itemobjectscriptreader.cpp b/Source/Objects/itemobjectscriptreader.cpp
new file mode 100644
index 00000000..cf532ac7
--- /dev/null
+++ b/Source/Objects/itemobjectscriptreader.cpp
@@ -0,0 +1,161 @@
+//-----------------------------------------------------------------------------
+// Name: itemobjectscriptreader.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "itemobjectscriptreader.h"
+
+#include <Objects/itemobject.h>
+#include <Scripting/angelscript/ascontext.h>
+
+//-----------------------------------------------------------------------------
+//Functions
+//-----------------------------------------------------------------------------
+
+void ItemObjectScriptReader::Invalidate()
+{
+ if(invalidate_callback){
+ invalidate_callback(this, callback_ptr_);
+ }
+ //obj = NULL;
+}
+
+
+void ItemObjectScriptReader::Detach()
+{
+ if(obj){
+ obj->RemoveReader(this);
+ if(constraint){
+ obj->RemoveConstraint(&constraint);
+ }
+ }
+ obj = NULL;
+}
+
+void ItemObjectScriptReader::AttachToItemObject( ItemObject* _obj )
+{
+ Detach();
+ obj = _obj;
+ obj->AddReader(this);
+}
+
+ItemObjectScriptReader::~ItemObjectScriptReader()
+{
+ Detach();
+}
+
+bool ItemObjectScriptReader::valid() const
+{
+ return obj != NULL;
+}
+
+ItemObjectScriptReader::ItemObjectScriptReader():
+ obj(NULL),
+ constraint(NULL),
+ just_created(true),
+ holding(false),
+ stuck(false),
+ char_id(-1),
+ attachment_type(_at_unspecified)
+{}
+
+
+vec3 ItemObjectScriptReader::GetPhysicsPosition()
+{
+ if(obj){
+ return obj->GetPhysicsPosition();
+ }
+ return vec3(0.0f);
+}
+
+void ItemObjectScriptReader::SetPhysicsTransform( mat4 transform )
+{
+ if(obj){
+ obj->SetPhysicsTransform(transform);
+ }
+}
+
+void ItemObjectScriptReader::ActivatePhysics()
+{
+ if(obj){
+ obj->ActivatePhysics();
+ }
+}
+
+void ItemObjectScriptReader::SetInterpInfo( int count, int period )
+{
+ if(obj){
+ obj->SetInterpolation(period-count, period);
+ }
+}
+
+float ItemObjectScriptReader::GetRangeExtender()
+{
+ if(obj){
+ return obj->item_ref()->GetRangeExtender();
+ } else {
+ return 0.0f;
+ }
+}
+
+
+ItemObject* ItemObjectScriptReader::GetAttached() const
+{
+ return obj;
+}
+
+void ItemObjectScriptReader::SetPhysicsVel( const vec3 &linear_vel, const vec3 &angular_vel )
+{
+ if(obj){
+ return obj->SetVelocities(linear_vel, angular_vel);
+ }
+}
+
+mat4 ItemObjectScriptReader::GetPhysicsTransform()
+{
+ return obj->GetPhysicsTransform();
+}
+
+void ItemObjectScriptReader::GetPhysicsVel( vec3 &linear_vel, vec3 &angular_vel )
+{
+ return obj->GetPhysicsVel(linear_vel, angular_vel);
+}
+
+void ItemObjectScriptReader::SetInvalidateCallback(void(*func)(ItemObjectScriptReader*, void*), void* callback_ptr) {
+ invalidate_callback = func;
+ callback_ptr_ = callback_ptr;
+}
+
+float ItemObjectScriptReader::GetRangeMultiplier()
+{
+ return obj->item_ref()->GetRangeMultiplier();
+}
+
+void ItemObjectScriptReader::AddConstraint( BulletObject* bullet_object ) {
+ RemoveConstraint();
+ constraint = obj->AddConstraint(bullet_object);
+}
+
+void ItemObjectScriptReader::RemoveConstraint( )
+{
+ if(constraint){
+ obj->RemoveConstraint(&constraint);
+ }
+}
diff --git a/Source/Objects/itemobjectscriptreader.h b/Source/Objects/itemobjectscriptreader.h
new file mode 100644
index 00000000..4bfabc28
--- /dev/null
+++ b/Source/Objects/itemobjectscriptreader.h
@@ -0,0 +1,78 @@
+//-----------------------------------------------------------------------------
+// Name: itemobjectscriptreader.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+#include <Math/mat4.h>
+
+#include <Game/attachment_type.h>
+#include <Asset/Asset/attachmentasset.h>
+
+#include <string>
+
+//-----------------------------------------------------------------------------
+// Class Definition
+//-----------------------------------------------------------------------------
+
+class ASContext;
+class ItemObject;
+class BulletObject;
+class btTypedConstraint;
+
+class ItemObjectScriptReader {
+public:
+ ItemObject* obj;
+ btTypedConstraint* constraint;
+ mat4 velocity;
+ bool just_created;
+ bool holding;
+ bool stuck;
+ int char_id;
+ AttachmentType attachment_type;
+ AttachmentRef attachment_ref;
+ bool attachment_mirror;
+ void* callback_ptr_;
+ void (*invalidate_callback)(ItemObjectScriptReader*, void*);
+ void SetInvalidateCallback(void(*func)(ItemObjectScriptReader*, void*), void* callback_ptr);
+ ItemObjectScriptReader();
+ virtual ~ItemObjectScriptReader();
+ void AttachToItemObject(ItemObject* _obj);
+ ItemObject* GetAttached() const;
+ void Invalidate();
+ bool valid() const;
+ vec3 GetPhysicsPosition();
+ void SetPhysicsTransform(mat4 transform);
+ mat4 GetPhysicsTransform();
+ void SetPhysicsVel(const vec3 &linear_vel, const vec3 &angular_vel);
+ void GetPhysicsVel(vec3 &linear_vel, vec3 &angular_vel);
+ void Detach();
+ void ActivatePhysics();
+ void SetInterpInfo( int count, int period );
+ float GetRangeExtender();
+ float GetRangeMultiplier();
+ ItemObject* operator->() const {
+ return obj;
+ }
+ void AddConstraint( BulletObject* bullet_object );
+ void RemoveConstraint( );
+};
diff --git a/Source/Objects/lightprobeobject.cpp b/Source/Objects/lightprobeobject.cpp
new file mode 100644
index 00000000..506e13fa
--- /dev/null
+++ b/Source/Objects/lightprobeobject.cpp
@@ -0,0 +1,116 @@
+//-----------------------------------------------------------------------------
+// Name: lightprobeobject.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "lightprobeobject.h"
+
+#include <Game/EntityDescription.h>
+#include <Main/scenegraph.h>
+#include <Utility/assert.h>
+
+bool LightProbeObject::Initialize() {
+ LOG_ASSERT(probe_id_ == -1);
+ sp.ASAddIntCheckbox("Negative", false);
+ bool negative = (GetScriptParams()->ASGetInt("Negative") == 1);
+ probe_id_ = scenegraph_->light_probe_collection.AddProbe(GetTranslation(), negative, stored_coefficients);
+
+ if (stored_coefficients) {
+ delete[] stored_coefficients;
+ stored_coefficients = NULL;
+ }
+ obj_file = "light_probe_object";
+ return true;
+}
+
+void LightProbeObject::SetScriptParams( const ScriptParamMap& spm ) {
+ Object::SetScriptParams(spm);
+ if(scenegraph_){
+ bool negative = (GetScriptParams()->ASGetInt("Negative") == 1);
+ bool success = scenegraph_->light_probe_collection.SetNegative(probe_id_, negative);
+ LOG_ASSERT(success);
+ }
+}
+
+void LightProbeObject::Moved(Object::MoveType type) {
+ Object::Moved(type);
+ if(scenegraph_){
+ bool success = scenegraph_->light_probe_collection.MoveProbe(probe_id_, GetTranslation());
+ LOG_ASSERT(success);
+ }
+}
+
+void LightProbeObject::Dispose() {
+ Object::Dispose();
+ if(scenegraph_){
+ bool success = scenegraph_->light_probe_collection.DeleteProbe(probe_id_);
+ LOG_ASSERT(success);
+ probe_id_ = -1;
+ }
+}
+
+LightProbeObject::LightProbeObject() {
+ box_.dims = vec3(1.0f);
+ probe_id_ = -1;
+ stored_coefficients = NULL;
+}
+
+LightProbeObject::~LightProbeObject() {
+ LOG_ASSERT(probe_id_ == -1);
+}
+
+void LightProbeObject::GetDesc(EntityDescription &desc) const {
+ LOG_ASSERT(probe_id_ != -1);
+ LOG_ASSERT(scenegraph_);
+ Object::GetDesc(desc);
+
+ const size_t data_size = kLightProbeNumCoeffs * sizeof(float);
+ std::vector<char> data(data_size);
+
+ memcpy(&data[0], &scenegraph_->light_probe_collection.GetProbeFromID(probe_id_)->ambient_cube_color, data_size);
+ desc.AddData(EDF_GI_COEFFICIENTS, data);
+}
+
+bool LightProbeObject::SetFromDesc( const EntityDescription& desc ) {
+ LOG_ASSERT(probe_id_ == -1);
+ LOG_ASSERT(!stored_coefficients);
+ bool ret = Object::SetFromDesc(desc);
+ if( ret ) {
+ for(unsigned i=0; i<desc.fields.size(); ++i){
+ const EntityDescriptionField& field = desc.fields[i];
+ switch(field.type){
+ case EDF_NEGATIVE_LIGHT_PROBE: {
+ int negative_int;
+ field.ReadInt(&negative_int);
+ sp.ASAddIntCheckbox("Negative", false);
+ sp.ASSetInt("Negative", negative_int);
+ break;}
+
+ case EDF_GI_COEFFICIENTS: {
+ LOG_ASSERT(!stored_coefficients);
+ stored_coefficients = new float[kLightProbeNumCoeffs];
+ memcpy(stored_coefficients, &field.data[0], kLightProbeNumCoeffs * sizeof(float));
+ break;}
+ }
+ }
+ }
+
+ return ret;
+}
diff --git a/Source/Objects/lightprobeobject.h b/Source/Objects/lightprobeobject.h
new file mode 100644
index 00000000..1d70d080
--- /dev/null
+++ b/Source/Objects/lightprobeobject.h
@@ -0,0 +1,47 @@
+//-----------------------------------------------------------------------------
+// Name: lightprobeobject.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Objects/object.h>
+
+//-----------------------------------------------------------------------------
+// Class Definition
+//-----------------------------------------------------------------------------
+
+class LightProbeObject: public Object
+{
+public:
+ virtual EntityType GetType() const { return _light_probe_object; }
+ virtual bool Initialize();
+
+ virtual void SetScriptParams( const ScriptParamMap& spm );
+ virtual void Moved(Object::MoveType type);
+ virtual void Dispose();
+ virtual void GetDesc(EntityDescription &desc) const;
+ virtual bool SetFromDesc( const EntityDescription& desc );
+ LightProbeObject();
+ virtual ~LightProbeObject();
+private:
+ int probe_id_;
+ float *stored_coefficients;
+};
diff --git a/Source/Objects/lightvolume.cpp b/Source/Objects/lightvolume.cpp
new file mode 100644
index 00000000..fb4fd689
--- /dev/null
+++ b/Source/Objects/lightvolume.cpp
@@ -0,0 +1,67 @@
+//-----------------------------------------------------------------------------
+// Name: lightvolume.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "lightvolume.h"
+
+#include <Graphics/camera.h>
+#include <Graphics/shaders.h>
+#include <Graphics/graphics.h>
+#include <Graphics/sky.h>
+
+#include <Game/EntityDescription.h>
+#include <Main/scenegraph.h>
+#include <Editors/map_editor.h>
+#include <Utility/assert.h>
+
+bool LightVolumeObject::Initialize() {
+ obj_file = "light_volume_object";
+ return true;
+}
+
+void LightVolumeObject::Moved(Object::MoveType type) {
+ Object::Moved(type);
+ dirty = true;
+}
+
+void LightVolumeObject::Dispose() {
+ Object::Dispose();
+}
+
+LightVolumeObject::LightVolumeObject():
+ dirty(true)
+{
+ box_.dims = vec3(2.0f);
+}
+
+void LightVolumeObject::Draw() {
+}
+
+LightVolumeObject::~LightVolumeObject() {
+}
+
+void LightVolumeObject::GetDesc(EntityDescription &desc) const {
+ Object::GetDesc(desc);
+}
+
+bool LightVolumeObject::SetFromDesc( const EntityDescription& desc ) {
+ return Object::SetFromDesc(desc);
+}
diff --git a/Source/Objects/lightvolume.h b/Source/Objects/lightvolume.h
new file mode 100644
index 00000000..5c963f0c
--- /dev/null
+++ b/Source/Objects/lightvolume.h
@@ -0,0 +1,47 @@
+//-----------------------------------------------------------------------------
+// Name: lightvolume.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Objects/object.h>
+#include <Graphics/textureref.h>
+
+//-----------------------------------------------------------------------------
+// Class Definition
+//-----------------------------------------------------------------------------
+
+class LightVolumeObject: public Object
+{
+public:
+ virtual EntityType GetType() const { return _light_volume_object; }
+ virtual bool Initialize();
+
+ virtual void Moved(Object::MoveType type);
+ virtual void Dispose();
+ virtual void GetDesc(EntityDescription &desc) const;
+ virtual bool SetFromDesc( const EntityDescription& desc );
+ LightVolumeObject();
+ void Draw();
+ virtual ~LightVolumeObject();
+
+ bool dirty;
+};
diff --git a/Source/Objects/movementobject.cpp b/Source/Objects/movementobject.cpp
new file mode 100644
index 00000000..97d6eed8
--- /dev/null
+++ b/Source/Objects/movementobject.cpp
@@ -0,0 +1,4994 @@
+//-----------------------------------------------------------------------------
+// Name: movementobject.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "movementobject.h"
+
+#include <Objects/riggedobject.h>
+#include <Objects/itemobject.h>
+#include <Objects/pathpointobject.h>
+#include <Objects/group.h>
+#include <Objects/hotspot.h>
+
+#include <Graphics/pxdebugdraw.h>
+#include <Graphics/particles.h>
+#include <Graphics/models.h>
+#include <Graphics/text.h>
+
+#include <Asset/Asset/material.h>
+#include <Asset/Asset/voicefile.h>
+
+#include <Internal/common.h>
+#include <Internal/filesystem.h>
+#include <Internal/memwrite.h>
+#include <Internal/comma_separated_list.h>
+#include <Internal/checksum.h>
+#include <Internal/profiler.h>
+
+#include <Math/vec2math.h>
+#include <Math/vec3math.h>
+#include <Math/vec4math.h>
+
+#include <Editors/mem_read_entity_description.h>
+#include <Editors/actors_editor.h>
+#include <Editors/map_editor.h>
+
+#include <Sound/sound.h>
+#include <Sound/threaded_sound_wrapper.h>
+
+#include <Asset/Asset/objectfile.h>
+#include <Asset/Asset/actorfile.h>
+
+#include <Online/online.h>
+#include <Online/Message/set_object_enabled_message.h>
+#include <Online/online_datastructures.h>
+
+#include <Main/scenegraph.h>
+#include <Main/engine.h>
+
+#include <Scripting/angelscript/add_on/scriptarray/scriptarray.h>
+#include <Scripting/angelscript/asfuncs.h>
+
+#include <Game/level.h>
+#include <Logging/logdata.h>
+#include <UserInput/input.h>
+#include <AI/navmesh.h>
+
+#include <angelscript.h>
+#include <SDL.h>
+
+extern AnimationConfig animation_config;
+extern std::string script_dir_path;
+extern Timer game_timer;
+
+extern bool g_perform_occlusion_query;
+
+extern bool g_debug_runtime_disable_movement_object_draw;
+extern bool g_debug_runtime_disable_movement_object_draw_depth_map;
+extern bool g_debug_runtime_disable_movement_object_pre_draw_camera;
+extern bool g_debug_runtime_disable_movement_object_pre_draw_frame;
+
+const int MovementObject::_awake = 0;
+const int MovementObject::_unconscious = 1;
+const int MovementObject::_dead = 2;
+//-----------------------------------------------------------------------------
+//Functions
+//-----------------------------------------------------------------------------
+
+MovementObject::MovementObject():
+ controlled(false),
+ was_controlled(false),
+ static_char(false),
+ voice_sound(0),
+ connected_pathpoint_id(-1),
+ controller_id(0),
+ camera_id(0),
+ visible(true),
+ velocity(0.0f),
+ is_player(false),
+ reset_time(0.0f),
+ do_connection_finalization_remap(false),
+ char_sphere(NULL),
+ shader("3d_color #NO_VELOCITY_BUF"),
+ no_grab(0),
+ focused_character(false),
+ remote(false)
+{
+ box_.dims = vec3(2.0f);
+ created_on_the_fly = false;
+}
+
+void MovementObject::GetChildren(std::vector<Object*>* ret_children) {
+ if(!env_object_attach_data.empty()){
+ std::vector<AttachedEnvObject> attached_env_objects;
+ Deserialize(env_object_attach_data, attached_env_objects);
+ ret_children->reserve(attached_env_objects.size());
+ for(int i=0, len=attached_env_objects.size(); i<len; ++i){
+ Object* obj = attached_env_objects[i].direct_ptr;
+ if(obj){
+ ret_children->push_back(attached_env_objects[i].direct_ptr);
+ }
+ }
+ }
+}
+
+void MovementObject::GetBottomUpCompleteChildren(std::vector<Object*>* ret_children) {
+ if(!env_object_attach_data.empty()){
+ std::vector<AttachedEnvObject> attached_env_objects;
+ Deserialize(env_object_attach_data, attached_env_objects);
+ ret_children->reserve(attached_env_objects.size());
+ for(int i=0, len=attached_env_objects.size(); i<len; ++i){
+ Object* obj = attached_env_objects[i].direct_ptr;
+ if(obj){
+ obj->GetBottomUpCompleteChildren(ret_children);
+ ret_children->push_back(attached_env_objects[i].direct_ptr);
+ }
+ }
+ }
+}
+
+bool MovementObject::IsMultiplayerSupported() {
+ ASArglist arglist;
+
+ ASArg return_val;
+ bool multiplayer_supported = false;
+ return_val.type = _as_bool;
+ return_val.data = &multiplayer_supported;
+
+ if(as_context->CallScriptFunction(as_funcs.is_multiplayer_supported, &arglist, &return_val)) {
+ return multiplayer_supported;
+ } else {
+ return false;
+ }
+}
+
+void MovementObject::UpdatePaused() {
+ if(as_context.get()){
+ as_context->CallScriptFunction(as_funcs.update_paused);
+ }
+}
+
+int MovementObject::AboutToBeHitByItem(int id) {
+ asDWORD return_val_int = 0;
+ ASArg return_val;
+ return_val.type = _as_int;
+ return_val.data = &return_val_int;
+ ASArglist args;
+ args.Add(id);
+ if(as_context.get()){
+ as_context->CallScriptFunction(as_funcs.about_to_be_hit_by_item, &args, &return_val);
+ }
+ return return_val_int;
+}
+
+void MovementObject::PreDrawFrame(float curr_game_time) {
+ Online* online = Online::Instance();
+
+ if (g_debug_runtime_disable_movement_object_pre_draw_frame) {
+ return;
+ }
+
+ PROFILER_GPU_ZONE(g_profiler_ctx, "MovementObject::PreDrawFrame");
+ needs_predraw_update = true;
+ // Hide shadow decals
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Angelscript PreDrawFrame()");
+ ASArglist args;
+ args.Add(curr_game_time);
+ as_context->CallScriptFunction(as_funcs.pre_draw_frame, &args);
+ }
+
+ // Update attached objects
+
+ if (!online->IsClient()) {
+ int num_attached_env_objects = rigged_object_->children.size();
+ for (int i = 0; i < num_attached_env_objects; ++i) {
+ AttachedEnvObject& attached_env_object = rigged_object_->children[i];
+ Object* obj = attached_env_object.direct_ptr;
+ if (obj) {
+ if (IsBeingMoved(scenegraph_->map_editor, obj) && obj->Selected()) {
+ attached_env_object.bone_connection_dirty = true;
+ }
+ else {
+ if (attached_env_object.bone_connection_dirty) {
+ for (unsigned i = 0; i < kMaxBoneConnects; ++i) {
+ BoneConnect& bone_connect = attached_env_object.bone_connects[i];
+ if (bone_connect.bone_id != -1) {
+ mat4 transform_mat = rigged_object_->display_bone_transforms[bone_connect.bone_id].GetMat4();
+ mat4 env_mat = Mat4FromQuaternion(obj->GetRotation());
+ env_mat.SetTranslationPart(obj->GetTranslation());
+ bone_connect.rel_mat = invert(transform_mat) * env_mat;
+ }
+ }
+ attached_env_object.bone_connection_dirty = false;
+ }
+ bool had_moved = obj->online_transform_dirty;
+ obj->SetTranslation(vec3(0.0f, -999999.0f, 0.0f));
+ obj->online_transform_dirty = had_moved;
+ if (obj->IsGroupDerived()) {
+ obj->PropagateTransformsDown(true);
+ }
+ }
+ }
+ }
+ }
+}
+
+void MovementObject::Reload() {
+ as_context->Reload();
+}
+
+const float kCullRadius = 2.0f;
+
+void MovementObject::ActualPreDraw(float curr_game_time) {
+ bool dirty_attachment = false;
+ for(int i=0, len=rigged_object_->children.size(); i<len; ++i){
+ if(rigged_object_->children[i].bone_connection_dirty){
+ dirty_attachment = true;
+ }
+ }
+ rigged_object_->PreDrawFrame(curr_game_time);
+ for(int i=0, len=rigged_object_->children.size(); i<len; ++i){
+ AttachedEnvObject& child = rigged_object_->children[i];
+ Object* obj = child.direct_ptr;
+ if(obj){
+ obj->PreDrawFrame(curr_game_time);
+ }
+ }
+
+ if(dirty_attachment){
+ Serialize(rigged_object()->children, env_object_attach_data); //TODO(david): make this only happen when needed
+ }
+
+ DrawNametag();
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Angelscript PreDrawCamera()");
+ ASArglist args;
+ args.Add(curr_game_time);
+ as_context->CallScriptFunction(as_funcs.pre_draw_camera, &args);
+ }
+ needs_predraw_update = false;
+}
+
+void MovementObject::DrawNametag() {
+ RegenerateNametag(); // TODO DEBUG remove this! This should be removed once we can hook this function call into an "OnAvatarChange" event
+ if(nametag_string.length() > 1) {
+ // Figure out text draw position, apply some smoothing based on the last position
+ vec3 base_position = rigged_object_->GetDisplayBonePosition(0);
+ nametag_last_position = vec3(
+ nametag_last_position.x() * 0.2f + 0.8f * base_position.x(),
+ nametag_last_position.y() * 0.8f + 0.2f * (base_position.y() + 1),
+ nametag_last_position.z() * 0.2f + 0.8f * base_position.z()
+ );
+
+ // Figure out text opacity, fade out when away from camera
+ float fadeoutStart = 10;
+ float fadeoutEnd = 15;
+
+ float distance_to_camera = length(position - ActiveCameras::Get()->GetPos());
+ float opacity = 1.0f - (distance_to_camera - fadeoutStart) / (fadeoutEnd - fadeoutStart);
+ opacity = std::min(1.0f, std::max(0.0f, opacity)); // Clamp opacity to [0;1]
+
+ // Get Text color
+ vec4 color = vec4( // Disco lights
+ std::cos(game_timer.game_time * 2 + 0.0f) * 0.5f + 0.5f,
+ std::cos(game_timer.game_time * 2 + 2.0f) * 0.5f + 0.5f,
+ std::cos(game_timer.game_time * 2 + 4.0f) * 0.5f + 0.5f,
+ opacity * 0.8f
+ );
+
+ // Draw Text
+ DebugDraw::Instance()->AddText(nametag_last_position, nametag_string, 1.0f, LifespanFromInt(_delete_on_draw), _DD_SCREEN_SPACE, color);
+ }
+}
+
+void MovementObject::RegenerateNametag() { // TODO This must be called when avatar possession changes, do we have an event for this?
+ // nametag_string = "ID: " + std::to_string(GetID()) + " (" + std::to_string(Online::Instance()->GetOriginalID(GetID())) + "), controlled: " + std::to_string(controlled) + ", controller_id: " + std::to_string(controller_id) + ", camera_id: " + std::to_string(camera_id) + ", remote: " + std::to_string(remote) + ", is_player: " + std::to_string(is_player);
+ // return;
+
+ nametag_string = "";
+ if(controlled && !remote) {
+ return; // Don't show nametag on character controlled by us
+ }
+
+ PlayerState player_state;
+ if(Online::Instance()->TryGetPlayerState(player_state, GetID())) {
+ nametag_last_position = rigged_object_->GetDisplayBonePosition(0) + vec3(0, 1, 0);
+ nametag_string = player_state.playername;
+ } else {
+ nametag_string = "";
+ }
+}
+
+int GetStateID(std::vector<MovementObject::OcclusionState>& occlusion_states, int cam_id) {
+ int state_id = -1;
+ for(int state_index=0, state_len=occlusion_states.size();
+ state_index < state_len;
+ ++state_index)
+ {
+ if(occlusion_states[state_index].cam_id == cam_id){
+ state_id = state_index;
+ break;
+ }
+ }
+ if(state_id == -1){
+ state_id = occlusion_states.size();
+ occlusion_states.resize(occlusion_states.size()+1);
+ occlusion_states[state_id].cam_id = cam_id;
+ }
+ return state_id;
+}
+
+void MovementObject::DrawDepthMap(const mat4& proj_view_matrix, const vec4* cull_planes, int num_cull_planes, Object::DrawType draw_type) {
+ if (g_debug_runtime_disable_movement_object_draw_depth_map) {
+ return;
+ }
+
+ if(visible){
+ bool culled = false;
+ for(int plane=0; plane<num_cull_planes; ++plane){
+ if( dot(position, cull_planes[plane].xyz()) +
+ cull_planes[plane][3] + kCullRadius <= 0.0f )
+ {
+ culled = true;
+ break;
+ }
+ }
+ if(!culled){
+ // Check if character is occluded from pov of active camera
+ bool occluded = false;
+ for(int state_index=0, state_len=occlusion_states.size();
+ state_index < state_len;
+ ++state_index)
+ {
+ if(occlusion_states[state_index].cam_id == ActiveCameras::GetID()){
+ occluded = occlusion_states[state_index].occluded;
+ break;
+ }
+ }
+
+ if(!occluded){
+ rigged_object_->Draw(proj_view_matrix, RiggedObject::kDrawDepthOnly);
+ }
+ }
+ }
+}
+
+void MovementObject::PreDrawCamera(float curr_game_time) {
+ Online* online = Online::Instance();
+
+ if (g_debug_runtime_disable_movement_object_pre_draw_camera) {
+ return;
+ }
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Angelscript PreDrawCameraNoCull()");
+ ASArglist args;
+ args.Add(curr_game_time);
+ as_context->CallScriptFunction(as_funcs.pre_draw_camera_no_cull, &args);
+ }
+ if(visible && enabled_){
+ Camera* cam = ActiveCameras::Get();
+ if(cam->checkSphereInFrustum(position, kCullRadius) || online->IsActive()){
+ bool occluded = false;
+ if( g_perform_occlusion_query ) {
+ // Check previous occlusion queries to update per-camera occlusion
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Process occlusion queries");
+
+ for(int query_index=0, num_queries=occlusion_queries.size(); query_index<num_queries; ++query_index){
+ OcclusionQuery* query = &occlusion_queries[query_index];
+ if(!query->in_progress){
+ continue;
+ }
+ PROFILER_ZONE(g_profiler_ctx, "Process occlusion query");
+ int count = -1;
+ {
+ PROFILER_ZONE(g_profiler_ctx, "GL_QUERY_RESULT");
+ glGetQueryObjectiv( query->id, GL_QUERY_RESULT_AVAILABLE, &count);
+ if(count == 1){
+ glGetQueryObjectiv( query->id, GL_QUERY_RESULT, &count);
+ } else {
+ count = -1;
+ }
+ }
+ if(count != -1){
+ //LOGI << "Query completed with " << count << "samples visible";
+ query->in_progress = false;
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Update occlusion state");
+ int state_id = GetStateID(occlusion_states, query->cam_id);
+
+ if(count == 0){
+ occlusion_states[state_id].occluded = true;
+ } else {
+ occlusion_states[state_id].occluded = false;
+ }
+ }
+ }
+ }
+ }
+
+ if(distance_squared(position, ActiveCameras::Get()->GetPos()) <= kCullRadius){
+ int state_id = GetStateID(occlusion_states, ActiveCameras::GetID());
+ occlusion_states[state_id].occluded = false;
+ }
+
+ // Check if character is occluded from pov of active camera
+ for(int state_index=0, state_len=occlusion_states.size();
+ state_index < state_len;
+ ++state_index)
+ {
+ if(occlusion_states[state_index].cam_id == ActiveCameras::GetID()){
+ occluded = occlusion_states[state_index].occluded;
+ break;
+ }
+ }
+
+ if(distance_squared(position, cam->GetPos()) <= kCullRadius * kCullRadius){
+ occluded = false;
+ }
+ }
+
+ if(!occluded || online->IsActive()){
+ if(needs_predraw_update){
+ ActualPreDraw(curr_game_time);
+ }
+ rigged_object_->PreDrawCamera(game_timer.GetRenderTime());
+ }
+ }
+ }
+}
+
+
+//Interpolate the network data.
+void MovementObject::ClientBeforeDraw() {
+ Online* online = Online::Instance();
+ if(online->IsClient()) {
+ rigged_object_->ClientBeforeDraw();
+ }
+}
+
+void MovementObject::Draw() {
+ if (g_debug_runtime_disable_movement_object_draw) {
+ return;
+ }
+
+ ClientBeforeDraw();
+
+ if(visible){
+ PROFILER_ZONE(g_profiler_ctx, "MovementObject::Draw");
+ Camera* cam = ActiveCameras::Get();
+ if(cam->checkSphereInFrustum(position, kCullRadius)){
+ // Check if character is occluded from pov of active camera
+ bool occluded = false;
+ if( g_perform_occlusion_query ) {
+ for(int state_index=0, state_len=occlusion_states.size();
+ state_index < state_len;
+ ++state_index)
+ {
+ if(occlusion_states[state_index].cam_id == ActiveCameras::GetID()){
+ occluded = occlusion_states[state_index].occluded;
+ break;
+ }
+ }
+
+ if(distance_squared(position, cam->GetPos()) <= kCullRadius * kCullRadius){
+ occluded = false;
+ } else {
+ glColorMask(false, false, false, false);
+ PROFILER_ZONE(g_profiler_ctx, "Launch occlusion query");
+ int active_cam_id = ActiveCameras::Instance()->GetID();
+ int curr_query_id = -1;
+ for(int i=0, len=occlusion_queries.size(); i<len; ++i){
+ if(occlusion_queries[i].in_progress == false){
+ curr_query_id = i;
+ break;
+ }
+ }
+ if(curr_query_id == -1){
+ curr_query_id = occlusion_queries.size();
+ occlusion_queries.resize(occlusion_queries.size() + 1);
+ GLuint val;
+ glGenQueries( 1, &val );
+ occlusion_queries[curr_query_id].id = val;
+ }
+ occlusion_queries[curr_query_id].cam_id = active_cam_id;
+ occlusion_queries[curr_query_id].in_progress = true;
+
+ //LOGI << "glBeginQuery on " << occlusion_queries[curr_query_id].id << std::endl;
+ glBeginQuery( GL_SAMPLES_PASSED, occlusion_queries[curr_query_id].id );
+
+ GLState gl_state;
+ gl_state.blend = false;
+ gl_state.cull_face = true;
+ gl_state.depth_test = true;
+ gl_state.depth_write = false;
+
+ Graphics::Instance()->setGLState(gl_state);
+
+ int shader_id = Shaders::Instance()->returnProgram(shader);
+ Shaders::Instance()->setProgram(shader_id);
+
+ mat4 char_mat;
+ char_mat.SetScale(vec3(rigged_object_->GetCharScale()));
+ char_mat.SetTranslationPart(position);
+
+ mat4 mvp = cam->GetProjMatrix() * cam->GetViewMatrix() * char_mat;
+
+ Shaders::Instance()->SetUniformMat4("mvp", mvp);
+ int vert_attrib_id = Shaders::Instance()->returnShaderAttrib("vert_attrib", shader_id);
+ Model* probe_model = &Models::Instance()->GetModel(scenegraph_->light_probe_collection.probe_model_id);
+ if(!probe_model->vbo_loaded){
+ probe_model->createVBO();
+ }
+ probe_model->VBO_vertices.Bind();
+ probe_model->VBO_faces.Bind();
+ Graphics::Instance()->EnableVertexAttribArray(vert_attrib_id);
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, 3 * sizeof(GL_FLOAT), 0);
+ Graphics::Instance()->DrawElements(GL_TRIANGLES, probe_model->faces.size(), GL_UNSIGNED_INT, 0);
+ Graphics::Instance()->ResetVertexAttribArrays();
+
+ //LOGI << "glEndQuery on " << occlusion_queries[curr_query_id].id << std::endl;
+ glEndQuery( GL_SAMPLES_PASSED );
+ glColorMask(true, true, true, true);
+ }
+ }
+
+ if(!occluded){
+ rigged_object_->Draw(cam->GetProjMatrix() * cam->GetViewMatrix(), RiggedObject::kFullDraw);
+ }
+ }
+ }
+
+ if(scenegraph_->map_editor->IsTypeEnabled(_movement_object) &&
+ !Graphics::Instance()->media_mode() &&
+ scenegraph_->map_editor->state_ != MapEditor::kInGame)
+ {
+ if(connected_pathpoint_id != -1){
+ Object* obj = scenegraph_->GetObjectFromID(connected_pathpoint_id);
+ if(obj){
+ vec3 pos;
+ if(obj->GetType() == _path_point_object){
+ pos = ((PathPointObject*)obj)->GetTranslation();
+ } else if(obj->GetType() == _movement_object){
+ pos = obj->GetTranslation();
+ }
+ DebugDraw::Instance()->AddLine(GetTranslation(), pos, vec4(vec3(1.0f),0.5f), vec4(0.0f,0.5f,0.5f,0.5f),_delete_on_draw);
+ }
+ }
+ std::list<ItemObjectScriptReader> &ic = item_connections;
+ for(std::list<ItemObjectScriptReader>::iterator iter = ic.begin();
+ iter != ic.end(); ++iter)
+ {
+ ItemObjectScriptReader& item = (*iter);
+ if(item.valid()){
+ DebugDraw::Instance()->AddLine(GetTranslation(),
+ item->GetTranslation(),
+ vec4(0.0f,0.5f,0.5f,0.5f),
+ vec4(0.0f,0.5f,0.5f,0.5f),
+ _delete_on_draw);
+ }
+ }
+ if(is_player) {
+ box_color = vec4(0.0f, 1.0f, 0.0f, 1.0f);
+ } else {
+ box_color = vec4(1.0f, 1.0f, 1.0f, 1.0f);
+ }
+ }
+}
+
+void MovementObject::HandleTransformationOccured() {
+ velocity = vec3(0.0f);
+ position = GetTranslation();
+}
+
+void MovementObject::ASPlaySoundGroupAttached(std::string path, vec3 location) {
+ //SoundGroupRef sgr = SoundGroups::Instance()->ReturnRef(path);
+
+
+ SoundGroupRef sgr = Engine::Instance()->GetAssetManager()->LoadSync<SoundGroup>(path);
+ SoundGroupPlayInfo sgpi(*sgr, location);
+ sgpi.occlusion_position = position;
+ sgpi.created_by_id = GetID();
+
+ unsigned long handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->PlayGroup(handle,sgpi);
+ attached_sounds.push_back(handle);
+}
+
+void MovementObject::ASPlaySoundAttached(std::string path, vec3 location) {
+ SoundPlayInfo spi;
+ spi.path = path;
+ spi.position = location;
+ spi.occlusion_position = position;
+ spi.created_by_id = GetID();
+
+ unsigned long handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->Play(handle,spi);
+ attached_sounds.push_back(handle);
+}
+
+void MovementObject::PlaySoundGroupVoice(std::string path, float delay) {
+ Online* online = Online::Instance();
+
+ if(path.empty()){
+ return;
+ }
+
+ if (online->IsHosting()) {
+ online->Send<OnlineMessages::AudioPlaySoundGroupVoiceMessage>(path, GetID(), delay);
+ }
+ //CharacterRef c_ref = Characters::Instance()->ReturnRef(character_path);
+ CharacterRef c_ref = Engine::Instance()->GetAssetManager()->LoadSync<Character>(character_path);
+ const std::string &voice_path = c_ref->GetVoicePath();
+ if(voice_path.empty()){
+ //printf("No voice file.\n");
+ return;
+ }
+ float pitch = c_ref->GetVoicePitch();
+ //VoiceFileRef v_ref = VoiceFiles::Instance()->ReturnRef(voice_path);
+ VoiceFileRef v_ref = Engine::Instance()->GetAssetManager()->LoadSync<VoiceFile>(voice_path);
+ const std::string voice_file = v_ref->GetVoicePath(path);
+ if(voice_file.empty()){
+ //printf("No voice path for %s.\n",path.c_str());
+ return;
+ }
+ voice_queue.push_back(VoiceQueue(voice_file, delay, pitch));
+}
+
+
+void MovementObject::ForceSoundGroupVoice(std::string path, float delay) {
+ //CharacterRef c_ref = Characters::Instance()->ReturnRef(character_path);
+ CharacterRef c_ref = Engine::Instance()->GetAssetManager()->LoadSync<Character>(character_path);
+ float pitch = c_ref->GetVoicePitch();
+ voice_queue.push_back(VoiceQueue(path, delay, pitch));
+}
+
+std::string MovementObject::GetTeamString()
+{
+ return sp.GetStringVal("Teams");
+}
+
+void MovementObject::StartPoseAnimation(std::string path)
+{
+ ASArglist args;
+ args.AddObject(&path);
+
+ bool val;
+ ASArg return_val;
+ return_val.type = _as_bool;
+ return_val.data = &val;
+ as_context->CallScriptFunction(as_funcs.start_pose, &args, &return_val);
+}
+
+void MovementObject::StopVoice() {
+ voice_queue.clear();
+ Engine::Instance()->GetSound()->Stop(voice_sound);
+}
+
+struct GridCellInfo {
+ bool solid;
+ bool walkable;
+ bool boundary;
+ vec3 avg_collide;
+};
+
+static std::vector<GridCellInfo> grid_cells;
+const int _grid_size = 11;
+const float cell_size = 0.2f;
+
+int GridIndexFromCoord(int x, int y, int z){
+ return z + y*_grid_size + x*_grid_size*_grid_size;
+}
+
+void GridTest(vec3 pos, BulletWorld *bw, float _leg_sphere_size) {
+ if(grid_cells.empty()){
+ grid_cells.resize(_grid_size*_grid_size*_grid_size);
+ }
+ int index=0;
+ for(int i=0; i<_grid_size; ++i){
+ for(int j=0; j<_grid_size; ++j){
+ for(int k=0; k<_grid_size; ++k){
+ vec3 quantized_pos = vec3(floor(pos[0]/cell_size), floor(pos[1]/cell_size), floor(pos[2]/cell_size))*cell_size;
+ quantized_pos[0] += (i - (_grid_size/2)) * cell_size;
+ quantized_pos[1] += (j - (_grid_size/2)) * cell_size;
+ quantized_pos[2] += (k - (_grid_size/2)) * cell_size;
+ ContactSlideCallback cb;
+ cb.single_sided = false;
+ bw->GetSphereCollisions(quantized_pos, _leg_sphere_size, cb);
+ if(cb.collision_info.contacts.size()==0){
+ grid_cells[index].solid = false;
+ } else {
+ grid_cells[index].solid = true;
+ }
+ grid_cells[index].walkable = false;
+ grid_cells[index].boundary = false;
+ ++index;
+ }
+ }
+ }
+
+
+ for(int i=0; i<_grid_size; ++i){
+ for(int j=0; j<_grid_size; ++j){
+ for(int k=0; k<_grid_size; ++k){
+ if(!grid_cells[GridIndexFromCoord(i,j,k)].solid){
+ bool next_to_solid = false;
+ if(i != 0 && grid_cells[GridIndexFromCoord(i-1,j,k)].solid == true){
+ next_to_solid = true;
+ } else if(i != _grid_size-1 && grid_cells[GridIndexFromCoord(i+1,j,k)].solid == true){
+ next_to_solid = true;
+ } else if(k != 0 && grid_cells[GridIndexFromCoord(i,j,k-1)].solid == true){
+ next_to_solid = true;
+ } else if(k != _grid_size-1 && grid_cells[GridIndexFromCoord(i,j,k+1)].solid == true){
+ next_to_solid = true;
+ } else if(j != 0 && grid_cells[GridIndexFromCoord(i,j-1,k)].solid == true){
+ next_to_solid = true;
+ } else if(j != _grid_size-1 && grid_cells[GridIndexFromCoord(i,j+1,k)].solid == true){
+ next_to_solid = true;
+ }
+ if(next_to_solid){
+ grid_cells[GridIndexFromCoord(i,j,k)].boundary = true;
+ vec3 quantized_pos = vec3(floor(pos[0]/cell_size), floor(pos[1]/cell_size), floor(pos[2]/cell_size))*cell_size;
+ quantized_pos[0] += (i - (_grid_size/2)) * cell_size;
+ quantized_pos[1] += (j - (_grid_size/2)) * cell_size;
+ quantized_pos[2] += (k - (_grid_size/2)) * cell_size;
+ DebugDraw::Instance()->AddWireSphere(quantized_pos, 0.1f, vec4(vec3(0.0f,1.0f,0.0f),0.2f), _delete_on_update);
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ for(int i=0; i<_grid_size; ++i){
+ for(int j=1; j<_grid_size; ++j){
+ for(int k=0; k<_grid_size; ++k){
+ if(grid_cells[GridIndexFromCoord(i,j-1,k)].solid == true && grid_cells[GridIndexFromCoord(i,j,k)].solid == false){
+ vec3 quantized_pos = vec3(int(pos[0]/cell_size), int(pos[1]/cell_size), int(pos[2]/cell_size))*cell_size;
+ quantized_pos[0] += (i - (_grid_size/2)) * cell_size;
+ quantized_pos[1] += (j - (_grid_size/2)) * cell_size;
+ quantized_pos[2] += (k - (_grid_size/2)) * cell_size;
+
+ // Check slope of ground
+ vec3 upper_pos = quantized_pos+vec3(0,0.1f,0);
+ vec3 lower_pos = quantized_pos+vec3(0,-0.2f,0);
+ SweptSlideCallback cb;
+ cb.m_collisionFilterMask = btBroadphaseProxy::StaticFilter;
+ bw->GetSweptSphereCollisions(upper_pos, lower_pos, _leg_sphere_size, cb);
+ vec3 sphere_pos = mix(upper_pos,lower_pos,cb.true_closest_hit_fraction);
+
+ ContactSlideCallback cb2;
+ cb2.m_collisionFilterMask = btBroadphaseProxy::StaticFilter;
+ cb2.single_sided = false;
+ bw->GetSphereCollisions(sphere_pos, _leg_sphere_size * 1.1f, cb2);
+ vec3 adjusted_position = bw->ApplySphereSlide(sphere_pos, _leg_sphere_size * 1.1f, cb2.collision_info);
+
+ //quantized_pos = sphere_col.position;
+ vec3 norm = normalize(adjusted_position - sphere_pos);
+ grid_cells[GridIndexFromCoord(i,j,k)].avg_collide = vec3(0.0f);
+ for(int l=0, len=cb2.collision_info.points.size(); l<len; ++l){
+ grid_cells[GridIndexFromCoord(i,j,k)].avg_collide += ToVec3(cb2.collision_info.points[l]);
+ }
+ if(cb2.collision_info.points.size() > 0){
+ grid_cells[GridIndexFromCoord(i,j,k)].avg_collide /= (float)cb2.collision_info.points.size();
+ }
+ norm = normalize(norm);
+ if(norm[1] > 0.7f){
+ grid_cells[GridIndexFromCoord(i,j,k)].walkable = true;
+ //DebugDraw::Instance()->AddWireSphere(quantized_pos, 0.1f, vec3(1.0f), _delete_on_update);
+ //DebugDraw::Instance()->AddLine(quantized_pos, quantized_pos + norm, vec3(1.0f,0.0f,0.0f), _delete_on_update);
+ } else {
+ if(cb.true_closest_hit_fraction != 1.0f){
+ //DebugDraw::Instance()->AddWireSphere(sphere_pos, _leg_sphere_size * 1.1f, vec3(0.0f,1.0f,0.0f), _delete_on_update);
+ }
+ //DebugDraw::Instance()->AddWireSphere(quantized_pos, 0.1f, vec3(1.0f,1.0f,0.0f), _delete_on_update);
+ //DebugDraw::Instance()->AddLine(quantized_pos, quantized_pos + norm, vec3(1.0f,0.0f,0.0f), _delete_on_update);
+ }
+ }
+ }
+ }
+ }
+
+ for(int i=1; i<_grid_size-1; ++i){
+ for(int j=2; j<_grid_size-2; ++j){
+ for(int k=1; k<_grid_size-1; ++k){
+ if(grid_cells[GridIndexFromCoord(i,j,k)].walkable == true){
+ bool next_to_nonwalkable = false;
+ {
+ bool dir = true;
+ for(int l=j-2; l<=j+2; ++l){
+ if(grid_cells[GridIndexFromCoord(i-1,l,k)].walkable){
+ dir = false;
+ }
+ }
+ if(dir){
+ next_to_nonwalkable = true;
+ }
+ }
+ {
+ bool dir = true;
+ for(int l=j-2; l<=j+2; ++l){
+ if(grid_cells[GridIndexFromCoord(i+1,l,k)].walkable){
+ dir = false;
+ }
+ }
+ if(dir){
+ next_to_nonwalkable = true;
+ }
+ }
+ {
+ bool dir = true;
+ for(int l=j-2; l<=j+2; ++l){
+ if(grid_cells[GridIndexFromCoord(i,l,k-1)].walkable){
+ dir = false;
+ }
+ }
+ if(dir){
+ next_to_nonwalkable = true;
+ }
+ }
+ {
+ bool dir = true;
+ for(int l=j-2; l<=j+2; ++l){
+ if(grid_cells[GridIndexFromCoord(i,l,k+1)].walkable){
+ dir = false;
+ }
+ }
+ if(dir){
+ next_to_nonwalkable = true;
+ }
+ }
+
+ vec3 quantized_pos = vec3(int(pos[0]/cell_size), int(pos[1]/cell_size), int(pos[2]/cell_size))*cell_size;
+ quantized_pos[0] += (i - (_grid_size/2)) * cell_size;
+ quantized_pos[1] += (j - (_grid_size/2)) * cell_size;
+ quantized_pos[2] += (k - (_grid_size/2)) * cell_size;
+ if(next_to_nonwalkable){
+ DebugDraw::Instance()->AddWireSphere(grid_cells[GridIndexFromCoord(i,j,k)].avg_collide, 0.01f, vec3(1.0f), _delete_on_update);
+ //DebugDraw::Instance()->AddWireSphere(quantized_pos, 0.1f, vec3(1.0f), _delete_on_update);
+ } else {
+ //DebugDraw::Instance()->AddWireSphere(quantized_pos, 0.1f, vec4(vec3(1.0f),0.2f), _delete_on_update);
+ }
+ } else if(grid_cells[GridIndexFromCoord(i,j,k)].boundary) {
+ vec3 quantized_pos = vec3(int(pos[0]/cell_size), int(pos[1]/cell_size), int(pos[2]/cell_size))*cell_size;
+ quantized_pos[0] += (i - (_grid_size/2)) * cell_size;
+ quantized_pos[1] += (j - (_grid_size/2)) * cell_size;
+ quantized_pos[2] += (k - (_grid_size/2)) * cell_size;
+ //DebugDraw::Instance()->AddWireSphere(quantized_pos, 0.1f, vec4(vec3(0.0f,1.0f,0.0f),0.2f), _delete_on_update);
+ }
+ }
+ }
+ }*/
+}
+
+//static btSoftBody* soft_body = NULL;
+//static btRigidBody* soft_body_attach = NULL;
+
+void MovementObject::Moved(Object::MoveType type) {
+ Object::Moved(type);
+ velocity = vec3(0.0f);
+ position = GetTranslation();
+ if(type & Object::kRotate){
+ SetRotationFromEditorTransform();
+ }
+}
+
+void MovementObject::Update(float timestep) {
+ //GridTest(position, scenegraph_->bullet_world_, 0.45f);
+ ThreadedSound* si = Engine::Instance()->GetSound();
+ Online* online = Online::Instance();
+
+ while (message_queue.empty() == false) {
+ ReceiveMessage(message_queue.front());
+ message_queue.pop();
+ }
+
+ std::list<VoiceQueue>::iterator iter;
+ for (iter = voice_queue.begin(); iter != voice_queue.end();) {
+ VoiceQueue& vq = (*iter);
+ vq.delay -= timestep;
+ if (vq.delay <= 0.0f) {
+ si->SetVolume(voice_sound, 0.0f);
+ //si->Stop(voice_sound);
+ //SoundGroupRef sgr = SoundGroups::Instance()->ReturnRef(vq.path);
+ SoundGroupRef sgr = Engine::Instance()->GetAssetManager()->LoadSync<SoundGroup>(vq.path);
+ std::string snd_path = sgr->GetSoundPath();
+ //printf("%s\n",snd_path.c_str());
+ std::string lpsnc_path = snd_path.substr(0, snd_path.size() - 4) + ".txt";
+ //printf("%s\n",lpsnc_path.c_str());
+ if (FileExists(lpsnc_path.c_str(), kDataPaths | kModPaths)) {
+ //LipSyncFileRef lsf_ref = LipSyncFiles::Instance()->ReturnRef(lpsnc_path.c_str());
+ LipSyncFileRef lsf_ref = Engine::Instance()->GetAssetManager()->LoadSync<LipSyncFile>(lpsnc_path.c_str());
+ rigged_object_->lipsync_reader.AttachTo(lsf_ref);
+ }
+ SoundGroupPlayInfo sgpi(*sgr, rigged_object_->GetAvgIKChainPos("head"));
+ sgpi.specific_path = snd_path;
+ sgpi.created_by_id = GetID();
+ sgpi.pitch_shift = vq.pitch;
+ sgpi.priority = _sound_priority_high;
+ if (controlled) {
+ sgpi.priority = _sound_priority_very_high;
+ }
+ voice_sound = si->CreateHandle(__FUNCTION__);
+ si->PlayGroup(voice_sound, sgpi);
+ attached_sounds.push_back(voice_sound);
+ iter = voice_queue.erase(iter);
+ }
+ else {
+ ++iter;
+ }
+ }
+
+ for (int i = (int)attached_sounds.size() - 1; i >= 0; --i) {
+ if (!si->IsHandleValid(attached_sounds[i])) {
+ attached_sounds.erase(attached_sounds.begin() + i);
+ }
+ }
+ if (!rigged_object_->animated) {
+ velocity = rigged_object_->GetAvgVelocity();
+ }
+ for (unsigned i = 0; i < attached_sounds.size(); ++i) {
+ si->SetOcclusionPosition(attached_sounds[i], position);
+ si->TranslatePosition(attached_sounds[i], velocity * timestep);
+ //DebugDraw::Instance()->AddWireSphere(si->GetPosition(attached_sounds[i]), 1.0f, vec4(1.0f), _delete_on_update);
+ }
+
+ if (controlled && (Engine::Instance()->GetSplitScreen() || controller_id == 0)) {
+ ActiveCameras::Set(controller_id);
+ ActiveCameras::Get()->IncrementProgress();
+ }
+
+ --update_script_counter;
+ if (update_script_counter <= 0) {
+ ASArglist args;
+ args.Add(update_script_period);
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Angelscript Update()");
+
+ as_context->CallScriptFunction(as_funcs.update, &args);
+ angle_script_ready = true;
+ }
+
+ update_script_counter += update_script_period;
+ }
+
+
+ //Get data from host, if we are a client and won't generate it locally.
+ if (online->IsClient()) {
+ list<OnlineMessageRef>& frames = incoming_movement_object_frames;
+
+ while(frames.size() > 1) {
+ network_time_interpolator.timestamps.Clear();
+ for(const OnlineMessageRef& frame : frames) {
+ //TODO: Optimized this multiple GetData() call into a single grouped GetDatas() to reduce lock count to one
+ MovementObjectUpdate* frame_data = static_cast<MovementObjectUpdate*>(frame.GetData());
+ network_time_interpolator.timestamps.PushValue(frame_data->timestamp);
+ }
+ int interpolator_code = network_time_interpolator.Update();
+
+ if(interpolator_code == 1) {
+ continue;
+ } else if(interpolator_code == 2) {
+ last_walltime_diff = network_time_interpolator.timestamps.from_begin(1) - network_time_interpolator.timestamps.from_begin(0);
+ frames.pop_front();
+ continue;
+ } else if(interpolator_code == 3) {
+ break;
+ }
+
+ MovementObjectUpdate* current_frame = static_cast<MovementObjectUpdate*>(frames.begin()->GetData());
+ MovementObjectUpdate* next_frame = static_cast<MovementObjectUpdate*>(std::next(frames.begin())->GetData());
+
+ float interpolation_step = network_time_interpolator.interpolation_step;
+
+ if(disable_network_bone_interpolation) {
+ interpolation_step = 0.0f;
+ }
+
+ position = lerp(current_frame->position, next_frame->position, interpolation_step);
+ velocity = lerp(current_frame->velocity, next_frame->velocity, interpolation_step);
+ facing = lerp(current_frame->facing, next_frame->facing, interpolation_step);
+
+
+ size_t bone_count = rigged_object_->skeleton_.physics_bones.size();
+ rigged_object_->network_display_bone_matrices.resize(bone_count);
+ for(size_t i = 0; i < bone_count; i++) {
+ rigged_object_->network_display_bone_matrices[i] = RiggedObject::InterpolateBetweenTwoBones(current_frame->rigged_body_frame.bones[i], next_frame->rigged_body_frame.bones[i], interpolation_step);
+ }
+
+ break;
+ }
+
+ for (auto& it : angelscript_update) {
+ as_context->CallMPCallBack(it.first, it.second);
+ }
+ angelscript_update.clear();
+ }
+
+ if (as_context->HasFunction(as_funcs.update_multiplayer)) {
+ ASArglist args;
+ args.Add(update_script_period);
+ as_context->CallScriptFunction(as_funcs.update_multiplayer, &args);
+ }
+
+ if (online->IsClient()) {
+ if(incoming_cut_lines.size() > 0) {
+ CutLine* cutline = static_cast<CutLine*>(incoming_cut_lines.begin()->GetData());
+
+ rigged_object_->MPCutPlane(cutline->normal, cutline->pos, cutline->dir, cutline->type, cutline->depth, cutline->hit_list, cutline->points);
+
+ rigged_object()->blood_surface.Update(scenegraph_, timestep);
+
+ incoming_cut_lines.pop_front();
+ }
+
+ //TODO: Fix listener update in host.
+ //This should be controlled in angelscript, same way as on the host side, to ensure that the level of control is equivalent.
+ //This cannot be tied to the character, not only is it wrong, as by default the audio listener is usually on the camera position.
+ //But the support of a custom listener location has likely been used in third party mods. /Max
+ //ODOT
+ if (controlled && !remote) {
+ Engine::Instance()->GetSound()->updateListener(position, velocity, facing, vec3(0.0, 1.0, 0.0));
+ }
+
+ if(incoming_material_sound_events.size() > 0) {
+ MaterialSoundEvent* mse = static_cast<MaterialSoundEvent*>(incoming_material_sound_events.begin()->GetData());
+ HandleMaterialEvent(mse->event_name, mse->pos, mse->gain);
+ incoming_material_sound_events.pop_front();
+ }
+
+ if (game_timer.game_time < rigged_object_->blood_surface.sleep_time) {
+ rigged_object_->blood_surface.Update(Engine::Instance()->GetSceneGraph(), timestep);
+ }
+
+ } else {
+ ActiveCameras::Set(0);
+
+ if (rigged_object_->animated) {
+ position += velocity * timestep;
+ }
+ else {
+ position = rigged_object_->GetAvgPosition();
+ velocity = rigged_object_->GetAvgVelocity();
+ }
+
+
+ //if(controlled){
+ // ApplyCameraControls();
+ //}
+ vec3 ground_pos = position - vec3(0.0f, _leg_sphere_size + rigged_object_->floor_height, 0.0f);
+ rigged_object_->SetTranslation(ground_pos);
+ rigged_object_->static_char = static_char;
+ rigged_object_->Update(timestep);
+
+ position += rigged_object_->FetchCenterOffset();
+ float rot = rigged_object_->FetchRotation();
+ if (rot) {
+ mat4 rot_mat;
+ rot_mat.SetRotationY(rot);
+ rigged_object_->anim_client.SetRotationMatrix(
+ rot_mat * rigged_object_->anim_client.GetRotationMatrix());
+ }
+
+ char_sphere->SetPosition(position);
+ char_sphere->body->setInterpolationWorldTransform(char_sphere->body->getWorldTransform());
+ char_sphere->UpdateTransform();
+ char_sphere->Activate();
+ /*DebugDraw::Instance()->AddWireSphere(char_sphere->GetPosition(), _leg_sphere_size, vec3(1.0), _delete_on_update);
+ btVector3 min_val, max_val;
+ {
+ char_sphere->body->getAabb(min_val, max_val);
+ vec3 a(min_val[0], min_val[1], min_val[2]);
+ vec3 b(max_val[0], max_val[1], max_val[2]);
+ DebugDraw::Instance()->AddWireBox((b+a)*0.5, b-a, vec3(1.0), _delete_on_update);
+ }
+ {
+ min_val = char_sphere->body->getBroadphaseProxy()->m_aabbMin;
+ max_val = char_sphere->body->getBroadphaseProxy()->m_aabbMax;
+ vec3 a(min_val[0], min_val[1], min_val[2]);
+ vec3 b(max_val[0], max_val[1], max_val[2]);
+ DebugDraw::Instance()->AddWireBox((b+a)*0.5, b-a, vec3(1.0), _delete_on_update);
+ }*/
+
+ /*if(Input::Instance()->getKeyboard().wasKeyPressed(SDLK_h)){
+ soft_body = scenegraph_->bullet_world_->AddCloth(position);
+ soft_body_attach = new btRigidBody(0.0f, NULL, NULL);
+ btTransform transform;
+ transform.setIdentity();
+ transform.setOrigin(btVector3(position[0], position[1], position[2]));
+ soft_body_attach->setWorldTransform(transform);
+ for(int i=0; i<31; ++i){
+ soft_body->appendAnchor(i, soft_body_attach, btVector3(0+i*1.6/32.0f,0,0), false, 1.0f);
+ }
+ }
+
+ if(soft_body){
+ int num_nodes = soft_body->m_nodes.size();
+ for(int i=0;i<num_nodes;++i) {
+ const btVector3 &point = soft_body->m_nodes[i].m_x;
+ DebugDraw::Instance()->AddWireSphere(vec3(point[0], point[1], point[2]), 0.01f, vec4(1.0f), _delete_on_update);
+ }
+ btTransform transform;
+ transform.setIdentity();
+ transform.setOrigin(btVector3(position[0], position[1]+0.5f, position[2]));
+ soft_body_attach->setWorldTransform(transform);
+ }*/
+
+
+
+
+ if (was_controlled != controlled && scenegraph_) {
+ std::vector<Object*>::iterator hotit;
+ for (hotit = scenegraph_->hotspots_.begin();
+ hotit != scenegraph_->hotspots_.end();
+ hotit++)
+ {
+ Hotspot* hs = static_cast<Hotspot*>(*hotit);
+ if (controlled) {
+ hs->HandleEvent("engaged_player_control", this);
+ }
+ else {
+ hs->HandleEvent("disengaged_player_control", this);
+ }
+ }
+ was_controlled = controlled;
+ }
+
+ /*
+ if (9 == GetID()) {
+ static float y = 0.f;
+ y += 0.002f * timestep; // timestep
+ position.y() = y;
+ }
+ */
+ }
+
+ return;
+}
+
+mat4 RotationFromVectors(const vec3 &front,
+ const vec3 &right,
+ const vec3 &up)
+{
+ mat4 the_matrix;
+ the_matrix.SetColumn(0,right);
+ the_matrix.SetColumn(1,up);
+ the_matrix.SetColumn(2,front);
+ return the_matrix;
+}
+
+void MovementObject::SetRotationFromFacing(vec3 facing) {
+ if(length_squared(facing)<0.00001f){
+ return;
+ }
+ vec3 front = normalize(facing);
+ vec3 straight_up = vec3(0.0f,1.0f,0.0f);
+ vec3 right = normalize(cross(straight_up,facing));
+ vec3 up = normalize(cross(front,right));
+
+ mat4 rotation = RotationFromVectors(front, right, up);
+ if(rigged_object_.get()){
+ rigged_object_->anim_client.SetRotationMatrix(rotation);
+ }
+}
+
+void MovementObject::GetDisplayName(char* buf, int buf_size) {
+ if( GetName().empty() ) {
+ FormatString(buf, buf_size, "%d: Character: %s", GetID(), obj_file.c_str());
+ } else {
+ FormatString(buf, buf_size, "%s: Character: %s", GetName().c_str(), obj_file.c_str());
+ }
+}
+
+void MovementObject::RemapReferences(std::map<int,int> id_map) {
+ connection_finalization_remap = id_map;
+ do_connection_finalization_remap = true;
+}
+
+vec3 MovementObject::GetFacing() {
+ return rigged_object_->anim_client.GetRotationMatrix()
+ * vec3(0.0f,0.0f,1.0f);
+}
+
+void MovementObject::Ragdoll() {
+ if(!rigged_object_->animated){
+ return;
+ }
+ rigged_object_->Ragdoll(velocity);
+}
+
+void MovementObject::ApplyForce(vec3 force) {
+ ASArglist args;
+ args.AddObject(&force);
+ as_context->CallScriptFunction(as_funcs.force_applied, &args);
+}
+
+void MovementObject::UnRagdoll(){
+ position = rigged_object_->GetAvgPosition();
+ velocity = rigged_object_->GetAvgVelocity();
+ rigged_object_->UnRagdoll();
+}
+
+void MovementObject::SetAnimation(std::string path, float fade_speed, char flags){
+ rigged_object_->anim_client.SetAnimation(path, fade_speed, flags);
+ rigged_object_->SetCharAnim("");
+}
+
+void MovementObject::SetAnimation(std::string path, float fade_speed){
+ rigged_object_->anim_client.SetAnimation(path, fade_speed);
+ rigged_object_->SetCharAnim("");
+}
+
+void MovementObject::SetAnimation(std::string path){
+ rigged_object_->anim_client.SetAnimation(path);
+ rigged_object_->SetCharAnim("");
+}
+
+void MovementObject::OverrideCharAnim(const std::string &label, const std::string &new_path){
+ character_script_getter.OverrideCharAnim(label, new_path);
+}
+
+void MovementObject::SwapAnimation(std::string path){
+ rigged_object_->anim_client.SwapAnimation(path);
+ rigged_object_->SetCharAnim("");
+}
+
+void MovementObject::SetCharAnimation(const std::string &path, float fade_speed, char flags){
+ char extra_flags = character_script_getter.GetAnimFlags(path);
+ if(extra_flags & _ANM_MIRRORED){
+ flags = flags ^ _ANM_MIRRORED;
+ }
+ rigged_object_->anim_client.SetAnimation(character_script_getter.GetAnimPath(path), fade_speed, flags);
+ rigged_object_->SetCharAnim(path, flags);
+}
+
+void MovementObject::ASSetCharAnimation(std::string path, float fade_speed, char flags){
+ if (Online::Instance()->IsClient())
+ return;
+
+ SetCharAnimation(path, fade_speed, flags);
+}
+
+void MovementObject::SetAnimAndCharAnim(std::string path, float fade_speed, char flags, std::string anim_path){
+ rigged_object_->anim_client.SetAnimation(path, fade_speed, flags);
+ rigged_object_->SetCharAnim(anim_path, flags, path);
+}
+
+
+void MovementObject::ASSetCharAnimation(std::string path, float fade_speed){
+ SetCharAnimation(path, fade_speed);
+}
+
+void MovementObject::ASSetCharAnimation(std::string path){
+ SetCharAnimation(path);
+}
+
+vec4 MovementObject::GetAvgRotationVec4() {
+ quaternion quat = rigged_object_->GetAvgRotation();
+ return vec4(quat.entries[0], quat.entries[1], quat.entries[2], quat.entries[3]);
+}
+
+float MovementObject::GetTempHealth(){
+ ASArglist args;
+ float val;
+ ASArg return_val;
+ return_val.type = _as_float;
+ return_val.data = &val;
+ as_context->CallScriptFunction(as_funcs.get_temp_health, &args, &return_val);
+ return val;
+}
+
+void MovementObject::addAngelScriptUpdate(uint32_t state, std::vector<uint32_t> data) {
+ std::pair<uint32_t, std::vector<uint32_t>> val(state, data);
+ angelscript_update.push_back(val);
+}
+
+bool MovementObject::HasFunction(const std::string& function_definition) {
+ bool result = false;
+ result = as_context->HasFunction(function_definition);
+ return result;
+}
+
+int MovementObject::QueryIntFunction(std::string func){
+ ASArglist args;
+ int val;
+ ASArg return_val;
+ return_val.type = _as_int;
+ return_val.data = &val;
+ as_context->CallScriptFunction(func, &args, &return_val);
+ return val;
+}
+
+void MovementObject::HandleMaterialEvent(std::string the_event, vec3 event_pos, float gain) {
+ Online* online = Online::Instance();
+
+ if(event_pos != event_pos){
+ return;
+ }
+
+ if (online->IsHosting()) {
+ online->Send<OnlineMessages::MaterialSoundEvent>(GetID(), the_event, event_pos, gain);
+ }
+
+
+ const MaterialEvent* me = scenegraph_->GetMaterialEvent(the_event, event_pos, character_script_getter.GetSoundMod());
+ if(me && !me->soundgroup.empty()){
+ //SoundGroupRef sgr = SoundGroups::Instance()->ReturnRef(me->soundgroup);
+ SoundGroupRef sgr = Engine::Instance()->GetAssetManager()->LoadSync<SoundGroup>(me->soundgroup);
+ touched_surface_refs.insert(sgr);
+ SoundGroupPlayInfo sgpi(*sgr, event_pos);
+ sgpi.gain = gain;
+ sgpi.occlusion_position = position;
+ if(me->max_distance != 0.0f){
+ sgpi.max_distance = me->max_distance * gain;
+ }
+ sgpi.created_by_id = GetID();
+ if(controlled){
+ sgpi.priority = _sound_priority_very_high;
+ }
+ unsigned long sound_handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->PlayGroup(sound_handle, sgpi);
+ if(sound_handle != 0 && me->attached){
+ attached_sounds.push_back(sound_handle);
+ }
+ }
+
+ const std::string clothing_path = character_script_getter.GetClothingPath();
+ if(!clothing_path.empty()){
+ //MaterialRef clothing = Materials::Instance()->ReturnRef(clothing_path);
+ MaterialRef clothing = Engine::Instance()->GetAssetManager()->LoadSync<Material>(clothing_path);
+ clothing_refs.insert(clothing);
+ const MaterialEvent* me = &clothing->GetEvent(the_event);
+ if(me && !me->soundgroup.empty()){
+ //SoundGroupRef sgr = SoundGroups::Instance()->ReturnRef(me->soundgroup);
+ SoundGroupRef sgr = Engine::Instance()->GetAssetManager()->LoadSync<SoundGroup>(me->soundgroup);
+ touched_surface_refs.insert(sgr);
+ SoundGroupPlayInfo sgpi(*sgr, event_pos);
+ sgpi.gain = gain;
+ sgpi.created_by_id = GetID();
+ sgpi.occlusion_position = position;
+ if(me->max_distance != 0.0f){
+ sgpi.max_distance = me->max_distance * gain;
+ }
+ if(controlled){
+ sgpi.priority = _sound_priority_very_high;
+ }
+ unsigned long sound_handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->PlayGroup(sound_handle, sgpi);
+ if(sound_handle != 0 && me->attached){
+ attached_sounds.push_back(sound_handle);
+ }
+ }
+ }
+}
+
+void MovementObject::HandleMaterialEventDefault(std::string the_event, vec3 event_pos) {
+ HandleMaterialEvent(the_event, event_pos);
+}
+
+void MovementObject::MaterialParticleAtBone(std::string type, std::string bone_name) {
+ int bone = rigged_object_->skeleton_.simple_ik_bones[bone_name].bone_id;
+ vec3 pos = rigged_object_->GetBonePosition(bone);
+ vec3 vel = rigged_object_->GetBoneLinearVel(bone);
+
+ const MaterialParticle *mp = scenegraph_->GetMaterialParticle(type, pos);
+ if(!mp || mp->particle_path.empty()){
+ return;
+ }
+ pos += vec3(0.0f,-0.1f,0.0f);
+ vel = vec3(0.0f,1.0f*min(1.0f,length(vel)),0.0f);
+ vec3 tint =scenegraph_->GetColorAtPoint(pos);
+ scenegraph_->particle_system->MakeParticle(
+ scenegraph_, mp->particle_path, pos, vel, tint);
+}
+
+static void asMaterialParticle(MovementObject* mo, const std::string& type, const vec3 &pos, const vec3 &vel) {
+ const MaterialParticle *mp = mo->scenegraph_->GetMaterialParticle(type, pos);
+ if(!mp || mp->particle_path.empty()){
+ return;
+ }
+ vec3 particle_pos = pos;
+ vec3 tint = mo->scenegraph_->GetColorAtPoint(pos);
+ mo->scenegraph_->particle_system->MakeParticle(
+ mo->scenegraph_, mp->particle_path, particle_pos, vel, tint);
+}
+
+int MovementObject::ASWasHit(std::string type, std::string attack_path, vec3 dir, vec3 pos, int attacker_id, float attack_damage_mult, float attack_knockback_mult){
+ ASArglist args;
+ args.AddObject(&type);
+ args.AddObject(&attack_path);
+ args.AddObject(&dir);
+ args.AddObject(&pos);
+ args.Add(attacker_id);
+ args.Add(attack_damage_mult);
+ args.Add(attack_knockback_mult);
+ int val;
+ ASArg return_val;
+ return_val.type = _as_int;
+ return_val.data = &val;
+ as_context->CallScriptFunction(as_funcs.was_hit, &args, &return_val);
+ return val;
+}
+
+void MovementObject::ASWasBlocked(){
+ as_context->CallScriptFunction(as_funcs.was_blocked);
+}
+
+void MovementObject::CreateRiggedObject(){
+ OGPalette old_palette = palette;
+ //for(int i=0; i<100; ++i){
+ rigged_object_.reset(new RiggedObject());
+ rigged_object_->char_id = GetID();
+ palette.clear();
+ rigged_object_->SetCharacterScriptGetter(character_script_getter);
+ if(GetScriptParams()->HasParam("Character Scale")){
+ rigged_object_->SetCharScale(GetScriptParams()->ASGetFloat("Character Scale"));
+ } else {
+ rigged_object_->SetCharScale(1.0f);
+ }
+ {
+ PROFILER_ZONE(g_profiler_ctx, "rigged_object_->Load");
+ rigged_object_->Load(character_path,
+ position,
+ scenegraph_,
+ palette);
+
+ }
+ //}
+ for(unsigned i=0; i<palette.size(); ++i){
+ for(unsigned j=0; j<old_palette.size(); ++j){
+ if(palette[i].label == old_palette[j].label){
+ palette[i].color = old_palette[j].color;
+ }
+ }
+ }
+ rigged_object_->SetSkeletonOwner(this);
+ rigged_object_->Initialize();
+ rigged_object_->animated = true;
+ rigged_object_->SetASContext(as_context.get());
+ rigged_object_->ApplyPalette(palette);
+ if(!env_object_attach_data.empty()){
+ Deserialize(env_object_attach_data, rigged_object_->children);
+ }
+}
+
+void MovementObject::RecreateRiggedObject(std::string _char_path){
+ character_path = _char_path;
+ CreateRiggedObject();
+ update_script_period = (current_control_script_path==scenegraph_->level->GetPCScript(this))?1:4;
+ update_script_counter = rand()%update_script_period-update_script_period;
+ rigged_object_->SetAnimUpdatePeriod(max(2,update_script_period));
+ SetRotationFromEditorTransform();
+}
+
+int MovementObject::GetWaypointTarget() {
+ return connected_pathpoint_id;
+}
+
+void MovementObject::Reset(){
+ as_context->CallScriptFunction(as_funcs.reset);
+ position = GetTranslation();
+ char_sphere->SetPosition(position);
+ char_sphere->body->setInterpolationWorldTransform(char_sphere->body->getWorldTransform());
+ char_sphere->UpdateTransform();
+ SetRotationFromEditorTransform();
+ velocity = vec3(0.0f);
+ no_grab = 0;
+ as_context->ResetGlobals();
+ as_context->CallScriptFunction(as_funcs.set_parameters);
+ as_context->CallScriptFunction(as_funcs.post_reset);
+ for(std::list<ItemObjectScriptReader>::iterator iter = item_connections.begin();
+ iter != item_connections.end(); ++iter)
+ {
+ ItemObjectScriptReader &item_connection = (*iter);
+ if(item_connection.attachment_type != _at_unspecified){
+ int which = item_connection->GetID();
+ AttachmentType type = item_connection.attachment_type;
+ bool mirrored = item_connection.attachment_mirror;
+
+ AttachItemToSlotAttachmentRef(which, type, mirrored, &item_connection.attachment_ref);
+ ASArglist args;
+ args.Add(which);
+ args.Add(type);
+ args.Add(mirrored);
+ as_context->CallScriptFunction(as_funcs.handle_editor_attachment, &args);
+ } else {
+ ASArglist args;
+ args.Add(item_connection->GetID());
+ switch(item_connection->item_ref()->GetType()){
+ case _weapon:
+ as_context->CallScriptFunction(as_funcs.attach_weapon, &args);
+ break;
+ case _misc:
+ as_context->CallScriptFunction(as_funcs.attach_misc, &args);
+ break;
+ default:
+ DisplayError("Error", "Resetting MovementObject with Item of unknown type");
+ break;
+ }
+ }
+ }
+
+ RegisterMPCallbacks();
+}
+
+bool MovementObject::ASOnSameTeam(MovementObject* other) {
+ if( other ) {
+ CSLIterator iter(sp.GetStringVal("Teams"));
+ std::string team;
+
+ while(iter.GetNext(&team)){
+ if(other->OnTeam(team)){
+ return true;
+ }
+ }
+ } else {
+ LOGE << "Received null pointer to movement object" << std::endl;
+ }
+ return false;
+}
+
+bool MovementObject::OnTeam(const std::string &other_team){
+ CSLIterator iter(sp.GetStringVal("Teams"));
+ std::string team;
+
+ while(iter.GetNext(&team)){
+ if(team == other_team){
+ return true;
+ }
+ }
+ return false;
+}
+
+static void FixDiscontinuity(MovementObject* mo) {
+ mo->rigged_object()->static_char = mo->static_char;
+ mo->rigged_object()->FixDiscontinuity();
+}
+
+static std::string ASGetCurrentControlScript(MovementObject* mo) {
+ return mo->GetCurrentControlScript();
+}
+
+static std::string AsGetActorScript(MovementObject* mo) {
+ return mo->GetActorScript();
+}
+
+static std::string AsGetNPCObjectScript(MovementObject* mo) {
+ return mo->GetNPCObjectScript();
+}
+
+static std::string ASGetPCObjectScript(MovementObject* mo) {
+ return mo->GetPCObjectScript();
+}
+
+static std::string ASGetMovementObjectNPCScript(MovementObject* mo) {
+ return mo->scenegraph_->level->GetNPCScript(mo);
+}
+
+static std::string ASGetMovementObjectPCScript(MovementObject* mo) {
+ return mo->scenegraph_->level->GetPCScript(mo);
+}
+
+void DefineMovementObjectTypePublic(ASContext* as_context) {
+ as_context->RegisterGlobalProperty("const int _awake", (void*)&MovementObject::_awake);
+ as_context->RegisterGlobalProperty("const int _unconscious", (void*)&MovementObject::_unconscious);
+ as_context->RegisterGlobalProperty("const int _dead", (void*)&MovementObject::_dead);
+
+ as_context->RegisterObjectType("MovementObject", 0, asOBJ_REF | asOBJ_NOCOUNT);
+ as_context->RegisterObjectProperty("MovementObject","vec3 position",asOFFSET(MovementObject,position));
+ as_context->RegisterObjectProperty("MovementObject","vec3 velocity",asOFFSET(MovementObject,velocity));
+ as_context->RegisterObjectProperty("MovementObject","bool visible",asOFFSET(MovementObject,visible));
+ as_context->RegisterObjectProperty("MovementObject","int no_grab",asOFFSET(MovementObject,no_grab));
+ as_context->RegisterObjectProperty("MovementObject","string char_path",asOFFSET(MovementObject,character_path));
+ as_context->RegisterObjectProperty("MovementObject","bool controlled",asOFFSET(MovementObject,controlled));
+ as_context->RegisterObjectProperty("MovementObject", "bool remote", asOFFSET(MovementObject, remote));
+ as_context->RegisterObjectProperty("MovementObject","bool is_player",asOFFSET(MovementObject,is_player));
+ as_context->RegisterObjectProperty("MovementObject","bool static_char", asOFFSET(MovementObject,static_char));
+ as_context->RegisterObjectProperty("MovementObject","int controller_id",asOFFSET(MovementObject,controller_id));
+ as_context->RegisterObjectProperty("MovementObject","int update_script_counter",asOFFSET(MovementObject,update_script_counter));
+ as_context->RegisterObjectProperty("MovementObject","int update_script_period",asOFFSET(MovementObject,update_script_period));
+ as_context->RegisterObjectProperty("MovementObject","bool focused_character",asOFFSET(MovementObject,focused_character));
+
+ as_context->RegisterObjectMethod("MovementObject", "string GetTeamString()", asMETHOD(MovementObject, GetTeamString), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","RiggedObject@ rigged_object()",asMETHOD(MovementObject, rigged_object), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void SetScriptUpdatePeriod(int steps)",asMETHOD(MovementObject, ASSetScriptUpdatePeriod), asCALL_THISCALL, "Script update() is called every X engine timesteps");
+ as_context->RegisterObjectMethod("MovementObject", "bool HasFunction(const string &in function_definition)", asMETHOD(MovementObject,HasFunction), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject", "int QueryIntFunction(string function)", asMETHOD(MovementObject,QueryIntFunction), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject", "void Execute(string code)", asMETHOD(MovementObject,Execute), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject", "bool HasVar(string var_name)", asMETHOD(MovementObject,ASHasVar), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject", "int GetIntVar(string var_name)", asMETHOD(MovementObject,ASGetIntVar), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject", "int GetArrayIntVar(const string &in var_name, int index)", asMETHOD(MovementObject,ASGetArrayIntVar), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject", "float GetFloatVar(string var_name)", asMETHOD(MovementObject,ASGetFloatVar), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject", "bool GetBoolVar(string var_name)", asMETHOD(MovementObject,ASGetBoolVar), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject", "void SetIntVar(string var_name, int value)", asMETHOD(MovementObject,ASSetIntVar), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject", "void SetArrayIntVar(const string &in var_name, int index, int value)", asMETHOD(MovementObject,ASSetArrayIntVar), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject", "void SetFloatVar(string var_name, float value)", asMETHOD(MovementObject,ASSetFloatVar), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject", "void SetBoolVar(string var_name, bool value)", asMETHOD(MovementObject,ASSetBoolVar), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject", "bool OnSameTeam(MovementObject@ other)", asMETHOD(MovementObject,ASOnSameTeam), asCALL_THISCALL);
+ //Keeping ReceiveMessage for compatibility
+ as_context->RegisterObjectMethod("MovementObject", "void ReceiveMessage(const string &in)",asMETHOD(MovementObject, ReceiveScriptMessage), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject", "void ReceiveScriptMessage(const string &in)", asMETHOD(Object, ReceiveScriptMessage), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject", "void QueueScriptMessage(const string &in)", asMETHOD(Object, QueueScriptMessage), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","int GetID()",asMETHOD(MovementObject, GetID), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void SetRotationFromFacing(vec3 facing)",asMETHOD(MovementObject, SetRotationFromFacing), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void FixDiscontinuity()",asFUNCTION(FixDiscontinuity), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("MovementObject", "string GetCurrentControlScript()", asFUNCTION(ASGetCurrentControlScript), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("MovementObject", "string GetActorScript()", asFUNCTION(AsGetActorScript), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("MovementObject", "string GetNPCObjectScript()", asFUNCTION(AsGetNPCObjectScript), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("MovementObject", "string GetPCObjectScript()", asFUNCTION(ASGetPCObjectScript), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("MovementObject", "string GetNPCScript()", asFUNCTION(ASGetMovementObjectNPCScript), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("MovementObject", "string GetPCScript()", asFUNCTION(ASGetMovementObjectPCScript), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("MovementObject", "void ChangeControlScript(const string &path)", asMETHOD(MovementObject,ChangeControlScript), asCALL_THISCALL);
+}
+
+namespace {
+int GetNumBoneChildren(CScriptArray* bone_children_index, int bone){
+ return *(int*)bone_children_index->At(bone+1) - *(int*)bone_children_index->At(bone);
+}
+
+enum ChainPointLabels {kHandPoint, kWristPoint, kElbowPoint, kShoulderPoint, kCollarTipPoint, kCollarPoint, kNumArmPoints};
+enum IKLabel {kLeftArmIK, kRightArmIK, kLeftLegIK, kRightLegIK,
+ kHeadIK, kLeftEarIK, kRightEarIK, kTorsoIK,
+ kTailIK, kNumIK };
+enum IdleType{_stand, _active, _combat};
+
+// Key transform enums
+const int kHeadKey = 0;
+const int kLeftArmKey = 1;
+const int kRightArmKey = 2;
+const int kLeftLegKey = 3;
+const int kRightLegKey = 4;
+const int kChestKey = 5;
+const int kHipKey = 6;
+const int kNumKeys = 7;
+
+enum Species {
+ _rabbit = 0,
+ _wolf = 1,
+ _dog = 2,
+ _rat = 3,
+ _cat = 4
+};
+
+static void CDrawArmsImpl(
+ MovementObject& this_mo,
+ RiggedObject& rigged_object,
+ Skeleton& skeleton,
+ float& breath_amount,
+ float& max_speed,
+ float& true_max_speed,
+ int& idle_type,
+ bool& on_ground,
+ bool& flip_info_flipping,
+ float& threat_amount,
+ bool& ledge_info_on_ledge,
+ CScriptArray* skeleton_bind_transforms,
+ CScriptArray* inv_skeleton_bind_transforms,
+ CScriptArray* ik_chain_start_index,
+ CScriptArray* ik_chain_elements,
+ CScriptArray* ik_chain_length,
+ CScriptArray* key_transforms,
+ CScriptArray* arm_points,
+ CScriptArray* old_arm_points,
+ CScriptArray* temp_old_arm_points,
+ CScriptArray* bone_children,
+ CScriptArray* bone_children_index,
+ const BoneTransform& chest_transform, const BoneTransform& l_hand_transform, const BoneTransform& r_hand_transform, int num_frames) {
+ // Get relative chest transformation
+ int chest_bone = ASIKBoneStart(&skeleton, "torso");
+ BoneTransform chest_frame_matrix = rigged_object.animation_frame_bone_matrices[chest_bone];
+ BoneTransform chest_bind_matrix = *(BoneTransform*)skeleton_bind_transforms->At(chest_bone);
+ BoneTransform rel_mat = chest_transform * invert(chest_frame_matrix * chest_bind_matrix);
+
+ // Get points in arm IK chain transformed by chest
+ float upper_arm_length[2];
+ float upper_arm_weight[2];
+ float lower_arm_length[2];
+ float lower_arm_weight[2];
+ vec3 chain_points[kNumArmPoints * 2];
+
+ for(int right=0; right<2; ++right){
+ int chain_start = *(int*)ik_chain_start_index->At(kLeftArmIK+right);
+ vec3 hand = GetTransformedBonePoint(&rigged_object, *(int*)ik_chain_elements->At(chain_start+0), 1);
+ vec3 wrist = GetTransformedBonePoint(&rigged_object, *(int*)ik_chain_elements->At(chain_start+0), 0);
+ vec3 elbow = GetTransformedBonePoint(&rigged_object, *(int*)ik_chain_elements->At(chain_start+2), 0);
+ vec3 shoulder = GetTransformedBonePoint(&rigged_object, *(int*)ik_chain_elements->At(chain_start+4), 0);
+ vec3 collar_tip = GetTransformedBonePoint(&rigged_object, *(int*)ik_chain_elements->At(chain_start+5), 1);
+ vec3 collar = GetTransformedBonePoint(&rigged_object, *(int*)ik_chain_elements->At(chain_start+5), 0);
+
+ // Get more metrics about arm lengths
+ upper_arm_length[right] = distance(elbow, shoulder);
+ lower_arm_length[right] = distance(wrist, elbow);
+
+ vec3 mid_low_arm = GetTransformedBonePoint(&rigged_object, *(int*)ik_chain_elements->At(chain_start+1), 0);
+ vec3 mid_up_arm = GetTransformedBonePoint(&rigged_object, *(int*)ik_chain_elements->At(chain_start+3), 0);
+
+ lower_arm_weight[right] = distance(elbow, mid_low_arm) / distance(wrist, elbow);
+ upper_arm_weight[right] = distance(shoulder, mid_up_arm) / distance(shoulder, elbow);
+
+
+ BoneTransform world_chest = *(BoneTransform*)key_transforms->At(kChestKey) * *(BoneTransform*)inv_skeleton_bind_transforms->At(*(int*)ik_chain_start_index->At(kTorsoIK));
+ vec3 breathe_dir = world_chest.rotation * normalize(vec3(0.0f, -0.3f, 1.0f));
+ float scale = rigged_object.GetCharScale();
+ shoulder += breathe_dir * breath_amount * 0.005f * scale;
+
+ BoneTransform old_hand_transform;
+ { // Apply traditional IK
+ vec3 old_wrist = wrist;
+ vec3 old_shoulder = shoulder;
+ BoneTransform hand_transform = right==1?r_hand_transform:l_hand_transform;
+ shoulder = rel_mat * shoulder;
+
+
+ hand = hand_transform * skeleton.points[skeleton.bones[*(int*)ik_chain_elements->At(chain_start+0)].points[1]];
+ wrist = hand_transform * skeleton.points[skeleton.bones[*(int*)ik_chain_elements->At(chain_start+0)].points[0]];
+
+ // Rotate arm to match new IK pos
+ quaternion rotation;
+ GetRotationBetweenVectors(old_wrist - old_shoulder, wrist - shoulder, rotation);
+ elbow = shoulder + ASMult(rotation, elbow-old_shoulder);
+
+ // Scale to put elbow in approximately the right place
+ float old_length = distance(old_wrist, old_shoulder);
+ float new_length = distance(wrist, shoulder);
+ elbow = shoulder + (elbow - shoulder) * (new_length / old_length);
+
+ // Enforce arm lengths to finalize elbow position
+ const int iterations = 2;
+ for(int i=0; i<iterations; ++i){
+ vec3 offset;
+ offset += (shoulder + normalize(elbow-shoulder) * upper_arm_length[right]) - elbow;
+ offset += (wrist + normalize(elbow-wrist) * lower_arm_length[right]) - elbow;
+ elbow += offset;
+ }
+ }
+
+ collar_tip = rel_mat * collar_tip;
+ collar = rel_mat * collar;
+
+ int points_offset = kNumArmPoints * right;
+ chain_points[kHandPoint+points_offset] = hand;
+ chain_points[kWristPoint+points_offset] = wrist;
+ chain_points[kElbowPoint+points_offset] = elbow;
+ chain_points[kShoulderPoint+points_offset] = shoulder;
+ chain_points[kCollarTipPoint+points_offset] = collar_tip;
+ chain_points[kCollarPoint+points_offset] = collar;
+ }
+
+ old_arm_points->Resize(6);
+ temp_old_arm_points->Resize(6);
+ if(arm_points->GetSize()!=6){ // Initialize arm physics particles
+ arm_points->Resize(6);
+ *(vec3*)arm_points->At(0) = chain_points[kShoulderPoint];
+ *(vec3*)arm_points->At(1) = chain_points[kElbowPoint];
+ *(vec3*)arm_points->At(2) = chain_points[kWristPoint];
+ *(vec3*)arm_points->At(3) = chain_points[kShoulderPoint+kNumArmPoints];
+ *(vec3*)arm_points->At(4) = chain_points[kElbowPoint+kNumArmPoints];
+ *(vec3*)arm_points->At(5) = chain_points[kWristPoint+kNumArmPoints];
+ for(int i=0, len=arm_points->GetSize(); i<len; ++i){
+ *(vec3*)old_arm_points->At(i) = *(vec3*)arm_points->At(i);
+ *(vec3*)temp_old_arm_points->At(i) = *(vec3*)arm_points->At(i);
+ }
+ } else { // Simulate arm physics
+ for(int i=0; i<6; ++i){
+ *(vec3*)temp_old_arm_points->At(i) = *(vec3*)arm_points->At(i);
+ }
+ for(int right=0; right<2; ++right){
+ int start = right*3;
+
+ float arm_drag = 0.92f;
+ // Determine how loose the arms should be
+ if(max_speed == 0.0f){
+ max_speed = true_max_speed;
+ }
+ float arm_loose = 1.0f-length(this_mo.velocity)/max_speed;
+ if(idle_type == _combat){
+ arm_loose = 0.0f;
+ }
+ if(!on_ground){
+ arm_loose = 0.7f;
+ }
+ //TODO: fix
+ if(flip_info_flipping){
+ arm_loose = 0.0f;
+ }
+ arm_loose = max(0.0f, arm_loose - max(0.0f, threat_amount));
+ float arm_stiffness = mix(0.9f, 0.97f, arm_loose);
+ float shoulder_stiffness = arm_stiffness;
+ float elbow_stiffness = arm_stiffness;
+
+ vec3 shoulder = chain_points[kShoulderPoint + kNumArmPoints*right];
+ vec3 elbow = chain_points[kElbowPoint + kNumArmPoints*right];
+ vec3 wrist = chain_points[kWristPoint + kNumArmPoints*right];
+ *(vec3*)arm_points->At(start+0) = shoulder;
+ { // Apply arm velocity
+ vec3 full_vel_offset = this_mo.velocity * game_timer.timestep * (float) num_frames;
+ vec3 vel_offset = ((*(vec3*)arm_points->At(start+1) - *(vec3*)old_arm_points->At(start+1)) - full_vel_offset) * pow(arm_drag, num_frames) + full_vel_offset;
+ *(vec3*)arm_points->At(start+1) += vel_offset;
+ vel_offset = ((*(vec3*)arm_points->At(start+2) - *(vec3*)old_arm_points->At(start+2)) - full_vel_offset) * pow(arm_drag, num_frames) + full_vel_offset;
+ *(vec3*)arm_points->At(start+2) += vel_offset;
+ }
+ quaternion rotation;
+ { // Apply linear force towards target positions
+ *(vec3*)arm_points->At(start+1) += (elbow - *(vec3*)arm_points->At(start+1)) * (1.0f - pow(shoulder_stiffness, num_frames));
+ GetRotationBetweenVectors(elbow-shoulder, *(vec3*)arm_points->At(start+1)-shoulder, rotation);
+ vec3 rotated_tip = ASMult(rotation, wrist-elbow)+elbow;
+ *(vec3*)arm_points->At(start+2) += (rotated_tip - *(vec3*)arm_points->At(start+2)) * (1.0f - pow(elbow_stiffness, num_frames));
+ }
+ const char* armblends[] = { "leftarm_blend", "rightarm_blend" };
+ float softness_override = rigged_object.GetStatusKeyValue(armblends[right]);
+ //softness_override = mix(softness_override, 1.0f, min(1.0f, flip_ik_fade));
+
+ //We want to make the arms stiff when the character is climbing with arms,
+ // TODO: fix
+ if(ledge_info_on_ledge){
+ softness_override = 1.0f;
+ }
+
+ //Check if character has been teleported and prevent affects of arm physics if that's the case.
+ if( length(*(vec3*)arm_points->At(start+2)-*(vec3*)old_arm_points->At(start+2)) > 10.0f )
+ {
+ //Log(warning, "Massive movement in arm positions detected, skipping arm physics for this frame. TODO\n");
+ softness_override = 1.0f;
+ }
+
+ { // Blend with original position to override physics
+ *(vec3*)arm_points->At(start+0) = mix(*(vec3*)arm_points->At(start+0), shoulder, softness_override);
+ *(vec3*)arm_points->At(start+1) = mix(*(vec3*)arm_points->At(start+1), elbow, softness_override);
+ *(vec3*)arm_points->At(start+2) = mix(*(vec3*)arm_points->At(start+2), wrist, softness_override);
+ }
+
+ { // Enforce constraints
+ // Get hinge joint info
+ int chain_start = *(int*)ik_chain_start_index->At(kLeftArmIK+right);
+ BoneTransform elbow_mat = rigged_object.animation_frame_bone_matrices[*(int*)ik_chain_elements->At(chain_start+3)];
+ vec3 elbow_axis = ASMult(rotation, elbow_mat.rotation * vec3(1,0,0));
+ vec3 elbow_front = ASMult(rotation, elbow_mat.rotation * vec3(0,1,0));
+ vec3 shoulder_offset, elbow_offset, wrist_offset;
+
+ for(int i=0; i<1; ++i){
+ float iter_strength = 0.75f * (1.0f - softness_override);
+
+ // Distance constraints
+ elbow_offset = (shoulder + normalize(*(vec3*)arm_points->At(start+1)-shoulder) * upper_arm_length[right]) - *(vec3*)arm_points->At(start+1);
+ vec3 mid = (*(vec3*)arm_points->At(start+1) + *(vec3*)arm_points->At(start+2))*0.5f;
+ vec3 dir = normalize(*(vec3*)arm_points->At(start+1) - *(vec3*)arm_points->At(start+2));
+ wrist_offset = (mid - dir * lower_arm_length[right] * 0.5f) - *(vec3*)arm_points->At(start+2);
+ elbow_offset += (mid + dir * lower_arm_length[right] * 0.5f) - *(vec3*)arm_points->At(start+1);
+
+ // Hinge constraints
+ vec3 offset;
+ offset += elbow_axis * dot(elbow_axis, *(vec3*)arm_points->At(start+2)-*(vec3*)arm_points->At(start+1));
+ float front_amount = dot(elbow_front, *(vec3*)arm_points->At(start+2)-*(vec3*)arm_points->At(start+1));
+ if(front_amount < 0.0f){
+ offset += elbow_front * front_amount;
+ }
+ elbow_offset += offset * 0.5f;
+ wrist_offset -= offset * 0.5f;
+
+ // Apply scaled correction vectors
+ *(vec3*)arm_points->At(start+1) += elbow_offset * iter_strength;
+ *(vec3*)arm_points->At(start+2) += wrist_offset * iter_strength;
+ }
+ }
+ }
+ /*{ // Apply arm physics to actual elbow, hand and wrist positions
+ int point_offset = kNumArmPoints;
+ vec3 hand = chain_points[kHandPoint+point_offset];
+ vec3 wrist = chain_points[kWristPoint+point_offset];
+ vec3 elbow = chain_points[kElbowPoint+point_offset];
+ int start = 3;
+ quaternion hand_rotation;
+ GetRotationBetweenVectors(elbow-wrist, *(vec3*)arm_points->At(start+1)-*(vec3*)arm_points->At(start+2), hand_rotation);
+
+ vec3 hand_offset = hand-wrist;
+ elbow = *(vec3*)arm_points->At(start+1);
+ wrist = *(vec3*)arm_points->At(start+2);
+ hand = wrist + Mult(hand_rotation, hand_offset);
+
+ BoneTransform temp_r_hand_transform;
+ temp_r_hand_transform.origin = (wrist+hand) * 0.5f;
+ temp_r_hand_transform.rotation = hand_rotation * right_hand.rotation;
+ BoneTransform temp_l_hand_transform = temp_r_hand_transform*invert(right_hand)*left_hand;
+ vec3 offset = temp_l_hand_transform.origin - *(vec3*)arm_points->At(2);
+ *(vec3*)arm_points->At(2) += offset * 0.5f;
+ // *(vec3*)arm_points->At(5) -= offset * 0.5f;
+ }*/
+ for(int i=0; i<6; ++i){
+ *(vec3*)old_arm_points->At(i) = *(vec3*)temp_old_arm_points->At(i);
+ }
+ }
+
+ for(int right=0; right<2; ++right){
+ int chain_start = *(int*)ik_chain_start_index->At(kLeftArmIK+right);
+ int point_offset = right * kNumArmPoints;
+ vec3 hand = chain_points[kHandPoint+point_offset];
+ vec3 wrist = chain_points[kWristPoint+point_offset];
+ vec3 elbow = chain_points[kElbowPoint+point_offset];
+ vec3 shoulder = chain_points[kShoulderPoint+point_offset];
+ vec3 collar_tip = chain_points[kCollarTipPoint+point_offset];
+ vec3 collar = chain_points[kCollarPoint+point_offset];
+
+
+ { // Apply arm physics to actual elbow, hand and wrist positions
+ int start = right*3;
+ quaternion hand_rotation;
+ GetRotationBetweenVectors(elbow-wrist, *(vec3*)arm_points->At(start+1)-*(vec3*)arm_points->At(start+2), hand_rotation);
+
+ vec3 hand_offset = hand-wrist;
+ // Enforce arm length
+ elbow = *(vec3*)arm_points->At(start) + normalize(*(vec3*)arm_points->At(start+1) - *(vec3*)arm_points->At(start)) * upper_arm_length[right];
+ wrist = elbow + normalize(*(vec3*)arm_points->At(start+2) - *(vec3*)arm_points->At(start+1)) * lower_arm_length[right];
+ hand = wrist + ASMult(hand_rotation, hand_offset);
+ }
+
+ BoneTransform old_hand_matrix = rigged_object.animation_frame_bone_matrices[*(int*)ik_chain_elements->At(chain_start+0)];
+
+ for(int i=4, len=*(int*)ik_chain_length->At(kLeftArmIK+right); i<len; ++i){
+ rigged_object.animation_frame_bone_matrices[*(int*)ik_chain_elements->At(chain_start+i)] = rel_mat * rigged_object.animation_frame_bone_matrices[*(int*)ik_chain_elements->At(chain_start+i)];
+ }
+
+ RotateBoneToMatchVec(&rigged_object, collar, collar_tip, *(int*)ik_chain_elements->At(chain_start+5));
+ RotateBoneToMatchVec(&rigged_object, shoulder, mix(shoulder, elbow, upper_arm_weight[right]), *(int*)ik_chain_elements->At(chain_start+4));
+ RotateBoneToMatchVec(&rigged_object, mix(shoulder, elbow, upper_arm_weight[right]), elbow, *(int*)ik_chain_elements->At(chain_start+3));
+ RotateBoneToMatchVec(&rigged_object, elbow, mix(elbow, wrist, lower_arm_weight[right]), *(int*)ik_chain_elements->At(chain_start+2));
+ RotateBoneToMatchVec(&rigged_object, mix(elbow, wrist, lower_arm_weight[right]), wrist, *(int*)ik_chain_elements->At(chain_start+1));
+
+ {
+ BoneTransform hand_transform = right==1?r_hand_transform:l_hand_transform;
+ rigged_object.animation_frame_bone_matrices[*(int*)ik_chain_elements->At(chain_start+0)] = hand_transform * *(BoneTransform*)inv_skeleton_bind_transforms->At(*(int*)ik_chain_elements->At(chain_start+0));
+ RotateBoneToMatchVec(&rigged_object, wrist, hand, *(int*)ik_chain_elements->At(chain_start+0));
+ }
+
+ BoneTransform hand_rel = rigged_object.animation_frame_bone_matrices[*(int*)ik_chain_elements->At(chain_start+0)] * invert(old_hand_matrix);
+
+ // Apply hand rotation to child bones (like fingers)
+ for(int i=0, len=GetNumBoneChildren(bone_children_index, *(int*)ik_chain_elements->At(chain_start+0)); i<len; ++i){
+ int child = *(int*)bone_children->At(*(int*)bone_children_index->At(*(int*)ik_chain_elements->At(chain_start+0))+i);
+ rigged_object.animation_frame_bone_matrices[child] = hand_rel * rigged_object.animation_frame_bone_matrices[child];
+ }
+ }
+}
+
+static void CDrawArms(MovementObject* mo, const BoneTransform& chest_transform, const BoneTransform& l_hand_transform, const BoneTransform& r_hand_transform, int num_frames) {
+ PROFILER_ZONE(g_profiler_ctx, "C++ Draw Arms")
+ MovementObject& this_mo = *mo;
+ RiggedObject& rigged_object = *this_mo.rigged_object();
+ Skeleton& skeleton = rigged_object.skeleton();
+
+ PROFILER_ENTER(g_profiler_ctx, "Get AS pointers");
+ ASContext* as_context = this_mo.as_context.get();
+ float& breath_amount = *(float*)as_context->module.GetVarPtrCache("breath_amount");
+ float& max_speed = *(float*)as_context->module.GetVarPtrCache("max_speed");
+ float& true_max_speed = *(float*)as_context->module.GetVarPtrCache("true_max_speed");
+ int& idle_type = *(int*)as_context->module.GetVarPtrCache("idle_type");
+ bool& on_ground = *(bool*)as_context->module.GetVarPtrCache("on_ground");
+ bool& flip_info_flipping = *(bool*)((asIScriptObject*)as_context->module.GetVarPtrCache("flip_info"))->GetAddressOfProperty(0);
+ float& threat_amount = *(float*)as_context->module.GetVarPtrCache("threat_amount");
+ bool& ledge_info_on_ledge = *(bool*)((asIScriptObject*)as_context->module.GetVarPtrCache("ledge_info"))->GetAddressOfProperty(0);
+
+ CScriptArray* skeleton_bind_transforms = (CScriptArray*)as_context->module.GetVarPtrCache("skeleton_bind_transforms");
+ CScriptArray* inv_skeleton_bind_transforms = (CScriptArray*)as_context->module.GetVarPtrCache("inv_skeleton_bind_transforms");
+ CScriptArray* ik_chain_start_index = (CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_start_index");
+ CScriptArray* ik_chain_elements = (CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_elements");
+ CScriptArray* ik_chain_length = (CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_length");
+ CScriptArray* key_transforms = (CScriptArray*)as_context->module.GetVarPtrCache("key_transforms");
+ CScriptArray* arm_points = (CScriptArray*)as_context->module.GetVarPtrCache("arm_points");
+ CScriptArray* old_arm_points = (CScriptArray*)as_context->module.GetVarPtrCache("old_arm_points");
+ CScriptArray* temp_old_arm_points = (CScriptArray*)as_context->module.GetVarPtrCache("temp_old_arm_points");
+ CScriptArray* bone_children = (CScriptArray*)as_context->module.GetVarPtrCache("bone_children");
+ CScriptArray* bone_children_index = (CScriptArray*)as_context->module.GetVarPtrCache("bone_children_index");
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ CDrawArmsImpl(
+ this_mo, rigged_object, skeleton,
+ breath_amount,
+ max_speed,
+ true_max_speed, idle_type, on_ground, flip_info_flipping, threat_amount, ledge_info_on_ledge,
+ skeleton_bind_transforms, inv_skeleton_bind_transforms, ik_chain_start_index, ik_chain_elements, ik_chain_length, key_transforms, arm_points, old_arm_points, temp_old_arm_points, bone_children, bone_children_index, chest_transform, l_hand_transform, r_hand_transform, num_frames);
+}
+
+template <typename T>
+class CScriptArrayWrapper {
+public:
+ CScriptArray* c_script_array_;
+
+ CScriptArrayWrapper(CScriptArray* c_script_array):
+ c_script_array_(c_script_array)
+ {}
+
+ T& operator[](int index) {
+ return *(T*)c_script_array_->At(index);
+ }
+
+ const T& operator[](int index) const {
+ return *(T*)c_script_array_->At(index);
+ }
+
+ void resize(int size){
+ c_script_array_->Resize(size);
+ }
+
+ int size(){
+ return (int)c_script_array_->GetSize();
+ }
+
+ void push_back(const T& val){
+ c_script_array_->InsertLast((void*)&val);
+ }
+};
+
+#define rigged_object_SetFrameMatrix( A, B ) (rigged_object.animation_frame_bone_matrices[A] = B)
+#define rigged_object_GetFrameMatrix( A ) (rigged_object.animation_frame_bone_matrices[A])
+#define rigged_object_GetTransformedBonePoint( A, B ) (GetTransformedBonePoint(&rigged_object, A, B))
+#define rigged_object_RotateBoneToMatchVec( A, B, C ) (RotateBoneToMatchVec(&rigged_object, A, B, C))
+
+static void CDisplayMatrixUpdateImpl(
+ MovementObject* mo,
+ float breath_amount,
+ const CScriptArrayWrapper<int>& ik_chain_start_index,
+ const CScriptArrayWrapper<int>& ik_chain_elements,
+ const CScriptArrayWrapper<BoneTransform>& skeleton_bind_transforms,
+ const CScriptArrayWrapper<BoneTransform>& inv_skeleton_bind_transforms) {
+ MovementObject& this_mo = *mo;
+ RiggedObject& rigged_object = *this_mo.rigged_object();
+
+ float scale = rigged_object.GetCharScale();
+ int chest_bone = ik_chain_elements[ik_chain_start_index[kTorsoIK]];
+ BoneTransform world_chest = BoneTransform(rigged_object.display_bone_matrices[chest_bone]) * inv_skeleton_bind_transforms[chest_bone];
+ vec3 breathe_dir = world_chest.rotation * normalize(vec3(0, 0, 1));
+ vec3 breathe_front = world_chest.rotation * normalize(vec3(0, 1, 0));
+ vec3 breathe_side = world_chest.rotation * normalize(vec3(1, 0, 0));
+
+ for (int i = 0; i < 2; ++i) {
+ int collar_bone = ik_chain_elements[ik_chain_start_index[kLeftArmIK + i] + 5];
+ BoneTransform collar = BoneTransform(rigged_object.display_bone_matrices[collar_bone]) * inv_skeleton_bind_transforms[collar_bone];
+ collar.origin += breathe_dir * breath_amount * 0.01f * scale;
+ collar.origin += breathe_front * breath_amount * 0.01f * scale;
+ collar = collar * skeleton_bind_transforms[collar_bone];
+ rigged_object.display_bone_matrices[collar_bone] = collar.GetMat4();
+
+ int shoulder_bone = ik_chain_elements[ik_chain_start_index[kLeftArmIK + i] + 4];
+ BoneTransform shoulder = BoneTransform(rigged_object.display_bone_matrices[shoulder_bone]) * inv_skeleton_bind_transforms[shoulder_bone];
+ shoulder.origin += breathe_dir * breath_amount * 0.002f * scale;
+ shoulder = shoulder * skeleton_bind_transforms[shoulder_bone];
+ rigged_object.display_bone_matrices[shoulder_bone] = shoulder.GetMat4();
+ }
+
+ world_chest.origin += breathe_dir * breath_amount * 0.01f * scale;
+ world_chest.origin += breathe_front * breath_amount * 0.01f * scale;
+ world_chest.rotation = quaternion(vec4(breathe_side.x(), breathe_side.y(), breathe_side.z(), breath_amount * 0.05f)) * world_chest.rotation;
+ world_chest = world_chest * skeleton_bind_transforms[chest_bone];
+ rigged_object.display_bone_matrices[chest_bone] = world_chest.GetMat4();
+
+ int abdomen_bone = ik_chain_elements[ik_chain_start_index[kTorsoIK] + 1];
+ BoneTransform abdomen = BoneTransform(rigged_object.display_bone_matrices[abdomen_bone]) * inv_skeleton_bind_transforms[abdomen_bone];
+ abdomen.origin += breathe_dir * breath_amount * 0.008f * scale;
+ abdomen.origin += breathe_front * breath_amount * 0.005f * scale;
+ abdomen.rotation = quaternion(vec4(breathe_side.x(), breathe_side.y(), breathe_side.z(), breath_amount * -0.02f)) * abdomen.rotation;
+ abdomen = abdomen * skeleton_bind_transforms[abdomen_bone];
+ rigged_object.display_bone_matrices[abdomen_bone] = abdomen.GetMat4();
+
+ int neck_bone = ik_chain_elements[ik_chain_start_index[kHeadIK] + 1];
+ BoneTransform neck = BoneTransform(rigged_object.display_bone_matrices[neck_bone]) * inv_skeleton_bind_transforms[neck_bone];
+ neck.origin += breathe_dir * breath_amount * 0.005f * scale;
+ neck = neck * skeleton_bind_transforms[neck_bone];
+ rigged_object.display_bone_matrices[neck_bone] = neck.GetMat4();
+
+}
+
+static void CDisplayMatrixUpdate(MovementObject* mo) {
+ PROFILER_ZONE(g_profiler_ctx, "C++ Display Matrix Update")
+ MovementObject& this_mo = *mo;
+
+ PROFILER_ENTER(g_profiler_ctx, "Get AS pointers");
+ ASContext* as_context = this_mo.as_context.get();
+ float& breath_amount = *(float*)as_context->module.GetVarPtrCache("breath_amount");
+
+ CScriptArrayWrapper<int> ik_chain_start_index((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_start_index"));
+ CScriptArrayWrapper<int> ik_chain_elements((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_elements"));
+ CScriptArrayWrapper<BoneTransform> skeleton_bind_transforms((CScriptArray*)as_context->module.GetVarPtrCache("skeleton_bind_transforms"));
+ CScriptArrayWrapper<BoneTransform> inv_skeleton_bind_transforms((CScriptArray*)as_context->module.GetVarPtrCache("inv_skeleton_bind_transforms"));
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ CDisplayMatrixUpdateImpl(
+ mo,
+ breath_amount,
+ ik_chain_start_index, ik_chain_elements, skeleton_bind_transforms, inv_skeleton_bind_transforms);
+}
+
+static vec3 CGetCenterOfMassEstimateImpl(
+ MovementObject* mo,
+ CScriptArrayWrapper<int>& ik_chain_start_index,
+ CScriptArrayWrapper<int>& ik_chain_elements,
+ CScriptArrayWrapper<BoneTransform>& key_transforms,
+ CScriptArrayWrapper<float>& key_masses,
+ CScriptArrayWrapper<int>& root_bone) {
+ MovementObject& this_mo = *mo;
+ RiggedObject& rigged_object = *this_mo.rigged_object();
+ Skeleton& skeleton = rigged_object.skeleton();
+
+ vec3 estimate_com;
+ float body_mass = 0.0f;
+
+ for (int j = 0; j < 2; ++j) {
+ int bone = ik_chain_elements[ik_chain_start_index[kLeftLegIK + j]];
+ vec3 point = skeleton.points[skeleton.bones[bone].points[0]];
+ vec3 foot_point = key_transforms[kLeftLegKey + j] * point;
+ vec3 hip_point = key_transforms[kHipKey] * skeleton.points[skeleton.bones[root_bone[kLeftLegKey + j]].points[0]];
+ estimate_com += (hip_point + foot_point) * 0.5f * key_masses[kLeftLegKey + j];
+ body_mass += key_masses[kLeftLegKey + j];
+ }
+
+ for (int j = 0; j < 2; ++j) {
+ int bone = ik_chain_elements[ik_chain_start_index[kLeftArmIK + j]];
+ vec3 foot_point = key_transforms[j == 0 ? kLeftArmKey : kRightArmKey] * skeleton.points[skeleton.bones[bone].points[0]];
+ vec3 hip_point = key_transforms[kChestKey] * skeleton.points[skeleton.bones[root_bone[kLeftArmKey + j]].points[0]];
+ estimate_com += (hip_point + foot_point) * 0.5f * key_masses[kLeftArmKey + j];
+ body_mass += key_masses[kLeftArmKey + j];
+ }
+
+ {
+ int bone = ik_chain_elements[ik_chain_start_index[kTorsoIK]];
+ vec3 foot_point = key_transforms[kChestKey] * skeleton.points[skeleton.bones[bone].points[1]];
+ vec3 hip_point = key_transforms[kHipKey] * skeleton.points[skeleton.bones[root_bone[kChestKey]].points[0]];
+ estimate_com += (hip_point + foot_point) * 0.5f * key_masses[kChestKey];
+ body_mass += key_masses[kChestKey];
+ }
+
+ {
+ int bone = ik_chain_elements[ik_chain_start_index[kHeadIK]];
+ vec3 foot_point = key_transforms[kHeadKey] * skeleton.points[skeleton.bones[bone].points[1]];
+ vec3 hip_point = key_transforms[kChestKey] * skeleton.points[skeleton.bones[root_bone[kHeadKey]].points[0]];
+ estimate_com += (hip_point + foot_point) * 0.5f * key_masses[kHeadKey];
+ body_mass += key_masses[kHeadKey];
+ }
+
+ return estimate_com / body_mass;
+}
+
+static vec3 CGetCenterOfMassEstimate(MovementObject* mo, CScriptArray& key_transforms, CScriptArray& key_masses, CScriptArray& root_bone) {
+ PROFILER_ZONE(g_profiler_ctx, "C++ Do Get Center of Mass Estimate")
+ MovementObject& this_mo = *mo;
+
+ PROFILER_ENTER(g_profiler_ctx, "Get AS pointers");
+ ASContext* as_context = this_mo.as_context.get();
+
+ CScriptArrayWrapper<int> ik_chain_start_index((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_start_index"));
+ CScriptArrayWrapper<int> ik_chain_elements((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_elements"));
+
+ CScriptArrayWrapper<BoneTransform> key_transforms_wrapper(&key_transforms);
+ CScriptArrayWrapper<float> key_masses_wrapper(&key_masses);
+ CScriptArrayWrapper<int> root_bone_wrapper(&root_bone);
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ return CGetCenterOfMassEstimateImpl(
+ mo,
+ ik_chain_start_index, ik_chain_elements,
+ key_transforms_wrapper, key_masses_wrapper, root_bone_wrapper);
+}
+
+static void CDoChestIKImpl(
+ RiggedObject& rigged_object,
+ float& time,
+ float& time_step,
+ float& idle_stance_amount,
+ bool& asleep,
+ bool& sitting,
+ vec3& old_chest_facing,
+ vec2& old_chest_angle_vec,
+ vec2& chest_angle,
+ vec2& target_chest_angle,
+ vec2& chest_angle_vel,
+ float& old_chest_angle,
+ vec3& torso_look,
+ int& queue_reset_secondary_animation,
+ bool& dialogue_control,
+ float& test_talking_amount,
+ CScriptArrayWrapper<BoneTransform>& key_transforms,
+ CScriptArrayWrapper<int>& ik_chain_start_index,
+ CScriptArrayWrapper<int>& ik_chain_elements,
+ CScriptArrayWrapper<BoneTransform>& skeleton_bind_transforms,
+ CScriptArrayWrapper<BoneTransform>& inv_skeleton_bind_transforms,
+ float chest_tilt_offset, float angle_threshold, float torso_damping, float torso_stiffness, int num_frames) {
+ if (torso_look != torso_look) {
+ torso_look = vec3(0.0);
+ }
+
+ float head_look_amount = length(torso_look);
+
+ vec3 head_dir = normalize(key_transforms[kChestKey].rotation * vec3(0, 0, 1));
+ vec3 head_up = normalize(key_transforms[kChestKey].rotation * vec3(0, 1, 0));
+ vec3 head_right = cross(head_dir, head_up);
+
+ {
+ vec2 head_look_flat;
+ head_look_flat.x() = dot(old_chest_facing, head_right);
+ head_look_flat.y() = dot(old_chest_facing, head_dir);
+
+ if (!(abs(dot(normalize(old_chest_facing), head_up)) > 0.9f)) {
+ old_chest_angle_vec.x() = atan2(-head_look_flat.x(), head_look_flat.y());
+ }
+
+ float head_up_val = dot(old_chest_facing, head_up);
+ old_chest_angle_vec.y() = 0.0f;
+ old_chest_angle_vec.y() = asin(dot(old_chest_facing, head_up));
+
+ if (old_chest_angle_vec.y() != old_chest_angle_vec.y()) {
+ old_chest_angle_vec.y() = 0.0f;
+ }
+
+ old_chest_facing = head_dir;
+ }
+
+ vec2 head_look_flat;
+ head_look_flat.x() = dot(torso_look, head_right);
+ head_look_flat.y() = dot(torso_look, head_dir);
+ float angle = atan2(-head_look_flat.x(), head_look_flat.y());
+
+ if (abs(dot(normalize(torso_look), head_up)) > 0.9f) {
+ angle = old_chest_angle;
+ }
+
+ float head_up_val = dot(torso_look, head_up);
+ float angle2 = 0.0f;
+ float preangle_dot = dot(torso_look, head_up) + chest_tilt_offset;
+
+ if (preangle_dot >= -1.0f && preangle_dot <= 1.0f) {
+ angle2 = asin(preangle_dot);
+ }
+
+ // Avoid head flip-flopping when trying to look straight back
+ float head_range = 1.0f;
+
+ if (angle > angle_threshold && old_chest_angle <= -head_range) {
+ angle = -head_range;
+ }
+ else if (angle < -angle_threshold && old_chest_angle >= head_range) {
+ angle = head_range;
+ }
+
+ angle = min(head_range, max(-head_range, angle));
+
+ old_chest_angle = angle;
+
+ target_chest_angle.x() = angle * head_look_amount;
+ target_chest_angle.y() = idle_stance_amount * (angle2 * head_look_amount);
+
+ float torso_shake_amount = 0.005f;
+ target_chest_angle.x() += RangedRandomFloat(-torso_shake_amount, torso_shake_amount);
+ target_chest_angle.y() += RangedRandomFloat(-torso_shake_amount, torso_shake_amount);
+
+ chest_angle_vel *= pow(torso_damping, num_frames);
+ chest_angle_vel += (target_chest_angle - chest_angle) * torso_stiffness * (float)num_frames;
+ chest_angle += chest_angle_vel * time_step * (float)num_frames;
+
+ if (queue_reset_secondary_animation != 0) {
+ chest_angle = target_chest_angle;
+ chest_angle_vel = 0.0;
+ }
+
+ int neck_bone = ik_chain_elements[ik_chain_start_index[kHeadIK] + 1];
+ int chest_bone = ik_chain_elements[ik_chain_start_index[kTorsoIK]];
+ BoneTransform chest_frame_matrix = rigged_object_GetFrameMatrix(chest_bone);
+ BoneTransform chest_bind_matrix = skeleton_bind_transforms[chest_bone];
+ BoneTransform rel_mat = key_transforms[kChestKey] * invert(chest_frame_matrix * chest_bind_matrix);
+ vec3 neck = rigged_object_GetTransformedBonePoint(neck_bone, 0);
+
+ if (dialogue_control) {
+ float head_bob = 0.1f * test_talking_amount + 0.02f;
+ head_bob *= 0.1f;
+ chest_angle.x() += (sin(time * 5.5f) * 0.1f + sin(time * 9.5f) * 0.1f) * head_bob;
+ chest_angle.y() += (sin(time * 4.5f) * 0.1f + sin(time * 7.5f) * 0.1f) * head_bob;
+ }
+
+ quaternion rotation(vec4(head_up.x(), head_up.y(), head_up.z(), chest_angle.x()));
+ quaternion rotation2(vec4(head_right.x(), head_right.y(), head_right.z(), chest_angle.y()));
+
+ quaternion identity;
+ int abdomen_bone = ik_chain_elements[ik_chain_start_index[kTorsoIK] + 1];
+ vec3 abdomen_top = rigged_object_GetTransformedBonePoint(abdomen_bone, 1);
+ vec3 abdomen_bottom = rigged_object_GetTransformedBonePoint(abdomen_bone, 0);
+
+ BoneTransform old_chest_transform = key_transforms[kChestKey];
+ key_transforms[kChestKey] = key_transforms[kChestKey] * inv_skeleton_bind_transforms[ik_chain_elements[ik_chain_start_index[kTorsoIK]]];
+ key_transforms[kChestKey].origin -= abdomen_top;
+ quaternion chest_rotate = rotation * rotation2;
+ key_transforms[kChestKey] = mix(chest_rotate, identity, 0.5f) * key_transforms[kChestKey];
+ key_transforms[kChestKey].origin += abdomen_top;
+ key_transforms[kChestKey].origin -= abdomen_bottom;
+ key_transforms[kChestKey] = mix(chest_rotate, identity, 0.5f) * key_transforms[kChestKey];
+ key_transforms[kChestKey].origin += abdomen_bottom;
+ key_transforms[kChestKey] = key_transforms[kChestKey] * skeleton_bind_transforms[ik_chain_elements[ik_chain_start_index[kTorsoIK]]];
+ BoneTransform offset = key_transforms[kChestKey] * invert(old_chest_transform);
+
+ if (!sitting && !asleep) {
+ key_transforms[kLeftArmKey] = offset * key_transforms[kLeftArmKey];
+ key_transforms[kRightArmKey] = offset * key_transforms[kRightArmKey];
+ }
+
+ key_transforms[kHeadKey] = offset * key_transforms[kHeadKey];
+}
+
+static void CDoChestIK(MovementObject* mo, float chest_tilt_offset, float angle_threshold, float torso_damping, float torso_stiffness, int num_frames) {
+ PROFILER_ZONE(g_profiler_ctx, "C++ Do Chest IK")
+ MovementObject& this_mo = *mo;
+ RiggedObject& rigged_object = *this_mo.rigged_object();
+
+ PROFILER_ENTER(g_profiler_ctx, "Get AS pointers");
+ ASContext* as_context = this_mo.as_context.get();
+ float& time = game_timer.game_time;
+ float& time_step = game_timer.timestep;
+ float& idle_stance_amount = *(float*)as_context->module.GetVarPtrCache("idle_stance_amount");
+ bool& asleep = *(bool*)as_context->module.GetVarPtrCache("asleep");
+ bool& sitting = *(bool*)as_context->module.GetVarPtrCache("sitting");
+ int& queue_reset_secondary_animation = *(int*)as_context->module.GetVarPtrCache("queue_reset_secondary_animation");
+ bool& dialogue_control = *(bool*)as_context->module.GetVarPtrCache("dialogue_control");
+ float& test_talking_amount = *(float*)as_context->module.GetVarPtrCache("test_talking_amount");
+
+ CScriptArrayWrapper<BoneTransform> key_transforms((CScriptArray*)as_context->module.GetVarPtrCache("key_transforms"));
+ CScriptArrayWrapper<int> ik_chain_start_index((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_start_index"));
+ CScriptArrayWrapper<int> ik_chain_elements((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_elements"));
+ CScriptArrayWrapper<BoneTransform> skeleton_bind_transforms((CScriptArray*)as_context->module.GetVarPtrCache("skeleton_bind_transforms"));
+ CScriptArrayWrapper<BoneTransform> inv_skeleton_bind_transforms((CScriptArray*)as_context->module.GetVarPtrCache("inv_skeleton_bind_transforms"));
+
+ vec3& old_chest_facing = *(vec3*)as_context->module.GetVarPtrCache("old_chest_facing");
+ vec2& old_chest_angle_vec = *(vec2*)as_context->module.GetVarPtrCache("old_chest_angle_vec");
+ vec2& chest_angle = *(vec2*)as_context->module.GetVarPtrCache("chest_angle");
+ vec2& target_chest_angle = *(vec2*)as_context->module.GetVarPtrCache("target_chest_angle");
+ vec2& chest_angle_vel = *(vec2*)as_context->module.GetVarPtrCache("chest_angle_vel");
+ float& old_chest_angle = *(float*)as_context->module.GetVarPtrCache("old_chest_angle");
+ vec3& torso_look = *(vec3*)as_context->module.GetVarPtrCache("torso_look");
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ CDoChestIKImpl(
+ rigged_object,
+ time, time_step, idle_stance_amount, asleep, sitting, old_chest_facing, old_chest_angle_vec, chest_angle, target_chest_angle, chest_angle_vel, old_chest_angle, torso_look, queue_reset_secondary_animation, dialogue_control, test_talking_amount,
+ key_transforms, ik_chain_start_index, ik_chain_elements, skeleton_bind_transforms, inv_skeleton_bind_transforms,
+ chest_tilt_offset, angle_threshold, torso_damping, torso_stiffness, num_frames);
+}
+
+static void CDoHeadIKImpl(
+ RiggedObject& rigged_object,
+ float& time,
+ float& time_step,
+ vec3& old_head_facing,
+ vec2& old_angle,
+ vec2& head_angle,
+ vec2& target_head_angle,
+ vec2& head_angle_vel,
+ vec2& head_angle_accel,
+ float& old_head_angle,
+ vec3& head_look,
+ Species& species,
+ float& flip_ik_fade,
+ int& queue_reset_secondary_animation,
+ bool& dialogue_control,
+ float& test_talking_amount,
+ CScriptArrayWrapper<BoneTransform>& key_transforms,
+ CScriptArrayWrapper<int>& ik_chain_start_index,
+ CScriptArrayWrapper<int>& ik_chain_elements,
+ CScriptArrayWrapper<BoneTransform>& skeleton_bind_transforms,
+ CScriptArrayWrapper<BoneTransform>& inv_skeleton_bind_transforms,
+ float head_tilt_offset, float angle_threshold, float head_damping, float head_accel_inertia, float head_accel_damping, float head_stiffness, int num_frames) {
+ vec3 head_dir = key_transforms[kHeadKey].rotation * vec3(0, 0, 1);
+ vec3 head_up = key_transforms[kHeadKey].rotation * vec3(0, 1, 0);
+ vec3 head_right = cross(head_dir, head_up);
+
+ {
+ vec2 head_look_flat;
+ head_look_flat.x() = dot(old_head_facing, head_right);
+ head_look_flat.y() = dot(old_head_facing, head_dir);
+
+ if (!(abs(dot(normalize(old_head_facing), head_up)) > 0.9f)) {
+ old_angle.x() = atan2(-head_look_flat.x(), head_look_flat.y());
+ }
+
+ float head_up_val = dot(old_head_facing, head_up);
+ old_angle.y() = 0.0f;
+ float dotp_head = dot(old_head_facing, head_up);
+
+ if (dotp_head >= -1.0f && dotp_head <= 1.0f) {
+ old_angle.y() = asin(dotp_head);
+ }
+ else {
+ old_angle.y() = 0.0f;
+ }
+
+ old_head_facing = head_dir;
+ }
+
+ if (head_look != head_look) {
+ head_look = vec3(0.0);
+ }
+
+ float head_look_amount = length(head_look);
+ vec2 head_look_flat;
+ head_look_flat.x() = dot(head_look, head_right);
+ head_look_flat.y() = dot(head_look, head_dir);
+ float angle = atan2(-head_look_flat.x(), head_look_flat.y());
+
+ if (abs(dot(normalize(head_look), head_up)) > 0.9f) {
+ angle = old_head_angle;
+ }
+
+ float head_up_val = dot(head_look, head_up);
+ float angle2 = 0.0f;
+
+ if (head_up_val >= -1.0f && head_up_val <= 1.0f) {
+ angle2 = (asin(head_up_val) + head_tilt_offset);
+ }
+
+ // Avoid head flip-flopping when trying to look straight back
+ float head_range = 1.3f;
+
+ if (species != _rabbit) {
+ head_range *= 0.7f;
+ }
+
+ if (angle > angle_threshold && old_head_angle <= -head_range) {
+ angle = -head_range;
+ }
+ else if (angle < -angle_threshold && old_head_angle >= head_range) {
+ angle = head_range;
+ }
+
+ angle = min(head_range, max(-head_range, angle));
+ angle2 = min(0.8f, max(-0.8f, angle2));
+
+ old_head_angle = angle;
+
+ target_head_angle.x() = angle * head_look_amount;
+ target_head_angle.y() = angle2 * head_look_amount;
+
+ float head_shake_amount = 0.001f;
+ target_head_angle.x() += RangedRandomFloat(-head_shake_amount, head_shake_amount);
+ target_head_angle.y() += RangedRandomFloat(-head_shake_amount, head_shake_amount);
+
+ head_angle_vel *= pow(head_damping, num_frames);
+ head_angle_accel *= pow(head_accel_damping, num_frames);
+ head_angle_accel = mix((target_head_angle - head_angle) * head_stiffness, head_angle_accel, pow(head_accel_inertia, num_frames));
+
+ if (head_angle_accel != head_angle_accel) {
+ head_angle_accel = vec2(0.0);
+ }
+
+ head_angle_vel += head_angle_accel * (float)num_frames;
+ head_angle_vel.x() = min(max(head_angle_vel.x(), -15.0f), 15.0f);
+ head_angle_vel.y() = min(max(head_angle_vel.y(), -15.0f), 15.0f);
+ head_angle += head_angle_vel * time_step * (float)num_frames;
+
+ head_angle.x() = min(max(head_angle.x(), -1.5f), 1.5f);
+ head_angle.y() = min(max(head_angle.y(), -1.5f), 1.5f);
+ target_head_angle.x() = min(max(target_head_angle.x(), -1.5f), 1.5f);
+
+ if (queue_reset_secondary_animation != 0) {
+ head_angle = target_head_angle;
+ head_angle_vel = 0.0;
+ head_angle_accel = 0.0;
+ }
+
+ vec2 old_offset(old_angle * (1.0f - flip_ik_fade));
+
+ if ((head_angle.x() > target_head_angle.x() && old_offset.x() < 0.0f) ||
+ (head_angle.x() < target_head_angle.x() && old_offset.x() > 0.0f))
+ {
+ head_angle.x() = MoveTowards(head_angle.x(), target_head_angle.x(), abs(old_offset.x()));
+ }
+
+ if ((head_angle.y() > target_head_angle.x() && old_offset.y() < 0.0f) ||
+ (head_angle.y() < target_head_angle.x() && old_offset.y() > 0.0f))
+ {
+ head_angle.y() = MoveTowards(head_angle.y(), target_head_angle.y(), abs(old_offset.y()));
+ }
+
+ int neck_bone = ik_chain_elements[ik_chain_start_index[kHeadIK] + 1];
+ int chest_bone = ik_chain_elements[ik_chain_start_index[kTorsoIK]];
+ BoneTransform chest_frame_matrix = rigged_object_GetFrameMatrix(chest_bone);
+ BoneTransform chest_bind_matrix = skeleton_bind_transforms[chest_bone];
+ BoneTransform rel_mat = key_transforms[kChestKey] * invert(chest_frame_matrix * chest_bind_matrix);
+ vec3 neck = rel_mat * rigged_object_GetTransformedBonePoint(neck_bone, 0);
+
+ vec2 temp_head_angle = head_angle;
+
+ if (dialogue_control) {
+ float head_bob = 0.3f * test_talking_amount + 0.02f;
+ head_bob *= 0.7f;
+ temp_head_angle.y() += (sin(time * 5) * 0.1f + sin(time * 12) * 0.1f) * head_bob;
+ temp_head_angle.x() += (sin(time * 4) * 0.1f + sin(time * 10) * 0.1f) * head_bob;
+ }
+
+ quaternion rotation(vec4(head_up.x(), head_up.y(), head_up.z(), temp_head_angle.x()));
+ quaternion rotation2(vec4(head_right.x(), head_right.y(), head_right.z(), temp_head_angle.y()));
+ quaternion combined_rotation = rotation * rotation2;
+
+ quaternion identity;
+ key_transforms[kHeadKey] = key_transforms[kHeadKey] * inv_skeleton_bind_transforms[ik_chain_elements[ik_chain_start_index[kHeadIK]]];
+ BoneTransform neck_mat = rel_mat * rigged_object_GetFrameMatrix(neck_bone);
+ neck_mat = mix(combined_rotation, identity, 0.5f) * neck_mat;
+ rigged_object_SetFrameMatrix(neck_bone, neck_mat);
+ key_transforms[kHeadKey].origin -= neck;
+ key_transforms[kHeadKey] = mix(combined_rotation, identity, 0.5f) * key_transforms[kHeadKey];
+ key_transforms[kHeadKey].rotation = mix(combined_rotation, identity, 0.5f) * key_transforms[kHeadKey].rotation;
+ key_transforms[kHeadKey].origin += neck;
+ key_transforms[kHeadKey] = key_transforms[kHeadKey] * skeleton_bind_transforms[ik_chain_elements[ik_chain_start_index[kHeadIK]]];
+}
+
+static void CDoHeadIK(MovementObject* mo, float head_tilt_offset, float angle_threshold, float head_damping, float head_accel_inertia, float head_accel_damping, float head_stiffness, int num_frames) {
+ PROFILER_ZONE(g_profiler_ctx, "C++ Do Head IK")
+ MovementObject& this_mo = *mo;
+ RiggedObject& rigged_object = *this_mo.rigged_object();
+
+ PROFILER_ENTER(g_profiler_ctx, "Get AS pointers");
+ ASContext* as_context = this_mo.as_context.get();
+ float& time = game_timer.game_time;
+ float& time_step = game_timer.timestep;
+ Species& species = *(Species*)as_context->module.GetVarPtrCache("species");
+ float& flip_ik_fade = *(float*)as_context->module.GetVarPtrCache("flip_ik_fade");
+ int& queue_reset_secondary_animation = *(int*)as_context->module.GetVarPtrCache("queue_reset_secondary_animation");
+ bool& dialogue_control = *(bool*)as_context->module.GetVarPtrCache("dialogue_control");
+ float& test_talking_amount = *(float*)as_context->module.GetVarPtrCache("test_talking_amount");
+
+ CScriptArrayWrapper<BoneTransform> key_transforms((CScriptArray*)as_context->module.GetVarPtrCache("key_transforms"));
+ CScriptArrayWrapper<int> ik_chain_start_index((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_start_index"));
+ CScriptArrayWrapper<int> ik_chain_elements((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_elements"));
+ CScriptArrayWrapper<BoneTransform> skeleton_bind_transforms((CScriptArray*)as_context->module.GetVarPtrCache("skeleton_bind_transforms"));
+ CScriptArrayWrapper<BoneTransform> inv_skeleton_bind_transforms((CScriptArray*)as_context->module.GetVarPtrCache("inv_skeleton_bind_transforms"));
+
+ vec3& old_head_facing = *(vec3*)as_context->module.GetVarPtrCache("old_head_facing");
+ vec2& old_angle = *(vec2*)as_context->module.GetVarPtrCache("old_angle");
+ vec2& head_angle = *(vec2*)as_context->module.GetVarPtrCache("head_angle");
+ vec2& target_head_angle = *(vec2*)as_context->module.GetVarPtrCache("target_head_angle");
+ vec2& head_angle_vel = *(vec2*)as_context->module.GetVarPtrCache("head_angle_vel");
+ vec2& head_angle_accel = *(vec2*)as_context->module.GetVarPtrCache("head_angle_accel");
+ float& old_head_angle = *(float*)as_context->module.GetVarPtrCache("old_head_angle");
+ vec3& head_look = *(vec3*)as_context->module.GetVarPtrCache("head_look");
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ CDoHeadIKImpl(
+ rigged_object,
+ time, time_step, old_head_facing, old_angle, head_angle, target_head_angle, head_angle_vel, head_angle_accel, old_head_angle, head_look, species, flip_ik_fade, queue_reset_secondary_animation, dialogue_control, test_talking_amount,
+ key_transforms, ik_chain_start_index, ik_chain_elements, skeleton_bind_transforms, inv_skeleton_bind_transforms,
+ head_tilt_offset, angle_threshold, head_damping, head_accel_inertia, head_accel_damping, head_stiffness, num_frames);
+}
+
+static void CDoFootIKImpl(
+ RiggedObject& rigged_object,
+ Skeleton& skeleton,
+ ASCollisions& col,
+ float& roll_ik_fade,
+ bool& balancing,
+ CScriptArrayWrapper<vec3>& foot_info_pos,
+ CScriptArrayWrapper<float>& foot_info_height,
+ vec3& balance_pos,
+ CScriptArrayWrapper<float>& old_foot_offset,
+ CScriptArrayWrapper<std::string>& legs,
+ bool& ik_failed,
+ CScriptArrayWrapper<quaternion>& old_foot_rotate,
+ CScriptArrayWrapper<BoneTransform>& key_transforms,
+ CScriptArrayWrapper<int>& ik_chain_start_index,
+ CScriptArrayWrapper<int>& ik_chain_elements,
+ CScriptArrayWrapper<BoneTransform>& skeleton_bind_transforms,
+ CScriptArrayWrapper<BoneTransform>& inv_skeleton_bind_transforms,
+ const BoneTransform& local_to_world, int num_frames) {
+ SphereCollision& sphere_col = col.as_col;
+
+ bool ground_collision[2] = { false, false };
+ vec3 offset[2];
+ vec3 foot_balance_offset;
+
+ for (int j = 0; j < 2; ++j) {
+ // Get initial foot information
+ std::string& ik_label = legs[j];
+ BoneTransform mat = local_to_world * GetIKTransform(&rigged_object, ik_label) * skeleton_bind_transforms[ik_chain_elements[ik_chain_start_index[kLeftLegIK + j]]];
+ int bone = ASIKBoneStart(&skeleton, ik_label);
+ float weight = rigged_object.GetIKWeight(ik_label);
+ vec3 anim_pos = GetUnmodifiedIKTransform(&rigged_object, ik_label).GetTranslationPart();
+ weight *= (1.0f - roll_ik_fade);
+
+ if (weight > 0.0f) {
+ vec3 foot_center = (skeleton.points[skeleton.bones[bone].points[0]] +
+ skeleton.points[skeleton.bones[bone].points[1]]) * 0.5f;
+
+ BoneTransform transform = mix(key_transforms[kLeftLegKey + j], mat, weight);
+ vec3 pos = mat * foot_center;
+ vec3 check_pos = pos + foot_info_pos[j];
+
+ if (balancing) {
+ ground_collision[j] = true;
+ float ground_height = balance_pos[1] - 0.01f;
+
+ if (old_foot_offset[j] != 0.0f) {
+ old_foot_offset[j] = min(ground_height + 0.1f, max(ground_height - 0.1f, old_foot_offset[j]));
+ ground_height = mix(ground_height, old_foot_offset[j], (float)pow(0.8, num_frames));
+ }
+
+ old_foot_offset[j] = ground_height;
+ float ground_offset = ground_height - pos.y();
+ vec3 new_pos = pos + vec3(0, ground_offset, 0);
+
+ vec3 vec(1, 0, 0);
+ quaternion quat(vec4(0.0f, 1.0f, 0.0f, 3.1417f * 0.25f));
+
+ for (int i = 0; i < 8; ++i) {
+ col.ASGetSweptSphereCollision(balance_pos + vec * 0.4f,
+ balance_pos,
+ 0.05f);
+
+ if (sphere_col.NumContacts() > 0) {
+ // DebugDrawWireSphere(sphere_col.position, 0.05f, vec3(1.0), _delete_on_draw);
+
+ if (dot(sphere_col.position, vec) - 0.05f < dot(new_pos, vec)) {
+ new_pos += vec * (dot(sphere_col.position, vec) - 0.05f - dot(new_pos, vec));
+ }
+ }
+
+ vec = quat * vec;
+ }
+
+ new_pos.y() += anim_pos.y() - 0.05f;
+ new_pos.y() += foot_info_height[j];
+ new_pos.x() += foot_info_pos[j].x();
+ new_pos.z() += foot_info_pos[j].z();
+ offset[j] = (new_pos - pos) * weight;
+ foot_balance_offset += offset[j] * 0.5;
+ transform.origin += offset[j];
+ }
+ else {
+ // Check where ground is under foot
+ col.ASGetSweptSphereCollision(check_pos + vec3(0.0f, 0.2f, 0.0f),
+ check_pos + vec3(0.0f, -0.6f, 0.0f),
+ 0.05f);
+
+ if (sphere_col.NumContacts() > 0) {
+ ground_collision[j] = true;
+ float ground_height = sphere_col.position.y() - 0.01f;
+
+ if (old_foot_offset[j] != 0.0f) {
+ old_foot_offset[j] = min(ground_height + 0.1f, max(ground_height - 0.1f, old_foot_offset[j]));
+ ground_height = mix(ground_height, old_foot_offset[j], (float)pow(0.8, num_frames));
+ }
+
+ old_foot_offset[j] = ground_height;
+ float ground_offset = ground_height - pos.y();
+ vec3 new_pos = pos + vec3(0, ground_offset, 0);
+ new_pos.y() += anim_pos.y() - 0.05f;
+ new_pos.y() += foot_info_height[j];
+ new_pos.x() += foot_info_pos[j].x();
+ new_pos.z() += foot_info_pos[j].z();
+ offset[j] = (new_pos - pos) * weight;
+ transform.origin += offset[j];
+ }
+ else {
+ ik_failed = true;
+ }
+ }
+
+ // Check ground normal
+ col.ASGetSlidingSphereCollision(sphere_col.position + vec3(0.0f, -0.01f, 0.0f), 0.05f);
+ vec3 normal = normalize(sphere_col.adjusted_position - sphere_col.position);
+
+ float rotate_weight = 1.0f;
+ rotate_weight -= (anim_pos.y() - 0.05f) * 4.0f;
+ rotate_weight = max(0.0f, rotate_weight);
+
+ quaternion rotation;
+ GetRotationBetweenVectors(vec3(0.0f, 1.0f, 0.0f), normal, rotation);
+ rotation = mix(rotation, old_foot_rotate[j], (float)pow(0.8, num_frames));
+ old_foot_rotate[j] = rotation;
+ quaternion identity;
+ rotation = mix(identity, rotation, weight * rotate_weight);
+
+ transform = transform * inv_skeleton_bind_transforms[bone];
+
+ if (!balancing) {
+ transform.rotation = rotation * transform.rotation;
+ }
+
+ transform = transform * skeleton_bind_transforms[bone];
+ key_transforms[kLeftLegKey + j] = transform;
+ }
+ }
+
+ for (int i = 0; i < 2; ++i) {
+ int other = (i + 1) % 2;
+
+ if (ground_collision[i] && !ground_collision[other]) {
+ key_transforms[kLeftLegKey + other].origin += offset[i];
+ }
+ }
+
+ if (balancing) {
+ key_transforms[kHipKey].origin += foot_balance_offset * 0.7f;
+ key_transforms[kChestKey].origin += foot_balance_offset * 0.5f;
+ key_transforms[kHeadKey].origin += foot_balance_offset * 0.4f;
+ key_transforms[kLeftArmKey].origin += foot_balance_offset * 1.0f;
+ key_transforms[kRightArmKey].origin += foot_balance_offset * 1.0f;
+ }
+}
+
+static void CDoFootIK(MovementObject* mo, const BoneTransform& local_to_world, int num_frames) {
+ PROFILER_ZONE(g_profiler_ctx, "C++ Do Foot IK")
+ MovementObject& this_mo = *mo;
+ RiggedObject& rigged_object = *this_mo.rigged_object();
+ Skeleton& skeleton = rigged_object.skeleton();
+ ASCollisions* col = mo->as_collisions.get();
+
+ PROFILER_ENTER(g_profiler_ctx, "Get AS pointers");
+ ASContext* as_context = this_mo.as_context.get();
+ float& roll_ik_fade = *(float*)as_context->module.GetVarPtrCache("roll_ik_fade");
+ bool& balancing = *(bool*)as_context->module.GetVarPtrCache("balancing");
+ vec3& balance_pos = *(vec3*)as_context->module.GetVarPtrCache("balance_pos");
+ bool& ik_failed = *(bool*)as_context->module.GetVarPtrCache("ik_failed");
+
+ CScriptArrayWrapper<BoneTransform> key_transforms((CScriptArray*)as_context->module.GetVarPtrCache("key_transforms"));
+ CScriptArrayWrapper<int> ik_chain_start_index((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_start_index"));
+ CScriptArrayWrapper<int> ik_chain_elements((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_elements"));
+ CScriptArrayWrapper<float> ik_chain_bone_lengths((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_bone_lengths"));
+ CScriptArrayWrapper<BoneTransform> skeleton_bind_transforms((CScriptArray*)as_context->module.GetVarPtrCache("skeleton_bind_transforms"));
+ CScriptArrayWrapper<BoneTransform> inv_skeleton_bind_transforms((CScriptArray*)as_context->module.GetVarPtrCache("inv_skeleton_bind_transforms"));
+
+ CScriptArrayWrapper<vec3> foot_info_pos((CScriptArray*)as_context->module.GetVarPtrCache("foot_info_pos")); // TODO: Does this work???
+ CScriptArrayWrapper<float> foot_info_height((CScriptArray*)as_context->module.GetVarPtrCache("foot_info_height")); // TODO: Does this work???
+ CScriptArrayWrapper<float> old_foot_offset((CScriptArray*)as_context->module.GetVarPtrCache("old_foot_offset"));
+ CScriptArrayWrapper<std::string> legs((CScriptArray*)as_context->module.GetVarPtrCache("legs"));
+ CScriptArrayWrapper<quaternion> old_foot_rotate((CScriptArray*)as_context->module.GetVarPtrCache("old_foot_rotate")); // TODO: Does this work???
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ CDoFootIKImpl( // TODO: Verfiy that all the variables are actually getting grabbed from AS correctly
+ rigged_object, skeleton, *col,
+ roll_ik_fade, balancing, foot_info_pos, foot_info_height, balance_pos, old_foot_offset, legs, ik_failed, old_foot_rotate,
+ key_transforms, ik_chain_start_index, ik_chain_elements, skeleton_bind_transforms, inv_skeleton_bind_transforms,
+ local_to_world, num_frames);
+}
+
+#define skeleton_GetParent( A ) (skeleton.parents[A])
+
+static void DoHipIKImpl(
+ MovementObject* mo,
+ CScriptArrayWrapper<float>& target_leg_length,
+ vec3& old_hip_offset,
+ CScriptArrayWrapper<BoneTransform>& key_transforms,
+ CScriptArrayWrapper<int>& ik_chain_start_index,
+ CScriptArrayWrapper<int>& ik_chain_elements,
+ CScriptArrayWrapper<int>& ik_chain_length,
+ BoneTransform& hip_offset, quaternion& hip_rotate, int num_frames) {
+ MovementObject& this_mo = *mo;
+ RiggedObject& rigged_object = *this_mo.rigged_object();
+ Skeleton& skeleton = rigged_object.skeleton();
+
+ vec3 temp_foot_pos[2];
+ vec3 hip_pos[2];
+ vec3 orig_hip_pos[2];
+
+ for (int j = 0; j < 2; ++j) {
+ int bone = ik_chain_start_index[kLeftLegIK + j];
+ int bone_len = ik_chain_length[kLeftLegIK + j];;
+ temp_foot_pos[j] = key_transforms[kLeftLegKey + j] * skeleton.points[skeleton.bones[ik_chain_elements[bone]].points[0]];
+ hip_pos[j] = key_transforms[kHipKey] * skeleton.points[skeleton.bones[ik_chain_elements[bone + bone_len - 1]].points[0]];
+ orig_hip_pos[j] = hip_pos[j];
+ }
+
+ vec3 orig_mid = (orig_hip_pos[0] + orig_hip_pos[1]) * 0.5f;
+
+ for (int i = 0; i < 2; ++i) {
+ float hip_dist = distance(hip_pos[0], hip_pos[1]);
+
+ for (int j = 0; j < 2; ++j) {
+ hip_pos[j] = temp_foot_pos[j] + normalize(hip_pos[j] - temp_foot_pos[j]) * target_leg_length[j];
+ }
+
+ vec3 mid = (hip_pos[0] + hip_pos[1]) * 0.5f;
+ mid = vec3(orig_mid.x(), mid.y(), orig_mid.z());
+ vec3 dir = normalize((hip_pos[1] - hip_pos[0]) + (orig_hip_pos[1] - orig_hip_pos[0]) * 3.0f);
+ hip_pos[0] = mid + dir * hip_dist * -0.5f;
+ hip_pos[1] = mid + dir * hip_dist * 0.5f;
+ }
+
+ quaternion rotation;
+
+ {
+ vec3 orig_hip_vec = normalize(orig_hip_pos[1] - orig_hip_pos[0]);
+ vec3 new_hip_vec = normalize(hip_pos[1] - hip_pos[0]);
+ vec3 rotate_axis = normalize(cross(orig_hip_vec, new_hip_vec));
+ vec3 right_vec = cross(orig_hip_vec, rotate_axis);
+ float rotate_angle = atan2(-dot(new_hip_vec, right_vec), dot(new_hip_vec, orig_hip_vec));
+ float flat_speed = sqrt(this_mo.velocity.x() * this_mo.velocity.x() + this_mo.velocity.z() * this_mo.velocity.z());
+ float max_rotate_angle = max(0.0f, 0.2f - flat_speed * 0.2f);
+ rotate_angle = max(-max_rotate_angle, min(max_rotate_angle, rotate_angle));
+ rotation = quaternion(vec4(rotate_axis, rotate_angle));
+ }
+
+ hip_rotate = rotation;
+ int hip_bone = ASIKBoneStart(&skeleton, "torso");
+ int hip_bone_len = ASIKBoneLength(&skeleton, "torso");
+
+ for (int i = 0; i < hip_bone_len - 1; ++i) {
+ hip_bone = skeleton_GetParent(hip_bone);
+ }
+
+ vec3 hip_root = key_transforms[kHipKey] * ((skeleton.points[skeleton.bones[hip_bone].points[1]]));
+ vec3 orig_hip_offset = (orig_hip_pos[0] + orig_hip_pos[1]) * 0.5f - hip_root;
+ hip_offset.origin = (hip_pos[0] + hip_pos[1]) * 0.5f - (orig_hip_pos[0] + orig_hip_pos[1]) * 0.5f + orig_hip_offset - hip_rotate * orig_hip_offset;
+ vec3 temp = hip_offset.origin;
+
+ if (old_hip_offset == vec3(0.0f)) {
+ old_hip_offset = temp;
+ }
+
+ hip_offset.origin = mix(temp, old_hip_offset, pow(0.95f, num_frames));
+ old_hip_offset = hip_offset.origin;
+}
+
+static void CDoHipIK(MovementObject* mo, BoneTransform& hip_offset, quaternion& hip_rotate, int num_frames) {
+ PROFILER_ZONE(g_profiler_ctx, "C++ Do Hip IK")
+ MovementObject& this_mo = *mo;
+
+ PROFILER_ENTER(g_profiler_ctx, "Get AS pointers");
+ ASContext* as_context = this_mo.as_context.get();
+
+ CScriptArrayWrapper<BoneTransform> key_transforms((CScriptArray*)as_context->module.GetVarPtrCache("key_transforms"));
+ CScriptArrayWrapper<int> ik_chain_start_index((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_start_index"));
+ CScriptArrayWrapper<int> ik_chain_elements((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_elements"));
+ CScriptArrayWrapper<int> ik_chain_length((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_length"));
+
+ CScriptArrayWrapper<float> target_leg_length((CScriptArray*)as_context->module.GetVarPtrCache("target_leg_length"));
+ vec3& old_hip_offset = *(vec3*)as_context->module.GetVarPtrCache("old_hip_offset");
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ DoHipIKImpl(
+ mo,
+ target_leg_length, old_hip_offset,
+ key_transforms, ik_chain_start_index, ik_chain_elements, ik_chain_length,
+ hip_offset, hip_rotate, num_frames);
+}
+
+static void CDrawLegImpl(
+ RiggedObject& rigged_object,
+ Skeleton& skeleton,
+ CScriptArrayWrapper<int>& ik_chain_start_index,
+ CScriptArrayWrapper<int>& ik_chain_elements,
+ CScriptArrayWrapper<float>& ik_chain_bone_lengths,
+ CScriptArrayWrapper<BoneTransform>& skeleton_bind_transforms,
+ CScriptArrayWrapper<BoneTransform>& inv_skeleton_bind_transforms,
+ bool right, const BoneTransform& hip_transform, const BoneTransform& foot_transform) {
+
+ int ik_chain_start = ik_chain_start_index[kLeftLegIK + (right ? 1 : 0)];
+
+ // Get important joint positions
+ vec3 foot_tip, foot_base, ankle, knee, hip;
+ foot_tip = GetTransformedBonePoint(&rigged_object, ik_chain_elements[ik_chain_start + 0], 1);
+ foot_base = GetTransformedBonePoint(&rigged_object, ik_chain_elements[ik_chain_start + 0], 0);
+ ankle = GetTransformedBonePoint(&rigged_object, ik_chain_elements[ik_chain_start + 1], 0);
+ knee = GetTransformedBonePoint(&rigged_object, ik_chain_elements[ik_chain_start + 3], 0);
+ hip = GetTransformedBonePoint(&rigged_object, ik_chain_elements[ik_chain_start + 5], 0);
+
+ vec3 old_foot_dir = foot_tip - foot_base;
+ float upper_foot_length = ik_chain_bone_lengths[ik_chain_start + 1];
+ float lower_leg_length = (ik_chain_bone_lengths[ik_chain_start + 2] + ik_chain_bone_lengths[ik_chain_start + 3]);
+ float upper_leg_length = (ik_chain_bone_lengths[ik_chain_start + 4] + ik_chain_bone_lengths[ik_chain_start + 5]);
+
+ float lower_leg_weight = ik_chain_bone_lengths[ik_chain_start + 2] / lower_leg_length;
+ float upper_leg_weight = ik_chain_bone_lengths[ik_chain_start + 4] / upper_leg_length;
+
+ vec3 old_hip = hip;
+ float old_length = distance(foot_base, old_hip);
+
+ // New hip position based on key hip transform
+ hip = hip_transform * skeleton.points[skeleton.bones[ik_chain_elements[ik_chain_start + 5]].points[0]];
+
+ // New foot_base position based on key foot transform
+ vec3 ik_target = foot_transform * skeleton.points[skeleton.bones[ik_chain_elements[ik_chain_start + 0]].points[0]];
+
+ quaternion rotate;
+ GetRotationBetweenVectors(foot_base - old_hip, ik_target - hip, rotate);
+ mat3 rotate_mat = Mat3FromQuaternion(rotate);
+ knee = rotate_mat * (knee - old_hip) + hip;
+ ankle = rotate_mat * (ankle - old_hip) + hip;
+ foot_base = rotate_mat * (foot_base - old_hip) + hip;
+ float new_length = distance(ik_target, hip);
+ float weight = new_length / old_length;
+ knee = mix(hip, knee, weight);
+ ankle = mix(hip, ankle, weight);
+ foot_base = mix(hip, foot_base, weight);
+ foot_tip = foot_transform * skeleton.points[skeleton.bones[ik_chain_elements[ik_chain_start + 0]].points[1]];
+
+ int num_iterations = 2;
+
+ for (int i = 0; i < num_iterations; ++i) {
+ knee = hip + normalize(knee - hip) * upper_leg_length;
+ ankle = foot_base + normalize(ankle - foot_base) * upper_foot_length;
+ vec3 knee_ankle_vec = normalize(knee - ankle);
+ vec3 mid = (knee + ankle) * 0.5f;
+ ankle = mid - knee_ankle_vec * 0.5f * lower_leg_length;
+ knee = mid + knee_ankle_vec * 0.5f * lower_leg_length;
+ }
+
+ rigged_object.animation_frame_bone_matrices[ik_chain_elements[ik_chain_start + 0]] = foot_transform * inv_skeleton_bind_transforms[ik_chain_elements[ik_chain_start + 0]];
+
+ RotateBoneToMatchVec(&rigged_object, foot_base, foot_tip, ik_chain_elements[ik_chain_start + 0]);
+ RotateBoneToMatchVec(&rigged_object, ankle, foot_base, ik_chain_elements[ik_chain_start + 1]);
+ RotateBonesToMatchVec(&rigged_object, knee, ankle, ik_chain_elements[ik_chain_start + 3], ik_chain_elements[ik_chain_start + 2], lower_leg_weight);
+ RotateBonesToMatchVec(&rigged_object, hip, knee, ik_chain_elements[ik_chain_start + 5], ik_chain_elements[ik_chain_start + 4], upper_leg_weight);
+}
+
+static void CDrawLeg(MovementObject* mo, bool right, const BoneTransform& hip_transform, const BoneTransform& foot_transform, int num_frames) {
+ PROFILER_ZONE(g_profiler_ctx, "C++ Draw Leg")
+ MovementObject& this_mo = *mo;
+ RiggedObject& rigged_object = *this_mo.rigged_object();
+ Skeleton& skeleton = rigged_object.skeleton();
+
+ PROFILER_ENTER(g_profiler_ctx, "Get AS pointers");
+ ASContext* as_context = this_mo.as_context.get();
+ CScriptArrayWrapper<int> ik_chain_start_index((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_start_index"));
+ CScriptArrayWrapper<int> ik_chain_elements((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_elements"));
+ CScriptArrayWrapper<float> ik_chain_bone_lengths((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_bone_lengths"));
+ CScriptArrayWrapper<BoneTransform> skeleton_bind_transforms((CScriptArray*)as_context->module.GetVarPtrCache("skeleton_bind_transforms"));
+ CScriptArrayWrapper<BoneTransform> inv_skeleton_bind_transforms((CScriptArray*)as_context->module.GetVarPtrCache("inv_skeleton_bind_transforms"));
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ CDrawLegImpl(
+ rigged_object, skeleton,
+ ik_chain_start_index, ik_chain_elements, ik_chain_bone_lengths, skeleton_bind_transforms, inv_skeleton_bind_transforms,
+ right, hip_transform, foot_transform);
+}
+
+static void CDrawHeadImpl(
+ RiggedObject& rigged_object,
+ Skeleton& skeleton,
+ float& breath_amount,
+ CScriptArrayWrapper<BoneTransform>& key_transforms,
+ CScriptArrayWrapper<BoneTransform>& inv_skeleton_bind_transforms,
+ CScriptArrayWrapper<int>& ik_chain_start_index,
+ CScriptArrayWrapper<int>& ik_chain_elements,
+ CScriptArrayWrapper<int>& bone_children,
+ CScriptArrayWrapper<int>& bone_children_index,
+ const BoneTransform& chest_transform, const BoneTransform& head_transform) {
+ int start = ik_chain_start_index[kHeadIK];
+ int head_bone = ik_chain_elements[start + 0];
+ int neck_bone = ik_chain_elements[start + 1];
+
+ int chest_bone = ik_chain_elements[ik_chain_start_index[kTorsoIK]];
+ BoneTransform chest_frame_matrix = rigged_object.animation_frame_bone_matrices[chest_bone];
+ BoneTransform chest_bind_matrix = inv_skeleton_bind_transforms[chest_bone];
+ BoneTransform rel_mat = chest_transform * invert(BoneTransform(chest_frame_matrix * chest_bind_matrix));
+
+ BoneTransform old_head_matrix = rigged_object.animation_frame_bone_matrices[head_bone];
+
+ vec3 crown, skull, neck;
+ crown = head_transform * skeleton.points[skeleton.bones[head_bone].points[1]];
+ skull = head_transform * skeleton.points[skeleton.bones[head_bone].points[0]];
+ neck = chest_transform * skeleton.points[skeleton.bones[neck_bone].points[0]];
+
+ BoneTransform world_chest = key_transforms[kChestKey] * inv_skeleton_bind_transforms[ik_chain_start_index[kTorsoIK]];
+ vec3 breathe_dir = world_chest.rotation * normalize(vec3(0.0f, -0.3f, 1.0f));
+ float scale = rigged_object.GetCharScale();
+ skull += breathe_dir * breath_amount * 0.005f * scale;
+ crown += breathe_dir * breath_amount * 0.002f * scale;
+
+ rigged_object.animation_frame_bone_matrices[head_bone] = head_transform * inv_skeleton_bind_transforms[head_bone];
+ RotateBoneToMatchVec(&rigged_object, neck, skull, neck_bone);
+ RotateBoneToMatchVec(&rigged_object, skull, crown, head_bone);
+
+ BoneTransform head_rel = rigged_object.animation_frame_bone_matrices[head_bone] * invert(old_head_matrix);
+
+ for (int i = 0, len = GetNumBoneChildren(bone_children_index.c_script_array_, head_bone); i < len; ++i) {
+ int child = bone_children[bone_children_index[head_bone] + i];
+ rigged_object.animation_frame_bone_matrices[child] = head_rel * rigged_object.animation_frame_bone_matrices[child];
+ }
+}
+
+static void CDrawHead(MovementObject* mo, const BoneTransform& chest_transform, const BoneTransform& head_transform, int num_frames) {
+ PROFILER_ZONE(g_profiler_ctx, "C++ Draw Head")
+ MovementObject& this_mo = *mo;
+ RiggedObject& rigged_object = *this_mo.rigged_object();
+ Skeleton& skeleton = rigged_object.skeleton();
+
+ PROFILER_ENTER(g_profiler_ctx, "Get AS pointers");
+ ASContext* as_context = this_mo.as_context.get();
+ float& breath_amount = *(float*)as_context->module.GetVarPtrCache("breath_amount");
+
+ CScriptArrayWrapper<BoneTransform> key_transforms((CScriptArray*)as_context->module.GetVarPtrCache("key_transforms"));
+ CScriptArrayWrapper<BoneTransform> inv_skeleton_bind_transforms((CScriptArray*)as_context->module.GetVarPtrCache("inv_skeleton_bind_transforms"));
+ CScriptArrayWrapper<int> ik_chain_start_index((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_start_index"));
+ CScriptArrayWrapper<int> ik_chain_elements((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_elements"));
+ CScriptArrayWrapper<int> bone_children((CScriptArray*)as_context->module.GetVarPtrCache("bone_children"));
+ CScriptArrayWrapper<int> bone_children_index((CScriptArray*)as_context->module.GetVarPtrCache("bone_children_index"));
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ CDrawHeadImpl(
+ rigged_object, skeleton,
+ breath_amount,
+ key_transforms, inv_skeleton_bind_transforms, ik_chain_start_index, ik_chain_elements, bone_children, bone_children_index,
+ chest_transform, head_transform);
+}
+
+static void CDrawBodyImpl(
+ RiggedObject& rigged_object,
+ Skeleton& skeleton,
+ CScriptArrayWrapper<BoneTransform>& inv_skeleton_bind_transforms,
+ CScriptArrayWrapper<int>& ik_chain_start_index,
+ CScriptArrayWrapper<int>& ik_chain_elements,
+ CScriptArrayWrapper<int>& ik_chain_length,
+ const BoneTransform& hip_transform, const BoneTransform& chest_transform) {
+ int start = ik_chain_start_index[kTorsoIK];
+
+ BoneTransform old_hip_matrix = rigged_object.animation_frame_bone_matrices[ik_chain_elements[start + 2]];
+
+ int chest_bone = ik_chain_elements[start + 0];
+ int abdomen_bone = ik_chain_elements[start + 1];
+ int hip_bone = ik_chain_elements[start + 2];
+
+ vec3 collarbone = chest_transform * skeleton.points[skeleton.bones[chest_bone].points[1]];
+ vec3 ribs = chest_transform * skeleton.points[skeleton.bones[chest_bone].points[0]];
+ vec3 stomach = hip_transform * skeleton.points[skeleton.bones[abdomen_bone].points[0]];
+ vec3 hips = hip_transform * skeleton.points[skeleton.bones[hip_bone].points[0]];
+
+ rigged_object.animation_frame_bone_matrices[chest_bone] = chest_transform * inv_skeleton_bind_transforms[chest_bone];
+ BoneTransform temp = mix(chest_transform, hip_transform, 0.5f);
+ temp.rotation = mix(hip_transform.rotation, chest_transform.rotation, 0.5f);
+ rigged_object.animation_frame_bone_matrices[abdomen_bone] = temp * inv_skeleton_bind_transforms[abdomen_bone];
+
+ RotateBoneToMatchVec(&rigged_object, ribs, collarbone, chest_bone);
+ RotateBoneToMatchVec(&rigged_object, stomach, ribs, abdomen_bone);
+ RotateBoneToMatchVec(&rigged_object, hips, stomach, hip_bone);
+
+ BoneTransform hip_rel = rigged_object.animation_frame_bone_matrices[hip_bone] * invert(old_hip_matrix);
+
+ start = ik_chain_start_index[kTailIK];
+ int len = ik_chain_length[kTailIK];
+
+ for (int i = 0; i < len; ++i) {
+ int bone = ik_chain_elements[start + i];
+ rigged_object.animation_frame_bone_matrices[bone] = hip_rel * rigged_object.animation_frame_bone_matrices[bone];
+ }
+}
+
+static void CDrawBody(MovementObject* mo, const BoneTransform& hip_transform, const BoneTransform& chest_transform) {
+ PROFILER_ZONE(g_profiler_ctx, "C++ Draw Body")
+ MovementObject& this_mo = *mo;
+ RiggedObject& rigged_object = *this_mo.rigged_object();
+ Skeleton& skeleton = rigged_object.skeleton();
+
+ PROFILER_ENTER(g_profiler_ctx, "Get AS pointers");
+ ASContext* as_context = this_mo.as_context.get();
+ CScriptArrayWrapper<BoneTransform> inv_skeleton_bind_transforms((CScriptArray*)as_context->module.GetVarPtrCache("inv_skeleton_bind_transforms"));
+ CScriptArrayWrapper<int> ik_chain_start_index((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_start_index"));
+ CScriptArrayWrapper<int> ik_chain_elements((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_elements"));
+ CScriptArrayWrapper<int> ik_chain_length((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_length"));
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ CDrawBodyImpl(
+ rigged_object, skeleton,
+ inv_skeleton_bind_transforms, ik_chain_start_index, ik_chain_elements, ik_chain_length,
+ hip_transform, chest_transform);
+}
+
+void CDrawEarImpl(
+ MovementObject* mo,
+ float& time,
+ float& time_step,
+ int& species,
+ int& skip_ear_physics_counter,
+ bool& flip_info_flipping,
+ bool& on_ground,
+ CScriptArrayWrapper<int>& ik_chain_start_index,
+ CScriptArrayWrapper<int>& ik_chain_elements,
+ CScriptArrayWrapper<float>& ear_rotation,
+ CScriptArrayWrapper<float>& ear_rotation_time,
+ CScriptArrayWrapper<float>& target_ear_rotation,
+ CScriptArrayWrapper<BoneTransform>& inv_skeleton_bind_transforms,
+ CScriptArrayWrapper<vec3>& ear_points,
+ CScriptArrayWrapper<vec3>& old_ear_points,
+ CScriptArrayWrapper<vec3>& temp_old_ear_points,
+ bool right, const BoneTransform& head_transform, int num_frames) {
+ MovementObject& this_mo = *mo;
+ RiggedObject& rigged_object = *this_mo.rigged_object();
+
+ int chain_start = ik_chain_start_index[kLeftEarIK+(right?1:0)];
+
+ vec3 tip, middle, base;
+ tip = rigged_object_GetTransformedBonePoint(ik_chain_elements[chain_start+0], 1);
+ middle = rigged_object_GetTransformedBonePoint(ik_chain_elements[chain_start+0], 0);
+ base = rigged_object_GetTransformedBonePoint(ik_chain_elements[chain_start+1], 0);
+
+ if(ear_rotation.size()==0){
+ ear_rotation.resize(2);
+ ear_rotation_time.resize(2);
+ target_ear_rotation.resize(2);
+ for(int i=0; i<2; ++i){
+ target_ear_rotation[i] = 0.0f;
+ ear_rotation[i] = 0.0f;
+ ear_rotation_time[i] = 0.0f;
+ }
+ } else {
+ int which = right?1:0;
+ if(ear_rotation_time[which] < time){
+ target_ear_rotation[which] = RangedRandomFloat(-0.4f, 0.8f);
+ ear_rotation_time[which] = time+RangedRandomFloat(0.7f, 4.0f);
+ }
+ ear_rotation[which] = mix(target_ear_rotation[which], ear_rotation[which], pow(0.9f,num_frames));
+ }
+
+ bool ear_rotate_test = true;
+ if(ear_rotate_test){
+ //vec3 head_up = head_transform.rotation * inv_skeleton_bind_transforms[ik_chain_elements[ik_chain_start_index[kHeadIK]]].rotation * vec3(0,0,1);
+ vec3 head_dir = head_transform.rotation * inv_skeleton_bind_transforms[ik_chain_elements[ik_chain_start_index[kHeadIK]]].rotation * vec3(0,1,0);
+ //vec3 head_right = cross(head_dir, head_up);
+ //float ear_back_amount = 2.5f;
+ //float ear_twist_amount = 3.0f * (right?-1.0f:1.0f);
+ float ear_back_amount = 0.0f;
+ float ear_twist_amount = ear_rotation[right?1:0];
+ if(right){
+ ear_twist_amount *= -1.0f;
+ }
+ if(species != _rabbit){
+ ear_twist_amount *= 0.3f;
+ }
+ BoneTransform ear_back_mat;
+ vec3 ear_right = normalize(cross(head_dir, middle-base));
+ ear_back_mat.rotation = quaternion(vec4(ear_right, ear_back_amount));
+ BoneTransform ear_twist_mat;
+ ear_twist_mat.rotation = quaternion(vec4(ear_back_mat.rotation*normalize(middle-base), ear_twist_amount));
+ BoneTransform base_offset;
+ base_offset.origin = base;
+ BoneTransform ear_transform = base_offset * ear_twist_mat * ear_back_mat * invert(base_offset);
+ BoneTransform tip_ear_transform = base_offset * ear_twist_mat * ear_twist_mat * ear_back_mat * invert(base_offset);
+ rigged_object_SetFrameMatrix(ik_chain_elements[chain_start+0], tip_ear_transform * rigged_object_GetFrameMatrix(ik_chain_elements[chain_start+0]));
+ rigged_object_SetFrameMatrix(ik_chain_elements[chain_start+1], ear_transform * rigged_object_GetFrameMatrix(ik_chain_elements[chain_start+1]));
+
+ tip = rigged_object_GetTransformedBonePoint(ik_chain_elements[chain_start+0], 1);
+ middle = rigged_object_GetTransformedBonePoint(ik_chain_elements[chain_start+0], 0);
+ }
+
+ float low_dist = distance(base, middle);
+ float high_dist = distance(middle, tip);
+
+ old_ear_points.resize(6);
+ temp_old_ear_points.resize(6);
+ if(ear_points.size()!=6){
+ ear_points.push_back(base);
+ ear_points.push_back(middle);
+ ear_points.push_back(tip);
+ for(int i=0, len=ear_points.size(); i<len; ++i){
+ old_ear_points[i] = ear_points[i];
+ temp_old_ear_points[i] = ear_points[i];
+ }
+ } else {
+ int start = right?3:0;
+
+ for(int i=0; i<3; ++i){
+ temp_old_ear_points[start+i] = ear_points[start+i];
+ }
+
+ //The following contains ear physics, they are acting odd in cutscenes, therefore we disabled them during them.
+ //One of the values in this routine goes insane for some reason.
+
+ float ear_damping = 0.95f;
+ float low_ear_rotation_damping = 0.9f;
+ float up_ear_rotation_damping = 0.92f;
+
+ ear_points[start+0] = base;
+ vec3 vel_offset = this_mo.velocity * time_step * (float)num_frames;
+
+ if( length(ear_points[start+0]-old_ear_points[start+0]) > 10.0f )
+ {
+ //Log(warning, "Massive movement in ear_position detected, skipping ear physics for a couple of frames until position is gussed to have returned to stable. TODO\n");
+ //TODO: Find the source of the massive position change, and fix it, often caused by something
+ //when using the dialogue editor
+ skip_ear_physics_counter = 5;
+ }
+
+ if( skip_ear_physics_counter == 0 )
+ {
+ ear_points[start+1] += (((ear_points[start+1] - old_ear_points[start+1]) - vel_offset) * pow(ear_damping, num_frames) + vel_offset) * ear_damping * ear_damping;
+ ear_points[start+2] += (((ear_points[start+2] - old_ear_points[start+2]) - vel_offset) * pow(ear_damping, num_frames) + vel_offset) * ear_damping * ear_damping;
+ quaternion rotation;
+ GetRotationBetweenVectors(middle-base, ear_points[start+1]-base, rotation);
+ vec3 rotated_tip = ASMult(rotation, tip-middle)+ear_points[start+1];
+ ear_points[start+2] += (rotated_tip - ear_points[start+2]) * (1.0f - pow(up_ear_rotation_damping, num_frames));
+ ear_points[start+1] += (middle - ear_points[start+1]) * (1.0f - pow(low_ear_rotation_damping, num_frames));
+
+ for(int i=0; i<3; ++i){
+ ear_points[start+1] = base + normalize(ear_points[start+1]-base) * low_dist;
+ vec3 mid = (ear_points[start+1] + ear_points[start+2])*0.5f;
+ vec3 dir = normalize(ear_points[start+1] - ear_points[start+2]);
+ ear_points[start+2] = mid - dir * high_dist * 0.5f;
+ ear_points[start+1] = mid + dir * high_dist * 0.5f;
+ }
+
+ if(flip_info_flipping && on_ground) {
+ ASCollisions* col = mo->as_collisions.get();
+ col->GetSweptSphereCollision(ear_points[start+0], ear_points[start+1], 0.03f, col->as_col);
+ ear_points[start+1] = col->as_col.adjusted_position;
+ col->GetSweptSphereCollision(ear_points[start+1], ear_points[start+2], 0.03f, col->as_col);
+ ear_points[start+2] = col->as_col.adjusted_position;
+ }
+
+ //debug_lines.push_back(DebugDrawLine(ear_points[start+0], ear_points[start+1], vec3(1.0f), _fade));
+ //debug_lines.push_back(DebugDrawLine(ear_points[start+1], ear_points[start+2], vec3(1.0f), _fade));
+ rigged_object_RotateBoneToMatchVec(ear_points[start+0], ear_points[start+1], ik_chain_elements[chain_start+1]);
+ rigged_object_RotateBoneToMatchVec(ear_points[start+1], ear_points[start+2], ik_chain_elements[chain_start+0]);
+ middle = ear_points[start+1];
+ tip = ear_points[start+2];
+ }
+ else
+ {
+ skip_ear_physics_counter--;
+ }
+
+ for(int i=0; i<3; ++i){
+ old_ear_points[start+i] = temp_old_ear_points[start+i];
+ }
+ }
+}
+
+void CDrawEar(MovementObject* mo, bool right, const BoneTransform& head_transform, int num_frames) {
+ PROFILER_ZONE(g_profiler_ctx, "C++ DrawEar");
+ MovementObject& this_mo = *mo;
+
+ PROFILER_ENTER(g_profiler_ctx, "Get AS pointers");
+ ASContext* as_context = this_mo.as_context.get();
+ float& time = game_timer.game_time;
+ float& time_step = game_timer.timestep;
+ int& species = *(int*)as_context->module.GetVarPtrCache("species");
+ int& skip_ear_physics_counter = *(int*)as_context->module.GetVarPtrCache("skip_ear_physics_counter");
+ bool& flip_info_flipping = *(bool*)((asIScriptObject*)as_context->module.GetVarPtrCache("flip_info"))->GetAddressOfProperty(0);
+ bool& on_ground = *(bool*)as_context->module.GetVarPtrCache("on_ground");
+
+ CScriptArrayWrapper<int> ik_chain_start_index((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_start_index"));
+ CScriptArrayWrapper<int> ik_chain_elements((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_elements"));
+ CScriptArrayWrapper<float> ear_rotation((CScriptArray*)as_context->module.GetVarPtrCache("ear_rotation"));
+ CScriptArrayWrapper<float> ear_rotation_time((CScriptArray*)as_context->module.GetVarPtrCache("ear_rotation_time"));
+ CScriptArrayWrapper<float> target_ear_rotation((CScriptArray*)as_context->module.GetVarPtrCache("target_ear_rotation"));
+ CScriptArrayWrapper<BoneTransform> inv_skeleton_bind_transforms((CScriptArray*)as_context->module.GetVarPtrCache("inv_skeleton_bind_transforms"));
+ CScriptArrayWrapper<vec3> ear_points((CScriptArray*)as_context->module.GetVarPtrCache("ear_points"));
+ CScriptArrayWrapper<vec3> old_ear_points((CScriptArray*)as_context->module.GetVarPtrCache("old_ear_points"));
+ CScriptArrayWrapper<vec3> temp_old_ear_points((CScriptArray*)as_context->module.GetVarPtrCache("temp_old_ear_points"));
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ CDrawEarImpl(
+ mo,
+ time, time_step, species, skip_ear_physics_counter, flip_info_flipping, on_ground,
+ ik_chain_start_index, ik_chain_elements, ear_rotation, ear_rotation_time, target_ear_rotation, inv_skeleton_bind_transforms, ear_points, old_ear_points, temp_old_ear_points,
+ right, head_transform, num_frames);
+}
+
+void CDrawTailImpl(
+ MovementObject* mo,
+ float& time,
+ float& time_step,
+ CScriptArrayWrapper<int>& ik_chain_elements,
+ CScriptArrayWrapper<int>& ik_chain_start_index,
+ CScriptArrayWrapper<int>& ik_chain_length,
+ CScriptArrayWrapper<vec3>& tail_points,
+ CScriptArrayWrapper<vec3>& old_tail_points,
+ CScriptArrayWrapper<vec3>& temp_old_tail_points,
+ CScriptArrayWrapper<vec3>& tail_correction,
+ CScriptArrayWrapper<float>& tail_section_length,
+ int num_frames) {
+ MovementObject& this_mo = *mo;
+ RiggedObject& rigged_object = *this_mo.rigged_object();
+ Skeleton& skeleton = rigged_object.skeleton();
+
+ int chain_start = ik_chain_start_index[kTailIK];
+ int chain_length = ik_chain_length[kTailIK];
+
+ // Tail wag behavior
+ bool wag_tail = false;
+ if(wag_tail){
+ float wag_freq = 5.0f;
+ vec3 tail_root = rigged_object_GetTransformedBonePoint(ik_chain_elements[chain_start+chain_length-1], 0);
+ int hip_bone = skeleton_GetParent(ik_chain_elements[chain_start+chain_length-1]);
+ vec3 axis = rigged_object_GetFrameMatrix(hip_bone).rotation * vec3(0,0,1);
+ quaternion rotation(vec4(axis,sin(time*wag_freq)));
+ for(int i=0, len=chain_length; i<len; ++i){
+ BoneTransform mat = rigged_object_GetFrameMatrix(ik_chain_elements[chain_start+i]);
+ mat.origin -= tail_root;
+ mat = rotation * mat;
+ mat.origin += tail_root;
+ rigged_object_SetFrameMatrix(ik_chain_elements[chain_start+i], mat);
+ }
+ }
+
+ bool ambient_tail = false;
+ if(ambient_tail){
+ vec3 tail_root = rigged_object_GetTransformedBonePoint(ik_chain_elements[chain_start+chain_length-1], 0);
+ int hip_bone = skeleton_GetParent(ik_chain_elements[chain_start+chain_length-1]);
+ vec3 axis = rigged_object_GetFrameMatrix(hip_bone).rotation * vec3(0,0,1);
+ quaternion rotation(vec4(axis,(sin(time)+sin(time*1.3f))*0.2f));
+ for(int i=0, len=chain_length; i<len; ++i){
+ BoneTransform mat = rigged_object_GetFrameMatrix(ik_chain_elements[chain_start+i]);
+ mat.origin -= tail_root;
+ mat = rotation * mat;
+ mat.origin += tail_root;
+ rigged_object_SetFrameMatrix(ik_chain_elements[chain_start+i], mat);
+ }
+ }
+
+ bool twitch_tail_tip = false;
+ if(twitch_tail_tip){
+ float wag_freq = 5.0f;
+ vec3 tail_root = rigged_object_GetTransformedBonePoint(ik_chain_elements[chain_start+0], 0);
+ int hip_bone = skeleton_GetParent(ik_chain_elements[chain_start+chain_length-1]);
+ vec3 axis = rigged_object_GetFrameMatrix(hip_bone).rotation * vec3(0,0,1);
+ quaternion rotation(vec4(axis,sin(time*wag_freq)));
+ for(int i=0; i<1; ++i){
+ BoneTransform mat = rigged_object_GetFrameMatrix(ik_chain_elements[chain_start+i]);
+ mat.origin -= tail_root;
+ mat = rotation * mat;
+ mat.origin += tail_root;
+ rigged_object_SetFrameMatrix(ik_chain_elements[chain_start+i], mat);
+ }
+ }
+
+ tail_section_length.resize(chain_length);
+ tail_correction.resize(chain_length+1);
+ old_tail_points.resize(chain_length+1);
+ temp_old_tail_points.resize(chain_length+1);
+ if(tail_points.size()==0){
+ tail_points.resize(chain_length+1);
+ for(int i=0; i<chain_length; ++i){
+ tail_points[i] = rigged_object_GetTransformedBonePoint(ik_chain_elements[chain_start+i], 1);
+ old_tail_points[i] = tail_points[i];
+ temp_old_tail_points[i] = tail_points[i];
+ }
+ tail_points[chain_length] = rigged_object_GetTransformedBonePoint(ik_chain_elements[chain_start+chain_length-1], 0);
+ for(int i=0; i<chain_length; ++i){
+ tail_section_length[i] = distance(tail_points[i], tail_points[i+1]);
+ }
+ }
+ {
+ tail_points[chain_length] = rigged_object_GetTransformedBonePoint(ik_chain_elements[chain_start+chain_length-1], 0);
+ for(int i=0; i<chain_length+1; ++i){
+ temp_old_tail_points[i] = tail_points[i];
+ }
+ // Damping
+ for(int i=0; i<chain_length; ++i){
+ tail_points[i] += (tail_points[i] - old_tail_points[i]) * 0.95f;
+ }
+ // Gravity
+ for(int i=0; i<chain_length; ++i){
+ tail_points[i].y() -= time_step * num_frames * 0.1f;
+ }
+ tail_correction[chain_length-1] += (rigged_object_GetTransformedBonePoint(ik_chain_elements[chain_start+chain_length-1], 1) - tail_points[chain_length-1]) * (1.0f - pow(0.9f, num_frames));
+ for(int i=chain_length-2; i>=0; --i){
+ vec3 offset = rigged_object_GetTransformedBonePoint(ik_chain_elements[chain_start+i], 1) - rigged_object_GetTransformedBonePoint(ik_chain_elements[chain_start+i+1], 1);
+ quaternion rotation;
+ GetRotationBetweenVectors(rigged_object_GetTransformedBonePoint(ik_chain_elements[chain_start+i+1], 1) - rigged_object_GetTransformedBonePoint(ik_chain_elements[chain_start+i+1], 0), tail_points[i+1]-tail_points[i+2], rotation);
+ tail_correction[i] = ((tail_points[i+1]+ASMult(rotation, offset)) - tail_points[i]) * (0.2f) * 0.5f;
+ tail_correction[i+1] -= tail_correction[i];
+ }
+ for(int i=chain_length-1; i>=0; --i){
+ tail_points[i] += tail_correction[i];
+ }
+
+ for(int j=0, len=max(5, int(num_frames*1.5)); j<len; ++j){
+ for(int i=0; i<chain_length+1; ++i){
+ tail_correction[i] = vec3(0.0f);
+ }
+ for(int i=0; i<chain_length; ++i){
+ vec3 mid = (tail_points[i] + tail_points[i+1])*0.5f;
+ vec3 dir = normalize(tail_points[i]-tail_points[i+1]);
+ tail_correction[i] += (mid + dir*tail_section_length[i]*0.5f - tail_points[i])*0.75f;
+ tail_correction[i+1] += (mid - dir*tail_section_length[i]*0.5f - tail_points[i+1])*0.25f;
+ }
+ for(int i=chain_length-1; i>=0; --i){
+ tail_points[i] += tail_correction[i] * min(1.0f, (0.5f + j*0.125f));
+ }
+ }
+
+ for(int i=chain_length-1; i>=0; --i){
+ ASCollisions* col = mo->as_collisions.get();
+ col->GetSweptSphereCollision(tail_points[i+1], tail_points[i], 0.03f, col->as_col);
+ tail_points[i] = col->as_col.adjusted_position;
+ }
+
+ for(int i=0; i<chain_length+1; ++i){
+ old_tail_points[i] = temp_old_tail_points[i];
+ }
+ }
+
+ // Enforce tail lengths for drawing
+ vec3 root = rigged_object_GetTransformedBonePoint(ik_chain_elements[chain_start+chain_length-1], 0);
+ vec3 root_tail = rigged_object_GetTransformedBonePoint(ik_chain_elements[chain_start+chain_length-1], 1);
+ float root_len = distance(root, root_tail);
+ temp_old_tail_points[chain_length-1] = root + normalize(tail_points[chain_length-1] - root) * root_len;
+ for(int i=chain_length-1; i>0; --i){
+ temp_old_tail_points[i-1] = temp_old_tail_points[i] + normalize(tail_points[i-1] - tail_points[i]) * tail_section_length[i-1];
+ }
+
+ for(int i=0; i<chain_length; ++i){
+ rigged_object_RotateBoneToMatchVec(temp_old_tail_points[i+1], temp_old_tail_points[i], ik_chain_elements[chain_start+i]);
+ }
+}
+
+void CDrawTail(MovementObject* mo, int num_frames){
+ PROFILER_ZONE(g_profiler_ctx, "C++ DrawTail");
+ MovementObject& this_mo = *mo;
+
+ PROFILER_ENTER(g_profiler_ctx, "Get AS pointers");
+ ASContext* as_context = this_mo.as_context.get();
+ float& time = game_timer.game_time;
+ float& time_step = game_timer.timestep;
+
+ CScriptArrayWrapper<int> ik_chain_elements((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_elements"));
+ CScriptArrayWrapper<int> ik_chain_start_index((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_start_index"));
+ CScriptArrayWrapper<int> ik_chain_length((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_length"));
+ CScriptArrayWrapper<vec3> tail_points((CScriptArray*)as_context->module.GetVarPtrCache("tail_points"));
+ CScriptArrayWrapper<vec3> old_tail_points((CScriptArray*)as_context->module.GetVarPtrCache("old_tail_points"));
+ CScriptArrayWrapper<vec3> temp_old_tail_points((CScriptArray*)as_context->module.GetVarPtrCache("temp_old_tail_points"));
+ CScriptArrayWrapper<vec3> tail_correction((CScriptArray*)as_context->module.GetVarPtrCache("tail_correction"));
+ CScriptArrayWrapper<float> tail_section_length((CScriptArray*)as_context->module.GetVarPtrCache("tail_section_length"));
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ CDrawTailImpl(
+ mo,
+ time, time_step,
+ ik_chain_elements, ik_chain_start_index, ik_chain_length, tail_points, old_tail_points, temp_old_tail_points, tail_correction, tail_section_length,
+ num_frames);
+}
+
+static void CDrawFinalBoneIK(MovementObject* mo, int num_frames) {
+ PROFILER_ZONE(g_profiler_ctx, "C++ Draw Final Bone IK");
+ MovementObject& this_mo = *mo;
+ RiggedObject& rigged_object = *this_mo.rigged_object();
+ Skeleton& skeleton = rigged_object.skeleton();
+
+ PROFILER_ENTER(g_profiler_ctx, "Get AS pointers");
+ ASContext* as_context = this_mo.as_context.get();
+ float& breath_amount = *(float*)as_context->module.GetVarPtrCache("breath_amount");
+ float& max_speed = *(float*)as_context->module.GetVarPtrCache("max_speed");
+ float& true_max_speed = *(float*)as_context->module.GetVarPtrCache("true_max_speed");
+ int& idle_type = *(int*)as_context->module.GetVarPtrCache("idle_type");
+ bool& on_ground = *(bool*)as_context->module.GetVarPtrCache("on_ground");
+ bool& flip_info_flipping = *(bool*)((asIScriptObject*)as_context->module.GetVarPtrCache("flip_info"))->GetAddressOfProperty(0);
+ float& threat_amount = *(float*)as_context->module.GetVarPtrCache("threat_amount");
+ bool& ledge_info_on_ledge = *(bool*)((asIScriptObject*)as_context->module.GetVarPtrCache("ledge_info"))->GetAddressOfProperty(0);
+ float& time = game_timer.game_time;
+ float& time_step = game_timer.timestep;
+ int& species = *(int*)as_context->module.GetVarPtrCache("species");
+ int& skip_ear_physics_counter = *(int*)as_context->module.GetVarPtrCache("skip_ear_physics_counter");
+
+ CScriptArrayWrapper<BoneTransform> key_transforms((CScriptArray*)as_context->module.GetVarPtrCache("key_transforms"));
+ CScriptArrayWrapper<int> ik_chain_start_index((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_start_index"));
+ CScriptArrayWrapper<int> ik_chain_elements((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_elements"));
+ CScriptArrayWrapper<int> ik_chain_length((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_length"));
+ CScriptArrayWrapper<float> ik_chain_bone_lengths((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_bone_lengths"));
+ CScriptArrayWrapper<int> bone_children((CScriptArray*)as_context->module.GetVarPtrCache("bone_children"));
+ CScriptArrayWrapper<int> bone_children_index((CScriptArray*)as_context->module.GetVarPtrCache("bone_children_index"));
+ CScriptArrayWrapper<BoneTransform> skeleton_bind_transforms((CScriptArray*)as_context->module.GetVarPtrCache("skeleton_bind_transforms"));
+ CScriptArrayWrapper<BoneTransform> inv_skeleton_bind_transforms((CScriptArray*)as_context->module.GetVarPtrCache("inv_skeleton_bind_transforms"));
+
+ CScriptArrayWrapper<vec3> arm_points((CScriptArray*)as_context->module.GetVarPtrCache("arm_points"));
+ CScriptArrayWrapper<vec3> old_arm_points((CScriptArray*)as_context->module.GetVarPtrCache("old_arm_points"));
+ CScriptArrayWrapper<vec3> temp_old_arm_points((CScriptArray*)as_context->module.GetVarPtrCache("temp_old_arm_points"));
+ CScriptArrayWrapper<float> ear_rotation((CScriptArray*)as_context->module.GetVarPtrCache("ear_rotation"));
+ CScriptArrayWrapper<float> ear_rotation_time((CScriptArray*)as_context->module.GetVarPtrCache("ear_rotation_time"));
+ CScriptArrayWrapper<float> target_ear_rotation((CScriptArray*)as_context->module.GetVarPtrCache("target_ear_rotation"));
+ CScriptArrayWrapper<vec3> ear_points((CScriptArray*)as_context->module.GetVarPtrCache("ear_points"));
+ CScriptArrayWrapper<vec3> old_ear_points((CScriptArray*)as_context->module.GetVarPtrCache("old_ear_points"));
+ CScriptArrayWrapper<vec3> temp_old_ear_points((CScriptArray*)as_context->module.GetVarPtrCache("temp_old_ear_points"));
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ CDrawLegImpl(
+ rigged_object, skeleton,
+ ik_chain_start_index, ik_chain_elements, ik_chain_bone_lengths, skeleton_bind_transforms, inv_skeleton_bind_transforms,
+ false, key_transforms[kHipKey], key_transforms[kLeftLegKey]);
+ CDrawLegImpl(
+ rigged_object, skeleton,
+ ik_chain_start_index, ik_chain_elements, ik_chain_bone_lengths, skeleton_bind_transforms, inv_skeleton_bind_transforms,
+ true, key_transforms[kHipKey], key_transforms[kRightLegKey]);
+
+ CDrawArmsImpl(
+ this_mo, rigged_object, skeleton,
+ breath_amount, max_speed, true_max_speed, idle_type, on_ground, flip_info_flipping, threat_amount, ledge_info_on_ledge,
+ skeleton_bind_transforms.c_script_array_, inv_skeleton_bind_transforms.c_script_array_, ik_chain_start_index.c_script_array_, ik_chain_elements.c_script_array_, ik_chain_length.c_script_array_, key_transforms.c_script_array_, arm_points.c_script_array_, old_arm_points.c_script_array_, temp_old_arm_points.c_script_array_, bone_children.c_script_array_, bone_children_index.c_script_array_,
+ key_transforms[kChestKey], key_transforms[kLeftArmKey], key_transforms[kRightArmKey], num_frames);
+
+ CDrawHeadImpl(
+ rigged_object, skeleton,
+ breath_amount,
+ key_transforms, inv_skeleton_bind_transforms, ik_chain_start_index, ik_chain_elements, bone_children, bone_children_index,
+ key_transforms[kChestKey], key_transforms[kHeadKey]);
+
+ CDrawBodyImpl(
+ rigged_object, skeleton,
+ inv_skeleton_bind_transforms, ik_chain_start_index, ik_chain_elements, ik_chain_length,
+ key_transforms[kHipKey], key_transforms[kChestKey]);
+
+ CDrawEarImpl(
+ mo,
+ time, time_step, species, skip_ear_physics_counter, flip_info_flipping, on_ground,
+ ik_chain_start_index, ik_chain_elements, ear_rotation, ear_rotation_time, target_ear_rotation, inv_skeleton_bind_transforms, ear_points, old_ear_points, temp_old_ear_points,
+ false, key_transforms[kHeadKey], num_frames);
+ CDrawEarImpl(
+ mo,
+ time, time_step, species, skip_ear_physics_counter, flip_info_flipping, on_ground,
+ ik_chain_start_index, ik_chain_elements, ear_rotation, ear_rotation_time, target_ear_rotation, inv_skeleton_bind_transforms, ear_points, old_ear_points, temp_old_ear_points,
+ true, key_transforms[kHeadKey], num_frames);
+
+ if (ik_chain_length[kTailIK] != 0) {
+ PROFILER_ENTER(g_profiler_ctx, "Get AS pointers");
+ CScriptArrayWrapper<vec3> tail_points((CScriptArray*)as_context->module.GetVarPtrCache("tail_points"));
+ CScriptArrayWrapper<vec3> old_tail_points((CScriptArray*)as_context->module.GetVarPtrCache("old_tail_points"));
+ CScriptArrayWrapper<vec3> temp_old_tail_points((CScriptArray*)as_context->module.GetVarPtrCache("temp_old_tail_points"));
+ CScriptArrayWrapper<vec3> tail_correction((CScriptArray*)as_context->module.GetVarPtrCache("tail_correction"));
+ CScriptArrayWrapper<float> tail_section_length((CScriptArray*)as_context->module.GetVarPtrCache("tail_section_length"));
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ CDrawTailImpl(
+ mo,
+ time, time_step,
+ ik_chain_elements, ik_chain_start_index, ik_chain_length, tail_points, old_tail_points, temp_old_tail_points, tail_correction, tail_section_length,
+ num_frames);
+ }
+}
+
+static void CSetEyeLookDirImpl(RiggedObject& rigged_object, vec3 eye_dir) {
+ // Set weights for carnivore
+ rigged_object.SetMTTargetWeight("look_r", max(0.0f, eye_dir.x()), 1.0f);
+ rigged_object.SetMTTargetWeight("look_l", max(0.0f, -eye_dir.x()), 1.0f);
+ rigged_object.SetMTTargetWeight("look_u", max(0.0f, eye_dir.y()), 1.0f);
+ rigged_object.SetMTTargetWeight("look_d", max(0.0f, -eye_dir.y()), 1.0f);
+
+ // Set weights for herbivore
+ rigged_object.SetMTTargetWeight("look_u", max(0.0f, eye_dir.y()), 1.0f);
+ rigged_object.SetMTTargetWeight("look_d", max(0.0f, -eye_dir.y()), 1.0f);
+ rigged_object.SetMTTargetWeight("look_f", max(0.0f, eye_dir.z()), 1.0f);
+ rigged_object.SetMTTargetWeight("look_b", max(0.0f, -eye_dir.z()), 1.0f);
+
+ // Set weights for independent-eye herbivore
+ rigged_object.SetMTTargetWeight("look_u_l", max(0.0f, eye_dir.y()), 1.0f);
+ rigged_object.SetMTTargetWeight("look_u_r", max(0.0f, eye_dir.y()), 1.0f);
+ rigged_object.SetMTTargetWeight("look_d_l", max(0.0f, -eye_dir.y()), 1.0f);
+ rigged_object.SetMTTargetWeight("look_d_r", max(0.0f, -eye_dir.y()), 1.0f);
+
+ float right_front = eye_dir.z();
+ float left_front = eye_dir.z();
+
+ rigged_object.SetMTTargetWeight("look_f_r", max(0.0f, right_front), 1.0f);
+ rigged_object.SetMTTargetWeight("look_b_r", max(0.0f, -right_front), 1.0f);
+ rigged_object.SetMTTargetWeight("look_f_l", max(0.0f, left_front), 1.0f);
+ rigged_object.SetMTTargetWeight("look_b_l", max(0.0f, -left_front), 1.0f);
+}
+
+typedef enum {
+ kLeftEye,
+ kRightEye
+} WhichEye;
+
+static bool CGetEyeDirImpl(
+ MovementObject& this_mo,
+ const CScriptArrayWrapper<BoneTransform>& skeleton_bind_transforms,
+ WhichEye which_eye, const std::string& morph_label, vec3& start, vec3& end) {
+ if (this_mo.character_script_getter.GetMorphMetaPoints(morph_label, start, end)) {
+ if (which_eye == kLeftEye) {
+ start.x() *= -1.0f;
+ end.x() *= -1.0f;
+ }
+
+ RiggedObject& rigged_object = *this_mo.rigged_object();
+
+ Skeleton& skeleton = rigged_object.skeleton();
+ BoneTransform test = skeleton_bind_transforms[ASIKBoneStart(&skeleton, "head")];
+ float temp;
+ vec3 model_center = ASGetModelCenter(&rigged_object);
+ temp = start.z();
+ start.z() = -start.y();
+ start.y() = temp;
+ temp = end.z();
+ end.z() = -end.y();
+ end.y() = temp;
+ start -= model_center;
+ end -= model_center;
+ start = test * start;
+ end = test * end;
+
+ return true;
+ }
+ else {
+ return false;
+ }
+}
+
+static void CUpdateShadowImpl(
+ MovementObject* mo,
+ int& shadow_id,
+ int& lf_shadow_id,
+ int& rf_shadow_id,
+ const CScriptArrayWrapper<BoneTransform>& skeleton_bind_transforms) {
+ MovementObject& this_mo = *mo;
+ RiggedObject& rigged_object = *this_mo.rigged_object();
+ Skeleton& skeleton = rigged_object.skeleton();
+
+ int bone = ASIKBoneStart(&skeleton, "torso");
+ BoneTransform transform = rigged_object.display_bone_matrices[bone];
+ BoneTransform bind_matrix = invert(skeleton_bind_transforms[bone]);
+ transform = transform;
+ vec3 torso = transform * skeleton.points[skeleton.bones[bone].points[0]];
+
+ bone = ASIKBoneStart(&skeleton, "head");
+ transform = rigged_object.display_bone_matrices[bone];
+ bind_matrix = invert(skeleton_bind_transforms[bone]);
+ transform = transform;
+ vec3 head = transform * skeleton.points[skeleton.bones[bone].points[0]];
+
+ bone = ASIKBoneStart(&skeleton, "left_leg");
+ transform = rigged_object.display_bone_matrices[bone];
+ bind_matrix = invert(skeleton_bind_transforms[bone]);
+ transform = transform;
+ vec3 left_foot = transform * skeleton.points[skeleton.bones[bone].points[0]];
+
+ bone = ASIKBoneStart(&skeleton, "right_leg");
+ transform = rigged_object.display_bone_matrices[bone];
+ bind_matrix = invert(skeleton_bind_transforms[bone]);
+ transform = transform;
+ vec3 right_foot = transform * skeleton.points[skeleton.bones[bone].points[0]];
+
+ {
+ if (shadow_id == -1) {
+ shadow_id = ASCreateObject("Data/Objects/Decals/blob_shadow.xml", true);
+ }
+
+ Object* shadow_obj = ReadObjectFromID(shadow_id);
+ vec3 scale = vec3(1.5f, max(1.5f, distance((left_foot + right_foot) * 0.5f, head) * 2.0f), 1.5f);
+ shadow_obj->SetScale(scale);
+
+ shadow_obj->SetTranslation(torso + vec3(0.0f, -0.3f, 0.0f));
+ }
+
+ {
+ if (lf_shadow_id == -1) {
+ lf_shadow_id = ASCreateObject("Data/Objects/Decals/blob_shadow.xml", true);
+ }
+
+ Object* shadow_obj = ReadObjectFromID(lf_shadow_id);
+
+ shadow_obj->SetTranslation(left_foot + vec3(0.0f));
+ shadow_obj->SetScale(vec3(0.4f));
+ }
+
+ {
+ if (rf_shadow_id == -1) {
+ rf_shadow_id = ASCreateObject("Data/Objects/Decals/blob_shadow.xml", true);
+ }
+
+ Object* shadow_obj = ReadObjectFromID(rf_shadow_id);
+
+ shadow_obj->SetTranslation(right_foot + vec3(0.0f, 0.0f, 0.0f));
+ shadow_obj->SetScale(vec3(0.4f));
+ }
+}
+
+static void CUpdateShadow(MovementObject* mo) {
+ PROFILER_ZONE(g_profiler_ctx, "C++ Update Shadow")
+ MovementObject& this_mo = *mo;
+
+ PROFILER_ENTER(g_profiler_ctx, "Get AS pointers");
+ ASContext* as_context = this_mo.as_context.get();
+ int& shadow_id = *(int*)as_context->module.GetVarPtrCache("shadow_id");
+ int& lf_shadow_id = *(int*)as_context->module.GetVarPtrCache("lf_shadow_id");
+ int& rf_shadow_id = *(int*)as_context->module.GetVarPtrCache("rf_shadow_id");
+
+ CScriptArrayWrapper<BoneTransform> skeleton_bind_transforms((CScriptArray*)as_context->module.GetVarPtrCache("skeleton_bind_transforms"));
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ CUpdateShadowImpl(
+ mo,
+ shadow_id, lf_shadow_id, rf_shadow_id,
+ skeleton_bind_transforms);
+}
+
+static void CUpdateEyeLookImpl(
+ MovementObject* mo,
+ float& time,
+ vec3& eye_dir,
+ vec3 eye_look_target,
+ vec3& eye_offset,
+ float& eye_offset_time,
+ Species species,
+ int knocked_out,
+ const CScriptArrayWrapper<int>& ik_chain_elements,
+ const CScriptArrayWrapper<int>& ik_chain_start_index,
+ const CScriptArrayWrapper<BoneTransform>& skeleton_bind_transforms) {
+ MovementObject& this_mo = *mo;
+
+ if (knocked_out != this_mo._awake) {
+ return;
+ }
+
+ RiggedObject& rigged_object = *this_mo.rigged_object();
+
+ vec3 target_pos = eye_look_target;
+ BoneTransform head_mat = rigged_object_GetFrameMatrix(ik_chain_elements[ik_chain_start_index[kHeadIK]]);
+ vec3 temp_target_pos = normalize(invert(head_mat) * target_pos);
+
+ vec3 base_start, base_end;
+ WhichEye which_eye = kRightEye;
+ bool valid = CGetEyeDirImpl(this_mo, skeleton_bind_transforms, kRightEye, "look_c", base_start, base_end);
+
+ if (valid) {
+ eye_dir = 0;
+
+ if (species == _rabbit || species == _rat) {
+ if ((invert(head_mat) * ActiveCameras::GetCamera(this_mo.camera_id)->GetPos()).x() < 0.0f) {
+ which_eye = kLeftEye;
+ }
+
+ CGetEyeDirImpl(this_mo, skeleton_bind_transforms, which_eye, "look_c", base_start, base_end);
+ }
+
+ vec3 start1, end1, start2, end2;
+ {
+ CGetEyeDirImpl(this_mo, skeleton_bind_transforms, which_eye, "look_u", start1, end1);
+ CGetEyeDirImpl(this_mo, skeleton_bind_transforms, which_eye, "look_d", start2, end2);
+ vec3 normal = normalize(cross(end2 - base_start, end1 - base_start));
+ vec3 test_targ = temp_target_pos - normal * dot(normal, temp_target_pos);
+ vec3 neutral_dir = normalize(base_end - base_start);
+ vec3 normal2 = normalize(cross(normal, neutral_dir));
+ float up_angle = asin(dot(normalize(end1 - start1), normal2));
+ float down_angle = asin(dot(normalize(end2 - start2), normal2));
+ float targ_angle = asin(dot(normalize(test_targ - base_start), normal2));
+
+ if (targ_angle > 0.0f) {
+ eye_dir.y() = min(1.0f, targ_angle / up_angle);
+ }
+ else {
+ eye_dir.y() = -min(1.0f, targ_angle / down_angle);
+ }
+ }
+
+ if (species == _rabbit || species == _rat) {
+ CGetEyeDirImpl(this_mo, skeleton_bind_transforms, which_eye, "look_f", start1, end1);
+ CGetEyeDirImpl(this_mo, skeleton_bind_transforms, which_eye, "look_b", start2, end2);
+ vec3 normal = normalize(cross(end2 - base_start, end1 - base_start));
+ vec3 test_targ = temp_target_pos - normal * dot(normal, temp_target_pos);
+ vec3 neutral_dir = normalize(base_end - base_start);
+ vec3 normal2 = normalize(cross(normal, neutral_dir));
+ float front_angle = asin(dot(normalize(end1 - start1), normal2));
+ float back_angle = asin(dot(normalize(end2 - start2), normal2));
+ float targ_angle = asin(dot(normalize(test_targ - base_start), normal2));
+
+ if (targ_angle > 0.0f) {
+ eye_dir.z() = min(1.0f, targ_angle / front_angle);
+ }
+ else {
+ eye_dir.z() = -min(1.0f, targ_angle / back_angle);
+ }
+ }
+ else {
+ CGetEyeDirImpl(this_mo, skeleton_bind_transforms, which_eye, "look_l", start1, end1);
+ CGetEyeDirImpl(this_mo, skeleton_bind_transforms, which_eye, "look_r", start2, end2);
+ vec3 normal = normalize(cross(end2 - base_start, end1 - base_start));
+ vec3 test_targ = temp_target_pos - normal * dot(normal, temp_target_pos);
+ vec3 neutral_dir = normalize(base_end - base_start);
+ vec3 normal2 = normalize(cross(normal, neutral_dir));
+ float front_angle = asin(dot(normalize(end1 - start1), normal2));
+ float back_angle = asin(dot(normalize(end2 - start2), normal2));
+ float targ_angle = asin(dot(normalize(test_targ - base_start), normal2));
+
+ if (targ_angle > 0.0f) {
+ eye_dir.x() = -min(1.0f, targ_angle / front_angle);
+ }
+ else {
+ eye_dir.x() = min(1.0f, targ_angle / back_angle);
+ }
+ }
+ }
+
+ if (eye_offset_time < time) {
+ eye_offset = vec3(RangedRandomFloat(-0.2f, 0.2f),
+ RangedRandomFloat(-0.2f, 0.2f),
+ RangedRandomFloat(-0.2f, 0.2f));
+ eye_offset_time = time + RangedRandomFloat(0.2f, 1.0f);
+ }
+
+ CSetEyeLookDirImpl(rigged_object, eye_dir + eye_offset);
+}
+
+static void CUpdateEyeLook(MovementObject* mo) {
+ PROFILER_ZONE(g_profiler_ctx, "C++ Update Eye Look")
+ MovementObject& this_mo = *mo;
+
+ PROFILER_ENTER(g_profiler_ctx, "Get AS pointers");
+ ASContext* as_context = this_mo.as_context.get();
+ float& time = game_timer.game_time;
+ vec3& eye_dir = *(vec3*)as_context->module.GetVarPtrCache("eye_dir");
+ vec3 eye_look_target = *(vec3*)as_context->module.GetVarPtrCache("eye_look_target");
+ vec3& eye_offset = *(vec3*)as_context->module.GetVarPtrCache("eye_offset");
+ float& eye_offset_time = *(float*)as_context->module.GetVarPtrCache("eye_offset_time");
+ Species species = *(Species*)as_context->module.GetVarPtrCache("species");
+ int knocked_out = *(int*)as_context->module.GetVarPtrCache("knocked_out");
+
+ CScriptArrayWrapper<int> ik_chain_elements((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_elements"));
+ CScriptArrayWrapper<int> ik_chain_start_index((CScriptArray*)as_context->module.GetVarPtrCache("ik_chain_start_index"));
+ CScriptArrayWrapper<BoneTransform> skeleton_bind_transforms((CScriptArray*)as_context->module.GetVarPtrCache("skeleton_bind_transforms"));
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ CUpdateEyeLookImpl(
+ mo,
+ time, eye_dir, eye_look_target, eye_offset, eye_offset_time, species, knocked_out,
+ ik_chain_elements, ik_chain_start_index, skeleton_bind_transforms);
+}
+
+vec3 GetWind(vec3 check_where, float curr_game_time, float change_rate) {
+ vec3 wind_vel;
+ check_where[0] += curr_game_time*0.7f*change_rate;
+ check_where[1] += curr_game_time*0.3f*change_rate;
+ check_where[2] += curr_game_time*0.5f*change_rate;
+ wind_vel[0] = sin(check_where[0])+cos(check_where[1]*1.3f)+sin(check_where[2]*3.0f);
+ wind_vel[1] = sin(check_where[0]*1.2f)+cos(check_where[1]*1.8f)+sin(check_where[2]*0.8f);
+ wind_vel[2] = sin(check_where[0]*1.6f)+cos(check_where[1]*0.5f)+sin(check_where[2]*1.2f);
+
+ return wind_vel;
+}
+
+void CFireRibbonUpdate(MovementObject* mo, asIScriptObject* obj, float delta_time, float curr_game_time) {
+ ASContext* as_context = mo->as_context.get();
+ bool& on_fire = *(bool*)as_context->module.GetVarPtrCache("on_fire");
+ vec3& pos = *(vec3*)obj->GetAddressOfProperty(2);
+ vec3& vel = *(vec3*)obj->GetAddressOfProperty(3);
+ float& spawn_new_particle_delay = *(float*)obj->GetAddressOfProperty(5);
+
+ CScriptArrayWrapper<asIScriptObject*> particles((CScriptArray*)obj->GetAddressOfProperty(0));
+
+ bool non_zero_particles = false;
+ for(int i=0, len=particles.size(); i<len; ++i){
+ asIScriptObject* particle = (asIScriptObject*)(particles.c_script_array_->At(i));
+ //asIScriptObject*& particle = particles[i];
+ float& heat = *(float*)particle->GetAddressOfProperty(3);
+ if(heat > 0.0f){
+ non_zero_particles = true;
+ }
+ }
+ if(non_zero_particles || on_fire){
+ spawn_new_particle_delay -= delta_time;
+ if(spawn_new_particle_delay <= 0.0f){
+ particles.resize(particles.size()+1);
+ asIScriptObject* particle = (asIScriptObject*)(particles.c_script_array_->At(particles.size()-1));
+ //asIScriptObject*& particle = particles[particles.size()-1];
+ vec3& particle_pos = *(vec3*)particle->GetAddressOfProperty(0);
+ vec3& particle_vel = *(vec3*)particle->GetAddressOfProperty(1);
+ float& particle_width = *(float*)particle->GetAddressOfProperty(2);
+ float& particle_heat = *(float*)particle->GetAddressOfProperty(3);
+ float& particle_spawn_time = *(float*)particle->GetAddressOfProperty(4);
+ particle_pos = pos;
+ particle_vel = vel * 0.1f;
+ particle_width = 0.12f * min(2.0f, (length(vel) * 0.1f + 1.0f));
+ particle_heat = RangedRandomFloat(0.0,1.5);
+ if(!on_fire || particles.size()==0){
+ particle_heat = 0.0f;
+ }
+ particle_spawn_time = curr_game_time;
+
+ while(spawn_new_particle_delay <= 0.0f){
+ spawn_new_particle_delay += 0.1f;
+ }
+ }
+ int max_particles = 5;
+ if(int(particles.size()) > max_particles){
+ for(int i=0; i<max_particles; ++i) {
+ asIScriptObject* dst_particle = (asIScriptObject*)(particles.c_script_array_->At(i));
+ asIScriptObject* src_particle = (asIScriptObject*)(particles.c_script_array_->At(particles.size()-max_particles+i));
+ //asIScriptObject*& dst_particle = particles[i];
+ //asIScriptObject*& src_particle = particles[particles.size()-max_particles+i];
+ dst_particle->CopyFrom(src_particle);
+ }
+ particles.resize(max_particles);
+ }
+ if(particles.size() > 0){
+ asIScriptObject* particle = (asIScriptObject*)(particles.c_script_array_->At(particles.size()-1));
+ //asIScriptObject*& particle = particles[particles.size()-1];
+ vec3& particle_pos = *(vec3*)particle->GetAddressOfProperty(0);
+ particle_pos = pos;
+ }
+ for(int i=0, len=particles.size(); i<len; ++i){
+ asIScriptObject* particle = (asIScriptObject*)(particles.c_script_array_->At(i));
+ //asIScriptObject*& particle = particles[i];
+ vec3& particle_pos = *(vec3*)particle->GetAddressOfProperty(0);
+ vec3& particle_vel = *(vec3*)particle->GetAddressOfProperty(1);
+ float& particle_width = *(float*)particle->GetAddressOfProperty(2);
+ float& particle_heat = *(float*)particle->GetAddressOfProperty(3);
+ float& particle_spawn_time = *(float*)particle->GetAddressOfProperty(4);
+ particle_vel *= pow(0.2f, delta_time);
+ particle_pos += particle_vel * delta_time;
+ particle_vel += GetWind(particle_pos * 5.0f, curr_game_time, 10.0f) * delta_time * 1.0f;
+ particle_vel += GetWind(particle_pos * 30.0f, curr_game_time, 10.0f) * delta_time * 2.0f;
+ float max_dist = 0.2f;
+ if(i != len-1){
+ asIScriptObject* next_particle = (asIScriptObject*)(particles.c_script_array_->At(i+1));
+ //asIScriptObject*& next_particle = particles[i+1];
+ vec3& next_particle_pos = *(vec3*)next_particle->GetAddressOfProperty(0);
+ if(distance(next_particle_pos, particle_pos) > max_dist){
+ //particles[i].pos = normalize(particles[i].pos - particles[i+1].pos) * max_dist + particles[i+1].pos;
+ particle_vel += normalize(next_particle_pos - particle_pos) * (distance(next_particle_pos, particle_pos) - max_dist) * 100.0f * delta_time;
+ }
+ particle_heat -= length(next_particle_pos - particle_pos) * 10.0f * delta_time;
+ }
+ particle_heat -= delta_time * 3.0f;
+ particle_vel[1] += delta_time * 12.0f;
+
+ vec3 rel = particle_pos - mo->position;
+ rel[1] = 0.0;
+ //particles[i].heat -= delta_time * (2.0f + min(1.0f, pow(dot(rel,rel), 2.0)*64.0f)) * 2.0f;
+ if(dot(rel,rel) > 1.0){
+ rel = normalize(rel);
+ }
+
+ particle_vel += rel * delta_time * -3.0f * 6.0f;
+ }
+ /*for(int i=0, len=particles.size()-1; i<len; ++i){
+ //DebugDrawLine(particles[i].pos, particles[i+1].pos, ColorFromHeat(particles[i].heat), ColorFromHeat(particles[i+1].heat), _delete_on_update);
+ DebugDrawRibbon(particles[i].pos, particles[i+1].pos, ColorFromHeat(particles[i].heat), ColorFromHeat(particles[i+1].heat), flame_width * max(particles[i].heat, 0.0), flame_width * max(particles[i+1].heat, 0.0), _delete_on_update);
+ }*/
+ } else {
+ particles.resize(0);
+ }
+}
+
+#undef rigged_object_SetFrameMatrix
+#undef rigged_object_GetFrameMatrix
+#undef rigged_object_GetTransformedBonePoint
+#undef rigged_object_RotateBoneToMatchVec
+#undef skeleton_GetParent
+
+} //namespace ""
+
+void MovementObject::SetEnabled(bool val) {
+ if(val != enabled_){
+ if(Online::Instance()->IsHosting()) {
+ Online::Instance()->Send<OnlineMessages::SetObjectEnabledMessage>(GetID(), val);
+ }
+ Object::SetEnabled(val);
+ ASArglist args;
+ args.Add(val);
+ as_context->CallScriptFunction(as_funcs.set_enabled, &args);
+
+ if(!enabled_){
+ scenegraph_->UnlinkUpdateObject(this, update_list_entry);
+ update_list_entry = -1;
+ scenegraph_->abstract_bullet_world_->UnlinkObject(char_sphere);
+ } else {
+ update_list_entry = scenegraph_->LinkUpdateObject(this);
+ scenegraph_->abstract_bullet_world_->LinkObject(char_sphere);
+ }
+ }
+}
+
+void MovementObject::ReceiveMessage(std::string msg){
+ ASArglist args;
+ args.AddObject((void*)&msg);
+ as_context->CallScriptFunction(as_funcs.receive_message, &args);
+}
+
+bool MovementObject::Initialize() {
+ SDL_assert(update_list_entry == -1);
+ update_list_entry = scenegraph_->LinkUpdateObject(this);
+
+ PROFILER_ENTER(g_profiler_ctx, "Preparing movementobject angelscript context");
+ ASData as_data;
+ as_data.scenegraph = scenegraph_;
+ as_data.gui = scenegraph_->map_editor->gui;
+ ASContext *ctx = new ASContext("movement_object", as_data);
+ as_context.reset(ctx);
+ AttachUIQueries(ctx);
+ AttachMovementObjectCamera(ctx, this);
+ AttachScreenWidth(ctx);
+ AttachPhysics(ctx);
+ AttachTextCanvasTextureToASContext(ctx);
+ ScriptParams::RegisterScriptType(ctx);
+ AttachLevel(ctx);
+ AttachInterlevelData(ctx);
+ AttachNavMesh(ctx);
+ AttachMessages(ctx);
+ AttachTokenIterator(ctx);
+ AttachAttackScriptGetter(as_context.get());
+ sp.RegisterScriptInstance(ctx);
+ AttachStringConvert(ctx);
+ AttachOnline(ctx);
+
+ as_collisions.reset(new ASCollisions(scenegraph_));
+ as_collisions->AttachToContext(ctx);
+
+ DefineRiggedObjectTypePublic(ctx);
+
+ DefineMovementObjectTypePublic(ctx);
+ as_context->GetEngine()->RegisterInterface("C_ACCEL");
+ as_context->RegisterObjectMethod("MovementObject","void AddToAttackHistory(const string &in attack_path)",asMETHOD(MovementObject, AddToAttackHistory), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","float CheckAttackHistory(const string &in attack_path)",asMETHOD(MovementObject, CheckAttackHistory), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void ClearAttackHistory()",asMETHOD(MovementObject, ClearAttackHistory), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","int WasHit(string type, string attack_path, vec3 dir, vec3 pos, int attacker_id, float attack_damage_mult, float attack_knockback_mult)",asMETHOD(MovementObject, ASWasHit), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void WasBlocked()",asMETHOD(MovementObject, ASWasBlocked), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject", "float GetTempHealth()", asMETHOD(MovementObject,GetTempHealth), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void Ragdoll()",asMETHOD(MovementObject, Ragdoll), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void ApplyForce(vec3 force)",asMETHOD(MovementObject, ApplyForce), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","vec3 GetFacing()",asMETHOD(MovementObject, GetFacing), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void UnRagdoll()",asMETHOD(MovementObject, UnRagdoll), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void SetAnimation(string anim_path)",asMETHODPR(MovementObject, SetAnimation, (std::string), void), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void SetAnimAndCharAnim(string anim_path, float transition_speed, int8 flags, string char_anim)",asMETHOD(MovementObject, SetAnimAndCharAnim), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void SwapAnimation(string anim_path)",asMETHODPR(MovementObject, SwapAnimation,(std::string), void), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void SetAnimation(string anim_path, float transition_speed)",asMETHODPR(MovementObject, SetAnimation, (std::string, float), void), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void SetAnimation(string anim_path, float transition_speed, int8 flags)",asMETHODPR(MovementObject, SetAnimation, (std::string, float, char), void), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void OverrideCharAnim(const string &in char_anim, const string &in anim_path)",asMETHOD(MovementObject, OverrideCharAnim), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void SetCharAnimation(string char_anim, float transition_speed, int8 flags)",asMETHODPR(MovementObject, ASSetCharAnimation, (std::string, float, char), void), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void SetCharAnimation(string char_anim, float transition_speed)",asMETHODPR(MovementObject, ASSetCharAnimation, (std::string, float), void), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void SetCharAnimation(string char_anim)",asMETHODPR(MovementObject, ASSetCharAnimation, (std::string), void), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void MaterialEvent(string event, vec3 position)",asMETHOD(MovementObject, HandleMaterialEventDefault), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void MaterialEvent(string event, vec3 position, float audio_gain)",asMETHOD(MovementObject, HandleMaterialEvent), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void PlaySoundGroupAttached(string path, vec3 position)",asMETHOD(MovementObject, ASPlaySoundGroupAttached), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void PlaySoundAttached(string path, vec3 position)",asMETHOD(MovementObject, ASPlaySoundAttached), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void PlaySoundGroupVoice(string voice_key, float delay)",asMETHOD(MovementObject, PlaySoundGroupVoice), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void ForceSoundGroupVoice(string voice_key, float delay)",asMETHOD(MovementObject, ForceSoundGroupVoice), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void StopVoice()",asMETHOD(MovementObject, StopVoice), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","vec4 GetAvgRotationVec4()",asMETHOD(MovementObject, GetAvgRotationVec4), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","int getID()",asMETHOD(MovementObject, GetID), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void AttachItemToSlot(int item_id, int attachment_type, bool mirrored)",asMETHOD(MovementObject, AttachItemToSlot), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void DetachItem(int item_id)",asMETHOD(MovementObject, ASDetachItem), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void DetachAllItems()",asMETHOD(MovementObject, ASDetachAllItems), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void MaterialParticleAtBone(string type, string IK_label)",asMETHOD(MovementObject, MaterialParticleAtBone), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void MaterialParticle(const string &in type, const vec3 &in pos, const vec3 &in vel)", asFUNCTION(asMaterialParticle), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("MovementObject","void RecreateRiggedObject(string character_path)",asMETHOD(MovementObject, RecreateRiggedObject), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","int GetWaypointTarget()",asMETHOD(MovementObject, GetWaypointTarget), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void UpdateWeapons()",asMETHOD(MovementObject, UpdateWeapons), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("MovementObject","void CDisplayMatrixUpdate()", asFUNCTION(CDisplayMatrixUpdate), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("MovementObject","vec3 CGetCenterOfMassEstimate(array<BoneTransform> &in key_transforms, const array<float> &in key_masses, const array<int> &in root_bone)", asFUNCTION(CGetCenterOfMassEstimate), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("MovementObject","void CDoChestIK(float chest_tilt_offset, float angle_threshold, float torso_damping, float torso_stiffness, int num_frames)", asFUNCTION(CDoChestIK), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("MovementObject","void CDoHeadIK(float head_tilt_offset, float angle_threshold, float head_damping, float head_accel_inertia, float head_accel_damping, float head_stiffness, int num_frames)", asFUNCTION(CDoHeadIK), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("MovementObject","void CDoFootIK(const BoneTransform &in local_to_world, int num_frames)", asFUNCTION(CDoFootIK), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("MovementObject","void CDoHipIK(BoneTransform &inout hip_offset, quaternion &inout hip_rotate, int num_frames)", asFUNCTION(CDoHipIK), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("MovementObject","void CDrawLeg(bool right, const BoneTransform &in hip_transform, const BoneTransform &in foot_transform, int num_frames)", asFUNCTION(CDrawLeg), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("MovementObject","void CDrawArms(const BoneTransform &in chest_transform, const BoneTransform &in l_hand_transform, const BoneTransform &in r_hand_transform, int num_frames)", asFUNCTION(CDrawArms), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("MovementObject","void CDrawHead(const BoneTransform &in chest_transform, const BoneTransform &in head_transform, int num_frames)", asFUNCTION(CDrawHead), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("MovementObject","void CDrawBody(const BoneTransform &in hip_transform, const BoneTransform &in chest_transform)", asFUNCTION(CDrawBody), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("MovementObject","void CDrawEar(bool right, const BoneTransform &in head_transform, int num_frames)", asFUNCTION(CDrawEar), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("MovementObject","void CDrawTail(int num_frames)", asFUNCTION(CDrawTail), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("MovementObject","void CDrawFinalBoneIK(int num_frames)", asFUNCTION(CDrawFinalBoneIK), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("MovementObject","void CUpdateShadow()", asFUNCTION(CUpdateShadow), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("MovementObject","void CUpdateEyeLook()", asFUNCTION(CUpdateEyeLook), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("MovementObject","void CFireRibbonUpdate(C_ACCEL @, float delta_time, float curr_game_time)", asFUNCTION(CFireRibbonUpdate), asCALL_CDECL_OBJFIRST);
+
+ as_context->DocsCloseBrace();
+
+ as_funcs.init = as_context->RegisterExpectedFunction("bool Init(string)", true);
+ as_funcs.init_multiplayer = as_context->RegisterExpectedFunction("void InitMultiplayer()", false);
+ as_funcs.is_multiplayer_supported = as_context->RegisterExpectedFunction("void IsMultiplayerSupported()", false);
+ as_funcs.set_parameters = as_context->RegisterExpectedFunction("void SetParameters()", true);
+ as_funcs.notify_item_detach = as_context->RegisterExpectedFunction("void NotifyItemDetach(int)", true);
+ as_funcs.handle_editor_attachment = as_context->RegisterExpectedFunction("void HandleEditorAttachment(int, int, bool)", true);
+ as_funcs.contact = as_context->RegisterExpectedFunction("void Contact()", true);
+ as_funcs.collided = as_context->RegisterExpectedFunction("void Collided(float, float, float, float, float)", true);
+ as_funcs.movement_object_deleted = as_context->RegisterExpectedFunction("void MovementObjectDeleted(int id)", true);
+ as_funcs.script_swap = as_context->RegisterExpectedFunction("void ScriptSwap()", true);
+ as_funcs.handle_collisions_btc = as_context->RegisterExpectedFunction("void HandleCollisionsBetweenTwoCharacters(MovementObject @other)", true);
+ as_funcs.hit_by_item = as_context->RegisterExpectedFunction("void HitByItem(string material, vec3 point, int id, int type)", true);
+ as_funcs.update = as_context->RegisterExpectedFunction("void Update(int)", true);
+ as_funcs.update_multiplayer = as_context->RegisterExpectedFunction("void UpdateMultiplayer(int frames)", false);
+ as_funcs.force_applied = as_context->RegisterExpectedFunction("void ForceApplied(vec3 force)", true);
+ as_funcs.get_temp_health = as_context->RegisterExpectedFunction("float GetTempHealth()", true);
+ as_funcs.was_hit = as_context->RegisterExpectedFunction("int WasHit(string type, string attack_path, vec3 dir, vec3 pos, int attacker_id, float attack_damage_mult, float attack_knockback_mult)", true);
+ as_funcs.reset = as_context->RegisterExpectedFunction("void Reset()", true);
+ as_funcs.post_reset = as_context->RegisterExpectedFunction("void PostReset()", true);
+ as_funcs.attach_weapon = as_context->RegisterExpectedFunction("void AttachWeapon(int)", true);
+ as_funcs.set_enabled = as_context->RegisterExpectedFunction("void SetEnabled(bool)", true);
+ as_funcs.receive_message = as_context->RegisterExpectedFunction("void ReceiveMessage(string)", true);
+ as_funcs.pre_draw_camera = as_context->RegisterExpectedFunction("void PreDrawCamera(float curr_game_time)", false);
+ as_funcs.pre_draw_frame = as_context->RegisterExpectedFunction("void PreDrawFrame(float curr_game_time)", false);
+ as_funcs.pre_draw_camera_no_cull = as_context->RegisterExpectedFunction("void PreDrawCameraNoCull(float curr_game_time)", false);
+ as_funcs.update_paused = as_context->RegisterExpectedFunction("void UpdatePaused()", true);
+ as_funcs.about_to_be_hit_by_item = as_context->RegisterExpectedFunction("int AboutToBeHitByItem(int id)", true);
+ as_funcs.InputToEngine = as_context->RegisterExpectedFunction("uint InputToEngine(int input", false);
+ as_funcs.attach_misc = as_context->RegisterExpectedFunction("void AttachMisc(int)", false);
+ as_funcs.was_blocked = as_context->RegisterExpectedFunction("void WasBlocked()", false);
+ as_funcs.reset_waypoint_target = as_context->RegisterExpectedFunction("void ResetWaypointTarget()", false);
+ as_funcs.dispose = as_context->RegisterExpectedFunction("void Dispose()", false);
+ as_funcs.apply_host_input = as_context->RegisterExpectedFunction("void ApplyHostInput(uint inputs)", false);
+ as_funcs.register_mp_callbacks = as_context->RegisterExpectedFunction("void RegisterMPCallBacks()", false);
+ as_funcs.set_damage_time_from_socket= as_context->RegisterExpectedFunction("void SetDamageTimeFromSocket()", false);
+ as_funcs.set_damage_blood_time_from_socket = as_context->RegisterExpectedFunction("void SetDamageBloodTimeFromSocket()", false);
+ as_funcs.start_pose = as_context->RegisterExpectedFunction("bool StartPose(string animation_path)", false);
+
+ as_funcs.apply_host_camera_flat_facing = as_context->RegisterExpectedFunction("void ApplyHostCameraFlatFacing(vec3 direction)", false);
+
+ AttachEngine(ctx);
+ AttachScenegraph(ctx, scenegraph_);
+
+ as_context->RegisterGlobalFunction("float GetAnimationEventTime( string &in anim_path, string &in event_label )", asFUNCTION(GetAnimationEventTime), asCALL_CDECL);
+ as_context->RegisterGlobalProperty("MovementObject this_mo", this);
+
+ static const int _as_at_grip = _at_grip;
+ static const int _as_at_sheathe = _at_sheathe;
+
+ as_context->RegisterGlobalProperty("const int _at_grip", (void*)&_as_at_grip);
+ as_context->RegisterGlobalProperty("const int _at_sheathe", (void*)&_as_at_sheathe);
+
+ static const unsigned char __ANM_MIRRORED = _ANM_MIRRORED;
+ static const unsigned char __ANM_MOBILE = _ANM_MOBILE;
+ static const unsigned char __ANM_SUPER_MOBILE = _ANM_SUPER_MOBILE;
+ static const unsigned char __ANM_SWAP = _ANM_SWAP;
+ static const unsigned char __ANM_FROM_START = _ANM_FROM_START;
+
+ as_context->RegisterGlobalProperty("const uint8 _ANM_MIRRORED", (void*)&__ANM_MIRRORED);
+ as_context->RegisterGlobalProperty("const uint8 _ANM_MOBILE", (void*)&__ANM_MOBILE);
+ as_context->RegisterGlobalProperty("const uint8 _ANM_SUPER_MOBILE", (void*)&__ANM_SUPER_MOBILE);
+ as_context->RegisterGlobalProperty("const uint8 _ANM_SWAP", (void*)&__ANM_SWAP);
+ as_context->RegisterGlobalProperty("const uint8 _ANM_FROM_START", (void*)&__ANM_FROM_START);
+
+ character_script_getter.AttachToScript(as_context.get(), "character_getter");
+ reaction_script_getter.AttachToScript(as_context.get(), "reaction_getter");
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ PROFILER_ENTER(g_profiler_ctx, "Loading script");
+ /*if(!object_script_path.empty()) {
+ current_control_script_path = object_script_path;
+ } else {*/
+ current_control_script_path = scenegraph_->level->GetNPCScript(this);
+ //}
+ Path script_path = FindFilePath(AssemblePath(script_dir_path,current_control_script_path), kDataPaths | kModPaths);
+ while(as_context->LoadScript(script_path)==false){};
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Calling script Init()");
+ ASArglist args;
+ args.AddObject(&character_path);
+ ASArg ret;
+ asBYTE v;
+ ret.type = _as_bool;
+ ret.data = &v;
+ if( as_context->CallScriptFunction(as_funcs.init, &args, &ret) ) {
+ if( *(asBYTE*)ret.data ) {
+ //All good;
+ } else {
+ LOGE << "Failed in Init on MovementObject script" << std::endl;
+ return false;
+ }
+ } else {
+ LOGE << "Failed at calling Init on MovementObject script" << std::endl;
+ return false;
+ }
+ }
+ as_context->CallScriptFunction(as_funcs.set_parameters);
+ SetRotationFromEditorTransform();
+
+ char_sphere = scenegraph_->abstract_bullet_world_->CreateSphere(
+ position, _leg_sphere_size, 0);
+ char_sphere->SetVisibility(true);
+ char_sphere->owner_object = this;
+ char_sphere->body->setCollisionFlags(char_sphere->body->getCollisionFlags() |
+ btCollisionObject::CF_NO_CONTACT_RESPONSE);
+
+ PROFILER_ENTER(g_profiler_ctx, "Exporting docs");
+ char path[kPathSize];
+ FormatString(path, kPathSize, "%saschar_docs.h", GetWritePath(CoreGameModID).c_str());
+ as_context->ExportDocs(path);
+
+
+ RegisterMPCallbacks();
+ PROFILER_LEAVE(g_profiler_ctx);
+ return true;
+}
+
+bool MovementObject::InitializeMultiplayer() {
+ if (!as_context->CallScriptFunction(as_funcs.init_multiplayer)) {
+ LOGE << "Failed to initialize mutliplayer script for client " << std::endl;
+ return false;
+ }
+ return true;
+}
+
+void MovementObject::GetShaderNames(std::map<std::string, int>& shaders) {
+ shaders[shader] = 0;
+ if(rigged_object_.get() != NULL) {
+ rigged_object_.get()->GetShaderNames(shaders);
+ }
+}
+
+extern std::stack<ASContext*> active_context_stack;
+static void ItemObjectError(int which) __attribute__((noreturn));
+static void ItemObjectError(int which) {
+ std::string callstack = active_context_stack.top()->GetCallstack();
+ FatalError("Error", "There is no item object %d.\n Called from: %s\n", callstack.c_str(), which);
+}
+
+void MovementObject::AttachItemToSlotAttachmentRef( int which, AttachmentType type, bool mirrored, const AttachmentRef* ref, bool from_socket ) {
+ Online* online = Online::Instance();
+
+ Object* obj = scenegraph_->GetObjectFromID(which);
+ if(!obj || obj->GetType() != _item_object){
+ ItemObjectError(which);
+ }
+
+ if (online->IsActive() && !from_socket) {
+ online->Send<OnlineMessages::AttachToMessage>(GetID(), which, type, true, mirrored);
+ }
+ ItemObject* io = (ItemObject*)obj;
+ io->InvalidateHeldReaders();
+ io->SetHolderID(GetID());
+ rigged_object_->AttachItemToSlot(io, type, mirrored, ref);
+ character_script_getter.ItemsChanged(rigged_object_->GetWieldedItemRefs());
+ reaction_script_getter.ItemsChanged(rigged_object_->GetWieldedItemRefs());
+}
+
+void MovementObject::UpdateWeapons() {
+ character_script_getter.ItemsChanged(rigged_object_->GetWieldedItemRefs());
+ reaction_script_getter.ItemsChanged(rigged_object_->GetWieldedItemRefs());
+}
+
+void MovementObject::AttachItemToSlot( int which, AttachmentType type, bool mirrored ) {
+ AttachItemToSlotAttachmentRef(which, type, mirrored, NULL, true);
+}
+
+void MovementObject::AttachItemToSlotEditor(
+ int which,
+ AttachmentType type,
+ bool mirrored,
+ const AttachmentRef& attachment_ref,
+ bool from_socket)
+{
+
+ Object* object = scenegraph_->GetObjectFromID(which);
+ if( object != nullptr && object->GetType() == EntityType::_item_object ) {
+ ItemObject* item_object = (ItemObject*) object;
+ item_object->InvalidateReaders();
+
+ ASArglist args2;
+ args2.Add(which);
+ as_context->CallScriptFunction(as_funcs.notify_item_detach, &args2);
+ if(type == _at_unspecified){
+ type = _at_grip;
+ mirrored = false;
+ }
+ AttachItemToSlotAttachmentRef(which, type, mirrored, &attachment_ref, from_socket);
+ ASArglist args;
+ args.Add(which);
+ args.Add(type);
+ args.Add(mirrored);
+ as_context->CallScriptFunction(as_funcs.handle_editor_attachment, &args);
+
+ for(std::list<ItemObjectScriptReader>::iterator iter = item_connections.begin();
+ iter != item_connections.end();)
+ {
+ if((*iter)->GetID() == which) {
+ iter = item_connections.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+
+ item_connections.resize(item_connections.size()+1);
+ ItemObjectScriptReader& item_connection = item_connections.back();
+ item_connection.AttachToItemObject((ItemObject*)scenegraph_->GetObjectFromID(which));
+ item_connection.attachment_type = type;
+ item_connection.attachment_mirror = mirrored;
+ item_connection.attachment_ref = attachment_ref;
+ item_connection.SetInvalidateCallback(&MovementObject::InvalidatedItemCallback, this);
+ } else {
+ LOGE << "Tried to attach item id -1 to movement object " << *this << std::endl;
+ }
+}
+
+void MovementObject::ASDetachItem( int which ) {
+ Object* obj = scenegraph_->GetObjectFromID(which);
+ if(!obj || obj->GetType() != _item_object) {
+ ItemObjectError(which);
+ }
+ rigged_object_->DetachItem((ItemObject*)obj);
+ character_script_getter.ItemsChanged(rigged_object_->GetWieldedItemRefs());
+ reaction_script_getter.ItemsChanged(rigged_object_->GetWieldedItemRefs());
+}
+
+void MovementObject::ASDetachAllItems() {
+ rigged_object_->DetachAllItems();
+ character_script_getter.ItemsChanged(rigged_object_->GetWieldedItemRefs());
+ reaction_script_getter.ItemsChanged(rigged_object_->GetWieldedItemRefs());
+}
+
+void MovementObject::RegisterMPCallbacks() const {
+ if (as_context->HasFunction(as_funcs.register_mp_callbacks)) {
+ as_context->CallScriptFunction(as_funcs.register_mp_callbacks);
+ }
+}
+
+void MovementObject::Collided( const vec3& pos, float impulse, const CollideInfo &collide_info, BulletObject* object ) {
+ as_context->CallScriptFunction(as_funcs.contact);
+ if(!collide_info.true_impact){
+ return;
+ }
+ float hardness = scenegraph_->GetMaterialHardness(pos);
+ //printf("Hardness: %f\n", hardness);
+
+ if(rigged_object_->InHeadChain(object)){
+ hardness *= 5.0f;
+ //printf("HEAD HIT %f\n", impulse * hardness);
+ }
+
+ ASArglist args;
+ args.Add(pos[0]);
+ args.Add(pos[1]);
+ args.Add(pos[2]); // For some reason was getting asCONTEXT_NOT_PREPARED when trying to send whole vec3 at once
+ args.Add(impulse);
+ args.Add(hardness);
+ as_context->CallScriptFunction(as_funcs.collided, &args);
+}
+
+void MovementObject::InvalidatedItem(ItemObjectScriptReader *invalidated) {
+ int weap_id = -1;
+ for(std::list<ItemObjectScriptReader>::iterator iter = item_connections.begin();
+ iter != item_connections.end();)
+ {
+ if(&(*iter) == invalidated){
+ weap_id = (*iter)->GetID();
+ iter = item_connections.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+ if(as_context.get()){
+ ASArglist args;
+ args.Add(weap_id);
+ as_context->CallScriptFunction(as_funcs.notify_item_detach, &args);
+ }
+}
+
+
+void MovementObject::InvalidatedItemCallback(ItemObjectScriptReader *invalidated, void* this_ptr) {
+ ((MovementObject*)this_ptr)->InvalidatedItem(invalidated);
+}
+
+bool MovementObject::ConnectTo( Object& other, bool checking_other /*= false*/ ) {
+ EntityType other_type = other.GetType();
+ if(other.GetType() == _hotspot_object) {
+ return Object::ConnectTo(other, checking_other);
+ } else if(other_type == _path_point_object || other_type == _movement_object){
+ connected_pathpoint_id = other.GetID();
+ as_context->CallScriptFunction(as_funcs.reset_waypoint_target);
+ return true;
+ } else {
+ if(checking_other){
+ return false;
+ } else {
+ return other.ConnectTo(*this, true);
+ }
+ }
+}
+
+bool MovementObject::AcceptConnectionsFrom(Object::ConnectionType type, Object& object) {
+ return type == kCTPathPoints
+ || type == kCTMovementObjects
+ || type == kCTEnvObjectsAndGroups
+ || type == kCTItemObjects
+ || type == kCTPlaceholderObjects;
+}
+
+bool MovementObject::Disconnect( Object& other, bool from_socket /* = false */, bool checking_other /*= false*/ ) {
+ Online* online = Online::Instance();
+ if (online->IsActive() && !from_socket) {
+ online->Send<OnlineMessages::AttachToMessage>(GetID(), other.GetID(), 0, false, false);
+ }
+
+ if(connected_pathpoint_id == other.GetID() && (other.GetType() == _path_point_object || other.GetType() == _movement_object)){
+ connected_pathpoint_id = -1;
+ as_context->CallScriptFunction(as_funcs.reset_waypoint_target);
+ return true;
+ } else if(other.GetType() == _item_object){
+ for(std::list<ItemObjectScriptReader>::iterator iter = item_connections.begin();
+ iter != item_connections.end();)
+ {
+ if((*iter)->GetID() == other.GetID()){
+ iter = item_connections.erase(iter);
+ if(as_context.get()){
+ ASArglist args;
+ args.Add(other.GetID());
+ as_context->CallScriptFunction(as_funcs.notify_item_detach, &args);
+ }
+ rigged_object_->DetachItem((ItemObject*)&other);
+ ((ItemObject*)(&other))->Reset();
+ } else {
+ ++iter;
+ }
+ }
+ return true;
+ } else if(other.GetType() == _hotspot_object) {
+ return Object::Disconnect(other, checking_other);
+ } else {
+ if(checking_other){
+ return false;
+ } else {
+ return other.Disconnect(*this, true);
+ }
+ }
+}
+
+void MovementObject::GetConnectionIDs(std::vector<int>* cons) {
+ if( connected_pathpoint_id != -1 ) {
+ cons->push_back(connected_pathpoint_id);
+ }
+ if( rigged_object() ) {
+ AttachedItemList::iterator attached_it = rigged_object()->attached_items.items.begin();
+ for(; attached_it != rigged_object()->attached_items.items.end(); attached_it++ ) {
+ ItemObject* item = attached_it->item.GetAttached();
+ if( item ) {
+ cons->push_back(item->GetID());
+ }
+ }
+
+ AttachedItemList::iterator stuck_it = rigged_object()->stuck_items.items.begin();
+ for( ;stuck_it != rigged_object()->stuck_items.items.end(); stuck_it++ ) {
+ ItemObject* item = stuck_it->item.GetAttached();
+ if( item ) {
+ cons->push_back(item->GetID());
+ }
+ }
+ }
+}
+
+void MovementObject::NotifyDeleted(Object* other) {
+ Object::NotifyDeleted(other);
+ if(other->GetID() == connected_pathpoint_id){
+ connected_pathpoint_id = -1;
+ as_context->CallScriptFunction(as_funcs.reset_waypoint_target);
+ }
+ for(std::list<ItemObjectScriptReader>::iterator iter = item_connections.begin();
+ iter != item_connections.end();)
+ {
+ if(other->GetID() == (*iter)->GetID()){
+ iter = item_connections.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+ if(other->GetType() == _movement_object){
+ ASArglist args;
+ args.Add(other->GetID());
+ as_context->CallScriptFunction(as_funcs.movement_object_deleted, &args);
+ }
+}
+
+void MovementObject::FinalizeLoadedConnections() {
+ Object::FinalizeLoadedConnections();
+ if( do_connection_finalization_remap ) {
+ if( connection_finalization_remap.find(connected_pathpoint_id) != connection_finalization_remap.end() ) {
+ connected_pathpoint_id = connection_finalization_remap[connected_pathpoint_id];
+ } else {
+ connected_pathpoint_id = -1;
+ }
+ }
+
+ for(unsigned i=0; i<item_connection_vec.size(); ++i){
+ ItemConnection &item_connection = item_connection_vec[i];
+
+ if( do_connection_finalization_remap ) {
+ if( connection_finalization_remap.find(item_connection.id) != connection_finalization_remap.end() ) {
+ item_connection.id = connection_finalization_remap[item_connection.id];
+ } else {
+ item_connection.id = -1;
+ }
+ }
+
+ int which = item_connection.id;
+ AttachmentType type = item_connection.attachment_type;
+ bool mirrored = item_connection.mirrored;
+ AttachItemToSlotEditor(which, type, mirrored, item_connection.attachment_ref);
+ }
+ item_connection_vec.clear();
+
+ if(!env_object_attach_data.empty()){
+ std::vector<AttachedEnvObject> attached_env_objects;
+ Deserialize(env_object_attach_data, attached_env_objects);
+ for(int i=0; i<(int)attached_env_objects.size(); ) {
+ if(!attached_env_objects[i].direct_ptr){
+ if( do_connection_finalization_remap ) {
+ if( connection_finalization_remap.find(attached_env_objects[i].legacy_obj_id) != connection_finalization_remap.end() ) {
+ attached_env_objects[i].legacy_obj_id = connection_finalization_remap[attached_env_objects[i].legacy_obj_id];
+ } else {
+ attached_env_objects[i].legacy_obj_id = -1;
+ }
+ }
+
+ Object* obj = scenegraph_->GetObjectFromID(attached_env_objects[i].legacy_obj_id);
+ if(obj){
+ attached_env_objects[i].direct_ptr = obj;
+ obj->SetParent(this);
+ ++i;
+ } else {
+ attached_env_objects[i] = attached_env_objects.back();
+ attached_env_objects.resize(attached_env_objects.size()-1);
+ }
+ } else {
+ ++i;
+ }
+ }
+ if(rigged_object_.get()){
+ rigged_object_->children = attached_env_objects;
+ }
+ Serialize(attached_env_objects, env_object_attach_data);
+ }
+ connection_finalization_remap.clear();
+ do_connection_finalization_remap = false;
+}
+
+void MovementObject::GetDesc(EntityDescription &desc) const {
+ Object::GetDesc(desc);
+ desc.AddString(EDF_FILE_PATH, obj_file);
+ if(!object_npc_script_path.empty())
+ desc.AddString(EDF_NPC_SCRIPT_PATH, object_npc_script_path);
+ if(!object_pc_script_path.empty())
+ desc.AddString(EDF_PC_SCRIPT_PATH, object_pc_script_path);
+ desc.AddInt(EDF_IS_PLAYER, (int)is_player);
+
+ std::vector<int> connections;
+ connections.push_back(connected_pathpoint_id);
+ desc.AddIntVec(EDF_CONNECTIONS, connections);
+
+ std::vector<ItemConnectionData> item_connections_vec;
+ for(std::list<ItemObjectScriptReader>::const_iterator iter = item_connections.begin();
+ iter != item_connections.end(); ++iter)
+ {
+ const ItemObjectScriptReader& script_reader = (*iter);
+ ItemConnectionData item_connection;
+ item_connection.id = script_reader->GetID();
+ item_connection.attachment_type = script_reader.attachment_type;
+ item_connection.mirrored = script_reader.attachment_mirror;
+ if(script_reader.attachment_ref.valid()){
+ item_connection.attachment_str = script_reader.attachment_ref->path_;
+ }
+ item_connections_vec.push_back(item_connection);
+ }
+ desc.AddItemConnectionVec(EDF_ITEM_CONNECTIONS, item_connections_vec);
+ desc.AddPalette(EDF_PALETTE, palette);
+
+ if(!rigged_object_->children.empty()){
+ std::vector<char> temp_env_object_attach_data;
+ Serialize(rigged_object_->children, temp_env_object_attach_data);
+ desc.AddData(EDF_ENV_OBJECT_ATTACH, temp_env_object_attach_data);
+ }
+ for(int i=0, len=rigged_object_->children.size(); i<len; ++i){
+ Object* env_obj = rigged_object_->children[i].direct_ptr;
+ if(env_obj){
+ desc.children.resize(desc.children.size()+1);
+ env_obj->GetDesc(desc.children.back());
+ }
+ }
+}
+
+void MovementObject::ASSetScriptUpdatePeriod(int val) {
+ update_script_period = val;
+}
+
+void MovementObject::ChangeControlScript( const std::string &script_path ) {
+ PROFILER_ZONE(g_profiler_ctx, "ChangeControlScript");
+ if(current_control_script_path != script_path) {
+ Path found_path = FindFilePath(AssemblePath(script_dir_path,script_path.c_str()),kDataPaths|kModPaths);
+ if(found_path.isValid()) {
+ current_control_script_path = script_path;
+ if(as_context.get()){
+ as_context->SaveGlobalVars();
+ as_context->LoadScript(found_path);
+ as_context->LoadGlobalVars();
+ update_script_period = (script_path==scenegraph_->level->GetPCScript(this))?1:4;
+ update_script_counter = rand()%update_script_period-update_script_period;
+ rigged_object_->SetAnimUpdatePeriod(max(2,update_script_period));
+ as_context->CallScriptFunction(as_funcs.script_swap);
+ }
+ } else {
+ DisplayFormatMessage("Cannot find file", "Cannot change to script file \"%s\" because it couldn't be found", script_path.c_str());
+ }
+ if(script_path == "playermpcontrol.as" || script_path == "remotecontrol.as") {
+ InitializeMultiplayer();
+ }
+ }
+}
+
+void MovementObject::SetRotationFromEditorTransform() {
+ vec3 dir = (transform_*vec4(0.0f,0.0f,1.0f,0.0f)).xyz();
+ dir[1] = 0.0f;
+ dir = normalize(dir);
+ if(length_squared(dir) == 0.0f){
+ dir = vec3(1.0f,0.0f,0.0f);
+ }
+ SetRotationFromFacing(dir);
+}
+
+void MovementObject::CollideWith( MovementObject* other ) {
+ ASArglist args;
+ args.AddAddress(other);
+ as_context->CallScriptFunction(as_funcs.handle_collisions_btc, &args);
+}
+
+void MovementObject::SetScriptParams( const ScriptParamMap& spm ) {
+ Object::SetScriptParams(spm);
+}
+
+void MovementObject::UpdateScriptParams() {
+ if(as_context.get()){
+ as_context->CallScriptFunction(as_funcs.set_parameters);
+ }
+}
+
+void MovementObject::ApplyPalette( const OGPalette& _palette, bool from_socket ) {
+ Online* online = Online::Instance();
+ palette = _palette;
+
+ if (online->IsActive() && !from_socket) {
+ online->SendAvatarPaletteChange(_palette, GetID()); // TODO Is this something clients should send to the host?
+ }
+
+ if(rigged_object_.get() != nullptr){
+ rigged_object_->ApplyPalette(palette);
+ }
+}
+
+OGPalette * MovementObject::GetPalette() {
+ return &palette;
+}
+
+void MovementObject::ReceiveObjectMessageVAList( OBJECT_MSG::Type type, va_list args ) {
+ switch(type){
+ case OBJECT_MSG::QUEUE_SCRIPT: {
+ const std::string &str = *va_arg(args, std::string*);
+ message_queue.push(str);
+ break;}
+ case OBJECT_MSG::SCRIPT: {
+ const std::string &str = *va_arg(args, std::string*);
+ ReceiveMessage(str);
+ break;}
+ case OBJECT_MSG::UPDATE_GPU_SKINNING:
+ rigged_object_->UpdateGPUSkinning();
+ break;
+ default:
+ Object::ReceiveObjectMessageVAList(type, args);
+ break;
+ }
+}
+
+void MovementObject::HitByItem( int id, const vec3 &point, const std::string &material, int type) {
+ ASArglist args;
+ args.AddObject((void*)&material);
+ args.AddObject((void*)&point);
+ args.Add(id);
+ args.Add(type);
+ as_context->CallScriptFunction(as_funcs.hit_by_item, &args);
+}
+
+void MovementObject::Execute( std::string string ) {
+ as_context->Execute(string);
+}
+
+bool MovementObject::ASHasVar( std::string name ) {
+ return as_context->GetVarPtr(name.c_str()) != NULL;
+}
+
+int MovementObject::ASGetIntVar( std::string name ) {
+ return *((int*)as_context->GetVarPtr(name.c_str()));
+}
+
+int MovementObject::ASGetArrayIntVar( std::string name, int index ) {
+ return *((int*)as_context->GetArrayVarPtr(name, index));
+}
+
+float MovementObject::ASGetFloatVar( std::string name ) {
+ return *((float*)as_context->GetVarPtr(name.c_str()));
+}
+
+bool MovementObject::ASGetBoolVar( std::string name ) {
+ return *((bool*)as_context->GetVarPtr(name.c_str()));
+}
+
+void MovementObject::ASSetIntVar( std::string name, int value ) {
+ *(int*)as_context->GetVarPtr(name.c_str()) = value;
+}
+
+void MovementObject::ASSetArrayIntVar( std::string name, int index, int value ) {
+ *(int*)as_context->GetArrayVarPtr(name, index) = value;
+}
+
+void MovementObject::ASSetFloatVar( std::string name, float value ) {
+ *(float*)as_context->GetVarPtr(name.c_str()) = value;
+}
+
+void MovementObject::ASSetBoolVar( std::string name, bool value ) {
+ *(bool*)as_context->GetVarPtr(name.c_str()) = value;
+}
+
+void MovementObject::Dispose() {
+ as_context->CallScriptFunction(as_funcs.dispose);
+ as_context.reset(NULL);
+ rigged_object_->as_context = NULL;
+
+ for(int i=0, len=occlusion_queries.size(); i<len; ++i){
+ const GLuint val = occlusion_queries[i].id;
+ glDeleteQueries(1, &val);
+ }
+ occlusion_queries.clear();
+}
+
+void MovementObject::AddToAttackHistory( const std::string &str ) {
+ attack_history.Add(str);
+}
+
+float MovementObject::CheckAttackHistory( const std::string &str) {
+ return attack_history.Check(str);
+}
+
+void MovementObject::ClearAttackHistory() {
+ attack_history.Clear();
+}
+
+void MovementObject::RemovePhysicsShapes() {
+ if( char_sphere ) {
+ scenegraph_->abstract_bullet_world_->RemoveObject(&char_sphere);
+ char_sphere = NULL;
+ }
+}
+
+bool MovementObject::SetFromDesc( const EntityDescription &desc ) {
+ bool ret = Object::SetFromDesc(desc);
+ if( ret ) {
+ for(unsigned i=0; i<desc.fields.size(); ++i){
+ const EntityDescriptionField& field = desc.fields[i];
+ switch(field.type){
+ case EDF_FILE_PATH: {
+ std::string type_file;
+ field.ReadString(&type_file);
+ if(obj_file != type_file){
+ //ActorFileRef afr = ActorFiles::Instance()->ReturnRef(type_file);
+ ActorFileRef afr = Engine::Instance()->GetAssetManager()->LoadSync<ActorFile>(type_file);
+ if( afr.valid() ) {
+ actor_file_ref = afr;
+ actor_script_path = afr->script;
+ character_path = afr->character;
+ obj_file = type_file;
+ } else {
+ LOGE << "Failed at loading ActorFile \"" << type_file << "\" for MovementObject." << std::endl;
+ ret = false;
+ }
+ }
+ break;}
+ case EDF_IS_PLAYER:{
+ int read_is_player;
+ memread(&read_is_player, sizeof(int), 1, field.data);
+ is_player = (read_is_player != 0);
+ break;}
+ case EDF_CONNECTIONS:{
+ std::vector<int> connections;
+ field.ReadIntVec(&connections);
+ if(!connections.empty()){
+ connected_pathpoint_id = connections[0];
+ } else {
+ connected_pathpoint_id = -1;
+ }
+ break;}
+ case EDF_ITEM_CONNECTIONS:{
+ std::vector<ItemConnectionData> item_connections;
+ field.ReadItemConnectionDataVec(&item_connections);
+ for(int i=0, len=item_connections.size(); i<len; ++i){
+ if( item_connections[i].id != -1 ) {
+ ItemConnection item_connection;
+ item_connection.id = item_connections[i].id;
+ item_connection.mirrored = item_connections[i].mirrored != 0;
+ item_connection.attachment_type = (AttachmentType)item_connections[i].attachment_type;
+ if(!item_connections[i].attachment_str.empty()){
+ item_connection.attachment_ref = Engine::Instance()->GetAssetManager()->LoadSync<Attachment>(item_connections[i].attachment_str);
+ if( item_connection.attachment_ref.valid() == false ) {
+ LOGE << "Unable to load attachment " << item_connections[i].attachment_str << std::endl;
+ }
+ }
+ item_connection_vec.push_back(item_connection);
+ } else {
+ LOGE << "movementobject " << *this << " has item connection with id -1, skipping." << std::endl;
+ }
+ }
+ break;}
+ case EDF_PALETTE: {
+ OGPalette palette;
+ ReadPaletteFromRAM(palette, field.data);
+ ApplyPalette(palette);
+ break;}
+ case EDF_ENV_OBJECT_ATTACH: {
+ env_object_attach_data = field.data;
+ break;}
+ case EDF_NPC_SCRIPT_PATH: {
+ field.ReadString(&object_npc_script_path);
+ break;}
+ case EDF_PC_SCRIPT_PATH: {
+ field.ReadString(&object_pc_script_path);
+ break;}
+ }
+ }
+
+ if(!env_object_attach_data.empty()){
+ std::vector<AttachedEnvObject> attached_env_objects;
+ Deserialize(env_object_attach_data, attached_env_objects);
+ for (int i=0, len=attached_env_objects.size(); i<len; ++i) {
+ attached_env_objects[i].direct_ptr = NULL;
+ }
+ for (int i=0, len=desc.children.size(); i<len; ++i) {
+ EntityDescription new_desc = desc.children[i];
+ Object* obj = CreateObjectFromDesc(new_desc);
+ if( obj ) {
+ attached_env_objects[i].direct_ptr = obj;
+ obj->SetParent(this);
+ } else {
+ attached_env_objects[i].direct_ptr = NULL;
+ LOGE << "Failed at creating object for movementobject attachment" << std::endl;
+ }
+ }
+ Serialize(attached_env_objects, env_object_attach_data);
+ }
+ }
+ return ret;
+}
+
+void MovementObject::ChildLost(Object* obj){
+ Online* online = Online::Instance();
+
+ if(rigged_object_.get()){
+ for(int i=0, len=rigged_object_->children.size(); i<len; ++i){
+ if(rigged_object_->children[i].direct_ptr == obj){
+ rigged_object_->children.erase(rigged_object_->children.begin()+i);
+ if (online->IsActive()) {
+ online->Send<OnlineMessages::AttachToMessage>(GetID(), obj->GetID(), 0, false, false);
+ }
+
+ break;
+ }
+ }
+ Serialize(rigged_object_->children, env_object_attach_data);
+ }
+}
+
+RiggedObject* MovementObject::rigged_object() {
+ return rigged_object_.get();
+}
+
+MovementObject::~MovementObject() {
+ RemovePhysicsShapes();
+ while(!item_connections.empty()){
+ ItemObject* obj = item_connections.front().obj;
+ Disconnect(*obj);
+ obj->ActivatePhysics();
+ }
+}
+
+const float kAttackTransitionTime = 2.0f;
+
+void AttackHistory::Add( const std::string &str ) {
+ AttackHistoryEntry entry;
+ entry.attack_script_getter.Load(str);
+ entry.time = game_timer.game_time;
+ if(!entries.empty() && entries.back().time > entry.time - kAttackTransitionTime){
+ const float kTransitionDecaySubtract = 0.05f;
+ const float kTransitionDecayMultiply = 0.9f;
+ OuterTransitionMap::iterator iter = transitions.begin();
+ while(iter != transitions.end()){
+ InnerTransitionMap& itm = iter->second;
+ InnerTransitionMap::iterator iter2 = itm.begin();
+ while(iter2 != itm.end()){
+ iter2->second *= kTransitionDecayMultiply;
+ iter2->second -= kTransitionDecaySubtract;
+ if(iter2->second <= 0.0f){
+ itm.erase(iter2++);
+ } else {
+ ++iter2;
+ }
+ }
+ if(itm.empty()){
+ transitions.erase(iter++);
+ } else {
+ ++iter;
+ }
+ }
+
+ const AttackHistoryEntry &last_entry = entries.back();
+ unsigned long hash = djb2_string_hash(last_entry.attack_script_getter.attack_ref()->path_.c_str());
+ transitions[hash][entry.attack_script_getter.attack_ref()] += 1.0f;
+ }
+ entries.clear();
+ entries.push_back(entry);
+}
+
+void GetAttackInfo(const AttackScriptGetter &asg, AttackHeight* ah, AttackDirection* ad, std::string* name) {
+ const AttackRef& attack_ref = asg.attack_ref();
+ bool mirror = asg.mirror();
+ *ah = attack_ref->height;
+ *ad = attack_ref->direction;
+ *name = attack_ref->path_;
+ if(mirror){
+ switch(*ad){
+ case _right: *ad = _left; break;
+ case _left: *ad = _right; break;
+ default: break;
+ }
+ }
+}
+
+float AttackHistory::Check( const std::string &str ) {
+ if(entries.empty()){
+ return 0.5f;
+ }
+ float cur_time = game_timer.game_time;
+ AttackScriptGetter attack_script_getter;
+ attack_script_getter.Load(str);
+ AttackHeight attack_height;
+ AttackDirection attack_direction;
+ std::string name;
+ GetAttackInfo(attack_script_getter, &attack_height, &attack_direction, &name);
+
+ const AttackHistoryEntry &entry = entries.back();
+ const AttackScriptGetter &old_attack_script_getter = entry.attack_script_getter;
+ AttackHeight old_attack_height;
+ AttackDirection old_attack_direction;
+ std::string old_name;
+ GetAttackInfo(old_attack_script_getter, &old_attack_height, &old_attack_direction, &old_name);
+
+ float similarity = 0.5f;
+ if(old_attack_height == attack_height){
+ similarity += 0.15f;
+ } else if((old_attack_height == _low && attack_height == _high) ||
+ (old_attack_height == _high && attack_height == _low))
+ {
+ similarity -= 0.15f;
+ }
+ if(old_attack_direction == attack_direction){
+ similarity += 0.15f;
+ } else if((old_attack_direction == _left && attack_direction == _right) ||
+ (old_attack_direction == _right && attack_direction == _left))
+ {
+ similarity -= 0.15f;
+ }
+ if(old_name == name){
+ similarity += 0.15f;
+ } else {
+ similarity -= 0.15f;
+ }
+
+ unsigned long hash = djb2_string_hash(old_name.c_str());
+ const InnerTransitionMap &itm = transitions[hash];
+ InnerTransitionMap::const_iterator iter = itm.find(attack_script_getter.attack_ref());
+ float transition_probability = 0.0f;
+ if(entry.time > cur_time - kAttackTransitionTime){
+ float num = 0.0f;
+ if(iter != itm.end()){
+ num = iter->second;
+ }
+ if(num != 0){
+ float total = 0;
+ for(InnerTransitionMap::const_iterator iter = itm.begin();
+ iter != itm.end(); ++iter)
+ {
+ total += iter->second;
+ }
+ transition_probability = num / total;
+ }
+ }
+ similarity += (1.0f - similarity) * transition_probability;
+ return similarity;
+}
+
+void AttackHistory::Clear() {
+ entries.clear();
+ transitions.clear();
+}
diff --git a/Source/Objects/movementobject.h b/Source/Objects/movementobject.h
new file mode 100644
index 00000000..243b94c8
--- /dev/null
+++ b/Source/Objects/movementobject.h
@@ -0,0 +1,361 @@
+//-----------------------------------------------------------------------------
+// Name: movementobject.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Objects/object.h>
+#include <Objects/itemobjectscriptreader.h>
+#include <Objects/riggedobject.h>
+
+#include <Graphics/camera.h>
+#include <Graphics/palette.h>
+
+#include <Game/attackscript.h>
+#include <Game/reactionscript.h>
+#include <Game/characterscript.h>
+
+#include <Online/time_interpolator.h>
+#include <Online/online_datastructures.h>
+
+#include <Asset/Asset/material.h>
+#include <Asset/Asset/actorfile.h>
+#include <Asset/Asset/soundgroup.h>
+#include <Asset/Asset/attacks.h>
+
+#include <Scripting/angelscript/ascontext.h>
+#include <Scripting/angelscript/ascollisions.h>
+
+#include <Internal/timer.h>
+#include <Math/quaternions.h>
+#include <AI/pathfind.h>
+#include <Sound/soundplayinfo.h>
+
+#include <queue>
+//-----------------------------------------------------------------------------
+// Class Definition
+//-----------------------------------------------------------------------------
+
+const float _cam_follow_distance = 2.0f;
+const float _leg_sphere_size = 0.5f;
+
+class Decal;
+class ItemObject;
+class SweptSlideCallback;
+class ContactSlideCallback;
+class PathPointObject;
+class IMUIContext;
+
+using std::list;
+
+struct VoiceQueue {
+ std::string path;
+ float delay;
+ float pitch;
+ VoiceQueue(const std::string &_path, float _delay, float _pitch):
+ path(_path),
+ delay(_delay),
+ pitch(_pitch)
+ {}
+};
+
+class AttackHistory {
+public:
+ void Add(const std::string &str);
+ float Check(const std::string &str);
+ void Clear();
+
+private:
+ struct AttackHistoryEntry {
+ AttackScriptGetter attack_script_getter;
+ float time;
+ };
+ typedef std::list<AttackHistoryEntry> AttackHistoryEntries;
+ AttackHistoryEntries entries;
+ typedef std::map<AttackRef, float> InnerTransitionMap;
+ typedef std::map<unsigned long, InnerTransitionMap> OuterTransitionMap;
+ OuterTransitionMap transitions;
+};
+
+class MovementObject: public Object {
+public:
+ float mp_time = 0.0;
+ float tickRate = 0.0;
+ bool angle_script_ready;
+ const static int _awake;
+ const static int _unconscious;
+ const static int _dead;
+
+ struct OcclusionQuery {
+ int id;
+ bool in_progress;
+ int cam_id;
+ };
+ struct OcclusionState {
+ int cam_id;
+ bool occluded;
+ };
+
+ std::queue<std::string> message_queue;
+ std::vector<OcclusionState> occlusion_states;
+ std::vector<OcclusionQuery> occlusion_queries;
+
+ virtual EntityType GetType() const { return _movement_object; }
+ bool controlled;
+ bool remote;
+ bool was_controlled;
+ bool static_char;
+
+ unsigned long voice_sound;
+ std::list<VoiceQueue> voice_queue;
+ std::vector<unsigned long> attached_sounds;
+ std::string character_path;
+ ReactionScriptGetter reaction_script_getter;
+ CharacterScriptGetter character_script_getter;
+ std::auto_ptr<ASContext> as_context;
+ OGPalette palette;
+ int connected_pathpoint_id;
+ std::vector<ItemConnection> item_connection_vec;
+ std::map<int,int> connection_finalization_remap;
+ bool do_connection_finalization_remap;
+ std::list<ItemObjectScriptReader> item_connections;
+ int update_script_counter;
+ int update_script_period;
+ int controller_id;
+ int camera_id;
+ bool visible;
+ int no_grab;
+ bool focused_character;
+ std::string object_npc_script_path;
+ std::string object_pc_script_path;
+
+ std::string nametag_string;
+ vec3 nametag_last_position;
+
+ std::auto_ptr<ASCollisions> as_collisions;
+
+ ActorFileRef actor_file_ref;
+ std::set<SoundGroupRef> touched_surface_refs;
+ std::set<MaterialRef> clothing_refs;
+
+ std::vector<char> env_object_attach_data;
+ bool is_player;
+
+ vec3 position;
+ vec3 velocity;
+
+ double delta = 0.0;
+ bool needs_predraw_update;
+ float reset_time;
+
+ list<OnlineMessageRef> incoming_material_sound_events;
+ list<OnlineMessageRef> incoming_movement_object_frames;
+ list<OnlineMessageRef> incoming_cut_lines;
+ bool disable_network_bone_interpolation = false;
+ TimeInterpolator network_time_interpolator;
+ float last_walltime_diff = 0.0f;
+
+ MovementObject();
+ virtual ~MovementObject();
+
+ bool Initialize();
+ bool InitializeMultiplayer();
+ void GetShaderNames(std::map<std::string, int>& shaders);
+
+ void Update(float timestep);
+ void Draw();
+ void ClientBeforeDraw();
+ virtual void HandleTransformationOccured();
+ void PreDrawFrame(float curr_game_time);
+
+ virtual void Reload();
+ void ActualPreDraw(float curr_game_time);
+ virtual void NotifyDeleted(Object* other);
+ virtual bool ConnectTo(Object& other, bool checking_other = false);
+ virtual bool AcceptConnectionsFrom(ConnectionType type, Object& object);
+ virtual bool Disconnect(Object& other, bool from_socket = false, bool checking_other = false);
+ void FinalizeLoadedConnections();
+ void GetDesc(EntityDescription &desc) const;
+ virtual bool SetFromDesc(const EntityDescription &desc);
+ void ChangeControlScript(const std::string &script_path);
+ void CollideWith( MovementObject* other );
+ void ASSetScriptUpdatePeriod(int val);
+ bool HasFunction(const std::string& function_definition);
+ int QueryIntFunction(std::string func);
+ void SetScriptParams( const ScriptParamMap& spm );
+ void ApplyPalette( const OGPalette& palette, bool from_socket = false );
+ OGPalette *GetPalette();
+ void HitByItem( int id, const vec3 &point, const std::string &material, int type );
+ void Execute(std::string);
+ float ASGetFloatVar( std::string name );
+ bool ASGetBoolVar( std::string name );
+ bool ASHasVar( std::string name );
+ int ASGetIntVar( std::string name );
+ int ASGetArrayIntVar( std::string name, int index );
+ void ASSetIntVar( std::string name, int value );
+ void ASSetArrayIntVar( std::string name, int index, int value );
+ void ASSetFloatVar( std::string name, float value );
+ void ASSetBoolVar( std::string name, bool value );
+ void Dispose( );
+ bool ASOnSameTeam(MovementObject* other);
+ void AttachItemToSlot( int which, AttachmentType type, bool mirrored );
+ void AttachItemToSlotEditor( int which, AttachmentType type, bool mirrored, const AttachmentRef& attachment_ref, bool from_socket = false );
+ void RemovePhysicsShapes();
+ void ReceiveMessage(std::string msg);
+ void UpdateScriptParams();
+ void SetRotationFromFacing(vec3 facing);
+ virtual void GetDisplayName(char* buf, int buf_size);
+ RiggedObject* rigged_object();
+
+ virtual void RemapReferences(std::map<int,int> id_map);
+
+ virtual void GetConnectionIDs(std::vector<int>* cons);
+
+ void UpdatePaused();
+ int AboutToBeHitByItem(int id);
+
+ int InputFromAngelScript();
+
+ const std::string& GetCurrentControlScript() const { return current_control_script_path; }
+ const std::string& GetActorScript() const { return actor_script_path; }
+ const std::string& GetNPCObjectScript() const { return object_npc_script_path; }
+ const std::string& GetPCObjectScript() const { return object_pc_script_path; }
+ vec3 facing;
+ vec3 GetFacing();
+ float GetTempHealth();
+ void addAngelScriptUpdate(uint32_t state, std::vector<uint32_t> data);
+ std::vector<std::pair<uint32_t, std::vector<uint32_t>>> angelscript_update;
+ void SetCharAnimation(const std::string &path, float fade_speed = _default_fade_speed, char flags = 0);
+ void SetAnimAndCharAnim(std::string path, float fade_speed, char flags, std::string anim_path);
+ std::string GetTeamString();
+ void StartPoseAnimation(std::string path);
+private:
+ std::auto_ptr<RiggedObject> rigged_object_;
+ std::string current_control_script_path;
+ std::string actor_script_path;
+ BulletObject* char_sphere;
+ AttackHistory attack_history;
+
+ uint64_t last_timestamp = 0;
+
+ struct {
+ ASFunctionHandle init;
+ ASFunctionHandle init_multiplayer;
+ ASFunctionHandle is_multiplayer_supported;
+ ASFunctionHandle set_parameters;
+ ASFunctionHandle handle_editor_attachment;
+ ASFunctionHandle contact;
+ ASFunctionHandle collided;
+ ASFunctionHandle notify_item_detach;
+ ASFunctionHandle movement_object_deleted;
+ ASFunctionHandle script_swap;
+ ASFunctionHandle handle_collisions_btc;
+ ASFunctionHandle hit_by_item;
+ ASFunctionHandle pre_draw_frame;
+ ASFunctionHandle pre_draw_camera;
+ ASFunctionHandle update;
+ ASFunctionHandle update_multiplayer;
+ ASFunctionHandle force_applied;
+ ASFunctionHandle get_temp_health;
+ ASFunctionHandle was_hit;
+ ASFunctionHandle was_blocked;
+ ASFunctionHandle reset;
+ ASFunctionHandle post_reset;
+ ASFunctionHandle attach_weapon;
+ ASFunctionHandle attach_misc;
+ ASFunctionHandle set_enabled;
+ ASFunctionHandle receive_message;
+ ASFunctionHandle update_paused;
+ ASFunctionHandle about_to_be_hit_by_item;
+ ASFunctionHandle InputToEngine;
+ ASFunctionHandle apply_host_input;
+ ASFunctionHandle apply_host_camera_flat_facing;
+ ASFunctionHandle set_damage_time_from_socket;
+ ASFunctionHandle set_damage_blood_time_from_socket;
+ ASFunctionHandle reset_waypoint_target;
+ ASFunctionHandle dispose;
+ ASFunctionHandle pre_draw_camera_no_cull;
+ ASFunctionHandle register_mp_callbacks;
+ ASFunctionHandle start_pose;
+ } as_funcs;
+
+ const char* shader;
+
+ void ReInitializeASFunctions();
+
+ void RegisterMPCallbacks() const;
+ void Collided( const vec3& pos, float impulse, const CollideInfo &collide_info, BulletObject* object );
+ void DrawDepthMap(const mat4& proj_view_matrix, const vec4* cull_planes, int num_cull_planes, Object::DrawType draw_type);
+ void PreDrawCamera(float curr_game_time);
+ void GoLimp();
+ void ApplyForce(vec3 force);
+ void Ragdoll();
+ void UnRagdoll();
+ void ASWasBlocked();
+ void SetAnimation(std::string path, float fade_speed);
+ void SetAnimation(std::string path);
+ void SetAnimation(std::string path, float fade_speed, char flags);
+ void SwapAnimation(std::string path);
+ void HandleMaterialEvent(std::string the_event, vec3 event_pos, float gain=1.0f);
+
+ vec4 GetAvgRotationVec4();
+ int ASWasHit(std::string type, std::string attack_path, vec3 dir, vec3 pos, int attacker_id, float attack_damage_mult, float attack_knockback_mult);
+ void ASPlaySoundGroupAttached(std::string path, vec3 location);
+ void ASPlaySoundAttached(std::string path, vec3 location);
+ void MaterialParticleAtBone(std::string type, std::string bone_name);
+ void CreateRiggedObject();
+ void RecreateRiggedObject(std::string _char_path);
+ void ASDetachItem( int which );
+ void HandleMaterialEventDefault(std::string the_event, vec3 event_pos);
+ void ForceSoundGroupVoice(std::string path, float delay);
+ int GetWaypointTarget();
+ void Reset();
+ void SetRotationFromEditorTransform();
+ void ASSetCharAnimation(std::string path, float fade_speed, char flags);
+ void ASSetCharAnimation(std::string path, float fade_speed);
+ void ASSetCharAnimation(std::string path);
+ void StopVoice();
+ void InvalidatedItem(ItemObjectScriptReader *invalidated);
+ static void InvalidatedItemCallback(ItemObjectScriptReader *invalidated, void* this_ptr);
+ void ASDetachAllItems();
+ bool OnTeam(const std::string &other_team);
+ virtual void SetEnabled(bool val);
+ void AttachItemToSlotAttachmentRef( int which, AttachmentType type, bool mirrored, const AttachmentRef* ref, bool from_socket = false );
+ void AddToAttackHistory(const std::string &str);
+ float CheckAttackHistory( const std::string &str);
+ void ClearAttackHistory();
+ virtual void ReceiveObjectMessageVAList( OBJECT_MSG::Type type, va_list args );
+ void OverrideCharAnim(const std::string &label, const std::string &new_path);
+ void UpdateWeapons();
+ virtual void Moved(Object::MoveType type);
+ void ChildLost(Object* obj);
+ virtual void GetChildren(std::vector<Object*>* ret_children);
+ virtual void GetBottomUpCompleteChildren(std::vector<Object*>* ret_children);
+ virtual bool IsMultiplayerSupported();
+ void RegenerateNametag();
+ void DrawNametag();
+
+public:
+ void PlaySoundGroupVoice(std::string path, float delay);
+};
+
+mat4 RotationFromVectors(const vec3 &front, const vec3 &right, const vec3 &up);
+void DefineMovementObjectTypePublic(ASContext* as_context);
diff --git a/Source/Objects/navmeshconnectionobject.cpp b/Source/Objects/navmeshconnectionobject.cpp
new file mode 100644
index 00000000..e19f3b42
--- /dev/null
+++ b/Source/Objects/navmeshconnectionobject.cpp
@@ -0,0 +1,368 @@
+//-----------------------------------------------------------------------------
+// Name: navmeshconnectionobject.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "navmeshconnectionobject.h"
+
+#include <Graphics/pxdebugdraw.h>
+#include <Graphics/graphics.h>
+
+#include <Internal/memwrite.h>
+#include <Game/EntityDescription.h>
+#include <Editors/map_editor.h>
+#include <Scripting/angelscript/ascontext.h>
+#include <Main/scenegraph.h>
+#include <Math/vec3math.h>
+#include <Logging/logdata.h>
+
+#include <algorithm>
+
+extern bool g_debug_runtime_disable_navmesh_connection_object_draw;
+
+//-----------------------------------------------------------------------------
+//Functions
+//-----------------------------------------------------------------------------
+
+NavmeshConnectionObject::NavmeshConnectionObject() {
+ permission_flags &= ~(Object::CAN_SCALE | Object::CAN_ROTATE);
+ box_.dims = vec3(1.0f);
+}
+
+bool NavmeshConnectionObject::ConnectTo( Object& other, bool checking_other /*= false*/ ) {
+ if(other.GetType() != this->GetType()){
+ return false;
+ } else if(other.GetType() == _hotspot_object) {
+ return Object::ConnectTo(other, checking_other);
+ } else {
+ NavmeshConnectionObject* ppo = (NavmeshConnectionObject*)&other;
+ if(ppo == this){
+ return false;
+ } else {
+ int other_id = ppo->GetID();
+ for(unsigned i=0; i<connections.size(); ++i) {
+ if(connections[i].other_object_id == other_id) {
+ return false;
+ }
+ }
+ NavMeshConnectionData d;
+ d.other_object_id = other_id;
+ d.offmesh_connection_id = -1;
+ d.poly_area = SAMPLE_POLYAREA_DISABLED;
+ connections.push_back(d);
+
+ if(!checking_other) {
+ ppo->ConnectTo(*this, true);
+ }
+ return true;
+ }
+ }
+}
+
+bool NavmeshConnectionObject::AcceptConnectionsFrom(Object::ConnectionType type, Object& object) {
+ return type == kCTNavmeshConnections;
+}
+
+bool NavmeshConnectionObject::Disconnect( Object& other, bool checking_other ) {
+ if(other.GetType() == _movement_object) {
+ return Object::Disconnect(other, checking_other);
+ } else if(other.GetType() != this->GetType()){
+ return false;
+ } else {
+ NavmeshConnectionObject* ppo = (NavmeshConnectionObject*)&other;
+ if(ppo == this){
+ return false;
+ } else {
+ int other_id = ppo->GetID();
+ std::vector<NavMeshConnectionData>::iterator iter = connections.begin();
+
+ for(; iter != connections.end(); iter++ )
+ {
+ if( iter->other_object_id == other_id )
+ {
+ connections.erase(iter);
+ break;
+ }
+ }
+
+ if(!checking_other){
+ ppo->Disconnect(*this, true);
+ }
+ return true;
+ }
+ }
+}
+
+void NavmeshConnectionObject::GetConnectionIDs(std::vector<int>* cons) {
+ for( uint32_t i = 0; i < connections.size(); i++ ) {
+ cons->push_back(connections[i].other_object_id);
+ }
+}
+
+void NavmeshConnectionObject::ResetConnectionOffMeshReference()
+{
+ std::vector<NavMeshConnectionData>::iterator iter = connections.begin();
+
+ for(; iter != connections.end(); iter++ )
+ {
+ iter->offmesh_connection_id = -1;
+
+ //We have to sync up the connection back to us.
+ Object *other_obj = scenegraph_->GetObjectFromID( iter->other_object_id );
+ if( other_obj && other_obj->GetType() == _navmesh_connection_object )
+ {
+ NavmeshConnectionObject* nmco = static_cast<NavmeshConnectionObject*>(other_obj);
+ std::vector<NavMeshConnectionData>::iterator other = nmco->GetConnectionTo( this->GetID() );
+ if( other != nmco->connections.end() )
+ {
+ other->offmesh_connection_id = -1;
+ }
+ }
+ }
+}
+
+
+void NavmeshConnectionObject::ResetPolyAreaAssignment()
+{
+ std::vector<NavMeshConnectionData>::iterator iter = connections.begin();
+
+ for(; iter != connections.end(); iter++ )
+ {
+ iter->poly_area = SAMPLE_POLYAREA_DISABLED;
+
+ //We have to sync up the connection back to us.
+ Object *other_obj = scenegraph_->GetObjectFromID( iter->other_object_id );
+ if( other_obj && other_obj->GetType() == _navmesh_connection_object )
+ {
+ NavmeshConnectionObject* nmco = static_cast<NavmeshConnectionObject*>(other_obj);
+ std::vector<NavMeshConnectionData>::iterator other = nmco->GetConnectionTo( this->GetID() );
+ if( other != nmco->connections.end() )
+ {
+ other->poly_area = SAMPLE_POLYAREA_DISABLED;
+ }
+ }
+ }
+}
+
+std::vector<NavMeshConnectionData>::iterator NavmeshConnectionObject::GetConnectionTo(int other_object_id)
+{
+ std::vector<NavMeshConnectionData>::iterator iter = connections.begin();
+
+ for(; iter != connections.end(); iter++ )
+ {
+ if( iter->other_object_id == other_object_id )
+ {
+ return iter;
+ }
+ }
+ return connections.end();
+}
+
+void NavmeshConnectionObject::NotifyDeleted( Object* o ) {
+ Object::NotifyDeleted(o);
+ Disconnect( *o, true );
+}
+
+void NavmeshConnectionObject::GetDesc(EntityDescription &desc) const {
+ Object::GetDesc(desc);
+ desc.AddString(EDF_FILE_PATH, "");
+ desc.AddNavMeshConnnectionVec(EDF_NAV_MESH_CONNECTIONS, connections);
+}
+
+bool NavmeshConnectionObject::SetFromDesc( const EntityDescription& desc ) {
+ bool ret = Object::SetFromDesc(desc);
+ if( ret ) {
+ //Keeping this to maintain compatability for now. There shouldn't be any alphas running this though.
+ for(unsigned i=0; i<desc.fields.size(); ++i){
+ const EntityDescriptionField& field = desc.fields[i];
+ switch(field.type){
+ case EDF_NAV_MESH_CONNECTIONS:
+ field.ReadNavMeshConnectionDataVec(&connections);
+ break;
+ }
+ }
+ }
+ return ret;
+}
+
+bool NavmeshConnectionObject::Initialize() {
+ assert(update_list_entry == -1);
+ update_list_entry = scenegraph_->LinkUpdateObject(this);
+ return true;
+}
+
+void NavmeshConnectionObject::Update(float timestep)
+{
+ //We do this in update aswell, in case we're not drawing.
+ UpdatePolyAreas();
+}
+
+void NavmeshConnectionObject::RegisterToScript(ASContext* as_context) {
+}
+
+void NavmeshConnectionObject::UpdatePolyAreas()
+{
+ std::vector<NavMeshConnectionData>::iterator iter = connections.begin();
+ for( ; iter != connections.end(); iter++ )
+ {
+ //Categorize the poly
+ if( iter->poly_area == SAMPLE_POLYAREA_DISABLED )
+ {
+ Object *other = scenegraph_->GetObjectFromID(iter->other_object_id);
+ if( other )
+ {
+ float dist = length( this->GetTranslation() - other->GetTranslation() );
+ if( dist < 2.0f )
+ {
+ iter->poly_area = SAMPLE_POLYAREA_JUMP1;
+ }
+ else if( dist < 5.0f )
+ {
+ iter->poly_area = SAMPLE_POLYAREA_JUMP2;
+ }
+ else if( dist < 9.0f )
+ {
+ iter->poly_area = SAMPLE_POLYAREA_JUMP3;
+ }
+ else if( dist < 14.0f )
+ {
+ iter->poly_area = SAMPLE_POLYAREA_JUMP4;
+ }
+ else
+ {
+ iter->poly_area = SAMPLE_POLYAREA_JUMP5;
+ }
+ }
+ }
+ }
+}
+
+void NavmeshConnectionObject::Draw() {
+ if (g_debug_runtime_disable_navmesh_connection_object_draw) {
+ return;
+ }
+
+ if(!Graphics::Instance()->media_mode() && scenegraph_->map_editor->state_ != MapEditor::kInGame && this->editor_visible){
+ DebugDraw::Instance()->AddWireSphere(GetTranslation(), 0.5f, vec4(0.2f,0.2f,0.9f,0.5f), _delete_on_draw);
+
+ //Need to do this to ensure the state is updated for rendering.
+ UpdatePolyAreas();
+
+ std::vector<NavMeshConnectionData>::iterator iter = connections.begin();
+ for( ; iter != connections.end(); iter++ )
+ {
+ Object* obj = scenegraph_->GetObjectFromID(iter->other_object_id);
+ if(obj == NULL) {
+ LOGW_ONCE("Navemesh connection object is connected to -1");
+ return;
+ }
+
+ //All connections are two way right now, so we only draw in one direction.
+ if( this->GetID() > obj->GetID() )
+ {
+ vec4 color = vec4(1.0f,0,0,1.0f);
+ DDFlag dd_flag = _DD_NO_FLAG;
+
+ if( iter->offmesh_connection_id == -1 )
+ {
+ dd_flag = _DD_DASHED;
+ }
+
+ SamplePolyAreas poly_area = iter->poly_area;
+ if( poly_area == SAMPLE_POLYAREA_JUMP1 )
+ {
+ color = vec4(0, 0.9f,0,1.0f);
+ }
+ else if( poly_area == SAMPLE_POLYAREA_JUMP2 )
+ {
+ color = vec4(83/255.0f,255/255.0f,26/255.0f, 1.0f);
+ }
+ else if( poly_area == SAMPLE_POLYAREA_JUMP3 )
+ {
+ color = vec4(184/255.0f,138/255.0f,0/255.0f, 1.0f);
+ }
+ else if( poly_area == SAMPLE_POLYAREA_JUMP4 )
+ {
+ color = vec4(0,61/255.0f,245/255.0f,1.0f);
+ }
+ else if( poly_area == SAMPLE_POLYAREA_JUMP5 )
+ {
+ color = vec4(102/255.0f,51/255.0f, 255/255.0f, 1.0f);
+ }
+
+ DebugDraw::Instance()->AddLine(GetTranslation(), obj->GetTranslation(), color, _delete_on_draw, dd_flag);
+ }
+ }
+ }
+}
+
+void NavmeshConnectionObject::FinalizeLoadedConnections() {
+ Object::FinalizeLoadedConnections();
+ // Remove all connections that aren't to other navmesh objects.
+ for(size_t i=0; i<connections.size(); ++i){
+ Object* obj = scenegraph_->GetObjectFromID(connections[i].other_object_id);
+ if(obj){
+ if( obj->GetType() != _navmesh_connection_object ) {
+ LOGW << "We have an unexpected connection between a " << CStringFromEntityType(this->GetType()) << " and a " << CStringFromEntityType(obj->GetType()) << " removing."<< std::endl;
+ connections.erase(connections.begin()+i);
+ i--;
+ }
+ }
+ }
+
+ // Make sure all connections are symmetric
+ for(int i=0, len=connections.size(); i<len; ++i){
+ Object* obj = scenegraph_->GetObjectFromID(connections[i].other_object_id);
+ if(obj){
+ obj->ConnectTo(*this, true);
+ }
+ }
+}
+
+void NavmeshConnectionObject::RemapReferences(std::map<int,int> id_map) {
+ for(int i=0, len=connections.size(); i<len; ++i){
+ if( id_map.find(connections[i].other_object_id) != id_map.end() ) {
+ connections[i].other_object_id = id_map[connections[i].other_object_id];
+ } else {
+ //The remapped id could belong to this, in which case it is valid
+ bool is_this = false;
+ for(std::map<int,int>::iterator iter = id_map.begin(); iter != id_map.end(); ++iter) {
+ if(iter->second == this->GetID()) {
+ is_this = true;
+ break;
+ }
+ }
+ if(!is_this)
+ connections[i].other_object_id = -1;
+ }
+ }
+}
+
+void NavmeshConnectionObject::Moved(Object::MoveType type)
+{
+ Object::Moved(type);
+ //These beomce outdated if we are modified.
+ //When pasting the object, it is moved before adding it to the scenegraph
+ if(scenegraph_ != NULL) {
+ ResetConnectionOffMeshReference();
+ ResetPolyAreaAssignment();
+ }
+}
+
diff --git a/Source/Objects/navmeshconnectionobject.h b/Source/Objects/navmeshconnectionobject.h
new file mode 100644
index 00000000..62c429ce
--- /dev/null
+++ b/Source/Objects/navmeshconnectionobject.h
@@ -0,0 +1,69 @@
+//-----------------------------------------------------------------------------
+// Name: navmeshconnectionobject.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Objects/object.h>
+#include <Game/EntityDescription.h>
+
+#include <vector>
+#include <string>
+#include <list>
+
+//-----------------------------------------------------------------------------
+// Class Definition
+//-----------------------------------------------------------------------------
+
+class NavmeshConnectionObject: public Object
+{
+public:
+ virtual EntityType GetType() const { return _navmesh_connection_object; }
+ std::vector<NavMeshConnectionData> connections;
+
+ NavmeshConnectionObject();
+
+ virtual bool ConnectTo(Object& other, bool checking_other = false);
+ virtual bool AcceptConnectionsFrom(ConnectionType type, Object& object);
+ virtual bool Disconnect( Object& other, bool checking_other = false );
+ virtual void GetConnectionIDs(std::vector<int>* cons);
+
+ void ResetConnectionOffMeshReference();
+ void ResetPolyAreaAssignment();
+ std::vector<NavMeshConnectionData>::iterator GetConnectionTo(int other_object_id);
+ void UpdatePolyAreas();
+
+ int GetModelID();
+ virtual void NotifyDeleted( Object* o);
+ void GetDesc(EntityDescription &desc) const;
+
+ virtual bool SetFromDesc( const EntityDescription& desc );
+ static void RegisterToScript(ASContext* as_context);
+ virtual void Draw();
+ virtual void Update(float timestep);
+ virtual void FinalizeLoadedConnections();
+ virtual bool Initialize();
+
+ virtual void Moved(Object::MoveType type);
+
+ virtual void RemapReferences(std::map<int,int> id_map);
+
+};
diff --git a/Source/Objects/navmeshhintobject.cpp b/Source/Objects/navmeshhintobject.cpp
new file mode 100644
index 00000000..34934b00
--- /dev/null
+++ b/Source/Objects/navmeshhintobject.cpp
@@ -0,0 +1,120 @@
+//-----------------------------------------------------------------------------
+// Name: navmeshhintobject.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "navmeshhintobject.h"
+
+#include <Graphics/textures.h>
+#include <Graphics/Billboard.h>
+#include <Graphics/pxdebugdraw.h>
+#include <Graphics/shaders.h>
+#include <Graphics/camera.h>
+#include <Graphics/geometry.h>
+
+#include <Editors/actors_editor.h>
+#include <Editors/map_editor.h>
+
+#include <Game/EntityDescription.h>
+#include <Internal/memwrite.h>
+#include <Main/scenegraph.h>
+#include <Objects/envobject.h>
+#include <Logging/logdata.h>
+
+extern bool g_debug_runtime_disable_navmesh_hint_object_draw;
+
+NavmeshHintObject::NavmeshHintObject()
+{
+ box_.dims = vec3(1.0f);
+ box_color = vec4(0.4f,0.4f,0.9f,1.0f);
+
+ vec3 corners[] =
+ {
+ //Upper four corners
+ vec3( 0.5f, 0.5f, 0.5f ),
+ vec3( 0.5f, 0.5f, -0.5f ),
+ vec3( -0.5f, 0.5f, -0.5f ),
+ vec3( -0.5f, 0.5f, 0.5f ),
+
+ //Lower four corners
+ vec3( 0.5f, -0.5f, 0.5f ),
+ vec3( 0.5f, -0.5f, -0.5f ),
+ vec3( -0.5f, -0.5f, -0.5f ),
+ vec3( -0.5f, -0.5f, 0.5f ),
+ };
+
+ for( int i = 0; i < 8; i++ )
+ {
+ for( int k = i; k < 8; k++ )
+ {
+ if( i != k )
+ {
+ if(
+ corners[i][0] == corners[k][0] ||
+ corners[i][1] == corners[k][1] ||
+ corners[i][2] == corners[k][2]
+ )
+ {
+ cross_marking.push_back(corners[i]);
+ cross_marking.push_back(corners[k]);
+ }
+ }
+ }
+ }
+}
+
+NavmeshHintObject::~NavmeshHintObject() {
+}
+
+void NavmeshHintObject::Draw() {
+ if (g_debug_runtime_disable_navmesh_hint_object_draw) {
+ return;
+ }
+
+ //Check if we should be drawn.
+ if(scenegraph_->map_editor->state_ == MapEditor::kInGame || Graphics::Instance()->media_mode() || ActiveCameras::Instance()->Get()->GetFlags() == Camera::kPreviewCamera)
+ {
+ }
+ else if( this->editor_visible )
+ {
+ if( !unit_box_vbo->valid() )
+ {
+ unit_box_vbo->Fill(
+ kVBOStatic | kVBOFloat,
+ cross_marking.size() * sizeof(vec3),
+ &cross_marking[0] );
+ }
+ vec4 draw_color(box_color);
+ draw_color[3] *= selected_ ? 1.0f : 0.2f;
+ DebugDraw::Instance()->AddLineObject(unit_box_vbo, GetTransform(), draw_color, _delete_on_draw);
+ }
+}
+
+void NavmeshHintObject::GetDesc(EntityDescription &desc) const {
+ Object::GetDesc(desc);
+}
+
+bool NavmeshHintObject::SetFromDesc( const EntityDescription& desc ) {
+ return Object::SetFromDesc(desc);
+}
+
+bool NavmeshHintObject::Initialize() {
+ return true;
+}
diff --git a/Source/Objects/navmeshhintobject.h b/Source/Objects/navmeshhintobject.h
new file mode 100644
index 00000000..9ae1d8c6
--- /dev/null
+++ b/Source/Objects/navmeshhintobject.h
@@ -0,0 +1,46 @@
+//-----------------------------------------------------------------------------
+// Name: navmeshhintobject.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Graphics/textureref.h>
+#include <Graphics/vbocontainer.h>
+
+#include <Objects/object.h>
+
+#include <string>
+#include <vector>
+
+class NavmeshHintObject: public Object {
+private:
+ RC_VBOContainer unit_box_vbo;
+ std::vector<vec3> cross_marking;
+public:
+ NavmeshHintObject();
+ virtual ~NavmeshHintObject();
+ virtual EntityType GetType() const { return _navmesh_hint_object; }
+ virtual bool Initialize();
+ virtual void Draw();
+ virtual void GetDesc(EntityDescription &desc) const;
+ virtual bool SetFromDesc( const EntityDescription& desc );
+};
+
diff --git a/Source/Objects/navmeshregionobject.cpp b/Source/Objects/navmeshregionobject.cpp
new file mode 100644
index 00000000..8cc470eb
--- /dev/null
+++ b/Source/Objects/navmeshregionobject.cpp
@@ -0,0 +1,77 @@
+//-----------------------------------------------------------------------------
+// Name: navmeshregionobject.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "navmeshregionobject.h"
+
+#include <Graphics/textures.h>
+#include <Graphics/Billboard.h>
+#include <Graphics/camera.h>
+#include <Graphics/pxdebugdraw.h>
+#include <Graphics/shaders.h>
+#include <Graphics/geometry.h>
+
+#include <Editors/actors_editor.h>
+#include <Editors/map_editor.h>
+
+#include <Game/EntityDescription.h>
+#include <Internal/memwrite.h>
+#include <Main/scenegraph.h>
+#include <Objects/envobject.h>
+
+NavmeshRegionObject::NavmeshRegionObject()
+{
+ permission_flags = CAN_TRANSLATE | CAN_SCALE | CAN_SELECT | CAN_COPY | CAN_DELETE;
+ box_.dims = vec3(1.0f);
+ box_color = vec4(0.1f,0.1f,1.0f,1.0f);
+}
+
+NavmeshRegionObject::~NavmeshRegionObject() {
+}
+
+void NavmeshRegionObject::Draw() {
+ //Check if we should be drawn.
+ if(scenegraph_->map_editor->state_ == MapEditor::kInGame || Graphics::Instance()->media_mode() || ActiveCameras::Instance()->Get()->GetFlags() == Camera::kPreviewCamera){
+ return;
+ }
+}
+
+void NavmeshRegionObject::GetDesc(EntityDescription &desc) const {
+ Object::GetDesc(desc);
+}
+
+bool NavmeshRegionObject::SetFromDesc( const EntityDescription& desc ) {
+ return Object::SetFromDesc(desc);
+}
+
+bool NavmeshRegionObject::Initialize() {
+ return true;
+}
+
+vec3 NavmeshRegionObject::GetMinBounds()
+{
+ return (GetTransform() * vec4(-0.5,-0.5,-0.5,1.0f)).xyz();
+}
+
+vec3 NavmeshRegionObject::GetMaxBounds()
+{
+ return (GetTransform() * vec4(0.5,0.5,0.5,1.0f) ).xyz();
+}
diff --git a/Source/Objects/navmeshregionobject.h b/Source/Objects/navmeshregionobject.h
new file mode 100644
index 00000000..e40d5b4d
--- /dev/null
+++ b/Source/Objects/navmeshregionobject.h
@@ -0,0 +1,47 @@
+//-----------------------------------------------------------------------------
+// Name: navmeshregionobject.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Graphics/textureref.h>
+#include <Objects/object.h>
+#include <Math/vec3.h>
+
+#include <string>
+#include <vector>
+
+class NavMesh;
+
+class NavmeshRegionObject: public Object {
+public:
+ NavmeshRegionObject();
+ virtual ~NavmeshRegionObject();
+
+ virtual EntityType GetType() const { return _navmesh_region_object; }
+ virtual bool Initialize();
+ virtual void Draw();
+ virtual void GetDesc(EntityDescription &desc) const;
+ virtual bool SetFromDesc( const EntityDescription& desc );
+
+ vec3 GetMinBounds();
+ vec3 GetMaxBounds();
+};
diff --git a/Source/Objects/object.cpp b/Source/Objects/object.cpp
new file mode 100644
index 00000000..31578844
--- /dev/null
+++ b/Source/Objects/object.cpp
@@ -0,0 +1,538 @@
+//-----------------------------------------------------------------------------
+// Name: object.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "object.h"
+
+#include <Internal/common.h>
+#include <Internal/memwrite.h>
+
+#include <Math/vec3math.h>
+#include <Math/vec4math.h>
+
+#include <Objects/prefab.h>
+#include <Asset/Asset/material.h>
+#include <Game/level.h>
+#include <Editors/map_editor.h>
+#include <Utility/ieee.h>
+#include <GUI/gui.h>
+
+#include <tinyxml.h>
+#include <SDL_assert.h>
+
+#include <cstdarg>
+
+extern Timer game_timer;
+
+void Object::SaveHistoryState( std::list<SavedChunk> &chunk_list, int state_id ) {
+ SavedChunk saved_chunk;
+ saved_chunk.obj_id = GetID();
+ if(GetType() != _group && GetType() != _prefab){
+ saved_chunk.type = ChunkType::OBJECT;
+ } else {
+ saved_chunk.type = ChunkType::GROUP;
+ }
+
+ GetDesc(saved_chunk.desc);
+
+ AddChunkToHistory(chunk_list, state_id, saved_chunk);
+}
+
+int Object::lineCheck(const vec3 &start, const vec3 &end, vec3 *point, vec3 *normal) {
+ return LineCheckEditorCube(start, end, point, normal);
+}
+
+int Object::LineCheckEditorCube( const vec3 &start, const vec3 &end, vec3 *point, vec3 *normal/*=0*/ ) {
+ int intersecting = box_.lineCheck(invert(transform_)*start,
+ invert(transform_)*end,
+ point,
+ normal);
+ if(intersecting!=-1){
+ if(point) {
+ *point = transform_ * *point;
+ }
+ if(normal) {
+ *normal = rotation_ * *normal;
+ }
+ }
+ return intersecting;
+}
+
+void Object::HandleTransformationOccurred(){
+ Online* online = Online::Instance();
+ if (online->IsActive()) {
+ online->Send<OnlineMessages::EditorTransformChange>(GetID(), translation_, scale_, rotation_);
+ }
+}
+
+const MaterialEvent& Object::GetMaterialEvent( const std::string &the_event, const vec3 &event_pos, int *tri ) {
+ //MaterialRef ref = Materials::Instance()->ReturnRef("Data/Materials/default.xml");
+ MaterialRef ref = Engine::Instance()->GetAssetManager()->LoadSync<Material>("Data/Materials/default.xml");
+ return ref->GetEvent(the_event);
+}
+
+const MaterialEvent& Object::GetMaterialEvent( const std::string &the_event, const vec3 &event_pos, const std::string &mod, int *tri ) {
+ //MaterialRef ref = Materials::Instance()->ReturnRef("Data/Materials/default.xml");
+ MaterialRef ref = Engine::Instance()->GetAssetManager()->LoadSync<Material>("Data/Materials/default.xml");
+ return ref->GetEvent(the_event, mod);
+}
+
+const MaterialDecal& Object::GetMaterialDecal( const std::string &type, const vec3 &pos, int *tri ) {
+ //MaterialRef ref = Materials::Instance()->ReturnRef("Data/Materials/default.xml");
+ MaterialRef ref = Engine::Instance()->GetAssetManager()->LoadSync<Material>("Data/Materials/default.xml");
+ return ref->GetDecal(type);
+}
+
+const MaterialParticle& Object::GetMaterialParticle( const std::string &type, const vec3 &pos, int *tri ) {
+ //MaterialRef ref = Materials::Instance()->ReturnRef("Data/Materials/default.xml");
+ MaterialRef ref = Engine::Instance()->GetAssetManager()->LoadSync<Material>("Data/Materials/default.xml");
+ return ref->GetParticle(type);
+}
+
+vec3 Object::GetColorAtPoint( const vec3 &pos, int *tri ) {
+ return vec3(1.0f);
+}
+
+void Object::InfrequentUpdate() {
+ if( parent ) {
+ selectable_ = !parent->LockedChildren();
+ } else {
+ selectable_ = true;
+ }
+
+ if( selectable_ == false ) {
+ selected_ = false;
+ }
+}
+
+MaterialRef Object::GetMaterial( const vec3 &pos, int* tri ) {
+ //return (*Materials::Instance()->ReturnRef("Data/Materials/default.xml"));
+ return Engine::Instance()->GetAssetManager()->LoadSync<Material>("Data/Materials/default.xml");
+}
+
+const ScriptParamMap& Object::GetScriptParamMap() {
+ return sp.GetParameterMap();
+}
+
+void Object::SetScriptParams( const ScriptParamMap& spm ) {
+ sp.SetParameterMap(spm);
+ UpdateScriptParams();
+}
+
+OGPalette *Object::GetPalette() {
+ DisplayError("Error", "Getting palette of Object that doesn't have one.");
+ return NULL;
+}
+
+bool Object::ConnectTo(Object& other, bool checking_other/*= false*/) {
+ if(other.GetType() == _hotspot_object) {
+ connected_to.push_back(other.GetID());
+ other.ConnectedFrom(*this);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool Object::Disconnect(Object& other, bool from_socket, bool checking_other/*= false*/) {
+ if(other.GetType() == _hotspot_object) {
+ std::vector<int>::iterator iter = std::remove(connected_to.begin(), connected_to.end(), other.GetID());
+ if(iter == connected_to.end())
+ return false;
+ other.DisconnectedFrom(*this);
+ connected_to.erase(iter, connected_to.end());
+ return true;
+ }
+ return false;
+}
+
+void Object::ConnectedFrom(Object& object) {
+ connected_from.push_back(object.GetID());
+}
+
+void Object::DisconnectedFrom(Object& object) {
+ connected_from.erase(std::remove(connected_from.begin(), connected_from.end(), object.GetID()), connected_from.end());
+}
+
+void Object::SaveToXML( TiXmlElement* parent ) {
+ // Don't save if object has "No Save" parameter with value 1
+ bool no_save = false;
+ const ScriptParamMap &script_param_map = sp.GetParameterMap();
+ const ScriptParamMap::const_iterator it = script_param_map.find("No Save");
+ if(it != script_param_map.end()){
+ const ScriptParam &param = it->second;
+ if(param.GetInt() == 1){
+ no_save = true;
+ }
+ }
+ if(!no_save) {
+ EntityDescription desc;
+ GetDesc(desc);
+ desc.SaveToXML(parent);
+ }
+}
+
+bool Object::SetFromDesc( const EntityDescription& desc ) {
+ for(unsigned i=0; i<desc.fields.size(); ++i){
+ const EntityDescriptionField& field = desc.fields[i];
+ switch(field.type){
+ case EDF_ID: {
+ int id;
+ field.ReadInt(&id);
+ SetID(id);
+ break;}
+ case EDF_NAME: {
+ std::string ename;
+ field.ReadString(&ename);
+ SetName(ename);
+ break;}
+ case EDF_TRANSLATION: {
+ vec3 translation;
+ memread(translation.entries, sizeof(float), 3, field.data);
+ loaded_translation_ = translation;
+ SetTranslation(translation);
+ break;}
+ case EDF_SCALE:{
+ vec3 scale;
+ memread(scale.entries, sizeof(float), 3, field.data);
+ loaded_scale_ = scale;
+ SetScale(scale);
+ break;}
+ case EDF_ROTATION:{
+ vec4 rotation;
+ memread(rotation.entries, sizeof(float), 4, field.data);
+ loaded_rotation_ = quaternion(rotation.entries[0], rotation.entries[1], rotation.entries[2], rotation.entries[3]);
+ SetRotation(loaded_rotation_);
+ break;}
+ case EDF_ROTATION_EULER:{
+ vec3 rotation;
+ memread(rotation.entries, sizeof(float), 3, field.data);
+ rotation_euler_ = rotation;
+ break;}
+ case EDF_SCRIPT_PARAMS:{
+ ScriptParamMap spm;
+ ReadScriptParametersFromRAM(spm, field.data);
+ SetScriptParams(spm);
+ break;}
+ case EDF_HOTSPOT_CONNECTED_TO: {
+ field.ReadIntVec(&unfinalized_connected_to);
+ break;}
+ case EDF_HOTSPOT_CONNECTED_FROM: {
+ field.ReadIntVec(&unfinalized_connected_from);
+ break;}
+ }
+ }
+ return true;
+}
+
+void Object::DrawImGuiEditor() {
+
+}
+
+bool IsDifferentEnough(const vec3 &a, const vec3 &b){
+ bool same = true;
+ for(int i=0; i<3; ++i){
+ if(fabs(a[i] - b[i]) > 0.0001){
+ same = false;
+ }
+ }
+ return !same;
+}
+
+bool IsDifferentEnough(const quaternion &a, const quaternion &b){
+ bool same = true;
+ for(int i=0; i<4; ++i){
+ if(fabs(a[i] - b[i]) > 0.0001){
+ same = false;
+ }
+ }
+ return !same;
+}
+
+
+void Object::GetDesc( EntityDescription &desc ) const {
+ desc.AddEntityType(EDF_ENTITY_TYPE, GetType());
+ desc.AddString(EDF_NAME, name);
+ // Use loaded transforms if they haven't changed much, avoids spamming
+ // level xml diffs with changes from slight rounding errors
+ if(IsDifferentEnough(loaded_translation_, GetTranslation())){
+ desc.AddVec3( EDF_TRANSLATION, GetTranslation());
+ } else {
+ desc.AddVec3( EDF_TRANSLATION, loaded_translation_ );
+ }
+
+ if(IsDifferentEnough(loaded_scale_, GetScale())){
+ desc.AddVec3( EDF_SCALE, GetScale());
+ } else {
+ desc.AddVec3( EDF_SCALE, loaded_scale_ );
+ }
+
+ if(IsDifferentEnough(loaded_rotation_, GetRotation())){
+ desc.AddQuaternion( EDF_ROTATION, GetRotation() );
+ } else {
+ desc.AddQuaternion( EDF_ROTATION, loaded_rotation_ );
+ }
+
+ if(!rotation_updated) {
+ desc.AddVec3( EDF_ROTATION_EULER, rotation_euler_ );
+ } else {
+ desc.AddVec3( EDF_ROTATION_EULER, QuaternionToEuler(GetRotation()) );
+ }
+
+ desc.AddInt( EDF_ID, GetID());
+ desc.AddScriptParams(EDF_SCRIPT_PARAMS, sp.GetParameterMap());
+ desc.AddIntVec( EDF_HOTSPOT_CONNECTED_TO, connected_to);
+ desc.AddIntVec( EDF_HOTSPOT_CONNECTED_FROM, connected_from);
+}
+
+void Object::ReceiveObjectMessage(OBJECT_MSG::Type type, ...) {
+ va_list args;
+ va_start(args, type);
+ ReceiveObjectMessageVAList(type, args);
+ va_end(args);
+}
+
+void Object::ReceiveObjectMessageVAList( OBJECT_MSG::Type type, va_list args ) {
+ switch(type){
+ case OBJECT_MSG::TOGGLE_IMPOSTER: ToggleImposter(); break;
+ case OBJECT_MSG::RESET: Reset(); break;
+ case OBJECT_MSG::RELOAD: Reload(); break;
+ case OBJECT_MSG::FINALIZE_LOADED_CONNECTIONS: FinalizeLoadedConnections(); break;
+ case OBJECT_MSG::DRAW: Draw(); break;
+ case OBJECT_MSG::UPDATE:
+ Update((float)va_arg(args,double));
+ walltime_last_update = game_timer.GetWallTime();
+ break;
+ case OBJECT_MSG::INFREQUENT_UPDATE: InfrequentUpdate(); break;
+ default:
+ break;
+ }
+}
+
+void Object::ReceiveScriptMessage( const std::string &msg ) {
+ receive_depth++;
+ if(receive_depth > 8 ) {
+ LOGW << "Deep stack on receive scripts... message: \"" << msg << "\" count: " << receive_depth << std::endl;
+ }
+ if(receive_depth > 32) {
+ LOGE << "I've gotten recursed 32 times now, assuming we're in a loop, queueing this message instead \"" << msg << "\"" << std::endl;
+ ReceiveObjectMessage(OBJECT_MSG::QUEUE_SCRIPT, &msg);
+ } else {
+ ReceiveObjectMessage(OBJECT_MSG::SCRIPT, &msg);
+ }
+ receive_depth--;
+}
+
+void Object::QueueScriptMessage( const std::string &msg ) {
+ ReceiveObjectMessage(OBJECT_MSG::QUEUE_SCRIPT, &msg);
+}
+
+void Object::SetParent(Object* new_parent) {
+ if(parent && parent != new_parent){
+ parent->ChildLost(this);
+ }
+ parent = new_parent;
+ UpdateParentHierarchy();
+}
+
+bool Object::HasParent() {
+ return parent != NULL;
+}
+
+void Object::NotifyDeleted(Object* o) {
+ if(parent == o){
+ SetParent(NULL);
+ }
+ connected_to.erase(std::remove(connected_to.begin(), connected_to.end(), o->GetID()), connected_to.end());
+}
+
+void Object::Moved(Object::MoveType type) {
+ if(parent){
+ parent->ChildMoved(type);
+ }
+
+ online_transform_dirty = true;
+}
+
+void Object::FinalizeLoadedConnections() {
+ for(size_t i = 0; i < unfinalized_connected_to.size(); ++i) {
+ Object* object = scenegraph_->GetObjectFromID(unfinalized_connected_to[i]);
+ if(object) {
+ if(!ConnectTo(*object, false)) {
+ LOGW << "Failed to connect to the object with id " << unfinalized_connected_to[i] << " after loading" << std::endl;
+ }
+ } else {
+ LOGW << "Connected to an invalid id " << unfinalized_connected_to[i] << " after loading" << std::endl;
+ }
+ }
+
+ for(size_t i = 0; i < unfinalized_connected_from.size(); ++i) {
+ Object* object = scenegraph_->GetObjectFromID(unfinalized_connected_from[i]);
+ if(object) {
+ if(!object->ConnectTo(*this, false)) {
+ LOGW << "Failed to connect to the object with id " << unfinalized_connected_from[i] << " after loading" << std::endl;
+ }
+ } else {
+ LOGW << "Connected to an invalid id " << unfinalized_connected_from[i] << " after loading" << std::endl;
+ }
+ }
+
+ unfinalized_connected_to.resize(0);
+ unfinalized_connected_from.resize(0);
+}
+
+Object::~Object() {
+ if(scenegraph_ && update_list_entry != -1){
+ scenegraph_->UnlinkUpdateObject(this, update_list_entry);
+ update_list_entry = -1;
+ }
+ SDL_assert(update_list_entry == -1);
+}
+
+void Object::UpdateTransform() {
+ LOG_ASSERT(!IsNan(rotation_[0]));
+ rotation_mat_ = Mat4FromQuaternion(rotation_);
+ mat4 translation_mat;
+ translation_mat.SetTranslation(translation_);
+ mat4 scale_mat;
+ scale_mat.SetScale(scale_);
+ transform_ = translation_mat * rotation_mat_ * scale_mat;
+}
+
+void Object::SetTranslation(const vec3& trans) {
+ if(translation_ != trans){
+ translation_ = trans;
+ UpdateTransform();
+ Moved(Object::kTranslate);
+ }
+}
+
+void Object::SetScale(const vec3& trans) {
+ if(scale_ != trans){
+ scale_ = trans;
+ UpdateTransform();
+ Moved(Object::kScale);
+ }
+}
+
+void Object::SetRotation(const quaternion& trans) {
+ if(IsDifferentEnough(rotation_, trans)){
+ rotation_ = trans;
+ LOG_ASSERT(!IsNan(rotation_[0]));
+ UpdateTransform();
+ Moved(Object::kRotate);
+ rotation_updated = true;
+ }
+}
+
+void Object::SetTranslationRotationFast(const vec3& trans, const quaternion& rotation) {
+ bool changed_something = false;
+ if(translation_ != trans){
+ translation_ = trans;
+ changed_something = true;
+ }
+ if(IsDifferentEnough(rotation_, rotation)){
+ rotation_ = rotation;
+ LOG_ASSERT(!IsNan(rotation_[0]));
+ rotation_updated = true;
+ changed_something = true;
+ }
+ if(changed_something){
+ UpdateTransform();
+ }
+}
+
+void Object::SetTransformationMatrix(const mat4& transform) {
+ if(transform_ != transform){
+ translation_ = transform.GetTranslationPart();
+ rotation_ = QuaternionFromMat4(transform);
+ LOG_ASSERT(!IsNan(rotation_[0]));
+ scale_[0] = length(transform * vec4(1,0,0,0));
+ scale_[1] = length(transform * vec4(0,1,0,0));
+ scale_[2] = length(transform * vec4(0,0,1,0));
+ UpdateTransform();
+ Moved(Object::kAll);
+ }
+}
+
+void Object::GetDisplayName(char* buf, int buf_size) {
+ if( GetName().empty() ) {
+ FormatString(buf, buf_size, "%d, Object from %s", GetID(), CStringFromEntityType(GetType()));
+ } else {
+ FormatString(buf, buf_size, "%s, Object from %s", GetName().c_str(), CStringFromEntityType(GetType()));
+ }
+}
+
+int Object::GetGroupDepth() {
+ if( parent == NULL ) {
+ return 1;
+ } else {
+ return parent->GetGroupDepth()+1;
+ }
+}
+
+Prefab* Object::GetPrefabParent() {
+ if( parent != NULL ) {
+ if( parent->GetType() == _prefab ) {
+ return static_cast<Prefab*>(parent);
+ } else {
+ return parent->GetPrefabParent();
+ }
+ }
+ return NULL;
+}
+
+bool Object::IsInPrefab() {
+ return GetPrefabParent() != NULL;
+}
+
+void Object::Select(bool val) {
+ static unsigned int selection_counter_ = 1;
+
+ if( val && selectable_ ) {
+ selected_ = selection_counter_++;
+ } else {
+ selected_ = false;
+ }
+}
+
+ObjectSanityState Object::GetSanity() {
+ return ObjectSanityState(GetType(),id,0);
+}
+
+unsigned int Object::Selected() {
+ return selected_;
+}
+
+void Object::SetRotationEuler(const vec3& trans) {
+ rotation_euler_ = trans;
+ SetRotation(EulerToQuaternion(rotation_euler_));
+ rotation_updated = false;
+}
+
+vec3 Object::GetRotationEuler() {
+ if(rotation_updated) {
+ rotation_euler_ = QuaternionToEuler(rotation_);
+ rotation_updated = false;
+ }
+
+ return rotation_euler_;
+}
diff --git a/Source/Objects/object.h b/Source/Objects/object.h
new file mode 100644
index 00000000..9b4e3ffa
--- /dev/null
+++ b/Source/Objects/object.h
@@ -0,0 +1,311 @@
+//-----------------------------------------------------------------------------
+// Name: object.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Editors/entity_type.h>
+#include <Editors/object_sanity_state.h>
+
+#include <Math/overgrowth_geometry.h>
+#include <Math/quaternions.h>
+
+#include <Objects/object_msg.h>
+#include <Graphics/palette.h>
+#include <Scripting/scriptparams.h>
+#include <Asset/Asset/material.h>
+
+#include <list>
+#include <cstdarg>
+#include <string>
+#include <map>
+//-----------------------------------------------------------------------------
+// Class Definition
+//-----------------------------------------------------------------------------
+
+class SceneGraph;
+class Collision;
+class Prefab;
+struct LineSegment;
+struct MaterialEvent;
+struct MaterialDecal;
+struct MaterialParticle;
+class Material;
+class BulletObject;
+struct CollideInfo;
+struct EntityDescription;
+struct SavedChunk;
+class TiXmlNode;
+
+typedef int32_t ObjectID;
+
+struct SeparatedTransform {
+ vec3 translation;
+ vec3 scale;
+ quaternion rotation;
+};
+
+class Object {
+ public:
+ enum MoveType {
+ kTranslate = (1<<0),
+ kRotate = (1<<1),
+ kScale = (1<<2),
+ kAll = kTranslate | kRotate | kScale
+ };
+ enum DrawType {
+ kUnknown,
+ kDrawDepthOnly,
+ kDrawAllShadowCascades,
+ kDrawDepthNoAA,
+ kFullDraw,
+ kWireframe,
+ kDecal
+ };
+ enum ConnectionType {
+ kCTNone,
+ kCTMovementObjects,
+ kCTItemObjects,
+ kCTEnvObjectsAndGroups,
+ kCTPathPoints,
+ kCTPlaceholderObjects,
+ kCTNavmeshConnections,
+ kCTHotspots
+ };
+
+ // Transforms
+ const vec3& GetTranslation() const {return translation_;}
+ const vec3& GetScale() const {return scale_;}
+ const quaternion& GetRotation() const {return rotation_;}
+ const mat4& GetTransform() const {return transform_;}
+ vec3 GetRotationEuler();
+
+ virtual void SetTranslation(const vec3& trans);
+ virtual void SetScale(const vec3& trans);
+ virtual void SetRotation(const quaternion& trans);
+ virtual void SetTranslationRotationFast(const vec3& trans, const quaternion& rotation);
+ void SetRotationEuler(const vec3& trans);
+ virtual void SetTransformationMatrix(const mat4& transform);
+ virtual void GetDisplayName(char* buf, int buf_size);
+
+ enum PermissionFlags {
+ CAN_ROTATE = (1<<0),
+ CAN_TRANSLATE = (1<<1),
+ CAN_SCALE = (1<<2),
+ CAN_SELECT = (1<<3),
+ CAN_DELETE = (1<<4),
+ CAN_COPY = (1<<5)
+ };
+
+ std::string name;
+ int permission_flags;
+ bool selectable_;
+ bool enabled_;
+ vec4 box_color;
+ Box box_;
+ bool editor_visible;
+ SeparatedTransform start_transform; // transform of object when editor starts to move it
+
+ bool online_transform_dirty = false;
+ bool interp_started = false;
+ bool interp = false;
+ bool overshot = false;
+ bool behind = false;
+
+ float walltime_last_update = 0.0f;
+
+ std::string obj_file; // generic xml data for my entity type
+ std::string editor_label;
+ vec3 editor_label_offset;
+ float editor_label_scale;
+ bool collidable;
+ bool created_on_the_fly;
+ bool transparent;
+ bool exclude_from_undo;
+ bool exclude_from_save;
+ int update_list_entry;
+ Object* parent;
+ std::vector<int> unfinalized_connected_from;
+ std::vector<int> unfinalized_connected_to;
+ // These need to be here so copying an object correctly connects
+ // it to and from other objects.
+ // Currently they are only used when connecting to and from hotspots,
+ // since hotspots should be able to connect to all objects, and all
+ // objects should be able to connect to hotspots.
+ std::vector<int> connected_from; // Other objects connected to this object
+ std::vector<int> connected_to; // This object connected to other objects
+
+ SceneGraph *scenegraph_;
+
+ Object(SceneGraph *parent_scenegraph = 0)
+ :
+ permission_flags(CAN_ROTATE | CAN_TRANSLATE | CAN_SCALE | CAN_SELECT | CAN_COPY | CAN_DELETE),
+ selected_(0),
+ box_color(1.0f),
+ editor_visible(true),
+ collidable(false),
+ transparent(false),
+ exclude_from_undo(false),
+ exclude_from_save(false),
+ update_list_entry(-1),
+ enabled_(true),
+ parent(NULL),
+ scenegraph_(parent_scenegraph),
+ scale_(1.0f),
+ rotation_(),
+ translation_(0.0f),
+ loaded_rotation_(),
+ loaded_translation_(0.0f),
+ loaded_scale_(0.0f),
+ id(-1),
+ editor_label(""),
+ editor_label_offset(0.0f),
+ editor_label_scale(10),
+ selectable_(true),
+ receive_depth(0),
+ rotation_updated(true)
+
+ {
+ UpdateTransform();
+ }
+
+ virtual EntityType GetType() const = 0;
+ virtual ~Object();
+ virtual void SetParent(Object* new_parent);
+ virtual bool HasParent();
+
+
+ ObjectID GetID() const {return id;}
+ void SetID(const int _id) { id = _id; sp.SetObjectID(_id); }
+ std::string GetName() const {return name;}
+ void SetName(const std::string& _name) {name = _name;}
+
+ virtual void Collided(const vec3& pos, float impulse, const CollideInfo &collide_info, BulletObject* obj){}
+
+ virtual void SetEnabled(bool val){ enabled_ = val; }
+ virtual void SetCollisionEnabled(bool val){};
+ virtual void HandleTransformationOccurred();
+ virtual void UpdateParentHierarchy(){};
+ virtual void PropagateTransformsDown(bool deep) {}
+ virtual void ChildMoved(Object::MoveType type){}
+ virtual const MaterialEvent& GetMaterialEvent( const std::string &the_event, const vec3 &event_pos, int *tri = NULL );
+ virtual const MaterialEvent& GetMaterialEvent( const std::string &the_event, const vec3 &event_pos, const std::string &mod, int *tri = NULL );
+ virtual const MaterialDecal& GetMaterialDecal( const std::string &type, const vec3 &pos, int *tri = NULL );
+ virtual const MaterialParticle& GetMaterialParticle( const std::string &type, const vec3 &pos, int *tri = NULL );
+ virtual MaterialRef GetMaterial( const vec3 &pos, int *tri = NULL );
+ virtual vec3 GetColorAtPoint( const vec3 &pos, int *tri = NULL );
+ virtual void HandleMaterialEvent( const std::string &the_event, const vec3 &event_pos, int *tri = NULL ){}
+
+ virtual void Update(float timestep){} // Is only called on certain objects that are registered as needing a frequent update.
+ virtual void InfrequentUpdate(); //Is called on all objects, but there is no guarantee on how often.
+ // Interpolation boilerplate for all objects in multiplayer
+
+ virtual bool Initialize() = 0;
+ virtual void SetImposter(bool set){}
+ virtual void drawShadow(vec3 origin, float distance){}
+ virtual int lineCheck(const vec3 &start, const vec3 &end, vec3 *point, vec3 *normal=0);
+ virtual bool AcceptConnectionsFrom(ConnectionType type, Object& object) {return false;}
+ virtual bool ConnectTo(Object& other, bool checking_other = false);
+ virtual bool Disconnect(Object& other, bool from_socket = false, bool checking_other = false);
+ virtual void ConnectedFrom(Object& object);
+ virtual void DisconnectedFrom(Object& object);
+ virtual void Dispose(){}
+ virtual void SaveToXML(TiXmlElement* parent);
+ virtual void GetDesc(EntityDescription &desc) const;
+ virtual void NotifyDeleted(Object* o);
+ void ReceiveObjectMessage(OBJECT_MSG::Type type, ...);
+ void ReceiveScriptMessage(const std::string &msg);
+ void QueueScriptMessage(const std::string &msg);
+ virtual void ReceiveObjectMessageVAList(OBJECT_MSG::Type type, va_list args);
+ virtual void GetChildren(std::vector<Object*>* children) {}
+ virtual void GetBottomUpCompleteChildren(std::vector<Object*>* ret_children) {}
+ virtual void GetTopDownCompleteChildren(std::vector<Object*>* ret_children) {}
+
+ ScriptParams* GetScriptParams(){return &sp;}
+ const ScriptParamMap& GetScriptParamMap();
+ virtual void SetScriptParams( const ScriptParamMap& spm );
+ virtual void ApplyPalette( const OGPalette & palette ){}
+ virtual OGPalette* GetPalette();
+ virtual void ReceiveASVec3Message( int type, const vec3 &vec_a, const vec3 &vec_b ){}
+ virtual void SaveHistoryState( std::list<SavedChunk> &chunk_list, int state_id );
+ virtual bool SetFromDesc( const EntityDescription& desc );
+ virtual void UpdateScriptParams() {}
+ virtual void ChildLost(Object* obj) {}
+ virtual void DrawDepthMap(const mat4& proj_view_matrix, const vec4* cull_planes, int num_cull_planes, Object::DrawType draw_type){}
+ virtual void PreDrawFrame(float curr_game_time){}
+ virtual void PreDrawCamera(float curr_game_time){}
+ virtual bool IsGroupDerived() const {return false;}
+ virtual bool LockedChildren() {return !selectable_;}
+
+ virtual void GetConnectionIDs(std::vector<int>* cons) {}
+
+ //Remap all attachment references etc to new id's, used for prefabs.
+ virtual void RemapReferences(std::map<int,int> id_map) {};
+
+ virtual void DrawImGuiEditor();
+ virtual int GetGroupDepth();
+
+ virtual Prefab* GetPrefabParent();
+ virtual bool IsInPrefab();
+
+ virtual void Select(bool val);
+ virtual unsigned int Selected();
+
+ //If this is non-zero that indicates that there's something wrong with the object data state.
+ virtual ObjectSanityState GetSanity();
+ virtual void GetShaderNames(std::map<std::string, int>& shaders) {}
+
+ virtual bool IsMultiplayerSupported() { return true; }
+
+ protected:
+ unsigned int selected_;
+ ScriptParams sp;
+ mat4 transform_;
+ mat4 rotation_mat_;
+ vec3 scale_;
+ vec3 translation_;
+ quaternion loaded_rotation_;
+ vec3 loaded_translation_;
+ vec3 loaded_scale_;
+ void UpdateTransform();
+ virtual void Moved(Object::MoveType type);
+ // These are accessed via ReceiveObjectMessage()
+ virtual void Draw(){}
+ virtual void SaveShadow(){}
+ virtual void LoadShadow(){}
+ virtual void Reload(){}
+ virtual void FinalizeLoadedConnections();
+ virtual void Reset(){}
+ virtual void ToggleImposter(){}
+ int LineCheckEditorCube( const vec3 &start, const vec3 &end, vec3 *point, vec3 *normal );
+ private:
+ int receive_depth;
+ ObjectID id;
+ bool rotation_updated;
+ quaternion rotation_;
+ vec3 rotation_euler_;
+};
+
+inline std::ostream& operator<<( std::ostream& out, const Object& obj )
+{
+ out << "Object(id:" << obj.GetID() << ",type:" << obj.GetType() << ",file:" << obj.obj_file << ")";
+ return out;
+}
diff --git a/Source/Objects/object_msg.h b/Source/Objects/object_msg.h
new file mode 100644
index 00000000..e485ab99
--- /dev/null
+++ b/Source/Objects/object_msg.h
@@ -0,0 +1,41 @@
+//-----------------------------------------------------------------------------
+// Name: object_msg.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+namespace OBJECT_MSG {
+ enum Type {
+ LIGHTING_CHANGED,
+ TOGGLE_IMPOSTER,
+ RESET,
+ RELOAD,
+ SET_COLOR,
+ SET_OVERBRIGHT,
+ FINALIZE_LOADED_CONNECTIONS,
+ DRAW,
+ SCRIPT,
+ QUEUE_SCRIPT,
+ UPDATE_GPU_SKINNING,
+ UPDATE,
+ INFREQUENT_UPDATE
+ };
+}
diff --git a/Source/Objects/pathpointobject.cpp b/Source/Objects/pathpointobject.cpp
new file mode 100644
index 00000000..22fefbc2
--- /dev/null
+++ b/Source/Objects/pathpointobject.cpp
@@ -0,0 +1,201 @@
+//-----------------------------------------------------------------------------
+// Name: pathpointobject.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include <Graphics/pxdebugdraw.h>
+#include <Graphics/graphics.h>
+
+#include <Objects/pathpointobject.h>
+#include <Internal/memwrite.h>
+#include <Game/EntityDescription.h>
+#include <Editors/map_editor.h>
+#include <Scripting/angelscript/ascontext.h>
+#include <Main/scenegraph.h>
+
+#include <algorithm>
+
+extern bool g_debug_runtime_disable_pathpoint_object_draw;
+
+//-----------------------------------------------------------------------------
+//Functions
+//-----------------------------------------------------------------------------
+
+PathPointObject::PathPointObject() {
+ permission_flags &= ~(Object::CAN_SCALE | Object::CAN_ROTATE);
+ box_.dims = vec3(1.0f);
+}
+
+bool PathPointObject::ConnectTo( Object& other, bool checking_other /*= false*/ ) {
+ if(other.GetType() == _movement_object){
+ return other.ConnectTo(*this, true);
+ } else if(other.GetType() == _hotspot_object) {
+ return Object::ConnectTo(other, checking_other);
+ } else if(other.GetType() != _path_point_object){
+ return false;
+ } else { // Connecting to another pathpoint
+ PathPointObject* ppo = (PathPointObject*)&other;
+ if(ppo == this){ // Can't connect to itself
+ return false;
+ } else {
+ int other_id = ppo->GetID();
+ for(unsigned i=0; i<connection_ids.size(); ++i){ // Disconnect if we're already connected
+ if(connection_ids[i] == other_id){
+ Disconnect(other, checking_other);
+ }
+ }
+ if(!checking_other){
+ connection_ids.insert(connection_ids.begin(), other_id); // We're first, so add to beginning
+ ppo->ConnectTo(*this, true); // Make sure other path point logs the connection also
+ } else {
+ connection_ids.push_back(other_id); // We're second, so add to end
+ }
+ return true;
+ }
+ }
+}
+
+bool PathPointObject::AcceptConnectionsFrom(Object::ConnectionType type, Object& object) {
+ return type == kCTMovementObjects || type == kCTPathPoints;
+}
+
+bool PathPointObject::Disconnect( Object& other, bool checking_other ) {
+ if(other.GetType() == _movement_object){
+ return other.Disconnect(*this, true);
+ } else if(other.GetType() == _hotspot_object) {
+ return Object::Disconnect(other, checking_other);
+ } else if(other.GetType() != _path_point_object){
+ return false;
+ } else {
+ PathPointObject* ppo = (PathPointObject*)&other;
+ if(ppo == this){
+ return false;
+ } else {
+ int other_id = ppo->GetID();
+ std::vector<int>::iterator iter = std::find(connection_ids.begin(), connection_ids.end(), other_id);
+ if(iter != connection_ids.end()){
+ connection_ids.erase(iter);
+ }
+ if(!checking_other){
+ ppo->Disconnect(*this, true);
+ }
+ return true;
+ }
+ }
+}
+
+void PathPointObject::GetConnectionIDs(std::vector<int>* cons) {
+ for( uint32_t i = 0; i < connection_ids.size(); i++ ) {
+ cons->push_back(connection_ids[i]);
+ }
+}
+
+void PathPointObject::NotifyDeleted( Object* o ) {
+ Object::NotifyDeleted(o);
+ std::vector<int>::iterator iter =
+ std::find(connection_ids.begin(), connection_ids.end(), o->GetID());
+ if(iter != connection_ids.end()){
+ connection_ids.erase(iter);
+ }
+}
+
+void PathPointObject::GetDesc(EntityDescription &desc) const {
+ Object::GetDesc(desc);
+ desc.AddString(EDF_FILE_PATH, "");
+ desc.AddIntVec(EDF_CONNECTIONS, connection_ids);
+}
+
+bool PathPointObject::SetFromDesc( const EntityDescription& desc ) {
+ bool ret = Object::SetFromDesc(desc);
+ if( ret ) {
+ for(unsigned i=0; i<desc.fields.size(); ++i){
+ const EntityDescriptionField& field = desc.fields[i];
+ switch(field.type){
+ case EDF_CONNECTIONS:
+ field.ReadIntVec(&connection_ids);
+ break;
+ }
+ }
+ }
+ return ret;
+}
+
+bool PathPointObject::Initialize() {
+ sp.ASAddFloat("Wait", 0.0f);
+ sp.ASAddString("Type", "Stand");
+ obj_file = "path_point_object";
+ return true;
+}
+
+static int ASNumConnectionIDs(PathPointObject* obj){
+ return (int) obj->connection_ids.size();
+}
+
+static int ASGetConnectionID(PathPointObject* obj, int which){
+ return obj->connection_ids[which];
+}
+
+void PathPointObject::RegisterToScript(ASContext* as_context) {
+ as_context->RegisterObjectType("PathPointObject", 0, asOBJ_REF | asOBJ_NOCOUNT);
+ as_context->RegisterObjectMethod("PathPointObject",
+ "int NumConnectionIDs()",
+ asFUNCTION(ASNumConnectionIDs), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("PathPointObject",
+ "int GetConnectionID(int which)",
+ asFUNCTION(ASGetConnectionID), asCALL_CDECL_OBJFIRST);
+ as_context->DocsCloseBrace();
+}
+
+void PathPointObject::Draw() {
+ if (g_debug_runtime_disable_pathpoint_object_draw) {
+ return;
+ }
+
+ if(scenegraph_->map_editor->IsTypeEnabled(_path_point_object) &&
+ !Graphics::Instance()->media_mode() &&
+ scenegraph_->map_editor->state_ != MapEditor::kInGame)
+ {
+ DebugDraw::Instance()->AddWireSphere(GetTranslation(), 0.5f, vec4(0.0f,0.5f,0.5f,0.5f), _delete_on_draw);
+ for(unsigned i=0; i<connection_ids.size(); ++i){
+ Object* obj = scenegraph_->GetObjectFromID(connection_ids[i]);
+ DebugDraw::Instance()->AddLine(GetTranslation(), obj->GetTranslation(), vec4(0.0f,0.5f,0.5f,0.5f), _delete_on_draw);
+ }
+ }
+}
+
+void PathPointObject::RemapReferences(std::map<int,int> id_map) {
+ for( unsigned i = 0; i < connection_ids.size(); i++ ) {
+ if( id_map.find(connection_ids[i]) != id_map.end() ) {
+ connection_ids[i] = id_map[connection_ids[i]];
+ } else {
+ //The remapped id could belong to this, in which case it is valid
+ bool is_this = false;
+ for(std::map<int,int>::iterator iter = id_map.begin(); iter != id_map.end(); ++iter) {
+ if(iter->second == this->GetID()) {
+ is_this = true;
+ break;
+ }
+ }
+ if(!is_this)
+ connection_ids[i] = -1;
+ }
+ }
+}
diff --git a/Source/Objects/pathpointobject.h b/Source/Objects/pathpointobject.h
new file mode 100644
index 00000000..6af0d98b
--- /dev/null
+++ b/Source/Objects/pathpointobject.h
@@ -0,0 +1,59 @@
+//-----------------------------------------------------------------------------
+// Name: pathpointobject.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Objects/object.h>
+
+#include <vector>
+#include <string>
+#include <list>
+
+//-----------------------------------------------------------------------------
+// Class Definition
+//-----------------------------------------------------------------------------
+
+class PathPointObject: public Object
+{
+public:
+ virtual EntityType GetType() const { return _path_point_object; }
+ std::vector<int> connection_ids;
+
+ PathPointObject();
+
+ virtual bool ConnectTo(Object& other, bool checking_other = false);
+ virtual bool AcceptConnectionsFrom(ConnectionType type, Object& object);
+ virtual bool Disconnect( Object& other, bool checking_other = false );
+ virtual void GetConnectionIDs(std::vector<int>* cons);
+
+ int GetModelID();
+ virtual void NotifyDeleted( Object* o);
+ void GetDesc(EntityDescription &desc) const;
+
+ virtual bool SetFromDesc( const EntityDescription& desc );
+ static void RegisterToScript(ASContext* as_context);
+ virtual void Draw();
+ virtual bool Initialize();
+
+ virtual void RemapReferences(std::map<int,int> id_map);
+
+};
diff --git a/Source/Objects/placeholderobject.cpp b/Source/Objects/placeholderobject.cpp
new file mode 100644
index 00000000..8253354d
--- /dev/null
+++ b/Source/Objects/placeholderobject.cpp
@@ -0,0 +1,386 @@
+//-----------------------------------------------------------------------------
+// Name: placeholderobject.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "placeholderobject.h"
+
+#include <Editors/actors_editor.h>
+#include <Editors/map_editor.h>
+
+#include <Graphics/textures.h>
+#include <Graphics/Billboard.h>
+#include <Graphics/camera.h>
+#include <Graphics/pxdebugdraw.h>
+#include <Graphics/shaders.h>
+
+#include <Internal/common.h>
+#include <Internal/memwrite.h>
+#include <Internal/profiler.h>
+
+#include <Objects/envobject.h>
+#include <Objects/prefab.h>
+
+#include <Main/scenegraph.h>
+#include <Main/engine.h>
+
+#include <Math/vec3math.h>
+#include <Game/EntityDescription.h>
+
+extern bool shadow_cache_dirty;
+extern bool g_debug_runtime_disable_placeholder_object_draw;
+
+PlaceholderObject::PlaceholderObject():
+ special_type_(kSpawn),
+ connect_to_type_filter_flags_(1ULL << EntityType::_movement_object),
+ visible_(true),
+ connect_id_(-1),
+ title_debug_string_id(-1),
+ editor_label_offset_prev(0.0f),
+ editor_label_scale_prev(0.0f),
+ unsaved_changes(false)
+{
+ box_.dims = vec3(1.0f);
+}
+
+void PlaceholderObject::DisposePreviewObjects() {
+ for(unsigned i=0, len=preview_objects_.size(); i < len; ++i){
+ delete preview_objects_[i];
+ }
+ preview_objects_.clear();
+}
+
+void PlaceholderObject::SetPreview( const std::string& path ) {
+ if(preview_path_ == path){
+ return;
+ }
+ preview_path_ = path;
+ DisposePreviewObjects();
+ EntityDescriptionList desc_list;
+ std::string file_type;
+ Path source;
+ ActorsEditor_LoadEntitiesFromFile(path, desc_list,&file_type, &source);
+ preview_objects_.reserve(desc_list.size());
+ for(unsigned i=0, len=desc_list.size(); i < len; ++i){
+ Object* obj = CreateObjectFromDesc(desc_list[i]);
+ if(obj) {
+ if(obj->GetType() == _env_object){
+ EnvObject* eo = (EnvObject*)obj;
+ eo->placeholder_ = true;
+ eo->added_to_physics_scene_ = true;
+ // TODO: find another way to specify stipple
+ //eo->shader_id_ = Shaders::Instance()->returnProgram(eo->ofr->shader_name + " #HALFTONE_STIPPLE");
+ }
+ if(scenegraph_){
+ obj->scenegraph_ = scenegraph_;
+ obj->Initialize();
+ }
+ preview_objects_.push_back(obj);
+ } else {
+ LOGE << "Failed at loading content for Placeholder Preview object" << std::endl;
+ }
+ }
+}
+
+PlaceholderObject::~PlaceholderObject() {
+ ClearEditorLabel();
+ DisposePreviewObjects();
+}
+
+void PlaceholderObject::Draw() {
+ if (g_debug_runtime_disable_placeholder_object_draw) {
+ return;
+ }
+
+ if(!scenegraph_->map_editor->IsTypeEnabled(_placeholder_object) ||
+ scenegraph_->map_editor->state_ == MapEditor::kInGame ||
+ !visible_ ||
+ Graphics::Instance()->media_mode() ||
+ ActiveCameras::Instance()->Get()->GetFlags() == Camera::kPreviewCamera)
+ {
+ if(title_debug_string_id != -1){
+ DebugDraw::Instance()->SetVisible(title_debug_string_id, false);
+ }
+ } else {
+ if(title_debug_string_id != -1){
+ DebugDraw::Instance()->SetVisible(title_debug_string_id, true);
+ }
+
+ bool old_shadow_cache_dirty = shadow_cache_dirty;
+ for(unsigned i=0, len=preview_objects_.size(); i < len; ++i){
+ preview_objects_[i]->SetTransformationMatrix(GetTransform());
+ preview_objects_[i]->ReceiveObjectMessage(OBJECT_MSG::DRAW);
+ }
+ shadow_cache_dirty = old_shadow_cache_dirty; // We don't want to update static shadow maps just because we moved this preview object
+
+ if(ActiveCameras::Get()->GetFlags() == Camera::kEditorCamera) {
+ if(billboard_texture_ref_.valid() ) {
+ DebugDraw::Instance()->AddBillboard(billboard_texture_ref_->GetTextureRef(), GetTranslation(), GetScale()[0], vec4(1.0f), kStraight, _delete_on_draw);
+ }
+ DrawEditorLabel();
+ }
+ else
+ {
+ ClearEditorLabel();
+ }
+
+ if(connect_id_ != -1){
+ Object* obj = scenegraph_->GetObjectFromID(connect_id_);
+ if(obj){
+ DebugDraw::Instance()->AddLine(GetTranslation(), obj->GetTranslation(), vec4(1.0f,1.0f,1.0f,0.5f), _delete_on_draw);
+ }
+ }
+ }
+}
+
+void PlaceholderObject::GetDesc(EntityDescription &desc) const {
+ Object::GetDesc(desc);
+ desc.AddString(EDF_FILE_PATH, preview_path_);
+ if(billboard_texture_ref_.valid()){
+ desc.AddString(EDF_BILLBOARD_PATH, billboard_path_);
+ }
+ desc.AddInt(EDF_SPECIAL_TYPE, special_type_);
+ std::vector<int> connections;
+ connections.push_back(connect_id_);
+ desc.AddIntVec(EDF_CONNECTIONS, connections);
+}
+
+bool PlaceholderObject::SetFromDesc( const EntityDescription& desc ) {
+ bool ret = Object::SetFromDesc(desc);
+
+ if( ret ) {
+ // Kludge to fill in the idName: (if the idName field is blank -- fill it in based on the object id
+ if( sp.HasParam("LocName") && sp.IsParamString("LocName") && sp.GetStringVal("LocName") == "" )
+ {
+ char locationChars[256];
+ sprintf( locationChars, "location%d", GetID() );
+ std::string locationStr( locationChars );
+ sp.ASRemove("LocName");
+ sp.ASAddString("LocName", locationStr );
+
+ }
+
+ if( sp.HasParam("Name") && sp.IsParamString("Name") && sp.GetStringVal("Name") == "arena_spawn" )
+ {
+ SetBillboard("Data/UI/spawner/thumbs/Utility/placeholder_arena_spawn.png");
+ }
+
+ if( sp.HasParam("Name") && sp.IsParamString("Name") && sp.GetStringVal("Name") == "arena_battle" )
+ {
+ SetBillboard("Data/UI/spawner/thumbs/Utility/placeholder_arena_battle.png");
+ }
+
+ for(unsigned i=0; i<desc.fields.size(); ++i){
+ const EntityDescriptionField& field = desc.fields[i];
+ switch(field.type){
+ case EDF_FILE_PATH: {
+ std::string path;
+ field.ReadString(&path);
+ SetPreview(path);
+ break;}
+ case EDF_BILLBOARD_PATH: {
+ std::string path;
+ field.ReadString(&path);
+ SetBillboard(path);
+ break;}
+ case EDF_SPECIAL_TYPE:
+ memread(&special_type_, sizeof(int), 1, field.data);
+ break;
+ case EDF_CONNECTIONS: {
+ std::vector<int> connections;
+ field.ReadIntVec(&connections);
+ if(!connections.empty()){
+ connect_id_ = connections[0];
+ }
+ break;}
+ }
+ }
+ }
+
+ return ret;
+}
+
+void PlaceholderObject::SetVisible(bool visible) {
+ visible_ = visible;
+}
+
+PlaceholderObject::PlaceholderObjectType PlaceholderObject::GetSpecialType() {
+ return special_type_;
+}
+
+void PlaceholderObject::SetSpecialType(PlaceholderObject::PlaceholderObjectType val) {
+ special_type_ = val;
+}
+
+bool PlaceholderObject::Initialize() {
+ for(int i=0, len=preview_objects_.size(); i<len; ++i){
+ Object* obj = preview_objects_[i];
+ obj->scenegraph_ = scenegraph_;
+ obj->Initialize();
+ }
+ obj_file = "placeholder_object";
+ return true;
+}
+
+void PlaceholderObject::SetBillboard(const std::string& path) {
+ Textures::Instance()->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE);
+ billboard_texture_ref_ = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(path, PX_SRGB, 0x0);
+ billboard_path_ = path;
+}
+
+uint64_t PlaceholderObject::GetConnectToTypeFilterFlags() {
+ return connect_to_type_filter_flags_;
+}
+
+void PlaceholderObject::SetConnectToTypeFilterFlags(uint64_t entity_type_flags) {
+ connect_to_type_filter_flags_ = entity_type_flags;
+}
+
+bool PlaceholderObject::ConnectTo( Object& other, bool checking_other /*= false*/ ) {
+ if(other.GetType() == _hotspot_object) {
+ return Object::ConnectTo(other, checking_other);
+ }
+ if(!connectable()){
+ return false;
+ }
+ if(connect_to_type_filter_flags_ == (1ULL << EntityType::_any_type) ||
+ (connect_to_type_filter_flags_ != 1ULL << EntityType::_no_type && (1ULL << other.GetType() & connect_to_type_filter_flags_) != 0)) {
+ //If the target object is part of a prefab, we want to connect the placeholder to the prefab, to maintain the connection.
+ Prefab* parent = other.GetPrefabParent();
+ if( parent ) {
+ connect_id_ = parent->GetID();
+ } else {
+ connect_id_ = other.GetID();
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool PlaceholderObject::AcceptConnectionsFrom(Object::ConnectionType type, Object& object) {
+ return connectable() && type == kCTMovementObjects; // Not sure this is correct because I have no clue how placeholders work yet
+}
+
+bool PlaceholderObject::Disconnect( Object& other, bool checking_other ) {
+ if(other.GetType() == _hotspot_object) {
+ return Object::Disconnect(other, checking_other);
+ }
+ if(!connectable()){
+ return false;
+ }
+ if(connect_id_ == other.GetID()){
+ connect_id_ = -1;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void PlaceholderObject::GetConnectionIDs(std::vector<int>* cons) {
+ if( connect_id_ != -1 ) {
+ cons->push_back(connect_id_);
+ }
+}
+
+bool PlaceholderObject::connectable() {
+ return (special_type_ == kPlayerConnect);
+}
+
+int PlaceholderObject::GetConnectID() {
+ return connect_id_;
+}
+
+void PlaceholderObject::NotifyDeleted( Object* o ) {
+ Object::NotifyDeleted(o);
+ if(connect_id_ == o->GetID()){
+ connect_id_ = -1;
+ }
+}
+
+void PlaceholderObject::Moved(Object::MoveType type) {
+ Object::Moved(type);
+ label_position_update = true;
+}
+
+void PlaceholderObject::GetDisplayName(char* buf, int buf_size) {
+ if(editor_display_name.length() > 0){
+ if( GetName().empty() ) {
+ FormatString(buf, buf_size, "%d, %s", GetID(), editor_display_name.c_str());
+ } else {
+ FormatString(buf, buf_size, "%s, %s", GetName().c_str(), editor_display_name.c_str());
+ }
+ } else {
+ if( GetName().empty() ) {
+ FormatString(buf, buf_size, "%d: Empty Object", GetID());
+ } else {
+ FormatString(buf, buf_size, "%s: Empty Object", GetName().c_str());
+ }
+ }
+}
+
+ObjectSanityState PlaceholderObject::GetSanity() {
+ uint32_t sanity_flags = 0;
+ if( special_type_ == kPlayerConnect && connect_id_ == -1 ) {
+ if( connect_id_ == -1 ) {
+ sanity_flags |= kObjectSanity_PO_UnsetConnectID;
+ }
+ }
+ return ObjectSanityState(GetType(),GetID(),sanity_flags);
+}
+
+void PlaceholderObject::DrawEditorLabel() {
+ PROFILER_ZONE(g_profiler_ctx, "DrawEditorLabel");
+ if( prev_editor_label != editor_label || editor_label_scale != editor_label_scale_prev)
+ {
+ ClearEditorLabel();
+ }
+
+ if( title_debug_string_id == -1 && editor_label.empty() == false )
+ {
+ title_debug_string_id = DebugDraw::Instance()->AddText(vec3(0.0f), editor_label, editor_label_scale, _persistent, _DD_NO_FLAG);
+ prev_editor_label = editor_label;
+ editor_label_scale_prev = editor_label_scale;
+ label_position_update = true;
+ }
+
+ if( (label_position_update || editor_label_offset != editor_label_offset_prev) && title_debug_string_id != -1 )
+ {
+ DebugDraw::Instance()->SetPosition(title_debug_string_id, GetTranslation() + editor_label_offset * GetScale()[1]);
+ editor_label_offset_prev = editor_label_offset;
+ label_position_update = false;
+ }
+}
+
+void PlaceholderObject::ClearEditorLabel() {
+ if( title_debug_string_id != -1 )
+ {
+ DebugDraw::Instance()->Remove(title_debug_string_id);
+ title_debug_string_id = -1;
+ }
+}
+
+void PlaceholderObject::RemapReferences(std::map<int,int> id_map) {
+ if( id_map.find(connect_id_) != id_map.end() ) {
+ connect_id_ = id_map[connect_id_];
+ } else {
+ connect_id_ = -1;
+ }
+}
diff --git a/Source/Objects/placeholderobject.h b/Source/Objects/placeholderobject.h
new file mode 100644
index 00000000..eadf558f
--- /dev/null
+++ b/Source/Objects/placeholderobject.h
@@ -0,0 +1,86 @@
+//-----------------------------------------------------------------------------
+// Name: placeholderobject.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Graphics/textureref.h>
+#include <Objects/object.h>
+#include <Asset/Asset/texture.h>
+
+#include <string>
+#include <vector>
+
+class PlaceholderObject: public Object {
+public:
+ enum PlaceholderObjectType {
+ kSpawn, kCamPreview, kPlayerConnect
+ };
+ PlaceholderObject();
+ virtual ~PlaceholderObject();
+ virtual EntityType GetType() const { return _placeholder_object; }
+ PlaceholderObjectType GetSpecialType();
+ void SetSpecialType(PlaceholderObjectType val);
+ virtual bool Initialize();
+ void SetPreview(const std::string& path);
+ void SetBillboard(const std::string& path);
+ virtual bool AcceptConnectionsFrom(ConnectionType type, Object& object);
+ virtual void Draw();
+ virtual void GetDesc(EntityDescription &desc) const;
+ virtual bool SetFromDesc( const EntityDescription& desc );
+ void SetVisible(bool visible);
+ bool connectable();
+ uint64_t GetConnectToTypeFilterFlags();
+ void SetConnectToTypeFilterFlags(uint64_t entity_type_flags);
+ virtual bool ConnectTo( Object& other, bool checking_other = false );
+ virtual bool Disconnect( Object& other, bool checking_other = false );
+ virtual void GetConnectionIDs(std::vector<int>* cons);
+
+ int GetConnectID();
+ virtual void NotifyDeleted( Object* o );
+ virtual void Moved(Object::MoveType type);
+ std::string editor_display_name;
+ virtual void GetDisplayName(char* buf, int buf_size);
+
+ virtual ObjectSanityState GetSanity();
+
+ bool unsaved_changes;
+private:
+ TextureAssetRef billboard_texture_ref_;
+ PlaceholderObjectType special_type_;
+ uint64_t connect_to_type_filter_flags_;
+ bool visible_;
+ int connect_id_;
+ std::string preview_path_;
+ std::string billboard_path_;
+ std::vector<Object*> preview_objects_;
+ void DisposePreviewObjects();
+ std::string prev_editor_label;
+ int title_debug_string_id;
+ bool label_position_update;
+ vec3 editor_label_offset_prev;
+ float editor_label_scale_prev;
+
+ void DrawEditorLabel();
+ void ClearEditorLabel();
+
+ virtual void RemapReferences(std::map<int,int> id_map);
+};
diff --git a/Source/Objects/prefab.cpp b/Source/Objects/prefab.cpp
new file mode 100644
index 00000000..87a8e571
--- /dev/null
+++ b/Source/Objects/prefab.cpp
@@ -0,0 +1,97 @@
+//-----------------------------------------------------------------------------
+// Name: prefab.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "prefab.h"
+
+#include <Internal/memwrite.h>
+
+Prefab::Prefab() :
+ Group(),
+ prefab_locked(true),
+ original_scale_(0.0f)
+{}
+
+bool Prefab::Initialize() {
+ return true;
+}
+
+void Prefab::GetDisplayName(char* buf, int buf_size) {
+ if( GetName().empty() ) {
+ FormatString(buf, buf_size, "%d, Prefab with %d children", GetID(), children.size());
+ } else {
+ FormatString(buf, buf_size, "%s, Prefab with %d children", GetName().c_str(), children.size());
+ }
+}
+
+bool Prefab::SetFromDesc( const EntityDescription &desc ) {
+ bool ret = Group::SetFromDesc(desc);
+ if( ret ) {
+ const EntityDescriptionField* l_prefab_locked = desc.GetField(EDF_PREFAB_LOCKED);
+ if(l_prefab_locked){
+ l_prefab_locked->ReadBool(&prefab_locked);
+ }
+
+ const EntityDescriptionField* l_prefab_path = desc.GetField(EDF_PREFAB_PATH);
+ if(l_prefab_path){
+ l_prefab_path->ReadPath(&prefab_path);
+ }
+
+ const EntityDescriptionField* l_original_scale = desc.GetField(EDF_ORIGINAL_SCALE);
+ if(l_original_scale){
+ memread(original_scale_.entries, sizeof(float), 3, l_original_scale->data);
+ }
+
+ InitRelMats();
+ }
+ return ret;
+}
+
+void Prefab::GetDesc(EntityDescription &desc) const {
+ Group::GetDesc(desc);
+ desc.AddBool(EDF_PREFAB_LOCKED, prefab_locked);
+ desc.AddPath(EDF_PREFAB_PATH, prefab_path);
+ desc.AddVec3(EDF_ORIGINAL_SCALE, original_scale_);
+}
+
+void Prefab::InfrequentUpdate() {
+ Group::InfrequentUpdate();
+ if( prefab_locked ) {
+ box_color = vec4(22/255.0f, 183/255.0f, 204/255.0f, 1.0f);
+ } else {
+ box_color = vec4(53/255.0f, 253/255.0f, 104/255.0f, 1.0f);
+ }
+}
+
+void Prefab::InitShape() {
+ Group::InitShape();
+ original_scale_ = scale_;
+}
+
+bool Prefab::LockedChildren() {
+ return prefab_locked || Group::LockedChildren();
+}
+
+ObjectSanityState Prefab::GetSanity() {
+ ObjectSanityState sanity = Group::GetSanity();
+ sanity.type = _prefab;
+ return sanity;
+}
diff --git a/Source/Objects/prefab.h b/Source/Objects/prefab.h
new file mode 100644
index 00000000..d466500d
--- /dev/null
+++ b/Source/Objects/prefab.h
@@ -0,0 +1,55 @@
+//-----------------------------------------------------------------------------
+// Name: prefab.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Objects/object.h>
+#include <Objects/group.h>
+
+#include <Math/mat4.h>
+#include <Math/quaternions.h>
+
+#include <Game/EntityDescription.h>
+
+class Prefab : public Group {
+public:
+ Prefab();
+ bool prefab_locked;
+ Path prefab_path;
+
+ vec3 original_scale_;
+
+ bool Initialize();
+ virtual EntityType GetType() const { return _prefab; }
+
+ virtual void GetDisplayName(char* buf, int buf_size);
+
+ virtual bool SetFromDesc( const EntityDescription &desc );
+ virtual void GetDesc(EntityDescription &desc) const;
+ virtual void InfrequentUpdate();
+
+ virtual void InitShape();
+
+ virtual bool LockedChildren();
+
+ virtual ObjectSanityState GetSanity();
+};
diff --git a/Source/Objects/reflectioncaptureobject.cpp b/Source/Objects/reflectioncaptureobject.cpp
new file mode 100644
index 00000000..6487360a
--- /dev/null
+++ b/Source/Objects/reflectioncaptureobject.cpp
@@ -0,0 +1,146 @@
+//-----------------------------------------------------------------------------
+// Name: reflectioncaptureobject.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "reflectioncaptureobject.h"
+
+#include <Graphics/camera.h>
+#include <Graphics/shaders.h>
+#include <Graphics/graphics.h>
+#include <Graphics/sky.h>
+#include <Graphics/models.h>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <Internal/profiler.h>
+#include <Internal/common.h>
+
+#include <Game/EntityDescription.h>
+#include <Main/scenegraph.h>
+#include <Editors/map_editor.h>
+#include <Utility/assert.h>
+
+extern bool g_debug_runtime_disable_reflection_capture_object_draw;
+
+bool ReflectionCaptureObject::Initialize() {
+ obj_file = "reflection_capture_object";
+ sp.ASAddIntCheckbox("Global", false);
+ return true;
+}
+
+void ReflectionCaptureObject::Moved(Object::MoveType type) {
+ Object::Moved(type);
+ dirty = true;
+}
+
+void ReflectionCaptureObject::Dispose() {
+ Object::Dispose();
+}
+
+ReflectionCaptureObject::ReflectionCaptureObject() {
+ box_.dims = vec3(2.0f);
+}
+
+void ReflectionCaptureObject::Draw() {
+ if (g_debug_runtime_disable_reflection_capture_object_draw) {
+ return;
+ }
+
+ if(scenegraph_->map_editor->IsTypeEnabled(_reflection_capture_object) &&
+ cube_map_ref.valid() &&
+ !Graphics::Instance()->media_mode() &&
+ scenegraph_->map_editor->state_ != MapEditor::kInGame &&
+ ActiveCameras::Instance()->Get()->GetFlags() == Camera::kEditorCamera)
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "ReflectionCaptureObject::Draw");
+
+ Shaders* shaders = Shaders::Instance();
+ Graphics* graphics = Graphics::Instance();
+
+ GLState gl_state;
+ gl_state.blend = false;
+ gl_state.cull_face = true;
+ gl_state.depth_test = true;
+ gl_state.depth_write = true;
+
+ graphics->setGLState(gl_state);
+
+ int shader_id = shaders->returnProgram("reflectioncapture");
+ shaders->setProgram(shader_id);
+
+ Camera* camera = ActiveCameras::Get();
+ shaders->SetUniformVec3("cam_pos", camera->GetPos());
+ shaders->SetUniformMat4("model_mat", GetTransform());
+ shaders->SetUniformMat4("view_mat", camera->GetViewMatrix());
+ shaders->SetUniformMat4("proj_mat", camera->GetProjMatrix());
+
+ Textures::Instance()->bindTexture(cube_map_ref);
+
+ int vert_attrib_id = shaders->returnShaderAttrib("vertex", shader_id);
+ Model* probe_model = &Models::Instance()->GetModel(scenegraph_->light_probe_collection.probe_model_id);
+ if(!probe_model->vbo_loaded){
+ probe_model->createVBO();
+ }
+ probe_model->VBO_vertices.Bind();
+ probe_model->VBO_faces.Bind();
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, 3 * sizeof(GL_FLOAT), 0);
+ graphics->DrawElements(GL_TRIANGLES, (unsigned int) probe_model->faces.size(), GL_UNSIGNED_INT, 0);
+ graphics->ResetVertexAttribArrays();
+ }
+}
+
+ReflectionCaptureObject::~ReflectionCaptureObject() {
+}
+
+void ReflectionCaptureObject::GetDisplayName(char* buf, int buf_size) {
+ if( GetName().empty() ) {
+ FormatString(buf, buf_size, "%d: Reflection Capture", GetID());
+ } else {
+ FormatString(buf, buf_size, "%s: Reflection Capture", GetName().c_str());
+ }
+}
+
+void ReflectionCaptureObject::GetDesc(EntityDescription &desc) const {
+ Object::GetDesc(desc);
+
+ const size_t data_size = kLightProbeNumCoeffs * sizeof(float);
+ std::vector<char> data(data_size);
+
+ memcpy(&data[0], &avg_color, data_size);
+ desc.AddData(EDF_GI_COEFFICIENTS, data);
+}
+
+bool ReflectionCaptureObject::SetFromDesc( const EntityDescription& desc ) {
+ bool ret = Object::SetFromDesc(desc);
+ if( ret ) {
+ for(unsigned i=0; i<desc.fields.size(); ++i){
+ const EntityDescriptionField& field = desc.fields[i];
+ switch(field.type){
+ case EDF_GI_COEFFICIENTS: {
+ memcpy(avg_color, &field.data[0], kLightProbeNumCoeffs * sizeof(float));
+ break;}
+ }
+ }
+ }
+ return ret;
+}
diff --git a/Source/Objects/reflectioncaptureobject.h b/Source/Objects/reflectioncaptureobject.h
new file mode 100644
index 00000000..77892517
--- /dev/null
+++ b/Source/Objects/reflectioncaptureobject.h
@@ -0,0 +1,55 @@
+//-----------------------------------------------------------------------------
+// Name: reflectioncaptureobject.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Objects/object.h>
+#include <Graphics/textureref.h>
+#include <Asset/Asset/texture.h>
+
+//-----------------------------------------------------------------------------
+// Class Definition
+//-----------------------------------------------------------------------------
+
+class ReflectionCaptureObject: public Object
+{
+public:
+ virtual EntityType GetType() const { return _reflection_capture_object; }
+ virtual bool Initialize();
+
+ virtual void Moved(Object::MoveType type);
+ virtual void Dispose();
+ virtual void GetDesc(EntityDescription &desc) const;
+ virtual bool SetFromDesc( const EntityDescription& desc );
+ ReflectionCaptureObject();
+ void Draw();
+ virtual ~ReflectionCaptureObject();
+ virtual void GetDisplayName(char* buf, int buf_size);
+
+ bool dirty;
+ TextureRef cube_map_ref;
+ vec3 avg_color[6];
+
+private:
+ int shape;
+ int parallax_shape;
+};
diff --git a/Source/Objects/riggedobject.cpp b/Source/Objects/riggedobject.cpp
new file mode 100644
index 00000000..90800644
--- /dev/null
+++ b/Source/Objects/riggedobject.cpp
@@ -0,0 +1,4922 @@
+//-----------------------------------------------------------------------------
+// Name: riggedobject.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "riggedobject.h"
+
+#include <Graphics/graphics.h>
+#include <Graphics/particles.h>
+#include <Graphics/camera.h>
+#include <Graphics/shaders.h>
+#include <Graphics/textures.h>
+#include <Graphics/models.h>
+#include <Graphics/particles.h>
+#include <Graphics/sky.h>
+#include <Graphics/palette.h>
+#include <Graphics/pxdebugdraw.h>
+#include <Graphics/simplify.hpp>
+
+#include <Internal/timer.h>
+#include <Internal/datemodified.h>
+#include <Internal/collisiondetection.h>
+
+#include <Objects/group.h>
+#include <Objects/movementobject.h>
+#include <Objects/itemobject.h>
+#include <Objects/envobject.h>
+#include <Objects/decalobject.h>
+#include <Objects/lightvolume.h>
+
+#include <XML/xml_helper.h>
+#include <XML/level_loader.h>
+
+#include <Scripting/angelscript/ascontext.h>
+#include <Scripting/angelscript/add_on/scriptarray/scriptarray.h>
+
+#include <Physics/bulletobject.h>
+#include <Physics/bulletworld.h>
+#include <Physics/physics.h>
+
+#include <Asset/Asset/skeletonasset.h>
+#include <Asset/Asset/fzx_file.h>
+#include <Asset/Asset/material.h>
+#include <Asset/Asset/attacks.h>
+
+#include <GUI/gui.h>
+#include <GUI/IMUI/imui.h>
+
+#include <Internal/varstring.h>
+#include <Internal/checksum.h>
+#include <Internal/memwrite.h>
+#include <Internal/filesystem.h>
+#include <Internal/profiler.h>
+#include <Internal/common.h>
+#include <Internal/filesystem.h>
+
+#include <Math/vec2math.h>
+#include <Math/vec3math.h>
+#include <Math/vec4math.h>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <Main/engine.h>
+#include <Main/scenegraph.h>
+
+#include <Online/online.h>
+#include <Online/online_datastructures.h>
+
+#include <Compat/fileio.h>
+#include <Logging/logdata.h>
+#include <Utility/assert.h>
+#include <Threading/thread_sanity.h>
+#include <Sound/sound.h>
+#include <Editors/map_editor.h>
+#include <Memory/allocation.h>
+
+#include <tinyxml.h>
+
+#include <forward_list>
+#include <cmath>
+#include <sstream>
+#include <cmath>
+
+#ifdef PLATFORM_LINUX
+ #include <malloc.h>
+#endif
+const bool _draw_char_collision_world = false;
+bool GPU_skinning = false;
+
+extern AnimationConfig animation_config;
+extern bool g_simple_shadows;
+extern bool g_level_shadows;
+extern char* global_shader_suffix;
+extern bool g_no_decals;
+extern bool g_no_reflection_capture;
+extern bool g_character_decals_enabled;
+
+extern bool g_debug_runtime_disable_morph_target_pre_draw_camera;
+extern bool g_debug_runtime_disable_rigged_object_draw;
+extern bool g_debug_runtime_disable_rigged_object_pre_draw_camera;
+extern bool g_debug_runtime_disable_rigged_object_pre_draw_frame;
+
+const int kMaxBones = 200;
+
+//-----------------------------------------------------------------------------
+//Functions
+//-----------------------------------------------------------------------------
+
+struct RiggedObjectGLState {
+ GLState gl_state;
+ RiggedObjectGLState() {
+ gl_state.depth_test = true;
+ gl_state.cull_face = true;
+ gl_state.depth_write = true;
+ gl_state.blend = false;
+ }
+};
+static const RiggedObjectGLState rigged_object_gl_state;
+
+
+
+float RiggedObject::GetRootBoneVelocity() {
+ return velocity;
+}
+
+const std::vector<mat4>& RiggedObject::GetViewBones() const
+{
+ return display_bone_matrices;
+}
+
+RiggedObject::RiggedObject() :
+ static_char(false),
+ primary_weapon_id(-1),
+ total_rotation(0.0f),
+ character_script_getter(NULL),
+ as_context(NULL),
+ char_id(-1),
+ lod_level(0),
+ shadow_group_id(-1),
+ char_scale(1.0f),
+ transformed_vertex_getter_(this),
+ blood_surface(&transformed_vertex_getter_),
+ vel_vbo(V_MIBIBYTE, kVBOFloat | kVBODynamic),
+ fur_transform_vec_vbo0(V_MIBIBYTE, kVBOFloat | kVBODynamic),
+ fur_transform_vec_vbo1(V_MIBIBYTE, kVBOFloat | kVBODynamic),
+ fur_transform_vec_vbo2(V_MIBIBYTE, kVBOFloat | kVBODynamic),
+ fur_tex_transform_vbo(V_MIBIBYTE, kVBOFloat | kVBODynamic),
+ last_draw_time(0.0f),
+ shader("envobject #CHARACTER"),
+ water_cube("character_accumulate #WATER_CUBE"),
+ water_cube_expand("character_accumulate #EXPAND"),
+ update_camera_pos(true)
+{
+ for( int i = 0; i < kLodLevels; i++ ) {
+ transform_vec_vbo0[i] = new VBORingContainer(V_MIBIBYTE, kVBOFloat | kVBODynamic);
+ transform_vec_vbo1[i] = new VBORingContainer(V_MIBIBYTE, kVBOFloat | kVBODynamic);
+ transform_vec_vbo2[i] = new VBORingContainer(V_MIBIBYTE, kVBOFloat | kVBODynamic);
+ tex_transform_vbo[i] = new VBORingContainer(V_MIBIBYTE, kVBOFloat | kVBODynamic);
+ need_vbo_update[i] = true;
+ }
+
+ palette_colors.resize(max_palette_elements, vec3(1.0f));
+ palette_colors_srgb.resize(max_palette_elements, vec3(1.0f));
+ for(int i=0; i<4; ++i){
+ model_id[i] = -1;
+ }
+
+ anim_update_period = 2;
+ max_time_until_next_anim_update = anim_update_period;
+
+ time_until_next_anim_update = 0;
+ curr_anim_update_time = -1;
+ prev_anim_update_time = -1;
+
+ collidable = false;
+
+ animated = false;
+
+ stab_texture = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/stabdecal.tga");
+}
+
+RiggedObject::~RiggedObject() {
+ #ifdef USE_SSE
+ #ifdef _WIN32
+ _aligned_free(simd_bone_mats);
+ #else
+ free(simd_bone_mats);
+ #endif
+ #endif
+ for(int i=0; i<4; ++i){
+ Models::Instance()->DeleteModel(model_id[i]);
+ }
+ for(unsigned i=0; i<morph_targets.size(); ++i){
+ morph_targets[i].Dispose();
+ }
+
+ // TODO: why do we get a black screen when returning to the main menu on Mac
+ // if the velocity vbo gets disposed?
+ // Maybe something to do with motion blur being enabled or not?
+ //vel_vbo.is_valid = false;
+
+ for( int i = 0; i < kLodLevels; i++ ) {
+ delete transform_vec_vbo0[i];
+ delete transform_vec_vbo1[i];
+ delete transform_vec_vbo2[i];
+ delete tex_transform_vbo[i];
+ }
+ blood_surface.Dispose();
+}
+
+mat4 ASGetBindMatrix(Skeleton* skeleton, int which_bone){
+ mat4 mat;
+ const PhysicsBone& p_bone = skeleton->physics_bones[which_bone];
+ mat.SetTranslationPart(p_bone.initial_position * -1.0f);
+ mat = transpose(p_bone.initial_rotation)*mat;
+ return mat;
+}
+
+int RiggedObject::GetTimeBetweenLastTwoAnimUpdates(){
+ return max_time_until_next_anim_update;
+ //return curr_anim_update_time - prev_anim_update_time;
+}
+
+int RiggedObject::GetTimeSinceLastAnimUpdate(){
+ return -curr_anim_update_time;
+}
+
+extern Timer game_timer;
+
+void RiggedObject::CalcRootBoneVelocity() {
+ vec3 pos(0.0f);
+ vec3 old_pos(0.0f);
+ pos = display_bone_matrices[0] * pos;
+ old_pos = old_root_bone * old_pos;
+
+ old_root_bone = display_bone_matrices[0];
+
+ velocity = (length(pos - old_pos)) / (game_timer.GetWallTime() - old_time);
+ old_time = game_timer.GetWallTime();
+}
+
+void RiggedObject::GetCubicSplineWeights(float interp, float *weights) {
+ float interp_squared = interp*interp;
+ float interp_cubed = interp_squared*interp;
+ weights[0] = 0.5f * (-interp_cubed + 2.0f * interp_squared - interp);
+ weights[1] = 0.5f * (3.0f * interp_cubed - 5.0f * interp_squared + 2.0f);
+ weights[2] = 0.5f * (-3.0f * interp_cubed + 4.0f * interp_squared + interp);
+ weights[3] = 0.5f * (interp_cubed - interp_squared);
+}
+
+const mat4 RiggedObject::InterpolateBetweenFourBonesQuadratic(const NetworkBone* bones, float inter_step) {
+ float weights[4];
+
+ GetCubicSplineWeights(inter_step, weights);
+
+ quaternion quaternions[4];
+
+ quaternions[0] = bones[0].rotation0;
+ quaternions[1] = bones[1].rotation0;
+ quaternions[2] = bones[2].rotation0;
+ quaternions[3] = bones[3].rotation0;
+
+ mat4 rotation0 = Mat4FromQuaternion(normalize(BlendFour(quaternions, weights)));
+
+ quaternions[0] = bones[0].rotation1;
+ quaternions[1] = bones[1].rotation1;
+ quaternions[2] = bones[2].rotation1;
+ quaternions[3] = bones[3].rotation1;
+
+ mat4 rotation1 = Mat4FromQuaternion(normalize(BlendFour(quaternions, weights)));
+
+ vec3 vectors[4];
+
+ vectors[0] = bones[0].translation0;
+ vectors[1] = bones[1].translation0;
+ vectors[2] = bones[2].translation0;
+ vectors[3] = bones[3].translation0;
+
+ vec3 translation0 = BlendFour(vectors, weights);
+
+ vectors[0] = bones[0].translation1;
+ vectors[1] = bones[1].translation1;
+ vectors[2] = bones[2].translation1;
+ vectors[3] = bones[3].translation1;
+
+ vec3 translation1 = BlendFour(vectors, weights);
+
+ float floats[4];
+
+ floats[0] = bones[0].scale;
+ floats[1] = bones[1].scale;
+ floats[2] = bones[2].scale;
+ floats[3] = bones[3].scale;
+
+ float scale = BlendFour(floats, weights);
+
+ floats[0] = bones[0].model_char_scale;
+ floats[1] = bones[1].model_char_scale;
+ floats[2] = bones[2].model_char_scale;
+ floats[3] = bones[3].model_char_scale;
+
+ float model_char_scale = BlendFour(floats, weights);
+
+ //'mat4 &mat = display_bone_matrices[index];
+ mat4 mat;
+ mat.LoadIdentity();
+ mat.SetTranslationPart(translation0);
+ mat = rotation0 * mat;
+ mat4 scale_mat;
+ scale_mat.SetScale(vec3(scale, scale, 1.0f) * model_char_scale);
+ mat = scale_mat * mat;
+ mat = rotation1 * mat;
+ mat.AddTranslation(translation1);
+ return mat;
+}
+
+const mat4 RiggedObject::InterpolateBetweenTwoBones(const NetworkBone& current, const NetworkBone& next, float interp_step) {
+
+ mat4 rotation0 = Mat4FromQuaternion(normalize(Slerp(current.rotation0, next.rotation0, interp_step)));
+
+ mat4 rotation1 = Mat4FromQuaternion(normalize(Slerp(current.rotation1, next.rotation1, interp_step)));
+
+ vec3 translation0 = mix(current.translation0, next.translation0, interp_step);
+ vec3 translation1 = mix(current.translation1, next.translation1, interp_step);
+
+ float scale = (1.0f - interp_step) * current.scale
+ + interp_step * next.scale;
+
+ float model_char_scale = (1.0f - interp_step) * current.model_char_scale
+ + interp_step * next.model_char_scale;
+
+ //'mat4 &mat = display_bone_matrices[index];
+ mat4 mat;
+ mat.LoadIdentity();
+ mat.SetTranslationPart(translation0);
+ mat = rotation0 * mat;
+ mat4 scale_mat;
+ scale_mat.SetScale(vec3(scale, scale, 1.0f) * model_char_scale);
+ mat = scale_mat * mat;
+ mat = rotation1 * mat;
+ mat.AddTranslation(translation1);
+ return mat;
+}
+
+void RiggedObject::PreDrawFrame(float curr_game_time) {
+ Online* online = Online::Instance();
+
+ if (g_debug_runtime_disable_rigged_object_pre_draw_frame) {
+ return;
+ }
+
+
+ CalcRootBoneVelocity();
+ if (!online->IsClient()) {
+ PROFILER_ZONE(g_profiler_ctx, "RiggedObject::PreDrawFrame");
+ {
+ PROFILER_ZONE(g_profiler_ctx, "needs_matrix_update");
+ float interp_weight;
+ if (animated) {
+ interp_weight = game_timer.GetInterpWeightX(GetTimeBetweenLastTwoAnimUpdates(), GetTimeSinceLastAnimUpdate());
+ }
+ else {
+ interp_weight = game_timer.GetInterpWeight();
+ }
+
+ int num_bones = skeleton_.physics_bones.size();
+ assert(num_bones < kMaxBones);
+ for (int j = 0; j < num_bones; ++j) {
+ const PhysicsBone& p_bone = skeleton_.physics_bones[j];
+ BulletObject* b_o = p_bone.bullet_object;
+ if (!b_o) {
+ continue;
+ }
+
+ mat4 &mat = display_bone_matrices[j];
+ mat.LoadIdentity();
+ mat.SetTranslationPart(p_bone.initial_position * -1.0f / model_char_scale);
+ mat = transpose(p_bone.initial_rotation)*mat;
+ mat = b_o->GetInterpWeightRotation(interp_weight) * mat;
+ mat.AddTranslation(b_o->GetInterpWeightPosition(interp_weight));
+ }
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Angelscript DisplayMatrixUpdate");
+ as_context->CallScriptFunction(as_funcs.display_matrix_update);
+ }
+
+ //Apply scaling of character bones. Do this after the initial posing of the character to be able to retrieve
+ //relative connection points of armor and other attachables.
+ for (int j = 0; j < num_bones; ++j) {
+ const PhysicsBone& p_bone = skeleton_.physics_bones[j];
+ BulletObject* b_o = p_bone.bullet_object;
+ if (!b_o) {
+ continue;
+ }
+ mat4 &mat = display_bone_matrices[j];
+ //Store attachment point
+ display_bone_transforms[j] = mat;
+ //Store scale for network bone.
+ mat4 scale_mat;
+ scale_mat.SetScale(vec3(skeleton_.physics_bones[j].display_scale, skeleton_.physics_bones[j].display_scale, 1.0f) * model_char_scale);
+
+ //Shift back the bone to origo
+ mat.AddTranslation(-b_o->GetInterpWeightPosition(interp_weight));
+ //Rotate back to identity rotation.
+ mat = invert(b_o->GetInterpWeightRotation(interp_weight)) * mat;
+ //Apply scaling
+ mat = scale_mat * mat;
+ //Re-apply rotation
+ mat = b_o->GetInterpWeightRotation(interp_weight) * mat;
+ //Re-apply translation
+ mat.AddTranslation(b_o->GetInterpWeightPosition(interp_weight));
+ }
+ }
+ }
+
+ blood_surface.PreDrawFrame(
+ Textures::Instance()->getWidth(texture_ref) / 4,
+ Textures::Instance()->getHeight(texture_ref) / 4);
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Update attached objects");
+ int num_attached_env_objects = children.size();
+ for (int i = 0; i < num_attached_env_objects; ++i) {
+ AttachedEnvObject& attached_env_object = children[i];
+ Object *obj = attached_env_object.direct_ptr;
+ if (obj) {
+ if (IsBeingMoved(scenegraph_->map_editor, obj) && obj->Selected()) {
+ }
+ else {
+ int total_connections = 0;
+ for (unsigned j = 0; j < kMaxBoneConnects; ++j) {
+ total_connections += attached_env_object.bone_connects[j].num_connections;
+ }
+
+ mat4 total_mat(0.0f);
+ for (unsigned j = 0; j < kMaxBoneConnects; ++j) {
+ const BoneConnect &bone_connect = attached_env_object.bone_connects[j];
+ if (bone_connect.num_connections) {
+ mat4 mat = display_bone_transforms[bone_connect.bone_id].GetMat4() * bone_connect.rel_mat;
+ total_mat += mat * ((float)bone_connect.num_connections / (float)total_connections);
+ }
+ }
+
+ if (!online->IsClient()) {
+ obj->SetTranslation(total_mat.GetTranslationPart());
+ obj->SetRotation(QuaternionFromMat4(total_mat.GetRotationPart()));
+ }
+ }
+ }
+ }
+ }
+}
+
+void RiggedObject::StoreNetworkBones() {
+ int num_bones = skeleton_.physics_bones.size();
+ assert(num_bones < kMaxBones);
+ network_bones.resize(num_bones);
+ network_bones_host_walltime = game_timer.GetWallTime();
+ for (int j = 0; j < num_bones; ++j) {
+ const PhysicsBone& p_bone = skeleton_.physics_bones[j];
+ BulletObject* b_o = p_bone.bullet_object;
+ if (!b_o) {
+ continue;
+ }
+
+ NetworkBone network_bone;
+ network_bone.translation0 = p_bone.initial_position * -1.0f / model_char_scale;
+ network_bone.model_char_scale = model_char_scale;
+ network_bone.rotation0 = QuaternionFromMat4(transpose(p_bone.initial_rotation));
+ network_bone.rotation1 = QuaternionFromMat4(b_o->transform.GetRotationPart());
+ network_bone.translation1 = b_o->transform.GetTranslationPart();//b_o->GetInterpWeightPosition(interp_weight);
+ network_bone.scale = skeleton_.physics_bones[j].display_scale;
+
+ network_bones[j] = (network_bone);
+ }
+}
+
+void RiggedObject::StoreNetworkMorphTargets() {
+ // first check if not present
+ if (morph_targets.size() != network_morphs.size()) {
+ for (uint32_t i = 0; i < morph_targets.size(); i++) {
+ bool present = false;
+
+ for (uint32_t j = 0; j < network_morphs.size(); j++) {
+ if (morph_targets[i].name == network_morphs[j].name) {
+ present = true;
+ break;
+ }
+
+ }
+
+ if (present == false) {
+ MorphTargetStateStorage missing_morph;
+ missing_morph.disp_weight = morph_targets[i].disp_weight;
+ missing_morph.name = morph_targets[i].name;
+ missing_morph.dirty = true;
+ missing_morph.mod_weight = morph_targets[i].mod_weight;
+ network_morphs.push_back(missing_morph);
+ }
+ }
+ }
+
+ for (uint32_t i = 0; i < morph_targets.size(); i++) {
+ for (uint32_t j = 0; j < network_morphs.size(); j++) {
+ if (morph_targets[i].name == network_morphs[j].name) {
+ float a = morph_targets[i].disp_weight;
+ float b = network_morphs[j].disp_weight;
+
+
+ float c = network_morphs[j].mod_weight;
+ float d = morph_targets[i].mod_weight;
+
+ if (std::abs(a - b) > 1e-4 ||
+ std::abs(c - d) > 1e-4) {
+
+ network_morphs[j].mod_weight = morph_targets[i].mod_weight;
+ network_morphs[j].disp_weight = morph_targets[i].disp_weight;
+ network_morphs[j].dirty = true;
+ }
+ }
+
+ }
+ }
+
+}
+
+void RiggedObject::PreDrawCamera(float curr_game_time) {
+ Online* online = Online::Instance();
+
+ if (g_debug_runtime_disable_rigged_object_pre_draw_camera) {
+ return;
+ }
+
+ PROFILER_ZONE(g_profiler_ctx, "RiggedObject::PreDraw");
+ for(int i=0; i<4; ++i){
+ need_vbo_update[i] = true;
+ }
+ Graphics::Instance()->setGLState(rigged_object_gl_state.gl_state);
+
+ shadow_group_id = -1;
+
+ lod_level = 0;
+ const vec3 &avg_pos = GetAvgPosition();
+ const vec3 &cam_pos = ActiveCameras::Get()->GetPos();
+ const float cam_zoom = 90.0f/ActiveCameras::Get()->GetFOV();
+ const float _lod_switch_threshold = 20.0f * cam_zoom * cam_zoom * char_scale * char_scale;
+ if (!online->IsActive()) {
+ if (distance_squared(avg_pos, cam_pos) > _lod_switch_threshold * 16.0f) {
+ lod_level = 3;
+ }
+ else if (distance_squared(avg_pos, cam_pos) > _lod_switch_threshold * 4.0f) {
+ lod_level = 2;
+ }
+ else if (distance_squared(avg_pos, cam_pos) > _lod_switch_threshold) {
+ lod_level = 1;
+ }
+ }
+
+ // Use base LOD if lods are not loaded
+ if(model_id[lod_level] == -1){
+ lod_level = 0;
+ }
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "ApplyBoneMatricesToModel");
+ ApplyBoneMatricesToModel(false, lod_level);
+ }
+}
+
+// Find the closest points between line segments ab and cd
+// Based on the theory described at: http://stackoverflow.com/questions/627563/calculating-the-shortest-distance-between-two-lines-line-segments-in-3d/702174#702174
+void ClosestPointsBetweenTwoLineSegments(const vec3 &a, const vec3 &b, const vec3 &c, const vec3 &d, vec3 *ab_closest, vec3 *cd_closest){
+ // Create two vectors perpendicular to AB
+ float ab_len = distance(a,b);
+ vec3 ab_dir = b-a;
+ if(ab_len != 0.0f){
+ ab_dir /= ab_len;
+ }
+ vec3 ab_perp[2];
+ ab_perp[0] = vec3(-ab_dir[1], ab_dir[0], 0.0f);
+ ab_perp[1] = cross(ab_dir, ab_perp[0]);
+ // Flatten start and end onto plane defined by AB
+ vec3 rel_c = c - a;
+ vec3 rel_d = d - a;
+ vec2 flat_c(dot(ab_perp[0],rel_c), dot(ab_perp[1], rel_c));
+ vec2 flat_d(dot(ab_perp[0],rel_d), dot(ab_perp[1], rel_d));
+ // Find vector perpendicular to flat CD
+ vec2 flat_cd_dir = normalize(flat_d - flat_c);
+ vec2 perp_dir(-flat_cd_dir[1], flat_cd_dir[0]);
+ // Move flat line so it intersects origin
+ vec2 shift = perp_dir * dot(flat_c, perp_dir);
+ flat_c -= shift;
+ flat_d -= shift;
+ // Find intersection point with origin in the space of flat CD
+ float t = 0.0f;
+ vec2 flat_dir = flat_d - flat_c;
+ if(flat_dir[0] != 0.0f){
+ t = -flat_c[0]/flat_dir[0];
+ } else if(flat_dir[1] != 0.0f){
+ t= -flat_c[1]/flat_dir[1];
+ }
+ // Clamp point to bounds of line segment CD
+ t = min(1.0f, max(0.0f, t));
+ // Find CD point in world space
+ *cd_closest = c + (d - c) * t;
+ // Find the point on AB nearest to the closest point on CD
+ float t2 = 0.0f;
+ if(ab_len != 0.0f){
+ t2 = dot(ab_dir, *cd_closest-a)/ab_len;
+ }
+ // Clamp point to bounds of line segment AB
+ t2 = min(1.0f, max(0.0f, t2));
+ // Find AB point in world space
+ *ab_closest = a + (b - a) * t2;
+}
+
+const int kOcclusionRadius = 2;
+
+void RiggedObject::ClientBeforeDraw() {
+ if(Online::Instance()->IsClient()) {
+ /*
+ std::list<RiggedObjectFrame>& bones = mp->bones[char_id];
+ while(bones.size() > 1) {
+ network_time_interpolator.timestamps.Clear();
+ for(const RiggedObjectFrame& bone : bones) {
+ network_time_interpolator.timestamps.PushValue(bone.host_walltime);
+ }
+
+ int interpolation_return = network_time_interpolator.Update();
+
+ if(interpolation_return == 1) {
+ continue;
+ } else if(interpolation_return == 2) {
+ delete[] bones.front().bones;
+ bones.pop_front();
+ continue;
+ } else if(interpolation_return == 3) {
+ break;
+ }
+
+ RiggedObjectFrame& current_bones = *bones.begin();
+ RiggedObjectFrame& next_bones = *std::next(bones.begin());
+
+ float prev_root_z = GetDisplayBonePosition(0).z();
+
+ for(uint32_t i = 0; i < skeleton_.physics_bones.size(); i++) {
+ InterpolateBetweenTwoBones(current_bones.bones[i], next_bones.bones[i], network_time_interpolator.interpolation_step, i);
+ }
+
+ break;
+ }
+ */
+
+ if(network_display_bone_matrices.size() == display_bone_matrices.size()) {
+ //Replace this with a better routine in the future, where we just use the network bone matrices on a client when rendering.
+ display_bone_matrices = network_display_bone_matrices;
+ }
+ }
+}
+
+void RiggedObject::Draw(const mat4& proj_view_matrix, Object::DrawType type) {
+ if (g_debug_runtime_disable_rigged_object_draw) {
+ return;
+ }
+
+ PROFILER_GPU_ZONE(g_profiler_ctx, "RiggedObject::Draw()");
+ if(type == Object::kFullDraw){
+ last_draw_time = game_timer.game_time;
+ }
+
+ const bool _draw_animation_bones_xray = false;
+ Graphics* graphics = Graphics::Instance();
+ Textures* textures = Textures::Instance();
+ Shaders* shaders = Shaders::Instance();
+ Camera* cam = ActiveCameras::Get();
+
+ CHECK_GL_ERROR();
+ if(_draw_char_collision_world && animated){
+ //skeleton.DrawLocalBulletWorld();
+ skeleton_.col_bullet_world->Draw(scenegraph_->sky->GetSpecularCubeMapTexture());
+ return;
+ }
+ /*if(!animated){
+ return;
+ }*/
+
+ if(model_id[0]==-1) {
+ return;
+ }
+
+ graphics->setGLState(rigged_object_gl_state.gl_state);
+ CHECK_GL_ERROR();
+
+ // Get active shader variation (based on shadow catching and alpha to coverage)
+
+ const int kShaderStrSize = 1024;
+ char buf[2][kShaderStrSize];
+ char* shader_str[2] = {buf[0], buf[1]};
+ FormatString(shader_str[0], kShaderStrSize, "%s %s", shader, global_shader_suffix);
+ if(GPU_skinning){
+ FormatString(shader_str[1], kShaderStrSize, "%s #GPU_SKINNING", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+
+ int the_shader = shaders->returnProgram(shader_str[0]);
+
+ if(the_shader!=-1) {
+ shaders->setProgram(the_shader);
+
+ CHECK_GL_ERROR();
+
+ shaders->SetUniformMat4("mvp",proj_view_matrix);
+ shaders->SetUniformMat4("projection_view_mat", proj_view_matrix);
+ if(GPU_skinning){
+ shaders->SetUniformMat4Array("bone_mats", display_bone_matrices);
+ }
+
+ CHECK_GL_ERROR();
+ }
+ else {
+ shaders->noProgram();
+ }
+ CHECK_GL_ERROR();
+
+ if(model_id[0]!=-1){
+ //int LOD = 0;
+ Model* model = &Models::Instance()->GetModel(model_id[lod_level]);
+ graphics->DebugTracePrint(model->path.c_str());
+ textures->bindBlankTexture(7);
+ if(type == RiggedObject::kFullDraw){
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Set up full draw state");
+ if(blood_surface.blood_tex_mipmap_dirty){
+ Textures::Instance()->GenerateMipmap(blood_surface.blood_tex);
+ blood_surface.blood_tex_mipmap_dirty = false;
+ }
+
+ std::vector<mat4> shadow_matrix;
+ shadow_matrix.resize(4);
+ for(int i=0; i<4; ++i){
+ shadow_matrix[i] = cam->biasMatrix * graphics->cascade_shadow_mat[i];
+ }
+ if(g_simple_shadows || !g_level_shadows){
+ shadow_matrix[3] = cam->biasMatrix * graphics->simple_shadow_mat;
+ }
+ shaders->SetUniformMat4Array("shadow_matrix",shadow_matrix);
+ shaders->SetUniformVec3("cam_pos",ActiveCameras::Get()->GetPos());
+ shaders->SetUniformVec3("ws_light",scenegraph_->primary_light.pos);
+ shaders->SetUniformVec4("primary_light_color",vec4(scenegraph_->primary_light.color, scenegraph_->primary_light.intensity));
+ shaders->SetUniformFloat("time",game_timer.GetRenderTime());
+ shaders->SetUniformVec3("blood_tint", vec3(-1,-1,-1)); // Presumably this is here to force refreshing this uniform?
+ shaders->SetUniformVec3("blood_tint", Graphics::Instance()->config_.blood_color());
+ std::vector<vec3> ambient_cube_color_vec;
+ for(int i=0; i<6; ++i){
+ ambient_cube_color_vec.push_back(ambient_cube_color[i]);
+ }
+ shaders->SetUniformVec3Array("ambient_cube_color", ambient_cube_color_vec);
+ shaders->SetUniformVec3Array("tint_palette", palette_colors_srgb);
+ if(scenegraph_->light_probe_collection.probe_lighting_enabled && scenegraph_->light_probe_collection.light_probe_buffer_object_id != -1){
+ shaders->SetUniformInt("num_tetrahedra", scenegraph_->light_probe_collection.ShaderNumTetrahedra());
+ shaders->SetUniformInt("num_light_probes", scenegraph_->light_probe_collection.ShaderNumLightProbes());
+ shaders->SetUniformVec3("grid_bounds_min", scenegraph_->light_probe_collection.grid_lookup.bounds[0]);
+ shaders->SetUniformVec3("grid_bounds_max", scenegraph_->light_probe_collection.grid_lookup.bounds[1]);
+ shaders->SetUniformInt("subdivisions_x", scenegraph_->light_probe_collection.grid_lookup.subdivisions[0]);
+ shaders->SetUniformInt("subdivisions_y", scenegraph_->light_probe_collection.grid_lookup.subdivisions[1]);
+ shaders->SetUniformInt("subdivisions_z", scenegraph_->light_probe_collection.grid_lookup.subdivisions[2]);
+ shaders->SetUniformInt(shaders->GetTexUniform(TEX_AMBIENT_COLOR_BUFFER), TEX_AMBIENT_COLOR_BUFFER);
+ shaders->SetUniformInt(shaders->GetTexUniform(TEX_AMBIENT_GRID_DATA), TEX_AMBIENT_GRID_DATA);
+ glBindBuffer(GL_TEXTURE_BUFFER, scenegraph_->light_probe_collection.light_probe_buffer_object_id);
+ }
+ shaders->SetUniformInt("reflection_capture_num", scenegraph_->ref_cap_matrix.size());
+ if(!scenegraph_->ref_cap_matrix.empty()){
+ assert(!scenegraph_->ref_cap_matrix_inverse.empty());
+ shaders->SetUniformMat4Array("reflection_capture_matrix", scenegraph_->ref_cap_matrix);
+ shaders->SetUniformMat4Array("reflection_capture_matrix_inverse", scenegraph_->ref_cap_matrix_inverse);
+ }
+ shaders->SetUniformFloat("haze_mult", scenegraph_->haze_mult);
+
+ std::vector<mat4> light_volume_matrix;
+ std::vector<mat4> light_volume_matrix_inverse;
+ for(int i=0, len=scenegraph_->light_volume_objects_.size(); i<len; ++i){
+ Object* obj = scenegraph_->light_volume_objects_[i];
+ const mat4 &mat = obj->GetTransform();
+ light_volume_matrix.push_back(mat);
+ light_volume_matrix_inverse.push_back(invert(mat));
+ }
+ shaders->SetUniformInt("light_volume_num", light_volume_matrix.size());
+ if(!light_volume_matrix.empty()){
+ assert(!light_volume_matrix_inverse.empty());
+ shaders->SetUniformMat4Array("light_volume_matrix", light_volume_matrix);
+ shaders->SetUniformMat4Array("light_volume_matrix_inverse", light_volume_matrix_inverse);
+ }
+
+ if( g_no_reflection_capture == false) {
+ textures->bindTexture(scenegraph_->cubemaps, 19);
+ }
+ if( g_character_decals_enabled ) {
+ scenegraph_->BindDecals(the_shader);
+ }
+ scenegraph_->BindLights(the_shader);
+
+ if(texture_ref.valid()){
+ textures->bindTexture(texture_ref, 0);
+ }
+ if(normal_texture_ref.valid()){
+ textures->bindTexture(normal_texture_ref, 1);
+ }
+ TextureAssetRef catcher_ref;
+ TextureAssetRef caster_ref;
+ if(!graphics->drawing_shadow){
+ if(g_simple_shadows || !g_level_shadows){
+ textures->bindTexture(graphics->static_shadow_depth_ref, 4);
+ } else {
+ textures->bindTexture(graphics->cascade_shadow_depth_ref, 4);
+ }
+ textures->bindBlankTexture(5);
+ } else {
+ textures->bindBlankTexture(4);
+ textures->bindBlankTexture(5);
+ }
+ if(cube_map.valid()){
+ textures->bindTexture(cube_map, 2);
+ } else {
+ textures->bindTexture(scenegraph_->sky->GetSpecularCubeMapTexture(), 2);
+ }
+ TextureRef blood_tex = blood_surface.blood_tex;
+ if(blood_tex.valid()){
+ textures->bindTexture(blood_tex, 6);
+ }
+ if(palette_texture_ref.valid()){
+ textures->bindTexture(palette_texture_ref, 8);
+ } else {
+ textures->bindBlankTexture(8);
+ }
+ if(scenegraph_->light_probe_collection.light_volume_enabled && scenegraph_->light_probe_collection.ambient_3d_tex.valid()){
+ textures->bindTexture(scenegraph_->light_probe_collection.ambient_3d_tex, 16);
+ }
+ textures->bindTexture(graphics->screen_color_tex, 17);
+ textures->bindTexture(graphics->screen_depth_tex, 18);
+ }
+
+ if(need_vbo_update[lod_level]) {
+ need_vbo_update[lod_level] = false;
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Fill dynamic vbos");
+ int model_num_verts = model->vertices.size()/3;
+ int fur_model_num_verts = fur_model.vertices.size()/3;
+ if(GPU_skinning){
+ PROFILER_ZONE(g_profiler_ctx, "GPU_skinning");
+ if(!transform_vec_vbo0[lod_level]->valid()){
+ transform_vec_vbo0[lod_level]->SetHint(model_num_verts*4*sizeof(GLfloat),kVBOStatic);
+ transform_vec_vbo0[lod_level]->Fill(model_num_verts*4*sizeof(GLfloat), &model->bone_ids[0]);
+
+ transform_vec_vbo1[lod_level]->SetHint(model_num_verts*4*sizeof(GLfloat),kVBOStatic);
+ transform_vec_vbo1[lod_level]->Fill(model_num_verts*4*sizeof(GLfloat), &model->bone_weights[0]);
+ }
+ if(fur_model.faces.size() > 0 && !fur_transform_vec_vbo0.valid()){
+ fur_transform_vec_vbo0.SetHint(fur_model_num_verts*4*sizeof(GLfloat),kVBOStatic);
+ fur_transform_vec_vbo0.Fill(fur_model_num_verts*4*sizeof(GLfloat), &fur_model.bone_ids[0]);
+
+ fur_transform_vec_vbo1.SetHint(fur_model_num_verts*4*sizeof(GLfloat),kVBOStatic);
+ fur_transform_vec_vbo1.Fill(fur_model_num_verts*4*sizeof(GLfloat), &fur_model.bone_weights[0]);
+ }
+ transform_vec_vbo2[lod_level]->SetHint(model_num_verts*3*sizeof(GLfloat),kVBODynamic);
+ transform_vec_vbo2[lod_level]->Fill(model_num_verts*3*sizeof(GLfloat), &model->morph_transform_vec[0]);
+ } else {
+ {
+ PROFILER_ZONE(g_profiler_ctx, "transform_vec_vbos");
+ transform_vec_vbo0[lod_level]->SetHint(model_num_verts*4*sizeof(GLfloat),kVBODynamic);
+ transform_vec_vbo0[lod_level]->Fill(model_num_verts*4*sizeof(GLfloat), &model->transform_vec[0][0]);
+
+ transform_vec_vbo1[lod_level]->SetHint(model_num_verts*4*sizeof(GLfloat),kVBODynamic);
+ transform_vec_vbo1[lod_level]->Fill(model_num_verts*4*sizeof(GLfloat), &model->transform_vec[1][0]);
+
+ transform_vec_vbo2[lod_level]->SetHint(model_num_verts*4*sizeof(GLfloat),kVBODynamic);
+ transform_vec_vbo2[lod_level]->Fill(model_num_verts*4*sizeof(GLfloat), &model->transform_vec[2][0]);
+ }
+ }
+
+ static std::vector<float> vel;
+ vel.resize(model_num_verts * 3);
+
+ if(!animated){ // If ragdolled, propagate velocity to fixed bones as well
+ for(int j=0, len=skeleton_.physics_bones.size(); j<len; j++){
+ if(skeleton_.fixed_obj[j] && skeleton_.physics_bones[j].bullet_object){
+ int bone = j;
+ while(skeleton_.parents[bone] != -1 && skeleton_.fixed_obj[bone]){
+ bone = skeleton_.parents[bone];
+ }
+ skeleton_.physics_bones[j].bullet_object->SetLinearVelocity(skeleton_.physics_bones[bone].bullet_object->GetLinearVelocity());
+ }
+ }
+ }
+
+ if(graphics->config_.motion_blur_amount_ > 0.01f){
+ PROFILER_ZONE(g_profiler_ctx, "Calculate vertex velocities");
+ for(int j=0; j<model_num_verts; j++){
+ vec3 vert_vel = skeleton_.physics_bones[(unsigned int)model->bone_ids[j][0]].bullet_object->GetLinearVelocity() * model->bone_weights[j][0];
+ for(int k=1; k<4; k++){
+ if(model->bone_weights[j][k] > 0.0f) {
+ vert_vel += skeleton_.physics_bones[(unsigned int)model->bone_ids[j][k]].bullet_object->GetLinearVelocity() * model->bone_weights[j][k];
+ }
+ }
+ vel[j*3+0] = vert_vel[0];
+ vel[j*3+1] = vert_vel[1];
+ vel[j*3+2] = vert_vel[2];
+ }
+ vel_vbo.SetHint(model_num_verts*3*sizeof(GLfloat),kVBODynamic);
+ vel_vbo.Fill(model_num_verts*3*sizeof(GLfloat), &vel[0]);
+ } else {
+ PROFILER_ZONE(g_profiler_ctx, "Calculate null vertex velocities");
+ for(int i=0, len=vel.size(); i<len; ++i){
+ vel[i] = 0.0f;
+ }
+ vel_vbo.SetHint(model_num_verts*3*sizeof(GLfloat),kVBODynamic);
+ vel_vbo.Fill(model_num_verts*3*sizeof(GLfloat), &vel[0]);
+ }
+
+ if(lod_level == 0 || !tex_transform_vbo[lod_level]->valid()){
+ PROFILER_ZONE(g_profiler_ctx, "tex_transform_vbo");
+ if(lod_level == 0) {
+ tex_transform_vbo[lod_level]->SetHint(model_num_verts*2*sizeof(GLfloat),kVBODynamic);
+ tex_transform_vbo[lod_level]->Fill(model_num_verts*2*sizeof(GLfloat), &model->tex_transform_vec[0]);
+ } else {
+ //Was static
+ tex_transform_vbo[lod_level]->SetHint(model_num_verts*2*sizeof(GLfloat),kVBOStatic);
+ tex_transform_vbo[lod_level]->Fill(model_num_verts*2*sizeof(GLfloat), &model->tex_transform_vec[0]);
+ }
+ }
+ if(lod_level == 0 && fur_model.faces.size() != 0){
+ PROFILER_ZONE(g_profiler_ctx, "fur_transform_vec_vbo");
+ if(GPU_skinning){
+ fur_transform_vec_vbo2.SetHint(fur_model_num_verts*3*sizeof(GLfloat),kVBODynamic);
+ fur_transform_vec_vbo2.Fill(fur_model_num_verts*3*sizeof(GLfloat), &fur_model.morph_transform_vec[0]);
+ } else {
+ fur_transform_vec_vbo0.SetHint(fur_model_num_verts*4*sizeof(GLfloat),kVBODynamic);
+ fur_transform_vec_vbo0.Fill(fur_model_num_verts*4*sizeof(GLfloat), &fur_model.transform_vec[0][0]);
+
+ fur_transform_vec_vbo1.SetHint(fur_model_num_verts*4*sizeof(GLfloat),kVBODynamic);
+ fur_transform_vec_vbo1.Fill(fur_model_num_verts*4*sizeof(GLfloat), &fur_model.transform_vec[1][0]);
+
+ fur_transform_vec_vbo2.SetHint(fur_model_num_verts*4*sizeof(GLfloat),kVBODynamic);
+ fur_transform_vec_vbo2.Fill(fur_model_num_verts*4*sizeof(GLfloat), &fur_model.transform_vec[2][0]);
+ }
+ if(!fur_tex_transform_vbo.valid()){
+ fur_tex_transform_vbo.SetHint(fur_model_num_verts*2*sizeof(GLfloat),kVBOStatic);
+ fur_tex_transform_vbo.Fill(fur_model_num_verts*2*sizeof(GLfloat), &fur_model.tex_transform_vec[0]);
+ }
+ }
+ }
+
+ DrawModel(model, lod_level);
+ /*ApplyBoneMatricesToModel(true);
+ transform_vec_vbo0.FillDynamic(model->num_vertices*4*sizeof(GLfloat), &model->transform_vec[0][0]);
+ transform_vec_vbo1.FillDynamic(model->num_vertices*4*sizeof(GLfloat), &model->transform_vec[1][0]);
+ transform_vec_vbo2.FillDynamic(model->num_vertices*4*sizeof(GLfloat), &model->transform_vec[2][0]);
+ tex_transform_vbo.FillDynamic(model->num_vertices*2*sizeof(GLfloat), &model->tex_transform_vec[0]);
+ DrawModel(model);*/
+ }
+
+ if(_draw_animation_bones_xray){
+ float closest_dist;
+ int closest_bone = -1;
+ for(unsigned i=0; i<display_bone_matrices.size(); ++i){
+ vec3 start = display_bone_matrices[i] * skeleton_.points[skeleton_.bones[skeleton_.physics_bones[i].bone].points[0]];
+ vec3 end = display_bone_matrices[i] * skeleton_.points[skeleton_.bones[skeleton_.physics_bones[i].bone].points[1]];
+ vec3 closest_point, closest_point_mouseray;
+ ClosestPointsBetweenTwoLineSegments(
+ ActiveCameras::Get()->GetPos(),
+ ActiveCameras::Get()->GetPos()+ActiveCameras::Get()->GetMouseRay()*200.0f,
+ start,
+ end,
+ &closest_point_mouseray,
+ &closest_point);
+ float dist = distance_squared(closest_point, closest_point_mouseray);
+ if(closest_bone == -1 || dist < closest_dist){
+ closest_dist = dist;
+ closest_bone = i;
+ }
+ }
+ const float kDistThreshold = 0.01f;
+ for(unsigned i=0; i<display_bone_matrices.size(); ++i){
+ vec3 start = display_bone_matrices[i] * skeleton_.points[skeleton_.bones[skeleton_.physics_bones[i].bone].points[0]];
+ vec3 end = display_bone_matrices[i] * skeleton_.points[skeleton_.bones[skeleton_.physics_bones[i].bone].points[1]];
+ vec4 color(1.0f);
+ if(closest_bone == (int)i && closest_dist < kDistThreshold){
+ color = vec4(1.0f,0.0f,0.0f,1.0f);
+ }
+ DebugDraw::Instance()->AddLine(start, end, color, _delete_on_draw, _DD_XRAY);
+ }
+ }
+ CHECK_GL_ERROR();
+}
+
+bool RiggedObject::DrawBoneConnectUI(Object* objects[], int num_obj_ids, IMUIContext &imui_context, EditorTypes::Tool tool, int id) {
+ Online* online = Online::Instance();
+ bool did_something = false;
+ float closest_dist;
+ int closest_bone = -1;
+ const float kDistThreshold = 0.01f;
+ const std::vector<vec3> &points = skeleton_.skeleton_asset_ref->GetData().points;
+ for(unsigned i=0; i<display_bone_matrices.size(); ++i){
+ if(!skeleton_.has_verts_assigned[skeleton_.physics_bones[i].bone]){
+ continue;
+ }
+ vec3 start = display_bone_matrices[i] * points[skeleton_.bones[skeleton_.physics_bones[i].bone].points[0]];
+ vec3 end = display_bone_matrices[i] * points[skeleton_.bones[skeleton_.physics_bones[i].bone].points[1]];
+ vec3 closest_point, closest_point_mouseray;
+ ClosestPointsBetweenTwoLineSegments(
+ ActiveCameras::Get()->GetPos(),
+ ActiveCameras::Get()->GetPos()+ActiveCameras::Get()->GetMouseRay()*200.0f,
+ start,
+ end,
+ &closest_point_mouseray,
+ &closest_point);
+ float dist = distance_squared(closest_point, closest_point_mouseray);
+ if(dist < kDistThreshold && (closest_bone == -1 || dist < closest_dist)){
+ closest_dist = dist;
+ closest_bone = i;
+ }
+ }
+
+ for(unsigned i=0; i<display_bone_matrices.size(); ++i){
+ if(!skeleton_.has_verts_assigned[skeleton_.physics_bones[i].bone]){
+ continue;
+ }
+ IMUIContext::UIState ui_state;
+ if(imui_context.DoButtonMouseOver(i + id * 100, closest_bone == (int)i,ui_state)){
+ did_something = true;
+ if(tool == EditorTypes::CONNECT){
+ for(int obj_id_iter = 0; obj_id_iter < num_obj_ids; ++obj_id_iter){
+ Object* obj = objects[obj_id_iter];
+ // Check if we already have a connection between this object and this character
+ bool already_attached = false;
+ for(unsigned j=0; j<children.size(); ++j){
+ AttachedEnvObject& attached_env_object = children[j];
+ if(attached_env_object.direct_ptr == obj){
+ // If this object is already attached to the selected bone, increment the number of connections
+ attached_env_object.bone_connection_dirty = true;
+ already_attached = true;
+ bool already_attached_to_bone = false;
+ for(unsigned k=0; k<kMaxBoneConnects; ++k){
+ BoneConnect& bone_connect = attached_env_object.bone_connects[k];
+ if(bone_connect.bone_id == (int)i){
+ ++bone_connect.num_connections;
+ already_attached_to_bone = true;
+ break;
+ }
+ }
+ // If this object is not already attached to the bone, try to attach it with a spare slot
+ if(!already_attached_to_bone){
+ for(unsigned k=0; k<kMaxBoneConnects; ++k){
+ BoneConnect& bone_connect = attached_env_object.bone_connects[k];
+ if(bone_connect.num_connections == 0){
+ bone_connect.bone_id = i;
+ bone_connect.num_connections = 1;
+ break;
+ }
+ }
+ }
+ }
+ }
+ // If object is not attached to character, then we need a new attachment
+ if(!already_attached){
+ // sync_attach_1 här sker själa ändringen, detta bör syncas
+ // Vi vill översätta detta till obj id's och replikeras hos clienten
+ // set parent och skapa ett attachedenvobj
+ children.resize(children.size() + 1);
+ AttachedEnvObject &attached_env_object = children.back();
+ attached_env_object.bone_connection_dirty = true;
+ attached_env_object.direct_ptr = obj;
+ attached_env_object.bone_connects[0].bone_id = i;
+ attached_env_object.bone_connects[0].num_connections = 1;
+ Object* obj = attached_env_object.direct_ptr;
+ obj->SetParent(scenegraph_->GetObjectFromID(char_id));
+
+ if (Online::Instance()->IsActive()) {
+ online->Send<OnlineMessages::AttachToMessage>(char_id, obj->GetID(), i, true, false);
+ }
+ for(unsigned j=1; j<kMaxBoneConnects; ++j){
+ BoneConnect &bone_connect = attached_env_object.bone_connects[j];
+ bone_connect.bone_id = -1;
+ bone_connect.num_connections = 0;
+ }
+ }
+ }
+ } else if(tool == EditorTypes::DISCONNECT){
+ for(int obj_id_iter = 0; obj_id_iter < num_obj_ids; ++obj_id_iter){
+ Object* obj = objects[obj_id_iter];
+ for(int j=children.size()-1; j>=0; --j){
+ AttachedEnvObject& attached_obj = children[j];
+ if(attached_obj.direct_ptr == obj){
+ int total_connections = 0; //NOTE(David): Number of connections remaining between object and character
+ for(unsigned k=0; k<kMaxBoneConnects; ++k){
+ BoneConnect& bone_connect = attached_obj.bone_connects[k];
+ if(bone_connect.bone_id == (int)i && bone_connect.num_connections > 0){
+ --bone_connect.num_connections;
+ }
+ total_connections += bone_connect.num_connections;
+ }
+ if(total_connections == 0){
+ Object* obj = attached_obj.direct_ptr;
+ obj->SetParent(NULL);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ vec4 bone_color(1.0f);
+ if(ui_state == IMUIContext::kActive){
+ bone_color = vec4(1.0f,0.0f,0.0f,1.0f);
+ }
+ if(ui_state == IMUIContext::kHot){
+ bone_color = vec4(1.0f,0.5f,0.5f,1.0f);
+ }
+ vec3 start = display_bone_matrices[i] * points[skeleton_.bones[skeleton_.physics_bones[i].bone].points[0]];
+ vec3 end = display_bone_matrices[i] * points[skeleton_.bones[skeleton_.physics_bones[i].bone].points[1]];
+ DebugDraw::Instance()->AddLine(start, end, bone_color, _delete_on_draw);
+ bone_color[3] *= 0.3f;
+ DebugDraw::Instance()->AddLine(start, end, bone_color, _delete_on_draw, _DD_XRAY);
+ }
+
+ for(unsigned i=0; i<children.size(); ++i){
+ const AttachedEnvObject& attached_env_object = children[i];
+ Object* obj = children[i].direct_ptr;
+ if(obj && obj->Selected()){
+ for(unsigned j=0; j<kMaxBoneConnects; ++j){
+ const BoneConnect& bone_connect = attached_env_object.bone_connects[j];
+ if(bone_connect.num_connections > 0){
+ int bone_id = bone_connect.bone_id;
+ const Bone& bone = skeleton_.bones[skeleton_.physics_bones[bone_id].bone];
+ vec3 start = obj->GetTranslation();
+ vec3 a = display_bone_matrices[bone_id] * skeleton_.points[bone.points[0]];
+ vec3 b = display_bone_matrices[bone_id] * skeleton_.points[bone.points[1]];
+ vec3 a_b = normalize(b-a);
+ vec3 end = display_bone_matrices[bone_id] * ((points[bone.points[0]]+skeleton_.points[bone.points[1]])*0.5f);
+ for(int k=0; k<bone_connect.num_connections; ++k){
+ float offset = ((float)k-(float)bone_connect.num_connections/2.0f)*0.03f;
+ DebugDraw::Instance()->AddLine(start+a_b*offset, end+a_b*offset, vec4(0.0f,1.0f,0.0f,0.8f), _delete_on_draw);
+ DebugDraw::Instance()->AddLine(start+a_b*offset, end+a_b*offset, vec4(0.0f,1.0f,0.0f,0.3f), _delete_on_draw, _DD_XRAY);
+ }
+ }
+ }
+ }
+ }
+ return did_something;
+}
+
+bool RiggedObject::Initialize() {
+ return true;
+}
+
+vec3 RiggedObject::GetTransformedVertex(int vert_id){
+ Model *model = &Models::Instance()->GetModel(model_id[0]);
+ LOG_ASSERT_LT(vert_id, (int)model->vertices.size()/3);
+ vec3 vert = vec3(model->vertices[vert_id*3+0],
+ model->vertices[vert_id*3+1],
+ model->vertices[vert_id*3+2]);
+ vec3 transformed;
+ for(unsigned i=0; i<4; ++i){
+ if(model->bone_weights[vert_id][i] > 0.0f){
+ transformed += display_bone_matrices[(unsigned int)model->bone_ids[vert_id][i]] * vert *
+ model->bone_weights[vert_id][i];
+ }
+ }
+ return transformed;
+}
+
+void RiggedObject::GetTransformedTri(int id, vec3* points){
+ Model *model = &Models::Instance()->GetModel(model_id[0]);
+ points[0] = GetTransformedVertex(model->faces[id*3+0]);
+ points[1] = GetTransformedVertex(model->faces[id*3+1]);
+ points[2] = GetTransformedVertex(model->faces[id*3+2]);
+}
+
+int GetIKAttachBone( const Skeleton &skeleton, const std::string & ik_attach ) {
+ Skeleton::SimpleIKBoneMap::const_iterator iter =
+ skeleton.simple_ik_bones.find(ik_attach);
+ int bone_id = -1;
+ if(iter != skeleton.simple_ik_bones.end()){
+ bone_id = iter->second.bone_id;
+ } else {
+ if(ik_attach == "hip"){
+ iter = skeleton.simple_ik_bones.find("torso");
+ bone_id = iter->second.bone_id;
+ bone_id = skeleton.parents[bone_id];
+ bone_id = skeleton.parents[bone_id];
+ }
+ }
+ return bone_id;
+}
+
+int GetAttachmentBone( const Skeleton &skeleton, const std::string &label, int bone ) {
+ Skeleton::SimpleIKBoneMap::const_iterator iter =
+ skeleton.simple_ik_bones.find(label);
+ int bone_id = -1;
+ if(iter != skeleton.simple_ik_bones.end()){
+ bone_id = iter->second.bone_id;
+ }
+
+ for(int i=0; i<bone; ++i){
+ bone_id = skeleton.parents[bone_id];
+ }
+ return bone_id;
+}
+
+void UpdateAttachedItemRagdoll(AttachedItem &stuck_item, const Skeleton& skeleton, const std::map<int, mat4> &weapon_offset) {
+ ItemObjectScriptReader& item = stuck_item.item;
+ //AttachmentType type = _at_grip;
+ if(item->state() == ItemObject::kSheathed){
+ //type = _at_sheathe;
+ }
+ int bone_id = stuck_item.bone_id;
+ mat4 bone_mat = skeleton.physics_bones[bone_id].bullet_object->GetTransform();
+ mat4 the_weap_mat = bone_mat * weapon_offset.find(item->GetID())->second;
+ mat4 temp_weap_mat = the_weap_mat;
+ temp_weap_mat.SetColumn(2, the_weap_mat.GetColumn(1)*-1.0f);
+ temp_weap_mat.SetColumn(1, the_weap_mat.GetColumn(2));
+ item.SetPhysicsTransform(temp_weap_mat);
+ if(item.just_created){
+ item.SetPhysicsTransform(temp_weap_mat);
+ item.just_created = false;
+ }
+ item.SetInterpInfo(1,1);
+}
+
+void RiggedObject::Update(float timestep) {
+ PROFILER_ZONE(g_profiler_ctx, "RiggedObject Update()");
+
+ Model* model = &Models::Instance()->GetModel(model_id[0]);
+ //blood_surface.CreateDripInTri(RangedRandomInt(0, model->faces.size()/3-1), vec3(1.0f/3.0f), 1.0f, 0.0f, true, SurfaceWalker::WATER);
+ {
+ if(game_timer.game_time < blood_surface.sleep_time){
+ PROFILER_ZONE(g_profiler_ctx, "Update blood surface");
+ blood_surface.Update(scenegraph_, timestep);
+ }
+ }
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Update anim client");
+ anim_client.Update(timestep);
+ }
+ {
+ PROFILER_ZONE(g_profiler_ctx, "HandleAnimationEvents");
+ HandleAnimationEvents(anim_client);
+ }
+ if(!animated) {
+ PROFILER_ZONE(g_profiler_ctx, "Update ragdoll");
+ if(!first_ragdoll_frame) {
+ skeleton_.UpdateTwistBones(true);
+ }
+ first_ragdoll_frame = false;
+ // Updated attached items
+ for(AttachedItemList::iterator iter = attached_items.items.begin(); iter != attached_items.items.end(); ++iter) {
+ UpdateAttachedItemRagdoll(*iter, skeleton_, weapon_offset);
+ }
+ // Make sure attached items are not asleep if skeleton is awake
+ for(AttachedItemList::iterator iter = stuck_items.items.begin(); iter != stuck_items.items.end(); ++iter) {
+ if(skeleton_.physics_bones[0].bullet_object->IsActive()){
+ iter->item->WakeUpPhysics();
+ }
+ }
+ }
+
+ --time_until_next_anim_update;
+ --curr_anim_update_time;
+ --prev_anim_update_time;
+
+ if(time_until_next_anim_update <= 0) {
+ for(unsigned i=0; i<morph_targets.size(); ++i){
+ morph_targets[i].UpdateForInterpolation();
+ }
+ if(!animated){
+ PROFILER_ZONE(g_profiler_ctx, "Update ragdoll animation");
+ // Get results from animation blend tree
+ AnimOutput anim_output;
+ anim_client.GetMatrices(anim_output, &skeleton_.parents);
+ if(!anim_output.matrices.empty()){
+ // Store animation frame
+ for(int i=0, len=animation_frame_bone_matrices.size(); i<len; i++){
+ anim_output.matrices[i].origin *= model_char_scale;
+ animation_frame_bone_matrices[i] = anim_output.matrices[i];
+ }
+ // Apply physics weights for active ragdoll
+ std::map<BulletObject*, int> boid;
+ for(unsigned i=0; i<skeleton_.physics_bones.size(); ++i){
+ boid[skeleton_.physics_bones[i].bullet_object] = i;
+ }
+ if(anim_output.physics_weights.size() >= animation_frame_bone_matrices.size()){
+ for(unsigned i=0; i<skeleton_.physics_joints.size(); ++i){
+ PhysicsJoint &joint = skeleton_.physics_joints[i];
+ int id[2];
+ id[0] = boid[joint.bt_bone[0]];
+ id[1] = boid[joint.bt_bone[1]];
+ float weight = max(anim_output.physics_weights[id[0]],
+ anim_output.physics_weights[id[1]]);
+ skeleton_.SetGFStrength(joint, weight * ragdoll_strength);
+ }
+ } else {
+ for(unsigned i=0; i<skeleton_.physics_joints.size(); ++i){
+ PhysicsJoint &joint = skeleton_.physics_joints[i];
+ skeleton_.SetGFStrength(joint, 1.0f * ragdoll_strength);
+ }
+ }
+ skeleton_.RefreshFixedJoints(animation_frame_bone_matrices);
+ // Apply morph weights from animation output
+ std::map<std::string, MorphTarget*> morph_target_names;
+ for(unsigned i=0; i<morph_targets.size(); ++i){
+ morph_target_names[morph_targets[i].name] = &morph_targets[i];
+ }
+ for(unsigned i=0; i<anim_output.shape_keys.size(); ++i){
+ const ShapeKeyBlend& skb = anim_output.shape_keys[i];
+ const std::string &label = skb.label;
+ if(morph_target_names.find(label) != morph_target_names.end()) {
+ morph_target_names[label]->anim_weight =
+ mix(morph_target_names[label]->anim_weight, skb.weight*skb.weight_weight, ragdoll_strength);
+ }
+ }
+ }
+ } else if(animated){
+ PROFILER_ZONE(g_profiler_ctx, "Update animation");
+ HandleLipSyncMorphTargets(timestep);
+ AnimOutput anim_output;
+ // Get results from animation blend tree
+ bool drawn_recently = last_draw_time > game_timer.game_time - 1.0f;
+ if(cached_animation_frame_bone_matrices.empty() || drawn_recently || !static_char){
+ {
+ PROFILER_ZONE(g_profiler_ctx, "anim_client.GetMatrices()");
+ anim_client.GetMatrices(anim_output, &skeleton_.parents);
+ }
+ for(int i=0, len=anim_output.matrices.size(); i<len; ++i){
+ anim_output.matrices[i].origin *= model_char_scale;
+ }
+ for(int i=0, len=anim_output.ik_bones.size(); i<len; ++i){
+ anim_output.ik_bones[i].transform.origin *= model_char_scale;
+ }
+ weap_anim_info_map = anim_output.weap_anim_info_map;
+ // Copy bone transforms for further manipulation
+ std::vector<BoneTransform> transforms = anim_output.matrices;
+ // Get info for moving and rotating entire character based on animation
+ total_center_offset += vec3(anim_output.delta_offset[0],
+ anim_output.delta_offset[1],
+ anim_output.delta_offset[2]) * model_char_scale;
+ total_rotation += anim_output.delta_rotation;
+
+ unmodified_transforms = anim_output.unmodified_matrices;
+
+ // Apply full-character rotation to bone and weapon transforms
+ vec4 angle_axis(0.0f,1.0f,0.0f,total_rotation);
+ quaternion rot(angle_axis);
+ mat4 rotmat4 = Mat4FromQuaternion(rot);
+ for(unsigned i=0; i<transforms.size(); i++){
+ transforms[i] = rotmat4 * transforms[i];
+ }
+ for(int i=0, len=anim_output.ik_bones.size(); i<len; ++i){
+ anim_output.ik_bones[i].transform = rotmat4 * anim_output.ik_bones[i].transform;
+ }
+ for(WeapAnimInfoMap::iterator iter = weap_anim_info_map.begin(); iter != weap_anim_info_map.end(); ++iter) {
+ WeapAnimInfo &weap_anim_info = iter->second;
+ if(weap_anim_info.relative_id == -1){
+ weap_anim_info.bone_transform = rotmat4 * weap_anim_info.bone_transform;
+ }
+ }
+
+ // Apply full-character translation to bone and weapon transforms
+ for(unsigned i=0; i<transforms.size(); i++){
+ transforms[i].origin += total_center_offset;
+ animation_frame_bone_matrices[i] = transforms[i];
+ }
+ for(int i=0, len=anim_output.ik_bones.size(); i<len; ++i){
+ anim_output.ik_bones[i].transform.origin += total_center_offset;
+ }
+ for(std::map<int, WeapAnimInfo>::iterator iter = weap_anim_info_map.begin(); iter != weap_anim_info_map.end(); ++iter) {
+ WeapAnimInfo &weap_anim_info = iter->second;
+ if(weap_anim_info.relative_id == -1){
+ weap_anim_info.bone_transform.origin += total_center_offset;
+ } else {
+ weap_anim_info.matrix = weap_anim_info.bone_transform.GetMat4();
+ }
+ }
+
+ // Apply animation morphs
+ std::map<std::string, MorphTarget*> morph_target_names;
+ for(unsigned i=0; i<morph_targets.size(); ++i){
+ morph_target_names[morph_targets[i].name] = &morph_targets[i];
+ }
+ for(unsigned i=0; i<anim_output.shape_keys.size(); ++i){
+ const ShapeKeyBlend& skb = anim_output.shape_keys[i];
+ const std::string &label = skb.label;
+ if(morph_target_names.find(label) != morph_target_names.end()) {
+ morph_target_names[label]->anim_weight = skb.weight*skb.weight_weight;
+ }
+ }
+
+ // Apply status keys
+ status_keys.clear();
+ for(unsigned i=0; i<anim_output.status_keys.size(); ++i){
+ const StatusKeyBlend key = anim_output.status_keys[i];
+ status_keys[key.label] = key.weight*key.weight_weight;
+ }
+
+ blended_bone_paths = anim_output.ik_bones;
+ cached_animation_frame_bone_matrices = animation_frame_bone_matrices;
+ } else {
+ animation_frame_bone_matrices = cached_animation_frame_bone_matrices;
+ }
+
+ ASArglist args;
+ if(drawn_recently){
+ args.Add(anim_update_period);
+ } else {
+ args.Add(-anim_update_period);
+ }
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Angelscript FinalAnimationMatrixUpdate()");
+ as_context->CallScriptFunction(as_funcs.final_animation_matrix_update, &args);
+ }
+ // Apply transform to physics objects
+ for(unsigned i=0; i<animation_frame_bone_matrices.size(); i++){
+ BulletObject* b_object = skeleton_.physics_bones[i].bullet_object;
+ if(!b_object){
+ continue;
+ }
+ b_object->SetRotationAndVel(animation_frame_bone_matrices[i].rotation, -curr_anim_update_time);
+ b_object->SetPositionAndVel(animation_frame_bone_matrices[i].origin, -curr_anim_update_time);
+ b_object->SetRotation(animation_frame_bone_matrices[i].rotation);
+ b_object->SetPosition(animation_frame_bone_matrices[i].origin);
+ if(animation_config.kDisablePhysicsInterpolation){
+ b_object->FixDiscontinuity();
+ b_object->SetPosition(animation_frame_bone_matrices[i].origin);
+ }
+ b_object->UpdateTransform();
+ }
+
+ UpdateAttachedItems();
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Angelscript FinalAttachedItemUpdate()");
+ as_context->CallScriptFunction(as_funcs.final_attached_item_update, &args);
+ }
+ }
+ max_time_until_next_anim_update = anim_update_period;
+ time_until_next_anim_update = anim_update_period;
+ prev_anim_update_time = curr_anim_update_time;
+ curr_anim_update_time = 0;
+
+ UpdateCollisionObjects();
+
+ //Store the current animation frame bones for the next network character update.
+ StoreNetworkBones();
+ }
+
+ if(animated){
+ for(AttachedItemList::iterator iter = attached_items.items.begin(); iter != attached_items.items.end(); ++iter) {
+ ItemObjectScriptReader& item = iter->item;
+ item.SetInterpInfo(GetTimeSinceLastAnimUpdate(), GetTimeBetweenLastTwoAnimUpdates());
+ }
+ for(AttachedItemList::iterator iter=stuck_items.items.begin(); iter != stuck_items.items.end(); ++iter) {
+ iter->item.SetInterpInfo(GetTimeSinceLastAnimUpdate(), GetTimeBetweenLastTwoAnimUpdates());
+ }
+ }
+}
+
+
+namespace {
+ struct BoneInfo {
+ int id;
+ float weight;
+ };
+ bool BoneInfoIDSort (const BoneInfo &a, const BoneInfo &b){
+ return a.id < b.id;
+ }
+ bool BoneInfoWeightSort (const BoneInfo &a, const BoneInfo &b){
+ return a.weight > b.weight;
+ }
+ void ApplyNewEdgeCollapseToModel(const Model &base_model,
+ Model &model,
+ const WOLFIRE_SIMPLIFY::SimplifyModel &sm,
+ const WOLFIRE_SIMPLIFY::ParentRecordListVec &vert_parent,
+ const WOLFIRE_SIMPLIFY::ParentRecordListVec &tex_parent)
+ {
+ std::vector<float> sm_vertices(sm.vertices.size());
+ std::vector<float> sm_tex_coords(sm.tex_coords.size());
+ for(int i=0, len=vert_parent.size(); i<len; ++i){
+ WOLFIRE_SIMPLIFY::ParentRecordList parents = vert_parent[i];
+ float total[3];
+ for(int j=0; j<3; ++j){
+ total[j] = 0.0f;
+ }
+ for(WOLFIRE_SIMPLIFY::ParentRecordList::iterator iter = parents.begin(); iter != parents.end(); ++iter){
+ WOLFIRE_SIMPLIFY::ParentRecord pr = (*iter);
+ if(pr.weight > 0.0f){
+ int parent_vert = sm.old_vert_id[pr.id];
+ int parent_vert_index = parent_vert*3;
+ for(int j=0; j<3; ++j){
+ total[j] += base_model.vertices[parent_vert_index+j] * pr.weight;
+ }
+ }
+ }
+ for(WOLFIRE_SIMPLIFY::ParentRecordList::iterator iter = parents.begin(); iter != parents.end(); ++iter){
+ WOLFIRE_SIMPLIFY::ParentRecord pr = (*iter);
+ int parent_vert_index = pr.id*3;
+ for(int j=0; j<3; ++j){
+ sm_vertices[parent_vert_index+j] = total[j];
+ }
+ }
+ }
+ for(int i=0, len=tex_parent.size(); i<len; ++i){
+ WOLFIRE_SIMPLIFY::ParentRecordList parents = tex_parent[i];
+ float total[2];
+ for(int j=0; j<2; ++j){
+ total[j] = 0.0f;
+ }
+ for(WOLFIRE_SIMPLIFY::ParentRecordList::iterator iter = parents.begin(); iter != parents.end(); ++iter){
+ WOLFIRE_SIMPLIFY::ParentRecord pr = (*iter);
+ int parent_tex_index = sm.old_tex_id[pr.id]*2;
+ for(int j=0; j<2; ++j){
+ total[j] += base_model.tex_coords[parent_tex_index+j] * pr.weight;
+ }
+ }
+ for(WOLFIRE_SIMPLIFY::ParentRecordList::iterator iter = parents.begin(); iter != parents.end(); ++iter){
+ WOLFIRE_SIMPLIFY::ParentRecord pr = (*iter);
+ int parent_tex_index = pr.id*2;
+ for(int j=0; j<2; ++j){
+ sm_tex_coords[parent_tex_index+j] = total[j];
+ }
+ }
+ }
+ int num_indices = sm.vert_indices.size();
+ for(int i=0; i<num_indices; ++i){
+ int vert_index = sm.vert_indices[i]*3;
+ for(int j=0; j<3; ++j){
+ model.vertices.push_back(sm_vertices[vert_index+j]);
+ }
+ int tex_index = sm.tex_indices[i]*2;
+ for(int j=0; j<2; ++j){
+ model.tex_coords.push_back(sm_tex_coords[tex_index+j]);
+ }
+ }
+ }
+} // namespace ""
+
+void RiggedObject::Load(const std::string& character_path, vec3 pos, SceneGraph* scenegraph_ptr, OGPalette &palette) {
+ //CharacterRef char_ref = Characters::Instance()->ReturnRef(character_path);
+ CharacterRef char_ref = Engine::Instance()->GetAssetManager()->LoadSync<Character>(character_path);
+ scenegraph_ = scenegraph_ptr;
+
+ //ofc = ObjectFiles::Instance()->ReturnRef(char_ref->GetObjPath());
+ ofc = Engine::Instance()->GetAssetManager()->LoadSync<ObjectFile>(char_ref->GetObjPath());
+
+ Models* mi = Models::Instance();
+ int the_model_id = mi->loadModel(ofc->model_name, _MDL_CENTER);
+ model_id[0] = the_model_id;
+
+ Textures *ti = Textures::Instance();
+ texture_ref = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(ofc->color_map, PX_SRGB, 0x0);
+ normal_texture_ref = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(ofc->normal_map);
+
+ if(!ofc->palette_map_path.empty()) {
+ palette_texture_ref = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(ofc->palette_map_path);
+ for(int i=0; i<max_palette_elements; ++i){
+ if(!ofc->palette_label[i].empty()){
+ LabeledColor lc;
+ lc.label = ofc->palette_label[i];
+ lc.channel = i;
+ lc.color = vec3(1.0f);
+ palette.push_back(lc);
+ }
+ }
+ }
+
+ model_id[0] = Models::Instance()->CopyModel(the_model_id);
+ Model *model = &Models::Instance()->GetModel(model_id[0]);
+ floor_height = model->min_coords[1];
+
+ UpdateGPUSkinning();
+ SetTranslation(pos);
+
+ skeleton_.SetBulletWorld(scenegraph_->bullet_world_);
+
+ const std::string& skeleton_path = char_ref->GetSkeletonPath();
+
+ FZXAssetRef fzx_ref;
+ std::string fzx_path = ofc->model_name.substr(0, ofc->model_name.size()-3)+"fzx";
+ if(FileExists(fzx_path.c_str(), kDataPaths|kModPaths)){
+ //fzx_ref = FZXAssets::Instance()->ReturnRef(fzx_path);
+ fzx_ref = Engine::Instance()->GetAssetManager()->LoadSync<FZXAsset>(fzx_path);
+ }
+ skeleton_.Read(skeleton_path, model_char_scale, char_scale, fzx_ref);
+
+ int num_verts = model->vertices.size()/3;
+ model->transform_vec.resize(4);
+ model->transform_vec[0].resize(num_verts);
+ model->transform_vec[1].resize(num_verts);
+ model->transform_vec[2].resize(num_verts);
+ model->transform_vec[3].resize(num_verts);
+ model->tex_transform_vec.resize(num_verts);
+ model->morph_transform_vec.resize(num_verts);
+
+ //SkeletonAssetRef skeleton_asset_ref = SkeletonAssets::Instance()->ReturnRef(skeleton_path);
+ SkeletonAssetRef skeleton_asset_ref = Engine::Instance()->GetAssetManager()->LoadSync<SkeletonAsset>(skeleton_path);
+ const SkeletonFileData &data = skeleton_asset_ref->GetData();
+ model->bone_weights = data.model_bone_weights;
+ model->bone_ids = data.model_bone_ids;
+
+ skeleton_.SetGravity(true);
+ skeleton_.ReduceROM(0.5f);
+ skeleton_.UnlinkFromBulletWorld();
+
+ anim_client.SetBoneMasses(skeleton_.GetBoneMasses());
+ anim_client.SetSkeleton(&skeleton_);
+ anim_client.SetRetargeting(skeleton_path);
+
+ const mat4 &transform = GetTransform();
+ for(unsigned i=0; i<skeleton_.physics_bones.size(); i++){
+ if(!skeleton_.physics_bones[i].bullet_object){
+ continue;
+ }
+ skeleton_.physics_bones[i].bullet_object->ApplyTransform(transform);
+ }
+
+ ik_offset.resize(skeleton_.physics_bones.size());
+ total_ik_offset = 0.0f;
+ ik_enabled = true;
+ ragdoll_strength = 1.0f;
+
+ lipsync_reader.SetVisemeMorphs(char_ref->GetVisemeMorphs());
+
+ if(skeleton_.simple_ik_bones.find("head")!=skeleton_.simple_ik_bones.end()){
+ const std::vector<int> &parents = skeleton_.parents;
+ const SimpleIKBone &head_sib = skeleton_.simple_ik_bones["head"];
+ int the_bone = head_sib.bone_id;
+ for(int i=0; i<head_sib.chain_length; ++i){
+ if(the_bone != -1){
+ head_chain.insert(skeleton_.physics_bones[the_bone].bullet_object);
+ the_bone = parents[the_bone];
+ }
+ }
+ }
+
+ const std::vector<MorphInfo> &morphs = char_ref->GetMorphs();
+ for(unsigned i=0; i<morphs.size(); ++i){
+ MorphTarget mt;
+ mt.Load(ofc->model_name, the_model_id, morphs[i].label, morphs[i].num_steps);
+ morph_targets.push_back(mt);
+ }
+
+
+ const std::string &morph_path = char_ref->GetPermanentMorphPath();
+ if(!morph_path.empty() && FileExists(morph_path.c_str(), kDataPaths|kModPaths)){
+ MorphTarget mt;
+ mt.Load(ofc->model_name, the_model_id, morph_path, 1, true);
+ mt.name = "permanent";
+ mt.script_weight_weight = 1.0f;
+ mt.script_weight = 1.0f;
+ morph_targets.push_back(mt);
+ }
+ if(!LoadSimplificationCache(char_ref->path_)){
+ const Model &base_model = Models::Instance()->GetModel(model_id[0]);
+ WOLFIRE_SIMPLIFY::SimplifyModelInput smi;
+ // Prepare model to pass to simplify algorithm
+ for(int i=0, len=base_model.faces.size(); i<len; ++i){
+ int vert_index = base_model.faces[i]*3;
+ for(int j=0; j<3; ++j){
+ smi.vertices.push_back(base_model.vertices[vert_index+j]);
+ }
+ smi.old_vert_id.push_back(base_model.faces[i]);
+ int tex_index = base_model.faces[i]*2;
+ for(int j=0; j<2; ++j){
+ smi.tex_coords.push_back(base_model.tex_coords[tex_index+j]);
+ }
+ smi.old_tex_id.push_back(base_model.faces[i]);
+ }
+
+ const int LOD_LEVELS = 5;
+ WOLFIRE_SIMPLIFY::MorphModel lod[LOD_LEVELS];
+ WOLFIRE_SIMPLIFY::ParentRecordListVec vert_parents[LOD_LEVELS];
+ WOLFIRE_SIMPLIFY::ParentRecordListVec tex_parents[LOD_LEVELS];
+ if(WOLFIRE_SIMPLIFY::SimplifyMorphLOD(smi, lod, vert_parents, tex_parents, LOD_LEVELS)){
+ std::vector<BoneInfo> bone_info_accumulate;
+ std::vector<vec4> bone_id;
+ std::vector<vec4> bone_weight;
+
+ for(int lod_index=1; lod_index<4; ++lod_index){
+ model_id[lod_index] = Models::Instance()->AddModel();
+ Model& model = Models::Instance()->GetModel(model_id[lod_index]);
+ WOLFIRE_SIMPLIFY::SimplifyModel sm = lod[lod_index].model;
+ WOLFIRE_SIMPLIFY::ParentRecordListVec vert_parent = vert_parents[lod_index-1];
+ WOLFIRE_SIMPLIFY::ParentRecordListVec tex_parent = tex_parents[lod_index-1];
+ bone_id.clear();
+ bone_weight.clear();
+ bone_id.resize(sm.vertices.size()/3, 0.0f);
+ bone_weight.resize(sm.vertices.size()/3, 0.0f);
+ for(int i=0, len=vert_parent.size(); i<len; ++i){
+ WOLFIRE_SIMPLIFY::ParentRecordList parents = vert_parent[i];
+ float total[3];
+ bone_info_accumulate.clear();
+ for(int j=0; j<3; ++j){
+ total[j] = 0.0f;
+ }
+ for(WOLFIRE_SIMPLIFY::ParentRecordList::iterator iter = parents.begin(); iter != parents.end(); ++iter){
+ WOLFIRE_SIMPLIFY::ParentRecord pr = (*iter);
+ if(pr.weight > 0.0f){
+ int parent_vert = sm.old_vert_id[pr.id];
+ int parent_vert_index = parent_vert*3;
+ for(int j=0; j<3; ++j){
+ total[j] += base_model.vertices[parent_vert_index+j] * pr.weight;
+ }
+ for(int j=0; j<4; ++j){
+ BoneInfo bi;
+ bi.id = (int)base_model.bone_ids[parent_vert][j];
+ bi.weight = base_model.bone_weights[parent_vert][j] * pr.weight;
+ if(bi.weight > 0.0f){
+ bone_info_accumulate.push_back(bi);
+ }
+ }
+ }
+ }
+ // Merge duplicates
+ std::sort(bone_info_accumulate.begin(), bone_info_accumulate.end(), BoneInfoIDSort);
+ if(!bone_info_accumulate.empty()){
+ int last_unique = 0;
+ for(int i=1,len=bone_info_accumulate.size(); i<len; ++i){
+ BoneInfo &bi = bone_info_accumulate[i];
+ BoneInfo &unique_bi = bone_info_accumulate[last_unique];
+ if(bi.id == unique_bi.id){
+ unique_bi.weight += bi.weight;
+ } else {
+ ++last_unique;
+ bone_info_accumulate[last_unique] = bi;
+ }
+ }
+ bone_info_accumulate.resize(last_unique+1);
+ }
+ // Only use most-weighted 4 bones
+ std::sort(bone_info_accumulate.begin(), bone_info_accumulate.end(), BoneInfoWeightSort);
+ if(bone_info_accumulate.size() > 4){
+ bone_info_accumulate.resize(4);
+ }
+ // Normalize weights to add up to 1.0
+ float total_weight = 0.0f;
+ for(int i=0,len=bone_info_accumulate.size(); i<len; ++i){
+ BoneInfo &bi = bone_info_accumulate[i];
+ total_weight += bi.weight;
+ }
+ if(total_weight != 0.0f){
+ for(int i=0,len=bone_info_accumulate.size(); i<len; ++i){
+ BoneInfo &bi = bone_info_accumulate[i];
+ bi.weight /= total_weight;
+ }
+ }
+ for(WOLFIRE_SIMPLIFY::ParentRecordList::iterator iter = parents.begin(); iter != parents.end(); ++iter){
+ WOLFIRE_SIMPLIFY::ParentRecord pr = (*iter);
+ int parent_vert_index = pr.id*3;
+ for(int j=0; j<3; ++j){
+ sm.vertices[parent_vert_index+j] = total[j];
+ }
+ for(int j=0,len=bone_info_accumulate.size(); j<len; ++j){
+ bone_id[pr.id][j] = (float)bone_info_accumulate[j].id;
+ bone_weight[pr.id][j] = bone_info_accumulate[j].weight;
+ }
+ }
+ }
+ for(int i=0, len=tex_parent.size(); i<len; ++i){
+ WOLFIRE_SIMPLIFY::ParentRecordList parents = tex_parent[i];
+ float total[2];
+ for(int j=0; j<2; ++j){
+ total[j] = 0.0f;
+ }
+ for(WOLFIRE_SIMPLIFY::ParentRecordList::iterator iter = parents.begin(); iter != parents.end(); ++iter){
+ WOLFIRE_SIMPLIFY::ParentRecord pr = (*iter);
+ int parent_tex_index = sm.old_tex_id[pr.id]*2;
+ for(int j=0; j<2; ++j){
+ total[j] += base_model.tex_coords[parent_tex_index+j] * pr.weight;
+ }
+ }
+ for(WOLFIRE_SIMPLIFY::ParentRecordList::iterator iter = parents.begin(); iter != parents.end(); ++iter){
+ WOLFIRE_SIMPLIFY::ParentRecord pr = (*iter);
+ int parent_tex_index = pr.id*2;
+ for(int j=0; j<2; ++j){
+ sm.tex_coords[parent_tex_index+j] = total[j];
+ }
+ }
+ }
+ int num_indices = sm.vert_indices.size();
+ for(int i=0; i<num_indices; ++i){
+ int vert_index = sm.vert_indices[i]*3;
+ for(int j=0; j<3; ++j){
+ model.vertices.push_back(sm.vertices[vert_index+j]);
+ }
+ model.bone_ids.push_back(bone_id[sm.vert_indices[i]]);
+ model.bone_weights.push_back(bone_weight[sm.vert_indices[i]]);
+ int tex_index = sm.tex_indices[i]*2;
+ for(int j=0; j<2; ++j){
+ model.tex_coords.push_back(sm.tex_coords[tex_index+j]);
+ }
+ model.faces.push_back(i);
+ }
+
+ model.RemoveDuplicatedVerts();
+ model.RemoveDegenerateTriangles();
+ model.OptimizeTriangleOrder();
+ model.OptimizeVertexOrder();
+
+ int model_num_verts = model.vertices.size()/3;
+ model.transform_vec.resize(4);
+ for(int j=0; j<4; ++j){
+ model.transform_vec[j].resize(model_num_verts);
+ }
+ model.tex_transform_vec.resize(model_num_verts);
+ model.morph_transform_vec.resize(model_num_verts);
+
+ std::queue<MorphTarget*> queue;
+ for(unsigned i=0; i<morph_targets.size(); ++i){
+ queue.push(&morph_targets[i]);
+ }
+ while(!queue.empty()){
+ MorphTarget& morph = (*queue.front());
+ queue.pop();
+ for(unsigned i=0; i<morph.morph_targets.size(); ++i){
+ queue.push(&morph.morph_targets[i]);
+ }
+ if(morph.model_id[0] != -1) {
+ morph.model_id[lod_index] = Models::Instance()->AddModel();
+ morph.model_copy[lod_index] = true;
+ Model& morph_model = Models::Instance()->GetModel(morph.model_id[lod_index]);
+ Model& base_morph_model = Models::Instance()->GetModel(morph.model_id[0]);
+ ApplyNewEdgeCollapseToModel(base_morph_model, morph_model, sm, vert_parent, tex_parent);
+ morph_model.CopyVertCollapse(model);
+ }
+ }
+ for(unsigned i=0; i<morph_targets.size(); ++i){
+ morph_targets[i].CalcVertsModified(model_id[lod_index],lod_index);
+ }
+ }
+ SaveSimplificationCache(char_ref->path_);
+ } else {
+ LOGI << "Simplification failed!" << std::endl;
+ }
+ }
+
+ Model *attach_model = &Models::Instance()->GetModel(model_id[0]);
+ blood_surface.AttachToModel(attach_model);
+ blood_surface.sleep_time = game_timer.game_time;
+
+ if (!blood_inited && IsMainThread()) {
+ blood_surface.PreDrawFrame(
+ Textures::Instance()->getWidth(texture_ref) / 4,
+ Textures::Instance()->getHeight(texture_ref) / 4);
+ blood_surface.Update(scenegraph_, 0.001f, true);
+ blood_inited = true;
+ }
+
+ const std::string &fur_path = char_ref->GetFurPath();
+ int fur_model_id = -1;
+ if(!fur_path.empty() && FileExists(fur_path.c_str(), kDataPaths|kModPaths)){
+ fur_model_id = mi->loadModel(fur_path.c_str(), 0);
+ CreateFurModel(model_id[0], fur_model_id);
+ }
+
+ Textures::Instance()->setWrap(GL_REPEAT, GL_CLAMP_TO_EDGE);
+ fur_tex = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/fur_fin.tga");
+ Textures::Instance()->setWrap(GL_REPEAT, GL_REPEAT);
+
+ skeleton_.AlternateHull(ofc->model_name, Models::Instance()->GetModel(model_id[0]).old_center, model_char_scale);
+ display_bone_matrices.resize(skeleton_.physics_bones.size());
+ display_bone_transforms.resize(skeleton_.physics_bones.size());
+ animation_frame_bone_matrices.resize(skeleton_.physics_bones.size());
+
+ // Allocate aligned memory for SIMD matrix palette skinning
+ #ifdef USE_SSE
+ int bytes_needed = display_bone_matrices.size() * sizeof(simd_mat4);
+ #ifdef _WIN32
+ simd_bone_mats = (simd_mat4*)_aligned_malloc(bytes_needed, 16);
+ #else
+ #ifdef __APPLE__
+ simd_bone_mats = (simd_mat4*)malloc(bytes_needed);
+ #else
+ simd_bone_mats = (simd_mat4*)memalign(16, bytes_needed);
+ #endif
+ #endif
+ #endif
+
+ cached_skeleton_info_.bind_matrices.resize(skeleton_.physics_bones.size());
+ for(int i=0, len=skeleton_.physics_bones.size(); i<len; ++i){
+ cached_skeleton_info_.bind_matrices[i] = ASGetBindMatrix(&skeleton_, i);
+ }
+}
+
+bool RiggedObject::InHeadChain(BulletObject* obj) {
+ if(head_chain.find(obj) != head_chain.end()) {
+ return true;
+ }
+ return false;
+}
+
+void RiggedObject::InvalidatedItem(ItemObjectScriptReader *invalidated) {
+ int weap_id = -1;
+ for(AttachedItemList::iterator iter = attached_items.items.begin(); iter != attached_items.items.end(); ++iter) {
+ if(&(iter->item) == invalidated){
+ weap_id = (iter->item)->GetID();
+ break;
+ }
+ }
+ attached_items.InvalidatedItem(invalidated);
+ if(as_context){
+ ASArglist args;
+ args.Add(weap_id);
+ as_context->CallScriptFunction(as_funcs.notify_item_detach, &args);
+ }
+ char_anim.clear();
+}
+
+mat4 LoadAttachPose(const std::string &anim_path, const std::string &ik_attach, const Skeleton &skeleton, int* bone_id_ptr, bool mirror, bool blade_orient, int chain_depth){
+ int& bone_id = *bone_id_ptr;
+ //AnimationRef anim_ref = Animations::Instance()->ReturnRef(anim_path);
+ AnimationRef anim_ref = Engine::Instance()->GetAssetManager()->LoadSync<Animation>(anim_path);
+ if( anim_ref.valid() ) {
+ AnimOutput anim_output;
+ BlendMap blend_map;
+ AnimInput anim_input(blend_map, NULL);
+ anim_ref->GetMatrices(0.0f, anim_output, anim_input);
+
+ BoneTransform weapon_bt = anim_output.weapon_matrices[0];
+ weapon_bt.origin += anim_output.center_offset;
+
+ bone_id = GetIKAttachBone(skeleton, ik_attach);
+ for(int i=0; i<chain_depth; ++i){
+ bone_id = skeleton.parents[bone_id];
+ }
+ if(mirror){
+ bone_id = skeleton.skeleton_asset_ref->GetData().symmetry[bone_id];
+ anim_input.mirrored = mirror;
+ anim_ref->GetMatrices(0.0f, anim_output, anim_input);
+ }
+ BoneTransform hand_bt = anim_output.matrices[bone_id];
+ hand_bt.origin += anim_output.center_offset;
+ if(mirror){
+ weapon_bt.origin[0] *= -1.0f;
+ {
+ quaternion old_rotation = weapon_bt.rotation;
+ vec4 aa = Quat_2_AA(old_rotation);
+ aa[0] *= -1.0f;
+ aa[3] *= -1.0f;
+ weapon_bt.rotation = quaternion(aa);
+ if(blade_orient){
+ quaternion new_rotation = weapon_bt.rotation;
+ vec3 tip_dir;
+ vec3 blade_dir;
+ if(ik_attach == "hip"){
+ tip_dir = normalize(vec3(0.0f,-0.5f,1.0f));
+ blade_dir = vec3(0.0f,-1.0f,0.0f);
+ } else {
+ tip_dir = vec3(0.0f,0.0f,-1.0f);
+ blade_dir = normalize(vec3(-0.25f, -0.165f, 0.0f));
+ }
+ vec3 new_blade_dir = new_rotation * invert(old_rotation) * blade_dir;
+ vec3 targ_blade_dir;
+ if(ik_attach == "hip"){
+ targ_blade_dir = vec3(0.0f,-1.0f,0.0f);
+ } else {
+ targ_blade_dir = vec3(0.25f, -0.165f, 0.0f);
+ }
+ if(dot(new_blade_dir, targ_blade_dir) < 0.0f){
+ //quaternion quat(vec4(0.0f,0.0f,1.0f,3.1415f));
+ //weapon_bt.rotation = quat * weapon_bt.rotation;
+ vec3 axis_candidate[3];
+ axis_candidate[0] = new_rotation * vec3(1.0f,0.0f,0.0f);
+ axis_candidate[1] = new_rotation * vec3(0.0f,1.0f,0.0f);
+ axis_candidate[2] = new_rotation * vec3(0.0f,0.0f,1.0f);
+ int which = 0;
+ if(fabs(axis_candidate[1][2]) > fabs(axis_candidate[0][2]) &&
+ fabs(axis_candidate[1][2]) > fabs(axis_candidate[2][2]))
+ {
+ which = 1;
+ } else if(fabs(axis_candidate[2][2]) > fabs(axis_candidate[0][2]))
+ {
+ which = 2;
+ }
+ quaternion quat(vec4(axis_candidate[which],3.1415f));
+ weapon_bt.rotation = quat * weapon_bt.rotation;
+ }
+ }
+ }
+ }
+ mat4 hand_mat = hand_bt.GetMat4();
+ mat4 weapon_mat = weapon_bt.GetMat4();
+ return invert(hand_mat) * weapon_mat;
+ } else {
+ return mat4(1.0f);
+ }
+}
+
+void RiggedObject::SetWeaponOffset(AttachedItem &stuck_item, AttachmentType type, bool mirror){
+ ItemObjectScriptReader& item = stuck_item.item;
+ mat4& wo = weapon_offset[item->GetID()];
+ mat4& wor = weapon_offset_retarget[item->GetID()];
+
+ int bone_id;
+ const ItemRef& ir = item.GetAttached()->item_ref();
+ std::string ir_attachanimpath = ir->GetAttachAnimPath(type);
+ if( ir_attachanimpath.empty() == false ) {
+ wo = LoadAttachPose(ir_attachanimpath, ir->GetIKAttach(type), skeleton_, &bone_id, mirror, true, 0);
+ if(!ir->GetAttachAnimBasePath(type).empty()){
+ wor = LoadAttachPose(ir->GetAttachAnimBasePath(type), ir->GetIKAttach(type), skeleton_, &bone_id, mirror, true, 0);
+ wor = invert(wor) * wo;
+ } else {
+ wor.LoadIdentity();
+ }
+ } else {
+ LOGE << "No AttachAnimPath for type " << type << " in " << GetName() << std::endl;
+ }
+
+ stuck_item.rel_mat = wo;
+ stuck_item.bone_id = bone_id;
+}
+
+void RiggedObject::SetItemAttachment(AttachedItem &stuck_item, AttachmentRef attachment_ref, bool mirror){
+ ItemObjectScriptReader& item = stuck_item.item;
+ mat4& wo = weapon_offset[item->GetID()];
+ mat4& wor = weapon_offset_retarget[item->GetID()];
+
+ int bone_id;
+ wo = LoadAttachPose(attachment_ref->anim, attachment_ref->ik_chain_label, skeleton_, &bone_id, mirror, false, attachment_ref->ik_chain_bone);
+
+ /*AnimationRef anim_ref = Animations::Instance()->ReturnRef(attachment_ref->anim);
+ AnimOutput anim_output;
+ BlendMap blend_map;
+ AnimInput anim_input(blend_map, NULL);
+ anim_ref->GetMatrices(0.0f, anim_output, anim_input);
+
+ int bone_id = GetAttachmentBone(skeleton, attachment_ref->ik_chain_label, attachment_ref->ik_chain_bone);
+ mat4 bone_mat = anim_output.matrices[bone_id].GetMat4();
+ mat4 weapon_mat = anim_output.weapon_matrices[0].GetMat4();
+ wo = invert(bone_mat) * weapon_mat;*/
+
+ wor.LoadIdentity();
+ stuck_item.rel_mat = wo;
+ stuck_item.bone_id = bone_id;
+}
+
+void RiggedObject::SheatheItem(int id, bool on_right){
+ AttachedItem *item_ptr = NULL;
+ for(std::list<AttachedItem>::iterator iter = attached_items.items.begin();
+ iter != attached_items.items.end(); ++iter)
+ {
+ ItemObjectScriptReader& item = iter->item;
+ if(item->GetID() == id){
+ item_ptr = &(*iter);
+ }
+ }
+ if(!item_ptr){
+ return;
+ }
+ ItemObjectScriptReader &item = item_ptr->item;
+ item->SetState(ItemObject::kSheathed);
+ bool mirror = on_right;
+ SetWeaponOffset(*item_ptr, _at_sheathe, mirror);
+ char_anim.clear();
+ character_script_getter->ItemsChanged(GetWieldedItemRefs());
+}
+
+void RiggedObject::UnSheatheItem(int id, bool right_hand){
+ AttachedItem *item_ptr = NULL;
+ for(std::list<AttachedItem>::iterator iter = attached_items.items.begin();
+ iter != attached_items.items.end(); ++iter)
+ {
+ ItemObjectScriptReader& item = iter->item;
+ if(item->GetID() == id){
+ item_ptr = &(*iter);
+ }
+ }
+ if(!item_ptr){
+ return;
+ }
+ ItemObjectScriptReader &item = item_ptr->item;
+ item->SetState(ItemObject::kWielded);
+ bool mirror = !right_hand;
+ SetWeaponOffset(*item_ptr, _at_grip, mirror);
+ char_anim.clear();
+ character_script_getter->ItemsChanged(GetWieldedItemRefs());
+}
+
+void RiggedObject::DetachItem(ItemObject* item_object){
+ int item_id = item_object->GetID();
+ attached_items.Remove(item_id);
+ char_anim.clear();
+}
+
+void RiggedObject::DetachAllItems(){
+ attached_items.RemoveAll();
+ char_anim.clear();
+
+ for(std::list<AttachedItem>::iterator iter = stuck_items.items.begin();
+ iter != stuck_items.items.end(); ++iter)
+ {
+ iter->item->ActivatePhysics();
+ }
+ stuck_items.items.clear();
+}
+
+
+void RiggedObject::InvalidatedItemCallback(ItemObjectScriptReader *invalidated, void* this_ptr) {
+ ((RiggedObject*)this_ptr)->InvalidatedItem(invalidated);
+}
+
+vec3 CalcVertexOffset (const vec3 &world_pos, float wind_amount) {
+ const float time = game_timer.game_time*2.0f;
+ vec3 vertex_offset = vec3(0.0f);
+
+ float wind_shake_amount = 0.02f;
+ float wind_time_scale = 8.0f;
+ float wind_shake_detail = 60.0f;
+ float wind_shake_offset = (world_pos[0]+world_pos[1])*wind_shake_detail;
+ /*wind_shake_amount *= max(0.0f,sinf((world_pos[0]+world_pos[1])+time*0.3f));
+ wind_shake_amount *= sinf((world_pos[0]*0.1f+world_pos[2])*0.3f+time*0.6f)+1.0f;
+ wind_shake_amount = max(0.002f,wind_shake_amount);
+ wind_shake_amount *= wind_amount;*/
+
+ wind_shake_amount = 0.05f*wind_amount;
+
+ vertex_offset[0] += sinf(time*wind_time_scale+wind_shake_offset);
+ vertex_offset[2] += cosf(time*wind_time_scale*1.2f+wind_shake_offset);
+ vertex_offset[1] += cosf(time*wind_time_scale*1.4f+wind_shake_offset);
+
+ vertex_offset *= wind_shake_amount;
+
+ return vertex_offset;
+}
+
+bool RiggedObject::GetOnlineIncomingMorphTargetState(MorphTargetStateStorage& dest, const char* name) {
+ if (incoming_network_morphs.find(name) == incoming_network_morphs.end()) {
+ return false;
+ }
+
+ dest = incoming_network_morphs[name];
+ return true;
+}
+
+void RiggedObject::ApplyBoneMatricesToModel(bool old, int lod_level) {
+ Model* model = &Models::Instance()->GetModel(model_id[lod_level]);
+ Online* online = Online::Instance();
+
+ std::vector<MorphTarget*> active_morph_targets;
+ const bool kMorphTargetsEnabled = true;
+ if(kMorphTargetsEnabled){
+ // Determine which morphs are active (have effective weight > 0)
+ for(unsigned k=0; k<morph_targets.size(); ++k){
+ MorphTarget& m_t = morph_targets[k];
+ if(online->IsClient() || m_t.weight > 0.0f ||
+ (m_t.anim_weight > 0.0f && m_t.script_weight_weight < 1.0f) ||
+ (m_t.script_weight > 0.0f && m_t.script_weight_weight > 0.0f))
+ {
+ m_t.PreDrawCamera(GetTimeBetweenLastTwoAnimUpdates(), GetTimeSinceLastAnimUpdate(), lod_level, char_id);
+ if(online->IsClient() || m_t.weight > 0.0f ){
+ active_morph_targets.push_back(&m_t);
+ }
+ }
+ }
+
+ // Initialize per-vertex morph accumulators
+ for(int j=0, len=model->vertices.size()/3; j<len; j++){
+ model->tex_transform_vec[j] = 0.0f;
+ model->morph_transform_vec[j] = 0.0f;
+ }
+
+ // Accumulate the effect of all active morphs
+ Models *mi = Models::Instance();
+ int vert_id, vert_id_index;
+ for(unsigned k=0, len=active_morph_targets.size(); k<len; ++k){
+ MorphTarget* morph = active_morph_targets[k];
+
+ const Model *morph_model = &mi->GetModel(morph->model_id[lod_level]);
+ const std::vector<int> &modified_tc = morph->modified_tc[lod_level];
+ for(unsigned j=0, len2=modified_tc.size(); j<len2; ++j){
+ vert_id = modified_tc[j];
+ vert_id_index = vert_id*2;
+ model->tex_transform_vec[vert_id] +=
+ vec2(morph_model->tex_coords[vert_id_index+0],
+ morph_model->tex_coords[vert_id_index+1]) *
+ morph->disp_weight;
+ }
+ const std::vector<int> &modified_vert = morph->modified_verts[lod_level];
+
+ for(unsigned j=0,len2=modified_vert.size(); j<len2; ++j){
+ vert_id = modified_vert[j];
+ vert_id_index = vert_id*3;
+
+ if (online->IsClient()) {
+ MorphTargetStateStorage state;
+ if (GetOnlineIncomingMorphTargetState(state, morph->name.c_str())) {
+ morph->disp_weight = state.disp_weight;
+ }
+ }
+ model->morph_transform_vec[vert_id][0] += morph_model->vertices[vert_id_index+0] * morph->disp_weight;
+ model->morph_transform_vec[vert_id][1] += morph_model->vertices[vert_id_index+1] * morph->disp_weight;
+ model->morph_transform_vec[vert_id][2] += morph_model->vertices[vert_id_index+2] * morph->disp_weight;
+ }
+ }
+
+ if (online->IsHosting()) {
+ StoreNetworkMorphTargets();
+ }
+ }
+ if(lod_level == 0 && !fur_model.faces.empty()){
+ for(int j=0, len=fur_model.vertices.size()/3; j<len; j++){
+ fur_model.morph_transform_vec[j] = model->morph_transform_vec[fur_base_vertex_ids[j]];
+ }
+ }
+ if(GPU_skinning){
+ return;
+ }
+
+#ifndef USE_SSE
+ #pragma omp parallel for
+ for(int j=0; j<model->num_vertices; j++){
+ mat4 transform = display_bone_matrices[(unsigned int)model->bone_ids[j][0]] * model->bone_weights[j][0];
+ for(int k=1; k<4; k++){
+ if(model->bone_weights[j][k] > 0.0f) {
+ transform += display_bone_matrices[(unsigned int)model->bone_ids[j][k]] * model->bone_weights[j][k];
+ }
+ }
+ transform.SetTranslationPart(transform.GetTranslationPart() +
+ transform.GetRotatedvec3(model->morph_transform_vec[j]));
+ transform.ToVec4(&model->transform_vec[0][j],
+ &model->transform_vec[1][j],
+ &model->transform_vec[2][j],
+ &model->transform_vec[3][j]);
+ }
+#else
+#pragma omp parallel for
+ for(int j=0; j<(int)display_bone_matrices.size(); j++){
+ simd_bone_mats[j].assign(display_bone_matrices[j].entries);
+ }
+
+#pragma omp parallel for
+ for(int j=0,len=model->vertices.size()/3; j<len; j++){
+ simd_mat4 transform = simd_bone_mats[(unsigned int)model->bone_ids[j][0]] * model->bone_weights[j][0];
+ for(int k=1; k<4; k++){
+ if(model->bone_weights[j][k] > 0.0f) {
+ transform += simd_bone_mats[(unsigned int)model->bone_ids[j][k]] * model->bone_weights[j][k];
+ }
+ }
+ transform.AddRotatedVec3AndSave(model->morph_transform_vec[j].entries,
+ model->transform_vec[0][j].entries,
+ model->transform_vec[1][j].entries,
+ model->transform_vec[2][j].entries,
+ model->transform_vec[3][j].entries);
+ }
+
+ if(lod_level == 0 && !fur_model.faces.empty()){
+ #pragma omp parallel for
+ for(int j=0, len=fur_model.vertices.size()/3; j<len; j++){
+ simd_mat4 transform = simd_bone_mats[(unsigned int)fur_model.bone_ids[j][0]] * fur_model.bone_weights[j][0];
+ for(int k=1; k<4; k++){
+ if(fur_model.bone_weights[j][k] > 0.0f) {
+ transform += simd_bone_mats[(unsigned int)fur_model.bone_ids[j][k]] * fur_model.bone_weights[j][k];
+ }
+ }
+ transform.AddRotatedVec3AndSave(fur_model.morph_transform_vec[j].entries,
+ fur_model.transform_vec[0][j].entries,
+ fur_model.transform_vec[1][j].entries,
+ fur_model.transform_vec[2][j].entries,
+ fur_model.transform_vec[3][j].entries);
+ }
+ }
+#endif
+}
+
+void MorphTarget::SetScriptWeight(float weight, float weight_weight) {
+ script_weight = weight;
+ script_weight_weight = weight_weight;
+}
+
+void RiggedObject::SetMTTargetWeight( const std::string &target_name, float weight, float weight_weight ) {
+ for(unsigned i=0; i<morph_targets.size(); ++i){
+ if(morph_targets[i].name == target_name){
+ morph_targets[i].SetScriptWeight(weight, weight_weight);
+ return;
+ }
+ }
+}
+
+vec3 RiggedObject::FetchCenterOffset() {
+ vec3 offset = total_center_offset;
+ total_center_offset = vec3(0.0f);
+ return offset;
+}
+
+float RiggedObject::FetchRotation() {
+ float rot = total_rotation;
+ total_rotation = 0.0f;
+ return rot;
+}
+
+void RiggedObject::DrawModel( Model* model, int lod_level ) {
+ PROFILER_ZONE(g_profiler_ctx, "RiggedObject::DrawModel()");
+ Graphics* graphics = Graphics::Instance();
+ Shaders* shaders = Shaders::Instance();
+
+ if(!model->vbo_loaded){
+ PROFILER_ENTER(g_profiler_ctx, "Create VBO");
+ model->createVBO();
+ PROFILER_LEAVE(g_profiler_ctx);
+ }
+
+ PROFILER_ENTER(g_profiler_ctx, "Get shader attributes");
+ CHECK_GL_ERROR();
+ int shader = shaders->bound_program;
+ int vertex_attrib_id = shaders->returnShaderAttrib("vertex_attrib", shader);
+ int tex_coord_attrib_id = shaders->returnShaderAttrib("tex_coord_attrib", shader);
+ int morph_tex_offset_attrib_id = shaders->returnShaderAttrib("morph_tex_offset_attrib", shader);
+ int fur_tex_coord_attrib_id = shaders->returnShaderAttrib("fur_tex_coord_attrib", shader);
+ int bone_ids_id = shaders->returnShaderAttrib("bone_ids", shader);
+ int bone_weights_id = shaders->returnShaderAttrib("bone_weights", shader);
+ int morph_offsets_id = shaders->returnShaderAttrib("morph_offsets", shader);
+ int transform_mat_column_a_id = shaders->returnShaderAttrib("transform_mat_column_a", shader);
+ int transform_mat_column_b_id = shaders->returnShaderAttrib("transform_mat_column_b", shader);
+ int transform_mat_column_c_id = shaders->returnShaderAttrib("transform_mat_column_c", shader);
+ int vel_id = shaders->returnShaderAttrib("vel_attrib", shader);
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ PROFILER_ENTER(g_profiler_ctx, "Enable shader attributes");
+ CHECK_GL_ERROR();
+ if(vertex_attrib_id != -1){
+ graphics->EnableVertexAttribArray(vertex_attrib_id);
+ model->VBO_vertices.Bind();
+ glVertexAttribPointer(vertex_attrib_id, 3, GL_FLOAT, false, 0, 0);
+ }
+ if(tex_coord_attrib_id != -1){
+ graphics->EnableVertexAttribArray(tex_coord_attrib_id);
+ model->VBO_tex_coords.Bind();
+ glVertexAttribPointer(tex_coord_attrib_id, 2, GL_FLOAT, false, 0, 0);
+ }
+
+ if (GPU_skinning) {
+ if(bone_ids_id != -1){
+ graphics->EnableVertexAttribArray(bone_ids_id);
+ transform_vec_vbo0[lod_level]->Bind();
+ glVertexAttribPointer(bone_ids_id, 4, GL_FLOAT, false, 0, (const void*)transform_vec_vbo0[lod_level]->offset());
+ }
+ if(bone_weights_id != -1){
+ graphics->EnableVertexAttribArray(bone_weights_id);
+ transform_vec_vbo1[lod_level]->Bind();
+ glVertexAttribPointer(bone_weights_id, 4, GL_FLOAT, false, 0, (const void*)transform_vec_vbo1[lod_level]->offset());
+ }
+ if(morph_offsets_id != -1){
+ graphics->EnableVertexAttribArray(morph_offsets_id);
+ transform_vec_vbo2[lod_level]->Bind();
+ glVertexAttribPointer(morph_offsets_id, 3, GL_FLOAT, false, 0, (const void*)transform_vec_vbo2[lod_level]->offset());
+ }
+ } else {
+ if(transform_mat_column_a_id != -1){
+ graphics->EnableVertexAttribArray(transform_mat_column_a_id);
+ transform_vec_vbo0[lod_level]->Bind();
+ glVertexAttribPointer(transform_mat_column_a_id, 4, GL_FLOAT, false, 0, (const void*)transform_vec_vbo0[lod_level]->offset());
+ }
+ if(transform_mat_column_b_id != -1){
+ graphics->EnableVertexAttribArray(transform_mat_column_b_id);
+ transform_vec_vbo1[lod_level]->Bind();
+ glVertexAttribPointer(transform_mat_column_b_id, 4, GL_FLOAT, false, 0, (const void*)transform_vec_vbo1[lod_level]->offset());
+ }
+ if(transform_mat_column_c_id != -1){
+ graphics->EnableVertexAttribArray(transform_mat_column_c_id);
+ transform_vec_vbo2[lod_level]->Bind();
+ glVertexAttribPointer(transform_mat_column_c_id, 4, GL_FLOAT, false, 0, (const void*)transform_vec_vbo2[lod_level]->offset());
+ }
+ }
+ if(morph_tex_offset_attrib_id != -1){
+ graphics->EnableVertexAttribArray(morph_tex_offset_attrib_id);
+ tex_transform_vbo[lod_level]->Bind();
+ glVertexAttribPointer(morph_tex_offset_attrib_id, 2, GL_FLOAT, false, 0, (const void*)tex_transform_vbo[lod_level]->offset());
+ }
+ if(fur_tex_coord_attrib_id != -1){
+ graphics->EnableVertexAttribArray(fur_tex_coord_attrib_id);
+ model->VBO_tex_coords.Bind();
+ glVertexAttribPointer(fur_tex_coord_attrib_id, 2, GL_FLOAT, false, 0, 0);
+ }
+ if(vel_id != -1){
+ graphics->EnableVertexAttribArray(vel_id);
+ vel_vbo.Bind();
+ glVertexAttribPointer(vel_id, 3, GL_FLOAT, false, 0, (const void*)vel_vbo.offset());
+ }
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ PROFILER_ENTER(g_profiler_ctx, "Bind faces VBO");
+ model->VBO_faces.Bind();
+ PROFILER_LEAVE(g_profiler_ctx);
+ GL_PERF_START();
+ PROFILER_ENTER(g_profiler_ctx, "glDrawElements");
+ graphics->DrawElements(GL_TRIANGLES, model->faces.size(), GL_UNSIGNED_INT, 0);
+ PROFILER_LEAVE(g_profiler_ctx);
+ GL_PERF_END();
+ CHECK_GL_ERROR();
+
+ if(lod_level == 0 && !fur_model.faces.empty() && !graphics->drawing_shadow){
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw fur");
+ CHECK_GL_ERROR();
+ Textures::Instance()->bindTexture(fur_tex, 7);
+ graphics->setCullFace(false);
+ graphics->SetBlendFunc(GL_ONE,GL_ZERO,GL_ONE,GL_ONE);
+ graphics->setBlend(true);
+
+ if(graphics->use_sample_alpha_to_coverage){
+ glEnable( GL_SAMPLE_ALPHA_TO_COVERAGE );
+ }
+ if(!fur_model.vbo_loaded){
+ fur_model.createVBO();
+ }
+
+ if(vertex_attrib_id != -1){
+ fur_model.VBO_vertices.Bind();
+ glVertexAttribPointer(vertex_attrib_id, 3, GL_FLOAT, false, 0, 0);
+ }
+ if(tex_coord_attrib_id != -1){
+ fur_model.VBO_tex_coords.Bind();
+ glVertexAttribPointer(tex_coord_attrib_id, 2, GL_FLOAT, false, 0, 0);
+ }
+ if (GPU_skinning) {
+ if(bone_ids_id != -1){
+ graphics->EnableVertexAttribArray(bone_ids_id);
+ fur_transform_vec_vbo0.Bind();
+ glVertexAttribPointer(bone_ids_id, 4, GL_FLOAT, false, 0, (const void*)fur_transform_vec_vbo0.offset());
+ }
+ if(bone_weights_id != -1){
+ graphics->EnableVertexAttribArray(bone_weights_id);
+ fur_transform_vec_vbo1.Bind();
+ glVertexAttribPointer(bone_weights_id, 4, GL_FLOAT, false, 0, (const void*)fur_transform_vec_vbo1.offset());
+ }
+ if(morph_offsets_id != -1){
+ graphics->EnableVertexAttribArray(morph_offsets_id);
+ fur_transform_vec_vbo2.Bind();
+ glVertexAttribPointer(morph_offsets_id, 3, GL_FLOAT, false, 0, (const void*)fur_transform_vec_vbo2.offset());
+ }
+ } else {
+ if(transform_mat_column_a_id != -1){
+ fur_transform_vec_vbo0.Bind();
+ glVertexAttribPointer(transform_mat_column_a_id, 4, GL_FLOAT, false, 0, (const void*)fur_transform_vec_vbo0.offset());
+ }
+ if(transform_mat_column_b_id != -1){
+ fur_transform_vec_vbo1.Bind();
+ glVertexAttribPointer(transform_mat_column_b_id, 4, GL_FLOAT, false, 0, (const void*)fur_transform_vec_vbo1.offset());
+ }
+ if(transform_mat_column_c_id != -1){
+ fur_transform_vec_vbo2.Bind();
+ glVertexAttribPointer(transform_mat_column_c_id, 4, GL_FLOAT, false, 0, (const void*)fur_transform_vec_vbo2.offset());
+ }
+ }
+ if(morph_tex_offset_attrib_id != -1){
+ fur_tex_transform_vbo.Bind();
+ glVertexAttribPointer(morph_tex_offset_attrib_id, 2, GL_FLOAT, false, 0, (const void*)fur_tex_transform_vbo.offset());
+ }
+ if(fur_tex_coord_attrib_id != -1){
+ graphics->EnableVertexAttribArray(fur_tex_coord_attrib_id);
+ fur_model.VBO_tex_coords2.Bind();
+ glVertexAttribPointer(fur_tex_coord_attrib_id, 2, GL_FLOAT, false, 0, 0);
+ }
+ fur_model.VBO_faces.Bind();
+ graphics->DrawElements(GL_TRIANGLES, fur_model.faces.size(), GL_UNSIGNED_INT, 0);
+ graphics->setCullFace(true);
+
+ if(graphics->use_sample_alpha_to_coverage){
+ glDisable( GL_SAMPLE_ALPHA_TO_COVERAGE );
+ }
+ }
+
+ PROFILER_ENTER(g_profiler_ctx, "Disable shader attributes");
+ CHECK_GL_ERROR();
+ graphics->ResetVertexAttribArrays();
+
+ CHECK_GL_ERROR();
+ PROFILER_LEAVE(g_profiler_ctx);
+}
+
+void RiggedObject::AddAnimation( std::string path, float weight_coord )
+{
+ //AnimationRef new_anim = Animations::Instance()->ReturnRef(path);
+ //(animation_group.AddAnimation(new_anim, weight_coord);
+}
+
+void RiggedObject::FixDiscontinuity() {
+ time_until_next_anim_update = 0;
+ Update(game_timer.timestep);
+ time_until_next_anim_update = 0;
+ Update(game_timer.timestep);
+ for(unsigned i=0; i<skeleton_.physics_bones.size(); i++){
+ if(skeleton_.physics_bones[i].bullet_object){
+ skeleton_.physics_bones[i].bullet_object->FixDiscontinuity();
+ }
+ }
+}
+
+void RiggedObject::RefreshRagdoll()
+{
+ if(!animated){
+ for(unsigned i=0; i<skeleton_.physics_bones.size(); i++){
+ BulletObject* b_object = skeleton_.physics_bones[i].bullet_object;
+ if(!b_object){
+ continue;
+ }
+ b_object->Activate();
+ }
+ }
+}
+
+
+void RiggedObject::Ragdoll(const vec3 &velocity) {
+ if(animated){
+ animated = false;
+ first_ragdoll_frame = true;
+
+ for(unsigned i=0; i<skeleton_.physics_bones.size(); i++){
+ BulletObject* b_object = skeleton_.physics_bones[i].bullet_object;
+ if(!b_object){
+ continue;
+ }
+ b_object->Activate();
+ b_object->old_transform.SetTranslationPart(b_object->GetInterpPositionX(GetTimeBetweenLastTwoAnimUpdates(), GetTimeSinceLastAnimUpdate()+1));
+ b_object->old_transform.SetRotationPart(b_object->GetInterpRotationX(GetTimeBetweenLastTwoAnimUpdates(), GetTimeSinceLastAnimUpdate()+1));
+ }
+
+ skeleton_.LinkToBulletWorld();
+ for(unsigned i=0; i<skeleton_.physics_joints.size(); ++i){
+ skeleton_.SetGFStrength(skeleton_.physics_joints[i], 0.0f);
+ }
+
+ for(std::list<AttachedItem>::iterator iter = stuck_items.items.begin();
+ iter != stuck_items.items.end(); ++iter)
+ {
+ iter->item.ActivatePhysics();
+ iter->item.AddConstraint(skeleton_.physics_bones[iter->bone_id].bullet_object);
+ }
+ }
+}
+
+void RiggedObject::SetDamping(float amount){
+ for(unsigned i=0; i<skeleton_.physics_bones.size(); i++){
+ BulletObject* b_object = skeleton_.physics_bones[i].bullet_object;
+ if(!b_object){
+ continue;
+ }
+ b_object->SetDamping(amount);
+ if(amount >= 1.0f){
+ b_object->Sleep();
+ }
+ }
+ if(amount >= 1.0f){
+ for(std::list<AttachedItem>::iterator iter = stuck_items.items.begin();
+ iter != stuck_items.items.end(); ++iter)
+ {
+ iter->item->SleepPhysics();
+ }
+ }
+}
+
+void RiggedObject::UnRagdoll() {
+ //mat4 inv_transform = invert(GetTransformationMatrix());
+ if(!animated){
+ for(std::list<AttachedItem>::iterator iter = stuck_items.items.begin();
+ iter != stuck_items.items.end(); ++iter)
+ {
+ iter->item.RemoveConstraint();
+ }
+
+ anim_client.Reset();
+
+ animated = true;
+
+ skeleton_.UnlinkFromBulletWorld();
+ skeleton_.UpdateTwistBones(true);
+
+ time_until_next_anim_update = rand()%anim_update_period-anim_update_period;
+ }
+}
+
+vec3 RiggedObject::GetAvgPosition()
+{
+ vec3 total(0.0f);
+ int num_total = 0;
+ for(unsigned i=0; i<skeleton_.physics_bones.size(); i++){
+ if(!skeleton_.physics_bones[i].bullet_object){
+ continue;
+ }
+ num_total++;
+ total += skeleton_.physics_bones[i].bullet_object->GetPosition();
+ }
+
+ total /= (float)num_total;
+
+ return total;
+}
+
+vec3 RiggedObject::GetAvgVelocity()
+{
+ vec3 total(0.0f);
+ float divisor = 0.0f;
+ for(unsigned i=0; i<skeleton_.physics_bones.size(); i++){
+ if(!skeleton_.physics_bones[i].bullet_object ||
+ !skeleton_.physics_bones[i].bullet_object->linked){
+ continue;
+ }
+ const float mass = skeleton_.physics_bones[i].bullet_object->GetMass();
+ divisor += mass;
+ total += skeleton_.physics_bones[i].bullet_object->GetLinearVelocity() * mass;
+ }
+
+ total /= divisor;
+
+ return total;
+}
+
+vec3 RiggedObject::GetAvgAngularVelocity() {
+ vec3 total(0.0f);
+ float divisor = 0.0f;
+ for(unsigned i=0; i<skeleton_.physics_bones.size(); i++){
+ if(!skeleton_.physics_bones[i].bullet_object ||
+ !skeleton_.physics_bones[i].bullet_object->linked){
+ continue;
+ }
+ total += skeleton_.physics_bones[i].bullet_object->GetAngularVelocity() *
+ skeleton_.physics_bones[i].bullet_object->GetMass();
+ divisor += skeleton_.physics_bones[i].bullet_object->GetMass();
+ }
+
+ total /= divisor;
+
+ return total;
+}
+
+
+quaternion RiggedObject::GetAvgRotation()
+{
+ quaternion total;
+ for(unsigned i=0; i<skeleton_.physics_bones.size(); i++){
+ if(!skeleton_.physics_bones[i].bullet_object){
+ continue;
+ }
+ const PhysicsBone& bone = skeleton_.physics_bones[i];
+ mat4 rotation = bone.bullet_object->GetRotation() *
+ invert(bone.initial_rotation);
+ total += QuaternionFromMat4(rotation) * bone.bullet_object->GetMass();
+ }
+
+ total = normalize(total);
+
+ return total;
+}
+
+vec3 RiggedObject::GetIKTargetPosition( const std::string &target_name )
+{
+ if(animation_frame_bone_matrices.empty()){
+ return vec3(0.0f);
+ }
+ Skeleton::SimpleIKBoneMap::iterator iter;
+ iter = skeleton_.simple_ik_bones.find(target_name);
+ if(iter != skeleton_.simple_ik_bones.end()){
+ return animation_frame_bone_matrices[iter->second.bone_id].origin;
+ }
+ return vec3(0.0f);
+}
+
+float RiggedObject::GetIKWeight( const std::string &target_name ) {
+ for(unsigned i=0; i<blended_bone_paths.size(); ++i){
+ if(blended_bone_paths[i].ik_bone.label == target_name){
+ return blended_bone_paths[i].weight;
+ }
+ }
+ return 0.0f;
+}
+
+BoneTransform GetIKTransform( RiggedObject* rigged_object, const std::string &target_name ) {
+ for(unsigned i=0; i<rigged_object->blended_bone_paths.size(); ++i){
+ if(rigged_object->blended_bone_paths[i].ik_bone.label == target_name){
+ return rigged_object->blended_bone_paths[i].transform;
+ }
+ }
+ BoneTransform identity;
+ return identity;
+}
+
+mat4 GetUnmodifiedIKTransform( RiggedObject* rigged_object, const std::string &target_name ) {
+ for(unsigned i=0; i<rigged_object->blended_bone_paths.size(); ++i){
+ if(rigged_object->blended_bone_paths[i].ik_bone.label == target_name){
+ return rigged_object->blended_bone_paths[i].unmodified_transform.GetMat4();
+ }
+ }
+ mat4 identity;
+ return identity;
+}
+
+vec3 RiggedObject::GetIKTargetAnimPosition( const std::string &target_name ) {
+ for(unsigned i=0; i<blended_bone_paths.size(); ++i){
+ if(blended_bone_paths[i].ik_bone.label == target_name){
+ int target_bone = blended_bone_paths[i].ik_bone.bone_path.back();
+ return unmodified_transforms[target_bone].origin;
+ }
+ }
+ return vec3(0.0f);
+}
+
+void RiggedObject::SetASContext( ASContext* _as_context ) {
+ as_context = _as_context;
+
+ as_funcs.handle_animation_event = as_context->RegisterExpectedFunction("void HandleAnimationEvent(string,vec3)", true);
+ as_funcs.display_matrix_update = as_context->RegisterExpectedFunction("void DisplayMatrixUpdate()", true);
+ as_funcs.final_animation_matrix_update = as_context->RegisterExpectedFunction("void FinalAnimationMatrixUpdate(int)", true);
+ as_funcs.final_attached_item_update = as_context->RegisterExpectedFunction("void FinalAttachedItemUpdate(int)", true);
+ as_funcs.notify_item_detach = as_context->RegisterExpectedFunction("void NotifyItemDetach(int)", true);
+
+ as_context->LoadExpectedFunctions();
+
+ anim_client.SetASContext(_as_context);
+}
+
+void MorphTarget::CalcVertsModified(int base_model_id, int lod_level) {
+ Model& model = Models::Instance()->GetModel(model_id[lod_level]);
+ int model_num_verts = model.vertices.size()/3;
+ if(morph_targets.empty()){
+ verts_modified[lod_level].resize(model_num_verts, false);
+ tc_modified[lod_level].resize(model_num_verts, false);
+ for(int i=0; i<model_num_verts; ++i){
+ if(fabsf(model.vertices[i*3+0]) > EPSILON ||
+ fabsf(model.vertices[i*3+1]) > EPSILON ||
+ fabsf(model.vertices[i*3+2]) > EPSILON)
+ {
+ modified_verts[lod_level].push_back(i);
+ verts_modified[lod_level][i] = true;
+ }
+ if(fabsf(model.tex_coords[i*2+0]) > EPSILON ||
+ fabsf(model.tex_coords[i*2+1]) > EPSILON)
+ {
+ modified_tc[lod_level].push_back(i);
+ tc_modified[lod_level][i] = true;
+ }
+ }
+ } else {
+ verts_modified[lod_level].resize(model_num_verts, false);
+ tc_modified[lod_level].resize(model_num_verts, false);
+ for(unsigned i=0; i<morph_targets.size(); ++i){
+ MorphTarget &mt = morph_targets[i];
+ mt.CalcVertsModified(base_model_id, lod_level);
+ for(int j=0; j<model_num_verts; ++j){
+ if(mt.verts_modified[lod_level][j]){
+ verts_modified[lod_level][j] = true;
+ }
+ if(mt.tc_modified[lod_level][j]){
+ tc_modified[lod_level][j] = true;
+ }
+ }
+ }
+ for(int i=0; i<model_num_verts; ++i){
+ if(verts_modified[lod_level][i]){
+ modified_verts[lod_level].push_back(i);
+ }
+ if(tc_modified[lod_level][i]){
+ modified_tc[lod_level].push_back(i);
+ }
+ }
+ }
+}
+
+void MorphTarget::Load(const std::string &base_model_name,
+ int base_model_id,
+ const std::string &_name,
+ int parts,
+ bool absolute_path)
+{
+ static const int kBufSize = 256;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "Loading morph target: %s", _name.c_str());
+ PROFILER_ZONE_DYNAMIC_STRING(g_profiler_ctx, buf);
+
+ name = _name;
+ old_weight = 0.0f;
+ weight = 0.0f;
+ anim_weight = 0.0f;
+ script_weight = 0.0f;
+ script_weight_weight = 0.0f;
+ checksum = 0;
+
+ for(int i=0; i<4; ++i){
+ model_id[i] = -1;
+ }
+
+ if(parts == 1){
+ std::string path;
+ if(absolute_path){
+ path = _name;
+ } else {
+ path = GetMorphPath(base_model_name, _name);
+ }
+
+ model_id[0] = Models::Instance()->LoadModelAsMorph(path.c_str(), base_model_id, base_model_name);
+ char abs_path[kPathSize];
+ if(FindFilePath(path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths) == -1){
+ FatalError("Error", "Could not find %s", path.c_str());
+ }
+ checksum = Checksum(abs_path);
+ model_copy[0] = false;
+ if(model_id[0] == -1){
+ return;
+ }
+ } else {
+ model_id[0] = Models::Instance()->CopyModel(base_model_id);
+ Model& model = Models::Instance()->GetModel(model_id[0]);
+ for(int i=0, len=(int)model.vertices.size(); i<len; ++i){
+ model.vertices[i] = 0.0f;
+ }
+ for(int i=0, len=(int)model.tex_coords.size(); i<len; ++i){
+ model.tex_coords[i] = 0.0f;
+ }
+ model_copy[0] = true;
+ for(int i=0; i<parts; ++i){
+ MorphTarget mt;
+ std::stringstream oss;
+ oss << _name << i;
+ mt.Load(base_model_name, base_model_id, oss.str());
+ checksum += mt.checksum;
+ morph_targets.push_back(mt);
+ }
+ }
+ CalcVertsModified(base_model_id, 0);
+}
+
+void MorphTarget::PreDrawCamera(int num, int progress, int lod_level, uint32_t char_id) {
+ if (g_debug_runtime_disable_morph_target_pre_draw_camera) {
+ return;
+ }
+
+ Online* online = Online::Instance();
+ MovementObject* mov = (MovementObject*)Engine::Instance()->GetSceneGraph()->GetObjectFromID(char_id);
+ RiggedObject* rigged_object = nullptr;
+
+ if(mov != nullptr) {
+ rigged_object = mov->rigged_object();
+ }
+
+ float interp_weight = mix(old_weight, weight, game_timer.GetInterpWeightX(num, progress));
+ if(!morph_targets.empty()){
+ Model& model = Models::Instance()->GetModel(model_id[lod_level]);
+ int num_parts = morph_targets.size();
+ mod_weight = max(0.0f,interp_weight*(float)(num_parts)-1.0f);
+ int start = max(0, min((int)mod_weight, num_parts-1));
+ int end = max(0, min(start+1, num_parts-1));
+ float new_weight = mod_weight - (float)start;
+
+ if (online->IsClient()) {
+ MorphTargetStateStorage state;
+ if(rigged_object != nullptr) {
+ if(rigged_object->GetOnlineIncomingMorphTargetState(state, name.c_str())) {
+ mod_weight = state.mod_weight;
+ start = max(0, min((int)mod_weight, num_parts - 1));
+ end = max(0, min(start + 1, num_parts - 1));
+ }
+ }
+ }
+
+ Model& morph_model_start = Models::Instance()->GetModel(morph_targets[start].model_id[lod_level]);
+ Model& morph_model_end = Models::Instance()->GetModel(morph_targets[end].model_id[lod_level]);
+
+ if(new_weight == 0.0f || new_weight == 1.0f){
+ Model *the_model = (new_weight == 0.0f)?&morph_model_start:&morph_model_end;
+ for(unsigned j=0; j<modified_verts[lod_level].size(); ++j){
+ const int index = modified_verts[lod_level][j]*3;
+ model.vertices[index+0] = the_model->vertices[index+0];
+ model.vertices[index+1] = the_model->vertices[index+1];
+ model.vertices[index+2] = the_model->vertices[index+2];
+ }
+ for(unsigned j=0; j<modified_tc[lod_level].size(); ++j){
+ const int tc_index = modified_tc[lod_level][j]*2;
+ model.tex_coords[tc_index+0] = the_model->tex_coords[tc_index+0];
+ model.tex_coords[tc_index+1] = the_model->tex_coords[tc_index+1];
+ }
+ } else {
+ for(unsigned j=0; j<modified_verts[lod_level].size(); ++j){
+ const int index = modified_verts[lod_level][j]*3;
+ model.vertices[index+0] = morph_model_start.vertices[index+0]*(1.0f-new_weight);
+ model.vertices[index+1] = morph_model_start.vertices[index+1]*(1.0f-new_weight);
+ model.vertices[index+2] = morph_model_start.vertices[index+2]*(1.0f-new_weight);
+ model.vertices[index+0] += morph_model_end.vertices[index+0]*new_weight;
+ model.vertices[index+1] += morph_model_end.vertices[index+1]*new_weight;
+ model.vertices[index+2] += morph_model_end.vertices[index+2]*new_weight;
+ }
+ for(unsigned j=0; j<modified_tc[lod_level].size(); ++j){
+ const int tc_index = modified_tc[lod_level][j]*2;
+ model.tex_coords[tc_index+0] = morph_model_start.tex_coords[tc_index+0]*(1.0f-new_weight);
+ model.tex_coords[tc_index+1] = morph_model_start.tex_coords[tc_index+1]*(1.0f-new_weight);
+ model.tex_coords[tc_index+0] += morph_model_end.tex_coords[tc_index+0]*new_weight;
+ model.tex_coords[tc_index+1] += morph_model_end.tex_coords[tc_index+1]*new_weight;
+ }
+ }
+ disp_weight = max(0.0f, min(1.0f,interp_weight*(float)(num_parts)));
+ } else {
+ disp_weight = max(0.0f, min(1.0f,interp_weight));
+ }
+}
+
+float MorphTarget::GetScriptWeight()
+{
+ return script_weight;
+}
+
+float MorphTarget::GetWeight() {
+ return mix(anim_weight, script_weight, script_weight_weight);
+}
+
+void MorphTarget::UpdateForInterpolation()
+{
+ old_weight = weight;
+ weight = GetWeight();
+ weight = min(1.0f, max(0.0f, weight));
+}
+
+void MorphTarget::Dispose() {
+ for(int i=0; i<4; ++i){
+ if(model_copy[i]){
+ Models::Instance()->DeleteModel(model_id[i]);
+ }
+ }
+ for(unsigned i=0; i<morph_targets.size(); ++i){
+ morph_targets[i].Dispose();
+ }
+}
+
+float RiggedObject::GetStatusKeyValue( const std::string &label ) {
+ if(status_keys.find(label) != status_keys.end()){
+ return status_keys[label];
+ } else {
+ return 0.0f;
+ }
+}
+
+void RiggedObject::ApplyForceToRagdoll( const vec3 &force, const vec3 &position ) {
+ std::vector<float> dist(skeleton_.physics_bones.size());
+ float total = 0.0f;
+ for(unsigned i=0; i<skeleton_.physics_bones.size(); ++i){
+ BulletObject* bo = skeleton_.physics_bones[i].bullet_object;
+ if(!bo || skeleton_.fixed_obj[i]){
+ continue;
+ }
+ dist[i] = 1.0f / (distance_squared(position, bo->GetPosition())+0.01f);
+ dist[i] *= bo->GetMass();
+ total += dist[i];
+ }
+ for(unsigned i=0; i<skeleton_.physics_bones.size(); ++i){
+ BulletObject* bo = skeleton_.physics_bones[i].bullet_object;
+ if(!bo || skeleton_.fixed_obj[i]){
+ continue;
+ }
+ bo->ApplyForceAtWorldPoint(bo->GetPosition(), force*dist[i] / total);
+ }
+}
+
+void RiggedObject::ApplyForceToBone( const vec3 &force, const int &bone ) {
+ BulletObject* bo = skeleton_.physics_bones[bone].bullet_object;
+ bo->ApplyForceAtWorldPoint(bo->GetPosition(), force);
+}
+
+void RiggedObject::ApplyForceLineToRagdoll( const vec3 &force, const vec3 &position, const vec3 &line_dir ) {
+ std::vector<float> dist(skeleton_.physics_bones.size());
+ float total = 0.0f;
+ for(unsigned i=0; i<skeleton_.physics_bones.size(); ++i){
+ BulletObject* bo = skeleton_.physics_bones[i].bullet_object;
+ if(!bo || skeleton_.fixed_obj[i]){
+ continue;
+ }
+ vec3 temp_pos = bo->GetPosition() - position;
+ temp_pos -= line_dir * dot(line_dir, temp_pos);
+ dist[i] = 1.0f / (length_squared(temp_pos)+0.01f);
+ dist[i] *= bo->GetMass();
+ total += dist[i];
+ }
+ for(unsigned i=0; i<skeleton_.physics_bones.size(); ++i){
+ BulletObject* bo = skeleton_.physics_bones[i].bullet_object;
+ if(!bo || skeleton_.fixed_obj[i]){
+ continue;
+ }
+ bo->ApplyForceAtWorldPoint(bo->GetPosition(), force*dist[i] / total);
+ }
+}
+
+vec3 RiggedObject::GetDisplayBonePosition(int bone) {
+ return display_bone_matrices[bone].GetTranslationPart();
+}
+
+vec3 RiggedObject::GetBonePosition( int bone ) {
+ return skeleton_.physics_bones[bone].bullet_object->GetPosition();
+}
+
+vec3 RiggedObject::GetBoneLinearVel( int bone ) {
+ return skeleton_.physics_bones[bone].bullet_object->GetLinearVelocity();
+}
+
+mat4 RiggedObject::GetBoneRotation( int bone ) {
+ return skeleton_.physics_bones[bone].bullet_object->GetRotation();
+}
+
+void RiggedObject::SetSkeletonOwner( Object* owner ) {
+ for(unsigned i=0; i<skeleton_.physics_bones.size(); ++i){
+ if(!skeleton_.physics_bones[i].bullet_object){
+ continue;
+ }
+ skeleton_.physics_bones[i].bullet_object->owner_object = owner;
+ }
+}
+
+void RiggedObject::SetAnimUpdatePeriod( int _anim_update_period ) {
+ anim_update_period = _anim_update_period;
+}
+
+mat4 RiggedObject::GetIKTargetTransform( const std::string &target_name ) {
+ int bone = skeleton_.simple_ik_bones[target_name].bone_id;
+ return skeleton_.physics_bones[bone].bullet_object->GetTransform();
+}
+
+void RiggedObject::HandleAnimationEvents( AnimationClient &_anim_client ) {
+ // Loop through animation events and send them to Angelscript to handle
+ std::queue<AnimationEvent> active_events = _anim_client.GetActiveEvents();
+ while(!active_events.empty()){
+ AnimationEvent& the_event = active_events.front();
+ vec3 position = skeleton_.physics_bones[the_event.which_bone].
+ bullet_object->GetPosition();
+ ASArglist args;
+ args.AddObject(&the_event.event_string);
+ args.AddObject(&position);
+ as_context->CallScriptFunction(as_funcs.handle_animation_event, &args);
+ active_events.pop();
+ }
+}
+
+void RiggedObject::SetRagdollStrength( float amount ) {
+ ragdoll_strength = amount;
+}
+
+mat4 RiggedObject::GetAvgIKChainTransform( const std::string &target_name ) {
+ mat4 total(0.0f);
+ float total_num = 0.0f;
+ SimpleIKBone &sib = skeleton_.simple_ik_bones[target_name];
+ int bone = sib.bone_id;
+ for(int i=0; i<sib.chain_length; ++i){
+ total += skeleton_.physics_bones[bone].bullet_object->GetTransform();
+ total_num += 1.0f;
+ bone = skeleton_.parents[bone];
+ if(bone == -1){
+ break;
+ }
+ }
+ return total / total_num;
+}
+
+vec3 RiggedObject::GetAvgIKChainPos( const std::string &target_name ) {
+ vec3 total;
+ float total_num = 0.0f;
+ SimpleIKBone &sib = skeleton_.simple_ik_bones[target_name];
+ int bone = sib.bone_id;
+ for(int i=0; i<sib.chain_length; ++i){
+ total += skeleton_.physics_bones[bone].bullet_object->GetPosition();
+ total_num += 1.0f;
+ bone = skeleton_.parents[bone];
+ if(bone == -1){
+ break;
+ }
+ }
+ return total / total_num;
+}
+
+
+mat4 RiggedObject::GetIKChainTransform( const std::string &target_name, int which ) {
+ SimpleIKBone &sib = skeleton_.simple_ik_bones[target_name];
+ int bone = sib.bone_id;
+ for(int i=0; i<which; ++i){
+ bone = skeleton_.parents[bone];
+ if(bone == -1){
+ break;
+ }
+ }
+ return skeleton_.physics_bones[bone].bullet_object->GetTransform();
+}
+
+vec3 RiggedObject::GetIKChainPos( const std::string &target_name, int which )
+{
+ SimpleIKBone &sib = skeleton_.simple_ik_bones[target_name];
+ int bone = sib.bone_id;
+ for(int i=0; i<which; ++i){
+ bone = skeleton_.parents[bone];
+ if(bone == -1){
+ break;
+ }
+ }
+ return skeleton_.physics_bones[bone].bullet_object->GetPosition();
+}
+
+void RiggedObject::EnableSleep()
+{
+ for(unsigned i=0; i<skeleton_.physics_bones.size(); ++i){
+ if(skeleton_.physics_bones[i].bullet_object){
+ skeleton_.physics_bones[i].bullet_object->CanSleep();
+ }
+ }
+}
+
+void RiggedObject::DisableSleep()
+{
+ for(unsigned i=0; i<skeleton_.physics_bones.size(); ++i){
+ if(skeleton_.physics_bones[i].bullet_object){
+ skeleton_.physics_bones[i].bullet_object->NoSleep();
+ }
+ }
+}
+
+void RiggedObject::CheckForNAN()
+{
+ skeleton_.CheckForNAN();
+}
+
+void RiggedObject::HandleLipSyncMorphTargets(float timestep) {
+ if(lipsync_reader.valid()){
+ lipsync_reader.UpdateWeights();
+ const std::map<std::string, float> &mw = lipsync_reader.GetMorphWeights();
+ std::map<std::string, float>::const_iterator iter;
+ for(iter = mw.begin(); iter != mw.end(); ++iter){
+ for(unsigned i=0; i<morph_targets.size(); ++i){
+ if(morph_targets[i].name == iter->first){
+ morph_targets[i].script_weight =
+ mix(morph_targets[i].script_weight, iter->second, 0.5f);
+ morph_targets[i].script_weight_weight = 1.0f;
+ break;
+ }
+ }
+ }
+ lipsync_reader.Update(timestep);
+ if(!lipsync_reader.valid()){
+ for(iter = mw.begin(); iter != mw.end(); ++iter){
+ for(unsigned i=0; i<morph_targets.size(); ++i){
+ if(morph_targets[i].name == iter->first){
+ morph_targets[i].script_weight_weight = 0.0f;
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+void RiggedObject::CreateBloodDrip( const std::string &ik_name, int ik_link, const vec3 &direction )
+{
+ Skeleton::SimpleIKBoneMap::iterator iter =
+ skeleton_.simple_ik_bones.find(ik_name);
+ if(iter == skeleton_.simple_ik_bones.end()){
+ return;
+ }
+ SimpleIKBone &sib = iter->second;
+ int look_link = ik_link;
+ int bone_id = sib.bone_id;
+ while(look_link > 0){
+ bone_id = skeleton_.parents[bone_id];
+ --look_link;
+ }
+ vec3 bone_pos = skeleton_.physics_bones[bone_id].initial_position;
+
+ blood_surface.CreateBloodDripNearestPointDirection(bone_pos, direction, 1.0f, true);
+ blood_surface.sleep_time = game_timer.game_time + 5.0f;
+}
+
+void RiggedObject::CreateBloodDripAtBone( int bone_id, int ik_link, const vec3 &direction )
+{
+ BulletObject* bo = skeleton_.physics_bones[bone_id].bullet_object;
+ if(skeleton_.fixed_obj[bone_id]){
+ bo = skeleton_.physics_bones[skeleton_.parents[bone_id]].bullet_object;
+ }
+ if(skeleton_.fixed_obj[skeleton_.parents[bone_id]]){
+ bo = skeleton_.physics_bones[skeleton_.parents[skeleton_.parents[bone_id]]].bullet_object;
+ }
+ vec3 curr_pos = bo->GetPosition();
+ blood_surface.CreateBloodDripNearestPointDirection(curr_pos, direction, 1.0f, true);
+ blood_surface.sleep_time = game_timer.game_time + 5.0f;
+}
+
+void RiggedObject::CleanBlood() {
+ blood_surface.CleanBlood();
+}
+
+void RiggedObject::SetCharAnim( const std::string &path, char flags, const std::string &override ) {
+ if(char_anim == path && override == char_anim_override){
+ return;
+ }
+ char_anim_override = override;
+ char_anim = path;
+ char_anim_flags = flags;
+ CheckItemAnimBlends(char_anim, flags);
+}
+
+const float _item_blend_fade_speed = 5.0f;
+void RiggedObject::CheckItemAnimBlends(const std::string &path, char flags) {
+ anim_client.ClearBlendAnimation();
+ for(std::list<AttachedItem>::iterator iter = attached_items.items.begin();
+ iter != attached_items.items.end(); ++iter)
+ {
+ ItemObjectScriptReader& item = iter->item;
+ if(item->item_ref()->HasAnimBlend(path) && item->state() == ItemObject::kWielded){
+ const std::string &a_path = item->item_ref()->GetAnimBlend(path);
+ anim_client.SetBlendAnimation(a_path);
+ }
+ }
+}
+
+void RiggedObject::MPCutPlane(const vec3 & normal, const vec3 & pos, const vec3 & dir, int type, int depth, std::vector<int> mp_hit_list, vec3 mp_points[3])
+{
+ if (Graphics::Instance()->config_.blood() == BloodLevel::kNone) {
+ return;
+ }
+
+
+
+ Model* model = &Models::Instance()->GetModel(model_id[0]);
+ vec3 points[3];
+
+ for (int i = 0; i < 3; i++)
+ points[i] = mp_points[i];
+
+ int num_pos_side = 0;
+ int num_neg_side = 0;
+ int num_hit = 0;
+ std::vector<int> hit_list;
+
+ hit_list = mp_hit_list;
+ num_hit = mp_hit_list.size();
+
+
+
+ if (num_hit < 10 && depth < 4) {
+ if (num_neg_side > num_pos_side) {
+ MPCutPlane(normal, pos - normal * 0.1f, dir, type, depth + 1, mp_hit_list, mp_points);
+ }
+ else {
+ MPCutPlane(normal, pos + normal * 0.1f, dir, type, depth + 1, mp_hit_list, mp_points);
+ }
+ }
+ else {
+ //blood_surface.AddBloodToTriangles(hit_list);
+ for (unsigned i = 0; i < hit_list.size(); ++i) {
+ GetTransformedTri(hit_list[i], mp_points);
+ if (dot(cross(mp_points[1] - mp_points[0], mp_points[2] - mp_points[0]), dir) > 0.0f) {
+ continue;
+ }
+ float time[3];
+ for (unsigned j = 0; j < 3; ++j) {
+ const vec3 d = mp_points[(j + 1) % 3] - mp_points[j];
+ const vec3 o = mp_points[j] - pos;
+ const vec3 &n = normal;
+ time[j] = (-o[0] * n[0] - o[1] * n[1] - o[2] * n[2]) /
+ (d[0] * n[0] + d[1] * n[1] + d[2] * n[2]);
+ }
+ vec2 tex_points[3];
+ for (unsigned j = 0; j < 3; ++j) {
+ tex_points[j][0] = model->tex_coords[model->faces[hit_list[i] * 3 + j] * 2 + 0];
+ tex_points[j][1] = model->tex_coords[model->faces[hit_list[i] * 3 + j] * 2 + 1];
+
+
+ }
+ vec2 tex_intersect_points[2];
+ vec3 intersect_points[2];
+ int which_point = 0;
+ for (unsigned j = 0; j < 3; ++j) {
+ if (time[j] >= 0.0f && time[j] <= 1.0f) {
+ tex_intersect_points[which_point] =
+ mix(tex_points[j], tex_points[(j + 1) % 3], time[j]);
+ intersect_points[which_point] =
+ mix(points[j], mp_points[(j + 1) % 3], time[j]);
+ ++which_point;
+ if (which_point > 1) {
+ break;
+ }
+ }
+ }
+ //DebugDraw::Instance()->AddLine(intersect_points[0], intersect_points[1], vec4(1.0f), _persistent);
+ blood_surface.AddBloodLine(tex_intersect_points[0], tex_intersect_points[1]);
+
+
+ if (type == _light || RangedRandomFloat(0.0f, 1.0f) < 0.6f) {
+ vec2 bleed_point = mix(tex_intersect_points[0], tex_intersect_points[1], 0.5f);
+ vec3 coords = bary_coords(bleed_point, tex_points[0], tex_points[1], tex_points[2]);
+ blood_surface.CreateDripInTri(hit_list[i], coords, RangedRandomFloat(0.1f, 1.0f), RangedRandomFloat(0.0f, 1.0f), true, SurfaceWalker::BLOOD);
+ }
+
+ if (type == _heavy || RangedRandomFloat(0.0f, 1.0f) < 0.2f) {
+ vec2 bleed_point = mix(tex_intersect_points[0], tex_intersect_points[1], 0.5f);
+ vec3 coords = bary_coords(bleed_point, tex_points[0], tex_points[1], tex_points[2]);
+ blood_surface.CreateDripInTri(hit_list[i], coords, RangedRandomFloat(0.1f, 0.5f), RangedRandomFloat(0.0f, 10.0f), true, SurfaceWalker::BLOOD);
+ }
+
+ //dot(offset+dir*time, normal) = 0.0f
+ // ox*nx + dx*t*nx + oy*ny + dy*t*ny + oz*nz + dz*t*nz = 0
+ // dx*t*nx + dy*t*ny + dz*t*nz = -ox*nx-oy*ny-oz*nz
+ // t = (-ox*nx-oy*ny-oz*nz) / (dx*nx + dy*ny + dz*nz)
+
+ bool add_blood_cloud = (rand() % 10 == 0);
+ bool add_blood_splat = (type == _light && rand() % 40 == 0);
+ if (add_blood_cloud || add_blood_splat) {
+ vec3 pos = (points[0] + points[1] + points[2]) / 3.0f;
+ if (add_blood_cloud) {
+ scenegraph_->particle_system->MakeParticle(
+ scenegraph_, "Data/Particles/bloodcloud.xml", pos,
+ vec3(RangedRandomFloat(-1.0f, 1.0f), RangedRandomFloat(-1.0f, 1.0f), RangedRandomFloat(-1.0f, 1.0f)),
+ Graphics::Instance()->config_.blood_color());
+ }
+ if (add_blood_splat) {
+ scenegraph_->particle_system->MakeParticle(
+ scenegraph_, "Data/Particles/bloodsplat.xml", pos,
+ vec3(RangedRandomFloat(-2.0f, 2.0f), RangedRandomFloat(-2.0f, 2.0f), RangedRandomFloat(-2.0f, 2.0f)),
+ Graphics::Instance()->config_.blood_color());
+ }
+ }
+ }
+ }
+ blood_surface.sleep_time = game_timer.game_time + 5.0f;
+}
+
+void UpdateAttachedItem(AttachedItem& attached_item, const Skeleton &skeleton, float char_scale, const std::map<int, mat4> weapon_offset, const WeapAnimInfoMap &weap_anim_info_map, const std::map<int, mat4> &weapon_offset_retarget) {
+ ItemObjectScriptReader& item = attached_item.item;
+ int bone = attached_item.bone_id;
+ mat4 bone_mat = skeleton.physics_bones[bone].bullet_object->GetTransform();
+ mat4 weap_mat = weapon_offset.find(item->GetID())->second;
+ weap_mat.SetTranslationPart(weap_mat.GetTranslationPart() * char_scale);
+ mat4 the_weap_mat = bone_mat * weap_mat;
+ if(item->state() == ItemObject::kWielded){
+ WeapAnimInfoMap::const_iterator iter = weap_anim_info_map.find(item->GetID());
+ if(iter != weap_anim_info_map.end()){
+ const WeapAnimInfo &weap_anim_info = iter->second;
+ mat4 anim_mat = weap_anim_info.matrix;
+ if(weap_anim_info.relative_id != -1){
+ anim_mat = skeleton.physics_bones[weap_anim_info.relative_id].bullet_object->GetTransform() * anim_mat;
+ }
+ anim_mat = anim_mat * weapon_offset_retarget.find(item->GetID())->second;
+ the_weap_mat = mix(the_weap_mat, anim_mat, weap_anim_info.weight);
+ }
+ }
+ mat4 temp_weap_mat = the_weap_mat;
+ temp_weap_mat.SetColumn(2, the_weap_mat.GetColumn(1)*-1.0f);
+ temp_weap_mat.SetColumn(1, the_weap_mat.GetColumn(2));
+ if(!item.just_created){
+ item.SetPhysicsTransform(temp_weap_mat);
+ }
+ if(item.just_created){
+ item.SetPhysicsTransform(temp_weap_mat);
+ item.SetPhysicsTransform(temp_weap_mat);
+ item.just_created = false;
+ }
+}
+
+void UpdateStuckItem(AttachedItem& stuck_item, const Skeleton &skeleton, bool animated) {
+ int bone = stuck_item.bone_id;
+ mat4 bone_mat = skeleton.physics_bones[bone].bullet_object->GetTransform();
+ mat4 the_weap_mat = bone_mat * stuck_item.rel_mat;
+ mat4 temp_weap_mat = the_weap_mat;
+ ItemObjectScriptReader &item = stuck_item.item;
+ item.SetPhysicsTransform(temp_weap_mat);
+ if(item.just_created){
+ item.SetPhysicsTransform(temp_weap_mat);
+ item.just_created = false;
+ }
+ if(!animated){
+ item.SetInterpInfo(1,1);
+ }
+}
+
+void RiggedObject::UpdateAttachedItems() {
+ for(AttachedItemList::iterator iter = attached_items.items.begin(); iter != attached_items.items.end(); ++iter){
+ UpdateAttachedItem(*iter, skeleton_, model_char_scale, weapon_offset, weap_anim_info_map, weapon_offset_retarget);
+ }
+ for(AttachedItemList::iterator iter = stuck_items.items.begin(); iter != stuck_items.items.end(); ++iter) {
+ UpdateStuckItem(*iter, skeleton_, animated);
+ }
+}
+
+void RiggedObject::CutPlane( const vec3 &normal, const vec3 &pos, const vec3 &dir, int type, int depth){
+ Online* online = Online::Instance();
+
+ if(Graphics::Instance()->config_.blood() == BloodLevel::kNone){
+ return;
+ }
+
+ Model* model = &Models::Instance()->GetModel(model_id[0]);
+ vec3 points[3];
+ bool pos_side;
+ bool neg_side;
+ int num_pos_side = 0;
+ int num_neg_side = 0;
+ int num_hit = 0;
+ std::vector<int> hit_list;
+ if (!online->IsClient()) {
+ for (int i = 0, len = model->faces.size() / 3; i < len; ++i) {
+ GetTransformedTri(i, points);
+ pos_side = false;
+ neg_side = false;
+ for (unsigned j = 0; j < 3; ++j) {
+ if (dot(points[j] - pos, normal) < 0.0f) {
+ neg_side = true;
+ }
+ else {
+ pos_side = true;
+ }
+ }
+ if (neg_side && pos_side) {
+ hit_list.push_back(i);
+ ++num_hit;
+ }
+ else if (neg_side) {
+ ++num_neg_side;
+ }
+ else {
+ ++num_pos_side;
+ }
+ }
+ }
+
+ if(num_hit < 10 && depth < 4){
+ if(num_neg_side > num_pos_side){
+ CutPlane(normal, pos-normal*0.1f, dir, type, depth+1);
+ } else {
+ CutPlane(normal, pos+normal*0.1f, dir, type, depth+1);
+ }
+ } else {
+ //blood_surface.AddBloodToTriangles(hit_list);
+ for(unsigned i=0; i<hit_list.size(); ++i){
+ GetTransformedTri(hit_list[i], points);
+ if(dot(cross(points[1] - points[0], points[2] - points[0]), dir) > 0.0f){
+ continue;
+ }
+ float time[3];
+ for(unsigned j=0; j<3; ++j){
+ const vec3 d = points[(j+1)%3]-points[j];
+ const vec3 o = points[j]-pos;
+ const vec3 &n = normal;
+ time[j] = (-o[0]*n[0] - o[1]*n[1] - o[2]*n[2])/
+ (d[0]*n[0] + d[1]*n[1] + d[2]*n[2]);
+ }
+ vec2 tex_points[3];
+ for(unsigned j=0; j<3; ++j){
+ tex_points[j][0] = model->tex_coords[model->faces[hit_list[i]*3+j]*2+0];
+ tex_points[j][1] = model->tex_coords[model->faces[hit_list[i]*3+j]*2+1];
+ }
+ vec2 tex_intersect_points[2];
+ vec3 intersect_points[2];
+ int which_point = 0;
+ for(unsigned j=0; j<3; ++j){
+ if(time[j] >= 0.0f && time[j] <= 1.0f){
+ tex_intersect_points[which_point] =
+ mix(tex_points[j], tex_points[(j+1)%3], time[j]);
+ intersect_points[which_point] =
+ mix(points[j], points[(j+1)%3], time[j]);
+ ++which_point;
+ if(which_point>1){
+ break;
+ }
+ }
+ }
+ //DebugDraw::Instance()->AddLine(intersect_points[0], intersect_points[1], vec4(1.0f), _persistent);
+ blood_surface.AddBloodLine(tex_intersect_points[0], tex_intersect_points[1]);
+
+ if(type == _light || RangedRandomFloat(0.0f,1.0f)<0.6f){
+ vec2 bleed_point = mix(tex_intersect_points[0], tex_intersect_points[1], 0.5f);
+ vec3 coords = bary_coords(bleed_point, tex_points[0], tex_points[1], tex_points[2]);
+
+ //LOGI << "BLEED POINT " << bleed_point << " coords " << coords << std::endl;
+ blood_surface.CreateDripInTri(hit_list[i], coords, RangedRandomFloat(0.1f,1.0f), RangedRandomFloat(0.0f,1.0f), true, SurfaceWalker::BLOOD);
+ }
+
+ if(type == _heavy || RangedRandomFloat(0.0f,1.0f)<0.2f){
+ vec2 bleed_point = mix(tex_intersect_points[0], tex_intersect_points[1], 0.5f);
+ vec3 coords = bary_coords(bleed_point, tex_points[0], tex_points[1], tex_points[2]);
+
+ //LOGI << "BLEED POINT " << bleed_point << " coords " << coords << std::endl;
+ blood_surface.CreateDripInTri(hit_list[i], coords, RangedRandomFloat(0.1f,0.5f), RangedRandomFloat(0.0f,10.0f), true, SurfaceWalker::BLOOD);
+ }
+
+ //dot(offset+dir*time, normal) = 0.0f
+ // ox*nx + dx*t*nx + oy*ny + dy*t*ny + oz*nz + dz*t*nz = 0
+ // dx*t*nx + dy*t*ny + dz*t*nz = -ox*nx-oy*ny-oz*nz
+ // t = (-ox*nx-oy*ny-oz*nz) / (dx*nx + dy*ny + dz*nz)
+
+ bool add_blood_cloud = (rand()%10 == 0);
+ bool add_blood_splat = (type == _light && rand()%40 == 0);
+ if(add_blood_cloud || add_blood_splat){
+ vec3 pos = (points[0] + points[1] + points[2]) / 3.0f;
+ if(add_blood_cloud){
+ scenegraph_->particle_system->MakeParticle(
+ scenegraph_, "Data/Particles/bloodcloud.xml", pos,
+ vec3(RangedRandomFloat(-1.0f,1.0f),RangedRandomFloat(-1.0f,1.0f),RangedRandomFloat(-1.0f,1.0f)),
+ Graphics::Instance()->config_.blood_color());
+ }
+ if(add_blood_splat){
+ scenegraph_->particle_system->MakeParticle(
+ scenegraph_, "Data/Particles/bloodsplat.xml",pos,
+ vec3(RangedRandomFloat(-2.0f,2.0f),RangedRandomFloat(-2.0f,2.0f),RangedRandomFloat(-2.0f,2.0f)),
+ Graphics::Instance()->config_.blood_color());
+ }
+ }
+ }
+ }
+
+ if (online->IsHosting()) {
+ online->Send<OnlineMessages::CutLine>(
+ char_id,
+ points,
+ pos,
+ normal,
+ dir,
+ type,
+ depth,
+ num_hit,
+ hit_list);
+ }
+ blood_surface.sleep_time = game_timer.game_time + 5.0f;
+}
+
+void RiggedObject::Stab( const vec3 &pos, const vec3 &dir, int type, int depth ) {
+ const float _stab_threshold = pow(0.001f,1.0f/(float)(depth*0.3f+1));
+ Model* model = &Models::Instance()->GetModel(model_id[0]);
+ vec3 points[3];
+ bool close_enough;
+ std::vector<int> hit_list;
+ for(int i=0, len=model->faces.size()/3; i<len; ++i){
+ close_enough = false;
+ GetTransformedTri(i, points);
+ for(unsigned j=0; j<3; ++j){
+ points[j] -= dir * dot(dir, points[j] - pos);
+ if(distance_squared(points[j], pos) < _stab_threshold){
+ close_enough = true;
+ }
+ }
+ if(close_enough){
+ hit_list.push_back(i);
+ }
+ }
+ if(hit_list.size() < 5 && depth < 12){
+ Stab(pos, dir, type, depth+1);
+ return;
+ }
+ if(Graphics::Instance()->config_.blood() == BloodLevel::kNone){
+ return;
+ }
+ blood_surface.AddDecalToTriangles(hit_list,
+ pos, dir, (depth + 1) * 0.1f,
+ stab_texture);
+ vec3 start = pos - dir * 1000.0f;
+ vec3 end = pos + dir * 1000.0f;
+ vec3 intersect;
+ for(unsigned i=0; i<hit_list.size(); ++i){
+ GetTransformedTri(hit_list[i], points);
+ if(LineFacet(start, end, points[0], points[1], points[2], &intersect)){
+ for(unsigned j=0; j<10; ++j){
+ blood_surface.CreateDripInTri(hit_list[i], vec3(0.333f,0.333f,0.333f), RangedRandomFloat(0.1f,1.0f), RangedRandomFloat(0.0f,10.0f), true, SurfaceWalker::BLOOD);
+ }
+ }
+ }
+
+ for(unsigned i=0; i<hit_list.size(); ++i){
+ GetTransformedTri(hit_list[i], points);
+ vec3 pos = (points[0] + points[1] + points[2]) / 3.0f;
+ if(rand()%(hit_list.size()) == 0){
+ scenegraph_->particle_system->MakeParticle(
+ scenegraph_, "Data/Particles/bloodcloud.xml",pos,
+ vec3(RangedRandomFloat(-1.0f,1.0f),RangedRandomFloat(-1.0f,1.0f),RangedRandomFloat(-1.0f,1.0f))+dir,
+ Graphics::Instance()->config_.blood_color());
+ scenegraph_->particle_system->MakeParticle(
+ scenegraph_, "Data/Particles/bloodcloud.xml",pos,
+ vec3(RangedRandomFloat(-1.0f,1.0f),RangedRandomFloat(-1.0f,1.0f),RangedRandomFloat(-1.0f,1.0f))-dir,
+ Graphics::Instance()->config_.blood_color());
+ }
+ if(type == _light && rand()%40 == 0){
+ scenegraph_->particle_system->MakeParticle(
+ scenegraph_, "Data/Particles/bloodsplat.xml",pos,
+ vec3(RangedRandomFloat(-2.0f,2.0f),RangedRandomFloat(-2.0f,2.0f),RangedRandomFloat(-2.0f,2.0f)),
+ Graphics::Instance()->config_.blood_color());
+ }
+ }
+ blood_surface.sleep_time = game_timer.game_time + 5.0f;
+}
+
+std::vector<int> water_logged;
+
+void RiggedObject::AddWaterCube( const mat4 &transform ) {
+ if(blood_surface.blood_tex.valid()){
+ PROFILER_GPU_ZONE(g_profiler_ctx, "RiggedObject::AddWaterCube");
+
+ std::swap(blood_surface.blood_tex, blood_surface.blood_work_tex);
+ blood_surface.StartDrawingToBloodTexture();
+ Graphics::Instance()->setBlend(false);
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ Graphics::Instance()->Clear(true);
+ Shaders* shaders = Shaders::Instance();
+ int the_shader = shaders->returnProgram(water_cube);
+
+ if(the_shader!=-1) {
+ shaders->setProgram(the_shader);
+
+ std::vector<mat4> fire_decal_matrix;
+ std::vector<mat4> water_decal_matrix;
+ for(int i=0, len=scenegraph_->decal_objects_.size(); i<len; ++i){
+ DecalObject* obj = (DecalObject*)scenegraph_->decal_objects_[i];
+ if(obj->decal_file_ref->special_type == 3){
+ fire_decal_matrix.push_back(obj->GetTransform());
+ }
+ if(obj->decal_file_ref->special_type == 5){
+ water_decal_matrix.push_back(obj->GetTransform());
+ }
+ }
+ shaders->SetUniformInt("fire_decal_num", fire_decal_matrix.size());
+ if(!fire_decal_matrix.empty()){
+ shaders->SetUniformMat4Array("fire_decal_matrix", fire_decal_matrix);
+ }
+ shaders->SetUniformInt("water_decal_num", water_decal_matrix.size());
+ if(!water_decal_matrix.empty()){
+ shaders->SetUniformMat4Array("water_decal_matrix", water_decal_matrix);
+ }
+
+ shaders->SetUniformMat4("transform", transform);
+ shaders->SetUniformFloat("time", game_timer.GetRenderTime());
+ Textures::Instance()->bindTexture(blood_surface.blood_work_tex);
+
+ Model* model;
+ model =&Models::Instance()->GetModel(model_id[lod_level]);
+ DrawModel(model, lod_level);
+ }
+ blood_surface.EndDrawingToBloodTexture();
+
+ // Expand blood texels by one pixel to fill in seams
+ std::swap(blood_surface.blood_tex, blood_surface.blood_work_tex);
+ blood_surface.StartDrawingToBloodTexture();
+ Graphics::Instance()->setBlend(false);
+ Graphics::Instance()->SetBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
+ int shader_id = Shaders::Instance()->returnProgram(water_cube_expand);
+ Shaders::Instance()->setProgram(shader_id);
+ Textures::Instance()->bindTexture(blood_surface.blood_work_tex);
+ Shaders::Instance()->SetUniformInt("tex_width", Textures::Instance()->getWidth(blood_surface.blood_work_tex));
+ Shaders::Instance()->SetUniformInt("tex_height", Textures::Instance()->getHeight(blood_surface.blood_work_tex));
+
+ static const GLfloat data[] = {0,0, 1,0, 1,1, 0,1};
+ static const GLuint index[] = {0,1,2, 0,2,3};
+ static VBOContainer square_vbo;
+ static VBOContainer index_vbo;
+ if(!square_vbo.valid()){
+ square_vbo.Fill(kVBOFloat | kVBOStatic, sizeof(data), (void*)data);
+ index_vbo.Fill(kVBOElement | kVBOStatic, sizeof(index), (void*)index);
+ }
+ square_vbo.Bind();
+ index_vbo.Bind();
+
+ int vert_attrib_id = Shaders::Instance()->returnShaderAttrib("vert_coord", shader_id);
+ Graphics::Instance()->EnableVertexAttribArray(vert_attrib_id);
+ glVertexAttribPointer(vert_attrib_id, 2, GL_FLOAT, false, 2*sizeof(GLfloat), 0);
+ Graphics::Instance()->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
+ Graphics::Instance()->ResetVertexAttribArrays();
+
+ blood_surface.EndDrawingToBloodTexture();
+ Textures::Instance()->GenerateMipmap(blood_surface.blood_tex);
+ }
+}
+
+void RiggedObject::UpdateCollisionObjects() {
+ for(unsigned i=0; i<skeleton_.physics_bones.size(); ++i){
+ BulletObject* cbo = skeleton_.physics_bones[i].col_bullet_object;
+ if(!cbo){
+ continue;
+ }
+ cbo->SetTransform(skeleton_.physics_bones[i].bullet_object->GetTransform());
+ cbo->UpdateTransform();
+ }
+ skeleton_.UpdateCollisionWorldAABB();
+}
+
+void RiggedObject::AddBloodAtPoint( const vec3 & point ) {
+ blood_surface.CreateBloodDripNearestPointTransformed(point, 0.1f, false);
+ blood_surface.sleep_time = game_timer.game_time + 5.0f;
+}
+
+enum LogReadStage { _deleted_faces, _first_vert, _second_vert, _offset_x, _offset_y, _offset_z, _modified_faces };
+
+static void ASSetBoneInflate(Skeleton* skeleton, int which, float val){
+ skeleton->physics_bones[which].display_scale = val;
+}
+
+static int ASGetParent(Skeleton* skeleton, int which){
+ return skeleton->parents[which];
+}
+
+static bool ASIKBoneExists(Skeleton* skeleton, const std::string& name){
+ return skeleton->simple_ik_bones.find(name) != skeleton->simple_ik_bones.end();
+}
+
+int ASIKBoneStart(Skeleton* skeleton, const std::string& name){
+ return skeleton->simple_ik_bones[name].bone_id;
+}
+
+int ASIKBoneLength(Skeleton* skeleton, const std::string& name){
+ return skeleton->simple_ik_bones[name].chain_length;
+}
+
+static int ASNumBones(Skeleton* skeleton){
+ return skeleton->physics_bones.size();
+}
+
+static vec3 ASGetBoneLinearVelocity(Skeleton* skeleton, int which){
+ if(!skeleton->physics_bones[which].bullet_object){
+ return vec3(0.0f);
+ } else {
+ return skeleton->physics_bones[which].bullet_object->GetLinearVelocity();
+ }
+}
+
+static mat4 ASGetBoneTransform(Skeleton* skeleton, int which){
+ mat4 return_mat = skeleton->physics_bones[which].bullet_object->GetRotation();
+ return_mat.SetTranslationPart(skeleton->physics_bones[which].bullet_object->GetPosition());
+ return return_mat;
+}
+
+static void ASSetBoneTransform(Skeleton* skeleton, int which, const mat4 &transform){
+ skeleton->physics_bones[which].bullet_object->SetTransform(transform);
+}
+
+static BoneTransform ASGetFrameMatrix(RiggedObject* rigged_object, int which){
+ return rigged_object->animation_frame_bone_matrices[which];
+}
+
+static void ASSetFrameMatrix(RiggedObject* rigged_object, int which, const BoneTransform &transform){
+ rigged_object->animation_frame_bone_matrices[which] = transform;
+}
+
+static vec3 ASGetPointPos(Skeleton* skeleton, int which){
+ return skeleton->points[which];
+}
+
+static int ASGetBonePoint(Skeleton* skeleton, int which_bone, int which_point){
+ return skeleton->bones[which_bone].points[which_point];
+}
+
+static void ASAddVelocity(Skeleton* skeleton, const vec3& vel){
+ for(int bone_index=0, num_bones=skeleton->physics_bones.size(); bone_index<num_bones; ++bone_index){
+ BulletObject* bullet_object = skeleton->physics_bones[bone_index].bullet_object;
+ if(bullet_object){
+ bullet_object->AddLinearVelocity(vel);
+ }
+ }
+}
+
+void RotateBoneToMatchVec(RiggedObject* rigged_object, const vec3 &a, const vec3 &b, int bone){
+ BoneTransform &mat = rigged_object->animation_frame_bone_matrices[bone];
+ mat.origin = (a+b)*0.5f;
+ vec3 dir = mat.rotation * vec3(0,0,1);
+ quaternion rot;
+ GetRotationBetweenVectors(dir, b - a, rot);
+ mat.rotation = rot * mat.rotation;
+}
+
+void RotateBonesToMatchVec(RiggedObject* rigged_object, const vec3& a, const vec3& c, int bone, int bone2, float weight) {
+ vec3 b = mix(a,c,1.0f-weight);
+
+ BoneTransform &mat = rigged_object->animation_frame_bone_matrices[bone];
+ mat.origin = (a+b)*0.5f;
+ vec3 dir = mat.rotation * vec3(0,0,1);
+ quaternion rot;
+ GetRotationBetweenVectors(dir, b - a, rot);
+ mat.rotation = rot * mat.rotation;
+
+ BoneTransform &mat2 = rigged_object->animation_frame_bone_matrices[bone2];
+ mat2.origin = (b+c)*0.5f;
+ mat2.rotation = rot * mat2.rotation;
+}
+
+static void TransformAllFrameMats(RiggedObject* rigged_object, const BoneTransform &transform) {
+ std::vector<BoneTransform> &mats = rigged_object->animation_frame_bone_matrices;
+ for(int i=0, len=mats.size(); i<len; ++i){
+ mats[i] = transform * mats[i];
+ }
+}
+
+static vec3 GetFrameCenterOfMass(RiggedObject* rigged_object) {
+ Skeleton& skeleton = rigged_object->skeleton_;
+ int num_bones = skeleton.physics_bones.size();
+ vec3 com;
+ float total_mass = 0.0f;
+ for(int i=0; i<num_bones; ++i){
+ BoneTransform &transform = rigged_object->animation_frame_bone_matrices[i];
+ float bone_mass = skeleton.bones[i].mass;
+ com += transform.origin * bone_mass;
+ total_mass += bone_mass;
+ }
+ if(total_mass != 0.0f){
+ com /= total_mass;
+ }
+ return com;
+}
+
+vec3 GetTransformedBonePoint(RiggedObject* rigged_object, int bone, int point){
+ Skeleton& skeleton = rigged_object->skeleton_;
+ BoneTransform transform = rigged_object->animation_frame_bone_matrices[bone] *
+ rigged_object->cached_skeleton_info_.bind_matrices[bone];
+ const vec3 &bone_point = skeleton.points[skeleton.bones[bone].points[point]];
+ return transform * bone_point;
+}
+
+static float ASGetBoneMass(Skeleton* skeleton, int which_bone){
+ return skeleton->bones[which_bone].mass;
+}
+
+vec3 ASGetModelCenter(RiggedObject* rigged_object){
+ const Model& model = Models::Instance()->GetModel(rigged_object->model_id[0]);
+ return model.old_center;
+}
+
+static mat4 GetDisplayBoneMatrix(RiggedObject* rigged_object, int bone){
+ return rigged_object->display_bone_matrices[bone];
+}
+
+static void SetDisplayBoneMatrix(RiggedObject* rigged_object, int bone, const mat4 &matrix){
+ rigged_object->display_bone_matrices[bone] = matrix;
+}
+
+static bool ASHasPhysics(Skeleton* skeleton, int which){
+ return (skeleton->physics_bones[which].bullet_object != NULL);
+}
+
+static CScriptArray *ASGetConvexHullPoints(Skeleton* skeleton, int bone){
+ std::vector<float> points;
+ BulletObject* bullet_obj = skeleton->physics_bones[bone].col_bullet_object;
+ if(bullet_obj){
+ btCollisionShape* shape = bullet_obj->shape.get();
+ if(shape->getShapeType() == CONVEX_HULL_SHAPE_PROXYTYPE){
+ btConvexHullShape* convex_hull_shape = (btConvexHullShape*)shape;
+ int num_verts = convex_hull_shape->getNumVertices();
+ points.reserve(num_verts*3);
+ for(int i=0; i<num_verts; ++i){
+ btVector3 vert;
+ convex_hull_shape->getVertex(i, vert);
+ points.push_back(vert[0]);
+ points.push_back(vert[1]);
+ points.push_back(vert[2]);
+ }
+ }
+ }
+
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<float>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+ array->Reserve(points.size());
+
+ for(int i=0, len=points.size(); i<len; ++i) {
+ array->InsertLast(&points[i]);
+ }
+ return array;
+}
+
+static BoneTransform ASApplyParentRotations(Skeleton* skeleton, const CScriptArray *matrices, int id) {
+ int parent_id = skeleton->parents[id];
+ if(parent_id == -1){
+ return *(BoneTransform*)(matrices->At(id));
+ } else {
+ return ASApplyParentRotations(skeleton, matrices, parent_id) * *(BoneTransform*)(matrices->At(id));
+ }
+}
+
+static void ASRemoveAllLayers(AnimationClient* animation_client){
+ animation_client->RemoveAllLayers();
+}
+
+static void ASSetFire(RiggedObject* rigged_object, float fire){
+ rigged_object->blood_surface.SetFire(fire);
+}
+
+static void ASIgnite(RiggedObject* rigged_object){
+ rigged_object->blood_surface.Ignite();
+ rigged_object->blood_surface.sleep_time = game_timer.game_time + 5.0f;
+}
+
+static void ASExtinguish(RiggedObject* rigged_object){
+ rigged_object->blood_surface.Extinguish();
+}
+
+static void ASSetWet(RiggedObject* rigged_object, float wet){
+ rigged_object->blood_surface.SetWet(wet);
+}
+
+void DefineRiggedObjectTypePublic(ASContext* ctx) {
+ if(ctx->TypeExists("AnimationClient")){
+ return;
+ }
+ ctx->RegisterObjectType("AnimationClient", 0, asOBJ_REF | asOBJ_NOCOUNT);
+ ctx->RegisterObjectMethod("AnimationClient","float GetAnimationEventTime(const string &in event)",asMETHOD(AnimationClient, GetAnimationEventTime), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("AnimationClient","float GetTimeUntilEvent(const string &in event)",asMETHOD(AnimationClient, GetTimeUntilEvent), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("AnimationClient","void SetAnimationCallback(const string &in function)",asMETHOD(AnimationClient, SetCallbackString), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("AnimationClient","void AddAnimationOffset(const vec3 &in)",asMETHOD(AnimationClient, AddAnimationOffset), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("AnimationClient","void AddAnimationRotOffset(float radians)",asMETHOD(AnimationClient, AddAnimationRotOffset), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("AnimationClient","int AddLayer(const string &in anim_path, float transition_speed, int8 flags)",asMETHOD(AnimationClient, AddLayer), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("AnimationClient","int RemoveLayer(int id, float transition_speed)",asMETHOD(AnimationClient, RemoveLayer), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("AnimationClient","void SetBlendCoord(const string &in coord_label, float coord_value)",asMETHOD(AnimationClient, SetBlendCoord), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("AnimationClient","void SetLayerOpacity(int id, float opacity)",asMETHOD(AnimationClient, SetLayerOpacity), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("AnimationClient","void SetSpeedMult(float)",asMETHOD(AnimationClient, SetSpeedMult), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("AnimationClient","void SetAnimatedItemID(int index, int id)",asMETHOD(AnimationClient, SetAnimatedItemID), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("AnimationClient","void SetLayerItemID(int layer, int index, int id)",asMETHOD(AnimationClient, SetLayerItemID), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("AnimationClient","const string& GetCurrAnim()",asMETHOD(AnimationClient, GetCurrAnim), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("AnimationClient","float GetNormalizedAnimTime()",asMETHOD(AnimationClient, GetNormalizedAnimTime), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("AnimationClient","float GetAnimationSpeed()",asMETHOD(AnimationClient, GetAnimationSpeed), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("AnimationClient","void RemoveAllLayers()",asFUNCTION(ASRemoveAllLayers), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("AnimationClient","void Reset()",asMETHOD(AnimationClient, Reset), asCALL_THISCALL);
+ ctx->DocsCloseBrace();
+
+ ctx->RegisterObjectType("Skeleton", 0, asOBJ_REF | asOBJ_NOCOUNT);
+ ctx->RegisterObjectMethod("Skeleton","vec3 GetCenterOfMass()",asMETHOD(Skeleton, GetCenterOfMass), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("Skeleton","void SetBoneInflate(int bone_id, float amount)",asFUNCTION(ASSetBoneInflate), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("Skeleton","int GetParent(int bone_id)",asFUNCTION(ASGetParent), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("Skeleton","bool IKBoneExists(const string& in)",asFUNCTION(ASIKBoneExists), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("Skeleton","float GetBoneMass(int bone_id)",asFUNCTION(ASGetBoneMass), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("Skeleton","int IKBoneStart(const string& in IK_label)",asFUNCTION(ASIKBoneStart), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("Skeleton","int IKBoneLength(const string& in IK_label)",asFUNCTION(ASIKBoneLength), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("Skeleton","int NumBones()",asFUNCTION(ASNumBones), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("Skeleton","vec3 GetBoneLinearVelocity(int which)",asFUNCTION(ASGetBoneLinearVelocity), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("Skeleton","mat4 GetBoneTransform(int bone_id)",asFUNCTION(ASGetBoneTransform), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("Skeleton","void SetBoneTransform(int bone_id, const mat4 &in transform)",asFUNCTION(ASSetBoneTransform), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("Skeleton","vec3 GetPointPos(int bone_id)",asFUNCTION(ASGetPointPos), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("Skeleton","int GetBonePoint(int bone_id, int which_point)",asFUNCTION(ASGetBonePoint), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("Skeleton","void AddVelocity(const vec3 &in vel)",asFUNCTION(ASAddVelocity), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("Skeleton","mat4 GetBindMatrix(int bone_id)",asFUNCTION(ASGetBindMatrix), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("Skeleton","bool HasPhysics(int bone_id)",asFUNCTION(ASHasPhysics), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("Skeleton","array<float> @GetConvexHullPoints(int bone_id)",asFUNCTION(ASGetConvexHullPoints), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("Skeleton","BoneTransform ApplyParentRotations(const array<BoneTransform> &in matrices, int id)",asFUNCTION(ASApplyParentRotations), asCALL_CDECL_OBJFIRST);
+ ctx->DocsCloseBrace();
+
+ ctx->RegisterObjectType("RiggedObject", 0, asOBJ_REF | asOBJ_NOCOUNT);
+ ctx->RegisterObjectProperty("RiggedObject", "bool ik_enabled", asOFFSET(RiggedObject,ik_enabled));
+ ctx->RegisterObjectProperty("RiggedObject", "int anim_update_period", asOFFSET(RiggedObject,anim_update_period));
+ ctx->RegisterObjectProperty("RiggedObject", "int curr_anim_update_time", asOFFSET(RiggedObject,curr_anim_update_time));
+ ctx->RegisterObjectMethod("RiggedObject","vec3 GetBonePosition(int bone)",asMETHOD(RiggedObject, GetBonePosition), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","void AddWaterCube(const mat4 &in transform)",asMETHOD(RiggedObject, AddWaterCube), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","void Ignite()",asFUNCTION(ASIgnite), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("RiggedObject","void Extinguish()",asFUNCTION(ASExtinguish), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("RiggedObject","void SetFire(float fire)",asFUNCTION(ASSetFire), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("RiggedObject","void SetWet(float wet)",asFUNCTION(ASSetWet), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("RiggedObject","BoneTransform GetFrameMatrix(int which)",asFUNCTION(ASGetFrameMatrix), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("RiggedObject","void SetFrameMatrix(int which, const BoneTransform &in transform)",asFUNCTION(ASSetFrameMatrix), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("RiggedObject","float GetCharScale()", asMETHOD(RiggedObject,GetCharScale), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","float GetRelativeCharScale()", asMETHOD(RiggedObject,GetRelativeCharScale), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","void Draw()", asMETHOD(RiggedObject,Draw), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","void CutPlane(const vec3 &in normal, const vec3 &in position, const vec3 &in direction, int type, int depth)",asMETHOD(RiggedObject, CutPlane), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","void Stab(const vec3 &in position, const vec3 &in direction, int type, int depth)",asMETHOD(RiggedObject, Stab), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","void SetRagdollStrength(float)",asMETHOD(RiggedObject, SetRagdollStrength), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","void RefreshRagdoll()",asMETHOD(RiggedObject, RefreshRagdoll), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","void SetRagdollDamping(float)",asMETHOD(RiggedObject, SetDamping), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","void ApplyForceToRagdoll(const vec3 &in force, const vec3 &in position)",asMETHOD(RiggedObject, ApplyForceToRagdoll), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","void ApplyForceToBone(const vec3 &in force, const int &in bone)",asMETHOD(RiggedObject, ApplyForceToBone), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","void ApplyForceLineToRagdoll(const vec3 &in force, const vec3 &in position, const vec3 &in line_direction)",asMETHOD(RiggedObject, ApplyForceLineToRagdoll), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","void MoveRagdollPart(const string &in, const vec3 &in, float)",asMETHOD(RiggedObject, MoveRagdollPart), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","void FixedRagdollPart(int boneID, const vec3 &in)",asMETHOD(RiggedObject, FixedRagdollPart), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","void SpikeRagdollPart(int boneID, const vec3 &start, const vec3 &end, const vec3 &pos)",asMETHOD(RiggedObject, SpikeRagdollPart), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","void ClearBoneConstraints()",asMETHOD(RiggedObject, ClearBoneConstraints), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","vec3 GetAvgPosition()",asMETHOD(RiggedObject, GetAvgPosition), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","vec3 GetAvgVelocity()",asMETHOD(RiggedObject, GetAvgVelocity), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","void CleanBlood()",asMETHOD(RiggedObject, CleanBlood), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","void CreateBloodDrip(const string &in IK_label, int IK_link, const vec3 &in raycast_direction)",asMETHOD(RiggedObject, CreateBloodDrip), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","void CreateBloodDripAtBone(int bone_id, int IK_link, const vec3 &in raycast_direction)",asMETHOD(RiggedObject, CreateBloodDripAtBone), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","vec3 GetAvgAngularVelocity()",asMETHOD(RiggedObject, GetAvgAngularVelocity), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","vec3 GetIKTargetPosition(const string &in IK_label)",asMETHOD(RiggedObject, GetIKTargetPosition), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","float GetIKWeight(const string &in IK_label)",asMETHOD(RiggedObject, GetIKWeight), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","BoneTransform GetIKTransform(const string &in IK_label)",asFUNCTION(GetIKTransform), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("RiggedObject","mat4 GetUnmodifiedIKTransform(const string &in IK_label)",asFUNCTION(GetUnmodifiedIKTransform), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("RiggedObject","mat4 GetIKTargetTransform(const string &in IK_label)",asMETHOD(RiggedObject, GetIKTargetTransform), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","void SetMorphTargetWeight(const string &in morph_label, float weight, float weight_weight)",asMETHOD(RiggedObject, SetMTTargetWeight), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","float GetStatusKeyValue(const string &in status_key_label)",asMETHOD(RiggedObject, GetStatusKeyValue), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","vec3 GetIKTargetAnimPosition(const string &in IK_label)",asMETHOD(RiggedObject, GetIKTargetAnimPosition), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","mat4 GetAvgIKChainTransform(const string &in IK_label)",asMETHOD(RiggedObject, GetAvgIKChainTransform), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","mat4 GetIKChainTransform(const string &in IK_label, int IK_link)",asMETHOD(RiggedObject, GetIKChainTransform), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","vec3 GetIKChainPos(const string &in IK_label, int IK_link)",asMETHOD(RiggedObject, GetIKChainPos), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","void DisableSleep()",asMETHOD(RiggedObject, DisableSleep), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","void EnableSleep()",asMETHOD(RiggedObject, EnableSleep), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","vec3 GetAvgIKChainPos(const string &in IK_label)",asMETHOD(RiggedObject, GetAvgIKChainPos), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","void SetAnimUpdatePeriod(int steps)",asMETHOD(RiggedObject, SetAnimUpdatePeriod), asCALL_THISCALL, "Animation is updated every N engine time steps");
+ ctx->RegisterObjectMethod("RiggedObject","void SheatheItem(int item_id, bool on_right)",asMETHOD(RiggedObject, SheatheItem), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","void UnSheatheItem(int item_id, bool on_right)",asMETHOD(RiggedObject, UnSheatheItem), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject", "void UnStickItem(int item_id)", asMETHOD(RiggedObject,UnStickItem), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","void SetPrimaryWeaponID(int item_id)",asMETHOD(RiggedObject, SetPrimaryWeaponID), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","vec3 GetModelCenter()",asFunctionPtr(ASGetModelCenter), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("RiggedObject","void RotateBoneToMatchVec(const vec3 &in a, const vec3 &in b, int bone)",asFunctionPtr(RotateBoneToMatchVec), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("RiggedObject","void RotateBonesToMatchVec(const vec3 &in a, const vec3 &in c, int bone, int bone2, float weight)",asFunctionPtr(RotateBonesToMatchVec), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("RiggedObject","void TransformAllFrameMats(const BoneTransform &in transform)",asFunctionPtr(TransformAllFrameMats), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("RiggedObject","vec3 GetFrameCenterOfMass()",asFunctionPtr(GetFrameCenterOfMass), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("RiggedObject","vec3 GetTransformedBonePoint(int bone, int point)",asFunctionPtr(GetTransformedBonePoint), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("RiggedObject","mat4 GetDisplayBoneMatrix(int bone)",asFunctionPtr(GetDisplayBoneMatrix), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("RiggedObject","void SetDisplayBoneMatrix(int bone, const mat4 &in transform)",asFunctionPtr(SetDisplayBoneMatrix), asCALL_CDECL_OBJFIRST);
+ ctx->RegisterObjectMethod("RiggedObject","Skeleton &skeleton()",asMETHOD(RiggedObject, skeleton), asCALL_THISCALL);
+ ctx->RegisterObjectMethod("RiggedObject","AnimationClient &anim_client()",asMETHOD(RiggedObject, GetAnimClient), asCALL_THISCALL);
+ ctx->DocsCloseBrace();
+}
+
+AnimationClient &RiggedObject::GetAnimClient() {
+ return anim_client;
+}
+
+static const int simp_cache_vers = 11;
+
+void RiggedObject::SaveSimplificationCache(const std::string &path) {
+ FILE *file;
+ Path found_path = FindFilePath(path.c_str(), kAnyPath, false);
+ if(found_path.isValid())
+ file = my_fopen((GetWritePath(found_path.GetModsource()).c_str()+path+".lod_cache").c_str(), "wb");
+ else {
+ LOGW << "Couldn't find path to \"" << path << "\" when saving simplification cache. This might result in a mod writing to the wrong directory" << std::endl;
+ file = my_fopen((GetWritePath(CoreGameModID).c_str()+path+".lod_cache").c_str(), "wb");
+ }
+ unsigned short checksum = skeleton_.skeleton_asset_ref->checksum()+
+ Models::Instance()->GetModel(model_id[0]).checksum;
+ for(int i=0, len=morph_targets.size(); i<len; ++i){
+ checksum += morph_targets[i].checksum;
+ }
+ fwrite(&simp_cache_vers, sizeof(int), 1, file);
+ fwrite(&checksum, sizeof(unsigned short), 1, file);
+ for(unsigned i=1; i<4; ++i){
+ const Model& model = Models::Instance()->GetModel(model_id[i]);
+ int model_num_verts = model.vertices.size()/3;
+ fwrite(&model_num_verts, sizeof(int), 1, file);
+ fwrite(&model.vertices[0], sizeof(GLfloat), model_num_verts*3, file);
+ fwrite(&model.bone_weights[0], sizeof(vec4), model_num_verts, file);
+ fwrite(&model.bone_ids[0], sizeof(vec4), model_num_verts, file);
+ fwrite(&model.tex_coords[0], sizeof(GLfloat), model_num_verts*2, file);
+ int num_faces = model.faces.size()/3;
+ fwrite(&num_faces, sizeof(int), 1, file);
+ fwrite(&model.faces[0], sizeof(GLuint), model.faces.size(), file);
+ }
+
+ {
+ int num_morphs = morph_targets.size();
+ fwrite(&num_morphs, sizeof(int), 1, file);
+ }
+
+ std::queue<MorphTarget*> queue;
+ for(unsigned i=0; i<morph_targets.size(); ++i){
+ queue.push(&morph_targets[i]);
+ }
+ while(!queue.empty()){
+ MorphTarget& morph = (*queue.front());
+ queue.pop();
+ for(unsigned i=0; i<morph.morph_targets.size(); ++i){
+ queue.push(&morph.morph_targets[i]);
+ }
+ for(unsigned i=1; i<4; ++i){
+ const Model& model = Models::Instance()->GetModel(morph.model_id[i]);
+ int model_num_verts = model.vertices.size()/3;
+ fwrite(&model.vertices[0], sizeof(GLfloat), model_num_verts*3, file);
+ fwrite(&model.tex_coords[0], sizeof(GLfloat), model_num_verts*2, file);
+ std::vector<char> &vm = morph.verts_modified[i];
+ fwrite(&vm[0],sizeof(char), model_num_verts, file);
+ int mv_size = morph.modified_verts[i].size();
+ fwrite(&mv_size, sizeof(int), 1, file);
+ if(mv_size){
+ std::vector<int> &mv = morph.modified_verts[i];
+ fwrite(&mv[0],sizeof(int), mv_size, file);
+ }
+
+ std::vector<char> &tcm = morph.tc_modified[i];
+ fwrite(&tcm[0],sizeof(char), model_num_verts, file);
+ int mtc_size = morph.modified_tc[i].size();
+ fwrite(&mtc_size, sizeof(int), 1, file);
+ if(mtc_size){
+ std::vector<int> &mtc = morph.modified_tc[i];
+ fwrite(&mtc[0],sizeof(int), mtc_size, file);
+ }
+ }
+ }
+ fclose(file);
+}
+
+bool RiggedObject::LoadSimplificationCache(const std::string& path) {
+ char lod_cache_path[kPathSize];
+ if(FindFilePath((path+".lod_cache").c_str(), lod_cache_path, kPathSize, kAnyPath, false) == -1){
+ return false;
+ }
+ FILE *file = my_fopen(lod_cache_path, "rb");
+ if(!file){
+ return false;
+ }
+ int vers;
+ fread(&vers, sizeof(int), 1, file);
+ if(vers != simp_cache_vers){
+ return false;
+ }
+ unsigned short checksum = skeleton_.skeleton_asset_ref->checksum()+
+ Models::Instance()->GetModel(model_id[0]).checksum;
+ for(int i=0, len=morph_targets.size(); i<len; ++i){
+ checksum += morph_targets[i].checksum;
+ }
+ unsigned short file_checksum;
+ fread(&file_checksum, sizeof(unsigned short), 1, file);
+ if(file_checksum != checksum){
+ return false;
+ }
+ for(unsigned i=1; i<4; ++i){
+ model_id[i] = Models::Instance()->AddModel();
+ Model& model = Models::Instance()->GetModel(model_id[i]);
+ int model_num_verts;
+ fread(&model_num_verts, sizeof(int), 1, file);
+ model.vertices.resize(model_num_verts*3);
+ model.bone_weights.resize(model_num_verts);
+ model.bone_ids.resize(model_num_verts);
+ model.tex_coords.resize(model_num_verts*2);
+ model.transform_vec.resize(4);
+ model.transform_vec[0].resize(model_num_verts);
+ model.transform_vec[1].resize(model_num_verts);
+ model.transform_vec[2].resize(model_num_verts);
+ model.transform_vec[3].resize(model_num_verts);
+ model.tex_transform_vec.resize(model_num_verts);
+ model.morph_transform_vec.resize(model_num_verts);
+ fread(&model.vertices[0], sizeof(GLfloat), model_num_verts*3, file);
+ fread(&model.bone_weights[0], sizeof(vec4), model_num_verts, file);
+ fread(&model.bone_ids[0], sizeof(vec4), model_num_verts, file);
+ fread(&model.tex_coords[0], sizeof(GLfloat), model_num_verts*2, file);
+ int num_faces;
+ fread(&num_faces, sizeof(int), 1, file);
+ model.faces.resize(num_faces*3);
+ fread(&model.faces[0], sizeof(GLuint), num_faces*3, file);
+ }
+
+ {
+ int num_morphs;
+ fread(&num_morphs, sizeof(int), 1, file);
+ if(num_morphs != (int)morph_targets.size()){
+ return false;
+ }
+ }
+
+ std::queue<MorphTarget*> queue;
+ for(unsigned i=0; i<morph_targets.size(); ++i){
+ queue.push(&morph_targets[i]);
+ }
+ while(!queue.empty()){
+ MorphTarget& morph = (*queue.front());
+ queue.pop();
+ for(unsigned i=0; i<morph.morph_targets.size(); ++i){
+ queue.push(&morph.morph_targets[i]);
+ }
+ for(unsigned i=1; i<4; ++i){
+ morph.model_id[i] = Models::Instance()->AddModel();
+ morph.model_copy[i] = true;
+ const Model& base_model = Models::Instance()->GetModel(model_id[i]);
+ Model& model = Models::Instance()->GetModel(morph.model_id[i]);
+ int model_num_verts = base_model.vertices.size()/3;
+ int base_model_num_verts = base_model.vertices.size()/3;
+ model.vertices.resize(base_model_num_verts*3);
+ model.tex_coords.resize(base_model_num_verts*2);
+ morph.verts_modified[i].resize(base_model_num_verts);
+ morph.tc_modified[i].resize(base_model_num_verts);
+ fread(&model.vertices[0], sizeof(GLfloat), model_num_verts*3, file);
+ fread(&model.tex_coords[0], sizeof(GLfloat), model_num_verts*2, file);
+
+ std::vector<char> &vm = morph.verts_modified[i];
+ fread(&vm[0],sizeof(char), model_num_verts, file);
+ int mv_size;
+ fread(&mv_size, sizeof(int), 1, file);
+ morph.modified_verts[i].resize(mv_size);
+ std::vector<int> &mv = morph.modified_verts[i];
+ if(mv_size){
+ fread(&mv[0],sizeof(int), mv_size, file);
+ }
+
+ std::vector<char> &tcm = morph.tc_modified[i];
+ fread(&tcm[0],sizeof(char), model_num_verts, file);
+ int mtc_size;
+ fread(&mtc_size, sizeof(int), 1, file);
+ morph.modified_tc[i].resize(mtc_size);
+ std::vector<int> &mtc = morph.modified_tc[i];
+ if(mtc_size){
+ fread(&mtc[0],sizeof(int), mtc_size, file);
+ }
+ }
+ }
+ fclose(file);
+ return true;
+}
+
+void RiggedObject::CreateFurModel( int model_id, int fur_model_id ) {
+ const Model &base_model = Models::Instance()->GetModel(model_id);
+ const Model &fur_ref_model = Models::Instance()->GetModel(fur_model_id);
+
+ // Create map to look up duplicate vertex positions
+ std::map<vec3, std::vector<int> > base_model_points;
+ for(unsigned i=0; i<base_model.vertices.size(); i+=3){
+ base_model_points[vec3(base_model.vertices[i+0],
+ base_model.vertices[i+1],
+ base_model.vertices[i+2])].push_back(i/3);
+ }
+
+ {
+ // Create vector of fur vertices offset by base model center
+ std::vector<vec3> fur_verts;
+ fur_verts.reserve(fur_ref_model.vertices.size()/3);
+ for(unsigned i=0; i<fur_ref_model.vertices.size(); i+=3){
+ fur_verts.push_back(vec3(fur_ref_model.vertices[i+0],
+ fur_ref_model.vertices[i+1],
+ fur_ref_model.vertices[i+2]));
+ fur_verts.back() -= base_model.old_center;
+ }
+
+ // Create fur model and populate it with tris that have at least one vertex
+ // that is not in the base model
+ int new_tris = 0;
+ const std::vector<GLuint> &faces = fur_ref_model.faces;
+ for(unsigned i=0; i<faces.size(); i+=3){
+ if(base_model_points.find(fur_verts[faces[i+0]]) == base_model_points.end() ||
+ base_model_points.find(fur_verts[faces[i+1]]) == base_model_points.end() ||
+ base_model_points.find(fur_verts[faces[i+2]]) == base_model_points.end())
+ {
+ ++new_tris;
+ fur_model.faces.push_back(faces[i+0]);
+ fur_model.faces.push_back(faces[i+1]);
+ fur_model.faces.push_back(faces[i+2]);
+ }
+ }
+ }
+
+ // Populate fur model with vertices used by the fur tris, and remove unused
+ // verts
+ {
+ std::vector<bool> included(fur_ref_model.vertices.size()/3, false);
+ for(unsigned i=0; i<fur_model.faces.size(); ++i){
+ included[fur_model.faces[i]] = true;
+ }
+
+ int remap_index = 0;
+ int vert_index;
+ std::vector<int> remapped(fur_ref_model.vertices.size()/3);
+ for(int i=0, len=fur_ref_model.vertices.size()/3; i<len; ++i){
+ if(included[i]){
+ remapped[i] = remap_index++;
+ vert_index = i*3;
+ fur_model.vertices.push_back(fur_ref_model.vertices[vert_index+0] - base_model.old_center[0]);
+ fur_model.vertices.push_back(fur_ref_model.vertices[vert_index+1] - base_model.old_center[1]);
+ fur_model.vertices.push_back(fur_ref_model.vertices[vert_index+2] - base_model.old_center[2]);
+ }
+ }
+ for(unsigned i=0; i<fur_model.faces.size(); ++i){
+ fur_model.faces[i] = remapped[fur_model.faces[i]];
+ }
+ }
+
+ // Get vec3 vector of fur vertices
+ std::vector<vec3> fur_verts;
+ fur_verts.reserve(fur_model.vertices.size()/3);
+ for(unsigned i=0; i<fur_model.vertices.size(); i+=3){
+ fur_verts.push_back(vec3(fur_model.vertices[i+0],
+ fur_model.vertices[i+1],
+ fur_model.vertices[i+2]));
+ }
+
+ // Collapse duplicate vertices
+ {
+ std::vector<int> vert_dup(fur_model.vertices.size()/3,-1);
+ std::map<vec3, std::vector<int> > fur_model_points;
+ for(unsigned i=0; i<fur_model.vertices.size(); i+=3){
+ fur_model_points[vec3(fur_model.vertices[i+0],
+ fur_model.vertices[i+1],
+ fur_model.vertices[i+2])].push_back(i/3);
+ }
+ for(int i=0, len=fur_model.vertices.size()/3; i<len; ++i){
+ vert_dup[i] = fur_model_points[fur_verts[i]].front();
+ }
+ for(unsigned i=0; i<fur_model.faces.size(); ++i){
+ fur_model.faces[i] = vert_dup[fur_model.faces[i]];
+ }
+ }
+
+ // Get a vector of the base vertices at each fur vertex
+ std::vector<std::vector<int>* > attachments(fur_model.vertices.size()/3);
+ for(unsigned i=0; i<fur_verts.size(); ++i){
+ std::map<vec3, std::vector<int> >::iterator iter =
+ base_model_points.find(fur_verts[i]);
+ if(iter != base_model_points.end()){
+ attachments[i] = &(iter->second);
+ }
+ }
+
+ // Get a set of the fur vertices connected to each fur vertex
+ std::vector<std::set<int> > connections(fur_model.vertices.size()/3);
+ for(unsigned i=0; i<fur_model.faces.size(); i+=3){
+ for(unsigned j=0; j<3; ++j){
+ connections[fur_model.faces[i+j]].insert(fur_model.faces[i+(1+j)%3]);
+ connections[fur_model.faces[i+j]].insert(fur_model.faces[i+(2+j)%3]);
+ }
+ }
+
+ // Get a set of the base vertices connected to each base vertex
+ std::vector<std::set<int> > base_connections(base_model.vertices.size()/3);
+ for(unsigned i=0; i<base_model.faces.size(); i+=3){
+ for(unsigned j=0; j<3; ++j){
+ base_connections[base_model.faces[i+j]].insert(base_model.faces[i+(1+j)%3]);
+ base_connections[base_model.faces[i+j]].insert(base_model.faces[i+(2+j)%3]);
+ }
+ }
+
+ // Map fur edges to their equivalent base edges
+ typedef std::map< std::pair<int, int>, std::pair<int, int> > PairMap;
+ PairMap base_pairs;
+ {
+ bool found_edge;
+ const std::vector<int> *attach_ptr[2];
+ std::vector<int>::const_iterator attach_iter[2];
+ std::set<int>::const_iterator connect_iter;
+ std::pair<int, int> edge;
+ for(int i=0, len=fur_model.faces.size()/3; i<len; ++i){
+ edge.first = -1;
+ edge.second = -1;
+ for(unsigned j=0; j<3; ++j){
+ if(attachments[fur_model.faces[i*3+j]]){
+ if(edge.first == -1){
+ edge.first = fur_model.faces[i*3+j];
+ } else {
+ edge.second = fur_model.faces[i*3+j];
+ }
+ }
+ }
+ if(edge.first == -1 || edge.second == -1){
+ continue;
+ }
+ if(edge.first > edge.second){
+ std::swap(edge.first, edge.second);
+ }
+ found_edge = false;
+ attach_ptr[0] = attachments[edge.first];
+ attach_ptr[1] = attachments[edge.second];
+ for(attach_iter[0] = attach_ptr[0]->begin();
+ attach_iter[0] != attach_ptr[0]->end(); ++attach_iter[0])
+ {
+ const std::set<int>* connect_ptr =
+ &base_connections[*attach_iter[0]];
+ for(connect_iter = connect_ptr->begin();
+ connect_iter != connect_ptr->end(); ++connect_iter)
+ {
+ for(attach_iter[1] = attach_ptr[1]->begin();
+ attach_iter[1] != attach_ptr[1]->end(); ++attach_iter[1])
+ {
+ if((*connect_iter) == (*attach_iter[1])){
+ base_pairs[edge] = std::pair<int,int>((*attach_iter[0]), (*attach_iter[1]));
+ found_edge = true;
+ break;
+ }
+ }
+ if(found_edge){
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ std::vector<int> face_attach(fur_model.faces.size());
+ {
+ // Get vector specifying which fur verts are attached to base mesh
+ std::vector<bool> in_base_pair(fur_model.vertices.size()/3, false);
+ {
+ PairMap::const_iterator iter;
+ for(iter = base_pairs.begin(); iter != base_pairs.end(); ++iter){
+ in_base_pair[iter->first.first] = true;
+ in_base_pair[iter->first.second] = true;
+ }
+ }
+
+ // Get map of the neighbors for each triangle
+ // Get map of the faces that share each edge
+ std::map< int, std::set<int> > face_neighbors;
+ std::map< std::pair<int, int>, std::set<int> > edge_tris;
+ {
+ std::pair<int, int> edge;
+ for(int i=0, len=fur_model.faces.size()/3; i<len; ++i){
+ for(unsigned j=0; j<3; ++j){
+ edge.first = fur_model.faces[i*3+j];
+ edge.second = fur_model.faces[i*3+(j+1)%3];
+ if(edge.first > edge.second){
+ std::swap(edge.first, edge.second);
+ }
+ edge_tris[edge].insert(i);
+ }
+ }
+
+ std::map< std::pair<int, int>, std::set<int> >::const_iterator edge_iter;
+ std::set<int>::const_iterator set_iter;
+ edge_iter = edge_tris.begin();
+ for(;edge_iter != edge_tris.end(); ++edge_iter){
+ set_iter = edge_iter->second.begin();
+ for(;set_iter != edge_iter->second.end(); ++set_iter){
+ face_neighbors[(*set_iter)].insert(edge_iter->second.begin(), edge_iter->second.end());
+ }
+ }
+
+ for(int i=0, len=fur_model.faces.size()/3; i<len; ++i){
+ face_neighbors[i].erase(i);
+ }
+ }
+
+ // Get map of extruded edges and their base-attached equivalents
+ PairMap edge_pairs;
+ {
+ int base_tri;
+ float closest_dist;
+ float dist;
+ std::pair<int, int> edge;
+ std::set<int>::const_iterator set_iter;
+ PairMap::const_iterator base_pair_iter = base_pairs.begin();
+ for(;base_pair_iter != base_pairs.end(); ++base_pair_iter){
+ closest_dist = -1.0f;
+ base_tri = *(edge_tris[base_pair_iter->first].begin());
+ const std::set<int>& neighbors = face_neighbors[base_tri];
+ for(set_iter = neighbors.begin();
+ set_iter != neighbors.end(); ++set_iter)
+ {
+ for(unsigned j=0; j<3; ++j){
+ edge.first = fur_model.faces[(*set_iter)*3+j];
+ edge.second = fur_model.faces[(*set_iter)*3+(j+1)%3];
+ if(in_base_pair[edge.first] || in_base_pair[edge.second]){
+ continue;
+ }
+ if(edge.first > edge.second){
+ std::swap(edge.first, edge.second);
+ }
+ dist = distance_squared(
+ fur_verts[edge.first] + fur_verts[edge.second],
+ fur_verts[base_pair_iter->first.first] +
+ fur_verts[base_pair_iter->first.second]);
+ if(closest_dist == -1.0f || dist < closest_dist){
+ closest_dist = dist;
+ } else {
+ continue;
+ }
+ edge_pairs[base_pair_iter->first] = edge;
+ }
+ }
+ }
+ }
+
+ {
+ PairMap::const_iterator edge_pair_iter = edge_pairs.begin();
+ std::map<int, int> vert_attach;
+ std::map<int, int> edge_vert_attach;
+ for(;edge_pair_iter != edge_pairs.end(); ++edge_pair_iter){
+ vert_attach.clear();
+ const std::pair<int,int>& edge_pair = edge_pair_iter->second;
+ const std::pair<int,int>& base_pair = edge_pair_iter->first;
+ vert_attach[base_pair.first] = base_pairs[base_pair].first;
+ vert_attach[base_pair.second] = base_pairs[base_pair].second;
+ int edge_tri = *(edge_tris[edge_pair].begin());
+ int base_tri = *(edge_tris[base_pair].begin());
+ int edge_vert_in_base = 0;
+ int base_vert_in_edge = 0;
+ for(unsigned i=0; i<3; ++i){
+ if(in_base_pair[fur_model.faces[edge_tri*3+i]]){
+ edge_vert_in_base = fur_model.faces[edge_tri*3+i];
+ }
+ if(!in_base_pair[fur_model.faces[base_tri*3+i]]){
+ base_vert_in_edge = fur_model.faces[base_tri*3+i];
+ }
+ }
+ if((edge_vert_in_base == base_pair.first &&
+ base_vert_in_edge == edge_pair.first) ||
+ (edge_vert_in_base == base_pair.second &&
+ base_vert_in_edge == edge_pair.second))
+ {
+ vert_attach[edge_pair.first] = vert_attach[base_pair.second];
+ vert_attach[edge_pair.second] = vert_attach[base_pair.first];
+ edge_vert_attach[edge_pair.first] = base_pair.second;
+ edge_vert_attach[edge_pair.second] = base_pair.first;
+ } else {
+ vert_attach[edge_pair.first] = vert_attach[base_pair.first];
+ vert_attach[edge_pair.second] = vert_attach[base_pair.second];
+ edge_vert_attach[edge_pair.first] = base_pair.first;
+ edge_vert_attach[edge_pair.second] = base_pair.second;
+ }
+ for(unsigned i=0; i<3; ++i){
+ face_attach[edge_tri*3+i] = vert_attach[fur_model.faces[edge_tri*3+i]];
+ face_attach[base_tri*3+i] = vert_attach[fur_model.faces[base_tri*3+i]];
+ }
+ }
+
+ fur_model.tex_coords2.resize(fur_model.vertices.size()/3*2,0.0f);
+ {
+ PairMap::const_iterator edge_pair_iter = edge_pairs.begin();
+ for(;edge_pair_iter != edge_pairs.end(); ++edge_pair_iter){
+ const std::pair<int,int>& edge_pair = edge_pair_iter->second;
+ fur_model.tex_coords2[edge_pair.first*2+1] = 1.0f;
+ fur_model.tex_coords2[edge_pair.second*2+1] = 1.0f;
+ }
+ }
+ {
+ float tex_val;
+ static const float tex_scale = 20.0f;
+ int num_base_connections;
+ int starting_point, last_point, point, next_point;
+ std::set<int>::const_iterator set_iter;
+ std::vector<bool> fur_tex_set(fur_model.vertices.size()/3, false);
+ for(int i=0, len=fur_model.vertices.size()/3; i<len; ++i){
+ if(!in_base_pair[i] || fur_tex_set[i]){
+ continue;
+ }
+ starting_point = i;
+ point = starting_point;
+ next_point = starting_point;
+ num_base_connections = 2;
+ do {
+ last_point = point;
+ point = next_point;
+ num_base_connections = 0;
+ set_iter = connections[point].begin();
+ for(;set_iter != connections[point].end(); ++set_iter){
+ if(in_base_pair[(*set_iter)]){
+ if((*set_iter) != last_point){
+ next_point = (*set_iter);
+ }
+ ++num_base_connections;
+ }
+ }
+ } while(starting_point != next_point &&
+ num_base_connections == 2);
+
+ tex_val = 0.0f;
+ next_point = point;
+ do {
+ tex_val += distance(fur_verts[point], fur_verts[next_point]);
+ point = next_point;
+ fur_model.tex_coords2[point*2+0] = tex_val * tex_scale;
+ fur_tex_set[point] = true;
+ set_iter = connections[point].begin();
+ for(;set_iter != connections[point].end(); ++set_iter){
+ if(in_base_pair[(*set_iter)] &&
+ !fur_tex_set[(*set_iter)])
+ {
+ next_point = (*set_iter);
+ }
+ }
+ } while(fur_tex_set[next_point] == false);
+ }
+ }
+ {
+ std::map<int, int>::iterator iter = edge_vert_attach.begin();
+ for(;iter != edge_vert_attach.end(); ++iter){
+ fur_model.tex_coords2[iter->first*2+0] =
+ fur_model.tex_coords2[iter->second*2+0];
+ }
+ }
+ }
+ }
+
+
+ int next_vert = fur_model.vertices.size()/3;
+ std::vector< std::map<int, int> > vert_attach(fur_model.vertices.size()/3);
+ std::vector< int > attach_base(fur_model.vertices.size()/3);
+ std::pair<int, int> edge;
+ int attach_id, v_id;
+ for(int i=0, len=fur_model.faces.size(); i<len; i+=3){
+ if(face_attach[i+0] == 0 &&
+ face_attach[i+1] == 0 &&
+ face_attach[i+2] == 0)
+ {
+ fur_model.faces[i+0] = 0;
+ fur_model.faces[i+1] = 0;
+ fur_model.faces[i+2] = 0;
+ continue;
+ }
+ for(unsigned j=0; j<3; ++j){
+ v_id = fur_model.faces[i+j];
+ attach_id = face_attach[i+j];
+ if(vert_attach[v_id].find(attach_id) == vert_attach[v_id].end()){
+ if(vert_attach[v_id].empty()){
+ vert_attach[v_id][attach_id] = v_id;
+ attach_base[v_id] = attach_id;
+ } else {;
+ attach_base.push_back(attach_id);
+ fur_model.vertices.push_back(fur_model.vertices[v_id*3+0]);
+ fur_model.vertices.push_back(fur_model.vertices[v_id*3+1]);
+ fur_model.vertices.push_back(fur_model.vertices[v_id*3+2]);
+ fur_model.tex_coords2.push_back(fur_model.tex_coords2[v_id*2+0]);
+ fur_model.tex_coords2.push_back(fur_model.tex_coords2[v_id*2+1]);
+ fur_model.faces[i+j] = next_vert;
+ vert_attach[v_id][attach_id] = next_vert++;
+ }
+ } else {
+ fur_model.faces[i+j] = vert_attach[v_id][attach_id];
+ }
+ }
+ }
+
+ int fur_model_num_vertices = attach_base.size();
+ fur_model.tex_coords.resize(fur_model_num_vertices*2);
+ fur_model.bone_weights.resize(fur_model_num_vertices);
+ fur_model.bone_ids.resize(fur_model_num_vertices);
+ fur_base_vertex_ids.resize(fur_model_num_vertices);
+ for(int i=0; i<fur_model_num_vertices; ++i){
+ const int &base_id = attach_base[i];
+ fur_base_vertex_ids[i] = base_id;
+ fur_model.tex_coords[i*2+0] = base_model.tex_coords[base_id*2+0];
+ fur_model.tex_coords[i*2+1] = base_model.tex_coords[base_id*2+1];
+ fur_model.bone_weights[i] = base_model.bone_weights[base_id];
+ fur_model.bone_ids[i] = base_model.bone_ids[base_id];
+ }
+
+ fur_model.transform_vec.resize(4);
+ fur_model.transform_vec[0].resize(fur_model_num_vertices);
+ fur_model.transform_vec[1].resize(fur_model_num_vertices);
+ fur_model.transform_vec[2].resize(fur_model_num_vertices);
+ fur_model.transform_vec[3].resize(fur_model_num_vertices);
+ fur_model.tex_transform_vec.resize(fur_model_num_vertices, vec2(0.0f));
+ fur_model.morph_transform_vec.resize(fur_model_num_vertices, vec3(0.0f));
+}
+
+void RiggedObject::MoveRagdollPart( const std::string &label, const vec3 &position, float strength_mult ) const {
+ Skeleton::SimpleIKBoneMap::const_iterator iter =
+ skeleton_.simple_ik_bones.find(label);
+ if( iter == skeleton_.simple_ik_bones.end())
+ {
+ return;
+ }
+ const SimpleIKBone &sib = iter->second;
+ BulletObject* bo = skeleton_.physics_bones[sib.bone_id].bullet_object;
+ vec3 curr_pos = bo->GetPosition();
+ if(skeleton_.fixed_obj[sib.bone_id]){
+ bo = skeleton_.physics_bones[skeleton_.parents[sib.bone_id]].bullet_object;
+ }
+ if(skeleton_.fixed_obj[skeleton_.parents[sib.bone_id]]){
+ bo = skeleton_.physics_bones[skeleton_.parents[skeleton_.parents[sib.bone_id]]].bullet_object;
+ }
+ scenegraph_->bullet_world_->CreateTempDragConstraint(bo, curr_pos, position);
+}
+
+void RiggedObject::FixedRagdollPart( int boneID, const vec3 &position) const {
+ BulletObject* bo = skeleton_.physics_bones[boneID].bullet_object;
+ if(skeleton_.fixed_obj[boneID]){
+ bo = skeleton_.physics_bones[skeleton_.parents[boneID]].bullet_object;
+ }
+ if(skeleton_.fixed_obj[skeleton_.parents[boneID]]){
+ bo = skeleton_.physics_bones[skeleton_.parents[skeleton_.parents[boneID]]].bullet_object;
+ }
+ vec3 curr_pos = bo->GetPosition();
+ scenegraph_->bullet_world_->CreateBoneConstraint(bo, curr_pos, position);
+}
+
+void RiggedObject::SpikeRagdollPart( int boneID, const vec3 &start, const vec3 &end, const vec3 &pos) const {
+ BulletObject* bo = skeleton_.physics_bones[boneID].bullet_object;
+ if(skeleton_.fixed_obj[boneID]){
+ bo = skeleton_.physics_bones[skeleton_.parents[boneID]].bullet_object;
+ }
+ if(skeleton_.parents[boneID] != -1 && skeleton_.fixed_obj[skeleton_.parents[boneID]]){
+ bo = skeleton_.physics_bones[skeleton_.parents[skeleton_.parents[boneID]]].bullet_object;
+ }
+ scenegraph_->bullet_world_->CreateSpikeConstraint(bo, start, end, pos);
+}
+
+void RiggedObject::ClearBoneConstraints(){
+ scenegraph_->bullet_world_->ClearBoneConstraints();
+}
+
+void RiggedObject::ApplyPalette( const OGPalette& _palette ) {
+ unsigned num_iters = min(palette_colors.size(), _palette.size());
+ for(unsigned i=0; i<num_iters; ++i){
+ palette_colors_srgb[_palette[i].channel] = _palette[i].color;
+ palette_colors[_palette[i].channel] = _palette[i].color;
+ palette_colors[_palette[i].channel][0] = pow(palette_colors[_palette[i].channel][0], 1.0f/2.2f);
+ palette_colors[_palette[i].channel][1] = pow(palette_colors[_palette[i].channel][1], 1.0f/2.2f);
+ palette_colors[_palette[i].channel][2] = pow(palette_colors[_palette[i].channel][2], 1.0f/2.2f);
+ }
+}
+
+std::vector<vec3> * RiggedObject::GetPaletteColors() {
+ return &palette_colors_srgb;
+}
+
+void RiggedObject::StickItem( int id, const vec3 & start, const vec3 & end, const mat4 &transform ) {
+ int bone = skeleton_.CheckRayCollisionBone(start, end);
+ if(bone != -1){
+ Object* obj = scenegraph_->GetObjectFromID(id);
+ if(!obj || obj->GetType() != _item_object){
+ FatalError("Error", "There is no item object %d", id);
+ }
+ ItemObject* io = (ItemObject*)obj;
+ vec3 old_vel = io->GetLinearVelocity();
+ io->SetPhysicsTransform(transform);
+ io->SetLinearVelocity(old_vel);
+ mat4 rel_mat = invert(skeleton_.physics_bones[bone].bullet_object->GetTransform()) * transform;
+ stuck_items.Add(io, bone, rel_mat, char_id, scenegraph_->bullet_world_, skeleton_);
+ if(!animated){
+ stuck_items.items.back().item.ActivatePhysics();
+ stuck_items.items.back().item.AddConstraint(skeleton_.physics_bones[bone].bullet_object);
+ }
+ }
+}
+
+extern std::stack<ASContext*> active_context_stack;
+
+void RiggedObject::UnStickItem( int id ) {
+ stuck_items.Remove(id);
+}
+
+std::vector<ItemRef> RiggedObject::GetWieldedItemRefs() const {
+ int num_wielded = 0;
+ for(std::list<AttachedItem>::const_iterator iter = attached_items.items.begin();
+ iter != attached_items.items.end(); ++iter)
+ {
+ const ItemObjectScriptReader& item = iter->item;
+ if(item->GetID() == primary_weapon_id){
+ ++num_wielded;
+ }
+ }
+ std::vector<ItemRef> wielded_item_refs(num_wielded);
+ num_wielded = 0;
+ for(std::list<AttachedItem>::const_iterator iter = attached_items.items.begin();
+ iter != attached_items.items.end(); ++iter)
+ {
+ const ItemObjectScriptReader& item = iter->item;
+ if(item->GetID() == primary_weapon_id){
+ wielded_item_refs[num_wielded++] = item->item_ref();
+ }
+ }
+ return wielded_item_refs;
+}
+
+void RiggedObject::SetCharacterScriptGetter( CharacterScriptGetter &csg ) {
+ character_script_getter = &csg;
+}
+
+void RiggedObject::SetPrimaryWeaponID( int id ) {
+ primary_weapon_id = id;
+ character_script_getter->ItemsChanged(GetWieldedItemRefs());
+}
+
+void DrawAvailableAttachmentSlot(const ItemRef& item_ref, AttachmentType type, const Skeleton &skeleton, bool mirrored, std::list<AttachmentSlot> *list){
+ int bone_id;
+ mat4 rel_mat = LoadAttachPose(item_ref->GetAttachAnimPath(type), item_ref->GetIKAttach(type), skeleton, &bone_id, mirrored, true, 0);
+ mat4 bone_mat = skeleton.physics_bones[bone_id].bullet_object->GetTransform();
+ mat4 world_mat = bone_mat * rel_mat;
+
+ list->resize(list->size()+1);
+ AttachmentSlot &slot = list->back();
+ slot.pos = world_mat.GetTranslationPart();
+ slot.type = type;
+ slot.mirrored = mirrored;
+}
+
+void DrawAvailableAttachment(const AttachmentRef attachment_ref, const Skeleton &skeleton, bool mirrored, AttachmentSlotList *list){
+ int bone_id;
+ mat4 rel_mat = LoadAttachPose(attachment_ref->anim, attachment_ref->ik_chain_label, skeleton, &bone_id, mirrored, false, attachment_ref->ik_chain_bone);
+ mat4 bone_mat = skeleton.physics_bones[bone_id].bullet_object->GetTransform();
+ mat4 world_mat = bone_mat * rel_mat;
+
+ list->resize(list->size()+1);
+ AttachmentSlot &slot = list->back();
+ slot.pos = world_mat.GetTranslationPart();
+ slot.type = _at_attachment;
+ slot.attachment_ref = attachment_ref;
+ slot.mirrored = mirrored;
+}
+
+void DrawAvailableAttachmentSlots(const ItemRef& item_ref, AttachmentType type, const Skeleton &skeleton, AttachmentSlotList *list){
+ DrawAvailableAttachmentSlot(item_ref, type, skeleton, false, list);
+ DrawAvailableAttachmentSlot(item_ref, type, skeleton, true, list);
+}
+
+void RiggedObject::AvailableItemSlots( const ItemRef& item_ref, AttachmentSlotList *list ) const {
+ if(item_ref->HasAttachment(_at_grip)){
+ DrawAvailableAttachmentSlots(item_ref, _at_grip, skeleton_, list);
+ }
+ if(item_ref->HasAttachment(_at_sheathe)){
+ DrawAvailableAttachmentSlots(item_ref, _at_sheathe, skeleton_, list);
+ }
+ const std::list<std::string> &attachment_list = item_ref->GetAttachmentList();
+ for(std::list<std::string>::const_iterator iter = attachment_list.begin();
+ iter != attachment_list.end(); ++iter)
+ {
+ //AttachmentRef attachment_ref = Attachments::Instance()->ReturnRef((*iter));
+ AttachmentRef attachment_ref = Engine::Instance()->GetAssetManager()->LoadSync<Attachment>((*iter));
+ DrawAvailableAttachment(attachment_ref, skeleton_, false, list);
+ if(attachment_ref->mirror_allowed){
+ DrawAvailableAttachment(attachment_ref, skeleton_, true, list);
+ }
+ }
+}
+
+void RiggedObject::AttachItemToSlot( ItemObject* item_object, AttachmentType type, bool mirrored, const AttachmentRef* attachment_ref ) {
+ // Detach item if it is already attached
+ DetachItem(item_object);
+
+ // Set up item attachment
+ attached_items.items.resize(attached_items.items.size()+1);
+ AttachedItem &stuck_item = attached_items.items.back();
+ ItemObjectScriptReader &item = stuck_item.item;
+ item.char_id = char_id;
+ item.holding = true;
+ if(attachment_ref){
+ item.attachment_ref = *attachment_ref;
+ }
+ item.SetInvalidateCallback(&RiggedObject::InvalidatedItemCallback, this);
+ item.AttachToItemObject(item_object);
+ if(type == _at_grip){
+ item->SetState(ItemObject::kWielded);
+ } else if(type == _at_sheathe){
+ item->SetState(ItemObject::kSheathed);
+ }
+ if(attachment_ref && attachment_ref->valid()){
+ SetItemAttachment(stuck_item, *attachment_ref, mirrored);
+ } else {
+ SetWeaponOffset(stuck_item, type, mirrored);
+ }
+
+ if(animated){
+ UpdateAttachedItem(stuck_item, skeleton_, model_char_scale, weapon_offset, weap_anim_info_map, weapon_offset_retarget);
+ } else {
+ UpdateAttachedItemRagdoll(stuck_item, skeleton_, weapon_offset);
+ }
+}
+
+Skeleton & RiggedObject::skeleton() {
+ return skeleton_;
+}
+
+void RiggedObject::SetCharScale(float val) {
+ char_scale = val * character_script_getter->GetDefaultScale();
+ model_char_scale = char_scale / character_script_getter->GetModelScale();
+}
+
+float RiggedObject::GetCharScale() {
+ return char_scale;
+}
+
+float RiggedObject::GetRelativeCharScale() {
+ return char_scale / character_script_getter->GetDefaultScale();
+}
+
+void RiggedObject::UpdateGPUSkinning() {
+ GPU_skinning = Graphics::Instance()->config_.gpu_skinning();
+ for(int i=0; i<kLodLevels; ++i){
+ transform_vec_vbo0[i]->Dispose();
+ transform_vec_vbo1[i]->Dispose();
+ }
+ fur_transform_vec_vbo0.Dispose();
+ fur_transform_vec_vbo1.Dispose();
+}
+
+void RiggedObject::GetShaderNames(std::map<std::string, int>& shaders) {
+ const int kShaderStrSize = 1024;
+ char buf[2][kShaderStrSize];
+ char* shader_str[2] = {buf[0], buf[1]};
+ strcpy(shader_str[0], shader);
+ if(GPU_skinning){
+ FormatString(shader_str[1], kShaderStrSize, "%s #GPU_SKINNING", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ shaders[shader_str[0]] = SceneGraph::kPreloadTypeAll;
+ shaders[water_cube] = 0;
+ shaders[water_cube_expand] = 0;
+
+ blood_surface.GetShaderNames(shaders);
+}
+
+void AttachedItems::Add( ItemObject* io, int bone, mat4 rel_mat, int char_id, BulletWorld *bw, Skeleton& skeleton ) {
+ items.resize(items.size()+1);
+ AttachedItem& item = items.back();
+ item.bone_id = bone;
+ item.rel_mat = rel_mat;
+
+ ItemObjectScriptReader& iosr = item.item;
+ iosr.stuck = true;
+ iosr.char_id = char_id;
+ iosr.SetInvalidateCallback(&AttachedItems::InvalidatedItemCallback, this);
+ iosr.AttachToItemObject(io);
+}
+
+void AttachedItems::InvalidatedItem(ItemObjectScriptReader *invalidated) {
+ for(std::list<AttachedItem>::iterator iter = items.begin(); iter != items.end();) {
+ if(&(iter->item) == invalidated){
+ iter->item->ActivatePhysics();
+ iter = items.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+}
+
+void AttachedItems::InvalidatedItemCallback(ItemObjectScriptReader *invalidated, void* this_ptr) {
+ ((AttachedItems*)this_ptr)->InvalidatedItem(invalidated);
+}
+
+void AttachedItems::Remove( int id ) {
+ for(std::list<AttachedItem>::iterator iter = items.begin(); iter != items.end();) {
+ if(iter->item->GetID() == id){
+ iter->item->ActivatePhysics();
+ iter = items.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+}
+
+void AttachedItems::RemoveAll() {
+ for(std::list<AttachedItem>::iterator iter = items.begin(); iter != items.end(); ++iter) {
+ iter->item->ActivatePhysics();
+ }
+ items.clear();
+}
+
+vec3 RiggedTransformedVertexGetter::GetTransformedVertex(int val) {
+ return rigged_object->GetTransformedVertex(val);
+}
diff --git a/Source/Objects/riggedobject.h b/Source/Objects/riggedobject.h
new file mode 100644
index 00000000..a093eb23
--- /dev/null
+++ b/Source/Objects/riggedobject.h
@@ -0,0 +1,454 @@
+//-----------------------------------------------------------------------------
+// Name: riggedobject.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Graphics/skeleton.h>
+#include <Graphics/vbocontainer.h>
+#include <Graphics/modelsurfacewalker.h>
+#include <Graphics/bloodsurface.h>
+#include <Graphics/model.h>
+#include <Graphics/palette.h>
+#include <Graphics/textureref.h>
+#include <Graphics/animationclient.h>
+#include <Graphics/bonetransform.h>
+#include <Graphics/vboringcontainer.h>
+
+#include <Editors/editor_types.h>
+#include <Editors/editor_utilities.h>
+
+#include <Math/quaternions.h>
+#include <Math/overgrowth_geometry.h>
+
+#include <Objects/softinfo.h>
+#include <Objects/itemobjectscriptreader.h>
+#include <Objects/object.h>
+#include <Objects/envobjectattach.h>
+
+#include <Asset/Asset/animation.h>
+#include <Asset/Asset/lipsyncfile.h>
+#include <Asset/Asset/character.h>
+#include <Asset/Asset/item.h>
+#include <Asset/Asset/attachmentasset.h>
+#include <Asset/Asset/objectfile.h>
+
+#include <Online/time_interpolator.h>
+#include <Online/online_datastructures.h>
+
+#include <Game/characterscript.h>
+#include <Scripting/angelscript/ascontext.h>
+
+#include <string>
+
+#define USE_SSE
+#ifdef USE_SSE
+#include <Math/simd_mat4.h>
+#endif
+//-----------------------------------------------------------------------------
+// Class Definition
+//-----------------------------------------------------------------------------
+
+using std::unordered_map;
+using std::string;
+
+struct BlendableAnimation {
+ AnimationRef animation;
+ float weight;
+ float curr_time;
+};
+
+struct MorphTarget {
+ std::string name;
+ int model_id[4];
+ bool model_copy[4];
+ float default_val;
+ float anim_weight;
+ float script_weight;
+ float script_weight_weight;
+ float disp_weight;
+ float weight;
+ float old_weight;
+ float mod_weight; // used for multiplayer sync
+ unsigned short checksum;
+ std::vector<int> modified_verts[4];
+ std::vector<char> verts_modified[4];
+ std::vector<int> modified_tc[4];
+ std::vector<char> tc_modified[4];
+ std::vector<MorphTarget> morph_targets;
+
+ void PreDrawCamera(int num, int progress, int lod_level, uint32_t char_id = 0);
+ void UpdateForInterpolation();
+ void Load(const std::string &base_model_name,
+ int base_model_id,
+ const std::string &_name,
+ int parts=1,
+ bool absolute_path = false);
+ float GetScriptWeight();
+ void SetScriptWeight(float weight, float weight_weight);
+ float GetWeight();
+ void CalcVertsModified(int base_model_id, int lod_level);
+ void Dispose();
+};
+
+typedef std::list<BlendableAnimation> BlendableAnimationList;
+
+class BulletWorld;
+class ASContext;
+struct SphereCollision;
+
+struct SoftIKBone {
+ std::string bone_label;
+ vec3 position;
+ vec3 velocity;
+ bool wind;
+ float joint_strength;
+ float blend;
+ float default_blend;
+ float angular_damping;
+ std::vector<int> extra;
+ SoftIKBone():wind(false), joint_strength(0.0f),
+ blend(0.0f),
+ default_blend(0.0f),
+ angular_damping(0.0f){}
+};
+
+struct EdgeCollapse {
+ int vert_id[2];
+ vec3 offset;
+ std::vector<int> modified_faces;
+ std::vector<int> deleted_faces;
+};
+
+class Decal;
+
+class btPoint2PointConstraint;
+class btTypedConstraint;
+class IMUIContext;
+
+struct AttachedItem {
+ btTypedConstraint* constraint;
+ ItemObjectScriptReader item;
+ int bone_id;
+ mat4 rel_mat;
+};
+
+typedef std::list<AttachedItem> AttachedItemList;
+
+struct AttachedItems {
+ AttachedItemList items;
+ void Add( ItemObject* io, int bone, mat4 rel_mat, int char_id, BulletWorld *bw, Skeleton& skeleton );
+ void Remove( int id );
+ void InvalidatedItem(ItemObjectScriptReader *invalidated);
+ static void InvalidatedItemCallback(ItemObjectScriptReader *invalidated, void* this_ptr);
+ void RemoveAll();
+};
+
+struct AttachmentSlot {
+ vec3 pos;
+ AttachmentType type;
+ AttachmentRef attachment_ref;
+ bool mirrored;
+};
+
+struct CachedSkeletonInfo {
+ std::vector<BoneTransform> bind_matrices;
+};
+
+class RiggedObject;
+
+class RiggedTransformedVertexGetter : public BloodSurface::TransformedVertexGetter {
+public:
+ RiggedTransformedVertexGetter(RiggedObject* p_rigged_object):
+ rigged_object(p_rigged_object) {}
+ virtual vec3 GetTransformedVertex(int val);
+private:
+ RiggedObject* rigged_object;
+};
+
+typedef std::list<AttachmentSlot> AttachmentSlotList;
+typedef std::map<int, WeapAnimInfo> WeapAnimInfoMap;
+struct NetworkBone;
+struct MorphTargetStateStorage;
+class RiggedObject: public Object {
+ struct {
+ ASFunctionHandle handle_animation_event;
+ ASFunctionHandle display_matrix_update;
+ ASFunctionHandle final_animation_matrix_update;
+ ASFunctionHandle final_attached_item_update;
+ ASFunctionHandle notify_item_detach;
+ } as_funcs;
+
+ static const int kLodLevels = 4;
+ public:
+ void CalcRootBoneVelocity();
+ float GetRootBoneVelocity();
+ mat4 old_root_bone;
+ float velocity;
+ float old_time = .0f;
+ const std::vector<mat4>& GetViewBones() const;
+ std::vector<NetworkBone> network_bones;
+ std::vector<MorphTargetStateStorage> network_morphs;
+ unordered_map<string, MorphTargetStateStorage> incoming_network_morphs;
+ float network_bones_host_walltime = 0.0f;
+
+ bool static_char;
+ float last_draw_time;
+ vec3 ambient_cube_color[6];
+ CachedSkeletonInfo cached_skeleton_info_;
+ virtual EntityType GetType() const { return _rigged_object; }
+ // Textures
+ TextureAssetRef stab_texture;
+ TextureAssetRef texture_ref;
+ TextureAssetRef normal_texture_ref;
+ TextureAssetRef palette_texture_ref;
+ TextureAssetRef fur_tex;
+ TextureRef cube_map;
+ // Model and VBO data
+ int model_id[4];
+ std::vector<int> fur_base_vertex_ids;
+ Model fur_model;
+ bool need_vbo_update[kLodLevels];
+ VBORingContainer vel_vbo;
+ VBORingContainer* transform_vec_vbo0[kLodLevels];
+ VBORingContainer* transform_vec_vbo1[kLodLevels];
+ VBORingContainer* transform_vec_vbo2[kLodLevels];
+ VBORingContainer* tex_transform_vbo[kLodLevels];
+ VBORingContainer fur_transform_vec_vbo0;
+ VBORingContainer fur_transform_vec_vbo1;
+ VBORingContainer fur_transform_vec_vbo2;
+ VBORingContainer fur_tex_transform_vbo;
+ vec3 current_pos, next_pos;
+ vec3 last_pos;
+ bool dont = false;
+ float last_model_scale;
+
+ bool update_camera_pos;
+ int last_draw_frame = 0;
+
+ std::vector<mat4> network_display_bone_matrices; // Final interpolated bone matrices for rendering, from server when client.
+ std::vector<mat4> display_bone_matrices; // Final interpolated bone matrices for rendering
+ std::vector<BoneTransform> display_bone_transforms;
+ #ifdef USE_SSE
+ simd_mat4 *simd_bone_mats;
+ #endif
+ // Do we need to redo skinning?
+ bool needs_matrix_update;
+ // Item data
+ AttachedItems attached_items;
+ AttachedItems stuck_items;
+ WeapAnimInfoMap weap_anim_info_map; // Storing weapon info from current animation frame
+ std::map<int, mat4> weapon_offset;
+ std::map<int, mat4> weapon_offset_retarget;
+ int primary_weapon_id;
+ std::vector<SoftIKBone> soft_ik_bones;
+ // Ragdoll transition data
+ bool first_ragdoll_frame;
+ // Physics data
+ float ragdoll_strength;
+ Skeleton skeleton_;
+ // Animation data
+ AnimationClient anim_client;
+ bool animated;
+ std::vector<BoneTransform> animation_frame_bone_matrices;
+ std::vector<BoneTransform> cached_animation_frame_bone_matrices;
+ std::vector<BoneTransform> unmodified_transforms;
+ std::set<BulletObject*> head_chain;
+ std::vector<float> ik_offset;
+ float total_ik_offset;
+ bool ik_enabled;
+ vec3 total_center_offset;
+ float total_rotation;
+ std::vector<BlendedBonePath> blended_bone_paths;
+ std::vector<MorphTarget> morph_targets;
+ std::vector<AttachedEnvObject> children;
+ std::map<std::string, float> status_keys;
+ // Animation update data
+ int anim_update_period;
+ int max_time_until_next_anim_update;
+ int time_until_next_anim_update;
+ int curr_anim_update_time;
+ int prev_anim_update_time;
+ // Script data
+ CharacterScriptGetter *character_script_getter;
+ ASContext* as_context;
+ // Character animation info
+ std::string char_anim_override;
+ std::string char_anim;
+ char char_anim_flags;
+ // Misc
+ LipSyncFileReader lipsync_reader;
+ std::vector<vec3> palette_colors;
+ std::vector<vec3> palette_colors_srgb;
+ int char_id;
+ std::map<std::string, std::vector<Decal*> > last_decal;
+ int lod_level;
+ int shadow_group_id;
+ BloodSurface blood_surface;
+ float floor_height;
+ vec3 camera_translation;
+
+ RiggedObject();
+ virtual ~RiggedObject();
+
+ bool Initialize();
+ void Update(float timestep);
+
+
+ // Drawing
+ void Draw(const mat4& proj_view_matrix, DrawType type);
+ void ClientBeforeDraw();
+ void GetInterpolationWithLeftOver();
+ void InterpolateBetweenNetworkBones();
+ static void GetCubicSplineWeights(float interp, float *weights);
+ static const mat4 InterpolateBetweenFourBonesQuadratic(const NetworkBone* bones, float inter_step);
+ static const mat4 InterpolateBetweenTwoBones(const NetworkBone& current, const NetworkBone& next, float interp_step);
+ bool CheckIfNextStepLeadsToStop();
+ bool CheckIfOverShot();
+ void InterpolateNoNewData();
+ void DebugCalculateInterpolationVelocity();
+ void CalcNoDataInterpStep();
+ void StoreNetworkBones();
+ void StoreNetworkMorphTargets();
+ void PreDrawFrame(float curr_game_time);
+ void PreDrawCamera(float curr_game_time);
+ void DrawModel( Model* model, int lod_level );
+ void SetASContext(ASContext* _as_context);
+
+ std::vector<vec3> * GetPaletteColors();
+ void ApplyPalette( const OGPalette& palette );
+ float GetStatusKeyValue(const std::string &label);
+ void Ragdoll(const vec3 &velocity);
+ void AddAnimation( std::string path, float weight );
+ void ApplyBoneMatricesToModel(bool old, int lod_level);
+ void FixDiscontinuity();
+ vec3 GetAvgPosition();
+ vec3 GetAvgVelocity();
+ void UnRagdoll();
+ vec3 GetAvgAngularVelocity();
+ quaternion GetAvgRotation();
+ void SetMTTargetWeight( const std::string &target_name, float weight, float weight_weight );
+ vec3 GetIKTargetPosition( const std::string &target_name );
+ vec3 GetIKTargetAnimPosition( const std::string &target_name );
+ vec3 FetchCenterOffset();
+ float GetIKWeight( const std::string &target_name );
+ void ApplyForceToRagdoll( const vec3 &force, const vec3 &position );
+ void ApplyForceToBone( const vec3 &force, const int &bone );
+ void ApplyForceLineToRagdoll( const vec3 &force, const vec3 &position, const vec3 &line_dir );
+ vec3 GetDisplayBonePosition(int bone);
+ vec3 GetBonePosition( int bone );
+ mat4 GetBoneRotation( int bone );
+ vec3 GetBoneLinearVel( int bone );
+ void SetSkeletonOwner( Object* owner );
+ void Load(const std::string& character_path, vec3 pos, SceneGraph* _scenegraph, OGPalette &palette);
+ void SetAnimUpdatePeriod( int update_script_period );
+ float FetchRotation();
+ void SetDamping(float amount);
+ mat4 GetIKTargetTransform( const std::string &target_name );
+ void DetachItem(ItemObject* item_object);
+ void HandleAnimationEvents( AnimationClient &anim_client );
+ void SetRagdollStrength( float amount );
+ mat4 GetAvgIKChainTransform( const std::string &target_name );
+ void EnableSleep();
+ void DisableSleep();
+ vec3 GetAvgIKChainPos( const std::string &target_name );
+ void CheckForNAN();
+ void HandleLipSyncMorphTargets(float timestep);
+ bool InHeadChain(BulletObject* obj);
+ vec3 GetTransformedVertex(int vert_id);
+ void CreateBloodDrip( const std::string &ik_name, int ik_link, const vec3 &direction );
+ void CreateBloodDripAtBone( int bone_id, int ik_link, const vec3 &direction );
+ void CleanBlood();
+ void GetTransformedTri(int id, vec3* points);
+ void RemoveLayer( int which, float fade_speed );
+ void SetCharAnim( const std::string &path, char flags=0, const std::string &override = "");
+ void CheckItemAnimBlends(const std::string &path, char flags=0);
+ void CutPlane(const vec3 &normal, const vec3 &pos, const vec3 &dir, int type, int depth);
+ void MPCutPlane(const vec3 &normal, const vec3 &pos, const vec3 &dir, int type, int depth, std::vector<int> mp_hit_list, vec3 points[3]);
+ Skeleton &skeleton();
+ void UpdateCollisionObjects();
+ void AddBloodAtPoint( const vec3 & point );
+ void Stab( const vec3 &pos, const vec3 &dir, int type, int depth );
+ void AddWaterCube( const mat4 &transform );
+ void DetachAllItems();
+ static void InvalidatedItemCallback(ItemObjectScriptReader *invalidated, void* this_ptr);
+ void InvalidatedItem(ItemObjectScriptReader *invalidated);
+ int GetTimeBetweenLastTwoAnimUpdates();
+ int GetTimeSinceLastAnimUpdate();
+ void SaveSimplificationCache(const std::string& path);
+ bool LoadSimplificationCache(const std::string& path);
+ void CreateFurModel( int model_id, int fur_model_id );
+ void MoveRagdollPart( const std::string &label, const vec3 &position, float strength_mult ) const;
+ void FixedRagdollPart( int boneID, const vec3 &position ) const;
+ void SpikeRagdollPart( int boneID, const vec3 &start, const vec3 &end, const vec3 &pos) const;
+ void ClearBoneConstraints();
+ void RefreshRagdoll();
+ vec3 GetIKChainPos( const std::string &target_name, int which );
+ mat4 GetIKChainTransform( const std::string &target_name, int which );
+ void StickItem( int id, const vec3 & start, const vec3 & end, const mat4 &transform );
+ void SetWeaponOffset(AttachedItem &stuck_item, AttachmentType type, bool mirror);
+ void SheatheItem(int id, bool on_right);
+ void UnSheatheItem(int id, bool right_hand);
+ void SetCharAnimOverride( std::string path ) const;
+ void UnStickItem( int id );
+ void SetItemAttachment(AttachedItem &stuck_item, AttachmentRef attachment_ref, bool mirror);
+ std::vector<ItemRef> GetWieldedItemRefs() const;
+ void SetCharacterScriptGetter( CharacterScriptGetter &csg );
+ void SetPrimaryWeaponID( int id );
+ void AvailableItemSlots( const ItemRef& item_ref, AttachmentSlotList *list ) const;
+ void AttachItemToSlot( ItemObject* item_object, AttachmentType type, bool mirrored, const AttachmentRef* attachment_ref );
+ float GetCharScale();
+ AnimationClient &GetAnimClient();
+ void SetCharScale( float val );
+ float GetRelativeCharScale();
+ bool DrawBoneConnectUI(Object* objects[], int num_obj_ids, IMUIContext &imui_context, EditorTypes::Tool tool, int id);
+ void AddToDesc(EntityDescription &desc);
+ void UpdateGPUSkinning();
+ void GetShaderNames(std::map<std::string, int>& shaders);
+
+ bool GetOnlineIncomingMorphTargetState(MorphTargetStateStorage& dest, const char* name);
+ private:
+ ObjectFileRef ofc;
+ RiggedTransformedVertexGetter transformed_vertex_getter_;
+ float char_scale;
+ float model_char_scale;
+ void UpdateAttachedItems();
+
+ RiggedObject( const RiggedObject& other );
+ RiggedObject& operator=( const RiggedObject& other );
+
+ const char* shader;
+ const char* water_cube;
+ const char* water_cube_expand;
+};
+
+mat4 ASGetBindMatrix(Skeleton* skeleton, int which_bone);
+int ASIKBoneStart(Skeleton* skeleton, const std::string& name);
+int ASIKBoneLength(Skeleton* skeleton, const std::string& name);
+
+void RotateBoneToMatchVec(RiggedObject* rigged_object, const vec3 &a, const vec3 &b, int bone);
+void RotateBonesToMatchVec(RiggedObject* rigged_object, const vec3 &a, const vec3 &c, int bone, int bone2, float weight);
+vec3 GetTransformedBonePoint(RiggedObject* rigged_object, int bone, int point);
+void DefineRiggedObjectTypePublic(ASContext* as_context);
+BoneTransform GetIKTransform(RiggedObject* rigged_object, const std::string &target_name);
+mat4 GetUnmodifiedIKTransform(RiggedObject* rigged_object, const std::string &target_name);
+
+vec3 ASGetModelCenter(RiggedObject* rigged_object);
diff --git a/Source/Objects/softinfo.h b/Source/Objects/softinfo.h
new file mode 100644
index 00000000..ce18a17b
--- /dev/null
+++ b/Source/Objects/softinfo.h
@@ -0,0 +1,32 @@
+//-----------------------------------------------------------------------------
+// Name: softinfo.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+struct SoftInfo {
+ bool soft;
+ float blend;
+ float joint_strength;
+ float angular_damping;
+ int ik_bone;
+ SoftInfo():soft(false), blend(0.0f), joint_strength(0.0f), angular_damping(0.0f) {}
+};
diff --git a/Source/Objects/terrainobject.cpp b/Source/Objects/terrainobject.cpp
new file mode 100644
index 00000000..7264dcdf
--- /dev/null
+++ b/Source/Objects/terrainobject.cpp
@@ -0,0 +1,629 @@
+//-----------------------------------------------------------------------------
+// Name: terrainobject.cpp
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: The terrain object is an entity representing some terrain
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "terrainobject.h"
+
+#include <Graphics/camera.h>
+#include <Graphics/graphics.h>
+#include <Graphics/textures.h>
+#include <Graphics/shaders.h>
+#include <Graphics/sky.h>
+
+#include <Physics/bulletworld.h>
+#include <Physics/bulletobject.h>
+
+#include <Internal/common.h>
+#include <Internal/datemodified.h>
+#include <Internal/filesystem.h>
+#include <Internal/profiler.h>
+#include <Internal/timer.h>
+
+#include <Main/engine.h>
+#include <Main/scenegraph.h>
+
+#include <Timing/timingevent.h>
+#include <Timing/intel_gl_perf.h>
+
+#include <Compat/fileio.h>
+#include <Math/vec3math.h>
+#include <Objects/lightvolume.h>
+#include <Wrappers/glm.h>
+#include <Utility/compiler_macros.h>
+
+#include <SDL.h>
+
+extern bool g_simple_shadows;
+extern bool g_level_shadows;
+extern char* global_shader_suffix;
+extern bool g_no_decals;
+extern Timer game_timer;
+extern bool g_no_reflection_capture;
+
+extern bool g_debug_runtime_disable_terrain_object_draw_depth_map;
+extern bool g_debug_runtime_disable_terrain_object_draw_terrain;
+extern bool g_debug_runtime_disable_terrain_object_pre_draw_camera;
+
+static void DrawPatchModel(Model& model, Graphics* graphics, Shaders* shaders, int shader, bool use_tesselation){
+ PROFILER_GPU_ZONE(g_profiler_ctx, "DrawPatchModel");
+ if(!model.vbo_loaded){
+ PROFILER_ZONE(g_profiler_ctx, "createVBO");
+ model.createVBO();
+ }
+ model.VBO_faces.Bind();
+ int attrib_ids[4];
+ for(int i=0; i<4; ++i){
+ const char* attrib_str;
+ int num_el;
+ VBOContainer* vbo;
+ switch(i){
+ case 0:
+ attrib_str = "vertex_attrib";
+ num_el = 3;
+ vbo = &model.VBO_vertices;
+ break;
+ case 1:
+ attrib_str = "tangent_attrib";
+ num_el = 3;
+ vbo = &model.VBO_tangents;
+ break;
+ case 2:
+ attrib_str = "tex_coord_attrib";
+ num_el = 2;
+ vbo = &model.VBO_tex_coords;
+ break;
+ case 3:
+ attrib_str = "detail_tex_coord";
+ num_el = 2;
+ vbo = &model.VBO_tex_coords2;
+ break;
+ default:
+ __builtin_unreachable();
+ break;
+ }
+ CHECK_GL_ERROR();
+ vbo->Bind();
+ CHECK_GL_ERROR();
+ attrib_ids[i] = shaders->returnShaderAttrib(attrib_str, shader);
+ if( attrib_ids[i] != -1 )
+ {
+ CHECK_GL_ERROR();
+ graphics->EnableVertexAttribArray(attrib_ids[i]);
+ CHECK_GL_ERROR();
+ glVertexAttribPointer(attrib_ids[i], num_el, GL_FLOAT, false, num_el*sizeof(GLfloat), 0);
+ CHECK_GL_ERROR();
+ }
+ }
+ CHECK_GL_ERROR();
+ graphics->DrawElements(use_tesselation?GL_PATCHES:GL_TRIANGLES, (unsigned int)model.faces.size(), GL_UNSIGNED_INT, 0);
+ CHECK_GL_ERROR();
+ graphics->ResetVertexAttribArrays();
+ CHECK_GL_ERROR();
+ graphics->BindArrayVBO(0);
+ graphics->BindElementVBO(0);
+}
+
+//Check for collision with a line
+int TerrainObject::lineCheck(const vec3 &start, const vec3 &end, vec3 *point, vec3 *normal) {
+ if(bullet_object_){
+ bool hit = scenegraph_->bullet_world_->CheckRayCollisionObj(
+ start, end, *bullet_object_, point, normal) != -1;
+ if(hit) {
+ return 0;
+ } else {
+ return -1;
+ }
+ }
+ return -1;
+}
+
+void TerrainObject::ReceiveObjectMessageVAList( OBJECT_MSG::Type type, va_list args ) {
+ switch(type){
+ case OBJECT_MSG::LIGHTING_CHANGED:
+ terrain_.BakeTerrainTexture(terrain_.framebuffer, scenegraph_->sky->GetSpecularCubeMapTexture());
+ break;
+ default:
+ Object::ReceiveObjectMessageVAList(type, args);
+ break;
+ }
+}
+
+void TerrainObject::PreDrawFrame(float curr_game_time) {
+ CHECK_GL_ERROR();
+ terrain_.GLInit(scenegraph_->sky);
+ CHECK_GL_ERROR();
+ if(scenegraph_->sky->live_updated){
+ CHECK_GL_ERROR();
+ terrain_.BakeTerrainTexture(terrain_.framebuffer, scenegraph_->sky->GetSpecularCubeMapTexture());
+ scenegraph_->sky->BakeSecondPass(&terrain_.baked_texture_ref);
+ scenegraph_->sky->live_updated = false;
+ CHECK_GL_ERROR();
+ }
+}
+
+void TerrainObject::PreDrawCamera(float curr_game_time) {
+ if (g_debug_runtime_disable_terrain_object_pre_draw_camera) {
+ return;
+ }
+
+ static const mat4 identity;
+ for(std::list<DetailObjectSurface*>::iterator iter = terrain_.detail_object_surfaces.begin();
+ iter != terrain_.detail_object_surfaces.end(); ++iter)
+ {
+ (*iter)->PreDrawCamera(identity);
+ }
+}
+
+//Draw terrain
+void TerrainObject::Draw() {
+ if(Graphics::Instance()->queued_screenshot && Graphics::Instance()->screenshot_mode == Graphics::kTransparentGameplay){
+ return;
+ }
+ DrawTerrain();
+
+ if(!terrain_info_.minimal) {
+ static const mat4 identity;
+ for(std::list<DetailObjectSurface*>::iterator iter = terrain_.detail_object_surfaces.begin();
+ iter != terrain_.detail_object_surfaces.end(); ++iter)
+ {
+ (*iter)->Draw(identity, DetailObjectSurface::TERRAIN, vec3(1.0f), scenegraph_->sky->GetSpecularCubeMapTexture(), &scenegraph_->light_probe_collection, scenegraph_);
+ }
+ }
+}
+
+bool CheckBoxAgainstPlanes(const vec3& start, const vec3& end,
+ const vec4* cull_planes, int num_cull_planes)
+{
+ vec3 point;
+ for(int p = 0; p < num_cull_planes; ++p) {
+ int in_count = 8;
+ for (int i = 0; i < 8; ++i) {
+ if(i<4){
+ point[0]=start[0];
+ } else {
+ point[0]=end[0];
+ }
+ if(i%4<2) {
+ point[1]=start[1];
+ } else {
+ point[1]=end[1];
+ }
+ if(i%2==0){
+ point[2]=start[2];
+ } else {
+ point[2]=end[2];
+ }
+ // test this point against the planes
+ if( cull_planes[p][0] * point[0] +
+ cull_planes[p][1] * point[1] +
+ cull_planes[p][2] * point[2] +
+ cull_planes[p][3] < 0)
+ {
+ in_count--;
+ }
+ }
+
+ // were all the points outside of plane p?
+ if(in_count == 0) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void TerrainObject::DrawDepthMap(const mat4& proj_view_matrix, const vec4* cull_planes, int num_cull_planes, Object::DrawType draw_type) {
+ if (g_debug_runtime_disable_terrain_object_draw_depth_map) {
+ return;
+ }
+
+ if(terrain_info_.minimal || preview_mode)
+ return;
+ PROFILER_GPU_ZONE(g_profiler_ctx, "TerrainObject::DrawDepthMap");
+ Graphics *graphics = Graphics::Instance();
+ Shaders* shaders = Shaders::Instance();
+ //Textures* textures = Textures::Instance();
+ //Camera* cam = ActiveCameras::Get();
+ graphics->setGLState(gl_state_);
+ mat4 t = GetTransform(); // this seems to always be identity
+ vec4 test(1.0, 1.0, 1.0, 1.0);
+ LOG_ASSERT(t * test == test);
+
+ bool use_tesselation = false;// GLEW_ARB_tessellation_shader;
+
+ const int kShaderStrSize = 1024;
+ char buf[2][kShaderStrSize];
+ char* shader_str[2] = {buf[0], buf[1]};
+ FormatString(shader_str[0], kShaderStrSize, "envobject #TERRAIN #DEPTH_ONLY");
+ if (draw_type == kDrawAllShadowCascades) {
+ FormatString(shader_str[1], kShaderStrSize, "%s #SHADOW_CASCADE", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(use_tesselation){
+ FormatString(shader_str[1], kShaderStrSize, "%s #FRACTAL_DISPLACE", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ int shader = shaders->returnProgram(shader_str[0], use_tesselation?Shaders::kTesselation:Shaders::kNone);
+
+ shaders->setProgram(shader);
+ shaders->SetUniformMat4("projection_view_mat", proj_view_matrix);
+ shaders->SetUniformFloat("time",game_timer.GetRenderTime());
+ shaders->SetUniformVec3("cam_pos",ActiveCameras::Get()->GetPos());
+ const vec3 translation = GetTranslation();
+
+ int vert_attrib_id = shaders->returnShaderAttrib("vertex_attrib", shader);
+ graphics->EnableVertexAttribArray(vert_attrib_id);
+ for(std::list<Model>::iterator iter = terrain_.terrain_patches.begin(); iter != terrain_.terrain_patches.end(); ++iter) {
+ Model& patch = (*iter);
+ const vec3 adjusted_min = patch.min_coords + translation;
+ const vec3 adjusted_max = patch.max_coords + translation;
+ if(CheckBoxAgainstPlanes(adjusted_min, adjusted_max, cull_planes, num_cull_planes)) {
+ if(!patch.vbo_loaded){
+ patch.createVBO();
+ }
+ patch.VBO_faces.Bind();
+ patch.VBO_vertices.Bind();
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, 3*sizeof(GLfloat), 0);
+ graphics->DrawElements(use_tesselation?GL_PATCHES:GL_TRIANGLES, (unsigned int) patch.faces.size(), GL_UNSIGNED_INT, 0);
+ }
+ }
+ for(std::list<Model>::iterator iter = terrain_.edge_terrain_patches.begin(); iter != terrain_.edge_terrain_patches.end(); ++iter) {
+ Model& patch = (*iter);
+ const vec3 adjusted_min = patch.min_coords + translation;
+ const vec3 adjusted_max = patch.max_coords + translation;
+ if(CheckBoxAgainstPlanes(adjusted_min, adjusted_max, cull_planes, num_cull_planes)) {
+ if(!patch.vbo_loaded){
+ patch.createVBO();
+ }
+ patch.VBO_faces.Bind();
+ patch.VBO_vertices.Bind();
+ glVertexAttribPointer(vert_attrib_id, 3, GL_FLOAT, false, 3*sizeof(GLfloat), 0);
+ graphics->DrawElements(use_tesselation?GL_PATCHES:GL_TRIANGLES, (unsigned int) patch.faces.size(), GL_UNSIGNED_INT, 0);
+ }
+ }
+ graphics->ResetVertexAttribArrays();
+}
+
+bool TerrainObject::Initialize() {
+ terrain_.level_name = scenegraph_->level_name_;
+ PROFILER_ZONE(g_profiler_ctx, "CalcDetailTextures");
+ terrain_.CalcDetailTextures();
+ return true;
+}
+
+void TerrainObject::GetShaderNames(std::map<std::string, int>& preload_shaders) {
+ const int kShaderStrSize = 1024;
+ char buf[2][kShaderStrSize];
+ char* shader_str[2] = {buf[0], buf[1]};
+ FormatString(shader_str[0], kShaderStrSize, "envobject #TERRAIN #DETAILMAP4 %s", shader_extra.c_str());
+ preload_shaders[shader_str[0]] = SceneGraph::kPreloadTypeAll;
+ preload_shaders["envobject #TERRAIN #DEPTH_ONLY"] = 0;
+ preload_shaders["envobject #TERRAIN #DEPTH_ONLY #SHADOW_CASCADE"] = 0;
+
+ terrain_.GetShaderNames(preload_shaders);
+}
+
+TerrainObject::TerrainObject(const TerrainInfo& _terrain_info):
+ bullet_object_(NULL),
+ preview_mode(false)
+{
+ shader_extra = _terrain_info.shader_extra;
+ terrain_info_ = _terrain_info;
+ added_to_physics_scene_ = false;
+ terrain_.SetColorTexture(terrain_info_.colormap.c_str());
+ Textures::Instance()->setWrap(GL_REPEAT);
+ terrain_.weight_perturb_ref = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>("Data/Textures/weight_perturb.tga");
+ terrain_.SetDetailTextures(terrain_info_.detail_map_info);
+ terrain_.SetWeightTexture(terrain_info_.weightmap.c_str());
+ if(_terrain_info.minimal)
+ terrain_.LoadMinimal(terrain_info_.heightmap.c_str(), terrain_info_.model_override);
+ else
+ terrain_.Load(terrain_info_.heightmap.c_str(), terrain_info_.model_override);
+ terrain_.SetDetailObjectLayers(terrain_info_.detail_object_info);
+ collidable=true;
+ transparent=false;
+ gl_state_.depth_test = true;
+ gl_state_.cull_face = true;
+ gl_state_.depth_write = true;
+ gl_state_.blend = false;
+ edge_gl_state_.depth_test = true;
+ edge_gl_state_.cull_face = true;
+ edge_gl_state_.depth_write = true;
+ edge_gl_state_.blend = true;
+ permission_flags = 0;
+ exclude_from_undo = true;
+ exclude_from_save = true;
+}
+
+TerrainObject::~TerrainObject() {
+ if(added_to_physics_scene_)
+ scenegraph_->bullet_world_->RemoveStaticObject(&bullet_object_);
+}
+
+void TerrainObject::PreparePhysicsMesh() {
+ static const bool kCachePhysicsShape = false;
+ if(!added_to_physics_scene_){
+ btBvhTriangleMeshShape* cached_shape = NULL;
+ const char* cache_path = "terrain_serialize.bullet";
+ if(kCachePhysicsShape){
+ /*char abs_path[kPathSize];
+ if(FindFilePath(cache_path, abs_path, kPathSize, kDataPaths|kModPaths|kWriteDir|kModWriteDirs) != -1) {
+ btBulletWorldImporter import(0);//don't store info into the world
+ PROFILER_ZONE(g_profiler_ctx, "Loading cache file");
+ if (import.loadFile(abs_path)){
+ int numShape = import.getNumCollisionShapes();
+ if (numShape) {
+ cached_shape = (btBvhTriangleMeshShape*)import.getCollisionShapeByIndex(0);
+ }
+ }
+ }*/
+ }
+ if(cached_shape) {
+ PROFILER_ZONE(g_profiler_ctx, "Adding cached shape to physics scene");
+ SharedShapePtr shape;
+ shape.reset(cached_shape);
+ BulletObject* obj = scenegraph_->bullet_world_->CreateRigidBody(shape, 0.0f, BW_NO_FLAGS);
+ obj->body->setCollisionFlags(obj->body->getCollisionFlags() | btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK);
+ bullet_object_ = obj;
+ } else {
+ PROFILER_ZONE(g_profiler_ctx, "Creating new static mesh");
+ bullet_object_ = scenegraph_->bullet_world_->CreateStaticMesh(&terrain_.GetModel(), -1, BW_NO_FLAGS);
+
+ if(kCachePhysicsShape){
+ /*PROFILER_ZONE(g_profiler_ctx, "Serializing cached mesh to disk");
+ int maxSerializeBufferSize = 1024*1024*10;
+ btDefaultSerializer* serializer = new btDefaultSerializer(maxSerializeBufferSize);
+
+ serializer->startSerialization();
+ bullet_object_->shape->serializeSingleShape(serializer);
+ serializer->finishSerialization();
+
+ char path[kPathSize];
+ FormatString(path, kPathSize, "%s%s", GetWritePath(CoreGameModID).c_str(), cache_path);
+ FILE* file = my_fopen(path, "wb");
+ fwrite(serializer->getBufferPointer(),serializer->getCurrentBufferSize(),1, file);
+ fclose(file);
+
+ delete serializer;*/
+ }
+ }
+ bullet_object_->owner_object = this;
+ added_to_physics_scene_ = true;
+ }
+}
+
+const Model* TerrainObject::GetModel() const {
+ return &terrain_.GetModel();
+}
+
+void TerrainObject::HandleMaterialEvent( const std::string &the_event, const vec3 &event_pos, int* tri ) {
+ terrain_.HandleMaterialEvent(the_event, event_pos, tri);
+}
+
+const MaterialEvent& TerrainObject::GetMaterialEvent( const std::string &the_event, const vec3 &event_pos, int* tri ) {
+ MaterialRef material_ref = terrain_.GetMaterialAtPoint(event_pos, tri);
+ return material_ref->GetEvent(the_event);
+}
+
+const MaterialEvent& TerrainObject::GetMaterialEvent( const std::string &the_event, const vec3 &event_pos, const std::string &mod, int* tri ) {
+ MaterialRef material_ref = terrain_.GetMaterialAtPoint(event_pos, tri);
+ return material_ref->GetEvent(the_event, mod);
+}
+
+const MaterialDecal& TerrainObject::GetMaterialDecal( const std::string &type, const vec3 &pos, int* tri ) {
+ MaterialRef material_ref = terrain_.GetMaterialAtPoint(pos, tri);
+ return material_ref->GetDecal(type);
+}
+
+const MaterialParticle& TerrainObject::GetMaterialParticle( const std::string &type, const vec3 &pos, int* tri ) {
+ MaterialRef material_ref = terrain_.GetMaterialAtPoint(pos, tri);
+ return material_ref->GetParticle(type);
+}
+
+void TerrainObject::GetDisplayName(char* buf, int buf_size) {
+ if( GetName().empty() ) {
+ FormatString(buf, buf_size, "%d: Terrain: %s", GetID(), terrain_info_.heightmap.c_str());
+ } else {
+ FormatString(buf, buf_size, "%s: Terrain: %s", GetName().c_str(), terrain_info_.heightmap.c_str());
+ }
+}
+
+vec3 TerrainObject::GetColorAtPoint( const vec3 &pos, int *tri ) {
+ return terrain_.SampleColorMapAtPoint(pos, tri);
+}
+
+MaterialRef TerrainObject::GetMaterial( const vec3 &pos, int* tri ) {
+ return terrain_.GetMaterialAtPoint(pos, tri);
+}
+
+void TerrainObject::DrawTerrain() {
+ if (g_debug_runtime_disable_terrain_object_draw_terrain) {
+ return;
+ }
+
+ Graphics *graphics = Graphics::Instance();
+ graphics->setGLState(gl_state_);
+ glDisable( GL_SAMPLE_ALPHA_TO_COVERAGE );
+
+ Shaders* shaders = Shaders::Instance();
+ Textures* textures = Textures::Instance();
+ Camera* cam = ActiveCameras::Get();
+
+ mat4 projection_view_mat = cam->GetProjMatrix() * cam->GetViewMatrix();
+
+ const int kShaderStrSize = 1024;
+ char buf[2][kShaderStrSize];
+ char* shader_str[2] = {buf[0], buf[1]};
+
+ // Tessellation is ignored when preloading shaders because the use-case is unclear.
+ // Where is it set? Is it per-object? Is it set once?
+ bool use_tesselation = false;//GLEW_ARB_tessellation_shader;
+
+ FormatString(shader_str[0], kShaderStrSize, "envobject #TERRAIN #DETAILMAP4 %s %s", shader_extra.c_str(), global_shader_suffix);
+ if(preview_mode) {
+ FormatString(shader_str[1], kShaderStrSize, "%s #HALFTONE_STIPPLE", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ if(use_tesselation){
+ FormatString(shader_str[1], kShaderStrSize, "%s #FRACTAL_DISPLACE", shader_str[0]);
+ std::swap(shader_str[0], shader_str[1]);
+ }
+ int shader = shaders->returnProgram(shader_str[0], use_tesselation?Shaders::kTesselation:Shaders::kNone);
+
+ shaders->setProgram(shader);
+
+ vec4 temp(0.0f, 1.0f, 2.0f, 3.0f);
+ shaders->SetUniformVec4("detail_color_indices", temp);
+
+ textures->bindTexture(terrain_.detail_texture_weights, 5);
+ textures->bindTexture(terrain_.color_texture_ref, 0);
+ textures->bindTexture(scenegraph_->sky->GetSpecularCubeMapTexture(), 2);
+ if(terrain_.normal_map_ref.valid()) {
+ textures->bindTexture(terrain_.normal_map_ref, 1);
+ } else {
+ textures->bindBlankNormalTexture(1);
+ }
+ if(g_simple_shadows || !g_level_shadows){
+ textures->bindTexture(graphics->static_shadow_depth_ref, 4);
+ } else {
+ textures->bindTexture(graphics->cascade_shadow_depth_ref, 4);
+ }
+ textures->bindTexture(terrain_.detail_texture_ref, 6);
+ textures->bindTexture(terrain_.detail_normal_texture_ref, 7);
+ textures->bindTexture(terrain_.weight_perturb_ref, 14);
+ if(scenegraph_->light_probe_collection.light_volume_enabled && scenegraph_->light_probe_collection.ambient_3d_tex.valid()){
+ textures->bindTexture(scenegraph_->light_probe_collection.ambient_3d_tex, 16);
+ }
+ textures->bindTexture(graphics->screen_color_tex, 17);
+ textures->bindTexture(graphics->screen_depth_tex, 18);
+
+ shaders->SetUniformInt("weight_component", 0);
+ shaders->SetUniformVec4("avg_color0",terrain_.detail_texture_color_srgb[0]);
+ shaders->SetUniformVec4("avg_color1",terrain_.detail_texture_color_srgb[1]);
+ shaders->SetUniformVec4("avg_color2",terrain_.detail_texture_color_srgb[2]);
+ shaders->SetUniformVec4("avg_color3",terrain_.detail_texture_color_srgb[3]);
+ shaders->SetUniformVec3("ws_light",scenegraph_->primary_light.pos);
+ shaders->SetUniformVec4("primary_light_color",vec4(scenegraph_->primary_light.color, scenegraph_->primary_light.intensity));
+ shaders->SetUniformFloat("time",game_timer.GetRenderTime());
+ shaders->SetUniformVec3("cam_pos",cam->GetPos());
+ shaders->SetUniformMat4("projection_view_mat", projection_view_mat);
+ shaders->SetUniformFloat("haze_mult", scenegraph_->haze_mult);
+ std::vector<mat4> shadow_matrix;
+ shadow_matrix.resize(4);
+ for(int i=0; i<4; ++i){
+ shadow_matrix[i] = cam->biasMatrix * graphics->cascade_shadow_mat[i];
+ }
+ if(g_simple_shadows || !g_level_shadows){
+ shadow_matrix[3] = cam->biasMatrix * graphics->simple_shadow_mat;
+ }
+
+ shaders->SetUniformInt("reflection_capture_num", (int) scenegraph_->ref_cap_matrix.size());
+ if(!scenegraph_->ref_cap_matrix.empty()){
+ assert(!scenegraph_->ref_cap_matrix_inverse.empty());
+ shaders->SetUniformMat4Array("reflection_capture_matrix", scenegraph_->ref_cap_matrix);
+ shaders->SetUniformMat4Array("reflection_capture_matrix_inverse", scenegraph_->ref_cap_matrix_inverse);
+ }
+
+ std::vector<mat4> light_volume_matrix;
+ std::vector<mat4> light_volume_matrix_inverse;
+ for(size_t i=0, len=scenegraph_->light_volume_objects_.size(); i<len; ++i){
+ Object* obj = scenegraph_->light_volume_objects_[i];
+ const mat4 &mat = obj->GetTransform();
+ light_volume_matrix.push_back(mat);
+ light_volume_matrix_inverse.push_back(invert(mat));
+ }
+ shaders->SetUniformInt("light_volume_num", (int) light_volume_matrix.size());
+ if(!light_volume_matrix.empty()){
+ assert(!light_volume_matrix_inverse.empty());
+ shaders->SetUniformMat4Array("light_volume_matrix", light_volume_matrix);
+ shaders->SetUniformMat4Array("light_volume_matrix_inverse", light_volume_matrix_inverse);
+ }
+
+
+ if( g_no_reflection_capture == false) {
+ textures->bindTexture(scenegraph_->cubemaps, 19);
+ }
+
+ shaders->SetUniformInt("num_tetrahedra", scenegraph_->light_probe_collection.ShaderNumTetrahedra());
+ shaders->SetUniformInt("num_light_probes", scenegraph_->light_probe_collection.ShaderNumLightProbes());
+ shaders->SetUniformVec3("grid_bounds_min", scenegraph_->light_probe_collection.grid_lookup.bounds[0]);
+ shaders->SetUniformVec3("grid_bounds_max", scenegraph_->light_probe_collection.grid_lookup.bounds[1]);
+ shaders->SetUniformInt("subdivisions_x", scenegraph_->light_probe_collection.grid_lookup.subdivisions[0]);
+ shaders->SetUniformInt("subdivisions_y", scenegraph_->light_probe_collection.grid_lookup.subdivisions[1]);
+ shaders->SetUniformInt("subdivisions_z", scenegraph_->light_probe_collection.grid_lookup.subdivisions[2]);
+ shaders->SetUniformInt(shaders->GetTexUniform(TEX_AMBIENT_COLOR_BUFFER), TEX_AMBIENT_COLOR_BUFFER);
+ shaders->SetUniformInt(shaders->GetTexUniform(TEX_AMBIENT_GRID_DATA), TEX_AMBIENT_GRID_DATA);
+ if(scenegraph_->light_probe_collection.light_probe_buffer_object_id != -1){
+ glBindBuffer(GL_TEXTURE_BUFFER, scenegraph_->light_probe_collection.light_probe_buffer_object_id);
+ }
+
+ scenegraph_->BindDecals(shader);
+ scenegraph_->BindLights(shader);
+ shaders->SetUniformMat4Array("shadow_matrix",shadow_matrix);
+ const vec3 translation = GetTranslation();
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw opaque terrain patches");
+ for(std::list<Model>::iterator iter = terrain_.terrain_patches.begin(); iter != terrain_.terrain_patches.end(); ++iter) {
+ Model& patch = (*iter);
+ const vec3 adjusted_min = patch.min_coords + translation;
+ const vec3 adjusted_max = patch.max_coords + translation;
+ if(ActiveCameras::Get()->checkBoxInFrustum(adjusted_min,
+ adjusted_max))
+ {
+ DrawPatchModel(patch, graphics, shaders, shader, use_tesselation);
+ }
+ }
+ }
+ {
+ PROFILER_GPU_ZONE(g_profiler_ctx, "Draw edge terrain patches");
+ graphics->setGLState(edge_gl_state_);
+ for(std::list<Model>::iterator iter = terrain_.edge_terrain_patches.begin(); iter != terrain_.edge_terrain_patches.end(); ++iter) {
+ Model& patch = (*iter);
+ const vec3 adjusted_min = patch.min_coords + translation;
+ const vec3 adjusted_max = patch.max_coords + translation;
+ if(ActiveCameras::Get()->checkBoxInFrustum(adjusted_min,
+ adjusted_max))
+ {
+ DrawPatchModel(patch, graphics, shaders, shader, use_tesselation);
+ }
+ }
+ }
+}
+
+const TerrainInfo& TerrainObject::terrain_info() const {
+ return terrain_info_;
+}
+
+void TerrainObject::SetTerrainColorTexture(const char* path) {
+ terrain_.SetColorTexture(path);
+ terrain_info_.colormap = path;
+}
+
+void TerrainObject::SetTerrainWeightTexture(const char* path) {
+ terrain_.SetWeightTexture(path);
+ terrain_info_.weightmap = path;
+}
+
+void TerrainObject::SetTerrainDetailTextures(const std::vector<DetailMapInfo>& detail_map_info) {
+ terrain_.SetDetailTextures(detail_map_info);
+ terrain_.CalcDetailTextures();
+ terrain_info_.detail_map_info = detail_map_info;
+}
diff --git a/Source/Objects/terrainobject.h b/Source/Objects/terrainobject.h
new file mode 100644
index 00000000..6ecc192e
--- /dev/null
+++ b/Source/Objects/terrainobject.h
@@ -0,0 +1,87 @@
+//-----------------------------------------------------------------------------
+// Name: terrainobject.h
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: The terrain object is an entity representing some terrain
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Graphics/terrain.h>
+#include <Graphics/glstate.h>
+#include <Graphics/textureref.h>
+
+#include <Objects/object.h>
+
+#include <Internal/levelxml.h>
+
+//-----------------------------------------------------------------------------
+// Class Definition
+//-----------------------------------------------------------------------------
+
+struct MaterialEvent;
+struct MaterialDecal;
+class BulletObject;
+
+class TerrainObject: public Object {
+ public:
+ const TerrainInfo& terrain_info() const;
+ virtual EntityType GetType() const { return _terrain_type; }
+
+ TerrainObject(const TerrainInfo &terrain_info);
+ ~TerrainObject();
+ virtual void HandleMaterialEvent( const std::string &the_event, const vec3 &event_pos, int* tri );
+ virtual void PreDrawCamera(float curr_game_time);
+ virtual void PreDrawFrame(float curr_game_time);
+ virtual void Draw();
+ virtual void DrawDepthMap(const mat4& proj_view_matrix, const vec4* cull_planes, int num_cull_planes, Object::DrawType draw_type);
+ virtual int lineCheck(const vec3 &start, const vec3 &end, vec3 *point, vec3 *normal=0);
+ virtual bool Initialize();
+ virtual void GetShaderNames(std::map<std::string, int>& preload_shaders);
+ virtual const MaterialEvent& GetMaterialEvent( const std::string &the_event, const vec3 &event_pos, int* tri );
+ virtual const MaterialEvent& GetMaterialEvent( const std::string &the_event, const vec3 &event_pos, const std::string &mod, int* tri );
+ virtual const MaterialDecal& GetMaterialDecal( const std::string &type, const vec3 &pos, int* tri );
+ virtual const MaterialParticle& GetMaterialParticle( const std::string &type, const vec3 &pos, int* tri );
+ virtual void GetDisplayName(char* buf, int buf_size);
+ virtual MaterialRef GetMaterial( const vec3 &pos, int* tri = NULL );
+ virtual vec3 GetColorAtPoint( const vec3 &pos, int* tri );
+
+ void PreparePhysicsMesh();
+ const Model* GetModel() const;
+ void DrawTerrain();
+
+ bool preview_mode;
+
+ virtual void ReceiveObjectMessageVAList( OBJECT_MSG::Type type, va_list args );
+ BulletObject* bullet_object_;
+ Terrain terrain_;
+
+ void SetTerrainColorTexture(const char* path);
+ void SetTerrainWeightTexture(const char* path);
+ void SetTerrainDetailTextures(const std::vector<DetailMapInfo>& detail_map_info);
+private:
+ GLState gl_state_;
+ GLState edge_gl_state_;
+ int bullet_entry_;
+ bool added_to_physics_scene_;
+ std::string shader_extra;
+
+ TerrainInfo terrain_info_;
+
+};
diff --git a/Source/Ogda/Builders/actionbase.h b/Source/Ogda/Builders/actionbase.h
new file mode 100644
index 00000000..c900993a
--- /dev/null
+++ b/Source/Ogda/Builders/actionbase.h
@@ -0,0 +1,40 @@
+//-----------------------------------------------------------------------------
+// Name: actionbase.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Ogda/manifestresult.h>
+
+#include <vector>
+
+class JobHandler;
+class Item;
+
+class ActionBase
+{
+ public:
+ virtual ManifestResult Run(const JobHandler& jh, const Item& y) = 0;
+ virtual const char* GetName() const = 0;
+ virtual const char* GetVersion() const = 0;
+ virtual bool RunEvenOnIdenticalSource() const = 0;
+ virtual bool StoreResultInDatabase() const = 0;
+};
diff --git a/Source/Ogda/Builders/builder.cpp b/Source/Ogda/Builders/builder.cpp
new file mode 100644
index 00000000..49584ec6
--- /dev/null
+++ b/Source/Ogda/Builders/builder.cpp
@@ -0,0 +1,71 @@
+//-----------------------------------------------------------------------------
+// Name: builder.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "builder.h"
+#include <Utility/strings.h>
+
+Builder::Builder( ActionBase* action, const std::string& path_ending_, const std::string& type_pattern_re )
+: action(action), path_ending(path_ending_)
+{
+ try
+ {
+ type_pattern->Compile(type_pattern_re.c_str());
+ }
+ catch( const TRexParseException& pe )
+ {
+ LOGE << "Failed to compile the type_pattern regex " << type_pattern_re << " reason: " << pe.desc << std::endl;
+ }
+}
+
+bool Builder::IsMatch(const Item& t)
+{
+ if( endswith(t.GetPath().c_str(),path_ending.c_str()) && type_pattern->Match(t.type.c_str()) )
+ return true;
+ else
+ return false;
+}
+
+ManifestResult Builder::Run(const JobHandler& jh, const Item& t)
+{
+ return action->Run(jh, t);
+}
+
+std::string Builder::GetBuilderName() const
+{
+ return std::string(action.GetConst().GetName());
+}
+
+std::string Builder::GetBuilderVersion() const
+{
+ return std::string(action.GetConst().GetVersion());
+}
+
+bool Builder::RunEvenOnIdenticalSource()
+{
+ return action->RunEvenOnIdenticalSource();
+}
+
+bool Builder::StoreResultInDatabase()
+{
+ return action->StoreResultInDatabase();
+}
+
diff --git a/Source/Ogda/Builders/builder.h b/Source/Ogda/Builders/builder.h
new file mode 100644
index 00000000..d384f233
--- /dev/null
+++ b/Source/Ogda/Builders/builder.h
@@ -0,0 +1,47 @@
+//-----------------------------------------------------------------------------
+// Name: builder.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+#include "actionbase.h"
+
+#include <Logging/logdata.h>
+#include <Ogda/item.h>
+#include <Internal/referencecounter.h>
+
+#include <trex/trex.h>
+
+class Builder
+{
+public:
+ Builder( ActionBase* action, const std::string& path_ending_, const std::string& type_pattern_re );
+ bool IsMatch(const Item& t);
+ bool RunEvenOnIdenticalSource();
+ bool StoreResultInDatabase();
+
+ ManifestResult Run(const JobHandler& jh, const Item& t);
+ std::string GetBuilderName() const;
+ std::string GetBuilderVersion() const;
+private:
+ std::string path_ending;
+ ReferenceCounter<TRexpp> type_pattern;
+ ReferenceCounter<ActionBase> action;
+};
diff --git a/Source/Ogda/Builders/builderfactory.cpp b/Source/Ogda/Builders/builderfactory.cpp
new file mode 100644
index 00000000..2b15d72d
--- /dev/null
+++ b/Source/Ogda/Builders/builderfactory.cpp
@@ -0,0 +1,90 @@
+//-----------------------------------------------------------------------------
+// Name: builderfactory.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "builderfactory.h"
+#include "builder.h"
+
+#include "copyaction.h"
+#include "voidaction.h"
+#include "dxt5action.h"
+#include "crunchaction.h"
+
+BuilderFactory::BuilderFactory()
+{
+ actions.push_back( new ActionFactory<CopyAction>() );
+ actions.push_back( new ActionFactory<DXT5Action>() );
+ actions.push_back( new ActionFactory<CrunchAction>() );
+ actions.push_back( new ActionFactory<VoidAction>() );
+}
+
+BuilderFactory::~BuilderFactory()
+{
+ std::vector<ActionFactoryBase*>::iterator factoryit;
+
+ for( factoryit = actions.begin(); factoryit != actions.end(); factoryit++ )
+ {
+ delete *factoryit;
+ }
+
+ actions.clear();
+}
+
+bool BuilderFactory::HasBuilder( const std::string& builder )
+{
+ std::vector<ActionFactoryBase*>::iterator factoryit;
+ for( factoryit = actions.begin(); factoryit != actions.end(); factoryit++ )
+ {
+ if( (*factoryit)->GetActionName() == builder )
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+Builder BuilderFactory::CreateBuilder( const std::string& builder, const std::string& ending, const std::string& type_pattern_re )
+{
+ std::vector<ActionFactoryBase*>::iterator factoryit;
+ for( factoryit = actions.begin(); factoryit != actions.end(); factoryit++ )
+ {
+ if( (*factoryit)->GetActionName() == builder )
+ {
+ return Builder((*factoryit)->NewInstance(), ending, type_pattern_re);
+ }
+ }
+ LOGE << "Unable to find builder matching name " << builder << std::endl;
+ return Builder( new VoidAction(), ending, type_pattern_re );
+}
+
+bool BuilderFactory::StoreResultInDatabase( std::string builder )
+{
+ std::vector<ActionFactoryBase*>::iterator factoryit;
+ for( factoryit = actions.begin(); factoryit != actions.end(); factoryit++ )
+ {
+ if( (*factoryit)->GetActionName() == builder )
+ {
+ return (*factoryit)->StoreResultInDatabase();
+ }
+ }
+ LOGW << "Couldn't find builder with name " << builder << std::endl;
+ return false;
+}
diff --git a/Source/Ogda/Builders/builderfactory.h b/Source/Ogda/Builders/builderfactory.h
new file mode 100644
index 00000000..a0767663
--- /dev/null
+++ b/Source/Ogda/Builders/builderfactory.h
@@ -0,0 +1,65 @@
+//-----------------------------------------------------------------------------
+// Name: builderfactory.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+#include "builder.h"
+
+#include <vector>
+
+class BuilderFactory
+{
+private:
+ class ActionFactoryBase
+ {
+ public:
+ virtual ActionBase* NewInstance() = 0;
+ virtual std::string GetActionName() = 0;
+ virtual bool StoreResultInDatabase() = 0;
+ };
+
+ template<class Action>
+ class ActionFactory : public ActionFactoryBase
+ {
+ virtual ActionBase* NewInstance()
+ {
+ return new Action();
+ }
+
+ virtual std::string GetActionName()
+ {
+ return std::string(Action().GetName());
+ }
+
+ virtual bool StoreResultInDatabase()
+ {
+ return Action().StoreResultInDatabase();
+ }
+ };
+
+ std::vector<ActionFactoryBase*> actions;
+public:
+ BuilderFactory();
+ ~BuilderFactory();
+ bool HasBuilder( const std::string& builder );
+ Builder CreateBuilder( const std::string& builder, const std::string& ending, const std::string& type_pattern_re );
+ bool StoreResultInDatabase( std::string builder );
+};
diff --git a/Source/Ogda/Builders/copyaction.cpp b/Source/Ogda/Builders/copyaction.cpp
new file mode 100644
index 00000000..457dff9f
--- /dev/null
+++ b/Source/Ogda/Builders/copyaction.cpp
@@ -0,0 +1,49 @@
+//-----------------------------------------------------------------------------
+// Name: copyaction.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "copyaction.h"
+
+#include <Ogda/item.h>
+#include <Internal/filesystem.h>
+#include <Logging/logdata.h>
+#include <Ogda/jobhandler.h>
+
+#include <string>
+
+ManifestResult CopyAction::Run(const JobHandler& jh, const Item& y)
+{
+ LOGD << "Running CopyAction of " << y << std::endl;
+ std::string from = y.GetAbsPath();
+ std::string to = AssemblePath( jh.output_folder, y.GetPath() );
+
+ CreateParentDirs( to );
+ int size = copyfile( from, to );
+
+ LOGD << "Copy size " << size << std::endl;
+
+ if( size <= 0 )
+ {
+ LOGE << "Unable to copy item " << y << std::endl;
+ }
+
+ return ManifestResult(jh, y, y.GetPath(), (size > 0), *this, y.type);
+}
diff --git a/Source/Ogda/Builders/copyaction.h b/Source/Ogda/Builders/copyaction.h
new file mode 100644
index 00000000..c6357a40
--- /dev/null
+++ b/Source/Ogda/Builders/copyaction.h
@@ -0,0 +1,37 @@
+//-----------------------------------------------------------------------------
+// Name: copyaction.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "actionbase.h"
+
+class Item;
+class JobHandler;
+
+class CopyAction : public ActionBase {
+ public:
+ virtual ManifestResult Run(const JobHandler& jh, const Item& y);
+ virtual inline const char* GetName() const { return "copy"; }
+ virtual inline const char* GetVersion() const {return "2";}
+ virtual inline bool RunEvenOnIdenticalSource() const { return false; }
+ virtual inline bool StoreResultInDatabase() const { return false; }
+};
diff --git a/Source/Ogda/Builders/crunchaction.cpp b/Source/Ogda/Builders/crunchaction.cpp
new file mode 100644
index 00000000..9e8cad24
--- /dev/null
+++ b/Source/Ogda/Builders/crunchaction.cpp
@@ -0,0 +1,45 @@
+//-----------------------------------------------------------------------------
+// Name: crunchaction.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "crunchaction.h"
+
+#include <Images/image_export.hpp>
+#include <Images/texture_data.h>
+#include <Ogda/jobhandler.h>
+#include <Internal/filesystem.h>
+#include <Graphics/converttexture.h>
+#include <Images/freeimage_wrapper.h>
+#include <Internal/common.h>
+#include <Internal/filesystem.h>
+#include <Internal/datemodified.h>
+
+ManifestResult CrunchAction::Run(const JobHandler& jh, const Item& item)
+{
+ std::string full_source_path = item.GetAbsPath();
+ std::string partial_dest_path = item.GetPath() + "_converted.crn";
+ std::string full_dest_path = AssemblePath( jh.output_folder, partial_dest_path );
+ std::string temp_path = AssemblePath( jh.output_folder, partial_dest_path + ".tmp" );
+
+ bool suc = ConvertImage( full_source_path, full_dest_path, temp_path, TextureData::Nice );
+
+ return ManifestResult(jh, item, partial_dest_path, suc, *this, "dxt5");
+}
diff --git a/Source/Ogda/Builders/crunchaction.h b/Source/Ogda/Builders/crunchaction.h
new file mode 100644
index 00000000..08acb280
--- /dev/null
+++ b/Source/Ogda/Builders/crunchaction.h
@@ -0,0 +1,37 @@
+//-----------------------------------------------------------------------------
+// Name: crunchaction.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "actionbase.h"
+
+class Item;
+class JobHandler;
+
+class CrunchAction : public ActionBase {
+ public:
+ virtual ManifestResult Run(const JobHandler& jh, const Item& y);
+ virtual inline const char* GetName() const { return "crunch"; }
+ virtual inline const char* GetVersion() const {return "1";}
+ virtual inline bool RunEvenOnIdenticalSource() const { return false; }
+ virtual inline bool StoreResultInDatabase() const { return true; }
+};
diff --git a/Source/Ogda/Builders/dxt5action.cpp b/Source/Ogda/Builders/dxt5action.cpp
new file mode 100644
index 00000000..ba8fd763
--- /dev/null
+++ b/Source/Ogda/Builders/dxt5action.cpp
@@ -0,0 +1,45 @@
+//-----------------------------------------------------------------------------
+// Name: dxt5action.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "dxt5action.h"
+
+#include <Images/image_export.hpp>
+#include <Images/texture_data.h>
+#include <Ogda/jobhandler.h>
+#include <Internal/filesystem.h>
+#include <Graphics/converttexture.h>
+#include <Images/freeimage_wrapper.h>
+#include <Internal/common.h>
+#include <Internal/filesystem.h>
+#include <Internal/datemodified.h>
+
+ManifestResult DXT5Action::Run(const JobHandler& jh, const Item& item)
+{
+ std::string full_source_path = item.GetAbsPath();
+ std::string partial_dest_path = item.GetPath() + "_converted.dds";
+ std::string full_dest_path = AssemblePath( jh.output_folder, partial_dest_path );
+ std::string temp_path = AssemblePath( jh.output_folder, partial_dest_path + ".tmp" );
+
+ bool suc = ConvertImage( full_source_path, full_dest_path, temp_path, TextureData::Nice );
+
+ return ManifestResult(jh, item, partial_dest_path, suc, *this, "dxt5");
+}
diff --git a/Source/Ogda/Builders/dxt5action.h b/Source/Ogda/Builders/dxt5action.h
new file mode 100644
index 00000000..4b096a52
--- /dev/null
+++ b/Source/Ogda/Builders/dxt5action.h
@@ -0,0 +1,37 @@
+//-----------------------------------------------------------------------------
+// Name: dxt5action.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "actionbase.h"
+
+class Item;
+class JobHandler;
+
+class DXT5Action : public ActionBase {
+ public:
+ virtual ManifestResult Run(const JobHandler& jh, const Item& y);
+ virtual inline const char* GetName() const { return "dxt5"; }
+ virtual inline const char* GetVersion() const {return "6";}
+ virtual inline bool RunEvenOnIdenticalSource() const { return false; }
+ virtual inline bool StoreResultInDatabase() const { return true; }
+};
diff --git a/Source/Ogda/Builders/voidaction.cpp b/Source/Ogda/Builders/voidaction.cpp
new file mode 100644
index 00000000..2ffac6e5
--- /dev/null
+++ b/Source/Ogda/Builders/voidaction.cpp
@@ -0,0 +1,31 @@
+//-----------------------------------------------------------------------------
+// Name: voidaction.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "voidaction.h"
+
+#include <Logging/logdata.h>
+#include <Ogda/item.h>
+
+ManifestResult VoidAction::Run(const JobHandler& jh, const Item& y)
+{
+ return ManifestResult(jh, y, std::string(""), true, *this, "void");
+}
diff --git a/Source/Ogda/Builders/voidaction.h b/Source/Ogda/Builders/voidaction.h
new file mode 100644
index 00000000..d4a26866
--- /dev/null
+++ b/Source/Ogda/Builders/voidaction.h
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------------
+// Name: voidaction.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "actionbase.h"
+#include <iostream>
+
+class Item;
+class JobHandler;
+
+class VoidAction : public ActionBase
+{
+ public:
+ virtual ManifestResult Run(const JobHandler& jh, const Item& y);
+ virtual inline const char* GetName() const { return "void"; }
+ virtual inline const char* GetVersion() const {return "2";}
+ virtual inline bool RunEvenOnIdenticalSource() const { return true; }
+ virtual inline bool StoreResultInDatabase() const { return false; }
+};
diff --git a/Source/Ogda/Generators/creatorbase.h b/Source/Ogda/Generators/creatorbase.h
new file mode 100644
index 00000000..d85d3cc7
--- /dev/null
+++ b/Source/Ogda/Generators/creatorbase.h
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------------
+// Name: creatorbase.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Ogda/manifestresult.h>
+#include <Ogda/manifest.h>
+
+#include <vector>
+
+class JobHandler;
+class Item;
+
+class CreatorBase
+{
+ public:
+ virtual ManifestResult Run(const JobHandler& jh, const Manifest& manifest ) = 0;
+ virtual const char* GetName() const = 0;
+ virtual const char* GetVersion() const = 0;
+};
diff --git a/Source/Ogda/Generators/generator.cpp b/Source/Ogda/Generators/generator.cpp
new file mode 100644
index 00000000..ca8d4d57
--- /dev/null
+++ b/Source/Ogda/Generators/generator.cpp
@@ -0,0 +1,43 @@
+//-----------------------------------------------------------------------------
+// Name: generator.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "generator.h"
+
+Generator::Generator( CreatorBase* creator )
+: creator(creator)
+{
+}
+
+ManifestResult Generator::Run(const JobHandler& jh, const Manifest& manifest)
+{
+ return creator->Run(jh,manifest);
+}
+
+std::string Generator::GetGeneratorName() const
+{
+ return std::string(creator.GetConst().GetName());
+}
+
+std::string Generator::GetGeneratorVersion() const
+{
+ return std::string(creator.GetConst().GetVersion());
+}
diff --git a/Source/Ogda/Generators/generator.h b/Source/Ogda/Generators/generator.h
new file mode 100644
index 00000000..e4668499
--- /dev/null
+++ b/Source/Ogda/Generators/generator.h
@@ -0,0 +1,41 @@
+//-----------------------------------------------------------------------------
+// Name: generator.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+#include "creatorbase.h"
+
+#include <Logging/logdata.h>
+#include <Ogda/item.h>
+#include <Ogda/manifestresult.h>
+#include <Ogda/manifest.h>
+#include <Internal/referencecounter.h>
+
+class Generator
+{
+public:
+ Generator( CreatorBase* creation );
+ ManifestResult Run(const JobHandler& jh, const Manifest& manifest);
+ std::string GetGeneratorName() const;
+ std::string GetGeneratorVersion() const;
+private:
+ ReferenceCounter<CreatorBase> creator;
+};
diff --git a/Source/Ogda/Generators/generatorfactory.cpp b/Source/Ogda/Generators/generatorfactory.cpp
new file mode 100644
index 00000000..0033a077
--- /dev/null
+++ b/Source/Ogda/Generators/generatorfactory.cpp
@@ -0,0 +1,77 @@
+//-----------------------------------------------------------------------------
+// Name: generatorfactory.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "generatorfactory.h"
+#include "generator.h"
+
+#include "versionxmlcreator.h"
+#include "shortversioncreator.h"
+#include "voidcreator.h"
+#include "levellistcreator.h"
+
+#include <Logging/logdata.h>
+
+GeneratorFactory::GeneratorFactory()
+{
+ creators.push_back( new CreatorFactory<VersionXMLCreator>() );
+ creators.push_back( new CreatorFactory<ShortVersionCreator>() );
+ creators.push_back( new CreatorFactory<LevelListCreator>() );
+}
+
+GeneratorFactory::~GeneratorFactory()
+{
+ std::vector<CreatorFactoryBase*>::iterator factoryit;
+
+ for( factoryit = creators.begin(); factoryit != creators.end(); factoryit++ )
+ {
+ delete *factoryit;
+ }
+
+ creators.clear();
+}
+
+bool GeneratorFactory::HasGenerator( const std::string& generator )
+{
+ std::vector<CreatorFactoryBase*>::iterator factoryit;
+ for( factoryit = creators.begin(); factoryit != creators.end(); factoryit++ )
+ {
+ if( (*factoryit)->GetCreatorName() == generator )
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+Generator GeneratorFactory::CreateGenerator( const std::string& generator )
+{
+ std::vector<CreatorFactoryBase*>::iterator factoryit;
+ for( factoryit = creators.begin(); factoryit != creators.end(); factoryit++ )
+ {
+ if( (*factoryit)->GetCreatorName() == generator )
+ {
+ return Generator((*factoryit)->NewInstance());
+ }
+ }
+ LOGE << "Unable to find generator matching name " << generator << std::endl;
+ return Generator( new VoidCreator() );
+}
diff --git a/Source/Ogda/Generators/generatorfactory.h b/Source/Ogda/Generators/generatorfactory.h
new file mode 100644
index 00000000..78d25ce2
--- /dev/null
+++ b/Source/Ogda/Generators/generatorfactory.h
@@ -0,0 +1,59 @@
+//-----------------------------------------------------------------------------
+// Name: generatorfactory.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "generator.h"
+
+#include <vector>
+
+class GeneratorFactory
+{
+private:
+ class CreatorFactoryBase
+ {
+ public:
+ virtual CreatorBase* NewInstance() = 0;
+ virtual std::string GetCreatorName() = 0;
+ };
+
+ template<class Creator>
+ class CreatorFactory : public CreatorFactoryBase
+ {
+ virtual CreatorBase* NewInstance()
+ {
+ return new Creator();
+ }
+
+ virtual std::string GetCreatorName()
+ {
+ return std::string(Creator().GetName());
+ }
+ };
+
+ std::vector<CreatorFactoryBase*> creators;
+public:
+ GeneratorFactory();
+ ~GeneratorFactory();
+ bool HasGenerator( const std::string& generator );
+ Generator CreateGenerator( const std::string& generator );
+};
diff --git a/Source/Ogda/Generators/levellistcreator.cpp b/Source/Ogda/Generators/levellistcreator.cpp
new file mode 100644
index 00000000..41fd4f66
--- /dev/null
+++ b/Source/Ogda/Generators/levellistcreator.cpp
@@ -0,0 +1,55 @@
+//-----------------------------------------------------------------------------
+// Name: levellistcreator.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "levellistcreator.h"
+
+#include <fstream>
+#include <iostream>
+
+#include <tinyxml.h>
+#include <XML/xml_helper.h>
+#include <Version/version.h>
+#include <Internal/filesystem.h>
+#include <Ogda/jobhandler.h>
+
+ManifestResult LevelListCreator::Run(const JobHandler& jh, const Manifest& manifest)
+{
+ std::string destination("level_list");
+
+ std::string full_path = AssemblePath( jh.output_folder, destination );
+
+ std::ofstream f( full_path.c_str(), std::ios::out | std::ios::binary );
+
+ LOGI << "Writing level_list: " << full_path << std::endl;
+
+ std::vector<ManifestResult>::const_iterator itr = manifest.ResultsBegin();
+
+ for( ;itr != manifest.ResultsEnd(); itr++ )
+ {
+ if( itr->type == "level" )
+ f << itr->dest << std::endl;
+ }
+
+ f.close();
+
+ return ManifestResult(jh, destination, true, *this, "level_list");
+}
diff --git a/Source/Ogda/Generators/levellistcreator.h b/Source/Ogda/Generators/levellistcreator.h
new file mode 100644
index 00000000..61f1ec8a
--- /dev/null
+++ b/Source/Ogda/Generators/levellistcreator.h
@@ -0,0 +1,36 @@
+//-----------------------------------------------------------------------------
+// Name: levellistcreator.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "creatorbase.h"
+#include <iostream>
+
+class JobHandler;
+
+class LevelListCreator : public CreatorBase
+{
+ public:
+ virtual ManifestResult Run(const JobHandler& jh, const Manifest& manifest);
+ virtual inline const char* GetName() const { return "levellist"; }
+ virtual inline const char* GetVersion() const {return "1";}
+};
diff --git a/Source/Ogda/Generators/shortversioncreator.cpp b/Source/Ogda/Generators/shortversioncreator.cpp
new file mode 100644
index 00000000..a4050e77
--- /dev/null
+++ b/Source/Ogda/Generators/shortversioncreator.cpp
@@ -0,0 +1,49 @@
+//-----------------------------------------------------------------------------
+// Name: shortversioncreator.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "shortversioncreator.h"
+
+#include <fstream>
+#include <iostream>
+
+#include <tinyxml.h>
+#include <XML/xml_helper.h>
+#include <Version/version.h>
+#include <Internal/filesystem.h>
+#include <Ogda/jobhandler.h>
+
+ManifestResult ShortVersionCreator::Run(const JobHandler& jh, const Manifest& manifest)
+{
+ std::string destination("short_version");
+
+ std::string full_path = AssemblePath( jh.output_folder, destination );
+
+ std::ofstream f( full_path.c_str(), std::ios::out | std::ios::binary );
+
+ LOGI << "Writing short_version: " << full_path << std::endl;
+
+ f << GetBuildVersion() << std::endl;
+
+ f.close();
+
+ return ManifestResult(jh, destination, true, *this,"short_version");
+}
diff --git a/Source/Ogda/Generators/shortversioncreator.h b/Source/Ogda/Generators/shortversioncreator.h
new file mode 100644
index 00000000..dd978ef2
--- /dev/null
+++ b/Source/Ogda/Generators/shortversioncreator.h
@@ -0,0 +1,36 @@
+//-----------------------------------------------------------------------------
+// Name: shortversioncreator.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "creatorbase.h"
+#include <iostream>
+
+class JobHandler;
+
+class ShortVersionCreator : public CreatorBase
+{
+ public:
+ virtual ManifestResult Run(const JobHandler& jh, const Manifest& manifest);
+ virtual inline const char* GetName() const { return "shortversion"; }
+ virtual inline const char* GetVersion() const {return "1";}
+};
diff --git a/Source/Ogda/Generators/versionxmlcreator.cpp b/Source/Ogda/Generators/versionxmlcreator.cpp
new file mode 100644
index 00000000..9c9659a1
--- /dev/null
+++ b/Source/Ogda/Generators/versionxmlcreator.cpp
@@ -0,0 +1,97 @@
+//-----------------------------------------------------------------------------
+// Name: versionxmlcreator.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "versionxmlcreator.h"
+
+#include <cstring>
+
+#include <Ogda/jobhandler.h>
+#include <tinyxml.h>
+#include <XML/xml_helper.h>
+#include <Version/version.h>
+#include <Logging/logdata.h>
+#include <Internal/filesystem.h>
+
+ManifestResult VersionXMLCreator::Run(const JobHandler& jh, const Manifest& manifest)
+{
+ std::string destination("version.xml");
+
+ TiXmlDocument doc;
+ //TiXmlDeclaration * decl = new TiXmlDeclaration( "2.0", "", "" );
+ TiXmlElement * root = new TiXmlElement( "release" );
+
+ {
+ std::stringstream ss;
+
+ ss << 100000 + GetBuildID();
+ TiXmlElement* eVersion = new TiXmlElement( "version" );
+ eVersion->LinkEndChild( new TiXmlText( ss.str().c_str() ) );
+ root->LinkEndChild(eVersion);
+ }
+
+ {
+ TiXmlElement* eName = new TiXmlElement( "name" );
+ eName->LinkEndChild( new TiXmlText( GetBuildVersion() ) );
+ root->LinkEndChild(eName);
+ }
+
+ {
+ TiXmlElement* eShortname = new TiXmlElement( "shortname" );
+ eShortname->LinkEndChild( new TiXmlText( GetBuildVersion() ) );
+ root->LinkEndChild(eShortname);
+ }
+
+ {
+ TiXmlElement* eDate = new TiXmlElement( "date" );
+ eDate->LinkEndChild( new TiXmlText( GetBuildTimestamp() ) );
+ root->LinkEndChild(eDate);
+ }
+
+ {
+ TiXmlElement* eRev = new TiXmlElement( "rev" );
+ eRev->LinkEndChild( new TiXmlText( "9999" ) );
+ root->LinkEndChild(eRev);
+ }
+
+ {
+ TiXmlElement* eSvnrev = new TiXmlElement( "svnrev" );
+ eSvnrev->LinkEndChild( new TiXmlText( "9999" ) );
+ root->LinkEndChild(eSvnrev);
+ }
+
+ {
+ std::stringstream ss;
+ ss << GetBuildID();
+ std::string sss = ss.str();
+ TiXmlElement* e = new TiXmlElement( "build" );
+ e->LinkEndChild( new TiXmlText( sss.c_str() ) );
+ root->LinkEndChild(e);
+ }
+
+ //doc.LinkEndChild( decl );
+ doc.LinkEndChild( root );
+ std::string full_name = AssemblePath( jh.output_folder, destination );
+ doc.SaveFile( full_name.c_str() );
+
+ return ManifestResult(jh, destination, !doc.Error(), *this, "version");
+}
+
diff --git a/Source/Ogda/Generators/versionxmlcreator.h b/Source/Ogda/Generators/versionxmlcreator.h
new file mode 100644
index 00000000..7c3eead8
--- /dev/null
+++ b/Source/Ogda/Generators/versionxmlcreator.h
@@ -0,0 +1,36 @@
+//-----------------------------------------------------------------------------
+// Name: versionxmlcreator.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "creatorbase.h"
+#include <iostream>
+
+class JobHandler;
+
+class VersionXMLCreator : public CreatorBase
+{
+ public:
+ virtual ManifestResult Run(const JobHandler& jh, const Manifest& manifest);
+ virtual inline const char* GetName() const { return "versionxml"; }
+ virtual inline const char* GetVersion() const {return "1";}
+};
diff --git a/Source/Ogda/Generators/voidcreator.cpp b/Source/Ogda/Generators/voidcreator.cpp
new file mode 100644
index 00000000..53c56fec
--- /dev/null
+++ b/Source/Ogda/Generators/voidcreator.cpp
@@ -0,0 +1,28 @@
+//-----------------------------------------------------------------------------
+// Name: voidcreator.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "voidcreator.h"
+
+ManifestResult VoidCreator::Run(const JobHandler& jh, const Manifest& manifest)
+{
+ return ManifestResult(jh, std::string(""), false, *this,"void");
+}
diff --git a/Source/Ogda/Generators/voidcreator.h b/Source/Ogda/Generators/voidcreator.h
new file mode 100644
index 00000000..50508287
--- /dev/null
+++ b/Source/Ogda/Generators/voidcreator.h
@@ -0,0 +1,36 @@
+//-----------------------------------------------------------------------------
+// Name: voidcreator.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "creatorbase.h"
+#include <iostream>
+
+class JobHandler;
+
+class VoidCreator : public CreatorBase
+{
+ public:
+ virtual ManifestResult Run(const JobHandler& jh, const Manifest& manifest);
+ virtual inline const char* GetName() const { return "void"; }
+ virtual inline const char* GetVersion() const {return "1";}
+};
diff --git a/Source/Ogda/Searchers/Seekers/SeekerTools/attributescanner.cpp b/Source/Ogda/Searchers/Seekers/SeekerTools/attributescanner.cpp
new file mode 100644
index 00000000..cdba6d93
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/SeekerTools/attributescanner.cpp
@@ -0,0 +1,101 @@
+//-----------------------------------------------------------------------------
+// Name: attributescanner.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "attributescanner.h"
+
+#include <string>
+#include <cassert>
+
+#include <Ogda/jobhandler.h>
+#include <Internal/filesystem.h>
+#include <Utility/strings.h>
+#include <XML/xml_helper.h>
+
+#include <tinyxml.h>
+
+void AttributeScanner::Do(
+ std::vector<Item>& items,
+ const Item& item,
+ TiXmlElement *eElem,
+ const std::vector<attribpair>& attribs,
+ const std::vector<const char*>& attribs_ignore)
+{
+ if( eElem )
+ {
+ TiXmlAttribute *aV = eElem->FirstAttribute();
+ while( aV )
+ {
+ int id = -1;
+ if((id = FindStringInArray( attribs, aV->Name() )) >= 0 )
+ {
+ if( aV->Value() && strlen( aV->Value() ) > 0 )
+ {
+ items.push_back(Item(item.input_folder, aV->Value(), attribs[id].second, item.source));
+ }
+ else
+ {
+ LOGD << "Value is empty in " << item << std::endl;
+ }
+ }
+ else if( FindStringInArray( attribs_ignore, aV->Name() ) < 0 )
+ {
+ LOGE << "Unhandled attrib on row " << aV->Row() << " " << aV->Name() << " in " << item << std::endl;
+ }
+
+ aV = aV->Next();
+ }
+ }
+ else
+ {
+ LOGE << "Root element is null for " << item << std::endl;
+ }
+}
+
+void AttributeScanner::DoAllSame(
+ std::vector<Item>& items,
+ const Item& item,
+ TiXmlElement *eElem,
+ std::string type )
+{
+ if( eElem )
+ {
+ TiXmlAttribute *aV = eElem->FirstAttribute();
+ while( aV )
+ {
+ if( aV->Value() && strlen( aV->Value() ) > 0 )
+ {
+ items.push_back(Item(item.input_folder, aV->Value(), type, item.source));
+ }
+ else
+ {
+ LOGD << "Value is empty in " << item << std::endl;
+ }
+
+ aV = aV->Next();
+ }
+ }
+ else
+ {
+ LOGE << "Root element is null for " << item << std::endl;
+ }
+}
+
diff --git a/Source/Ogda/Searchers/Seekers/SeekerTools/attributescanner.h b/Source/Ogda/Searchers/Seekers/SeekerTools/attributescanner.h
new file mode 100644
index 00000000..9fbd26c5
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/SeekerTools/attributescanner.h
@@ -0,0 +1,50 @@
+//-----------------------------------------------------------------------------
+// Name: attributescanner.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <vector>
+#include <utility>
+#include <string>
+
+class TiXmlElement;
+class Item;
+
+typedef std::pair<const char*, const char*> attribpair;
+
+class AttributeScanner
+{
+public:
+static void Do(
+ std::vector<Item>& items,
+ const Item& item,
+ TiXmlElement *eElem,
+ const std::vector<attribpair>& attribs,
+ const std::vector<const char*>& attribs_ignore);
+
+static void DoAllSame(
+ std::vector<Item>& items,
+ const Item& item,
+ TiXmlElement *eElem,
+ std::string type );
+};
+
diff --git a/Source/Ogda/Searchers/Seekers/SeekerTools/elementscanner.cpp b/Source/Ogda/Searchers/Seekers/SeekerTools/elementscanner.cpp
new file mode 100644
index 00000000..a30d43f0
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/SeekerTools/elementscanner.cpp
@@ -0,0 +1,91 @@
+//-----------------------------------------------------------------------------
+// Name: elementscanner.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "elementscanner.h"
+
+#include <string>
+#include <cassert>
+
+#include <Ogda/jobhandler.h>
+#include <Internal/filesystem.h>
+#include <Utility/strings.h>
+#include <XML/xml_helper.h>
+#include <Ogda/Searchers/Seekers/xmlseekerbase.h>
+
+#include <tinyxml.h>
+
+void ElementScanner::Do(
+ std::vector<Item>& items,
+ const Item& item,
+ TiXmlNode *eRoot,
+ const std::vector<elempair>& elems,
+ const std::vector<const char*>& elems_ignore,
+ XMLSeekerBase* callback,
+ void* userdata
+ )
+{
+ if( eRoot )
+ {
+ TiXmlElement* eElem = eRoot->FirstChildElement();
+
+ while( eElem )
+ {
+ const char* name = eElem->Value();
+ const char* text = eElem->GetText();
+ if( name )
+ {
+ int id;
+ if( (id = FindStringInArray( elems, name )) >= 0 )
+ {
+ if( strlen( elems[id].second ) > 0 )
+ {
+ if( text && strlen(text) > 0 )
+ {
+ items.push_back(Item(item.input_folder, text,elems[id].second,item.source));
+ }
+ else
+ {
+ LOGW << "String value in " << item << " for element " << elems[id].first << " is empty, row " << eElem->Row() << std::endl;
+ }
+ }
+
+ if( callback )
+ callback->HandleElementCallback(items,eRoot,eElem,item,userdata);
+ }
+ else if( (id = FindStringInArray( elems_ignore, name )) >= 0 )
+ {
+ LOGD << "Ignored " << elems_ignore[id] << " in " << item << " row " << eElem->Row() << std::endl;
+ }
+ else
+ {
+ LOGE << "Unahandled subvalue from " << item << " called " << name << " row " << eElem->Row() << std::endl;
+ }
+ }
+ else
+ {
+ LOGE << "Generic warning" << std::endl;
+ }
+
+ eElem = eElem->NextSiblingElement();
+ }
+ }
+}
diff --git a/Source/Ogda/Searchers/Seekers/SeekerTools/elementscanner.h b/Source/Ogda/Searchers/Seekers/SeekerTools/elementscanner.h
new file mode 100644
index 00000000..d7144261
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/SeekerTools/elementscanner.h
@@ -0,0 +1,47 @@
+//-----------------------------------------------------------------------------
+// Name: elementscanner.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <vector>
+#include <utility>
+
+class TiXmlElement;
+class TiXmlNode;
+class Item;
+class XMLSeekerBase;
+
+typedef std::pair<const char*, const char*> elempair;
+
+class ElementScanner
+{
+public:
+static void Do(
+ std::vector<Item>& items,
+ const Item& item,
+ TiXmlNode *eRoot,
+ const std::vector<elempair>& elems,
+ const std::vector<const char*>& elems_ignore,
+ XMLSeekerBase* callback,
+ void* userdata
+ );
+};
diff --git a/Source/Ogda/Searchers/Seekers/SeekerTools/parameterscanner.cpp b/Source/Ogda/Searchers/Seekers/SeekerTools/parameterscanner.cpp
new file mode 100644
index 00000000..40c93734
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/SeekerTools/parameterscanner.cpp
@@ -0,0 +1,87 @@
+//-----------------------------------------------------------------------------
+// Name: parameterscanner.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "parameterscanner.h"
+
+#include <string>
+#include <cassert>
+
+#include <Ogda/jobhandler.h>
+#include <Internal/filesystem.h>
+#include <Utility/strings.h>
+#include <XML/xml_helper.h>
+#include <Ogda/Searchers/Seekers/xmlseekerbase.h>
+
+#include <tinyxml.h>
+
+void ParameterScanner::Do(
+ std::vector<Item>& items,
+ const Item& item,
+ TiXmlNode *eRoot,
+ const std::vector<parampair>& params,
+ const std::vector<const char*>& params_ignore
+ )
+{
+ if( eRoot )
+ {
+ TiXmlElement* eElem = eRoot->FirstChildElement();
+
+ while( eElem )
+ {
+ const char* name = eElem->Attribute("name");
+ const char* text = eElem->Attribute("val");
+
+ if( name )
+ {
+ int id;
+ if( (id = FindStringInArray( params, name )) >= 0 )
+ {
+ if( strlen( params[id].second ) > 0 )
+ {
+ if( text && strlen(text) > 0 )
+ {
+ items.push_back(Item(item.input_folder,text,params[id].second,item.source));
+ }
+ else
+ {
+ LOGW << "String value in " << item << " for element " << params[id].first << " is empty, row " << eElem->Row() << std::endl;
+ }
+ }
+ }
+ else if( (id = FindStringInArray( params_ignore, name )) >= 0 )
+ {
+ LOGD << "Ignored " << params_ignore[id] << " in " << item << " row " << eElem->Row() << std::endl;
+ }
+ else
+ {
+ LOGE << "Unahandled subvalue from " << item << " called " << name << " row " << eElem->Row() << std::endl;
+ }
+ }
+ else
+ {
+ LOGE << "Generic warning" << std::endl;
+ }
+
+ eElem = eElem->NextSiblingElement();
+ }
+ }
+}
diff --git a/Source/Ogda/Searchers/Seekers/SeekerTools/parameterscanner.h b/Source/Ogda/Searchers/Seekers/SeekerTools/parameterscanner.h
new file mode 100644
index 00000000..92f52269
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/SeekerTools/parameterscanner.h
@@ -0,0 +1,45 @@
+//-----------------------------------------------------------------------------
+// Name: parameterscanner.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <vector>
+#include <utility>
+
+class TiXmlElement;
+class TiXmlNode;
+class Item;
+class XMLSeekerBase;
+
+typedef std::pair<const char*, const char*> parampair;
+
+class ParameterScanner
+{
+public:
+static void Do(
+ std::vector<Item>& items,
+ const Item& item,
+ TiXmlNode *eRoot,
+ const std::vector<parampair>& params,
+ const std::vector<const char*>& params_ignore
+ );
+};
diff --git a/Source/Ogda/Searchers/Seekers/actorobjectlevelseeker.cpp b/Source/Ogda/Searchers/Seekers/actorobjectlevelseeker.cpp
new file mode 100644
index 00000000..b9c9cf96
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/actorobjectlevelseeker.cpp
@@ -0,0 +1,568 @@
+//-----------------------------------------------------------------------------
+// Name: actorobjectlevelseeker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "actorobjectlevelseeker.h"
+
+#include <tinyxml.h>
+#include <XML/xml_helper.h>
+
+#include <Internal/filesystem.h>
+#include <Logging/logdata.h>
+
+#define ARRLEN(arr) sizeof(arr)/sizeof(arr[0])
+
+struct Parameter
+{
+ const char* name;
+ const char* type;
+ bool ignored;
+ bool into_file;
+ const char* ignored_value;
+
+public:
+ Parameter(const char* name, const char* type, bool ignored, bool into_file) : name(name), type(type), ignored(ignored), into_file(into_file), ignored_value(NULL) {};
+ Parameter(const char* name, const char* type, bool ignored, bool into_file, const char* ignored_value) : name(name), type(type), ignored(ignored), into_file(into_file), ignored_value(ignored_value){ };
+
+ bool Search(std::vector<Item>& items, const Item& item, TiXmlElement* el )
+ {
+ const char* name_str = el->Attribute("name");
+ const char* val_str = el->Attribute("val");
+
+ if( name_str )
+ {
+ if(strcmp(name_str, name) == 0 )
+ {
+ if( !ignored )
+ {
+ if( ignored_value == NULL || strcmp(ignored_value,val_str) != 0 )
+ {
+ if( into_file )
+ {
+ Item i(item.input_folder, DumpIntoFile( val_str, strlen( val_str ) ), type, item.source);
+ i.SetOnlySearch(true);
+ i.SetDeleteOnExit(true);
+ items.push_back(i);
+ }
+ else
+ {
+ items.push_back(Item(item.input_folder, val_str, type, item.source));
+ }
+ }
+ }
+
+ return true;
+ }
+ }
+ else
+ {
+ LOGE << "Missing attribute name on row " << el->Row() << " in " << item << std::endl;
+ }
+ return false;
+ }
+};
+
+struct ActorObject
+{
+ const char* name;
+ const char* type;
+ bool ignored;
+ std::vector<Parameter> parameters;
+
+public:
+ ActorObject( const char* name, const char* type, bool ignored, std::vector<Parameter> parameters ) : name(name), type(type), ignored(ignored), parameters( parameters ){};
+
+ bool Search(std::vector<Item>& items, const Item& item, TiXmlElement* eao)
+ {
+ if( strcmp(eao->Value(), name) == 0 )
+ {
+ const char* t = eao->Attribute("type_file");
+
+ if( !t ) {
+ t = eao->Attribute("prefab_path");
+ }
+
+ if( !ignored )
+ {
+ if( t )
+ {
+ items.push_back(Item(item.input_folder, t, type, item.source));
+ }
+ else
+ {
+ LOGE << "Missing attribute type_file/prefab_path on row " << eao->Row() << " in " << item << std::endl;
+ }
+ }
+
+ TiXmlElement* p = eao->FirstChildElement( "parameters" );
+
+ if( p )
+ {
+ TiXmlElement* el = p->FirstChildElement();
+
+ while( el )
+ {
+ bool found = false;
+ for( int i = 0; i < parameters.size(); i++ )
+ {
+ found |= parameters[i].Search(
+ items,
+ item,
+ el);
+ }
+
+ if( !found )
+ {
+ const char* name_str = el->Attribute("name");
+ LOGW << "Missing attribute handler for name \"" << name_str << "\" on row " << eao->Row() << " in " << item << std::endl;
+ }
+
+ el = el->NextSiblingElement();
+ }
+ }
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+};
+
+void ActorObjectLevelSeeker::SearchGroup( std::vector<Item>& items, const Item& item, TiXmlElement* root )
+{
+ std::vector<Parameter> global_params;
+
+ global_params.push_back( Parameter("Type", "", true, false));
+ global_params.push_back( Parameter("Exclamation Character", "", true, false));
+ global_params.push_back( Parameter("Hearing Modifier", "", true, false));
+ global_params.push_back( Parameter("goal_0_post", "", true, false));
+ global_params.push_back( Parameter("goal_1_post", "", true, false));
+ global_params.push_back( Parameter("goal_2_post", "", true, false));
+
+ global_params.push_back( Parameter("goal_0_pre", "", true, false));
+ global_params.push_back( Parameter("goal_1_pre", "", true, false));
+ global_params.push_back( Parameter("goal_2_pre", "", true, false));
+ global_params.push_back( Parameter("goal_3_pre", "", true, false));
+ global_params.push_back( Parameter("goal_4_pre", "", true, false));
+ global_params.push_back( Parameter("goal_5_pre", "", true, false));
+ global_params.push_back( Parameter("goal_6_pre", "", true, false));
+ global_params.push_back( Parameter("goal_7_pre", "", true, false));
+ global_params.push_back( Parameter("goal_8_pre", "", true, false));
+ global_params.push_back( Parameter("goal_9_pre", "", true, false));
+ global_params.push_back( Parameter("goal_10_pre", "", true, false));
+ global_params.push_back( Parameter("goal_11_pre", "", true, false));
+ global_params.push_back( Parameter("goal_12_pre", "", true, false));
+ global_params.push_back( Parameter("goal_13_pre", "", true, false));
+ global_params.push_back( Parameter("goal_14_pre", "", true, false));
+ global_params.push_back( Parameter("goal_15_pre", "", true, false));
+ global_params.push_back( Parameter("goal_16_pre", "", true, false));
+ global_params.push_back( Parameter("goal_17_pre", "", true, false));
+
+ global_params.push_back( Parameter("goal_0_post", "", true, false));
+ global_params.push_back( Parameter("goal_1_post", "", true, false));
+ global_params.push_back( Parameter("goal_2_post", "", true, false));
+ global_params.push_back( Parameter("goal_3_post", "", true, false));
+ global_params.push_back( Parameter("goal_4_post", "", true, false));
+ global_params.push_back( Parameter("goal_5_post", "", true, false));
+ global_params.push_back( Parameter("goal_6_post", "", true, false));
+ global_params.push_back( Parameter("goal_7_post", "", true, false));
+ global_params.push_back( Parameter("goal_8_post", "", true, false));
+ global_params.push_back( Parameter("goal_9_post", "", true, false));
+ global_params.push_back( Parameter("goal_10_post", "", true, false));
+ global_params.push_back( Parameter("goal_11_post", "", true, false));
+ global_params.push_back( Parameter("goal_12_post", "", true, false));
+ global_params.push_back( Parameter("goal_13_post", "", true, false));
+ global_params.push_back( Parameter("goal_14_post", "", true, false));
+ global_params.push_back( Parameter("goal_15_post", "", true, false));
+ global_params.push_back( Parameter("goal_16_post", "", true, false));
+ global_params.push_back( Parameter("goal_17_post", "", true, false));
+
+ global_params.push_back( Parameter("goal_0", "", true, false));
+ global_params.push_back( Parameter("goal_1", "", true, false));
+ global_params.push_back( Parameter("goal_2", "", true, false));
+ global_params.push_back( Parameter("goal_3", "", true, false));
+ global_params.push_back( Parameter("goal_4", "", true, false));
+ global_params.push_back( Parameter("goal_5", "", true, false));
+ global_params.push_back( Parameter("goal_6", "", true, false));
+ global_params.push_back( Parameter("goal_7", "", true, false));
+ global_params.push_back( Parameter("goal_8", "", true, false));
+ global_params.push_back( Parameter("goal_9", "", true, false));
+ global_params.push_back( Parameter("goal_10", "", true, false));
+ global_params.push_back( Parameter("goal_11", "", true, false));
+ global_params.push_back( Parameter("goal_12", "", true, false));
+ global_params.push_back( Parameter("goal_13", "", true, false));
+ global_params.push_back( Parameter("goal_14", "", true, false));
+ global_params.push_back( Parameter("goal_15", "", true, false));
+ global_params.push_back( Parameter("goal_16", "", true, false));
+ global_params.push_back( Parameter("goal_17", "", true, false));
+
+ global_params.push_back( Parameter("Stick To Nav Mesh", "", true, false));
+
+ global_params.push_back( Parameter("fall_death", "", true, false));
+ global_params.push_back( Parameter("fall_damage_mult", "", true, false));
+
+ global_params.push_back( Parameter("time", "", true, false));
+
+ global_params.push_back( Parameter("Display Text", "", true, false));
+
+ global_params.push_back( Parameter("KillNPC", "", true, false));
+ global_params.push_back( Parameter("KillPlayer", "", true, false));
+ global_params.push_back( Parameter("characters", "", true, false));
+
+ global_params.push_back( Parameter("Start Disabled", "", true, false));
+
+ global_params.push_back( Parameter("Throw Counter Probability", "", true, false));
+ global_params.push_back( Parameter("Throw Trainer", "", true, false));
+ global_params.push_back( Parameter("Weapon Catch Skill", "", true, false));
+
+ global_params.push_back( Parameter("SavedTransform", "", true, false));
+
+ global_params.push_back( Parameter("rotation_scale", "", true, false));
+ global_params.push_back( Parameter("time_scale", "", true, false));
+ global_params.push_back( Parameter("translation_scale", "", true, false));
+
+ global_params.push_back( Parameter("music_layer_override", "", true, false));
+
+ global_params.push_back( Parameter("Invisible When Stationary", "", true, false));
+
+ global_params.push_back( Parameter("No Look Around", "", true, false));
+
+ global_params.push_back( Parameter("Short", "", true, false));
+ global_params.push_back( Parameter("Offset", "", true, false));
+ global_params.push_back( Parameter("Lethal", "", true, false));
+
+ std::vector<ActorObject> aos;
+ {
+ std::vector<Parameter> params = global_params;
+ params.push_back( Parameter("Dialogue", "dialogue",false,false, "empty"));
+
+ params.push_back( Parameter("Script", "dialogue",false,true));
+ params.push_back( Parameter("Battles", "battles",false,true));
+
+ params.push_back( Parameter("Name", "",true,false));
+ params.push_back( Parameter("DisplayName", "",true,false));
+ params.push_back( Parameter("NumParticipants", "",true,false));
+ params.push_back( Parameter("obj_1", "",true,false));
+ params.push_back( Parameter("obj_2", "",true,false));
+ params.push_back( Parameter("obj_3", "",true,false));
+ params.push_back( Parameter("obj_4", "",true,false));
+ params.push_back( Parameter("obj_5", "",true,false));
+ params.push_back( Parameter("obj_6", "",true,false));
+ params.push_back( Parameter("obj_7", "",true,false));
+ params.push_back( Parameter("obj_8", "",true,false));
+ params.push_back( Parameter("obj_9", "",true,false));
+ params.push_back( Parameter("game_type", "",true,false));
+ params.push_back( Parameter("team", "",true,false));
+ params.push_back( Parameter("LocName", "",true,false));
+ params.push_back( Parameter("Visible in game", "",true,false));
+ params.push_back( Parameter("Automatic", "",true,false));
+
+
+ ActorObject ao( "PlaceholderObject", "", true, params );
+ aos.push_back(ao);
+ }
+ {
+ std::vector<Parameter> params = global_params;
+
+ params.push_back( Parameter("Wait", "", true, false));
+
+ ActorObject ao( "PathPointObject", "", true, params );
+ aos.push_back(ao);
+ }
+ {
+ std::vector<Parameter> params = global_params;
+ ActorObject ao( "CameraObject", "", true, params );
+ aos.push_back(ao);
+ }
+ {
+ std::vector<Parameter> params = global_params;
+
+ ActorObject ao( "parameters", "", true, params );
+ aos.push_back(ao);
+ }
+ {
+ std::vector<Parameter> params = global_params;
+ ActorObject ao( "AmbientSoundObject", "ambient_sound_object", false, params );
+ aos.push_back(ao);
+ }
+ {
+ std::vector<Parameter> params = global_params;
+ params.push_back( Parameter("Rotating Y","", true,false));
+
+ ActorObject ao( "EnvObject", "object", false, params );
+ aos.push_back(ao);
+ }
+ {
+ std::vector<Parameter> params = global_params;
+ params.push_back( Parameter("Aggression","", true,false));
+ params.push_back( Parameter("Attack Damage","", true,false));
+ params.push_back( Parameter("Attack Knockback" ,"", true,false));
+ params.push_back( Parameter("Attack Speed","", true,false));
+ params.push_back( Parameter("Block Follow-up" ,"", true,false));
+ params.push_back( Parameter("Block Skill","", true,false));
+ params.push_back( Parameter("Character Scale","", true,false));
+ params.push_back( Parameter("Damage Resistance" ,"",true,false));
+ params.push_back( Parameter("Ear Size","", true,false));
+ params.push_back( Parameter("Fat","", true,false));
+ params.push_back( Parameter("Ground Aggression","", true,false));
+ params.push_back( Parameter("Left handed","", true,false));
+ params.push_back( Parameter("Lives","", true,false));
+ params.push_back( Parameter("Movement Speed","", true,false));
+ params.push_back( Parameter("Muscle","", true,false));
+ params.push_back( Parameter("Static","", true,false));
+ params.push_back( Parameter("Teams","", true,false));
+ params.push_back( Parameter("Knockout Shield","", true,false));
+ params.push_back( Parameter("Armor","", true,false));
+ params.push_back( Parameter("dead_body","", true,false));
+
+ params.push_back( Parameter("Peripheral FOV distance","", true,false));
+ params.push_back( Parameter("Peripheral FOV horizontal","", true,false));
+ params.push_back( Parameter("Peripheral FOV vertical","", true,false));
+
+ params.push_back( Parameter("Focus FOV distance","", true,false));
+ params.push_back( Parameter("Focus FOV horizontal","", true,false));
+ params.push_back( Parameter("Focus FOV vertical","", true,false));
+
+ ActorObject ao( "ActorObject", "actor_object", false, params );
+ aos.push_back(ao);
+ }
+ {
+ std::vector<Parameter> params = global_params;
+ ActorObject ao( "Decal", "decal_object", false, params );
+ aos.push_back(ao);
+ }
+ {
+ std::vector<Parameter> params = global_params;
+
+ params.push_back(Parameter("Collectables needed", "", true, false));
+ params.push_back(Parameter("Damage", "", true, false));
+ params.push_back(Parameter("Dialogue", "", true, false));
+ params.push_back(Parameter("LightDuration", "", true, false));
+ params.push_back(Parameter("ParticleInterval", "", true, false));
+ params.push_back(Parameter("TintFlames", "", true, false));
+ params.push_back(Parameter("UseFire", "", true, false));
+ params.push_back(Parameter("UseLights", "", true, false));
+ params.push_back(Parameter("InstantKill", "", true, false));
+ params.push_back(Parameter("XVel", "", true, false));
+ params.push_back(Parameter("YVel", "", true, false));
+ params.push_back(Parameter("ZVel", "", true, false));
+ params.push_back(Parameter("light_id", "", true, false));
+ params.push_back(Parameter("spawn_point", "", true, false));
+ params.push_back(Parameter("LastEnteredTime", "", true, false));
+ params.push_back(Parameter("music", "", true, false));
+ params.push_back(Parameter("Automatic", "", true, false));
+ params.push_back(Parameter("Visible in game", "", true, false));
+ params.push_back(Parameter("Armor", "", true, false));
+ params.push_back(Parameter("Portals", "", true, false));
+ params.push_back(Parameter("player_id", "", true, false));
+ params.push_back(Parameter("Delay Max", "", true, false));
+ params.push_back(Parameter("Delay Min", "", true, false));
+ params.push_back(Parameter("Fade Distance", "", true, false));
+ params.push_back(Parameter("Gain", "", true, false));
+ params.push_back(Parameter("Global", "", true, false));
+ params.push_back(Parameter("Invisible", "", true, false));
+ params.push_back(Parameter("Water Fog", "", true, false));
+ params.push_back(Parameter("Wave Density", "", true, false));
+ params.push_back(Parameter("Wave Height", "", true, false));
+ params.push_back(Parameter("Delay Max", "", true, false));
+ params.push_back(Parameter("Delay Min", "", true, false));
+ params.push_back(Parameter("Fade Distance", "", true, false));
+ params.push_back(Parameter("Fire Ribbons", "", true, false));
+ params.push_back(Parameter("Ignite Characters", "", true, false));
+ params.push_back(Parameter("Light Amplify", "", true, false));
+ params.push_back(Parameter("Light Distance", "", true, false));
+ params.push_back(Parameter("Light Type", "", true, false));
+ params.push_back(Parameter("Question Character", "", true, false));
+ params.push_back(Parameter("Visible in game", "", true, false));
+ params.push_back(Parameter("Hearing Modifier", "", true, false));
+ params.push_back(Parameter("Wind Scale", "", true, false));
+ params.push_back(Parameter("player_spawn", "", true, false));
+ params.push_back(Parameter("checkpoint_id", "", true, false));
+ params.push_back(Parameter("level_hotspot_id", "", true, false));
+ params.push_back(Parameter("Hotspot ID", "", true, false));
+ params.push_back(Parameter("Objects", "", true, false));
+
+ params.push_back(Parameter("Display Image", "texture", false, false));
+ params.push_back(Parameter("Level to load", "level", false, false));
+ params.push_back(Parameter("next_level", "level", false, false));
+ params.push_back(Parameter("Sound Path", "sound", false, false));
+
+ ActorObject ao( "Hotspot", "hotspot_object", false, params );
+ aos.push_back(ao);
+ }
+ {
+ std::vector<Parameter> params = global_params;
+ ActorObject ao( "ItemObject", "item", false, params );
+ aos.push_back(ao);
+ }
+ {
+ std::vector<Parameter> params = global_params;
+
+ params.push_back(Parameter("Negative", "", true, false));
+
+ ActorObject ao( "LightProbeObject", "", true, params );
+ aos.push_back(ao);
+ }
+ {
+ std::vector<Parameter> params = global_params;
+ ActorObject ao( "NavmeshRegionObject", "", true, params );
+ aos.push_back(ao);
+ }
+ {
+ std::vector<Parameter> params = global_params;
+ ActorObject ao( "NavmeshHintObject", "", true, params );
+ aos.push_back(ao);
+ }
+ {
+ std::vector<Parameter> params = global_params;
+ ActorObject ao( "NavmeshConnectionObject", "", true, params );
+ aos.push_back(ao);
+ }
+ {
+ std::vector<Parameter> params = global_params;
+
+ params.push_back(Parameter("Global", "", true, false));
+ ActorObject ao( "ReflectionCaptureObject", "", true, params );
+ aos.push_back(ao);
+ }
+ {
+ std::vector<Parameter> params = global_params;
+ ActorObject ao( "LightVolumeObject", "", true, params );
+ aos.push_back(ao);
+ }
+ {
+ std::vector<Parameter> params = global_params;
+
+ params.push_back( Parameter("Global", "",true,false));
+
+ ActorObject ao( "ReflectionCaptureObject", "", true, params );
+ aos.push_back(ao);
+ }
+ {
+ std::vector<Parameter> params = global_params;
+ params.push_back(Parameter("portal_a_light", "", true, false));
+
+ params.push_back( Parameter("Level to load", "", true,false));
+
+ ActorObject ao( "DynamicLightObject", "", true, params );
+ aos.push_back(ao);
+ }
+ {
+ std::vector<Parameter> params = global_params;
+
+ ActorObject ao( "Group", "", true, params );
+ aos.push_back(ao);
+ }
+ {
+ std::vector<Parameter> params = global_params;
+
+ ActorObject ao( "Prefab", "prefab", true, params );
+ aos.push_back(ao);
+ }
+ {
+ std::vector<Parameter> params = global_params;
+
+ ActorObject ao( "EnvObjectAttachments", "", true, params );
+ aos.push_back(ao);
+ }
+ {
+ std::vector<Parameter> params = global_params;
+
+ ActorObject ao( "Palette", "", true, params );
+ aos.push_back(ao);
+ }
+ {
+ std::vector<Parameter> params = global_params;
+
+ ActorObject ao( "ItemConnections", "", true, params );
+ aos.push_back(ao);
+ }
+ {
+ std::vector<Parameter> params = global_params;
+
+ ActorObject ao( "Connections", "", true, params );
+ aos.push_back(ao);
+ }
+
+
+ TiXmlElement* el = root->FirstChildElement();
+ while( el )
+ {
+ if( strcmp( el->Value(), "Group" ) == 0 )
+ {
+ SearchGroup(items,item,el);
+ }
+ else if( strcmp( el->Value(), "Prefab" ) == 0 )
+ {
+ SearchGroup(items,item,el);
+ }
+ else if( strcmp( el->Value(), "ActorObject" ) == 0 ) //Actor objects can have sub EnvObjects for attachments
+ {
+ SearchGroup(items,item,el);
+ }
+
+ bool found = false;
+
+ for( int i = 0; i < aos.size(); i++ )
+ {
+ found |= aos[i].Search( items, item, el );
+ }
+
+ if( !found )
+ {
+ LOGE << "Urecognized ActorObject " << el->Value() << " on row " << el->Row() << " in " << item << std::endl;
+ }
+
+ el = el->NextSiblingElement();
+ }
+}
+
+std::vector<Item> ActorObjectLevelSeeker::SearchLevelRoot( const Item& source, TiXmlHandle& hRoot )
+{
+ std::vector<Item> items;
+
+ //The standard root now seems to be ActorObjects, but for historic reasons we look for some more than that.
+ const char* entity_roots[] =
+ {
+ "ActorObjects", //Currently standard collected
+ "Groups",
+ "EnvObjects",
+ "Decals",
+ "Hotspots"
+ };
+
+ for( int i = 0; i < ARRLEN(entity_roots); i++ )
+ {
+ TiXmlElement* elem = hRoot.FirstChildElement(entity_roots[i]).ToElement();
+
+ if( elem )
+ {
+ SearchGroup( items, source, elem );
+ }
+ else
+ {
+ LOGD << source << " Is missing " << entity_roots[i] << std::endl;
+ }
+ }
+ return items;
+}
diff --git a/Source/Ogda/Searchers/Seekers/actorobjectlevelseeker.h b/Source/Ogda/Searchers/Seekers/actorobjectlevelseeker.h
new file mode 100644
index 00000000..a60dab30
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/actorobjectlevelseeker.h
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------------
+// Name: actorobjectlevelseeker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "levelseekerbase.h"
+
+class TiXmlElement;
+
+class ActorObjectLevelSeeker : public LevelSeekerBase
+{
+public:
+ virtual void SearchGroup( std::vector<Item>& items, const Item& item, TiXmlElement* root );
+ virtual std::vector<Item> SearchLevelRoot( const Item& item, TiXmlHandle& hRoot );
+
+ virtual inline const char* GetName()
+ {
+ return "actor_object_level_seeker";
+ }
+};
diff --git a/Source/Ogda/Searchers/Seekers/actorseeker.cpp b/Source/Ogda/Searchers/Seekers/actorseeker.cpp
new file mode 100644
index 00000000..09cd2337
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/actorseeker.cpp
@@ -0,0 +1,123 @@
+//-----------------------------------------------------------------------------
+// Name: actorseeker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "actorseeker.h"
+
+#include <cassert>
+
+#include <tinyxml.h>
+
+#include <Utility/strings.h>
+#include <Logging/logdata.h>
+
+std::vector<Item> ActorSeeker::SearchXML( const Item & item, TiXmlDocument& doc )
+{
+ const char* elems[] =
+ {
+ "Character"
+ };
+
+ const char* elems_type[] =
+ {
+ "character"
+ };
+
+ assert(ARRLEN(elems_type) == ARRLEN(elems));
+
+ const char* ignored[] =
+ {
+ "ControlScript"
+ };
+
+ std::vector<Item> items;
+
+ TiXmlHandle hRoot(&doc);
+
+ const char* roots[] =
+ {
+ "Actor"
+ };
+
+ TiXmlElement *eRoot = hRoot.FirstChildElement().Element();
+
+ if( !eRoot )
+ {
+ LOGE << "Can't find anything in file listed " << item << std::endl;
+ }
+
+ while( eRoot )
+ {
+ if( FindStringInArray( roots, ARRLEN(roots), eRoot->Value() ) < 0 )
+ {
+ LOGE << "Unknown root " << eRoot->Value() << std::endl;
+ }
+
+ TiXmlElement *eElem = eRoot->FirstChildElement();
+
+ while( eElem )
+ {
+ const char* name = eElem->Value();
+ const char* text = eElem->GetText();
+ if( name )
+ {
+ int id;
+ if( (id = FindStringInArray( elems, ARRLEN(elems), name )) >= 0 )
+ {
+ if( text && strlen(text) > 0 )
+ {
+ items.push_back(Item(item.input_folder,text,elems_type[id],item.source));
+ }
+ else
+ {
+ LOGW << "String value in " << item << " for element " << elems[id] << " is empty" << std::endl;
+ }
+ }
+ else if( (id = FindStringInArray( ignored, ARRLEN(ignored), name )) >= 0 )
+ {
+ LOGD << "Ignored " << ignored[id] << " in " << item << std::endl;
+ }
+ else if( strmtch( "animations", name ) )
+ {
+ std::vector<attribpair> a;
+ a.push_back(attribpair("idle", "animation"));
+
+ std::vector<const char*> i;
+
+ AttributeScanner::Do( items, item, eElem, a, i );
+ }
+ else
+ {
+ LOGE << "Unahandled subvalue in Character from " << item << " called " << name << std::endl;
+ }
+ }
+ else
+ {
+ LOGE << "Generic warning" << std::endl;
+ }
+
+ eElem = eElem->NextSiblingElement();
+ }
+ eRoot = eRoot->NextSiblingElement();
+ }
+ return items;
+
+}
diff --git a/Source/Ogda/Searchers/Seekers/actorseeker.h b/Source/Ogda/Searchers/Seekers/actorseeker.h
new file mode 100644
index 00000000..e5d2822f
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/actorseeker.h
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------------
+// Name: actorseeker.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "xmlseekerbase.h"
+
+class TiXmlDocument;
+class JobHandler;
+
+class ActorSeeker : public XMLSeekerBase
+{
+public:
+ virtual std::vector<Item> SearchXML( const Item & item, TiXmlDocument& doc );
+
+ virtual inline const char* GetName()
+ {
+ return "actor_seeker";
+ }
+};
diff --git a/Source/Ogda/Searchers/Seekers/ambientsoundlevelseeker.cpp b/Source/Ogda/Searchers/Seekers/ambientsoundlevelseeker.cpp
new file mode 100644
index 00000000..b82114cd
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/ambientsoundlevelseeker.cpp
@@ -0,0 +1,77 @@
+//-----------------------------------------------------------------------------
+// Name: ambientsoundlevelseeker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "ambientsoundlevelseeker.h"
+
+#include <tinyxml.h>
+
+#include <Logging/logdata.h>
+
+std::vector<Item> AmbientSoundLevelSeeker::SearchLevelRoot( const Item& item, TiXmlHandle& hRoot )
+{
+ std::vector<Item> items;
+ TiXmlElement *eElem = hRoot.FirstChildElement("AmbientSounds").FirstChildElement().Element();
+
+ while( eElem )
+ {
+ const char* path = eElem->Attribute("path");
+ if( path )
+ {
+ {
+ std::stringstream ss;
+ ss << path << ".xml";
+
+ Item i( item.input_folder, ss.str(), "sound", item.source );
+
+ if( i.FileAccess() )
+ {
+ items.push_back(i);
+ }
+ }
+
+ bool found_file = false;
+ int counter = 1;
+ do
+ {
+ std::stringstream ss;
+ ss << path << "_" << counter << ".wav";
+
+ Item i( item.input_folder, ss.str(), "sound", item.source );
+
+ if( i.FileAccess() )
+ {
+ found_file = true;
+ items.push_back(i);
+ }
+ else
+ {
+ found_file = false;
+ }
+ counter++;
+ }
+ while( found_file );
+ }
+ eElem = eElem->NextSiblingElement();
+ }
+
+ return items;
+}
diff --git a/Source/Ogda/Searchers/Seekers/ambientsoundlevelseeker.h b/Source/Ogda/Searchers/Seekers/ambientsoundlevelseeker.h
new file mode 100644
index 00000000..71d52cc2
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/ambientsoundlevelseeker.h
@@ -0,0 +1,38 @@
+//-----------------------------------------------------------------------------
+// Name: ambientsoundlevelseeker.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "levelseekerbase.h"
+
+class TiXmlElement;
+
+class AmbientSoundLevelSeeker : public LevelSeekerBase
+{
+public:
+ virtual std::vector<Item> SearchLevelRoot( const Item& item, TiXmlHandle& hRoot );
+
+ virtual inline const char* GetName()
+ {
+ return "ambient_sound_level_seeker";
+ }
+};
diff --git a/Source/Ogda/Searchers/Seekers/animationretargetseeker.cpp b/Source/Ogda/Searchers/Seekers/animationretargetseeker.cpp
new file mode 100644
index 00000000..245c8fe2
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/animationretargetseeker.cpp
@@ -0,0 +1,80 @@
+//-----------------------------------------------------------------------------
+// Name: animationretargetseeker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "animationretargetseeker.h"
+
+#include <cassert>
+
+#include <tinyxml.h>
+
+#include <Utility/strings.h>
+#include <Logging/logdata.h>
+
+std::vector<Item> AnimationRetargetSeeker::SearchXML( const Item & item, TiXmlDocument& doc )
+{
+ std::vector<Item> items;
+
+ TiXmlHandle hRoot(&doc);
+ TiXmlElement *eRoot = hRoot.FirstChildElement().Element();
+
+ std::vector<elempair> elems;
+ elems.push_back( elempair( "rig", "" ) );
+
+ std::vector<const char*> elems_ignore;
+
+ if( !eRoot )
+ {
+ LOGE << "Can't find anything in file listed " << item << std::endl;
+ }
+ else
+ {
+ ElementScanner::Do( items, item, eRoot, elems, elems_ignore, this, (void*)1 );
+ }
+
+ return items;
+}
+
+void AnimationRetargetSeeker::HandleElementCallback( std::vector<Item>& items, TiXmlNode* eRoot, TiXmlElement* eElem, const Item& item, void* userdata )
+{
+ if( userdata == (void*)1 && strmtch(eElem->Value(), "rig") )
+ {
+ {
+ std::vector<elempair> elems;
+ elems.push_back( elempair( "anim", "animation" ) );
+ std::vector<const char*> elems_ignore;
+ ElementScanner::Do( items, item, eElem, elems, elems_ignore, this, (void*)2 );
+ }
+ {
+ std::vector<attribpair> elems;
+ elems.push_back( attribpair( "path", "skeleton" ) );
+ std::vector<const char*> elems_ignore;
+ AttributeScanner::Do(items, item, eElem, elems, elems_ignore);
+ }
+ }
+ else if( userdata == (void*)2 )
+ {
+ }
+ else
+ {
+ XMLSeekerBase::HandleElementCallback( items, eRoot, eElem, item, userdata );
+ }
+}
diff --git a/Source/Ogda/Searchers/Seekers/animationretargetseeker.h b/Source/Ogda/Searchers/Seekers/animationretargetseeker.h
new file mode 100644
index 00000000..729003df
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/animationretargetseeker.h
@@ -0,0 +1,40 @@
+//-----------------------------------------------------------------------------
+// Name: animationretargetseeker.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "xmlseekerbase.h"
+
+class TiXmlDocument;
+class JobHandler;
+
+class AnimationRetargetSeeker : public XMLSeekerBase
+{
+public:
+ virtual std::vector<Item> SearchXML( const Item & item, TiXmlDocument& doc );
+ virtual void HandleElementCallback( std::vector<Item>& items, TiXmlNode* eRoot, TiXmlElement* eElem, const Item& item, void* userdata );
+
+ virtual inline const char* GetName()
+ {
+ return "animation_retarget_seeker";
+ }
+};
diff --git a/Source/Ogda/Searchers/Seekers/attackseeker.cpp b/Source/Ogda/Searchers/Seekers/attackseeker.cpp
new file mode 100644
index 00000000..4229aa71
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/attackseeker.cpp
@@ -0,0 +1,126 @@
+//-----------------------------------------------------------------------------
+// Name: attackseeker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "attackseeker.h"
+
+#include <cassert>
+
+#include <tinyxml.h>
+
+#include <Utility/strings.h>
+#include <Logging/logdata.h>
+
+std::vector<Item> AttackSeeker::SearchXML( const Item & item, TiXmlDocument& doc )
+{
+ const char* elems[] =
+ {
+ "reaction"
+ };
+
+ const char* elems_type[] =
+ {
+ "attack"
+ };
+
+ assert(ARRLEN(elems_type) == ARRLEN(elems));
+
+ const char* ignored[] =
+ {
+ "flags",
+ "impact_dir",
+ "block_damage",
+ "damage",
+ "sharp_damage",
+ "force",
+ "materialevent",
+ "stab_dir",
+ "cut_plane"
+ };
+
+ std::vector<Item> items;
+
+ TiXmlHandle hRoot(&doc);
+
+ TiXmlElement *eRoot = hRoot.FirstChildElement().Element();
+
+ if( !eRoot )
+ {
+ LOGE << "Can't find anything in file listed " << item << std::endl;
+ }
+
+ while( eRoot )
+ {
+ TiXmlElement *eElem = eRoot->FirstChildElement();
+
+ while( eElem )
+ {
+ const char* name = eElem->Value();
+ const char* text = eElem->GetText();
+ if( name )
+ {
+ int id;
+ if( (id = FindStringInArray( elems, ARRLEN(elems), name )) >= 0 )
+ {
+ if( text && strlen(text) > 0 )
+ {
+ items.push_back(Item(item.input_folder, text,elems_type[id],item.source));
+ }
+ else
+ {
+ LOGW << "String value in " << item << " for element " << elems[id] << " is empty" << std::endl;
+ }
+ }
+ else if( (id = FindStringInArray( ignored, ARRLEN(ignored), name )) >= 0 )
+ {
+ LOGD << "Ignored " << ignored[id] << " in " << item << std::endl;
+ }
+ else if( strmtch( "animations", name ) )
+ {
+ std::vector<attribpair> attribs;
+ attribs.push_back(attribpair("unblocked", "animation"));
+ attribs.push_back(attribpair("blocked", "animation"));
+ attribs.push_back(attribpair("throw", "animation"));
+ attribs.push_back(attribpair("thrown", "animation"));
+ attribs.push_back(attribpair("thrown_counter", "animation"));
+
+ std::vector<const char*> ignore;
+
+ AttributeScanner::Do( items, item, eElem, attribs, ignore );
+ }
+ else
+ {
+ LOGE << "Unahandled subvalue in Attack from " << item << " called " << name << std::endl;
+ }
+ }
+ else
+ {
+ LOGE << "Generic warning" << std::endl;
+ }
+
+ eElem = eElem->NextSiblingElement();
+ }
+ eRoot = eRoot->NextSiblingElement();
+ }
+
+ return items;
+
+}
diff --git a/Source/Ogda/Searchers/Seekers/attackseeker.h b/Source/Ogda/Searchers/Seekers/attackseeker.h
new file mode 100644
index 00000000..f5bdc7c0
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/attackseeker.h
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------------
+// Name: attackseeker.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "xmlseekerbase.h"
+
+class TiXmlDocument;
+class JobHandler;
+
+class AttackSeeker : public XMLSeekerBase
+{
+public:
+ virtual std::vector<Item> SearchXML( const Item & item, TiXmlDocument& doc );
+
+ virtual inline const char* GetName()
+ {
+ return "attack_seeker";
+ }
+};
diff --git a/Source/Ogda/Searchers/Seekers/characterseeker.cpp b/Source/Ogda/Searchers/Seekers/characterseeker.cpp
new file mode 100644
index 00000000..852cd090
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/characterseeker.cpp
@@ -0,0 +1,155 @@
+//-----------------------------------------------------------------------------
+// Name: characterseeker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "characterseeker.h"
+
+#include <cassert>
+
+#include <tinyxml.h>
+
+#include <Utility/strings.h>
+#include <Logging/logdata.h>
+
+std::vector<Item> CharacterSeeker::SearchXML( const Item & item, TiXmlDocument& doc )
+{
+ const char* elems[] =
+ {
+ "clothing"
+ };
+
+ const char* elems_type[] =
+ {
+ "material"
+ };
+
+ assert(ARRLEN(elems_type) == ARRLEN(elems));
+
+ const char* ignored[] =
+ {
+ "morphs",
+ "tags",
+ "visemes",
+ "morphs",
+ "team",
+ "soundmod"
+ };
+
+ std::vector<Item> items;
+
+ TiXmlHandle hRoot(&doc);
+
+ const char* roots[] =
+ {
+ "character"
+ };
+
+ TiXmlElement *eRoot = hRoot.FirstChildElement().Element();
+
+ if( !eRoot )
+ {
+ LOGE << "Can't find anything in file listed " << item << std::endl;
+ }
+
+ while( eRoot )
+ {
+ if( FindStringInArray( roots, ARRLEN(roots), eRoot->Value() ) < 0 )
+ {
+ LOGE << "Unknown root " << eRoot->Value() << " from " << item << std::endl;
+ }
+
+ TiXmlElement *eElem = eRoot->FirstChildElement();
+
+ while( eElem )
+ {
+ const char* name = eElem->Value();
+ const char* text = eElem->GetText();
+ if( name )
+ {
+ int id;
+ if( (id = FindStringInArray( elems, ARRLEN(elems), name )) >= 0 )
+ {
+ if( text && strlen(text) > 0 )
+ {
+ items.push_back(Item(item.input_folder, text,elems_type[id],item.source));
+ }
+ else
+ {
+ LOGW << "String value in " << item << " for element " << elems[id] << " is empty" << std::endl;
+ }
+ }
+ else if( (id = FindStringInArray( ignored, ARRLEN(ignored), name )) >= 0 )
+ {
+ LOGD << "Ignored " << ignored[id] << " in " << item << std::endl;
+ }
+ else if( strmtch( "animations", name ) )
+ {
+ AttributeScanner::DoAllSame( items, item, eElem, "animation" );
+ }
+ else if( strmtch( "attacks", name ) )
+ {
+ AttributeScanner::DoAllSame( items, item, eElem, "attack");
+ }
+ else if( strmtch( "appearance", name ) )
+ {
+ std::vector<attribpair> a;
+ a.push_back(attribpair("obj_path", "object"));
+ a.push_back(attribpair("morph", "model"));
+ a.push_back(attribpair("skeleton", "skeleton"));
+ a.push_back(attribpair("fur", "model"));
+
+ std::vector<const char*> i;
+ i.push_back("model_scale");
+ i.push_back("default_scale");
+ i.push_back("channel_0");
+ i.push_back("channel_1");
+ i.push_back("channel_2");
+ i.push_back("channel_3");
+ i.push_back("channel_4");
+
+ AttributeScanner::Do( items, item, eElem, a, i );
+ }
+ else if( strmtch( "voice", name ) )
+ {
+ std::vector<attribpair> a;
+ a.push_back(attribpair("path", "voice"));
+
+ std::vector<const char*> i;
+ i.push_back("pitch");
+
+ AttributeScanner::Do( items, item, eElem, a, i );
+ }
+ else
+ {
+ LOGE << "Unahandled subvalue in Character from " << item << " called " << name << std::endl;
+ }
+ }
+ else
+ {
+ LOGE << "Generic warning" << std::endl;
+ }
+
+ eElem = eElem->NextSiblingElement();
+ }
+ eRoot = eRoot->NextSiblingElement();
+ }
+ return items;
+}
diff --git a/Source/Ogda/Searchers/Seekers/characterseeker.h b/Source/Ogda/Searchers/Seekers/characterseeker.h
new file mode 100644
index 00000000..d340f47d
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/characterseeker.h
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------------
+// Name: characterseeker.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "xmlseekerbase.h"
+
+class TiXmlDocument;
+class JobHandler;
+
+class CharacterSeeker : public XMLSeekerBase
+{
+public:
+ virtual std::vector<Item> SearchXML( const Item & item, TiXmlDocument& doc );
+
+ virtual inline const char* GetName()
+ {
+ return "character_seeker";
+ }
+};
diff --git a/Source/Ogda/Searchers/Seekers/decalseeker.cpp b/Source/Ogda/Searchers/Seekers/decalseeker.cpp
new file mode 100644
index 00000000..ce7b54c9
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/decalseeker.cpp
@@ -0,0 +1,75 @@
+//-----------------------------------------------------------------------------
+// Name: decalseeker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "decalseeker.h"
+
+#include <tinyxml.h>
+
+#include <Logging/logdata.h>
+
+std::vector<Item> DecalSeeker::SearchXML( const Item& item, TiXmlDocument& doc )
+{
+ std::vector<Item> items;
+
+ TiXmlHandle root(&doc);
+
+ TiXmlElement* eColormap = root.FirstChildElement("DecalObject").FirstChildElement("ColorMap").Element();
+ TiXmlElement* eNormalmap = root.FirstChildElement("DecalObject").FirstChildElement("NormalMap").Element();
+
+ if( eColormap )
+ {
+ const char* colormap = eColormap->GetText();
+
+ if( colormap && strlen( colormap ) > 0 )
+ {
+ items.push_back(Item(item.input_folder,colormap,"texture",item.source));
+ }
+ else
+ {
+ LOGE << "ColorMap text missing" << std::endl;
+ }
+ }
+ else
+ {
+ LOGE << "ColorMap element missing in Decal" << std::endl;
+ }
+
+ if( eNormalmap )
+ {
+ const char* normalmap = eNormalmap->GetText();
+
+ if( normalmap && strlen( normalmap ) > 0 )
+ {
+ items.push_back(Item(item.input_folder, normalmap,"texture",item.source));
+ }
+ else
+ {
+ LOGE << "NormalMap text missing" << std::endl;
+ }
+ }
+ else
+ {
+ LOGE << "NormalMap element missing in Decal" << std::endl;
+ }
+
+ return items;
+}
diff --git a/Source/Ogda/Searchers/Seekers/decalseeker.h b/Source/Ogda/Searchers/Seekers/decalseeker.h
new file mode 100644
index 00000000..88537aa1
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/decalseeker.h
@@ -0,0 +1,38 @@
+//-----------------------------------------------------------------------------
+// Name: decalseeker.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "xmlseekerbase.h"
+
+class TiXmlHandle;
+
+class DecalSeeker : public XMLSeekerBase
+{
+public:
+ virtual std::vector<Item> SearchXML( const Item& item, TiXmlDocument& doc );
+
+ virtual inline const char* GetName()
+ {
+ return "decal_seeker";
+ }
+};
diff --git a/Source/Ogda/Searchers/Seekers/hotspotseeker.cpp b/Source/Ogda/Searchers/Seekers/hotspotseeker.cpp
new file mode 100644
index 00000000..787c5c58
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/hotspotseeker.cpp
@@ -0,0 +1,48 @@
+//-----------------------------------------------------------------------------
+// Name: hotspotseeker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "hotspotseeker.h"
+
+#include <tinyxml.h>
+
+#include <Logging/logdata.h>
+
+std::vector<Item> HotspotSeeker::SearchXML( const Item & item, TiXmlDocument& doc )
+{
+ std::vector<Item> items;
+
+ TiXmlHandle hRoot(&doc);
+
+ TiXmlElement *eElem = hRoot.FirstChildElement("Hotspot").FirstChildElement("BillboardColorMap").Element();
+
+ if( eElem )
+ {
+ const char* billboard = eElem->GetText();
+
+ if( billboard && strlen(billboard) > 0 )
+ {
+ items.push_back( Item( item.input_folder, billboard, "texture", item.source ));
+ }
+ }
+
+ return items;
+}
diff --git a/Source/Ogda/Searchers/Seekers/hotspotseeker.h b/Source/Ogda/Searchers/Seekers/hotspotseeker.h
new file mode 100644
index 00000000..f2677a95
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/hotspotseeker.h
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------------
+// Name: hotspotseeker.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "xmlseekerbase.h"
+
+class TiXmlDocument;
+class JobHandler;
+
+class HotspotSeeker : public XMLSeekerBase
+{
+public:
+ virtual std::vector<Item> SearchXML( const Item & item, TiXmlDocument& doc );
+
+ virtual inline const char* GetName()
+ {
+ return "hotspot_seeker";
+ }
+};
diff --git a/Source/Ogda/Searchers/Seekers/itemseeker.cpp b/Source/Ogda/Searchers/Seekers/itemseeker.cpp
new file mode 100644
index 00000000..a1c77b8e
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/itemseeker.cpp
@@ -0,0 +1,196 @@
+//-----------------------------------------------------------------------------
+// Name: itemseeker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "itemseeker.h"
+
+#include <cassert>
+
+#include <tinyxml.h>
+
+#include <Utility/strings.h>
+#include <Logging/logdata.h>
+
+enum
+{
+ ROOT = 1,
+ ITEM,
+ ATTACHMENT
+};
+
+std::vector<Item> ItemSeeker::SearchXML( const Item & item, TiXmlDocument& doc )
+{
+ std::vector<Item> items;
+
+ std::vector<elempair> elems;
+ elems.push_back( elempair( "item", "" ) );
+ elems.push_back( elempair( "attachment", "" ) );
+
+ std::vector<const char*> elems_ignore;
+
+ ElementScanner::Do( items, item, &doc, elems, elems_ignore, this, (void*)ROOT );
+
+ return items;
+}
+
+void ItemSeeker::HandleElementCallback( std::vector<Item>& items, TiXmlNode* eRoot, TiXmlElement* eElem, const Item& item, void* userdata )
+{
+ if( userdata == (void*)ROOT )
+ {
+ if( strmtch(eElem->Value(), "item") )
+ {
+ std::vector<elempair> elems;
+ elems.push_back( elempair( "appearance", "" ) );
+ elems.push_back( elempair( "grip", "" ) );
+ elems.push_back( elempair( "anim_blend", "" ) );
+ elems.push_back( elempair( "attack_override", "" ) );
+ elems.push_back( elempair( "anim_override", "" ) );
+ elems.push_back( elempair( "reaction_override", "" ) );
+ elems.push_back( elempair( "sheathe",""));
+ elems.push_back( elempair( "attachments",""));
+
+ std::vector<const char*> elems_ignore;
+ elems_ignore.push_back("type");
+ elems_ignore.push_back("points");
+ elems_ignore.push_back("lines");
+ elems_ignore.push_back("physics");
+ elems_ignore.push_back("label");
+ elems_ignore.push_back("range");
+ elems_ignore.push_back("anim_override_flags");
+
+ ElementScanner::Do( items, item, eElem, elems, elems_ignore, this, (void*)ITEM );
+ }
+ else if( strmtch(eElem->Value(), "attachment") )
+ {
+ std::vector<elempair> elems;
+ elems.push_back( elempair( "anim", "animation" ) );
+
+ std::vector<const char*> elems_ignore;
+ elems_ignore.push_back( "attach" );
+ elems_ignore.push_back( "mirror" );
+
+ ElementScanner::Do( items, item, eElem, elems, elems_ignore, this, (void*)ATTACHMENT );
+ }
+ else
+ {
+ LOGE << "Unknown item sub" << eElem->Value() << " " << item << std::endl;
+ }
+ }
+ else if( userdata == (void*)ITEM )
+ {
+ std::vector<attribpair> elems;
+ std::vector<const char*> elems_ignore;
+
+ if( strmtch(eElem->Value(), "appearance") )
+ {
+ elems.push_back(attribpair("obj_path", "object"));
+ }
+ else if( strmtch( eElem->Value(), "grip" ) )
+ {
+ elems.push_back(attribpair("anim", "animation"));
+ elems.push_back(attribpair("anim_base", "animation"));
+
+ elems_ignore.push_back("ik_attach");
+ elems_ignore.push_back("hands");
+ }
+ else if( strmtch( eElem->Value(), "anim_blend" ) )
+ {
+ elems.push_back(attribpair("idle", "animation"));
+ elems.push_back(attribpair("movement", "animation"));
+ }
+ else if( strmtch( eElem->Value(), "attack_override" ) )
+ {
+ elems.push_back(attribpair("stationary", "attack"));
+ elems.push_back(attribpair("moving", "attack"));
+ elems.push_back(attribpair("moving_close", "attack"));
+ elems.push_back(attribpair("stationary_close", "attack"));
+ elems.push_back(attribpair("low", "attack"));
+ }
+ else if( strmtch( eElem->Value(), "anim_override" ) )
+ {
+ elems.push_back(attribpair("idle", "animation"));
+ elems.push_back(attribpair("movement", "animation"));
+ elems.push_back(attribpair("medleftblock", "animation"));
+ elems.push_back(attribpair("medrightblock", "animation"));
+ elems.push_back(attribpair("highleftblock", "animation"));
+ elems.push_back(attribpair("highrightblock", "animation"));
+ elems.push_back(attribpair("lowleftblock", "animation"));
+ elems.push_back(attribpair("lowrightblock", "animation"));
+ elems.push_back(attribpair("blockflinch", "animation"));
+ }
+ else if( strmtch( eElem->Value(), "reaction_override" ) )
+ {
+ std::vector<elempair> e;
+ e.push_back( elempair( "reaction", "" ) );
+
+ std::vector<const char*> ei;
+
+ ElementScanner::Do( items, item, eElem, e, ei, this, (void*)ITEM );
+ }
+ else if( strmtch( eElem->Value(), "reaction" ) )
+ {
+ elems.push_back(attribpair("old", "attack"));
+ elems.push_back(attribpair("new", "attack"));
+ }
+ else if( strmtch( eElem->Value(), "sheathe" ) )
+ {
+ elems.push_back(attribpair("anim", "animation"));
+ elems.push_back(attribpair("anim_base", "animation"));
+ elems.push_back(attribpair("contains", "item"));
+
+ elems_ignore.push_back("ik_attach");
+ }
+ else if( strmtch( eElem->Value(), "attachments" ) )
+ {
+ std::vector<elempair> e;
+ e.push_back( elempair( "attachment", "item" ) );
+
+ std::vector<const char*> ei;
+
+ ElementScanner::Do( items, item, eElem, e, ei, this, (void*)0 );
+ }
+ else
+ {
+ LOGE << "Unknown item sub" << eElem->Value() << " " << item << std::endl;
+ }
+
+ AttributeScanner::Do( items, item, eElem, elems, elems_ignore);
+ }
+ else if( userdata == (void*)ATTACHMENT )
+ {
+ if( strmtch(eElem->Value(), "anim") )
+ {
+ //Ignore
+ }
+ else
+ {
+ LOGE << "Unknown item sub" << eElem->Value() << " " << item << std::endl;
+ }
+ }
+ else if( userdata == (void*)0 )
+ {
+
+ }
+ else
+ {
+ XMLSeekerBase::HandleElementCallback( items, eRoot, eElem, item, userdata );
+ }
+}
diff --git a/Source/Ogda/Searchers/Seekers/itemseeker.h b/Source/Ogda/Searchers/Seekers/itemseeker.h
new file mode 100644
index 00000000..0394430d
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/itemseeker.h
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------------
+// Name: itemseeker.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "xmlseekerbase.h"
+
+class TiXmlDocument;
+class JobHandler;
+
+class ItemSeeker : public XMLSeekerBase
+{
+public:
+ virtual std::vector<Item> SearchXML( const Item & item, TiXmlDocument& doc );
+ virtual void HandleElementCallback( std::vector<Item>& items, TiXmlNode* eRoot, TiXmlElement* eElem, const Item& item, void* userdata );
+ virtual inline const char* GetName()
+ {
+ return "item_seeker";
+ }
+};
diff --git a/Source/Ogda/Searchers/Seekers/jsonseekerbase.cpp b/Source/Ogda/Searchers/Seekers/jsonseekerbase.cpp
new file mode 100644
index 00000000..f380d7be
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/jsonseekerbase.cpp
@@ -0,0 +1,52 @@
+//-----------------------------------------------------------------------------
+// Name: jsonseekerbase.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "jsonseekerbase.h"
+
+#include <JSON/jsonhelper.h>
+#include <Logging/logdata.h>
+#include <fstream>
+#include <string>
+
+std::vector<Item> JSONSeekerBase::Search(const Item& item)
+{
+ std::fstream fs(item.GetAbsPath().c_str(), std::fstream::in);
+
+ if( fs.good() )
+ {
+ SimpleJSONWrapper json;
+ std::string err;
+ if( json.parseIstream( fs, err ) )
+ {
+ return SearchJSON(item, json.getRoot() );
+ }
+ else
+ {
+ LOGE << "Unable to parse " << item << " in json parser, reason: " << err << std::endl;
+ }
+ }
+ else
+ {
+ LOGE << "Unable to open " << item << " in json parser" << std::endl;
+ return std::vector<Item>();
+ }
+}
diff --git a/Source/Ogda/Searchers/Seekers/jsonseekerbase.h b/Source/Ogda/Searchers/Seekers/jsonseekerbase.h
new file mode 100644
index 00000000..83be11fc
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/jsonseekerbase.h
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------------
+// Name: jsonseekerbase.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "seekerbase.h"
+
+class JobHandler;
+
+namespace Json
+{
+ class Value;
+}
+
+class JSONSeekerBase : public SeekerBase
+{
+public:
+ virtual std::vector<Item> Search(const Item& item);
+ virtual std::vector<Item> SearchJSON(const Item & item, Json::Value& root ) = 0;
+};
diff --git a/Source/Ogda/Searchers/Seekers/levelnormseeker.cpp b/Source/Ogda/Searchers/Seekers/levelnormseeker.cpp
new file mode 100644
index 00000000..74b1daaa
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/levelnormseeker.cpp
@@ -0,0 +1,37 @@
+//-----------------------------------------------------------------------------
+// Name: levelnormseeker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "levelnormseeker.h"
+#include "Internal/filesystem.h"
+#include "Logging/logdata.h"
+
+std::vector<Item> LevelNormSeeker::Search(const Item& item ) {
+ std::vector<Item> items;
+ const std::string full_path = item.GetAbsPath();
+
+ std::string col_full_path = full_path + ".col_norm.zip";
+ if( CheckFileAccess(col_full_path.c_str()) ) {
+ items.push_back(Item(item.input_folder, item.GetPath() + ".col_norm.zip", "level_norm", item.source ));
+ }
+
+ return items;
+}
diff --git a/Source/Ogda/Searchers/Seekers/levelnormseeker.h b/Source/Ogda/Searchers/Seekers/levelnormseeker.h
new file mode 100644
index 00000000..65268bc7
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/levelnormseeker.h
@@ -0,0 +1,35 @@
+//-----------------------------------------------------------------------------
+// Name: levelnormseeker.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "seekerbase.h"
+
+class LevelNormSeeker : public SeekerBase
+{
+public:
+ virtual std::vector<Item> Search(const Item& item );
+ virtual const char* GetName()
+ {
+ return "level_norm_seeker";
+ }
+};
diff --git a/Source/Ogda/Searchers/Seekers/levelseekerbase.cpp b/Source/Ogda/Searchers/Seekers/levelseekerbase.cpp
new file mode 100644
index 00000000..e41b4a48
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/levelseekerbase.cpp
@@ -0,0 +1,43 @@
+//-----------------------------------------------------------------------------
+// Name: levelseekerbase.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "levelseekerbase.h"
+
+#include <tinyxml.h>
+
+#include <Logging/logdata.h>
+
+std::vector<Item> LevelSeekerBase::SearchXML( const Item& item, TiXmlDocument& doc )
+{
+ TiXmlElement* eRoot = doc.RootElement();
+ TiXmlHandle hRoot(eRoot);
+
+ if( strcmp(eRoot->Value(), "Level") == 0 )
+ {
+ return this->SearchLevelRoot(item, hRoot);
+ }
+ else
+ {
+ TiXmlHandle root(&(doc));
+ return this->SearchLevelRoot(item, root);
+ }
+}
diff --git a/Source/Ogda/Searchers/Seekers/levelseekerbase.h b/Source/Ogda/Searchers/Seekers/levelseekerbase.h
new file mode 100644
index 00000000..1bea4256
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/levelseekerbase.h
@@ -0,0 +1,33 @@
+//-----------------------------------------------------------------------------
+// Name: levelseekerbase.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "xmlseekerbase.h"
+
+class TiXmlHandle;
+
+class LevelSeekerBase : public XMLSeekerBase
+{
+ virtual std::vector<Item> SearchXML( const Item& item, TiXmlDocument& doc );
+ virtual std::vector<Item> SearchLevelRoot( const Item& item, TiXmlHandle& hRoot ) = 0;
+};
diff --git a/Source/Ogda/Searchers/Seekers/materialseeker.cpp b/Source/Ogda/Searchers/Seekers/materialseeker.cpp
new file mode 100644
index 00000000..54c02155
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/materialseeker.cpp
@@ -0,0 +1,191 @@
+//-----------------------------------------------------------------------------
+// Name: materialseeker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "materialseeker.h"
+
+#include <cassert>
+
+#include <tinyxml.h>
+
+#include <Utility/strings.h>
+#include <Logging/logdata.h>
+
+enum
+{
+ ROOT = 1,
+ EVENT,
+ MATERIAL,
+ PARTICLES,
+ DECALS
+};
+
+std::vector<Item> MaterialSeeker::SearchXML( const Item & item, TiXmlDocument& doc )
+{
+ std::vector<Item> items;
+
+ std::vector<elempair> elems;
+ elems.push_back( elempair( "material", "" ) );
+
+ std::vector<const char*> elems_ignore;
+
+ ElementScanner::Do( items, item, &doc, elems, elems_ignore, this, (void*)ROOT );
+
+ return items;
+}
+
+void MaterialSeeker::HandleElementCallback( std::vector<Item>& items, TiXmlNode* eRoot, TiXmlElement* eElem, const Item& item, void* userdata )
+{
+ if( userdata == (void*)ROOT )
+ {
+ if( strmtch(eElem->Value(), "material") )
+ {
+ std::vector<elempair> elems;
+
+ elems.push_back(elempair("events",""));
+ elems.push_back(elempair("particles",""));
+ elems.push_back(elempair("decals",""));
+
+ std::vector<const char*> elems_ignore;
+ elems_ignore.push_back( "physics" );
+
+ ElementScanner::Do( items, item, eElem, elems, elems_ignore, this, (void*)MATERIAL );
+ }
+ else
+ {
+ LOGE << "Unknown item sub " << eElem->Value() << " " << item << std::endl;
+ }
+ }
+ else if( userdata == (void*)MATERIAL )
+ {
+ if( strmtch(eElem->Value(), "events") )
+ {
+ std::vector<elempair> elems;
+
+ elems.push_back(elempair("leftrunstep",""));
+ elems.push_back(elempair("rightrunstep",""));
+ elems.push_back(elempair("leftwallstep",""));
+ elems.push_back(elempair("rightwallstep",""));
+ elems.push_back(elempair("leftwalkstep",""));
+ elems.push_back(elempair("rightwalkstep",""));
+ elems.push_back(elempair("leftcrouchwalkstep",""));
+ elems.push_back(elempair("rightcrouchwalkstep",""));
+ elems.push_back(elempair("leftcrouchwalkstep",""));
+ elems.push_back(elempair("rightcrouchwalkstep",""));
+ elems.push_back(elempair("land",""));
+ elems.push_back(elempair("land_soft",""));
+ elems.push_back(elempair("land_slide",""));
+ elems.push_back(elempair("slide",""));
+ elems.push_back(elempair("kick",""));
+ elems.push_back(elempair("sweep",""));
+ elems.push_back(elempair("jump",""));
+ elems.push_back(elempair("roll",""));
+ elems.push_back(elempair("edge_grab",""));
+ elems.push_back(elempair("edge_crawl",""));
+ elems.push_back(elempair("bodyfall",""));
+ elems.push_back(elempair("flip",""));
+ elems.push_back(elempair("choke_move",""));
+ elems.push_back(elempair("choke_grab",""));
+ elems.push_back(elempair("choke_full",""));
+ elems.push_back(elempair("choke_fall",""));
+
+ elems.push_back(elempair("weapon_drop_light",""));
+ elems.push_back(elempair("weapon_drop_medium",""));
+ elems.push_back(elempair("weapon_drop_heavy",""));
+
+ elems.push_back(elempair("bodyfall_light",""));
+ elems.push_back(elempair("bodyfall_medium",""));
+ elems.push_back(elempair("bodyfall_heavy",""));
+
+ elems.push_back(elempair("blood_spatter",""));
+ elems.push_back(elempair("blood_drip",""));
+
+ elems.push_back(elempair("physics",""));
+
+ std::vector<const char*> elems_ignore;
+
+ ElementScanner::Do( items, item, eElem, elems, elems_ignore, this, (void*)EVENT );
+ }
+ else if( strmtch(eElem->Value(), "particles") )
+ {
+ std::vector<elempair> elems;
+
+ elems.push_back(elempair("step",""));
+ elems.push_back(elempair("skid",""));
+
+ std::vector<const char*> elems_ignore;
+
+ ElementScanner::Do( items, item, eElem, elems, elems_ignore, this, (void*)PARTICLES );
+ }
+ else if( strmtch(eElem->Value(), "decals") )
+ {
+ std::vector<elempair> elems;
+
+ elems.push_back(elempair("step",""));
+ elems.push_back(elempair("skid",""));
+
+ std::vector<const char*> elems_ignore;
+
+ ElementScanner::Do( items, item, eElem, elems, elems_ignore, this, (void*)DECALS );
+ }
+ else
+ {
+ LOGE << "Unknown item sub " << eElem->Value() << " " << item << std::endl;
+ }
+ }
+ else if( userdata == (void*)DECALS )
+ {
+ std::vector<attribpair> elems;
+ std::vector<const char*> elems_ignore;
+
+ elems.push_back(attribpair("color", "texture"));
+ elems.push_back(attribpair("normal", "texture"));
+
+ elems_ignore.push_back("shader");
+
+ AttributeScanner::Do( items, item, eElem, elems, elems_ignore);
+ }
+ else if( userdata == (void*)EVENT )
+ {
+ std::vector<attribpair> elems;
+ std::vector<const char*> elems_ignore;
+
+ elems.push_back(attribpair("soundgroup", "sound"));
+
+ elems_ignore.push_back("attached");
+ elems_ignore.push_back("max_distance");
+
+ AttributeScanner::Do( items, item, eElem, elems, elems_ignore);
+ }
+ else if( userdata == (void*)PARTICLES )
+ {
+ std::vector<attribpair> elems;
+ std::vector<const char*> elems_ignore;
+
+ elems.push_back(attribpair("path", "particle"));
+
+ AttributeScanner::Do( items, item, eElem, elems, elems_ignore);
+ }
+ else
+ {
+ XMLSeekerBase::HandleElementCallback( items, eRoot, eElem, item, userdata );
+ }
+}
diff --git a/Source/Ogda/Searchers/Seekers/materialseeker.h b/Source/Ogda/Searchers/Seekers/materialseeker.h
new file mode 100644
index 00000000..b8b21484
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/materialseeker.h
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------------
+// Name: materialseeker.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "xmlseekerbase.h"
+
+class TiXmlDocument;
+class JobHandler;
+
+class MaterialSeeker : public XMLSeekerBase
+{
+public:
+ virtual std::vector<Item> SearchXML( const Item & item, TiXmlDocument& doc );
+ virtual void HandleElementCallback( std::vector<Item>& items, TiXmlNode* eRoot, TiXmlElement* eElem, const Item& item, void* userdata );
+ virtual inline const char* GetName()
+ {
+ return "material_seeker";
+ }
+};
diff --git a/Source/Ogda/Searchers/Seekers/objcolseeker.cpp b/Source/Ogda/Searchers/Seekers/objcolseeker.cpp
new file mode 100644
index 00000000..3aa985da
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/objcolseeker.cpp
@@ -0,0 +1,43 @@
+//-----------------------------------------------------------------------------
+// Name: objcolseeker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "objcolseeker.h"
+#include "Internal/filesystem.h"
+#include "Logging/logdata.h"
+
+std::vector<Item> ObjColSeeker::Search(const Item& item ) {
+ std::vector<Item> items;
+ const std::string full_path = item.GetAbsPath();
+
+ std::string col_full_path = full_path.substr(0,full_path.length()-4) + "_COL.obj";
+ if( CheckFileAccess(col_full_path.c_str()) ) {
+ items.push_back(Item(item.input_folder, item.GetPath().substr(0,item.path.length()-4) + "_COL.obj", "model_collision", item.source ));
+ } else {
+ col_full_path = full_path.substr(0,full_path.length()-4) + "_col.obj";
+ if( CheckFileAccess(col_full_path.c_str()) )
+ {
+ items.push_back(Item( item.input_folder, item.GetPath().substr(0,item.path.length()-4) + "_col.obj", "model_collision", item.source ));
+ }
+ }
+
+ return items;
+}
diff --git a/Source/Ogda/Searchers/Seekers/objcolseeker.h b/Source/Ogda/Searchers/Seekers/objcolseeker.h
new file mode 100644
index 00000000..4605b435
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/objcolseeker.h
@@ -0,0 +1,35 @@
+//-----------------------------------------------------------------------------
+// Name: objcolseeker.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "seekerbase.h"
+
+class ObjColSeeker : public SeekerBase
+{
+public:
+ virtual std::vector<Item> Search( const Item& item );
+ virtual const char* GetName()
+ {
+ return "objcol_seeker";
+ }
+};
diff --git a/Source/Ogda/Searchers/Seekers/objectseeker.cpp b/Source/Ogda/Searchers/Seekers/objectseeker.cpp
new file mode 100644
index 00000000..6c1babd2
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/objectseeker.cpp
@@ -0,0 +1,177 @@
+//-----------------------------------------------------------------------------
+// Name: objectseeker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "objectseeker.h"
+
+#include <tinyxml.h>
+
+#include <Logging/logdata.h>
+#include <Utility/strings.h>
+
+std::vector<Item> ObjectSeeker::SearchXML( const Item & item, TiXmlDocument& doc )
+{
+ const char* elems[] =
+ {
+ "Model",
+ "ColorMap",
+ "NormalMap",
+ "PaletteMap",
+ "WeightMap",
+ "TranslucencyMap",
+ "WindMap",
+ "MaterialPath",
+ "SharpnessMap"
+ };
+
+ const char* elems_type[] =
+ {
+ "model",
+ "texture",
+ "texture",
+ "texture",
+ "texture",
+ "texture",
+ "texture",
+ "material",
+ "texture"
+ };
+
+ assert(ARRLEN(elems_type) == ARRLEN(elems));
+
+ const char* ignored[] =
+ {
+ "Shader",
+ "ShaderName",
+ "ShaderPath",
+ "flags",
+ "GroundOffset",
+ "avg_color",
+ "label"
+ };
+
+ std::vector<Item> items;
+
+ TiXmlHandle hRoot(&doc);
+
+ TiXmlElement *eElem = hRoot.FirstChildElement("Object").FirstChildElement().Element();
+
+ if( !eElem )
+ {
+ LOGE << "Cant find right root node in " << item << std::endl;
+ }
+
+ while( eElem )
+ {
+ const char* name = eElem->Value();
+ const char* text = eElem->GetText();
+ if( name )
+ {
+ int id;
+ if( (id = FindStringInArray( elems, ARRLEN(elems), name )) >= 0 )
+ {
+ if( text && strlen(text) > 0 )
+ {
+ items.push_back(Item(item.input_folder, text,elems_type[id],item.source));
+ }
+ else
+ {
+ LOGW << "String value in " << item << " for element " << elems[id] << " is empty" << std::endl;
+ }
+ }
+ else if( (id = FindStringInArray( ignored, ARRLEN(ignored), name )) >= 0 )
+ {
+ LOGD << "Ignored " << ignored[id] << " in " << item << std::endl;
+ }
+ else if( strmtch( "DetailObjects", name ) )
+ {
+ TiXmlElement *eDetailObject = eElem->FirstChildElement();
+
+ while( eDetailObject )
+ {
+ {
+ const char* obj_path = eDetailObject->Attribute("obj_path");
+ if( obj_path )
+ {
+ items.push_back(Item(item.input_folder, obj_path, "object", item.source));
+ }
+ }
+
+ {
+ const char* weight_path = eDetailObject->Attribute("weight_path");
+ if( weight_path )
+ {
+ items.push_back(Item(item.input_folder, weight_path, "texture", item.source));
+ }
+ }
+
+ eDetailObject = eDetailObject->NextSiblingElement();
+ }
+ }
+ else if( strmtch( "DetailMaps", name ) )
+ {
+ TiXmlElement *eDetailMap = eElem->FirstChildElement();
+
+ while( eDetailMap )
+ {
+
+ {
+ const char* colorpath = eDetailMap->Attribute("colorpath");
+ if( colorpath )
+ {
+ items.push_back(Item(item.input_folder, colorpath,"texture",item.source));
+ }
+ }
+
+ {
+ const char* normalpath = eDetailMap->Attribute("normalpath");
+ if( normalpath )
+ {
+ items.push_back(Item(item.input_folder, normalpath,"texture",item.source));
+ }
+ }
+
+ {
+ const char* materialpath = eDetailMap->Attribute("materialpath");
+ if( materialpath )
+ {
+ items.push_back(Item(item.input_folder, materialpath,"material",item.source));
+ }
+ }
+
+ eDetailMap = eDetailMap->NextSiblingElement();
+ }
+ }
+ else
+ {
+ LOGE << "Unahandled subvalue in Object from " << item << " called " << name << std::endl;
+ }
+ }
+ else
+ {
+ LOGE << "Generic warning" << std::endl;
+ }
+
+ eElem = eElem->NextSiblingElement();
+ }
+
+ return items;
+}
diff --git a/Source/Ogda/Searchers/Seekers/objectseeker.h b/Source/Ogda/Searchers/Seekers/objectseeker.h
new file mode 100644
index 00000000..9ddfb68f
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/objectseeker.h
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------------
+// Name: objectseeker.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "xmlseekerbase.h"
+
+class TiXmlDocument;
+class JobHandler;
+
+class ObjectSeeker : public XMLSeekerBase
+{
+public:
+ virtual std::vector<Item> SearchXML( const Item & item, TiXmlDocument& doc );
+
+ virtual inline const char* GetName()
+ {
+ return "object_seeker";
+ }
+};
diff --git a/Source/Ogda/Searchers/Seekers/objhullseeker.cpp b/Source/Ogda/Searchers/Seekers/objhullseeker.cpp
new file mode 100644
index 00000000..64d1df92
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/objhullseeker.cpp
@@ -0,0 +1,48 @@
+//-----------------------------------------------------------------------------
+// Name: objhullseeker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "objhullseeker.h"
+#include "Internal/filesystem.h"
+#include "Logging/logdata.h"
+
+std::vector<Item> ObjHullSeeker::Search(const Item& item )
+{
+ std::vector<Item> items;
+ std::string full_path;
+
+ full_path = item.GetAbsPath();
+ full_path = full_path.substr(0,full_path.length()-4) + "HULL.obj";
+ if( CheckFileAccess(full_path.c_str()) )
+ {
+ items.push_back(Item( item.input_folder, item.GetPath().substr(0,item.path.length()-4) + "HULL.obj", "model_hull", item.source ));
+ }
+
+ full_path = item.GetAbsPath();
+ full_path = full_path.substr(0,full_path.length()-4) + "hull.obj";
+ if( CheckFileAccess(full_path.c_str()) )
+ {
+ items.push_back(Item( item.input_folder, item.GetPath().substr(0,item.path.length()-4) + "hull.obj", "model_hull", item.source ));
+ }
+
+ return items;
+}
+
diff --git a/Source/Ogda/Searchers/Seekers/objhullseeker.h b/Source/Ogda/Searchers/Seekers/objhullseeker.h
new file mode 100644
index 00000000..6836ed45
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/objhullseeker.h
@@ -0,0 +1,35 @@
+//-----------------------------------------------------------------------------
+// Name: objhullseeker.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "seekerbase.h"
+
+class ObjHullSeeker : public SeekerBase
+{
+public:
+ virtual std::vector<Item> Search(const Item& item );
+ virtual const char* GetName()
+ {
+ return "objhull_seeker";
+ }
+};
diff --git a/Source/Ogda/Searchers/Seekers/particleseeker.cpp b/Source/Ogda/Searchers/Seekers/particleseeker.cpp
new file mode 100644
index 00000000..6f1857a1
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/particleseeker.cpp
@@ -0,0 +1,122 @@
+//-----------------------------------------------------------------------------
+// Name: particleseeker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "particleseeker.h"
+
+#include <cassert>
+
+#include <tinyxml.h>
+
+#include <Utility/strings.h>
+#include <Logging/logdata.h>
+
+enum
+{
+ ROOT = 1,
+ PARTICLE
+};
+
+std::vector<Item> ParticleSeeker::SearchXML( const Item & item, TiXmlDocument& doc )
+{
+ std::vector<Item> items;
+
+ std::vector<elempair> elems;
+ elems.push_back( elempair( "particle", "" ) );
+
+ std::vector<const char*> elems_ignore;
+
+ ElementScanner::Do( items, item, &doc, elems, elems_ignore, this, (void*)ROOT );
+
+ return items;
+}
+
+void ParticleSeeker::HandleElementCallback( std::vector<Item>& items, TiXmlNode* eRoot, TiXmlElement* eElem, const Item& item, void* userdata )
+{
+ if( userdata == (void*)ROOT )
+ {
+ if( strmtch(eElem->Value(), "particle") )
+ {
+ std::vector<elempair> elems;
+ elems.push_back( elempair( "textures", "" ) );
+ elems.push_back( elempair( "collision", "" ) );
+
+ std::vector<const char*> elems_ignore;
+ elems_ignore.push_back("size");
+ elems_ignore.push_back("rotation");
+ elems_ignore.push_back("stretch");
+ elems_ignore.push_back("color");
+ elems_ignore.push_back("behaviour");
+ elems_ignore.push_back("behavior");
+ elems_ignore.push_back("no_rotation");
+ elems_ignore.push_back("quadratic_expansion");
+ elems_ignore.push_back("quadratic_dispersion");
+
+ ElementScanner::Do( items, item, eElem, elems, elems_ignore, this, (void*)PARTICLE );
+ }
+ else
+ {
+ LOGE << "Unknown item sub" << eElem->Value() << " " << item << std::endl;
+ }
+ }
+ else if( userdata == (void*)PARTICLE )
+ {
+ std::vector<attribpair> elems;
+ std::vector<const char*> elems_ignore;
+
+ if( strmtch(eElem->Value(), "textures") )
+ {
+ elems.push_back(attribpair("color_map", "texture"));
+ elems.push_back(attribpair("normal_map", "texture"));
+ //Ignoring this as it never seems to actually point to any real object.
+ //elems.push_back(attribpair("animation_effect", "animation_effect"));
+
+ elems_ignore.push_back( "animation_effect" );
+ elems_ignore.push_back( "shader" );
+ elems_ignore.push_back( "soft_shader" );
+ }
+ else if( strmtch(eElem->Value(), "collision") )
+ {
+ elems.push_back(attribpair("decal", "decal_object"));
+
+ elems_ignore.push_back( "decal_size_mult" );
+ elems_ignore.push_back( "destroy" );
+ elems_ignore.push_back( "character_collide" );
+ elems_ignore.push_back( "character_add_blood" );
+ elems_ignore.push_back( "material_event" );
+ elems_ignore.push_back( "materialevent" );
+ }
+ else
+ {
+ LOGE << "Unknown item sub" << eElem->Value() << " " << item << std::endl;
+ }
+
+ AttributeScanner::Do( items, item, eElem, elems, elems_ignore);
+ }
+ else if( userdata == (void*)0 )
+ {
+
+ }
+ else
+ {
+ XMLSeekerBase::HandleElementCallback( items, eRoot, eElem, item, userdata );
+ }
+}
diff --git a/Source/Ogda/Searchers/Seekers/particleseeker.h b/Source/Ogda/Searchers/Seekers/particleseeker.h
new file mode 100644
index 00000000..725c5908
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/particleseeker.h
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------------
+// Name: particleseeker.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "xmlseekerbase.h"
+
+class TiXmlDocument;
+class JobHandler;
+
+class ParticleSeeker : public XMLSeekerBase
+{
+public:
+ virtual std::vector<Item> SearchXML( const Item & item, TiXmlDocument& doc );
+ virtual void HandleElementCallback( std::vector<Item>& items, TiXmlNode* eRoot, TiXmlElement* eElem, const Item& item, void* userdata );
+ virtual inline const char* GetName()
+ {
+ return "particle_seeker";
+ }
+};
diff --git a/Source/Ogda/Searchers/Seekers/preconvertedddsshadowseeker.cpp b/Source/Ogda/Searchers/Seekers/preconvertedddsshadowseeker.cpp
new file mode 100644
index 00000000..85dde581
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/preconvertedddsshadowseeker.cpp
@@ -0,0 +1,40 @@
+//-----------------------------------------------------------------------------
+// Name: preconvertedddsshadowseeker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "preconvertedddsshadowseeker.h"
+
+#include <Internal/filesystem.h>
+
+std::vector<Item> PreConvertedDDSSeeker::Search( const Item& item )
+{
+ std::vector<Item> items;
+ std::string abs_path = item.GetAbsPath() + "_converted.dds";
+
+ if(CheckFileAccess(abs_path.c_str()))
+ {
+ Item nitem = Item( item.input_folder, item.GetPath() + "_converted.dds", item.type, item.source );
+ nitem.SetOvershadows(item);
+ items.push_back( nitem );
+ }
+
+ return items;
+}
diff --git a/Source/Ogda/Searchers/Seekers/preconvertedddsshadowseeker.h b/Source/Ogda/Searchers/Seekers/preconvertedddsshadowseeker.h
new file mode 100644
index 00000000..5ce9b832
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/preconvertedddsshadowseeker.h
@@ -0,0 +1,38 @@
+//-----------------------------------------------------------------------------
+// Name: preconvertedddsshadowseeker.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "seekerbase.h"
+
+class JobHandler;
+
+class PreConvertedDDSSeeker : public SeekerBase
+{
+public:
+ virtual std::vector<Item> Search( const Item& item );
+
+ virtual inline const char* GetName()
+ {
+ return "pre_converted_dds_shadow";
+ }
+};
diff --git a/Source/Ogda/Searchers/Seekers/prefabseeker.cpp b/Source/Ogda/Searchers/Seekers/prefabseeker.cpp
new file mode 100644
index 00000000..2eac5efc
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/prefabseeker.cpp
@@ -0,0 +1,929 @@
+//-----------------------------------------------------------------------------
+// Name: prefabseeker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "prefabseeker.h"
+
+#include <cassert>
+
+#include <tinyxml.h>
+
+#include <Utility/strings.h>
+#include <Logging/logdata.h>
+
+enum
+{
+ ROOT = 1,
+ GROUP,
+ PARAMETER
+};
+
+std::vector<Item> PrefabSeeker::SearchXML( const Item & item, TiXmlDocument& doc )
+{
+ std::vector<Item> items;
+
+ std::vector<elempair> elems;
+ elems.push_back( elempair( "Prefab", "" ) );
+ elems.push_back( elempair( "Groups", "" ) );
+ elems.push_back( elempair( "ActorObjects", "" ) );
+ elems.push_back( elempair( "EnvObjects", "" ) );
+ elems.push_back( elempair( "Decals", "" ) );
+ elems.push_back( elempair( "Hotspots", "" ) );
+
+ std::vector<const char*> elems_ignore;
+ elems_ignore.push_back( "Type" );
+
+ ElementScanner::Do( items, item, &doc, elems, elems_ignore, this, (void*)ROOT );
+
+ return items;
+}
+
+void PrefabSeeker::HandleElementCallback( std::vector<Item>& items, TiXmlNode* eRoot, TiXmlElement* eElem, const Item& item, void* userdata )
+{
+ if( userdata == (void*)ROOT )
+ {
+ std::vector<elempair> elems;
+ elems.push_back( elempair( "parameters", "") );
+ elems.push_back( elempair( "Prefab", "" ) );
+ elems.push_back( elempair( "Group", "" ) );
+ elems.push_back( elempair( "ActorObject","" ));
+ elems.push_back( elempair( "EnvObject","" ));
+ elems.push_back( elempair( "Decal","" ));
+ elems.push_back( elempair( "Hotspot","" ));
+ elems.push_back( elempair( "PlaceholderObject","" ));
+ elems.push_back( elempair( "PathPointObject", "" ));
+ elems.push_back( elempair( "LightProbeObject", "" ));
+ elems.push_back( elempair( "DynamicLightObject", "" ));
+ elems.push_back( elempair( "NavmeshHintObject", ""));
+ elems.push_back( elempair( "NavmeshConnectionObject", ""));
+ elems.push_back( elempair( "NavmeshRegionObject", ""));
+ elems.push_back( elempair( "ReflectionCaptureObject",""));
+ elems.push_back( elempair( "LightVolumeObject",""));
+
+ std::vector<const char*> elems_ignore;
+
+ ElementScanner::Do( items, item, eElem, elems, elems_ignore, this, (void*)GROUP );
+ }
+ else if( userdata == (void*)GROUP )
+ {
+ if( strmtch(eElem->Value(), "Group") )
+ {
+ std::vector<attribpair> attribs;
+
+ std::vector<const char*> attribs_ignore;
+
+ attribs_ignore.push_back( "id" );
+ attribs_ignore.push_back( "group_id" );
+
+ attribs_ignore.push_back( "s0" );
+ attribs_ignore.push_back( "s1" );
+ attribs_ignore.push_back( "s2" );
+
+ attribs_ignore.push_back( "t0" );
+ attribs_ignore.push_back( "t1" );
+ attribs_ignore.push_back( "t2" );
+
+ attribs_ignore.push_back( "color_r" );
+ attribs_ignore.push_back( "color_b" );
+ attribs_ignore.push_back( "color_g" );
+
+ attribs_ignore.push_back( "overbright" );
+
+ attribs_ignore.push_back( "r0" );
+ attribs_ignore.push_back( "r1" );
+ attribs_ignore.push_back( "r2" );
+ attribs_ignore.push_back( "r3" );
+ attribs_ignore.push_back( "r4" );
+ attribs_ignore.push_back( "r5" );
+ attribs_ignore.push_back( "r6" );
+ attribs_ignore.push_back( "r7" );
+ attribs_ignore.push_back( "r8" );
+ attribs_ignore.push_back( "r9" );
+ attribs_ignore.push_back( "r10" );
+ attribs_ignore.push_back( "r11" );
+ attribs_ignore.push_back( "r12" );
+ attribs_ignore.push_back( "r13" );
+ attribs_ignore.push_back( "r14" );
+ attribs_ignore.push_back( "r15" );
+
+ attribs_ignore.push_back( "uses_imposter" );
+
+ attribs_ignore.push_back( "d0" );
+ attribs_ignore.push_back( "d1" );
+ attribs_ignore.push_back( "d2" );
+
+ attribs_ignore.push_back( "c0" );
+ attribs_ignore.push_back( "c1" );
+ attribs_ignore.push_back( "c2" );
+
+ attribs_ignore.push_back( "q0" );
+ attribs_ignore.push_back( "q1" );
+ attribs_ignore.push_back( "q2" );
+ attribs_ignore.push_back( "q3" );
+
+ attribs_ignore.push_back( "version" );
+
+ AttributeScanner::Do( items, item, eElem, attribs, attribs_ignore );
+
+ std::vector<elempair> elems;
+ elems.push_back( elempair( "parameters", "") );
+ elems.push_back( elempair( "Prefab", "" ) );
+ elems.push_back( elempair( "Group", "" ) );
+ elems.push_back( elempair( "ActorObject","" ));
+ elems.push_back( elempair( "EnvObject","" ));
+ elems.push_back( elempair( "Decal","" ));
+ elems.push_back( elempair( "Hotspot","" ));
+ elems.push_back( elempair( "PlaceholderObject","" ));
+ elems.push_back( elempair( "PathPointObject", "" ));
+ elems.push_back( elempair( "LightProbeObject", "" ));
+ elems.push_back( elempair( "DynamicLightObject", "" ));
+ elems.push_back( elempair( "NavmeshHintObject", ""));
+ elems.push_back( elempair( "NavmeshConnectionObject", ""));
+ elems.push_back( elempair( "NavmeshRegionObject", ""));
+ elems.push_back( elempair( "ReflectionCaptureObject",""));
+ elems.push_back( elempair( "LightVolumeObject",""));
+
+ std::vector<const char*> elems_ignore;
+
+ ElementScanner::Do( items, item, eElem, elems, elems_ignore, this, (void*)GROUP );
+ }
+ else if( strmtch(eElem->Value(), "Prefab") )
+ {
+ std::vector<attribpair> attribs;
+
+ std::vector<const char*> attribs_ignore;
+
+ attribs_ignore.push_back( "id" );
+ attribs_ignore.push_back( "group_id" );
+
+ attribs_ignore.push_back( "s0" );
+ attribs_ignore.push_back( "s1" );
+ attribs_ignore.push_back( "s2" );
+
+ attribs_ignore.push_back( "t0" );
+ attribs_ignore.push_back( "t1" );
+ attribs_ignore.push_back( "t2" );
+
+ attribs_ignore.push_back( "color_r" );
+ attribs_ignore.push_back( "color_b" );
+ attribs_ignore.push_back( "color_g" );
+
+ attribs_ignore.push_back( "overbright" );
+
+ attribs_ignore.push_back( "r0" );
+ attribs_ignore.push_back( "r1" );
+ attribs_ignore.push_back( "r2" );
+ attribs_ignore.push_back( "r3" );
+ attribs_ignore.push_back( "r4" );
+ attribs_ignore.push_back( "r5" );
+ attribs_ignore.push_back( "r6" );
+ attribs_ignore.push_back( "r7" );
+ attribs_ignore.push_back( "r8" );
+ attribs_ignore.push_back( "r9" );
+ attribs_ignore.push_back( "r10" );
+ attribs_ignore.push_back( "r11" );
+ attribs_ignore.push_back( "r12" );
+ attribs_ignore.push_back( "r13" );
+ attribs_ignore.push_back( "r14" );
+ attribs_ignore.push_back( "r15" );
+
+ attribs_ignore.push_back( "uses_imposter" );
+
+ attribs_ignore.push_back( "d0" );
+ attribs_ignore.push_back( "d1" );
+ attribs_ignore.push_back( "d2" );
+
+ attribs_ignore.push_back( "c0" );
+ attribs_ignore.push_back( "c1" );
+ attribs_ignore.push_back( "c2" );
+
+ attribs_ignore.push_back( "q0" );
+ attribs_ignore.push_back( "q1" );
+ attribs_ignore.push_back( "q2" );
+ attribs_ignore.push_back( "q3" );
+
+ attribs_ignore.push_back( "version" );
+
+ AttributeScanner::Do( items, item, eElem, attribs, attribs_ignore );
+
+ std::vector<elempair> elems;
+ elems.push_back( elempair( "parameters", "") );
+ elems.push_back( elempair( "Prefab", "" ) );
+ elems.push_back( elempair( "Group", "" ) );
+ elems.push_back( elempair( "ActorObject","" ));
+ elems.push_back( elempair( "EnvObject","" ));
+ elems.push_back( elempair( "Decal","" ));
+ elems.push_back( elempair( "Hotspot","" ));
+ elems.push_back( elempair( "PlaceholderObject","" ));
+ elems.push_back( elempair( "PathPointObject", "" ));
+ elems.push_back( elempair( "LightProbeObject", "" ));
+ elems.push_back( elempair( "DynamicLightObject", "" ));
+ elems.push_back( elempair( "NavmeshHintObject", ""));
+ elems.push_back( elempair( "NavmeshConnectionObject", ""));
+ elems.push_back( elempair( "NavmeshRegionObject", ""));
+ elems.push_back( elempair( "ReflectionCaptureObject",""));
+ elems.push_back( elempair( "LightVolumeObject",""));
+
+ std::vector<const char*> elems_ignore;
+
+ ElementScanner::Do( items, item, eElem, elems, elems_ignore, this, (void*)GROUP );
+ }
+ else if( strmtch(eElem->Value(), "PlaceholderObject") )
+ {
+ std::vector<attribpair> attribs;
+ attribs.push_back( attribpair("type_file", "object") );
+
+ std::vector<const char*> attribs_ignore;
+ attribs_ignore.push_back( "id" );
+ attribs_ignore.push_back( "group_id" );
+ attribs_ignore.push_back( "s0" );
+ attribs_ignore.push_back( "s1" );
+ attribs_ignore.push_back( "s2" );
+
+ attribs_ignore.push_back( "t0" );
+ attribs_ignore.push_back( "t1" );
+ attribs_ignore.push_back( "t2" );
+
+ attribs_ignore.push_back( "color_r" );
+ attribs_ignore.push_back( "color_b" );
+ attribs_ignore.push_back( "color_g" );
+
+ attribs_ignore.push_back( "overbright" );
+
+ attribs_ignore.push_back( "r0" );
+ attribs_ignore.push_back( "r1" );
+ attribs_ignore.push_back( "r2" );
+ attribs_ignore.push_back( "r3" );
+ attribs_ignore.push_back( "r4" );
+ attribs_ignore.push_back( "r5" );
+ attribs_ignore.push_back( "r6" );
+ attribs_ignore.push_back( "r7" );
+ attribs_ignore.push_back( "r8" );
+ attribs_ignore.push_back( "r9" );
+ attribs_ignore.push_back( "r10" );
+ attribs_ignore.push_back( "r11" );
+ attribs_ignore.push_back( "r12" );
+ attribs_ignore.push_back( "r13" );
+ attribs_ignore.push_back( "r14" );
+ attribs_ignore.push_back( "r15" );
+
+ attribs_ignore.push_back( "q0" );
+ attribs_ignore.push_back( "q1" );
+ attribs_ignore.push_back( "q2" );
+ attribs_ignore.push_back( "q3" );
+
+ attribs_ignore.push_back( "special_type" );
+
+ AttributeScanner::Do( items, item, eElem, attribs, attribs_ignore );
+ std::vector<elempair> elems;
+ elems.push_back( elempair( "parameters", "" ) );
+
+ std::vector<const char*> elems_ignore;
+
+ ElementScanner::Do( items, item, eElem, elems, elems_ignore, this, (void*)GROUP );
+ }
+ else if( strmtch(eElem->Value(), "EnvObject") )
+ {
+ std::vector<attribpair> attribs;
+ attribs.push_back( attribpair("type_file", "object") );
+
+ std::vector<const char*> attribs_ignore;
+ attribs_ignore.push_back( "id" );
+ attribs_ignore.push_back( "group_id" );
+ attribs_ignore.push_back( "s0" );
+ attribs_ignore.push_back( "s1" );
+ attribs_ignore.push_back( "s2" );
+
+ attribs_ignore.push_back( "t0" );
+ attribs_ignore.push_back( "t1" );
+ attribs_ignore.push_back( "t2" );
+
+ attribs_ignore.push_back( "color_r" );
+ attribs_ignore.push_back( "color_b" );
+ attribs_ignore.push_back( "color_g" );
+
+ attribs_ignore.push_back( "overbright" );
+
+ attribs_ignore.push_back( "r0" );
+ attribs_ignore.push_back( "r1" );
+ attribs_ignore.push_back( "r2" );
+ attribs_ignore.push_back( "r3" );
+ attribs_ignore.push_back( "r4" );
+ attribs_ignore.push_back( "r5" );
+ attribs_ignore.push_back( "r6" );
+ attribs_ignore.push_back( "r7" );
+ attribs_ignore.push_back( "r8" );
+ attribs_ignore.push_back( "r9" );
+ attribs_ignore.push_back( "r10" );
+ attribs_ignore.push_back( "r11" );
+ attribs_ignore.push_back( "r12" );
+ attribs_ignore.push_back( "r13" );
+ attribs_ignore.push_back( "r14" );
+ attribs_ignore.push_back( "r15" );
+
+ attribs_ignore.push_back( "q0" );
+ attribs_ignore.push_back( "q1" );
+ attribs_ignore.push_back( "q2" );
+ attribs_ignore.push_back( "q3" );
+
+ AttributeScanner::Do( items, item, eElem, attribs, attribs_ignore );
+ std::vector<elempair> elems;
+ elems.push_back( elempair( "parameters", "" ) );
+
+ std::vector<const char*> elems_ignore;
+
+ ElementScanner::Do( items, item, eElem, elems, elems_ignore, this, (void*)GROUP );
+ }
+ else if( strmtch(eElem->Value(), "Decal") )
+ {
+ std::vector<attribpair> attribs;
+ attribs.push_back( attribpair("type_file", "decal_object") );
+
+ std::vector<const char*> attribs_ignore;
+ attribs_ignore.push_back( "i" );
+ attribs_ignore.push_back( "id" );
+ attribs_ignore.push_back( "group_id" );
+ attribs_ignore.push_back( "s0" );
+ attribs_ignore.push_back( "s1" );
+ attribs_ignore.push_back( "s2" );
+ attribs_ignore.push_back( "r0" );
+ attribs_ignore.push_back( "r1" );
+ attribs_ignore.push_back( "r2" );
+ attribs_ignore.push_back( "r3" );
+ attribs_ignore.push_back( "r4" );
+ attribs_ignore.push_back( "r5" );
+ attribs_ignore.push_back( "r6" );
+ attribs_ignore.push_back( "r7" );
+ attribs_ignore.push_back( "r8" );
+ attribs_ignore.push_back( "r9" );
+ attribs_ignore.push_back( "r10" );
+ attribs_ignore.push_back( "r11" );
+ attribs_ignore.push_back( "r12" );
+ attribs_ignore.push_back( "r13" );
+ attribs_ignore.push_back( "r14" );
+ attribs_ignore.push_back( "r15" );
+
+ attribs_ignore.push_back( "color_r" );
+ attribs_ignore.push_back( "color_b" );
+ attribs_ignore.push_back( "color_g" );
+ attribs_ignore.push_back( "overbright" );
+
+ attribs_ignore.push_back( "q0" );
+ attribs_ignore.push_back( "q1" );
+ attribs_ignore.push_back( "q2" );
+ attribs_ignore.push_back( "q3" );
+
+ attribs_ignore.push_back( "p0" );
+ attribs_ignore.push_back( "p1" );
+ attribs_ignore.push_back( "p2" );
+
+ attribs_ignore.push_back( "p_mode" );
+
+ attribs_ignore.push_back( "t0" );
+ attribs_ignore.push_back( "t1" );
+ attribs_ignore.push_back( "t2" );
+
+ AttributeScanner::Do( items, item, eElem, attribs, attribs_ignore );
+
+ std::vector<elempair> elems;
+ elems.push_back( elempair( "parameters", "" ) );
+
+ std::vector<const char*> elems_ignore;
+
+ ElementScanner::Do( items, item, eElem, elems, elems_ignore, this, (void*)GROUP );
+ }
+ else if( strmtch(eElem->Value(), "ActorObject") )
+ {
+ std::vector<attribpair> attribs;
+ attribs.push_back( attribpair("type_file", "actor_object") );
+
+ std::vector<const char*> attribs_ignore;
+ attribs_ignore.push_back( "id" );
+ attribs_ignore.push_back( "group_id" );
+ attribs_ignore.push_back( "s0" );
+ attribs_ignore.push_back( "s1" );
+ attribs_ignore.push_back( "s2" );
+
+ attribs_ignore.push_back( "t0" );
+ attribs_ignore.push_back( "t1" );
+ attribs_ignore.push_back( "t2" );
+
+ attribs_ignore.push_back( "color_r" );
+ attribs_ignore.push_back( "color_b" );
+ attribs_ignore.push_back( "color_g" );
+ attribs_ignore.push_back( "overbright" );
+
+ attribs_ignore.push_back( "r0" );
+ attribs_ignore.push_back( "r1" );
+ attribs_ignore.push_back( "r2" );
+ attribs_ignore.push_back( "r3" );
+ attribs_ignore.push_back( "r4" );
+ attribs_ignore.push_back( "r5" );
+ attribs_ignore.push_back( "r6" );
+ attribs_ignore.push_back( "r7" );
+ attribs_ignore.push_back( "r8" );
+ attribs_ignore.push_back( "r9" );
+ attribs_ignore.push_back( "r10" );
+ attribs_ignore.push_back( "r11" );
+ attribs_ignore.push_back( "r12" );
+ attribs_ignore.push_back( "r13" );
+ attribs_ignore.push_back( "r14" );
+ attribs_ignore.push_back( "r15" );
+
+ attribs_ignore.push_back( "color_r" );
+ attribs_ignore.push_back( "color_b" );
+ attribs_ignore.push_back( "color_g" );
+
+ attribs_ignore.push_back( "overbright" );
+
+ attribs_ignore.push_back( "q0" );
+ attribs_ignore.push_back( "q1" );
+ attribs_ignore.push_back( "q2" );
+ attribs_ignore.push_back( "q3" );
+
+ attribs_ignore.push_back( "is_player" );
+
+ AttributeScanner::Do( items, item, eElem, attribs, attribs_ignore );
+
+ std::vector<elempair> elems;
+ elems.push_back( elempair( "parameters", "" ) );
+
+ std::vector<const char*> elems_ignore;
+
+ ElementScanner::Do( items, item, eElem, elems, elems_ignore, this, (void*)GROUP );
+ }
+ else if( strmtch(eElem->Value(), "PathPointObject") )
+ {
+ std::vector<attribpair> attribs;
+ attribs.push_back( attribpair("type_file", "path_point_object") );
+
+ std::vector<const char*> attribs_ignore;
+ attribs_ignore.push_back( "id" );
+ attribs_ignore.push_back( "group_id" );
+ attribs_ignore.push_back( "s0" );
+ attribs_ignore.push_back( "s1" );
+ attribs_ignore.push_back( "s2" );
+
+ attribs_ignore.push_back( "t0" );
+ attribs_ignore.push_back( "t1" );
+ attribs_ignore.push_back( "t2" );
+
+ attribs_ignore.push_back( "color_r" );
+ attribs_ignore.push_back( "color_b" );
+ attribs_ignore.push_back( "color_g" );
+
+ attribs_ignore.push_back( "overbright" );
+
+ attribs_ignore.push_back( "r0" );
+ attribs_ignore.push_back( "r1" );
+ attribs_ignore.push_back( "r2" );
+ attribs_ignore.push_back( "r3" );
+ attribs_ignore.push_back( "r4" );
+ attribs_ignore.push_back( "r5" );
+ attribs_ignore.push_back( "r6" );
+ attribs_ignore.push_back( "r7" );
+ attribs_ignore.push_back( "r8" );
+ attribs_ignore.push_back( "r9" );
+ attribs_ignore.push_back( "r10" );
+ attribs_ignore.push_back( "r11" );
+ attribs_ignore.push_back( "r12" );
+ attribs_ignore.push_back( "r13" );
+ attribs_ignore.push_back( "r14" );
+ attribs_ignore.push_back( "r15" );
+
+ attribs_ignore.push_back( "q0" );
+ attribs_ignore.push_back( "q1" );
+ attribs_ignore.push_back( "q2" );
+ attribs_ignore.push_back( "q3" );
+
+ AttributeScanner::Do( items, item, eElem, attribs, attribs_ignore );
+
+ std::vector<elempair> elems;
+ elems.push_back( elempair( "parameters", "" ) );
+
+ std::vector<const char*> elems_ignore;
+ elems_ignore.push_back( "Connections" );
+
+ ElementScanner::Do( items, item, eElem, elems, elems_ignore, this, (void*)GROUP );
+ }
+ else if( strmtch(eElem->Value(), "LightProbeObject") )
+ {
+ std::vector<attribpair> attribs;
+
+ std::vector<const char*> attribs_ignore;
+ attribs_ignore.push_back( "id" );
+ attribs_ignore.push_back( "group_id" );
+ attribs_ignore.push_back( "s0" );
+ attribs_ignore.push_back( "s1" );
+ attribs_ignore.push_back( "s2" );
+
+ attribs_ignore.push_back( "t0" );
+ attribs_ignore.push_back( "t1" );
+ attribs_ignore.push_back( "t2" );
+
+ attribs_ignore.push_back( "color_r" );
+ attribs_ignore.push_back( "color_b" );
+ attribs_ignore.push_back( "color_g" );
+
+ attribs_ignore.push_back( "overbright" );
+
+ attribs_ignore.push_back( "r0" );
+ attribs_ignore.push_back( "r1" );
+ attribs_ignore.push_back( "r2" );
+ attribs_ignore.push_back( "r3" );
+ attribs_ignore.push_back( "r4" );
+ attribs_ignore.push_back( "r5" );
+ attribs_ignore.push_back( "r6" );
+ attribs_ignore.push_back( "r7" );
+ attribs_ignore.push_back( "r8" );
+ attribs_ignore.push_back( "r9" );
+ attribs_ignore.push_back( "r10" );
+ attribs_ignore.push_back( "r11" );
+ attribs_ignore.push_back( "r12" );
+ attribs_ignore.push_back( "r13" );
+ attribs_ignore.push_back( "r14" );
+ attribs_ignore.push_back( "r15" );
+
+ attribs_ignore.push_back( "q0" );
+ attribs_ignore.push_back( "q1" );
+ attribs_ignore.push_back( "q2" );
+ attribs_ignore.push_back( "q3" );
+
+ attribs_ignore.push_back( "special_type" );
+
+ AttributeScanner::Do( items, item, eElem, attribs, attribs_ignore );
+ }
+ else if( strmtch(eElem->Value(), "DynamicLightObject") )
+ {
+ std::vector<attribpair> attribs;
+
+ std::vector<const char*> attribs_ignore;
+ attribs_ignore.push_back( "id" );
+ attribs_ignore.push_back( "group_id" );
+ attribs_ignore.push_back( "s0" );
+ attribs_ignore.push_back( "s1" );
+ attribs_ignore.push_back( "s2" );
+
+ attribs_ignore.push_back( "t0" );
+ attribs_ignore.push_back( "t1" );
+ attribs_ignore.push_back( "t2" );
+
+ attribs_ignore.push_back( "color_r" );
+ attribs_ignore.push_back( "color_b" );
+ attribs_ignore.push_back( "color_g" );
+
+ attribs_ignore.push_back( "overbright" );
+
+ attribs_ignore.push_back( "r0" );
+ attribs_ignore.push_back( "r1" );
+ attribs_ignore.push_back( "r2" );
+ attribs_ignore.push_back( "r3" );
+ attribs_ignore.push_back( "r4" );
+ attribs_ignore.push_back( "r5" );
+ attribs_ignore.push_back( "r6" );
+ attribs_ignore.push_back( "r7" );
+ attribs_ignore.push_back( "r8" );
+ attribs_ignore.push_back( "r9" );
+ attribs_ignore.push_back( "r10" );
+ attribs_ignore.push_back( "r11" );
+ attribs_ignore.push_back( "r12" );
+ attribs_ignore.push_back( "r13" );
+ attribs_ignore.push_back( "r14" );
+ attribs_ignore.push_back( "r15" );
+
+ attribs_ignore.push_back( "q0" );
+ attribs_ignore.push_back( "q1" );
+ attribs_ignore.push_back( "q2" );
+ attribs_ignore.push_back( "q3" );
+
+ attribs_ignore.push_back( "special_type" );
+
+ AttributeScanner::Do( items, item, eElem, attribs, attribs_ignore );
+ }
+ else if( strmtch(eElem->Value(), "NavmeshHintObject") )
+ {
+ std::vector<attribpair> attribs;
+
+ std::vector<const char*> attribs_ignore;
+ attribs_ignore.push_back( "id" );
+ attribs_ignore.push_back( "group_id" );
+ attribs_ignore.push_back( "s0" );
+ attribs_ignore.push_back( "s1" );
+ attribs_ignore.push_back( "s2" );
+
+ attribs_ignore.push_back( "t0" );
+ attribs_ignore.push_back( "t1" );
+ attribs_ignore.push_back( "t2" );
+
+ attribs_ignore.push_back( "color_r" );
+ attribs_ignore.push_back( "color_b" );
+ attribs_ignore.push_back( "color_g" );
+
+ attribs_ignore.push_back( "overbright" );
+
+ attribs_ignore.push_back( "r0" );
+ attribs_ignore.push_back( "r1" );
+ attribs_ignore.push_back( "r2" );
+ attribs_ignore.push_back( "r3" );
+ attribs_ignore.push_back( "r4" );
+ attribs_ignore.push_back( "r5" );
+ attribs_ignore.push_back( "r6" );
+ attribs_ignore.push_back( "r7" );
+ attribs_ignore.push_back( "r8" );
+ attribs_ignore.push_back( "r9" );
+ attribs_ignore.push_back( "r10" );
+ attribs_ignore.push_back( "r11" );
+ attribs_ignore.push_back( "r12" );
+ attribs_ignore.push_back( "r13" );
+ attribs_ignore.push_back( "r14" );
+ attribs_ignore.push_back( "r15" );
+
+ attribs_ignore.push_back( "q0" );
+ attribs_ignore.push_back( "q1" );
+ attribs_ignore.push_back( "q2" );
+ attribs_ignore.push_back( "q3" );
+
+ attribs_ignore.push_back( "special_type" );
+
+ AttributeScanner::Do( items, item, eElem, attribs, attribs_ignore );
+ }
+ else if( strmtch(eElem->Value(), "NavmeshConnectionObject") )
+ {
+ std::vector<attribpair> attribs;
+
+ std::vector<const char*> attribs_ignore;
+ attribs_ignore.push_back( "id" );
+ attribs_ignore.push_back( "group_id" );
+ attribs_ignore.push_back( "s0" );
+ attribs_ignore.push_back( "s1" );
+ attribs_ignore.push_back( "s2" );
+
+ attribs_ignore.push_back( "t0" );
+ attribs_ignore.push_back( "t1" );
+ attribs_ignore.push_back( "t2" );
+
+ attribs_ignore.push_back( "color_r" );
+ attribs_ignore.push_back( "color_b" );
+ attribs_ignore.push_back( "color_g" );
+
+ attribs_ignore.push_back( "overbright" );
+
+ attribs_ignore.push_back( "r0" );
+ attribs_ignore.push_back( "r1" );
+ attribs_ignore.push_back( "r2" );
+ attribs_ignore.push_back( "r3" );
+ attribs_ignore.push_back( "r4" );
+ attribs_ignore.push_back( "r5" );
+ attribs_ignore.push_back( "r6" );
+ attribs_ignore.push_back( "r7" );
+ attribs_ignore.push_back( "r8" );
+ attribs_ignore.push_back( "r9" );
+ attribs_ignore.push_back( "r10" );
+ attribs_ignore.push_back( "r11" );
+ attribs_ignore.push_back( "r12" );
+ attribs_ignore.push_back( "r13" );
+ attribs_ignore.push_back( "r14" );
+ attribs_ignore.push_back( "r15" );
+
+ attribs_ignore.push_back( "q0" );
+ attribs_ignore.push_back( "q1" );
+ attribs_ignore.push_back( "q2" );
+ attribs_ignore.push_back( "q3" );
+
+ attribs_ignore.push_back( "special_type" );
+
+ AttributeScanner::Do( items, item, eElem, attribs, attribs_ignore );
+ }
+ else if( strmtch(eElem->Value(), "NavmeshRegionObject") )
+ {
+ std::vector<attribpair> attribs;
+
+ std::vector<const char*> attribs_ignore;
+ attribs_ignore.push_back( "id" );
+ attribs_ignore.push_back( "group_id" );
+ attribs_ignore.push_back( "s0" );
+ attribs_ignore.push_back( "s1" );
+ attribs_ignore.push_back( "s2" );
+
+ attribs_ignore.push_back( "t0" );
+ attribs_ignore.push_back( "t1" );
+ attribs_ignore.push_back( "t2" );
+
+ attribs_ignore.push_back( "color_r" );
+ attribs_ignore.push_back( "color_b" );
+ attribs_ignore.push_back( "color_g" );
+
+ attribs_ignore.push_back( "overbright" );
+
+ attribs_ignore.push_back( "r0" );
+ attribs_ignore.push_back( "r1" );
+ attribs_ignore.push_back( "r2" );
+ attribs_ignore.push_back( "r3" );
+ attribs_ignore.push_back( "r4" );
+ attribs_ignore.push_back( "r5" );
+ attribs_ignore.push_back( "r6" );
+ attribs_ignore.push_back( "r7" );
+ attribs_ignore.push_back( "r8" );
+ attribs_ignore.push_back( "r9" );
+ attribs_ignore.push_back( "r10" );
+ attribs_ignore.push_back( "r11" );
+ attribs_ignore.push_back( "r12" );
+ attribs_ignore.push_back( "r13" );
+ attribs_ignore.push_back( "r14" );
+ attribs_ignore.push_back( "r15" );
+
+ attribs_ignore.push_back( "q0" );
+ attribs_ignore.push_back( "q1" );
+ attribs_ignore.push_back( "q2" );
+ attribs_ignore.push_back( "q3" );
+
+ attribs_ignore.push_back( "special_type" );
+
+ AttributeScanner::Do( items, item, eElem, attribs, attribs_ignore );
+ }
+ else if( strmtch(eElem->Value(), "ReflectionCaptureObject") )
+ {
+ std::vector<attribpair> attribs;
+
+ std::vector<const char*> attribs_ignore;
+ attribs_ignore.push_back( "id" );
+ attribs_ignore.push_back( "group_id" );
+ attribs_ignore.push_back( "s0" );
+ attribs_ignore.push_back( "s1" );
+ attribs_ignore.push_back( "s2" );
+
+ attribs_ignore.push_back( "t0" );
+ attribs_ignore.push_back( "t1" );
+ attribs_ignore.push_back( "t2" );
+
+ attribs_ignore.push_back( "color_r" );
+ attribs_ignore.push_back( "color_b" );
+ attribs_ignore.push_back( "color_g" );
+
+ attribs_ignore.push_back( "overbright" );
+
+ attribs_ignore.push_back( "r0" );
+ attribs_ignore.push_back( "r1" );
+ attribs_ignore.push_back( "r2" );
+ attribs_ignore.push_back( "r3" );
+ attribs_ignore.push_back( "r4" );
+ attribs_ignore.push_back( "r5" );
+ attribs_ignore.push_back( "r6" );
+ attribs_ignore.push_back( "r7" );
+ attribs_ignore.push_back( "r8" );
+ attribs_ignore.push_back( "r9" );
+ attribs_ignore.push_back( "r10" );
+ attribs_ignore.push_back( "r11" );
+ attribs_ignore.push_back( "r12" );
+ attribs_ignore.push_back( "r13" );
+ attribs_ignore.push_back( "r14" );
+ attribs_ignore.push_back( "r15" );
+
+ attribs_ignore.push_back( "q0" );
+ attribs_ignore.push_back( "q1" );
+ attribs_ignore.push_back( "q2" );
+ attribs_ignore.push_back( "q3" );
+
+ attribs_ignore.push_back( "special_type" );
+
+ AttributeScanner::Do( items, item, eElem, attribs, attribs_ignore );
+ }
+ else if( strmtch(eElem->Value(), "Hotspot") )
+ {
+ std::vector<attribpair> attribs;
+ attribs.push_back( attribpair("type_file", "hotspot_object") );
+
+ std::vector<const char*> attribs_ignore;
+ attribs_ignore.push_back( "id" );
+ attribs_ignore.push_back( "group_id" );
+ attribs_ignore.push_back( "s0" );
+ attribs_ignore.push_back( "s1" );
+ attribs_ignore.push_back( "s2" );
+
+ attribs_ignore.push_back( "t0" );
+ attribs_ignore.push_back( "t1" );
+ attribs_ignore.push_back( "t2" );
+
+ attribs_ignore.push_back( "color_r" );
+ attribs_ignore.push_back( "color_b" );
+ attribs_ignore.push_back( "color_g" );
+
+ attribs_ignore.push_back( "overbright" );
+
+ attribs_ignore.push_back( "r0" );
+ attribs_ignore.push_back( "r1" );
+ attribs_ignore.push_back( "r2" );
+ attribs_ignore.push_back( "r3" );
+ attribs_ignore.push_back( "r4" );
+ attribs_ignore.push_back( "r5" );
+ attribs_ignore.push_back( "r6" );
+ attribs_ignore.push_back( "r7" );
+ attribs_ignore.push_back( "r8" );
+ attribs_ignore.push_back( "r9" );
+ attribs_ignore.push_back( "r10" );
+ attribs_ignore.push_back( "r11" );
+ attribs_ignore.push_back( "r12" );
+ attribs_ignore.push_back( "r13" );
+ attribs_ignore.push_back( "r14" );
+ attribs_ignore.push_back( "r15" );
+
+ attribs_ignore.push_back( "q0" );
+ attribs_ignore.push_back( "q1" );
+ attribs_ignore.push_back( "q2" );
+ attribs_ignore.push_back( "q3" );
+
+ attribs_ignore.push_back( "special_type" );
+
+ AttributeScanner::Do( items, item, eElem, attribs, attribs_ignore );
+ }
+ else if( strmtch(eElem->Value(), "LightVolumeObject") )
+ {
+ std::vector<attribpair> attribs;
+
+ std::vector<const char*> attribs_ignore;
+ attribs_ignore.push_back( "id" );
+ attribs_ignore.push_back( "s0" );
+ attribs_ignore.push_back( "s1" );
+ attribs_ignore.push_back( "s2" );
+
+ attribs_ignore.push_back( "t0" );
+ attribs_ignore.push_back( "t1" );
+ attribs_ignore.push_back( "t2" );
+
+ attribs_ignore.push_back( "color_r" );
+ attribs_ignore.push_back( "color_b" );
+ attribs_ignore.push_back( "color_g" );
+
+ attribs_ignore.push_back( "overbright" );
+
+ attribs_ignore.push_back( "r0" );
+ attribs_ignore.push_back( "r1" );
+ attribs_ignore.push_back( "r2" );
+ attribs_ignore.push_back( "r3" );
+ attribs_ignore.push_back( "r4" );
+ attribs_ignore.push_back( "r5" );
+ attribs_ignore.push_back( "r6" );
+ attribs_ignore.push_back( "r7" );
+ attribs_ignore.push_back( "r8" );
+ attribs_ignore.push_back( "r9" );
+ attribs_ignore.push_back( "r10" );
+ attribs_ignore.push_back( "r11" );
+ attribs_ignore.push_back( "r12" );
+ attribs_ignore.push_back( "r13" );
+ attribs_ignore.push_back( "r14" );
+ attribs_ignore.push_back( "r15" );
+
+ attribs_ignore.push_back( "q0" );
+ attribs_ignore.push_back( "q1" );
+ attribs_ignore.push_back( "q2" );
+ attribs_ignore.push_back( "q3" );
+
+ AttributeScanner::Do( items, item, eElem, attribs, attribs_ignore );
+ }
+ else if( strmtch(eElem->Value(), "parameters") )
+ {
+ std::vector<elempair> elems;
+ elems.push_back(elempair("parameter",""));
+
+ std::vector<const char*> elems_ignore;
+
+ ElementScanner::Do( items, item, eElem, elems, elems_ignore, this, (void*)PARAMETER );
+ }
+ else
+ {
+ LOGE << "Unknown item sub " << eElem->Value() << " " << item << std::endl;
+ }
+
+ }
+ else if( userdata == (void*)PARAMETER )
+ {
+ std::vector<parampair> params;
+
+ std::vector<const char*> params_ignore;
+ params_ignore.push_back( "Name" );
+
+ ParameterScanner::Do( items, item, eElem, params, params_ignore);
+ }
+ else
+ {
+ XMLSeekerBase::HandleElementCallback( items, eRoot, eElem, item, userdata );
+ }
+}
diff --git a/Source/Ogda/Searchers/Seekers/prefabseeker.h b/Source/Ogda/Searchers/Seekers/prefabseeker.h
new file mode 100644
index 00000000..ca8092a1
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/prefabseeker.h
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------------
+// Name: prefabseeker.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "xmlseekerbase.h"
+
+class TiXmlDocument;
+class JobHandler;
+
+class PrefabSeeker : public XMLSeekerBase
+{
+public:
+ virtual std::vector<Item> SearchXML( const Item & item, TiXmlDocument& doc );
+ virtual void HandleElementCallback( std::vector<Item>& items, TiXmlNode* eRoot, TiXmlElement* eElem, const Item& item, void* userdata );
+ virtual inline const char* GetName()
+ {
+ return "prefab_seeker";
+ }
+};
diff --git a/Source/Ogda/Searchers/Seekers/seekerbase.h b/Source/Ogda/Searchers/Seekers/seekerbase.h
new file mode 100644
index 00000000..92b2a069
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/seekerbase.h
@@ -0,0 +1,36 @@
+//-----------------------------------------------------------------------------
+// Name: seekerbase.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <vector>
+#include <Ogda/item.h>
+
+class JobHandler;
+class Item;
+
+class SeekerBase
+{
+public:
+ virtual std::vector<Item> Search(const Item& item ) = 0;
+ virtual const char* GetName() = 0;
+};
diff --git a/Source/Ogda/Searchers/Seekers/skeletonseeker.cpp b/Source/Ogda/Searchers/Seekers/skeletonseeker.cpp
new file mode 100644
index 00000000..c94a3047
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/skeletonseeker.cpp
@@ -0,0 +1,69 @@
+//-----------------------------------------------------------------------------
+// Name: skeletonseeker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "skeletonseeker.h"
+
+#include <cassert>
+
+#include <tinyxml.h>
+
+#include <Utility/strings.h>
+#include <Logging/logdata.h>
+
+std::vector<Item> SkeletonSeeker::SearchXML( const Item & item, TiXmlDocument& doc )
+{
+ std::vector<Item> items;
+
+ TiXmlHandle hRoot(&doc);
+
+ const char* roots[] =
+ {
+ "rig"
+ };
+
+ TiXmlElement *eRoot = hRoot.FirstChildElement().Element();
+
+ if( !eRoot )
+ {
+ LOGE << "Can't find anything in file listed " << item << std::endl;
+ }
+
+ while( eRoot )
+ {
+ if( FindStringInArray( roots, ARRLEN(roots), eRoot->Value() ) < 0 )
+ {
+ LOGE << "Unknown root " << eRoot->Value() << std::endl;
+ }
+
+ std::vector<attribpair> attribs;
+ attribs.push_back(attribpair("bone_path", "skeleton"));
+ attribs.push_back(attribpair("model_path", "model"));
+ attribs.push_back(attribpair("mass_path", "skeleton"));
+
+ std::vector<const char*> attribs_ignore;
+
+ AttributeScanner::Do( items, item, eRoot, attribs, attribs_ignore );
+
+ eRoot = eRoot->NextSiblingElement();
+ }
+ return items;
+}
diff --git a/Source/Ogda/Searchers/Seekers/skeletonseeker.h b/Source/Ogda/Searchers/Seekers/skeletonseeker.h
new file mode 100644
index 00000000..53e70d44
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/skeletonseeker.h
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------------
+// Name: skeletonseeker.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "xmlseekerbase.h"
+
+class TiXmlDocument;
+class JobHandler;
+
+class SkeletonSeeker : public XMLSeekerBase
+{
+public:
+ virtual std::vector<Item> SearchXML( const Item & item, TiXmlDocument& doc );
+
+ virtual inline const char* GetName()
+ {
+ return "skeleton_seeker";
+ }
+};
diff --git a/Source/Ogda/Searchers/Seekers/skylevelseeker.cpp b/Source/Ogda/Searchers/Seekers/skylevelseeker.cpp
new file mode 100644
index 00000000..be43f4bd
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/skylevelseeker.cpp
@@ -0,0 +1,45 @@
+//-----------------------------------------------------------------------------
+// Name: skylevelseeker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "skylevelseeker.h"
+
+#include <tinyxml.h>
+
+#include <Logging/logdata.h>
+
+std::vector<Item> SkyLevelSeeker::SearchLevelRoot(const Item& item, TiXmlHandle& hRoot)
+{
+ std::vector<Item> items;
+
+ TiXmlElement *eElem = hRoot.FirstChildElement("Sky").FirstChildElement("DomeTexture").Element();
+
+ if( eElem && eElem->GetText() && strlen(eElem->GetText()) > 0 )
+ {
+ items.push_back(Item(item.input_folder, eElem->GetText(), "texture", item.source));
+ }
+ else
+ {
+ LOGW << "Level missing Sky DomeTexture " << std::endl;
+ }
+
+ return items;
+}
diff --git a/Source/Ogda/Searchers/Seekers/skylevelseeker.h b/Source/Ogda/Searchers/Seekers/skylevelseeker.h
new file mode 100644
index 00000000..7ac803e3
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/skylevelseeker.h
@@ -0,0 +1,38 @@
+//-----------------------------------------------------------------------------
+// Name: skylevelseeker.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "levelseekerbase.h"
+
+class TiXmlElement;
+
+class SkyLevelSeeker : public LevelSeekerBase
+{
+public:
+ virtual std::vector<Item> SearchLevelRoot( const Item& item, TiXmlHandle& hRoot );
+
+ virtual inline const char* GetName()
+ {
+ return "sky_level_seeker";
+ }
+};
diff --git a/Source/Ogda/Searchers/Seekers/spawnerlistseeker.cpp b/Source/Ogda/Searchers/Seekers/spawnerlistseeker.cpp
new file mode 100644
index 00000000..936083d7
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/spawnerlistseeker.cpp
@@ -0,0 +1,84 @@
+//-----------------------------------------------------------------------------
+// Name: spawnerlistseeker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "spawnerlistseeker.h"
+
+#include <JSON/jsonhelper.h>
+#include <Logging/logdata.h>
+#include <string>
+
+std::vector<Item> SpawnerListSeeker::SearchJSON( const Item & item, Json::Value& root )
+{
+ std::vector<Item> items;
+
+ Json::Value spawner_tab = root["spawner_tab"];
+ Json::Value ogda_type = root["ogda_type"];
+ Json::Value objects = root["objects"];
+
+ if( spawner_tab.empty() )
+ {
+ LOGW << item << " is missing a spawner_tab value" << std::endl;
+ }
+
+ if( ogda_type.isString() )
+ {
+ std::string type_string = ogda_type.asString();
+
+ if( objects.isArray() )
+ {
+ for( int i = 0; i < objects.size(); i++ )
+ {
+ Json::Value json_item = objects[i];
+
+ if( json_item.isArray() )
+ {
+ Json::Value path = json_item[1];
+
+ if( path.isString() )
+ {
+ std::string path_string = path.asString();
+ Item i = Item( item.input_folder, path_string, type_string, item.source );
+ items.push_back(i);
+ }
+ else
+ {
+ LOGE << "Unexpected path value on index " << i << " in " << item << std::endl;
+ }
+ }
+ else
+ {
+ LOGE << "Unexpected item element on index " << i << " " << item << std::endl;
+ }
+ }
+ }
+ else
+ {
+ LOGE << "Malformed json file " << item << std::endl;
+ }
+ }
+ else
+ {
+ LOGE << "Missing ogda_type value in " << item << std::endl;
+ }
+
+ return items;
+}
diff --git a/Source/Ogda/Searchers/Seekers/spawnerlistseeker.h b/Source/Ogda/Searchers/Seekers/spawnerlistseeker.h
new file mode 100644
index 00000000..1f37620b
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/spawnerlistseeker.h
@@ -0,0 +1,37 @@
+//-----------------------------------------------------------------------------
+// Name: spawnerlistseeker.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "jsonseekerbase.h"
+
+namespace Json
+{
+ class Value;
+}
+
+class SpawnerListSeeker : public JSONSeekerBase
+{
+public:
+ virtual std::vector<Item> SearchJSON( const Item & item, Json::Value& root );
+ virtual const char* GetName() { return "spawner_list_seeker"; }
+};
diff --git a/Source/Ogda/Searchers/Seekers/syncedanimationgroupseeker.cpp b/Source/Ogda/Searchers/Seekers/syncedanimationgroupseeker.cpp
new file mode 100644
index 00000000..b4d26ce5
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/syncedanimationgroupseeker.cpp
@@ -0,0 +1,116 @@
+//-----------------------------------------------------------------------------
+// Name: syncedanimationgroupseeker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "syncedanimationgroupseeker.h"
+
+#include <cassert>
+
+#include <tinyxml.h>
+
+#include <Utility/strings.h>
+#include <Logging/logdata.h>
+
+std::vector<Item> SyncedAnimationGroupSeeker::SearchXML(const Item & item, TiXmlDocument& doc )
+{
+ const char* elems[] =
+ {
+ };
+
+ const char* elems_type[] =
+ {
+ };
+
+ assert(ARRLEN(elems_type) == ARRLEN(elems));
+
+ const char* ignored[] =
+ {
+ "CoordLabel",
+ "Overshoot",
+ "InAir"
+ };
+
+ std::vector<Item> items;
+
+ TiXmlHandle hRoot(&doc);
+
+ TiXmlElement *eElem = hRoot.FirstChildElement("SyncedAnimationGroup").FirstChildElement().Element();
+
+ if( !eElem )
+ {
+ LOGE << "Can't find anything in file listed " << item << std::endl;
+ }
+
+ while( eElem )
+ {
+ const char* name = eElem->Value();
+ const char* text = eElem->GetText();
+ if( name )
+ {
+ int id;
+ if( (id = FindStringInArray( elems, ARRLEN(elems), name )) >= 0 )
+ {
+ if( text && strlen(text) > 0 )
+ {
+ items.push_back(Item(item.input_folder,text,elems_type[id],item.source));
+ }
+ else
+ {
+ LOGW << "String value in " << item << " for element " << elems[id] << " is empty" << std::endl;
+ }
+ }
+ else if( (id = FindStringInArray( ignored, ARRLEN(ignored), name )) >= 0 )
+ {
+ LOGD << "Ignored " << ignored[id] << " in " << item << std::endl;
+ }
+ else if( strmtch( "Animations", name ) )
+ {
+ TiXmlElement *eDetailObject = eElem->FirstChildElement();
+
+ while( eDetailObject )
+ {
+ {
+ const char* path = eDetailObject->Attribute("path");
+ if( path )
+ {
+ items.push_back(Item(item.input_folder, path, "animation", item.source));
+ }
+ }
+
+ eDetailObject = eDetailObject->NextSiblingElement();
+ }
+ }
+ else
+ {
+ LOGE << "Unahandled subvalue in SyncedAnimationGroup from " << item << " called " << name << std::endl;
+ }
+ }
+ else
+ {
+ LOGE << "Generic warning" << std::endl;
+ }
+
+ eElem = eElem->NextSiblingElement();
+ }
+
+ return items;
+
+}
diff --git a/Source/Ogda/Searchers/Seekers/syncedanimationgroupseeker.h b/Source/Ogda/Searchers/Seekers/syncedanimationgroupseeker.h
new file mode 100644
index 00000000..33f9e24a
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/syncedanimationgroupseeker.h
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------------
+// Name: syncedanimationgroupseeker.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "xmlseekerbase.h"
+
+class TiXmlDocument;
+class JobHandler;
+
+class SyncedAnimationGroupSeeker : public XMLSeekerBase
+{
+public:
+ virtual std::vector<Item> SearchXML( const Item & item, TiXmlDocument& doc );
+
+ virtual inline const char* GetName()
+ {
+ return "synced_animation_group_seeker";
+ }
+};
diff --git a/Source/Ogda/Searchers/Seekers/terrainlevelseeker.cpp b/Source/Ogda/Searchers/Seekers/terrainlevelseeker.cpp
new file mode 100644
index 00000000..084e2a99
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/terrainlevelseeker.cpp
@@ -0,0 +1,137 @@
+//-----------------------------------------------------------------------------
+// Name: terrainlevelseeker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "terrainlevelseeker.h"
+
+#include <Utility/strings.h>
+#include <Logging/logdata.h>
+
+#include <tinyxml.h>
+
+std::vector<Item> TerrainLevelSeeker::SearchLevelRoot( const Item& item, TiXmlHandle& hRoot )
+{
+ std::vector<Item> items;
+ const char* simpleTextures[] = {
+ "Heightmap",
+ "DetailMap",
+ "ColorMap",
+ "WeightMap",
+ "ModelOverride"
+ };
+
+ const char* type[] =
+ {
+ "heightmap",
+ "texture",
+ "raw_texture",
+ "texture",
+ "model"
+ };
+
+ assert( ARRLEN(type) == ARRLEN(simpleTextures) );
+
+ TiXmlElement *eTerrain = hRoot.FirstChildElement("Terrain").Element();
+
+ if( eTerrain )
+ {
+ TiXmlElement *eElem = eTerrain->FirstChildElement();
+
+ while( eElem )
+ {
+ const char* name = eElem->Value();
+ int id;
+ if( (id = FindStringInArray( simpleTextures, ARRLEN(simpleTextures), name )) >= 0 )
+ {
+ const char* v = eElem->GetText();
+ if(v != NULL && strlen(v) > 0)
+ {
+ items.push_back(Item(item.input_folder, v,type[id], item.source));
+ }
+ }
+ else if( strmtch( name, "DetailMaps" ) )
+ {
+ TiXmlElement *eDetailMap = eElem->FirstChildElement();
+
+ while( eDetailMap )
+ {
+ std::vector<attribpair> attribs;
+ attribs.push_back(attribpair("colorpath", "texture"));
+ attribs.push_back(attribpair("normalpath", "texture"));
+ attribs.push_back(attribpair("materialpath","material"));
+
+ std::vector<const char*> ignore;
+
+ AttributeScanner::Do( items, item, eDetailMap, attribs, ignore );
+
+ eDetailMap = eDetailMap->NextSiblingElement();
+ }
+ }
+ else if( strmtch( name, "DetailObjects" ) )
+ {
+ TiXmlElement *eDetailObject = eElem->FirstChildElement();
+
+ while( eDetailObject )
+ {
+ //<DetailObject obj_path="Data/Objects/Plants/Groundcover/Grass/WildGrass.xml" weight_path="Data/Textures/Terrain/scrubby_hills/scrubby_hills_grass.png" normal_conform="0.5" density="7" min_embed="0" max_embed="0.4" min_scale="0.7" max_scale="2" view_distance="30" jitter_degrees="10" overbright="0" />
+
+ {
+ const char* obj_path = eDetailObject->Attribute("obj_path");
+ if( obj_path && strlen( obj_path ) > 0 )
+ {
+ items.push_back(Item(item.input_folder,obj_path,"object",item.source));
+ }
+ else
+ {
+ LOGW << "Missing expected attribute obj_path." << std::endl;
+ }
+ }
+
+ {
+ const char* weight_path = eDetailObject->Attribute("weight_path");
+ if( weight_path && strlen( weight_path ) > 0 )
+ {
+ items.push_back(Item(item.input_folder,weight_path,"texture",item.source));
+ }
+ else
+ {
+ LOGW << "Missing expected attribute weight_path." << std::endl;
+ }
+ }
+
+ eDetailObject = eDetailObject->NextSiblingElement();
+ }
+ }
+ else if( strmtch( name, "ShaderExtra" ) )
+ {
+
+ }
+ else
+ {
+ LOGW << "Missing subhandler for " << name << " in Terrain under level" << item << std::endl;
+ }
+
+ eElem = eElem->NextSiblingElement();
+ }
+ }
+
+ return items;
+}
diff --git a/Source/Ogda/Searchers/Seekers/terrainlevelseeker.h b/Source/Ogda/Searchers/Seekers/terrainlevelseeker.h
new file mode 100644
index 00000000..2d9776e1
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/terrainlevelseeker.h
@@ -0,0 +1,38 @@
+//-----------------------------------------------------------------------------
+// Name: terrainlevelseeker.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "levelseekerbase.h"
+
+class TiXmlElement;
+
+class TerrainLevelSeeker : public LevelSeekerBase
+{
+public:
+ virtual std::vector<Item> SearchLevelRoot( const Item& item, TiXmlHandle& hRoot );
+
+ virtual inline const char* GetName()
+ {
+ return "terrain_level_seeker";
+ }
+};
diff --git a/Source/Ogda/Searchers/Seekers/voidseeker.cpp b/Source/Ogda/Searchers/Seekers/voidseeker.cpp
new file mode 100644
index 00000000..9431ddd2
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/voidseeker.cpp
@@ -0,0 +1,31 @@
+//-----------------------------------------------------------------------------
+// Name: voidseeker.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "voidseeker.h"
+
+#include <Logging/logdata.h>
+#include <Ogda/item.h>
+
+std::vector<Item> VoidSeeker::Search( const Item& item )
+{
+ return std::vector<Item>();
+}
diff --git a/Source/Ogda/Searchers/Seekers/voidseeker.h b/Source/Ogda/Searchers/Seekers/voidseeker.h
new file mode 100644
index 00000000..5a18be48
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/voidseeker.h
@@ -0,0 +1,38 @@
+//-----------------------------------------------------------------------------
+// Name: voidseeker.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "seekerbase.h"
+
+class TiXmlDocument;
+class JobHandler;
+
+class VoidSeeker : public SeekerBase
+{
+public:
+ virtual std::vector<Item> Search( const Item& item );
+ virtual inline const char* GetName()
+ {
+ return "void";
+ }
+};
diff --git a/Source/Ogda/Searchers/Seekers/xmlseekerbase.cpp b/Source/Ogda/Searchers/Seekers/xmlseekerbase.cpp
new file mode 100644
index 00000000..60e2ddf1
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/xmlseekerbase.cpp
@@ -0,0 +1,52 @@
+//-----------------------------------------------------------------------------
+// Name: xmlseekerbase.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "xmlseekerbase.h"
+
+#include <string>
+#include <Ogda/jobhandler.h>
+#include <Internal/filesystem.h>
+#include <Logging/logdata.h>
+
+#include <tinyxml.h>
+#include <XML/xml_helper.h>
+
+std::vector<Item> XMLSeekerBase::Search( const Item& item )
+{
+ std::string full_path = item.GetAbsPath();
+ TiXmlDocument doc(full_path.c_str());
+ doc.LoadFile();
+
+ if( !doc.Error() )
+ {
+ return this->SearchXML(item,doc);
+ }
+ else
+ {
+ return std::vector<Item>();
+ }
+}
+
+void XMLSeekerBase::HandleElementCallback(std::vector<Item>& items, TiXmlNode* eRoot, TiXmlElement* eElem, const Item& item, void* userdata )
+{
+ LOGE << "Unimplemented HandleElementCallback for " << GetName() << " on " << item << std::endl;
+}
diff --git a/Source/Ogda/Searchers/Seekers/xmlseekerbase.h b/Source/Ogda/Searchers/Seekers/xmlseekerbase.h
new file mode 100644
index 00000000..c46a699f
--- /dev/null
+++ b/Source/Ogda/Searchers/Seekers/xmlseekerbase.h
@@ -0,0 +1,41 @@
+//-----------------------------------------------------------------------------
+// Name: xmlseekerbase.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "seekerbase.h"
+#include "SeekerTools/attributescanner.h"
+#include "SeekerTools/elementscanner.h"
+#include "SeekerTools/parameterscanner.h"
+
+class TiXmlNode;
+class TiXmlDocument;
+class JobHandler;
+
+class XMLSeekerBase : public SeekerBase
+{
+public:
+ virtual std::vector<Item> Search( const Item& item );
+ virtual std::vector<Item> SearchXML( const Item & item, TiXmlDocument& doc ) = 0;
+
+ virtual void HandleElementCallback( std::vector<Item>& items, TiXmlNode* eRoot, TiXmlElement* eElem, const Item& item, void* userdata );
+};
diff --git a/Source/Ogda/Searchers/searcher.cpp b/Source/Ogda/Searchers/searcher.cpp
new file mode 100644
index 00000000..551321a9
--- /dev/null
+++ b/Source/Ogda/Searchers/searcher.cpp
@@ -0,0 +1,97 @@
+//-----------------------------------------------------------------------------
+// Name: searcher.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "searcher.h"
+
+#include <string>
+
+#include <Ogda/jobhandler.h>
+
+#include <tinyxml.h>
+#include <XML/xml_helper.h>
+#include <Utility/strings.h>
+
+
+//Small convinence function so i don't write this block multiple times.
+void ai(JobHandler& j, const char* t, const char* type)
+{
+ if(t)
+ {
+ std::string ts(t);
+ if( !ts.empty() )
+ {
+ //j.AddItem(ts,type);
+ return;
+ }
+ }
+ LOGW << "Skipping ItemTextureHandler as string value is nonexistant or empty" << std::endl;
+}
+
+//Small convinence function so i don't write this block multiple times.
+void aie( JobHandler& j, TiXmlElement* e, const char* type )
+{
+ if(e)
+ {
+ ai(j, e->GetText(), type);
+ }
+}
+
+Searcher::Searcher( SeekerBase* seeker, const std::string& _path_ending, const std::string& type_pattern_re )
+: seeker(seeker), path_ending(_path_ending)
+{
+ try
+ {
+ type_pattern->Compile(type_pattern_re.c_str());
+ }
+ catch( const TRexParseException& pe )
+ {
+ LOGE << "Failed to compile the type_pattern regex " << type_pattern_re << " reason: " << pe.desc << std::endl;
+ }
+}
+
+bool Searcher::IsMatch(const Item& t)
+{
+ if( endswith(t.GetPath().c_str(), path_ending.c_str()) && type_pattern->Match(t.type.c_str()) )
+ return true;
+ else
+ return false;
+}
+
+std::vector<Item> Searcher::TrySearch( JobHandler& jh, const Item& item, int* matchcounter )
+{
+ if( IsMatch(item) )
+ {
+ LOGD << "Running " << seeker->GetName() << " on " << item << std::endl;
+ (*matchcounter)++;
+ return seeker->Search(item);
+ }
+ else
+ {
+ LOGD << "Skipping " << seeker->GetName() << " on " << item << ", doesn't match pattern." << std::endl;
+ return std::vector<Item>();
+ }
+}
+
+std::string Searcher::GetSeekerName()
+{
+ return std::string(seeker->GetName());
+}
diff --git a/Source/Ogda/Searchers/searcher.h b/Source/Ogda/Searchers/searcher.h
new file mode 100644
index 00000000..454a64b7
--- /dev/null
+++ b/Source/Ogda/Searchers/searcher.h
@@ -0,0 +1,50 @@
+//-----------------------------------------------------------------------------
+// Name: searcher.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <string>
+
+#include <Ogda/item.h>
+#include <Logging/logdata.h>
+#include <Internal/referencecounter.h>
+#include "Seekers/seekerbase.h"
+
+#include <trex/trex.h>
+
+class JobHandler;
+class TiXmlElement;
+
+class Searcher
+{
+public:
+
+ Searcher( SeekerBase* seeker, const std::string& path_ending, const std::string& type_pattern_re );
+ bool IsMatch(const Item& t);
+ std::vector<Item> TrySearch( JobHandler& jh, const Item& item, int* matchcounter );
+ std::string GetSeekerName();
+
+private:
+ std::string path_ending;
+ ReferenceCounter<TRexpp> type_pattern;
+ ReferenceCounter<SeekerBase> seeker;
+};
diff --git a/Source/Ogda/Searchers/searcherfactory.cpp b/Source/Ogda/Searchers/searcherfactory.cpp
new file mode 100644
index 00000000..38c25ccb
--- /dev/null
+++ b/Source/Ogda/Searchers/searcherfactory.cpp
@@ -0,0 +1,119 @@
+//-----------------------------------------------------------------------------
+// Name: searcherfactory.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <vector>
+
+#include "searcherfactory.h"
+
+#include "searcher.h"
+
+#include "Seekers/voidseeker.h"
+#include "Seekers/actorobjectlevelseeker.h"
+#include "Seekers/decalseeker.h"
+#include "Seekers/terrainlevelseeker.h"
+#include "Seekers/skylevelseeker.h"
+#include "Seekers/ambientsoundlevelseeker.h"
+#include "Seekers/hotspotseeker.h"
+#include "Seekers/objectseeker.h"
+#include "Seekers/syncedanimationgroupseeker.h"
+#include "Seekers/attackseeker.h"
+#include "Seekers/characterseeker.h"
+#include "Seekers/actorseeker.h"
+#include "Seekers/skeletonseeker.h"
+#include "Seekers/animationretargetseeker.h"
+#include "Seekers/itemseeker.h"
+#include "Seekers/materialseeker.h"
+#include "Seekers/particleseeker.h"
+#include "Seekers/prefabseeker.h"
+#include "Seekers/spawnerlistseeker.h"
+#include "Seekers/preconvertedddsshadowseeker.h"
+#include "Seekers/objcolseeker.h"
+#include "Seekers/objhullseeker.h"
+#include "Seekers/levelnormseeker.h"
+
+SearcherFactory::SearcherFactory()
+{
+ seekers.push_back( new SeekerFactory<VoidSeeker>() );
+ seekers.push_back( new SeekerFactory<ActorObjectLevelSeeker>() );
+ seekers.push_back( new SeekerFactory<TerrainLevelSeeker>() );
+ seekers.push_back( new SeekerFactory<DecalSeeker>() );
+ seekers.push_back( new SeekerFactory<SkyLevelSeeker>() );
+ seekers.push_back( new SeekerFactory<AmbientSoundLevelSeeker>() );
+ seekers.push_back( new SeekerFactory<HotspotSeeker>() );
+ seekers.push_back( new SeekerFactory<ObjectSeeker>() );
+ seekers.push_back( new SeekerFactory<SyncedAnimationGroupSeeker>() );
+ seekers.push_back( new SeekerFactory<AttackSeeker>() );
+ seekers.push_back( new SeekerFactory<CharacterSeeker>() );
+ seekers.push_back( new SeekerFactory<ActorSeeker>() );
+ seekers.push_back( new SeekerFactory<SkeletonSeeker>() );
+ seekers.push_back( new SeekerFactory<AnimationRetargetSeeker>() );
+ seekers.push_back( new SeekerFactory<ItemSeeker>() );
+ seekers.push_back( new SeekerFactory<MaterialSeeker>() );
+ seekers.push_back( new SeekerFactory<ParticleSeeker>() );
+ seekers.push_back( new SeekerFactory<PrefabSeeker>() );
+ seekers.push_back( new SeekerFactory<SpawnerListSeeker>() );
+ seekers.push_back( new SeekerFactory<PreConvertedDDSSeeker>() );
+ seekers.push_back( new SeekerFactory<ObjColSeeker>() );
+ seekers.push_back( new SeekerFactory<ObjHullSeeker>() );
+ seekers.push_back( new SeekerFactory<LevelNormSeeker>() );
+}
+
+SearcherFactory::~SearcherFactory()
+{
+ std::vector<SeekerFactoryBase*>::iterator factoryit;
+
+ for( factoryit = seekers.begin(); factoryit != seekers.end(); factoryit++ )
+ {
+ delete *factoryit;
+ }
+
+ seekers.clear();
+}
+
+bool SearcherFactory::HasSearcher( const std::string& searcher )
+{
+ std::vector<SeekerFactoryBase*>::iterator factoryit;
+ for( factoryit = seekers.begin(); factoryit != seekers.end(); factoryit++ )
+ {
+ if( (*factoryit)->GetSeekerName() == searcher )
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+Searcher SearcherFactory::CreateSearcher( const std::string& searcher, const std::string& path_ending, const std::string& type_pattern_re )
+{
+ std::vector<SeekerFactoryBase*>::iterator factoryit;
+ for( factoryit = seekers.begin(); factoryit != seekers.end(); factoryit++ )
+ {
+ if( (*factoryit)->GetSeekerName() == searcher )
+ {
+ return Searcher((*factoryit)->NewInstance(), path_ending, type_pattern_re);
+ }
+ }
+
+ LOGE << "Unable to find searcher matching name " << searcher << std::endl;
+
+ return Searcher( new VoidSeeker(), path_ending, type_pattern_re );
+}
diff --git a/Source/Ogda/Searchers/searcherfactory.h b/Source/Ogda/Searchers/searcherfactory.h
new file mode 100644
index 00000000..e0f18a00
--- /dev/null
+++ b/Source/Ogda/Searchers/searcherfactory.h
@@ -0,0 +1,57 @@
+//-----------------------------------------------------------------------------
+// Name: searcherfactory.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+#include "searcher.h"
+#include <vector>
+
+class SearcherFactory
+{
+private:
+ class SeekerFactoryBase
+ {
+ public:
+ virtual SeekerBase* NewInstance() = 0;
+ virtual std::string GetSeekerName() = 0;
+ };
+
+ template<class Seeker>
+ class SeekerFactory : public SeekerFactoryBase
+ {
+ virtual SeekerBase* NewInstance()
+ {
+ return new Seeker();
+ }
+
+ virtual std::string GetSeekerName()
+ {
+ return std::string(Seeker().GetName());
+ }
+ };
+
+ std::vector<SeekerFactoryBase*> seekers;
+public:
+ SearcherFactory();
+ ~SearcherFactory();
+ bool HasSearcher( const std::string& searcher );
+ Searcher CreateSearcher( const std::string& searcher, const std::string& path_ending, const std::string& type_pattern_re );
+};
diff --git a/Source/Ogda/database_manifest.cpp b/Source/Ogda/database_manifest.cpp
new file mode 100644
index 00000000..5da9d06f
--- /dev/null
+++ b/Source/Ogda/database_manifest.cpp
@@ -0,0 +1,244 @@
+//-----------------------------------------------------------------------------
+// Name: database_manifest.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "database_manifest.h"
+
+#include <tinyxml.h>
+#include <XML/xml_helper.h>
+
+#include <Utility/commonregex.h>
+#include <Logging/logdata.h>
+#include <Internal/filesystem.h>
+#include <XML/Parsers/jobxmlparser.h>
+#include <Version/version.h>
+
+#include "jobhandler.h"
+
+DatabaseManifest::DatabaseManifest()
+{
+}
+
+bool DatabaseManifest::Load(const std::string& manifest)
+{
+ results.clear();
+ CommonRegex cr;
+ TiXmlDocument doc( manifest.c_str() );
+ doc.LoadFile();
+
+ if( !doc.Error() )
+ {
+ TiXmlElement* pRoot = doc.RootElement();
+
+ if( pRoot )
+ {
+ TiXmlHandle hRoot(pRoot);
+
+ TiXmlNode* nResult = hRoot.FirstChild().ToNode();
+
+ while( nResult )
+ {
+ TiXmlElement* eResult = nResult->ToElement();
+
+ if( eResult )
+ {
+ if( strcmp(eResult->Value(), "DatabaseResult") == 0 )
+ {
+
+ const char* dest = eResult->Attribute("dest");
+ const char* dest_hash = eResult->Attribute("dest_hash");
+ const char* builder = eResult->Attribute("builder");
+ const char* builder_version = eResult->Attribute("builder_version");
+ const char* type = eResult->Attribute("type");
+
+ std::string dest_s;
+ if( dest )
+ dest_s = std::string( dest );
+ std::string dest_hash_s;
+ if( dest_hash )
+ dest_hash_s = std::string( dest_hash );
+ std::string builder_s;
+ if( builder )
+ builder_s = std::string( builder );
+ std::string builder_version_s;
+ if( builder_version )
+ builder_version_s = std::string(builder_version);
+ std::string type_s;
+ if( type )
+ type_s = std::string(type);
+
+ TiXmlElement* eItem = nResult->FirstChild("Item")->ToElement();
+
+ if( eItem )
+ {
+ const char* item_path = eItem->Attribute("path");
+ const char* item_type = eItem->Attribute("type");
+ const char* item_hash = eItem->Attribute("hash");
+
+ std::string item_path_s;
+ if( item_path )
+ item_path_s = std::string( item_path );
+ std::string item_type_s;
+ if( item_type )
+ item_type_s = std::string( item_type );
+ std::string item_hash_s;
+ if( item_hash )
+ item_hash_s = std::string( item_hash );
+
+
+ eItem = eItem->NextSiblingElement("Item");
+
+
+ results.push_back( DatabaseManifestResult( Item("", item_path_s, item_type_s, item_hash_s, JobXMLParser::Item()), dest_hash_s, dest_s, builder_s, builder_version_s, type_s ) );
+ } else {
+ LOGE << "Missing item for DatabaseManifestResult" << std::endl;
+ }
+
+ }
+ else
+ {
+ LOGE << "Unknown element name: " << eResult->Value() << std::endl;
+ }
+ }
+ else
+ {
+ LOGE << "Malformed element in manifest" << std::endl;
+ }
+
+ nResult = nResult->NextSibling();
+ }
+ }
+ else
+ {
+ LOGE << "Problem loading manifest file" << manifest << std::endl;
+ return false;
+ }
+ }
+ else
+ {
+ LOGE << "Error parsing manifest file: \"" << doc.ErrorDesc() << "\"" << std::endl;
+ return false;
+ }
+
+ std::vector<DatabaseManifestResult>::const_iterator mrit;
+ for( mrit = results.begin(); mrit != results.end(); mrit++ )
+ {
+ hash_set.insert(mrit->item.GetSubHash());
+ }
+
+ return true;
+}
+
+bool DatabaseManifest::Save(const std::string& manifest)
+{
+ TiXmlDocument doc;
+ TiXmlDeclaration * decl = new TiXmlDeclaration( "2.0", "", "" );
+ TiXmlElement * root = new TiXmlElement( "DatabaseManifest" );
+ root->SetAttribute("version", "1");
+
+ std::vector<DatabaseManifestResult>::const_iterator mrit;
+
+ for( mrit = results.begin(); mrit != results.end(); mrit++ )
+ {
+ TiXmlElement* eResult = new TiXmlElement( "DatabaseResult" );
+
+ TiXmlElement* eItem = new TiXmlElement( "Item" );
+
+ eItem->SetAttribute( "path", mrit->item.GetPath().c_str() );
+ eItem->SetAttribute( "type", mrit->item.type.c_str() );
+ eItem->SetAttribute( "hash", mrit->item.hash.c_str() );
+
+ eResult->LinkEndChild( eItem );
+
+ eResult->SetAttribute( "dest", mrit->dest.c_str() );
+ eResult->SetAttribute( "dest_hash", mrit->dest_hash.c_str() );
+ eResult->SetAttribute( "builder", mrit->name.c_str() );
+ eResult->SetAttribute( "builder_version", mrit->version.c_str() );
+ eResult->SetAttribute( "type", mrit->type.c_str() );
+
+ root->LinkEndChild( eResult );
+ }
+
+ doc.LinkEndChild( decl );
+ doc.LinkEndChild( root );
+ doc.SaveFile( manifest.c_str() );
+
+ return !doc.Error();
+}
+
+void DatabaseManifest::AddResult( const DatabaseManifestResult& a ) {
+ results.push_back(a);
+ hash_set.insert(a.item.GetSubHash());
+}
+
+bool DatabaseManifest::HasBuiltResultFor( JobHandler& jh, const Item& item, const Builder& builder )
+{
+ std::vector<DatabaseManifestResult>::iterator mrit;
+ if( hash_set.find(item.GetSubHash()) != hash_set.end() )
+ {
+ for( mrit = results.begin(); mrit != results.end(); mrit++ )
+ {
+ if( mrit->item == item )
+ {
+ //Check that it's the same builder, one builder, one type of possible output file
+ if( mrit->name == builder.GetBuilderName() )
+ {
+ //Check the builder version, to verify that the expected output wouldn't change.
+ if( mrit->version == builder.GetBuilderVersion() )
+ {
+ //Check if the claimed destination file from the manifest differs from the file on disk according to hash
+ //and that the hash isn't invalid
+ if( mrit->GetCurrentDestHash(jh.databasedir) == mrit->dest_hash && mrit->dest_hash != std::string("") )
+ {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+}
+
+DatabaseManifestResult DatabaseManifest::GetPreviouslyBuiltResult(const Item& item, const Builder& builder)
+{
+ std::vector<DatabaseManifestResult>::const_iterator mrit;
+ for( mrit = results.begin(); mrit != results.end(); mrit++ )
+ {
+ //First, check that it's the same item.
+ //This "magically" includes the hash as item contains the hash value
+ if( mrit->item == item )
+ {
+ //Check that it's the same builder, one builder, one type of possible output file
+ if( mrit->name == builder.GetBuilderName() )
+ {
+ //This is mostly for sanity, but should never really evaluate to false as we already found the right builder and there is only one per name.
+ //If this was false, that would mean we have changed the version of the builder and the asset we're looking for is actually outdated.
+ //Meaning that previous checks and evalutation should have lead us to the conclusion to rebuild this item.
+ if( mrit->version == builder.GetBuilderVersion() )
+ {
+ return *mrit;
+ }
+ }
+ }
+ }
+ throw "Big error";
+}
diff --git a/Source/Ogda/database_manifest.h b/Source/Ogda/database_manifest.h
new file mode 100644
index 00000000..bc24f788
--- /dev/null
+++ b/Source/Ogda/database_manifest.h
@@ -0,0 +1,48 @@
+//-----------------------------------------------------------------------------
+// Name: database_manifest.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+#include "databasemanifestresult.h"
+
+#include <vector>
+#include <set>
+
+class JobHandler;
+class Builder;
+class Item;
+
+class DatabaseManifest
+{
+ int thread;
+ std::vector<DatabaseManifestResult> results;
+ std::set<uint64_t> hash_set;
+public:
+ DatabaseManifest();
+
+ bool Load(const std::string& manifest);
+ bool Save(const std::string& manifest);
+
+ void AddResult( const DatabaseManifestResult& results );
+
+ bool HasBuiltResultFor( JobHandler& jh, const Item& item, const Builder& builder );
+ DatabaseManifestResult GetPreviouslyBuiltResult( const Item& item, const Builder& builder);
+};
diff --git a/Source/Ogda/databasemanifestresult.cpp b/Source/Ogda/databasemanifestresult.cpp
new file mode 100644
index 00000000..c965f708
--- /dev/null
+++ b/Source/Ogda/databasemanifestresult.cpp
@@ -0,0 +1,48 @@
+//-----------------------------------------------------------------------------
+// Name: databasemanifestresult.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "databasemanifestresult.h"
+
+#include <string>
+
+#include <Internal/filesystem.h>
+#include <Ogda/jobhandler.h>
+#include <Ogda/Builders/actionbase.h>
+#include <Ogda/Generators/creatorbase.h>
+#include <Ogda/ogda_hash.h>
+
+DatabaseManifestResult::DatabaseManifestResult( const Item& item, const std::string& dest_hash, const std::string& dest, const std::string& name, const std::string& version, const std::string& type )
+: item(item), dest(dest), dest_hash(dest_hash), name(name), version(version), type(type)
+{
+}
+
+void DatabaseManifestResult::CalculateHash(const char* base_path)
+{
+ current_dest_hash = GetFileHash(AssemblePath(base_path, AssemblePath("files",AssemblePath(item.hash,dest_hash))).c_str()).ToString();
+}
+
+const std::string& DatabaseManifestResult::GetCurrentDestHash(const std::string& base_path)
+{
+ if( current_dest_hash.empty() )
+ CalculateHash(base_path.c_str());
+ return current_dest_hash;
+}
diff --git a/Source/Ogda/databasemanifestresult.h b/Source/Ogda/databasemanifestresult.h
new file mode 100644
index 00000000..51c1cdee
--- /dev/null
+++ b/Source/Ogda/databasemanifestresult.h
@@ -0,0 +1,53 @@
+//-----------------------------------------------------------------------------
+// Name: databasemanifestresult.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Utility/hash.h>
+#include "item.h"
+
+class ActionBase;
+class CreatorBase;
+class JobHandler;
+
+class DatabaseManifestResult
+{
+private:
+ std::string current_dest_hash;
+
+public:
+ DatabaseManifestResult( const Item& item, const std::string& dest_hash, const std::string& dest, const std::string& name, const std::string& version, const std::string& type );
+
+ void CalculateHash(const char* base_path);
+ const std::string& GetCurrentDestHash(const std::string& base_path);
+
+ std::string dest;
+ std::string dest_hash;
+
+ std::string name; //Either creator or action
+ std::string version; //Either creator or action
+ std::string type;
+
+ //Only in built data does this come in as relevant.
+ //And even then, we don't have support for more than one source at this time.
+ Item item;
+};
diff --git a/Source/Ogda/filterbase.h b/Source/Ogda/filterbase.h
new file mode 100644
index 00000000..43ef2efd
--- /dev/null
+++ b/Source/Ogda/filterbase.h
@@ -0,0 +1,27 @@
+//-----------------------------------------------------------------------------
+// Name: filterbase.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+class FilterBase
+{
+};
diff --git a/Source/Ogda/item.cpp b/Source/Ogda/item.cpp
new file mode 100644
index 00000000..9742b829
--- /dev/null
+++ b/Source/Ogda/item.cpp
@@ -0,0 +1,247 @@
+//-----------------------------------------------------------------------------
+// Name: item.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include "item.h"
+#include <iostream>
+
+#include <Utility/hash.h>
+#include <Internal/filesystem.h>
+#include <Logging/logdata.h>
+#include <Internal/datemodified.h>
+#include <Ogda/ogda_hash.h>
+
+Item::Item( const std::string& input_folder, const std::string& path, const std::string& type, const JobXMLParser::Item& source ) :
+ input_folder(input_folder),
+ type(type),
+ hash(""),
+ source(source),
+ only_parse(false),
+ delete_on_exit(false),
+ is_overshadowed(false)
+{
+ SetPath(path);
+}
+
+Item::Item( const std::string& input_folder, const std::string& path, const std::string& type, const std::string& hash, const JobXMLParser::Item& source ) :
+ input_folder(input_folder),
+ type(type),
+ hash(hash),
+ source(source),
+ only_parse(false),
+ delete_on_exit(false),
+ is_overshadowed(false)
+{
+ SetPath(path);
+}
+
+void Item::SetPath( std::string path )
+{
+ //This is a bit of a hack, to look for Data, but it's almost part of the standard by now.
+ if( path.substr(0,5) == std::string("Data/") )
+ {
+ this->path = path.substr(5);
+ }
+ else if( path.substr(0,5) == std::string("Data\\") )
+ {
+ this->path = path.substr(5);
+ }
+ else if( path.substr(0,5) == std::string("data\\") )
+ {
+ this->path = path.substr(5);
+ }
+ else if( path.substr(0,5) == std::string("data/") )
+ {
+ this->path = path.substr(5);
+ }
+ else
+ {
+ this->path = path;
+ }
+}
+
+bool Item::operator==(const JobXMLParser::Item& rhs) const
+{
+ return this->type == rhs.type && this->path == rhs.path;
+}
+
+bool Item::operator==(const Item& rhs) const
+{
+ return this->hash == rhs.hash && this->path == rhs.path && this->type == rhs.type && this->only_parse == rhs.only_parse;
+}
+
+bool Item::operator<(const Item& rhs) const
+{
+ if( hash < rhs.hash )
+ {
+ return true;
+ }
+ else if( hash == rhs.hash )
+ {
+ if( path < rhs.path )
+ {
+ return true;
+ }
+ else if( path == rhs.path )
+ {
+ if( type < rhs.type )
+ {
+ return true;
+ }
+ else if( type == rhs.type )
+ {
+ if( only_parse < rhs.only_parse )
+ {
+ return true;
+ }
+ else if( only_parse == rhs.only_parse )
+ {
+ return false;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+}
+
+bool Item::FileAccess( )
+{
+ std::string fullpath = GetAbsPath();
+ return CheckFileAccess(fullpath.c_str());
+}
+
+void Item::CalculateHash()
+{
+ hash = OgdaGetFileHash(GetAbsPath());
+}
+
+std::string Item::GetPath() const
+{
+ return path;
+}
+
+void Item::VerifyPath()
+{
+ std::string assembled = GetAbsPath();
+ if(!CheckFileAccess(assembled.c_str()))
+ {
+ std::string new_path = CaseCorrect(assembled);
+
+ SetPath(new_path.substr(input_folder.size()));
+ LOGD << "Case Corrected path to " << path << std::endl;
+
+ std::string assembled = GetAbsPath();
+ if( !CheckFileAccess(assembled.c_str()) )
+ {
+ LOGE << "File still missing after case correction: " << assembled << std::endl;
+ }
+ }
+}
+
+std::string Item::GetAbsPath() const
+{
+ if( path[0] == '/' ) //Don't have to correct abs paths because they refer to a generated file.
+ {
+ return path;
+ }
+ else
+ {
+ return AssemblePath(input_folder, path);
+ }
+}
+
+bool Item::IsOnlySearch()
+{
+ return only_parse;
+}
+
+void Item::SetOnlySearch(bool value)
+{
+ only_parse = value;
+}
+
+bool Item::IsOvershadowed()
+{
+ return is_overshadowed;
+}
+
+void Item::SetOvershadowed(bool value)
+{
+ is_overshadowed = value;
+}
+
+bool Item::Overshadows( const Item& item )
+{
+ if( false == overshadows.empty() && overshadows == item.GetPath() )
+ {
+ return true;
+ }
+ return false;
+}
+
+void Item::SetOvershadows( const Item& item )
+{
+ overshadows = item.GetPath();
+}
+
+void Item::SetDeleteOnExit(bool value)
+{
+ delete_on_exit = value;
+}
+
+bool Item::IsDeleteOnExit()
+{
+ return delete_on_exit;
+}
+
+std::ostream& operator<<(std::ostream& out, const Item& item)
+{
+ return (out << "Item(" << item.path << "," << item.type << "," << item.hash << "," << item.only_parse << "," << item.source << ")");
+}
+
+uint64_t Item::GetSubHash() const{
+ char val[8];
+ if( hash.size() > 16 ) {
+ for( int i = 0; i < 8; i++ ) {
+ val[i] = hash[i*2] * 16 + hash[i*2+1];
+ }
+ } else {
+ LOGW << "Original hash is too small or nonexistant. " << *this << std::endl;
+ return 0;
+ }
+ return *reinterpret_cast<uint64_t*>(val);
+}
diff --git a/Source/Ogda/item.h b/Source/Ogda/item.h
new file mode 100644
index 00000000..97d184f3
--- /dev/null
+++ b/Source/Ogda/item.h
@@ -0,0 +1,85 @@
+//-----------------------------------------------------------------------------
+// Name: item.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <string>
+#include <XML/Parsers/jobxmlparser.h>
+
+class JobHandler;
+
+class Item
+{
+public:
+ bool only_parse;
+ bool delete_on_exit;
+ bool is_overshadowed;
+private:
+
+ void SetPath( std::string path );
+ std::string overshadows;
+public:
+ //Which of the input folders the item was found in
+ std::string input_folder;
+ //Relative path beneath the Data/ path
+ std::string path;
+
+ std::string type;
+ std::string hash;
+
+ //Original root source to this items existance, could be from a hierarchy of adds.
+ JobXMLParser::Item source;
+
+ Item(const std::string& input_folder, const std::string& path, const std::string& type, const JobXMLParser::Item& source);
+ Item(const std::string& input_folder, const std::string& path, const std::string& type, const std::string& hash, const JobXMLParser::Item& source);
+
+ bool operator==(const JobXMLParser::Item& rhs) const;
+ bool operator==(const Item& rhs) const;
+ bool operator<(const Item& rhs) const;
+
+ //Faster way of checking if the file is valid in case hash isn't calculated.
+ bool FileAccess();
+ void CalculateHash();
+ void VerifyPath();
+ std::string GetPath() const;
+ std::string GetAbsPath() const;
+
+ //Remove file when program shuts down
+ bool IsDeleteOnExit();
+ void SetDeleteOnExit(bool value);
+ //Used for inlined scripts in XML files that are stored to a temp place on disk.
+ bool IsOnlySearch();
+ void SetOnlySearch(bool value);
+
+ bool IsOvershadowed();
+ void SetOvershadowed(bool value);
+
+ bool Overshadows( const Item& item );
+ void SetOvershadows( const Item& item );
+
+ friend std::ostream& operator<<(std::ostream& out, const Item& item);
+
+ //Extract a hash based on the first part. this is endian sensitive, so it shouldn't be stored, only used in runtime for first order identification.
+ uint64_t GetSubHash() const;
+};
+
+std::ostream& operator<<(std::ostream& out, const Item& item);
diff --git a/Source/Ogda/jobhandler.cpp b/Source/Ogda/jobhandler.cpp
new file mode 100644
index 00000000..35964264
--- /dev/null
+++ b/Source/Ogda/jobhandler.cpp
@@ -0,0 +1,707 @@
+//-----------------------------------------------------------------------------
+// Name: jobhandler.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "database_manifest.h"
+#include "jobhandler.h"
+#include <trex/trex.h>
+#include <XML/Parsers/jobxmlparser.h>
+#include <Logging/logdata.h>
+#include "item.h"
+#include <Internal/filesystem.h>
+#include <Ogda/Builders/builderfactory.h>
+#include <Ogda/Searchers/searcherfactory.h>
+#include <Ogda/Generators/generatorfactory.h>
+#include "manifest.h"
+#include "database_manifest.h"
+#include <algorithm>
+#include <cstdio>
+#include <Utility/hash.h>
+#include "jobhandlerthreadpool.h"
+#include <cassert>
+#include "ogda_config.h"
+#include "main.h"
+#include <Utility/strings.h>
+
+JobHandler::JobHandler(std::string output_folder, std::string manifest_dest, std::string manifest_source, std::string databasedir, bool perform_removes, bool force_removes, int threads)
+: output_folder(output_folder), manifest_dest(manifest_dest), manifest_source(manifest_source), databasedir(databasedir), perform_removes(perform_removes),force_removes(force_removes), threads(threads)
+{
+
+}
+
+bool JobHandler::Run(const std::string& path )
+{
+ bool ret = true;
+ Manifest old_manifest(threads);
+ DatabaseManifest database_manifest;
+
+ std::string database_file = AssemblePath(databasedir, "database_manifest.xml");
+
+ if(!manifest_source.empty())
+ {
+ old_manifest.Load(manifest_source);
+ LOGI << "Calculating hashes of previously built files... [" << threads << "]" << std::endl;
+ {
+ old_manifest.PrecalculateCurrentDestinationHashes(output_folder);
+ }
+ }
+
+ if(!databasedir.empty() )
+ {
+ if( config_load_from_database || config_save_to_database ) {
+ database_manifest.Load(database_file);
+ }
+ }
+
+ {
+ JobXMLParser jobparser;
+ if( !jobparser.Load( path ) )
+ {
+ LOGE << "Error" << std::endl;
+ ret = false;
+ }
+ else
+ {
+ input_folders = jobparser.inputs;
+
+ LOGI << "Adding Items..." << std::endl;
+ {
+ size_t item_count = jobparser.items.size();
+ size_t cur = 0;
+ std::vector<JobXMLParser::Item>::iterator itemit;
+ for( itemit = jobparser.items.begin(); itemit != jobparser.items.end(); itemit++ )
+ {
+ std::string chosen_input_folder;
+ std::string item_path;
+ bool is_ok = false;
+
+ LOGD << "Loading Transfer: " << itemit->path << std::endl;
+ for(std::string input_folder : input_folders)
+ {
+ if(is_ok == false) {
+ if( CheckFileAccess(AssemblePath(input_folder,itemit->path).c_str()) ) {
+ item_path = itemit->path;
+ chosen_input_folder = input_folder;
+ is_ok = true;
+ } else {
+ std::string case_corrected = CaseCorrect(AssemblePath(input_folder,itemit->path));
+ if(CheckFileAccess(case_corrected.c_str())) {
+ item_path = case_corrected.substr(input_folder.size());
+ chosen_input_folder = input_folder;
+ is_ok = true;
+ LOGW << "Path \"" << itemit->path << "\" for item had to be case corrected to " << item_path << ". Row: " << itemit->row << std::endl;
+ }
+ }
+ }
+ }
+
+ if( is_ok ) {
+ if( itemit->recursive )
+ {
+ LOGD << "Transfer is recursive" << std::endl;
+
+ std::vector<std::string> manifest;
+ std::string manifest_path = AssemblePath(chosen_input_folder, item_path);
+ GenerateManifest(manifest_path.c_str(), manifest);
+
+ LOGD << "Loaded from " << manifest_path << " found " << manifest.size() << " files." << std::endl;
+
+ std::vector<std::string>::iterator manifestit;
+
+ for( manifestit = manifest.begin(); manifestit != manifest.end(); manifestit++ )
+ {
+ std::string full_sub_path = AssemblePath(item_path,*manifestit);
+ if(endswith(full_sub_path.c_str(), itemit->path_ending.c_str()))
+ {
+ LOGD << "Including " << full_sub_path << std::endl;
+ AddItem(chosen_input_folder, full_sub_path,itemit->type,*itemit);
+ }
+ else
+ {
+ LOGD << "Ignoring " << full_sub_path << std::endl;
+ }
+ }
+ }
+ else
+ {
+ AddItem(chosen_input_folder, item_path,itemit->type,*itemit);
+ }
+ } else {
+ LOGE << "Path \"" << itemit->path << "\" for item is invalid, even after case correction. Row: " << itemit->row << std::endl;
+ }
+
+ cur++;
+ SetPercent( item_path.c_str(), (int)(100.0f*((float)cur/(float)item_count)) );
+ }
+ }
+
+ if( config_print_item_list )
+ {
+ LOGI << "Printing item list from deployment file..." << std::endl;
+
+ std::vector<Item>::iterator itemit = items.begin();
+
+ for( ;itemit != items.end();itemit++ )
+ {
+ LOGI << *itemit << std::endl;
+ }
+ }
+
+ LOGI << "Adding Searchers..." << std::endl;
+ {
+ SearcherFactory sf;
+ std::vector<JobXMLParser::Searcher>::iterator searcherit;
+ for( searcherit = jobparser.searchers.begin(); searcherit != jobparser.searchers.end(); searcherit++ )
+ {
+ if( sf.HasSearcher(searcherit->searcher))
+ {
+ Searcher searcher = sf.CreateSearcher( searcherit->searcher, searcherit->path_ending, searcherit->type_pattern_re);
+ searchers.push_back(searcher);
+ }
+ else
+ {
+ ret = false;
+ LOGE << "Unable to find searcher matching name " << searcherit->searcher << std::endl;
+ }
+ }
+ }
+
+ LOGI << "Adding Builders..." << std::endl;
+ {
+ BuilderFactory bf;
+ std::vector<JobXMLParser::Builder>::iterator builderit;
+ for( builderit = jobparser.builders.begin(); builderit != jobparser.builders.end(); builderit++ )
+ {
+ if( bf.HasBuilder( builderit->builder ) )
+ {
+ Builder builder = bf.CreateBuilder( builderit->builder, builderit->path_ending, builderit->type_pattern_re);
+ builders.push_back(builder);
+ }
+ else
+ {
+ LOGE << "Unable to find builder matching name " << builderit->builder << std::endl;
+ ret = false;
+ }
+ }
+ }
+
+ LOGI << "Adding Generators..." << std::endl;
+ {
+ GeneratorFactory bf;
+ std::vector<JobXMLParser::Generator>::iterator generatorit;
+ for( generatorit = jobparser.generators.begin(); generatorit != jobparser.generators.end(); generatorit++ )
+ {
+ if( bf.HasGenerator( generatorit->generator ) )
+ {
+ Generator generator = bf.CreateGenerator( generatorit->generator );
+ generators.push_back(generator);
+ }
+ else
+ {
+ LOGE << "Unable to find generator matching name " << generatorit->generator << std::endl;
+ ret = false;
+ }
+ }
+
+ }
+
+ LOGD << jobparser << std::endl;
+ }
+ }
+
+ LOGI << "Running searchers through Items..." << std::endl;
+ {
+ std::vector<Item>::iterator itemit;
+ for( itemit = items.begin(); itemit != items.end(); itemit++ )
+ {
+ RunRecursiveSearchOn( *itemit );
+ }
+
+ LOGI << "Found a total of " << foundItems.size() << " objects when searching" << std::endl;
+ items.insert( items.end(), foundItems.begin(), foundItems.end() );
+ }
+
+ LOGI << "Marking overshadowed Items" << std::endl;
+ {
+ std::vector<Item>::iterator itemit;
+ for( itemit = items.begin(); itemit != items.end(); itemit++ )
+ {
+ std::vector<Item>::iterator item2it;
+ for( item2it = items.begin(); item2it != items.end(); item2it++ )
+ {
+ if( itemit->Overshadows(*item2it) )
+ {
+ item2it->SetOvershadowed(true);
+ }
+ }
+ }
+ }
+
+ if( config_print_duplicates )
+ {
+ LOGI << "Looking for duplicate items..." << std::endl;
+ {
+ std::vector<Item>::iterator itemit;
+ std::vector<Item>::iterator itemit2;
+
+ std::set<JobXMLParser::Item> first_level_duplicates;
+
+ for( itemit = items.begin(); itemit != items.end(); itemit++ )
+ {
+ for( itemit2 = itemit + 1; itemit2 != items.end(); itemit2++ )
+ {
+ if( itemit->GetAbsPath() == itemit2->GetAbsPath()
+ && itemit->type == itemit2->type )
+ {
+ if( *itemit == itemit->source )
+ {
+ first_level_duplicates.insert( itemit->source );
+ LOGW << "Found duplicated of " << *itemit << " " << "sourced from rows: " << itemit->source.row << " and " << itemit2->source.row << "." << std::endl;
+ }
+ else if( *itemit2 == itemit2->source )
+ {
+ first_level_duplicates.insert( itemit2->source );
+ LOGW << "Found duplicated of " << *itemit << " " << "sourced from rows: " << itemit->source.row << " and " << itemit2->source.row << "." << std::endl;
+ }
+ }
+ }
+ }
+
+ LOGI << "Listing all first-level first-time references to duplicates... (For simple removal)" << std::endl;
+ {
+ std::set<JobXMLParser::Item>::iterator parserit;
+
+ for( parserit = first_level_duplicates.begin(); parserit != first_level_duplicates.end(); parserit++ )
+ {
+ fprintf( stderr, "RMLN:%d\n", parserit->row );
+ }
+ }
+ }
+ }
+
+ if( config_print_missing )
+ {
+ LOGI << "Printing files in folder but not in deploy list..." << std::endl;
+
+ std::vector<std::string> manifest;
+
+ for(std::string input_folder : input_folders)
+ {
+ GenerateManifest( input_folder.c_str(), manifest );
+ }
+
+ std::vector<std::string>::iterator manifestit = manifest.begin();
+
+ for( ; manifestit != manifest.end(); manifestit++ )
+ {
+ if( false == HasItemWithPath( *manifestit ) )
+ {
+ std::cout << *manifestit << std::endl;
+ }
+ }
+ }
+
+ /*
+ LOGI << "Checking what items don't have a searcher..." << std::endl;
+ {
+ std::map<std::string,int>::iterator searchTypeIt;
+
+ for( searchTypeIt = typeSearcherCount.begin(); searchTypeIt != typeSearcherCount.end(); searchTypeIt++ )
+ {
+ if( searchTypeIt->second == 0 )
+ {
+ LOGW << "Item type: " << searchTypeIt->first << " has no assigned searchers." << std::endl;
+ }
+ }
+ }
+ */
+
+ LOGI << "Calculating Item hashes...[" << threads << "]"<< std::endl;
+ {
+ JobHandlerThreadPool jhtp(threads);
+ jhtp.RunHashCalculation(items);
+ }
+
+ if( !config_mute_missing )
+ {
+ LOGI << "Listing referenced items missing from disk..." << std::endl;
+ {
+ std::vector<Item>::iterator itemit;
+
+ for( itemit = items.begin(); itemit != items.end(); itemit++ )
+ {
+ if( itemit->hash.empty() && itemit->IsOvershadowed() == false )
+ {
+ LOGE << "Missing item " << *itemit << std::endl;
+ }
+ }
+ }
+ }
+
+ Manifest result_manifest(threads);
+ LOGI << "Running Builders through Items..." << std::endl;
+ {
+ size_t item_count = items.size();
+ size_t cur = 0;
+ std::vector<Item>::iterator itemit;
+ for( itemit = items.begin(); itemit != items.end(); itemit++ )
+ {
+ if( itemit->IsOvershadowed() )
+ {
+ LOGI << "Skipping " << *itemit << " because it's overshadowed" << std::endl;
+ }
+ else if( itemit->IsOnlySearch() )
+ {
+ LOGD << "Skipping " << *itemit << " because it's search only" << std::endl;
+ }
+ else
+ {
+ std::vector<Builder>::iterator builderit;
+ int count = 0;
+ for( builderit = builders.begin(); builderit != builders.end(); builderit++ )
+ {
+ if( builderit->IsMatch( *itemit ))
+ {
+ count++;
+ if(builderit->RunEvenOnIdenticalSource() == false && old_manifest.IsUpToDate( *this, *itemit, *builderit ) )
+ {
+ LOGD << "Using cached result on " << *itemit << std::endl;
+ result_manifest.AddResult(old_manifest.GetPreviouslyBuiltResult( *itemit, *builderit ));
+ }
+ else if( config_load_from_database && builderit->RunEvenOnIdenticalSource() == false && builderit->StoreResultInDatabase() && database_manifest.HasBuiltResultFor(*this, *itemit, *builderit) )
+ {
+ LOGI << "Using database value on " << *itemit << std::endl;
+ DatabaseManifestResult dmr = database_manifest.GetPreviouslyBuiltResult(*itemit, *builderit);
+
+ std::string result_source_path = AssemblePath(databasedir,AssemblePath("files",AssemblePath(dmr.item.hash,dmr.dest_hash)));
+ std::string result_dest_path = AssemblePath(output_folder,dmr.dest);
+
+ CreateParentDirs(result_dest_path);
+ copyfile(result_source_path,result_dest_path);
+ result_manifest.AddResult(ManifestResult(dmr.dest_hash, *itemit, dmr.dest, true, dmr.name, dmr.version, ManifestResult::DATABASE, dmr.type));
+ }
+ else
+ {
+ //Check if item has source hash, meaning if the source file exists.
+ if( !itemit->hash.empty() )
+ {
+ LOGD << "Running " << builderit->GetBuilderName() << " on " << *itemit << std::endl;
+ result_manifest.AddResult(builderit->Run(*this, *itemit));
+ }
+ else
+ {
+ LOGE << "Unable to run " << builderit->GetBuilderName() << " on " << *itemit << " file missing." << std::endl;
+ ret = false;
+ }
+ }
+ }
+ else
+ {
+ LOGD << "Skipping " << builderit->GetBuilderName() << " on " << *itemit << ", doesn't match pattern." << std::endl;
+ }
+ }
+
+ if( count == 0 && !itemit->hash.empty() )
+ LOGW << *itemit << " has no assigned builder" << std::endl;
+ if( count > typeBuilderCount[itemit->type] )
+ typeBuilderCount[itemit->type] = count;
+ }
+ cur++;
+ SetPercent( itemit->GetPath().c_str(), (int)(100.0f*((float)cur/(float)item_count)) );
+ }
+ }
+
+ //Copy manifest to use when running the generators.
+ const Manifest generated_manifest = result_manifest;
+
+ LOGI << "Running Generators..." << std::endl;
+ {
+ std::vector<Generator>::iterator generatorit;
+ int count = 0;
+ for( generatorit = generators.begin(); generatorit != generators.end(); generatorit++ )
+ {
+ count++;
+ LOGD << "Running " << generatorit->GetGeneratorName() << std::endl;
+ result_manifest.AddResult(generatorit->Run(*this,generated_manifest));
+ }
+ }
+
+ LOGI << "Checking what items don't have a builder..." << std::endl;
+ {
+ std::map<std::string,int>::iterator builderTypeIt;
+
+ for( builderTypeIt = typeBuilderCount.begin(); builderTypeIt != typeBuilderCount.end(); builderTypeIt++ )
+ {
+ if( builderTypeIt->second == 0 )
+ {
+ LOGW << "Item type: " << builderTypeIt->first << " has no assigned builder." << std::endl;
+ }
+ }
+ }
+
+ if( result_manifest.HasError() )
+ {
+ LOGE << "Some builder(s) caused an error, see manifest for more info." << std::endl;
+ ret = false;
+ }
+
+ //Unlinks are serious business, so we do this carefully
+ if( ret || force_removes )
+ {
+ LOGI << "Removing items not listed in the generated manifest" << std::endl;
+
+ std::vector<std::string> destination_file_list;
+ GenerateManifest( output_folder.c_str(), destination_file_list );
+ {
+ std::vector<std::string>::iterator missit;
+ for(missit = destination_file_list.begin(); missit != destination_file_list.end(); missit++ )
+ {
+ LOGD << "In Output Folder: " << *missit << std::endl;
+ }
+ }
+
+ std::vector<std::string> new_manifest_file_list = result_manifest.GetDestinationFiles();
+ {
+ std::vector<std::string>::iterator missit;
+ for(missit = new_manifest_file_list.begin(); missit != new_manifest_file_list.end(); missit++ )
+ {
+ LOGD << "New Manifest File: " << *missit << std::endl;
+ }
+ }
+
+ std::vector<std::string> old_manifest_file_list = old_manifest.GetDestinationFiles();
+ {
+ std::vector<std::string>::iterator missit;
+ for(missit = old_manifest_file_list.begin(); missit != old_manifest_file_list.end(); missit++ )
+ {
+ LOGD << "Old Manifest File: " << *missit << std::endl;
+ }
+ }
+
+ std::vector<std::string> unlisted_files;
+
+ std::sort( destination_file_list.begin(), destination_file_list.end() );
+ std::sort( new_manifest_file_list.begin(), new_manifest_file_list.end() );
+ std::sort( old_manifest_file_list.begin(), old_manifest_file_list.end() );
+
+ std::set_difference( destination_file_list.begin(), destination_file_list.end(),
+ new_manifest_file_list.begin(), new_manifest_file_list.end(),
+ std::back_inserter(unlisted_files) );
+
+ {
+ std::vector<std::string>::iterator missit;
+ for(missit = unlisted_files.begin(); missit != unlisted_files.end(); missit++ )
+ {
+ LOGI << "Unlisted: " << *missit << std::endl;
+ }
+ }
+
+ std::vector<std::string> remove_list;
+
+
+ //Remove files that are in the folder but not known from previous builds?
+ if( config_remove_unlisted_files )
+ {
+ LOGI << "Adding all unlisted files into the remove list. (--remove-unlisted)" << std::endl;
+ remove_list.insert(remove_list.begin(), unlisted_files.begin(), unlisted_files.end() );
+ }
+ else
+ {
+ //Only remove unlisted files that are mentioned in the old manifest.
+ std::set_intersection( unlisted_files.begin(), unlisted_files.end(),
+ old_manifest_file_list.begin(), old_manifest_file_list.end(),
+ std::back_inserter(remove_list) );
+ }
+
+ //If this passes i'm comfortable removing files.
+ if( remove_list == unlisted_files || force_removes )
+ {
+ if( remove_list == unlisted_files )
+ {
+ LOGI << "Unlisted files match old manifest, removing them" << std::endl;
+ }
+ else
+ {
+ LOGI << "Dictated to forcefully remove all found items." << std::endl;
+ }
+
+ {
+ std::vector<std::string>::iterator missit;
+ for(missit = remove_list.begin(); missit != remove_list.end(); missit++ )
+ {
+ std::string full_remove_path = AssemblePath(output_folder,*missit);
+ if( perform_removes )
+ {
+ LOGW << "Removing " << full_remove_path << std::endl;
+ remove( full_remove_path.c_str() );
+ }
+ else
+ {
+ LOGI << "Pretending to remove (no --perform-removes): " << full_remove_path << std::endl;
+ }
+ }
+ }
+ }
+ else
+ {
+ LOGF << "Unlisted files and old manifest files don't match, i refuse to try and remove anything because this is a hint that something isn't right in this fully managed directory." << std::endl;
+ ret = false;
+ }
+ }
+ else
+ {
+ LOGE << "Skipping removal due to previous error(s)" << std::endl;
+ }
+
+ LOGI << "Removing temporary items..." << std::endl;
+ {
+ std::vector<Item>::iterator itemit;
+ for( itemit = items.begin(); itemit != items.end(); itemit++ )
+ {
+ if( itemit->IsDeleteOnExit() )
+ {
+ if( perform_removes )
+ {
+ LOGD << "Removing " << *itemit << std::endl;
+ std::string path = itemit->GetAbsPath();
+ remove( path.c_str() );
+ }
+ else
+ {
+ LOGD << "Skipping remove of " << *itemit << " as --perform-removes isn't specified" << std::endl;
+ }
+
+ }
+ }
+ }
+
+ if( !manifest_dest.empty() )
+ {
+ LOGI << "Saving resulting manifest to disk: " << manifest_dest << std::endl;
+ result_manifest.Save(manifest_dest);
+ }
+
+ if(!databasedir.empty() ) {
+ if( config_save_to_database ) {
+ std::vector<ManifestResult>::const_iterator mareit = result_manifest.ResultsBegin();
+ BuilderFactory builder_factory;
+ for(; mareit != result_manifest.ResultsEnd(); mareit++ ) {
+ if( mareit->mr_type == ManifestResult::BUILT ) {
+ if( mareit->items.size() == 1 ) {
+ if( builder_factory.StoreResultInDatabase(mareit->name) ) {
+
+ LOGI << "Storing " << *mareit << " in database for future use." << std::endl;
+ std::string result_source_path = AssemblePath(output_folder,mareit->dest);
+ std::string result_dest_path = AssemblePath(databasedir,AssemblePath("files",AssemblePath(mareit->items[0].hash,mareit->dest_hash)));
+
+ CreateParentDirs(result_dest_path);
+ copyfile(result_source_path,result_dest_path);
+
+ database_manifest.AddResult(DatabaseManifestResult(
+ mareit->items[0],
+ mareit->dest_hash,
+ mareit->dest,
+ mareit->name,
+ mareit->version,
+ mareit->type
+ ));
+ }
+ } else {
+ LOGW << "Database doesn't support multi item sources" << std::endl;
+ }
+ }
+ }
+ CreateParentDirs(database_file);
+ database_manifest.Save(database_file);
+ }
+ }
+
+ return ret;
+}
+
+void JobHandler::RunRecursiveSearchOn( const Item& item )
+{
+ std::vector<Item> foundSum;
+ if( std::find(searchedItems.begin(),searchedItems.end(),item) == searchedItems.end() )
+ {
+ std::vector<Searcher>::iterator searcherit;
+ int count = 0;
+ for( searcherit = searchers.begin(); searcherit != searchers.end(); searcherit++ )
+ {
+ std::vector<Item> f = searcherit->TrySearch(*this,item,&count);
+
+ foundSum.insert(foundSum.end(),f.begin(),f.end());
+ }
+ assert((count > 0 && foundSum.size() > 0) || foundSum.size() == 0);
+
+ if( count == 0 )
+ {
+ LOGW << item << " has no assigned searcher" << std::endl;
+ }
+
+ if( count > typeSearcherCount[item.type] )
+ typeSearcherCount[item.type] = count;
+
+ searchedItems.push_back(item);
+
+ std::vector<Item>::iterator foundSumIt;
+
+ for( foundSumIt = foundSum.begin(); foundSumIt != foundSum.end(); foundSumIt++ )
+ {
+ //We sometimes get broken paths from the files (bad case etc)
+ foundSumIt->VerifyPath();
+ RunRecursiveSearchOn( *foundSumIt );
+ }
+
+ if( foundSum.size() > 0 )
+ LOGD << "Found " << foundSum.size() << " in " << item << std::endl;
+ foundItems.insert(foundSum.begin(), foundSum.end());
+ }
+}
+
+void JobHandler::AddItem(const std::string& input_folder, const std::string& path, const std::string& type, const JobXMLParser::Item& source)
+{
+ LOGD << "Adding " << type << ": " << path << std::endl;
+ items.push_back(Item(input_folder,path,type,source));
+}
+
+std::vector<Item>::const_iterator JobHandler::ItemsBegin() const
+{
+ return items.begin();
+}
+
+std::vector<Item>::const_iterator JobHandler::ItemsEnd() const
+{
+ return items.end();
+}
+
+bool JobHandler::HasItemWithPath( const std::string& path )
+{
+ std::vector<Item>::iterator itemit = items.begin();
+ for( ; itemit != items.end(); itemit++ )
+ {
+ if( itemit->path == path )
+ {
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/Source/Ogda/jobhandler.h b/Source/Ogda/jobhandler.h
new file mode 100644
index 00000000..1fef0822
--- /dev/null
+++ b/Source/Ogda/jobhandler.h
@@ -0,0 +1,65 @@
+//-----------------------------------------------------------------------------
+// Name: jobhandler.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <string>
+#include <set>
+#include <vector>
+
+
+#include "item.h"
+#include <Ogda/Builders/builder.h>
+#include <Ogda/Searchers/searcher.h>
+#include <Ogda/Generators/generator.h>
+
+class JobHandler
+{
+ std::map<std::string,int> typeSearcherCount;
+ std::map<std::string,int> typeBuilderCount;
+
+ std::vector<Item> items;
+ std::vector<Item> searchedItems;
+ std::set<Item> foundItems;
+
+ std::vector<Builder> builders;
+ std::vector<Searcher> searchers;
+ std::vector<Generator> generators;
+
+ int threads;
+
+ void RunRecursiveSearchOn( const Item& item );
+public:
+ std::vector<std::string> input_folders;
+ std::string output_folder, manifest_dest, manifest_source, databasedir;
+ bool perform_removes;
+ bool force_removes;
+
+ JobHandler(std::string output_folder, std::string manifest_dest, std::string manifest_source, std::string databasedir, bool perform_removes, bool force_removes, int threads);
+ bool Run(const std::string& path);
+ void AddItem(const std::string& input_folder, const std::string& path, const std::string& type, const JobXMLParser::Item& source);
+
+ std::vector<Item>::const_iterator ItemsBegin() const;
+ std::vector<Item>::const_iterator ItemsEnd() const;
+
+ bool HasItemWithPath( const std::string& path );
+};
diff --git a/Source/Ogda/jobhandlerthreadpool.cpp b/Source/Ogda/jobhandlerthreadpool.cpp
new file mode 100644
index 00000000..05a64de2
--- /dev/null
+++ b/Source/Ogda/jobhandlerthreadpool.cpp
@@ -0,0 +1,119 @@
+//-----------------------------------------------------------------------------
+// Name: jobhandlerthreadpool.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <Memory/allocation.h>
+#include "jobhandlerthreadpool.h"
+
+#include <Logging/logdata.h>
+#include <cstdlib>
+#include <pthread.h>
+#include <iostream>
+#include "main.h"
+
+#ifdef PLATFORM_LINUX
+#include <unistd.h>
+#endif
+
+struct ItemJob
+{
+ Item* item;
+ int step;
+ int count;
+ volatile int performed;
+};
+
+static void* CalculateItemHash( void* item )
+{
+ ItemJob* ij = static_cast<ItemJob*>(item);
+ ij->performed = 0;
+ for( int i = 0; i < ij->count; i++ )
+ {
+ ij->item[i*ij->step].CalculateHash();
+ ij->performed++;
+ }
+ return 0;
+}
+
+JobHandlerThreadPool::JobHandlerThreadPool( int thread_count ) : thread_count(thread_count)
+{
+ threads = static_cast<pthread_t*>(OG_MALLOC( sizeof(pthread_t) * thread_count ));
+}
+
+JobHandlerThreadPool::~JobHandlerThreadPool()
+{
+ OG_FREE(threads);
+}
+
+void JobHandlerThreadPool::RunHashCalculation(std::vector<Item>& arr)
+{
+ ItemJob* itemjobs = static_cast<ItemJob*>(OG_MALLOC(sizeof(ItemJob)*thread_count));
+
+ int step = arr.size()/thread_count;
+
+ for( int i = 0; i < thread_count; i++ )
+ {
+ itemjobs[i].item = &arr[i];
+ itemjobs[i].step = thread_count;
+ itemjobs[i].count = step;
+
+ int err = pthread_create( &threads[i], NULL, CalculateItemHash, &itemjobs[i]);
+
+ if( err != 0 )
+ {
+ LOGE << "Thread create failed" << std::endl;
+ }
+ }
+
+ //Do the remaining items in our main thread.
+ for( int i = step*thread_count; i < arr.size(); i++ )
+ {
+ arr[i].CalculateHash();
+ }
+
+ int count = 0;
+ while( count < arr.size() )
+ {
+#ifdef PLATFORM_LINUX
+ sleep(1);
+#endif
+ std::stringstream ss;
+ count = arr.size()-step*thread_count;
+ for( int i = 0; i < thread_count; i++ )
+ {
+ count += itemjobs[i].performed;
+ ss << ((itemjobs[i].performed == itemjobs[i].count) ? "d" : ".");
+ }
+ SetPercent(ss.str().c_str(), (int)(100.0f*((float)count/(float)arr.size())));
+ }
+
+ for( int i = 0; i < thread_count; i++ )
+ {
+ void* retval;
+ int err = pthread_join( threads[i], &retval );
+
+ if( err != 0 )
+ {
+ LOGE << "Thread join failed" << std::endl;
+ }
+ }
+ OG_FREE( itemjobs );
+}
diff --git a/Source/Ogda/jobhandlerthreadpool.h b/Source/Ogda/jobhandlerthreadpool.h
new file mode 100644
index 00000000..a49a8081
--- /dev/null
+++ b/Source/Ogda/jobhandlerthreadpool.h
@@ -0,0 +1,40 @@
+//-----------------------------------------------------------------------------
+// Name: jobhandlerthreadpool.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <pthread.h>
+
+#include <vector>
+#include "item.h"
+
+class JobHandlerThreadPool
+{
+private:
+ pthread_t* threads;
+ int thread_count;
+public:
+ JobHandlerThreadPool(int thread_count);
+ ~JobHandlerThreadPool();
+
+ void RunHashCalculation(std::vector<Item>& arr);
+};
diff --git a/Source/Ogda/levelreader.h b/Source/Ogda/levelreader.h
new file mode 100644
index 00000000..44a1ef8c
--- /dev/null
+++ b/Source/Ogda/levelreader.h
@@ -0,0 +1,30 @@
+//-----------------------------------------------------------------------------
+// Name: levelreader.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "readerbase.h"
+
+class LevelReader: public ReaderBase
+{
+
+};
diff --git a/Source/Ogda/main.cpp b/Source/Ogda/main.cpp
new file mode 100644
index 00000000..fb973994
--- /dev/null
+++ b/Source/Ogda/main.cpp
@@ -0,0 +1,179 @@
+//-----------------------------------------------------------------------------
+// Name: main.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <cstdlib>
+
+#include <Version/version.h>
+#include <tclap/CmdLine.h>
+#include <XML/Parsers/jobxmlparser.h>
+#include <Logging/logdata.h>
+#include <Logging/consolehandler.h>
+#include <Internal/common.h>
+#include <Internal/error.h>
+#include <Memory/allocation.h>
+
+#include "ogda_config.h"
+#include "jobhandler.h"
+#include <cstdlib>
+#include <cstdarg>
+
+void SetPercent( const char* message, int percent )
+{
+ if( !config_hide_progress )
+ {
+#ifdef PLATFORM_LINUX
+ printf( "%c[2K", 27 );
+#endif
+ printf( "[%d%%]:%s\r", percent, message );
+ fflush( stdout );
+ }
+}
+
+//Replacement for the main program version of the same fucntion
+void FatalError(const char* title, const char* fmt, ...)
+{
+ static const int kBufSize = 1024;
+ char err_buf[kBufSize];
+ va_list args;
+ va_start(args, fmt);
+ VFormatString(err_buf, kBufSize, fmt, args);
+ va_end(args);
+ LOGF << title << "," << err_buf << std::endl;
+ exit(10);
+}
+
+ErrorResponse DisplayError(const char* title, const char* contents, ErrorType type, bool allow_repetition)
+{
+ LOGF << title << ", " << contents << std::endl;
+ exit(1);
+ return _continue;
+}
+
+ErrorResponse DisplayFormatError(ErrorType type,
+ bool allow_repetition,
+ const char* title,
+ const char* fmtcontents,
+ ... ) {
+ static const int kBufSize = 2048;
+ char err_buf[kBufSize];
+ va_list args;
+ va_start(args, fmtcontents);
+ VFormatString(err_buf, kBufSize, fmtcontents, args);
+ va_end(args);
+ return DisplayError(title, err_buf, type, allow_repetition);
+}
+
+Allocation alloc;
+
+int main( int argc, const char** argv )
+{
+ alloc.Init();
+
+ TCLAP::CmdLine cmd("Ogda", ' ', GetBuildVersion());
+
+ TCLAP::ValueArg<std::string> inputArg("i", "input-dir", "Input Directory", true, "", "string"); cmd.add(inputArg);
+ TCLAP::ValueArg<std::string> outputArg("o", "output-dir", "Output directory", true, "", "string"); cmd.add(outputArg);
+ TCLAP::ValueArg<std::string> jobfileArg("j", "job-file", "job file", true, "", "string"); cmd.add(jobfileArg);
+ TCLAP::ValueArg<std::string> manifestOutArg("", "manifest-output", "Manifest output file, destionation to store a complete manifest of the generated files.", false, "","string"); cmd.add(manifestOutArg);
+ TCLAP::ValueArg<std::string> manifestInArg("", "manifest-input", "Manifest input file containing a manifest from the previous build, allows the program to skip converstion of some files.", false, "","string"); cmd.add(manifestInArg);
+ TCLAP::ValueArg<std::string> databaseDirArg("", "database-dir", "Path to database storage for auxiliary storage of shared files.", false, "","string"); cmd.add(databaseDirArg);
+
+ TCLAP::SwitchArg debugOutput("d","debug-output","Start game with debug output", cmd, false);
+ TCLAP::SwitchArg performRemoves("","perform-removes","Actually do removes that the program intend, instead of faking them", cmd, false);
+ TCLAP::SwitchArg forceRemove("", "force-removes", "Always perform removes, even if there is an error in execution or the destination folder contains files which aren't listed in the manifest.", cmd, false);
+ TCLAP::SwitchArg removeUnlisted("", "remove-unlisted", "Remove files in dest folder that aren't mentioned in the manifest. (Requires --force-removes)", cmd, false);
+
+ TCLAP::SwitchArg printMissing("", "print-missing", "List all files which aren't mentioned in the manifest",cmd,false);
+
+ TCLAP::SwitchArg muteMissing("", "mute-missing", "", cmd, false);
+ TCLAP::SwitchArg hideProgress("", "hide-progress", "", cmd, false);
+
+ TCLAP::SwitchArg dateModifiedHash("", "date-modified-hash", "Use the date modified as check for file change, less reliable but faster (BROKEN)", cmd, false);
+
+ TCLAP::SwitchArg printDuplicates("", "print-duplicates", "Give warnings about duplicate references and print a line number list of unnecessary references to items that are recursively referenced from elsewhere", cmd, false);
+
+ TCLAP::SwitchArg printItemList("", "print-item-list", "Print all items initially included via the deployment file", cmd, false);
+
+ TCLAP::SwitchArg loadFromDatabase("", "load-from-database", "Load files from a shared auxiliary database", cmd, false);
+ TCLAP::SwitchArg saveToDatabase("", "save-to-database", "Save files to a shared auxiliary database", cmd, false);
+
+ TCLAP::ValueArg<int> threadArg("", "threads", "thread count", false, 8, "int"); cmd.add(threadArg);
+
+ cmd.parse( argc, argv );
+
+ std::string jobfile = jobfileArg.getValue();
+ std::string outputfolder = outputArg.getValue();
+ std::string inputfolder = inputArg.getValue();
+ std::string manifestout = manifestOutArg.getValue();
+ std::string manifestin = manifestInArg.getValue();
+ std::string databasedir = databaseDirArg.getValue();
+ bool debug_output = debugOutput.getValue();
+ bool perform_removes = performRemoves.getValue();
+ bool force_removes = forceRemove.getValue();
+ config_remove_unlisted_files = removeUnlisted.getValue();
+ config_print_missing = printMissing.getValue();
+ config_mute_missing = muteMissing.getValue();
+ config_hide_progress = hideProgress.getValue();
+ config_use_date_modified_as_hash = dateModifiedHash.getValue();
+ config_print_duplicates = printDuplicates.getValue();
+ config_print_item_list = printItemList.getValue();
+ config_load_from_database = loadFromDatabase.getValue();
+ config_save_to_database = saveToDatabase.getValue();
+ int threads = threadArg.getValue();
+
+
+ int ret = 0;
+ ConsoleHandler consoleHandler;
+ LogTypeMask level =
+ LogSystem::info
+ | LogSystem::warning
+ | LogSystem::error
+ | LogSystem::fatal;
+
+ if( debug_output )
+ level |= LogSystem::debug;
+
+ LogSystem::RegisterLogHandler( level, &consoleHandler );
+
+ LOGI << "Starting Ogda "<< GetBuildVersion() << std::endl;
+
+ if( ( config_load_from_database || config_load_from_database ) && databasedir.empty() ) {
+ LOGW << "Asked to load or/and save database, but missing the --database-dir flag, ignoring these two other flags." << std::endl;
+ }
+
+ std::vector<std::string> input_folders;
+ input_folders.push_back(inputfolder);
+ JobHandler jobhandler(outputfolder,manifestout,manifestin,databasedir,perform_removes,force_removes,threads);
+
+ bool run_was_clean = jobhandler.Run(jobfile);
+
+ ret = run_was_clean ? 0 : 1;
+
+ if( ret != 0 )
+ LOGE << "One or more error(s) caused unwanted execution, see log for more information" << std::endl;
+
+ LogSystem::DeregisterLogHandler( &consoleHandler );
+
+ alloc.Dispose();
+
+ return ret;
+}
diff --git a/Source/Ogda/main.h b/Source/Ogda/main.h
new file mode 100644
index 00000000..8edf52a5
--- /dev/null
+++ b/Source/Ogda/main.h
@@ -0,0 +1,24 @@
+//-----------------------------------------------------------------------------
+// Name: main.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+void SetPercent( const char* message, int percent );
diff --git a/Source/Ogda/manifest.cpp b/Source/Ogda/manifest.cpp
new file mode 100644
index 00000000..bc5ebb87
--- /dev/null
+++ b/Source/Ogda/manifest.cpp
@@ -0,0 +1,456 @@
+//-----------------------------------------------------------------------------
+// Name: manifest.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "manifest.h"
+
+#include <tinyxml.h>
+#include <XML/xml_helper.h>
+
+#include <Utility/commonregex.h>
+#include <Logging/logdata.h>
+#include <Internal/filesystem.h>
+#include <XML/Parsers/jobxmlparser.h>
+#include <Version/version.h>
+
+#include "jobhandler.h"
+#include "manifestthreadpool.h"
+
+Manifest::Manifest(int thread): thread(thread)
+{
+}
+
+bool Manifest::Load(const std::string& manifest)
+{
+ results.clear();
+ CommonRegex cr;
+ TiXmlDocument doc( manifest.c_str() );
+ doc.LoadFile();
+
+ if( !doc.Error() )
+ {
+ TiXmlElement* pRoot = doc.RootElement();
+
+ if( pRoot )
+ {
+ TiXmlHandle hRoot(pRoot);
+
+ TiXmlNode* nResult = hRoot.FirstChild().ToNode();
+
+ while( nResult )
+ {
+ TiXmlElement* eResult = nResult->ToElement();
+
+ if( eResult )
+ {
+ if( strcmp(eResult->Value(), "Result") == 0 //Result is a backwards compatible name, never generated.
+ || strcmp(eResult->Value(), "BuilderResult") == 0 )
+ {
+
+ const char* dest = eResult->Attribute("dest");
+ const char* dest_hash = eResult->Attribute("dest_hash");
+ const char* builder = eResult->Attribute("builder");
+ const char* builder_version = eResult->Attribute("builder_version");
+ const char* type = eResult->Attribute("type");
+ bool success = cr.saysTrue(eResult->Attribute("success"));
+
+ std::string dest_s;
+ if( dest )
+ dest_s = std::string( dest );
+ std::string dest_hash_s;
+ if( dest_hash )
+ dest_hash_s = std::string( dest_hash );
+ std::string builder_s;
+ if( builder )
+ builder_s = std::string( builder );
+ std::string builder_version_s;
+ if( builder_version )
+ builder_version_s = std::string(builder_version);
+ std::string type_s;
+ if( type )
+ type_s = std::string(type);
+
+ std::vector<Item> items;
+
+ TiXmlElement* eItem = nResult->FirstChild("Item")->ToElement();
+
+ while( eItem )
+ {
+ const char* item_path = eItem->Attribute("path");
+ const char* item_type = eItem->Attribute("type");
+ const char* item_hash = eItem->Attribute("hash");
+
+ std::string item_path_s;
+ if( item_path )
+ item_path_s = std::string( item_path );
+ std::string item_type_s;
+ if( item_type )
+ item_type_s = std::string( item_type );
+ std::string item_hash_s;
+ if( item_hash )
+ item_hash_s = std::string( item_hash );
+
+ items.push_back(Item("", item_path_s, item_type_s, item_hash_s, JobXMLParser::Item()));
+
+ eItem = eItem->NextSiblingElement("Item");
+ }
+
+ results.push_back( ManifestResult( dest_hash_s, items, dest_s, success, builder_s, builder_version_s, ManifestResult::BUILT, type_s ) );
+ }
+ else if( strcmp(eResult->Value(), "DatabaseResult") == 0 )
+ {
+
+ const char* dest = eResult->Attribute("dest");
+ const char* dest_hash = eResult->Attribute("dest_hash");
+ const char* builder = eResult->Attribute("builder");
+ const char* builder_version = eResult->Attribute("builder_version");
+ const char* type = eResult->Attribute("type");
+
+ std::string dest_s;
+ if( dest )
+ dest_s = std::string( dest );
+ std::string dest_hash_s;
+ if( dest_hash )
+ dest_hash_s = std::string( dest_hash );
+ std::string builder_s;
+ if( builder )
+ builder_s = std::string( builder );
+ std::string builder_version_s;
+ if( builder_version )
+ builder_version_s = std::string(builder_version);
+ std::string type_s;
+ if( type )
+ type_s = std::string(type);
+
+ std::vector<Item> items;
+
+ TiXmlElement* eItem = nResult->FirstChild("Item")->ToElement();
+
+ while( eItem )
+ {
+ const char* item_path = eItem->Attribute("path");
+ const char* item_type = eItem->Attribute("type");
+ const char* item_hash = eItem->Attribute("hash");
+
+ std::string item_path_s;
+ if( item_path )
+ item_path_s = std::string( item_path );
+ std::string item_type_s;
+ if( item_type )
+ item_type_s = std::string( item_type );
+ std::string item_hash_s;
+ if( item_hash )
+ item_hash_s = std::string( item_hash );
+
+ items.push_back(Item("", item_path_s, item_type_s, item_hash_s, JobXMLParser::Item()));
+
+ eItem = eItem->NextSiblingElement("Item");
+ }
+
+ results.push_back( ManifestResult( dest_hash_s, items, dest_s, true, builder_s, builder_version_s, ManifestResult::DATABASE, type_s ) );
+ }
+ else if(strcmp(eResult->Value(), "GeneratorResult") == 0 )
+ {
+ const char* dest = eResult->Attribute("dest");
+ const char* dest_hash = eResult->Attribute("dest_hash");
+ const char* generator = eResult->Attribute("generator");
+ const char* generator_version = eResult->Attribute("generator_version");
+ const char* type = eResult->Attribute("type");
+ bool success = cr.saysTrue(eResult->Attribute("success"));
+
+ std::string dest_s;
+ if( dest )
+ dest_s = std::string( dest );
+ std::string dest_hash_s;
+ if( dest_hash )
+ dest_hash_s = std::string( dest_hash );
+ std::string generator_s;
+ if( generator )
+ generator_s = std::string( generator );
+ std::string generator_version_s;
+ if( generator_version )
+ generator_version_s = std::string(generator_version);
+ std::string type_s;
+ if( type )
+ type_s = std::string(type);
+
+ results.push_back( ManifestResult( dest_hash_s, dest_s, success, generator_s, generator_version_s, ManifestResult::GENERATED, type_s ) );
+
+ }
+ else
+ {
+ LOGE << "Unknown element name: " << eResult->Value() << std::endl;
+ }
+ }
+ else
+ {
+ LOGE << "Malformed element in manifest" << std::endl;
+ }
+
+ nResult = nResult->NextSibling();
+ }
+ }
+ else
+ {
+ LOGE << "Problem loading manifest file" << manifest << std::endl;
+ return false;
+ }
+ }
+ else
+ {
+ LOGE << "Error parsing manifest file: \"" << doc.ErrorDesc() << "\"" << std::endl;
+ return false;
+ }
+
+ std::vector<ManifestResult>::const_iterator mrit;
+ for( mrit = results.begin(); mrit != results.end(); mrit++ )
+ {
+ std::vector<Item>::const_iterator itemit;
+ for( itemit = mrit->items.begin(); itemit != mrit->items.end(); itemit++ )
+ {
+ hash_set.insert(itemit->GetSubHash());
+ }
+ }
+
+ return true;
+}
+
+bool Manifest::Save(const std::string& manifest)
+{
+ TiXmlDocument doc;
+ TiXmlDeclaration * decl = new TiXmlDeclaration( "2.0", "", "" );
+ TiXmlElement * root = new TiXmlElement( "Manifest" );
+
+ TiXmlElement* builder_info = new TiXmlElement( "ProgramInfo" );
+ builder_info->SetAttribute("name", "Ogda");
+ builder_info->SetAttribute("build_version", GetBuildVersion());
+ builder_info->SetAttribute("build_date", GetBuildTimestamp());
+ builder_info->SetAttribute("platform", GetPlatform());
+ builder_info->SetAttribute("arch", GetArch());
+ root->LinkEndChild(builder_info);
+
+ TiXmlElement* execution_info = new TiXmlElement( "ExecutionInfo" );
+ //Add timestamp when it was run in here.
+ //Add how long it took here.
+ root->LinkEndChild(execution_info);
+
+ std::vector<ManifestResult>::const_iterator mrit;
+
+ for( mrit = results.begin(); mrit != results.end(); mrit++ )
+ {
+ if( mrit->mr_type == ManifestResult::BUILT )
+ {
+ TiXmlElement* eResult = new TiXmlElement( "BuilderResult" );
+
+ std::vector<Item>::const_iterator itemit;
+
+ for( itemit = mrit->items.begin(); itemit != mrit->items.end(); itemit++ )
+ {
+ TiXmlElement* eItem = new TiXmlElement( "Item" );
+
+ eItem->SetAttribute( "path", itemit->GetPath().c_str() );
+ eItem->SetAttribute( "type", itemit->type.c_str() );
+ eItem->SetAttribute( "hash", itemit->hash.c_str() );
+
+ eResult->LinkEndChild( eItem );
+ }
+
+ eResult->SetAttribute( "dest", mrit->dest.c_str() );
+ eResult->SetAttribute( "dest_hash", mrit->dest_hash.c_str() );
+ eResult->SetAttribute( "builder", mrit->name.c_str() );
+ eResult->SetAttribute( "builder_version", mrit->version.c_str() );
+ eResult->SetAttribute( "type", mrit->type.c_str() );
+ eResult->SetAttribute( "success", mrit->success ? "true" : "false" );
+ eResult->SetAttribute( "fresh_built", mrit->fresh_built ? "true" : "false" );
+
+ root->LinkEndChild( eResult );
+ }
+ else if( mrit->mr_type == ManifestResult::DATABASE )
+ {
+ TiXmlElement* eResult = new TiXmlElement( "DatabaseResult" );
+
+ std::vector<Item>::const_iterator itemit;
+
+ for( itemit = mrit->items.begin(); itemit != mrit->items.end(); itemit++ )
+ {
+ TiXmlElement* eItem = new TiXmlElement( "Item" );
+
+ eItem->SetAttribute( "path", itemit->GetPath().c_str() );
+ eItem->SetAttribute( "type", itemit->type.c_str() );
+ eItem->SetAttribute( "hash", itemit->hash.c_str() );
+
+ eResult->LinkEndChild( eItem );
+ }
+
+ eResult->SetAttribute( "dest", mrit->dest.c_str() );
+ eResult->SetAttribute( "dest_hash", mrit->dest_hash.c_str() );
+ eResult->SetAttribute( "builder", mrit->name.c_str() );
+ eResult->SetAttribute( "builder_version", mrit->version.c_str() );
+ eResult->SetAttribute( "type", mrit->type.c_str() );
+
+ root->LinkEndChild( eResult );
+ }
+ else if( mrit->mr_type == ManifestResult::GENERATED )
+ {
+ TiXmlElement* eResult = new TiXmlElement( "GeneratorResult" );
+
+ eResult->SetAttribute( "dest", mrit->dest.c_str() );
+ eResult->SetAttribute( "dest_hash", mrit->dest_hash.c_str() );
+ eResult->SetAttribute( "generator", mrit->name.c_str() );
+ eResult->SetAttribute( "generator_version", mrit->version.c_str() );
+ eResult->SetAttribute( "typ", mrit->type.c_str() );
+ eResult->SetAttribute( "success", mrit->success ? "true" : "false" );
+ eResult->SetAttribute( "fresh_built", mrit->fresh_built ? "true" : "false" );
+
+ root->LinkEndChild( eResult );
+ }
+ else
+ {
+ LOGE << "Unknown ManifestResult type" << std::endl;
+ }
+ }
+
+ doc.LinkEndChild( decl );
+ doc.LinkEndChild( root );
+ doc.SaveFile( manifest.c_str() );
+
+ return !doc.Error();
+}
+
+void Manifest::AddResult( const ManifestResult& a ) {
+ results.push_back(a);
+ std::vector<Item>::const_iterator itemit;
+ for( itemit = a.items.begin(); itemit != a.items.end(); itemit++ ) {
+ hash_set.insert(itemit->GetSubHash());
+ }
+}
+
+bool Manifest::HasError()
+{
+ std::vector<ManifestResult>::const_iterator mrit;
+ for( mrit = results.begin(); mrit != results.end(); mrit++ )
+ {
+ if( mrit->success == false )
+ return true;
+ }
+ return false;
+}
+
+std::vector<std::string> Manifest::GetDestinationFiles()
+{
+ std::vector<std::string> values;
+
+ std::vector<ManifestResult>::const_iterator mrit;
+ for( mrit = results.begin(); mrit != results.end(); mrit++ )
+ {
+ values.push_back( mrit->dest );
+ }
+
+ return values;
+}
+
+bool Manifest::IsUpToDate( JobHandler& jh, const Item& item, const Builder& builder )
+{
+ std::vector<ManifestResult>::iterator mrit;
+ if( hash_set.find(item.GetSubHash()) != hash_set.end() )
+ {
+ for( mrit = results.begin(); mrit != results.end(); mrit++ )
+ {
+ if( mrit->items.size() == 1 )
+ {
+ //First, check that it's the same item.
+ //This "magically" includes the hash as item contains the hash value
+ if( mrit->items[0] == item )
+ {
+ //Check that it's the same builder, one builder, one type of possible output file
+ if( mrit->name == builder.GetBuilderName() )
+ {
+ //Check the builder version, to verify that the expected output wouldn't change.
+ if( mrit->version == builder.GetBuilderVersion() )
+ {
+ //Check if the claimed destination file from the manifest differs from the file on disk according to hash
+ //and that the hash isn't invalid
+ if( mrit->GetCurrentDestHash(jh.output_folder) == mrit->dest_hash && mrit->dest_hash != std::string("") )
+ {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ else if( mrit->items.size() > 1 )
+ {
+ LOGW << "System is not currently built to handle matching ManifestResults with more than one item" << std::endl;
+ }
+ }
+ }
+ return false;
+}
+
+ManifestResult Manifest::GetPreviouslyBuiltResult(const Item& item, const Builder& builder)
+{
+ std::vector<ManifestResult>::const_iterator mrit;
+ for( mrit = results.begin(); mrit != results.end(); mrit++ )
+ {
+ if( mrit->items.size() == 1 )
+ {
+ //First, check that it's the same item.
+ //This "magically" includes the hash as item contains the hash value
+ if( mrit->items[0] == item )
+ {
+ //Check that it's the same builder, one builder, one type of possible output file
+ if( mrit->name == builder.GetBuilderName() )
+ {
+ //This is mostly for sanity, but should never really evaluate to false as we already found the right builder and there is only one per name.
+ //If this was false, that would mean we have changed the version of the builder and the asset we're looking for is actually outdated.
+ //Meaning that previous checks and evalutation should have lead us to the conclusion to rebuild this item.
+ if( mrit->version == builder.GetBuilderVersion() )
+ {
+ return *mrit;
+ }
+ }
+ }
+ }
+ else if( mrit->items.size() > 1 )
+ {
+ LOGW << "System is not currently built to handle matching ManifestResults with more than one item" << std::endl;
+ }
+ }
+ throw "Big error";
+}
+
+
+void Manifest::PrecalculateCurrentDestinationHashes( const std::string& dest_path )
+{
+ ManifestThreadPool mtp(thread);
+
+ mtp.RunHashCalculation(dest_path, results);
+}
+
+std::vector<ManifestResult>::const_iterator Manifest::ResultsBegin() const
+{
+ return results.begin();
+}
+
+std::vector<ManifestResult>::const_iterator Manifest::ResultsEnd() const
+{
+ return results.end();
+}
diff --git a/Source/Ogda/manifest.h b/Source/Ogda/manifest.h
new file mode 100644
index 00000000..4b9f747b
--- /dev/null
+++ b/Source/Ogda/manifest.h
@@ -0,0 +1,54 @@
+//-----------------------------------------------------------------------------
+// Name: manifest.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+#include "manifestresult.h"
+
+#include <vector>
+#include <set>
+
+class JobHandler;
+class Builder;
+class Item;
+
+class Manifest
+{
+ int thread;
+ std::vector<ManifestResult> results;
+ std::set<uint64_t> hash_set;
+public:
+ Manifest(int thread);
+ bool Load(const std::string& manifest);
+ bool Save(const std::string& manifest);
+ void AddResult( const ManifestResult& results );
+ bool HasError();
+
+ std::vector<std::string> GetDestinationFiles();
+
+ bool IsUpToDate( JobHandler& jh, const Item& item, const Builder& builder );
+ ManifestResult GetPreviouslyBuiltResult( const Item& item, const Builder& builder);
+
+ void PrecalculateCurrentDestinationHashes( const std::string& dest_path );
+
+ std::vector<ManifestResult>::const_iterator ResultsBegin() const;
+ std::vector<ManifestResult>::const_iterator ResultsEnd() const;
+};
diff --git a/Source/Ogda/manifestresult.cpp b/Source/Ogda/manifestresult.cpp
new file mode 100644
index 00000000..61c6ea7b
--- /dev/null
+++ b/Source/Ogda/manifestresult.cpp
@@ -0,0 +1,84 @@
+//-----------------------------------------------------------------------------
+// Name: manifestresult.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "manifestresult.h"
+
+#include <string>
+
+#include <Internal/filesystem.h>
+#include <Ogda/jobhandler.h>
+#include <Ogda/Builders/actionbase.h>
+#include <Ogda/Generators/creatorbase.h>
+#include <Ogda/ogda_hash.h>
+
+ManifestResult::ManifestResult( const JobHandler& jh, const std::string& dest, bool success, const CreatorBase& creator, const std::string& type )
+: dest(dest), success(success), name(creator.GetName()), version(creator.GetVersion()), fresh_built(true), mr_type(GENERATED), type(type)
+{
+ std::string full_dest_path = AssemblePath(jh.output_folder, dest);
+ dest_hash = OgdaGetFileHash(full_dest_path.c_str());
+}
+
+ManifestResult::ManifestResult( const JobHandler& jh, const Item& item, const std::string& dest, bool success, const ActionBase& action, const std::string& type )
+: dest(dest), success(success), name(action.GetName()), version(action.GetVersion()), fresh_built(true), mr_type(BUILT), type(type)
+{
+ items.push_back(item);
+ std::string full_dest_path = AssemblePath(jh.output_folder, dest);
+ dest_hash = OgdaGetFileHash(full_dest_path.c_str());
+}
+
+ManifestResult::ManifestResult( const std::string& dest_hash, const std::vector<Item>& items, const std::string& dest, bool success, const std::string& name, const std::string& version, const ManifestResultType mr_type, const std::string& type )
+: items(items), dest(dest), success(success), dest_hash(dest_hash), name(name), version(version), fresh_built(false), mr_type(mr_type), type(type)
+{
+}
+
+ManifestResult::ManifestResult( const std::string& dest_hash, Item& item, const std::string& dest, bool success, const std::string& name, const std::string& version, const ManifestResultType mr_type, const std::string& type )
+: dest(dest), success(success), dest_hash(dest_hash), name(name), version(version), fresh_built(false), mr_type(mr_type), type(type)
+{
+ items.push_back(item);
+}
+
+ManifestResult::ManifestResult( const std::string& dest_hash, const std::string& dest, bool success, const std::string& name, const std::string& version, const ManifestResultType mr_type, const std::string& type )
+: dest(dest), success(success), dest_hash(dest_hash), name(name), version(version), fresh_built(false), mr_type(mr_type), type(type)
+{
+}
+
+void ManifestResult::CalculateHash(const char* base_path)
+{
+ current_dest_hash = GetFileHash(AssemblePath(std::string(base_path),dest).c_str()).ToString();
+}
+
+const std::string& ManifestResult::GetCurrentDestHash(const std::string& base_path)
+{
+ if( current_dest_hash.empty() )
+ CalculateHash(base_path.c_str());
+ return current_dest_hash;
+}
+
+std::ostream& operator<<(std::ostream& out, const ManifestResult& mr) {
+
+ out << "ManifestResult( items { ";
+ for( int i = 0; i < mr.items.size(); i++ ) {
+ out << mr.items[i] << ",";
+ }
+ out << "}," << mr.dest << "," << mr.dest_hash << "," << mr.name << "," << mr.version << "," << mr.type << ")";
+ return out;
+}
diff --git a/Source/Ogda/manifestresult.h b/Source/Ogda/manifestresult.h
new file mode 100644
index 00000000..a73ea0cd
--- /dev/null
+++ b/Source/Ogda/manifestresult.h
@@ -0,0 +1,72 @@
+//-----------------------------------------------------------------------------
+// Name: manifestresult.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Utility/hash.h>
+#include "item.h"
+
+class ActionBase;
+class CreatorBase;
+class JobHandler;
+
+class ManifestResult
+{
+private:
+ std::string current_dest_hash;
+
+public:
+ enum ManifestResultType
+ {
+ GENERATED,
+ BUILT,
+ DATABASE
+ } mr_type;
+
+ ManifestResult( const JobHandler& jh, const std::string& dest, bool success, const CreatorBase& creator, const std::string& type );
+ ManifestResult( const JobHandler& jh, const Item& item, const std::string& dest, bool success, const ActionBase& action, const std::string& type );
+
+ ManifestResult( const std::string& dest_hash, const std::vector<Item>& items, const std::string& dest, bool success, const std::string& builder, const std::string& builder_version, const ManifestResultType mr_type, const std::string& type );
+ ManifestResult( const std::string& dest_hash, Item& item, const std::string& dest, bool success, const std::string& name, const std::string& version, const ManifestResultType mr_type, const std::string& type );
+ ManifestResult( const std::string& dest_hash, const std::string& dest, bool success, const std::string& builder, const std::string& builder_version, const ManifestResultType mr_type, const std::string& type );
+
+ void CalculateHash(const char* base_path);
+ const std::string& GetCurrentDestHash(const std::string& base_path);
+
+ std::string dest;
+ std::string dest_hash;
+
+ std::string name; //Either creator or action
+ std::string version; //Either creator or action
+ std::string type;
+
+ //Only in built data does this come in as relevant.
+ //And even then, we don't have support for more than one source at this time.
+ std::vector<Item> items;
+
+ bool success;
+ bool fresh_built;
+
+ friend std::ostream& operator<<(std::ostream& out, const ManifestResult& item);
+};
+
+std::ostream& operator<<(std::ostream& out, const ManifestResult& item);
diff --git a/Source/Ogda/manifestthreadpool.cpp b/Source/Ogda/manifestthreadpool.cpp
new file mode 100644
index 00000000..c89d8e3d
--- /dev/null
+++ b/Source/Ogda/manifestthreadpool.cpp
@@ -0,0 +1,121 @@
+//-----------------------------------------------------------------------------
+// Name: manifestthreadpool.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <Memory/allocation.h>
+#include "manifestthreadpool.h"
+
+#include <Logging/logdata.h>
+#include <cstdlib>
+#include <pthread.h>
+#include <iostream>
+#include "main.h"
+
+#ifdef PLATFORM_LINUX
+#include <unistd.h>
+#endif
+
+struct ManifestResultJob
+{
+ const char* base_path;
+ ManifestResult* item;
+ int step;
+ int count;
+ volatile int performed;
+};
+
+static void* CalculateManifestResultHash( void* item )
+{
+ ManifestResultJob* ij = static_cast<ManifestResultJob*>(item);
+ ij->performed = 0;
+ for( int i = 0; i < ij->count; i++ )
+ {
+ ij->item[i*ij->step].CalculateHash(ij->base_path);
+ ij->performed++;
+ }
+ return 0;
+}
+
+ManifestThreadPool::ManifestThreadPool( int thread_count ) : thread_count(thread_count)
+{
+ threads = static_cast<pthread_t*>(OG_MALLOC( sizeof(pthread_t) * thread_count ));
+}
+
+ManifestThreadPool::~ManifestThreadPool()
+{
+ OG_FREE(threads);
+}
+
+void ManifestThreadPool::RunHashCalculation(const std::string& base_path, std::vector<ManifestResult>& arr)
+{
+ ManifestResultJob* itemjobs = static_cast<ManifestResultJob*>(OG_MALLOC(sizeof(ManifestResultJob)*thread_count));
+
+ int step = arr.size()/thread_count;
+
+ for( int i = 0; i < thread_count; i++ )
+ {
+ itemjobs[i].item = &arr[i];
+ itemjobs[i].step = thread_count;
+ itemjobs[i].count = step;
+ itemjobs[i].base_path = base_path.c_str();
+
+ int err = pthread_create( &threads[i], NULL, CalculateManifestResultHash, &itemjobs[i]);
+
+ if( err != 0 )
+ {
+ LOGE << "Thread create failed" << std::endl;
+ }
+ }
+
+ //Do the remaining items in our main thread.
+ for( int i = step*thread_count; i < arr.size(); i++ )
+ {
+ arr[i].CalculateHash(base_path.c_str());
+ }
+
+ int count = 0;
+ while( count < arr.size() )
+ {
+#ifdef PLATFORM_LINUX
+ sleep(1);
+#endif
+ std::stringstream ss;
+ count = arr.size()-step*thread_count;
+ for( int i = 0; i < thread_count; i++ )
+ {
+ count += itemjobs[i].performed;
+ ss << ((itemjobs[i].performed == itemjobs[i].count) ? "d" : ".");
+ }
+ SetPercent(ss.str().c_str(), (int)(100.0f*((float)count/(float)arr.size())));
+ }
+
+ for( int i = 0; i < thread_count; i++ )
+ {
+ void* retval;
+ int err = pthread_join( threads[i], &retval );
+
+ if( err != 0 )
+ {
+ LOGE << "Thread join failed" << std::endl;
+ }
+ }
+ OG_FREE( itemjobs );
+}
diff --git a/Source/Ogda/manifestthreadpool.h b/Source/Ogda/manifestthreadpool.h
new file mode 100644
index 00000000..ff8da984
--- /dev/null
+++ b/Source/Ogda/manifestthreadpool.h
@@ -0,0 +1,40 @@
+//-----------------------------------------------------------------------------
+// Name: manifestthreadpool.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <pthread.h>
+
+#include <vector>
+#include "manifestresult.h"
+
+class ManifestThreadPool
+{
+private:
+ pthread_t* threads;
+ int thread_count;
+public:
+ ManifestThreadPool(int thread_count);
+ ~ManifestThreadPool();
+
+ void RunHashCalculation(const std::string& base_path, std::vector<ManifestResult>& arr);
+};
diff --git a/Source/Ogda/ogda_config.cpp b/Source/Ogda/ogda_config.cpp
new file mode 100644
index 00000000..6babd730
--- /dev/null
+++ b/Source/Ogda/ogda_config.cpp
@@ -0,0 +1,33 @@
+//-----------------------------------------------------------------------------
+// Name: ogda_config.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "ogda_config.h"
+
+bool config_mute_missing;
+bool config_hide_progress;
+bool config_remove_unlisted_files;
+bool config_use_date_modified_as_hash;
+bool config_print_duplicates;
+bool config_print_missing;
+bool config_print_item_list;
+bool config_load_from_database;
+bool config_save_to_database;
diff --git a/Source/Ogda/ogda_config.h b/Source/Ogda/ogda_config.h
new file mode 100644
index 00000000..0185e7dd
--- /dev/null
+++ b/Source/Ogda/ogda_config.h
@@ -0,0 +1,33 @@
+//-----------------------------------------------------------------------------
+// Name: ogda_config.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+extern bool config_mute_missing;
+extern bool config_hide_progress;
+extern bool config_remove_unlisted_files;
+extern bool config_print_missing;
+extern bool config_use_date_modified_as_hash;
+extern bool config_print_duplicates;
+extern bool config_print_item_list;
+extern bool config_load_from_database;
+extern bool config_save_to_database;
diff --git a/Source/Ogda/ogda_hash.cpp b/Source/Ogda/ogda_hash.cpp
new file mode 100644
index 00000000..ef045ee3
--- /dev/null
+++ b/Source/Ogda/ogda_hash.cpp
@@ -0,0 +1,44 @@
+//-----------------------------------------------------------------------------
+// Name: ogda_hash.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "ogda_config.h"
+#include <iostream>
+
+#include <Utility/hash.h>
+#include <Internal/filesystem.h>
+#include <Logging/logdata.h>
+#include <Internal/datemodified.h>
+#include <Ogda/ogda_config.h>
+
+std::string OgdaGetFileHash(std::string path)
+{
+ if( config_use_date_modified_as_hash )
+ {
+ std::stringstream ss;
+ ss << GetDateModifiedInt64(path.c_str());
+ return ss.str();
+ }
+ else
+ {
+ return GetFileHash(path.c_str()).ToString();
+ }
+}
diff --git a/Source/Ogda/ogda_hash.h b/Source/Ogda/ogda_hash.h
new file mode 100644
index 00000000..c88f329d
--- /dev/null
+++ b/Source/Ogda/ogda_hash.h
@@ -0,0 +1,25 @@
+//-----------------------------------------------------------------------------
+// Name: ogda_hash.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <string>
+
+std::string OgdaGetFileHash(std::string path);
diff --git a/Source/Ogda/readerbase.h b/Source/Ogda/readerbase.h
new file mode 100644
index 00000000..20f6514e
--- /dev/null
+++ b/Source/Ogda/readerbase.h
@@ -0,0 +1,28 @@
+//-----------------------------------------------------------------------------
+// Name: readerbase.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+class ReaderBase
+{
+
+};
diff --git a/Source/Online/Message/angelscript_data.cpp b/Source/Online/Message/angelscript_data.cpp
new file mode 100644
index 00000000..d23b8954
--- /dev/null
+++ b/Source/Online/Message/angelscript_data.cpp
@@ -0,0 +1,88 @@
+//-----------------------------------------------------------------------------
+// Name: angelscript_data.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "angelscript_data.h"
+#include <Online/online.h>
+
+namespace OnlineMessages {
+ AngelscriptData::AngelscriptData() :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ state(0), data(), is_persistent_sync(false)
+ {
+
+ }
+
+ AngelscriptData::AngelscriptData(uint32_t state, vector<char> data, bool is_persistent_sync) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ state(state), data(data), is_persistent_sync(is_persistent_sync)
+ {
+
+ }
+
+ binn* AngelscriptData::Serialize(void* object) {
+ AngelscriptData* ad = static_cast<AngelscriptData*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_uint32(l, "s", ad->state);
+ binn_object_set_blob(l, "b", ad->data.data(), ad->data.size());
+ binn_object_set_bool(l, "sync", ad->is_persistent_sync);
+
+ return l;
+ }
+
+ void AngelscriptData::Deserialize(void* object, binn* l) {
+ AngelscriptData* ad = static_cast<AngelscriptData*>(object);
+ binn_object_get_uint32(l, "s", &ad->state);
+ void* data_ptr;
+ int data_size;
+ binn_object_get_blob(l, "b", &data_ptr, &data_size);
+ ad->data.resize(data_size);
+ memcpy(ad->data.data(), data_ptr, data_size);
+ BOOL is_persistent_sync_binn;
+ binn_object_get_bool(l, "sync", &is_persistent_sync_binn);
+ ad->is_persistent_sync = is_persistent_sync_binn;
+ }
+
+ void AngelscriptData::Execute(const OnlineMessageRef& ref, void* object, PeerID peer) {
+ AngelscriptData* ad = static_cast<AngelscriptData*>(object);
+
+ AngelScriptUpdate asu;
+
+ asu.data = ad->data;
+ asu.state = ad->state;
+
+ if(ad->is_persistent_sync) {
+ Online::Instance()->online_session->peer_queued_sync_updates.push(asu);
+ } else {
+ Online::Instance()->online_session->peer_queued_level_updates.push(asu);
+ }
+ }
+
+ void* AngelscriptData::Construct(void* mem) {
+ return new(mem) AngelscriptData();
+ }
+
+ void AngelscriptData::Destroy(void* object) {
+ AngelscriptData* ad = static_cast<AngelscriptData*>(object);
+ ad->~AngelscriptData();
+ }
+}
diff --git a/Source/Online/Message/angelscript_data.h b/Source/Online/Message/angelscript_data.h
new file mode 100644
index 00000000..515c712e
--- /dev/null
+++ b/Source/Online/Message/angelscript_data.h
@@ -0,0 +1,51 @@
+//-----------------------------------------------------------------------------
+// Name: angelscript_data.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+#include "online_message_base.h"
+
+#include <Online/online_datastructures.h>
+
+#include <vector>
+
+using std::vector;
+
+namespace OnlineMessages {
+ class AngelscriptData : public OnlineMessageBase {
+ public:
+ uint32_t state;
+ vector<char> data;
+ bool is_persistent_sync;
+
+ public:
+
+ AngelscriptData();
+ AngelscriptData(uint32_t state, vector<char> data, bool is_persistent_sync);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* l);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID peer);
+
+ static void* Construct(void *mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/angelscript_object_data.cpp b/Source/Online/Message/angelscript_object_data.cpp
new file mode 100644
index 00000000..94baf31e
--- /dev/null
+++ b/Source/Online/Message/angelscript_object_data.cpp
@@ -0,0 +1,89 @@
+//-----------------------------------------------------------------------------
+// Name: angelscript_object_data.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "angelscript_object_data.h"
+
+#include <Online/online.h>
+#include <Main/engine.h>
+
+namespace OnlineMessages {
+ AngelscriptObjectData::AngelscriptObjectData() :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ object_id(-1), state(0), data()
+ {
+
+ }
+
+ AngelscriptObjectData::AngelscriptObjectData(ObjectID object_id, uint32_t state, vector<uint32_t> data) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ state(state), data(data)
+ {
+ this->object_id = Online::Instance()->GetOriginalID(object_id);
+ }
+
+ binn* AngelscriptObjectData::Serialize(void* object) {
+ AngelscriptObjectData* ad = static_cast<AngelscriptObjectData*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_int32(l, "id", ad->object_id);
+ binn_object_set_uint32(l, "s", ad->state);
+ binn_object_set_blob(l, "b", ad->data.data(), ad->data.size() * sizeof(uint32_t));
+
+ return l;
+ }
+
+ void AngelscriptObjectData::Deserialize(void* object, binn* l) {
+ AngelscriptObjectData* ad = static_cast<AngelscriptObjectData*>(object);
+
+ binn_object_get_int32(l, "id", &ad->object_id);
+ binn_object_get_uint32(l, "s", &ad->state);
+ void* data_ptr;
+ int data_size;
+ binn_object_get_blob(l, "b", &data_ptr, &data_size);
+ ad->data.resize(data_size/sizeof(uint32_t));
+ memcpy(ad->data.data(), data_ptr, data_size);
+ }
+
+ void AngelscriptObjectData::Execute(const OnlineMessageRef& ref, void* object, PeerID peer) {
+ AngelscriptObjectData* ad = static_cast<AngelscriptObjectData*>(object);
+ ObjectID object_id = Online::Instance()->GetObjectID(ad->object_id);
+
+ SceneGraph* sg = Engine::Instance()->GetSceneGraph();
+
+ if(sg != nullptr) {
+ MovementObject* mo = static_cast<MovementObject*>(sg->GetObjectFromID(object_id));
+
+ if(mo != nullptr) {
+ mo->addAngelScriptUpdate(ad->state, ad->data);
+ }
+ }
+ }
+
+ void* AngelscriptObjectData::Construct(void* mem) {
+ return new(mem) AngelscriptObjectData();
+ }
+
+ void AngelscriptObjectData::Destroy(void* object) {
+ AngelscriptObjectData* ad = static_cast<AngelscriptObjectData*>(object);
+ ad->~AngelscriptObjectData();
+ }
+}
diff --git a/Source/Online/Message/angelscript_object_data.h b/Source/Online/Message/angelscript_object_data.h
new file mode 100644
index 00000000..1e3fb4d0
--- /dev/null
+++ b/Source/Online/Message/angelscript_object_data.h
@@ -0,0 +1,50 @@
+//-----------------------------------------------------------------------------
+// Name: angelscript_object_data.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+#include "online_message_base.h"
+
+#include <Online/online_datastructures.h>
+
+#include <vector>
+
+using std::vector;
+
+namespace OnlineMessages {
+ class AngelscriptObjectData : public OnlineMessageBase {
+ public:
+ CommonObjectID object_id;
+ uint32_t state;
+ vector<uint32_t> data;
+
+ public:
+ AngelscriptObjectData();
+ AngelscriptObjectData(ObjectID object, uint32_t state, vector<uint32_t> data);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* l);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID peer);
+
+ static void* Construct(void *mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/attach_to_message.cpp b/Source/Online/Message/attach_to_message.cpp
new file mode 100644
index 00000000..fb2096d8
--- /dev/null
+++ b/Source/Online/Message/attach_to_message.cpp
@@ -0,0 +1,124 @@
+//-----------------------------------------------------------------------------
+// Name: attach_to_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "attach_to_message.h"
+
+#include <Main/engine.h>
+#include <Online/online.h>
+#include <Utility/binn_util.h>
+
+namespace OnlineMessages {
+ AttachToMessage::AttachToMessage(ObjectID parent_id, ObjectID child_id, uint32_t bone_id, bool attach, bool mirrored) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_PERSISTENT),
+ bone_id(bone_id), attach(attach), mirrored(mirrored) {
+
+ this->parent_id = Online::Instance()->GetOriginalID(parent_id);
+ this->child_id = Online::Instance()->GetOriginalID(child_id);
+ }
+
+ binn* AttachToMessage::Serialize(void* object) {
+ AttachToMessage* t = static_cast<AttachToMessage*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_int32(l, "parent_id", t->parent_id);
+ binn_object_set_int32(l, "child_id", t->child_id);
+ binn_object_set_uint32(l, "bone_id", t->bone_id);
+ binn_object_set_bool(l, "attach", t->attach);
+ binn_object_set_bool(l, "mirrored", t->mirrored);
+
+ return l;
+ }
+
+ void AttachToMessage::Deserialize(void* object, binn* l) {
+ AttachToMessage* t = static_cast<AttachToMessage*>(object);
+
+ binn_object_get_int32(l, "parent_id", &t->parent_id);
+ binn_object_get_int32(l, "child_id", &t->child_id);
+ binn_object_get_uint32(l, "bone_id", &t->bone_id);
+
+ BOOL attach, mirrored;
+ binn_object_get_bool(l, "attach", &attach);
+ binn_object_get_bool(l, "mirrored", &mirrored);
+ t->attach = attach;
+ t->mirrored = mirrored;
+ }
+
+ void AttachToMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ AttachToMessage* t = static_cast<AttachToMessage*>(object);
+ ObjectID parent_object_id = Online::Instance()->GetObjectID(t->parent_id);
+ ObjectID child_object_id = Online::Instance()->GetObjectID(t->child_id);
+
+ SceneGraph* graph = Engine::Instance()->GetSceneGraph();
+
+ // We need to make sure that we actually know what objects
+ // the sender is referencing
+ Object * parent = graph->GetObjectFromID(parent_object_id);
+ Object * child = graph->GetObjectFromID(child_object_id);
+
+ if (parent != nullptr && child != nullptr) {
+ if (t->attach) {
+ child->SetParent(parent);
+ if (child->GetType() == EntityType::_item_object) {
+ ItemObject* item_object = (ItemObject*)child;
+ MovementObject* movement_object = (MovementObject*)parent;
+ AttachmentSlotList attachment_slots;
+ movement_object->rigged_object()->AvailableItemSlots(item_object->item_ref(), &attachment_slots);
+ for (AttachmentSlotList::iterator iterator = attachment_slots.begin(); iterator != attachment_slots.end(); ++iterator) {
+ AttachmentSlot& slot = (*iterator);
+ if (slot.type == (AttachmentType)t->bone_id && slot.mirrored == t->mirrored) {
+ movement_object->AttachItemToSlotEditor(item_object->GetID(), slot.type, slot.mirrored, slot.attachment_ref, true);
+ break;
+ }
+ }
+
+ } else if (parent->GetType() == EntityType::_movement_object) {
+ MovementObject * mov = (MovementObject *)parent;
+ RiggedObject * rigged = mov->rigged_object();
+ // this is copied and pasted from the "original code" -rewrite so that it does not default construct inside the vector
+ rigged->children.resize(rigged->children.size() + 1);
+
+ // dummy data
+ AttachedEnvObject &attached_env_object = rigged->children.back();
+ attached_env_object.bone_connection_dirty = true;
+ attached_env_object.direct_ptr = child;
+ attached_env_object.bone_connects[0].bone_id = t->bone_id;
+ attached_env_object.bone_connects[0].num_connections = 1;
+ }
+ } else {
+ if (child->GetType() == EntityType::_item_object) {
+ parent->Disconnect(*child, true, false);
+ } else {
+ parent->ChildLost(child);
+ }
+ }
+ }
+ }
+
+ void* AttachToMessage::Construct(void *mem) {
+ return new(mem) AttachToMessage(0, 0, 0, false, false);
+ }
+
+ void AttachToMessage::Destroy(void* object) {
+ AttachToMessage* t = static_cast<AttachToMessage*>(object);
+ t->~AttachToMessage();
+ }
+}
diff --git a/Source/Online/Message/attach_to_message.h b/Source/Online/Message/attach_to_message.h
new file mode 100644
index 00000000..0073b6bf
--- /dev/null
+++ b/Source/Online/Message/attach_to_message.h
@@ -0,0 +1,45 @@
+//-----------------------------------------------------------------------------
+// Name: attach_to_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class AttachToMessage : public OnlineMessageBase {
+ public:
+ CommonObjectID parent_id;
+ CommonObjectID child_id;
+ uint32_t bone_id;
+ bool attach;
+ bool mirrored;
+
+ public:
+ AttachToMessage(ObjectID parent_id, ObjectID child_id, uint32_t bone_id, bool attach, bool mirrored);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/audio_play_group_priority_message.cpp b/Source/Online/Message/audio_play_group_priority_message.cpp
new file mode 100644
index 00000000..e84fbb30
--- /dev/null
+++ b/Source/Online/Message/audio_play_group_priority_message.cpp
@@ -0,0 +1,73 @@
+//-----------------------------------------------------------------------------
+// Name: audio_play_group_priority_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "audio_play_group_priority_message.h"
+
+#include <Main/engine.h>
+#include <Utility/binn_util.h>
+
+namespace OnlineMessages {
+ AudioPlayGroupPriorityMessage::AudioPlayGroupPriorityMessage(const std::string & path, vec3 pos, int priority) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ path(path), pos(pos), priority(priority)
+ {
+
+ }
+
+ binn* AudioPlayGroupPriorityMessage::Serialize(void* object) {
+ AudioPlayGroupPriorityMessage* t = static_cast<AudioPlayGroupPriorityMessage*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_std_string(l, "path", t->path);
+ binn_object_set_vec3(l, "pos", t->pos);
+ binn_object_set_int32(l, "priority", t->priority);
+
+ return l;
+ }
+
+ void AudioPlayGroupPriorityMessage::Deserialize(void* object, binn* l) {
+ AudioPlayGroupPriorityMessage* t = static_cast<AudioPlayGroupPriorityMessage*>(object);
+
+ binn_object_get_std_string(l, "path", &t->path);
+ binn_object_get_vec3(l, "pos", &t->pos);
+ binn_object_get_int32(l, "priority", &t->priority);
+ }
+
+ void AudioPlayGroupPriorityMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ AudioPlayGroupPriorityMessage* t = static_cast<AudioPlayGroupPriorityMessage*>(object);
+
+ SoundGroupRef sgr = Engine::Instance()->GetAssetManager()->LoadSync<SoundGroup>(t->path);
+ SoundGroupPlayInfo sgpi(*sgr, t->pos);
+ sgpi.priority = t->priority;
+ int handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->PlayGroup(handle, sgpi);
+ }
+
+ void* AudioPlayGroupPriorityMessage::Construct(void *mem) {
+ return new(mem) AudioPlayGroupPriorityMessage("", vec3(), 0);
+ }
+
+ void AudioPlayGroupPriorityMessage::Destroy(void* object) {
+ AudioPlayGroupPriorityMessage* t = static_cast<AudioPlayGroupPriorityMessage*>(object);
+ t->~AudioPlayGroupPriorityMessage();
+ }
+}
diff --git a/Source/Online/Message/audio_play_group_priority_message.h b/Source/Online/Message/audio_play_group_priority_message.h
new file mode 100644
index 00000000..5f6edaa6
--- /dev/null
+++ b/Source/Online/Message/audio_play_group_priority_message.h
@@ -0,0 +1,43 @@
+//-----------------------------------------------------------------------------
+// Name: audio_play_group_priority_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class AudioPlayGroupPriorityMessage : public OnlineMessageBase {
+ private:
+ std::string path;
+ vec3 pos;
+ int32_t priority;
+
+ public:
+ AudioPlayGroupPriorityMessage(const std::string& path, vec3 pos, int priority);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/audio_play_sound_group_gain_message.cpp b/Source/Online/Message/audio_play_sound_group_gain_message.cpp
new file mode 100644
index 00000000..e2aa88f5
--- /dev/null
+++ b/Source/Online/Message/audio_play_sound_group_gain_message.cpp
@@ -0,0 +1,73 @@
+//-----------------------------------------------------------------------------
+// Name: audio_play_group_gain_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "audio_play_sound_group_gain_message.h"
+
+#include <Main/engine.h>
+#include <Utility/binn_util.h>
+
+namespace OnlineMessages {
+ AudioPlaySoundGroupGainMessage::AudioPlaySoundGroupGainMessage(const std::string& path, vec3 pos, float gain) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ path(path), pos(pos), gain(gain)
+ {
+
+ }
+
+ binn* AudioPlaySoundGroupGainMessage::Serialize(void* object) {
+ AudioPlaySoundGroupGainMessage* t = static_cast<AudioPlaySoundGroupGainMessage*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_std_string(l, "path", t->path);
+ binn_object_set_vec3(l, "pos", t->pos);
+ binn_object_set_float(l, "gain", t->gain);
+
+ return l;
+ }
+
+ void AudioPlaySoundGroupGainMessage::Deserialize(void* object, binn* l) {
+ AudioPlaySoundGroupGainMessage* t = static_cast<AudioPlaySoundGroupGainMessage*>(object);
+
+ binn_object_get_std_string(l, "path", &t->path);
+ binn_object_get_vec3(l, "pos", &t->pos);
+ binn_object_get_float(l, "gain", &t->gain);
+ }
+
+ void AudioPlaySoundGroupGainMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ AudioPlaySoundGroupGainMessage* t = static_cast<AudioPlaySoundGroupGainMessage*>(object);
+
+ SoundGroupRef sgr = Engine::Instance()->GetAssetManager()->LoadSync<SoundGroup>(t->path);
+ SoundGroupPlayInfo sgpi(*sgr, t->pos);
+ sgpi.gain = t->gain;
+ int handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->PlayGroup(handle, sgpi);
+ }
+
+ void* AudioPlaySoundGroupGainMessage::Construct(void *mem) {
+ return new(mem) AudioPlaySoundGroupGainMessage("", vec3(), 0.0f);
+ }
+
+ void AudioPlaySoundGroupGainMessage::Destroy(void* object) {
+ AudioPlaySoundGroupGainMessage* t = static_cast<AudioPlaySoundGroupGainMessage*>(object);
+ t->~AudioPlaySoundGroupGainMessage();
+ }
+}
diff --git a/Source/Online/Message/audio_play_sound_group_gain_message.h b/Source/Online/Message/audio_play_sound_group_gain_message.h
new file mode 100644
index 00000000..db75b260
--- /dev/null
+++ b/Source/Online/Message/audio_play_sound_group_gain_message.h
@@ -0,0 +1,43 @@
+//-----------------------------------------------------------------------------
+// Name: audio_play_group_gain_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class AudioPlaySoundGroupGainMessage : public OnlineMessageBase {
+ private:
+ std::string path;
+ vec3 pos;
+ float gain;
+
+ public:
+ AudioPlaySoundGroupGainMessage(const std::string& path, vec3 pos, float gain);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/audio_play_sound_group_message.cpp b/Source/Online/Message/audio_play_sound_group_message.cpp
new file mode 100644
index 00000000..1dcef587
--- /dev/null
+++ b/Source/Online/Message/audio_play_sound_group_message.cpp
@@ -0,0 +1,69 @@
+//-----------------------------------------------------------------------------
+// Name: audio_play_sound_group_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "audio_play_sound_group_message.h"
+
+#include <Main/engine.h>
+#include <Utility/binn_util.h>
+
+namespace OnlineMessages {
+ AudioPlaySoundGroupMessage::AudioPlaySoundGroupMessage(const std::string& path, vec3 pos) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ path(path), pos(pos) {
+ }
+
+ binn* AudioPlaySoundGroupMessage::Serialize(void* object) {
+ AudioPlaySoundGroupMessage* t = static_cast<AudioPlaySoundGroupMessage*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_std_string(l, "path", t->path);
+ binn_object_set_vec3(l, "pos", t->pos);
+
+ return l;
+ }
+
+ void AudioPlaySoundGroupMessage::Deserialize(void* object, binn* l) {
+ AudioPlaySoundGroupMessage* t = static_cast<AudioPlaySoundGroupMessage*>(object);
+
+ binn_object_get_std_string(l, "path", &t->path);
+ binn_object_get_vec3(l, "pos", &t->pos);
+ }
+
+ void AudioPlaySoundGroupMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ AudioPlaySoundGroupMessage* t = static_cast<AudioPlaySoundGroupMessage*>(object);
+
+ int handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ //SoundGroupRef sgr = SoundGroups::Instance()->ReturnRef(path);
+ SoundGroupRef sgr = Engine::Instance()->GetAssetManager()->LoadSync<SoundGroup>(t->path);
+ SoundGroupPlayInfo sgpi(*sgr, t->pos);
+ Engine::Instance()->GetSound()->PlayGroup(handle, sgpi);
+ }
+
+ void* AudioPlaySoundGroupMessage::Construct(void *mem) {
+ return new(mem) AudioPlaySoundGroupMessage("", vec3());
+ }
+
+ void AudioPlaySoundGroupMessage::Destroy(void* object) {
+ AudioPlaySoundGroupMessage* t = static_cast<AudioPlaySoundGroupMessage*>(object);
+ t->~AudioPlaySoundGroupMessage();
+ }
+}
diff --git a/Source/Online/Message/audio_play_sound_group_message.h b/Source/Online/Message/audio_play_sound_group_message.h
new file mode 100644
index 00000000..46510bea
--- /dev/null
+++ b/Source/Online/Message/audio_play_sound_group_message.h
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------------
+// Name: audio_play_sound_group_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class AudioPlaySoundGroupMessage : public OnlineMessageBase {
+ private:
+ std::string path;
+ vec3 pos;
+
+ public:
+ AudioPlaySoundGroupMessage(const std::string& path, vec3 pos);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/audio_play_sound_group_relative_gain_message.cpp b/Source/Online/Message/audio_play_sound_group_relative_gain_message.cpp
new file mode 100644
index 00000000..d640e558
--- /dev/null
+++ b/Source/Online/Message/audio_play_sound_group_relative_gain_message.cpp
@@ -0,0 +1,73 @@
+//-----------------------------------------------------------------------------
+// Name: audio_play_sound_group_relative_gain_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "audio_play_sound_group_relative_gain_message.h"
+
+#include <Main/engine.h>
+#include <Utility/binn_util.h>
+
+namespace OnlineMessages {
+ AudioPlaySoundGroupRelativeGainMessage::AudioPlaySoundGroupRelativeGainMessage(const std::string& path, float gain) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ path(path), gain(gain)
+ {
+
+ }
+
+ binn* AudioPlaySoundGroupRelativeGainMessage::Serialize(void* object) {
+ AudioPlaySoundGroupRelativeGainMessage* t = static_cast<AudioPlaySoundGroupRelativeGainMessage*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_std_string(l, "path", t->path);
+ binn_object_set_float(l, "gain", t->gain);
+
+ return l;
+ }
+
+ void AudioPlaySoundGroupRelativeGainMessage::Deserialize(void* object, binn* l) {
+ AudioPlaySoundGroupRelativeGainMessage* t = static_cast<AudioPlaySoundGroupRelativeGainMessage*>(object);
+
+ binn_object_get_std_string(l, "path", &t->path);
+ binn_object_get_float(l, "gain", &t->gain);
+ }
+
+ void AudioPlaySoundGroupRelativeGainMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ AudioPlaySoundGroupRelativeGainMessage* t = static_cast<AudioPlaySoundGroupRelativeGainMessage*>(object);
+
+ SoundGroupRef sgr = Engine::Instance()->GetAssetManager()->LoadSync<SoundGroup>(t->path);
+ SoundGroupPlayInfo sgpi(SoundGroupPlayInfo(*sgr, vec3(0.0f)));
+ sgpi.flags = sgpi.flags | SoundFlags::kRelative;
+ sgpi.gain = t->gain;
+
+ int handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->PlayGroup(handle, sgpi);
+ }
+
+ void* AudioPlaySoundGroupRelativeGainMessage::Construct(void *mem) {
+ return new(mem) AudioPlaySoundGroupRelativeGainMessage("", 0.0f);
+ }
+
+ void AudioPlaySoundGroupRelativeGainMessage::Destroy(void* object) {
+ AudioPlaySoundGroupRelativeGainMessage* t = static_cast<AudioPlaySoundGroupRelativeGainMessage*>(object);
+ t->~AudioPlaySoundGroupRelativeGainMessage();
+ }
+}
diff --git a/Source/Online/Message/audio_play_sound_group_relative_gain_message.h b/Source/Online/Message/audio_play_sound_group_relative_gain_message.h
new file mode 100644
index 00000000..00eb5c24
--- /dev/null
+++ b/Source/Online/Message/audio_play_sound_group_relative_gain_message.h
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------------
+// Name: audio_play_sound_group_relative_gain_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class AudioPlaySoundGroupRelativeGainMessage : public OnlineMessageBase {
+ private:
+ std::string path;
+ float gain;
+
+ public:
+ AudioPlaySoundGroupRelativeGainMessage(const std::string& path, float gain);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/audio_play_sound_group_relative_message.cpp b/Source/Online/Message/audio_play_sound_group_relative_message.cpp
new file mode 100644
index 00000000..d8df4c08
--- /dev/null
+++ b/Source/Online/Message/audio_play_sound_group_relative_message.cpp
@@ -0,0 +1,71 @@
+//-----------------------------------------------------------------------------
+// Name: audio_play_sound_group_relative_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "audio_play_sound_group_relative_message.h"
+
+#include <Main/engine.h>
+#include <Utility/binn_util.h>
+
+namespace OnlineMessages {
+ AudioPlaySoundGroupRelativeMessage::AudioPlaySoundGroupRelativeMessage(const std::string & path) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ path(path)
+ {
+
+ }
+
+ binn* AudioPlaySoundGroupRelativeMessage::Serialize(void* object) {
+ AudioPlaySoundGroupRelativeMessage* t = static_cast<AudioPlaySoundGroupRelativeMessage*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_std_string(l, "path", t->path);
+
+ return l;
+ }
+
+ void AudioPlaySoundGroupRelativeMessage::Deserialize(void* object, binn* l) {
+ AudioPlaySoundGroupRelativeMessage* t = static_cast<AudioPlaySoundGroupRelativeMessage*>(object);
+
+ binn_object_get_std_string(l, "path", &t->path);
+ }
+
+ void AudioPlaySoundGroupRelativeMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ AudioPlaySoundGroupRelativeMessage* t = static_cast<AudioPlaySoundGroupRelativeMessage*>(object);
+
+ SoundGroupRef sgr = Engine::Instance()->GetAssetManager()->LoadSync<SoundGroup>(t->path);
+ SoundGroupPlayInfo sgpi(SoundGroupPlayInfo(*sgr, vec3(0.0f)));
+ sgpi.flags = sgpi.flags | SoundFlags::kRelative;
+
+ int handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+
+ Engine::Instance()->GetSound()->PlayGroup(handle, sgpi);
+ }
+
+ void* AudioPlaySoundGroupRelativeMessage::Construct(void *mem) {
+ return new(mem) AudioPlaySoundGroupRelativeMessage("");
+ }
+
+ void AudioPlaySoundGroupRelativeMessage::Destroy(void* object) {
+ AudioPlaySoundGroupRelativeMessage* t = static_cast<AudioPlaySoundGroupRelativeMessage*>(object);
+ t->~AudioPlaySoundGroupRelativeMessage();
+ }
+}
diff --git a/Source/Online/Message/audio_play_sound_group_relative_message.h b/Source/Online/Message/audio_play_sound_group_relative_message.h
new file mode 100644
index 00000000..e7c744bb
--- /dev/null
+++ b/Source/Online/Message/audio_play_sound_group_relative_message.h
@@ -0,0 +1,41 @@
+//-----------------------------------------------------------------------------
+// Name: audio_play_sound_group_relative_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class AudioPlaySoundGroupRelativeMessage : public OnlineMessageBase {
+ private:
+ std::string path;
+
+ public:
+ AudioPlaySoundGroupRelativeMessage(const std::string& path);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/audio_play_sound_group_voice_message.cpp b/Source/Online/Message/audio_play_sound_group_voice_message.cpp
new file mode 100644
index 00000000..d385fa0e
--- /dev/null
+++ b/Source/Online/Message/audio_play_sound_group_voice_message.cpp
@@ -0,0 +1,81 @@
+//-----------------------------------------------------------------------------
+// Name: audio_play_sound_group_voice_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "audio_play_sound_group_voice_message.h"
+
+#include <Main/engine.h>
+#include <Utility/binn_util.h>
+
+namespace OnlineMessages {
+ AudioPlaySoundGroupVoiceMessage::AudioPlaySoundGroupVoiceMessage(const std::string& path, ObjectID id, float delay) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ path(path), delay(delay)
+ {
+ this->id = Online::Instance()->GetOriginalID(id);
+ }
+
+ binn* AudioPlaySoundGroupVoiceMessage::Serialize(void* object) {
+ AudioPlaySoundGroupVoiceMessage* t = static_cast<AudioPlaySoundGroupVoiceMessage*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_std_string(l, "path", t->path);
+ binn_object_set_int32(l, "id", t->id);
+ binn_object_set_float(l, "delay", t->delay);
+
+ return l;
+ }
+
+ void AudioPlaySoundGroupVoiceMessage::Deserialize(void* object, binn* l) {
+ AudioPlaySoundGroupVoiceMessage* t = static_cast<AudioPlaySoundGroupVoiceMessage*>(object);
+
+ binn_object_get_std_string(l, "path", &t->path);
+ binn_object_get_int32(l, "id", &t->id);
+ binn_object_get_float(l, "delay", &t->delay);
+ }
+
+ void AudioPlaySoundGroupVoiceMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ AudioPlaySoundGroupVoiceMessage* t = static_cast<AudioPlaySoundGroupVoiceMessage*>(object);
+ ObjectID object_id = Online::Instance()->GetOriginalID(t->id);
+
+ SceneGraph* scene_graph = Engine::Instance()->GetSceneGraph();
+ if(scene_graph != nullptr) {
+ Object* obj = scene_graph->GetObjectFromID(object_id);
+ if(obj != nullptr && obj->GetType() == _movement_object) {
+ MovementObject* movement_object = (MovementObject*) obj;
+ movement_object->PlaySoundGroupVoice(t->path, t->delay);
+ } else {
+ LOGW << "Unable to find movement object with ID: " << object_id << " (" << t->id << ")" << std::endl;
+ }
+ } else {
+ LOGW << "Unable to apply group voice sound due to missing scene graph" << std::endl;
+ }
+ }
+
+ void* AudioPlaySoundGroupVoiceMessage::Construct(void *mem) {
+ return new(mem) AudioPlaySoundGroupVoiceMessage("", 0, 0.0f);
+ }
+
+ void AudioPlaySoundGroupVoiceMessage::Destroy(void* object) {
+ AudioPlaySoundGroupVoiceMessage* t = static_cast<AudioPlaySoundGroupVoiceMessage*>(object);
+ t->~AudioPlaySoundGroupVoiceMessage();
+ }
+}
diff --git a/Source/Online/Message/audio_play_sound_group_voice_message.h b/Source/Online/Message/audio_play_sound_group_voice_message.h
new file mode 100644
index 00000000..e2c28c48
--- /dev/null
+++ b/Source/Online/Message/audio_play_sound_group_voice_message.h
@@ -0,0 +1,43 @@
+//-----------------------------------------------------------------------------
+// Name: audio_play_sound_group_voice_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class AudioPlaySoundGroupVoiceMessage : public OnlineMessageBase {
+ private:
+ std::string path;
+ CommonObjectID id;
+ float delay;
+
+ public:
+ AudioPlaySoundGroupVoiceMessage(const std::string& path, ObjectID id, float delay);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/audio_play_sound_impact_item_message.cpp b/Source/Online/Message/audio_play_sound_impact_item_message.cpp
new file mode 100644
index 00000000..d6c13983
--- /dev/null
+++ b/Source/Online/Message/audio_play_sound_impact_item_message.cpp
@@ -0,0 +1,87 @@
+//-----------------------------------------------------------------------------
+// Name: audio_play_sound_impact_item_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "audio_play_sound_impact_item_message.h"
+
+#include <Objects/itemobject.h>
+
+#include <Main/engine.h>
+#include <Utility/binn_util.h>
+
+namespace OnlineMessages {
+ AudioPlaySoundImpactItemMessage::AudioPlaySoundImpactItemMessage(ObjectID id, vec3 pos, vec3 normal, float impulse) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ pos(pos),
+ normal(normal),
+ impulse(impulse) {
+
+ this->id = Online::Instance()->GetOriginalID(id);
+ }
+
+ binn* AudioPlaySoundImpactItemMessage::Serialize(void* object) {
+ AudioPlaySoundImpactItemMessage* t = static_cast<AudioPlaySoundImpactItemMessage*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_int32(l, "id", t->id);
+ binn_object_set_vec3(l, "pos", t->pos);
+ binn_object_set_vec3(l, "normal", t->normal);
+ binn_object_set_float(l, "impulse", t->impulse);
+
+ return l;
+ }
+
+ void AudioPlaySoundImpactItemMessage::Deserialize(void* object, binn* l) {
+ AudioPlaySoundImpactItemMessage* t = static_cast<AudioPlaySoundImpactItemMessage*>(object);
+
+ binn_object_get_int32(l, "id", &t->id);
+ binn_object_get_vec3(l, "pos", &t->pos);
+ binn_object_get_vec3(l, "normal", &t->normal);
+ binn_object_get_float(l, "impulse", &t->impulse);
+ }
+
+ void AudioPlaySoundImpactItemMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ AudioPlaySoundImpactItemMessage* t = static_cast<AudioPlaySoundImpactItemMessage*>(object);
+ ObjectID object_id = Online::Instance()->GetObjectID(t->id);
+
+ SceneGraph* scene_graph = Engine::Instance()->GetSceneGraph();
+ if(scene_graph != nullptr) {
+ Object* obj = scene_graph->GetObjectFromID(object_id);
+ if(obj != nullptr && obj->GetType() == _item_object) {
+ ItemObject* item_object = (ItemObject*) obj;
+ item_object->PlayImpactSound(t->pos, t->normal, t->impulse);
+ } else {
+ LOGW << "Unable to find item with ID: " << object_id << " (" << t->id << ")" << std::endl;
+ }
+ } else {
+ LOGW << "Unable to apply impact sound due to missing scene graph" << std::endl;
+ }
+ }
+
+ void* AudioPlaySoundImpactItemMessage::Construct(void *mem) {
+ return new(mem) AudioPlaySoundImpactItemMessage(0, vec3(), vec3(), 0.0f);
+ }
+
+ void AudioPlaySoundImpactItemMessage::Destroy(void* object) {
+ AudioPlaySoundImpactItemMessage* t = static_cast<AudioPlaySoundImpactItemMessage*>(object);
+ t->~AudioPlaySoundImpactItemMessage();
+ }
+}
diff --git a/Source/Online/Message/audio_play_sound_impact_item_message.h b/Source/Online/Message/audio_play_sound_impact_item_message.h
new file mode 100644
index 00000000..116d40d3
--- /dev/null
+++ b/Source/Online/Message/audio_play_sound_impact_item_message.h
@@ -0,0 +1,44 @@
+//-----------------------------------------------------------------------------
+// Name: audio_play_sound_impact_item_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class AudioPlaySoundImpactItemMessage : public OnlineMessageBase {
+ private:
+ CommonObjectID id;
+ vec3 pos;
+ vec3 normal;
+ float impulse;
+
+ public:
+ AudioPlaySoundImpactItemMessage(ObjectID id, vec3 pos, vec3 normal, float impulse);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/audio_play_sound_location_message.cpp b/Source/Online/Message/audio_play_sound_location_message.cpp
new file mode 100644
index 00000000..1faeb3d6
--- /dev/null
+++ b/Source/Online/Message/audio_play_sound_location_message.cpp
@@ -0,0 +1,71 @@
+//-----------------------------------------------------------------------------
+// Name: audio_play_sound_location_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "audio_play_sound_location_message.h"
+
+#include <Main/engine.h>
+#include <Utility/binn_util.h>
+
+namespace OnlineMessages {
+ AudioPlaySoundLocationMessage::AudioPlaySoundLocationMessage(const std::string & path, vec3 pos) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ path(path), pos(pos)
+ {
+
+ }
+
+ binn* AudioPlaySoundLocationMessage::Serialize(void* object) {
+ AudioPlaySoundLocationMessage* t = static_cast<AudioPlaySoundLocationMessage*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_std_string(l, "path", t->path);
+ binn_object_set_vec3(l, "pos", t->pos);
+
+ return l;
+ }
+
+ void AudioPlaySoundLocationMessage::Deserialize(void* object, binn* l) {
+ AudioPlaySoundLocationMessage* t = static_cast<AudioPlaySoundLocationMessage*>(object);
+
+ binn_object_get_std_string(l, "path", &t->path);
+ binn_object_get_vec3(l, "pos", &t->pos);
+ }
+
+ void AudioPlaySoundLocationMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ AudioPlaySoundLocationMessage* t = static_cast<AudioPlaySoundLocationMessage*>(object);
+
+ SoundPlayInfo spi;
+ spi.path = t->path;
+ spi.position = t->pos;
+ int handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->Play(handle, spi);
+ }
+
+ void* AudioPlaySoundLocationMessage::Construct(void *mem) {
+ return new(mem) AudioPlaySoundLocationMessage("", vec3());
+ }
+
+ void AudioPlaySoundLocationMessage::Destroy(void* object) {
+ AudioPlaySoundLocationMessage* t = static_cast<AudioPlaySoundLocationMessage*>(object);
+ t->~AudioPlaySoundLocationMessage();
+ }
+}
diff --git a/Source/Online/Message/audio_play_sound_location_message.h b/Source/Online/Message/audio_play_sound_location_message.h
new file mode 100644
index 00000000..071d6e08
--- /dev/null
+++ b/Source/Online/Message/audio_play_sound_location_message.h
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------------
+// Name: audio_play_sound_location_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class AudioPlaySoundLocationMessage : public OnlineMessageBase {
+ private:
+ std::string path;
+ vec3 pos;
+
+ public:
+ AudioPlaySoundLocationMessage(const std::string& path, vec3 pos);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/audio_play_sound_loop_at_location_message.cpp b/Source/Online/Message/audio_play_sound_loop_at_location_message.cpp
new file mode 100644
index 00000000..f9fc6c9b
--- /dev/null
+++ b/Source/Online/Message/audio_play_sound_loop_at_location_message.cpp
@@ -0,0 +1,75 @@
+//-----------------------------------------------------------------------------
+// Name: audio_play_sound_loop_at_location_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "audio_play_sound_loop_at_location_message.h"
+
+#include <Main/engine.h>
+#include <Utility/binn_util.h>
+
+namespace OnlineMessages {
+ AudioPlaySoundLoopAtLocationMessage::AudioPlaySoundLoopAtLocationMessage(const std::string& path, float gain, vec3 pos) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ path(path), gain(gain), pos(pos)
+ {
+
+ }
+
+ binn* AudioPlaySoundLoopAtLocationMessage::Serialize(void* object) {
+ AudioPlaySoundLoopAtLocationMessage* t = static_cast<AudioPlaySoundLoopAtLocationMessage*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_std_string(l, "path", t->path);
+ binn_object_set_vec3(l, "pos", t->pos);
+ binn_object_set_float(l, "gain", t->gain);
+
+ return l;
+ }
+
+ void AudioPlaySoundLoopAtLocationMessage::Deserialize(void* object, binn* l) {
+ AudioPlaySoundLoopAtLocationMessage* t = static_cast<AudioPlaySoundLoopAtLocationMessage*>(object);
+
+ binn_object_get_std_string(l, "path", &t->path);
+ binn_object_get_vec3(l, "pos", &t->pos);
+ binn_object_get_float(l, "gain", &t->gain);
+ }
+
+ void AudioPlaySoundLoopAtLocationMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ AudioPlaySoundLoopAtLocationMessage* t = static_cast<AudioPlaySoundLoopAtLocationMessage*>(object);
+
+ SoundPlayInfo spi;
+ spi.path = t->path;
+ spi.looping = true;
+ spi.volume = t->gain;
+ spi.position = t->pos;
+ int handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->Play(handle, spi);
+ }
+
+ void* AudioPlaySoundLoopAtLocationMessage::Construct(void *mem) {
+ return new(mem) AudioPlaySoundLoopAtLocationMessage("", 0.0f, vec3());
+ }
+
+ void AudioPlaySoundLoopAtLocationMessage::Destroy(void* object) {
+ AudioPlaySoundLoopAtLocationMessage* t = static_cast<AudioPlaySoundLoopAtLocationMessage*>(object);
+ t->~AudioPlaySoundLoopAtLocationMessage();
+ }
+}
diff --git a/Source/Online/Message/audio_play_sound_loop_at_location_message.h b/Source/Online/Message/audio_play_sound_loop_at_location_message.h
new file mode 100644
index 00000000..8fc42852
--- /dev/null
+++ b/Source/Online/Message/audio_play_sound_loop_at_location_message.h
@@ -0,0 +1,43 @@
+//-----------------------------------------------------------------------------
+// Name: audio_play_sound_loop_at_location_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class AudioPlaySoundLoopAtLocationMessage : public OnlineMessageBase {
+ private:
+ std::string path;
+ vec3 pos;
+ float gain;
+
+ public:
+ AudioPlaySoundLoopAtLocationMessage(const std::string& path, float gain, vec3 pos);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/audio_play_sound_loop_message.cpp b/Source/Online/Message/audio_play_sound_loop_message.cpp
new file mode 100644
index 00000000..5346a3b6
--- /dev/null
+++ b/Source/Online/Message/audio_play_sound_loop_message.cpp
@@ -0,0 +1,73 @@
+//-----------------------------------------------------------------------------
+// Name: audio_play_sound_loop_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "audio_play_sound_loop_message.h"
+
+#include <Main/engine.h>
+#include <Utility/binn_util.h>
+
+namespace OnlineMessages {
+ AudioPlaySoundLoopMessage::AudioPlaySoundLoopMessage(std::string path, float gain) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ path(path), gain(gain)
+ {
+
+ }
+
+ binn* AudioPlaySoundLoopMessage::Serialize(void* object) {
+ AudioPlaySoundLoopMessage* t = static_cast<AudioPlaySoundLoopMessage*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_std_string(l, "path", t->path);
+ binn_object_set_float(l, "gain", t->gain);
+
+ return l;
+ }
+
+ void AudioPlaySoundLoopMessage::Deserialize(void* object, binn* l) {
+ AudioPlaySoundLoopMessage* t = static_cast<AudioPlaySoundLoopMessage*>(object);
+
+ binn_object_get_std_string(l, "path", &t->path);
+ binn_object_get_float(l, "gain", &t->gain);
+ }
+
+ void AudioPlaySoundLoopMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ AudioPlaySoundLoopMessage* t = static_cast<AudioPlaySoundLoopMessage*>(object);
+
+ SoundPlayInfo spi;
+ spi.path = t->path;
+ spi.flags = spi.flags | SoundFlags::kRelative;
+ spi.looping = true;
+ spi.volume = t->gain;
+ int handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->Play(handle, spi);
+ }
+
+ void* AudioPlaySoundLoopMessage::Construct(void *mem) {
+ return new(mem) AudioPlaySoundLoopMessage("", 0.0f);
+ }
+
+ void AudioPlaySoundLoopMessage::Destroy(void* object) {
+ AudioPlaySoundLoopMessage* t = static_cast<AudioPlaySoundLoopMessage*>(object);
+ t->~AudioPlaySoundLoopMessage();
+ }
+}
diff --git a/Source/Online/Message/audio_play_sound_loop_message.h b/Source/Online/Message/audio_play_sound_loop_message.h
new file mode 100644
index 00000000..2f0119b6
--- /dev/null
+++ b/Source/Online/Message/audio_play_sound_loop_message.h
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------------
+// Name: audio_play_sound_loop_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class AudioPlaySoundLoopMessage : public OnlineMessageBase {
+ private:
+ std::string path;
+ float gain;
+
+ public:
+ AudioPlaySoundLoopMessage(std::string path, float gain);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/audio_play_sound_message.cpp b/Source/Online/Message/audio_play_sound_message.cpp
new file mode 100644
index 00000000..4a71a7e3
--- /dev/null
+++ b/Source/Online/Message/audio_play_sound_message.cpp
@@ -0,0 +1,69 @@
+//-----------------------------------------------------------------------------
+// Name: audio_play_sound_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "audio_play_sound_message.h"
+
+#include <Main/engine.h>
+#include <Utility/binn_util.h>
+
+namespace OnlineMessages {
+ AudioPlaySoundMessage::AudioPlaySoundMessage(const std::string & path) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ path(path)
+ {
+
+ }
+
+ binn* AudioPlaySoundMessage::Serialize(void* object) {
+ AudioPlaySoundMessage* t = static_cast<AudioPlaySoundMessage*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_std_string(l, "path", t->path);
+
+ return l;
+ }
+
+ void AudioPlaySoundMessage::Deserialize(void* object, binn* l) {
+ AudioPlaySoundMessage* t = static_cast<AudioPlaySoundMessage*>(object);
+
+ binn_object_get_std_string(l, "path", &t->path);
+ }
+
+ void AudioPlaySoundMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ AudioPlaySoundMessage* t = static_cast<AudioPlaySoundMessage*>(object);
+
+ SoundPlayInfo spi;
+ spi.path = t->path;
+ spi.flags = spi.flags | SoundFlags::kRelative;
+ int handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->Play(handle, spi);
+ }
+
+ void* AudioPlaySoundMessage::Construct(void *mem) {
+ return new(mem) AudioPlaySoundMessage("");
+ }
+
+ void AudioPlaySoundMessage::Destroy(void* object) {
+ AudioPlaySoundMessage* t = static_cast<AudioPlaySoundMessage*>(object);
+ t->~AudioPlaySoundMessage();
+ }
+}
diff --git a/Source/Online/Message/audio_play_sound_message.h b/Source/Online/Message/audio_play_sound_message.h
new file mode 100644
index 00000000..c45d82ec
--- /dev/null
+++ b/Source/Online/Message/audio_play_sound_message.h
@@ -0,0 +1,41 @@
+//-----------------------------------------------------------------------------
+// Name: audio_play_sound_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class AudioPlaySoundMessage : public OnlineMessageBase {
+ private:
+ std::string path;
+
+ public:
+ AudioPlaySoundMessage(const std::string& path);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/camera_transform_message.cpp b/Source/Online/Message/camera_transform_message.cpp
new file mode 100644
index 00000000..31141ae0
--- /dev/null
+++ b/Source/Online/Message/camera_transform_message.cpp
@@ -0,0 +1,91 @@
+//-----------------------------------------------------------------------------
+// Name: camera_transform_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "camera_transform_message.h"
+
+#include <Main/engine.h>
+#include <Online/online.h>
+#include <Utility/binn_util.h>
+
+namespace OnlineMessages {
+ CameraTransformMessage::CameraTransformMessage(vec3 position, vec3 flat_facing, vec3 facing, vec3 up, float x_rotation, float y_rotation) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ position(position), flat_facing(flat_facing), facing(facing), up(up), x_rotation(x_rotation), y_rotation(y_rotation)
+ {
+ reliable_delivery = false;
+ }
+
+ binn* CameraTransformMessage::Serialize(void* object) {
+ CameraTransformMessage* t = static_cast<CameraTransformMessage*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_vec3(l, "position", t->position);
+ binn_object_set_vec3(l, "flat_facing", t->flat_facing);
+ binn_object_set_vec3(l, "facing", t->facing);
+ binn_object_set_vec3(l, "up", t->up);
+ binn_object_set_float(l, "x_rotation", t->x_rotation);
+ binn_object_set_float(l, "y_rotation", t->y_rotation);
+
+ return l;
+ }
+
+ void CameraTransformMessage::Deserialize(void* object, binn* l) {
+ CameraTransformMessage* t = static_cast<CameraTransformMessage*>(object);
+
+ binn_object_get_vec3(l, "position", &t->position);
+ binn_object_get_vec3(l, "flat_facing", &t->flat_facing);
+ binn_object_get_vec3(l, "facing", &t->facing);
+ binn_object_get_vec3(l, "up", &t->up);
+ binn_object_get_float(l, "x_rotation", &t->x_rotation);
+ binn_object_get_float(l, "y_rotation", &t->y_rotation);
+ }
+
+ void CameraTransformMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ CameraTransformMessage* t = static_cast<CameraTransformMessage*>(object);
+
+ if (Online::Instance()->online_session->player_states.find(from) != Online::Instance()->online_session->player_states.end()) {
+ int camera_id = Online::Instance()->online_session->player_states[from].camera_id; // TODO this assumes peer_id == player_id
+ Camera* camera = ActiveCameras::Instance()->GetCamera(camera_id);
+ if (camera != nullptr) {
+ camera->SetPos(t->position);
+ camera->SetFlatFacing(t->flat_facing);
+ camera->SetFacing(t->facing);
+ camera->SetUp(t->up);
+ camera->SetXRotation(t->x_rotation);
+ camera->SetYRotation(t->y_rotation);
+ } else {
+ LOGW << "Got camera transform information for camera " << camera_id << " from client " << std::to_string(from) << " without a valid camera!" << std::endl;
+ }
+ } else {
+ LOGW << "Got camera transform information from a client we can no longer find, they might have disconnected!" << std::endl; // TODO should we even warn about this?
+ }
+ }
+
+ void* CameraTransformMessage::Construct(void *mem) {
+ return new(mem) CameraTransformMessage(vec3(), vec3(), vec3(), vec3(), 0, 0);
+ }
+
+ void CameraTransformMessage::Destroy(void* object) {
+ CameraTransformMessage* t = static_cast<CameraTransformMessage*>(object);
+ t->~CameraTransformMessage();
+ }
+}
diff --git a/Source/Online/Message/camera_transform_message.h b/Source/Online/Message/camera_transform_message.h
new file mode 100644
index 00000000..31c893c9
--- /dev/null
+++ b/Source/Online/Message/camera_transform_message.h
@@ -0,0 +1,46 @@
+//-----------------------------------------------------------------------------
+// Name: camera_transform_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class CameraTransformMessage : public OnlineMessageBase {
+ private:
+ vec3 position;
+ vec3 flat_facing;
+ vec3 facing;
+ vec3 up;
+ float x_rotation;
+ float y_rotation;
+
+ public:
+ CameraTransformMessage(vec3 position, vec3 flat_facing, vec3 facing, vec3 up, float x_rotation, float y_rotation);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/chat_entry_message.cpp b/Source/Online/Message/chat_entry_message.cpp
new file mode 100644
index 00000000..925962e4
--- /dev/null
+++ b/Source/Online/Message/chat_entry_message.cpp
@@ -0,0 +1,78 @@
+//-----------------------------------------------------------------------------
+// Name: chat_entry_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "chat_entry_message.h"
+
+#include <Main/engine.h>
+#include <Utility/binn_util.h>
+#include <Online/online_utility.h>
+
+namespace OnlineMessages {
+ ChatEntryMessage::ChatEntryMessage(const std::string & entry) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ entry(entry)
+ {
+
+ }
+
+ binn* ChatEntryMessage::Serialize(void* object) {
+ ChatEntryMessage* t = static_cast<ChatEntryMessage*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_std_string(l, "entry", t->entry);
+
+ return l;
+ }
+
+ void ChatEntryMessage::Deserialize(void* object, binn* l) {
+ ChatEntryMessage* t = static_cast<ChatEntryMessage*>(object);
+
+ binn_object_get_std_string(l, "entry", &t->entry);
+ }
+
+ void ChatEntryMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ ChatEntryMessage* t = static_cast<ChatEntryMessage*>(object);
+
+ // Hosts will prefix name of sender and send it to everyone, clients will simply apply it here.
+ if (Online::Instance()->IsActive()) {
+ if (Online::Instance()->IsHosting()) {
+ if (OnlineUtility::IsCommand(t->entry)) {
+ OnlineUtility::HandleCommand(from, t->entry); // TODO We assume that peer_id == player_id here
+ Online::Instance()->Send<ChatEntryMessage>(Online::Instance()->online_session->player_states[from].playername + ": " + t->entry);
+ } else {
+ Online::Instance()->SendRawChatMessage(Online::Instance()->online_session->player_states[from].playername + ": " + t->entry);
+ }
+ } else {
+ Online::Instance()->AddLocalChatMessage(t->entry);
+ }
+ }
+ }
+
+ void* ChatEntryMessage::Construct(void *mem) {
+ return new(mem) ChatEntryMessage("");
+ }
+
+ void ChatEntryMessage::Destroy(void* object) {
+ ChatEntryMessage* t = static_cast<ChatEntryMessage*>(object);
+ t->~ChatEntryMessage();
+ }
+}
diff --git a/Source/Online/Message/chat_entry_message.h b/Source/Online/Message/chat_entry_message.h
new file mode 100644
index 00000000..ffdb32d5
--- /dev/null
+++ b/Source/Online/Message/chat_entry_message.h
@@ -0,0 +1,41 @@
+//-----------------------------------------------------------------------------
+// Name: chat_entry_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class ChatEntryMessage : public OnlineMessageBase {
+ private:
+ std::string entry;
+
+ public:
+ ChatEntryMessage(const std::string& path);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/create_entity.cpp b/Source/Online/Message/create_entity.cpp
new file mode 100644
index 00000000..118c1cbb
--- /dev/null
+++ b/Source/Online/Message/create_entity.cpp
@@ -0,0 +1,79 @@
+//-----------------------------------------------------------------------------
+// Name: create_entity.cpp
+// Developer: Wolfire Games LLC
+// Author: Max Danielsson
+// Description: Create entity message, sent by the host to clients to inform
+// them of construction of a new entity.
+//
+// Clients will request creation of an entity to the host via a
+// different message.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "create_entity.h"
+
+#include <Online/online.h>
+#include <Main/engine.h>
+#include <Editors/map_editor.h>
+
+namespace OnlineMessages {
+ CreateEntity::CreateEntity(const string& path, const vec3& pos, ObjectID entity_object_id) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_PERSISTENT),
+ path(path), pos(pos), entity_object_id(entity_object_id)
+ {
+
+ }
+
+ binn* CreateEntity::Serialize(void* object) {
+ CreateEntity* ce = static_cast<CreateEntity*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_std_string(l, "path", ce->path);
+ binn_object_set_vec3(l, "pos", ce->pos);
+ binn_object_set_int32(l, "id", ce->entity_object_id);
+
+ return l;
+ }
+
+ void CreateEntity::Deserialize(void* object, binn* l) {
+ CreateEntity* ce = static_cast<CreateEntity*>(object);
+
+ binn_object_get_std_string(l, "path", &ce->path);
+ binn_object_get_vec3(l, "pos", &ce->pos);
+ binn_object_get_int32(l, "id", &ce->entity_object_id);
+ }
+
+ void CreateEntity::Execute(const OnlineMessageRef& ref, void* object, PeerID peer) {
+ CreateEntity* ce = static_cast<CreateEntity*>(object);
+ SceneGraph* sg = Engine::Instance()->GetSceneGraph();
+ Online* online = Online::Instance();
+
+ if(sg != nullptr) {
+ sg->map_editor->CreateObjectFromHost(ce->path, ce->pos, ce->entity_object_id);
+ }
+ }
+
+ void* CreateEntity::Construct(void* mem) {
+ return new(mem) CreateEntity("", vec3(), -1);
+ }
+
+ void CreateEntity::Destroy(void* object) {
+ CreateEntity* ce = static_cast<CreateEntity*>(object);
+ ce->~CreateEntity();
+ }
+}
diff --git a/Source/Online/Message/create_entity.h b/Source/Online/Message/create_entity.h
new file mode 100644
index 00000000..360705ec
--- /dev/null
+++ b/Source/Online/Message/create_entity.h
@@ -0,0 +1,55 @@
+//-----------------------------------------------------------------------------
+// Name: create_entity.h
+// Developer: Wolfire Games LLC
+// Author: Max Danielsson
+// Description: Create entity message, sent by the host to clients to inform
+// them of construction of a new entity.
+//
+// Clients will request creation of an entity to the host via a
+// different message.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+#include <Online/online_datastructures.h>
+#include <Math/vec3.h>
+
+#include <string>
+
+using std::string;
+
+namespace OnlineMessages {
+ class CreateEntity : public OnlineMessageBase {
+ private:
+ string path;
+ vec3 pos;
+ ObjectID entity_object_id;
+
+ public:
+ CreateEntity(const string& path, const vec3& pos, ObjectID entity_object_id);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* l);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID peer);
+ static void* Construct(void *mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/cut_line.cpp b/Source/Online/Message/cut_line.cpp
new file mode 100644
index 00000000..ef526986
--- /dev/null
+++ b/Source/Online/Message/cut_line.cpp
@@ -0,0 +1,105 @@
+//-----------------------------------------------------------------------------
+// Name: cut_line.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "cut_line.h"
+
+#include <Main/engine.h>
+
+namespace OnlineMessages {
+ CutLine::CutLine() :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ id(-1), pos(), normal(), dir(),
+ type(0), depth(0), num_hit(0), hit_list()
+ {
+
+ }
+
+ CutLine::CutLine(ObjectID id, const vec3* points, const vec3& pos, const vec3& normal, const vec3& dir, int type, int depth, int num_hit, const vector<int>& hit_list) :
+ OnlineMessageBase::OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ pos(pos), normal(normal), dir(dir), type(type),
+ depth(depth), num_hit(num_hit), hit_list(hit_list)
+ {
+ this->id = Online::Instance()->GetOriginalID(id);
+
+ for(int i = 0; i < 3; i++) {
+ this->points[i] = points[i];
+ }
+ }
+
+ binn* CutLine::Serialize(void* object) {
+ CutLine* cl = static_cast<CutLine*>(object);
+
+ binn* l = binn_object();
+
+ binn_object_set_int32(l, "id", cl->id);
+ binn_object_set_vec3(l, "p0", cl->points[0]);
+ binn_object_set_vec3(l, "p1", cl->points[1]);
+ binn_object_set_vec3(l, "p2", cl->points[2]);
+ binn_object_set_vec3(l, "pos", cl->pos);
+ binn_object_set_vec3(l, "n", cl->normal);
+ binn_object_set_vec3(l, "d", cl->dir);
+ binn_object_set_int32(l, "t", cl->type);
+ binn_object_set_int32(l, "de", cl->depth);
+ binn_object_set_int32(l, "nh", cl->num_hit);
+ binn_object_set_vector_int(l, "hl", cl->hit_list);
+
+ return l;
+ }
+
+ void CutLine::Deserialize(void* object, binn* l) {
+ CutLine* cl = static_cast<CutLine*>(object);
+ binn_object_get_int32(l, "id", &cl->id);
+ binn_object_get_vec3(l, "p0", &cl->points[0]);
+ binn_object_get_vec3(l, "p1", &cl->points[1]);
+ binn_object_get_vec3(l, "p2", &cl->points[2]);
+ binn_object_get_vec3(l, "pos", &cl->pos);
+ binn_object_get_vec3(l, "n", &cl->normal);
+ binn_object_get_vec3(l, "d", &cl->dir);
+ binn_object_get_int32(l, "t", &cl->type);
+ binn_object_get_int32(l, "de", &cl->depth);
+ binn_object_get_int32(l, "nh", &cl->num_hit);
+ binn_object_get_vector_int(l, "hl", &cl->hit_list);
+ }
+
+ void CutLine::Execute(const OnlineMessageRef& ref, void* object, PeerID peer) {
+ CutLine* cl = static_cast<CutLine*>(object);
+ ObjectID object_id = Online::Instance()->GetObjectID(cl->id);
+
+ SceneGraph* sg = Engine::Instance()->GetSceneGraph();
+
+ if(sg != nullptr) {
+ MovementObject* mo = static_cast<MovementObject*>(sg->GetObjectFromID(object_id));
+ if(mo != nullptr) {
+ mo->incoming_cut_lines.push_back(ref);
+ }
+ }
+ }
+
+ void* CutLine::Construct(void* mem) {
+ return new(mem) CutLine();
+ }
+
+ void CutLine::Destroy(void* object) {
+ CutLine* cl = static_cast<CutLine*>(object);
+ cl->~CutLine();
+ }
+}
diff --git a/Source/Online/Message/cut_line.h b/Source/Online/Message/cut_line.h
new file mode 100644
index 00000000..11357549
--- /dev/null
+++ b/Source/Online/Message/cut_line.h
@@ -0,0 +1,66 @@
+//-----------------------------------------------------------------------------
+// Name: cut_line.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+#include <Objects/movementobject.h>
+
+#include <vector>
+
+using std::vector;
+
+namespace OnlineMessages {
+ class CutLine : public OnlineMessageBase {
+ private:
+ CommonObjectID id;
+
+ public:
+ vec3 points[3];
+ vec3 pos;
+ vec3 normal;
+ vec3 dir;
+ int type;
+ int depth;
+ int num_hit;
+ vector<int> hit_list;
+
+ public:
+ CutLine();
+ CutLine(ObjectID id,
+ const vec3* points,
+ const vec3& pos,
+ const vec3& normal,
+ const vec3& dir,
+ int type,
+ int depth,
+ int num_hit,
+ const vector<int>& hit_list);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* l);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID peer);
+ static void* Construct(void *mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/editor_transform_change.cpp b/Source/Online/Message/editor_transform_change.cpp
new file mode 100644
index 00000000..51fca10d
--- /dev/null
+++ b/Source/Online/Message/editor_transform_change.cpp
@@ -0,0 +1,88 @@
+//-----------------------------------------------------------------------------
+// Name: editor_transform_change.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "editor_transform_change.h"
+
+#include <Main/engine.h>
+
+namespace OnlineMessages {
+ EditorTransformChange::EditorTransformChange(ObjectID id, const vec3& trans, const vec3& scale, const quaternion& rot) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ trans(trans), scale(scale), rot(rot)
+ {
+ this->id = Online::Instance()->GetOriginalID(id);
+ }
+
+ binn* EditorTransformChange::Serialize(void* object) {
+ EditorTransformChange* etc = static_cast<EditorTransformChange*>(object);
+
+ binn* l = binn_object();
+
+ binn_object_set_int32(l, "id", etc->id);
+ binn_object_set_vec3(l, "t", etc->trans);
+ binn_object_set_vec3(l, "s", etc->scale);
+ binn_object_set_quat(l, "r", etc->rot);
+
+ return l;
+ }
+
+ void EditorTransformChange::Deserialize(void* object, binn* l) {
+ EditorTransformChange* etc = static_cast<EditorTransformChange*>(object);
+
+ binn_object_get_int32(l, "id", &etc->id);
+ binn_object_get_vec3(l, "t", &etc->trans);
+ binn_object_get_vec3(l, "s", &etc->scale);
+ binn_object_get_quat(l, "r", &etc->rot);
+ }
+
+ void EditorTransformChange::Execute(const OnlineMessageRef& ref, void* object, PeerID peer) {
+ EditorTransformChange* etc = static_cast<EditorTransformChange*>(object);
+ ObjectID object_id = Online::Instance()->GetObjectID(etc->id);
+
+ SceneGraph* sg = Engine::Instance()->GetSceneGraph();
+
+ if (sg != nullptr) {
+ Object * obj = sg->GetObjectFromID(object_id);
+ if (obj != nullptr) {
+ obj->SetTranslation(etc->trans);
+ obj->SetRotation(etc->rot);
+ obj->SetScale(etc->scale);
+
+ if (obj->GetType() == EntityType::_env_object) {
+ EnvObject* envobj = (EnvObject *)obj;
+
+ envobj->UpdateBoundingSphere();
+ envobj->UpdatePhysicsTransform();
+ }
+ }
+ }
+ }
+
+ void* EditorTransformChange::Construct(void* mem) {
+ return new(mem) EditorTransformChange(0,vec3(),vec3(),quaternion());
+ }
+
+ void EditorTransformChange::Destroy(void* object) {
+ EditorTransformChange* etc = static_cast<EditorTransformChange*>(object);
+ etc->~EditorTransformChange();
+ }
+}
diff --git a/Source/Online/Message/editor_transform_change.h b/Source/Online/Message/editor_transform_change.h
new file mode 100644
index 00000000..bb17e17c
--- /dev/null
+++ b/Source/Online/Message/editor_transform_change.h
@@ -0,0 +1,45 @@
+//-----------------------------------------------------------------------------
+// Name: editor_transform_change.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class EditorTransformChange : public OnlineMessageBase {
+ private:
+ CommonObjectID id;
+ vec3 trans;
+ vec3 scale;
+ quaternion rot;
+
+ public:
+ EditorTransformChange(ObjectID id, const vec3& trans, const vec3& scale, const quaternion& rot);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* l);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID peer);
+
+ static void* Construct(void *mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/env_object_update.cpp b/Source/Online/Message/env_object_update.cpp
new file mode 100644
index 00000000..f401be29
--- /dev/null
+++ b/Source/Online/Message/env_object_update.cpp
@@ -0,0 +1,90 @@
+//-----------------------------------------------------------------------------
+// Name: env_object_update.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "env_object_update.h"
+
+#include <Main/engine.h>
+
+extern Timer game_timer;
+
+namespace OnlineMessages {
+ EnvObjectUpdate::EnvObjectUpdate() :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ id(-1), transform(mat4()), timestamp(0.0f)
+ {
+
+ }
+
+ EnvObjectUpdate::EnvObjectUpdate(EnvObject* object) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ transform(object->GetTransform()), timestamp(game_timer.GetWallTime())
+ {
+ this->id = Online::Instance()->GetOriginalID(object->GetID());
+ }
+
+ binn* EnvObjectUpdate::Serialize(void* object) {
+ EnvObjectUpdate* eou = static_cast<EnvObjectUpdate*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_int32(l, "id", eou->id);
+ binn_object_set_float(l, "ts", eou->timestamp);
+ binn_object_set_mat4(l, "t", eou->transform);
+
+ return l;
+ }
+
+ void EnvObjectUpdate::Deserialize(void* object, binn* l) {
+ EnvObjectUpdate* eou = static_cast<EnvObjectUpdate*>(object);
+
+ binn_object_get_int32(l, "id", &eou->id);
+ binn_object_get_float(l, "ts", &eou->timestamp);
+ binn_object_get_mat4(l, "t", &eou->transform);
+ }
+
+ void EnvObjectUpdate::Execute(const OnlineMessageRef& ref, void* object, PeerID peer) {
+ EnvObjectUpdate* eou = static_cast<EnvObjectUpdate*>(object);
+ ObjectID object_id = Online::Instance()->GetObjectID(eou->id);
+
+ SceneGraph* sg = Engine::Instance()->GetSceneGraph();
+
+ if(sg != nullptr && Online::Instance()->host_started_level) {
+ Object* object = sg->GetObjectFromID(object_id);
+ if (object != nullptr && object->GetType() == EntityType::_env_object) {
+ EnvObject* eo = static_cast<EnvObject*>(object);
+ eo->incoming_online_env_update.push_back(ref);
+ } else {
+ LOGW << "Received EnvObjectUpdate, but couldn't find an EnvObject with ID of \"" << object_id << " (" << eou->id << ")\"" << std::endl;
+ }
+ } else {
+ LOGW << "Client received EnvObjectUpdate, but wasn't ready to receive it. The host should not be sending this to us right now!" << std::endl;
+ }
+ }
+
+ void* EnvObjectUpdate::Construct(void* mem) {
+ return new(mem) EnvObjectUpdate();
+ }
+
+ void EnvObjectUpdate::Destroy(void* object) {
+ EnvObjectUpdate* eou = static_cast<EnvObjectUpdate*>(object);
+ eou->~EnvObjectUpdate();
+ }
+}
diff --git a/Source/Online/Message/env_object_update.h b/Source/Online/Message/env_object_update.h
new file mode 100644
index 00000000..68f9cefc
--- /dev/null
+++ b/Source/Online/Message/env_object_update.h
@@ -0,0 +1,50 @@
+//-----------------------------------------------------------------------------
+// Name: env_object_update.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+#include <Online/online_datastructures.h>
+#include <Objects/envobject.h>
+
+
+namespace OnlineMessages {
+ class EnvObjectUpdate : public OnlineMessageBase {
+ private:
+ CommonObjectID id;
+
+ public:
+ float timestamp;
+ mat4 transform;
+
+ public:
+ EnvObjectUpdate();
+ EnvObjectUpdate(EnvObject* object);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* l);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID peer);
+ static void* Construct(void *mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/host_session_flag.cpp b/Source/Online/Message/host_session_flag.cpp
new file mode 100644
index 00000000..db5e440c
--- /dev/null
+++ b/Source/Online/Message/host_session_flag.cpp
@@ -0,0 +1,74 @@
+//-----------------------------------------------------------------------------
+// Name: host_session_flag.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "host_session_flag.h"
+
+#include <Online/online.h>
+
+namespace OnlineMessages {
+ HostSessionFlag::HostSessionFlag(OnlineFlags flag, bool value) :
+ OnlineMessageBase(OnlineMessageCategory::TRANSIENT),
+ flag(flag), value(value)
+ {
+
+ }
+
+ binn* HostSessionFlag::Serialize(void* object) {
+ HostSessionFlag* hsf = static_cast<HostSessionFlag*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_uint32(l, "f", (uint32_t)hsf->flag);
+ binn_object_set_bool(l, "v", hsf->value);
+
+ return l;
+ }
+
+ void HostSessionFlag::Deserialize(void* object, binn* l) {
+ HostSessionFlag* hsf = static_cast<HostSessionFlag*>(object);
+
+ uint32_t flag;
+ binn_object_get_uint32(l, "f", &flag);
+ hsf->flag = (OnlineFlags)flag;
+
+ BOOL value;
+ binn_object_get_bool(l, "v", &value);
+ hsf->value = (bool)value;
+ }
+
+ void HostSessionFlag::Execute(const OnlineMessageRef& ref, void* object, PeerID peer) {
+ HostSessionFlag* hsf = static_cast<HostSessionFlag*>(object);
+ Online* online = Online::Instance();
+
+ if(online != nullptr && online->online_session != nullptr) {
+ online->online_session->host_session_flags[hsf->flag] = hsf->value;
+ }
+ }
+
+ void* HostSessionFlag::Construct(void* mem) {
+ return new(mem) HostSessionFlag(OnlineFlags::INVALID, false);
+ }
+
+ void HostSessionFlag::Destroy(void* object) {
+ HostSessionFlag* hsf = static_cast<HostSessionFlag*>(object);
+ hsf->~HostSessionFlag();
+ }
+}
diff --git a/Source/Online/Message/host_session_flag.h b/Source/Online/Message/host_session_flag.h
new file mode 100644
index 00000000..5b17680d
--- /dev/null
+++ b/Source/Online/Message/host_session_flag.h
@@ -0,0 +1,43 @@
+//-----------------------------------------------------------------------------
+// Name: host_session_flag.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+#include <Online/online_datastructures.h>
+
+namespace OnlineMessages {
+ class HostSessionFlag : public OnlineMessageBase {
+ public:
+ OnlineFlags flag;
+ bool value;
+
+ public:
+ HostSessionFlag(OnlineFlags flag, bool value);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* l);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID peer);
+ static void* Construct(void *mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/item_update.cpp b/Source/Online/Message/item_update.cpp
new file mode 100644
index 00000000..857c02b6
--- /dev/null
+++ b/Source/Online/Message/item_update.cpp
@@ -0,0 +1,94 @@
+//-----------------------------------------------------------------------------
+// Name: item_update.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "item_update.h"
+
+#include <Utility/binn_util.h>
+#include <Main/engine.h>
+#include <Online/online.h>
+
+extern Timer game_timer;
+
+namespace OnlineMessages {
+ ItemUpdate::ItemUpdate() :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ id(-1), timestamp(0), transform(mat4())
+ {
+
+ }
+
+ ItemUpdate::ItemUpdate(ItemObject* object) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ timestamp(game_timer.GetWallTime()), transform(object->GetTransform())
+ {
+ this->id = Online::Instance()->GetOriginalID(object->GetID());
+ }
+
+ binn* ItemUpdate::Serialize(void* object) {
+ ItemUpdate* iu = static_cast<ItemUpdate*>(object);
+
+ binn* l = binn_object();
+
+ binn_object_set_int32(l, "id", iu->id);
+ binn_object_set_float(l, "ts", iu->timestamp);
+ binn_object_set_mat4(l, "t", iu->transform);
+
+ return l;
+ }
+
+ void ItemUpdate::Deserialize(void* object, binn* l) {
+ ItemUpdate* iu = static_cast<ItemUpdate*>(object);
+
+ binn_object_get_int32(l, "id", &iu->id);
+ binn_object_get_float(l, "ts", &iu->timestamp);
+ binn_object_get_mat4(l, "t", &iu->transform);
+ }
+
+ void ItemUpdate::Execute(const OnlineMessageRef& ref, void* object, PeerID peer) {
+ ItemUpdate* iu = static_cast<ItemUpdate*>(object);
+ ObjectID object_id = Online::Instance()->GetObjectID(iu->id);
+ SceneGraph* sg = Engine::Instance()->GetSceneGraph();
+
+ if(sg != nullptr) {
+ Object* obj = sg->GetObjectFromID(object_id);
+ if(obj) {
+ ItemObject* item = static_cast<ItemObject*>(obj);
+
+ ItemObjectFrame iof;
+
+ iof.host_walltime = iu->timestamp;
+ iof.transform = iu->transform;
+
+ item->incoming_online_item_frames.push_back(iof);
+ }
+ }
+ }
+
+ void* ItemUpdate::Construct(void* mem) {
+ return new(mem) ItemUpdate();
+ }
+
+ void ItemUpdate::Destroy(void* object) {
+ ItemUpdate* iu = static_cast<ItemUpdate*>(object);
+ iu->~ItemUpdate();
+ }
+}
diff --git a/Source/Online/Message/item_update.h b/Source/Online/Message/item_update.h
new file mode 100644
index 00000000..cdc1de78
--- /dev/null
+++ b/Source/Online/Message/item_update.h
@@ -0,0 +1,46 @@
+//-----------------------------------------------------------------------------
+// Name: item_update.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+#include <Online/online_datastructures.h>
+#include <Objects/itemobject.h>
+
+namespace OnlineMessages {
+ class ItemUpdate : public OnlineMessageBase {
+ private:
+ CommonObjectID id;
+ float timestamp;
+ mat4 transform;
+
+ public:
+ ItemUpdate();
+ ItemUpdate(ItemObject* item);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* l);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID peer);
+ static void* Construct(void *mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/material_sound_event.cpp b/Source/Online/Message/material_sound_event.cpp
new file mode 100644
index 00000000..84251b56
--- /dev/null
+++ b/Source/Online/Message/material_sound_event.cpp
@@ -0,0 +1,75 @@
+//-----------------------------------------------------------------------------
+// Name: material_sound_event.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "material_sound_event.h"
+
+#include <Main/engine.h>
+
+namespace OnlineMessages {
+ MaterialSoundEvent::MaterialSoundEvent(ObjectID object_id, const string& ev, const vec3& pos, float gain) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ event_name(ev), pos(pos), gain(gain)
+ {
+ this->object_id = Online::Instance()->GetOriginalID(object_id);
+ }
+
+ binn* MaterialSoundEvent::Serialize(void* object) {
+ MaterialSoundEvent* mse = static_cast<MaterialSoundEvent*>(object);
+
+ binn* l = binn_object();
+
+ binn_object_set_int32(l, "id", mse->object_id);
+ binn_object_set_std_string(l, "event", mse->event_name);
+ binn_object_set_vec3(l, "pos", mse->pos);
+ binn_object_set_float(l, "gain", mse->gain);
+
+ return l;
+ }
+
+ void MaterialSoundEvent::Deserialize(void* object, binn* l) {
+ MaterialSoundEvent* mse = static_cast<MaterialSoundEvent*>(object);
+
+ binn_object_get_int32(l, "id", &mse->object_id);
+ binn_object_get_std_string(l, "event", &mse->event_name);
+ binn_object_get_vec3(l, "pos", &mse->pos);
+ binn_object_get_float(l, "gain", &mse->gain);
+ }
+
+ void MaterialSoundEvent::Execute(const OnlineMessageRef& ref, void* object, PeerID peer) {
+ MaterialSoundEvent* mse = static_cast<MaterialSoundEvent*>(object);
+ ObjectID object_id = Online::Instance()->GetObjectID(mse->object_id);
+
+ SceneGraph* sg = Engine::Instance()->GetSceneGraph();
+
+ if(sg != nullptr) {
+ }
+ }
+
+ void* MaterialSoundEvent::Construct(void* mem) {
+ return new(mem) MaterialSoundEvent(-1, "", vec3(), 0.0f);
+ }
+
+ void MaterialSoundEvent::Destroy(void* object) {
+ MaterialSoundEvent* mse = static_cast<MaterialSoundEvent*>(object);
+ mse->~MaterialSoundEvent();
+ }
+}
diff --git a/Source/Online/Message/material_sound_event.h b/Source/Online/Message/material_sound_event.h
new file mode 100644
index 00000000..52ed5652
--- /dev/null
+++ b/Source/Online/Message/material_sound_event.h
@@ -0,0 +1,52 @@
+//-----------------------------------------------------------------------------
+// Name: material_sound_event.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+#include <Math/vec3.h>
+
+#include <string>
+
+using std::string;
+
+namespace OnlineMessages {
+ class MaterialSoundEvent : public OnlineMessageBase {
+ private:
+ CommonObjectID object_id;
+
+ public:
+ string event_name;
+ vec3 pos;
+ float gain;
+
+ public:
+ MaterialSoundEvent(ObjectID object_id, const string& ev, const vec3& pos, float gain);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* l);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID peer);
+ static void* Construct(void *mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/morph_target_update.cpp b/Source/Online/Message/morph_target_update.cpp
new file mode 100644
index 00000000..48fec29d
--- /dev/null
+++ b/Source/Online/Message/morph_target_update.cpp
@@ -0,0 +1,98 @@
+//-----------------------------------------------------------------------------
+// Name: morph_target_update.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "morph_target_update.h"
+
+#include <Main/engine.h>
+
+namespace OnlineMessages {
+ MorphTargetUpdate::MorphTargetUpdate() :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ object_id(-1), disp_weight(0), mod_weight(0), name({'\0'})
+ {
+ reliable_delivery = false;
+ }
+
+ MorphTargetUpdate::MorphTargetUpdate(ObjectID object_id, const MorphTargetStateStorage& morph_state) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ disp_weight(morph_state.disp_weight), mod_weight(morph_state.mod_weight)
+ {
+ this->object_id = Online::Instance()->GetOriginalID(object_id);
+ strncpy(name.data(), morph_state.name.c_str(), name.size());
+ reliable_delivery = false;
+ }
+
+ binn* MorphTargetUpdate::Serialize(void* object) {
+ MorphTargetUpdate* mtu = static_cast<MorphTargetUpdate*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_int32(l, "o_id", mtu->object_id);
+ binn_object_set_float(l, "dw", mtu->disp_weight);
+ binn_object_set_float(l, "mw", mtu->mod_weight);
+ binn_object_set_str(l, "n", mtu->name.data());
+
+ return l;
+ }
+
+ void MorphTargetUpdate::Deserialize(void* object, binn* l) {
+ MorphTargetUpdate* mtu = static_cast<MorphTargetUpdate*>(object);
+
+ binn_object_get_int32(l, "o_id", &mtu->object_id);
+ binn_object_get_float(l, "dw", &mtu->disp_weight);
+ binn_object_get_float(l, "mw", &mtu->mod_weight);
+
+ char* b_name_str;
+ binn_object_get_str(l, "n", &b_name_str);
+ strncpy(mtu->name.data(), b_name_str, mtu->name.size());
+ }
+
+ void MorphTargetUpdate::Execute(const OnlineMessageRef& ref, void* object, PeerID peer) {
+ MorphTargetUpdate* mtu = static_cast<MorphTargetUpdate*>(object);
+ ObjectID object_id = Online::Instance()->GetObjectID(mtu->object_id);
+ SceneGraph* sg = Engine::Instance()->GetSceneGraph();
+
+ if(sg != nullptr && Online::Instance()->host_started_level) {
+ Object* object = sg->GetObjectFromID(object_id);
+ if (object != nullptr && object->GetType() == EntityType::_movement_object) {
+ MovementObject* mo = static_cast<MovementObject*>(object);
+ MorphTargetStateStorage mtss;
+ mtss.disp_weight = mtu->disp_weight;
+ mtss.mod_weight = mtu->mod_weight;
+ mtss.name = string(mtu->name.data());
+ mo->rigged_object()->incoming_network_morphs[mtss.name] = mtss;
+ } else {
+ LOGW << "Received MorphTargetUpdate, but couldn't find an MovementObject with ID of \"" << object_id << " (" << mtu->object_id << ")\"" << std::endl;
+ }
+ } else {
+ LOGW << "Client received MorphTargetUpdate, but wasn't ready to receive it. The host should not be sending this to us right now!" << std::endl;
+ }
+ }
+
+ void* MorphTargetUpdate::Construct(void *mem) {
+ return new (mem) MorphTargetUpdate();
+ }
+
+ void MorphTargetUpdate::Destroy(void* object) {
+ MorphTargetUpdate* mtu = static_cast<MorphTargetUpdate*>(object);
+ mtu->~MorphTargetUpdate();
+ }
+}
diff --git a/Source/Online/Message/morph_target_update.h b/Source/Online/Message/morph_target_update.h
new file mode 100644
index 00000000..ddc4b229
--- /dev/null
+++ b/Source/Online/Message/morph_target_update.h
@@ -0,0 +1,50 @@
+//-----------------------------------------------------------------------------
+// Name: morph_target_update.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+#include <Online/online_datastructures.h>
+
+#include <array>
+
+using std::array;
+
+namespace OnlineMessages {
+ class MorphTargetUpdate : public OnlineMessageBase {
+ public:
+ CommonObjectID object_id;
+ float disp_weight;
+ float mod_weight;
+ array<char,128> name;
+
+ public:
+ MorphTargetUpdate();
+ MorphTargetUpdate(ObjectID object_id, const MorphTargetStateStorage& morph_state);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* l);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID peer);
+ static void* Construct(void *mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/movement_object_update.cpp b/Source/Online/Message/movement_object_update.cpp
new file mode 100644
index 00000000..d0615744
--- /dev/null
+++ b/Source/Online/Message/movement_object_update.cpp
@@ -0,0 +1,121 @@
+//-----------------------------------------------------------------------------
+// Name: movement_object_update.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "movement_object_update.h"
+
+#include <Utility/binn_util.h>
+#include <Main/engine.h>
+
+namespace OnlineMessages {
+ MovementObjectUpdate::MovementObjectUpdate() :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT)
+ {
+ reliable_delivery = false;
+ }
+
+ MovementObjectUpdate::MovementObjectUpdate(MovementObject* mo) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT)
+ {
+ reliable_delivery = false;
+ RiggedObject* rigged_object = mo->rigged_object();
+
+ position = mo->position;
+ velocity = mo->velocity;
+ identifier = Online::Instance()->GetOriginalID(mo->GetID());
+ timestamp = mo->walltime_last_update;
+ rigged_body_frame.host_walltime = rigged_object->network_bones_host_walltime;
+ facing = mo->GetFacing();
+ rigged_body_frame.bone_count = rigged_object->network_bones.size();
+
+ for(int i = 0; i < rigged_object->network_bones.size() && i < rigged_body_frame.bones.size(); i++) {
+ rigged_body_frame.bones[i] = rigged_object->network_bones[i];
+ }
+ }
+
+ binn* MovementObjectUpdate::Serialize(void* object) {
+ MovementObjectUpdate* mou = static_cast<MovementObjectUpdate*>(object);
+
+ binn* l = binn_object();
+
+ binn_object_set_int32(l, "id", mou->identifier);
+ binn_object_set_float(l, "ts", mou->timestamp);
+ binn_object_set_vec3(l, "p", mou->position);
+ binn_object_set_vec3(l, "v", mou->velocity);
+ binn_object_set_vec3(l, "f", mou->facing);
+ binn* bo_rbf = mou->rigged_body_frame.Serialize();
+ binn_object_set_object(l, "rbf", bo_rbf);
+ binn_free(bo_rbf);
+
+ return l;
+ }
+
+ void MovementObjectUpdate::Deserialize(void* object, binn* l) {
+ MovementObjectUpdate* mou = static_cast<MovementObjectUpdate*>(object);
+
+ binn_object_get_int32(l, "id", &mou->identifier);
+ binn_object_get_float(l, "ts", &mou->timestamp);
+ binn_object_get_vec3(l, "p", &mou->position);
+ binn_object_get_vec3(l, "v", &mou->velocity);
+ binn_object_get_vec3(l, "f", &mou->facing);
+
+ void* bo_rbf;
+ binn_object_get_object(l, "rbf", &bo_rbf);
+ mou->rigged_body_frame.Deserialize((binn*)bo_rbf);
+ }
+
+ void MovementObjectUpdate::Execute(const OnlineMessageRef& object_ref, void* object, PeerID peer) {
+ MovementObjectUpdate* mou = static_cast<MovementObjectUpdate*>(object);
+ ObjectID object_id = Online::Instance()->GetObjectID(mou->identifier);
+ SceneGraph* sg = Engine::Instance()->GetSceneGraph();
+
+ if(sg != nullptr && Online::Instance()->host_started_level) {
+ Object* object = sg->GetObjectFromID(object_id);
+ if (object != nullptr && object->GetType() == EntityType::_movement_object) {
+ MovementObject* mo = static_cast<MovementObject*>(object);
+
+ // Check if we already have an update for the specified time
+ for (auto it = mo->incoming_movement_object_frames.begin(); it != mo->incoming_movement_object_frames.end(); it++) {
+ MovementObjectUpdate* update = static_cast<MovementObjectUpdate*>(it->GetData());
+ if (update->timestamp == mou->timestamp) {
+ LOGW << "Received MovementObjectUpdate with an identical timestamp of an existing update for object: \"" << object_id << " (" << mou->identifier << ")\"" << std::endl;
+ return;
+ }
+ }
+
+ mo->incoming_movement_object_frames.push_back(object_ref);
+ } else {
+ LOGW << "Received MovementObjectUpdate, but couldn't find an MovementObject with ID of \"" << object_id << " (" << mou->identifier << ")\"" << std::endl;
+ }
+ } else {
+ LOGW << "Client received MorphTargetUpdate, but wasn't ready to receive it. The host should not be sending this to us right now!" << std::endl;
+ }
+ }
+
+ void* MovementObjectUpdate::Construct(void *mem) {
+ return new (mem) MovementObjectUpdate();
+ }
+
+ void MovementObjectUpdate::Destroy(void* object) {
+ MovementObjectUpdate *mou = static_cast<MovementObjectUpdate*>(object);
+ mou->~MovementObjectUpdate();
+ }
+}
diff --git a/Source/Online/Message/movement_object_update.h b/Source/Online/Message/movement_object_update.h
new file mode 100644
index 00000000..3a6742df
--- /dev/null
+++ b/Source/Online/Message/movement_object_update.h
@@ -0,0 +1,52 @@
+//-----------------------------------------------------------------------------
+// Name: movement_object_update.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+#include <Online/online_datastructures.h>
+#include <Objects/riggedobject.h>
+#include <Objects/movementobject.h>
+
+namespace OnlineMessages {
+ class MovementObjectUpdate : public OnlineMessageBase {
+ public:
+ float timestamp; // needed, but maybe uint64 bit instead?
+ CommonObjectID identifier;
+
+ vec3 position; // needed for client side culling, either send this or disable culling
+ vec3 velocity;
+ vec3 facing; // Needed for correct facing for audio listener
+ RiggedObjectFrame rigged_body_frame;
+
+ public:
+ MovementObjectUpdate(MovementObject* mo);
+ MovementObjectUpdate();
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* l);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID peer);
+ static void* Construct(void *mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/online_message_base.cpp b/Source/Online/Message/online_message_base.cpp
new file mode 100644
index 00000000..180ad11f
--- /dev/null
+++ b/Source/Online/Message/online_message_base.cpp
@@ -0,0 +1,26 @@
+//-----------------------------------------------------------------------------
+// Name: online_message_base.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "online_message_base.h"
+
+OnlineMessageBase::OnlineMessageBase(OnlineMessageCategory cat) : cat(cat), reliable_delivery(true){
+}
diff --git a/Source/Online/Message/online_message_base.h b/Source/Online/Message/online_message_base.h
new file mode 100644
index 00000000..3a0ab207
--- /dev/null
+++ b/Source/Online/Message/online_message_base.h
@@ -0,0 +1,51 @@
+//-----------------------------------------------------------------------------
+// Name: online_message_base.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+
+#include <Online/online_datastructures.h>
+
+#include <Math/vec3.h>
+
+#include <binn.h>
+
+#include <string>
+
+
+//These are message types, they control the behaviour and allowance of a message
+//this only controls messages from the host to the clients, messages sent from the client are always
+//treated as equivalent to being TRANSIENT, as the client doesn't maintain session state.
+enum class OnlineMessageCategory {
+ TRANSIENT, //Messages sent to a single client before a client has loaded a level and is ready to receive all messages
+ LEVEL_TRANSIENT, //Messages sent to a single or all clients when a level has fully loaded
+ LEVEL_PERSISTENT //Persistent level messages sent to all clients, and future new client who connect.
+};
+
+
+class OnlineMessageBase {
+public:
+ bool reliable_delivery;
+ OnlineMessageCategory cat;
+public:
+ OnlineMessageBase(OnlineMessageCategory cat);
+};
diff --git a/Source/Online/Message/pcs_assign_player_id.cpp b/Source/Online/Message/pcs_assign_player_id.cpp
new file mode 100644
index 00000000..03706e44
--- /dev/null
+++ b/Source/Online/Message/pcs_assign_player_id.cpp
@@ -0,0 +1,64 @@
+//-----------------------------------------------------------------------------
+// Name: pcs_assign_player_id.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "pcs_assign_player_id.h"
+
+#include <Online/online.h>
+#include <Utility/binn_util.h>
+
+namespace OnlineMessages {
+ PCSAssignPlayerID::PCSAssignPlayerID(PlayerID player_id) :
+ OnlineMessageBase(OnlineMessageCategory::TRANSIENT),
+ player_id(player_id)
+ {
+
+ }
+
+ binn* PCSAssignPlayerID::Serialize(void* object) {
+ PCSAssignPlayerID* t = static_cast<PCSAssignPlayerID*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_uint8(l, "player_id", t->player_id);
+
+ return l;
+ }
+
+ void PCSAssignPlayerID::Deserialize(void* object, binn* l) {
+ PCSAssignPlayerID* t = static_cast<PCSAssignPlayerID*>(object);
+
+ binn_object_get_uint8(l, "player_id", &t->player_id);
+ }
+
+ void PCSAssignPlayerID::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ PCSAssignPlayerID* t = static_cast<PCSAssignPlayerID*>(object);
+ Online::Instance()->online_session->local_player_id = t->player_id;
+ }
+
+ void* PCSAssignPlayerID::Construct(void *mem) {
+ return new(mem) PCSAssignPlayerID(0);
+ }
+
+ void PCSAssignPlayerID::Destroy(void* object) {
+ PCSAssignPlayerID* t = static_cast<PCSAssignPlayerID*>(object);
+ t->~PCSAssignPlayerID();
+ }
+}
diff --git a/Source/Online/Message/pcs_assign_player_id.h b/Source/Online/Message/pcs_assign_player_id.h
new file mode 100644
index 00000000..3f5dc3a0
--- /dev/null
+++ b/Source/Online/Message/pcs_assign_player_id.h
@@ -0,0 +1,41 @@
+//-----------------------------------------------------------------------------
+// Name: pcs_assign_player_id.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class PCSAssignPlayerID : public OnlineMessageBase {
+ public:
+ PlayerID player_id;
+
+ public:
+ PCSAssignPlayerID(PlayerID player_id);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/pcs_build_version_message.cpp b/Source/Online/Message/pcs_build_version_message.cpp
new file mode 100644
index 00000000..359fb9e4
--- /dev/null
+++ b/Source/Online/Message/pcs_build_version_message.cpp
@@ -0,0 +1,66 @@
+//-----------------------------------------------------------------------------
+// Name: pcs_build_version_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "pcs_build_version_message.h"
+
+#include <Online/online.h>
+
+namespace OnlineMessages {
+ PCSBuildVersionMessage::PCSBuildVersionMessage(const int32_t build_id) :
+ OnlineMessageBase(OnlineMessageCategory::TRANSIENT),
+ build_id(build_id)
+ {
+
+ }
+
+ binn* PCSBuildVersionMessage::Serialize(void* object) {
+ PCSBuildVersionMessage* t = static_cast<PCSBuildVersionMessage*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_int32(l, "build_id", t->build_id);
+
+ return l;
+ }
+
+ void PCSBuildVersionMessage::Deserialize(void* object, binn* l) {
+ PCSBuildVersionMessage* t = static_cast<PCSBuildVersionMessage*>(object);
+
+ binn_object_get_int32(l, "build_id", &t->build_id);
+ }
+
+ void PCSBuildVersionMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ PCSBuildVersionMessage* t = static_cast<PCSBuildVersionMessage*>(object);
+
+ if (Online::Instance()->IsHosting()) {
+ Online::Instance()->online_session->client_connection_manager.ApplyPackage(from, t);
+ }
+ }
+
+ void* PCSBuildVersionMessage::Construct(void* mem) {
+ return new (mem) PCSBuildVersionMessage(-1);
+ }
+
+ void PCSBuildVersionMessage::Destroy(void* object) {
+ PCSBuildVersionMessage* bvr = static_cast<PCSBuildVersionMessage*>(object);
+ bvr->~PCSBuildVersionMessage();
+ }
+}
diff --git a/Source/Online/Message/pcs_build_version_message.h b/Source/Online/Message/pcs_build_version_message.h
new file mode 100644
index 00000000..a91ebba6
--- /dev/null
+++ b/Source/Online/Message/pcs_build_version_message.h
@@ -0,0 +1,41 @@
+//-----------------------------------------------------------------------------
+// Name: pcs_build_version_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class PCSBuildVersionMessage : public OnlineMessageBase {
+ public:
+ int32_t build_id;
+
+ public:
+ PCSBuildVersionMessage(int32_t build_id);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* l);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void *mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/pcs_build_version_request_message.cpp b/Source/Online/Message/pcs_build_version_request_message.cpp
new file mode 100644
index 00000000..59d2c8e2
--- /dev/null
+++ b/Source/Online/Message/pcs_build_version_request_message.cpp
@@ -0,0 +1,57 @@
+//-----------------------------------------------------------------------------
+// Name: pcs_build_version_request_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "pcs_build_version_request_message.h"
+
+#include <Online/Message/pcs_build_version_message.h>
+#include <Online/online.h>
+
+#include <Version/version.h>
+
+namespace OnlineMessages {
+ PCSBuildVersionRequestMessage::PCSBuildVersionRequestMessage() :
+ OnlineMessageBase(OnlineMessageCategory::TRANSIENT)
+ {
+
+ }
+
+ binn* PCSBuildVersionRequestMessage::Serialize(void* object) {
+ return binn_object();
+ }
+
+ void PCSBuildVersionRequestMessage::Deserialize(void* object, binn* l) {
+
+ }
+
+ void PCSBuildVersionRequestMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ Online::Instance()->SendTo<OnlineMessages::PCSBuildVersionMessage>(Online::Instance()->GetPeerFromID(from)->conn_id, GetBuildID());
+ }
+
+ void* PCSBuildVersionRequestMessage::Construct(void* mem) {
+ return new (mem) PCSBuildVersionRequestMessage();
+ }
+
+ void PCSBuildVersionRequestMessage::Destroy(void* object) {
+ PCSBuildVersionRequestMessage* bvr = static_cast<PCSBuildVersionRequestMessage*>(object);
+ bvr->~PCSBuildVersionRequestMessage();
+ }
+}
diff --git a/Source/Online/Message/pcs_build_version_request_message.h b/Source/Online/Message/pcs_build_version_request_message.h
new file mode 100644
index 00000000..538024c1
--- /dev/null
+++ b/Source/Online/Message/pcs_build_version_request_message.h
@@ -0,0 +1,40 @@
+//-----------------------------------------------------------------------------
+// Name: pcs_build_version_request_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class PCSBuildVersionRequestMessage : public OnlineMessageBase {
+ private:
+
+ public:
+ PCSBuildVersionRequestMessage();
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* l);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void *mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/pcs_client_parameters_message.cpp b/Source/Online/Message/pcs_client_parameters_message.cpp
new file mode 100644
index 00000000..64ad7078
--- /dev/null
+++ b/Source/Online/Message/pcs_client_parameters_message.cpp
@@ -0,0 +1,68 @@
+//-----------------------------------------------------------------------------
+// Name: pcs_client_parameters_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "pcs_client_parameters_message.h"
+
+#include <Online/online.h>
+#include <Utility/binn_util.h>
+
+namespace OnlineMessages {
+ PCSClientParametersMessage::PCSClientParametersMessage(const std::string& player_name, const std::string& enabled_mods_string) :
+ OnlineMessageBase(OnlineMessageCategory::TRANSIENT),
+ player_name(player_name), enabled_mods_string(enabled_mods_string)
+ {
+
+ }
+
+ binn* PCSClientParametersMessage::Serialize(void* object) {
+ PCSClientParametersMessage* t = static_cast<PCSClientParametersMessage*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_std_string(l, "player_name", t->player_name);
+ binn_object_set_std_string(l, "enabled_mods_string", t->enabled_mods_string);
+
+ return l;
+ }
+
+ void PCSClientParametersMessage::Deserialize(void* object, binn* l) {
+ PCSClientParametersMessage* t = static_cast<PCSClientParametersMessage*>(object);
+
+ binn_object_get_std_string(l, "player_name", &t->player_name);
+ binn_object_get_std_string(l, "enabled_mods_string", &t->enabled_mods_string);
+ }
+
+ void PCSClientParametersMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ PCSClientParametersMessage* t = static_cast<PCSClientParametersMessage*>(object);
+
+ assert(Online::Instance()->IsHosting());
+ Online::Instance()->online_session->client_connection_manager.ApplyPackage(from, t);
+ }
+
+ void* PCSClientParametersMessage::Construct(void *mem) {
+ return new(mem) PCSClientParametersMessage("", "");
+ }
+
+ void PCSClientParametersMessage::Destroy(void* object) {
+ PCSClientParametersMessage* t = static_cast<PCSClientParametersMessage*>(object);
+ t->~PCSClientParametersMessage();
+ }
+}
diff --git a/Source/Online/Message/pcs_client_parameters_message.h b/Source/Online/Message/pcs_client_parameters_message.h
new file mode 100644
index 00000000..1f7167bc
--- /dev/null
+++ b/Source/Online/Message/pcs_client_parameters_message.h
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------------
+// Name: pcs_client_parameters_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class PCSClientParametersMessage : public OnlineMessageBase {
+ public:
+ std::string player_name;
+ std::string enabled_mods_string;
+
+ public:
+ PCSClientParametersMessage(const std::string& player_name, const std::string& enabled_mods_string);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/pcs_file_transfer_metadata_message.cpp b/Source/Online/Message/pcs_file_transfer_metadata_message.cpp
new file mode 100644
index 00000000..5fb412b0
--- /dev/null
+++ b/Source/Online/Message/pcs_file_transfer_metadata_message.cpp
@@ -0,0 +1,87 @@
+//-----------------------------------------------------------------------------
+// Name: pcs_file_transfer_metadata_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "pcs_file_transfer_metadata_message.h"
+
+#include <Online/online.h>
+#include <Online/online_utility.h>
+
+#include <Main/engine.h>
+#include <Internal/modloading.h>
+#include <Utility/binn_util.h>
+
+#include <string>
+
+namespace OnlineMessages {
+ PCSFileTransferMetadataMessage::PCSFileTransferMetadataMessage(const std::string& map_name, const std::string& campaign_id) :
+ OnlineMessageBase(OnlineMessageCategory::TRANSIENT),
+ map_name(map_name), campaign_id(campaign_id)
+ {
+
+ }
+
+ binn* PCSFileTransferMetadataMessage::Serialize(void* object) {
+ PCSFileTransferMetadataMessage* t = static_cast<PCSFileTransferMetadataMessage*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_std_string(l, "map_name", t->map_name);
+ binn_object_set_std_string(l, "campaign_id", t->campaign_id);
+
+ return l;
+ }
+
+ void PCSFileTransferMetadataMessage::Deserialize(void* object, binn* l) {
+ PCSFileTransferMetadataMessage* t = static_cast<PCSFileTransferMetadataMessage*>(object);
+
+ binn_object_get_std_string(l, "map_name", &t->map_name);
+ binn_object_get_std_string(l, "campaign_id", &t->campaign_id);
+ }
+
+ void PCSFileTransferMetadataMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ PCSFileTransferMetadataMessage* t = static_cast<PCSFileTransferMetadataMessage*>(object);
+
+ // We now know what level the host wants us to load. Host hasn't actually send us the level for now.
+ // This is placeholder behaviour, as we'd now want to wait for the host to send us a bunch of files
+ // Due to the required refactor for this behaviour, we can just start loading what we assume is the map the host would have sent us.
+
+ Online::Instance()->level_name = t->map_name;
+ if (ModLoading::Instance().WhichCampaignLevelBelongsTo(t->map_name) != "") {
+ Online::Instance()->online_session->free_avatars.clear();
+
+ Engine::Instance()->ScriptableUICallback("set_campaign " + t->campaign_id);
+ Engine::Instance()->ScriptableUICallback(t->map_name);
+ } else {
+ LOGE << "Unabled to load level: \"" + t->map_name + "\" in campaign: " + t->campaign_id << std::endl;
+ Online::Instance()->QueueStopMultiplayer();
+ Engine::Instance()->QueueErrorMessage("Error", "Unabled to load level: \"" + t->map_name + "\" in campaign: " + t->campaign_id);
+ }
+ }
+
+ void* PCSFileTransferMetadataMessage::Construct(void *mem) {
+ return new(mem) PCSFileTransferMetadataMessage("", "");
+ }
+
+ void PCSFileTransferMetadataMessage::Destroy(void* object) {
+ PCSFileTransferMetadataMessage* t = static_cast<PCSFileTransferMetadataMessage*>(object);
+ t->~PCSFileTransferMetadataMessage();
+ }
+}
diff --git a/Source/Online/Message/pcs_file_transfer_metadata_message.h b/Source/Online/Message/pcs_file_transfer_metadata_message.h
new file mode 100644
index 00000000..8f29d92e
--- /dev/null
+++ b/Source/Online/Message/pcs_file_transfer_metadata_message.h
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------------
+// Name: pcs_file_transfer_metadata_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class PCSFileTransferMetadataMessage : public OnlineMessageBase {
+ private:
+ std::string map_name;
+ std::string campaign_id;
+
+ public:
+ PCSFileTransferMetadataMessage(const std::string& map_name, const std::string& campaign_id);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/pcs_loading_completed_message.cpp b/Source/Online/Message/pcs_loading_completed_message.cpp
new file mode 100644
index 00000000..5a527b62
--- /dev/null
+++ b/Source/Online/Message/pcs_loading_completed_message.cpp
@@ -0,0 +1,74 @@
+//-----------------------------------------------------------------------------
+// Name: pcs_loading_completed_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "pcs_loading_completed_message.h"
+
+#include <Online/online.h>
+#include <Utility/binn_util.h>
+
+namespace OnlineMessages {
+ PCSLoadingCompletedMessage::PCSLoadingCompletedMessage() :
+ OnlineMessageBase(OnlineMessageCategory::TRANSIENT)
+ {
+ level_name = Online::Instance()->level_name;
+ }
+
+ binn* PCSLoadingCompletedMessage::Serialize(void* object) {
+ PCSLoadingCompletedMessage* t = static_cast<PCSLoadingCompletedMessage*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_std_string(l, "level_name", t->level_name);
+
+ return l;
+ }
+
+ void PCSLoadingCompletedMessage::Deserialize(void* object, binn* l) {
+ PCSLoadingCompletedMessage* t = static_cast<PCSLoadingCompletedMessage*>(object);
+
+ binn_object_get_std_string(l, "level_name", &t->level_name);
+ }
+
+ void PCSLoadingCompletedMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ PCSLoadingCompletedMessage* t = static_cast<PCSLoadingCompletedMessage*>(object);
+
+ if (Online::Instance()->IsHosting()) {
+ if (Online::Instance()->level_name == t->level_name) {
+ // A client just told us (host) they are done loading, update actor in state machine
+ Online::Instance()->online_session->client_connection_manager.ApplyPackage(from, t);
+ } else {
+ LOGW << "Client completed loading \"" << t->level_name << "\", but we are in \"" << Online::Instance()->level_name << "\"" << std::endl;
+ }
+ } else {
+ // Host started game session. We are about to get carpet bombed with packages!
+ Online::Instance()->SessionStarted(true);
+ }
+ }
+
+ void* PCSLoadingCompletedMessage::Construct(void *mem) {
+ return new(mem) PCSLoadingCompletedMessage();
+ }
+
+ void PCSLoadingCompletedMessage::Destroy(void* object) {
+ PCSLoadingCompletedMessage* t = static_cast<PCSLoadingCompletedMessage*>(object);
+ t->~PCSLoadingCompletedMessage();
+ }
+}
diff --git a/Source/Online/Message/pcs_loading_completed_message.h b/Source/Online/Message/pcs_loading_completed_message.h
new file mode 100644
index 00000000..aa1dce56
--- /dev/null
+++ b/Source/Online/Message/pcs_loading_completed_message.h
@@ -0,0 +1,41 @@
+//-----------------------------------------------------------------------------
+// Name: pcs_loading_completed_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class PCSLoadingCompletedMessage : public OnlineMessageBase {
+ private:
+ std::string level_name;
+
+ public:
+ PCSLoadingCompletedMessage();
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/pcs_session_parameters_message.cpp b/Source/Online/Message/pcs_session_parameters_message.cpp
new file mode 100644
index 00000000..8fbfd4e8
--- /dev/null
+++ b/Source/Online/Message/pcs_session_parameters_message.cpp
@@ -0,0 +1,100 @@
+//-----------------------------------------------------------------------------
+// Name: pcs_session_parameters_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "pcs_session_parameters_message.h"
+
+#include <Online/online.h>
+#include <Online/online_utility.h>
+
+#include <Utility/binn_util.h>
+
+namespace OnlineMessages {
+ PCSSessionParametersMessage::PCSSessionParametersMessage(std::map<std::string, uint8_t> bind_id_map) :
+ OnlineMessageBase(OnlineMessageCategory::TRANSIENT),
+ bind_id_map(bind_id_map)
+ {
+
+ }
+
+ binn* PCSSessionParametersMessage::Serialize(void* object) {
+ PCSSessionParametersMessage* t = static_cast<PCSSessionParametersMessage*>(object);
+ binn* l = binn_object();
+
+ binn* list;
+ list = binn_list();
+
+ for (auto& binding : t->bind_id_map) {
+ binn_list_add_std_string(list, binding.first);
+ binn_list_add_uint8(list, binding.second);
+ }
+ binn_object_set_list(l, "bindings", list);
+ return l;
+ }
+
+ void PCSSessionParametersMessage::Deserialize(void* object, binn* l) {
+ PCSSessionParametersMessage* t = static_cast<PCSSessionParametersMessage*>(object);
+
+ binn* list;
+ binn_object_get_list(l, "bindings", (void**) &list);
+
+ t->bind_id_map.clear();
+
+ int count = binn_count(list);
+ std::string key;
+ uint8_t value;
+ for (int i = 1; i <= count; i+=2) {
+ binn_list_get_std_string(list, i, &key);
+ binn_list_get_uint8(list, i + 1, &value);
+ t->bind_id_map.emplace(key, value);
+ }
+ }
+
+ void PCSSessionParametersMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ PCSSessionParametersMessage* t = static_cast<PCSSessionParametersMessage*>(object);
+
+ // This should never run on the host, kill connections that try to make us
+ if (Online::Instance()->IsHosting()) {
+ Online::Instance()->CloseConnection(Online::Instance()->GetPeerFromID(from)->conn_id, ConnectionClosedReason::BAD_REQUEST);
+ LOGW << "A client just told us to discard the current session. This is something the client should never do. Closing connection" << std::endl;
+ return;
+ }
+
+ // We should forget about certain things here!
+ Online::Instance()->online_session->player_states.clear();
+ Online::Instance()->SessionStarted(false);
+
+ Online::Instance()->online_session->binding_id_map_ = t->bind_id_map;
+
+ // Send own parameters to host
+ Online::Instance()->Send<OnlineMessages::PCSClientParametersMessage>(OnlineUtility::GetPlayerName(), OnlineUtility::GetActiveModsString());
+ }
+
+ void* PCSSessionParametersMessage::Construct(void *mem) {
+ std::map<std::string, uint8_t> bind_id_map;
+ return new(mem) PCSSessionParametersMessage(bind_id_map);
+ }
+
+ void PCSSessionParametersMessage::Destroy(void* object) {
+ PCSSessionParametersMessage* t = static_cast<PCSSessionParametersMessage*>(object);
+ t->~PCSSessionParametersMessage();
+ }
+}
diff --git a/Source/Online/Message/pcs_session_parameters_message.h b/Source/Online/Message/pcs_session_parameters_message.h
new file mode 100644
index 00000000..7e6e2156
--- /dev/null
+++ b/Source/Online/Message/pcs_session_parameters_message.h
@@ -0,0 +1,41 @@
+//-----------------------------------------------------------------------------
+// Name: pcs_session_parameters_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class PCSSessionParametersMessage : public OnlineMessageBase {
+ private:
+ std::map<std::string, uint8_t> bind_id_map;
+
+ public:
+ PCSSessionParametersMessage(std::map<std::string, uint8_t> bind_id_map);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/ping.cpp b/Source/Online/Message/ping.cpp
new file mode 100644
index 00000000..fcb8a886
--- /dev/null
+++ b/Source/Online/Message/ping.cpp
@@ -0,0 +1,66 @@
+//-----------------------------------------------------------------------------
+// Name: ping.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "ping.h"
+
+#include <Online/online.h>
+
+namespace OnlineMessages {
+ Ping::Ping(uint32_t ping_id) :
+ OnlineMessageBase(OnlineMessageCategory::TRANSIENT),
+ ping_id(ping_id)
+ {
+
+ }
+
+ binn* Ping::Serialize(void* object) {
+ Ping *p = static_cast<Ping*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_uint32(l, "id", p->ping_id);
+
+ return l;
+ }
+
+ void Ping::Deserialize(void* object, binn* l) {
+ Ping *p = static_cast<Ping*>(object);
+
+ binn_object_get_uint32(l, "id", &p->ping_id);
+ }
+
+ void Ping::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ Ping *p = static_cast<Ping*>(object);
+ Peer* peer = Online::Instance()->GetPeerFromID(from);
+ if(peer != nullptr) {
+ Online::Instance()->SendTo<OnlineMessages::Pong>(peer->conn_id, p->ping_id);
+ }
+ }
+
+ void* Ping::Construct(void* mem) {
+ return new(mem) Ping(std::numeric_limits<uint32_t>::max());
+ }
+
+ void Ping::Destroy(void* object) {
+ Ping *p = static_cast<Ping*>(object);
+ p->~Ping();
+ }
+}
diff --git a/Source/Online/Message/ping.h b/Source/Online/Message/ping.h
new file mode 100644
index 00000000..a81fcb52
--- /dev/null
+++ b/Source/Online/Message/ping.h
@@ -0,0 +1,41 @@
+//-----------------------------------------------------------------------------
+// Name: ping.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class Ping : public OnlineMessageBase {
+ public:
+ uint32_t ping_id;
+
+ public:
+ Ping(uint32_t ping_id);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/player_input_message.cpp b/Source/Online/Message/player_input_message.cpp
new file mode 100644
index 00000000..2ff492eb
--- /dev/null
+++ b/Source/Online/Message/player_input_message.cpp
@@ -0,0 +1,78 @@
+//-----------------------------------------------------------------------------
+// Name: player_input_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "player_input_message.h"
+
+#include <Main/engine.h>
+#include <Online/online.h>
+#include <Utility/binn_util.h>
+
+namespace OnlineMessages {
+ PlayerInputMessage::PlayerInputMessage(float depth, uint16_t depth_count, uint16_t count, uint8_t id) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ depth(depth), depth_count(depth_count), count(count), id(id)
+ {
+
+ }
+
+ binn* PlayerInputMessage::Serialize(void* object) {
+ PlayerInputMessage* t = static_cast<PlayerInputMessage*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_float(l, "depth", t->depth);
+ binn_object_set_uint16(l, "depth_count", t->depth_count);
+ binn_object_set_uint16(l, "count", t->count);
+ binn_object_set_uint8(l, "id", t->id);
+
+ return l;
+ }
+
+ void PlayerInputMessage::Deserialize(void* object, binn* l) {
+ PlayerInputMessage* t = static_cast<PlayerInputMessage*>(object);
+
+ binn_object_get_float(l, "depth", &t->depth);
+ binn_object_get_uint16(l, "depth_count", &t->depth_count);
+ binn_object_get_uint16(l, "count", &t->count);
+ binn_object_get_uint8(l, "id", &t->id);
+ }
+
+ void PlayerInputMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ PlayerInputMessage* t = static_cast<PlayerInputMessage*>(object);
+
+ // Don't process "quit"
+ if(t->id == Online::Instance()->GetBindID("quit")) {
+ return;
+ }
+
+ // TODO We'll need to replace PeerID with a player ID in order to support multiple inputs
+ Online::Instance()->online_session->player_inputs[from].push_back(ref);
+ }
+
+ void* PlayerInputMessage::Construct(void *mem) {
+ return new(mem) PlayerInputMessage(0, 0, 0, 0);
+ }
+
+ void PlayerInputMessage::Destroy(void* object) {
+ PlayerInputMessage* t = static_cast<PlayerInputMessage*>(object);
+ t->~PlayerInputMessage();
+ }
+}
diff --git a/Source/Online/Message/player_input_message.h b/Source/Online/Message/player_input_message.h
new file mode 100644
index 00000000..13161ebf
--- /dev/null
+++ b/Source/Online/Message/player_input_message.h
@@ -0,0 +1,44 @@
+//-----------------------------------------------------------------------------
+// Name: player_input_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class PlayerInputMessage : public OnlineMessageBase {
+ public:
+ float depth;
+ uint16_t depth_count;
+ uint16_t count;
+ uint8_t id;
+
+ public:
+ PlayerInputMessage(float depth, uint16_t depth_count, uint16_t count, uint8_t id);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/pong.cpp b/Source/Online/Message/pong.cpp
new file mode 100644
index 00000000..eca47e2f
--- /dev/null
+++ b/Source/Online/Message/pong.cpp
@@ -0,0 +1,71 @@
+//-----------------------------------------------------------------------------
+// Name: pong.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "pong.h"
+
+#include <Online/online.h>
+#include <Internal/timer.h>
+
+extern Timer game_timer;
+
+namespace OnlineMessages {
+ Pong::Pong(uint32_t ping_id) :
+ OnlineMessageBase(OnlineMessageCategory::TRANSIENT),
+ ping_id(ping_id)
+ {
+
+ }
+
+ binn* Pong::Serialize(void* object) {
+ Pong *p = static_cast<Pong*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_uint32(l, "id", p->ping_id);
+
+ return l;
+ }
+
+ void Pong::Deserialize(void* object, binn* l) {
+ Pong *p = static_cast<Pong*>(object);
+
+ binn_object_get_uint32(l, "id", &p->ping_id);
+ }
+
+ void Pong::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ Pong* p = static_cast<Pong*>(object);
+ Online* online = Online::Instance();
+
+ Peer* peer = online->GetPeerFromID(from);
+ if(peer != nullptr && online->online_session->last_ping_id == p->ping_id) {
+ peer->current_ping_delta = game_timer.wall_time - online->online_session->last_ping_time;
+ }
+ }
+
+ void* Pong::Construct(void* mem) {
+ return new(mem) Pong(std::numeric_limits<uint32_t>::max());
+ }
+
+ void Pong::Destroy(void* object) {
+ Pong *p = static_cast<Pong*>(object);
+ p->~Pong();
+ }
+}
diff --git a/Source/Online/Message/pong.h b/Source/Online/Message/pong.h
new file mode 100644
index 00000000..49543bd4
--- /dev/null
+++ b/Source/Online/Message/pong.h
@@ -0,0 +1,41 @@
+//-----------------------------------------------------------------------------
+// Name: pong.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class Pong : public OnlineMessageBase {
+ public:
+ uint32_t ping_id;
+
+ public:
+ Pong(uint32_t ping_id);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/remove_object.cpp b/Source/Online/Message/remove_object.cpp
new file mode 100644
index 00000000..1d60d705
--- /dev/null
+++ b/Source/Online/Message/remove_object.cpp
@@ -0,0 +1,81 @@
+//-----------------------------------------------------------------------------
+// Name: remove_object.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "remove_object.h"
+
+#include <Main/engine.h>
+#include <Online/online.h>
+#include <Editors/map_editor.h>
+
+namespace OnlineMessages {
+ RemoveObject::RemoveObject(ObjectID id, EntityType type) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_PERSISTENT),
+ type(type)
+ {
+ this->id = Online::Instance()->GetOriginalID(id);
+ }
+
+ binn* RemoveObject::Serialize(void* object) {
+ RemoveObject* ro = static_cast<RemoveObject*>(object);
+
+ binn* l = binn_object();
+
+ binn_object_set_int32(l, "id", ro->id);
+ binn_object_set_int32(l, "type", ro->type);
+
+ return l;
+ }
+
+ void RemoveObject::Deserialize(void* object, binn* l) {
+ RemoveObject* ro = static_cast<RemoveObject*>(object);
+
+ binn_object_get_int32(l, "id", &ro->id);
+ int32_t type;
+ binn_object_get_int32(l, "type", &type);
+ ro->type = (EntityType)type;
+ }
+
+ void RemoveObject::Execute(const OnlineMessageRef& ref, void* object, PeerID peer) {
+ RemoveObject* ro = static_cast<RemoveObject*>(object);
+ ObjectID object_id = Online::Instance()->GetObjectID(ro->id);
+ SceneGraph* sg = Engine::Instance()->GetSceneGraph();
+
+ if(sg != nullptr) {
+ Object* o = sg->GetObjectFromID(object_id);
+ if (o != nullptr) {
+ sg->map_editor->RemoveObject(o, sg, true);
+ Online::Instance()->DeRegisterHostClientIDTranslation(object_id);
+ } else {
+ LOGW << "Was asked to delete object: " << object_id << " (" << ro->id << "), but was unable to find it";
+ }
+ }
+ }
+
+ void* RemoveObject::Construct(void* mem) {
+ return new(mem) RemoveObject((ObjectID)-1,(EntityType)0);
+ }
+
+ void RemoveObject::Destroy(void* object) {
+ RemoveObject* ro = static_cast<RemoveObject*>(object);
+ ro->~RemoveObject();
+ }
+}
diff --git a/Source/Online/Message/remove_object.h b/Source/Online/Message/remove_object.h
new file mode 100644
index 00000000..f28c6785
--- /dev/null
+++ b/Source/Online/Message/remove_object.h
@@ -0,0 +1,43 @@
+//-----------------------------------------------------------------------------
+// Name: remove_object.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class RemoveObject : public OnlineMessageBase {
+ private:
+ CommonObjectID id;
+ EntityType type;
+
+ public:
+ RemoveObject(ObjectID id, EntityType type);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* l);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID peer);
+
+ static void* Construct(void *mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/remove_player_state.cpp b/Source/Online/Message/remove_player_state.cpp
new file mode 100644
index 00000000..671f4687
--- /dev/null
+++ b/Source/Online/Message/remove_player_state.cpp
@@ -0,0 +1,67 @@
+//-----------------------------------------------------------------------------
+// Name: remove_player_state.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "remove_player_state.h"
+
+#include <Online/online.h>
+#include <Utility/binn_util.h>
+
+namespace OnlineMessages {
+ RemovePlayerState::RemovePlayerState(PlayerID player_id) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ player_id(player_id)
+ {
+
+ }
+
+ binn* RemovePlayerState::Serialize(void* object) {
+ RemovePlayerState* t = static_cast<RemovePlayerState*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_uint8(l, "player_id", t->player_id);
+
+ return l;
+ }
+
+ void RemovePlayerState::Deserialize(void* object, binn* l) {
+ RemovePlayerState* t = static_cast<RemovePlayerState*>(object);
+
+ binn_object_get_uint8(l, "player_id", &t->player_id);
+ }
+
+ void RemovePlayerState::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ RemovePlayerState* t = static_cast<RemovePlayerState*>(object);
+
+ if (Online::Instance()->online_session->player_states.find(t->player_id) != Online::Instance()->online_session->player_states.end()) {
+ Online::Instance()->online_session->player_states.erase(t->player_id);
+ }
+ }
+
+ void* RemovePlayerState::Construct(void *mem) {
+ return new(mem) RemovePlayerState(0);
+ }
+
+ void RemovePlayerState::Destroy(void* object) {
+ RemovePlayerState* t = static_cast<RemovePlayerState*>(object);
+ t->~RemovePlayerState();
+ }
+}
diff --git a/Source/Online/Message/remove_player_state.h b/Source/Online/Message/remove_player_state.h
new file mode 100644
index 00000000..b66c29c1
--- /dev/null
+++ b/Source/Online/Message/remove_player_state.h
@@ -0,0 +1,41 @@
+//-----------------------------------------------------------------------------
+// Name: remove_player_state.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class RemovePlayerState : public OnlineMessageBase {
+ private:
+ PlayerID player_id;
+
+ public:
+ RemovePlayerState(PlayerID player_id);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/send_level_message.cpp b/Source/Online/Message/send_level_message.cpp
new file mode 100644
index 00000000..9db40c84
--- /dev/null
+++ b/Source/Online/Message/send_level_message.cpp
@@ -0,0 +1,72 @@
+//-----------------------------------------------------------------------------
+// Name: send_level_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "send_level_message.h"
+
+#include <Main/engine.h>
+#include <Game/level.h>
+
+namespace OnlineMessages {
+ SendLevelMessage::SendLevelMessage(string msg) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ msg(msg)
+ {
+
+ }
+
+ binn* SendLevelMessage::Serialize(void* object) {
+ binn* l = binn_object();
+
+ SendLevelMessage* lm = static_cast<SendLevelMessage*>(object);
+
+ binn_object_set_std_string(l, "msg", lm->msg);
+
+ return l;
+ }
+
+ void SendLevelMessage::Deserialize(void* object, binn* l) {
+ SendLevelMessage* lm = static_cast<SendLevelMessage*>(object);
+
+ binn_object_get_std_string(l, "msg", &lm->msg);
+ }
+
+ void SendLevelMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID peer) {
+ SendLevelMessage* lm = static_cast<SendLevelMessage*>(object);
+
+ SceneGraph* sg = Engine::Instance()->GetSceneGraph();
+
+ if(sg != nullptr) {
+ sg->level->Message(lm->msg);
+ } else {
+ LOGW << "Received level message: \"" << lm->msg << "\" but SceneGraph is nullptr" << std::endl;
+ }
+ }
+
+ void* SendLevelMessage::Construct(void* mem) {
+ return new(mem) SendLevelMessage("");
+ }
+
+ void SendLevelMessage::Destroy(void* object) {
+ SendLevelMessage* lm = static_cast<SendLevelMessage*>(object);
+ lm->~SendLevelMessage();
+ }
+}
diff --git a/Source/Online/Message/send_level_message.h b/Source/Online/Message/send_level_message.h
new file mode 100644
index 00000000..21676c3c
--- /dev/null
+++ b/Source/Online/Message/send_level_message.h
@@ -0,0 +1,45 @@
+//-----------------------------------------------------------------------------
+// Name: send_level_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+#include <string>
+
+using std::string;
+
+namespace OnlineMessages {
+ class SendLevelMessage : public OnlineMessageBase {
+ private:
+ string msg;
+
+ public:
+ SendLevelMessage(string msg);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* l);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID peer);
+ static void* Construct(void *mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/set_avatar_palette.cpp b/Source/Online/Message/set_avatar_palette.cpp
new file mode 100644
index 00000000..5754564c
--- /dev/null
+++ b/Source/Online/Message/set_avatar_palette.cpp
@@ -0,0 +1,80 @@
+//-----------------------------------------------------------------------------
+// Name: set_avatar_palette.cpp
+// Developer: Wolfire Games LLC
+// Description: Host telling client that palette of movementobject has changed
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "set_avatar_palette.h"
+
+#include <Main/engine.h>
+
+namespace OnlineMessages {
+ SetAvatarPalette::SetAvatarPalette(ObjectID id, const vec3& color, uint8_t channel) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_PERSISTENT),
+ color(color), channel(channel)
+ {
+ this->id = Online::Instance()->GetOriginalID(id);
+ }
+
+ binn* SetAvatarPalette::Serialize(void* object) {
+ SetAvatarPalette* t = static_cast<SetAvatarPalette*>(object);
+
+ binn* l = binn_object();
+
+ binn_object_set_int32(l, "id", t->id);
+ binn_object_set_vec3(l, "color", t->color);
+ binn_object_set_uint8(l, "channel", t->channel);
+
+ return l;
+ }
+
+ void SetAvatarPalette::Deserialize(void* object, binn* l) {
+ SetAvatarPalette* t = static_cast<SetAvatarPalette*>(object);
+
+ binn_object_get_int32(l, "id", &t->id);
+ binn_object_get_vec3(l, "color", &t->color);
+ binn_object_get_uint8(l, "channel", &t->channel);
+ }
+
+ void SetAvatarPalette::Execute(const OnlineMessageRef& ref, void* object, PeerID peer) {
+ SetAvatarPalette* sap = static_cast<SetAvatarPalette*>(object);
+ ObjectID object_id = Online::Instance()->GetObjectID(sap->id);
+ SceneGraph* sg = Engine::Instance()->GetSceneGraph();
+
+ if(sg != nullptr) {
+ Object * o = sg->GetObjectFromID(object_id);
+ if (o != nullptr && o->GetType() == _movement_object) {
+ MovementObject * mov = static_cast<MovementObject*>(o);
+ OGPalette palette(*mov->GetPalette());
+ palette[sap->channel].color = sap->color;
+
+ mov->ApplyPalette(palette, true);
+ }
+ }
+ }
+
+ void* SetAvatarPalette::Construct(void* mem) {
+ return new(mem) SetAvatarPalette(0, vec3(), 0);
+ }
+
+ void SetAvatarPalette::Destroy(void* object) {
+ SetAvatarPalette* sap = static_cast<SetAvatarPalette*>(object);
+ sap->~SetAvatarPalette();
+ }
+}
diff --git a/Source/Online/Message/set_avatar_palette.h b/Source/Online/Message/set_avatar_palette.h
new file mode 100644
index 00000000..3713dac6
--- /dev/null
+++ b/Source/Online/Message/set_avatar_palette.h
@@ -0,0 +1,44 @@
+//-----------------------------------------------------------------------------
+// Name: set_avatar_palette.h
+// Developer: Wolfire Games LLC
+// Description: Host telling client that palette of movementobject has changed
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class SetAvatarPalette : public OnlineMessageBase {
+ private:
+ CommonObjectID id;
+ vec3 color;
+ uint8_t channel;
+
+ public:
+ SetAvatarPalette(ObjectID id, const vec3& color, uint8_t channel);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* l);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID peer);
+
+ static void* Construct(void *mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/set_object_enabled_message.cpp b/Source/Online/Message/set_object_enabled_message.cpp
new file mode 100644
index 00000000..6d543bb0
--- /dev/null
+++ b/Source/Online/Message/set_object_enabled_message.cpp
@@ -0,0 +1,77 @@
+//-----------------------------------------------------------------------------
+// Name: set_object_enabled_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "set_object_enabled_message.h"
+
+#include <Main/engine.h>
+#include <Online/online.h>
+#include <Utility/binn_util.h>
+
+namespace OnlineMessages {
+ SetObjectEnabledMessage::SetObjectEnabledMessage(ObjectID object_id, bool is_enabled) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_PERSISTENT),
+ is_enabled(is_enabled)
+ {
+
+ this->object_id = Online::Instance()->GetOriginalID(object_id);
+ }
+
+ binn* SetObjectEnabledMessage::Serialize(void* object) {
+ SetObjectEnabledMessage* t = static_cast<SetObjectEnabledMessage*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_int32(l, "object_id", t->object_id);
+ binn_object_set_bool(l, "is_enabled", t->is_enabled);
+
+ return l;
+ }
+
+ void SetObjectEnabledMessage::Deserialize(void* object, binn* l) {
+ SetObjectEnabledMessage* t = static_cast<SetObjectEnabledMessage*>(object);
+ BOOL temp;
+ binn_object_get_bool(l, "is_enabled", &temp);
+ binn_object_get_int32(l, "object_id", &t->object_id);
+
+ t->is_enabled = temp;
+ }
+
+ void SetObjectEnabledMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ SetObjectEnabledMessage* t = static_cast<SetObjectEnabledMessage*>(object);
+ ObjectID object_id = Online::Instance()->GetObjectID(t->object_id);
+
+ Object* obj = Engine::Instance()->GetSceneGraph()->GetObjectFromID(object_id);
+ if(obj) {
+ obj->SetEnabled(t->is_enabled);
+ } else {
+ LOGW << "We were sent an update for \"" << object_id << " (" << t->object_id << ")\", but were not able to apply it due to not finding said object" << endl;
+ }
+ }
+
+ void* SetObjectEnabledMessage::Construct(void *mem) {
+ return new(mem) SetObjectEnabledMessage(0, true);
+ }
+
+ void SetObjectEnabledMessage::Destroy(void* object) {
+ SetObjectEnabledMessage* t = static_cast<SetObjectEnabledMessage*>(object);
+ t->~SetObjectEnabledMessage();
+ }
+}
diff --git a/Source/Online/Message/set_object_enabled_message.h b/Source/Online/Message/set_object_enabled_message.h
new file mode 100644
index 00000000..59ea4302
--- /dev/null
+++ b/Source/Online/Message/set_object_enabled_message.h
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------------
+// Name: set_object_enabled_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class SetObjectEnabledMessage : public OnlineMessageBase {
+ private:
+ CommonObjectID object_id;
+ bool is_enabled;
+
+ public:
+ SetObjectEnabledMessage(ObjectID object_id, bool is_enabled);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/set_player_state.cpp b/Source/Online/Message/set_player_state.cpp
new file mode 100644
index 00000000..c55514ce
--- /dev/null
+++ b/Source/Online/Message/set_player_state.cpp
@@ -0,0 +1,94 @@
+//-----------------------------------------------------------------------------
+// Name: set_player_state.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "set_player_state.h"
+
+#include <Online/online.h>
+#include <Utility/binn_util.h>
+
+namespace OnlineMessages {
+ SetPlayerState::SetPlayerState(PlayerID player_id, const PlayerState* player_state) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ player_id(player_id)
+ {
+ object_id = Online::Instance()->GetOriginalID(player_state->object_id);
+ playername = player_state->playername;
+ ping = player_state->ping;
+ camera_id = player_state->camera_id;
+ controller_id = player_state->controller_id;
+ }
+
+ binn* SetPlayerState::Serialize(void* object) {
+ SetPlayerState* t = static_cast<SetPlayerState*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_int32(l, "object_id", t->object_id);
+ binn_object_set_std_string(l, "playername", t->playername);
+ binn_object_set_uint16(l, "ping", t->ping);
+ binn_object_set_uint8(l, "player_id", t->player_id);
+ binn_object_set_int32(l, "camera_id", t->camera_id);
+ binn_object_set_int32(l, "controller_id", t->controller_id);
+
+ return l;
+ }
+
+ void SetPlayerState::Deserialize(void* object, binn* l) {
+ SetPlayerState* t = static_cast<SetPlayerState*>(object);
+
+ binn_object_get_int32(l, "object_id", &t->object_id);
+ binn_object_get_std_string(l, "playername", &t->playername);
+ binn_object_get_uint16(l, "ping", &t->ping);
+ binn_object_get_uint8(l, "player_id", &t->player_id);
+ binn_object_get_int32(l, "camera_id", &t->camera_id);
+ binn_object_get_int32(l, "controller_id", &t->controller_id);
+ }
+
+ void SetPlayerState::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ SetPlayerState* t = static_cast<SetPlayerState*>(object);
+ ObjectID object_id = Online::Instance()->GetObjectID(t->object_id);
+
+ // Clients have not (and should not) registered a camera id locally applying the host's camera_id will lead to a crash.
+ if (Online::Instance()->IsClient()) {
+ t->camera_id = 0;
+ }
+
+ PlayerState new_player_state;
+ new_player_state.object_id = object_id;
+ new_player_state.playername = t->playername;
+ new_player_state.ping = t->ping;
+ new_player_state.camera_id = t->camera_id;
+ new_player_state.controller_id = t->controller_id;
+
+ // Set new state
+ Online::Instance()->online_session->player_states[t->player_id] = new_player_state;
+ }
+
+ void* SetPlayerState::Construct(void *mem) {
+ PlayerState player_state;
+ return new(mem) SetPlayerState(0, &player_state);
+ }
+
+ void SetPlayerState::Destroy(void* object) {
+ SetPlayerState* t = static_cast<SetPlayerState*>(object);
+ t->~SetPlayerState();
+ }
+}
diff --git a/Source/Online/Message/set_player_state.h b/Source/Online/Message/set_player_state.h
new file mode 100644
index 00000000..015d3c4f
--- /dev/null
+++ b/Source/Online/Message/set_player_state.h
@@ -0,0 +1,50 @@
+//-----------------------------------------------------------------------------
+// Name: set_player_state.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+struct PlayerState;
+
+namespace OnlineMessages {
+ // TODO It would be nice if we could only send the deltas instead of resending everything every time
+ class SetPlayerState : public OnlineMessageBase {
+ private:
+ PlayerID player_id;
+
+ CommonObjectID object_id;
+ std::string playername;
+ uint16_t ping;
+ int32_t controller_id;
+ int32_t camera_id;
+
+ public:
+ SetPlayerState(PlayerID player_id, const PlayerState* player_state);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/sp_remove_message.cpp b/Source/Online/Message/sp_remove_message.cpp
new file mode 100644
index 00000000..93f70385
--- /dev/null
+++ b/Source/Online/Message/sp_remove_message.cpp
@@ -0,0 +1,73 @@
+//-----------------------------------------------------------------------------
+// Name: sp_remove_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "sp_remove_message.h"
+
+#include <Main/engine.h>
+#include <Online/online.h>
+#include <Utility/binn_util.h>
+
+namespace OnlineMessages {
+ SPRemoveMessage::SPRemoveMessage(ObjectID param_id, const std::string& key_name) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_PERSISTENT),
+ key_name(key_name)
+ {
+ this->param_id = Online::Instance()->GetOriginalID(param_id);
+ }
+
+ binn* SPRemoveMessage::Serialize(void* object) {
+ SPRemoveMessage* t = static_cast<SPRemoveMessage*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_int32(l, "param_id", t->param_id);
+ binn_object_set_std_string(l, "key_name", t->key_name);
+
+ return l;
+ }
+
+ void SPRemoveMessage::Deserialize(void* object, binn* l) {
+ SPRemoveMessage* t = static_cast<SPRemoveMessage*>(object);
+
+ binn_object_get_int32(l, "param_id", &t->param_id);
+ binn_object_get_std_string(l, "key_name", &t->key_name);
+ }
+
+ void SPRemoveMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ SPRemoveMessage* t = static_cast<SPRemoveMessage*>(object);
+ ObjectID object_id = Online::Instance()->GetObjectID(t->param_id);
+
+ ScriptParams* params = Online::Instance()->GetScriptParamsFromID(object_id);
+ if(params != nullptr) {
+ params->ASRemove(t->key_name);
+ Online::Instance()->UpdateMovementObjectFromID(object_id);
+ }
+ }
+
+ void* SPRemoveMessage::Construct(void *mem) {
+ return new(mem) SPRemoveMessage(0, "");
+ }
+
+ void SPRemoveMessage::Destroy(void* object) {
+ SPRemoveMessage* t = static_cast<SPRemoveMessage*>(object);
+ t->~SPRemoveMessage();
+ }
+}
diff --git a/Source/Online/Message/sp_remove_message.h b/Source/Online/Message/sp_remove_message.h
new file mode 100644
index 00000000..e35f1ae8
--- /dev/null
+++ b/Source/Online/Message/sp_remove_message.h
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------------
+// Name: sp_remove_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class SPRemoveMessage : public OnlineMessageBase {
+ private:
+ CommonObjectID param_id;
+ std::string key_name;
+
+ public:
+ SPRemoveMessage(ObjectID param_id, const std::string& key_name);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/sp_rename_message.cpp b/Source/Online/Message/sp_rename_message.cpp
new file mode 100644
index 00000000..c007dff7
--- /dev/null
+++ b/Source/Online/Message/sp_rename_message.cpp
@@ -0,0 +1,80 @@
+//-----------------------------------------------------------------------------
+// Name: sp_rename_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "sp_rename_message.h"
+
+#include <Main/engine.h>
+#include <Online/online.h>
+#include <Utility/binn_util.h>
+
+namespace OnlineMessages {
+ SPRenameMessage::SPRenameMessage(ObjectID param_id, const std::string& current_key_name, const std::string& new_key_name) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_PERSISTENT),
+ current_key_name(current_key_name), new_key_name(new_key_name)
+ {
+ this->param_id = Online::Instance()->GetOriginalID(param_id);
+ }
+
+ binn* SPRenameMessage::Serialize(void* object) {
+ SPRenameMessage* t = static_cast<SPRenameMessage*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_int32(l, "param_id", t->param_id);
+ binn_object_set_std_string(l, "current_key_name", t->current_key_name);
+ binn_object_set_std_string(l, "new_key_name", t->new_key_name);
+
+ return l;
+ }
+
+ void SPRenameMessage::Deserialize(void* object, binn* l) {
+ SPRenameMessage* t = static_cast<SPRenameMessage*>(object);
+
+ binn_object_get_int32(l, "param_id", &t->param_id);
+ binn_object_get_std_string(l, "current_key_name", &t->current_key_name);
+ binn_object_get_std_string(l, "new_key_name", &t->new_key_name);
+ }
+
+ void SPRenameMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ SPRenameMessage* t = static_cast<SPRenameMessage*>(object);
+ ObjectID object_id = Online::Instance()->GetObjectID(t->param_id);
+
+ ScriptParams* params = Online::Instance()->GetScriptParamsFromID(object_id);
+ if(params != nullptr) {
+ if (params->RenameParameterKey(t->current_key_name, t->new_key_name)) {
+ Online::Instance()->UpdateMovementObjectFromID(object_id);
+ } else {
+ LOGE << "Tried renaming \"" << t->current_key_name << "\" to \"" << t->new_key_name << "\" but failed finding key to rename" << std::endl;
+ }
+ } else {
+ LOGW << "Tried renaming \"" << t->current_key_name << "\" to \"" << t->new_key_name << "\" but couldn't find params with id: " << object_id << " (" << t->param_id << ")" << std::endl;
+ }
+ }
+
+ void* SPRenameMessage::Construct(void *mem) {
+ return new(mem) SPRenameMessage(0, "", "");
+ }
+
+ void SPRenameMessage::Destroy(void* object) {
+ SPRenameMessage* t = static_cast<SPRenameMessage*>(object);
+ t->~SPRenameMessage();
+ }
+}
diff --git a/Source/Online/Message/sp_rename_message.h b/Source/Online/Message/sp_rename_message.h
new file mode 100644
index 00000000..b7300720
--- /dev/null
+++ b/Source/Online/Message/sp_rename_message.h
@@ -0,0 +1,43 @@
+//-----------------------------------------------------------------------------
+// Name: sp_rename_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class SPRenameMessage : public OnlineMessageBase {
+ private:
+ CommonObjectID param_id;
+ std::string current_key_name;
+ std::string new_key_name;
+
+ public:
+ SPRenameMessage(ObjectID param_id, const std::string& current_key_name, const std::string& new_key_name);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/sp_string_message.cpp b/Source/Online/Message/sp_string_message.cpp
new file mode 100644
index 00000000..7fa401cc
--- /dev/null
+++ b/Source/Online/Message/sp_string_message.cpp
@@ -0,0 +1,90 @@
+//-----------------------------------------------------------------------------
+// Name: sp_string_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "sp_string_message.h"
+
+#include <Main/engine.h>
+#include <Online/online.h>
+#include <Utility/binn_util.h>
+
+namespace OnlineMessages {
+ SPStringMessage::SPStringMessage(ObjectID param_id, const std::string& key_name, const std::string& value, ScriptParamEditorType::Type editor_type, const std::string& editor_details) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_PERSISTENT),
+ key_name(key_name), value(value), editor_type(editor_type), editor_details(editor_details)
+ {
+ this->param_id = Online::Instance()->GetOriginalID(param_id);
+ }
+
+ binn* SPStringMessage::Serialize(void* object) {
+ SPStringMessage* t = static_cast<SPStringMessage*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_int32(l, "param_id", t->param_id);
+ binn_object_set_std_string(l, "key_name", t->key_name);
+ binn_object_set_std_string(l, "value", t->value);
+ binn_object_set_uint8(l, "editor_type", t->editor_type);
+ binn_object_set_std_string(l, "editor_details", t->editor_details);
+
+ return l;
+ }
+
+ void SPStringMessage::Deserialize(void* object, binn* l) {
+ SPStringMessage* t = static_cast<SPStringMessage*>(object);
+
+ binn_object_get_int32(l, "param_id", &t->param_id);
+ binn_object_get_std_string(l, "key_name", &t->key_name);
+ binn_object_get_std_string(l, "value", &t->value);
+
+ uint8_t editor_type;
+ binn_object_get_uint8(l, "editor_type", &editor_type);
+ t->editor_type = (ScriptParamEditorType::Type)editor_type;
+
+ binn_object_get_std_string(l, "editor_details", &t->editor_details);
+ }
+
+ void SPStringMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ SPStringMessage* t = static_cast<SPStringMessage*>(object);
+ ObjectID object_id = Online::Instance()->GetObjectID(t->param_id);
+
+ ScriptParam param;
+ param.SetString(t->value);
+ param.editor().SetType(t->editor_type);
+ param.editor().SetDetails(t->editor_details);
+
+ ScriptParams* params = Online::Instance()->GetScriptParamsFromID(object_id);
+ if(params != nullptr) {
+ params->InsertScriptParam(t->key_name, param);
+ Online::Instance()->UpdateMovementObjectFromID(object_id);
+ } else {
+ LOGW << "Unable to apply script param update for param_id: " << object_id << " (" << t->param_id << ")" << endl;
+ }
+ }
+
+ void* SPStringMessage::Construct(void *mem) {
+ return new(mem) SPStringMessage(0, "", "", ScriptParamEditorType::Type::UNDEFINED, "");
+ }
+
+ void SPStringMessage::Destroy(void* object) {
+ SPStringMessage* t = static_cast<SPStringMessage*>(object);
+ t->~SPStringMessage();
+ }
+}
diff --git a/Source/Online/Message/sp_string_message.h b/Source/Online/Message/sp_string_message.h
new file mode 100644
index 00000000..8203c488
--- /dev/null
+++ b/Source/Online/Message/sp_string_message.h
@@ -0,0 +1,45 @@
+//-----------------------------------------------------------------------------
+// Name: sp_string_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class SPStringMessage : public OnlineMessageBase {
+ private:
+ CommonObjectID param_id;
+ std::string key_name;
+ std::string value;
+ ScriptParamEditorType::Type editor_type;
+ std::string editor_details;
+
+ public:
+ SPStringMessage(ObjectID param_id, const std::string& key_name, const std::string& value, ScriptParamEditorType::Type editor_type, const std::string& editor_details);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/sp_union_message.cpp b/Source/Online/Message/sp_union_message.cpp
new file mode 100644
index 00000000..65aab56e
--- /dev/null
+++ b/Source/Online/Message/sp_union_message.cpp
@@ -0,0 +1,116 @@
+//-----------------------------------------------------------------------------
+// Name: sp_union_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "sp_union_message.h"
+
+#include <Main/engine.h>
+#include <Online/online.h>
+#include <Utility/binn_util.h>
+
+namespace OnlineMessages {
+ SPUnionMessage::SPUnionMessage(ObjectID param_id, const std::string& key_name, int32_t value, ScriptParam::ScriptParamType type, ScriptParamEditorType::Type editor_type, const std::string& editor_details) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_PERSISTENT),
+ key_name(key_name), type(type), editor_type(editor_type), editor_details(editor_details)
+ {
+ value_int = value;
+ this->param_id = Online::Instance()->GetOriginalID(param_id);
+ }
+
+ binn* SPUnionMessage::Serialize(void* object) {
+ SPUnionMessage* t = static_cast<SPUnionMessage*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_int32(l, "param_id", t->param_id);
+ binn_object_set_std_string(l, "key_name", t->key_name);
+ binn_object_set_uint8(l, "type", t->type);
+
+ if(t->type == ScriptParam::ScriptParamType::FLOAT) {
+ binn_object_set_float(l, "value", t->value_float);
+ } else if(t->type == ScriptParam::ScriptParamType::INT) {
+ binn_object_set_int32(l, "value", t->value_int);
+ } else {
+ LOGE << "Unhandled type" << endl;
+ }
+
+ binn_object_set_uint8(l, "editor_type", t->editor_type);
+ binn_object_set_std_string(l, "editor_details", t->editor_details);
+
+ return l;
+ }
+
+ void SPUnionMessage::Deserialize(void* object, binn* l) {
+ SPUnionMessage* t = static_cast<SPUnionMessage*>(object);
+
+ binn_object_get_int32(l, "param_id", &t->param_id);
+ binn_object_get_std_string(l, "key_name", &t->key_name);
+
+ uint8_t type;
+ binn_object_get_uint8(l, "type", &type);
+ t->type = (ScriptParam::ScriptParamType)type;
+
+ if(t->type == ScriptParam::ScriptParamType::FLOAT) {
+ binn_object_get_float(l, "value", &t->value_float);
+ } else if(t->type == ScriptParam::ScriptParamType::INT) {
+ binn_object_get_int32(l, "value", &t->value_int);
+ } else {
+ LOGE << "Unhandled type" << endl;
+ }
+
+ uint8_t editor_type;
+ binn_object_get_uint8(l, "editor_type", &editor_type);
+ t->editor_type = (ScriptParamEditorType::Type)editor_type;
+
+ binn_object_get_std_string(l, "editor_details", &t->editor_details);
+ }
+
+ void SPUnionMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ SPUnionMessage* t = static_cast<SPUnionMessage*>(object);
+ ObjectID object_id = Online::Instance()->GetObjectID(t->param_id);
+
+ ScriptParam param;
+ if (t->type == ScriptParam::ScriptParamType::FLOAT) {
+ param.SetFloat(t->value_float);
+ } else {
+ param.SetInt(t->value_int);
+ }
+
+ param.editor().SetDetails(t->editor_details);
+ param.editor().SetType(t->editor_type);
+
+ ScriptParams* params = Online::Instance()->GetScriptParamsFromID(object_id);
+ if(params != nullptr) {
+ params->InsertScriptParam(t->key_name, param);
+ Online::Instance()->UpdateMovementObjectFromID(object_id);
+ } else {
+ LOGW << "Unable to apply script param update for param_id: " << object_id << " (" << t->param_id << ")" << endl;
+ }
+ }
+
+ void* SPUnionMessage::Construct(void *mem) {
+ return new(mem) SPUnionMessage(0, "", 0, ScriptParam::ScriptParamType::OTHER, ScriptParamEditorType::Type::UNDEFINED, "");
+ }
+
+ void SPUnionMessage::Destroy(void* object) {
+ SPUnionMessage* t = static_cast<SPUnionMessage*>(object);
+ t->~SPUnionMessage();
+ }
+}
diff --git a/Source/Online/Message/sp_union_message.h b/Source/Online/Message/sp_union_message.h
new file mode 100644
index 00000000..f965841c
--- /dev/null
+++ b/Source/Online/Message/sp_union_message.h
@@ -0,0 +1,52 @@
+//-----------------------------------------------------------------------------
+// Name: sp_union_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class SPUnionMessage : public OnlineMessageBase {
+ private:
+ CommonObjectID param_id;
+ std::string key_name;
+
+ ScriptParam::ScriptParamType type;
+ ScriptParamEditorType::Type editor_type;
+
+ union {
+ float value_float;
+ int32_t value_int;
+ };
+
+ std::string editor_details;
+
+ public:
+ SPUnionMessage(ObjectID param_id, const std::string& key_name, int32_t value, ScriptParam::ScriptParamType type, ScriptParamEditorType::Type editor_type, const std::string& editor_details);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* source);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/test_message.cpp b/Source/Online/Message/test_message.cpp
new file mode 100644
index 00000000..27f56294
--- /dev/null
+++ b/Source/Online/Message/test_message.cpp
@@ -0,0 +1,73 @@
+//-----------------------------------------------------------------------------
+// Name: test_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "test_message.h"
+
+#include <Logging/loghandler.h>
+
+#include <iostream>
+#include <string>
+
+using std::endl;
+using std::string;
+
+namespace OnlineMessages {
+ TestMessage::TestMessage(string message) :
+ OnlineMessageBase(OnlineMessageCategory::TRANSIENT),
+ message(message)
+ {
+
+ }
+
+ binn* TestMessage::Serialize(void* object) {
+ TestMessage* test_message = static_cast<TestMessage*>(object);
+
+ binn* l = binn_object();
+
+ binn_object_set_str(l, "message", test_message->message.c_str());
+
+ return l;
+ }
+
+ void TestMessage::Deserialize(void* object, binn* l) {
+ TestMessage* test_message = static_cast<TestMessage*>(object);
+
+ char* message;
+ binn_object_get_str(l, "message", &message);
+
+ test_message->message = string(message);
+ }
+
+ void TestMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ TestMessage* test_message = static_cast<TestMessage*>(object);
+ LOGW << "TestMessage Received and being Executed: " << test_message->message << endl;
+ }
+
+ void* TestMessage::Construct(void* mem) {
+ return new(mem) TestMessage("");
+ }
+
+ void TestMessage::Destroy(void* object) {
+ TestMessage* test_message = static_cast<TestMessage*>(object);
+ test_message->~TestMessage();
+ }
+}
diff --git a/Source/Online/Message/test_message.h b/Source/Online/Message/test_message.h
new file mode 100644
index 00000000..354f76c1
--- /dev/null
+++ b/Source/Online/Message/test_message.h
@@ -0,0 +1,43 @@
+//-----------------------------------------------------------------------------
+// Name: test_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+#include <string>
+
+using std::string;
+
+namespace OnlineMessages {
+ class TestMessage : public OnlineMessageBase {
+ public:
+ string message;
+ TestMessage(string message);
+
+ static binn* Serialize(void *object);
+ static void Deserialize(void* object, binn* l);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/timed_slow_motion.cpp b/Source/Online/Message/timed_slow_motion.cpp
new file mode 100644
index 00000000..881f6000
--- /dev/null
+++ b/Source/Online/Message/timed_slow_motion.cpp
@@ -0,0 +1,70 @@
+//-----------------------------------------------------------------------------
+// Name: timed_slow_motion.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "timed_slow_motion.h"
+
+#include <Main/engine.h>
+
+extern Timer game_timer;
+
+namespace OnlineMessages {
+ TimedSlowMotion::TimedSlowMotion(float target_time_scale, float how_long, float delay) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT),
+ target_time_scale(target_time_scale), how_long(how_long), delay(delay)
+ {
+
+ }
+
+ binn* TimedSlowMotion::Serialize(void* object) {
+ TimedSlowMotion* tsm = static_cast<TimedSlowMotion*>(object);
+ binn* l = binn_object();
+
+ binn_object_set_float(l, "tts", tsm->target_time_scale);
+ binn_object_set_float(l, "hl", tsm->how_long);
+ binn_object_set_float(l, "d", tsm->delay);
+
+ return l;
+ }
+
+ void TimedSlowMotion::Deserialize(void* object, binn* l) {
+ TimedSlowMotion* tsm = static_cast<TimedSlowMotion*>(object);
+
+ binn_object_get_float(l, "tts", &tsm->target_time_scale);
+ binn_object_get_float(l, "hl", &tsm->how_long);
+ binn_object_get_float(l, "d", &tsm->delay);
+ }
+
+ void TimedSlowMotion::Execute(const OnlineMessageRef& ref, void* object, PeerID peer) {
+ TimedSlowMotion* tsm = static_cast<TimedSlowMotion*>(object);
+
+ game_timer.AddTimedSlowMotionLayer(tsm->target_time_scale, tsm->how_long, tsm->delay);
+ }
+
+ void* TimedSlowMotion::Construct(void* mem) {
+ return new (mem) TimedSlowMotion(.0f, .0f, .0f);
+ }
+
+ void TimedSlowMotion::Destroy(void* object) {
+ TimedSlowMotion* tsm = static_cast<TimedSlowMotion*>(object);
+ tsm->~TimedSlowMotion();
+ }
+}
diff --git a/Source/Online/Message/timed_slow_motion.h b/Source/Online/Message/timed_slow_motion.h
new file mode 100644
index 00000000..05e9faf8
--- /dev/null
+++ b/Source/Online/Message/timed_slow_motion.h
@@ -0,0 +1,45 @@
+//-----------------------------------------------------------------------------
+// Name: timed_slow_motion.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+#include <Online/online_datastructures.h>
+
+namespace OnlineMessages {
+ class TimedSlowMotion : public OnlineMessageBase {
+ private:
+ float target_time_scale;
+ float how_long;
+ float delay;
+
+ public:
+ TimedSlowMotion(float target_time_scale, float how_long, float delay);
+
+ static binn* Serialize(void* object);
+ static void Deserialize(void* object, binn* l);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID peer);
+ static void* Construct(void *mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/Message/whoosh_sound_message.cpp b/Source/Online/Message/whoosh_sound_message.cpp
new file mode 100644
index 00000000..1ab082f9
--- /dev/null
+++ b/Source/Online/Message/whoosh_sound_message.cpp
@@ -0,0 +1,68 @@
+//-----------------------------------------------------------------------------
+// Name: whoosh_sound_message.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "whoosh_sound_message.h"
+
+#include <Main/engine.h>
+
+namespace OnlineMessages {
+ WhooshSoundMessage::WhooshSoundMessage(float whoosh_amount, float whoosh_pitch) :
+ OnlineMessageBase(OnlineMessageCategory::LEVEL_TRANSIENT)
+ {
+ this->whoosh_amount = whoosh_amount;
+ this->whoosh_pitch = whoosh_pitch;
+ }
+
+ void WhooshSoundMessage::Execute(const OnlineMessageRef& ref, void* object, PeerID from) {
+ WhooshSoundMessage* wsm = static_cast<WhooshSoundMessage*>(object);
+
+ Engine::Instance()->GetSound()->setAirWhoosh(wsm->whoosh_amount, wsm->whoosh_pitch);
+ }
+
+
+ binn* WhooshSoundMessage::Serialize(void *object) {
+ WhooshSoundMessage* wsm = static_cast<WhooshSoundMessage*>(object);
+
+ binn* l = binn_object();
+
+ binn_object_set_float(l, "amount", wsm->whoosh_amount);
+ binn_object_set_float(l, "pitch", wsm->whoosh_pitch);
+
+ return l;
+ }
+
+ void WhooshSoundMessage::Deserialize(void* object, binn* l) {
+ WhooshSoundMessage* wsm = static_cast<WhooshSoundMessage*>(object);
+
+ binn_object_get_float(l, "amount", &wsm->whoosh_amount);
+ binn_object_get_float(l, "pitch", &wsm->whoosh_pitch);
+ }
+
+ void* WhooshSoundMessage::Construct(void* mem) {
+ return new (mem) WhooshSoundMessage(0,0);
+ }
+
+ void WhooshSoundMessage::Destroy(void *object) {
+ WhooshSoundMessage* wsm = static_cast<WhooshSoundMessage*>(object);
+ wsm->~WhooshSoundMessage();
+ }
+}
diff --git a/Source/Online/Message/whoosh_sound_message.h b/Source/Online/Message/whoosh_sound_message.h
new file mode 100644
index 00000000..d36a24ba
--- /dev/null
+++ b/Source/Online/Message/whoosh_sound_message.h
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------------
+// Name: whoosh_sound_message.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "online_message_base.h"
+
+namespace OnlineMessages {
+ class WhooshSoundMessage : public OnlineMessageBase {
+ private:
+ float whoosh_amount;
+ float whoosh_pitch;
+
+ public:
+ WhooshSoundMessage(float whoosh_amount, float whoosh_pitch);
+
+ static binn* Serialize(void *object);
+ static void Deserialize(void* object, binn* l);
+ static void Execute(const OnlineMessageRef& ref, void* object, PeerID from);
+ static void* Construct(void* mem);
+ static void Destroy(void* object);
+ };
+}
diff --git a/Source/Online/online.cpp b/Source/Online/online.cpp
new file mode 100644
index 00000000..0a9d1eb3
--- /dev/null
+++ b/Source/Online/online.cpp
@@ -0,0 +1,1594 @@
+//-----------------------------------------------------------------------------
+// Name: online.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "online.h"
+
+#include <Game/level.h>
+#include <Game/connection_closed_reason.h>
+
+#include <Math/vec3math.h>
+#include <Math/vec4math.h>
+
+#include <Steam/steamworks.h>
+#include <Steam/steamworks_util.h>
+
+#include <Objects/object.h>
+#include <Objects/envobject.h>
+#include <Objects/itemobject.h>
+#include <Objects/movementobject.h>
+#include <Objects/envobjectattach.h>
+#include <Objects/riggedobject.h>
+#include <Objects/cameraobject.h>
+
+#include <Editors/map_editor.h>
+#include <Editors/actors_editor.h>
+
+#include <Online/online_file_transfer_handler.h>
+#include <Online/online_utility.h>
+#include <Online/package_header.h>
+
+#include <Compat/compat.h>
+#include <Compat/time.h>
+
+#include <Graphics/pxdebugdraw.h>
+#include <Main/scenegraph.h>
+#include <Logging/logdata.h>
+
+#if ENABLE_STEAMWORKS
+#include <isteamnetworkingutils.h>
+#include <isteamfriends.h>
+#endif
+
+#include <zstd.h>
+#include <zstd-1.5.0/lib/common/zstd_common.c>
+
+#include <limits>
+
+extern Timer game_timer;
+
+OnlineMessageHandler message_handler;
+
+OnlineMessageRef::OnlineMessageRef() : message_id(std::numeric_limits<OnlineMessageID>::max()) {
+}
+
+OnlineMessageRef::OnlineMessageRef(const OnlineMessageID message_id) : message_id(message_id) {
+ message_handler.Acquire(message_id);
+}
+
+OnlineMessageRef::OnlineMessageRef(const OnlineMessageRef& rhs) {
+ //Debug the fact that this RHS is corrupt sometimes when loading in debug.
+ message_id = rhs.message_id;
+ message_handler.Acquire(message_id);
+}
+
+OnlineMessageRef::~OnlineMessageRef() {
+ if(IsValid()) {
+ message_handler.Release(message_id);
+ }
+}
+
+OnlineMessageRef& OnlineMessageRef::operator=(const OnlineMessageRef& rhs){
+ if(IsValid()) {
+ message_handler.Release(message_id);
+ }
+
+ message_id = rhs.message_id;
+
+ message_handler.Acquire(message_id);
+ return *this;
+}
+
+OnlineMessageID OnlineMessageRef::GetID() const {
+ return this->message_id;
+}
+
+void* OnlineMessageRef::GetData() const {
+ return message_handler.GetMessageData(*this);
+}
+
+bool OnlineMessageRef::IsValid() const {
+ return message_id != std::numeric_limits<OnlineMessageID>::max();
+}
+
+void Online::GetConnectionStatus(const Peer& peer, ConnectionStatus* status) const {
+ return net.GetConnectionStatus(peer.conn_id, status);
+}
+
+void Online::RegisterHostObjectID(CommonObjectID hostid, ObjectID clientid) {
+ to_object_id[hostid] = clientid;
+ to_common_id[clientid] = hostid;
+}
+
+void Online::DeRegisterHostClientIDTranslation(ObjectID clientid) {
+ // check if we need to remove a translation
+ auto translation = GetOriginalID(clientid);
+
+ if (translation) {
+ to_object_id.erase(translation);
+ to_common_id.erase(clientid);
+ }
+}
+
+ObjectID Online::GetObjectID(CommonObjectID hostid) const {
+ auto it = to_object_id.find(hostid);
+ if (it != to_object_id.end()) {
+ return it->second;
+ } else {
+ return hostid;
+ }
+}
+
+CommonObjectID Online::GetOriginalID(ObjectID clientid) const {
+ auto it = to_common_id.find(clientid);
+ if (it != to_common_id.end()) {
+ return it->second;
+ } else {
+ return clientid;
+ }
+}
+
+void Online::ClearIDTranslations() {
+ to_object_id.clear();
+ to_common_id.clear();
+}
+
+bool Online::SendingRemovePackages() const {
+ return !loading;
+}
+
+void Online::RemoveObject(Object * o, ObjectID my_id) {
+ if (NetworkRemoveableType(o) && SendingRemovePackages()) {
+ Send<OnlineMessages::RemoveObject>(my_id, o->GetType());
+ }
+}
+
+bool Online::NetworkRemoveableType(Object * o) const {
+ EntityType type = o->GetType();
+ return type == _env_object || type == _movement_object || type == _item_object;
+}
+
+void Online::ChangeLevel(const string & id) {
+ campaign_id = ModLoading::Instance().WhichCampaignLevelBelongsTo(id);
+ host_started_level = false;
+ level_name = id;
+
+ RemoveAllAvatarIds();
+ loading = true;
+
+ online_session->outgoing_messages_lock.lock();
+ // Clear out Level persistent messages
+ for (int i = online_session->persistent_outgoing_messages.size() - 1; i >= 0; i--) {
+ OnlineMessageBase* message_base = (OnlineMessageBase*) online_session->persistent_outgoing_messages[i].GetData();
+ if (message_base->cat == OnlineMessageCategory::LEVEL_PERSISTENT) {
+ online_session->persistent_outgoing_messages.erase(online_session->persistent_outgoing_messages.begin() + i);
+ }
+ }
+ online_session->outgoing_messages_lock.unlock();
+
+ // Reset every peer to a pending connection state.
+ online_session->client_connection_manager.ResetConnections();
+
+ // Reset every player's possession
+ online_session->connected_peers_lock.lock();
+ for (auto& it : online_session->player_states) {
+ it.second.object_id = -1;
+ }
+ online_session->connected_peers_lock.unlock();
+}
+
+Online::Online() : mode(MultiplayerMode::NoMultiplayer),
+ initial_ts(0), next_available_state_id(1), next_free_peer_id(1),
+ attach_avatar_camera(true), loading(false),
+ default_hot_join_characte_path("Data/Objects/IGF_Characters/IGF_TurnerActor.xml"),
+ compression_level(10), net(this), host_started_level(false)
+{
+
+ zstdCContext = ZSTD_createCCtx();
+ zstdDContext = ZSTD_createDCtx();
+}
+
+void Online::Dispose() {
+ StopMultiplayer();
+}
+
+bool Online::IsClient() const {
+ return mode == MultiplayerMode::Client;
+}
+
+void Online::SendAvatarPaletteChange(const OGPalette & palette, ObjectID object_id) {
+ for (const auto& it : palette) {
+ Send<OnlineMessages::SetAvatarPalette>(object_id, it.color, it.channel);
+ }
+}
+
+bool Online::IsAvatarPossessed(ObjectID object_id) {
+ Object* object = Engine::Instance()->GetSceneGraph()->GetObjectFromID(object_id);
+ if (object != nullptr && object->GetType() == EntityType::_movement_object) {
+ MovementObject* mov = (MovementObject*) object;
+ return mov->controlled;
+ }
+
+ LOGW << "IsAvatarPossessed was called with invalid id: " << object_id << endl;
+ return false;
+}
+
+void Online::PossessAvatar(PlayerID player_id, ObjectID object_id) {
+ // LOGI << std::to_string(player_id) << " possessed " << std::to_string(object_id) << std::endl;
+ online_session->player_states[player_id].object_id = object_id;
+ Send<OnlineMessages::SetPlayerState>(player_id, &online_session->player_states[player_id]);
+}
+
+ObjectID Online::CreateCharacter() {
+ SceneGraph * scenegraph = Engine::Instance()->GetSceneGraph();
+
+ ObjectID id = scenegraph->map_editor->CreateObject(default_hot_join_characte_path);
+ std::string team = "";
+ vec3 position = vec3(0.0f);
+
+ // Attempt to move character close to an existing one.
+ vector<ObjectID> local_avatar_ids = GetLocalAvatarIDs();
+ if(local_avatar_ids.size() > 0) {
+ MovementObject* mymo = static_cast<MovementObject*>(scenegraph->GetObjectFromID(local_avatar_ids[0]));
+ team = mymo->GetScriptParams()->GetStringVal("Teams");
+ position = mymo->GetTranslation();
+ } else {
+ LOGW << "Unable to find locally controlled character while creating character for new connection, using fallback parameters" << std::endl;
+ }
+
+ MovementObject* mo = static_cast<MovementObject*>(scenegraph->GetObjectFromID(id));
+ mo->SetTranslation(position);
+ mo->is_player = true;
+ mo->created_on_the_fly = true;
+
+ ScriptParams * sp = mo->GetScriptParams();
+ sp->ASSetString("Teams", team);
+
+ Send<OnlineMessages::CreateEntity>(default_hot_join_characte_path, position, id);
+
+ // Set player color for new character
+ OGPalette* palettes = mo->GetPalette();
+ for (auto& palette : *palettes) {
+ if (palette.label == "Cloth") {
+ palette.color = vec3(1.0f, 0.0f, 0.0f);
+ }
+ }
+ mo->ApplyPalette(*palettes);
+ return id;
+}
+
+void Online::SetNoDataInterpStepOverRide(float override) {
+ no_data_interpstep_override = override;
+}
+
+float Online::GetNoDataInterpStepOverRide() const {
+ return no_data_interpstep_override;
+}
+
+//Client side sending camera info to host
+void Online::SendPlayerCameraState() {
+ Camera* camera = ActiveCameras::Instance()->GetCamera(0);
+ Send<OnlineMessages::CameraTransformMessage>(camera->GetPos(), camera->GetFlatFacing(), camera->GetFacing(), camera->GetUpVector(), camera->GetXRotation(), camera->GetYRotation());
+}
+
+void Online::SendPlayerInputState() {
+ //Assume player one control. For split screen multiplayer we have to start dealing with multiplayer controllers here.
+ PlayerInput* player_input = Input::Instance()->GetController(0);
+
+ for(string binding : Input::Instance()->GetAllAvailableBindings()) {
+
+ // TODO Can we make it so these bindings don't even show up in available bindings?
+ if(binding == "look_left" || binding == "look_right" || binding == "look_up" || binding == "look_down") {
+ continue;
+ }
+
+ KeyState local_keystate = player_input->key_down[binding];
+ KeyState remote_keystate = client_key_down_map_state[binding];
+
+ // if we have something that the remote does not have, update
+ if(local_keystate != remote_keystate) {
+ Send<OnlineMessages::PlayerInputMessage>(local_keystate.depth, local_keystate.depth_count, local_keystate.count, online_session->binding_id_map_[binding]);
+ client_key_down_map_state[binding] = local_keystate;
+ }
+ }
+}
+
+void Online::LateUpdate(SceneGraph * scenegraph) {
+ if (IsActive()) {
+ //Send a ping every second.
+ if(online_session != nullptr && game_timer.wall_time > online_session->last_ping_time + 1.0f) {
+ online_session->last_ping_id = online_session->ping_counter++;
+ online_session->last_ping_time = game_timer.wall_time;
+ Send<OnlineMessages::Ping>(online_session->last_ping_id);
+ }
+
+ online_session->client_connection_manager.Update();
+
+ if (IsHosting()) {
+ SyncHostSessionFlags();
+ }
+ }
+}
+
+void Online::SyncHostSessionFlags() {
+ lock_guard<mutex> guard(online_session->connected_peers_lock);
+ for(auto& conn : online_session->connected_peers) {
+ for(auto& flag : online_session->host_session_flags) {
+ if(conn.host_session_flags.find(flag.first) == conn.host_session_flags.end() || conn.host_session_flags.find(flag.first)->second != flag.second) {
+ SendTo<OnlineMessages::HostSessionFlag>(conn.conn_id, flag.first, flag.second);
+ conn.host_session_flags[flag.first] = flag.second;
+ }
+ }
+ }
+}
+
+void Online::GenerateStateForNewConnection(NetConnectionID conn) {
+ SceneGraph* graph = Engine::Instance()->GetSceneGraph();
+ if (graph != nullptr) {
+ GenerateMovementSyncPackages(conn, graph);
+ GenerateEnvObjectSyncPackages(conn, graph);
+ }
+}
+
+void Online::GenerateEnvObjectSyncPackages(NetConnectionID conn, SceneGraph* graph) {
+ std::vector<Object*> envobjects = graph->GetObjectsOfType(EntityType::_env_object);
+
+ for (const auto& it : envobjects) {
+ EnvObject* eo = static_cast<EnvObject*>(it);
+ SendTo<OnlineMessages::EnvObjectUpdate>(conn, eo);
+ }
+}
+
+void Online::GenerateMovementSyncPackages(NetConnectionID conn, SceneGraph * graph) {
+ std::vector<Object*> movobjects = graph->GetObjectsOfType(EntityType::_movement_object);
+
+ for (const auto& it : movobjects) {
+ MovementObject * mov = static_cast<MovementObject*>(it);
+ ObjectID avatarid = mov->GetID();
+
+ // riggedbody blood?
+ std::vector<MorphTargetStateStorage> network_morphs = mov->rigged_object()->network_morphs;
+
+ for (const auto& it : network_morphs) {
+ SendTo<OnlineMessages::MorphTargetUpdate>(conn, avatarid, it);
+ }
+ }
+}
+
+bool Online::CheckLoadedMapAndCampaignState(const string & campagin, const string & level_name) {
+ Engine * engine = Engine::Instance();
+ SceneGraph * graph = engine->GetSceneGraph();
+
+ ScriptableCampaign * loaded_campagin = engine->GetCurrentCampaign();
+ if (loaded_campagin == nullptr || graph == nullptr) {
+ return false;
+ }
+
+ string current_campaign = loaded_campagin->GetCampaignID();
+ string campaignId = engine->GetCurrentCampaign()->GetCampaignID();
+ string current_level = graph->level_path_.GetOriginalPathStr();
+ return (current_campaign == campagin) && (level_name == current_level);
+}
+
+void Online::SetLevelLoaded() {
+ if (IsClient()) {
+ // We notify the host about having loaded, the host can only finish loading if we have.
+ // From the client's perspective! The host might already be ingame, client doesn't know.
+ Send<OnlineMessages::PCSLoadingCompletedMessage>();
+ }
+
+ // Host finished loading
+ if (IsHosting()) {
+ loading = false;
+ RemoveAllAvatarIds();
+ AddFreeAvatarsIds();
+ ObjectID avatar = GetFreeAvatarID();
+
+ campaign_id = ModLoading::Instance().WhichCampaignLevelBelongsTo(level_name);
+ LOG_ASSERT(level_name == Engine::Instance()->GetSceneGraph()->level_path_.GetOriginalPathStr());
+ }
+}
+
+uint32_t Online::IncomingMessageCount() const {
+ if(IsActive()) {
+ return online_session->incoming_messages.size();
+ }
+ return 0;
+}
+
+uint32_t Online::OutgoingMessageCount() const {
+ if(IsActive()) {
+ return online_session->outgoing_messages.size();
+ }
+ return 0;
+}
+
+bool Online::GetIfPendingAngelScriptUpdates() {
+ return online_session->peer_queued_level_updates.empty() == false;
+}
+
+bool Online::GetIfPendingAngelScriptStates() {
+ return online_session->peer_queued_sync_updates.empty() == false;
+}
+
+AngelScriptUpdate Online::GetAngelScriptUpdate() {
+ if (online_session->peer_queued_level_updates.empty() == false) {
+ return online_session->peer_queued_level_updates.front();
+ }
+ return AngelScriptUpdate();
+}
+
+void Online::MoveAngelScriptQueueForward() {
+ online_session->peer_queued_level_updates.pop();
+}
+
+void Online::MoveAngelStateQueueForward() {
+ online_session->peer_queued_sync_updates.pop();
+}
+
+AngelScriptUpdate Online::GetAngelScriptStates() {
+ if (online_session->peer_queued_sync_updates.empty() == false) {
+ return online_session->peer_queued_sync_updates.front();
+ }
+ return AngelScriptUpdate();
+}
+
+uint32_t Online::AddSyncState(uint32_t state, const vector<char>& data) {
+ AngelScriptUpdate temp;
+
+ temp.state = state;
+ temp.data = data;
+
+ lock_guard<mutex> guard(online_session->state_packages_lock);
+ online_session->state_packages[state] = temp;
+
+ return state;
+}
+
+uint32_t Online::RegisterMPState(const string& state) {
+ if (states.find(state) != states.end()) {
+ return states[state];
+ }
+
+ states[state] = next_available_state_id++;
+ return states[state];
+}
+
+void Online::SetTickperiod(const uint32_t & tickperiod) {
+ tick_period = tickperiod;
+}
+
+bool Online::IsActive() const {
+ return (IsHosting() || IsClient()) && online_session != nullptr;
+}
+
+bool Online::IsHosting() const {
+ return mode == MultiplayerMode::Host;
+}
+
+bool Online::IsAwaitingShutdown() const {
+ return mode == MultiplayerMode::AwaitingShutdown;
+}
+
+void Online::StartMultiplayer(MultiplayerMode multiplayerMode) {
+ //for(string binding : Input::Instance()->GetAllAvailableBindings()) {
+ // LOGI << binding << " " << (int)Input::Instance()->GetBindID(binding) << endl;
+ //}
+
+ //for(string binding_cat : Input::Instance()->GetAvailableBindingCategories()) {
+ // LOGI << binding_cat << endl;
+ //}
+
+ if(online_session != nullptr) {
+ LOGE << "Attempting to start multiplayer while the previous session is still valid!" << endl;
+ // TODO can we close the application here with a big error? This should be a fatal issue!
+ }
+
+ if(OnlineUtility::HasActiveIncompatibleMods()) {
+ LOGE << "Online multiplayer was enabled despite using unsupported mods, this might cause issues!" << std::endl;
+ LOGE << "Incompatible mods: " << OnlineUtility::GetActiveIncompatibleModsString() << std::endl;
+ }
+
+ online_session = new OnlineSession();
+
+ host_started_level = false;
+ online_session->host_session_flags[OnlineFlags::ALLOWSEDITOR] = false;
+
+ mode = multiplayerMode;
+
+ Engine * engine = Engine::Instance();
+ SceneGraph * graph = engine->GetSceneGraph();
+
+ string mpcachedir = string(write_path) + string("multiplayercache/");
+
+ CreateParentDirs(mpcachedir);
+
+ AddPath(mpcachedir.c_str(), PathFlags::kDataPaths);
+
+ if (mode == MultiplayerMode::Host) {
+ // assert(scenegraph);
+ // TODO this might never be true due to how we handle starting multiplayer now
+ if (engine->GetSceneGraph() != nullptr) {
+ RemoveAllAvatarIds();
+ AddFreeAvatarsIds();
+ host_started_level = true;
+
+ // index 0 is probably not correct
+ ScriptableCampaign* sc = engine->GetCurrentCampaign();
+ if (sc != nullptr) {
+ campaign_id = sc->GetCampaignID();
+ } else {
+ campaign_id = -1;
+ }
+
+ // you can check with scenegraph
+ level_name = graph->level_path_.GetOriginalPathStr();
+ }
+
+ //Assign mappings for input bindings.
+ for(string binding : Input::Instance()->GetAllAvailableBindings()) {
+ AssignBindID(binding);
+ }
+
+ online_session->local_player_id = -1;
+ online_session->player_states[online_session->local_player_id] = GenerateHostPlayerState();
+ }
+
+ net.Initialize();
+
+ engine->menu_paused = false;
+ engine->CommitPause();
+
+ tick_period = 30;
+ socket_thread = (thread(&Online::NetworkDataThread, this));
+}
+
+void Online::StartHostingMultiplayer() {
+ if(!IsActive()) {
+ StartMultiplayer(MultiplayerMode::Host);
+ CreateListenSocketIP("");
+ }
+}
+
+void Online::PerformLevelChangeCleanUp() {
+ lock_guard<mutex> guard(online_session->state_packages_lock);
+ online_session->state_packages.clear();
+}
+
+const vector<Peer> Online::GetPeers() {
+ lock_guard<mutex> guard(online_session->connected_peers_lock);
+ return online_session->connected_peers;
+}
+
+Peer* Online::GetPeerFromConnection(NetConnectionID conn_id) {
+ for(int i = 0; i < online_session->connected_peers.size(); i++) {
+ if(online_session->connected_peers[i].conn_id == conn_id) {
+ return &online_session->connected_peers[i];
+ }
+ }
+ return nullptr;
+}
+
+Peer* Online::GetPeerFromID(PeerID peer_id) {
+ for(int i = 0; i < online_session->connected_peers.size(); i++) {
+ if(online_session->connected_peers[i].peer_id == peer_id) {
+ return &online_session->connected_peers[i];
+ }
+ }
+ return nullptr;
+}
+
+vector<Peer>::iterator Online::GetPeerIt(NetConnectionID conn_id) {
+ for(int i = 0; i < online_session->connected_peers.size(); i++) {
+ if(online_session->connected_peers[i].conn_id == conn_id) {
+ return online_session->connected_peers.begin() + i;
+ }
+ }
+ return online_session->connected_peers.end();
+}
+
+void Online::QueueStopMultiplayer() {
+ if(IsActive()) {
+ mode = MultiplayerMode::AwaitingShutdown;
+ }
+}
+
+void Online::StopMultiplayer() {
+ if(IsActive() || mode == MultiplayerMode::AwaitingShutdown) {
+ mode = MultiplayerMode::CleanUp;
+
+ { // Immediately disconnect all clients
+ lock_guard<mutex> guard(online_session->connected_peers_lock);
+ for (auto& it : online_session->connected_peers) {
+ // We kill the connection on the socket directly, we no longer want to keep track of any connection changes
+ // We can't call CloseConnectionImmediate here, as it modifies the array we are iterating over and uses the same lock
+ net.CloseConnection(it.conn_id, ConnectionClosedReason::HOST_STOPPED_HOSTING);
+ }
+ online_session->connected_peers.clear(); // We shouldn't have to clear this, since we plan on getting rid of online_session shortyly
+ StopListening();
+ }
+
+ // Clear up on the fly
+ SceneGraph* graph = Engine::Instance()->GetSceneGraph();
+ if (graph != nullptr) {
+ vector<Object *> objects = graph->GetObjectsOfType(EntityType::_movement_object);
+
+ for (auto it : objects) {
+ if (it->created_on_the_fly) {
+ graph->map_editor->RemoveObject(it, graph, true);
+ DeRegisterHostClientIDTranslation(it->GetID());
+ }
+ }
+ }
+
+ next_available_state_id = 1;
+ next_free_peer_id = 1;
+
+ host_started_level = false;
+ loading = false;
+ p2p_sockets_active = false;
+
+ if (socket_thread.joinable()) {
+ socket_thread.join();
+ }
+
+ // Make sure we get booted into the main menu, if we aren't there already.
+ if(Engine::Instance()->current_engine_state_.type != EngineStateType::kEngineScriptableUIState) {
+ Engine::Instance()->ScriptableUICallback("back_to_main_menu");
+ }
+
+ mode = MultiplayerMode::NoMultiplayer;
+
+ delete online_session;
+ online_session = nullptr;
+ }
+}
+
+bool Online::IsLocalAvatar(const ObjectID avatar_id) const {
+ if (online_session == nullptr) {
+ // Everything is local when playing offline
+ return true;
+ }
+
+ // Is this avatar controlled by a playerstate? If yes, is it us?
+ for (const auto& it : online_session->player_states) {
+ if (it.second.object_id == avatar_id) {
+ return it.first == online_session->local_player_id;
+ }
+ }
+
+ // As a host, everything not convered in a playerstate is local
+ // As a client, everything not controlled by us playerstate is remote
+ return IsHosting();
+}
+
+vector<ObjectID> Online::GetLocalAvatarIDs() const {
+ vector<ObjectID> local_avatar_ids;
+ SceneGraph* sg = Engine::Instance()->GetSceneGraph();
+ if(sg != nullptr) {
+ for (const auto& it : sg->GetControllableMovementObjects()) {
+ if (IsLocalAvatar(it->GetID())) {
+ local_avatar_ids.push_back(it->GetID());
+ }
+ }
+ }
+ return local_avatar_ids;
+}
+
+PlayerState Online::GenerateHostPlayerState() const {
+ if(!IsHosting()) {
+ LOGW << "Called GenerateHostPlayerState(), but wasn't hosting. Only the host should generate playerstates, especially his own!" << std::endl;
+ PlayerState player_state;
+ return player_state;
+ }
+
+ PlayerState host_player_state;
+ host_player_state.ping = 0;
+ host_player_state.playername = OnlineUtility::GetPlayerName();
+ host_player_state.object_id = -1;
+ return host_player_state;
+}
+
+void Online::AddLocalChatMessage(string chat_message) {
+ lock_guard<mutex> guard(online_session->chat_messages_lock);
+ online_session->chat_messages.push_back({game_timer.game_time, chat_message});
+}
+
+void Online::RemoveOldChatMessages(float threshold) {
+ lock_guard<mutex> guard(online_session->chat_messages_lock);
+ online_session->chat_messages.erase(remove_if(online_session->chat_messages.begin(), online_session->chat_messages.end(),
+ [threshold](ChatMessage i) { return game_timer.game_time - i.time > threshold; }), online_session->chat_messages.end());
+ }
+
+const vector<ChatMessage>& Online::GetChatMessages() const {
+ return online_session->chat_messages;
+}
+
+void Online::AddPeer(NetFrameworkConnectionStatusChanged *data) {
+ if (IsHosting()) {
+ if (IsLobbyFull()) {
+ CloseConnection(data->conn_id, ConnectionClosedReason::LOBBY_FULL);
+ AddLocalChatMessage(string("Somebody tried connecting but we don't have any slots free"));
+ } else {
+ Peer peer;
+ peer.conn_id = data->conn_id;
+ peer.peer_id = next_free_peer_id++;
+ peer.m_info = data->conn_info;
+ {
+ lock_guard<mutex> guard(online_session->connected_peers_lock);
+ online_session->connected_peers.push_back(peer);
+ online_session->client_connection_manager.AddConnection(peer.peer_id, data->conn_id);
+ }
+ }
+ } else {
+ Peer peer;
+ peer.conn_id = data->conn_id;
+ peer.peer_id = 0;
+ peer.m_info = data->conn_info;
+ {
+ lock_guard<mutex> guard(online_session->connected_peers_lock);
+ online_session->connected_peers.push_back(peer);
+ }
+ }
+}
+
+/// Queue termination of a connection
+void Online::CloseConnection(NetConnectionID conn_id, ConnectionClosedReason reason) {
+ if(online_session != nullptr) {
+ ConnectionToBeClosed temp;
+ temp.conn_id = conn_id;
+ temp.reason = reason;
+
+ online_session->connections_waiting_to_be_closed.push(temp);
+ } else {
+ LOGW << "Attempted to close a connection without a valid online session" << endl;
+ assert(online_session != nullptr); // Kill the application for now, this should not be happening!
+ }
+}
+
+/// Close a connection right away, possibly unsafe to call from anywhere
+void Online::CloseConnectionImmediate(NetConnectionID conn_id, ConnectionClosedReason reason) {
+ lock_guard<mutex> guard(online_session->connected_peers_lock);
+
+ net.CloseConnection(conn_id,reason);
+
+ vector<Peer>::iterator peer = GetPeerIt(conn_id);
+ if (peer != online_session->connected_peers.end()) {
+ PlayerID player_id = peer->peer_id; // TODO We assume peer_id == player_id
+ if (online_session->player_states.find(player_id) != online_session->player_states.end()) {
+ auto& player_state = online_session->player_states[player_id];
+
+ SendRawChatMessage(player_state.playername + " disconnected!");
+
+ // Clean up objects assigned to disconnected player
+ SceneGraph * graph = Engine::Instance()->GetSceneGraph();
+ if (graph != nullptr) {
+ Object * o = graph->GetObjectFromID(player_state.object_id);
+ if (o != nullptr) {
+ if (o->created_on_the_fly) {
+ graph->map_editor->RemoveObject(o, graph);
+ } else {
+ online_session->free_avatars.push_back(player_state.object_id);
+ }
+ ((MovementObject*)o)->remote = false;
+ }
+ }
+
+ // Remove playerstate
+ online_session->player_states.erase(player_id);
+ Send<OnlineMessages::RemovePlayerState>(player_id);
+
+ // TODO do we need to clean the camera and controller of the player?
+ }
+
+ online_session->client_connection_manager.RemoveConnection(peer->peer_id);
+ online_session->connected_peers.erase(peer);
+ } else {
+ LOGW << "Tried closing a connection, but there is no peer assigned to NetConnectionID: " << conn_id << std::endl;
+ }
+}
+
+void Online::CheckPendingMessages() {
+ //Locking online_session->connected_peers_lock here because it's used in some arrays here, and if done more specifically
+ //theres a risk for overlapping locks with CheckNewMessages(), causing a full lockup.
+ //TODO: Reduce number of locks in multiplayer.
+ lock_guard<mutex> guard(online_session->connected_peers_lock);
+
+ {
+ lock_guard<mutex> guard(online_session->incoming_messages_lock);
+
+ for(auto& it : online_session->incoming_messages) {
+ message_handler.Execute(it.message, it.from);
+ }
+
+ online_session->incoming_messages.clear();
+ }
+
+ if (host_started_level) {
+ ApplyPlayerInput();
+ }
+}
+
+void Online::ApplyPlayerInput() {
+ for(auto& it : online_session->player_inputs) {
+ // We don't want to apply two inputs of the same key on the same frame
+ // Releasing and pressing down the key should not be processed on the same frame
+ unordered_map<uint8_t, int> depth;
+
+ for (list<OnlineMessageRef>::iterator i = it.second.begin(); i != it.second.end();) {
+ PlayerInputMessage* piks = static_cast<PlayerInputMessage*>(it.second.front().GetData());
+
+ // Get player involved in the input, discard input if missing
+ if (online_session->player_states.find(it.first) == online_session->player_states.end()) {
+ break;
+ }
+
+ int controller_id = online_session->player_states[it.first].controller_id;
+ PlayerInput* player_input = Input::Instance()->GetController(controller_id);
+ if (player_input == nullptr) {
+ break;
+ }
+
+ if (depth.find(piks->id) != depth.end()) {
+ int velocity = piks->count - depth[piks->id];
+
+ // a negative velocity mean't that we pressed and release
+ // process this later.
+ if (velocity < 0) {
+ i++;
+ continue;
+ }
+
+ // this is a first frame down, process all other inputs on the next frame
+ if (depth[piks->id] == 1) {
+ i++;
+ continue;
+ }
+ }
+
+ // process the input
+ KeyState& key_state = player_input->key_down[online_session->binding_name_map_[piks->id]];
+ depth[piks->id] = piks->count;
+ key_state.count = piks->count;
+ key_state.depth_count = piks->depth_count;
+ key_state.depth = piks->depth;
+
+ // we are done with this input
+ it.second.erase(i++);
+ }
+ }
+}
+
+PlayerState Online::GetOwnPlayerState() {
+ std::lock_guard<std::mutex> guard (online_session->connected_peers_lock);
+
+ auto it = online_session->player_states.find(online_session->local_player_id);
+ if (it != online_session->player_states.end()) {
+ return it->second;
+ }
+
+ LOGW << "Tried to get own players state, but there is no state registered for player id: " << std::to_string(online_session->local_player_id) << std::endl;
+ PlayerState fallback;
+ return fallback;
+}
+
+bool Online::TryGetPlayerState(PlayerState& player_state, ObjectID object_id) const {
+ if (IsActive()) {
+ for (auto& it : online_session->player_states) {
+ if (it.second.object_id == object_id) {
+ player_state = it.second;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+void Online::AssignNewControllerForPlayer(PlayerID player_id) {
+ online_session->player_states[player_id].controller_id = Input::Instance()->AllocateRemotePlayerInput();
+ online_session->player_states[player_id].camera_id = ActiveCameras::Instance()->CreateVirtualCameraInstance();
+}
+
+size_t Online::CompressData(vector<char>& target_buffer, const void* source_buffer, uint32_t source_size) {
+ size_t maxSize = ZSTD_compressBound(source_size);
+
+ if(target_buffer.size() < maxSize) {
+ target_buffer.resize(maxSize);
+ }
+
+ size_t compressedSize = ZSTD_compressCCtx(zstdCContext,
+ target_buffer.data(),
+ target_buffer.size(),
+ source_buffer,
+ source_size,
+ compression_level);
+
+ if(compressedSize > maxSize) {
+ LOGE << "Failed to compress data, assumed size was smaller than the real size" << endl;
+ }
+
+ target_buffer.resize(compressedSize);
+
+ return compressedSize;
+}
+
+size_t Online::DecompressData(vector<char>& target_buffer, const void* source_buffer, size_t source_size) {
+ size_t decompressed_size = ZSTD_getFrameContentSize(source_buffer, source_size);
+
+ if(decompressed_size == ZSTD_CONTENTSIZE_UNKNOWN) {
+ LOGW << "Unable to determine uncompressed package size" << std::endl;
+ } else if (decompressed_size == ZSTD_CONTENTSIZE_ERROR) {
+ LOGE << "Error while trying to determine content size" << std::endl;
+ } else if(target_buffer.size() < decompressed_size) {
+ target_buffer.resize(decompressed_size);
+ }
+
+ size_t final_decompressed_size = 0;
+ while(true) {
+ size_t final_decompressed_size = ZSTD_decompressDCtx(zstdDContext, target_buffer.data(), target_buffer.size(), source_buffer, source_size);
+ if (ZSTD_isError(final_decompressed_size)) {
+ LOGE << ZSTD_getErrorString(ZSTD_getErrorCode(final_decompressed_size)) << endl;
+ } else if(final_decompressed_size > target_buffer.size()) {
+ //If the final decompressed size is larger than the target buffer, re-do the decompression after resizing.
+ target_buffer.resize(final_decompressed_size);
+ continue;
+ }
+ break;
+ }
+
+ return final_decompressed_size;
+}
+
+uint32_t Online::GetPlayerCount() {
+ lock_guard<mutex> guard(online_session->connected_peers_lock);
+ return online_session->player_states.size();
+}
+
+/// Hosts will add their name here, clients will have their name added by the host later
+void Online::BroadcastChatMessage(const string& chat_message) {
+ if(IsActive()) {
+ if(IsHosting()) {
+ if (OnlineUtility::IsCommand(chat_message)) {
+ OnlineUtility::HandleCommand(online_session->local_player_id, chat_message);
+ AddLocalChatMessage(OnlineUtility::GetPlayerName() + ": " + chat_message);
+ } else {
+ SendRawChatMessage(OnlineUtility::GetPlayerName() + ": " + chat_message);
+ }
+ } else {
+ SendRawChatMessage(chat_message);
+ }
+ }
+}
+
+void Online::SendRawChatMessage(const string& raw_chat_entry) {
+ if(IsActive()) {
+ Send<OnlineMessages::ChatEntryMessage>(raw_chat_entry);
+ if (IsHosting()) {
+ AddLocalChatMessage(raw_chat_entry);
+ }
+ }
+}
+
+vector<PlayerState> Online::GetPlayerStates() {
+ if (IsActive()) {
+ vector<PlayerState> player_states;
+ for (const auto& it : online_session->player_states) {
+ player_states.push_back(it.second);
+ }
+ return player_states;
+ }
+
+ vector<PlayerState> empty;
+ return empty;
+}
+
+void Online::Send(const OnlineMessageRef& message_ref) {
+ if(IsActive()) {
+ lock_guard<mutex> guard(online_session->outgoing_messages_lock);
+ online_session->outgoing_messages.push_back({true, 0, message_ref});
+ }
+}
+
+void Online::SendTo(NetConnectionID target, const OnlineMessageRef& message_ref) {
+ if(IsActive()) {
+ lock_guard<mutex> guard(online_session->outgoing_messages_lock);
+ online_session->outgoing_messages.push_back({false, target, message_ref});
+ }
+}
+
+void Online::SendIntFloatScriptParam(uint32_t id, const string& key, const ScriptParam& param) {
+ if (param.IsFloat()) {
+ Send<OnlineMessages::SPUnionMessage>(id, key, param.GetFloat(), ScriptParam::ScriptParamType::FLOAT, param.editor().type(), param.editor().GetDetails());
+ } else {
+ Send<OnlineMessages::SPUnionMessage>(id, key, param.GetInt(), ScriptParam::ScriptParamType::INT, param.editor().type(), param.editor().GetDetails());
+ }
+}
+
+void Online::SendStringScriptParam(uint32_t id, const string& key, const ScriptParam& param) {
+ Send<OnlineMessages::SPStringMessage>(id, key, param.GetStringForSocket(), param.editor().type(), param.editor().GetDetails());
+}
+
+void Online::SendScriptParam(uint32_t id, const string& key, const ScriptParam& param) {
+ if (param.IsString()) {
+ SendStringScriptParam(id, key, param);
+ } else {
+ SendIntFloatScriptParam(id, key, param);
+ }
+}
+
+void Online::SendScriptParamMap(uint32_t id, const ScriptParamMap& param){
+ for (auto& it : param) {
+ SendScriptParam(id, it.first, it.second);
+ }
+}
+
+bool Online::GetHostAllowsClientEditor() const {
+ if (online_session == nullptr)
+ return false;
+ return online_session->host_session_flags[OnlineFlags::ALLOWSEDITOR];
+}
+
+void Online::SetIfHostAllowsClientEditor(bool mode) {
+ if (online_session != nullptr) {
+ online_session->host_session_flags[OnlineFlags::ALLOWSEDITOR] = mode;
+ }
+}
+
+void Online::SetDefaultHotJoinCharacter(const string & path) {
+ default_hot_join_characte_path = path;
+}
+
+string Online::GetDefaultHotJoinCharacter() const {
+ return default_hot_join_characte_path;
+}
+
+void Online::SessionStarted(bool host_started_the_level) {
+ host_started_level = host_started_the_level;
+}
+
+bool Online::ForceMapStartOnLoad() const {
+ return host_started_level;
+}
+
+bool Online::AllClientsReady() {
+ lock_guard<mutex> guard(online_session->connected_peers_lock);
+ return online_session->client_connection_manager.IsEveryClientLoaded();
+}
+
+ScriptParams* Online::GetScriptParamsFromID(ObjectID id) {
+ if(Engine::Instance()->GetSceneGraph() != nullptr) {
+ if(id == numeric_limits<uint32_t>::max())
+ return &Engine::Instance()->GetSceneGraph()->level->script_params();
+
+ Object* o = Engine::Instance()->GetSceneGraph()->GetObjectFromID(id);
+ if (o != nullptr)
+ return o->GetScriptParams();
+ }
+ return nullptr;
+}
+
+void Online::UpdateMovementObjectFromID(uint32_t id) {
+ if(id != numeric_limits<uint32_t>::max()) {
+ Object * o = Engine::Instance()->GetSceneGraph()->GetObjectFromID(id);
+ if(o != nullptr && o->GetType() == EntityType::_movement_object) {
+ ((MovementObject*)o)->UpdateScriptParams();
+ }
+ }
+}
+
+bool Online::SetAvatarCameraAttachedMode(bool mode) {
+ return attach_avatar_camera = mode;
+}
+
+bool Online::IsAvatarCameraAttached() const {
+ return attach_avatar_camera && Engine::Instance()->GetSceneGraph()->map_editor->state_ == MapEditor::kInGame;
+}
+
+void Online::SendLevelMessage(const string& msg) {
+ // we currently only want start_dialogue
+ if (msg.find("reset") == string::npos)
+ return; // tutorial is very spammy
+
+ Send<OnlineMessages::SendLevelMessage>(msg);
+}
+
+void Online::UpdateObjects(SceneGraph *scenegraph_) {
+ uint32_t wall_ticks = game_timer.GetWallTicks();
+ uint32_t tick_delta = wall_ticks - last_update_wall_ticks;
+ uint32_t quarter_tick_delta = wall_ticks - last_quarter_update_wall_ticks;
+
+ bool is_quarter_tick = quarter_tick_delta >= tick_period/4;
+ bool is_tick = tick_delta >= tick_period;
+
+ if (IsHosting()) {
+ for(Object *o : scenegraph_->movement_objects_) {
+ MovementObject* mo = static_cast<MovementObject*>(o);
+ RiggedObject* rigged_object = mo->rigged_object();
+
+ // TODO: Loop over peers and do a more frequent update of their own character.
+ // Increse the rate further whenever there's a recent input package from that peer tied to the character.
+ // We want to send a package the same frame that the object's movement intention has changed to keep it up-to-date.
+ // We check when the movementobject was last updated. There is no reason to send updates if it hasn't updated
+ static std::unordered_map<MovementObject*, float> last_timestamps;
+ if (is_quarter_tick && last_timestamps[mo] != mo->walltime_last_update) {
+ last_timestamps[mo] = mo->walltime_last_update;
+ Send<OnlineMessages::MovementObjectUpdate>(mo);
+
+ vector<MorphTargetStateStorage>& network_morphs = rigged_object->network_morphs;
+ for (auto& it : network_morphs) {
+ if (it.dirty) {
+ Send<OnlineMessages::MorphTargetUpdate>(mo->GetID(), it);
+ it.dirty = false;
+ }
+ }
+ }
+ }
+
+ if (is_tick) {
+ for (Object *o : scenegraph_->objects_) {
+ switch (o->GetType()) {
+ case _env_object:
+ if (o->online_transform_dirty) {
+ Send<OnlineMessages::EnvObjectUpdate>(static_cast<EnvObject*>(o));
+ }
+ break;
+
+ case _item_object:
+ Send<OnlineMessages::ItemUpdate>(static_cast<ItemObject*>(o));
+ break;
+ }
+ o->online_transform_dirty = false;
+ }
+ }
+ } else if(IsClient()) {
+ if (is_tick) {
+ SendPlayerCameraState();
+ }
+ //Send input information every frame, we want to make sure we get the latest.
+ SendPlayerInputState();
+ }
+
+ if(is_tick) {
+ last_update_wall_ticks += tick_delta;
+ }
+
+ if (is_quarter_tick) {
+ last_quarter_update_wall_ticks += quarter_tick_delta;
+ }
+}
+
+bool Online::HasFreeAvatars() const {
+ return !online_session->free_avatars.empty();
+}
+
+uint32_t Online::GetNumberOfFreeAvatars() const {
+ return online_session->free_avatars.size();
+}
+
+uint8_t Online::GetPlayerLimit() {
+ if(Engine::Instance()->GetSceneGraph() != nullptr && Engine::Instance()->GetSceneGraph()->level != nullptr) {
+ if(Engine::Instance()->GetSceneGraph()->level->script_params().HasParam("Player Limit")) {
+ return Engine::Instance()->GetSceneGraph()->level->script_params().ASGetInt("Player Limit");
+ }
+ return 8; // Default to 8 players (arbitrary limit)
+ }
+ return 1; // No level, no players
+}
+
+bool Online::IsLobbyFull() {
+ return GetPlayerLimit() <= GetPlayerCount();
+}
+
+void Online::AddFreeAvatarsIds() {
+ Engine::Instance()->GetAvatarIds(online_session->free_avatars);
+}
+
+void Online::RemoveAllAvatarIds() {
+ online_session->free_avatars.clear();
+ online_session->taken_avatars.clear();
+}
+
+void Online::RemoveFreeAvatarId(uint32_t id) {
+ vector<ObjectID>::iterator it = find(online_session->free_avatars.begin(), online_session->free_avatars.end(), id);
+
+ if (it != online_session->free_avatars.end()) {
+ online_session->free_avatars.erase(it);
+ }
+}
+
+void Online::AddFreeAvatarId(uint32_t id) {
+ vector<ObjectID>::iterator it = find(online_session->taken_avatars.begin(), online_session->taken_avatars.end(), id);
+
+ if (it == online_session->taken_avatars.end()) {
+ online_session->free_avatars.push_back(id);
+ }
+}
+
+ObjectID Online::GetFreeAvatarID() {
+ ObjectID avatarID = -1;
+ if (!online_session->free_avatars.empty()) {
+ auto front = online_session->free_avatars.begin();
+ avatarID = *front;
+ online_session->taken_avatars.push_back(avatarID);
+ online_session->free_avatars.erase(front);
+ }
+
+ return avatarID;
+}
+
+void Online::OnConnectionChange(NetFrameworkConnectionStatusChanged *data) {
+ switch (data->conn_info.connection_state) {
+ case NetFrameworkConnectionState::Connecting: {
+ if (IsHosting()) {
+ bool accept_connection = true;
+ if(accept_connection) {
+ int accept_connection_result;
+ bool is_ok = net.AcceptConnection(data->conn_id, &accept_connection_result);
+
+ if (is_ok) {
+ AddLocalChatMessage(string("Accepted connection: ") + to_string(data->conn_id));
+ } else {
+ string error_string = to_string(data->conn_id) + " failed to accept connection: (" + net.GetResultString(accept_connection_result) + ")";
+ AddLocalChatMessage(error_string);
+ LOGW << error_string << endl;
+ }
+ } else {
+ net.CloseConnection(data->conn_id, ConnectionClosedReason::UNSPECIFIED);
+ }
+ } else {
+ //We come here when we have an outward connection from the client.
+ }
+ break;
+ }
+
+ case NetFrameworkConnectionState::ClosedByPeer:
+ {
+ ConnectionClosedReason reason = data->conn_info.end_reason;
+ CloseConnection(data->conn_id, reason);
+ if (IsClient()) {
+ StopMultiplayer();
+
+ if(ConnectionClosedReasonUtil::IsUnusualReason(reason)) {
+ string error = ConnectionClosedReasonUtil::GetErrorMessage(reason);
+ Engine::Instance()->QueueErrorMessage("Connection Closed", error);
+ }
+ }
+ break;
+ }
+
+ case NetFrameworkConnectionState::ProblemDetectedLocally:
+ {
+ ConnectionClosedReason reason = data->conn_info.end_reason;
+ if (IsClient()) {
+ if(ConnectionClosedReasonUtil::IsUnusualReason(reason)) {
+ string error = ConnectionClosedReasonUtil::GetErrorMessage(reason);
+ Engine::Instance()->QueueErrorMessage("Connection Lost", error);
+ }
+ }
+
+ CloseConnection(data->conn_id, reason);
+ break;
+ }
+
+ case NetFrameworkConnectionState::FindingRoute: {
+ // packages will be dropped in here
+ break;
+ }
+
+ case NetFrameworkConnectionState::Connected: {
+ AddPeer(data);
+ break;
+ }
+
+ default: {
+ LOGE << "Online::OnConnectionChange: Connection has unhandeld state" << endl;
+ LOGE << data->conn_info.connection_state << endl;
+ }
+ }
+}
+
+void Online::StopListening() {
+ for (auto& socket : listen_sockets) {
+ net.CloseListenSocket(socket);
+ }
+
+ listen_sockets.clear();
+}
+
+bool Online::NetFrameworkHasFriendInviteOverlay() const {
+ return net.HasFriendInviteOverlay();
+}
+
+void Online::ActivateGameOverlayInviteDialog() {
+ if (!IsActive()) {
+ LOGW << "Attempted to show Game Overlay Invite dialogue while offline!" << std::endl;
+ return;
+ }
+
+#if ENABLE_STEAMWORKS
+ // TODO this is steam specific code, it needs to be moved to SteamNetFramework
+ // Preferably called "ActivateFriendInviteOverlay()" to match HasFriendInviteOverlay()
+ SteamworksMatchmaking *matchmaking = Steamworks::Instance()->GetMatchmaking();
+ if (matchmaking) {
+ if (!p2p_sockets_active) {
+ HSteamListenSocket listen = matchmaking->ActivateGameOverlayInviteDialog();
+ listen_sockets.insert(listen);
+ p2p_sockets_active = true;
+ } else {
+ matchmaking->OpenInviteDialog();
+ }
+ } else {
+ LOGE << "Couldn't open invite overlay page" << endl;
+ }
+#else
+ LOGW << "Tried opening a game invite overlay, but we haven't compiled in any support for one." << endl;
+#endif
+}
+
+void Online::CreateListenSocketIP(const string &local_address) {
+ NetListenSocketID listen_socket = net.CreateListenSocketIP(local_address);
+ listen_sockets.insert(listen_socket);
+}
+
+void Online::ConnectByIPAddress(const string &address) {
+ if (!IsActive()) {
+ StartMultiplayer(MultiplayerMode::Client);
+
+ NetConnectionID conn_id;
+
+ if(net.ConnectByIPAddress(address, &conn_id) == false) {
+ StopMultiplayer();
+ }
+ }
+}
+
+uint8_t Online::GetBindID(string binding_name) {
+ return online_session->binding_id_map_[binding_name];
+}
+
+void Online::AssignBindID(string binding_name) {
+ if(online_session->binding_id_map_.find(binding_name) == online_session->binding_id_map_.end()) {
+ online_session->binding_id_map_[binding_name] = ++online_session->binding_id_map_counter_;
+ online_session->binding_name_map_[online_session->binding_id_map_[binding_name]] = binding_name;
+ } else {
+ LOGW << "Binding \"" << binding_name << "\" has been assigned multiple times!" << std::endl;
+ }
+}
+
+void Online::NetworkDataThread() {
+ while (IsActive()) {
+ net.Update();
+ CheckNewMessages();
+ SendMessages();
+
+ if(IsHosting()) {
+ HandleConnectionsToBeClosed();
+ }
+
+ message_handler.StepFreeUnreferencedMessage();
+ }
+}
+
+void Online::ClearOnlineSession() {
+ delete online_session;
+ online_session = new OnlineSession();
+}
+
+void Online::SendMessages() {
+ SendMessageObjects();
+ if (IsHosting()) {
+ SendStateMessages();
+ }
+}
+
+void Online::SendMessageObjects() {
+ online_session->connected_peers_lock.lock();
+
+ online_session->outgoing_messages_lock.lock();
+
+ //First, send persistent packages, if we see there's a new client who isn't up to date.
+ if(host_started_level && IsHosting()) {
+ for(int k = 0; k < online_session->connected_peers.size(); k++) {
+ Peer& peer = online_session->connected_peers[k];
+
+ if(online_session->client_connection_manager.IsClientLoaded(peer.peer_id) && online_session->client_connection_manager.HasClientGottenPersistentQueue(peer.peer_id) == false) {
+ for(int i = 0; i < online_session->persistent_outgoing_messages.size(); i++) {
+ OnlineMessageBase* omb = static_cast<OnlineMessageBase*>(message_handler.GetMessageData(online_session->persistent_outgoing_messages[i]));
+
+ if(omb != nullptr) {
+ PackageHeader package_header;
+
+ package_header.package_type = PackageHeader::Type::MESSAGE_OBJECT;
+ package_header.message_ref = online_session->persistent_outgoing_messages[i];
+
+ binn* l = package_header.Serialize();
+
+ size_t compressed_size = CompressData(compressed_serialization_buffer, binn_ptr(l), binn_size(l));
+
+ binn_free(l);
+
+ SendMessageToConnection(peer.conn_id, compressed_serialization_buffer.data(), compressed_size, true, false);
+ } else {
+ LOGE << "Package data pointer was null, this is unexpected and odd." << endl;
+ }
+ }
+ online_session->client_connection_manager.SetClientGottenPersistentQueue(peer.peer_id);
+ }
+ }
+ }
+
+ //Send the standard message queues. Store away persistent messages for future client peers if we are host.
+ for(int i = 0; i < online_session->outgoing_messages.size(); i++) {
+ OnlineSession::OutgoingMessage& outgoing_message = online_session->outgoing_messages[i];
+ OnlineMessageBase* omb = static_cast<OnlineMessageBase*>(message_handler.GetMessageData(outgoing_message.message));
+
+ if(omb != nullptr) {
+ PackageHeader package_header;
+
+ package_header.package_type = PackageHeader::Type::MESSAGE_OBJECT;
+ package_header.message_ref = outgoing_message.message;
+
+ binn* l = package_header.Serialize();
+
+ size_t compressed_size = CompressData(compressed_serialization_buffer, binn_ptr(l), binn_size(l));
+
+ binn_free(l);
+
+ for(int k = 0; k < online_session->connected_peers.size(); k++) {
+ Peer& peer = online_session->connected_peers[k];
+ bool send_message = true;
+
+ if(outgoing_message.broadcast == false && outgoing_message.target != peer.conn_id) {
+ send_message = false;
+ //Don't send packags to client peers who have recently connected, but hasn't loaded the level, or finished getting the persistent queue yet.
+ } else if(IsHosting() && omb->cat != OnlineMessageCategory::TRANSIENT && online_session->client_connection_manager.HasClientGottenPersistentQueue(peer.peer_id) == false) {
+ send_message = false;
+ }
+
+ if(send_message) {
+ SendMessageToConnection(peer.conn_id, compressed_serialization_buffer.data(), compressed_size, omb->reliable_delivery, i+1 == online_session->outgoing_messages.size());
+ }
+ }
+
+ if(IsHosting()) {
+ if(omb->cat == OnlineMessageCategory::LEVEL_PERSISTENT) {
+ online_session->persistent_outgoing_messages.push_back(outgoing_message.message);
+ }
+ }
+ } else {
+ LOGE << "Package data pointer was null, this is unexpected and odd." << endl;
+ }
+ }
+
+ online_session->outgoing_messages.clear();
+
+ online_session->outgoing_messages_lock.unlock();
+
+ online_session->connected_peers_lock.unlock();
+}
+
+void Online::HandleConnectionsToBeClosed() {
+ while (online_session->connections_waiting_to_be_closed.empty() == false) {
+ ConnectionToBeClosed& connection = online_session->connections_waiting_to_be_closed.front();
+ CloseConnectionImmediate(connection.conn_id, connection.reason);
+ online_session->connections_waiting_to_be_closed.pop();
+ }
+}
+
+void Online::SendMessageToConnection(NetConnectionID conn_id, char* buffer, uint32_t bytes, bool reliable, bool flush) {
+ net.SendMessageToConnection(conn_id, buffer, bytes, reliable, flush);
+}
+
+void Online::SendStateMessages() {
+ lock_guard<mutex> guard(online_session->connected_peers_lock);
+ for (auto& conn : online_session->connected_peers) {
+ if(conn.should_send_state_messages) {
+ {
+ lock_guard<mutex> guard(online_session->state_packages_lock);
+ for (auto& package : online_session->state_packages) {
+ SendTo<OnlineMessages::AngelscriptData>(conn.conn_id, package.first, package.second.data, true);
+ }
+ }
+
+ conn.should_send_state_messages = false;
+ }
+ }
+}
+
+void Online::CheckNewMessages() {
+ // we are locking over a big segment and I/O, but it's not dangerous
+ // calls that could be waiting are not time sensitive.
+ lock_guard<mutex> guard(online_session->connected_peers_lock);
+
+ //static SteamNetworkingMessage_t ** msg = (SteamNetworkingMessage_t **)malloc(sizeof(SteamNetworkingMessage_t*) * 1);
+ NetworkingMessage msg;
+
+ for (auto& it : online_session->connected_peers) {
+ int nrOfMessages = net.ReceiveMessageOnConnection(it.conn_id, &msg);
+
+ if (nrOfMessages == -1) {
+ LOGE << "Invalid connection handle: " << it.conn_id << endl;
+ }
+
+ for (int i = 0; i < nrOfMessages; i++) {
+ PackageHeader package_header;
+ static vector<char> data;
+ DecompressData(data, msg.GetData(), msg.GetSize());
+ package_header.Deserialize(data.data());
+
+ if (package_header.package_type == PackageHeader::Type::MESSAGE_OBJECT) {
+ online_session->incoming_messages_lock.lock();
+ online_session->incoming_messages.push_back({it.peer_id, package_header.message_ref});
+ online_session->incoming_messages_lock.unlock();
+ } else {
+ LOGE << "Unrecognized package type: " << (int)package_header.package_type << endl;
+ }
+ msg.Release();
+
+ }
+ }
+}
+
+binn* PackageHeader::Serialize() {
+ binn* l = binn_list();
+
+ binn_list_add_int8(l, (int8_t)package_type);
+ if(package_type == Type::MESSAGE_OBJECT) {
+ binn_list_add_int8(l, (int8_t)message_handler.GetMessageType(message_ref));
+ binn* serialized_object = message_handler.Serialize(message_ref);
+ if(serialized_object != nullptr) {
+ binn_list_add_object(l,serialized_object);
+ } else {
+ LOGE << "Serialization of PackageHeader message resulted in a nullptr" << endl;
+ }
+ binn_free(serialized_object);
+ }
+
+ return l;
+}
+
+void PackageHeader::Deserialize(void* data) {
+ //Do a raw read, don't worry about copying the data first.
+ binn* l = (binn*)data;
+
+ int pos = 1;
+
+ int8_t v_package_type;
+ if(binn_list_get_int8(l, pos++, &v_package_type) == false) {
+ LOGE << "Failed to read package type from binn message" << endl;
+ }
+ this->package_type = (Type)v_package_type;
+
+ if(package_type == Type::MESSAGE_OBJECT) {
+ int8_t message_type;
+ binn_list_get_int8(l, pos++, &message_type);
+
+ void* serialized_object;
+ binn_list_get_object(l, pos++, &serialized_object);
+
+ if(serialized_object != nullptr) {
+ message_ref = message_handler.Deserialize((OnlineMessageType)message_type, (binn*)serialized_object);
+ } else {
+ LOGE << "Got a nullptr when trying to fetch a MESSAGE_OBJECT binn from a PackageHeader" << endl;
+ }
+ }
+}
diff --git a/Source/Online/online.h b/Source/Online/online.h
new file mode 100644
index 00000000..32502deb
--- /dev/null
+++ b/Source/Online/online.h
@@ -0,0 +1,417 @@
+//-----------------------------------------------------------------------------
+// Name: online.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Online/online_datastructures.h>
+#include <Online/online_session.h>
+#include <Online/online_peer.h>
+#include <Online/online_file_transfer_handler.h>
+#include <Online/online_message_handler.h>
+#include <Online/state_machine.h>
+
+#include <Online/Message/audio_play_sound_group_relative_gain_message.h>
+#include <Online/Message/audio_play_sound_group_gain_message.h>
+#include <Online/Message/audio_play_sound_loop_message.h>
+#include <Online/Message/audio_play_sound_loop_at_location_message.h>
+#include <Online/Message/whoosh_sound_message.h>
+#include <Online/Message/audio_play_group_priority_message.h>
+#include <Online/Message/audio_play_sound_message.h>
+#include <Online/Message/audio_play_sound_location_message.h>
+#include <Online/Message/audio_play_sound_group_relative_message.h>
+#include <Online/Message/audio_play_sound_group_message.h>
+#include <Online/Message/audio_play_sound_group_voice_message.h>
+#include <Online/Message/audio_play_sound_impact_item_message.h>
+#include <Online/Message/pcs_build_version_request_message.h>
+#include <Online/Message/pcs_build_version_message.h>
+#include <Online/Message/pcs_loading_completed_message.h>
+#include <Online/Message/pcs_client_parameters_message.h>
+#include <Online/Message/pcs_session_parameters_message.h>
+#include <Online/Message/pcs_assign_player_id.h>
+#include <Online/Message/pcs_file_transfer_metadata_message.h>
+#include <Online/Message/set_player_state.h>
+#include <Online/Message/remove_player_state.h>
+#include <Online/Message/sp_string_message.h>
+#include <Online/Message/sp_union_message.h>
+#include <Online/Message/sp_remove_message.h>
+#include <Online/Message/sp_rename_message.h>
+#include <Online/Message/chat_entry_message.h>
+#include <Online/Message/set_object_enabled_message.h>
+#include <Online/Message/player_input_message.h>
+#include <Online/Message/attach_to_message.h>
+#include <Online/Message/camera_transform_message.h>
+#include <Online/Message/test_message.h>
+#include <Online/Message/movement_object_update.h>
+#include <Online/Message/morph_target_update.h>
+#include <Online/Message/item_update.h>
+#include <Online/Message/env_object_update.h>
+#include <Online/Message/cut_line.h>
+#include <Online/Message/angelscript_data.h>
+#include <Online/Message/angelscript_object_data.h>
+#include <Online/Message/material_sound_event.h>
+#include <Online/Message/host_session_flag.h>
+#include <Online/Message/timed_slow_motion.h>
+#include <Online/Message/create_entity.h>
+#include <Online/Message/remove_object.h>
+#include <Online/Message/set_avatar_palette.h>
+#include <Online/Message/send_level_message.h>
+#include <Online/Message/editor_transform_change.h>
+#include <Online/Message/ping.h>
+#include <Online/Message/pong.h>
+
+#include <Math/vec3.h>
+#include <Math/mat4.h>
+
+#include <Game/connection_closed_reason.h>
+#include <Game/levelinfo.h>
+
+#include <UserInput/input.h>
+#include <Math/quaternions.h>
+#include <Utility/block_allocator.h>
+#include <Internal/filesystem.h>
+#include <Network/net_framework.h>
+
+#include <cstdint>
+#include <list>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+#include <queue>
+#include <array>
+#include <list>
+#include <thread>
+#include <mutex>
+
+class MovementObject;
+class SceneGraph;
+class EnvObject;
+class Object;
+class ItemObject;
+class Engine;
+struct PlayerInput;
+
+struct ZSTD_CCtx_s;
+typedef struct ZSTD_CCtx_s ZSTD_CCtx;
+struct ZSTD_DCtx_s;
+typedef struct ZSTD_DCtx_s ZSTD_DCtx;
+
+using std::array;
+using std::unordered_map;
+using std::string;
+using std::list;
+using std::vector;
+using std::thread;
+using std::mutex;
+using std::unordered_set;
+using std::numeric_limits;
+using std::lock_guard;
+using std::pair;
+using std::endl;
+using std::to_string;
+using std::stringstream;
+using std::runtime_error;
+using std::map;
+
+using namespace OnlineMessages;
+
+typedef OnlineMessageHandlerTemplate<
+ TestMessage,
+ AudioPlayGroupPriorityMessage,
+ AudioPlaySoundLoopMessage,
+ AudioPlaySoundGroupGainMessage,
+ AudioPlaySoundLocationMessage,
+ AudioPlaySoundMessage,
+ AudioPlaySoundLoopAtLocationMessage,
+ AudioPlaySoundGroupRelativeMessage,
+ AudioPlaySoundGroupMessage,
+ AudioPlaySoundGroupVoiceMessage,
+ AudioPlaySoundImpactItemMessage,
+ AudioPlaySoundGroupRelativeGainMessage,
+ WhooshSoundMessage,
+ ChatEntryMessage,
+ SetObjectEnabledMessage,
+ PlayerInputMessage,
+ AttachToMessage,
+ CameraTransformMessage,
+ MovementObjectUpdate,
+ PCSBuildVersionRequestMessage,
+ PCSBuildVersionMessage,
+ PCSLoadingCompletedMessage,
+ PCSSessionParametersMessage,
+ PCSClientParametersMessage,
+ PCSAssignPlayerID,
+ PCSFileTransferMetadataMessage,
+ SetPlayerState,
+ RemovePlayerState,
+ SPStringMessage,
+ SPUnionMessage,
+ SPRemoveMessage,
+ SPRenameMessage,
+ MorphTargetUpdate,
+ ItemUpdate,
+ EnvObjectUpdate,
+ CutLine,
+ AngelscriptData,
+ AngelscriptObjectData,
+ MaterialSoundEvent,
+ HostSessionFlag,
+ TimedSlowMotion,
+ CreateEntity,
+ RemoveObject,
+ SetAvatarPalette,
+ SendLevelMessage,
+ EditorTransformChange,
+ Ping,
+ Pong
+> OnlineMessageHandler;
+
+extern OnlineMessageHandler message_handler;
+
+class Online {
+public:
+ int compression_level;
+
+ string level_name;
+ string campaign_id;
+ bool host_started_level;
+
+ OnlineSession* online_session;
+
+private:
+ NetFramework net;
+
+ OnlineFileTransferHandler online_file_transfer_handler;
+
+ vector<char> compressed_serialization_buffer;
+ thread socket_thread;
+ unordered_map<string, uint32_t> states; // This is angelscript states, these need to exist independent of Multiplayer to allow for states to be set while not hosting to transfer when starting to host
+ unordered_map<CommonObjectID, ObjectID> to_object_id;
+ unordered_map<ObjectID, CommonObjectID> to_common_id;
+
+ bool attach_avatar_camera;
+ MultiplayerMode mode;
+ uint64_t initial_ts;
+ uint32_t next_available_state_id;
+ bool loading;
+
+ PeerID next_free_peer_id;
+ uint32_t tick_period;
+ bool p2p_sockets_active = false;
+ unordered_set<NetListenSocketID> listen_sockets;
+
+ float no_data_interpstep_override = 0.5f;
+ uint32_t last_update_wall_ticks = 0;
+ uint32_t last_quarter_update_wall_ticks = 0;
+ string default_hot_join_characte_path;
+
+ ZSTD_CCtx *zstdCContext;
+ ZSTD_DCtx *zstdDContext;
+
+ map<string, KeyState> client_key_down_map_state;
+
+public:
+
+ static Online* Instance() {
+ static Online online;
+ return &online;
+ }
+
+ Online();
+ void Dispose();
+
+
+ bool IsClient() const;
+ bool IsActive() const;
+ bool IsHosting() const;
+ bool IsAwaitingShutdown() const;
+ uint32_t GetPlayerCount();
+
+ void StartMultiplayer(MultiplayerMode multiplayerMode);
+ void StartHostingMultiplayer();
+
+ void QueueStopMultiplayer();
+ void StopMultiplayer();
+ void StopListening();
+
+ vector<PlayerState> GetPlayerStates();
+
+ void CloseConnection(NetConnectionID conn_id, ConnectionClosedReason reason);
+
+ bool TryGetPlayerState(PlayerState& player_state, ObjectID object_id) const;
+
+ void AssignNewControllerForPlayer(PlayerID player_id);
+ void ClearOnlineSession();
+
+ template<typename Message, typename... Args>
+ OnlineMessageRef& CreateMessage(Args... args);
+ void Send(const OnlineMessageRef& message);
+ void SendTo(NetConnectionID target, const OnlineMessageRef& message);
+ template<typename Message, typename... Args>
+ void Send(Args... args);
+ template<typename Message, typename... Args>
+ void SendTo(NetConnectionID target, Args... args);
+
+ void SendIntFloatScriptParam(uint32_t i, const string& key, const ScriptParam& param);
+ void SendStringScriptParam(uint32_t i, const string& key, const ScriptParam& param);
+ void SendScriptParam(uint32_t id, const string& key, const ScriptParam& param);
+ void SendScriptParamMap(uint32_t id, const ScriptParamMap& param);
+ bool GetHostAllowsClientEditor() const;
+ void SetIfHostAllowsClientEditor(bool mode);
+ void SetDefaultHotJoinCharacter(const string& path);
+ string GetDefaultHotJoinCharacter() const;
+ void SessionStarted(bool host_started_the_level);
+ bool ForceMapStartOnLoad() const;
+ bool AllClientsReady();
+ ScriptParams* GetScriptParamsFromID(ObjectID id);
+ void UpdateMovementObjectFromID(uint32_t id);
+ void SocketMessageApplyLevelChange();
+ bool SetAvatarCameraAttachedMode(bool mode);
+ bool IsAvatarCameraAttached() const;
+ void SendLevelMessage(const string& msg);
+ void SendAvatarPaletteChange(const OGPalette& palette, ObjectID object_id);
+ bool IsAvatarPossessed(ObjectID avatar_id);
+ void PossessAvatar(PlayerID player_id, ObjectID object_id);
+ ObjectID CreateCharacter();
+ void SetNoDataInterpStepOverRide(float override);
+ float GetNoDataInterpStepOverRide() const;
+ void LateUpdate(SceneGraph * scenegraph);
+
+ void SyncHostSessionFlags();
+ void GenerateEnvObjectSyncPackages(NetConnectionID conn, SceneGraph* graph);
+ void GenerateMovementSyncPackages(NetConnectionID conn, SceneGraph * graph);
+ void GenerateStateForNewConnection(NetConnectionID conn);
+
+ bool CheckLoadedMapAndCampaignState(const string& campagin, const string& level_name);
+ void SetLevelLoaded();
+ uint32_t IncomingMessageCount() const;
+ uint32_t OutgoingMessageCount() const;
+ bool GetIfPendingAngelScriptUpdates();
+ bool GetIfPendingAngelScriptStates();
+ AngelScriptUpdate GetAngelScriptUpdate();
+ void MoveAngelScriptQueueForward();
+ void MoveAngelStateQueueForward();
+ AngelScriptUpdate GetAngelScriptStates();
+ uint32_t AddSyncState(uint32_t state, const vector<char>& data);
+ uint32_t RegisterMPState(const string& state);
+ void SetTickperiod(const uint32_t& tickperiod);
+ void GetConnectionStatus(const Peer& peer, ConnectionStatus* status) const;
+
+ void RegisterHostObjectID(CommonObjectID hostid, ObjectID clientid);
+ void DeRegisterHostClientIDTranslation(ObjectID clientid);
+ ObjectID GetObjectID(CommonObjectID hostid) const;
+ CommonObjectID GetOriginalID(ObjectID clientid) const;
+ void ClearIDTranslations();
+
+ bool SendingRemovePackages() const;
+ void RemoveObject(Object * o, ObjectID my_id);
+ bool NetworkRemoveableType(Object* o) const;
+ void ChangeLevel(const string& id);
+ void PerformLevelChangeCleanUp();
+
+ const vector<Peer> GetPeers();
+ vector<Peer>::iterator GetPeerIt(NetConnectionID conn_id);
+ bool IsLocalAvatar(const ObjectID avatar) const;
+ vector<ObjectID> GetLocalAvatarIDs() const;
+ PlayerState GenerateHostPlayerState() const;
+
+ void OnConnectionChange(NetFrameworkConnectionStatusChanged *data);
+
+ void BroadcastChatMessage(const string& chat_message);
+ void SendRawChatMessage(const string& raw_chat_entry);
+ void AddLocalChatMessage(string chat_message);
+ void RemoveOldChatMessages(float threshold);
+ const vector<ChatMessage>& GetChatMessages() const;
+
+ void AddPeer(NetFrameworkConnectionStatusChanged *data);
+ Peer* GetPeerFromConnection(NetConnectionID conn_id);
+ Peer* GetPeerFromID(PeerID peer_id);
+
+ bool HasFreeAvatars() const;
+ void UpdateObjects(SceneGraph *scenegraph_);
+ uint32_t GetNumberOfFreeAvatars() const;
+ uint8_t GetPlayerLimit();
+ bool IsLobbyFull();
+ void AddFreeAvatarsIds();
+ void RemoveAllAvatarIds();
+ void RemoveFreeAvatarId(uint32_t id);
+ ObjectID GetFreeAvatarID();
+ void AddFreeAvatarId(uint32_t id);
+ void CreateListenSocketIP(const string &localAddress);
+ void ConnectByIPAddress(const string &address);
+
+ bool NetFrameworkHasFriendInviteOverlay() const;
+ void ActivateGameOverlayInviteDialog();
+
+ void CheckPendingMessages();
+ void ApplyPlayerInput();
+
+ PlayerState GetOwnPlayerState();
+
+ //Host and Client use, used to convert a PlayerInput binding name to a shared integer representation.
+ uint8_t GetBindID(string bind_name);
+
+private:
+ //Explicitally disable copy constructors and assignment to prevent misuse.
+ Online(const Online&) = delete;
+ Online &operator=(const Online &) = delete;
+ Online(Online &&) = delete;
+ Online &operator=(Online &&) = delete;
+
+ //Host only function, clients get their bindings from the host.
+ void AssignBindID(string bind_name);
+
+ //Client side cache, not used on the server.
+ void SendPlayerInputState();
+ void SendPlayerCameraState();
+
+ void CloseConnectionImmediate(NetConnectionID conn, ConnectionClosedReason reason);
+
+ //Functions run inside a separate socket communication thread.
+ void NetworkDataThread();
+ void SendMessages();
+ void SendMessageObjects();
+ void HandleConnectionsToBeClosed();
+
+ void SendMessageToConnection(NetConnectionID conn_id, char * buffer, uint32_t bytes, bool reliable, bool flush);
+ void SendStateMessages();
+
+ void CheckNewMessages();
+
+ size_t CompressData(vector<char>& target_buffer, const void* source_buffer, uint32_t source_size);
+ size_t DecompressData(vector<char>& target_buffer, const void* source_buffer, size_t source_size);
+};
+
+template<typename Message, typename... Args>
+void Online::Send(Args... args) {
+ Send(message_handler.Create<Message>(args...));
+}
+
+template<typename Message, typename... Args>
+void Online::SendTo(NetConnectionID target, Args... args) {
+ SendTo(target, message_handler.Create<Message>(args...));
+}
+
+template<typename Message, typename... Args>
+OnlineMessageRef& Online::CreateMessage(Args... args) {
+ return message_handler.Create<Message>(args...);
+}
diff --git a/Source/Online/online_client_connection_manager.cpp b/Source/Online/online_client_connection_manager.cpp
new file mode 100644
index 00000000..ca366836
--- /dev/null
+++ b/Source/Online/online_client_connection_manager.cpp
@@ -0,0 +1,101 @@
+//-----------------------------------------------------------------------------
+// Name: online_client_connection_manager.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "online_client_connection_manager.h"
+
+#include <Online/online_datastructures.h>
+#include <Online/online_connection_states.h>
+
+#include <Online/Message/pcs_build_version_message.h>
+#include <Online/Message/pcs_loading_completed_message.h>
+#include <Online/Message/pcs_client_parameters_message.h>
+
+#include <Online/online.h>
+
+bool ClientConnectionManager::IsEveryClientLoaded() const {
+ for (auto& it : connection_states) {
+ if (!it.second.actor.finished_loading) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool ClientConnectionManager::HasClientGottenPersistentQueue(const PeerID peer_id) const {
+ if (HasConnection(peer_id))
+ return connection_states.at(peer_id).actor.finished_sending_persistent_queue;
+
+ LOGW << "Attempted to query persistent queue state of a non-connected client. This will always return false!" << std::endl;
+ return false;
+}
+
+bool ClientConnectionManager::IsClientLoaded(const PeerID peer_id) const {
+ if (HasConnection(peer_id))
+ return connection_states.at(peer_id).actor.finished_loading;
+
+ LOGW << "Attempted to query loading state of a non-connected client. This will always return false!" << std::endl;
+ return false;
+}
+
+bool ClientConnectionManager::HasConnection(const PeerID peer_id) const {
+ return connection_states.find(peer_id) != connection_states.end();
+}
+
+void ClientConnectionManager::Update() {
+ for (auto& it : connection_states) {
+ it.second.Update();
+ }
+}
+
+void ClientConnectionManager::RemoveConnection(const PeerID peer_id) {
+ connection_states.erase(peer_id);
+}
+
+void ClientConnectionManager::AddConnection(const PeerID peer_id, const NetConnectionID net_connection_id) {
+ connection_states.emplace(std::piecewise_construct, std::make_tuple(peer_id), std::make_tuple(new StateVersionCheck(), PendingConnection(net_connection_id)));
+}
+
+void ClientConnectionManager::ResetConnections() {
+ connection_states.clear();
+ for (auto& peer : Online::Instance()->GetPeers()) {
+ connection_states.emplace(std::piecewise_construct, std::make_tuple(peer.peer_id), std::make_tuple(new StateVersionCheck(), PendingConnection(peer.conn_id)));
+ }
+}
+
+void ClientConnectionManager::ApplyPackage(const PeerID peer_id, PCSBuildVersionMessage* build_version) {
+ connection_states[peer_id].actor.received_version = true;
+ connection_states[peer_id].actor.build_id = build_version->build_id;
+}
+
+void ClientConnectionManager::ApplyPackage(const PeerID peer_id, PCSLoadingCompletedMessage* loading_completed) {
+ connection_states[peer_id].actor.finished_loading = true;
+}
+
+void ClientConnectionManager::ApplyPackage(const PeerID peer_id, PCSClientParametersMessage* client_parameters) {
+ connection_states[peer_id].actor.player_name = client_parameters->player_name;
+ connection_states[peer_id].actor.active_mods_string = client_parameters->enabled_mods_string;
+ connection_states[peer_id].actor.received_client_params = true;
+}
+
+void ClientConnectionManager::SetClientGottenPersistentQueue(const PeerID peer_id) {
+ connection_states[peer_id].actor.finished_sending_persistent_queue = true;
+}
diff --git a/Source/Online/online_client_connection_manager.h b/Source/Online/online_client_connection_manager.h
new file mode 100644
index 00000000..3b4d8113
--- /dev/null
+++ b/Source/Online/online_client_connection_manager.h
@@ -0,0 +1,57 @@
+//-----------------------------------------------------------------------------
+// Name: online_client_connection_manager.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Online/online_datastructures.h>
+#include <Online/state_machine.h>
+#include <Online/online_connection_states.h>
+
+#include <unordered_map>
+
+namespace OnlineMessages {
+ class PCSBuildVersionMessage;
+ class PCSLoadingCompletedMessage;
+ class PCSClientParametersMessage;
+}
+
+class ClientConnectionManager {
+private:
+ std::unordered_map<PeerID, StateMachine<PendingConnection>> connection_states;
+
+ bool HasConnection(const PeerID peer_id) const;
+
+public:
+ bool IsEveryClientLoaded() const;
+ bool IsClientLoaded(const PeerID peer_id) const;
+ bool HasClientGottenPersistentQueue(const PeerID peer_id) const;
+
+ void Update();
+ void RemoveConnection(const PeerID peer_id);
+ void AddConnection(const PeerID peer_id, const NetConnectionID net_connection_id);
+ void ResetConnections();
+
+ void ApplyPackage(const PeerID peer_id, OnlineMessages::PCSBuildVersionMessage* build_version);
+ void ApplyPackage(const PeerID peer_id, OnlineMessages::PCSLoadingCompletedMessage* loading_completed);
+ void ApplyPackage(const PeerID peer_id, OnlineMessages::PCSClientParametersMessage* client_parameters);
+ void SetClientGottenPersistentQueue(const PeerID peer_id);
+};
diff --git a/Source/Online/online_connection_states.cpp b/Source/Online/online_connection_states.cpp
new file mode 100644
index 00000000..6a33a080
--- /dev/null
+++ b/Source/Online/online_connection_states.cpp
@@ -0,0 +1,163 @@
+//-----------------------------------------------------------------------------
+// Name: online_connection_states.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "online_connection_states.h"
+
+#include <Online/online.h>
+#include <Online/online_utility.h>
+
+#include <Main/engine.h>
+#include <Version/version.h>
+
+// ---------------- StateStateSuspended ----------------
+
+BaseState<PendingConnection>* StateSuspended::OnUpdate() {
+ return this;
+}
+
+// ---------------- StateVersionCheck ----------------
+
+void StateVersionCheck::OnEnter() {
+ Online::Instance()->SendTo<OnlineMessages::PCSBuildVersionRequestMessage>(actor->net_connection_id);
+}
+
+BaseState<PendingConnection>* StateVersionCheck::OnUpdate() {
+ if (actor->received_version) {
+ // Does the client have the same version as us?
+ if (actor->build_id == GetBuildID()) {
+ return new StateHandshake();
+ }
+
+ // Is there an editor build doing the version check? Allow it, but send a warning.
+ if (actor->build_id == -1 || GetBuildID() == -1) {
+ Online::Instance()->SendRawChatMessage("[Warning] Client with another version connected, but was granted access since the host or client is running a Development version");
+ return new StateHandshake();
+ }
+
+ // Failed version check, close connection
+ ConnectionClosedReason reason = (actor->build_id < GetBuildID()) ? ConnectionClosedReason::CLIENT_OUTDATED : ConnectionClosedReason::SERVER_OUTDATED;
+ Online::Instance()->CloseConnection(actor->net_connection_id, reason);
+ return nullptr;
+ }
+ return this;
+}
+
+
+// ---------------- StateHandshake ----------------
+
+void StateHandshake::OnEnter() {
+ // Send session parameters
+ Online::Instance()->SendTo<OnlineMessages::PCSSessionParametersMessage>(actor->net_connection_id, Online::Instance()->online_session->binding_id_map_);
+}
+
+BaseState<PendingConnection>* StateHandshake::OnUpdate() {
+ if (actor->received_client_params) {
+ if(!OnlineUtility::IsValidPlayerName(actor->player_name)) {
+ actor->player_name = "InvalidName";
+ }
+
+ if (actor->active_mods_string != OnlineUtility::GetActiveModsString()) {
+ Online::Instance()->AddLocalChatMessage("\"" + actor->player_name + "\" tried to connect, but was rejected entry due to a mod mismatch");
+ Online::Instance()->AddLocalChatMessage(" - Expected mods: \"" + OnlineUtility::GetActiveModsString() + "\"");
+ Online::Instance()->AddLocalChatMessage(" - Client's mods: \"" + actor->active_mods_string + "\"");
+
+ Online::Instance()->CloseConnection(actor->net_connection_id, ConnectionClosedReason::MOD_MISMATCH);
+ return new StateSuspended();
+ }
+
+ return new StateSync();
+ }
+ return this;
+}
+
+
+// ---------------- StateSync ----------------
+
+void StateSync::OnEnter() {
+ // TODO This package is a placeholder, we don't actually send any level files here right now, as this would require a bigger refactor to file transfer
+ // For now, we simply tell the client what map to load and *pray* the client loads the exact same map as the host! (pretty much like it did before)
+ Online::Instance()->SendTo<OnlineMessages::PCSFileTransferMetadataMessage>(actor->net_connection_id, Online::Instance()->level_name, Online::Instance()->campaign_id);
+
+ // TODO initiate level file transfers to client
+}
+
+BaseState<PendingConnection>* StateSync::OnUpdate() {
+ if (actor->finished_loading)
+ return new StateWaiting();
+ return this;
+}
+
+
+// ---------------- StateWaiting ----------------
+
+void StateWaiting::OnEnter() {
+
+}
+
+BaseState<PendingConnection>* StateWaiting::OnUpdate() {
+ if (Online::Instance()->ForceMapStartOnLoad())
+ return new StateEstablished();
+ return this;
+}
+
+
+// ---------------- StateEstablished ----------------
+
+void StateEstablished::OnEnter() {
+ actor->player_id = Online::Instance()->GetPeerFromConnection(actor->net_connection_id)->peer_id; // TODO We assume that peer_id == player_id here
+ Online::Instance()->SendTo<OnlineMessages::PCSAssignPlayerID>(actor->net_connection_id, actor->player_id);
+
+ // Create player state for the new player
+ PlayerState player_state;
+ player_state.playername = actor->player_name;
+ Online::Instance()->online_session->player_states[actor->player_id] = player_state;
+ Online::Instance()->Send<OnlineMessages::SetPlayerState>(actor->player_id, &player_state);
+
+ Online::Instance()->Send<OnlineMessages::PCSLoadingCompletedMessage>();
+
+ // Create new controllers, assign them via player_id
+ Online::Instance()->AssignNewControllerForPlayer(actor->player_id);
+
+ // "Reserve" character for peer
+ ObjectID free_id = Online::Instance()->GetFreeAvatarID();
+ if (free_id == -1) {
+ free_id = Online::Instance()->CreateCharacter();
+ }
+
+ // Posess avatar
+ Online::Instance()->PossessAvatar(actor->player_id, free_id);
+
+ // Display Join Message
+ Online::Instance()->SendRawChatMessage(actor->player_name + " just joined!");
+
+ // Dump playerstates onto new player
+ for (const auto& it : Online::Instance()->online_session->player_states) {
+ Online::Instance()->SendTo<OnlineMessages::SetPlayerState>(actor->net_connection_id, it.first, &it.second);
+ }
+
+ // This seems to be an important package
+ Online::Instance()->GenerateStateForNewConnection(actor->net_connection_id);
+}
+
+BaseState<PendingConnection>* StateEstablished::OnUpdate() {
+ return nullptr;
+}
diff --git a/Source/Online/online_connection_states.h b/Source/Online/online_connection_states.h
new file mode 100644
index 00000000..b67107cf
--- /dev/null
+++ b/Source/Online/online_connection_states.h
@@ -0,0 +1,74 @@
+//-----------------------------------------------------------------------------
+// Name: online_connection_states.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Online/state_machine.h>
+#include <Online/online_datastructures.h>
+
+struct PendingConnection {
+ NetConnectionID net_connection_id;
+
+ // Request replies
+ int build_id = 0;
+ bool received_version = false;
+
+ bool received_client_params = false;
+ std::string player_name;
+ std::string active_mods_string;
+ PlayerID player_id;
+
+ bool finished_loading = false;
+ bool finished_sending_persistent_queue = false;
+
+ PendingConnection(NetConnectionID net_connection_id) : net_connection_id(net_connection_id) {}
+ PendingConnection() {}
+};
+
+class StateSuspended : public BaseState<PendingConnection> {
+ BaseState<PendingConnection>* OnUpdate() override;
+};
+
+class StateVersionCheck : public BaseState<PendingConnection> {
+ void OnEnter() override;
+ BaseState<PendingConnection>* OnUpdate() override;
+};
+
+class StateSync : public BaseState<PendingConnection> {
+ void OnEnter() override;
+ BaseState<PendingConnection>* OnUpdate() override;
+};
+
+class StateHandshake : public BaseState<PendingConnection> {
+ void OnEnter() override;
+ BaseState<PendingConnection>* OnUpdate() override;
+};
+
+class StateWaiting : public BaseState<PendingConnection> {
+ void OnEnter() override;
+ BaseState<PendingConnection>* OnUpdate() override;
+};
+
+class StateEstablished : public BaseState<PendingConnection> {
+ void OnEnter() override;
+ BaseState<PendingConnection>* OnUpdate() override;
+};
diff --git a/Source/Online/online_datastructures.cpp b/Source/Online/online_datastructures.cpp
new file mode 100644
index 00000000..7f55623e
--- /dev/null
+++ b/Source/Online/online_datastructures.cpp
@@ -0,0 +1,81 @@
+//-----------------------------------------------------------------------------
+// Name: online_datastructures.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "online_datastructures.h"
+
+binn* NetworkBone::Serialize() {
+ binn* l = binn_object();
+
+ binn_object_set_float(l, "s", scale);
+ binn_object_set_vec3(l, "t0", translation0);
+ binn_object_set_vec3(l, "t1", translation1);
+ binn_object_set_quat(l, "r0", rotation0);
+ binn_object_set_quat(l, "r1", rotation1);
+ binn_object_set_float(l, "mcs", model_char_scale);
+
+ return l;
+}
+
+void NetworkBone::Deserialize(binn* l) {
+ binn_object_get_float(l, "s", &scale);
+ binn_object_get_vec3(l, "t0", &translation0);
+ binn_object_get_vec3(l, "t1", &translation1);
+ binn_object_get_quat(l, "r0", &rotation0);
+ binn_object_get_quat(l, "r1", &rotation1);
+ binn_object_get_float(l, "mcs", &model_char_scale);
+}
+
+
+binn* RiggedObjectFrame::Serialize() {
+ binn* l = binn_object();
+
+ binn_object_set_float(l, "ts", host_walltime);
+ binn_object_set_uint8(l, "bc", bone_count);
+
+ binn* bl_bones = binn_list();
+
+ for(int i = 0; i < bone_count && i < bones.size(); i++) {
+ binn* bo_bone = bones[i].Serialize();
+ binn_list_add_object(bl_bones,bo_bone);
+ binn_free(bo_bone);
+ }
+
+ binn_object_set_list(l, "bones", bl_bones);
+
+ binn_free(bl_bones);
+
+ return l;
+}
+
+void RiggedObjectFrame::Deserialize(binn* l) {
+ binn_object_get_float(l, "ts", &host_walltime);
+ binn_object_get_uint8(l, "bc", &bone_count);
+
+ void* bl_bones;
+ binn_object_get_list(l, "bones", &bl_bones);
+
+ for(int i = 0; i < bone_count && i < bones.size(); i++) {
+ void* bo_bone;
+ binn_list_get_object(bl_bones, i + 1, &bo_bone);
+ bones[i].Deserialize((binn*)bo_bone);
+ }
+}
diff --git a/Source/Online/online_datastructures.h b/Source/Online/online_datastructures.h
new file mode 100644
index 00000000..1c041fab
--- /dev/null
+++ b/Source/Online/online_datastructures.h
@@ -0,0 +1,133 @@
+//-----------------------------------------------------------------------------
+// Name: online_datastructures.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+#include <Math/mat4.h>
+#include <Math/quaternions.h>
+
+#include <Scripting/scriptparams.h>
+#include <Editors/entity_type.h>
+#include <Objects/object.h>
+#include <Network/net_framework.h>
+#include <Utility/binn_util.h>
+
+#include <vector>
+#include <string>
+#include <utility>
+#include <array>
+
+using std::array;
+
+typedef uint32_t OnlineMessageID;
+typedef uint16_t OnlineMessageType;
+typedef uint8_t PeerID;
+typedef uint8_t PlayerID;
+typedef int32_t CommonObjectID;
+
+class OnlineMessageRef {
+ OnlineMessageID message_id;
+public:
+ OnlineMessageRef();
+ OnlineMessageRef(const OnlineMessageID message_id);
+ OnlineMessageRef(const OnlineMessageRef& rhs);
+ ~OnlineMessageRef();
+ OnlineMessageRef& operator=(const OnlineMessageRef& rhs);
+ void* GetData() const;
+ OnlineMessageID GetID() const;
+ bool IsValid() const;
+};
+
+enum class OnlineFlags {
+ INVALID,
+ ALLOWSEDITOR
+};
+
+enum class MultiplayerMode : uint8_t {
+ NoMultiplayer,
+ Host,
+ Client,
+ CleanUp,
+ AwaitingShutdown,
+};
+
+struct PlayerState {
+ std::string playername = "UNKNOWN";
+ ObjectID object_id = 1;
+ uint16_t ping = 0;
+ int32_t controller_id = -1; // Incoming inputs are copied into this controller.
+
+ // Invalid on clients
+ int32_t camera_id = -1; // Virtual camera id, used for character logic, some data may be syned to/from this camera from the client.
+};
+
+struct MorphTargetStateStorage {
+ float disp_weight;
+ float mod_weight;
+ bool dirty;
+ std::string name;
+};
+
+struct NetworkBone {
+ float scale;
+ vec3 translation0;
+ vec3 translation1;
+ quaternion rotation0;
+ quaternion rotation1;
+ float model_char_scale;
+
+ binn* Serialize();
+ void Deserialize(binn* l);
+};
+
+struct RiggedObjectFrame {
+ float host_walltime;
+ uint8_t bone_count;
+ array<NetworkBone,64> bones;
+
+ binn* Serialize();
+ void Deserialize(binn* l);
+};
+
+struct MovementObjectFrame {
+ float host_walltime;
+ vec3 position;
+ vec3 velocity;
+ vec3 facing;
+ RiggedObjectFrame rigged_object_frame;
+};
+
+struct ItemObjectFrame {
+ float host_walltime;
+ mat4 transform;
+};
+
+struct AngelScriptUpdate {
+ uint32_t state;
+ vector<char> data;
+};
+
+struct ChatMessage {
+ float time;
+ std::string message;
+};
diff --git a/Source/Online/online_file_transfer_handler.cpp b/Source/Online/online_file_transfer_handler.cpp
new file mode 100644
index 00000000..7e6fcf57
--- /dev/null
+++ b/Source/Online/online_file_transfer_handler.cpp
@@ -0,0 +1,207 @@
+//-----------------------------------------------------------------------------
+// Name: online_file_transfer_handler.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "online_file_transfer_handler.h"
+
+#include <Internal/modloading.h>
+#include <Main/engine.h>
+
+#include <vector>
+#include <unordered_map>
+#include <mutex>
+
+/*
+void OnlineFileTransferHandler::SendLevelFiles(const HSteamNetConnection peer, const LevelInfo& level_info, const std::string& level_name) {
+ SendFile(peer, level_info.terrain_info_.heightmap.c_str(), FileTransfer::TEXTURE);
+ SendFile(peer, std::string("Data/Scripts/" + level_info.script_).c_str(), FileTransfer::SCRIPT);
+ SendFile(peer, (level_name).c_str(), FileTransfer::LEVEL);
+ SendFile(peer, level_info.terrain_info_.colormap.c_str(), FileTransfer::TEXTURE);
+ SendFile(peer, level_info.terrain_info_.weightmap.c_str(), FileTransfer::TEXTURE);
+ SendFile(peer, std::string("Data/Scripts/" + level_info.pc_script_).c_str(), FileTransfer::SCRIPT);
+ SendFile(peer, std::string("Data/Scripts/" + level_info.npc_script_).c_str(), FileTransfer::SCRIPT);
+ SendFile(peer, std::string(level_info.loading_screen_.image).c_str(), FileTransfer::TEXTURE);
+ SendFile(peer, std::string(level_info.sky_info_.dome_texture_path).c_str(), FileTransfer::TEXTURE);
+}
+
+void OnlineFileTransferHandler::SendCampaignFiles(HSteamNetConnection peer, const std::string& campaign_id) {
+ ModInstance* temp = ModLoading::Instance().GetModInstance(campaign_id);
+ if (temp != nullptr) {
+
+ std::string modpath = "Data/Mods/" + temp->path.substr(temp->path.find("//")).erase(0, 2);
+ SendFile(peer, (modpath + "/" + temp->thumbnail.c_str()).c_str(), FileTransfer::Type::TEXTURE); // Send xml
+
+ for (auto image : temp->preview_images) {
+ SendFile(peer, image.c_str(), FileTransfer::Type::TEXTURE); // Send xml
+ }
+
+ for (auto item : temp->main_menu_items) {
+ SendFile(peer, item.thumbnail.c_str(), FileTransfer::Type::TEXTURE); // Send xml
+ }
+
+ for (auto campaign : temp->campaigns) {
+ SendFile(peer, (modpath + "/Data/" + campaign.thumbnail.c_str()).c_str(), FileTransfer::Type::TEXTURE); // Send xml
+ }
+
+ SendFile(peer, (modpath + "/mod.xml").c_str(), FileTransfer::Type::CAMPAIGN); // Send xml+
+ } else {
+ LOGI << "I can't resolve the currently loaded campagin" << std::endl;
+ }
+}
+
+void OnlineFileTransferHandler::SendFile(HSteamNetConnection peer, const char * path, FileTransfer::Type filetype) {
+ if (FileExists(path, kAnyPath)) {
+ Path level_path = FindFilePath(path, kAnyPath);
+
+ std::vector<unsigned char> dat = readFile(level_path.GetFullPath());
+ std::vector<FileTransfer*> payLoads = SplitFile(path, dat, filetype);
+
+ for (auto& it : payLoads) {
+ //Online::Instance()->QueueFilesForTranfser(it, peer);
+ }
+
+ LOGI << "Sending file: " << path << " size: " << dat.size() * sizeof(unsigned char) << " we are sending the file in: " << payLoads.size() << " messages." << std::endl;
+ } else {
+ LOGI << "Sending file: " << path << " failed. Could not resolve path " << std::endl;
+ }
+}
+
+void OnlineFileTransferHandler::SaveFile(FileTransfer file) {
+ std::string file_location = GetTempFileTransferPath(file);
+
+ if (file.number_of_messages > 1) {
+
+ // First file is a corner case when you have multiple packages
+ if (file.file_id == 0) {
+ CreateDirectoriesForFile(file, file_location);
+
+ file_handle.open(file_location, std::fstream::out | std::ios::binary);
+ }
+
+ StreamFilePackageToDisk(file);
+
+ // Close if it's the last file number
+ if (file.file_id == (file.number_of_messages - 1)) {
+ file_handle.close();
+
+ FinishedStreamingFileToDisk(file_location);
+ }
+ } else {
+ CreateDirectoriesForFile(file, file_location);
+ file_handle.open(file_location, std::fstream::out | std::ios::binary);
+ StreamFilePackageToDisk(file);
+ file_handle.close();
+ FinishedStreamingFileToDisk(file_location);
+ }
+
+ delete file.file_data;
+ delete file.file_name;
+}
+
+void OnlineFileTransferHandler::StreamFilePackageToDisk(FileTransfer file) {
+ if (file_handle.is_open()) {
+ file_handle.write(reinterpret_cast<char*>(file.file_data), file.file_len);
+ } else {
+ LOGI << "Failed to create file" << std::endl;
+ }
+}
+
+void OnlineFileTransferHandler::FinishedStreamingFileToDisk(const std::string& file_location) {
+ std::string real_name = file_location;
+ size_t pos = file_location.find(std::string(".tmp"));
+
+ if (pos != std::string::npos) {
+ real_name.erase(pos, std::string(".tmp").size());
+ rename(file_location.c_str(), real_name.c_str());
+ }
+}
+
+std::string OnlineFileTransferHandler::GetTempFileTransferPath(const FileTransfer file_transfer) const {
+ std::string file_location = std::string(write_path) + std::string("/multiplayercache/") + std::string(file_transfer.file_name) + std::string(".tmp");
+ return SanitizePath(file_location);
+}
+
+void OnlineFileTransferHandler::CreateDirectoriesForFile(FileTransfer file, const std::string& file_location) {
+ CreateDirsFromPath(std::string(write_path) + std::string("/multiplayercache/"), file.file_name);
+}
+
+std::vector<FileTransfer*> OnlineFileTransferHandler::SplitFile(std::string path, std::vector<unsigned char> buffer, FileTransfer::Type filetype) {
+ // Even though steamnetworkingsockets splits up messages into package sizes they still impose
+ // a limit on how big a message can be.
+
+ int totalFileSizeInBytes = buffer.size() * sizeof(unsigned char);
+ int totalTransferSizeInBytes = totalFileSizeInBytes + path.size() * sizeof(char);
+ const uint32_t overhead = sizeof(FileTransfer);
+ const uint32_t maxBytesPerMessage = k_cbMaxSteamNetworkingSocketsMessageSizeSend - overhead;
+ uint32_t number_of_messages = totalTransferSizeInBytes / maxBytesPerMessage;
+ const uint32_t left_over = totalTransferSizeInBytes % maxBytesPerMessage ? 1 : 0;
+
+ number_of_messages += left_over;
+
+ uint32_t totalAllocatedBytes = 0;
+ uint32_t allocatedBytesForMessage = 0;
+ FileTransfer * file = new FileTransfer();
+ allocatedBytesForMessage += path.size() * sizeof(char) + 1;
+
+ file->type = filetype;
+ file->file_id = 0;
+ file->name_len = allocatedBytesForMessage;
+ file->number_of_messages = number_of_messages;
+
+ file->file_name = new char[file->name_len];
+ memcpy(file->file_name, path.c_str(), file->name_len);
+ file->file_name[file->name_len] = '\0';
+
+ // If it fits in one message, don't overallocate or go over the bounds.
+ if (file->number_of_messages > 1) {
+ file->file_len = maxBytesPerMessage - allocatedBytesForMessage;
+ } else {
+ file->file_len = totalFileSizeInBytes;
+ }
+
+ file->file_data = new unsigned char[file->file_len];
+ memcpy(file->file_data, &buffer.data()[totalAllocatedBytes], file->file_len);
+
+ totalAllocatedBytes += file->file_len;
+
+ std::vector<FileTransfer*> payLoads;
+ payLoads.push_back(file);
+ for (uint32_t i = 1; i < number_of_messages; i++) {
+ allocatedBytesForMessage = 0;
+ FileTransfer * file = new FileTransfer();
+ allocatedBytesForMessage = (totalTransferSizeInBytes - totalAllocatedBytes) > maxBytesPerMessage
+ ? maxBytesPerMessage : totalTransferSizeInBytes - totalAllocatedBytes;
+
+ file->type = filetype;
+ file->file_id = i;
+ file->file_len = allocatedBytesForMessage;
+ file->number_of_messages = number_of_messages;
+ file->name_len = 0;
+ file->file_data = new unsigned char[allocatedBytesForMessage];
+ memcpy(file->file_data, &buffer.data()[totalAllocatedBytes], allocatedBytesForMessage);
+
+ totalAllocatedBytes += allocatedBytesForMessage;
+
+ payLoads.push_back(file);
+ }
+ return payLoads;
+}
+*/
diff --git a/Source/Online/online_file_transfer_handler.h b/Source/Online/online_file_transfer_handler.h
new file mode 100644
index 00000000..0144209d
--- /dev/null
+++ b/Source/Online/online_file_transfer_handler.h
@@ -0,0 +1,71 @@
+//-----------------------------------------------------------------------------
+// Name: online_file_transfer_handler.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Online/online_datastructures.h>
+#include <Game/levelinfo.h>
+
+#if ENABLE_STEAMWORKS || ENABLE_GAMENETWORKINGSOCKETS
+#include <steamnetworkingtypes.h>
+#endif
+
+#include <vector>
+#include <unordered_map>
+#include <mutex>
+#include <fstream>
+
+struct FileTransfer {
+ enum Type {
+ SCRIPT = 1 << 0,
+ LEVEL = 1 << 1,
+ TEXTURE = 1 << 2,
+ CAMPAIGN = 1 << 3
+
+ };
+ Type type;
+ uint32_t name_len;
+ uint32_t file_len;
+ int8_t file_id;
+ int8_t number_of_messages;
+ char * file_name;
+ unsigned char * file_data;
+};
+
+/// TODO This class has a big assumption that we only send one file at a time!
+/// Attempting to send another file while the previous file is not fully send will cause all kinds of corruption issues
+class OnlineFileTransferHandler {
+private:
+ std::fstream file_handle; // TODO this shouldn't be a member variable
+
+ std::string GetTempFileTransferPath(const FileTransfer file_transfer) const;
+ void CreateDirectoriesForFile(FileTransfer file, const std::string& file_location);
+ void FinishedStreamingFileToDisk(const std::string& file_location);
+ void StreamFilePackageToDisk(FileTransfer file);
+ std::vector<FileTransfer*> SplitFile(std::string path, std::vector<unsigned char> buffer, FileTransfer::Type filetype);
+ //void SendFile(HSteamNetConnection peer, const char * path, FileTransfer::Type filetype);
+
+public:
+ //void SendLevelFiles(const HSteamNetConnection peer, const LevelInfo& level_info, const std::string& level_name);
+ //void SendCampaignFiles(HSteamNetConnection peer, const std::string& campaign_id);
+ void SaveFile(FileTransfer file);
+};
diff --git a/Source/Online/online_message_handler.cpp b/Source/Online/online_message_handler.cpp
new file mode 100644
index 00000000..8be38303
--- /dev/null
+++ b/Source/Online/online_message_handler.cpp
@@ -0,0 +1,26 @@
+//-----------------------------------------------------------------------------
+// Name: online_message_handler.cpp
+// Developer: Wolfire Games LLC
+// Author: Max Danielsson
+// Description: A manager for online messages, handling all the types,
+// their serialization and allocation
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "online_message_handler.h"
+
diff --git a/Source/Online/online_message_handler.h b/Source/Online/online_message_handler.h
new file mode 100644
index 00000000..d7371e17
--- /dev/null
+++ b/Source/Online/online_message_handler.h
@@ -0,0 +1,296 @@
+//-----------------------------------------------------------------------------
+// Name: online_message_handler.h
+// Developer: Wolfire Games LLC
+// Author: Max Danielsson
+// Description: A manager for online messages, handling all the types,
+// their serialization and allocation
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Memory/block_allocator.h>
+#include <Memory/allocation.h>
+#include <Online/online_datastructures.h>
+
+#include <binn.h>
+
+#include <tuple>
+#include <array>
+#include <map>
+#include <mutex>
+
+using std::tuple;
+using std::size_t;
+using std::array;
+using std::map;
+using std::mutex;
+using std::lock_guard;
+using std::get;
+using std::endl;
+
+/*
+ This is a template hack that allows us to calculate a static
+ Components id in compile-time
+*/
+template <class T, class Tuple>
+struct Index;
+
+template <class T, class... Types>
+struct Index<T, tuple<T, Types...>> {
+ static const size_t value = 0;
+};
+
+template <class T, class U, class... Types>
+struct Index<T, tuple<U, Types...>> {
+ static const size_t value = 1 + Index<T, tuple<Types...>>::value;
+};
+
+template<typename... Messages>
+class OnlineMessageHandlerTemplate {
+
+public:
+ static const int COUNT = sizeof...(Messages);
+
+private:
+ uint32_t message_id_counter = 1;
+
+ void* block_allocator_memory;
+ mutex block_allocator_lock;
+ BlockAllocator block_allocator;
+
+ mutex message_lock;
+ map<OnlineMessageID, tuple<uint32_t, OnlineMessageType, void*>> messages;
+
+ array<size_t, sizeof...(Messages)> sizes = {{(sizeof(Messages))...}};
+ array<binn* (*)(void*),sizeof...(Messages)> serialize_functions = {{(&Messages::Serialize)...}};
+ array<void (*)(void*, binn*), sizeof...(Messages)> deserialize_functions = {{(&Messages::Deserialize)...}};
+ array<void (*)(const OnlineMessageRef&, void*, PeerID), sizeof...(Messages)> execute_functions = {{(&Messages::Execute)...}};
+ array<void* (*)(void*), sizeof...(Messages)> construct_functions = {{(&Messages::Construct)...}};
+ array<void (*)(void*), sizeof...(Messages)> destruct_functions = {{(&Messages::Destroy)...}};
+
+public:
+ OnlineMessageHandlerTemplate() {
+ int block_size = 64;
+ int block_count = 1024 * 16;
+ block_allocator_memory = OG_MALLOC(block_size * block_count);
+ block_allocator_lock.lock();
+ block_allocator.Init(block_allocator_memory, block_count, block_size);
+ block_allocator_lock.unlock();
+ }
+
+ ~OnlineMessageHandlerTemplate() {
+ ForceRemoveAllMessages();
+ OG_FREE(block_allocator_memory);
+ }
+
+ void ForceRemoveAllMessages() {
+ message_lock.lock();
+ for(auto& v : messages) {
+ get<0>(v.second) = 0;
+ }
+ message_lock.unlock();
+
+ FreeUnreferencedMessages();
+ }
+
+ void FreeUnreferencedMessages() {
+ while(StepFreeUnreferencedMessage()) { }
+ }
+
+ bool StepFreeUnreferencedMessage() {
+ OnlineMessageID remove_message = 0;
+ OnlineMessageType remove_message_type = 0;
+ void* remove_message_ptr = nullptr;
+ bool ret_value = false;
+
+ message_lock.lock();
+ for(auto& v : messages) {
+ if(get<0>(v.second) <= 0) {
+ remove_message = v.first;
+ remove_message_type = get<1>(v.second);
+ remove_message_ptr = get<2>(v.second);
+ break;
+ }
+ }
+
+ if(remove_message != 0) {
+ destruct_functions[remove_message_type](remove_message_ptr);
+ block_allocator_lock.lock();
+ block_allocator.Free(get<2>(messages[remove_message]));
+ block_allocator_lock.unlock();
+
+ messages.erase(remove_message);
+ ret_value = true;
+ }
+
+ message_lock.unlock();
+ return ret_value;
+ }
+
+ template<typename Message, typename... VArgs>
+ OnlineMessageRef Create(VArgs... args) {
+ block_allocator_lock.lock();
+ void* memory = block_allocator.Alloc(sizeof(Message));
+ block_allocator_lock.unlock();
+
+ message_lock.lock();
+ OnlineMessageID message_id = message_id_counter;
+ message_id_counter++;
+
+ Message* message = new(memory) Message(args...);
+
+ //Set this to 1, so we do an initial Aquire on the object, to prevent it from being
+ //deleted while we create the reference.
+ if(message != nullptr) {
+ messages[message_id] = std::tuple<unsigned int, short unsigned int, void*>(1, GetMessageType<Message>(), message);
+ } else {
+ LOGF << "Got a nullptr when trying to create a message, this is a program killer" << endl;
+ }
+ message_lock.unlock();
+
+ //This constructor will call Acquire on the message id, requiring a lock.
+ OnlineMessageRef message_ref(message_id);
+
+ //Release the initial acquire, making the message_ref the only reference holder.
+ Release(message_id);
+
+ return message_ref;
+ }
+
+ //Return a const ref so we do a move up, and not an unecessary copy.
+ OnlineMessageRef Create(OnlineMessageType message_type) {
+ block_allocator_lock.lock();
+ void* memory = block_allocator.Alloc(sizes[message_type]);
+ block_allocator_lock.unlock();
+
+ message_lock.lock();
+ OnlineMessageID message_id = message_id_counter;
+ message_id_counter++;
+
+ void* message = construct_functions[message_type](memory);
+
+ //Set this to 1, so we do an initial Aquire on the object, to prevent it from being
+ //deleted while we create the reference.
+ if(message != nullptr) {
+ messages[message_id] = std::tuple<unsigned int, short unsigned int, void*>(1, message_type, message);
+ } else {
+ LOGF << "Got a nullptr when trying to create a message, this is a program killer" << endl;
+ }
+
+ message_lock.unlock();
+
+ //This constructor will call Acquire on the message id, requiring a lock.
+ OnlineMessageRef message_ref(message_id);
+
+ //Release the initial acquire, making the message_ref the only reference holder.
+ Release(message_id);
+
+ return message_ref;
+ }
+
+ template<typename Message>
+ OnlineMessageType GetMessageType() {
+ return Index<Message,tuple<Messages...>>::value;
+ }
+
+ /*
+ template<typename Message>
+ Message* Get(const OnlineMessageRef& message_ref) {
+ return static_cast<Message*>(get<2>(messages[message_ref.GetID()]));
+ }
+ */
+
+ void Release(OnlineMessageID message_id) {
+ //We can optimize the Acquire/Release here by keeping better track of id allocation
+ //and pre-creating an array that holds reference counts, rather than using a map.
+ lock_guard<mutex> lg(message_lock);
+ auto message = messages.find(message_id);
+ if(message != messages.end()) {
+ get<0>(message->second)--;
+ }
+ }
+
+ void Acquire(OnlineMessageID message_id) {
+ lock_guard<mutex> lg(message_lock);
+ auto message = messages.find(message_id);
+ if(message != messages.end()) {
+ get<0>(message->second)++;
+ }
+ }
+
+ binn* Serialize(const OnlineMessageRef& message_ref) {
+ message_lock.lock();
+ auto message = messages.find(message_ref.GetID());
+ message_lock.unlock();
+
+ if(message != messages.end()) {
+ return serialize_functions[get<1>(message->second)](get<2>(message->second));
+ }
+ return binn_object();
+ }
+
+ OnlineMessageRef Deserialize(OnlineMessageType message_type, binn* l) {
+ OnlineMessageRef message_ref = Create(message_type);
+
+ message_lock.lock();
+ auto message = messages.find(message_ref.GetID());
+ message_lock.unlock();
+
+ if(message != messages.end()) {
+ deserialize_functions[message_type](get<2>(message->second), l);
+ }
+
+ return message_ref;
+ }
+
+ void Execute(const OnlineMessageRef& message_ref, PeerID from) {
+ //We can optimize away this messages de-reference by storing the pointer and type in OnlineMessageRef,
+ //which could potentially be a speed-boost. The tradeoff is that the size of OnlineMessageRef would increase.
+ //same optimization is applicable to Deserialize, Serialize and Destroy.
+ message_lock.lock();
+ auto message = messages.find(message_ref.GetID());
+ message_lock.unlock();
+
+ if(message != messages.end()) {
+ execute_functions[get<1>(message->second)](message_ref, get<2>(message->second), from);
+ }
+ }
+
+ OnlineMessageType GetMessageType(const OnlineMessageRef& message_ref) {
+ lock_guard<mutex> lg(message_lock);
+ auto message = messages.find(message_ref.GetID());
+ if(message != messages.end()) {
+ return get<1>(message->second);
+ }
+
+ return static_cast<OnlineMessageType>(-1);
+ }
+
+ void* GetMessageData(const OnlineMessageRef& message_ref) {
+ message_lock.lock();
+ auto message = messages.find(message_ref.GetID());
+ message_lock.unlock();
+
+ if(message != messages.end()) {
+ return get<2>(message->second);
+ } else {
+ return nullptr;
+ }
+ }
+};
diff --git a/Source/Online/online_peer.h b/Source/Online/online_peer.h
new file mode 100644
index 00000000..4b2b949d
--- /dev/null
+++ b/Source/Online/online_peer.h
@@ -0,0 +1,49 @@
+//-----------------------------------------------------------------------------
+// Name: online_peer.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Online/online_datastructures.h>
+#include <Game/connection_closed_reason.h>
+#include <Network/net_framework.h>
+
+#include <string>
+
+// work around due to how the locks hinders us to call close connection
+// while inside the state_manager.update
+struct ConnectionToBeClosed {
+ NetConnectionID conn_id;
+ ConnectionClosedReason reason;
+};
+
+struct Peer {
+ PeerID peer_id = 0;
+ NetConnectionID conn_id;
+ NetConnectionInfo m_info;
+ OnlineMessageID last_sent_package_id = 0;
+
+ map<OnlineFlags, bool> host_session_flags;
+
+ //Host side flag to mark that we need to do an initial sync with this peer.
+ bool should_send_state_messages = false;
+ float current_ping_delta = 0.0f;
+};
diff --git a/Source/Online/online_session.h b/Source/Online/online_session.h
new file mode 100644
index 00000000..8538f6fd
--- /dev/null
+++ b/Source/Online/online_session.h
@@ -0,0 +1,119 @@
+//-----------------------------------------------------------------------------
+// Name: online_session.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Online/online_datastructures.h>
+#include <Online/online_peer.h>
+#include <Online/online_client_connection_manager.h>
+
+#include <Network/net_framework.h>
+
+#if ENABLE_STEAMWORKS || ENABLE_GAMENETWORKINGSOCKETS
+#include <steamnetworkingtypes.h> // TODO this should be removed once steam is abstracted out
+#endif
+
+#include <unordered_map>
+#include <vector>
+#include <list>
+#include <queue>
+#include <string>
+#include <mutex>
+#include <utility>
+
+using std::mutex;
+using std::queue;
+using std::vector;
+using std::string;
+using std::pair;
+using std::unordered_map;
+using std::list;
+
+class OnlineMessageBase;
+
+/// This class is designed to be "current session" data only.
+/// Once leaving multiplayer this object will be deallocated.
+class OnlineSession {
+public:
+ PlayerID local_player_id = 0; // TODO this will have to become an array to support multiple people per connection
+
+ queue<AngelScriptUpdate> peer_queued_level_updates;
+ queue<AngelScriptUpdate> peer_queued_sync_updates;
+
+ mutex connected_peers_lock;
+ vector<Peer> connected_peers; // will replace peers - Shared in steam and socket threads - should be protected
+ ClientConnectionManager client_connection_manager;
+
+ mutex state_packages_lock;
+ unordered_map<int, AngelScriptUpdate> state_packages; // hold updates needed when new clients join
+
+ mutex chat_messages_lock;
+ vector<ChatMessage> chat_messages;
+
+ unordered_map<PlayerID, PlayerState> player_states;
+ unordered_map<PlayerID, list<OnlineMessageRef>> player_inputs;
+
+ struct OutgoingMessage {
+ //Broadcast means send to all peers.
+ //If broadcast is false target has to be set.
+ bool broadcast;
+ NetConnectionID target;
+ OnlineMessageRef message;
+ };
+
+ //New outgoing message queue to replace all other examples of sending data from the main thread
+ //and queue into the networking thread.
+ mutex outgoing_messages_lock;
+ vector<OutgoingMessage> outgoing_messages;
+
+ //These are persistent outgoing messages from the host,
+ //messages that are sent to all current peers but are persistent are moved
+ //over to this list after being sent.
+ //When a new peer joins the game, all of these are queued up
+ //for the peer before the normal outgoing_messages queue is consumed for them.
+ vector<OnlineMessageRef> persistent_outgoing_messages;
+
+ struct IncomingMessage {
+ PeerID from;
+ OnlineMessageRef message;
+ };
+
+ //New incoming message queue to replace all other examples of messages coming in from the network socket to be excuted on the main thread.
+ mutex incoming_messages_lock;
+ vector<IncomingMessage> incoming_messages;
+
+
+ queue<ConnectionToBeClosed> connections_waiting_to_be_closed;
+ vector<ObjectID> free_avatars;
+ vector<ObjectID> taken_avatars;
+
+ map<OnlineFlags, bool> host_session_flags;
+
+ //ID Map between input-label strings and number id's.
+ unsigned binding_id_map_counter_ = 0;
+ map<string, uint8_t> binding_id_map_;
+ map<uint8_t, string> binding_name_map_;
+
+ float last_ping_time = 0.0f;
+ uint32_t ping_counter = 0;
+ uint32_t last_ping_id = 0;
+};
diff --git a/Source/Online/online_utility.cpp b/Source/Online/online_utility.cpp
new file mode 100644
index 00000000..1b2c4103
--- /dev/null
+++ b/Source/Online/online_utility.cpp
@@ -0,0 +1,141 @@
+//-----------------------------------------------------------------------------
+// Name: online_utility.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Online/online_utility.h>
+#include <Main/engine.h>
+#include <Internal/config.h>
+
+#if ENABLE_STEAMWORKS
+#include <isteamfriends.h>
+#endif
+
+// Name is invalid if (less than 3 letters) or (contains any non alpha numerical characters)
+bool OnlineUtility::IsValidPlayerName(const std::string& name) {
+ if (name.length() <= 2)
+ return false;
+
+ for (char c : name) {
+ bool valid_char = (48 <= c && c <= 57) || (65 <= c && c <= 90) || (97 <= c && c <= 122) || c == 32;
+ if (!valid_char) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+std::string OnlineUtility::GetDefaultPlayerName() {
+#if ENABLE_STEAMWORKS
+ if(SteamFriends() != nullptr) {
+ const char * name_ptr = SteamFriends()->GetPersonaName();
+ if(name_ptr != nullptr) {
+ std::string default_name(name_ptr);
+ if(IsValidPlayerName(default_name)) {
+ return default_name;
+ }
+ }
+ }
+#endif
+
+ // Steam name isn't valid, use fallback
+ return "Unknown Rabbit";
+}
+
+std::string OnlineUtility::GetPlayerName() {
+ return config.GetRef("playername").str();
+}
+
+std::string OnlineUtility::GetActiveModsString() {
+ bool active_mods = false;
+ stringstream modlist;
+ vector<ModInstance*> mods = ModLoading::Instance().GetAllMods();
+ for(size_t i = 0; i < mods.size(); i++) {
+ if(mods[i]->IsActive() && !mods[i]->IsCore()) {
+ if(active_mods) {
+ modlist << ", ";
+ }
+ active_mods = true;
+
+ if(mods[i]->SupportsOnline()) {
+ modlist << mods[i]->id;
+ } else {
+ modlist << "[" << mods[i]->id << "]";
+ }
+ }
+ }
+
+ return modlist.str();
+}
+
+bool OnlineUtility::HasActiveIncompatibleMods() {
+ for (auto& it : ModLoading::Instance().GetAllMods()) {
+ if (it->IsActive() && !it->IsCore() && !it->SupportsOnline()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+std::string OnlineUtility::GetActiveIncompatibleModsString() {
+ bool active_mods = false;
+ stringstream modlist;
+ vector<ModInstance*> mods = ModLoading::Instance().GetAllMods();
+ for(size_t i = 0; i < mods.size(); i++) {
+ if(mods[i]->IsActive() && !mods[i]->IsCore() && !mods[i]->SupportsOnline()) {
+ if(active_mods) {
+ modlist << ", ";
+ }
+ active_mods = true;
+ modlist << "\"" << mods[i]->id << "\"";
+ }
+ }
+
+ return modlist.str();
+}
+
+void OnlineUtility::HandleCommand(PlayerID playerID, const std::string& message)
+{
+ auto command = OnlineUtility::CommandFromString(message);
+ //TODO send command to angelscript
+}
+
+bool OnlineUtility::IsCommand(const std::string& message)
+{
+ return message.size() > 1 && message.front() == '/';
+}
+
+std::vector<std::string> OnlineUtility::CommandFromString(const std::string& message)
+{
+ std::string command_str = message.substr(1);
+ std::vector<std::string> command;
+
+ size_t start = command_str.find_first_not_of(' ');
+ size_t end = command_str.find_first_of(' ', start);
+ while (start != std::string::npos) {
+ command.push_back(command_str.substr(start, end != std::string::npos ? end - start : std::string::npos));
+ start = command_str.find_first_not_of(' ', end);
+ end = command_str.find_first_of(' ', start);
+ }
+ return command;
+}
diff --git a/Source/Online/online_utility.h b/Source/Online/online_utility.h
new file mode 100644
index 00000000..b047882a
--- /dev/null
+++ b/Source/Online/online_utility.h
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------------
+// Name: online_utility.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Online/online_datastructures.h>
+
+#include <string>
+#include <vector>
+
+class OnlineUtility {
+public:
+ static bool IsValidPlayerName(const std::string& name);
+ static std::string GetDefaultPlayerName();
+ static std::string GetPlayerName();
+ static std::string GetActiveModsString();
+ static bool HasActiveIncompatibleMods();
+ static std::string GetActiveIncompatibleModsString();
+ static bool IsCommand(const std::string& message);
+ static void HandleCommand(PlayerID playerID, const std::string& message);
+private:
+ static std::vector<std::string> CommandFromString(const std::string& message); // removes '/' and split the rest of the string on ' '
+};
diff --git a/Source/Online/package_header.cpp b/Source/Online/package_header.cpp
new file mode 100644
index 00000000..28aaa2e3
--- /dev/null
+++ b/Source/Online/package_header.cpp
@@ -0,0 +1,23 @@
+//-----------------------------------------------------------------------------
+// Name: package_header.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "package_header.h"
diff --git a/Source/Online/package_header.h b/Source/Online/package_header.h
new file mode 100644
index 00000000..96fea279
--- /dev/null
+++ b/Source/Online/package_header.h
@@ -0,0 +1,48 @@
+//-----------------------------------------------------------------------------
+// Name: package_header.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Online/online_datastructures.h>
+
+#include <binn.h>
+
+#include <vector>
+
+using std::vector;
+
+class PackageHeader {
+public:
+ enum class Type {
+ MESSAGE_OBJECT,
+ };
+
+ Type package_type;
+
+ //Data for OnlineMessageObject
+ OnlineMessageRef message_ref;
+
+ binn* Serialize();
+ void Deserialize(void* data);
+
+ PackageHeader() {};
+};
diff --git a/Source/Online/state_machine.h b/Source/Online/state_machine.h
new file mode 100644
index 00000000..2dd0ef34
--- /dev/null
+++ b/Source/Online/state_machine.h
@@ -0,0 +1,101 @@
+//-----------------------------------------------------------------------------
+// Name: state_machine.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+template <class T>
+class BaseState {
+protected:
+ T* actor;
+
+public:
+ void SetActor(T* actor) {
+ this->actor = actor;
+ }
+
+ virtual void OnEnter() {};
+ virtual BaseState<T>* OnUpdate() = 0;
+ virtual void OnExit() {};
+
+ virtual ~BaseState() {}
+};
+
+template <class T>
+class StateMachine {
+private:
+ BaseState<T>* current_state = nullptr;
+ BaseState<T>* start_state = nullptr;
+
+public:
+ T actor;
+
+private:
+ void SwitchToState(BaseState<T>* new_state) {
+ if(current_state != new_state) {
+ ExitState();
+
+ current_state = new_state;
+ if (new_state != nullptr) {
+ new_state->SetActor(&actor);
+ new_state->OnEnter();
+ }
+ }
+ }
+
+ void ExitState() {
+ if (current_state != nullptr) {
+ current_state->OnExit();
+ delete current_state;
+ current_state = nullptr;
+ }
+ }
+
+public:
+ void Update() {
+ if (current_state != nullptr) {
+ BaseState<T>* new_state = current_state->OnUpdate();
+
+ if(current_state != new_state) {
+ SwitchToState(new_state);
+ }
+ } else {
+ // When we first build the state machine, we'll be stateless but are given a pointer to the first state.
+ if (start_state != nullptr) {
+ SwitchToState(start_state);
+ start_state = nullptr;
+ }
+ }
+ }
+
+ StateMachine() {
+
+ }
+
+ StateMachine(BaseState<T>* initial_state, T actor) : actor(actor) {
+ // We can't call SwitchToState here, as that would make it fire in whatever thread we are right now
+ start_state = initial_state;
+ }
+
+ ~StateMachine() {
+ ExitState();
+ }
+};
diff --git a/Source/Online/time_interpolator.cpp b/Source/Online/time_interpolator.cpp
new file mode 100644
index 00000000..8c072bc4
--- /dev/null
+++ b/Source/Online/time_interpolator.cpp
@@ -0,0 +1,163 @@
+//-----------------------------------------------------------------------------
+// Name: time_interpolator.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "time_interpolator.h"
+
+#include <Internal/timer.h>
+#include <Logging/logdata.h>
+
+#include <cassert>
+#include <cmath>
+
+extern Timer game_timer;
+
+Timestamps::Timestamps() : timestamps(nullptr), timestamp_size(0), timestamp_count(0) {
+ SetSize(16);
+}
+
+Timestamps::~Timestamps() {
+ free(timestamps);
+ timestamp_size = 0;
+ timestamp_count = 0;
+}
+
+void Timestamps::Clear() {
+ timestamp_count = 0;
+}
+
+void Timestamps::PushValue(float value) {
+ assert(timestamp_size > 0);
+
+ if(timestamp_size <= timestamp_count) {
+ SetSize(timestamp_size + 16);
+ }
+
+ timestamps[timestamp_count] = value;
+ timestamp_count++;
+}
+
+void Timestamps::Pop() {
+ assert(timestamp_size > 0);
+
+ for (int i = timestamp_count - 1; i >= 1; i--) {
+ timestamps[i - 1] = timestamps[i];
+ }
+ timestamp_count--;
+}
+
+void Timestamps::SetSize(int size) {
+ if(size > timestamp_size) {
+ float* new_timestamps = (float*)realloc(timestamps, sizeof(float) * size);
+ if(new_timestamps != nullptr) {
+ timestamps = new_timestamps;
+ timestamp_size = size;
+ } else {
+ LOGF << "realloc() returned a null pointer, out of memory" << std::endl;
+ }
+ }
+}
+
+int Timestamps::size() {
+ return timestamp_count;
+}
+
+float Timestamps::from_begin(int offset) {
+ return timestamps[offset];
+}
+
+float Timestamps::from_end(int offset) {
+ return timestamps[timestamp_count - 1 - offset];
+}
+
+float TimeInterpolator::offset_shift_coefficient_factor = 5.0f;
+int TimeInterpolator::target_window_size = 5;
+
+int TimeInterpolator::Update() {
+ if(timestamps.size() > 1) {
+ current_walltime = game_timer.GetWallTime();
+
+ virtual_host_walltime = current_walltime + host_walltime_offset;
+
+ float current_frame_walltime = timestamps.from_begin(0);
+ float next_frame_walltime = timestamps.from_begin(1);
+ float second_last_frame_walltime = timestamps.from_end(1);
+ float last_frame_walltime = timestamps.from_end(0);
+
+ // The two frames we are trying to interpolate between are on the same time
+ if (current_frame_walltime == next_frame_walltime) {
+ LOGE << "Trying to interpolate between two identical timestamps! This can lead to a nan value in the system!" << std::endl;
+ timestamps.Pop();
+ return 3;
+ }
+
+ //We are beyond the last frame time, wait for enough to start interpolating and set it to the resting point
+ if(virtual_host_walltime > last_frame_walltime) {
+ if(timestamps.size() >= 2 * target_window_size) {
+ float third_last_frame_walltime = timestamps.from_end(target_window_size);
+ //Land on what the algorithm sees as the resting-point
+ host_walltime_offset = third_last_frame_walltime - current_walltime;
+ LOGI << "Resetting host_walltime_offset to: " << host_walltime_offset << std::endl;
+ return 1;
+ } else {
+ return 3;
+ }
+ }
+ //We are ahead the first frame time, wait for enough to start interpolating and set it to the resting point
+ if(virtual_host_walltime < current_frame_walltime) {
+ //If we behind the current frame, skip ahead to the resting-position in accordance with the
+ //speed adjusting algo, so we don't create a bouncing interpolating effect.
+ if(timestamps.size() >= 2 * target_window_size) {
+ float third_last_frame_walltime = timestamps.from_end(target_window_size);
+ //Land on what the algorithm sees as the resting-point
+ host_walltime_offset = third_last_frame_walltime - current_walltime;
+
+ if ((host_walltime_offset + current_walltime) < current_frame_walltime) {
+ // If this is true, then we will get stuck, it's true to what is probably a rounding error
+ // this is a band aid solution
+ float diff = next_frame_walltime - (host_walltime_offset + current_walltime);
+ host_walltime_offset += diff / 2.0f;
+ }
+ LOGI << "Data missing, jumping ahead to catch up" << std::endl;
+ return 1;
+ } else {
+ return 3;
+ }
+ }
+
+ //If we're past the next bones timestamp, pop the bones from the stack. and do the next one.
+ if(virtual_host_walltime > next_frame_walltime) {
+ return 2;
+ }
+
+ interpolation_step = (virtual_host_walltime - current_frame_walltime) / (next_frame_walltime - current_frame_walltime);
+
+ //Rough estimate how much data there's left in the buffer.
+ float current_window_size = timestamps.size() - interpolation_step;
+
+ //Pow function that ranges from -1.0f to 1.0f returning a shift direction accordingly.
+ //If we have target_window_size data left in the buffer, this should return 0.0f, meaning no time shift.
+ host_walltime_offset_shift_vel = offset_shift_coefficient_factor * std::pow((current_window_size - (float)target_window_size) / (float)target_window_size, 3.0f);
+
+ host_walltime_offset += host_walltime_offset_shift_vel * game_timer.timestep;
+ }
+ return 0;
+}
diff --git a/Source/Online/time_interpolator.h b/Source/Online/time_interpolator.h
new file mode 100644
index 00000000..9150c7de
--- /dev/null
+++ b/Source/Online/time_interpolator.h
@@ -0,0 +1,63 @@
+//-----------------------------------------------------------------------------
+// Name: time_interpolator.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+class Timestamps {
+private:
+ float* timestamps;
+ int timestamp_size;
+ int timestamp_count;
+
+public:
+ Timestamps();
+ ~Timestamps();
+
+ void Clear();
+ void PushValue(float value);
+ void Pop();
+ void SetSize(int size);
+ int size();
+ float from_begin(int offset);
+ float from_end(int offset);
+};
+
+class TimeInterpolator {
+public:
+ Timestamps timestamps;
+
+ float virtual_host_walltime = 0.f;
+ float current_walltime = 0.f;
+ float host_walltime_offset = 0.f;
+
+ float interpolation_time = 0.f;
+ float interpolation_step = 0.f;
+
+ float full_interpolation = 0.f;
+ float host_walltime_offset_shift_acc = 0.f;
+ float host_walltime_offset_shift_vel = 0.f;
+
+ static float offset_shift_coefficient_factor;
+ static int target_window_size;
+
+ int Update();
+};
diff --git a/Source/Physics/bulletcollision.cpp b/Source/Physics/bulletcollision.cpp
new file mode 100644
index 00000000..62d65907
--- /dev/null
+++ b/Source/Physics/bulletcollision.cpp
@@ -0,0 +1,267 @@
+//-----------------------------------------------------------------------------
+// Name: bulletcollision.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <Math/vec3.h>
+#include <Math/vec3math.h>
+#include <Math/vec4.h>
+
+#include <Physics/bulletcollision.h>
+#include <Physics/bulletobject.h>
+
+#include <Objects/object.h>
+#include <Objects/envobject.h>
+
+#include <Graphics/pxdebugdraw.h>
+#include <Math/enginemath.h>
+
+#include <BulletCollision/CollisionDispatch/btInternalEdgeUtility.h>
+#include <BulletCollision/CollisionShapes/btTriangleShape.h>
+
+btScalar ContactSlideCallback::addSingleResult( btManifoldPoint& cp, const btCollisionObjectWrapper* colObjWrap0, int partId0, int index0, const btCollisionObjectWrapper* colObj1Wrap, int partId1, int index1 ) {
+ //LOG_ASSERT2(index0 >= 0, index0);
+ LOG_ASSERT_ONCE(index1 >= 0);
+
+ collision_info.contacts.resize(collision_info.contacts.size() + 1);
+ btVector3 normal_world = cp.m_normalWorldOnB;
+ btVector3 override_dir;
+ if(single_sided){
+ // Modify normal to use single-sided collision with triangle meshes
+ static const bool draw_norm = false;
+ btVector3 btNormDir;
+ if(colObj1Wrap->getCollisionShape()->getShapeType() == TRIANGLE_SHAPE_PROXYTYPE){
+ const btTriangleShape* tri_shape = static_cast<const btTriangleShape*>(colObj1Wrap->getCollisionShape());
+ btVector3 tri_normal;
+ tri_shape->calcNormal(tri_normal);
+ btNormDir = quatRotate(colObj1Wrap->getWorldTransform().getRotation(), tri_normal);
+ if(colObj1Wrap->m_collisionObject && colObj1Wrap->m_collisionObject->getUserPointer() && ((BulletObject*)colObj1Wrap->m_collisionObject->getUserPointer())->owner_object){
+ Object* obj = ((BulletObject*)colObj1Wrap->m_collisionObject->getUserPointer())->owner_object;
+ collision_info.contacts.back().obj_id = obj->GetID();
+ if(obj->GetType() == _env_object){
+ EnvObject* eo = (EnvObject*)obj;
+ collision_info.contacts.back().tri = index1;
+ if((unsigned)index1 < eo->normal_override_custom.size()){
+ collision_info.contacts.back().custom_normal = eo->normal_override_custom[index1].xyz();
+ }
+ }
+ }
+ if(draw_norm){
+ const btTransform &bt_transform = colObj1Wrap->getCollisionObject()->getWorldTransform();
+ btVector3 bt_vert[3];
+ for(int i=0; i<3; ++i){
+ tri_shape->getVertex(i, bt_vert[i]);
+ bt_vert[i] = bt_transform * bt_vert[i];
+ }
+ btVector3 mid = (bt_vert[0] + bt_vert[1] + bt_vert[2]) / 3.0f;
+ DebugDraw::Instance()->AddLine(ToVec3(mid), ToVec3(mid+btNormDir), vec4(1.0f), _fade);
+ }
+ }
+ if(btNormDir.dot(cp.m_normalWorldOnB) < 0.0f){
+ return 1.0f;
+ }
+ }
+
+ btVector3 point_world = cp.m_positionWorldOnB;
+
+ normal_world.normalize();
+
+ collision_info.contacts.back().normal = vec3(normal_world[0], normal_world[1], normal_world[2]);
+ collision_info.contacts.back().point = vec3(point_world[0], point_world[1], point_world[2]);
+
+ return 1.0f;
+}
+
+btScalar SweptSlideCallback::addSingleResult( btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace ) {
+ collision_info.contacts.resize(collision_info.contacts.size() + 1);
+ const btCollisionShape* shape = convexResult.m_hitCollisionObject->getCollisionShape();
+ int shape_type = shape->getShapeType();
+ btVector3 override_dir;
+ if(single_sided){
+ // Get single-sided triangle normal to eliminate backface collisions
+ btVector3 btNormDir;
+ if(shape_type == TRIANGLE_MESH_SHAPE_PROXYTYPE || shape_type == SCALED_TRIANGLE_MESH_SHAPE_PROXYTYPE) {
+ btTriangleMeshShape* tri_mesh_shape;
+ switch(shape_type){
+ case TRIANGLE_MESH_SHAPE_PROXYTYPE:
+ tri_mesh_shape = (btTriangleMeshShape*)shape;
+ break;
+ case SCALED_TRIANGLE_MESH_SHAPE_PROXYTYPE:
+ tri_mesh_shape = ((btScaledBvhTriangleMeshShape*)shape)->getChildShape();
+ break;
+ }
+ btStridingMeshInterface* mesh_interface = tri_mesh_shape->getMeshInterface();
+
+ const unsigned char *vertexbase;
+ int numverts;
+ PHY_ScalarType type;
+ int stride;
+ const unsigned char *indexbase;
+ int indexstride;
+ int numfaces;
+ PHY_ScalarType indicestype;
+ mesh_interface->getLockedReadOnlyVertexIndexBase(&vertexbase, numverts, type, stride, &indexbase, indexstride, numfaces, indicestype, 0);
+
+ const btTransform &bt_transform = convexResult.m_hitCollisionObject->getWorldTransform();
+ const btVector3& scale = shape->getLocalScaling();
+
+ int tri_index = convexResult.m_localShapeInfo->m_triangleIndex;
+ LOG_ASSERT(tri_index >= 0);
+ btVector3 bt_vert[3];
+ for(int i=0; i<3; ++i){
+ int index = ((int*)indexbase)[tri_index * indexstride / sizeof(int) + i] * stride / sizeof(float);
+ for(int j=0; j<3; ++j){
+ bt_vert[i][j] = ((float*)vertexbase)[index+j];
+ }
+ bt_vert[i] *= scale;
+ bt_vert[i] = bt_transform * bt_vert[i];
+ }
+ btNormDir = btCross(bt_vert[1]-bt_vert[0], bt_vert[2]-bt_vert[0]);
+ mesh_interface->unLockReadOnlyVertexBase(0);
+ if(convexResult.m_hitCollisionObject && convexResult.m_hitCollisionObject->getUserPointer() && ((BulletObject*)convexResult.m_hitCollisionObject->getUserPointer())->owner_object){
+ Object* obj = ((BulletObject*)convexResult.m_hitCollisionObject->getUserPointer())->owner_object;
+ collision_info.contacts.back().obj_id = obj->GetID();
+ if(obj->GetType() == _env_object){
+ EnvObject* eo = (EnvObject*)obj;
+ collision_info.contacts.back().tri = tri_index;
+ if((unsigned)tri_index < eo->normal_override_custom.size()){
+ collision_info.contacts.back().custom_normal = eo->normal_override_custom[tri_index].xyz();
+ }
+ }
+ }
+ }
+ if(btNormDir.dot(end_pos-start_pos)>0.0f){
+ return 1.0f;
+ }
+ }
+
+ if(collision_info.contacts.size() == 0){
+ true_closest_hit_fraction = convexResult.m_hitFraction;
+ } else {
+ true_closest_hit_fraction = min(convexResult.m_hitFraction,
+ true_closest_hit_fraction);
+ }
+
+ btVector3 normal_world;
+ if (normalInWorldSpace) {
+ normal_world = convexResult.m_hitNormalLocal;
+ } else {
+ const btVector3& local_normal = convexResult.m_hitNormalLocal;
+ if(shape_type == SCALED_TRIANGLE_MESH_SHAPE_PROXYTYPE) {
+ normal_world = local_normal;
+ } else {
+ const btTransform &normal_transform =
+ convexResult.m_hitCollisionObject->getWorldTransform();
+ normal_world = quatRotate(normal_transform.getRotation(), local_normal);
+ }
+ }
+ normal_world.normalize();
+
+ if(normal_world.dot(end_pos-start_pos)>0.0f){
+ return 1.0f;
+ }
+
+ btVector3 point_world = convexResult.m_hitPointLocal;
+ collision_info.contacts.back().normal = vec3(normal_world[0], normal_world[1], normal_world[2]);
+ collision_info.contacts.back().point = vec3(point_world[0], point_world[1], point_world[2]);
+
+ // These are set to 1.0 in order to return all collisions, and
+ // not just the first one.
+ this->m_closestHitFraction = 1.0f;
+ return 1.0f;
+}
+
+btScalar ContactInfoCallback::addSingleResult( btManifoldPoint& cp, const btCollisionObjectWrapper* colObjWrap0, int partId0, int index0, const btCollisionObjectWrapper* colObjWrap1, int partId1, int index1 )
+{
+ btVector3 point_world = cp.m_positionWorldOnB;
+
+ ContactInfo ci;
+ ci.object = (BulletObject*)colObjWrap1->getCollisionObject()->getUserPointer();
+ if(!ci.object){
+ ci.object = (BulletObject*)colObjWrap0->getCollisionObject()->getUserPointer();
+ }
+ ci.point = point_world;
+ ci.tri = index1;
+ contact_info.push_back(ci);
+
+ return 1.0f;
+}
+
+btScalar SimpleRayResultCallbackInfo::addSingleResult(
+ btCollisionWorld::LocalRayResult& cp,
+ bool normal_in_world_space )
+{
+ RayContactInfo ci;
+ ci.object = (BulletObject*)cp.m_collisionObject->getUserPointer();
+ ci.hit_fraction = cp.m_hitFraction;
+ contact_info.push_back(ci);
+ return 1.0f;
+}
+
+btScalar SimpleRayTriResultCallback::addSingleResult( btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace )
+{
+ m_closestHitFraction = rayResult.m_hitFraction;
+ m_collisionObject = rayResult.m_collisionObject;
+ m_hit_normal = vec3(rayResult.m_hitNormalLocal[0],
+ rayResult.m_hitNormalLocal[1],
+ rayResult.m_hitNormalLocal[2]);
+ if(rayResult.m_localShapeInfo){
+ m_tri = rayResult.m_localShapeInfo->m_triangleIndex;
+ }
+ m_object = (BulletObject*)rayResult.m_collisionObject->getUserPointer();
+ return rayResult.m_hitFraction;
+}
+
+
+btScalar SimpleRayResultCallback::addSingleResult( btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace )
+{
+ m_closestHitFraction = rayResult.m_hitFraction;
+ m_collisionObject = rayResult.m_collisionObject;
+ m_hit_normal = vec3(rayResult.m_hitNormalLocal[0],
+ rayResult.m_hitNormalLocal[1],
+ rayResult.m_hitNormalLocal[2]);
+ return rayResult.m_hitFraction;
+}
+
+const vec3 & SimpleRayResultCallback::GetHitNormal()
+{
+ return m_hit_normal;
+}
+
+btScalar TriListCallback::addSingleResult( btManifoldPoint& cp, const btCollisionObjectWrapper* colObjWrap0, int partId0, int index0, const btCollisionObjectWrapper* colObjWrap1, int partId1, int index1 )
+{
+ BulletObject* obj = (BulletObject*)colObjWrap1->getCollisionObject()->getUserPointer();
+ if(obj){
+ tri_list[obj].push_back(index1);
+ }
+ return 1.0f;
+}
+
+vec3 vecFromBT(const btVector3 &bt_vec){
+ return vec3(bt_vec[0], bt_vec[1], bt_vec[2]);
+}
+
+btScalar MeshCollisionCallback::addSingleResult( btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0Wrap, int partId0, int index0, const btCollisionObjectWrapper* colObj1Wrap, int partId1, int index1 ) {
+ TriPair pair;
+ pair.first = index0;
+ pair.second = index1;
+ tri_pairs.insert(pair);
+ return 1.0f;
+}
diff --git a/Source/Physics/bulletcollision.h b/Source/Physics/bulletcollision.h
new file mode 100644
index 00000000..f98c3105
--- /dev/null
+++ b/Source/Physics/bulletcollision.h
@@ -0,0 +1,185 @@
+//-----------------------------------------------------------------------------
+// Name: bulletcollision.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+
+#include <btBulletDynamicsCommon.h>
+
+#include <map>
+#include <vector>
+#include <set>
+
+typedef btAlignedObjectArray<btVector3> btVec3Array;
+
+struct SlideCollisionContact {
+ vec3 point;
+ vec3 normal;
+ vec3 custom_normal;
+ int obj_id;
+ int tri;
+};
+
+struct SlideCollisionInfo {
+ std::vector<SlideCollisionContact> contacts;
+};
+
+class BulletObject;
+
+class ContactSlideCallback: public btCollisionWorld::ContactResultCallback {
+public:
+ SlideCollisionInfo collision_info;
+ bool single_sided;
+ ContactSlideCallback():
+ single_sided(true)
+ {}
+
+ btScalar addSingleResult(btManifoldPoint& cp,
+ const btCollisionObjectWrapper* colObj0Wrap,
+ int partId0,
+ int index0,
+ const btCollisionObjectWrapper* colObj1Wrap,
+ int partId1,
+ int index1);
+};
+
+typedef std::map<BulletObject*, std::vector<int> > TriListResults;
+
+class TriListCallback: public btCollisionWorld::ContactResultCallback {
+public:
+ TriListResults &tri_list;
+ TriListCallback(TriListResults &_tri_list):
+ tri_list(_tri_list)
+ {}
+ btScalar addSingleResult(btManifoldPoint& cp,
+ const btCollisionObjectWrapper* colObj0Wrap,
+ int partId0,
+ int index0,
+ const btCollisionObjectWrapper* colObj1Wrap,
+ int partId1,
+ int index1);
+};
+
+class MeshCollisionCallback: public btCollisionWorld::ContactResultCallback {
+public:
+ typedef std::pair<int, int> TriPair;
+ typedef std::set<TriPair> TriPairSet;
+ TriPairSet tri_pairs;
+ btScalar addSingleResult(btManifoldPoint& cp,
+ const btCollisionObjectWrapper* colObj0Wrap,
+ int partId0,
+ int index0,
+ const btCollisionObjectWrapper* colObj1Wrap,
+ int partId1,
+ int index1);
+};
+
+struct ContactInfo {
+ BulletObject* object;
+ btVector3 point;
+ int tri;
+};
+
+
+class ContactInfoCallback: public btCollisionWorld::ContactResultCallback {
+public:
+ btAlignedObjectArray<ContactInfo> contact_info;
+ btScalar addSingleResult(btManifoldPoint& cp,
+ const btCollisionObjectWrapper* colObj0Wrap,
+ int partId0,
+ int index0,
+ const btCollisionObjectWrapper* colObj1Wrap,
+ int partId1,
+ int index1);
+};
+
+class RayContactInfoCallback: public btCollisionWorld::ContactResultCallback {
+public:
+ btAlignedObjectArray<ContactInfo> contact_info;
+ btScalar addSingleResult(btManifoldPoint& cp,
+ const btCollisionObjectWrapper* colObj0Wrap,
+ int partId0,
+ int index0,
+ const btCollisionObjectWrapper* colObj1Wrap,
+ int partId1,
+ int index1);
+};
+
+class SweptSlideCallback: public btCollisionWorld::ConvexResultCallback {
+public:
+ SlideCollisionInfo collision_info;
+ bool single_sided;
+ float true_closest_hit_fraction;
+ btVector3 start_pos, end_pos;
+
+ SweptSlideCallback():
+ single_sided(true),
+ true_closest_hit_fraction(1.0f)
+ {}
+
+ btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,
+ bool normalInWorldSpace);
+};
+
+struct RayContactInfo {
+ BulletObject* object;
+ vec3 point;
+ vec3 normal;
+ float hit_fraction;
+};
+
+struct SimpleRayResultCallbackInfo : public btCollisionWorld::RayResultCallback
+{
+ btAlignedObjectArray<RayContactInfo> contact_info;
+ btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,
+ bool normalInWorldSpace);
+};
+
+struct SimpleRayResultCallback : public btCollisionWorld::RayResultCallback
+{
+ vec3 m_hit_normal;
+ btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,
+ bool normalInWorldSpace);
+
+ const vec3 &GetHitNormal();
+};
+
+struct SimpleRayTriResultCallback : public btCollisionWorld::RayResultCallback
+{
+ vec3 m_hit_normal;
+ int m_tri;
+ vec3 m_hit_pos;
+ BulletObject* m_object;
+ btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,
+ bool normalInWorldSpace);
+
+ const vec3 &GetHitNormal();
+};
+
+inline vec3 ToVec3(const btVector3 &vec){
+ return vec3(vec[0], vec[1], vec[2]);
+}
+
+inline btVector3 ToBtVector3(const vec3& vec){
+ return btVector3(vec[0], vec[1], vec[2]);
+}
diff --git a/Source/Physics/bulletobject.cpp b/Source/Physics/bulletobject.cpp
new file mode 100644
index 00000000..33a9bdf0
--- /dev/null
+++ b/Source/Physics/bulletobject.cpp
@@ -0,0 +1,588 @@
+//-----------------------------------------------------------------------------
+// Name: bulletobject.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <Physics/bulletobject.h>
+#include <Physics/physics.h>
+
+#include <Math/vec3math.h>
+#include <Math/vec4math.h>
+#include <Math/quaternions.h>
+
+#include <Graphics/geometry.h>
+#include <Graphics/graphics.h>
+
+#include <Internal/timer.h>
+#include <LinearMath/btTransformUtil.h>
+#include <Objects/object.h>
+#include <Logging/logdata.h>
+#include <Utility/assert.h>
+
+#include <btBulletDynamicsCommon.h>
+
+extern Timer game_timer;
+
+mat4 SafeGetOpenGLMatrix(const btTransform& bt_transform) {
+ // Make sure that transform mat4 is 16-byte aligned
+ // since Bullet may be reading to it from SSE intrinsics
+ char aligned_mem[sizeof(mat4)+16];
+ uintptr_t first = ((uintptr_t)aligned_mem)%16;
+ mat4* mat = (mat4*)&aligned_mem[16-first];
+ bt_transform.getOpenGLMatrix(mat->entries);
+ return *mat;
+}
+
+void ValidateTransform(const btTransform &bt_transform) {
+ /*const btMatrix3x3& basis = bt_transform.getBasis();
+ for(int i=0; i<3; ++i){
+ const btVector3& row = basis[i];
+ for(int j=0; j<3; ++j){
+ const btScalar &val = row[j];
+ if(val != val){
+ DisplayError("Error", "Invalid transform!");
+ }
+ }
+ }
+ const btVector3& origin = bt_transform.getOrigin();
+ for(int j=0; j<3; ++j){
+ const btScalar &val = origin[j];
+ if(val != val){
+ DisplayError("Error", "Invalid transform!");
+ }
+ }*/
+}
+
+void BulletObject::SetPosition( const vec3 &position) {
+ btTransform bt_transform = body->getWorldTransform();
+ vec3 new_pos = position + GetRotation() * com_offset;
+ bt_transform.setOrigin(btVector3(new_pos[0],
+ new_pos[1],
+ new_pos[2]));
+ ValidateTransform(bt_transform);
+ body->setWorldTransform(bt_transform);
+}
+
+void BulletObject::SetMargin( float _margin ) {
+ shape->setMargin(_margin);
+}
+
+float BulletObject::GetMargin( ) {
+ return shape->getMargin();
+}
+
+void BulletObject::SetRotation( const mat4 &rotation) {
+ btVector3 old_origin = body->getWorldTransform().getOrigin();
+ btTransform new_transform;
+ new_transform.setFromOpenGLMatrix(rotation.entries);
+ new_transform.setOrigin(old_origin);
+ ValidateTransform(new_transform);
+ body->setWorldTransform(new_transform);
+}
+
+void BulletObject::SetRotation( const quaternion &rotation) {
+ btVector3 old_origin = body->getWorldTransform().getOrigin();
+ btTransform new_transform;
+ new_transform.setRotation(btQuaternion(rotation.entries[0],rotation.entries[1],rotation.entries[2],rotation.entries[3]));
+ new_transform.setOrigin(old_origin);
+ ValidateTransform(new_transform);
+ body->setWorldTransform(new_transform);
+}
+
+void BulletObject::SetPositionAndVel( const vec3 &position, int frames) {
+ vec3 vel = (position - GetPosition())/game_timer.timestep;
+ vel /= (float)frames;
+ //SetPosition(position);
+ SetLinearVelocity(vel);
+}
+
+void BulletObject::SetRotationAndVel( const mat4 &rotation, int frames) {
+ btVector3 old_origin = body->getWorldTransform().getOrigin();
+ btTransform new_transform;
+ new_transform.setFromOpenGLMatrix(rotation.entries);
+ new_transform.setOrigin(old_origin);
+
+ btVector3 axis;
+ btScalar angle;
+ btTransformUtil::calculateDiffAxisAngle(body->getWorldTransform(),
+ new_transform,
+ axis,
+ angle);
+ btVector3 angVel = axis * angle / game_timer.timestep;
+ angVel /= (float)frames;
+
+ body->setAngularVelocity(angVel);
+
+ //body->setWorldTransform(new_transform);
+}
+
+void BulletObject::SetRotationAndVel( const quaternion &rotation, int frames) {
+ btVector3 old_origin = body->getWorldTransform().getOrigin();
+ btTransform new_transform;
+ new_transform.setRotation(btQuaternion(rotation.entries[0], rotation.entries[1], rotation.entries[2], rotation.entries[3]));
+ new_transform.setOrigin(old_origin);
+
+ btVector3 axis;
+ btScalar angle;
+ btTransformUtil::calculateDiffAxisAngle(body->getWorldTransform(),
+ new_transform,
+ axis,
+ angle);
+ btVector3 angVel = axis * angle / game_timer.timestep;
+ angVel /= (float)frames;
+
+ body->setAngularVelocity(angVel);
+
+ //body->setWorldTransform(new_transform);
+}
+
+
+void BulletObject::SetTransform( const vec4 &position, const mat4 &rotation, const vec4 &scale ) {
+ btTransform bt_transform;
+ bt_transform.setIdentity();
+ bt_transform.setFromOpenGLMatrix((btScalar*)rotation.entries);
+ vec3 new_pos = position.xyz() + GetRotation() * com_offset;
+ bt_transform.setOrigin(btVector3(new_pos[0],
+ new_pos[1],
+ new_pos[2]));
+ shape->setLocalScaling(btVector3(scale[0],
+ scale[1],
+ scale[2]));
+ ValidateTransform(bt_transform);
+ body->setWorldTransform(bt_transform);
+}
+
+void BulletObject::SetTransform( const mat4 &transform ) {
+ btTransform bt_transform;
+ bt_transform.setFromOpenGLMatrix((btScalar*)transform.entries);
+ ValidateTransform(bt_transform);
+ body->setWorldTransform(bt_transform);
+}
+
+void BulletObject::ApplyTransform( const mat4& transform ) {
+ mat4 old_transform_mat4 = SafeGetOpenGLMatrix(body->getWorldTransform());
+ old_transform_mat4 = transform * old_transform_mat4;
+ btTransform new_transform;
+ new_transform.setFromOpenGLMatrix(old_transform_mat4.entries);
+ ValidateTransform(new_transform);
+ body->setWorldTransform(new_transform);
+
+}
+
+void BulletObject::SetGravity( bool gravity ) {
+ if(!gravity){
+ body->setGravity(btVector3(0.0f,0.0f,0.0f));
+ } else {
+ const vec3& grav = Physics::Instance()->gravity;
+ body->setGravity(btVector3(grav[0],grav[1],grav[2]));
+ }
+}
+
+vec3 BulletObject::GetVelocityAtLocalPoint(const vec3& point) {
+ btVector3 vel = body->getVelocityInLocalPoint(
+ btVector3(point[0], point[1], point[2]));
+ return vec3(vel[0], vel[1], vel[2]);
+}
+
+void BulletObject::SetDamping( float damping ) {
+ body->setDamping(damping,damping);
+}
+
+void BulletObject::SetDamping( float lin_damping, float ang_damping ) {
+ body->setDamping(lin_damping,ang_damping);
+}
+
+
+vec3 BulletObject::GetInterpPosition() {
+ return mix(old_transform.GetTranslationPart(),
+ transform.GetTranslationPart(),
+ game_timer.GetInterpWeight());
+}
+
+mat4 BulletObject::GetInterpRotation() {
+ return mix(old_transform.GetRotationPart(),
+ transform.GetRotationPart(),
+ game_timer.GetInterpWeight());
+}
+
+vec3 BulletObject::GetInterpWeightPosition(float weight) {
+ return mix(old_transform.GetTranslationPart(),
+ transform.GetTranslationPart(),
+ weight);
+}
+
+mat4 BulletObject::GetInterpWeightRotation(float weight) {
+ return mix(old_transform.GetRotationPart(),
+ transform.GetRotationPart(),
+ weight);
+}
+
+vec3 BulletObject::GetInterpPositionX( int num, int progress ) {
+ return mix(old_transform.GetTranslationPart(),
+ transform.GetTranslationPart(),
+ game_timer.GetInterpWeightX(num, progress));
+}
+
+mat4 BulletObject::GetInterpRotationX( int num, int progress ) {
+ return mix(old_transform.GetRotationPart(),
+ transform.GetRotationPart(),
+ game_timer.GetInterpWeightX(num, progress));
+}
+
+
+vec3 BulletObject::GetHistoryInterpPositionX( int num, int progress, float offset ) {
+ float t_e = game_timer.timestep_error - offset;
+ float mult = (1.0f/(float)num);
+ float temp_timestep_error = mult*(float)progress + t_e*mult;
+ unsigned base = 0;
+ while(temp_timestep_error < 0){
+ ++base;
+ temp_timestep_error += 1.0f;
+ }
+ if(base+1>=transform_history.size()){
+ transform_history.resize(base+2, transform_history.back());
+ }
+ return transform_history[base].GetTranslationPart()*temp_timestep_error+transform_history[base+1].GetTranslationPart()*(1.0f-temp_timestep_error);
+}
+
+mat4 BulletObject::GetHistoryInterpRotationX( int num, int progress, float offset ) {
+ float t_e = game_timer.timestep_error - offset;
+ float mult = (1.0f/(float)num);
+ float temp_timestep_error = mult*(float)progress + t_e*mult;
+ unsigned base = 0;
+ while(temp_timestep_error < 0){
+ ++base;
+ temp_timestep_error += 1.0f;
+ }
+ if(base+1>=transform_history.size()){
+ transform_history.resize(base+2, transform_history.back());
+ }
+ return transform_history[base].GetRotationPart()*temp_timestep_error+transform_history[base+1].GetRotationPart()*(1.0f-temp_timestep_error);
+}
+
+void BulletObject::ClearVelocities() {
+ body->setLinearVelocity(btVector3(0.0f,0.0f,0.0f));
+ body->setAngularVelocity(btVector3(0.0f,0.0f,0.0f));
+}
+
+void BulletObject::SetAngularVelocity( const vec3& vel ) {
+ body->setAngularVelocity(btVector3(vel[0],vel[1],vel[2]));
+}
+
+void BulletObject::SetLinearVelocity( const vec3& vel ) {
+ body->setLinearVelocity(btVector3(vel[0],vel[1],vel[2]));
+}
+
+void BulletObject::AddAngularVelocity( const vec3& vel ) {
+ body->setAngularVelocity(body->getAngularVelocity() +
+ btVector3(vel[0],vel[1],vel[2]));
+}
+
+void BulletObject::AddLinearVelocity( const vec3& vel ) {
+ body->setLinearVelocity(body->getLinearVelocity() +
+ btVector3(vel[0],vel[1],vel[2]));
+}
+
+vec3 BulletObject::GetPosition() const {
+ btVector3 bt_origin = body->getWorldTransform().getOrigin();
+ vec3 com_display_offset = GetRotation() * -com_offset;
+ return vec3(bt_origin[0],bt_origin[1],bt_origin[2])+com_display_offset;
+}
+
+float BulletObject::GetMass() {
+ return 1.0f/body->getInvMass();
+}
+
+
+vec3 BulletObject::GetLinearVelocity() const {
+ btVector3 vec = body->getLinearVelocity();
+ return vec3(vec[0],vec[1],vec[2]);
+}
+
+mat4 BulletObject::GetRotation() const {
+ mat4 rotation = SafeGetOpenGLMatrix(body->getWorldTransform());
+ return rotation.GetRotationPart();
+}
+
+mat4 BulletObject::GetTransform() const {
+ return SafeGetOpenGLMatrix(body->getWorldTransform());
+}
+
+quaternion BulletObject::GetQuatRotation() {
+ btQuaternion quat = body->getWorldTransform().getRotation();
+ return quaternion(vec4(quat.getX(), quat.getY(), quat.getZ(), quat.getW()));
+}
+
+vec3 BulletObject::ObjectToWorld( const vec3 &point ) {
+ transform = SafeGetOpenGLMatrix(body->getWorldTransform());
+ return transform*point;
+}
+
+void BulletObject::ApplyForceAtWorldPoint( const vec3 &point, const vec3 &impulse ) {
+ vec3 local_point = WorldToObject(point);
+ body->applyForce(btVector3(impulse[0], impulse[1], impulse[2]),
+ btVector3(local_point[0], local_point[1], local_point[2]));
+ body->activate();
+}
+
+vec3 BulletObject::WorldToObject(const vec3 &world_point) {
+ transform = SafeGetOpenGLMatrix(body->getWorldTransform());
+ return invert(transform) * world_point;
+}
+
+void BulletObject::SetVisibility( const bool _visible ) {
+ visible = _visible;
+}
+
+bool BulletObject::IsVisible() const {
+ return visible;
+}
+
+void BulletObject::Activate() {
+ body->activate();
+}
+
+void BulletObject::Freeze() {
+ body->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT |
+ btCollisionObject::CF_NO_CONTACT_RESPONSE);
+ body->setActivationState(ISLAND_SLEEPING);
+}
+
+
+void BulletObject::Sleep() {
+ body->setActivationState(ISLAND_SLEEPING);
+}
+
+void BulletObject::NoSleep() {
+ body->setActivationState(DISABLE_DEACTIVATION);
+}
+
+void BulletObject::CanSleep() {
+ body->forceActivationState(ACTIVE_TAG);
+ body->activate(true);
+}
+
+void BulletObject::UnFreeze() {
+ body->setCollisionFlags(0);
+ body->activate();
+}
+
+void BulletObject::FixDiscontinuity() {
+ btTransform bt_transform;
+ GetDisplayTransform(&bt_transform);
+ transform = SafeGetOpenGLMatrix(bt_transform);
+ old_transform = transform;
+}
+
+void BulletObject::Dispose() {
+ if(body->getMotionState()){
+ delete body->getMotionState();
+ }
+ delete body;
+ body = NULL;
+ shape.reset();
+}
+
+void BulletObject::SetShape(SharedShapePtr _shape) {
+ shape = _shape;
+ body->setCollisionShape(shape.get());
+}
+
+BulletObject::BulletObject():
+ body(NULL),
+ com_offset(0.0f),
+ owner_object(NULL),
+ color(1.0f),
+ keep_history(false)
+{}
+
+BulletObject::~BulletObject() {
+ LOG_ASSERT(body == NULL);
+ // These should be NULL if Dispose() was called correctly
+}
+
+void BulletObject::CopyObjectTransform( const BulletObject* other ) {
+ //btTransform transform = other->body->getWorldTransform();
+ //transform.setOrigin(transform.getOrigin()*0.5f);
+ //body->setWorldTransform(transform);
+ ValidateTransform(other->body->getWorldTransform());
+ body->setWorldTransform(other->body->getWorldTransform());
+}
+
+
+void BulletObject::MixObjectTransform( const BulletObject* other,
+ float how_much )
+{
+ const btTransform &other_transform = other->body->getWorldTransform();
+ btTransform this_transform = body->getWorldTransform();
+ /*
+ btQuaternion q1 = this_transform.getRotation();
+ btQuaternion q2 = other_transform.getRotation();
+ */
+ btQuaternion result = slerp(this_transform.getRotation(),
+ other_transform.getRotation(),
+ how_much);
+ if(result[0] != result[0]){
+ /*
+ btQuaternion result = slerp(this_transform.getRotation(),
+ other_transform.getRotation(),
+ how_much);
+ */
+ return;
+ }
+ this_transform.setRotation(result);
+ this_transform.setOrigin(lerp(this_transform.getOrigin(),
+ other_transform.getOrigin(),
+ how_much));
+ ValidateTransform(this_transform);
+ body->setWorldTransform(this_transform);
+}
+
+void BulletObject::MixObjectVel( const BulletObject* other,
+ float how_much )
+{
+ const btVector3 &other_lin_vel = other->body->getLinearVelocity();
+ const btVector3 &other_ang_vel = other->body->getAngularVelocity();
+ const btVector3 &this_lin_vel = body->getLinearVelocity();
+ const btVector3 &this_ang_vel = body->getAngularVelocity();
+
+ body->setLinearVelocity(this_lin_vel * (1.0f - how_much) + other_lin_vel * how_much);
+ body->setAngularVelocity(this_ang_vel * (1.0f - how_much) + other_ang_vel * how_much);
+}
+
+
+void BulletObject::CopyObjectVel( const BulletObject* other ) {
+ body->setLinearVelocity(other->body->getLinearVelocity());
+ body->setAngularVelocity(other->body->getAngularVelocity());
+}
+
+
+void BulletObject::GetDisplayTransform(btTransform* display_transform) {
+ (*display_transform) = body->getWorldTransform();
+ btVector3 scale = shape->getLocalScaling();
+ scale[0] = fabs(scale[0]);
+ scale[1] = fabs(scale[1]);
+ scale[2] = fabs(scale[2]);
+ vec3 com_display_offset = GetRotation() * -com_offset;
+ display_transform->setOrigin(display_transform->getOrigin() +
+ btVector3(com_display_offset[0],
+ com_display_offset[1],
+ com_display_offset[2]));
+ /*btMatrix3x3 basis = display_transform->getBasis();
+ basis[0] *= scale[0];
+ basis[4] *= scale[1];
+ basis[8] *= scale[2];
+ display_transform->setBasis(basis);*/
+ display_transform->setBasis(display_transform->getBasis().scaled(scale));
+}
+
+vec3 BulletObject::GetAngularVelocity() const {
+ btVector3 vec = body->getAngularVelocity();
+ return vec3(vec[0],vec[1],vec[2]);
+}
+
+void BulletObject::ApplyTorque( const vec3 &torque ) {
+ btVector3 bt_torque(torque[0], torque[1], torque[2]);
+ body->applyTorque(bt_torque);
+}
+
+void BulletObject::UpdateTransform() {
+ btTransform bt_transform;
+ GetDisplayTransform(&bt_transform);
+ old_transform = transform;
+ transform = SafeGetOpenGLMatrix(bt_transform);
+ if(keep_history){
+ if(transform_history.empty()){
+ transform_history.resize(1);
+ }
+ const size_t _history_size = transform_history.size();
+ for(size_t i=0; i<_history_size-1; ++i){
+ transform_history[_history_size-1-i] = transform_history[_history_size-2-i];
+ }
+ transform_history[0] = transform;
+ }
+}
+
+void BulletObject::CheckForNAN() {
+ btTransform bt_transform;
+ GetDisplayTransform(&bt_transform);
+ mat4 test_mat = SafeGetOpenGLMatrix(body->getWorldTransform());
+ for(unsigned i=0; i<16; ++i){
+ if(test_mat.entries[i] != test_mat.entries[i]){
+ LOGE << "NAN found in BulletObject" << std::endl;
+ break;
+ }
+ }
+ btMatrix3x3 mat = bt_transform.getBasis().inverse();
+ if(mat.getColumn(0)[0] != mat.getColumn(0)[0]){
+ LOGE << "NAN found in BulletObject" << std::endl;
+ }
+ const btVector3& vel = body->getLinearVelocity();
+ for(unsigned i=0; i<3; ++i){
+ if(vel[i] != vel[i]){
+ LOGE << "NAN found in BulletObject" << std::endl;
+ break;
+ }
+ }
+ const btVector3& vel2 = body->getAngularVelocity();
+ for(unsigned i=0; i<3; ++i){
+ if(vel2[i] != vel2[i]){
+ LOGE << "NAN found in BulletObject" << std::endl;
+ break;
+ }
+ }
+}
+
+float BulletObject::GetMomentOfInertia( const vec3& axis ) {
+ btVector3 bt_axis(axis[0], axis[1], axis[2]);
+ //bt_axis = quatRotate(body->getWorldTransform().getRotation().inverse(),bt_axis);
+ return 1.0f/((body->getInvInertiaTensorWorld() * bt_axis * body->getAngularFactor()).dot(bt_axis));
+}
+
+bool BulletObject::IsActive() {
+ int activation_state = body->getActivationState();
+ return (activation_state==ACTIVE_TAG || activation_state==DISABLE_DEACTIVATION);
+}
+
+void BulletObject::Collided( const vec3& pos, float impulse, const CollideInfo &collide_info ) {
+ if(owner_object){
+ owner_object->Collided(pos, impulse, collide_info, this);
+ }
+}
+
+void BulletObject::SetMass( float mass ) {
+ btVector3 inv_inertia = body->getInvInertiaDiagLocal();
+ inv_inertia[0] = 1.0f / inv_inertia[0];
+ inv_inertia[1] = 1.0f / inv_inertia[1];
+ inv_inertia[2] = 1.0f / inv_inertia[2];
+ body->setMassProps(mass, inv_inertia);
+ const vec3& grav = Physics::Instance()->gravity;
+ body->setGravity(btVector3(grav[0],grav[1],grav[2]));
+}
+
+ShapeDisposalData::ShapeDisposalData():
+ index_vert_array(NULL),
+ triangle_info_map(NULL)
+{}
+
+ShapeDisposalData::~ShapeDisposalData() {
+ delete index_vert_array;
+ delete triangle_info_map;
+}
diff --git a/Source/Physics/bulletobject.h b/Source/Physics/bulletobject.h
new file mode 100644
index 00000000..d3d60e8f
--- /dev/null
+++ b/Source/Physics/bulletobject.h
@@ -0,0 +1,137 @@
+//-----------------------------------------------------------------------------
+// Name: bulletobject.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/quaternions.h>
+
+#include <opengl.h>
+
+#include <vector>
+#include <memory>
+
+class btRigidBody;
+class btCollisionShape;
+class btTransform;
+class Object;
+struct CollideInfo;
+
+enum CDLItype { _CDLI_CAPSULE };
+
+class btTriangleIndexVertexArray;
+struct btTriangleInfoMap;
+struct ShapeDisposalData {
+ std::vector<int> faces;
+ btTriangleIndexVertexArray *index_vert_array;
+ btTriangleInfoMap *triangle_info_map;
+ ShapeDisposalData();
+ ~ShapeDisposalData();
+};
+
+typedef std::shared_ptr<btCollisionShape> SharedShapePtr;
+
+class BulletObject {
+public:
+ mat4 transform;
+ mat4 old_transform;
+ std::vector<mat4> transform_history;
+ btRigidBody *body;
+ SharedShapePtr shape;
+ std::auto_ptr<ShapeDisposalData> shape_disposal_data;
+ bool visible;
+ vec3 com_offset;
+ short collision_group;
+ short collision_flags;
+ Object *owner_object;
+ vec3 color;
+ bool linked;
+ bool keep_history;
+
+ BulletObject();
+ ~BulletObject();
+
+ void Collided(const vec3& pos, float impulse, const CollideInfo &collide_info);
+ void SetTransform( const vec4 &position, const mat4 &rotation, const vec4 &scale );
+ void SetTransform( const mat4 &transform );
+ void SetPosition( const vec3 &position);
+ void ApplyTransform(const mat4& transform);
+ void SetGravity(bool _gravity);
+ void SetDamping( float damping );
+ void SetDamping( float lin_damping, float ang_damping );
+ void ApplyTorque(const vec3 &torque);
+ vec3 GetInterpPosition();
+ mat4 GetInterpRotation();
+ void SetRotation( const mat4 &rotation);
+ void SetRotation( const quaternion &rotation);
+ void ClearVelocities();
+ void SetAngularVelocity(const vec3& vel);
+ void SetLinearVelocity(const vec3& vel);
+ vec3 GetPosition() const;
+ vec3 GetLinearVelocity() const;
+ vec3 GetAngularVelocity() const;
+ mat4 GetRotation() const;
+ vec3 ObjectToWorld(const vec3 &point);
+ void ApplyForceAtWorldPoint(const vec3 &point, const vec3 &impulse);
+ vec3 WorldToObject(const vec3 &world_point);
+ void SetVisibility(const bool _visible);
+ bool IsVisible() const;
+ void Activate();
+ void Freeze();
+ void UnFreeze();
+ void FixDiscontinuity();
+ void Dispose();
+ float GetMomentOfInertia(const vec3& axis);
+ void CopyObjectTransform(const BulletObject* other);
+ void MixObjectTransform( const BulletObject* other, float how_much );
+ float GetMass();
+ void SetMass(float mass);
+ void GetDisplayTransform(btTransform* display_transform);
+ quaternion GetQuatRotation();
+ void GetQuatDeltaRotation() const;
+ void UpdateTransform();
+ void SetPositionAndVel( const vec3 &position, int frames = 1);
+ void SetRotationAndVel( const mat4 &rotation, int frames = 1);
+ void SetRotationAndVel( const quaternion &rotation, int frames = 1);
+ void AddAngularVelocity( const vec3& vel );
+ void AddLinearVelocity( const vec3& vel );
+ bool IsActive();
+ void MixObjectVel( const BulletObject* other, float how_much );
+ void CopyObjectVel( const BulletObject* other );
+ mat4 GetTransform() const;
+ vec3 GetInterpPositionX( int num, int progress );
+ mat4 GetInterpRotationX( int num, int progress );
+ void Sleep();
+ void SetMargin( float _margin );
+ vec3 GetVelocityAtLocalPoint(const vec3& point);
+ void NoSleep();
+ void CanSleep();
+ void CheckForNAN();
+ float GetMargin( );
+ void SetShape(SharedShapePtr _shape);
+ void SetColShape(btCollisionShape *_shape);
+ vec3 GetHistoryInterpPositionX( int num, int progress, float offset );
+ mat4 GetHistoryInterpRotationX( int num, int progress, float offset );
+ vec3 GetInterpWeightPosition(float weight);
+ mat4 GetInterpWeightRotation(float weight);
+};
+
+void ValidateTransform(const btTransform &bt_transform);
diff --git a/Source/Physics/bulletworld.cpp b/Source/Physics/bulletworld.cpp
new file mode 100644
index 00000000..796ded13
--- /dev/null
+++ b/Source/Physics/bulletworld.cpp
@@ -0,0 +1,2127 @@
+//-----------------------------------------------------------------------------
+// Name: bulletworld.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include <Physics/bulletworld.h>
+#include <Physics/physics.h>
+#include <Physics/bulletobject.h>
+#include <Physics/bulletcollision.h>
+
+#include <Graphics/camera.h>
+#include <Graphics/geometry.h>
+#include <Graphics/particles.h>
+#include <Graphics/textures.h>
+#include <Graphics/geometry.h>
+#include <Graphics/pxdebugdraw.h>
+#include <Graphics/model.h>
+#include <Graphics/models.h>
+
+#include <Internal/dialogues.h>
+#include <Internal/checksum.h>
+#include <Internal/filesystem.h>
+#include <Internal/profiler.h>
+#include <Internal/timer.h>
+
+#include <Sound/sound.h>
+#include <UserInput/input.h>
+#include <Math/vec3math.h>
+#include <GUI/gui.h>
+#include <Logging/logdata.h>
+#include <Utility/assert.h>
+
+#include <SDL.h>
+
+#include <btBulletDynamicsCommon.h>
+#include <LinearMath/btGeometryUtil.h>
+#include <BulletSoftBody/btSoftBodyHelpers.h>
+#include <BulletSoftBody/btSoftBodyRigidBodyCollisionConfiguration.h>
+#include <BulletCollision/CollisionDispatch/btInternalEdgeUtility.h>
+#include <BulletCollision/CollisionShapes/btShapeHull.h>
+#include <BulletCollision/CollisionShapes/btTriangleShape.h>
+#include <BulletCollision/Gimpact/btGImpactShape.h>
+#include <BulletCollision/Gimpact/btGImpactCollisionAlgorithm.h>
+#include <BulletCollision/NarrowPhaseCollision/btRaycastCallback.h>
+
+#include <set>
+
+extern bool g_simple_shadows;
+extern bool g_level_shadows;
+extern Timer game_timer;
+//#define ALLOW_SOFTBODY true
+
+inline btScalar calculateCombinedFriction(float friction0,float friction1) {
+ btScalar friction = friction0 * friction1;
+ const btScalar MAX_FRICTION = 10.f;
+ if (friction < -MAX_FRICTION)
+ friction = -MAX_FRICTION;
+ if (friction > MAX_FRICTION)
+ friction = MAX_FRICTION;
+ return friction;
+}
+
+inline btScalar calculateCombinedRestitution(float restitution0,float restitution1) {
+ return restitution0 * restitution1;
+}
+
+static bool CustomMaterialCombinerCallback(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0Wrap, int partId0, int index0, const btCollisionObjectWrapper* colObj1Wrap, int partId1, int index1) {
+ // Modify normal to use single-sided collision with triangle meshes
+ if(colObj1Wrap->getCollisionShape()->getShapeType() == TRIANGLE_SHAPE_PROXYTYPE){
+ const btTriangleShape* tri_shape = static_cast<const btTriangleShape*>(colObj1Wrap->getCollisionShape());
+ btVector3 tri_normal;
+ tri_shape->calcNormal(tri_normal);
+ cp.m_normalWorldOnB = colObj1Wrap->getWorldTransform().getBasis()*tri_normal;
+ }
+ // Uncomment to draw collision normals:
+ //const btVector3& start = cp.getPositionWorldOnB();
+ //const btVector3& end = start + cp.m_normalWorldOnB;
+ //DebugDraw::Instance()->AddLine(vec3(start[0],start[1],start[2]), vec3(end[0],end[1],end[2]), vec4(1.0f), _delete_on_update);
+ float friction0 = colObj0Wrap->getCollisionObject()->getFriction();
+ float friction1 = colObj1Wrap->getCollisionObject()->getFriction();
+ float restitution0 = colObj0Wrap->getCollisionObject()->getRestitution();
+ float restitution1 = colObj1Wrap->getCollisionObject()->getRestitution();
+ cp.m_combinedFriction = calculateCombinedFriction(friction0,friction1);
+ cp.m_combinedRestitution = calculateCombinedRestitution(restitution0,restitution1);
+ return true;
+}
+
+extern ContactAddedCallback gContactAddedCallback;
+void BulletWorld::Init() {
+ Dispose();
+ gContactAddedCallback = CustomMaterialCombinerCallback;
+ //collision_configuration_ = new btDefaultCollisionConfiguration(); // 4 mb ram
+#ifdef ALLOW_SOFTBODY
+ collision_configuration_ = new btSoftBodyRigidBodyCollisionConfiguration();
+#else
+ collision_configuration_ = new btDefaultCollisionConfiguration();
+#endif
+ collision_dispatcher_ = new btCollisionDispatcher(collision_configuration_);
+ btGImpactCollisionAlgorithm::registerAlgorithm(collision_dispatcher_);
+ broadphase_interface_ = new btDbvtBroadphase();
+ constraint_solver_ = new btSequentialImpulseConstraintSolver;
+#ifdef ALLOW_SOFTBODY
+ dynamics_world_ = new btSoftRigidDynamicsWorld(collision_dispatcher_,
+ broadphase_interface_,
+ constraint_solver_,
+ collision_configuration_);
+#else
+ dynamics_world_ = new btDiscreteDynamicsWorld(collision_dispatcher_,
+ broadphase_interface_,
+ constraint_solver_,
+ collision_configuration_);
+#endif
+ dynamics_world_->setForceUpdateAllAabbs(false);
+
+ soft_body_world_info_.m_dispatcher = collision_dispatcher_;
+ soft_body_world_info_.m_broadphase = broadphase_interface_;
+ soft_body_world_info_.m_sparsesdf.Initialize();
+}
+
+void BulletWorld::SetGravity(const vec3 &gravity) {
+ dynamics_world_->setGravity(btVector3(gravity[0], gravity[1], gravity[2]));
+ soft_body_world_info_.m_gravity.setValue(gravity[0], gravity[1], gravity[2]);
+}
+
+btSoftBody* BulletWorld::AddCloth(const vec3 &pos) {
+#ifdef ALLOW_SOFTBODY
+ //TRACEDEMO
+ btVector3 bt_pos(pos[0], pos[1], pos[2]);
+ const btScalar s=0.8;
+ btSoftBody* psb=btSoftBodyHelpers::CreatePatch( soft_body_world_info_, btVector3(-s,0,-s)+bt_pos,
+ btVector3(+s,0,-s)+bt_pos,
+ btVector3(-s,0,+s)+bt_pos,
+ btVector3(+s,0,+s)+bt_pos,
+ 31,31,
+ // 31,31,
+ 0,true);
+
+ psb->getCollisionShape()->setMargin(0.05);
+ btSoftBody::Material* pm=psb->appendMaterial();
+ pm->m_kLST = 0.4;
+ pm->m_flags -= btSoftBody::fMaterial::DebugDraw;
+ psb->generateBendingConstraints(2,pm);
+ psb->setTotalMass(15);
+ dynamics_world_->addSoftBody(psb);
+ return psb;
+#else
+ return NULL;
+#endif
+}
+
+void BulletWorld::Reset() {
+ if (dynamics_world_) {
+ btDispatcher* dispatcher = dynamics_world_->getDispatcher();
+ dynamics_world_->getBroadphase()->resetPool(dispatcher);
+ dynamics_world_->getConstraintSolver()->reset();
+ }
+}
+
+void BulletWorld::Dispose() {
+ if(dynamics_world_){
+ // Delete all constraints
+ int num_constraints = dynamics_world_->getNumConstraints();
+
+ if( num_constraints > 0 ) {
+ LOGE << "There are still " << num_constraints << " joints in the dynamics_world_, should be cleaned up from where they were created. "
+ << "It can't be done from here, because constraints have to be cleared before their objects." << std::endl;
+ }
+ }
+ delete dynamics_world_; dynamics_world_ = NULL;
+ delete constraint_solver_; constraint_solver_ = NULL;
+ delete broadphase_interface_; broadphase_interface_ = NULL;
+ delete collision_dispatcher_; collision_dispatcher_ = NULL;
+ delete collision_configuration_; collision_configuration_ = NULL;
+ for (BulletObjectList::iterator it = dynamic_objects_.begin(); it != dynamic_objects_.end(); ++it) {
+ BulletObject* object = (*it);
+ object->Dispose();
+ delete object;
+ }
+ dynamic_objects_.clear();
+ for (BulletObjectList::iterator it = static_objects_.begin(); it != static_objects_.end(); ++it) {
+ BulletObject* object = (*it);
+ object->Dispose();
+ delete object;
+ }
+ static_objects_.clear();
+ for(HullShapeCacheMap::iterator it = hull_shape_cache_.begin(); it != hull_shape_cache_.end(); ++it){
+ delete it->second;
+ }
+ hull_shape_cache_.clear();
+}
+
+void BulletWorld::Update(float timestep) {
+ if(dynamics_world_){
+ dynamics_world_->stepSimulation(1.0f,1,timestep);
+ HandleCollisionEffects();
+ UpdateBulletObjectTransforms();
+ RemoveTempConstraints();
+ }
+}
+
+std::map<void*, std::set<void*> > &BulletWorld::GetCollisions() {
+ PROFILER_ZONE(g_profiler_ctx, "BulletWorld::GetCollisions()");
+ collision_report_.clear();
+ if(false)
+ {
+ //PROFILER_ZONE(g_profiler_ctx, "performDiscreteCollisionDetection");
+ //dynamics_world_->performDiscreteCollisionDetection();
+ PROFILER_ENTER(g_profiler_ctx, "getDispatchInfo");
+ btDispatcherInfo& dispatchInfo = dynamics_world_->getDispatchInfo();
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ dynamics_world_->setForceUpdateAllAabbs(true);
+
+ PROFILER_ENTER(g_profiler_ctx, "updateAabbs");
+ dynamics_world_->updateAabbs();
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ PROFILER_ENTER(g_profiler_ctx, "computeOverlappingPairs");
+ dynamics_world_->computeOverlappingPairs();
+ PROFILER_LEAVE(g_profiler_ctx);
+
+
+ PROFILER_ENTER(g_profiler_ctx, "getDispatcher");
+ btDispatcher* dispatcher = dynamics_world_->getDispatcher();
+ PROFILER_LEAVE(g_profiler_ctx);
+ {
+ BT_PROFILE("dispatchAllCollisionPairs");
+ if (dispatcher){
+ PROFILER_ENTER(g_profiler_ctx, "dispatchAllCollisionPairs");
+ dispatcher->dispatchAllCollisionPairs(dynamics_world_->m_broadphasePairCache->getOverlappingPairCache(),dispatchInfo,dynamics_world_->m_dispatcher1);
+ PROFILER_LEAVE(g_profiler_ctx);
+ }
+ }
+ }
+
+ PROFILER_ENTER(g_profiler_ctx, "stepSimulation");
+ dynamics_world_->stepSimulation(1,1,(btScalar)0.000001);
+ PROFILER_LEAVE(g_profiler_ctx);
+ /*
+ btOverlappingPairCache* pairs = dynamics_world_->m_broadphasePairCache->getOverlappingPairCache();
+ btBroadphasePairArray& pair_array = pairs->getOverlappingPairArray();
+ for(int i=0, len=pair_array.size(); i<len; ++i){
+ btVector3 a = (pair_array[i].m_pProxy0->m_aabbMin + pair_array[i].m_pProxy0->m_aabbMax)*0.5;
+ btVector3 b = (pair_array[i].m_pProxy1->m_aabbMin + pair_array[i].m_pProxy1->m_aabbMax)*0.5;
+ DebugDraw::Instance()->AddLine(*((vec3*)&a),*((vec3*)&b), vec4(0.0f, 1.0f, 0.0f, 1.0f), vec4(0.0f, 1.0f, 0.0f, 1.0f), _delete_on_update);
+ btVector3 size = pair_array[i].m_pProxy0->m_aabbMax - pair_array[i].m_pProxy0->m_aabbMin;
+ DebugDraw::Instance()->AddWireBox(*((vec3*)&a),*((vec3*)&size), vec4(1.0f,0.0f,0.0f,0.3f), _delete_on_update);
+ size = pair_array[i].m_pProxy1->m_aabbMax - pair_array[i].m_pProxy1->m_aabbMin;
+ DebugDraw::Instance()->AddWireBox(*((vec3*)&b),*((vec3*)&size), vec4(1.0f,0.0f,0.0f,0.3f), _delete_on_update);
+ }*/
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Prepare report");
+ int numManifolds = collision_dispatcher_->getNumManifolds();
+ for(int i=0; i<numManifolds; i++) {
+ btPersistentManifold* contactManifold =
+ collision_dispatcher_->getManifoldByIndexInternal(i);
+ int numContacts = contactManifold->getNumContacts();
+ if(numContacts == 0){
+ continue;
+ }
+ const btCollisionObject* obA =
+ static_cast<const btCollisionObject*>(contactManifold->getBody0());
+ const btCollisionObject* obB =
+ static_cast<const btCollisionObject*>(contactManifold->getBody1());
+ void *ptrA = obA->getUserPointer();
+ void *ptrB = obB->getUserPointer();
+ if(!ptrA || !ptrB){
+ continue;
+ }
+ ptrA = ((BulletObject*)ptrA)->owner_object;
+ ptrB = ((BulletObject*)ptrB)->owner_object;
+ if(!ptrA || !ptrB){
+ continue;
+ }
+ if(ptrA < ptrB){
+ collision_report_[ptrA].insert(ptrB);
+ } else {
+ collision_report_[ptrB].insert(ptrA);
+ }
+ /*
+ for (int j=0;j<numContacts;j++)
+ {
+ btManifoldPoint& pt = contactManifold->getContactPoint(j);
+ const btVector3& ptA = pt.getPositionWorldOnA();
+ const btVector3& ptB = pt.getPositionWorldOnB();
+ btVector3 bt_col = (ptA + ptB)*0.5f;
+ vec3 collision_point(bt_col[0], bt_col[1], bt_col[2]);
+ DebugDraw::Instance()->AddWireSphere(collision_point,0.1f,vec4(1.0f,0.0f,0.0f,1.0f),_delete_on_update);
+ }*/
+ }
+ }
+ return collision_report_;
+}
+
+void BulletWorld::GetConvexHullCollisions( const std::string &path, const mat4 &transform, btCollisionWorld::ContactResultCallback &cb ) {
+ GetShapeCollisions(transform, GetHullShape(path), cb);
+}
+
+void BulletWorld::GetSphereCollisions( const vec3 &pos, float radius, btCollisionWorld::ContactResultCallback &cb ) {
+ btSphereShape shape(radius);
+ GetShapeCollisions(pos, shape, cb);
+}
+
+void BulletWorld::GetBoxCollisions( const vec3 &pos, const vec3 &dimensions, btCollisionWorld::ContactResultCallback &cb ) {
+ btBoxShape shape(btVector3(dimensions[0]*0.5f,
+ dimensions[1]*0.5f,
+ dimensions[2]*0.5f));
+ GetShapeCollisions(pos, shape, cb);
+}
+
+
+void BulletWorld::GetScaledSphereCollisions(const vec3 &pos,
+ float radius,
+ const vec3 &scale,
+ btCollisionWorld::ContactResultCallback &callback)
+{
+ btSphereShape shape(radius);
+ shape.setLocalScaling(btVector3(scale[0], scale[1], scale[2]));
+ GetShapeCollisions(pos, shape, callback);
+}
+
+
+void BulletWorld::GetCylinderCollisions(const vec3 &pos,
+ float radius,
+ float height,
+ ContactSlideCallback &callback)
+{
+ btCylinderShape shape(btVector3(radius,height*0.5f,radius));
+ GetShapeCollisions(pos, shape, callback);
+}
+
+void BulletWorld::GetShapeCollisions(const vec3 &pos,
+ btCollisionShape& shape,
+ btCollisionWorld::ContactResultCallback &callback)
+{
+ btTransform transform;
+ transform.setIdentity();
+ transform.setOrigin(btVector3(pos[0], pos[1], pos[2]));
+
+ btCollisionObject temp_object;
+ temp_object.setCollisionShape(&shape);
+ ValidateTransform(transform);
+ temp_object.setWorldTransform(transform);
+
+ dynamics_world_->contactTest(&temp_object, callback);
+}
+
+
+void BulletWorld::GetShapeCollisions(const mat4 &_transform,
+ btCollisionShape& shape,
+ btCollisionWorld::ContactResultCallback &callback)
+{
+ btTransform bt_transform;
+ bt_transform.setFromOpenGLMatrix((btScalar*)_transform.entries);
+
+ btCollisionObject temp_object;
+ temp_object.setCollisionShape(&shape);
+ ValidateTransform(bt_transform);
+ temp_object.setWorldTransform(bt_transform);
+
+ dynamics_world_->contactTest(&temp_object, callback);
+}
+
+struct DepthWithID {
+ float depth;
+ int id;
+};
+
+class DepthSorter {
+ public:
+ bool operator()(const DepthWithID &a, const DepthWithID &b) {
+ return a.depth > b.depth;
+ }
+};
+
+vec3 BulletWorld::ApplySphereSlide(const vec3 &pos,
+ float radius,
+ SlideCollisionInfo& info)
+{
+ std::vector<DepthWithID> dwi(info.contacts.size());
+ for(int i=0, len=info.contacts.size(); i<len; ++i){
+ const vec3 &plane_normal = info.contacts[i].normal;
+ const vec3 &plane_point = info.contacts[i].point;
+ const vec3 &obj_point = pos;
+ float d = dot(plane_normal, plane_point);
+ float d2 = dot(plane_normal, obj_point);
+ float depth = d-(d2-radius);
+ dwi[i].id = i;
+ dwi[i].depth = depth;
+ }
+ std::sort(dwi.begin(), dwi.end(), DepthSorter());
+ vec3 final_pos = pos;
+ for(int i=0, len=info.contacts.size(); i<len; ++i){
+ const vec3 &plane_normal = info.contacts[dwi[i].id].normal;
+ const vec3 &plane_point = info.contacts[dwi[i].id].point;
+ const vec3 &obj_point = final_pos;
+ float d = dot(plane_normal, plane_point);
+ float d2 = dot(plane_normal, obj_point);
+ float depth = d-(d2-radius);
+ if(depth > 0.00001f){
+ final_pos += plane_normal * depth;
+ }
+ }
+ return final_pos;
+}
+
+
+vec3 BulletWorld::ApplyScaledSphereSlide(const vec3 &pos,
+ float radius,
+ const vec3 &scale,
+ SlideCollisionInfo& info)
+{
+ std::vector<DepthWithID> dwi(info.contacts.size());
+ for(int i=0, len=info.contacts.size(); i<len; ++i){
+ const vec3 &plane_normal = info.contacts[i].normal;
+ const vec3 &plane_point = info.contacts[i].point;
+ const vec3 &obj_point = pos;
+ float d = dot(plane_normal, plane_point);
+ float d2 = dot(plane_normal, obj_point);
+ btScalar scaled_radius = radius *
+ sqrtf(square(scale[0] * plane_normal[0])+
+ square(scale[1] * plane_normal[1])+
+ square(scale[2] * plane_normal[2]));
+ btScalar depth = d-(d2-scaled_radius);
+ dwi[i].id = i;
+ dwi[i].depth = depth;
+ }
+ vec3 final_pos = pos;
+ std::sort(dwi.begin(), dwi.end(), DepthSorter());
+ for(int i=0, len=info.contacts.size(); i<len; ++i){
+ const vec3 &plane_normal = info.contacts[dwi[i].id].normal;
+ const vec3 &plane_point = info.contacts[dwi[i].id].point;
+ const vec3 &obj_point = final_pos;
+ float d = dot(plane_normal, plane_point);
+ float d2 = dot(plane_normal, obj_point);
+ btScalar scaled_radius = radius *
+ sqrtf(square(scale[0] * plane_normal[0])+
+ square(scale[1] * plane_normal[1])+
+ square(scale[2] * plane_normal[2]));
+ btScalar depth = d-(d2-scaled_radius);
+ if(depth > 0.00001f){
+ final_pos += plane_normal * depth;
+ }
+ }
+ return final_pos;
+}
+
+vec3 BulletWorld::CheckSphereCollisionSlide(const vec3 &pos,
+ float radius)
+{
+ ContactSlideCallback cb;
+
+ cb.m_collisionFilterMask = btBroadphaseProxy::StaticFilter;
+ GetSphereCollisions(pos, radius, cb);
+ return ApplySphereSlide(pos, radius, cb.collision_info);
+}
+
+void BulletWorld::SphereCollisionTriList(const vec3 &pos,
+ float radius,
+ TriListResults &tri_list)
+{
+ TriListCallback cb(tri_list);
+
+ cb.m_collisionFilterMask = btBroadphaseProxy::StaticFilter;
+ GetSphereCollisions(pos, radius, cb);
+}
+
+void BulletWorld::BoxCollisionTriList(const vec3 &pos,
+ const vec3 &dimensions,
+ TriListResults &tri_list)
+{
+ TriListCallback cb(tri_list);
+
+ cb.m_collisionFilterMask = btBroadphaseProxy::StaticFilter;
+ GetBoxCollisions(pos, dimensions, cb);
+}
+
+void BulletWorld::GetSweptSphereCollisions(const vec3 &start,
+ const vec3 &end,
+ float radius,
+ SweptSlideCallback &callback)
+{
+ btSphereShape sphere_shape(radius);
+ GetSweptShapeCollisions(start, end, sphere_shape, callback);
+}
+
+void BulletWorld::GetSweptCylinderCollisions(const vec3 &start,
+ const vec3 &end,
+ float radius,
+ float height,
+ SweptSlideCallback &callback){
+ btCylinderShape shape(btVector3(radius,height*0.5f,radius));
+ GetSweptShapeCollisions(start, end, shape, callback);
+}
+
+void BulletWorld::GetSweptShapeCollisions(const vec3 &start,
+ const vec3 &end,
+ const btConvexShape &shape,
+ SweptSlideCallback &callback){
+ btTransform start_transform;
+ start_transform.setIdentity();
+ start_transform.setOrigin(btVector3(start[0], start[1], start[2]));
+ btTransform end_transform;
+ end_transform.setIdentity();
+ end_transform.setOrigin(btVector3(end[0], end[1], end[2]));
+
+ callback.start_pos = btVector3(start[0], start[1], start[2]);
+ callback.end_pos = btVector3(end[0], end[1], end[2]);
+
+ //TODO: If distance between start and pos is really small, we should do a non-sweep collision check here.
+ //there doesn' seem to be a trivial replacement, so for now, let's try and just not do the collisions check.
+ //The reason why is that bullet will throw an assertion if we do.
+ if(length_squared(start - end) > std::numeric_limits<float>::epsilon()) {
+ dynamics_world_->convexSweepTest(&shape,
+ start_transform,
+ end_transform,
+ callback);
+ } else {
+ LOGW << "Tried to do an invalid sweep collision check with a zero distance between start and end" << std::endl;
+ }
+}
+
+void BulletWorld::GetSweptBoxCollisions(const vec3 &start,
+ const vec3 &end,
+ const vec3 &dimensions,
+ SweptSlideCallback &callback){
+ btBoxShape box_shape(btVector3(dimensions[0]*0.5f,
+ dimensions[1]*0.5f,
+ dimensions[2]*0.5f));
+ GetSweptShapeCollisions(start, end, box_shape, callback);
+}
+
+
+vec3 BulletWorld::CheckCapsuleCollisionSlide(const vec3 &start,
+ const vec3 &end,
+ float radius)
+{
+ SweptSlideCallback callback;
+ callback.m_collisionFilterMask = btBroadphaseProxy::StaticFilter;
+ GetSweptSphereCollisions(start,end,radius,callback);
+ return ApplySphereSlide(end, radius, callback.collision_info);
+}
+
+#include "Graphics/glstate.h"
+#include "Graphics/graphics.h"
+#include "Graphics/textures.h"
+#include "Graphics/shaders.h"
+#include "Graphics/camera.h"
+#include "Graphics/sky.h"
+
+void DrawBulletObject(BulletObject& object){
+ if(!object.IsVisible()){
+ return;
+ }
+ DebugDraw* debug_draw = DebugDraw::Instance();
+
+ Shaders* shaders = Shaders::Instance();
+ shaders->SetUniformVec4("color_tint", vec4(object.color, 1.0f));
+
+ const mat4& old_transform = object.old_transform;
+ const mat4& new_transform = object.transform;
+ mat4 interp = mix(old_transform, new_transform, game_timer.GetInterpWeight());
+ interp.AddTranslation(interp.GetRotatedvec3(object.com_offset));
+
+ // Add cross at center of mass
+ float com_length = 0.01f;
+ debug_draw->AddLine(interp * vec3(-com_length,0,0), interp * vec3(com_length,0,0), vec3(0,0,1), _delete_on_draw, _DD_XRAY);
+ debug_draw->AddLine(interp * vec3(0,-com_length,0), interp * vec3(0,com_length,0), vec3(0,0,1), _delete_on_draw, _DD_XRAY);
+ debug_draw->AddLine(interp * vec3(0,0,-com_length), interp * vec3(0,0,com_length), vec3(0,0,1), _delete_on_draw, _DD_XRAY);
+
+ shaders->SetUniformMat4("model_mat", interp);
+ shaders->SetUniformMat3("model_rotation_mat", interp.GetRotationPart());
+ CHECK_GL_ERROR();
+ CHECK_GL_ERROR();
+}
+
+void BulletWorld::Draw(const TextureRef& light_cube) {
+ Shaders* shaders = Shaders::Instance();
+ Graphics* graphics = Graphics::Instance();
+ Textures* textures = Textures::Instance();
+ Camera* cam = ActiveCameras::Get();
+
+ GLState gl_state;
+ gl_state.blend = false;
+ gl_state.cull_face = true;
+ gl_state.depth_test = true;
+ gl_state.depth_write = true;
+
+ graphics->setGLState(gl_state);
+
+ shaders->setProgram(shaders->returnProgram("cubemapdiffuse"));
+ textures->bindTexture(light_cube, 2);
+ shaders->SetUniformVec3("cam_pos",cam->GetPos());
+ shaders->SetUniformMat4("projection_view_mat",cam->GetProjMatrix() * cam->GetViewMatrix());
+ std::vector<mat4> shadow_matrix;
+ shadow_matrix.resize(4);
+ for(int i=0; i<4; ++i){
+ shadow_matrix[i] = cam->biasMatrix * graphics->cascade_shadow_mat[i];
+ }
+ if(g_simple_shadows || !g_level_shadows){
+ shadow_matrix[3] = cam->biasMatrix * graphics->simple_shadow_mat;
+ }
+ shaders->SetUniformMat4Array("shadow_matrix",shadow_matrix);
+
+ std::list<BulletObject*>::iterator iter;
+ for (iter = dynamic_objects_.begin();
+ iter != dynamic_objects_.end();
+ iter++)
+ {
+ BulletObject& object = *(*iter);
+ DrawBulletObject(object);
+ }
+ for (iter = static_objects_.begin();
+ iter != static_objects_.end();
+ iter++)
+ {
+ BulletObject& object = *(*iter);
+ DrawBulletObject(object);
+ }
+ CHECK_GL_ERROR();
+}
+
+btBvhTriangleMeshShape* BulletWorld::CreateMeshShape( const Model *the_mesh, ShapeDisposalData &data) {
+ PROFILER_ZONE(g_profiler_ctx, "CreateMeshShape");
+ const Model& mesh = (*the_mesh);
+
+ int index_stride = 3*sizeof(int);
+ int vertex_stride = 3*sizeof(GLfloat);
+
+ std::vector<int> &faces = data.faces;
+ faces.resize(mesh.faces.size());
+ for(unsigned i = 0; i<faces.size(); i++){
+ faces[i] = mesh.faces[i];
+ }
+
+ PROFILER_ENTER(g_profiler_ctx, "Creating btTriangleIndexVertexArray");
+ data.index_vert_array = new btTriangleIndexVertexArray(mesh.faces.size()/3,
+ &faces[0],
+ index_stride,
+ mesh.vertices.size()/3,
+ (btScalar*)&mesh.vertices[0],
+ vertex_stride);
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ PROFILER_ENTER(g_profiler_ctx, "Creating btBvhTriangleMeshShape");
+ btBvhTriangleMeshShape* shape = new btBvhTriangleMeshShape(data.index_vert_array,true);
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ PROFILER_ENTER(g_profiler_ctx, "btGenerateInternalEdgeInfo");
+ data.triangle_info_map = new btTriangleInfoMap();
+ btGenerateInternalEdgeInfo(shape,data.triangle_info_map);
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ return shape;
+}
+
+btGImpactMeshShape* BulletWorld::CreateDynamicMeshShape( std::vector<int> &indices, std::vector<float> &vertices, ShapeDisposalData &data) {
+ int index_stride = 3*sizeof(int);
+ int vertex_stride = 3*sizeof(GLfloat);
+
+ data.index_vert_array = new btTriangleIndexVertexArray(indices.size()/3,
+ &indices.front(),
+ index_stride,
+ vertices.size()/3,
+ (btScalar*)&vertices.front(),
+ vertex_stride);
+
+ btGImpactMeshShape* shape = new btGImpactMeshShape(data.index_vert_array);
+ shape->updateBound();
+ return shape;
+}
+
+
+struct CachedShape {
+ SharedShapePtr shape;
+ ShapeDisposalData disposal_data;
+};
+
+typedef std::map<int, CachedShape> CachedMeshShapes;
+static CachedMeshShapes cached_mesh_shapes;
+
+BulletObject* BulletWorld::CreateStaticMesh( const Model *the_mesh, int id, BWFlags flags) {
+ SharedShapePtr shape;
+ ShapeDisposalData *sdd = NULL;
+
+ CachedMeshShapes::iterator iter = cached_mesh_shapes.find(id);
+ if(id == -1){
+ sdd = new ShapeDisposalData();
+ shape.reset(CreateMeshShape(the_mesh, *sdd));
+ } else if(iter != cached_mesh_shapes.end()){
+ CachedShape &cached_shape = iter->second;
+ shape.reset(new btScaledBvhTriangleMeshShape((btBvhTriangleMeshShape*)cached_shape.shape.get(), btVector3(1.0f, 1.0f, 1.0f)));
+ } else {
+ CachedShape &cached_shape = cached_mesh_shapes[id];
+ cached_shape.shape.reset(CreateMeshShape(the_mesh, cached_shape.disposal_data));
+ shape.reset(new btScaledBvhTriangleMeshShape((btBvhTriangleMeshShape*)cached_shape.shape.get(), btVector3(1.0f, 1.0f, 1.0f)));
+ }
+
+ // Use this for now until hooking up btScaledBvhTriangleMeshShape
+ //sdd = new ShapeDisposalData();
+ //shape.reset(CreateMeshShape(the_mesh, *sdd));
+
+ BulletObject* obj = CreateRigidBody(shape, 0.0f, flags);
+ if(sdd){
+ obj->shape_disposal_data.reset(sdd);
+ }
+ obj->body->setCollisionFlags(obj->body->getCollisionFlags() | btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK);
+ return obj;
+}
+
+BulletObject* BulletWorld::CreateStaticHull( const std::string &path)
+{
+ SharedShapePtr shape(CreateConvexHullShape(path,NULL));
+ BulletObject* obj = CreateRigidBody(shape, 0.0f);
+ obj->body->setCollisionFlags(obj->body->getCollisionFlags() |
+ btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK);
+ return obj;
+}
+
+BulletWorld::BulletWorld():
+ dynamics_world_(NULL),
+ broadphase_interface_(NULL),
+ collision_dispatcher_(NULL),
+ constraint_solver_(NULL),
+ collision_configuration_(NULL)
+{
+}
+
+BulletWorld::~BulletWorld(){
+ Dispose();
+}
+
+BulletObject* BulletWorld::CreateRigidBody( SharedShapePtr shape,
+ float mass,
+ BWFlags flags,
+ vec3 *com)
+{
+ btTransform transform;
+ transform.setIdentity();
+ btVector3 local_inertia(0,0,0);
+ if(mass != 0.0f){
+ shape->calculateLocalInertia(mass,local_inertia);
+ }
+ btDefaultMotionState* motion_state = new btDefaultMotionState(transform);
+ btRigidBody::btRigidBodyConstructionInfo rbInfo(mass, motion_state, shape.get(), local_inertia);
+ btRigidBody* body = new btRigidBody(rbInfo);
+ body->setFriction(1.0f);
+ body->setRestitution(0.4f);
+ //body->setSleepingThresholds(0.8f,1.0f);
+ body->setSleepingThresholds(2.4f,3.0f);
+ std::list<BulletObject*> &object_list = mass!=0.0f ? dynamic_objects_ : static_objects_;
+ object_list.resize(object_list.size()+1);
+ object_list.back() = new BulletObject();
+ BulletObject* object = object_list.back();
+ object->body = body;
+ object->SetShape(shape);
+ object->visible = true;
+ short &collision_group = object->collision_group;
+ short &collision_flags = object->collision_flags;
+ if(mass!= 0.0f){
+ collision_flags = btBroadphaseProxy::AllFilter;
+ collision_group = btBroadphaseProxy::DefaultFilter;
+ } else {
+ collision_flags = btBroadphaseProxy::AllFilter ^ btBroadphaseProxy::StaticFilter;
+ collision_group = btBroadphaseProxy::StaticFilter;
+ }
+ if((flags & BW_NO_STATIC_COLLISIONS)!=0){
+ collision_flags = collision_flags ^ btBroadphaseProxy::StaticFilter;
+ }
+ if((flags & BW_NO_DYNAMIC_COLLISIONS)!=0){
+ collision_flags = collision_flags ^ btBroadphaseProxy::DefaultFilter;
+ }
+ if((flags & BW_DECALS_ONLY)!=0){
+ collision_flags = DecalsOnlyFilter;
+ }
+ if((flags & BW_ABSTRACT_ITEM)!=0){
+ collision_flags = btBroadphaseProxy::AllFilter ^ btBroadphaseProxy::StaticFilter;
+ collision_group = btBroadphaseProxy::StaticFilter;
+ }
+ dynamics_world_->addRigidBody(body, object->collision_group, object->collision_flags);
+ object->linked = true;
+ body->setUserPointer(object);
+ return object;
+}
+
+mat4 GetBoneMat(const vec3 &start, const vec3 &end) {
+/*
+ vec3 dir = normalize(end-start);
+ vec3 right;
+ vec3 up;
+ PlaneSpace(dir, right, up);
+ mat4 transform;
+ transform.SetColumn(0,right);
+ transform.SetColumn(1,up);
+ transform.SetColumn(2,dir);
+ transform.SetTranslationPart((start + end) * 0.5f);
+ return transform;
+ */
+
+ vec3 dir = normalize(end-start);
+ vec3 right;
+ vec3 up;
+ up = vec3(0,0,1);
+ if(fabs(dot(up,dir)) > 0.95f){
+ up = vec3(1,0,0);
+ right = normalize(cross(up, dir));
+ up = normalize(cross(dir,right));
+ } else {
+ right = normalize(cross(up, dir));
+ up = normalize(cross(dir,right));
+ }
+ //PlaneSpace(dir, right, up);
+ mat4 transform;
+ transform.SetColumn(0,right);
+ transform.SetColumn(1,up);
+ transform.SetColumn(2,dir);
+ transform.SetTranslationPart((start + end) * 0.5f);
+ return transform;
+}
+
+mat4 BulletWorld::GetCapsuleTransform(vec3 start, vec3 end) {
+ vec3 dir = normalize(end-start);
+ vec3 right;
+ vec3 up;
+ up = vec3(0,0,1);
+ if(fabs(dot(up,dir)) > 0.95f){
+ up = vec3(1,0,0);
+ right = normalize(cross(up, dir));
+ up = normalize(cross(dir,right));
+ } else {
+ right = normalize(cross(up, dir));
+ up = normalize(cross(dir,right));
+ }
+ //PlaneSpace(dir, right, up);
+ mat4 transform;
+ transform.SetColumn(0,right);
+ transform.SetColumn(1,up);
+ transform.SetColumn(2,dir);
+ transform.SetColumn(3, (start+end)*0.5f);
+ return transform;
+}
+
+BulletObject* BulletWorld::CreateCapsule(vec3 start,
+ vec3 end,
+ float radius,
+ float mass,
+ BWFlags flags,
+ vec3 *com)
+{
+ vec3 average = (start+end)*0.5f;
+ start -= average;
+ end -= average;
+
+ float height = distance(start,end);
+
+ btVector3 positions[2];
+ positions[0] = btVector3(0.0f,0.0f,height*0.5f);
+ positions[1] = btVector3(0.0f,0.0f,-height*0.5f);
+ //positions[0].setValue(start[0], start[1], start[2]);
+ //positions[1].setValue(end[0], end[1], end[2]);
+
+ btScalar radii[2];
+ radii[0] = radius;
+ radii[1] = radius;
+
+ SharedShapePtr shape(new btMultiSphereShape(positions, radii, 2));
+
+ vec3 v_positions[2];
+ v_positions[0] = vec3(0.0f,0.0f,height*0.5f);
+ v_positions[1] = vec3(0.0f,0.0f,-height*0.5f);
+ //float v_radii[2];
+ //v_radii[0] = radius;
+ //v_radii[1] = radius;
+
+ vec3 dir = normalize(end-start);
+ vec3 right;
+ vec3 up;
+ up = vec3(0,0,1);
+ if(fabs(dot(up,dir)) > 0.95f){
+ up = vec3(1,0,0);
+ right = normalize(cross(up, dir));
+ up = normalize(cross(dir,right));
+ } else {
+ right = normalize(cross(up, dir));
+ up = normalize(cross(dir,right));
+ }
+ //PlaneSpace(dir, right, up);
+ mat4 transform;
+ transform.SetColumn(0,right);
+ transform.SetColumn(1,up);
+ transform.SetColumn(2,dir);
+
+ vec3 transformed_offset;
+
+ BulletObject* object =
+ CreateRigidBody(shape, mass, flags, &transformed_offset);
+
+ object->SetTransform(average,transform,vec4(1.0f));
+
+ return object;
+}
+
+BulletObject* BulletWorld::CreateBox( const vec3 &position,
+ const vec3 &dimensions,
+ BWFlags flags)
+{
+ SharedShapePtr shape(new btBoxShape(btVector3(dimensions[0]*0.5f,
+ dimensions[1]*0.5f,
+ dimensions[2]*0.5f)));
+ float mass = 1.0f;
+ if(flags & BW_STATIC){
+ mass = 0.0f;
+ }
+
+ BulletObject* object = CreateRigidBody(shape, mass, flags);
+ object->SetPosition(position);
+
+ return object;
+}
+
+BulletObject* BulletWorld::CreateSphere( const vec3 &position,
+ float radius,
+ BWFlags flags)
+{
+ SharedShapePtr shape(new btSphereShape(radius));
+ float mass = 1.0f;
+ if(flags & BW_STATIC){
+ mass = 0.0f;
+ }
+
+ BulletObject* object = CreateRigidBody(shape, mass, flags);
+ object->SetPosition(position);
+
+ return object;
+}
+
+void BulletWorld::RemoveObject( BulletObject** bullet_object )
+{
+ if(!*bullet_object){
+ return;
+ }
+ UnlinkObject(*bullet_object);
+ (*bullet_object)->Dispose();
+ delete *bullet_object;
+ *bullet_object = NULL;
+}
+
+void BulletWorld::UnlinkObject( BulletObject* bullet_object )
+{
+ std::vector<std::pair<BulletObject*,btTypedConstraint*> >::iterator constraint_it;
+ for( constraint_it = temp_constraints_.begin(); constraint_it != temp_constraints_.end(); constraint_it++ ) {
+ if( constraint_it->first == bullet_object ) {
+ if( constraint_it->second ) {
+ dynamics_world_->removeConstraint(constraint_it->second);
+ delete constraint_it->second;
+ constraint_it->second = NULL;
+ }
+ }
+ }
+
+ for( constraint_it = fixed_constraints_.begin(); constraint_it != fixed_constraints_.end(); constraint_it++ ) {
+ if( constraint_it->first == bullet_object ) {
+ if( constraint_it->second ) {
+ dynamics_world_->removeConstraint(constraint_it->second);
+ delete constraint_it->second;
+ constraint_it->second = NULL;
+ }
+ }
+ }
+
+ std::list<BulletObject*>::iterator iter;
+ iter = std::find(dynamic_objects_.begin(), dynamic_objects_.end(), bullet_object);
+ if(iter != dynamic_objects_.end()){
+ dynamic_objects_.erase(iter);
+ }
+ iter = std::find(static_objects_.begin(), static_objects_.end(), bullet_object);
+ if(iter != static_objects_.end()){
+ static_objects_.erase(iter);
+ }
+ dynamics_world_->removeRigidBody(bullet_object->body);
+}
+
+void BulletWorld::UnlinkObjectPermanent( BulletObject* bullet_object )
+{
+ bullet_object->linked = false;
+ UnlinkObject(bullet_object);
+}
+
+void BulletWorld::LinkObject( BulletObject* bullet_object )
+{
+ dynamic_objects_.push_back(bullet_object);
+ dynamics_world_->addRigidBody(bullet_object->body,
+ bullet_object->collision_group,
+ bullet_object->collision_flags);
+ bullet_object->linked = true;
+}
+
+void BulletWorld::UnlinkConstraint( btTypedConstraint* constraint )
+{
+ dynamics_world_->removeConstraint(constraint);
+}
+
+void BulletWorld::LinkConstraint( btTypedConstraint* constraint )
+{
+ dynamics_world_->addConstraint(constraint, true);
+}
+
+void BulletWorld::RemoveStaticObject( BulletObject** _bullet_object )
+{
+ BulletObject* bullet_object = *_bullet_object;
+
+ std::vector<std::pair<BulletObject*,btTypedConstraint*> >::iterator constraint_it;
+ for( constraint_it = temp_constraints_.begin(); constraint_it != temp_constraints_.end(); constraint_it++ ) {
+ if( constraint_it->first == bullet_object ) {
+ if( constraint_it->second ) {
+ dynamics_world_->removeConstraint(constraint_it->second);
+ delete constraint_it->second;
+ constraint_it->second = NULL;
+ }
+ }
+ }
+
+ for( constraint_it = fixed_constraints_.begin(); constraint_it != fixed_constraints_.end(); constraint_it++ ) {
+ if( constraint_it->first == bullet_object ) {
+ if( constraint_it->second ) {
+ dynamics_world_->removeConstraint(constraint_it->second);
+ delete constraint_it->second;
+ constraint_it->second = NULL;
+ }
+ }
+ }
+
+ std::list<BulletObject*>::iterator iter;
+ iter = std::find(static_objects_.begin(), static_objects_.end(), bullet_object);
+ LOG_ASSERT(iter != static_objects_.end());
+ BulletObject* object = (*iter);
+ static_objects_.erase(iter);
+
+ dynamics_world_->removeRigidBody(object->body);
+ object->Dispose();
+ delete object;
+ *_bullet_object = NULL;
+}
+
+const btCollisionObject* BulletWorld::CheckRayCollision( const vec3 &start,
+ const vec3 &end,
+ vec3 *point,
+ vec3 *normal,
+ bool static_col) const
+{
+
+ btVector3 btstart(start[0], start[1], start[2]);
+ btVector3 btend(end[0], end[1], end[2]);
+ SimpleRayResultCallback callback;
+
+ if(static_col) {
+ callback.m_collisionFilterMask = btBroadphaseProxy::StaticFilter;
+ }
+
+ if(length_squared(start - end) > std::numeric_limits<float>::epsilon()) {
+ dynamics_world_->rayTest(btstart, btend, callback);
+ if(point) {
+ *point = end * callback.m_closestHitFraction +
+ start * (1.0f - callback.m_closestHitFraction);
+ }
+ if(normal) {
+ *normal = callback.m_hit_normal;
+ }
+ } else {
+ LOGW_ONCE("Tried to do a ray-cast with a zero-length ray, skipping test and returning null");
+ }
+
+ return callback.m_collisionObject;
+}
+
+int BulletWorld::CheckRayCollisionObj( const vec3 &start,
+ const vec3 &end,
+ const BulletObject& obj,
+ vec3 *point,
+ vec3 *normal)
+{
+ btVector3 btstart(start[0], start[1], start[2]);
+ btVector3 btend(end[0], end[1], end[2]);
+ SimpleRayTriResultCallback callback;
+ callback.m_flags = btTriangleRaycastCallback::kF_FilterBackfaces;
+
+ btTransform ray_from, ray_to;
+ ray_from.setOrigin(btstart);
+ ray_to.setOrigin(btend);
+
+ dynamics_world_->rayTestSingle(ray_from,
+ ray_to,
+ obj.body,
+ obj.shape.get(),
+ obj.body->getWorldTransform(),
+ callback);
+ if(point){
+ *point = end * callback.m_closestHitFraction +
+ start * (1.0f - callback.m_closestHitFraction);
+ }
+ if(normal){
+ *normal = callback.m_hit_normal;
+ }
+ if(callback.hasHit()){
+ return callback.m_tri;
+ } else {
+ return -1;
+ }
+}
+
+btTypedConstraint* BulletWorld::AddBallJoint( BulletObject* obj_a,
+ BulletObject* obj_b,
+ const vec3 &world_point )
+{
+ btVector3 bt_world_point(world_point[0], world_point[1], world_point[2]);
+ btTransform inverse_a = obj_a->body->getWorldTransform().inverse();
+ btTransform inverse_b = obj_b->body->getWorldTransform().inverse();
+ btVector3 a_local_point = inverse_a * bt_world_point;
+ btVector3 b_local_point = inverse_b * bt_world_point;
+
+ btPoint2PointConstraint *constraint =
+ new btPoint2PointConstraint(*(obj_a->body),
+ *(obj_b->body),
+ a_local_point,
+ b_local_point);
+
+ dynamics_world_->addConstraint(constraint, true);
+
+ return constraint;
+}
+
+void BulletWorld::UpdateFixedJoint( btTypedConstraint* _constraint, const mat4 &mat_a, const mat4 &mat_b ) {
+ btGeneric6DofConstraint* constraint = (btGeneric6DofConstraint*)_constraint;
+
+ btTransform a,b;
+ a.setFromOpenGLMatrix((btScalar*)mat_a.entries);
+ b.setFromOpenGLMatrix((btScalar*)mat_b.entries);
+
+ btVector3 anchor = (a.getOrigin() + b.getOrigin()) * 0.5f;
+
+ btTransform frameInW;
+ frameInW.setIdentity();
+ frameInW.setOrigin(anchor);
+
+ btTransform frameInA = a.inverse() * frameInW;
+ btTransform frameInB = b.inverse() * frameInW;
+ constraint->getFrameOffsetA().setBasis(frameInA.getBasis());
+ constraint->getFrameOffsetB().setBasis(frameInB.getBasis());
+}
+
+btTypedConstraint* BulletWorld::AddFixedJoint( BulletObject* obj_a, BulletObject* obj_b, const vec3 &_anchor ) {
+ btVector3 parentAxis(1.f, 0.f, 0.f);
+ btVector3 childAxis(0.f, 0.f, 1.f);
+ btVector3 anchor = btVector3(_anchor[0], _anchor[1], _anchor[2]);
+
+ btVector3 zAxis = parentAxis.normalize();
+ btVector3 yAxis = childAxis.normalize();
+ btVector3 xAxis = yAxis.cross(zAxis); // we want right coordinate system
+ btTransform frameInW;
+ frameInW.setIdentity();
+ frameInW.getBasis().setValue(xAxis[0], yAxis[0], zAxis[0],
+ xAxis[1], yAxis[1], zAxis[1],
+ xAxis[2], yAxis[2], zAxis[2]);
+ frameInW.setOrigin(anchor);
+
+ btRigidBody *pBodyA = obj_a->body;
+ btRigidBody *pBodyB = obj_b->body;
+ btTransform frameInA = pBodyA->getCenterOfMassTransform().inverse() * frameInW;
+ btTransform frameInB = pBodyB->getCenterOfMassTransform().inverse() * frameInW;
+
+ btGeneric6DofConstraint* constraint = new btGeneric6DofConstraint(*pBodyA, *pBodyB, frameInA, frameInB, true);
+
+ constraint->setLinearLowerLimit(btVector3(0., 0., 0.));
+ constraint->setLinearUpperLimit(btVector3(0., 0., 0.));
+ constraint->setAngularLowerLimit(btVector3(0.f, 0.0f, 0.0f));
+ constraint->setAngularUpperLimit(btVector3(0.f, 0.0f, 0.0f));
+ /*
+ for(int i=3; i<6; ++i){
+ constraint->setParam(BT_CONSTRAINT_STOP_ERP, 0.0f, i);
+ constraint->setParam(BT_CONSTRAINT_STOP_CFM, 1.0f, i);
+ constraint->setParam(BT_CONSTRAINT_CFM, 1.0f, i);
+ }
+*/
+ dynamics_world_->addConstraint(constraint, true);
+
+ return constraint;
+}
+
+btTypedConstraint* BulletWorld::AddNullConstraint( BulletObject* obj_a, BulletObject* obj_b )
+{
+ btVector3 bt_world_point = (obj_a->body->getWorldTransform().getOrigin() +
+ obj_b->body->getWorldTransform().getOrigin()) *
+ 0.5f;
+ btTransform inverse_a = obj_a->body->getWorldTransform().inverse();
+ btTransform inverse_b = obj_b->body->getWorldTransform().inverse();
+ btVector3 a_local_point = inverse_a * bt_world_point;
+ btVector3 b_local_point = inverse_b * bt_world_point;
+
+ btPoint2PointConstraint *constraint =
+ new btPoint2PointConstraint(*(obj_a->body),
+ *(obj_b->body),
+ a_local_point,
+ b_local_point);
+
+ constraint->m_setting.m_impulseClamp = 0.000001f;
+
+ dynamics_world_->addConstraint(constraint, true);
+
+ return constraint;
+}
+
+struct BinaryContactCallback : public btCollisionWorld::ContactResultCallback
+{
+ bool hit_something;
+
+ BinaryContactCallback():
+ ContactResultCallback(),
+ hit_something(false)
+ {}
+
+ btScalar addSingleResult(btManifoldPoint& cp,
+ const btCollisionObjectWrapper* colObjWrap0,
+ int partId0,
+ int index0,
+ const btCollisionObjectWrapper* colObjWrap1,
+ int partId1,
+ int index1)
+ {
+ if(cp.getDistance() < 0.0f){
+ hit_something = true;
+ return 0.0f;
+ } else {
+ return 0.0f;
+ }
+ }
+
+ bool HitSomething() {
+ return hit_something;
+ }
+};
+
+bool BulletWorld::CheckCollision( BulletObject* obj_a, BulletObject* obj_b ) {
+ BinaryContactCallback callback;
+ dynamics_world_->contactPairTest(obj_a->body, obj_b->body, callback);
+ return callback.HitSomething();
+}
+
+void BulletWorld::RemoveJoint( btTypedConstraint** bt_joint )
+{
+ dynamics_world_->removeConstraint(*bt_joint);
+ delete *bt_joint;
+ *bt_joint = NULL;
+}
+
+btTypedConstraint* BulletWorld::AddHingeJoint( BulletObject* obj_a, BulletObject* obj_b, const vec3 &anchor, const vec3 &axis, float low_limit, float high_limit, float* initial_angle_ptr )
+{
+ btVector3 bt_world_point(anchor[0], anchor[1], anchor[2]);
+ btVector3 bt_world_axis(axis[0], axis[1], axis[2]);
+ btTransform inverse_a = obj_a->body->getWorldTransform().inverse();
+ btTransform inverse_b = obj_b->body->getWorldTransform().inverse();
+ btVector3 a_local_point = inverse_a * bt_world_point;
+ btVector3 b_local_point = inverse_b * bt_world_point;
+ btVector3 a_local_axis = obj_a->body->getWorldTransform().inverse().getBasis() * bt_world_axis;
+ btVector3 b_local_axis = obj_b->body->getWorldTransform().inverse().getBasis() * bt_world_axis;
+
+ btHingeConstraint *constraint =
+ new btHingeConstraint(*(obj_a->body),
+ *(obj_b->body),
+ a_local_point,
+ b_local_point,
+ a_local_axis,
+ b_local_axis);
+
+ float initial_angle = constraint->getHingeAngle(obj_a->body->getWorldTransform(),obj_b->body->getWorldTransform());
+ constraint->setLimit(low_limit+initial_angle,high_limit+initial_angle);
+ if(initial_angle_ptr){
+ *initial_angle_ptr = initial_angle;
+ }
+
+ dynamics_world_->addConstraint(constraint, true);
+
+ //btHingeConstraint* hinge = (btHingeConstraint*)constraint;
+
+ //float bt_rot = hinge->getHingeAngle(hinge->getRigidBodyA().getWorldTransform(),
+ // hinge->getRigidBodyB().getWorldTransform());
+ //float bt_min = hinge->getLowerLimit();
+ //float bt_max = hinge->getUpperLimit();
+ //printf("Hinge: %f < %f < %f\n", bt_min, bt_rot, bt_max);
+
+ return constraint;
+}
+
+
+btTypedConstraint* BulletWorld::AddAngularStop(BulletObject* obj_a,
+ BulletObject* obj_b,
+ const vec3& _anchor,
+ const vec3& axis1,
+ const vec3& axis2,
+ const vec3& axis3,
+ float low_stop,
+ float high_stop)
+{
+ btVector3 parentAxis(axis1[0], axis1[1], axis1[2]);
+ btVector3 childAxis(axis2[0], axis2[1], axis2[2]);
+ btVector3 anchor(_anchor[0], _anchor[1], _anchor[2]);
+
+ // build frame basis
+ // 6DOF constraint uses Euler angles and to define limits
+ // it is assumed that rotational order is :
+ // Z - first, allowed limits are (-PI,PI);
+ // new position of Y - second (allowed limits are (-PI/2 + epsilon, PI/2 - epsilon), where epsilon is a small positive number
+ // used to prevent constraint from instability on poles;
+ // new position of X, allowed limits are (-PI,PI);
+ // So to simulate ODE Universal joint we should use parent axis as Z, child axis as Y and limit all other DOFs
+ // Build the frame in world coordinate system first
+ btVector3 xAxis = parentAxis.normalize();
+ btVector3 yAxis = childAxis.normalize();
+ btVector3 zAxis = xAxis.cross(yAxis); // we want right coordinate system
+ btTransform frameInW;
+ frameInW.setIdentity();
+ frameInW.getBasis().setValue(xAxis[0], yAxis[0], zAxis[0],
+ xAxis[1], yAxis[1], zAxis[1],
+ xAxis[2], yAxis[2], zAxis[2]);
+ frameInW.setOrigin(anchor);
+
+ btRigidBody *pBodyA = obj_a->body;
+ btRigidBody *pBodyB = obj_b->body;
+ btTransform frameInA = pBodyA->getCenterOfMassTransform().inverse() * frameInW;
+ btTransform frameInB = pBodyB->getCenterOfMassTransform().inverse() * frameInW;
+
+ btGeneric6DofConstraint* constraint = new btGeneric6DofConstraint(*pBodyA, *pBodyB, frameInA, frameInB, true);
+
+ constraint->setLinearLowerLimit(btVector3(0., 0., 0.));
+ constraint->setLinearUpperLimit(btVector3(0., 0., 0.));
+ constraint->setAngularLowerLimit(btVector3(low_stop, low_stop, low_stop));
+ constraint->setAngularUpperLimit(btVector3(high_stop, high_stop, high_stop));
+ dynamics_world_->addConstraint(constraint, true);
+
+ return constraint;
+}
+
+
+btTypedConstraint* BulletWorld::AddAngleConstraints( BulletObject* obj_a,
+ BulletObject* obj_b,
+ const vec3 &anchor,
+ float limit )
+{
+ btTransform display_transform;
+ obj_a->GetDisplayTransform(&display_transform);
+ btVector3 pos_a = display_transform.getOrigin();
+ btVector3 bt_vec = pos_a - btVector3(anchor[0],anchor[1],anchor[2]);
+
+ vec3 vec = normalize(vec3(bt_vec[0],bt_vec[1],bt_vec[2]));
+ vec3 right = normalize(cross(vec, vec3(0.0001f,1.0001f,0.000003f)));
+ vec3 up = normalize(cross(vec,right));
+ right = normalize(cross(vec,up));
+
+ return AddAngularStop(obj_a, obj_b,
+ anchor,
+ vec, right, up,
+ -limit,limit);
+}
+
+void BulletWorld::ApplyD6Torque( btTypedConstraint* constraint,
+ float axis_0_torque )
+{
+ btGeneric6DofConstraint *genericdof = (btGeneric6DofConstraint*)constraint;
+ btVector3 world_axis = genericdof->getAxis(0);
+ btRigidBody *obj_a = &genericdof->getRigidBodyA();
+ btRigidBody *obj_b = &genericdof->getRigidBodyB();
+ btMatrix3x3 inverse_a = obj_a->getWorldTransform().getBasis().inverse();
+ btMatrix3x3 inverse_b = obj_b->getWorldTransform().getBasis().inverse();
+ obj_a->applyTorque(inverse_a*(axis_0_torque*world_axis));
+ obj_b->applyTorque(inverse_b*(-axis_0_torque*world_axis));
+ obj_a->activate();
+ obj_b->activate();
+}
+
+BulletObject* BulletWorld::CreatePlane( const vec3& normal, float d )
+{
+ btVector3 bt_normal(normal[0],
+ normal[1],
+ normal[2]);
+ SharedShapePtr shape(new btStaticPlaneShape(bt_normal, d));
+ return CreateRigidBody(shape, 0.0f);
+}
+
+vec3 BulletWorld::GetD6Axis( btTypedConstraint* constraint, int which )
+{
+ btGeneric6DofConstraint *genericdof = (btGeneric6DofConstraint*)constraint;
+ btVector3 bt_axis = genericdof->getAxis(which);
+ return vec3(bt_axis[0], bt_axis[1], bt_axis[2]);
+}
+
+void BulletWorld::CreateD6TempTwist( btTypedConstraint* constraint, float how_much )
+{
+ btGeneric6DofConstraint *genericdof = (btGeneric6DofConstraint*)constraint;
+ btRigidBody *pBodyA = &genericdof->getRigidBodyA();
+ btRigidBody *pBodyB = &genericdof->getRigidBodyB();
+ btTransform frameInA = genericdof->getFrameOffsetA();
+ btTransform frameInB = genericdof->getFrameOffsetB();
+
+ btGeneric6DofConstraint* temp_constraint =
+ new btGeneric6DofConstraint(*pBodyA,
+ *pBodyB,
+ frameInA,
+ frameInB,
+ true);
+
+ temp_constraint->setLinearLowerLimit(btVector3(0., 0., 0.));
+ temp_constraint->setLinearUpperLimit(btVector3(0., 0., 0.));
+
+ btRotationalLimitMotor* limits[3];
+ for(int i=0; i<3; i++){
+ limits[i] = genericdof->getRotationalLimitMotor(i);
+ }
+
+ temp_constraint->setAngularLowerLimit(btVector3(genericdof->getAngle(0)+how_much,
+ limits[1]->m_loLimit,
+ limits[2]->m_loLimit));
+ temp_constraint->setAngularUpperLimit(btVector3(genericdof->getAngle(0)+how_much,
+ limits[1]->m_hiLimit,
+ limits[2]->m_hiLimit));
+
+ pBodyA->activate();
+ pBodyB->activate();
+
+ dynamics_world_->addConstraint(temp_constraint, false);
+ temp_constraints_.push_back(std::pair<BulletObject*, btTypedConstraint*>(NULL,temp_constraint));
+}
+
+vec3 BulletWorld::GetConstraintAnchor( btTypedConstraint* joint )
+{
+ if(joint->getConstraintType() == HINGE_CONSTRAINT_TYPE){
+ btHingeConstraint *hinge = (btHingeConstraint*) joint;
+ btRigidBody* obj_a = &hinge->getRigidBodyA();
+ btTransform a_world = obj_a->getWorldTransform();
+ btTransform a_frame = hinge->getAFrame();
+ btVector3 bt_anchor = (a_world*a_frame).getOrigin();
+ return vec3(bt_anchor[0], bt_anchor[1], bt_anchor[2]);
+ }
+ if(joint->getConstraintType() == D6_CONSTRAINT_TYPE){
+ btGeneric6DofConstraint *generic_dof = (btGeneric6DofConstraint*) joint;
+ btRigidBody* obj_a = &generic_dof->getRigidBodyA();
+ btTransform a_world = obj_a->getWorldTransform();
+ btTransform a_frame = generic_dof->getFrameOffsetA();
+ btVector3 bt_anchor = (a_world*a_frame).getOrigin();
+ return vec3(bt_anchor[0], bt_anchor[1], bt_anchor[2]);
+ }
+ LOG_ASSERT(NULL); //Should never get here
+ return vec3(0.0f);
+}
+
+void BulletWorld::SetD6Limits( btTypedConstraint* joint, vec3 low, vec3 high )
+{
+ LOG_ASSERT(joint->getConstraintType() == D6_CONSTRAINT_TYPE);
+ btGeneric6DofConstraint *generic_dof = (btGeneric6DofConstraint*) joint;
+ generic_dof->setAngularLowerLimit(btVector3(low[0],low[1],low[2]));
+ generic_dof->setAngularUpperLimit(btVector3(high[0],high[1],high[2]));
+ generic_dof->getRigidBodyA().activate();
+ generic_dof->getRigidBodyB().activate();
+}
+
+void BulletWorld::HandleContactEffect(const btCollisionObject* obj_a,
+ const btCollisionObject* obj_b,
+ const btManifoldPoint& pt)
+{
+ bool true_impact = false;
+ if (pt.getDistance()<0.f &&
+ pt.getLifeTime()<3 &&
+ pt.getAppliedImpulse() > 0.1f)
+ {
+ true_impact = true;
+ }
+ const btVector3& ptA = pt.getPositionWorldOnA();
+ const btVector3& ptB = pt.getPositionWorldOnB();
+
+ btVector3 bt_col = (ptA + ptB)*0.5f;
+ vec3 collision_point(bt_col[0], bt_col[1], bt_col[2]);
+
+ CollideInfo collide_info;
+ collide_info.normal = vec3(pt.m_normalWorldOnB[0],
+ pt.m_normalWorldOnB[1],
+ pt.m_normalWorldOnB[2]);
+ collide_info.true_impact = true_impact;
+
+ if(obj_a->getUserPointer()){
+ ((BulletObject*)(obj_a->getUserPointer()))->Collided(collision_point, pt.getAppliedImpulse(), collide_info);
+ }
+ if(obj_b->getUserPointer()){
+ ((BulletObject*)(obj_b->getUserPointer()))->Collided(collision_point, pt.getAppliedImpulse(), collide_info);
+ }
+}
+
+void BulletWorld::HandleCollisionEffects() {
+ int numManifolds = collision_dispatcher_->getNumManifolds();
+ for (int i=0;i<numManifolds;i++) {
+ btPersistentManifold* contactManifold =
+ collision_dispatcher_->getManifoldByIndexInternal(i);
+ const btCollisionObject* obA =
+ static_cast<const btCollisionObject*>(contactManifold->getBody0());
+ const btCollisionObject* obB =
+ static_cast<const btCollisionObject*>(contactManifold->getBody1());
+
+ bool active_collision = false;
+ if(obA->getUserPointer()){
+ BulletObject* obj_a = (BulletObject*)obA->getUserPointer();
+ if(obj_a->IsActive()){
+ active_collision = true;
+ }
+ }
+ if(obB->getUserPointer()){
+ BulletObject* obj_b = (BulletObject*)obB->getUserPointer();
+ if(obj_b->IsActive()){
+ active_collision = true;
+ }
+ }
+ if(active_collision){
+ int numContacts = contactManifold->getNumContacts();
+ for (int j=0;j<numContacts;j++) {
+ btManifoldPoint& pt = contactManifold->getContactPoint(j);
+ HandleContactEffect(obA,obB,pt);
+ }
+ }
+ }
+}
+
+void BulletWorld::UpdateBulletObjectTransforms()
+{
+ std::list<BulletObject*>::iterator iter = dynamic_objects_.begin();
+ for(;iter != dynamic_objects_.end(); ++iter){
+ BulletObject& object = **iter;
+ object.UpdateTransform();
+ }
+}
+
+void BulletWorld::RemoveTempConstraints()
+{
+ for(size_t i=0; i<temp_constraints_.size(); i++){
+ if(temp_constraints_[i].second){
+ dynamics_world_->removeConstraint(temp_constraints_[i].second);
+ delete temp_constraints_[i].second;
+ }
+ }
+ temp_constraints_.clear();
+}
+
+void CenterAtCOMTri(Model &mesh){
+ std::vector<int> vertex_faces(mesh.vertices.size()/3);
+ for(int i=0, len=mesh.faces.size(); i<len; i+=3){
+ ++vertex_faces[mesh.faces[i+0]];
+ ++vertex_faces[mesh.faces[i+1]];
+ ++vertex_faces[mesh.faces[i+2]];
+ }
+ vec3 avg_pos;
+ int num_avg = 0;
+ for(unsigned i=0; i<vertex_faces.size(); ++i){
+ if(vertex_faces[i] == 1){
+ avg_pos += vec3(mesh.vertices[vertex_faces[i]*3+0],
+ mesh.vertices[vertex_faces[i]*3+1],
+ mesh.vertices[vertex_faces[i]*3+2]);
+ ++num_avg;
+ }
+ }
+ if(num_avg != 3){
+ return;
+ }
+ avg_pos /= (float)num_avg;
+ for(int i=0, len=mesh.vertices.size()/3; i<len; ++i){
+ mesh.vertices[i*3+0] -= avg_pos[0];
+ mesh.vertices[i*3+1] -= avg_pos[1];
+ mesh.vertices[i*3+2] -= avg_pos[2];
+ }
+ int mesh_vert_num = mesh.vertices.size()/3;
+ for(int i=0; i<mesh_vert_num; ++i){
+ while(vertex_faces[i] == 1 && i<mesh_vert_num){
+ vertex_faces[i] = vertex_faces[mesh_vert_num-1];
+ mesh.vertices[i*3+0] = mesh.vertices[(mesh_vert_num-1)*3+0];
+ mesh.vertices[i*3+1] = mesh.vertices[(mesh_vert_num-1)*3+1];
+ mesh.vertices[i*3+2] = mesh.vertices[(mesh_vert_num-1)*3+2];
+ --mesh_vert_num;
+ }
+ }
+ mesh.vertices.resize(mesh_vert_num*3);
+ mesh.old_center += avg_pos;
+}
+
+btConvexHullShape* CreateConvexHullShape(const std::string &path, GLuint *display_list, vec3 *offset) {
+ std::string hull_path = path.substr(0, path.size()-4)+"hull.obj";
+ char abs_path[kPathSize];
+ if(FindFilePath(hull_path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths) != -1){
+ Model mesh;
+ mesh.LoadObj(hull_path.c_str(), _MDL_SIMPLE | _MDL_CENTER);
+ mesh.old_center = vec3(0.0f);
+ CenterAtCOMTri(mesh);
+ btConvexHullShape* convex_hull_shape = new btConvexHullShape();
+ for (int i=0, len=mesh.vertices.size()/3; i<len; ++i) {
+ convex_hull_shape->addPoint(btVector3(mesh.vertices[i*3+0],
+ mesh.vertices[i*3+1],
+ mesh.vertices[i*3+2]));
+ }
+ convex_hull_shape->setMargin(0.00f);
+ if(offset){
+ (*offset) = mesh.old_center;
+ }
+ return convex_hull_shape;
+ } else {
+ DisplayError("Warning",("No convex hull found for "+path).c_str());
+ }
+
+ Model mesh;
+ mesh.LoadObj(path.c_str());
+
+ int index_stride = 3*sizeof(int);
+ int vertex_stride = 3*sizeof(GLfloat);
+
+ std::vector<int> faces(mesh.faces.size());
+ for(unsigned i = 0; i<faces.size(); i++){
+ faces[i] = mesh.faces[i];
+ }
+ btTriangleIndexVertexArray* m_indexVertexArrays;
+ m_indexVertexArrays = new btTriangleIndexVertexArray(mesh.faces.size()/3,
+ &faces[0],
+ index_stride,
+ mesh.vertices.size()/3,
+ (btScalar*)&mesh.vertices[0],
+ vertex_stride);
+
+ btConvexTriangleMeshShape *tmpshape = new btConvexTriangleMeshShape(m_indexVertexArrays);
+
+ btShapeHull *hull = new btShapeHull(tmpshape);
+ hull->buildHull(0.0f);
+ if(display_list){
+ *display_list = CreateHullDisplayList(hull);
+ }
+
+ btAlignedObjectArray<btVector3> vertices;
+ for (int i=0;i<hull->numVertices();i++){
+ vertices.push_back(hull->getVertexPointer()[i]);
+ }
+
+ int sz;
+ /*btAlignedObjectArray<btVector3> planes;
+ btGeometryUtil::getPlaneEquationsFromVertices(vertices, planes);
+ sz = planes.size();
+ for (int i=0 ; i<sz ; ++i) {
+ planes[i][3] += CONVEX_DISTANCE_MARGIN;
+ }
+ vertices.clear();
+ btGeometryUtil::getVerticesFromPlaneEquations(planes, vertices);
+*/
+ sz = vertices.size();
+ btConvexHullShape* convex_hull_shape = new btConvexHullShape();
+ for (int i=0 ; i<sz ; ++i) {
+ convex_hull_shape->addPoint(vertices[i]);
+ }
+ convex_hull_shape->setMargin(0.00f);
+
+ delete tmpshape;
+ delete hull;
+
+ return convex_hull_shape;
+}
+
+void GetSimplifiedHull(const std::vector<vec3> &input, std::vector<vec3> &output, std::vector<int> &faces) {
+ btConvexHullShape* tmpshape = CreateConvexHullShape(input);
+ btShapeHull *hull = new btShapeHull(tmpshape);
+ hull->buildHull(0.0f);
+
+ const unsigned int *hull_faces = hull->getIndexPointer();
+ unsigned index = 0;
+ for(int i=0; i<hull->numTriangles(); i++){
+ faces.push_back(hull_faces[index]);
+ faces.push_back(hull_faces[index+1]);
+ faces.push_back(hull_faces[index+2]);
+ index += 3;
+ }
+
+ output.resize(hull->numVertices());
+ for(unsigned i=0; i<output.size(); ++i){
+ output[i] = ToVec3(hull->getVertexPointer()[i]);
+ }
+ delete tmpshape;
+ delete hull;
+}
+
+btConvexHullShape* CreateConvexHullShape(const std::vector<vec3> &verts) {
+ btConvexHullShape* convex_hull_shape = new btConvexHullShape();
+ for (unsigned i=0 ; i<verts.size() ; ++i) {
+ convex_hull_shape->addPoint(btVector3(verts[i][0],verts[i][1],verts[i][2]));
+ }
+ convex_hull_shape->setMargin(0.00f);
+ return convex_hull_shape;
+}
+
+BulletObject* BulletWorld::CreateConvexObject( const std::vector<vec3> &verts, const std::vector<int> &faces, bool is_static ) {
+ SharedShapePtr shape(CreateConvexHullShape(verts));
+
+ BulletObject* obj = CreateRigidBody(shape, is_static?0.0f:1.0f);
+
+ return obj;
+}
+
+BulletObject* BulletWorld::CreateConvexModel( vec3 pos, std::string path, vec3 *offset, BWFlags flags )
+{
+ GLuint display_list;
+ SharedShapePtr shape(CreateConvexHullShape(path, &display_list, offset));
+
+ BulletObject* obj = CreateRigidBody(shape, 1.0f, flags);
+
+ obj->SetPosition(pos);
+
+ return obj;
+}
+
+GLuint CreateHullDisplayList( btShapeHull * hull )
+{
+ GLuint display_list = glGenLists(1);
+ glNewList(display_list,GL_COMPILE);
+
+ const unsigned int *faces = hull->getIndexPointer();
+ const btVector3 *vertices = hull->getVertexPointer();
+
+ glBegin(GL_TRIANGLES);
+ unsigned index = 0;
+ for(int i=0; i<hull->numTriangles(); i++){
+ btVector3 normal =
+ (vertices[faces[index+1]]-vertices[faces[index+0]]).cross(
+ (vertices[faces[index+2]]-vertices[faces[index+0]])).normalize();
+ glNormal3f(normal[0], normal[1], normal[2]);
+ glVertex3f(vertices[faces[index+0]][0],
+ vertices[faces[index+0]][1],
+ vertices[faces[index+0]][2]);
+ glVertex3f(vertices[faces[index+1]][0],
+ vertices[faces[index+1]][1],
+ vertices[faces[index+1]][2]);
+ glVertex3f(vertices[faces[index+2]][0],
+ vertices[faces[index+2]][1],
+ vertices[faces[index+2]][2]);
+ index += 3;
+ }
+ glEnd();
+
+ glEndList();
+
+ return display_list;
+}
+
+vec3 BulletWorld::CheckCapsuleCollision( const vec3 &start, const vec3 &end, float radius )
+{
+ SweptSlideCallback cb;
+ cb.m_collisionFilterMask = btBroadphaseProxy::StaticFilter;
+ GetSweptSphereCollisions(start,
+ end,
+ radius,
+ cb);
+
+ if(cb.true_closest_hit_fraction == 1.0f){
+ return end;
+ }
+
+ return start + (end-start) * cb.true_closest_hit_fraction;
+}
+
+void BulletWorld::SetJointStrength( btTypedConstraint* joint, float strength )
+{
+ if(joint->getConstraintType() != D6_CONSTRAINT_TYPE){
+ return;
+ }
+ for(int i=3; i<6; ++i){
+ joint->setParam(BT_CONSTRAINT_STOP_ERP, 0.2f * strength, i);
+ joint->setParam(BT_CONSTRAINT_STOP_CFM, 1.0f - strength, i);
+ joint->setParam(BT_CONSTRAINT_CFM, 1.0f - strength, i);
+ }
+}
+
+void BulletWorld::CheckRayCollisionInfo( const vec3 &start,
+ const vec3 &end,
+ SimpleRayResultCallbackInfo &cb,
+ bool static_only)
+{
+ btVector3 btstart(start[0], start[1], start[2]);
+ btVector3 btend(end[0], end[1], end[2]);
+ if(static_only){
+ cb.m_collisionFilterMask = btBroadphaseProxy::StaticFilter;
+ }
+
+ dynamics_world_->rayTest(btstart, btend, cb);
+ for(int i=0; i<cb.contact_info.size(); ++i){
+ RayContactInfo &ci = cb.contact_info[i];
+ ci.point = end * ci.hit_fraction + start * (1.0f - ci.hit_fraction);
+ }
+}
+
+void BulletWorld::CheckRayTriCollisionInfo( const vec3 &start,
+ const vec3 &end,
+ SimpleRayTriResultCallback &cb,
+ bool static_only)
+{
+ btVector3 btstart(start[0], start[1], start[2]);
+ btVector3 btend(end[0], end[1], end[2]);
+ if(static_only){
+ cb.m_collisionFilterMask = btBroadphaseProxy::StaticFilter;
+ }
+ dynamics_world_->rayTest(btstart, btend, cb);
+ if(cb.hasHit()){
+ cb.m_hit_pos = end * cb.m_closestHitFraction + start * (1.0f - cb.m_closestHitFraction);
+ }
+}
+
+void BulletWorld::UpdateAABB() {
+ dynamics_world_->updateAabbs();
+}
+
+btConvexHullShape& BulletWorld::GetHullShape( const std::string & path )
+{
+ HullShapeCacheMap::iterator hsc_iter = hull_shape_cache_.find(path);
+ if(hsc_iter == hull_shape_cache_.end()){
+ Model mesh;
+ mesh.LoadObj(path.c_str(), _MDL_SIMPLE);
+ btConvexHullShape *shape = new btConvexHullShape();
+ for (unsigned i=0 ; i<mesh.vertices.size() ; i+=3) {
+ shape->addPoint(btVector3(mesh.vertices[i+0],mesh.vertices[i+1],mesh.vertices[i+2]));
+ }
+ shape->setMargin(0.00f);
+ hull_shape_cache_[path] = shape;
+ return *shape;
+ } else {
+ return *hsc_iter->second;
+ }
+}
+
+void BulletWorld::CreateCustomHullShape( const std::string& key, const std::vector<vec3>& points)
+{
+ HullShapeCacheMap::iterator hsc_iter = hull_shape_cache_.find(key);
+ if(hsc_iter != hull_shape_cache_.end()){
+ delete hsc_iter->second;
+ }
+ btConvexHullShape *shape = new btConvexHullShape();
+ for (int i=0, len=points.size(); i<len; ++i) {
+ shape->addPoint(btVector3(points[i][0], points[i][1], points[i][2]));
+ }
+ shape->setMargin(0.00f);
+ hull_shape_cache_[key] = shape;
+}
+
+void BulletWorld::MergeShape( BulletObject& a, const BulletObject& b ) {
+ btCollisionShape *my_shape = a.shape.get();
+ btCollisionShape *other_shape = b.shape.get();
+ if(my_shape->getShapeType() == MULTI_SPHERE_SHAPE_PROXYTYPE &&
+ other_shape->getShapeType() == MULTI_SPHERE_SHAPE_PROXYTYPE)
+ {
+ btMultiSphereShape* mss[2];
+ mss[0] = (btMultiSphereShape*)my_shape;
+ mss[1] = (btMultiSphereShape*)other_shape;
+ vec3 points[4];
+ {
+ const btVector3 &vec = mss[0]->getSpherePosition(0);
+ points[0] = vec3(vec[0], vec[1], vec[2]);
+ }
+ {
+ const btVector3 &vec = mss[0]->getSpherePosition(1);
+ points[1] = vec3(vec[0], vec[1], vec[2]);
+ }
+ {
+ const btVector3 &vec = mss[1]->getSpherePosition(0);
+ points[2] = vec3(vec[0], vec[1], vec[2]);
+ }
+ {
+ const btVector3 &vec = mss[1]->getSpherePosition(1);
+ points[3] = vec3(vec[0], vec[1], vec[2]);
+ }
+ points[0] = a.transform * points[0];
+ points[1] = a.transform * points[1];
+ points[2] = b.transform * points[2];
+ points[3] = b.transform * points[3];
+
+ float dist, farthest_dist = 0.0f;
+ std::pair<int, int> farthest_pair(-1,-1);
+ for(unsigned i=0; i<4-1; ++i){
+ for(unsigned j=i+1; j<4; ++j){
+ dist = distance_squared(points[i], points[j]);
+ if(farthest_pair.first == -1 || dist > farthest_dist){
+ farthest_dist = dist;
+ farthest_pair.first = i;
+ farthest_pair.second = j;
+ }
+ }
+ }
+
+ vec3 capsule_points[2];
+ capsule_points[0] = invert(a.transform) * points[farthest_pair.first];
+ capsule_points[1] = invert(a.transform) * points[farthest_pair.second];
+
+ //float capsule_radii[2];
+ //capsule_radii[0] = mss[0]->getSphereRadius(0)*3.0f;
+ //capsule_radii[1] = mss[0]->getSphereRadius(0)*3.0f;
+
+ btVector3 bt_capsule_points[2];
+ bt_capsule_points[0] = btVector3(capsule_points[0][0],
+ capsule_points[0][1],
+ capsule_points[0][2]);
+ bt_capsule_points[1] = btVector3(capsule_points[1][0],
+ capsule_points[1][1],
+ capsule_points[1][2]);
+
+ /*
+ btScalar bt_capsule_radii[2];
+ bt_capsule_radii[0] = mss[0]->getSphereRadius(0);
+ bt_capsule_radii[1] = mss[0]->getSphereRadius(0);
+ */
+
+ //SwapShape(new btMultiSphereShape(bt_capsule_points, bt_capsule_radii, 2), &a);
+
+ LOGE << "Two multispheres" << std::endl;
+ }
+}
+
+void BulletWorld::GetPairCollisions( btCollisionObject& a, btCollisionObject& b, btCollisionWorld::ContactResultCallback &cb ) {
+ dynamics_world_->contactPairTest(&a, &b, cb);
+}
+
+int BulletWorld::NumObjects() {
+ return dynamic_objects_.size() + static_objects_.size();
+}
+
+void BulletWorld::CreateTempDragConstraint( BulletObject* obj, const vec3 &from, const vec3 &to )
+{
+ btRigidBody *body = obj->body;
+ body->activate();
+ const btVector3 drag_point_world(from[0],from[1],from[2]);
+ const btVector3 drag_point_local =
+ body->getCenterOfMassTransform().inverse() * drag_point_world;
+
+ btPoint2PointConstraint* constraint =
+ new btPoint2PointConstraint(*body,drag_point_local);
+
+ constraint->setPivotB(btVector3(to[0],to[1],to[2]));
+
+ const float _drag_clamping = 3.0f;
+
+ constraint->m_setting.m_impulseClamp = _drag_clamping;
+ constraint->m_setting.m_tau = 0.001f;
+
+ //float strength = 0.9f;
+ //constraint->setParam(BT_CONSTRAINT_STOP_ERP, 0.2f * strength, -1);
+ //constraint->setParam(BT_CONSTRAINT_STOP_CFM, 1.0f - strength, -1);
+
+ dynamics_world_->addConstraint(constraint);
+
+ temp_constraints_.push_back(std::pair<BulletObject*, btTypedConstraint*>(obj, constraint));
+}
+
+void BulletWorld::CreateBoneConstraint( BulletObject* obj, const vec3 &from, const vec3 &to )
+{
+ btRigidBody *body = obj->body;
+ body->activate();
+ const btVector3 drag_point_world(from[0],from[1],from[2]);
+ const btVector3 drag_point_local =
+ body->getCenterOfMassTransform().inverse() * drag_point_world;
+
+ btPoint2PointConstraint* constraint =
+ new btPoint2PointConstraint(*body,drag_point_local);
+
+ constraint->setPivotB(btVector3(to[0],to[1],to[2]));
+
+ const float _drag_clamping = 0.5f;
+
+ constraint->m_setting.m_impulseClamp = _drag_clamping;
+ //constraint->m_setting.m_tau = 0.001f;
+
+ //float strength = 0.9f;
+ //constraint->setParam(BT_CONSTRAINT_STOP_ERP, 0.2f * strength, -1);
+ //constraint->setParam(BT_CONSTRAINT_STOP_CFM, 1.0f - strength, -1);
+
+ dynamics_world_->addConstraint(constraint);
+ fixed_constraints_.push_back(std::pair<BulletObject*, btTypedConstraint*>(obj,constraint));
+}
+
+void BulletWorld::CreateSpikeConstraint( BulletObject* obj, const vec3 &from, const vec3 &to, const vec3 &pos )
+{
+ vec3 axis = normalize(to - from);
+ vec3 temp_perp = vec3(31.1341f, 51513.6f, 6314123.1231f); // Arbitrary number
+ vec3 perp = normalize(cross(axis, temp_perp));
+
+ vec3 _anchor = pos;
+ btVector3 parentAxis(axis[0], axis[1], axis[2]);
+ btVector3 childAxis(perp[0], perp[1], perp[2]);
+ btVector3 anchor = btVector3(_anchor[0], _anchor[1], _anchor[2]);
+
+ btVector3 zAxis = parentAxis.normalize();
+ btVector3 yAxis = childAxis.normalize();
+ btVector3 xAxis = yAxis.cross(zAxis); // we want right coordinate system
+ btTransform frameInW;
+ frameInW.setIdentity();
+ frameInW.getBasis().setValue(xAxis[0], yAxis[0], zAxis[0],
+ xAxis[1], yAxis[1], zAxis[1],
+ xAxis[2], yAxis[2], zAxis[2]);
+ frameInW.setOrigin(anchor);
+
+ btRigidBody *pBodyB = obj->body;
+ btTransform frameInB = pBodyB->getCenterOfMassTransform().inverse() * frameInW;
+
+ btGeneric6DofConstraint* constraint = new btGeneric6DofConstraint(*pBodyB, frameInB, true);
+
+ constraint->setLinearLowerLimit(btVector3(0, 0., -distance(from, pos)));
+ constraint->setLinearUpperLimit(btVector3(0, 0., distance(to, pos)));
+ constraint->setAngularLowerLimit(btVector3(0.f, 0.0f, 1));
+ constraint->setAngularUpperLimit(btVector3(0.f, 0.0f, -1));
+
+ // Soften constraint a bit to stabilize
+ float strength = 0.999f;
+ constraint->setParam(BT_CONSTRAINT_STOP_ERP, 0.2f * strength, 0); //x
+ constraint->setParam(BT_CONSTRAINT_STOP_CFM, 1.0f - strength, 0);
+
+ constraint->setParam(BT_CONSTRAINT_STOP_ERP, 0.2f * strength, 1); //y
+ constraint->setParam(BT_CONSTRAINT_STOP_CFM, 1.0f - strength, 1);
+
+ constraint->setParam(BT_CONSTRAINT_STOP_ERP, 0.2f * strength, 2); //z
+ constraint->setParam(BT_CONSTRAINT_STOP_CFM, 1.0f - strength, 2);
+
+ constraint->setParam(BT_CONSTRAINT_STOP_ERP, 0.2f * strength, 3); //angular
+ constraint->setParam(BT_CONSTRAINT_STOP_CFM, 1.0f - strength, 3);
+
+ constraint->setParam(BT_CONSTRAINT_STOP_ERP, 0.2f * strength, 4); //angular
+ constraint->setParam(BT_CONSTRAINT_STOP_CFM, 1.0f - strength, 4);
+
+ constraint->setParam(BT_CONSTRAINT_STOP_ERP, 0.2f * strength, 5); //angular
+ constraint->setParam(BT_CONSTRAINT_STOP_CFM, 1.0f - strength, 5);
+
+ // Add some friction
+ constraint->getTranslationalLimitMotor()->m_enableMotor[2] = true;
+ constraint->getTranslationalLimitMotor()->m_targetVelocity[2] = 0.0f;
+ constraint->getTranslationalLimitMotor()->m_maxMotorForce[2] = 5.0f;
+
+ constraint->getRotationalLimitMotor(2)->m_enableMotor = true;
+ constraint->getRotationalLimitMotor(2)->m_targetVelocity = 0.0f;
+ constraint->getRotationalLimitMotor(2)->m_maxMotorForce = 5.0f;
+
+ dynamics_world_->addConstraint(constraint);
+ fixed_constraints_.push_back(std::pair<BulletObject*, btTypedConstraint*>(obj, constraint));
+}
+
+void BulletWorld::ClearBoneConstraints(){
+ for(size_t i=0; i<fixed_constraints_.size(); i++){
+ if( fixed_constraints_[i].second ) {
+ dynamics_world_->removeConstraint(fixed_constraints_[i].second);
+ delete fixed_constraints_[i].second;
+ }
+ }
+ fixed_constraints_.clear();
+}
+
+void BulletWorld::UpdateSingleAABB(BulletObject* bullet_object) {
+ dynamics_world_->updateSingleAabb(bullet_object->body);
+}
+
+void BulletWorld::FinalizeStaticEntries() {
+ // Create meta model of all models combined
+ vert_indices.clear();
+ vertices.clear();
+ for(int i=0, len=static_entries.size(); i<len; ++i){
+ Model& model = Models::Instance()->GetModel(static_entries[i].model_id);
+ mat4 &transform = static_entries[i].transform;
+ //int start_vert_indices = vert_indices.size();
+ int start_vertices = vertices.size()/3;
+ for(int j=0, len=model.vertices.size(); j<len; j+=3){
+ vec3 vec(model.vertices[j+0], model.vertices[j+1], model.vertices[j+2]);
+ vec = transform * vec;
+ vertices.push_back(vec[0]);
+ vertices.push_back(vec[1]);
+ vertices.push_back(vec[2]);
+ }
+ for(int j=0, len=model.faces.size(); j<len; ++j){
+ vert_indices.push_back(model.faces[j]+start_vertices);
+ }
+ }
+ // Create physics object
+ SharedShapePtr shape;
+ ShapeDisposalData *sdd = NULL;
+ sdd = new ShapeDisposalData();
+ btBvhTriangleMeshShape* new_shape;
+ {
+ int index_stride = 3*sizeof(int);
+ int vertex_stride = 3*sizeof(GLfloat);
+ std::vector<int> &faces = sdd->faces;
+ faces.resize(vert_indices.size());
+ for(unsigned i = 0; i<faces.size(); i++){
+ faces[i] = vert_indices[i];
+ }
+ sdd->index_vert_array = new btTriangleIndexVertexArray(vert_indices.size()/3,
+ &vert_indices[0],
+ index_stride,
+ vertices.size()/3,
+ (btScalar*)&vertices[0],
+ vertex_stride);
+ new_shape = new btBvhTriangleMeshShape(sdd->index_vert_array,true);
+ sdd->triangle_info_map = new btTriangleInfoMap();
+ btGenerateInternalEdgeInfo(new_shape, sdd->triangle_info_map);
+ }
+ shape.reset(new_shape);
+ BulletObject* obj = CreateRigidBody(shape, 0.0f, BW_NO_FLAGS);
+ if(sdd){
+ obj->shape_disposal_data.reset(sdd);
+ }
+ obj->body->setCollisionFlags(obj->body->getCollisionFlags() | btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK);
+ merged_obj = obj;
+}
diff --git a/Source/Physics/bulletworld.h b/Source/Physics/bulletworld.h
new file mode 100644
index 00000000..7c62bafa
--- /dev/null
+++ b/Source/Physics/bulletworld.h
@@ -0,0 +1,222 @@
+//-----------------------------------------------------------------------------
+// Name: bulletworld.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+#include <Math/mat4.h>
+
+#include <Physics/bulletcollision.h>
+#include <Physics/bulletobject.h>
+
+#include <Asset/Asset/texture.h>
+
+#include <BulletSoftBody/btSoftBody.h>
+#include <BulletSoftBody/btSoftRigidDynamicsWorld.h>
+#include <LinearMath/btAlignedObjectArray.h>
+#include <opengl.h>
+
+#include <memory>
+#include <list>
+#include <vector>
+#include <set>
+
+class btBroadphaseInterface;
+class btCollisionDispatcher;
+class btConstraintSolver;
+class btDefaultCollisionConfiguration;
+class btDiscreteDynamicsWorld;
+class btCollisionShape;
+class BulletObject;
+class Model;
+class ContactSlideCallback;
+class ContactInfoCallback;
+class SweptSlideCallback;
+struct SlideCollisionInfo;
+struct SimpleRayResultCallbackInfo;
+class btPoint2PointConstraint;
+class btTypedConstraint;
+class btCollisionObject;
+class btManifoldPoint;
+class btShapeHull;
+class btBvhTriangleMeshShape;
+class btCompoundShape;
+class btConvexHullShape;
+class btConvexShape;
+class btRigidBody;
+class btGImpactMeshShape;
+
+typedef unsigned char BWFlags;
+
+const int DecalsOnlyFilter = (1<<6);
+
+struct CollideInfo {
+ vec3 normal;
+ bool true_impact;
+};
+
+enum BWFlagValues {
+ BW_NO_FLAGS = 0,
+ BW_NO_STATIC_COLLISIONS = (1<<0),
+ BW_NO_DYNAMIC_COLLISIONS = (1<<1),
+ BW_STATIC = (1<<3),
+ BW_DECALS_ONLY = (1<<4),
+ BW_ABSTRACT_ITEM = (1<<5)
+};
+
+class BulletWorld {
+public:
+ BulletWorld();
+ ~BulletWorld();
+
+ void Init();
+ void Dispose();
+
+ void Update( float timestep);
+ void Draw(const TextureRef& light_cube);
+
+ // Collision checks
+ vec3 CheckCapsuleCollision(const vec3 &start, const vec3 &end, float radius);
+ vec3 CheckCapsuleCollisionSlide(const vec3 &start, const vec3 &end, float radius);
+ const btCollisionObject* CheckRayCollision( const vec3 &start, const vec3 &end, vec3 *point = NULL, vec3 *normal = NULL, bool static_col = true) const;
+ void CheckRayCollisionInfo( const vec3 &start, const vec3 &end, SimpleRayResultCallbackInfo &cb, bool static_only = true);
+ void CheckRayTriCollisionInfo( const vec3 &start, const vec3 &end, SimpleRayTriResultCallback &cb, bool static_only = true);
+ vec3 CheckSphereCollisionSlide(const vec3 &start, float radius);
+ void GetSphereCollisions(const vec3 &pos, float radius, btCollisionWorld::ContactResultCallback &cb);
+ void GetSweptSphereCollisions(const vec3 &start, const vec3 &end, float radius, SweptSlideCallback &callback);
+ void GetSweptBoxCollisions(const vec3 &start, const vec3 &end, const vec3 &dimensions, SweptSlideCallback &callback);
+ void GetSweptShapeCollisions(const vec3 &start, const vec3 &end, const btConvexShape &shape, SweptSlideCallback &callback);
+ void GetSweptCylinderCollisions(const vec3 &start, const vec3 &end, float radius, float height, SweptSlideCallback &callback);
+ void GetShapeCollisions(const vec3 &pos, btCollisionShape& shape, btCollisionWorld::ContactResultCallback &callback);
+ void GetShapeCollisions(const mat4 &_transform, btCollisionShape& shape, btCollisionWorld::ContactResultCallback &callback);
+ void GetCylinderCollisions(const vec3 &pos, float radius, float height, ContactSlideCallback &callback);
+ bool CheckCollision( BulletObject* obj_a, BulletObject* obj_b );
+ int CheckRayCollisionObj( const vec3 &start, const vec3 &end, const BulletObject& obj, vec3 *point, vec3 *normal);
+ void SphereCollisionTriList(const vec3 &pos, float radius, TriListResults &tri_list);
+ void BoxCollisionTriList(const vec3 &pos, const vec3 &dimensions, TriListResults &tri_list);
+ void GetBoxCollisions( const vec3 &pos, const vec3 &dimensions, btCollisionWorld::ContactResultCallback &cb );
+ void GetPairCollisions( btCollisionObject& a, btCollisionObject& b, btCollisionWorld::ContactResultCallback &cb );
+ void GetScaledSphereCollisions(const vec3 &pos, float radius, const vec3 &scale, btCollisionWorld::ContactResultCallback &callback);
+ std::map<void*, std::set<void*> > &GetCollisions();
+ void GetConvexHullCollisions( const std::string &path, const mat4 &transform, btCollisionWorld::ContactResultCallback &cb );
+ // Collision response
+ vec3 ApplySphereSlide(const vec3 &pos, float radius, SlideCollisionInfo& info);
+ vec3 ApplyScaledSphereSlide(const vec3 &pos, float radius, const vec3 &scale, SlideCollisionInfo& info);
+ // Creating objects
+ BulletObject* CreateBox(const vec3 &position, const vec3 &dimensions, BWFlags flags = 0);
+ BulletObject* CreateRigidBody( SharedShapePtr shape, float mass, BWFlags flags = 0, vec3 *com = NULL);
+ BulletObject* CreateCapsule(vec3 start, vec3 end, float radius, float mass = 1.0f, BWFlags flags = 0, vec3 *com = NULL);
+ BulletObject* CreatePlane( const vec3& normal, float d );
+ BulletObject* CreateConvexModel( vec3 pos, std::string path, vec3 *offset = NULL, BWFlags flags = 0 );
+ btBvhTriangleMeshShape* CreateMeshShape( const Model *the_mesh, ShapeDisposalData &data);
+ BulletObject* CreateStaticHull( const std::string &path);
+ BulletObject* CreateConvexObject( const std::vector<vec3> &verts, const std::vector<int> &faces, bool is_static = false);
+ BulletObject* CreateSphere( const vec3 &position, float radius, BWFlags flags);
+ BulletObject* CreateStaticMesh( const Model *the_mesh, int id, BWFlags flags);
+ // Creating constraints
+ btTypedConstraint* AddBallJoint( BulletObject* obj_a, BulletObject* obj_b, const vec3 &world_point );
+ btTypedConstraint* AddFixedJoint( BulletObject* obj_a, BulletObject* obj_b, const vec3 &anchor );
+ btTypedConstraint* AddNullConstraint( BulletObject* obj_a, BulletObject* obj_b );
+ btTypedConstraint* AddHingeJoint( BulletObject* obj_a, BulletObject* obj_b, const vec3 &anchor, const vec3 &axis, float low_limit, float high_limit, float *initial_angle_ptr );
+ btTypedConstraint* AddAngleConstraints( BulletObject* obj_a, BulletObject* obj_b, const vec3 &anchor, float limit );
+ btTypedConstraint* AddAngularStop(BulletObject* obj_a, BulletObject* obj_b, const vec3& _anchor, const vec3& axis1, const vec3& axis2, const vec3& axis3, float low_stop, float high_stop);
+ void CreateTempDragConstraint( BulletObject* obj, const vec3 &from, const vec3 &to );
+ void CreateBoneConstraint( BulletObject* obj, const vec3 &from, const vec3 &to );
+ void CreateSpikeConstraint( BulletObject* obj, const vec3 &from, const vec3 &to, const vec3 &pos );
+ void ClearBoneConstraints();
+ // Manipulating constraints
+ void RemoveJoint( btTypedConstraint** bt_joint );
+ void ApplyD6Torque( btTypedConstraint* constraint, float axis_0_torque );
+ vec3 GetD6Axis( btTypedConstraint* constraint, int which );
+ void CreateD6TempTwist( btTypedConstraint* constraint, float how_much );
+ vec3 GetConstraintAnchor( btTypedConstraint* joint );
+ void SetD6Limits( btTypedConstraint* joint, vec3 low, vec3 high );
+ void RemoveTempConstraints();
+ static void UpdateFixedJoint( btTypedConstraint* _constraint, const mat4 &mat_a, const mat4 &mat_b );
+ void SetJointStrength( btTypedConstraint* joint, float strength );
+ // Update functions
+ void HandleCollisionEffects();
+ void UpdateBulletObjectTransforms();
+ void HandleContactEffect(const btCollisionObject* obj_a, const btCollisionObject* obj_b, const btManifoldPoint& pt);
+ void UpdateAABB();
+ // Linking and unlinking
+ void UnlinkObject( BulletObject* bullet_object );
+ void LinkObject( BulletObject* bullet_object );
+ void LinkConstraint( btTypedConstraint* constraint );
+ void UnlinkConstraint( btTypedConstraint* constraint );
+ void UnlinkObjectPermanent( BulletObject* bullet_object );
+ void RemoveObject( BulletObject** _bullet_object );
+ void RemoveStaticObject( BulletObject** _bullet_object );
+ // Misc
+ void SetGravity(const vec3 &gravity);
+ void MergeShape( BulletObject& a, const BulletObject& b );
+ void UpdateSingleAABB( BulletObject* bullet_object );
+ int NumObjects();
+ void FinalizeStaticEntries();
+ static mat4 GetCapsuleTransform(vec3 start, vec3 end);
+ static btGImpactMeshShape* CreateDynamicMeshShape( std::vector<int> &indices, std::vector<float> &vertices, ShapeDisposalData &data);
+ btSoftBody* AddCloth(const vec3 &pos);
+ void CreateCustomHullShape( const std::string& key, const std::vector<vec3>& points);
+public:
+ btSoftBodyWorldInfo soft_body_world_info_;
+#ifdef ALLOW_SOFTBODY
+ btSoftRigidDynamicsWorld* dynamics_world_;
+#else
+ btDiscreteDynamicsWorld* dynamics_world_;
+#endif
+ btBroadphaseInterface* broadphase_interface_;
+ btCollisionDispatcher* collision_dispatcher_;
+ btConstraintSolver* constraint_solver_;
+ btDefaultCollisionConfiguration* collision_configuration_;
+
+ typedef std::list<BulletObject*> BulletObjectList;
+ BulletObjectList dynamic_objects_;
+ BulletObjectList static_objects_;
+
+ std::vector<std::pair<BulletObject*,btTypedConstraint*> >temp_constraints_;
+ std::vector<std::pair<BulletObject*,btTypedConstraint*> >fixed_constraints_;
+
+ struct StaticEntry {
+ int model_id;
+ mat4 transform;
+ void *owner_object;
+ };
+
+ std::vector<StaticEntry> static_entries;
+ std::vector<int> vert_indices;
+ std::vector<float> vertices;
+ BulletObject* merged_obj;
+
+ std::map<void*, std::set<void*> > collision_report_;
+ typedef std::map<std::string, btConvexHullShape*> HullShapeCacheMap;
+ HullShapeCacheMap hull_shape_cache_;
+
+ btConvexHullShape& GetHullShape( const std::string & path );
+ void Reset();
+};
+
+GLuint CreateHullDisplayList( btShapeHull * hull );
+btConvexHullShape* CreateConvexHullShape(const std::string &path, GLuint *display_list, vec3 *offset = NULL);
+btConvexHullShape* CreateConvexHullShape(const std::vector<vec3> &verts);
+mat4 GetBoneMat(const vec3 &start, const vec3 &end);
+
+void GetSimplifiedHull(const std::vector<vec3> &input, std::vector<vec3> &output, std::vector<int> &faces);
diff --git a/Source/Physics/physics.cpp b/Source/Physics/physics.cpp
new file mode 100644
index 00000000..88bd9f99
--- /dev/null
+++ b/Source/Physics/physics.cpp
@@ -0,0 +1,47 @@
+//-----------------------------------------------------------------------------
+// Name: physics.cpp
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: This class handles wind and gravity
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "physics.h"
+
+#include <cmath>
+
+//-----------------------------------------------------------------------------
+//Functions
+//-----------------------------------------------------------------------------
+
+//Draw a particle
+void Physics::Update()
+{
+}
+
+vec3 Physics::GetWind(vec3 check_where, float curr_game_time, float change_rate) {
+ vec3 wind_vel;
+ check_where[0] += curr_game_time*0.7f*change_rate;
+ check_where[1] += curr_game_time*0.3f*change_rate;
+ check_where[2] += curr_game_time*0.5f*change_rate;
+ wind_vel[0] = sinf(check_where[0])+cosf(check_where[1]*1.3f)+sinf(check_where[2]*3.0f);
+ wind_vel[1] = sinf(check_where[0]*1.2f)+cosf(check_where[1]*1.8f)+sinf(check_where[2]*0.8f);
+ wind_vel[2] = sinf(check_where[0]*1.6f)+cosf(check_where[1]*0.5f)+sinf(check_where[2]*1.2f);
+
+ return wind_vel;
+}
diff --git a/Source/Physics/physics.h b/Source/Physics/physics.h
new file mode 100644
index 00000000..2bbe5e73
--- /dev/null
+++ b/Source/Physics/physics.h
@@ -0,0 +1,48 @@
+//-----------------------------------------------------------------------------
+// Name: physics.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+
+const int _collision_floor = -999;
+
+class Physics
+{
+ public:
+ vec3 gravity;
+ float wind;
+
+ vec3 GetWind(vec3 check_where, float curr_game_time, float change_rate);
+
+ Physics()
+ :gravity(0,-9.8f,0)
+ {}
+
+ static Physics* Instance()
+ {
+ static Physics instance;
+ return &instance;
+ }
+
+ void Update();
+};
diff --git a/Source/Scripting/angelscript/add_on/autowrapper/aswrappedcall.h b/Source/Scripting/angelscript/add_on/autowrapper/aswrappedcall.h
new file mode 100644
index 00000000..880265ef
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/autowrapper/aswrappedcall.h
@@ -0,0 +1,581 @@
+#ifndef AS_GEN_WRAPPER_H
+#define AS_GEN_WRAPPER_H
+
+#ifndef ANGELSCRIPT_H
+// Avoid having to inform include path if header is already include before
+#include <angelscript.h>
+#endif
+
+#include <new>
+
+namespace gw {
+
+template <typename T> class Proxy {
+ public:
+ T value;
+ Proxy(T value) : value(value) {}
+ static T cast(void * ptr) {
+ return reinterpret_cast<Proxy<T> *>(&ptr)->value;
+ }
+ private:
+ Proxy(const Proxy &);
+ Proxy & operator=(const Proxy &);
+};
+
+template <typename T> struct Wrapper {};
+template <typename T> struct ObjFirst {};
+template <typename T> struct ObjLast {};
+template <typename T> struct Constructor {};
+
+template <typename T>
+void destroy(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ static_cast<T *>(gen->GetObject())->~T();
+}
+template <>
+struct Wrapper<void (*)(void)> {
+ template <void (*fp)(void)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * /*gen*/) {
+ ((fp)());
+ }
+};
+template <typename R>
+struct Wrapper<R (*)(void)> {
+ template <R (*fp)(void)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetAddressOfReturnLocation()) Proxy<R>((fp)());
+ }
+};
+template <typename T>
+struct Wrapper<void (T::*)(void)> {
+ template <void (T::*fp)(void)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ ((static_cast<T *>(gen->GetObject())->*fp)());
+ }
+};
+template <typename T, typename R>
+struct Wrapper<R (T::*)(void)> {
+ template <R (T::*fp)(void)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetAddressOfReturnLocation()) Proxy<R>((static_cast<T *>(gen->GetObject())->*fp)());
+ }
+};
+template <typename T>
+struct Wrapper<void (T::*)(void) const> {
+ template <void (T::*fp)(void) const>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ ((static_cast<T *>(gen->GetObject())->*fp)());
+ }
+};
+template <typename T, typename R>
+struct Wrapper<R (T::*)(void) const> {
+ template <R (T::*fp)(void) const>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetAddressOfReturnLocation()) Proxy<R>((static_cast<T *>(gen->GetObject())->*fp)());
+ }
+};
+template <typename T>
+struct ObjFirst<void (*)(T)> {
+ template <void (*fp)(T)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ ((fp)(
+ Proxy<T>::cast(gen->GetObject())));
+ }
+};
+template <typename T, typename R>
+struct ObjFirst<R (*)(T)> {
+ template <R (*fp)(T)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetAddressOfReturnLocation()) Proxy<R>((fp)(
+ Proxy<T>::cast(gen->GetObject())));
+ }
+};
+template <typename T>
+struct ObjLast<void (*)(T)> {
+ template <void (*fp)(T)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ ((fp)(
+ Proxy<T>::cast(gen->GetObject())));
+ }
+};
+template <typename T, typename R>
+struct ObjLast<R (*)(T)> {
+ template <R (*fp)(T)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetAddressOfReturnLocation()) Proxy<R>((fp)(
+ Proxy<T>::cast(gen->GetObject())));
+ }
+};
+template <typename T>
+struct Constructor <T ()> {
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetObject()) T();
+ }
+};
+template <typename A0>
+struct Wrapper<void (*)(A0)> {
+ template <void (*fp)(A0)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ ((fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value));
+ }
+};
+template <typename R, typename A0>
+struct Wrapper<R (*)(A0)> {
+ template <R (*fp)(A0)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetAddressOfReturnLocation()) Proxy<R>((fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value));
+ }
+};
+template <typename T, typename A0>
+struct Wrapper<void (T::*)(A0)> {
+ template <void (T::*fp)(A0)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ ((static_cast<T *>(gen->GetObject())->*fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value));
+ }
+};
+template <typename T, typename R, typename A0>
+struct Wrapper<R (T::*)(A0)> {
+ template <R (T::*fp)(A0)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetAddressOfReturnLocation()) Proxy<R>((static_cast<T *>(gen->GetObject())->*fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value));
+ }
+};
+template <typename T, typename A0>
+struct Wrapper<void (T::*)(A0) const> {
+ template <void (T::*fp)(A0) const>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ ((static_cast<T *>(gen->GetObject())->*fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value));
+ }
+};
+template <typename T, typename R, typename A0>
+struct Wrapper<R (T::*)(A0) const> {
+ template <R (T::*fp)(A0) const>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetAddressOfReturnLocation()) Proxy<R>((static_cast<T *>(gen->GetObject())->*fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value));
+ }
+};
+template <typename T, typename A0>
+struct ObjFirst<void (*)(T, A0)> {
+ template <void (*fp)(T, A0)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ ((fp)(
+ Proxy<T>::cast(gen->GetObject()),
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value));
+ }
+};
+template <typename T, typename R, typename A0>
+struct ObjFirst<R (*)(T, A0)> {
+ template <R (*fp)(T, A0)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetAddressOfReturnLocation()) Proxy<R>((fp)(
+ Proxy<T>::cast(gen->GetObject()),
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value));
+ }
+};
+template <typename T, typename A0>
+struct ObjLast<void (*)(A0, T)> {
+ template <void (*fp)(A0, T)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ ((fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ Proxy<T>::cast(gen->GetObject())));
+ }
+};
+template <typename T, typename R, typename A0>
+struct ObjLast<R (*)(A0, T)> {
+ template <R (*fp)(A0, T)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetAddressOfReturnLocation()) Proxy<R>((fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ Proxy<T>::cast(gen->GetObject())));
+ }
+};
+template <typename T, typename A0>
+struct Constructor <T (A0)> {
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetObject()) T(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value);
+ }
+};
+template <typename A0, typename A1>
+struct Wrapper<void (*)(A0, A1)> {
+ template <void (*fp)(A0, A1)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ ((fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value));
+ }
+};
+template <typename R, typename A0, typename A1>
+struct Wrapper<R (*)(A0, A1)> {
+ template <R (*fp)(A0, A1)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetAddressOfReturnLocation()) Proxy<R>((fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value));
+ }
+};
+template <typename T, typename A0, typename A1>
+struct Wrapper<void (T::*)(A0, A1)> {
+ template <void (T::*fp)(A0, A1)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ ((static_cast<T *>(gen->GetObject())->*fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value));
+ }
+};
+template <typename T, typename R, typename A0, typename A1>
+struct Wrapper<R (T::*)(A0, A1)> {
+ template <R (T::*fp)(A0, A1)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetAddressOfReturnLocation()) Proxy<R>((static_cast<T *>(gen->GetObject())->*fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value));
+ }
+};
+template <typename T, typename A0, typename A1>
+struct Wrapper<void (T::*)(A0, A1) const> {
+ template <void (T::*fp)(A0, A1) const>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ ((static_cast<T *>(gen->GetObject())->*fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value));
+ }
+};
+template <typename T, typename R, typename A0, typename A1>
+struct Wrapper<R (T::*)(A0, A1) const> {
+ template <R (T::*fp)(A0, A1) const>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetAddressOfReturnLocation()) Proxy<R>((static_cast<T *>(gen->GetObject())->*fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value));
+ }
+};
+template <typename T, typename A0, typename A1>
+struct ObjFirst<void (*)(T, A0, A1)> {
+ template <void (*fp)(T, A0, A1)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ ((fp)(
+ Proxy<T>::cast(gen->GetObject()),
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value));
+ }
+};
+template <typename T, typename R, typename A0, typename A1>
+struct ObjFirst<R (*)(T, A0, A1)> {
+ template <R (*fp)(T, A0, A1)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetAddressOfReturnLocation()) Proxy<R>((fp)(
+ Proxy<T>::cast(gen->GetObject()),
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value));
+ }
+};
+template <typename T, typename A0, typename A1>
+struct ObjLast<void (*)(A0, A1, T)> {
+ template <void (*fp)(A0, A1, T)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ ((fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value,
+ Proxy<T>::cast(gen->GetObject())));
+ }
+};
+template <typename T, typename R, typename A0, typename A1>
+struct ObjLast<R (*)(A0, A1, T)> {
+ template <R (*fp)(A0, A1, T)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetAddressOfReturnLocation()) Proxy<R>((fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value,
+ Proxy<T>::cast(gen->GetObject())));
+ }
+};
+template <typename T, typename A0, typename A1>
+struct Constructor <T (A0, A1)> {
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetObject()) T(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value);
+ }
+};
+template <typename A0, typename A1, typename A2>
+struct Wrapper<void (*)(A0, A1, A2)> {
+ template <void (*fp)(A0, A1, A2)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ ((fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value,
+ static_cast<Proxy <A2> *>(gen->GetAddressOfArg(2))->value));
+ }
+};
+template <typename R, typename A0, typename A1, typename A2>
+struct Wrapper<R (*)(A0, A1, A2)> {
+ template <R (*fp)(A0, A1, A2)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetAddressOfReturnLocation()) Proxy<R>((fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value,
+ static_cast<Proxy <A2> *>(gen->GetAddressOfArg(2))->value));
+ }
+};
+template <typename T, typename A0, typename A1, typename A2>
+struct Wrapper<void (T::*)(A0, A1, A2)> {
+ template <void (T::*fp)(A0, A1, A2)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ ((static_cast<T *>(gen->GetObject())->*fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value,
+ static_cast<Proxy <A2> *>(gen->GetAddressOfArg(2))->value));
+ }
+};
+template <typename T, typename R, typename A0, typename A1, typename A2>
+struct Wrapper<R (T::*)(A0, A1, A2)> {
+ template <R (T::*fp)(A0, A1, A2)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetAddressOfReturnLocation()) Proxy<R>((static_cast<T *>(gen->GetObject())->*fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value,
+ static_cast<Proxy <A2> *>(gen->GetAddressOfArg(2))->value));
+ }
+};
+template <typename T, typename A0, typename A1, typename A2>
+struct Wrapper<void (T::*)(A0, A1, A2) const> {
+ template <void (T::*fp)(A0, A1, A2) const>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ ((static_cast<T *>(gen->GetObject())->*fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value,
+ static_cast<Proxy <A2> *>(gen->GetAddressOfArg(2))->value));
+ }
+};
+template <typename T, typename R, typename A0, typename A1, typename A2>
+struct Wrapper<R (T::*)(A0, A1, A2) const> {
+ template <R (T::*fp)(A0, A1, A2) const>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetAddressOfReturnLocation()) Proxy<R>((static_cast<T *>(gen->GetObject())->*fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value,
+ static_cast<Proxy <A2> *>(gen->GetAddressOfArg(2))->value));
+ }
+};
+template <typename T, typename A0, typename A1, typename A2>
+struct ObjFirst<void (*)(T, A0, A1, A2)> {
+ template <void (*fp)(T, A0, A1, A2)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ ((fp)(
+ Proxy<T>::cast(gen->GetObject()),
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value,
+ static_cast<Proxy <A2> *>(gen->GetAddressOfArg(2))->value));
+ }
+};
+template <typename T, typename R, typename A0, typename A1, typename A2>
+struct ObjFirst<R (*)(T, A0, A1, A2)> {
+ template <R (*fp)(T, A0, A1, A2)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetAddressOfReturnLocation()) Proxy<R>((fp)(
+ Proxy<T>::cast(gen->GetObject()),
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value,
+ static_cast<Proxy <A2> *>(gen->GetAddressOfArg(2))->value));
+ }
+};
+template <typename T, typename A0, typename A1, typename A2>
+struct ObjLast<void (*)(A0, A1, A2, T)> {
+ template <void (*fp)(A0, A1, A2, T)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ ((fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value,
+ static_cast<Proxy <A2> *>(gen->GetAddressOfArg(2))->value,
+ Proxy<T>::cast(gen->GetObject())));
+ }
+};
+template <typename T, typename R, typename A0, typename A1, typename A2>
+struct ObjLast<R (*)(A0, A1, A2, T)> {
+ template <R (*fp)(A0, A1, A2, T)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetAddressOfReturnLocation()) Proxy<R>((fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value,
+ static_cast<Proxy <A2> *>(gen->GetAddressOfArg(2))->value,
+ Proxy<T>::cast(gen->GetObject())));
+ }
+};
+template <typename T, typename A0, typename A1, typename A2>
+struct Constructor <T (A0, A1, A2)> {
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetObject()) T(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value,
+ static_cast<Proxy <A2> *>(gen->GetAddressOfArg(2))->value);
+ }
+};
+template <typename A0, typename A1, typename A2, typename A3>
+struct Wrapper<void (*)(A0, A1, A2, A3)> {
+ template <void (*fp)(A0, A1, A2, A3)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ ((fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value,
+ static_cast<Proxy <A2> *>(gen->GetAddressOfArg(2))->value,
+ static_cast<Proxy <A3> *>(gen->GetAddressOfArg(3))->value));
+ }
+};
+template <typename R, typename A0, typename A1, typename A2, typename A3>
+struct Wrapper<R (*)(A0, A1, A2, A3)> {
+ template <R (*fp)(A0, A1, A2, A3)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetAddressOfReturnLocation()) Proxy<R>((fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value,
+ static_cast<Proxy <A2> *>(gen->GetAddressOfArg(2))->value,
+ static_cast<Proxy <A3> *>(gen->GetAddressOfArg(3))->value));
+ }
+};
+template <typename T, typename A0, typename A1, typename A2, typename A3>
+struct Wrapper<void (T::*)(A0, A1, A2, A3)> {
+ template <void (T::*fp)(A0, A1, A2, A3)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ ((static_cast<T *>(gen->GetObject())->*fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value,
+ static_cast<Proxy <A2> *>(gen->GetAddressOfArg(2))->value,
+ static_cast<Proxy <A3> *>(gen->GetAddressOfArg(3))->value));
+ }
+};
+template <typename T, typename R, typename A0, typename A1, typename A2, typename A3>
+struct Wrapper<R (T::*)(A0, A1, A2, A3)> {
+ template <R (T::*fp)(A0, A1, A2, A3)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetAddressOfReturnLocation()) Proxy<R>((static_cast<T *>(gen->GetObject())->*fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value,
+ static_cast<Proxy <A2> *>(gen->GetAddressOfArg(2))->value,
+ static_cast<Proxy <A3> *>(gen->GetAddressOfArg(3))->value));
+ }
+};
+template <typename T, typename A0, typename A1, typename A2, typename A3>
+struct Wrapper<void (T::*)(A0, A1, A2, A3) const> {
+ template <void (T::*fp)(A0, A1, A2, A3) const>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ ((static_cast<T *>(gen->GetObject())->*fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value,
+ static_cast<Proxy <A2> *>(gen->GetAddressOfArg(2))->value,
+ static_cast<Proxy <A3> *>(gen->GetAddressOfArg(3))->value));
+ }
+};
+template <typename T, typename R, typename A0, typename A1, typename A2, typename A3>
+struct Wrapper<R (T::*)(A0, A1, A2, A3) const> {
+ template <R (T::*fp)(A0, A1, A2, A3) const>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetAddressOfReturnLocation()) Proxy<R>((static_cast<T *>(gen->GetObject())->*fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value,
+ static_cast<Proxy <A2> *>(gen->GetAddressOfArg(2))->value,
+ static_cast<Proxy <A3> *>(gen->GetAddressOfArg(3))->value));
+ }
+};
+template <typename T, typename A0, typename A1, typename A2, typename A3>
+struct ObjFirst<void (*)(T, A0, A1, A2, A3)> {
+ template <void (*fp)(T, A0, A1, A2, A3)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ ((fp)(
+ Proxy<T>::cast(gen->GetObject()),
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value,
+ static_cast<Proxy <A2> *>(gen->GetAddressOfArg(2))->value,
+ static_cast<Proxy <A3> *>(gen->GetAddressOfArg(3))->value));
+ }
+};
+template <typename T, typename R, typename A0, typename A1, typename A2, typename A3>
+struct ObjFirst<R (*)(T, A0, A1, A2, A3)> {
+ template <R (*fp)(T, A0, A1, A2, A3)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetAddressOfReturnLocation()) Proxy<R>((fp)(
+ Proxy<T>::cast(gen->GetObject()),
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value,
+ static_cast<Proxy <A2> *>(gen->GetAddressOfArg(2))->value,
+ static_cast<Proxy <A3> *>(gen->GetAddressOfArg(3))->value));
+ }
+};
+template <typename T, typename A0, typename A1, typename A2, typename A3>
+struct ObjLast<void (*)(A0, A1, A2, A3, T)> {
+ template <void (*fp)(A0, A1, A2, A3, T)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ ((fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value,
+ static_cast<Proxy <A2> *>(gen->GetAddressOfArg(2))->value,
+ static_cast<Proxy <A3> *>(gen->GetAddressOfArg(3))->value,
+ Proxy<T>::cast(gen->GetObject())));
+ }
+};
+template <typename T, typename R, typename A0, typename A1, typename A2, typename A3>
+struct ObjLast<R (*)(A0, A1, A2, A3, T)> {
+ template <R (*fp)(A0, A1, A2, A3, T)>
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetAddressOfReturnLocation()) Proxy<R>((fp)(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value,
+ static_cast<Proxy <A2> *>(gen->GetAddressOfArg(2))->value,
+ static_cast<Proxy <A3> *>(gen->GetAddressOfArg(3))->value,
+ Proxy<T>::cast(gen->GetObject())));
+ }
+};
+template <typename T, typename A0, typename A1, typename A2, typename A3>
+struct Constructor <T (A0, A1, A2, A3)> {
+ static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {
+ new (gen->GetObject()) T(
+ static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value,
+ static_cast<Proxy <A1> *>(gen->GetAddressOfArg(1))->value,
+ static_cast<Proxy <A2> *>(gen->GetAddressOfArg(2))->value,
+ static_cast<Proxy <A3> *>(gen->GetAddressOfArg(3))->value);
+ }
+};
+template <typename T>
+struct Id {
+ template <T fn_ptr> AS_NAMESPACE_QUALIFIER asSFuncPtr f(void) { return asFUNCTION(&Wrapper<T>::template f<fn_ptr>); }
+ template <T fn_ptr> AS_NAMESPACE_QUALIFIER asSFuncPtr of(void) { return asFUNCTION(&ObjFirst<T>::template f<fn_ptr>); }
+ template <T fn_ptr> AS_NAMESPACE_QUALIFIER asSFuncPtr ol(void) { return asFUNCTION(&ObjLast<T>::template f<fn_ptr>); }
+};
+
+template <typename T>
+Id<T> id(T /*fn_ptr*/) { return Id<T>(); }
+
+// On some versions of GNUC it is necessary to use the template keyword as disambiguator,
+// on others the template keyword gives an error, hence the need for the following define.
+// MSVC on the other hand seems to accept both with or without the template keyword.
+#if defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 4))
+ // GNUC 4.4.3 doesn't need the template keyword, and
+ // hopefully upcoming versions won't need it either
+ #define TMPL template
+#else
+ #define TMPL
+#endif
+
+#define WRAP_FN(name) (::gw::id(name).TMPL f< name >())
+#define WRAP_MFN(ClassType, name) (::gw::id(&ClassType::name).TMPL f< &ClassType::name >())
+#define WRAP_OBJ_FIRST(name) (::gw::id(name).TMPL of< name >())
+#define WRAP_OBJ_LAST(name) (::gw::id(name).TMPL ol< name >())
+
+#define WRAP_FN_PR(name, Parameters, ReturnType) asFUNCTION((::gw::Wrapper<ReturnType (*)Parameters>::TMPL f< name >))
+#define WRAP_MFN_PR(ClassType, name, Parameters, ReturnType) asFUNCTION((::gw::Wrapper<ReturnType (ClassType::*)Parameters>::TMPL f< &ClassType::name >))
+#define WRAP_OBJ_FIRST_PR(name, Parameters, ReturnType) asFUNCTION((::gw::ObjFirst<ReturnType (*)Parameters>::TMPL f< name >))
+#define WRAP_OBJ_LAST_PR(name, Parameters, ReturnType) asFUNCTION((::gw::ObjLast<ReturnType (*)Parameters>::TMPL f< name >))
+
+#define WRAP_CON(ClassType, Parameters) asFUNCTION((::gw::Constructor<ClassType Parameters>::f))
+#define WRAP_DES(ClassType) asFUNCTION((::gw::destroy<ClassType>))
+
+
+} // end namespace gw
+
+#endif
diff --git a/Source/Scripting/angelscript/add_on/autowrapper/generator/generateheader.cpp b/Source/Scripting/angelscript/add_on/autowrapper/generator/generateheader.cpp
new file mode 100644
index 00000000..277799a0
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/autowrapper/generator/generateheader.cpp
@@ -0,0 +1,166 @@
+//
+// This generator creates a header file that implements automatic
+// wrapper functions for the generic calling convention.
+//
+// Originally implemented by George Yohng from 4Front Technologies in 2009-03-11
+// Modifications by Pierre Fortin in order to add constructor wrapper generation
+//
+// A completely new implementation of automatic wrapper functions was
+// implemented by SiCrane at GameDev.net in 2011-12-18. The generator was
+// adapted from Python to C++ by Andreas.
+//
+// ref: http://www.gamedev.net/topic/617111-more-angelscript-binding-wrappers/
+//
+
+
+#include <stdio.h>
+#include <string>
+
+// Generate templates for up to this number of function parameters
+const int max_args = 4;
+
+using namespace std;
+
+void PrintTemplate(const char *base, const char *typeNameList, const char *retType, const char *objType, const char *isConst, const char *newExpr, const char *objExpr, const char *argList1, const char *argList2, const char *wrapName);
+void PrintConstructor(const char *comma, const char *typeNameList, const char *typeList, const char *argList);
+
+int main()
+{
+ printf("#ifndef AS_GEN_WRAPPER_H\n"
+ "#define AS_GEN_WRAPPER_H\n"
+ "\n"
+ "#ifndef ANGELSCRIPT_H\n"
+ "// Avoid having to inform include path if header is already include before\n"
+ "#include <angelscript.h>\n"
+ "#endif\n"
+ "#include <new>\n"
+ "\n"
+ "namespace gw {\n"
+ "\n"
+ "template <typename T> class Proxy {\n"
+ " public:\n"
+ " T value;\n"
+ " Proxy(T value) : value(value) {}\n"
+ " static T cast(void * ptr) {\n"
+ " return reinterpret_cast<Proxy<T> *>(&ptr)->value;\n"
+ " }\n"
+ " private:\n"
+ " Proxy(const Proxy &);\n"
+ " Proxy & operator=(const Proxy &);\n"
+ "};\n"
+ "\n"
+ "template <typename T> struct Wrapper {};\n"
+ "template <typename T> struct ObjFirst {};\n"
+ "template <typename T> struct ObjLast {};\n"
+ "template <typename T> struct Constructor {};\n"
+ "\n"
+ "template <typename T>\n"
+ "void destroy(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {\n"
+ " static_cast<T *>(gen->GetObject())->~T();\n"
+ "}\n");
+
+ string typename_list = "typename A0";
+ string type_list = "A0";
+ string arg_list = "\n static_cast<Proxy <A0> *>(gen->GetAddressOfArg(0))->value";
+ string new_exp = "new (gen->GetAddressOfReturnLocation()) Proxy<R>";
+ string obj_exp = "static_cast<T *>(gen->GetObject())->*";
+ string obj_arg_exp = "\n Proxy<T>::cast(gen->GetObject())";
+
+ PrintTemplate("", "", "void", "", "", "", "", "void", "", "Wrapper");
+ PrintTemplate("typename R", "", "R", "", "", new_exp.c_str(), "", "void", "", "Wrapper");
+ PrintTemplate("typename T", "", "void", "T::", "", "", obj_exp.c_str(), "void", "", "Wrapper");
+ PrintTemplate("typename T, typename R", "", "R", "T::", "", new_exp.c_str(), obj_exp.c_str(), "void", "", "Wrapper");
+ PrintTemplate("typename T", "", "void", "T::", " const", "", obj_exp.c_str(), "void", "", "Wrapper");
+ PrintTemplate("typename T, typename R", "", "R", "T::", " const", new_exp.c_str(), obj_exp.c_str(), "void", "", "Wrapper");
+
+ PrintTemplate("typename T", "", "void", "", "", "", "", "T", obj_arg_exp.c_str(), "ObjFirst");
+ PrintTemplate("typename T, typename R", "", "R", "", "", new_exp.c_str(), "", "T", obj_arg_exp.c_str(), "ObjFirst");
+ PrintTemplate("typename T", "", "void", "", "", "", "", "T", obj_arg_exp.c_str(), "ObjLast");
+ PrintTemplate("typename T, typename R", "", "R", "", "", new_exp.c_str(), "", "T", obj_arg_exp.c_str(), "ObjLast");
+
+ PrintConstructor("", "", "", "");
+
+ for( int i = 0; i < max_args; i++ )
+ {
+ PrintTemplate("", typename_list.c_str(), "void", "", "", "", "", type_list.c_str(), arg_list.c_str(), "Wrapper");
+ PrintTemplate("typename R, ", typename_list.c_str(), "R", "", "", new_exp.c_str(), "", type_list.c_str(), arg_list.c_str(), "Wrapper");
+ PrintTemplate("typename T, ", typename_list.c_str(), "void", "T::", "", "", obj_exp.c_str(), type_list.c_str(), arg_list.c_str(), "Wrapper");
+ PrintTemplate("typename T, typename R, ", typename_list.c_str(), "R", "T::", "", new_exp.c_str(), obj_exp.c_str(), type_list.c_str(), arg_list.c_str(), "Wrapper");
+ PrintTemplate("typename T, ", typename_list.c_str(), "void", "T::", " const", "", obj_exp.c_str(), type_list.c_str(), arg_list.c_str(), "Wrapper");
+ PrintTemplate("typename T, typename R, ", typename_list.c_str(), "R", "T::", " const", new_exp.c_str(), obj_exp.c_str(), type_list.c_str(), arg_list.c_str(), "Wrapper");
+
+ PrintTemplate("typename T, ", typename_list.c_str(), "void", "", "", "", "", ("T, " + type_list).c_str(), (obj_arg_exp + "," + arg_list).c_str(), "ObjFirst");
+ PrintTemplate("typename T, typename R, ", typename_list.c_str(), "R", "", "", new_exp.c_str(), "", ("T, " + type_list).c_str(), (obj_arg_exp + "," + arg_list).c_str(), "ObjFirst");
+ PrintTemplate("typename T, ", typename_list.c_str(), "void", "", "", "", "", (type_list + ", T").c_str(), (arg_list + "," + obj_arg_exp).c_str(), "ObjLast");
+ PrintTemplate("typename T, typename R, ", typename_list.c_str(), "R", "", "", new_exp.c_str(), "", (type_list + ", T").c_str(), (arg_list + "," + obj_arg_exp).c_str(), "ObjLast");
+
+ PrintConstructor(", ", typename_list.c_str(), type_list.c_str(), arg_list.c_str());
+
+ char buf[5];
+ sprintf(buf, "%d", i + 1);
+ typename_list += ", typename A" + string(buf);
+ type_list += ", A" + string(buf);
+ arg_list += ",\n static_cast<Proxy <A" + string(buf) + "> *>(gen->GetAddressOfArg(" + string(buf) + "))->value";
+ }
+
+ printf("template <typename T>\n"
+ "struct Id {\n"
+ " template <T fn_ptr> AS_NAMESPACE_QUALIFIER asSFuncPtr f(void) { return asFUNCTION(&Wrapper<T>::template f<fn_ptr>); }\n"
+ " template <T fn_ptr> AS_NAMESPACE_QUALIFIER asSFuncPtr of(void) { return asFUNCTION(&ObjFirst<T>::template f<fn_ptr>); }\n"
+ " template <T fn_ptr> AS_NAMESPACE_QUALIFIER asSFuncPtr ol(void) { return asFUNCTION(&ObjLast<T>::template f<fn_ptr>); }\n"
+ "};\n"
+ "\n"
+ "template <typename T>\n"
+ "Id<T> id(T fn_ptr) { return Id<T>(); }\n"
+ "\n"
+ "// On some versions of GNUC it is necessary to use the template keyword as disambiguator,\n"
+ "// on others the template keyword gives an error, hence the need for the following define.\n"
+ "// MSVC on the other hand seems to accept both with or without the template keyword.\n"
+ "#if defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 4))\n"
+ " // GNUC 4.4.3 doesn't need the template keyword, and\n"
+ " // hopefully upcoming versions won't need it either\n"
+ " #define TMPL template\n"
+ "#else\n"
+ " #define TMPL\n"
+ "#endif\n"
+ "\n"
+ "#define WRAP_FN(name) (::gw::id(name).TMPL f< name >())\n"
+ "#define WRAP_MFN(ClassType, name) (::gw::id(&ClassType::name).TMPL f< &ClassType::name >())\n"
+ "#define WRAP_OBJ_FIRST(name) (::gw::id(name).TMPL of< name >())\n"
+ "#define WRAP_OBJ_LAST(name) (::gw::id(name).TMPL ol< name >())\n"
+ "\n"
+ "#define WRAP_FN_PR(name, Parameters, ReturnType) asFUNCTION((::gw::Wrapper<ReturnType (*)Parameters>::TMPL f< name >))\n"
+ "#define WRAP_MFN_PR(ClassType, name, Parameters, ReturnType) asFUNCTION((::gw::Wrapper<ReturnType (ClassType::*)Parameters>::TMPL f< &ClassType::name >))\n"
+ "#define WRAP_OBJ_FIRST_PR(name, Parameters, ReturnType) asFUNCTION((::gw::ObjFirst<ReturnType (*)Parameters>::TMPL f< name >))\n"
+ "#define WRAP_OBJ_LAST_PR(name, Parameters, ReturnType) asFUNCTION((::gw::ObjLast<ReturnType (*)Parameters>::TMPL f< name >))\n"
+ "\n"
+ "#define WRAP_CON(ClassType, Parameters) asFUNCTION((::gw::Constructor<ClassType Parameters>::f))\n"
+ "#define WRAP_DES(ClassType) asFUNCTION((::gw::destroy<ClassType>))\n"
+ "\n"
+ "} // end namespace gw\n"
+ "\n"
+ "#endif\n");
+
+ return 0;
+}
+
+void PrintTemplate(const char *base, const char *typeNameList, const char *retType, const char *objType, const char *isConst, const char *newExpr, const char *objExpr, const char *argList1, const char *argList2, const char *wrapName)
+{
+ printf("template <%s%s>\n", base, typeNameList);
+ printf("struct %s<%s (%s*)(%s)%s> {\n", wrapName, retType, objType, argList1, isConst);
+ printf(" template <%s (%s*fp)(%s)%s>\n", retType, objType, argList1, isConst);
+ printf(" static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {\n");
+ printf(" %s((%sfp)(%s));\n", newExpr, objExpr, argList2);
+ printf(" }\n");
+ printf("};\n");
+}
+
+void PrintConstructor(const char *comma, const char *typeNameList, const char *typeList, const char *argList)
+{
+ printf("template <typename T%s%s>\n", comma, typeNameList);
+ printf("struct Constructor <T (%s)> {\n", typeList);
+ printf(" static void f(AS_NAMESPACE_QUALIFIER asIScriptGeneric * gen) {\n");
+ printf(" new (gen->GetObject()) T(%s);\n", argList);
+ printf(" }\n");
+ printf("};\n");
+} \ No newline at end of file
diff --git a/Source/Scripting/angelscript/add_on/autowrapper/generator/generator.sln b/Source/Scripting/angelscript/add_on/autowrapper/generator/generator.sln
new file mode 100644
index 00000000..b84712a1
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/autowrapper/generator/generator.sln
@@ -0,0 +1,20 @@
+
+Microsoft Visual Studio Solution File, Format Version 10.00
+# Visual C++ Express 2008
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "generator", "generator.vcproj", "{086A2F1A-01B1-4EB3-A8FA-0926FF10E953}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Release|Win32 = Release|Win32
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {086A2F1A-01B1-4EB3-A8FA-0926FF10E953}.Debug|Win32.ActiveCfg = Debug|Win32
+ {086A2F1A-01B1-4EB3-A8FA-0926FF10E953}.Debug|Win32.Build.0 = Debug|Win32
+ {086A2F1A-01B1-4EB3-A8FA-0926FF10E953}.Release|Win32.ActiveCfg = Release|Win32
+ {086A2F1A-01B1-4EB3-A8FA-0926FF10E953}.Release|Win32.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/Source/Scripting/angelscript/add_on/autowrapper/generator/generator.vcproj b/Source/Scripting/angelscript/add_on/autowrapper/generator/generator.vcproj
new file mode 100644
index 00000000..e663fdc1
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/autowrapper/generator/generator.vcproj
@@ -0,0 +1,236 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9,00"
+ Name="generator"
+ ProjectGUID="{086A2F1A-01B1-4EB3-A8FA-0926FF10E953}"
+ TargetFrameworkVersion="0"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory=".\Debug"
+ IntermediateDirectory=".\Debug"
+ ConfigurationType="1"
+ InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC60.vsprops"
+ UseOfMFC="0"
+ ATLMinimizesCRunTimeLibraryUsage="false"
+ CharacterSet="2"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ TypeLibraryName=".\Debug/generator.tlb"
+ HeaderFileName=""
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="1"
+ PrecompiledHeaderFile=".\Debug/generator.pch"
+ AssemblerListingLocation=".\Debug/"
+ ObjectFile=".\Debug/"
+ ProgramDataBaseFileName=".\Debug/"
+ WarningLevel="3"
+ SuppressStartupBanner="true"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ PreprocessorDefinitions="_DEBUG"
+ Culture="1033"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ OutputFile=".\Debug/generator.exe"
+ LinkIncremental="2"
+ SuppressStartupBanner="true"
+ GenerateDebugInformation="true"
+ ProgramDatabaseFile=".\Debug/generator.pdb"
+ SubSystem="1"
+ RandomizedBaseAddress="1"
+ DataExecutionPrevention="0"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ SuppressStartupBanner="true"
+ OutputFile=".\Debug/generator.bsc"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory=".\Release"
+ IntermediateDirectory=".\Release"
+ ConfigurationType="1"
+ InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC60.vsprops"
+ UseOfMFC="0"
+ ATLMinimizesCRunTimeLibraryUsage="false"
+ CharacterSet="2"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ TypeLibraryName=".\Release/generator.tlb"
+ HeaderFileName=""
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ InlineFunctionExpansion="1"
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
+ StringPooling="true"
+ RuntimeLibrary="0"
+ EnableFunctionLevelLinking="true"
+ PrecompiledHeaderFile=".\Release/generator.pch"
+ AssemblerListingLocation=".\Release/"
+ ObjectFile=".\Release/"
+ ProgramDataBaseFileName=".\Release/"
+ WarningLevel="3"
+ SuppressStartupBanner="true"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ PreprocessorDefinitions="NDEBUG"
+ Culture="1033"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ OutputFile=".\Release/generator.exe"
+ LinkIncremental="1"
+ SuppressStartupBanner="true"
+ ProgramDatabaseFile=".\Release/generator.pdb"
+ SubSystem="1"
+ RandomizedBaseAddress="1"
+ DataExecutionPrevention="0"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ SuppressStartupBanner="true"
+ OutputFile=".\Release/generator.bsc"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Source Files"
+ Filter="cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+ >
+ <File
+ RelativePath="generateheader.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ </Filter>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl"
+ >
+ </Filter>
+ <Filter
+ Name="Resource Files"
+ Filter="ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+ >
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/Source/Scripting/angelscript/add_on/contextmgr/contextmgr.cpp b/Source/Scripting/angelscript/add_on/contextmgr/contextmgr.cpp
new file mode 100644
index 00000000..e6bbc611
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/contextmgr/contextmgr.cpp
@@ -0,0 +1,401 @@
+#include <assert.h>
+#include <string>
+
+#include "contextmgr.h"
+
+using namespace std;
+
+// TODO: Should have a pool of free asIScriptContext so that new contexts
+// won't be allocated every time. The application must not keep
+// its own references, instead it must tell the context manager
+// that it is using the context. Otherwise the context manager may
+// think it can reuse the context too early.
+
+// TODO: Need to have a callback for when scripts finishes, so that the
+// application can receive return values.
+
+BEGIN_AS_NAMESPACE
+
+// The id for the context manager user data.
+// The add-ons have reserved the numbers 1000
+// through 1999 for this purpose, so we should be fine.
+const asPWORD CONTEXT_MGR = 1002;
+
+struct SContextInfo
+{
+ asUINT sleepUntil;
+ vector<asIScriptContext*> coRoutines;
+ asUINT currentCoRoutine;
+ asIScriptContext * keepCtxAfterExecution;
+};
+
+static void ScriptSleep(asUINT milliSeconds)
+{
+ // Get a pointer to the context that is currently being executed
+ asIScriptContext *ctx = asGetActiveContext();
+ if( ctx )
+ {
+ // Get the context manager from the user data
+ CContextMgr *ctxMgr = reinterpret_cast<CContextMgr*>(ctx->GetUserData(CONTEXT_MGR));
+ if( ctxMgr )
+ {
+ // Suspend its execution. The VM will continue until the current
+ // statement is finished and then return from the Execute() method
+ ctx->Suspend();
+
+ // Tell the context manager when the context is to continue execution
+ ctxMgr->SetSleeping(ctx, milliSeconds);
+ }
+ }
+}
+
+static void ScriptYield()
+{
+ // Get a pointer to the context that is currently being executed
+ asIScriptContext *ctx = asGetActiveContext();
+ if( ctx )
+ {
+ // Get the context manager from the user data
+ CContextMgr *ctxMgr = reinterpret_cast<CContextMgr*>(ctx->GetUserData(CONTEXT_MGR));
+ if( ctxMgr )
+ {
+ // Let the context manager know that it should run the next co-routine
+ ctxMgr->NextCoRoutine();
+
+ // The current context must be suspended so that VM will return from
+ // the Execute() method where the context manager will continue.
+ ctx->Suspend();
+ }
+ }
+}
+
+void ScriptCreateCoRoutine(asIScriptFunction *func, CScriptDictionary *arg)
+{
+ if( func == 0 )
+ return;
+
+ asIScriptContext *ctx = asGetActiveContext();
+ if( ctx )
+ {
+ // Get the context manager from the user data
+ CContextMgr *ctxMgr = reinterpret_cast<CContextMgr*>(ctx->GetUserData(CONTEXT_MGR));
+ if( ctxMgr )
+ {
+ // Create a new context for the co-routine
+ asIScriptContext *coctx = ctxMgr->AddContextForCoRoutine(ctx, func);
+
+ // Pass the argument to the context
+ coctx->SetArgObject(0, arg);
+
+ // The context manager will call Execute() on the context when it is time
+ }
+ }
+}
+
+#ifdef AS_MAX_PORTABILITY
+void ScriptYield_generic(asIScriptGeneric *)
+{
+ ScriptYield();
+}
+
+void ScriptCreateCoRoutine_generic(asIScriptGeneric *gen)
+{
+ asIScriptFunction *func = reinterpret_cast<asIScriptFunction*>(gen->GetArgAddress(0));
+ CScriptDictionary *dict = reinterpret_cast<CScriptDictionary*>(gen->GetArgAddress(1));
+ ScriptCreateCoRoutine(func, dict);
+}
+#endif
+
+CContextMgr::CContextMgr()
+{
+ m_getTimeFunc = 0;
+ m_currentThread = 0;
+
+ m_numExecutions = 0;
+ m_numGCObjectsCreated = 0;
+ m_numGCObjectsDestroyed = 0;
+}
+
+CContextMgr::~CContextMgr()
+{
+ asUINT n;
+
+ // Free the memory
+ for( n = 0; n < m_threads.size(); n++ )
+ {
+ if( m_threads[n] )
+ {
+ for( asUINT c = 0; c < m_threads[n]->coRoutines.size(); c++ )
+ {
+ asIScriptContext *ctx = m_threads[n]->coRoutines[c];
+ if( ctx )
+ {
+ // Return the context to the engine (and possible context pool configured in it)
+ ctx->GetEngine()->ReturnContext(ctx);
+ }
+ }
+
+ delete m_threads[n];
+ }
+ }
+
+ for( n = 0; n < m_freeThreads.size(); n++ )
+ {
+ if( m_freeThreads[n] )
+ {
+ assert( m_freeThreads[n]->coRoutines.size() == 0 );
+
+ delete m_freeThreads[n];
+ }
+ }
+}
+
+int CContextMgr::ExecuteScripts()
+{
+ // TODO: Should have an optional time out for this function. If not all scripts executed before the
+ // time out, the next time the function is called the loop should continue
+ // where it left off.
+
+ // TODO: There should be a time out per thread as well. If a thread executes for too
+ // long, it should be aborted. A group of co-routines count as a single thread.
+
+ // Check if the system time is higher than the time set for the contexts
+ asUINT time = m_getTimeFunc ? m_getTimeFunc() : asUINT(-1);
+ for( m_currentThread = 0; m_currentThread < m_threads.size(); m_currentThread++ )
+ {
+ SContextInfo *thread = m_threads[m_currentThread];
+ if( thread->sleepUntil < time )
+ {
+ int currentCoRoutine = thread->currentCoRoutine;
+
+ // Gather some statistics from the GC
+ asIScriptEngine *engine = thread->coRoutines[currentCoRoutine]->GetEngine();
+ asUINT gcSize1, gcSize2, gcSize3;
+ engine->GetGCStatistics(&gcSize1);
+
+ // Execute the script for this thread and co-routine
+ int r = thread->coRoutines[currentCoRoutine]->Execute();
+
+ // Determine how many new objects were created in the GC
+ engine->GetGCStatistics(&gcSize2);
+ m_numGCObjectsCreated += gcSize2 - gcSize1;
+ m_numExecutions++;
+
+ if( r != asEXECUTION_SUSPENDED )
+ {
+ // The context has terminated execution (for one reason or other)
+ // Unless the application has requested to keep the context we'll return it to the pool now
+ if( thread->keepCtxAfterExecution != thread->coRoutines[currentCoRoutine] )
+ engine->ReturnContext(thread->coRoutines[currentCoRoutine]);
+ thread->coRoutines[currentCoRoutine] = 0;
+
+ thread->coRoutines.erase(thread->coRoutines.begin() + thread->currentCoRoutine);
+ if( thread->currentCoRoutine >= thread->coRoutines.size() )
+ thread->currentCoRoutine = 0;
+
+ // If this was the last co-routine terminate the thread
+ if( thread->coRoutines.size() == 0 )
+ {
+ m_freeThreads.push_back(thread);
+ m_threads.erase(m_threads.begin() + m_currentThread);
+ m_currentThread--;
+ }
+ }
+
+ // Destroy all known garbage if any new objects were created
+ if( gcSize2 > gcSize1 )
+ {
+ engine->GarbageCollect(asGC_FULL_CYCLE | asGC_DESTROY_GARBAGE);
+
+ // Determine how many objects were destroyed
+ engine->GetGCStatistics(&gcSize3);
+ m_numGCObjectsDestroyed += gcSize3 - gcSize2;
+ }
+
+ // TODO: If more objects are created per execution than destroyed on average
+ // then it may be necessary to run more iterations of the detection of
+ // cyclic references. At the startup of an application there is usually
+ // a lot of objects created that will live on through out the application
+ // so the average number of objects created per execution will be higher
+ // than the number of destroyed objects in the beginning, but afterwards
+ // it usually levels out to be more or less equal.
+
+ // Just run an incremental step for detecting cyclic references
+ engine->GarbageCollect(asGC_ONE_STEP | asGC_DETECT_GARBAGE);
+ }
+ }
+
+ return int(m_threads.size());
+}
+
+void CContextMgr::DoneWithContext(asIScriptContext *ctx)
+{
+ ctx->GetEngine()->ReturnContext(ctx);
+}
+
+void CContextMgr::NextCoRoutine()
+{
+ m_threads[m_currentThread]->currentCoRoutine++;
+ if( m_threads[m_currentThread]->currentCoRoutine >= m_threads[m_currentThread]->coRoutines.size() )
+ m_threads[m_currentThread]->currentCoRoutine = 0;
+}
+
+void CContextMgr::AbortAll()
+{
+ // Abort all contexts and release them. The script engine will make
+ // sure that all resources held by the scripts are properly released.
+
+ for( asUINT n = 0; n < m_threads.size(); n++ )
+ {
+ for( asUINT c = 0; c < m_threads[n]->coRoutines.size(); c++ )
+ {
+ asIScriptContext *ctx = m_threads[n]->coRoutines[c];
+ if( ctx )
+ {
+ ctx->Abort();
+ ctx->GetEngine()->ReturnContext(ctx);
+ ctx = 0;
+ }
+ }
+ m_threads[n]->coRoutines.resize(0);
+
+ m_freeThreads.push_back(m_threads[n]);
+ }
+
+ m_threads.resize(0);
+
+ m_currentThread = 0;
+}
+
+asIScriptContext *CContextMgr::AddContext(asIScriptEngine *engine, asIScriptFunction *func, bool keepCtxAfterExec)
+{
+ // Use RequestContext instead of CreateContext so we can take
+ // advantage of possible context pooling configured with the engine
+ asIScriptContext *ctx = engine->RequestContext();
+ if( ctx == 0 )
+ return 0;
+
+ // Prepare it to execute the function
+ int r = ctx->Prepare(func);
+ if( r < 0 )
+ {
+ engine->ReturnContext(ctx);
+ return 0;
+ }
+
+ // Set the context manager as user data with the context so it
+ // can be retrieved by the functions registered with the engine
+ ctx->SetUserData(this, CONTEXT_MGR);
+
+ // Add the context to the list for execution
+ SContextInfo *info = 0;
+ if( m_freeThreads.size() > 0 )
+ {
+ info = *m_freeThreads.rbegin();
+ m_freeThreads.pop_back();
+ }
+ else
+ {
+ info = new SContextInfo;
+ }
+
+ info->coRoutines.push_back(ctx);
+ info->currentCoRoutine = 0;
+ info->sleepUntil = 0;
+ info->keepCtxAfterExecution = keepCtxAfterExec ? ctx : 0;
+ m_threads.push_back(info);
+
+ return ctx;
+}
+
+asIScriptContext *CContextMgr::AddContextForCoRoutine(asIScriptContext *currCtx, asIScriptFunction *func)
+{
+ asIScriptEngine *engine = currCtx->GetEngine();
+ asIScriptContext *coctx = engine->RequestContext();
+ if( coctx == 0 )
+ {
+ return 0;
+ }
+
+ // Prepare the context
+ int r = coctx->Prepare(func);
+ if( r < 0 )
+ {
+ // Couldn't prepare the context
+ engine->ReturnContext(coctx);
+ return 0;
+ }
+
+ // Set the context manager as user data with the context so it
+ // can be retrieved by the functions registered with the engine
+ coctx->SetUserData(this, CONTEXT_MGR);
+
+ // Find the current context thread info
+ // TODO: Start with the current thread so that we can find the group faster
+ for( asUINT n = 0; n < m_threads.size(); n++ )
+ {
+ if( m_threads[n]->coRoutines[m_threads[n]->currentCoRoutine] == currCtx )
+ {
+ // Add the coRoutine to the list
+ m_threads[n]->coRoutines.push_back(coctx);
+ }
+ }
+
+ return coctx;
+}
+
+void CContextMgr::SetSleeping(asIScriptContext *ctx, asUINT milliSeconds)
+{
+ assert( m_getTimeFunc != 0 );
+
+ // Find the context and update the timeStamp
+ // for when the context is to be continued
+
+ // TODO: Start with the current thread
+
+ for( asUINT n = 0; n < m_threads.size(); n++ )
+ {
+ if( m_threads[n]->coRoutines[m_threads[n]->currentCoRoutine] == ctx )
+ {
+ m_threads[n]->sleepUntil = (m_getTimeFunc ? m_getTimeFunc() : 0) + milliSeconds;
+ }
+ }
+}
+
+void CContextMgr::RegisterThreadSupport(asIScriptEngine *engine)
+{
+ int r;
+
+ // Must set the get time callback function for this to work
+ assert( m_getTimeFunc != 0 );
+
+ // Register the sleep function
+ r = engine->RegisterGlobalFunction("void sleep(uint)", asFUNCTION(ScriptSleep), asCALL_CDECL); assert( r >= 0 );
+
+ // TODO: Add support for spawning new threads, waiting for signals, etc
+}
+
+void CContextMgr::RegisterCoRoutineSupport(asIScriptEngine *engine)
+{
+ int r;
+
+ // The dictionary add-on must have been registered already
+ assert( engine->GetTypeInfoByDecl("dictionary") );
+
+#ifndef AS_MAX_PORTABILITY
+ r = engine->RegisterGlobalFunction("void yield()", asFUNCTION(ScriptYield), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterFuncdef("void coroutine(dictionary@)");
+ r = engine->RegisterGlobalFunction("void createCoRoutine(coroutine @+, dictionary @+)", asFUNCTION(ScriptCreateCoRoutine), asCALL_CDECL); assert( r >= 0 );
+#else
+ r = engine->RegisterGlobalFunction("void yield()", asFUNCTION(ScriptYield_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterFuncdef("void coroutine(dictionary@)");
+ r = engine->RegisterGlobalFunction("void createCoRoutine(coroutine @, dictionary @)", asFUNCTION(ScriptCreateCoRoutine_generic), asCALL_GENERIC); assert( r >= 0 );
+#endif
+}
+
+void CContextMgr::SetGetTimeCallback(TIMEFUNC_t func)
+{
+ m_getTimeFunc = func;
+}
+
+END_AS_NAMESPACE
diff --git a/Source/Scripting/angelscript/add_on/contextmgr/contextmgr.h b/Source/Scripting/angelscript/add_on/contextmgr/contextmgr.h
new file mode 100644
index 00000000..f57630cf
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/contextmgr/contextmgr.h
@@ -0,0 +1,99 @@
+#ifndef CONTEXTMGR_H
+#define CONTEXTMGR_H
+
+// The context manager simplifies the management of multiple concurrent scripts
+
+// More than one context manager can be used, if you wish to control different
+// groups of scripts separately, e.g. game object scripts, and GUI scripts.
+
+// OBSERVATION: This class is currently not thread safe.
+
+#ifndef ANGELSCRIPT_H
+// Avoid having to inform include path if header is already include before
+#include <angelscript.h>
+#endif
+
+#include <vector>
+
+BEGIN_AS_NAMESPACE
+
+class CScriptDictionary;
+
+// The internal structure for holding contexts
+struct SContextInfo;
+
+// The signature of the get time callback function
+typedef asUINT (*TIMEFUNC_t)();
+
+class CContextMgr
+{
+public:
+ CContextMgr();
+ ~CContextMgr();
+
+ // Set the function that the manager will use to obtain the time in milliseconds
+ void SetGetTimeCallback(TIMEFUNC_t func);
+
+ // Registers the following:
+ //
+ // void sleep(uint milliseconds)
+ //
+ // The application must set the get time callback for this to work
+ void RegisterThreadSupport(asIScriptEngine *engine);
+
+ // Registers the following:
+ //
+ // funcdef void coroutine(dictionary@)
+ // void createCoRoutine(coroutine @func, dictionary @args)
+ // void yield()
+ void RegisterCoRoutineSupport(asIScriptEngine *engine);
+
+ // Create a new context, prepare it with the function id, then return
+ // it so that the application can pass the argument values. The context
+ // will be released by the manager after the execution has completed.
+ // Set keepCtxAfterExecution to true if the application needs to retrieve
+ // information from the context after it the script has finished.
+ asIScriptContext *AddContext(asIScriptEngine *engine, asIScriptFunction *func, bool keepCtxAfterExecution = false);
+
+ // If the context was kept after the execution, this method must be
+ // called when the application is done with the context so it can be
+ // returned to the pool for reuse.
+ void DoneWithContext(asIScriptContext *ctx);
+
+ // Create a new context, prepare it with the function id, then return
+ // it so that the application can pass the argument values. The context
+ // will be added as a co-routine in the same thread as the currCtx.
+ asIScriptContext *AddContextForCoRoutine(asIScriptContext *currCtx, asIScriptFunction *func);
+
+ // Execute each script that is not currently sleeping. The function returns after
+ // each script has been executed once. The application should call this function
+ // for each iteration of the message pump, or game loop, or whatever.
+ // Returns the number of scripts still in execution.
+ int ExecuteScripts();
+
+ // Put a script to sleep for a while
+ void SetSleeping(asIScriptContext *ctx, asUINT milliSeconds);
+
+ // Switch the execution to the next co-routine in the group.
+ // Returns true if the switch was successful.
+ void NextCoRoutine();
+
+ // Abort all scripts
+ void AbortAll();
+
+protected:
+ std::vector<SContextInfo*> m_threads;
+ std::vector<SContextInfo*> m_freeThreads;
+ asUINT m_currentThread;
+ TIMEFUNC_t m_getTimeFunc;
+
+ // Statistics for Garbage Collection
+ asUINT m_numExecutions;
+ asUINT m_numGCObjectsCreated;
+ asUINT m_numGCObjectsDestroyed;
+};
+
+
+END_AS_NAMESPACE
+
+#endif
diff --git a/Source/Scripting/angelscript/add_on/datetime/datetime.cpp b/Source/Scripting/angelscript/add_on/datetime/datetime.cpp
new file mode 100644
index 00000000..2ac9f6d7
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/datetime/datetime.cpp
@@ -0,0 +1,109 @@
+#include "datetime.h"
+#include <string.h>
+#include <assert.h>
+#include <new>
+
+using namespace std;
+using namespace std::chrono;
+
+BEGIN_AS_NAMESPACE
+
+// TODO: Allow setting the timezone to use
+
+// TODO: Allow taking the difference of two CDateTimes. Should return the difference in seconds
+// TODO: Allow adding seconds
+// TODO: Allow setting each component year, month, day, hour, minute, second.
+// SetDate(y,m,d): year, month and day must be set in a single function to make sure the function can validate the date
+// SetTime(h,m,s): hour, minute and second shall be set in a single function too for consistency
+
+static tm time_point_to_tm(const std::chrono::time_point<std::chrono::system_clock> &tp)
+{
+ time_t t = system_clock::to_time_t(tp);
+ tm local;
+#ifdef _MSC_VER
+ localtime_s(&local, &t);
+#else
+ local = *localtime(&t);
+#endif
+ return local;
+}
+
+CDateTime::CDateTime() : tp(std::chrono::system_clock::now())
+{
+}
+
+CDateTime::CDateTime(const CDateTime &o) : tp(o.tp)
+{
+}
+
+CDateTime &CDateTime::operator=(const CDateTime &o)
+{
+ tp = o.tp;
+ return *this;
+}
+
+asUINT CDateTime::getYear() const
+{
+ tm local = time_point_to_tm(tp);
+ return local.tm_year + 1900;
+}
+
+asUINT CDateTime::getMonth() const
+{
+ tm local = time_point_to_tm(tp);
+ return local.tm_mon + 1;
+}
+
+asUINT CDateTime::getDay() const
+{
+ tm local = time_point_to_tm(tp);
+ return local.tm_mday;
+}
+
+asUINT CDateTime::getHour() const
+{
+ tm local = time_point_to_tm(tp);
+ return local.tm_hour;
+}
+
+asUINT CDateTime::getMinute() const
+{
+ tm local = time_point_to_tm(tp);
+ return local.tm_min;
+}
+
+asUINT CDateTime::getSecond() const
+{
+ tm local = time_point_to_tm(tp);
+ return local.tm_sec;
+}
+
+static void Construct(CDateTime *mem)
+{
+ new(mem) CDateTime();
+}
+
+static void ConstructCopy(CDateTime *mem, const CDateTime &o)
+{
+ new(mem) CDateTime(o);
+}
+
+void RegisterScriptDateTime(asIScriptEngine *engine)
+{
+ // TODO: Add support for generic calling convention
+ assert(strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") == 0);
+
+ int r = engine->RegisterObjectType("datetime", sizeof(CDateTime), asOBJ_VALUE | asOBJ_POD | asGetTypeTraits<CDateTime>()); assert(r >= 0);
+
+ r = engine->RegisterObjectBehaviour("datetime", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(Construct), asCALL_CDECL_OBJLAST); assert(r >= 0);
+ r = engine->RegisterObjectBehaviour("datetime", asBEHAVE_CONSTRUCT, "void f(const datetime &in)", asFUNCTION(ConstructCopy), asCALL_CDECL_OBJLAST); assert(r >= 0);
+ r = engine->RegisterObjectMethod("datetime", "datetime &opAssign(const datetime &in)", asMETHOD(CDateTime, operator=), asCALL_THISCALL); assert(r >= 0);
+ r = engine->RegisterObjectMethod("datetime", "uint get_year() const", asMETHOD(CDateTime, getYear), asCALL_THISCALL); assert(r >= 0);
+ r = engine->RegisterObjectMethod("datetime", "uint get_month() const", asMETHOD(CDateTime, getMonth), asCALL_THISCALL); assert(r >= 0);
+ r = engine->RegisterObjectMethod("datetime", "uint get_day() const", asMETHOD(CDateTime, getDay), asCALL_THISCALL); assert(r >= 0);
+ r = engine->RegisterObjectMethod("datetime", "uint get_hour() const", asMETHOD(CDateTime, getHour), asCALL_THISCALL); assert(r >= 0);
+ r = engine->RegisterObjectMethod("datetime", "uint get_minute() const", asMETHOD(CDateTime, getMinute), asCALL_THISCALL); assert(r >= 0);
+ r = engine->RegisterObjectMethod("datetime", "uint get_second() const", asMETHOD(CDateTime, getSecond), asCALL_THISCALL); assert(r >= 0);
+}
+
+END_AS_NAMESPACE \ No newline at end of file
diff --git a/Source/Scripting/angelscript/add_on/datetime/datetime.h b/Source/Scripting/angelscript/add_on/datetime/datetime.h
new file mode 100644
index 00000000..cb087cf4
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/datetime/datetime.h
@@ -0,0 +1,43 @@
+#ifndef SCRIPTDATETIME_H
+#define SCRIPTDATETIME_H
+
+#ifndef ANGELSCRIPT_H
+// Avoid having to inform include path if header is already include before
+#include <angelscript.h>
+#endif
+
+#ifdef AS_CAN_USE_CPP11
+#include <chrono>
+#else
+#error Sorry, this requires C++11 which your compiler doesnt appear to support
+#endif
+
+BEGIN_AS_NAMESPACE
+
+class CDateTime
+{
+public:
+ // Constructors
+ CDateTime();
+ CDateTime(const CDateTime &other);
+
+ // Copy the stored value from another any object
+ CDateTime &operator=(const CDateTime &other);
+
+ // Accessors
+ asUINT getYear() const;
+ asUINT getMonth() const;
+ asUINT getDay() const;
+ asUINT getHour() const;
+ asUINT getMinute() const;
+ asUINT getSecond() const;
+
+protected:
+ std::chrono::system_clock::time_point tp;
+};
+
+void RegisterScriptDateTime(asIScriptEngine *engine);
+
+END_AS_NAMESPACE
+
+#endif
diff --git a/Source/Scripting/angelscript/add_on/debugger/debugger.cpp b/Source/Scripting/angelscript/add_on/debugger/debugger.cpp
new file mode 100644
index 00000000..21ed8a36
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/debugger/debugger.cpp
@@ -0,0 +1,841 @@
+#include "debugger.h"
+#include <iostream> // cout
+#include <sstream> // stringstream
+#include <stdlib.h> // atoi
+#include <assert.h> // assert
+
+using namespace std;
+
+BEGIN_AS_NAMESPACE
+
+CDebugger::CDebugger()
+{
+ m_action = CONTINUE;
+ m_lastFunction = 0;
+ m_engine = 0;
+}
+
+CDebugger::~CDebugger()
+{
+ SetEngine(0);
+}
+
+string CDebugger::ToString(void *value, asUINT typeId, int expandMembers, asIScriptEngine *engine)
+{
+ if( value == 0 )
+ return "<null>";
+
+ // If no engine pointer was provided use the default
+ if( engine == 0 )
+ engine = m_engine;
+
+ stringstream s;
+ if( typeId == asTYPEID_VOID )
+ return "<void>";
+ else if( typeId == asTYPEID_BOOL )
+ return *(bool*)value ? "true" : "false";
+ else if( typeId == asTYPEID_INT8 )
+ s << (int)*(signed char*)value;
+ else if( typeId == asTYPEID_INT16 )
+ s << (int)*(signed short*)value;
+ else if( typeId == asTYPEID_INT32 )
+ s << *(signed int*)value;
+ else if( typeId == asTYPEID_INT64 )
+#if defined(_MSC_VER) && _MSC_VER <= 1200
+ s << "{...}"; // MSVC6 doesn't like the << operator for 64bit integer
+#else
+ s << *(asINT64*)value;
+#endif
+ else if( typeId == asTYPEID_UINT8 )
+ s << (unsigned int)*(unsigned char*)value;
+ else if( typeId == asTYPEID_UINT16 )
+ s << (unsigned int)*(unsigned short*)value;
+ else if( typeId == asTYPEID_UINT32 )
+ s << *(unsigned int*)value;
+ else if( typeId == asTYPEID_UINT64 )
+#if defined(_MSC_VER) && _MSC_VER <= 1200
+ s << "{...}"; // MSVC6 doesn't like the << operator for 64bit integer
+#else
+ s << *(asQWORD*)value;
+#endif
+ else if( typeId == asTYPEID_FLOAT )
+ s << *(float*)value;
+ else if( typeId == asTYPEID_DOUBLE )
+ s << *(double*)value;
+ else if( (typeId & asTYPEID_MASK_OBJECT) == 0 )
+ {
+ // The type is an enum
+ s << *(asUINT*)value;
+
+ // Check if the value matches one of the defined enums
+ if( engine )
+ {
+ asITypeInfo *t = engine->GetTypeInfoById(typeId);
+ for( int n = t->GetEnumValueCount(); n-- > 0; )
+ {
+ int enumVal;
+ const char *enumName = t->GetEnumValueByIndex(n, &enumVal);
+ if( enumVal == *(int*)value )
+ {
+ s << ", " << enumName;
+ break;
+ }
+ }
+ }
+ }
+ else if( typeId & asTYPEID_SCRIPTOBJECT )
+ {
+ // Dereference handles, so we can see what it points to
+ if( typeId & asTYPEID_OBJHANDLE )
+ value = *(void**)value;
+
+ asIScriptObject *obj = (asIScriptObject *)value;
+
+ // Print the address of the object
+ s << "{" << obj << "}";
+
+ // Print the members
+ if( obj && expandMembers > 0 )
+ {
+ asITypeInfo *type = obj->GetObjectType();
+ for( asUINT n = 0; n < obj->GetPropertyCount(); n++ )
+ {
+ if( n == 0 )
+ s << " ";
+ else
+ s << ", ";
+
+ s << type->GetPropertyDeclaration(n) << " = " << ToString(obj->GetAddressOfProperty(n), obj->GetPropertyTypeId(n), expandMembers - 1, type->GetEngine());
+ }
+ }
+ }
+ else
+ {
+ // Dereference handles, so we can see what it points to
+ if( typeId & asTYPEID_OBJHANDLE )
+ value = *(void**)value;
+
+ // Print the address for reference types so it will be
+ // possible to see when handles point to the same object
+ if( engine )
+ {
+ asITypeInfo *type = engine->GetTypeInfoById(typeId);
+ if( type->GetFlags() & asOBJ_REF )
+ s << "{" << value << "}";
+
+ if( value )
+ {
+ // Check if there is a registered to-string callback
+ map<const asITypeInfo*, ToStringCallback>::iterator it = m_toStringCallbacks.find(type);
+ if( it == m_toStringCallbacks.end() )
+ {
+ // If the type is a template instance, there might be a
+ // to-string callback for the generic template type
+ if( type->GetFlags() & asOBJ_TEMPLATE )
+ {
+ asITypeInfo *tmplType = engine->GetTypeInfoByName(type->GetName());
+ it = m_toStringCallbacks.find(tmplType);
+ }
+ }
+
+ if( it != m_toStringCallbacks.end() )
+ {
+ if( type->GetFlags() & asOBJ_REF )
+ s << " ";
+
+ // Invoke the callback to get the string representation of this type
+ string str = it->second(value, expandMembers, this);
+ s << str;
+ }
+ }
+ }
+ else
+ s << "{no engine}";
+ }
+
+ return s.str();
+}
+
+void CDebugger::RegisterToStringCallback(const asITypeInfo *ot, ToStringCallback callback)
+{
+ if( m_toStringCallbacks.find(ot) == m_toStringCallbacks.end() )
+ m_toStringCallbacks.insert(map<const asITypeInfo*, ToStringCallback>::value_type(ot, callback));
+}
+
+void CDebugger::LineCallback(asIScriptContext *ctx)
+{
+ assert( ctx );
+
+ // This should never happen, but it doesn't hurt to validate it
+ if( ctx == 0 )
+ return;
+
+ // By default we ignore callbacks when the context is not active.
+ // An application might override this to for example disconnect the
+ // debugger as the execution finished.
+ if( ctx->GetState() != asEXECUTION_ACTIVE )
+ return;
+
+ if( m_action == CONTINUE )
+ {
+ if( !CheckBreakPoint(ctx) )
+ return;
+ }
+ else if( m_action == STEP_OVER )
+ {
+ if( ctx->GetCallstackSize() > m_lastCommandAtStackLevel )
+ {
+ if( !CheckBreakPoint(ctx) )
+ return;
+ }
+ }
+ else if( m_action == STEP_OUT )
+ {
+ if( ctx->GetCallstackSize() >= m_lastCommandAtStackLevel )
+ {
+ if( !CheckBreakPoint(ctx) )
+ return;
+ }
+ }
+ else if( m_action == STEP_INTO )
+ {
+ CheckBreakPoint(ctx);
+
+ // Always break, but we call the check break point anyway
+ // to tell user when break point has been reached
+ }
+
+ stringstream s;
+ const char *file = 0;
+ int lineNbr = ctx->GetLineNumber(0, 0, &file);
+ s << (file ? file : "{unnamed}") << ":" << lineNbr << "; " << ctx->GetFunction()->GetDeclaration() << endl;
+ Output(s.str());
+
+ TakeCommands(ctx);
+}
+
+bool CDebugger::CheckBreakPoint(asIScriptContext *ctx)
+{
+ if( ctx == 0 )
+ return false;
+
+ // TODO: Should cache the break points in a function by checking which possible break points
+ // can be hit when entering a function. If there are no break points in the current function
+ // then there is no need to check every line.
+
+ const char *tmp = 0;
+ int lineNbr = ctx->GetLineNumber(0, 0, &tmp);
+
+ // Consider just filename, not the full path
+ string file = tmp ? tmp : "";
+ size_t r = file.find_last_of("\\/");
+ if( r != string::npos )
+ file = file.substr(r+1);
+
+ // Did we move into a new function?
+ asIScriptFunction *func = ctx->GetFunction();
+ if( m_lastFunction != func )
+ {
+ // Check if any breakpoints need adjusting
+ for( size_t n = 0; n < m_breakPoints.size(); n++ )
+ {
+ // We need to check for a breakpoint at entering the function
+ if( m_breakPoints[n].func )
+ {
+ if( m_breakPoints[n].name == func->GetName() )
+ {
+ stringstream s;
+ s << "Entering function '" << m_breakPoints[n].name << "'. Transforming it into break point" << endl;
+ Output(s.str());
+
+ // Transform the function breakpoint into a file breakpoint
+ m_breakPoints[n].name = file;
+ m_breakPoints[n].lineNbr = lineNbr;
+ m_breakPoints[n].func = false;
+ m_breakPoints[n].needsAdjusting = false;
+ }
+ }
+ // Check if a given breakpoint fall on a line with code or else adjust it to the next line
+ else if( m_breakPoints[n].needsAdjusting &&
+ m_breakPoints[n].name == file )
+ {
+ int line = func->FindNextLineWithCode(m_breakPoints[n].lineNbr);
+ if( line >= 0 )
+ {
+ m_breakPoints[n].needsAdjusting = false;
+ if( line != m_breakPoints[n].lineNbr )
+ {
+ stringstream s;
+ s << "Moving break point " << n << " in file '" << file << "' to next line with code at line " << line << endl;
+ Output(s.str());
+
+ // Move the breakpoint to the next line
+ m_breakPoints[n].lineNbr = line;
+ }
+ }
+ }
+ }
+ }
+ m_lastFunction = func;
+
+ // Determine if there is a breakpoint at the current line
+ for( size_t n = 0; n < m_breakPoints.size(); n++ )
+ {
+ // TODO: do case-less comparison for file name
+
+ // Should we break?
+ if( !m_breakPoints[n].func &&
+ m_breakPoints[n].lineNbr == lineNbr &&
+ m_breakPoints[n].name == file )
+ {
+ stringstream s;
+ s << "Reached break point " << n << " in file '" << file << "' at line " << lineNbr << endl;
+ Output(s.str());
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void CDebugger::TakeCommands(asIScriptContext *ctx)
+{
+ for(;;)
+ {
+ char buf[512];
+
+ Output("[dbg]> ");
+ cin.getline(buf, 512);
+
+ if( InterpretCommand(string(buf), ctx) )
+ break;
+ }
+}
+
+bool CDebugger::InterpretCommand(const string &cmd, asIScriptContext *ctx)
+{
+ if( cmd.length() == 0 ) return true;
+
+ switch( cmd[0] )
+ {
+ case 'c':
+ m_action = CONTINUE;
+ break;
+
+ case 's':
+ m_action = STEP_INTO;
+ break;
+
+ case 'n':
+ if( ctx == 0 )
+ {
+ Output("No script is running\n");
+ return false;
+ }
+ m_action = STEP_OVER;
+ m_lastCommandAtStackLevel = ctx->GetCallstackSize();
+ break;
+
+ case 'o':
+ if( ctx == 0 )
+ {
+ Output("No script is running\n");
+ return false;
+ }
+ m_action = STEP_OUT;
+ m_lastCommandAtStackLevel = ctx->GetCallstackSize();
+ break;
+
+ case 'b':
+ {
+ // Set break point
+ size_t div = cmd.find(':');
+ if( div != string::npos && div > 2 )
+ {
+ string file = cmd.substr(2, div-2);
+ string line = cmd.substr(div+1);
+
+ int nbr = atoi(line.c_str());
+
+ AddFileBreakPoint(file, nbr);
+ }
+ else if( div == string::npos && (div = cmd.find_first_not_of(" \t", 1)) != string::npos )
+ {
+ string func = cmd.substr(div);
+
+ AddFuncBreakPoint(func);
+ }
+ else
+ {
+ Output("Incorrect format for setting break point, expected one of:\n"
+ "b <file name>:<line number>\n"
+ "b <function name>\n");
+ }
+ }
+ // take more commands
+ return false;
+
+ case 'r':
+ {
+ // Remove break point
+ if( cmd.length() > 2 )
+ {
+ string br = cmd.substr(2);
+ if( br == "all" )
+ {
+ m_breakPoints.clear();
+ Output("All break points have been removed\n");
+ }
+ else
+ {
+ int nbr = atoi(br.c_str());
+ if( nbr >= 0 && nbr < (int)m_breakPoints.size() )
+ m_breakPoints.erase(m_breakPoints.begin()+nbr);
+ ListBreakPoints();
+ }
+ }
+ else
+ {
+ Output("Incorrect format for removing break points, expected:\n"
+ "r <all|number of break point>\n");
+ }
+ }
+ // take more commands
+ return false;
+
+ case 'l':
+ {
+ // List something
+ size_t p = cmd.find_first_not_of(" \t", 1);
+ if( p != string::npos )
+ {
+ if( cmd[p] == 'b' )
+ {
+ ListBreakPoints();
+ }
+ else if( cmd[p] == 'v' )
+ {
+ ListLocalVariables(ctx);
+ }
+ else if( cmd[p] == 'g' )
+ {
+ ListGlobalVariables(ctx);
+ }
+ else if( cmd[p] == 'm' )
+ {
+ ListMemberProperties(ctx);
+ }
+ else if( cmd[p] == 's' )
+ {
+ ListStatistics(ctx);
+ }
+ else
+ {
+ Output("Unknown list option, expected one of:\n"
+ "b - breakpoints\n"
+ "v - local variables\n"
+ "m - member properties\n"
+ "g - global variables\n"
+ "s - statistics\n");
+ }
+ }
+ else
+ {
+ Output("Incorrect format for list, expected:\n"
+ "l <list option>\n");
+ }
+ }
+ // take more commands
+ return false;
+
+ case 'h':
+ PrintHelp();
+ // take more commands
+ return false;
+
+ case 'p':
+ {
+ // Print a value
+ size_t p = cmd.find_first_not_of(" \t", 1);
+ if( p != string::npos )
+ {
+ PrintValue(cmd.substr(p), ctx);
+ }
+ else
+ {
+ Output("Incorrect format for print, expected:\n"
+ "p <expression>\n");
+ }
+ }
+ // take more commands
+ return false;
+
+ case 'w':
+ // Where am I?
+ PrintCallstack(ctx);
+ // take more commands
+ return false;
+
+ case 'a':
+ // abort the execution
+ if( ctx == 0 )
+ {
+ Output("No script is running\n");
+ return false;
+ }
+ ctx->Abort();
+ break;
+
+ default:
+ Output("Unknown command\n");
+ // take more commands
+ return false;
+ }
+
+ // Continue execution
+ return true;
+}
+
+void CDebugger::PrintValue(const std::string &expr, asIScriptContext *ctx)
+{
+ if( ctx == 0 )
+ {
+ Output("No script is running\n");
+ return;
+ }
+
+ asIScriptEngine *engine = ctx->GetEngine();
+
+ // Tokenize the input string to get the variable scope and name
+ asUINT len = 0;
+ string scope;
+ string name;
+ string str = expr;
+ asETokenClass t = engine->ParseToken(str.c_str(), 0, &len);
+ while( t == asTC_IDENTIFIER || (t == asTC_KEYWORD && len == 2 && str.compare("::")) )
+ {
+ if( t == asTC_KEYWORD )
+ {
+ if( scope == "" && name == "" )
+ scope = "::"; // global scope
+ else if( scope == "::" || scope == "" )
+ scope = name; // namespace
+ else
+ scope += "::" + name; // nested namespace
+ name = "";
+ }
+ else if( t == asTC_IDENTIFIER )
+ name.assign(str.c_str(), len);
+
+ // Skip the parsed token and get the next one
+ str = str.substr(len);
+ t = engine->ParseToken(str.c_str(), 0, &len);
+ }
+
+ if( name.size() )
+ {
+ // Find the variable
+ void *ptr = 0;
+ int typeId = 0;
+
+ asIScriptFunction *func = ctx->GetFunction();
+ if( !func ) return;
+
+ // skip local variables if a scope was informed
+ if( scope == "" )
+ {
+ // We start from the end, in case the same name is reused in different scopes
+ for( asUINT n = func->GetVarCount(); n-- > 0; )
+ {
+ if( ctx->IsVarInScope(n) && name == ctx->GetVarName(n) )
+ {
+ ptr = ctx->GetAddressOfVar(n);
+ typeId = ctx->GetVarTypeId(n);
+ break;
+ }
+ }
+
+ // Look for class members, if we're in a class method
+ if( !ptr && func->GetObjectType() )
+ {
+ if( name == "this" )
+ {
+ ptr = ctx->GetThisPointer();
+ typeId = ctx->GetThisTypeId();
+ }
+ else
+ {
+ asITypeInfo *type = engine->GetTypeInfoById(ctx->GetThisTypeId());
+ for( asUINT n = 0; n < type->GetPropertyCount(); n++ )
+ {
+ const char *propName = 0;
+ int offset = 0;
+ bool isReference = 0;
+ int compositeOffset = 0;
+ bool isCompositeIndirect = false;
+ type->GetProperty(n, &propName, &typeId, 0, 0, &offset, &isReference, 0, &compositeOffset, &isCompositeIndirect);
+ if( name == propName )
+ {
+ ptr = (void*)(((asBYTE*)ctx->GetThisPointer())+compositeOffset);
+ if (isCompositeIndirect) ptr = *(void**)ptr;
+ ptr = (void*)(((asBYTE*)ptr) + offset);
+ if( isReference ) ptr = *(void**)ptr;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Look for global variables
+ if( !ptr )
+ {
+ if( scope == "" )
+ {
+ // If no explicit scope was informed then use the namespace of the current function by default
+ scope = func->GetNamespace();
+ }
+ else if( scope == "::" )
+ {
+ // The global namespace will be empty
+ scope = "";
+ }
+
+ asIScriptModule *mod = func->GetModule();
+ if( mod )
+ {
+ for( asUINT n = 0; n < mod->GetGlobalVarCount(); n++ )
+ {
+ const char *varName = 0, *nameSpace = 0;
+ mod->GetGlobalVar(n, &varName, &nameSpace, &typeId);
+
+ // Check if both name and namespace match
+ if( name == varName && scope == nameSpace )
+ {
+ ptr = mod->GetAddressOfGlobalVar(n);
+ break;
+ }
+ }
+ }
+ }
+
+ if( ptr )
+ {
+ // TODO: If there is a . after the identifier, check for members
+ // TODO: If there is a [ after the identifier try to call the 'opIndex(expr) const' method
+
+ stringstream s;
+ // TODO: Allow user to set if members should be expanded
+ // Expand members by default to 3 recursive levels only
+ s << ToString(ptr, typeId, 3, engine) << endl;
+ Output(s.str());
+ }
+ }
+ else
+ {
+ Output("Invalid expression. Expected identifier\n");
+ }
+}
+
+void CDebugger::ListBreakPoints()
+{
+ // List all break points
+ stringstream s;
+ for( size_t b = 0; b < m_breakPoints.size(); b++ )
+ if( m_breakPoints[b].func )
+ s << b << " - " << m_breakPoints[b].name << endl;
+ else
+ s << b << " - " << m_breakPoints[b].name << ":" << m_breakPoints[b].lineNbr << endl;
+ Output(s.str());
+}
+
+void CDebugger::ListMemberProperties(asIScriptContext *ctx)
+{
+ if( ctx == 0 )
+ {
+ Output("No script is running\n");
+ return;
+ }
+
+ void *ptr = ctx->GetThisPointer();
+ if( ptr )
+ {
+ stringstream s;
+ // TODO: Allow user to define if members should be expanded or not
+ // Expand members by default to 3 recursive levels only
+ s << "this = " << ToString(ptr, ctx->GetThisTypeId(), 3, ctx->GetEngine()) << endl;
+ Output(s.str());
+ }
+}
+
+void CDebugger::ListLocalVariables(asIScriptContext *ctx)
+{
+ if( ctx == 0 )
+ {
+ Output("No script is running\n");
+ return;
+ }
+
+ asIScriptFunction *func = ctx->GetFunction();
+ if( !func ) return;
+
+ stringstream s;
+ for( asUINT n = 0; n < func->GetVarCount(); n++ )
+ {
+ if( ctx->IsVarInScope(n) )
+ {
+ // TODO: Allow user to set if members should be expanded or not
+ // Expand members by default to 3 recursive levels only
+ s << func->GetVarDecl(n) << " = " << ToString(ctx->GetAddressOfVar(n), ctx->GetVarTypeId(n), 3, ctx->GetEngine()) << endl;
+ }
+ }
+ Output(s.str());
+}
+
+void CDebugger::ListGlobalVariables(asIScriptContext *ctx)
+{
+ if( ctx == 0 )
+ {
+ Output("No script is running\n");
+ return;
+ }
+
+ // Determine the current module from the function
+ asIScriptFunction *func = ctx->GetFunction();
+ if( !func ) return;
+
+ asIScriptModule *mod = func->GetModule();
+ if( !mod ) return;
+
+ stringstream s;
+ for( asUINT n = 0; n < mod->GetGlobalVarCount(); n++ )
+ {
+ int typeId = 0;
+ mod->GetGlobalVar(n, 0, 0, &typeId);
+ // TODO: Allow user to set how many recursive expansions should be done
+ // Expand members by default to 3 recursive levels only
+ s << mod->GetGlobalVarDeclaration(n) << " = " << ToString(mod->GetAddressOfGlobalVar(n), typeId, 3, ctx->GetEngine()) << endl;
+ }
+ Output(s.str());
+}
+
+void CDebugger::ListStatistics(asIScriptContext *ctx)
+{
+ if( ctx == 0 )
+ {
+ Output("No script is running\n");
+ return;
+ }
+
+ asIScriptEngine *engine = ctx->GetEngine();
+
+ asUINT gcCurrSize, gcTotalDestr, gcTotalDet, gcNewObjects, gcTotalNewDestr;
+ engine->GetGCStatistics(&gcCurrSize, &gcTotalDestr, &gcTotalDet, &gcNewObjects, &gcTotalNewDestr);
+
+ stringstream s;
+ s << "Garbage collector:" << endl;
+ s << " current size: " << gcCurrSize << endl;
+ s << " total destroyed: " << gcTotalDestr << endl;
+ s << " total detected: " << gcTotalDet << endl;
+ s << " new objects: " << gcNewObjects << endl;
+ s << " new objects destroyed: " << gcTotalNewDestr << endl;
+
+ Output(s.str());
+}
+
+void CDebugger::PrintCallstack(asIScriptContext *ctx)
+{
+ if( ctx == 0 )
+ {
+ Output("No script is running\n");
+ return;
+ }
+
+ stringstream s;
+ const char *file = 0;
+ int lineNbr = 0;
+ for( asUINT n = 0; n < ctx->GetCallstackSize(); n++ )
+ {
+ lineNbr = ctx->GetLineNumber(n, 0, &file);
+ s << file << ":" << lineNbr << "; " << ctx->GetFunction(n)->GetDeclaration() << endl;
+ }
+ Output(s.str());
+}
+
+void CDebugger::AddFuncBreakPoint(const string &func)
+{
+ // Trim the function name
+ size_t b = func.find_first_not_of(" \t");
+ size_t e = func.find_last_not_of(" \t");
+ string actual = func.substr(b, e != string::npos ? e-b+1 : string::npos);
+
+ stringstream s;
+ s << "Adding deferred break point for function '" << actual << "'" << endl;
+ Output(s.str());
+
+ BreakPoint bp(actual, 0, true);
+ m_breakPoints.push_back(bp);
+}
+
+void CDebugger::AddFileBreakPoint(const string &file, int lineNbr)
+{
+ // Store just file name, not entire path
+ size_t r = file.find_last_of("\\/");
+ string actual;
+ if( r != string::npos )
+ actual = file.substr(r+1);
+ else
+ actual = file;
+
+ // Trim the file name
+ size_t b = actual.find_first_not_of(" \t");
+ size_t e = actual.find_last_not_of(" \t");
+ actual = actual.substr(b, e != string::npos ? e-b+1 : string::npos);
+
+ stringstream s;
+ s << "Setting break point in file '" << actual << "' at line " << lineNbr << endl;
+ Output(s.str());
+
+ BreakPoint bp(actual, lineNbr, false);
+ m_breakPoints.push_back(bp);
+}
+
+void CDebugger::PrintHelp()
+{
+ Output("c - Continue\n"
+ "s - Step into\n"
+ "n - Next step\n"
+ "o - Step out\n"
+ "b - Set break point\n"
+ "l - List various things\n"
+ "r - Remove break point\n"
+ "p - Print value\n"
+ "w - Where am I?\n"
+ "a - Abort execution\n"
+ "h - Print this help text\n");
+}
+
+void CDebugger::Output(const string &str)
+{
+ // By default we just output to stdout
+ cout << str;
+}
+
+void CDebugger::SetEngine(asIScriptEngine *engine)
+{
+ if( m_engine != engine )
+ {
+ if( m_engine )
+ m_engine->Release();
+ m_engine = engine;
+ if( m_engine )
+ m_engine->AddRef();
+ }
+}
+
+asIScriptEngine *CDebugger::GetEngine()
+{
+ return m_engine;
+}
+
+END_AS_NAMESPACE
diff --git a/Source/Scripting/angelscript/add_on/debugger/debugger.h b/Source/Scripting/angelscript/add_on/debugger/debugger.h
new file mode 100644
index 00000000..3bb560c7
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/debugger/debugger.h
@@ -0,0 +1,87 @@
+#ifndef DEBUGGER_H
+#define DEBUGGER_H
+
+#ifndef ANGELSCRIPT_H
+// Avoid having to inform include path if header is already include before
+#include <angelscript.h>
+#endif
+
+#include <string>
+#include <vector>
+#include <map>
+
+BEGIN_AS_NAMESPACE
+
+class CDebugger
+{
+public:
+ CDebugger();
+ virtual ~CDebugger();
+
+ // Register callbacks to handle to-string conversions of application types
+ // The expandMembersLevel is a counter for how many recursive levels the members should be expanded.
+ // If the object that is being converted to a string has members of its own the callback should call
+ // the debugger's ToString passing in expandMembersLevel - 1.
+ typedef std::string (*ToStringCallback)(void *obj, int expandMembersLevel, CDebugger *dbg);
+ virtual void RegisterToStringCallback(const asITypeInfo *ti, ToStringCallback callback);
+
+ // User interaction
+ virtual void TakeCommands(asIScriptContext *ctx);
+ virtual void Output(const std::string &str);
+
+ // Line callback invoked by context
+ virtual void LineCallback(asIScriptContext *ctx);
+
+ // Commands
+ virtual void PrintHelp();
+ virtual void AddFileBreakPoint(const std::string &file, int lineNbr);
+ virtual void AddFuncBreakPoint(const std::string &func);
+ virtual void ListBreakPoints();
+ virtual void ListLocalVariables(asIScriptContext *ctx);
+ virtual void ListGlobalVariables(asIScriptContext *ctx);
+ virtual void ListMemberProperties(asIScriptContext *ctx);
+ virtual void ListStatistics(asIScriptContext *ctx);
+ virtual void PrintCallstack(asIScriptContext *ctx);
+ virtual void PrintValue(const std::string &expr, asIScriptContext *ctx);
+
+ // Helpers
+ virtual bool InterpretCommand(const std::string &cmd, asIScriptContext *ctx);
+ virtual bool CheckBreakPoint(asIScriptContext *ctx);
+ virtual std::string ToString(void *value, asUINT typeId, int expandMembersLevel, asIScriptEngine *engine);
+
+ // Optionally set the engine pointer in the debugger so it can be retrieved
+ // by callbacks that need it. This will hold a reference to the engine.
+ virtual void SetEngine(asIScriptEngine *engine);
+ virtual asIScriptEngine *GetEngine();
+
+protected:
+ enum DebugAction
+ {
+ CONTINUE, // continue until next break point
+ STEP_INTO, // stop at next instruction
+ STEP_OVER, // stop at next instruction, skipping called functions
+ STEP_OUT // run until returning from current function
+ };
+ DebugAction m_action;
+ asUINT m_lastCommandAtStackLevel;
+ asIScriptFunction *m_lastFunction;
+
+ struct BreakPoint
+ {
+ BreakPoint(std::string f, int n, bool _func) : name(f), lineNbr(n), func(_func), needsAdjusting(true) {}
+ std::string name;
+ int lineNbr;
+ bool func;
+ bool needsAdjusting;
+ };
+ std::vector<BreakPoint> m_breakPoints;
+
+ asIScriptEngine *m_engine;
+
+ // Registered callbacks for converting types to strings
+ std::map<const asITypeInfo*, ToStringCallback> m_toStringCallbacks;
+};
+
+END_AS_NAMESPACE
+
+#endif \ No newline at end of file
diff --git a/Source/Scripting/angelscript/add_on/scriptany/scriptany.cpp b/Source/Scripting/angelscript/add_on/scriptany/scriptany.cpp
new file mode 100644
index 00000000..b0451f98
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/scriptany/scriptany.cpp
@@ -0,0 +1,480 @@
+#include "scriptany.h"
+#include <new>
+#include <assert.h>
+#include <string.h>
+
+BEGIN_AS_NAMESPACE
+
+// We'll use the generic interface for the factories as we need the engine pointer
+static void ScriptAnyFactory_Generic(asIScriptGeneric *gen)
+{
+ asIScriptEngine *engine = gen->GetEngine();
+
+ *(CScriptAny**)gen->GetAddressOfReturnLocation() = new CScriptAny(engine);
+}
+
+static void ScriptAnyFactory2_Generic(asIScriptGeneric *gen)
+{
+ asIScriptEngine *engine = gen->GetEngine();
+ void *ref = (void*)gen->GetArgAddress(0);
+ int refType = gen->GetArgTypeId(0);
+
+ *(CScriptAny**)gen->GetAddressOfReturnLocation() = new CScriptAny(ref,refType,engine);
+}
+
+static CScriptAny &ScriptAnyAssignment(CScriptAny *other, CScriptAny *self)
+{
+ return *self = *other;
+}
+
+static void ScriptAnyAssignment_Generic(asIScriptGeneric *gen)
+{
+ CScriptAny *other = (CScriptAny*)gen->GetArgObject(0);
+ CScriptAny *self = (CScriptAny*)gen->GetObject();
+
+ *self = *other;
+
+ gen->SetReturnObject(self);
+}
+
+static void ScriptAny_Store_Generic(asIScriptGeneric *gen)
+{
+ void *ref = (void*)gen->GetArgAddress(0);
+ int refTypeId = gen->GetArgTypeId(0);
+ CScriptAny *self = (CScriptAny*)gen->GetObject();
+
+ self->Store(ref, refTypeId);
+}
+
+static void ScriptAny_StoreInt_Generic(asIScriptGeneric *gen)
+{
+ asINT64 *ref = (asINT64*)gen->GetArgAddress(0);
+ CScriptAny *self = (CScriptAny*)gen->GetObject();
+
+ self->Store(*ref);
+}
+
+static void ScriptAny_StoreFlt_Generic(asIScriptGeneric *gen)
+{
+ double *ref = (double*)gen->GetArgAddress(0);
+ CScriptAny *self = (CScriptAny*)gen->GetObject();
+
+ self->Store(*ref);
+}
+
+static void ScriptAny_Retrieve_Generic(asIScriptGeneric *gen)
+{
+ void *ref = (void*)gen->GetArgAddress(0);
+ int refTypeId = gen->GetArgTypeId(0);
+ CScriptAny *self = (CScriptAny*)gen->GetObject();
+
+ *(bool*)gen->GetAddressOfReturnLocation() = self->Retrieve(ref, refTypeId);
+}
+
+static void ScriptAny_RetrieveInt_Generic(asIScriptGeneric *gen)
+{
+ asINT64 *ref = (asINT64*)gen->GetArgAddress(0);
+ CScriptAny *self = (CScriptAny*)gen->GetObject();
+
+ *(bool*)gen->GetAddressOfReturnLocation() = self->Retrieve(*ref);
+}
+
+static void ScriptAny_RetrieveFlt_Generic(asIScriptGeneric *gen)
+{
+ double *ref = (double*)gen->GetArgAddress(0);
+ CScriptAny *self = (CScriptAny*)gen->GetObject();
+
+ *(bool*)gen->GetAddressOfReturnLocation() = self->Retrieve(*ref);
+}
+
+static void ScriptAny_AddRef_Generic(asIScriptGeneric *gen)
+{
+ CScriptAny *self = (CScriptAny*)gen->GetObject();
+ self->AddRef();
+}
+
+static void ScriptAny_Release_Generic(asIScriptGeneric *gen)
+{
+ CScriptAny *self = (CScriptAny*)gen->GetObject();
+ self->Release();
+}
+
+static void ScriptAny_GetRefCount_Generic(asIScriptGeneric *gen)
+{
+ CScriptAny *self = (CScriptAny*)gen->GetObject();
+ *(int*)gen->GetAddressOfReturnLocation() = self->GetRefCount();
+}
+
+static void ScriptAny_SetFlag_Generic(asIScriptGeneric *gen)
+{
+ CScriptAny *self = (CScriptAny*)gen->GetObject();
+ self->SetFlag();
+}
+
+static void ScriptAny_GetFlag_Generic(asIScriptGeneric *gen)
+{
+ CScriptAny *self = (CScriptAny*)gen->GetObject();
+ *(bool*)gen->GetAddressOfReturnLocation() = self->GetFlag();
+}
+
+static void ScriptAny_EnumReferences_Generic(asIScriptGeneric *gen)
+{
+ CScriptAny *self = (CScriptAny*)gen->GetObject();
+ asIScriptEngine *engine = *(asIScriptEngine**)gen->GetAddressOfArg(0);
+ self->EnumReferences(engine);
+}
+
+static void ScriptAny_ReleaseAllHandles_Generic(asIScriptGeneric *gen)
+{
+ CScriptAny *self = (CScriptAny*)gen->GetObject();
+ asIScriptEngine *engine = *(asIScriptEngine**)gen->GetAddressOfArg(0);
+ self->ReleaseAllHandles(engine);
+}
+
+void RegisterScriptAny(asIScriptEngine *engine)
+{
+ if( strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") )
+ RegisterScriptAny_Generic(engine);
+ else
+ RegisterScriptAny_Native(engine);
+}
+
+void RegisterScriptAny_Native(asIScriptEngine *engine)
+{
+ int r;
+ r = engine->RegisterObjectType("any", sizeof(CScriptAny), asOBJ_REF | asOBJ_GC); assert( r >= 0 );
+
+ // We'll use the generic interface for the constructor as we need the engine pointer
+ r = engine->RegisterObjectBehaviour("any", asBEHAVE_FACTORY, "any@ f()", asFUNCTION(ScriptAnyFactory_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("any", asBEHAVE_FACTORY, "any@ f(?&in)", asFUNCTION(ScriptAnyFactory2_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("any", asBEHAVE_FACTORY, "any@ f(const int64&in)", asFUNCTION(ScriptAnyFactory2_Generic), asCALL_GENERIC); assert(r >= 0);
+ r = engine->RegisterObjectBehaviour("any", asBEHAVE_FACTORY, "any@ f(const double&in)", asFUNCTION(ScriptAnyFactory2_Generic), asCALL_GENERIC); assert(r >= 0);
+
+ r = engine->RegisterObjectBehaviour("any", asBEHAVE_ADDREF, "void f()", asMETHOD(CScriptAny,AddRef), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("any", asBEHAVE_RELEASE, "void f()", asMETHOD(CScriptAny,Release), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("any", "any &opAssign(any&in)", asFUNCTION(ScriptAnyAssignment), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("any", "void store(?&in)", asMETHODPR(CScriptAny,Store,(void*,int),void), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("any", "void store(const int64&in)", asMETHODPR(CScriptAny,Store,(asINT64&),void), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("any", "void store(const double&in)", asMETHODPR(CScriptAny,Store,(double&),void), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("any", "bool retrieve(?&out)", asMETHODPR(CScriptAny,Retrieve,(void*,int) const,bool), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("any", "bool retrieve(int64&out)", asMETHODPR(CScriptAny,Retrieve,(asINT64&) const,bool), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("any", "bool retrieve(double&out)", asMETHODPR(CScriptAny,Retrieve,(double&) const,bool), asCALL_THISCALL); assert( r >= 0 );
+
+ // Register GC behaviours
+ r = engine->RegisterObjectBehaviour("any", asBEHAVE_GETREFCOUNT, "int f()", asMETHOD(CScriptAny,GetRefCount), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("any", asBEHAVE_SETGCFLAG, "void f()", asMETHOD(CScriptAny,SetFlag), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("any", asBEHAVE_GETGCFLAG, "bool f()", asMETHOD(CScriptAny,GetFlag), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("any", asBEHAVE_ENUMREFS, "void f(int&in)", asMETHOD(CScriptAny,EnumReferences), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("any", asBEHAVE_RELEASEREFS, "void f(int&in)", asMETHOD(CScriptAny,ReleaseAllHandles), asCALL_THISCALL); assert( r >= 0 );
+}
+
+void RegisterScriptAny_Generic(asIScriptEngine *engine)
+{
+ int r;
+ r = engine->RegisterObjectType("any", sizeof(CScriptAny), asOBJ_REF | asOBJ_GC); assert( r >= 0 );
+
+ // We'll use the generic interface for the constructor as we need the engine pointer
+ r = engine->RegisterObjectBehaviour("any", asBEHAVE_FACTORY, "any@ f()", asFUNCTION(ScriptAnyFactory_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("any", asBEHAVE_FACTORY, "any@ f(?&in)", asFUNCTION(ScriptAnyFactory2_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("any", asBEHAVE_FACTORY, "any@ f(const int64&in)", asFUNCTION(ScriptAnyFactory2_Generic), asCALL_GENERIC); assert(r >= 0);
+ r = engine->RegisterObjectBehaviour("any", asBEHAVE_FACTORY, "any@ f(const double&in)", asFUNCTION(ScriptAnyFactory2_Generic), asCALL_GENERIC); assert(r >= 0);
+
+ r = engine->RegisterObjectBehaviour("any", asBEHAVE_ADDREF, "void f()", asFUNCTION(ScriptAny_AddRef_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("any", asBEHAVE_RELEASE, "void f()", asFUNCTION(ScriptAny_Release_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("any", "any &opAssign(any&in)", asFUNCTION(ScriptAnyAssignment_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("any", "void store(?&in)", asFUNCTION(ScriptAny_Store_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("any", "void store(const int64&in)", asFUNCTION(ScriptAny_StoreInt_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("any", "void store(const double&in)", asFUNCTION(ScriptAny_StoreFlt_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("any", "bool retrieve(?&out) const", asFUNCTION(ScriptAny_Retrieve_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("any", "bool retrieve(int64&out) const", asFUNCTION(ScriptAny_RetrieveInt_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("any", "bool retrieve(double&out) const", asFUNCTION(ScriptAny_RetrieveFlt_Generic), asCALL_GENERIC); assert( r >= 0 );
+
+ // Register GC behaviours
+ r = engine->RegisterObjectBehaviour("any", asBEHAVE_GETREFCOUNT, "int f()", asFUNCTION(ScriptAny_GetRefCount_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("any", asBEHAVE_SETGCFLAG, "void f()", asFUNCTION(ScriptAny_SetFlag_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("any", asBEHAVE_GETGCFLAG, "bool f()", asFUNCTION(ScriptAny_GetFlag_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("any", asBEHAVE_ENUMREFS, "void f(int&in)", asFUNCTION(ScriptAny_EnumReferences_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("any", asBEHAVE_RELEASEREFS, "void f(int&in)", asFUNCTION(ScriptAny_ReleaseAllHandles_Generic), asCALL_GENERIC); assert( r >= 0 );
+}
+
+
+CScriptAny &CScriptAny::operator=(const CScriptAny &other)
+{
+ // Hold on to the object type reference so it isn't destroyed too early
+ if( (other.value.typeId & asTYPEID_MASK_OBJECT) )
+ {
+ asITypeInfo *ti = engine->GetTypeInfoById(other.value.typeId);
+ if( ti )
+ ti->AddRef();
+ }
+
+ FreeObject();
+
+ value.typeId = other.value.typeId;
+ if( value.typeId & asTYPEID_OBJHANDLE )
+ {
+ // For handles, copy the pointer and increment the reference count
+ value.valueObj = other.value.valueObj;
+ engine->AddRefScriptObject(value.valueObj, engine->GetTypeInfoById(value.typeId));
+ }
+ else if( value.typeId & asTYPEID_MASK_OBJECT )
+ {
+ // Create a copy of the object
+ value.valueObj = engine->CreateScriptObjectCopy(other.value.valueObj, engine->GetTypeInfoById(value.typeId));
+ }
+ else
+ {
+ // Primitives can be copied directly
+ value.valueInt = other.value.valueInt;
+ }
+
+ return *this;
+}
+
+int CScriptAny::CopyFrom(const CScriptAny *other)
+{
+ if( other == 0 ) return asINVALID_ARG;
+
+ *this = *(CScriptAny*)other;
+
+ return 0;
+}
+
+CScriptAny::CScriptAny(asIScriptEngine *engine)
+{
+ this->engine = engine;
+ refCount = 1;
+ gcFlag = false;
+
+ value.typeId = 0;
+ value.valueInt = 0;
+
+ // Notify the garbage collector of this object
+ engine->NotifyGarbageCollectorOfNewObject(this, engine->GetTypeInfoByName("any"));
+}
+
+CScriptAny::CScriptAny(void *ref, int refTypeId, asIScriptEngine *engine)
+{
+ this->engine = engine;
+ refCount = 1;
+ gcFlag = false;
+
+ value.typeId = 0;
+ value.valueInt = 0;
+
+ // Notify the garbage collector of this object
+ engine->NotifyGarbageCollectorOfNewObject(this, engine->GetTypeInfoByName("any"));
+
+ Store(ref, refTypeId);
+}
+
+CScriptAny::~CScriptAny()
+{
+ FreeObject();
+}
+
+void CScriptAny::Store(void *ref, int refTypeId)
+{
+ // This method is not expected to be used for primitive types, except for bool, int64, or double
+ assert( refTypeId > asTYPEID_DOUBLE || refTypeId == asTYPEID_VOID || refTypeId == asTYPEID_BOOL || refTypeId == asTYPEID_INT64 || refTypeId == asTYPEID_DOUBLE );
+
+ // Hold on to the object type reference so it isn't destroyed too early
+ if( (refTypeId & asTYPEID_MASK_OBJECT) )
+ {
+ asITypeInfo *ti = engine->GetTypeInfoById(refTypeId);
+ if( ti )
+ ti->AddRef();
+ }
+
+ FreeObject();
+
+ value.typeId = refTypeId;
+ if( value.typeId & asTYPEID_OBJHANDLE )
+ {
+ // We're receiving a reference to the handle, so we need to dereference it
+ value.valueObj = *(void**)ref;
+ engine->AddRefScriptObject(value.valueObj, engine->GetTypeInfoById(value.typeId));
+ }
+ else if( value.typeId & asTYPEID_MASK_OBJECT )
+ {
+ // Create a copy of the object
+ value.valueObj = engine->CreateScriptObjectCopy(ref, engine->GetTypeInfoById(value.typeId));
+ }
+ else
+ {
+ // Primitives can be copied directly
+ value.valueInt = 0;
+
+ // Copy the primitive value
+ // We receive a pointer to the value.
+ int size = engine->GetSizeOfPrimitiveType(value.typeId);
+ memcpy(&value.valueInt, ref, size);
+ }
+}
+
+void CScriptAny::Store(double &ref)
+{
+ Store(&ref, asTYPEID_DOUBLE);
+}
+
+void CScriptAny::Store(asINT64 &ref)
+{
+ Store(&ref, asTYPEID_INT64);
+}
+
+
+bool CScriptAny::Retrieve(void *ref, int refTypeId) const
+{
+ // This method is not expected to be used for primitive types, except for bool, int64, or double
+ assert( refTypeId > asTYPEID_DOUBLE || refTypeId == asTYPEID_BOOL || refTypeId == asTYPEID_INT64 || refTypeId == asTYPEID_DOUBLE );
+
+ if( refTypeId & asTYPEID_OBJHANDLE )
+ {
+ // Is the handle type compatible with the stored value?
+
+ // A handle can be retrieved if the stored type is a handle of same or compatible type
+ // or if the stored type is an object that implements the interface that the handle refer to.
+ if( (value.typeId & asTYPEID_MASK_OBJECT) )
+ {
+ // Don't allow the retrieval if the stored handle is to a const object but not the wanted handle
+ if( (value.typeId & asTYPEID_HANDLETOCONST) && !(refTypeId & asTYPEID_HANDLETOCONST) )
+ return false;
+
+ // RefCastObject will increment the refCount of the returned pointer if successful
+ engine->RefCastObject(value.valueObj, engine->GetTypeInfoById(value.typeId), engine->GetTypeInfoById(refTypeId), reinterpret_cast<void**>(ref));
+ if( *(asPWORD*)ref == 0 )
+ return false;
+ return true;
+ }
+ }
+ else if( refTypeId & asTYPEID_MASK_OBJECT )
+ {
+ // Is the object type compatible with the stored value?
+
+ // Copy the object into the given reference
+ if( value.typeId == refTypeId )
+ {
+ engine->AssignScriptObject(ref, value.valueObj, engine->GetTypeInfoById(value.typeId));
+ return true;
+ }
+ }
+ else
+ {
+ // Is the primitive type compatible with the stored value?
+
+ if( value.typeId == refTypeId )
+ {
+ int size = engine->GetSizeOfPrimitiveType(refTypeId);
+ memcpy(ref, &value.valueInt, size);
+ return true;
+ }
+
+ // We know all numbers are stored as either int64 or double, since we register overloaded functions for those
+ if( value.typeId == asTYPEID_INT64 && refTypeId == asTYPEID_DOUBLE )
+ {
+ *(double*)ref = double(value.valueInt);
+ return true;
+ }
+ else if( value.typeId == asTYPEID_DOUBLE && refTypeId == asTYPEID_INT64 )
+ {
+ *(asINT64*)ref = asINT64(value.valueFlt);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool CScriptAny::Retrieve(asINT64 &outValue) const
+{
+ return Retrieve(&outValue, asTYPEID_INT64);
+}
+
+bool CScriptAny::Retrieve(double &outValue) const
+{
+ return Retrieve(&outValue, asTYPEID_DOUBLE);
+}
+
+int CScriptAny::GetTypeId() const
+{
+ return value.typeId;
+}
+
+void CScriptAny::FreeObject()
+{
+ // If it is a handle or a ref counted object, call release
+ if( value.typeId & asTYPEID_MASK_OBJECT )
+ {
+ // Let the engine release the object
+ asITypeInfo *ti = engine->GetTypeInfoById(value.typeId);
+ engine->ReleaseScriptObject(value.valueObj, ti);
+
+ // Release the object type info
+ if( ti )
+ ti->Release();
+
+ value.valueObj = 0;
+ value.typeId = 0;
+ }
+
+ // For primitives, there's nothing to do
+}
+
+
+void CScriptAny::EnumReferences(asIScriptEngine *inEngine)
+{
+ // If we're holding a reference, we'll notify the garbage collector of it
+ if( value.valueObj && (value.typeId & asTYPEID_MASK_OBJECT) )
+ {
+ inEngine->GCEnumCallback(value.valueObj);
+
+ // The object type itself is also garbage collected
+ asITypeInfo *ti = inEngine->GetTypeInfoById(value.typeId);
+ if( ti )
+ inEngine->GCEnumCallback(ti);
+ }
+}
+
+void CScriptAny::ReleaseAllHandles(asIScriptEngine * /*engine*/)
+{
+ FreeObject();
+}
+
+int CScriptAny::AddRef() const
+{
+ // Increase counter and clear flag set by GC
+ gcFlag = false;
+ return asAtomicInc(refCount);
+}
+
+int CScriptAny::Release() const
+{
+ // Decrease the ref counter
+ gcFlag = false;
+ if( asAtomicDec(refCount) == 0 )
+ {
+ // Delete this object as no more references to it exists
+ delete this;
+ return 0;
+ }
+
+ return refCount;
+}
+
+int CScriptAny::GetRefCount()
+{
+ return refCount;
+}
+
+void CScriptAny::SetFlag()
+{
+ gcFlag = true;
+}
+
+bool CScriptAny::GetFlag()
+{
+ return gcFlag;
+}
+
+
+END_AS_NAMESPACE
diff --git a/Source/Scripting/angelscript/add_on/scriptany/scriptany.h b/Source/Scripting/angelscript/add_on/scriptany/scriptany.h
new file mode 100644
index 00000000..4b23f576
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/scriptany/scriptany.h
@@ -0,0 +1,76 @@
+#ifndef SCRIPTANY_H
+#define SCRIPTANY_H
+
+#ifndef ANGELSCRIPT_H
+// Avoid having to inform include path if header is already include before
+#include <angelscript.h>
+#endif
+
+
+BEGIN_AS_NAMESPACE
+
+class CScriptAny
+{
+public:
+ // Constructors
+ CScriptAny(asIScriptEngine *engine);
+ CScriptAny(void *ref, int refTypeId, asIScriptEngine *engine);
+
+ // Memory management
+ int AddRef() const;
+ int Release() const;
+
+ // Copy the stored value from another any object
+ CScriptAny &operator=(const CScriptAny&);
+ int CopyFrom(const CScriptAny *other);
+
+ // Store the value, either as variable type, integer number, or real number
+ void Store(void *ref, int refTypeId);
+ void Store(asINT64 &value);
+ void Store(double &value);
+
+ // Retrieve the stored value, either as variable type, integer number, or real number
+ bool Retrieve(void *ref, int refTypeId) const;
+ bool Retrieve(asINT64 &value) const;
+ bool Retrieve(double &value) const;
+
+ // Get the type id of the stored value
+ int GetTypeId() const;
+
+ // GC methods
+ int GetRefCount();
+ void SetFlag();
+ bool GetFlag();
+ void EnumReferences(asIScriptEngine *engine);
+ void ReleaseAllHandles(asIScriptEngine *engine);
+
+protected:
+ virtual ~CScriptAny();
+ void FreeObject();
+
+ mutable int refCount;
+ mutable bool gcFlag;
+ asIScriptEngine *engine;
+
+ // The structure for holding the values
+ struct valueStruct
+ {
+ union
+ {
+ asINT64 valueInt;
+ double valueFlt;
+ void *valueObj;
+ };
+ int typeId;
+ };
+
+ valueStruct value;
+};
+
+void RegisterScriptAny(asIScriptEngine *engine);
+void RegisterScriptAny_Native(asIScriptEngine *engine);
+void RegisterScriptAny_Generic(asIScriptEngine *engine);
+
+END_AS_NAMESPACE
+
+#endif
diff --git a/Source/Scripting/angelscript/add_on/scriptarray/scriptarray.cpp b/Source/Scripting/angelscript/add_on/scriptarray/scriptarray.cpp
new file mode 100644
index 00000000..d3852ad3
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/scriptarray/scriptarray.cpp
@@ -0,0 +1,2144 @@
+#include <new>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdio.h> // sprintf
+#include <string>
+
+#include "scriptarray.h"
+
+using namespace std;
+
+BEGIN_AS_NAMESPACE
+
+// This macro is used to avoid warnings about unused variables.
+// Usually where the variables are only used in debug mode.
+#define UNUSED_VAR(x) (void)(x)
+
+// Set the default memory routines
+// Use the angelscript engine's memory routines by default
+static asALLOCFUNC_t userAlloc = asAllocMem;
+static asFREEFUNC_t userFree = asFreeMem;
+
+// Allows the application to set which memory routines should be used by the array object
+void CScriptArray::SetMemoryFunctions(asALLOCFUNC_t allocFunc, asFREEFUNC_t freeFunc)
+{
+ userAlloc = allocFunc;
+ userFree = freeFunc;
+}
+
+static void RegisterScriptArray_Native(asIScriptEngine *engine);
+static void RegisterScriptArray_Generic(asIScriptEngine *engine);
+
+struct SArrayBuffer
+{
+ asDWORD maxElements;
+ asDWORD numElements;
+ asBYTE data[1];
+};
+
+struct SArrayCache
+{
+ asIScriptFunction *cmpFunc;
+ asIScriptFunction *eqFunc;
+ int cmpFuncReturnCode; // To allow better error message in case of multiple matches
+ int eqFuncReturnCode;
+};
+
+// We just define a number here that we assume nobody else is using for
+// object type user data. The add-ons have reserved the numbers 1000
+// through 1999 for this purpose, so we should be fine.
+const asPWORD ARRAY_CACHE = 1000;
+
+static void CleanupTypeInfoArrayCache(asITypeInfo *type)
+{
+ SArrayCache *cache = reinterpret_cast<SArrayCache*>(type->GetUserData(ARRAY_CACHE));
+ if( cache )
+ {
+ cache->~SArrayCache();
+ userFree(cache);
+ }
+}
+
+CScriptArray* CScriptArray::Create(asITypeInfo *ti, asUINT length)
+{
+ // Allocate the memory
+ void *mem = userAlloc(sizeof(CScriptArray));
+ if( mem == 0 )
+ {
+ asIScriptContext *ctx = asGetActiveContext();
+ if( ctx )
+ ctx->SetException("Out of memory");
+
+ return 0;
+ }
+
+ // Initialize the object
+ CScriptArray *a = new(mem) CScriptArray(length, ti);
+
+ return a;
+}
+
+CScriptArray* CScriptArray::Create(asITypeInfo *ti, void *initList)
+{
+ // Allocate the memory
+ void *mem = userAlloc(sizeof(CScriptArray));
+ if( mem == 0 )
+ {
+ asIScriptContext *ctx = asGetActiveContext();
+ if( ctx )
+ ctx->SetException("Out of memory");
+
+ return 0;
+ }
+
+ // Initialize the object
+ CScriptArray *a = new(mem) CScriptArray(ti, initList);
+
+ return a;
+}
+
+CScriptArray* CScriptArray::Create(asITypeInfo *ti, asUINT length, void *defVal)
+{
+ // Allocate the memory
+ void *mem = userAlloc(sizeof(CScriptArray));
+ if( mem == 0 )
+ {
+ asIScriptContext *ctx = asGetActiveContext();
+ if( ctx )
+ ctx->SetException("Out of memory");
+
+ return 0;
+ }
+
+ // Initialize the object
+ CScriptArray *a = new(mem) CScriptArray(length, defVal, ti);
+
+ return a;
+}
+
+CScriptArray* CScriptArray::Create(asITypeInfo *ti)
+{
+ return CScriptArray::Create(ti, asUINT(0));
+}
+
+// This optional callback is called when the template type is first used by the compiler.
+// It allows the application to validate if the template can be instantiated for the requested
+// subtype at compile time, instead of at runtime. The output argument dontGarbageCollect
+// allow the callback to tell the engine if the template instance type shouldn't be garbage collected,
+// i.e. no asOBJ_GC flag.
+static bool ScriptArrayTemplateCallback(asITypeInfo *ti, bool &dontGarbageCollect)
+{
+ // Make sure the subtype can be instantiated with a default factory/constructor,
+ // otherwise we won't be able to instantiate the elements.
+ int typeId = ti->GetSubTypeId();
+ if( typeId == asTYPEID_VOID )
+ return false;
+ if( (typeId & asTYPEID_MASK_OBJECT) && !(typeId & asTYPEID_OBJHANDLE) )
+ {
+ asITypeInfo *subtype = ti->GetEngine()->GetTypeInfoById(typeId);
+ asDWORD flags = subtype->GetFlags();
+ if( (flags & asOBJ_VALUE) && !(flags & asOBJ_POD) )
+ {
+ // Verify that there is a default constructor
+ bool found = false;
+ for( asUINT n = 0; n < subtype->GetBehaviourCount(); n++ )
+ {
+ asEBehaviours beh;
+ asIScriptFunction *func = subtype->GetBehaviourByIndex(n, &beh);
+ if( beh != asBEHAVE_CONSTRUCT ) continue;
+
+ if( func->GetParamCount() == 0 )
+ {
+ // Found the default constructor
+ found = true;
+ break;
+ }
+ }
+
+ if( !found )
+ {
+ // There is no default constructor
+ // TODO: Should format the message to give the name of the subtype for better understanding
+ ti->GetEngine()->WriteMessage("array", 0, 0, asMSGTYPE_ERROR, "The subtype has no default constructor");
+ return false;
+ }
+ }
+ else if( (flags & asOBJ_REF) )
+ {
+ bool found = false;
+
+ // If value assignment for ref type has been disabled then the array
+ // can be created if the type has a default factory function
+ if( !ti->GetEngine()->GetEngineProperty(asEP_DISALLOW_VALUE_ASSIGN_FOR_REF_TYPE) )
+ {
+ // Verify that there is a default factory
+ for( asUINT n = 0; n < subtype->GetFactoryCount(); n++ )
+ {
+ asIScriptFunction *func = subtype->GetFactoryByIndex(n);
+ if( func->GetParamCount() == 0 )
+ {
+ // Found the default factory
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if( !found )
+ {
+ // No default factory
+ // TODO: Should format the message to give the name of the subtype for better understanding
+ ti->GetEngine()->WriteMessage("array", 0, 0, asMSGTYPE_ERROR, "The subtype has no default factory");
+ return false;
+ }
+ }
+
+ // If the object type is not garbage collected then the array also doesn't need to be
+ if( !(flags & asOBJ_GC) )
+ dontGarbageCollect = true;
+ }
+ else if( !(typeId & asTYPEID_OBJHANDLE) )
+ {
+ // Arrays with primitives cannot form circular references,
+ // thus there is no need to garbage collect them
+ dontGarbageCollect = true;
+ }
+ else
+ {
+ assert( typeId & asTYPEID_OBJHANDLE );
+
+ // It is not necessary to set the array as garbage collected for all handle types.
+ // If it is possible to determine that the handle cannot refer to an object type
+ // that can potentially form a circular reference with the array then it is not
+ // necessary to make the array garbage collected.
+ asITypeInfo *subtype = ti->GetEngine()->GetTypeInfoById(typeId);
+ asDWORD flags = subtype->GetFlags();
+ if( !(flags & asOBJ_GC) )
+ {
+ if( (flags & asOBJ_SCRIPT_OBJECT) )
+ {
+ // Even if a script class is by itself not garbage collected, it is possible
+ // that classes that derive from it may be, so it is not possible to know
+ // that no circular reference can occur.
+ if( (flags & asOBJ_NOINHERIT) )
+ {
+ // A script class declared as final cannot be inherited from, thus
+ // we can be certain that the object cannot be garbage collected.
+ dontGarbageCollect = true;
+ }
+ }
+ else
+ {
+ // For application registered classes we assume the application knows
+ // what it is doing and don't mark the array as garbage collected unless
+ // the type is also garbage collected.
+ dontGarbageCollect = true;
+ }
+ }
+ }
+
+ // The type is ok
+ return true;
+}
+
+// Registers the template array type
+void RegisterScriptArray(asIScriptEngine *engine, bool defaultArray)
+{
+ if( strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") == 0 )
+ RegisterScriptArray_Native(engine);
+ else
+ RegisterScriptArray_Generic(engine);
+
+ if( defaultArray )
+ {
+ int r = engine->RegisterDefaultArrayType("array<T>"); assert( r >= 0 );
+ UNUSED_VAR(r);
+ }
+}
+
+static void RegisterScriptArray_Native(asIScriptEngine *engine)
+{
+ int r = 0;
+ UNUSED_VAR(r);
+
+ // Register the object type user data clean up
+ engine->SetTypeInfoUserDataCleanupCallback(CleanupTypeInfoArrayCache, ARRAY_CACHE);
+
+ // Register the array type as a template
+ r = engine->RegisterObjectType("array<class T>", 0, asOBJ_REF | asOBJ_GC | asOBJ_TEMPLATE); assert( r >= 0 );
+
+ // Register a callback for validating the subtype before it is used
+ r = engine->RegisterObjectBehaviour("array<T>", asBEHAVE_TEMPLATE_CALLBACK, "bool f(int&in, bool&out)", asFUNCTION(ScriptArrayTemplateCallback), asCALL_CDECL); assert( r >= 0 );
+
+ // Templates receive the object type as the first parameter. To the script writer this is hidden
+ r = engine->RegisterObjectBehaviour("array<T>", asBEHAVE_FACTORY, "array<T>@ f(int&in)", asFUNCTIONPR(CScriptArray::Create, (asITypeInfo*), CScriptArray*), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("array<T>", asBEHAVE_FACTORY, "array<T>@ f(int&in, uint length)", asFUNCTIONPR(CScriptArray::Create, (asITypeInfo*, asUINT), CScriptArray*), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("array<T>", asBEHAVE_FACTORY, "array<T>@ f(int&in, uint length, const T &in value)", asFUNCTIONPR(CScriptArray::Create, (asITypeInfo*, asUINT, void *), CScriptArray*), asCALL_CDECL); assert( r >= 0 );
+
+ // Register the factory that will be used for initialization lists
+ r = engine->RegisterObjectBehaviour("array<T>", asBEHAVE_LIST_FACTORY, "array<T>@ f(int&in type, int&in list) {repeat T}", asFUNCTIONPR(CScriptArray::Create, (asITypeInfo*, void*), CScriptArray*), asCALL_CDECL); assert( r >= 0 );
+
+ // The memory management methods
+ r = engine->RegisterObjectBehaviour("array<T>", asBEHAVE_ADDREF, "void f()", asMETHOD(CScriptArray,AddRef), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("array<T>", asBEHAVE_RELEASE, "void f()", asMETHOD(CScriptArray,Release), asCALL_THISCALL); assert( r >= 0 );
+
+ // The index operator returns the template subtype
+ r = engine->RegisterObjectMethod("array<T>", "T &opIndex(uint index)", asMETHODPR(CScriptArray, At, (asUINT), void*), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "const T &opIndex(uint index) const", asMETHODPR(CScriptArray, At, (asUINT) const, const void*), asCALL_THISCALL); assert( r >= 0 );
+
+ // The assignment operator
+ r = engine->RegisterObjectMethod("array<T>", "array<T> &opAssign(const array<T>&in)", asMETHOD(CScriptArray, operator=), asCALL_THISCALL); assert( r >= 0 );
+
+ // Other methods
+ r = engine->RegisterObjectMethod("array<T>", "void insertAt(uint index, const T&in value)", asMETHODPR(CScriptArray, InsertAt, (asUINT, void *), void), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "void insertAt(uint index, const array<T>& arr)", asMETHODPR(CScriptArray, InsertAt, (asUINT, const CScriptArray &), void), asCALL_THISCALL); assert(r >= 0);
+ r = engine->RegisterObjectMethod("array<T>", "void insertLast(const T&in value)", asMETHOD(CScriptArray, InsertLast), asCALL_THISCALL); assert(r >= 0);
+ r = engine->RegisterObjectMethod("array<T>", "void removeAt(uint index)", asMETHOD(CScriptArray, RemoveAt), asCALL_THISCALL); assert(r >= 0);
+ r = engine->RegisterObjectMethod("array<T>", "void removeLast()", asMETHOD(CScriptArray, RemoveLast), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "void removeRange(uint start, uint count)", asMETHOD(CScriptArray, RemoveRange), asCALL_THISCALL); assert(r >= 0);
+ // TODO: Should length() and resize() be deprecated as the property accessors do the same thing?
+ r = engine->RegisterObjectMethod("array<T>", "uint length() const", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "void reserve(uint length)", asMETHOD(CScriptArray, Reserve), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "void resize(uint length)", asMETHODPR(CScriptArray, Resize, (asUINT), void), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "void sortAsc()", asMETHODPR(CScriptArray, SortAsc, (), void), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "void sortAsc(uint startAt, uint count)", asMETHODPR(CScriptArray, SortAsc, (asUINT, asUINT), void), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "void sortDesc()", asMETHODPR(CScriptArray, SortDesc, (), void), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "void sortDesc(uint startAt, uint count)", asMETHODPR(CScriptArray, SortDesc, (asUINT, asUINT), void), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "void reverse()", asMETHOD(CScriptArray, Reverse), asCALL_THISCALL); assert( r >= 0 );
+ // The token 'if_handle_then_const' tells the engine that if the type T is a handle, then it should refer to a read-only object
+ r = engine->RegisterObjectMethod("array<T>", "int find(const T&in if_handle_then_const value) const", asMETHODPR(CScriptArray, Find, (void*) const, int), asCALL_THISCALL); assert( r >= 0 );
+ // TODO: It should be "int find(const T&in value, uint startAt = 0) const"
+ r = engine->RegisterObjectMethod("array<T>", "int find(uint startAt, const T&in if_handle_then_const value) const", asMETHODPR(CScriptArray, Find, (asUINT, void*) const, int), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "int findByRef(const T&in if_handle_then_const value) const", asMETHODPR(CScriptArray, FindByRef, (void*) const, int), asCALL_THISCALL); assert( r >= 0 );
+ // TODO: It should be "int findByRef(const T&in value, uint startAt = 0) const"
+ r = engine->RegisterObjectMethod("array<T>", "int findByRef(uint startAt, const T&in if_handle_then_const value) const", asMETHODPR(CScriptArray, FindByRef, (asUINT, void*) const, int), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "bool opEquals(const array<T>&in) const", asMETHOD(CScriptArray, operator==), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "bool isEmpty() const", asMETHOD(CScriptArray, IsEmpty), asCALL_THISCALL); assert( r >= 0 );
+
+ // Sort with callback for comparison
+ r = engine->RegisterFuncdef("bool array<T>::less(const T&in a, const T&in b)");
+ r = engine->RegisterObjectMethod("array<T>", "void sort(const less &in, uint startAt = 0, uint count = uint(-1))", asMETHODPR(CScriptArray, Sort, (asIScriptFunction*, asUINT, asUINT), void), asCALL_THISCALL); assert(r >= 0);
+
+ // Register virtual properties
+ r = engine->RegisterObjectMethod("array<T>", "uint get_length() const", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "void set_length(uint)", asMETHODPR(CScriptArray, Resize, (asUINT), void), asCALL_THISCALL); assert( r >= 0 );
+
+ // Register GC behaviours in case the array needs to be garbage collected
+ r = engine->RegisterObjectBehaviour("array<T>", asBEHAVE_GETREFCOUNT, "int f()", asMETHOD(CScriptArray, GetRefCount), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("array<T>", asBEHAVE_SETGCFLAG, "void f()", asMETHOD(CScriptArray, SetFlag), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("array<T>", asBEHAVE_GETGCFLAG, "bool f()", asMETHOD(CScriptArray, GetFlag), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("array<T>", asBEHAVE_ENUMREFS, "void f(int&in)", asMETHOD(CScriptArray, EnumReferences), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("array<T>", asBEHAVE_RELEASEREFS, "void f(int&in)", asMETHOD(CScriptArray, ReleaseAllHandles), asCALL_THISCALL); assert( r >= 0 );
+
+#if AS_USE_STLNAMES == 1
+ // Same as length
+ r = engine->RegisterObjectMethod("array<T>", "uint size() const", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL); assert( r >= 0 );
+ // Same as isEmpty
+ r = engine->RegisterObjectMethod("array<T>", "bool empty() const", asMETHOD(CScriptArray, IsEmpty), asCALL_THISCALL); assert( r >= 0 );
+ // Same as insertLast
+ r = engine->RegisterObjectMethod("array<T>", "void push_back(const T&in)", asMETHOD(CScriptArray, InsertLast), asCALL_THISCALL); assert( r >= 0 );
+ // Same as removeLast
+ r = engine->RegisterObjectMethod("array<T>", "void pop_back()", asMETHOD(CScriptArray, RemoveLast), asCALL_THISCALL); assert( r >= 0 );
+ // Same as insertAt
+ r = engine->RegisterObjectMethod("array<T>", "void insert(uint index, const T&in value)", asMETHODPR(CScriptArray, InsertAt, (asUINT, void *), void), asCALL_THISCALL); assert(r >= 0);
+ r = engine->RegisterObjectMethod("array<T>", "void insert(uint index, const array<T>& arr)", asMETHODPR(CScriptArray, InsertAt, (asUINT, const CScriptArray &), void), asCALL_THISCALL); assert(r >= 0);
+ // Same as removeAt
+ r = engine->RegisterObjectMethod("array<T>", "void erase(uint)", asMETHOD(CScriptArray, RemoveAt), asCALL_THISCALL); assert( r >= 0 );
+#endif
+}
+
+CScriptArray &CScriptArray::operator=(const CScriptArray &other)
+{
+ // Only perform the copy if the array types are the same
+ if( &other != this &&
+ other.GetArrayObjectType() == GetArrayObjectType() )
+ {
+ // Make sure the arrays are of the same size
+ Resize(other.buffer->numElements);
+
+ // Copy the value of each element
+ CopyBuffer(buffer, other.buffer);
+ }
+
+ return *this;
+}
+
+CScriptArray::CScriptArray(asITypeInfo *ti, void *buf)
+{
+ // The object type should be the template instance of the array
+ assert( ti && string(ti->GetName()) == "array" );
+
+ refCount = 1;
+ gcFlag = false;
+ objType = ti;
+ objType->AddRef();
+ buffer = 0;
+
+ Precache();
+
+ asIScriptEngine *engine = ti->GetEngine();
+
+ // Determine element size
+ if( subTypeId & asTYPEID_MASK_OBJECT )
+ elementSize = sizeof(asPWORD);
+ else
+ elementSize = engine->GetSizeOfPrimitiveType(subTypeId);
+
+ // Determine the initial size from the buffer
+ asUINT length = *(asUINT*)buf;
+
+ // Make sure the array size isn't too large for us to handle
+ if( !CheckMaxSize(length) )
+ {
+ // Don't continue with the initialization
+ return;
+ }
+
+ // Copy the values of the array elements from the buffer
+ if( (ti->GetSubTypeId() & asTYPEID_MASK_OBJECT) == 0 )
+ {
+ CreateBuffer(&buffer, length);
+
+ // Copy the values of the primitive type into the internal buffer
+ if( length > 0 )
+ memcpy(At(0), (((asUINT*)buf)+1), length * elementSize);
+ }
+ else if( ti->GetSubTypeId() & asTYPEID_OBJHANDLE )
+ {
+ CreateBuffer(&buffer, length);
+
+ // Copy the handles into the internal buffer
+ if( length > 0 )
+ memcpy(At(0), (((asUINT*)buf)+1), length * elementSize);
+
+ // With object handles it is safe to clear the memory in the received buffer
+ // instead of increasing the ref count. It will save time both by avoiding the
+ // call the increase ref, and also relieve the engine from having to release
+ // its references too
+ memset((((asUINT*)buf)+1), 0, length * elementSize);
+ }
+ else if( ti->GetSubType()->GetFlags() & asOBJ_REF )
+ {
+ // Only allocate the buffer, but not the objects
+ subTypeId |= asTYPEID_OBJHANDLE;
+ CreateBuffer(&buffer, length);
+ subTypeId &= ~asTYPEID_OBJHANDLE;
+
+ // Copy the handles into the internal buffer
+ if( length > 0 )
+ memcpy(buffer->data, (((asUINT*)buf)+1), length * elementSize);
+
+ // For ref types we can do the same as for handles, as they are
+ // implicitly stored as handles.
+ memset((((asUINT*)buf)+1), 0, length * elementSize);
+ }
+ else
+ {
+ // TODO: Optimize by calling the copy constructor of the object instead of
+ // constructing with the default constructor and then assigning the value
+ // TODO: With C++11 ideally we should be calling the move constructor, instead
+ // of the copy constructor as the engine will just discard the objects in the
+ // buffer afterwards.
+ CreateBuffer(&buffer, length);
+
+ // For value types we need to call the opAssign for each individual object
+ for( asUINT n = 0; n < length; n++ )
+ {
+ void *obj = At(n);
+ asBYTE *srcObj = (asBYTE*)buf;
+ srcObj += 4 + n*ti->GetSubType()->GetSize();
+ engine->AssignScriptObject(obj, srcObj, ti->GetSubType());
+ }
+ }
+
+ // Notify the GC of the successful creation
+ if( objType->GetFlags() & asOBJ_GC )
+ objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType);
+}
+
+CScriptArray::CScriptArray(asUINT length, asITypeInfo *ti)
+{
+ // The object type should be the template instance of the array
+ assert( ti && string(ti->GetName()) == "array" );
+
+ refCount = 1;
+ gcFlag = false;
+ objType = ti;
+ objType->AddRef();
+ buffer = 0;
+
+ Precache();
+
+ // Determine element size
+ if( subTypeId & asTYPEID_MASK_OBJECT )
+ elementSize = sizeof(asPWORD);
+ else
+ elementSize = objType->GetEngine()->GetSizeOfPrimitiveType(subTypeId);
+
+ // Make sure the array size isn't too large for us to handle
+ if( !CheckMaxSize(length) )
+ {
+ // Don't continue with the initialization
+ return;
+ }
+
+ CreateBuffer(&buffer, length);
+
+ // Notify the GC of the successful creation
+ if( objType->GetFlags() & asOBJ_GC )
+ objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType);
+}
+
+CScriptArray::CScriptArray(const CScriptArray &other)
+{
+ refCount = 1;
+ gcFlag = false;
+ objType = other.objType;
+ objType->AddRef();
+ buffer = 0;
+
+ Precache();
+
+ elementSize = other.elementSize;
+
+ if( objType->GetFlags() & asOBJ_GC )
+ objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType);
+
+ CreateBuffer(&buffer, 0);
+
+ // Copy the content
+ *this = other;
+}
+
+CScriptArray::CScriptArray(asUINT length, void *defVal, asITypeInfo *ti)
+{
+ // The object type should be the template instance of the array
+ assert( ti && string(ti->GetName()) == "array" );
+
+ refCount = 1;
+ gcFlag = false;
+ objType = ti;
+ objType->AddRef();
+ buffer = 0;
+
+ Precache();
+
+ // Determine element size
+ if( subTypeId & asTYPEID_MASK_OBJECT )
+ elementSize = sizeof(asPWORD);
+ else
+ elementSize = objType->GetEngine()->GetSizeOfPrimitiveType(subTypeId);
+
+ // Make sure the array size isn't too large for us to handle
+ if( !CheckMaxSize(length) )
+ {
+ // Don't continue with the initialization
+ return;
+ }
+
+ CreateBuffer(&buffer, length);
+
+ // Notify the GC of the successful creation
+ if( objType->GetFlags() & asOBJ_GC )
+ objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType);
+
+ // Initialize the elements with the default value
+ for( asUINT n = 0; n < GetSize(); n++ )
+ SetValue(n, defVal);
+}
+
+void CScriptArray::SetValue(asUINT index, void *value)
+{
+ // At() will take care of the out-of-bounds checking, though
+ // if called from the application then nothing will be done
+ void *ptr = At(index);
+ if( ptr == 0 ) return;
+
+ if( (subTypeId & ~asTYPEID_MASK_SEQNBR) && !(subTypeId & asTYPEID_OBJHANDLE) )
+ objType->GetEngine()->AssignScriptObject(ptr, value, objType->GetSubType());
+ else if( subTypeId & asTYPEID_OBJHANDLE )
+ {
+ void *tmp = *(void**)ptr;
+ *(void**)ptr = *(void**)value;
+ objType->GetEngine()->AddRefScriptObject(*(void**)value, objType->GetSubType());
+ if( tmp )
+ objType->GetEngine()->ReleaseScriptObject(tmp, objType->GetSubType());
+ }
+ else if( subTypeId == asTYPEID_BOOL ||
+ subTypeId == asTYPEID_INT8 ||
+ subTypeId == asTYPEID_UINT8 )
+ *(char*)ptr = *(char*)value;
+ else if( subTypeId == asTYPEID_INT16 ||
+ subTypeId == asTYPEID_UINT16 )
+ *(short*)ptr = *(short*)value;
+ else if( subTypeId == asTYPEID_INT32 ||
+ subTypeId == asTYPEID_UINT32 ||
+ subTypeId == asTYPEID_FLOAT ||
+ subTypeId > asTYPEID_DOUBLE ) // enums have a type id larger than doubles
+ *(int*)ptr = *(int*)value;
+ else if( subTypeId == asTYPEID_INT64 ||
+ subTypeId == asTYPEID_UINT64 ||
+ subTypeId == asTYPEID_DOUBLE )
+ *(double*)ptr = *(double*)value;
+}
+
+CScriptArray::~CScriptArray()
+{
+ if( buffer )
+ {
+ DeleteBuffer(buffer);
+ buffer = 0;
+ }
+ if( objType ) objType->Release();
+}
+
+asUINT CScriptArray::GetSize() const
+{
+ return buffer->numElements;
+}
+
+bool CScriptArray::IsEmpty() const
+{
+ return buffer->numElements == 0;
+}
+
+void CScriptArray::Reserve(asUINT maxElements)
+{
+ if( maxElements <= buffer->maxElements )
+ return;
+
+ if( !CheckMaxSize(maxElements) )
+ return;
+
+ // Allocate memory for the buffer
+ SArrayBuffer *newBuffer = reinterpret_cast<SArrayBuffer*>(userAlloc(sizeof(SArrayBuffer)-1 + elementSize*maxElements));
+ if( newBuffer )
+ {
+ newBuffer->numElements = buffer->numElements;
+ newBuffer->maxElements = maxElements;
+ }
+ else
+ {
+ // Out of memory
+ asIScriptContext *ctx = asGetActiveContext();
+ if( ctx )
+ ctx->SetException("Out of memory");
+ return;
+ }
+
+ // As objects in arrays of objects are not stored inline, it is safe to use memcpy here
+ // since we're just copying the pointers to objects and not the actual objects.
+ memcpy(newBuffer->data, buffer->data, buffer->numElements*elementSize);
+
+ // Release the old buffer
+ userFree(buffer);
+
+ buffer = newBuffer;
+}
+
+void CScriptArray::Resize(asUINT numElements)
+{
+ if( !CheckMaxSize(numElements) )
+ return;
+
+ Resize((int)numElements - (int)buffer->numElements, (asUINT)-1);
+}
+
+void CScriptArray::RemoveRange(asUINT start, asUINT count)
+{
+ if (count == 0)
+ return;
+
+ if( buffer == 0 || start > buffer->numElements )
+ {
+ // If this is called from a script we raise a script exception
+ asIScriptContext *ctx = asGetActiveContext();
+ if (ctx)
+ ctx->SetException("Index out of bounds");
+ return;
+ }
+
+ // Cap count to the end of the array
+ if (start + count > buffer->numElements)
+ count = buffer->numElements - start;
+
+ // Destroy the elements that are being removed
+ Destruct(buffer, start, start + count);
+
+ // Compact the elements
+ // As objects in arrays of objects are not stored inline, it is safe to use memmove here
+ // since we're just copying the pointers to objects and not the actual objects.
+ memmove(buffer->data + start*elementSize, buffer->data + (start + count)*elementSize, (buffer->numElements - start - count)*elementSize);
+ buffer->numElements -= count;
+}
+
+// Internal
+void CScriptArray::Resize(int delta, asUINT at)
+{
+ if( delta < 0 )
+ {
+ if( -delta > (int)buffer->numElements )
+ delta = -(int)buffer->numElements;
+ if( at > buffer->numElements + delta )
+ at = buffer->numElements + delta;
+ }
+ else if( delta > 0 )
+ {
+ // Make sure the array size isn't too large for us to handle
+ if( delta > 0 && !CheckMaxSize(buffer->numElements + delta) )
+ return;
+
+ if( at > buffer->numElements )
+ at = buffer->numElements;
+ }
+
+ if( delta == 0 ) return;
+
+ if( buffer->maxElements < buffer->numElements + delta )
+ {
+ // Allocate memory for the buffer
+ SArrayBuffer *newBuffer = reinterpret_cast<SArrayBuffer*>(userAlloc(sizeof(SArrayBuffer)-1 + elementSize*(buffer->numElements + delta)));
+ if( newBuffer )
+ {
+ newBuffer->numElements = buffer->numElements + delta;
+ newBuffer->maxElements = newBuffer->numElements;
+ }
+ else
+ {
+ // Out of memory
+ asIScriptContext *ctx = asGetActiveContext();
+ if( ctx )
+ ctx->SetException("Out of memory");
+ return;
+ }
+
+ // As objects in arrays of objects are not stored inline, it is safe to use memcpy here
+ // since we're just copying the pointers to objects and not the actual objects.
+ memcpy(newBuffer->data, buffer->data, at*elementSize);
+ if( at < buffer->numElements )
+ memcpy(newBuffer->data + (at+delta)*elementSize, buffer->data + at*elementSize, (buffer->numElements-at)*elementSize);
+
+ // Initialize the new elements with default values
+ Construct(newBuffer, at, at+delta);
+
+ // Release the old buffer
+ userFree(buffer);
+
+ buffer = newBuffer;
+ }
+ else if( delta < 0 )
+ {
+ Destruct(buffer, at, at-delta);
+ // As objects in arrays of objects are not stored inline, it is safe to use memmove here
+ // since we're just copying the pointers to objects and not the actual objects.
+ memmove(buffer->data + at*elementSize, buffer->data + (at-delta)*elementSize, (buffer->numElements - (at-delta))*elementSize);
+ buffer->numElements += delta;
+ }
+ else
+ {
+ // As objects in arrays of objects are not stored inline, it is safe to use memmove here
+ // since we're just copying the pointers to objects and not the actual objects.
+ memmove(buffer->data + (at+delta)*elementSize, buffer->data + at*elementSize, (buffer->numElements - at)*elementSize);
+ Construct(buffer, at, at+delta);
+ buffer->numElements += delta;
+ }
+}
+
+// internal
+bool CScriptArray::CheckMaxSize(asUINT numElements)
+{
+ // This code makes sure the size of the buffer that is allocated
+ // for the array doesn't overflow and becomes smaller than requested
+
+ asUINT maxSize = 0xFFFFFFFFul - sizeof(SArrayBuffer) + 1;
+ if( elementSize > 0 )
+ maxSize /= elementSize;
+
+ if( numElements > maxSize )
+ {
+ asIScriptContext *ctx = asGetActiveContext();
+ if( ctx )
+ ctx->SetException("Too large array size");
+
+ return false;
+ }
+
+ // OK
+ return true;
+}
+
+asITypeInfo *CScriptArray::GetArrayObjectType() const
+{
+ return objType;
+}
+
+int CScriptArray::GetArrayTypeId() const
+{
+ return objType->GetTypeId();
+}
+
+int CScriptArray::GetElementTypeId() const
+{
+ return subTypeId;
+}
+
+void CScriptArray::InsertAt(asUINT index, void *value)
+{
+ if( index > buffer->numElements )
+ {
+ // If this is called from a script we raise a script exception
+ asIScriptContext *ctx = asGetActiveContext();
+ if( ctx )
+ ctx->SetException("Index out of bounds");
+ return;
+ }
+
+ // Make room for the new element
+ Resize(1, index);
+
+ // Set the value of the new element
+ SetValue(index, value);
+}
+
+void CScriptArray::InsertAt(asUINT index, const CScriptArray &arr)
+{
+ if (index > buffer->numElements)
+ {
+ asIScriptContext *ctx = asGetActiveContext();
+ if (ctx)
+ ctx->SetException("Index out of bounds");
+ return;
+ }
+
+ if (objType != arr.objType)
+ {
+ // This shouldn't really be possible to happen when
+ // called from a script, but let's check for it anyway
+ asIScriptContext *ctx = asGetActiveContext();
+ if (ctx)
+ ctx->SetException("Mismatching array types");
+ return;
+ }
+
+ asUINT elements = arr.GetSize();
+ Resize(elements, index);
+ if (&arr != this)
+ {
+ for (asUINT n = 0; n < arr.GetSize(); n++)
+ {
+ // This const cast is allowed, since we know the
+ // value will only be used to make a copy of it
+ void *value = const_cast<void*>(arr.At(n));
+ SetValue(index + n, value);
+ }
+ }
+ else
+ {
+ // The array that is being inserted is the same as this one.
+ // So we should iterate over the elements before the index,
+ // and then the elements after
+ for (asUINT n = 0; n < index; n++)
+ {
+ // This const cast is allowed, since we know the
+ // value will only be used to make a copy of it
+ void *value = const_cast<void*>(arr.At(n));
+ SetValue(index + n, value);
+ }
+
+ for (asUINT n = index + elements, m = 0; n < arr.GetSize(); n++, m++)
+ {
+ // This const cast is allowed, since we know the
+ // value will only be used to make a copy of it
+ void *value = const_cast<void*>(arr.At(n));
+ SetValue(index + index + m, value);
+ }
+ }
+}
+
+void CScriptArray::InsertLast(void *value)
+{
+ InsertAt(buffer->numElements, value);
+}
+
+void CScriptArray::RemoveAt(asUINT index)
+{
+ if( index >= buffer->numElements )
+ {
+ // If this is called from a script we raise a script exception
+ asIScriptContext *ctx = asGetActiveContext();
+ if( ctx )
+ ctx->SetException("Index out of bounds");
+ return;
+ }
+
+ // Remove the element
+ Resize(-1, index);
+}
+
+void CScriptArray::RemoveLast()
+{
+ RemoveAt(buffer->numElements-1);
+}
+
+// Return a pointer to the array element. Returns 0 if the index is out of bounds
+const void *CScriptArray::At(asUINT index) const
+{
+ if( buffer == 0 || index >= buffer->numElements )
+ {
+ // If this is called from a script we raise a script exception
+ asIScriptContext *ctx = asGetActiveContext();
+ if( ctx )
+ ctx->SetException("Index out of bounds");
+ return 0;
+ }
+
+ if( (subTypeId & asTYPEID_MASK_OBJECT) && !(subTypeId & asTYPEID_OBJHANDLE) )
+ return *(void**)(buffer->data + elementSize*index);
+ else
+ return buffer->data + elementSize*index;
+}
+void *CScriptArray::At(asUINT index)
+{
+ return const_cast<void*>(const_cast<const CScriptArray *>(this)->At(index));
+}
+
+void *CScriptArray::GetBuffer()
+{
+ return buffer->data;
+}
+
+
+// internal
+void CScriptArray::CreateBuffer(SArrayBuffer **buf, asUINT numElements)
+{
+ *buf = reinterpret_cast<SArrayBuffer*>(userAlloc(sizeof(SArrayBuffer)-1+elementSize*numElements));
+
+ if( *buf )
+ {
+ (*buf)->numElements = numElements;
+ (*buf)->maxElements = numElements;
+ Construct(*buf, 0, numElements);
+ }
+ else
+ {
+ // Oops, out of memory
+ asIScriptContext *ctx = asGetActiveContext();
+ if( ctx )
+ ctx->SetException("Out of memory");
+ }
+}
+
+// internal
+void CScriptArray::DeleteBuffer(SArrayBuffer *buf)
+{
+ Destruct(buf, 0, buf->numElements);
+
+ // Free the buffer
+ userFree(buf);
+}
+
+// internal
+void CScriptArray::Construct(SArrayBuffer *buf, asUINT start, asUINT end)
+{
+ if( (subTypeId & asTYPEID_MASK_OBJECT) && !(subTypeId & asTYPEID_OBJHANDLE) )
+ {
+ // Create an object using the default constructor/factory for each element
+ void **max = (void**)(buf->data + end * sizeof(void*));
+ void **d = (void**)(buf->data + start * sizeof(void*));
+
+ asIScriptEngine *engine = objType->GetEngine();
+ asITypeInfo *subType = objType->GetSubType();
+
+ for( ; d < max; d++ )
+ {
+ *d = (void*)engine->CreateScriptObject(subType);
+ if( *d == 0 )
+ {
+ // Set the remaining entries to null so the destructor
+ // won't attempt to destroy invalid objects later
+ memset(d, 0, sizeof(void*)*(max-d));
+
+ // There is no need to set an exception on the context,
+ // as CreateScriptObject has already done that
+ return;
+ }
+ }
+ }
+ else
+ {
+ // Set all elements to zero whether they are handles or primitives
+ void *d = (void*)(buf->data + start * elementSize);
+ memset(d, 0, (end-start)*elementSize);
+ }
+}
+
+// internal
+void CScriptArray::Destruct(SArrayBuffer *buf, asUINT start, asUINT end)
+{
+ if( subTypeId & asTYPEID_MASK_OBJECT )
+ {
+ asIScriptEngine *engine = objType->GetEngine();
+
+ void **max = (void**)(buf->data + end * sizeof(void*));
+ void **d = (void**)(buf->data + start * sizeof(void*));
+
+ for( ; d < max; d++ )
+ {
+ if( *d )
+ engine->ReleaseScriptObject(*d, objType->GetSubType());
+ }
+ }
+}
+
+
+// internal
+bool CScriptArray::Less(const void *a, const void *b, bool asc, asIScriptContext *ctx, SArrayCache *cache)
+{
+ if( !asc )
+ {
+ // Swap items
+ const void *TEMP = a;
+ a = b;
+ b = TEMP;
+ }
+
+ if( !(subTypeId & ~asTYPEID_MASK_SEQNBR) )
+ {
+ // Simple compare of values
+ switch( subTypeId )
+ {
+ #define COMPARE(T) *((T*)a) < *((T*)b)
+ case asTYPEID_BOOL: return COMPARE(bool);
+ case asTYPEID_INT8: return COMPARE(signed char);
+ case asTYPEID_UINT8: return COMPARE(unsigned char);
+ case asTYPEID_INT16: return COMPARE(signed short);
+ case asTYPEID_UINT16: return COMPARE(unsigned short);
+ case asTYPEID_INT32: return COMPARE(signed int);
+ case asTYPEID_UINT32: return COMPARE(unsigned int);
+ case asTYPEID_FLOAT: return COMPARE(float);
+ case asTYPEID_DOUBLE: return COMPARE(double);
+ default: return COMPARE(signed int); // All enums fall in this case
+ #undef COMPARE
+ }
+ }
+ else
+ {
+ int r = 0;
+
+ if( subTypeId & asTYPEID_OBJHANDLE )
+ {
+ // Allow sort to work even if the array contains null handles
+ if( *(void**)a == 0 ) return true;
+ if( *(void**)b == 0 ) return false;
+ }
+
+ // Execute object opCmp
+ if( cache && cache->cmpFunc )
+ {
+ // TODO: Add proper error handling
+ r = ctx->Prepare(cache->cmpFunc); assert(r >= 0);
+
+ if( subTypeId & asTYPEID_OBJHANDLE )
+ {
+ r = ctx->SetObject(*((void**)a)); assert(r >= 0);
+ r = ctx->SetArgObject(0, *((void**)b)); assert(r >= 0);
+ }
+ else
+ {
+ r = ctx->SetObject((void*)a); assert(r >= 0);
+ r = ctx->SetArgObject(0, (void*)b); assert(r >= 0);
+ }
+
+ r = ctx->Execute();
+
+ if( r == asEXECUTION_FINISHED )
+ {
+ return (int)ctx->GetReturnDWord() < 0;
+ }
+ }
+ }
+
+ return false;
+}
+
+void CScriptArray::Reverse()
+{
+ asUINT size = GetSize();
+
+ if( size >= 2 )
+ {
+ asBYTE TEMP[16];
+
+ for( asUINT i = 0; i < size / 2; i++ )
+ {
+ Copy(TEMP, GetArrayItemPointer(i));
+ Copy(GetArrayItemPointer(i), GetArrayItemPointer(size - i - 1));
+ Copy(GetArrayItemPointer(size - i - 1), TEMP);
+ }
+ }
+}
+
+bool CScriptArray::operator==(const CScriptArray &other) const
+{
+ if( objType != other.objType )
+ return false;
+
+ if( GetSize() != other.GetSize() )
+ return false;
+
+ asIScriptContext *cmpContext = 0;
+ bool isNested = false;
+
+ if( subTypeId & ~asTYPEID_MASK_SEQNBR )
+ {
+ // Try to reuse the active context
+ cmpContext = asGetActiveContext();
+ if( cmpContext )
+ {
+ if( cmpContext->GetEngine() == objType->GetEngine() && cmpContext->PushState() >= 0 )
+ isNested = true;
+ else
+ cmpContext = 0;
+ }
+ if( cmpContext == 0 )
+ {
+ // TODO: Ideally this context would be retrieved from a pool, so we don't have to
+ // create a new one everytime. We could keep a context with the array object
+ // but that would consume a lot of resources as each context is quite heavy.
+ cmpContext = objType->GetEngine()->CreateContext();
+ }
+ }
+
+ // Check if all elements are equal
+ bool isEqual = true;
+ SArrayCache *cache = reinterpret_cast<SArrayCache*>(objType->GetUserData(ARRAY_CACHE));
+ for( asUINT n = 0; n < GetSize(); n++ )
+ if( !Equals(At(n), other.At(n), cmpContext, cache) )
+ {
+ isEqual = false;
+ break;
+ }
+
+ if( cmpContext )
+ {
+ if( isNested )
+ {
+ asEContextState state = cmpContext->GetState();
+ cmpContext->PopState();
+ if( state == asEXECUTION_ABORTED )
+ cmpContext->Abort();
+ }
+ else
+ cmpContext->Release();
+ }
+
+ return isEqual;
+}
+
+// internal
+bool CScriptArray::Equals(const void *a, const void *b, asIScriptContext *ctx, SArrayCache *cache) const
+{
+ if( !(subTypeId & ~asTYPEID_MASK_SEQNBR) )
+ {
+ // Simple compare of values
+ switch( subTypeId )
+ {
+ #define COMPARE(T) *((T*)a) == *((T*)b)
+ case asTYPEID_BOOL: return COMPARE(bool);
+ case asTYPEID_INT8: return COMPARE(signed char);
+ case asTYPEID_UINT8: return COMPARE(unsigned char);
+ case asTYPEID_INT16: return COMPARE(signed short);
+ case asTYPEID_UINT16: return COMPARE(unsigned short);
+ case asTYPEID_INT32: return COMPARE(signed int);
+ case asTYPEID_UINT32: return COMPARE(unsigned int);
+ case asTYPEID_FLOAT: return COMPARE(float);
+ case asTYPEID_DOUBLE: return COMPARE(double);
+ default: return COMPARE(signed int); // All enums fall here
+ #undef COMPARE
+ }
+ }
+ else
+ {
+ int r = 0;
+
+ if( subTypeId & asTYPEID_OBJHANDLE )
+ {
+ // Allow the find to work even if the array contains null handles
+ if( *(void**)a == *(void**)b ) return true;
+ }
+
+ // Execute object opEquals if available
+ if( cache && cache->eqFunc )
+ {
+ // TODO: Add proper error handling
+ r = ctx->Prepare(cache->eqFunc); assert(r >= 0);
+
+ if( subTypeId & asTYPEID_OBJHANDLE )
+ {
+ r = ctx->SetObject(*((void**)a)); assert(r >= 0);
+ r = ctx->SetArgObject(0, *((void**)b)); assert(r >= 0);
+ }
+ else
+ {
+ r = ctx->SetObject((void*)a); assert(r >= 0);
+ r = ctx->SetArgObject(0, (void*)b); assert(r >= 0);
+ }
+
+ r = ctx->Execute();
+
+ if( r == asEXECUTION_FINISHED )
+ return ctx->GetReturnByte() != 0;
+
+ return false;
+ }
+
+ // Execute object opCmp if available
+ if( cache && cache->cmpFunc )
+ {
+ // TODO: Add proper error handling
+ r = ctx->Prepare(cache->cmpFunc); assert(r >= 0);
+
+ if( subTypeId & asTYPEID_OBJHANDLE )
+ {
+ r = ctx->SetObject(*((void**)a)); assert(r >= 0);
+ r = ctx->SetArgObject(0, *((void**)b)); assert(r >= 0);
+ }
+ else
+ {
+ r = ctx->SetObject((void*)a); assert(r >= 0);
+ r = ctx->SetArgObject(0, (void*)b); assert(r >= 0);
+ }
+
+ r = ctx->Execute();
+
+ if( r == asEXECUTION_FINISHED )
+ return (int)ctx->GetReturnDWord() == 0;
+
+ return false;
+ }
+ }
+
+ return false;
+}
+
+int CScriptArray::FindByRef(void *ref) const
+{
+ return FindByRef(0, ref);
+}
+
+int CScriptArray::FindByRef(asUINT startAt, void *ref) const
+{
+ // Find the matching element by its reference
+ asUINT size = GetSize();
+ if( subTypeId & asTYPEID_OBJHANDLE )
+ {
+ // Dereference the pointer
+ ref = *(void**)ref;
+ for( asUINT i = startAt; i < size; i++ )
+ {
+ if( *(void**)At(i) == ref )
+ return i;
+ }
+ }
+ else
+ {
+ // Compare the reference directly
+ for( asUINT i = startAt; i < size; i++ )
+ {
+ if( At(i) == ref )
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+int CScriptArray::Find(void *value) const
+{
+ return Find(0, value);
+}
+
+int CScriptArray::Find(asUINT startAt, void *value) const
+{
+ // Check if the subtype really supports find()
+ // TODO: Can't this be done at compile time too by the template callback
+ SArrayCache *cache = 0;
+ if( subTypeId & ~asTYPEID_MASK_SEQNBR )
+ {
+ cache = reinterpret_cast<SArrayCache*>(objType->GetUserData(ARRAY_CACHE));
+ if( !cache || (cache->cmpFunc == 0 && cache->eqFunc == 0) )
+ {
+ asIScriptContext *ctx = asGetActiveContext();
+ asITypeInfo* subType = objType->GetEngine()->GetTypeInfoById(subTypeId);
+
+ // Throw an exception
+ if( ctx )
+ {
+ char tmp[512];
+
+ if( cache && cache->eqFuncReturnCode == asMULTIPLE_FUNCTIONS )
+#if defined(_MSC_VER) && _MSC_VER >= 1500 && !defined(__S3E__)
+ sprintf_s(tmp, 512, "Type '%s' has multiple matching opEquals or opCmp methods", subType->GetName());
+#else
+ sprintf(tmp, "Type '%s' has multiple matching opEquals or opCmp methods", subType->GetName());
+#endif
+ else
+#if defined(_MSC_VER) && _MSC_VER >= 1500 && !defined(__S3E__)
+ sprintf_s(tmp, 512, "Type '%s' does not have a matching opEquals or opCmp method", subType->GetName());
+#else
+ sprintf(tmp, "Type '%s' does not have a matching opEquals or opCmp method", subType->GetName());
+#endif
+ ctx->SetException(tmp);
+ }
+
+ return -1;
+ }
+ }
+
+ asIScriptContext *cmpContext = 0;
+ bool isNested = false;
+
+ if( subTypeId & ~asTYPEID_MASK_SEQNBR )
+ {
+ // Try to reuse the active context
+ cmpContext = asGetActiveContext();
+ if( cmpContext )
+ {
+ if( cmpContext->GetEngine() == objType->GetEngine() && cmpContext->PushState() >= 0 )
+ isNested = true;
+ else
+ cmpContext = 0;
+ }
+ if( cmpContext == 0 )
+ {
+ // TODO: Ideally this context would be retrieved from a pool, so we don't have to
+ // create a new one everytime. We could keep a context with the array object
+ // but that would consume a lot of resources as each context is quite heavy.
+ cmpContext = objType->GetEngine()->CreateContext();
+ }
+ }
+
+ // Find the matching element
+ int ret = -1;
+ asUINT size = GetSize();
+
+ for( asUINT i = startAt; i < size; i++ )
+ {
+ // value passed by reference
+ if( Equals(At(i), value, cmpContext, cache) )
+ {
+ ret = (int)i;
+ break;
+ }
+ }
+
+ if( cmpContext )
+ {
+ if( isNested )
+ {
+ asEContextState state = cmpContext->GetState();
+ cmpContext->PopState();
+ if( state == asEXECUTION_ABORTED )
+ cmpContext->Abort();
+ }
+ else
+ cmpContext->Release();
+ }
+
+ return ret;
+}
+
+
+
+// internal
+// Copy object handle or primitive value
+// Even in arrays of objects the objects are allocated on
+// the heap and the array stores the pointers to the objects
+void CScriptArray::Copy(void *dst, void *src)
+{
+ memcpy(dst, src, elementSize);
+}
+
+
+// internal
+// Return pointer to array item (object handle or primitive value)
+void *CScriptArray::GetArrayItemPointer(int index)
+{
+ return buffer->data + index * elementSize;
+}
+
+// internal
+// Return pointer to data in buffer (object or primitive)
+void *CScriptArray::GetDataPointer(void *buf)
+{
+ if ((subTypeId & asTYPEID_MASK_OBJECT) && !(subTypeId & asTYPEID_OBJHANDLE) )
+ {
+ // Real address of object
+ return reinterpret_cast<void*>(*(size_t*)buf);
+ }
+ else
+ {
+ // Primitive is just a raw data
+ return buf;
+ }
+}
+
+
+// Sort ascending
+void CScriptArray::SortAsc()
+{
+ Sort(0, GetSize(), true);
+}
+
+// Sort ascending
+void CScriptArray::SortAsc(asUINT startAt, asUINT count)
+{
+ Sort(startAt, count, true);
+}
+
+// Sort descending
+void CScriptArray::SortDesc()
+{
+ Sort(0, GetSize(), false);
+}
+
+// Sort descending
+void CScriptArray::SortDesc(asUINT startAt, asUINT count)
+{
+ Sort(startAt, count, false);
+}
+
+
+// internal
+void CScriptArray::Sort(asUINT startAt, asUINT count, bool asc)
+{
+ // Subtype isn't primitive and doesn't have opCmp
+ SArrayCache *cache = reinterpret_cast<SArrayCache*>(objType->GetUserData(ARRAY_CACHE));
+ if( subTypeId & ~asTYPEID_MASK_SEQNBR )
+ {
+ if( !cache || cache->cmpFunc == 0 )
+ {
+ asIScriptContext *ctx = asGetActiveContext();
+ asITypeInfo* subType = objType->GetEngine()->GetTypeInfoById(subTypeId);
+
+ // Throw an exception
+ if( ctx )
+ {
+ char tmp[512];
+
+ if( cache && cache->cmpFuncReturnCode == asMULTIPLE_FUNCTIONS )
+#if defined(_MSC_VER) && _MSC_VER >= 1500 && !defined(__S3E__)
+ sprintf_s(tmp, 512, "Type '%s' has multiple matching opCmp methods", subType->GetName());
+#else
+ sprintf(tmp, "Type '%s' has multiple matching opCmp methods", subType->GetName());
+#endif
+ else
+#if defined(_MSC_VER) && _MSC_VER >= 1500 && !defined(__S3E__)
+ sprintf_s(tmp, 512, "Type '%s' does not have a matching opCmp method", subType->GetName());
+#else
+ sprintf(tmp, "Type '%s' does not have a matching opCmp method", subType->GetName());
+#endif
+
+ ctx->SetException(tmp);
+ }
+
+ return;
+ }
+ }
+
+ // No need to sort
+ if( count < 2 )
+ {
+ return;
+ }
+
+ int start = startAt;
+ int end = startAt + count;
+
+ // Check if we could access invalid item while sorting
+ if( start >= (int)buffer->numElements || end > (int)buffer->numElements )
+ {
+ asIScriptContext *ctx = asGetActiveContext();
+
+ // Throw an exception
+ if( ctx )
+ {
+ ctx->SetException("Index out of bounds");
+ }
+
+ return;
+ }
+
+ asBYTE tmp[16];
+ asIScriptContext *cmpContext = 0;
+ bool isNested = false;
+
+ if( subTypeId & ~asTYPEID_MASK_SEQNBR )
+ {
+ // Try to reuse the active context
+ cmpContext = asGetActiveContext();
+ if( cmpContext )
+ {
+ if( cmpContext->GetEngine() == objType->GetEngine() && cmpContext->PushState() >= 0 )
+ isNested = true;
+ else
+ cmpContext = 0;
+ }
+ if( cmpContext == 0 )
+ {
+ cmpContext = objType->GetEngine()->RequestContext();
+ }
+ }
+
+ // Insertion sort
+ for( int i = start + 1; i < end; i++ )
+ {
+ Copy(tmp, GetArrayItemPointer(i));
+
+ int j = i - 1;
+
+ while( j >= start && Less(GetDataPointer(tmp), At(j), asc, cmpContext, cache) )
+ {
+ Copy(GetArrayItemPointer(j + 1), GetArrayItemPointer(j));
+ j--;
+ }
+
+ Copy(GetArrayItemPointer(j + 1), tmp);
+ }
+
+ if( cmpContext )
+ {
+ if( isNested )
+ {
+ asEContextState state = cmpContext->GetState();
+ cmpContext->PopState();
+ if( state == asEXECUTION_ABORTED )
+ cmpContext->Abort();
+ }
+ else
+ objType->GetEngine()->ReturnContext(cmpContext);
+ }
+}
+
+// Sort with script callback for comparing elements
+void CScriptArray::Sort(asIScriptFunction *func, asUINT startAt, asUINT count)
+{
+ // No need to sort
+ if (count < 2)
+ return;
+
+ // Check if we could access invalid item while sorting
+ asUINT start = startAt;
+ asUINT end = asQWORD(startAt) + asQWORD(count) >= (asQWORD(1)<<32) ? 0xFFFFFFFF : startAt + count;
+ if (end > buffer->numElements)
+ end = buffer->numElements;
+
+ if (start >= buffer->numElements)
+ {
+ asIScriptContext *ctx = asGetActiveContext();
+
+ // Throw an exception
+ if (ctx)
+ ctx->SetException("Index out of bounds");
+
+ return;
+ }
+
+ asBYTE tmp[16];
+ asIScriptContext *cmpContext = 0;
+ bool isNested = false;
+
+ // Try to reuse the active context
+ cmpContext = asGetActiveContext();
+ if (cmpContext)
+ {
+ if (cmpContext->GetEngine() == objType->GetEngine() && cmpContext->PushState() >= 0)
+ isNested = true;
+ else
+ cmpContext = 0;
+ }
+ if (cmpContext == 0)
+ cmpContext = objType->GetEngine()->RequestContext();
+
+ // Insertion sort
+ for (asUINT i = start + 1; i < end; i++)
+ {
+ Copy(tmp, GetArrayItemPointer(i));
+
+ asUINT j = i - 1;
+
+ while (j != 0xFFFFFFFF && j >= start )
+ {
+ cmpContext->Prepare(func);
+ cmpContext->SetArgAddress(0, GetDataPointer(tmp));
+ cmpContext->SetArgAddress(1, At(j));
+ int r = cmpContext->Execute();
+ if (r != asEXECUTION_FINISHED)
+ break;
+ if (*(bool*)(cmpContext->GetAddressOfReturnValue()))
+ {
+ Copy(GetArrayItemPointer(j + 1), GetArrayItemPointer(j));
+ j--;
+ }
+ else
+ break;
+ }
+
+ Copy(GetArrayItemPointer(j + 1), tmp);
+ }
+
+ if (cmpContext)
+ {
+ if (isNested)
+ {
+ asEContextState state = cmpContext->GetState();
+ cmpContext->PopState();
+ if (state == asEXECUTION_ABORTED)
+ cmpContext->Abort();
+ }
+ else
+ objType->GetEngine()->ReturnContext(cmpContext);
+ }
+}
+
+// internal
+void CScriptArray::CopyBuffer(SArrayBuffer *dst, SArrayBuffer *src)
+{
+ asIScriptEngine *engine = objType->GetEngine();
+ if( subTypeId & asTYPEID_OBJHANDLE )
+ {
+ // Copy the references and increase the reference counters
+ if( dst->numElements > 0 && src->numElements > 0 )
+ {
+ int count = dst->numElements > src->numElements ? src->numElements : dst->numElements;
+
+ void **max = (void**)(dst->data + count * sizeof(void*));
+ void **d = (void**)dst->data;
+ void **s = (void**)src->data;
+
+ for( ; d < max; d++, s++ )
+ {
+ void *tmp = *d;
+ *d = *s;
+ if( *d )
+ engine->AddRefScriptObject(*d, objType->GetSubType());
+ // Release the old ref after incrementing the new to avoid problem incase it is the same ref
+ if( tmp )
+ engine->ReleaseScriptObject(tmp, objType->GetSubType());
+ }
+ }
+ }
+ else
+ {
+ if( dst->numElements > 0 && src->numElements > 0 )
+ {
+ int count = dst->numElements > src->numElements ? src->numElements : dst->numElements;
+ if( subTypeId & asTYPEID_MASK_OBJECT )
+ {
+ // Call the assignment operator on all of the objects
+ void **max = (void**)(dst->data + count * sizeof(void*));
+ void **d = (void**)dst->data;
+ void **s = (void**)src->data;
+
+ asITypeInfo *subType = objType->GetSubType();
+ for( ; d < max; d++, s++ )
+ engine->AssignScriptObject(*d, *s, subType);
+ }
+ else
+ {
+ // Primitives are copied byte for byte
+ memcpy(dst->data, src->data, count*elementSize);
+ }
+ }
+ }
+}
+
+// internal
+// Precache some info
+void CScriptArray::Precache()
+{
+ subTypeId = objType->GetSubTypeId();
+
+ // Check if it is an array of objects. Only for these do we need to cache anything
+ // Type ids for primitives and enums only has the sequence number part
+ if( !(subTypeId & ~asTYPEID_MASK_SEQNBR) )
+ return;
+
+ // The opCmp and opEquals methods are cached because the searching for the
+ // methods is quite time consuming if a lot of array objects are created.
+
+ // First check if a cache already exists for this array type
+ SArrayCache *cache = reinterpret_cast<SArrayCache*>(objType->GetUserData(ARRAY_CACHE));
+ if( cache ) return;
+
+ // We need to make sure the cache is created only once, even
+ // if multiple threads reach the same point at the same time
+ asAcquireExclusiveLock();
+
+ // Now that we got the lock, we need to check again to make sure the
+ // cache wasn't created while we were waiting for the lock
+ cache = reinterpret_cast<SArrayCache*>(objType->GetUserData(ARRAY_CACHE));
+ if( cache )
+ {
+ asReleaseExclusiveLock();
+ return;
+ }
+
+ // Create the cache
+ cache = reinterpret_cast<SArrayCache*>(userAlloc(sizeof(SArrayCache)));
+ memset(cache, 0, sizeof(SArrayCache));
+
+ // If the sub type is a handle to const, then the methods must be const too
+ bool mustBeConst = (subTypeId & asTYPEID_HANDLETOCONST) ? true : false;
+
+ asITypeInfo *subType = objType->GetEngine()->GetTypeInfoById(subTypeId);
+ if( subType )
+ {
+ for( asUINT i = 0; i < subType->GetMethodCount(); i++ )
+ {
+ asIScriptFunction *func = subType->GetMethodByIndex(i);
+
+ if( func->GetParamCount() == 1 && (!mustBeConst || func->IsReadOnly()) )
+ {
+ asDWORD flags = 0;
+ int returnTypeId = func->GetReturnTypeId(&flags);
+
+ // The method must not return a reference
+ if( flags != asTM_NONE )
+ continue;
+
+ // opCmp returns an int and opEquals returns a bool
+ bool isCmp = false, isEq = false;
+ if( returnTypeId == asTYPEID_INT32 && strcmp(func->GetName(), "opCmp") == 0 )
+ isCmp = true;
+ if( returnTypeId == asTYPEID_BOOL && strcmp(func->GetName(), "opEquals") == 0 )
+ isEq = true;
+
+ if( !isCmp && !isEq )
+ continue;
+
+ // The parameter must either be a reference to the subtype or a handle to the subtype
+ int paramTypeId;
+ func->GetParam(0, &paramTypeId, &flags);
+
+ if( (paramTypeId & ~(asTYPEID_OBJHANDLE|asTYPEID_HANDLETOCONST)) != (subTypeId & ~(asTYPEID_OBJHANDLE|asTYPEID_HANDLETOCONST)) )
+ continue;
+
+ if( (flags & asTM_INREF) )
+ {
+ if( (paramTypeId & asTYPEID_OBJHANDLE) || (mustBeConst && !(flags & asTM_CONST)) )
+ continue;
+ }
+ else if( paramTypeId & asTYPEID_OBJHANDLE )
+ {
+ if( mustBeConst && !(paramTypeId & asTYPEID_HANDLETOCONST) )
+ continue;
+ }
+ else
+ continue;
+
+ if( isCmp )
+ {
+ if( cache->cmpFunc || cache->cmpFuncReturnCode )
+ {
+ cache->cmpFunc = 0;
+ cache->cmpFuncReturnCode = asMULTIPLE_FUNCTIONS;
+ }
+ else
+ cache->cmpFunc = func;
+ }
+ else if( isEq )
+ {
+ if( cache->eqFunc || cache->eqFuncReturnCode )
+ {
+ cache->eqFunc = 0;
+ cache->eqFuncReturnCode = asMULTIPLE_FUNCTIONS;
+ }
+ else
+ cache->eqFunc = func;
+ }
+ }
+ }
+ }
+
+ if( cache->eqFunc == 0 && cache->eqFuncReturnCode == 0 )
+ cache->eqFuncReturnCode = asNO_FUNCTION;
+ if( cache->cmpFunc == 0 && cache->cmpFuncReturnCode == 0 )
+ cache->cmpFuncReturnCode = asNO_FUNCTION;
+
+ // Set the user data only at the end so others that retrieve it will know it is complete
+ objType->SetUserData(cache, ARRAY_CACHE);
+
+ asReleaseExclusiveLock();
+}
+
+// GC behaviour
+void CScriptArray::EnumReferences(asIScriptEngine *engine)
+{
+ // TODO: If garbage collection can be done from a separate thread, then this method must be
+ // protected so that it doesn't get lost during the iteration if the array is modified
+
+ // If the array is holding handles, then we need to notify the GC of them
+ if( subTypeId & asTYPEID_MASK_OBJECT )
+ {
+ void **d = (void**)buffer->data;
+ for( asUINT n = 0; n < buffer->numElements; n++ )
+ {
+ if( d[n] )
+ engine->GCEnumCallback(d[n]);
+ }
+ }
+}
+
+// GC behaviour
+void CScriptArray::ReleaseAllHandles(asIScriptEngine *)
+{
+ // Resizing to zero will release everything
+ Resize(0);
+}
+
+void CScriptArray::AddRef() const
+{
+ // Clear the GC flag then increase the counter
+ gcFlag = false;
+ asAtomicInc(refCount);
+}
+
+void CScriptArray::Release() const
+{
+ // Clearing the GC flag then descrease the counter
+ gcFlag = false;
+ if( asAtomicDec(refCount) == 0 )
+ {
+ // When reaching 0 no more references to this instance
+ // exists and the object should be destroyed
+ this->~CScriptArray();
+ userFree(const_cast<CScriptArray*>(this));
+ }
+}
+
+// GC behaviour
+int CScriptArray::GetRefCount()
+{
+ return refCount;
+}
+
+// GC behaviour
+void CScriptArray::SetFlag()
+{
+ gcFlag = true;
+}
+
+// GC behaviour
+bool CScriptArray::GetFlag()
+{
+ return gcFlag;
+}
+
+//--------------------------------------------
+// Generic calling conventions
+
+static void ScriptArrayFactory_Generic(asIScriptGeneric *gen)
+{
+ asITypeInfo *ti = *(asITypeInfo**)gen->GetAddressOfArg(0);
+
+ *reinterpret_cast<CScriptArray**>(gen->GetAddressOfReturnLocation()) = CScriptArray::Create(ti);
+}
+
+static void ScriptArrayFactory2_Generic(asIScriptGeneric *gen)
+{
+ asITypeInfo *ti = *(asITypeInfo**)gen->GetAddressOfArg(0);
+ asUINT length = gen->GetArgDWord(1);
+
+ *reinterpret_cast<CScriptArray**>(gen->GetAddressOfReturnLocation()) = CScriptArray::Create(ti, length);
+}
+
+static void ScriptArrayListFactory_Generic(asIScriptGeneric *gen)
+{
+ asITypeInfo *ti = *(asITypeInfo**)gen->GetAddressOfArg(0);
+ void *buf = gen->GetArgAddress(1);
+
+ *reinterpret_cast<CScriptArray**>(gen->GetAddressOfReturnLocation()) = CScriptArray::Create(ti, buf);
+}
+
+static void ScriptArrayFactoryDefVal_Generic(asIScriptGeneric *gen)
+{
+ asITypeInfo *ti = *(asITypeInfo**)gen->GetAddressOfArg(0);
+ asUINT length = gen->GetArgDWord(1);
+ void *defVal = gen->GetArgAddress(2);
+
+ *reinterpret_cast<CScriptArray**>(gen->GetAddressOfReturnLocation()) = CScriptArray::Create(ti, length, defVal);
+}
+
+static void ScriptArrayTemplateCallback_Generic(asIScriptGeneric *gen)
+{
+ asITypeInfo *ti = *(asITypeInfo**)gen->GetAddressOfArg(0);
+ bool *dontGarbageCollect = *(bool**)gen->GetAddressOfArg(1);
+ *reinterpret_cast<bool*>(gen->GetAddressOfReturnLocation()) = ScriptArrayTemplateCallback(ti, *dontGarbageCollect);
+}
+
+static void ScriptArrayAssignment_Generic(asIScriptGeneric *gen)
+{
+ CScriptArray *other = (CScriptArray*)gen->GetArgObject(0);
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ *self = *other;
+ gen->SetReturnObject(self);
+}
+
+static void ScriptArrayEquals_Generic(asIScriptGeneric *gen)
+{
+ CScriptArray *other = (CScriptArray*)gen->GetArgObject(0);
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ gen->SetReturnByte(self->operator==(*other));
+}
+
+static void ScriptArrayFind_Generic(asIScriptGeneric *gen)
+{
+ void *value = gen->GetArgAddress(0);
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ gen->SetReturnDWord(self->Find(value));
+}
+
+static void ScriptArrayFind2_Generic(asIScriptGeneric *gen)
+{
+ asUINT index = gen->GetArgDWord(0);
+ void *value = gen->GetArgAddress(1);
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ gen->SetReturnDWord(self->Find(index, value));
+}
+
+static void ScriptArrayFindByRef_Generic(asIScriptGeneric *gen)
+{
+ void *value = gen->GetArgAddress(0);
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ gen->SetReturnDWord(self->FindByRef(value));
+}
+
+static void ScriptArrayFindByRef2_Generic(asIScriptGeneric *gen)
+{
+ asUINT index = gen->GetArgDWord(0);
+ void *value = gen->GetArgAddress(1);
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ gen->SetReturnDWord(self->FindByRef(index, value));
+}
+
+static void ScriptArrayAt_Generic(asIScriptGeneric *gen)
+{
+ asUINT index = gen->GetArgDWord(0);
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+
+ gen->SetReturnAddress(self->At(index));
+}
+
+static void ScriptArrayInsertAt_Generic(asIScriptGeneric *gen)
+{
+ asUINT index = gen->GetArgDWord(0);
+ void *value = gen->GetArgAddress(1);
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ self->InsertAt(index, value);
+}
+
+static void ScriptArrayInsertAtArray_Generic(asIScriptGeneric *gen)
+{
+ asUINT index = gen->GetArgDWord(0);
+ CScriptArray *array = (CScriptArray*)gen->GetArgAddress(1);
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ self->InsertAt(index, *array);
+}
+
+static void ScriptArrayRemoveAt_Generic(asIScriptGeneric *gen)
+{
+ asUINT index = gen->GetArgDWord(0);
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ self->RemoveAt(index);
+}
+
+static void ScriptArrayRemoveRange_Generic(asIScriptGeneric *gen)
+{
+ asUINT start = gen->GetArgDWord(0);
+ asUINT count = gen->GetArgDWord(1);
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ self->RemoveRange(start, count);
+}
+
+static void ScriptArrayInsertLast_Generic(asIScriptGeneric *gen)
+{
+ void *value = gen->GetArgAddress(0);
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ self->InsertLast(value);
+}
+
+static void ScriptArrayRemoveLast_Generic(asIScriptGeneric *gen)
+{
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ self->RemoveLast();
+}
+
+static void ScriptArrayLength_Generic(asIScriptGeneric *gen)
+{
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+
+ gen->SetReturnDWord(self->GetSize());
+}
+
+static void ScriptArrayResize_Generic(asIScriptGeneric *gen)
+{
+ asUINT size = gen->GetArgDWord(0);
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+
+ self->Resize(size);
+}
+
+static void ScriptArrayReserve_Generic(asIScriptGeneric *gen)
+{
+ asUINT size = gen->GetArgDWord(0);
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ self->Reserve(size);
+}
+
+static void ScriptArraySortAsc_Generic(asIScriptGeneric *gen)
+{
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ self->SortAsc();
+}
+
+static void ScriptArrayReverse_Generic(asIScriptGeneric *gen)
+{
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ self->Reverse();
+}
+
+static void ScriptArrayIsEmpty_Generic(asIScriptGeneric *gen)
+{
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ *reinterpret_cast<bool*>(gen->GetAddressOfReturnLocation()) = self->IsEmpty();
+}
+
+static void ScriptArraySortAsc2_Generic(asIScriptGeneric *gen)
+{
+ asUINT index = gen->GetArgDWord(0);
+ asUINT count = gen->GetArgDWord(1);
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ self->SortAsc(index, count);
+}
+
+static void ScriptArraySortDesc_Generic(asIScriptGeneric *gen)
+{
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ self->SortDesc();
+}
+
+static void ScriptArraySortDesc2_Generic(asIScriptGeneric *gen)
+{
+ asUINT index = gen->GetArgDWord(0);
+ asUINT count = gen->GetArgDWord(1);
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ self->SortDesc(index, count);
+}
+
+static void ScriptArraySortCallback_Generic(asIScriptGeneric *gen)
+{
+ asIScriptFunction *callback = (asIScriptFunction*)gen->GetArgAddress(0);
+ asUINT startAt = gen->GetArgDWord(1);
+ asUINT count = gen->GetArgDWord(2);
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ self->Sort(callback, startAt, count);
+}
+
+static void ScriptArrayAddRef_Generic(asIScriptGeneric *gen)
+{
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ self->AddRef();
+}
+
+static void ScriptArrayRelease_Generic(asIScriptGeneric *gen)
+{
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ self->Release();
+}
+
+static void ScriptArrayGetRefCount_Generic(asIScriptGeneric *gen)
+{
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ *reinterpret_cast<int*>(gen->GetAddressOfReturnLocation()) = self->GetRefCount();
+}
+
+static void ScriptArraySetFlag_Generic(asIScriptGeneric *gen)
+{
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ self->SetFlag();
+}
+
+static void ScriptArrayGetFlag_Generic(asIScriptGeneric *gen)
+{
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ *reinterpret_cast<bool*>(gen->GetAddressOfReturnLocation()) = self->GetFlag();
+}
+
+static void ScriptArrayEnumReferences_Generic(asIScriptGeneric *gen)
+{
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ asIScriptEngine *engine = *(asIScriptEngine**)gen->GetAddressOfArg(0);
+ self->EnumReferences(engine);
+}
+
+static void ScriptArrayReleaseAllHandles_Generic(asIScriptGeneric *gen)
+{
+ CScriptArray *self = (CScriptArray*)gen->GetObject();
+ asIScriptEngine *engine = *(asIScriptEngine**)gen->GetAddressOfArg(0);
+ self->ReleaseAllHandles(engine);
+}
+
+static void RegisterScriptArray_Generic(asIScriptEngine *engine)
+{
+ int r = 0;
+ UNUSED_VAR(r);
+
+ engine->SetTypeInfoUserDataCleanupCallback(CleanupTypeInfoArrayCache, ARRAY_CACHE);
+
+ r = engine->RegisterObjectType("array<class T>", 0, asOBJ_REF | asOBJ_GC | asOBJ_TEMPLATE); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("array<T>", asBEHAVE_TEMPLATE_CALLBACK, "bool f(int&in, bool&out)", asFUNCTION(ScriptArrayTemplateCallback_Generic), asCALL_GENERIC); assert( r >= 0 );
+
+ r = engine->RegisterObjectBehaviour("array<T>", asBEHAVE_FACTORY, "array<T>@ f(int&in)", asFUNCTION(ScriptArrayFactory_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("array<T>", asBEHAVE_FACTORY, "array<T>@ f(int&in, uint length)", asFUNCTION(ScriptArrayFactory2_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("array<T>", asBEHAVE_FACTORY, "array<T>@ f(int&in, uint length, const T &in value)", asFUNCTION(ScriptArrayFactoryDefVal_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("array<T>", asBEHAVE_LIST_FACTORY, "array<T>@ f(int&in, int&in) {repeat T}", asFUNCTION(ScriptArrayListFactory_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("array<T>", asBEHAVE_ADDREF, "void f()", asFUNCTION(ScriptArrayAddRef_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("array<T>", asBEHAVE_RELEASE, "void f()", asFUNCTION(ScriptArrayRelease_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "T &opIndex(uint index)", asFUNCTION(ScriptArrayAt_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "const T &opIndex(uint index) const", asFUNCTION(ScriptArrayAt_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "array<T> &opAssign(const array<T>&in)", asFUNCTION(ScriptArrayAssignment_Generic), asCALL_GENERIC); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("array<T>", "void insertAt(uint index, const T&in value)", asFUNCTION(ScriptArrayInsertAt_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "void insertAt(uint index, const array<T>& arr)", asFUNCTION(ScriptArrayInsertAtArray_Generic), asCALL_GENERIC); assert(r >= 0);
+ r = engine->RegisterObjectMethod("array<T>", "void insertLast(const T&in value)", asFUNCTION(ScriptArrayInsertLast_Generic), asCALL_GENERIC); assert(r >= 0);
+ r = engine->RegisterObjectMethod("array<T>", "void removeAt(uint index)", asFUNCTION(ScriptArrayRemoveAt_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "void removeLast()", asFUNCTION(ScriptArrayRemoveLast_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "void removeRange(uint start, uint count)", asFUNCTION(ScriptArrayRemoveRange_Generic), asCALL_GENERIC); assert(r >= 0);
+ r = engine->RegisterObjectMethod("array<T>", "uint length() const", asFUNCTION(ScriptArrayLength_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "void reserve(uint length)", asFUNCTION(ScriptArrayReserve_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "void resize(uint length)", asFUNCTION(ScriptArrayResize_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "void sortAsc()", asFUNCTION(ScriptArraySortAsc_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "void sortAsc(uint startAt, uint count)", asFUNCTION(ScriptArraySortAsc2_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "void sortDesc()", asFUNCTION(ScriptArraySortDesc_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "void sortDesc(uint startAt, uint count)", asFUNCTION(ScriptArraySortDesc2_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "void reverse()", asFUNCTION(ScriptArrayReverse_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "int find(const T&in if_handle_then_const value) const", asFUNCTION(ScriptArrayFind_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "int find(uint startAt, const T&in if_handle_then_const value) const", asFUNCTION(ScriptArrayFind2_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "int findByRef(const T&in if_handle_then_const value) const", asFUNCTION(ScriptArrayFindByRef_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "int findByRef(uint startAt, const T&in if_handle_then_const value) const", asFUNCTION(ScriptArrayFindByRef2_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "bool opEquals(const array<T>&in) const", asFUNCTION(ScriptArrayEquals_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "bool isEmpty() const", asFUNCTION(ScriptArrayIsEmpty_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterFuncdef("bool array<T>::less(const T&in a, const T&in b)");
+ r = engine->RegisterObjectMethod("array<T>", "void sort(const less &in, uint startAt = 0, uint count = uint(-1))", asFUNCTION(ScriptArraySortCallback_Generic), asCALL_GENERIC); assert(r >= 0);
+ r = engine->RegisterObjectMethod("array<T>", "uint get_length() const", asFUNCTION(ScriptArrayLength_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("array<T>", "void set_length(uint)", asFUNCTION(ScriptArrayResize_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("array<T>", asBEHAVE_GETREFCOUNT, "int f()", asFUNCTION(ScriptArrayGetRefCount_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("array<T>", asBEHAVE_SETGCFLAG, "void f()", asFUNCTION(ScriptArraySetFlag_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("array<T>", asBEHAVE_GETGCFLAG, "bool f()", asFUNCTION(ScriptArrayGetFlag_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("array<T>", asBEHAVE_ENUMREFS, "void f(int&in)", asFUNCTION(ScriptArrayEnumReferences_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("array<T>", asBEHAVE_RELEASEREFS, "void f(int&in)", asFUNCTION(ScriptArrayReleaseAllHandles_Generic), asCALL_GENERIC); assert( r >= 0 );
+}
+
+END_AS_NAMESPACE
diff --git a/Source/Scripting/angelscript/add_on/scriptarray/scriptarray.h b/Source/Scripting/angelscript/add_on/scriptarray/scriptarray.h
new file mode 100644
index 00000000..e6eb2783
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/scriptarray/scriptarray.h
@@ -0,0 +1,137 @@
+#ifndef SCRIPTARRAY_H
+#define SCRIPTARRAY_H
+
+#ifndef ANGELSCRIPT_H
+// Avoid having to inform include path if header is already include before
+#include <angelscript.h>
+#endif
+
+// Sometimes it may be desired to use the same method names as used by C++ STL.
+// This may for example reduce time when converting code from script to C++ or
+// back.
+//
+// 0 = off
+// 1 = on
+
+#ifndef AS_USE_STLNAMES
+#define AS_USE_STLNAMES 0
+#endif
+
+BEGIN_AS_NAMESPACE
+
+struct SArrayBuffer;
+struct SArrayCache;
+
+class CScriptArray
+{
+public:
+ // Set the memory functions that should be used by all CScriptArrays
+ static void SetMemoryFunctions(asALLOCFUNC_t allocFunc, asFREEFUNC_t freeFunc);
+
+ // Factory functions
+ static CScriptArray *Create(asITypeInfo *ot);
+ static CScriptArray *Create(asITypeInfo *ot, asUINT length);
+ static CScriptArray *Create(asITypeInfo *ot, asUINT length, void *defaultValue);
+ static CScriptArray *Create(asITypeInfo *ot, void *listBuffer);
+
+ // Memory management
+ void AddRef() const;
+ void Release() const;
+
+ // Type information
+ asITypeInfo *GetArrayObjectType() const;
+ int GetArrayTypeId() const;
+ int GetElementTypeId() const;
+
+ // Get the current size
+ asUINT GetSize() const;
+
+ // Returns true if the array is empty
+ bool IsEmpty() const;
+
+ // Pre-allocates memory for elements
+ void Reserve(asUINT maxElements);
+
+ // Resize the array
+ void Resize(asUINT numElements);
+
+ // Get a pointer to an element. Returns 0 if out of bounds
+ void *At(asUINT index);
+ const void *At(asUINT index) const;
+
+ // Set value of an element.
+ // The value arg should be a pointer to the value that will be copied to the element.
+ // Remember, if the array holds handles the value parameter should be the
+ // address of the handle. The refCount of the object will also be incremented
+ void SetValue(asUINT index, void *value);
+
+ // Copy the contents of one array to another (only if the types are the same)
+ CScriptArray &operator=(const CScriptArray&);
+
+ // Compare two arrays
+ bool operator==(const CScriptArray &) const;
+
+ // Array manipulation
+ void InsertAt(asUINT index, void *value);
+ void InsertAt(asUINT index, const CScriptArray &arr);
+ void InsertLast(void *value);
+ void RemoveAt(asUINT index);
+ void RemoveLast();
+ void RemoveRange(asUINT start, asUINT count);
+ void SortAsc();
+ void SortDesc();
+ void SortAsc(asUINT startAt, asUINT count);
+ void SortDesc(asUINT startAt, asUINT count);
+ void Sort(asUINT startAt, asUINT count, bool asc);
+ void Sort(asIScriptFunction *less, asUINT startAt, asUINT count);
+ void Reverse();
+ int Find(void *value) const;
+ int Find(asUINT startAt, void *value) const;
+ int FindByRef(void *ref) const;
+ int FindByRef(asUINT startAt, void *ref) const;
+
+ // Return the address of internal buffer for direct manipulation of elements
+ void *GetBuffer();
+
+ // GC methods
+ int GetRefCount();
+ void SetFlag();
+ bool GetFlag();
+ void EnumReferences(asIScriptEngine *engine);
+ void ReleaseAllHandles(asIScriptEngine *engine);
+
+protected:
+ mutable int refCount;
+ mutable bool gcFlag;
+ asITypeInfo *objType;
+ SArrayBuffer *buffer;
+ int elementSize;
+ int subTypeId;
+
+ // Constructors
+ CScriptArray(asITypeInfo *ot, void *initBuf); // Called from script when initialized with list
+ CScriptArray(asUINT length, asITypeInfo *ot);
+ CScriptArray(asUINT length, void *defVal, asITypeInfo *ot);
+ CScriptArray(const CScriptArray &other);
+ virtual ~CScriptArray();
+
+ bool Less(const void *a, const void *b, bool asc, asIScriptContext *ctx, SArrayCache *cache);
+ void *GetArrayItemPointer(int index);
+ void *GetDataPointer(void *buffer);
+ void Copy(void *dst, void *src);
+ void Precache();
+ bool CheckMaxSize(asUINT numElements);
+ void Resize(int delta, asUINT at);
+ void CreateBuffer(SArrayBuffer **buf, asUINT numElements);
+ void DeleteBuffer(SArrayBuffer *buf);
+ void CopyBuffer(SArrayBuffer *dst, SArrayBuffer *src);
+ void Construct(SArrayBuffer *buf, asUINT start, asUINT end);
+ void Destruct(SArrayBuffer *buf, asUINT start, asUINT end);
+ bool Equals(const void *a, const void *b, asIScriptContext *ctx, SArrayCache *cache) const;
+};
+
+void RegisterScriptArray(asIScriptEngine *engine, bool defaultArray);
+
+END_AS_NAMESPACE
+
+#endif
diff --git a/Source/Scripting/angelscript/add_on/scriptbuilder/scriptbuilder.cpp b/Source/Scripting/angelscript/add_on/scriptbuilder/scriptbuilder.cpp
new file mode 100644
index 00000000..e938c8f4
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/scriptbuilder/scriptbuilder.cpp
@@ -0,0 +1,1100 @@
+#include "scriptbuilder.h"
+#include <vector>
+#include <assert.h>
+using namespace std;
+
+#include <stdio.h>
+#if defined(_MSC_VER) && !defined(_WIN32_WCE) && !defined(__S3E__)
+#include <direct.h>
+#endif
+#ifdef _WIN32_WCE
+#include <windows.h> // For GetModuleFileName()
+#endif
+
+#if defined(__S3E__) || defined(__APPLE__) || defined(__GNUC__)
+#include <unistd.h> // For getcwd()
+#endif
+
+BEGIN_AS_NAMESPACE
+
+// Helper functions
+static string GetCurrentDir();
+static string GetAbsolutePath(const string &path);
+
+
+CScriptBuilder::CScriptBuilder()
+{
+ engine = 0;
+ module = 0;
+
+ includeCallback = 0;
+ callbackParam = 0;
+}
+
+void CScriptBuilder::SetIncludeCallback(INCLUDECALLBACK_t callback, void *userParam)
+{
+ includeCallback = callback;
+ callbackParam = userParam;
+}
+
+int CScriptBuilder::StartNewModule(asIScriptEngine *inEngine, const char *moduleName)
+{
+ if(inEngine == 0 ) return -1;
+
+ engine = inEngine;
+ module = inEngine->GetModule(moduleName, asGM_ALWAYS_CREATE);
+ if( module == 0 )
+ return -1;
+
+ ClearAll();
+
+ return 0;
+}
+
+asIScriptModule *CScriptBuilder::GetModule()
+{
+ return module;
+}
+
+unsigned int CScriptBuilder::GetSectionCount() const
+{
+ return (unsigned int)(includedScripts.size());
+}
+
+string CScriptBuilder::GetSectionName(unsigned int idx) const
+{
+ if( idx >= includedScripts.size() ) return "";
+
+#ifdef _WIN32
+ set<string, ci_less>::const_iterator it = includedScripts.begin();
+#else
+ set<string>::const_iterator it = includedScripts.begin();
+#endif
+ while( idx-- > 0 ) it++;
+ return *it;
+}
+
+// Returns 1 if the section was included
+// Returns 0 if the section was not included because it had already been included before
+// Returns <0 if there was an error
+int CScriptBuilder::AddSectionFromFile(const char *filename)
+{
+ // The file name stored in the set should be the fully resolved name because
+ // it is possible to name the same file in multiple ways using relative paths.
+ string fullpath = GetAbsolutePath(filename);
+
+ if( IncludeIfNotAlreadyIncluded(fullpath.c_str()) )
+ {
+ int r = LoadScriptSection(fullpath.c_str());
+ if( r < 0 )
+ return r;
+ else
+ return 1;
+ }
+
+ return 0;
+}
+
+// Returns 1 if the section was included
+// Returns 0 if the section was not included because it had already been included before
+// Returns <0 if there was an error
+int CScriptBuilder::AddSectionFromMemory(const char *sectionName, const char *scriptCode, unsigned int scriptLength, int lineOffset)
+{
+ if( IncludeIfNotAlreadyIncluded(sectionName) )
+ {
+ int r = ProcessScriptSection(scriptCode, scriptLength, sectionName, lineOffset);
+ if( r < 0 )
+ return r;
+ else
+ return 1;
+ }
+
+ return 0;
+}
+
+int CScriptBuilder::BuildModule()
+{
+ return Build();
+}
+
+void CScriptBuilder::DefineWord(const char *word)
+{
+ string sword = word;
+ if( definedWords.find(sword) == definedWords.end() )
+ {
+ definedWords.insert(sword);
+ }
+}
+
+void CScriptBuilder::ClearAll()
+{
+ includedScripts.clear();
+
+#if AS_PROCESS_METADATA == 1
+ currentClass = "";
+ currentNamespace = "";
+
+ foundDeclarations.clear();
+ typeMetadataMap.clear();
+ funcMetadataMap.clear();
+ varMetadataMap.clear();
+#endif
+}
+
+bool CScriptBuilder::IncludeIfNotAlreadyIncluded(const char *filename)
+{
+ string scriptFile = filename;
+ if( includedScripts.find(scriptFile) != includedScripts.end() )
+ {
+ // Already included
+ return false;
+ }
+
+ // Add the file to the set of included sections
+ includedScripts.insert(scriptFile);
+
+ return true;
+}
+
+int CScriptBuilder::LoadScriptSection(const char *filename)
+{
+ // Open the script file
+ string scriptFile = filename;
+#if _MSC_VER >= 1500 && !defined(__S3E__)
+ FILE *f = 0;
+ fopen_s(&f, scriptFile.c_str(), "rb");
+#else
+ FILE *f = fopen(scriptFile.c_str(), "rb");
+#endif
+ if( f == 0 )
+ {
+ // Write a message to the engine's message callback
+ string msg = "Failed to open script file '" + GetAbsolutePath(scriptFile) + "'";
+ engine->WriteMessage(filename, 0, 0, asMSGTYPE_ERROR, msg.c_str());
+
+ // TODO: Write the file where this one was included from
+
+ return -1;
+ }
+
+ // Determine size of the file
+ fseek(f, 0, SEEK_END);
+ int len = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ // On Win32 it is possible to do the following instead
+ // int len = _filelength(_fileno(f));
+
+ // Read the entire file
+ string code;
+ size_t c = 0;
+ if( len > 0 )
+ {
+ code.resize(len);
+ c = fread(&code[0], len, 1, f);
+ }
+
+ fclose(f);
+
+ if( c == 0 && len > 0 )
+ {
+ // Write a message to the engine's message callback
+ string msg = "Failed to load script file '" + GetAbsolutePath(scriptFile) + "'";
+ engine->WriteMessage(filename, 0, 0, asMSGTYPE_ERROR, msg.c_str());
+ return -1;
+ }
+
+ // Process the script section even if it is zero length so that the name is registered
+ return ProcessScriptSection(code.c_str(), (unsigned int)(code.length()), filename, 0);
+}
+
+int CScriptBuilder::ProcessScriptSection(const char *script, unsigned int length, const char *sectionname, int lineOffset)
+{
+ vector<string> includes;
+
+ // Perform a superficial parsing of the script first to store the metadata
+ if( length )
+ modifiedScript.assign(script, length);
+ else
+ modifiedScript = script;
+
+ // First perform the checks for #if directives to exclude code that shouldn't be compiled
+ unsigned int pos = 0;
+ int nested = 0;
+ while( pos < modifiedScript.size() )
+ {
+ asUINT len = 0;
+ asETokenClass t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
+ if( t == asTC_UNKNOWN && modifiedScript[pos] == '#' && (pos + 1 < modifiedScript.size()) )
+ {
+ int start = pos++;
+
+ // Is this an #if directive?
+ t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
+
+ string token;
+ token.assign(&modifiedScript[pos], len);
+
+ pos += len;
+
+ if( token == "if" )
+ {
+ t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
+ if( t == asTC_WHITESPACE )
+ {
+ pos += len;
+ t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
+ }
+
+ if( t == asTC_IDENTIFIER )
+ {
+ string word;
+ word.assign(&modifiedScript[pos], len);
+
+ // Overwrite the #if directive with space characters to avoid compiler error
+ pos += len;
+ OverwriteCode(start, pos-start);
+
+ // Has this identifier been defined by the application or not?
+ if( definedWords.find(word) == definedWords.end() )
+ {
+ // Exclude all the code until and including the #endif
+ pos = ExcludeCode(pos);
+ }
+ else
+ {
+ nested++;
+ }
+ }
+ }
+ else if( token == "endif" )
+ {
+ // Only remove the #endif if there was a matching #if
+ if( nested > 0 )
+ {
+ OverwriteCode(start, pos-start);
+ nested--;
+ }
+ }
+ }
+ else
+ pos += len;
+ }
+
+#if AS_PROCESS_METADATA == 1
+ // Preallocate memory
+ string metadata, name, declaration;
+ metadata.reserve(500);
+ declaration.reserve(100);
+#endif
+
+ // Then check for meta data and #include directives
+ pos = 0;
+ while( pos < modifiedScript.size() )
+ {
+ asUINT len = 0;
+ asETokenClass t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
+ if( t == asTC_COMMENT || t == asTC_WHITESPACE )
+ {
+ pos += len;
+ continue;
+ }
+
+#if AS_PROCESS_METADATA == 1
+ // Check if class
+ if( currentClass == "" && modifiedScript.substr(pos,len) == "class" )
+ {
+ // Get the identifier after "class"
+ do
+ {
+ pos += len;
+ if( pos >= modifiedScript.size() )
+ {
+ t = asTC_UNKNOWN;
+ break;
+ }
+ t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
+ } while(t == asTC_COMMENT || t == asTC_WHITESPACE);
+
+ if( t == asTC_IDENTIFIER )
+ {
+ currentClass = modifiedScript.substr(pos,len);
+
+ // Search until first { or ; is encountered
+ while( pos < modifiedScript.length() )
+ {
+ engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
+
+ // If start of class section encountered stop
+ if( modifiedScript[pos] == '{' )
+ {
+ pos += len;
+ break;
+ }
+ else if (modifiedScript[pos] == ';')
+ {
+ // The class declaration has ended and there are no children
+ currentClass = "";
+ pos += len;
+ break;
+ }
+
+ // Check next symbol
+ pos += len;
+ }
+ }
+
+ continue;
+ }
+
+ // Check if end of class
+ if( currentClass != "" && modifiedScript[pos] == '}' )
+ {
+ currentClass = "";
+ pos += len;
+ continue;
+ }
+
+ // Check if namespace
+ if( modifiedScript.substr(pos,len) == "namespace" )
+ {
+ // Get the identifier after "namespace"
+ do
+ {
+ pos += len;
+ t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
+ } while(t == asTC_COMMENT || t == asTC_WHITESPACE);
+
+ if( currentNamespace != "" )
+ currentNamespace += "::";
+ currentNamespace += modifiedScript.substr(pos,len);
+
+ // Search until first { is encountered
+ while( pos < modifiedScript.length() )
+ {
+ engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
+
+ // If start of namespace section encountered stop
+ if( modifiedScript[pos] == '{' )
+ {
+ pos += len;
+ break;
+ }
+
+ // Check next symbol
+ pos += len;
+ }
+
+ continue;
+ }
+
+ // Check if end of namespace
+ if( currentNamespace != "" && modifiedScript[pos] == '}' )
+ {
+ size_t found = currentNamespace.rfind( "::" );
+ if( found != string::npos )
+ {
+ currentNamespace.erase( found );
+ }
+ else
+ {
+ currentNamespace = "";
+ }
+ pos += len;
+ continue;
+ }
+
+ // Is this the start of metadata?
+ if( modifiedScript[pos] == '[' )
+ {
+ // Get the metadata string
+ pos = ExtractMetadataString(pos, metadata);
+
+ // Determine what this metadata is for
+ int type;
+ ExtractDeclaration(pos, name, declaration, type);
+
+ // Store away the declaration in a map for lookup after the build has completed
+ if( type > 0 )
+ {
+ SMetadataDecl decl(metadata, name, declaration, type, currentClass, currentNamespace);
+ foundDeclarations.push_back(decl);
+ }
+ }
+ else
+#endif
+ // Is this a preprocessor directive?
+ if( modifiedScript[pos] == '#' && (pos + 1 < modifiedScript.size()) )
+ {
+ int start = pos++;
+
+ t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
+ if( t == asTC_IDENTIFIER )
+ {
+ string token;
+ token.assign(&modifiedScript[pos], len);
+ if( token == "include" )
+ {
+ pos += len;
+ t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
+ if( t == asTC_WHITESPACE )
+ {
+ pos += len;
+ t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
+ }
+
+ if( t == asTC_VALUE && len > 2 && (modifiedScript[pos] == '"' || modifiedScript[pos] == '\'') )
+ {
+ // Get the include file
+ string includefile;
+ includefile.assign(&modifiedScript[pos+1], len-2);
+ pos += len;
+
+ // Store it for later processing
+ includes.push_back(includefile);
+
+ // Overwrite the include directive with space characters to avoid compiler error
+ OverwriteCode(start, pos-start);
+ }
+ }
+ }
+ }
+ // Don't search for metadata/includes within statement blocks or between tokens in statements
+ else
+ {
+ pos = SkipStatement(pos);
+ }
+ }
+
+ // Build the actual script
+ engine->SetEngineProperty(asEP_COPY_SCRIPT_SECTIONS, true);
+ module->AddScriptSection(sectionname, modifiedScript.c_str(), modifiedScript.size(), lineOffset);
+
+ if( includes.size() > 0 )
+ {
+ // If the callback has been set, then call it for each included file
+ if( includeCallback )
+ {
+ for( int n = 0; n < (int)includes.size(); n++ )
+ {
+ int r = includeCallback(includes[n].c_str(), sectionname, this, callbackParam);
+ if( r < 0 )
+ return r;
+ }
+ }
+ else
+ {
+ // By default we try to load the included file from the relative directory of the current file
+
+ // Determine the path of the current script so that we can resolve relative paths for includes
+ string path = sectionname;
+ size_t posOfSlash = path.find_last_of("/\\");
+ if( posOfSlash != string::npos )
+ path.resize(posOfSlash+1);
+ else
+ path = "";
+
+ // Load the included scripts
+ for( int n = 0; n < (int)includes.size(); n++ )
+ {
+ // If the include is a relative path, then prepend the path of the originating script
+ if( includes[n].find_first_of("/\\") != 0 &&
+ includes[n].find_first_of(":") == string::npos )
+ {
+ includes[n] = path + includes[n];
+ }
+
+ // Include the script section
+ int r = AddSectionFromFile(includes[n].c_str());
+ if( r < 0 )
+ return r;
+ }
+ }
+ }
+
+ return 0;
+}
+
+int CScriptBuilder::Build()
+{
+ int r = module->Build();
+ if( r < 0 )
+ return r;
+
+#if AS_PROCESS_METADATA == 1
+ // After the script has been built, the metadata strings should be
+ // stored for later lookup by function id, type id, and variable index
+ for( int n = 0; n < (int)foundDeclarations.size(); n++ )
+ {
+ SMetadataDecl *decl = &foundDeclarations[n];
+ module->SetDefaultNamespace(decl->nameSpace.c_str());
+ if( decl->type == MDT_TYPE )
+ {
+ // Find the type id
+ int typeId = module->GetTypeIdByDecl(decl->declaration.c_str());
+ assert( typeId >= 0 );
+ if( typeId >= 0 )
+ typeMetadataMap.insert(map<int, string>::value_type(typeId, decl->metadata));
+ }
+ else if( decl->type == MDT_FUNC )
+ {
+ if( decl->parentClass == "" )
+ {
+ // Find the function id
+ asIScriptFunction *func = module->GetFunctionByDecl(decl->declaration.c_str());
+ assert( func );
+ if( func )
+ funcMetadataMap.insert(map<int, string>::value_type(func->GetId(), decl->metadata));
+ }
+ else
+ {
+ // Find the method id
+ int typeId = module->GetTypeIdByDecl(decl->parentClass.c_str());
+ assert( typeId > 0 );
+ map<int, SClassMetadata>::iterator it = classMetadataMap.find(typeId);
+ if( it == classMetadataMap.end() )
+ {
+ classMetadataMap.insert(map<int, SClassMetadata>::value_type(typeId, SClassMetadata(decl->parentClass)));
+ it = classMetadataMap.find(typeId);
+ }
+
+ asITypeInfo *type = engine->GetTypeInfoById(typeId);
+ asIScriptFunction *func = type->GetMethodByDecl(decl->declaration.c_str());
+ assert( func );
+ if( func )
+ it->second.funcMetadataMap.insert(map<int, string>::value_type(func->GetId(), decl->metadata));
+ }
+ }
+ else if( decl->type == MDT_VIRTPROP )
+ {
+ if( decl->parentClass == "" )
+ {
+ // Find the global virtual property accessors
+ asIScriptFunction *func = module->GetFunctionByName(("get_" + decl->declaration).c_str());
+ if( func )
+ funcMetadataMap.insert(map<int, string>::value_type(func->GetId(), decl->metadata));
+ func = module->GetFunctionByName(("set_" + decl->declaration).c_str());
+ if( func )
+ funcMetadataMap.insert(map<int, string>::value_type(func->GetId(), decl->metadata));
+ }
+ else
+ {
+ // Find the method virtual property accessors
+ int typeId = module->GetTypeIdByDecl(decl->parentClass.c_str());
+ assert( typeId > 0 );
+ map<int, SClassMetadata>::iterator it = classMetadataMap.find(typeId);
+ if( it == classMetadataMap.end() )
+ {
+ classMetadataMap.insert(map<int, SClassMetadata>::value_type(typeId, SClassMetadata(decl->parentClass)));
+ it = classMetadataMap.find(typeId);
+ }
+
+ asITypeInfo *type = engine->GetTypeInfoById(typeId);
+ asIScriptFunction *func = type->GetMethodByName(("get_" + decl->declaration).c_str());
+ if( func )
+ it->second.funcMetadataMap.insert(map<int, string>::value_type(func->GetId(), decl->metadata));
+ func = type->GetMethodByName(("set_" + decl->declaration).c_str());
+ if( func )
+ it->second.funcMetadataMap.insert(map<int, string>::value_type(func->GetId(), decl->metadata));
+ }
+ }
+ else if( decl->type == MDT_VAR )
+ {
+ if( decl->parentClass == "" )
+ {
+ // Find the global variable index
+ int varIdx = module->GetGlobalVarIndexByName(decl->declaration.c_str());
+ assert( varIdx >= 0 );
+ if( varIdx >= 0 )
+ varMetadataMap.insert(map<int, string>::value_type(varIdx, decl->metadata));
+ }
+ else
+ {
+ int typeId = module->GetTypeIdByDecl(decl->parentClass.c_str());
+ assert( typeId > 0 );
+
+ // Add the classes if needed
+ map<int, SClassMetadata>::iterator it = classMetadataMap.find(typeId);
+ if( it == classMetadataMap.end() )
+ {
+ classMetadataMap.insert(map<int, SClassMetadata>::value_type(typeId, SClassMetadata(decl->parentClass)));
+ it = classMetadataMap.find(typeId);
+ }
+
+ // Add the variable to class
+ asITypeInfo *objectType = engine->GetTypeInfoById(typeId);
+ int idx = -1;
+
+ // Search through all properties to get proper declaration
+ for( asUINT i = 0; i < (asUINT)objectType->GetPropertyCount(); ++i )
+ {
+ const char *name;
+ objectType->GetProperty(i, &name);
+ if( decl->declaration == name )
+ {
+ idx = i;
+ break;
+ }
+ }
+
+ // If found, add it
+ assert( idx >= 0 );
+ if( idx >= 0 ) it->second.varMetadataMap.insert(map<int, string>::value_type(idx, decl->metadata));
+ }
+ }
+ else if (decl->type == MDT_FUNC_OR_VAR)
+ {
+ if (decl->parentClass == "")
+ {
+ // Find the global variable index
+ int varIdx = module->GetGlobalVarIndexByName(decl->name.c_str());
+ if (varIdx >= 0)
+ varMetadataMap.insert(map<int, string>::value_type(varIdx, decl->metadata));
+ else
+ {
+ asIScriptFunction *func = module->GetFunctionByDecl(decl->declaration.c_str());
+ assert(func);
+ if (func)
+ funcMetadataMap.insert(map<int, string>::value_type(func->GetId(), decl->metadata));
+ }
+ }
+ else
+ {
+ int typeId = module->GetTypeIdByDecl(decl->parentClass.c_str());
+ assert(typeId > 0);
+
+ // Add the classes if needed
+ map<int, SClassMetadata>::iterator it = classMetadataMap.find(typeId);
+ if (it == classMetadataMap.end())
+ {
+ classMetadataMap.insert(map<int, SClassMetadata>::value_type(typeId, SClassMetadata(decl->parentClass)));
+ it = classMetadataMap.find(typeId);
+ }
+
+ // Add the variable to class
+ asITypeInfo *objectType = engine->GetTypeInfoById(typeId);
+ int idx = -1;
+
+ // Search through all properties to get proper declaration
+ for (asUINT i = 0; i < (asUINT)objectType->GetPropertyCount(); ++i)
+ {
+ const char *name;
+ objectType->GetProperty(i, &name);
+ if (decl->name == name)
+ {
+ idx = i;
+ break;
+ }
+ }
+
+ // If found, add it
+ if (idx >= 0)
+ it->second.varMetadataMap.insert(map<int, string>::value_type(idx, decl->metadata));
+ else
+ {
+ // Look for the matching method instead
+ asITypeInfo *type = engine->GetTypeInfoById(typeId);
+ asIScriptFunction *func = type->GetMethodByDecl(decl->declaration.c_str());
+ assert(func);
+ if (func)
+ it->second.funcMetadataMap.insert(map<int, string>::value_type(func->GetId(), decl->metadata));
+ }
+ }
+ }
+ }
+ module->SetDefaultNamespace("");
+#endif
+
+ return 0;
+}
+
+int CScriptBuilder::SkipStatement(int pos)
+{
+ asUINT len = 0;
+
+ // Skip until ; or { whichever comes first
+ while( pos < (int)modifiedScript.length() && modifiedScript[pos] != ';' && modifiedScript[pos] != '{' )
+ {
+ engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
+ pos += len;
+ }
+
+ // Skip entire statement block
+ if( pos < (int)modifiedScript.length() && modifiedScript[pos] == '{' )
+ {
+ pos += 1;
+
+ // Find the end of the statement block
+ int level = 1;
+ while( level > 0 && pos < (int)modifiedScript.size() )
+ {
+ asETokenClass t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
+ if( t == asTC_KEYWORD )
+ {
+ if( modifiedScript[pos] == '{' )
+ level++;
+ else if( modifiedScript[pos] == '}' )
+ level--;
+ }
+
+ pos += len;
+ }
+ }
+ else
+ pos += 1;
+
+ return pos;
+}
+
+// Overwrite all code with blanks until the matching #endif
+int CScriptBuilder::ExcludeCode(int pos)
+{
+ asUINT len = 0;
+ int nested = 0;
+ while( pos < (int)modifiedScript.size() )
+ {
+ engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
+ if( modifiedScript[pos] == '#' )
+ {
+ modifiedScript[pos] = ' ';
+ pos++;
+
+ // Is it an #if or #endif directive?
+ engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
+ string token;
+ token.assign(&modifiedScript[pos], len);
+ OverwriteCode(pos, len);
+
+ if( token == "if" )
+ {
+ nested++;
+ }
+ else if( token == "endif" )
+ {
+ if( nested-- == 0 )
+ {
+ pos += len;
+ break;
+ }
+ }
+ }
+ else if( modifiedScript[pos] != '\n' )
+ {
+ OverwriteCode(pos, len);
+ }
+ pos += len;
+ }
+
+ return pos;
+}
+
+// Overwrite all characters except line breaks with blanks
+void CScriptBuilder::OverwriteCode(int start, int len)
+{
+ char *code = &modifiedScript[start];
+ for( int n = 0; n < len; n++ )
+ {
+ if( *code != '\n' )
+ *code = ' ';
+ code++;
+ }
+}
+
+#if AS_PROCESS_METADATA == 1
+int CScriptBuilder::ExtractMetadataString(int pos, string &metadata)
+{
+ metadata = "";
+
+ // Overwrite the metadata with space characters to allow compilation
+ modifiedScript[pos] = ' ';
+
+ // Skip opening brackets
+ pos += 1;
+
+ int level = 1;
+ asUINT len = 0;
+ while( level > 0 && pos < (int)modifiedScript.size() )
+ {
+ asETokenClass t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
+ if( t == asTC_KEYWORD )
+ {
+ if( modifiedScript[pos] == '[' )
+ level++;
+ else if( modifiedScript[pos] == ']' )
+ level--;
+ }
+
+ // Copy the metadata to our buffer
+ if( level > 0 )
+ metadata.append(&modifiedScript[pos], len);
+
+ // Overwrite the metadata with space characters to allow compilation
+ if( t != asTC_WHITESPACE )
+ OverwriteCode(pos, len);
+
+ pos += len;
+ }
+
+ return pos;
+}
+
+int CScriptBuilder::ExtractDeclaration(int pos, string &name, string &declaration, int &type)
+{
+ declaration = "";
+ type = 0;
+
+ int start = pos;
+
+ std::string token;
+ asUINT len = 0;
+ asETokenClass t = asTC_WHITESPACE;
+
+ // Skip white spaces, comments, and leading 'shared', 'external', 'final', 'abstract'
+ do
+ {
+ pos += len;
+ t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
+ token.assign(&modifiedScript[pos], len);
+ } while ( t == asTC_WHITESPACE || t == asTC_COMMENT || token == "shared" || token == "external" || token == "final" || token == "abstract" );
+
+ // We're expecting, either a class, interface, function, or variable declaration
+ if( t == asTC_KEYWORD || t == asTC_IDENTIFIER )
+ {
+ token.assign(&modifiedScript[pos], len);
+ if( token == "interface" || token == "class" || token == "enum" )
+ {
+ // Skip white spaces and comments
+ do
+ {
+ pos += len;
+ t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
+ } while ( t == asTC_WHITESPACE || t == asTC_COMMENT );
+
+ if( t == asTC_IDENTIFIER )
+ {
+ type = MDT_TYPE;
+ declaration.assign(&modifiedScript[pos], len);
+ pos += len;
+ return pos;
+ }
+ }
+ else
+ {
+ // For function declarations, store everything up to the start of the statement block
+
+ // For variable declaration store just the name as there can only be one
+
+ // We'll only know if the declaration is a variable or function declaration when we see the statement block, or absense of a statement block.
+ bool hasParenthesis = false;
+ declaration.append(&modifiedScript[pos], len);
+ pos += len;
+ for(; pos < (int)modifiedScript.size();)
+ {
+ t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len);
+ if( t == asTC_KEYWORD )
+ {
+ token.assign(&modifiedScript[pos], len);
+ if( token == "{" )
+ {
+ if( hasParenthesis )
+ {
+ // We've found the end of a function signature
+ type = MDT_FUNC;
+ }
+ else
+ {
+ // We've found a virtual property. Just keep the name
+ declaration = name;
+ type = MDT_VIRTPROP;
+ }
+ return pos;
+ }
+ if( (token == "=" && !hasParenthesis) || token == ";" )
+ {
+ if (hasParenthesis)
+ {
+ // The declaration is ambigous. It can be a variable with initialization, or a function prototype
+ type = MDT_FUNC_OR_VAR;
+ }
+ else
+ {
+ // Substitute the declaration with just the name
+ declaration = name;
+ type = MDT_VAR;
+ }
+ return pos;
+ }
+ else if( token == "(" )
+ {
+ // This is the first parenthesis we encounter. If the parenthesis isn't followed
+ // by a statement block, then this is a variable declaration, in which case we
+ // should only store the type and name of the variable, not the initialization parameters.
+ hasParenthesis = true;
+ }
+ }
+ else if( t == asTC_IDENTIFIER )
+ {
+ name.assign(&modifiedScript[pos], len);
+ }
+
+ declaration.append(&modifiedScript[pos], len);
+ pos += len;
+ }
+ }
+ }
+
+ return start;
+}
+
+const char *CScriptBuilder::GetMetadataStringForType(int typeId)
+{
+ map<int,string>::iterator it = typeMetadataMap.find(typeId);
+ if( it != typeMetadataMap.end() )
+ return it->second.c_str();
+
+ return "";
+}
+
+const char *CScriptBuilder::GetMetadataStringForFunc(asIScriptFunction *func)
+{
+ if( func )
+ {
+ map<int,string>::iterator it = funcMetadataMap.find(func->GetId());
+ if( it != funcMetadataMap.end() )
+ return it->second.c_str();
+ }
+
+ return "";
+}
+
+const char *CScriptBuilder::GetMetadataStringForVar(int varIdx)
+{
+ map<int,string>::iterator it = varMetadataMap.find(varIdx);
+ if( it != varMetadataMap.end() )
+ return it->second.c_str();
+
+ return "";
+}
+
+const char *CScriptBuilder::GetMetadataStringForTypeProperty(int typeId, int varIdx)
+{
+ map<int, SClassMetadata>::iterator typeIt = classMetadataMap.find(typeId);
+ if(typeIt == classMetadataMap.end()) return "";
+
+ map<int, string>::iterator propIt = typeIt->second.varMetadataMap.find(varIdx);
+ if(propIt == typeIt->second.varMetadataMap.end()) return "";
+
+ return propIt->second.c_str();
+}
+
+const char *CScriptBuilder::GetMetadataStringForTypeMethod(int typeId, asIScriptFunction *method)
+{
+ if( method )
+ {
+ map<int, SClassMetadata>::iterator typeIt = classMetadataMap.find(typeId);
+ if(typeIt == classMetadataMap.end()) return "";
+
+ map<int, string>::iterator methodIt = typeIt->second.funcMetadataMap.find(method->GetId());
+ if(methodIt == typeIt->second.funcMetadataMap.end()) return "";
+
+ return methodIt->second.c_str();
+ }
+
+ return "";
+}
+#endif
+
+string GetAbsolutePath(const string &file)
+{
+ string str = file;
+
+ // If this is a relative path, complement it with the current path
+ if( !((str.length() > 0 && (str[0] == '/' || str[0] == '\\')) ||
+ str.find(":") != string::npos) )
+ {
+ str = GetCurrentDir() + "/" + str;
+ }
+
+ // Replace backslashes for forward slashes
+ size_t pos = 0;
+ while( (pos = str.find("\\", pos)) != string::npos )
+ str[pos] = '/';
+
+ // Replace /./ with /
+ pos = 0;
+ while( (pos = str.find("/./", pos)) != string::npos )
+ str.erase(pos+1, 2);
+
+ // For each /../ remove the parent dir and the /../
+ pos = 0;
+ while( (pos = str.find("/../")) != string::npos )
+ {
+ size_t pos2 = str.rfind("/", pos-1);
+ if( pos2 != string::npos )
+ str.erase(pos2, pos+3-pos2);
+ else
+ {
+ // The path is invalid
+ break;
+ }
+ }
+
+ return str;
+}
+
+string GetCurrentDir()
+{
+ char buffer[1024];
+#if defined(_MSC_VER) || defined(_WIN32)
+ #ifdef _WIN32_WCE
+ static TCHAR apppath[MAX_PATH] = TEXT("");
+ if (!apppath[0])
+ {
+ GetModuleFileName(NULL, apppath, MAX_PATH);
+
+ int appLen = _tcslen(apppath);
+
+ // Look for the last backslash in the path, which would be the end
+ // of the path itself and the start of the filename. We only want
+ // the path part of the exe's full-path filename
+ // Safety is that we make sure not to walk off the front of the
+ // array (in case the path is nothing more than a filename)
+ while (appLen > 1)
+ {
+ if (apppath[appLen-1] == TEXT('\\'))
+ break;
+ appLen--;
+ }
+
+ // Terminate the string after the trailing backslash
+ apppath[appLen] = TEXT('\0');
+ }
+ #ifdef _UNICODE
+ wcstombs(buffer, apppath, min(1024, wcslen(apppath)*sizeof(wchar_t)));
+ #else
+ memcpy(buffer, apppath, min(1024, strlen(apppath)));
+ #endif
+
+ return buffer;
+ #elif defined(__S3E__)
+ // Marmalade uses its own portable C library
+ return getcwd(buffer, (int)1024);
+ #elif _XBOX_VER >= 200
+ // XBox 360 doesn't support the getcwd function, just use the root folder
+ return "game:/";
+ #elif defined(_M_ARM)
+ // TODO: How to determine current working dir on Windows Phone?
+ return "";
+ #else
+ return _getcwd(buffer, (int)1024);
+ #endif // _MSC_VER
+#elif defined(__APPLE__) || defined(__linux__)
+ return getcwd(buffer, 1024);
+#else
+ return "";
+#endif
+}
+
+END_AS_NAMESPACE
+
+
diff --git a/Source/Scripting/angelscript/add_on/scriptbuilder/scriptbuilder.h b/Source/Scripting/angelscript/add_on/scriptbuilder/scriptbuilder.h
new file mode 100644
index 00000000..5f7bbaf7
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/scriptbuilder/scriptbuilder.h
@@ -0,0 +1,202 @@
+#ifndef SCRIPTBUILDER_H
+#define SCRIPTBUILDER_H
+
+//---------------------------
+// Compilation settings
+//
+
+// Set this flag to turn on/off metadata processing
+// 0 = off
+// 1 = on
+#ifndef AS_PROCESS_METADATA
+#define AS_PROCESS_METADATA 1
+#endif
+
+// TODO: Implement flags for turning on/off include directives and conditional programming
+
+
+
+//---------------------------
+// Declaration
+//
+
+#ifndef ANGELSCRIPT_H
+// Avoid having to inform include path if header is already include before
+#include <angelscript.h>
+#endif
+
+
+#if defined(_MSC_VER) && _MSC_VER <= 1200
+// disable the annoying warnings on MSVC 6
+#pragma warning (disable:4786)
+#endif
+
+#include <string>
+#include <map>
+#include <set>
+#include <vector>
+#include <string.h> // _strcmpi
+
+BEGIN_AS_NAMESPACE
+
+class CScriptBuilder;
+
+// This callback will be called for each #include directive encountered by the
+// builder. The callback should call the AddSectionFromFile or AddSectionFromMemory
+// to add the included section to the script. If the include cannot be resolved
+// then the function should return a negative value to abort the compilation.
+typedef int (*INCLUDECALLBACK_t)(const char *include, const char *from, CScriptBuilder *builder, void *userParam);
+
+// Helper class for loading and pre-processing script files to
+// support include directives and metadata declarations
+class CScriptBuilder
+{
+public:
+ CScriptBuilder();
+
+ // Start a new module
+ int StartNewModule(asIScriptEngine *engine, const char *moduleName);
+
+ // Load a script section from a file on disk
+ // Returns 1 if the file was included
+ // 0 if the file had already been included before
+ // <0 on error
+ int AddSectionFromFile(const char *filename);
+
+ // Load a script section from memory
+ // Returns 1 if the section was included
+ // 0 if a section with the same name had already been included before
+ // <0 on error
+ int AddSectionFromMemory(const char *sectionName,
+ const char *scriptCode,
+ unsigned int scriptLength = 0,
+ int lineOffset = 0);
+
+ // Build the added script sections
+ int BuildModule();
+
+ // Returns the current module
+ asIScriptModule *GetModule();
+
+ // Register the callback for resolving include directive
+ void SetIncludeCallback(INCLUDECALLBACK_t callback, void *userParam);
+
+ // Add a pre-processor define for conditional compilation
+ void DefineWord(const char *word);
+
+ // Enumerate included script sections
+ unsigned int GetSectionCount() const;
+ std::string GetSectionName(unsigned int idx) const;
+
+#if AS_PROCESS_METADATA == 1
+ // Get metadata declared for classes, interfaces, and enums
+ const char *GetMetadataStringForType(int typeId);
+
+ // Get metadata declared for functions
+ const char *GetMetadataStringForFunc(asIScriptFunction *func);
+
+ // Get metadata declared for global variables
+ const char *GetMetadataStringForVar(int varIdx);
+
+ // Get metadata declared for class variables
+ const char *GetMetadataStringForTypeProperty(int typeId, int varIdx);
+
+ // Get metadata declared for class methods
+ const char *GetMetadataStringForTypeMethod(int typeId, asIScriptFunction *method);
+#endif
+
+protected:
+ void ClearAll();
+ int Build();
+ int ProcessScriptSection(const char *script, unsigned int length, const char *sectionname, int lineOffset);
+ int LoadScriptSection(const char *filename);
+ bool IncludeIfNotAlreadyIncluded(const char *filename);
+
+ int SkipStatement(int pos);
+
+ int ExcludeCode(int start);
+ void OverwriteCode(int start, int len);
+
+ asIScriptEngine *engine;
+ asIScriptModule *module;
+ std::string modifiedScript;
+
+ INCLUDECALLBACK_t includeCallback;
+ void *callbackParam;
+
+#if AS_PROCESS_METADATA == 1
+ int ExtractMetadataString(int pos, std::string &outMetadata);
+ int ExtractDeclaration(int pos, std::string &outName, std::string &outDeclaration, int &outType);
+
+ enum METADATATYPE
+ {
+ MDT_TYPE = 1,
+ MDT_FUNC = 2,
+ MDT_VAR = 3,
+ MDT_VIRTPROP = 4,
+ MDT_FUNC_OR_VAR = 5
+ };
+
+ // Temporary structure for storing metadata and declaration
+ struct SMetadataDecl
+ {
+ SMetadataDecl(std::string m, std::string n, std::string d, int t, std::string c, std::string ns) : metadata(m), name(n), declaration(d), type(t), parentClass(c), nameSpace(ns) {}
+ std::string metadata;
+ std::string name;
+ std::string declaration;
+ int type;
+ std::string parentClass;
+ std::string nameSpace;
+ };
+ std::vector<SMetadataDecl> foundDeclarations;
+ std::string currentClass;
+ std::string currentNamespace;
+
+ // Storage of metadata for global declarations
+ std::map<int, std::string> typeMetadataMap;
+ std::map<int, std::string> funcMetadataMap;
+ std::map<int, std::string> varMetadataMap;
+
+ // Storage of metadata for class member declarations
+ struct SClassMetadata
+ {
+ SClassMetadata(const std::string& aName) : className(aName) {}
+ std::string className;
+ std::map<int, std::string> funcMetadataMap;
+ std::map<int, std::string> varMetadataMap;
+ };
+ std::map<int, SClassMetadata> classMetadataMap;
+
+#endif
+
+#ifdef _WIN32
+ // On Windows the filenames are case insensitive so the comparisons to
+ // avoid duplicate includes must also be case insensitive. True case insensitive
+ // is not easy as it must be language aware, but a simple implementation such
+ // as strcmpi should suffice in almost all cases.
+ //
+ // ref: http://www.gotw.ca/gotw/029.htm
+ // ref: https://msdn.microsoft.com/en-us/library/windows/desktop/dd317761(v=vs.85).aspx
+ // ref: http://site.icu-project.org/
+
+ // TODO: Strings by default are treated as UTF8 encoded. If the application choses to
+ // use a different encoding, the comparison algorithm should be adjusted as well
+
+ struct ci_less
+ {
+ bool operator()(const std::string &a, const std::string &b) const
+ {
+ return _strcmpi(a.c_str(), b.c_str()) < 0;
+ }
+ };
+ std::set<std::string, ci_less> includedScripts;
+#else
+ std::set<std::string> includedScripts;
+#endif
+
+ std::set<std::string> definedWords;
+};
+
+END_AS_NAMESPACE
+
+#endif
diff --git a/Source/Scripting/angelscript/add_on/scriptdictionary/scriptdictionary.cpp b/Source/Scripting/angelscript/add_on/scriptdictionary/scriptdictionary.cpp
new file mode 100644
index 00000000..32d6a57e
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/scriptdictionary/scriptdictionary.cpp
@@ -0,0 +1,1222 @@
+#include <assert.h>
+#include <string.h>
+#include "scriptdictionary.h"
+#include "../scriptarray/scriptarray.h"
+
+BEGIN_AS_NAMESPACE
+
+using namespace std;
+
+//------------------------------------------------------------------------
+// Object types are cached as user data to avoid costly runtime lookups
+
+// We just define a number here that we assume nobody else is using for
+// object type user data. The add-ons have reserved the numbers 1000
+// through 1999 for this purpose, so we should be fine.
+const asPWORD DICTIONARY_CACHE = 1003;
+
+// This cache holds the object type of the dictionary type and array type
+// so it isn't necessary to look this up each time the dictionary or array
+// is created.
+struct SDictionaryCache
+{
+ asITypeInfo *dictType;
+ asITypeInfo *arrayType;
+
+ // This is called from RegisterScriptDictionary
+ static void Setup(asIScriptEngine *engine)
+ {
+ SDictionaryCache *cache = reinterpret_cast<SDictionaryCache*>(engine->GetUserData(DICTIONARY_CACHE));
+ if( cache == 0 )
+ {
+ cache = new SDictionaryCache;
+ engine->SetUserData(cache, DICTIONARY_CACHE);
+ engine->SetEngineUserDataCleanupCallback(SDictionaryCache::Cleanup, DICTIONARY_CACHE);
+
+ cache->dictType = engine->GetTypeInfoByName("dictionary");
+ cache->arrayType = engine->GetTypeInfoByDecl("array<string>");
+ }
+ }
+
+ // This is called from the engine when shutting down
+ static void Cleanup(asIScriptEngine *engine)
+ {
+ SDictionaryCache *cache = reinterpret_cast<SDictionaryCache*>(engine->GetUserData(DICTIONARY_CACHE));
+ if( cache )
+ delete cache;
+ }
+};
+
+//--------------------------------------------------------------------------
+// CScriptDictionary implementation
+
+CScriptDictionary *CScriptDictionary::Create(asIScriptEngine *engine)
+{
+ // Use the custom memory routine from AngelScript to allow application to better control how much memory is used
+ CScriptDictionary *obj = (CScriptDictionary*)asAllocMem(sizeof(CScriptDictionary));
+ new(obj) CScriptDictionary(engine);
+ return obj;
+}
+
+CScriptDictionary *CScriptDictionary::Create(asBYTE *buffer)
+{
+ // Use the custom memory routine from AngelScript to allow application to better control how much memory is used
+ CScriptDictionary *obj = (CScriptDictionary*)asAllocMem(sizeof(CScriptDictionary));
+ new(obj) CScriptDictionary(buffer);
+ return obj;
+}
+
+CScriptDictionary::CScriptDictionary(asIScriptEngine *engine)
+{
+ Init(engine);
+}
+
+void CScriptDictionary::Init(asIScriptEngine *e)
+{
+ // We start with one reference
+ refCount = 1;
+ gcFlag = false;
+
+ // Keep a reference to the engine for as long as we live
+ // We don't increment the reference counter, because the
+ // engine will hold a pointer to the object in the GC.
+ engine = e;
+
+ // The dictionary object type is cached to avoid dynamically parsing it each time
+ SDictionaryCache *cache = reinterpret_cast<SDictionaryCache*>(engine->GetUserData(DICTIONARY_CACHE));
+
+ // Notify the garbage collector of this object
+ engine->NotifyGarbageCollectorOfNewObject(this, cache->dictType);
+}
+
+CScriptDictionary::CScriptDictionary(asBYTE *buffer)
+{
+ // This constructor will always be called from a script
+ // so we can get the engine from the active context
+ asIScriptContext *ctx = asGetActiveContext();
+ Init(ctx->GetEngine());
+
+ // Initialize the dictionary from the buffer
+ asUINT length = *(asUINT*)buffer;
+ buffer += 4;
+
+ while( length-- )
+ {
+ // Align the buffer pointer on a 4 byte boundary in
+ // case previous value was smaller than 4 bytes
+ if( asPWORD(buffer) & 0x3 )
+ buffer += 4 - (asPWORD(buffer) & 0x3);
+
+ // Get the name value pair from the buffer and insert it in the dictionary
+ dictKey_t name = *(dictKey_t*)buffer;
+ buffer += sizeof(dictKey_t);
+
+ // Get the type id of the value
+ int typeId = *(int*)buffer;
+ buffer += sizeof(int);
+
+ // Depending on the type id, the value will inline in the buffer or a pointer
+ void *ref = (void*)buffer;
+
+ if( typeId >= asTYPEID_INT8 && typeId <= asTYPEID_DOUBLE )
+ {
+ // Convert primitive values to either int64 or double, so we can use the overloaded Set methods
+ asINT64 i64;
+ double d;
+ switch( typeId )
+ {
+ case asTYPEID_INT8: i64 = *(char*) ref; break;
+ case asTYPEID_INT16: i64 = *(short*) ref; break;
+ case asTYPEID_INT32: i64 = *(int*) ref; break;
+ case asTYPEID_INT64: i64 = *(asINT64*) ref; break;
+ case asTYPEID_UINT8: i64 = *(unsigned char*) ref; break;
+ case asTYPEID_UINT16: i64 = *(unsigned short*)ref; break;
+ case asTYPEID_UINT32: i64 = *(unsigned int*) ref; break;
+ case asTYPEID_UINT64: i64 = *(asINT64*) ref; break;
+ case asTYPEID_FLOAT: d = *(float*) ref; break;
+ case asTYPEID_DOUBLE: d = *(double*) ref; break;
+ }
+
+ if( typeId >= asTYPEID_FLOAT )
+ Set(name, d);
+ else
+ Set(name, i64);
+ }
+ else
+ {
+ if( (typeId & asTYPEID_MASK_OBJECT) &&
+ !(typeId & asTYPEID_OBJHANDLE) &&
+ (engine->GetTypeInfoById(typeId)->GetFlags() & asOBJ_REF) )
+ {
+ // Dereference the pointer to get the reference to the actual object
+ ref = *(void**)ref;
+ }
+
+ Set(name, ref, typeId);
+ }
+
+ // Advance the buffer pointer with the size of the value
+ if( typeId & asTYPEID_MASK_OBJECT )
+ {
+ asITypeInfo *ti = engine->GetTypeInfoById(typeId);
+ if( ti->GetFlags() & asOBJ_VALUE )
+ buffer += ti->GetSize();
+ else
+ buffer += sizeof(void*);
+ }
+ else if( typeId == 0 )
+ {
+ // null pointer
+ buffer += sizeof(void*);
+ }
+ else
+ {
+ buffer += engine->GetSizeOfPrimitiveType(typeId);
+ }
+ }
+}
+
+CScriptDictionary::~CScriptDictionary()
+{
+ // Delete all keys and values
+ DeleteAll();
+}
+
+void CScriptDictionary::AddRef() const
+{
+ // We need to clear the GC flag
+ gcFlag = false;
+ asAtomicInc(refCount);
+}
+
+void CScriptDictionary::Release() const
+{
+ // We need to clear the GC flag
+ gcFlag = false;
+ if( asAtomicDec(refCount) == 0 )
+ {
+ this->~CScriptDictionary();
+ asFreeMem(const_cast<CScriptDictionary*>(this));
+ }
+}
+
+int CScriptDictionary::GetRefCount()
+{
+ return refCount;
+}
+
+void CScriptDictionary::SetGCFlag()
+{
+ gcFlag = true;
+}
+
+bool CScriptDictionary::GetGCFlag()
+{
+ return gcFlag;
+}
+
+void CScriptDictionary::EnumReferences(asIScriptEngine *inEngine)
+{
+ // TODO: If garbage collection can be done from a separate thread, then this method must be
+ // protected so that it doesn't get lost during the iteration if the dictionary is modified
+
+ // Call the gc enum callback for each of the objects
+ dictMap_t::iterator it;
+ for( it = dict.begin(); it != dict.end(); it++ )
+ {
+ if( it->second.m_typeId & asTYPEID_MASK_OBJECT )
+ inEngine->GCEnumCallback(it->second.m_valueObj);
+ }
+}
+
+void CScriptDictionary::ReleaseAllReferences(asIScriptEngine * /*engine*/)
+{
+ // We're being told to release all references in
+ // order to break circular references for dead objects
+ DeleteAll();
+}
+
+CScriptDictionary &CScriptDictionary::operator =(const CScriptDictionary &other)
+{
+ // Clear everything we had before
+ DeleteAll();
+
+ // Do a shallow copy of the dictionary
+ dictMap_t::const_iterator it;
+ for( it = other.dict.begin(); it != other.dict.end(); it++ )
+ {
+ if( it->second.m_typeId & asTYPEID_OBJHANDLE )
+ Set(it->first, (void*)&it->second.m_valueObj, it->second.m_typeId);
+ else if( it->second.m_typeId & asTYPEID_MASK_OBJECT )
+ Set(it->first, (void*)it->second.m_valueObj, it->second.m_typeId);
+ else
+ Set(it->first, (void*)&it->second.m_valueInt, it->second.m_typeId);
+ }
+
+ return *this;
+}
+
+CScriptDictValue *CScriptDictionary::operator[](const dictKey_t &key)
+{
+ // Return the existing value if it exists, else insert an empty value
+ return &dict[key];
+}
+
+const CScriptDictValue *CScriptDictionary::operator[](const dictKey_t &key) const
+{
+ // Return the existing value if it exists
+ dictMap_t::const_iterator it;
+ it = dict.find(key);
+ if( it != dict.end() )
+ return &it->second;
+
+ // Else raise an exception
+ asIScriptContext *ctx = asGetActiveContext();
+ if( ctx )
+ ctx->SetException("Invalid access to non-existing value");
+
+ return 0;
+}
+
+void CScriptDictionary::Set(const dictKey_t &key, void *value, int typeId)
+{
+ dictMap_t::iterator it;
+ it = dict.find(key);
+ if( it == dict.end() )
+ it = dict.insert(dictMap_t::value_type(key, CScriptDictValue())).first;
+
+ it->second.Set(engine, value, typeId);
+}
+
+// This overloaded method is implemented so that all integer and
+// unsigned integers types will be stored in the dictionary as int64
+// through implicit conversions. This simplifies the management of the
+// numeric types when the script retrieves the stored value using a
+// different type.
+void CScriptDictionary::Set(const dictKey_t &key, const asINT64 &value)
+{
+ Set(key, const_cast<asINT64*>(&value), asTYPEID_INT64);
+}
+
+// This overloaded method is implemented so that all floating point types
+// will be stored in the dictionary as double through implicit conversions.
+// This simplifies the management of the numeric types when the script
+// retrieves the stored value using a different type.
+void CScriptDictionary::Set(const dictKey_t &key, const double &value)
+{
+ Set(key, const_cast<double*>(&value), asTYPEID_DOUBLE);
+}
+
+// Returns true if the value was successfully retrieved
+bool CScriptDictionary::Get(const dictKey_t &key, void *value, int typeId) const
+{
+ dictMap_t::const_iterator it;
+ it = dict.find(key);
+ if( it != dict.end() )
+ return it->second.Get(engine, value, typeId);
+
+ // AngelScript has already initialized the value with a default value,
+ // so we don't have to do anything if we don't find the element, or if
+ // the element is incompatible with the requested type.
+
+ return false;
+}
+
+// Returns the type id of the stored value
+int CScriptDictionary::GetTypeId(const dictKey_t &key) const
+{
+ dictMap_t::const_iterator it;
+ it = dict.find(key);
+ if( it != dict.end() )
+ return it->second.m_typeId;
+
+ return -1;
+}
+
+bool CScriptDictionary::Get(const dictKey_t &key, asINT64 &value) const
+{
+ return Get(key, &value, asTYPEID_INT64);
+}
+
+bool CScriptDictionary::Get(const dictKey_t &key, double &value) const
+{
+ return Get(key, &value, asTYPEID_DOUBLE);
+}
+
+bool CScriptDictionary::Exists(const dictKey_t &key) const
+{
+ dictMap_t::const_iterator it;
+ it = dict.find(key);
+ if( it != dict.end() )
+ return true;
+
+ return false;
+}
+
+bool CScriptDictionary::IsEmpty() const
+{
+ if( dict.size() == 0 )
+ return true;
+
+ return false;
+}
+
+asUINT CScriptDictionary::GetSize() const
+{
+ return asUINT(dict.size());
+}
+
+bool CScriptDictionary::Delete(const dictKey_t &key)
+{
+ dictMap_t::iterator it;
+ it = dict.find(key);
+ if( it != dict.end() )
+ {
+ it->second.FreeValue(engine);
+ dict.erase(it);
+ return true;
+ }
+
+ return false;
+}
+
+void CScriptDictionary::DeleteAll()
+{
+ dictMap_t::iterator it;
+ for( it = dict.begin(); it != dict.end(); it++ )
+ it->second.FreeValue(engine);
+
+ dict.clear();
+}
+
+CScriptArray* CScriptDictionary::GetKeys() const
+{
+ // Retrieve the object type for the array<string> from the cache
+ SDictionaryCache *cache = reinterpret_cast<SDictionaryCache*>(engine->GetUserData(DICTIONARY_CACHE));
+ asITypeInfo *ti = cache->arrayType;
+
+ // Create the array object
+ CScriptArray *array = CScriptArray::Create(ti, asUINT(dict.size()));
+ long current = -1;
+ dictMap_t::const_iterator it;
+ for( it = dict.begin(); it != dict.end(); it++ )
+ {
+ current++;
+ *(dictKey_t*)array->At(current) = it->first;
+ }
+
+ return array;
+}
+
+//--------------------------------------------------------------------------
+// Generic wrappers
+
+void ScriptDictionaryFactory_Generic(asIScriptGeneric *gen)
+{
+ *(CScriptDictionary**)gen->GetAddressOfReturnLocation() = CScriptDictionary::Create(gen->GetEngine());
+}
+
+void ScriptDictionaryListFactory_Generic(asIScriptGeneric *gen)
+{
+ asBYTE *buffer = (asBYTE*)gen->GetArgAddress(0);
+ *(CScriptDictionary**)gen->GetAddressOfReturnLocation() = CScriptDictionary::Create(buffer);
+}
+
+void ScriptDictionaryAddRef_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictionary *dict = (CScriptDictionary*)gen->GetObject();
+ dict->AddRef();
+}
+
+void ScriptDictionaryRelease_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictionary *dict = (CScriptDictionary*)gen->GetObject();
+ dict->Release();
+}
+
+void ScriptDictionaryAssign_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictionary *dict = (CScriptDictionary*)gen->GetObject();
+ CScriptDictionary *other = *(CScriptDictionary**)gen->GetAddressOfArg(0);
+ *dict = *other;
+ *(CScriptDictionary**)gen->GetAddressOfReturnLocation() = dict;
+}
+
+void ScriptDictionarySet_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictionary *dict = (CScriptDictionary*)gen->GetObject();
+ dictKey_t *key = *(dictKey_t**)gen->GetAddressOfArg(0);
+ void *ref = *(void**)gen->GetAddressOfArg(1);
+ int typeId = gen->GetArgTypeId(1);
+ dict->Set(*key, ref, typeId);
+}
+
+void ScriptDictionarySetInt_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictionary *dict = (CScriptDictionary*)gen->GetObject();
+ dictKey_t *key = *(dictKey_t**)gen->GetAddressOfArg(0);
+ void *ref = *(void**)gen->GetAddressOfArg(1);
+ dict->Set(*key, *(asINT64*)ref);
+}
+
+void ScriptDictionarySetFlt_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictionary *dict = (CScriptDictionary*)gen->GetObject();
+ dictKey_t *key = *(dictKey_t**)gen->GetAddressOfArg(0);
+ void *ref = *(void**)gen->GetAddressOfArg(1);
+ dict->Set(*key, *(double*)ref);
+}
+
+void ScriptDictionaryGet_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictionary *dict = (CScriptDictionary*)gen->GetObject();
+ dictKey_t *key = *(dictKey_t**)gen->GetAddressOfArg(0);
+ void *ref = *(void**)gen->GetAddressOfArg(1);
+ int typeId = gen->GetArgTypeId(1);
+ *(bool*)gen->GetAddressOfReturnLocation() = dict->Get(*key, ref, typeId);
+}
+
+void ScriptDictionaryGetInt_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictionary *dict = (CScriptDictionary*)gen->GetObject();
+ dictKey_t *key = *(dictKey_t**)gen->GetAddressOfArg(0);
+ void *ref = *(void**)gen->GetAddressOfArg(1);
+ *(bool*)gen->GetAddressOfReturnLocation() = dict->Get(*key, *(asINT64*)ref);
+}
+
+void ScriptDictionaryGetFlt_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictionary *dict = (CScriptDictionary*)gen->GetObject();
+ dictKey_t *key = *(dictKey_t**)gen->GetAddressOfArg(0);
+ void *ref = *(void**)gen->GetAddressOfArg(1);
+ *(bool*)gen->GetAddressOfReturnLocation() = dict->Get(*key, *(double*)ref);
+}
+
+void ScriptDictionaryExists_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictionary *dict = (CScriptDictionary*)gen->GetObject();
+ dictKey_t *key = *(dictKey_t**)gen->GetAddressOfArg(0);
+ bool ret = dict->Exists(*key);
+ *(bool*)gen->GetAddressOfReturnLocation() = ret;
+}
+
+void ScriptDictionaryIsEmpty_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictionary *dict = (CScriptDictionary*)gen->GetObject();
+ bool ret = dict->IsEmpty();
+ *(bool*)gen->GetAddressOfReturnLocation() = ret;
+}
+
+void ScriptDictionaryGetSize_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictionary *dict = (CScriptDictionary*)gen->GetObject();
+ asUINT ret = dict->GetSize();
+ *(asUINT*)gen->GetAddressOfReturnLocation() = ret;
+}
+
+void ScriptDictionaryDelete_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictionary *dict = (CScriptDictionary*)gen->GetObject();
+ dictKey_t *key = *(dictKey_t**)gen->GetAddressOfArg(0);
+ *(bool*)gen->GetAddressOfReturnLocation() = dict->Delete(*key);
+}
+
+void ScriptDictionaryDeleteAll_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictionary *dict = (CScriptDictionary*)gen->GetObject();
+ dict->DeleteAll();
+}
+
+static void ScriptDictionaryGetRefCount_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictionary *self = (CScriptDictionary*)gen->GetObject();
+ *(int*)gen->GetAddressOfReturnLocation() = self->GetRefCount();
+}
+
+static void ScriptDictionarySetGCFlag_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictionary *self = (CScriptDictionary*)gen->GetObject();
+ self->SetGCFlag();
+}
+
+static void ScriptDictionaryGetGCFlag_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictionary *self = (CScriptDictionary*)gen->GetObject();
+ *(bool*)gen->GetAddressOfReturnLocation() = self->GetGCFlag();
+}
+
+static void ScriptDictionaryEnumReferences_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictionary *self = (CScriptDictionary*)gen->GetObject();
+ asIScriptEngine *engine = *(asIScriptEngine**)gen->GetAddressOfArg(0);
+ self->EnumReferences(engine);
+}
+
+static void ScriptDictionaryReleaseAllReferences_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictionary *self = (CScriptDictionary*)gen->GetObject();
+ asIScriptEngine *engine = *(asIScriptEngine**)gen->GetAddressOfArg(0);
+ self->ReleaseAllReferences(engine);
+}
+
+static void CScriptDictionaryGetKeys_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictionary *self = (CScriptDictionary*)gen->GetObject();
+ *(CScriptArray**)gen->GetAddressOfReturnLocation() = self->GetKeys();
+}
+
+static void CScriptDictionary_opIndex_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictionary *self = (CScriptDictionary*)gen->GetObject();
+ dictKey_t *key = *(dictKey_t**)gen->GetAddressOfArg(0);
+ *(CScriptDictValue**)gen->GetAddressOfReturnLocation() = self->operator[](*key);
+}
+
+static void CScriptDictionary_opIndex_const_Generic(asIScriptGeneric *gen)
+{
+ const CScriptDictionary *self = (const CScriptDictionary*)gen->GetObject();
+ dictKey_t *key = *(dictKey_t**)gen->GetAddressOfArg(0);
+ *(const CScriptDictValue**)gen->GetAddressOfReturnLocation() = self->operator[](*key);
+}
+
+
+//-------------------------------------------------------------------------
+// CScriptDictValue
+
+CScriptDictValue::CScriptDictValue()
+{
+ m_valueObj = 0;
+ m_typeId = 0;
+}
+
+CScriptDictValue::CScriptDictValue(asIScriptEngine *engine, void *value, int typeId)
+{
+ m_valueObj = 0;
+ m_typeId = 0;
+ Set(engine, value, typeId);
+}
+
+CScriptDictValue::~CScriptDictValue()
+{
+ // Must not hold an object when destroyed, as then the object will never be freed
+ assert( (m_typeId & asTYPEID_MASK_OBJECT) == 0 );
+}
+
+void CScriptDictValue::FreeValue(asIScriptEngine *engine)
+{
+ // If it is a handle or a ref counted object, call release
+ if( m_typeId & asTYPEID_MASK_OBJECT )
+ {
+ // Let the engine release the object
+ engine->ReleaseScriptObject(m_valueObj, engine->GetTypeInfoById(m_typeId));
+ m_valueObj = 0;
+ m_typeId = 0;
+ }
+
+ // For primitives, there's nothing to do
+}
+
+void CScriptDictValue::Set(asIScriptEngine *engine, void *value, int typeId)
+{
+ FreeValue(engine);
+
+ m_typeId = typeId;
+ if( typeId & asTYPEID_OBJHANDLE )
+ {
+ // We're receiving a reference to the handle, so we need to dereference it
+ m_valueObj = *(void**)value;
+ engine->AddRefScriptObject(m_valueObj, engine->GetTypeInfoById(typeId));
+ }
+ else if( typeId & asTYPEID_MASK_OBJECT )
+ {
+ // Create a copy of the object
+ m_valueObj = engine->CreateScriptObjectCopy(value, engine->GetTypeInfoById(typeId));
+ }
+ else
+ {
+ // Copy the primitive value
+ // We receive a pointer to the value.
+ int size = engine->GetSizeOfPrimitiveType(typeId);
+ memcpy(&m_valueInt, value, size);
+ }
+}
+
+void CScriptDictValue::Set(asIScriptEngine *engine, CScriptDictValue &value)
+{
+ if( value.m_typeId & asTYPEID_OBJHANDLE )
+ Set(engine, (void*)&value.m_valueObj, value.m_typeId);
+ else if( value.m_typeId & asTYPEID_MASK_OBJECT )
+ Set(engine, (void*)value.m_valueObj, value.m_typeId);
+ else
+ Set(engine, (void*)&value.m_valueInt, value.m_typeId);
+}
+
+// This overloaded method is implemented so that all integer and
+// unsigned integers types will be stored in the dictionary as int64
+// through implicit conversions. This simplifies the management of the
+// numeric types when the script retrieves the stored value using a
+// different type.
+void CScriptDictValue::Set(asIScriptEngine *engine, const asINT64 &value)
+{
+ Set(engine, const_cast<asINT64*>(&value), asTYPEID_INT64);
+}
+
+// This overloaded method is implemented so that all floating point types
+// will be stored in the dictionary as double through implicit conversions.
+// This simplifies the management of the numeric types when the script
+// retrieves the stored value using a different type.
+void CScriptDictValue::Set(asIScriptEngine *engine, const double &value)
+{
+ Set(engine, const_cast<double*>(&value), asTYPEID_DOUBLE);
+}
+
+bool CScriptDictValue::Get(asIScriptEngine *engine, void *value, int typeId) const
+{
+ // Return the value
+ if( typeId & asTYPEID_OBJHANDLE )
+ {
+ // A handle can be retrieved if the stored type is a handle of same or compatible type
+ // or if the stored type is an object that implements the interface that the handle refer to.
+ if( (m_typeId & asTYPEID_MASK_OBJECT) )
+ {
+ // Don't allow the get if the stored handle is to a const, but the desired handle is not
+ if( (m_typeId & asTYPEID_HANDLETOCONST) && !(typeId & asTYPEID_HANDLETOCONST) )
+ return false;
+
+ // RefCastObject will increment the refcount if successful
+ engine->RefCastObject(m_valueObj, engine->GetTypeInfoById(m_typeId), engine->GetTypeInfoById(typeId), reinterpret_cast<void**>(value));
+
+ return true;
+ }
+ }
+ else if( typeId & asTYPEID_MASK_OBJECT )
+ {
+ // Verify that the copy can be made
+ bool isCompatible = false;
+
+ // Allow a handle to be value assigned if the wanted type is not a handle
+ if( (m_typeId & ~(asTYPEID_OBJHANDLE | asTYPEID_HANDLETOCONST) ) == typeId && m_valueObj != 0 )
+ isCompatible = true;
+
+ // Copy the object into the given reference
+ if( isCompatible )
+ {
+ engine->AssignScriptObject(value, m_valueObj, engine->GetTypeInfoById(typeId));
+
+ return true;
+ }
+ }
+ else
+ {
+ if( m_typeId == typeId )
+ {
+ int size = engine->GetSizeOfPrimitiveType(typeId);
+ memcpy(value, &m_valueInt, size);
+ return true;
+ }
+
+ // We know all numbers are stored as either int64 or double, since we register overloaded functions for those
+ // Only bool and enums needs to be treated separately
+ if( typeId == asTYPEID_DOUBLE )
+ {
+ if( m_typeId == asTYPEID_INT64 )
+ *(double*)value = double(m_valueInt);
+ else if (m_typeId == asTYPEID_BOOL)
+ {
+ // Use memcpy instead of type cast to make sure the code is endianess agnostic
+ char localValue;
+ memcpy(&localValue, &m_valueInt, sizeof(char));
+ *(double*)value = localValue ? 1.0 : 0.0;
+ }
+ else if (m_typeId > asTYPEID_DOUBLE && (m_typeId & asTYPEID_MASK_OBJECT) == 0)
+ {
+ // Use memcpy instead of type cast to make sure the code is endianess agnostic
+ int localValue;
+ memcpy(&localValue, &m_valueInt, sizeof(int));
+ *(double*)value = double(localValue); // enums are 32bit
+ }
+ else
+ {
+ // The stored type is an object
+ // TODO: Check if the object has a conversion operator to a primitive value
+ *(double*)value = 0;
+ }
+ return true;
+ }
+ else if( typeId == asTYPEID_INT64 )
+ {
+ if( m_typeId == asTYPEID_DOUBLE )
+ *(asINT64*)value = asINT64(m_valueFlt);
+ else if (m_typeId == asTYPEID_BOOL)
+ {
+ // Use memcpy instead of type cast to make sure the code is endianess agnostic
+ char localValue;
+ memcpy(&localValue, &m_valueInt, sizeof(char));
+ *(asINT64*)value = localValue ? 1 : 0;
+ }
+ else if (m_typeId > asTYPEID_DOUBLE && (m_typeId & asTYPEID_MASK_OBJECT) == 0)
+ {
+ // Use memcpy instead of type cast to make sure the code is endianess agnostic
+ int localValue;
+ memcpy(&localValue, &m_valueInt, sizeof(int));
+ *(asINT64*)value = localValue; // enums are 32bit
+ }
+ else
+ {
+ // The stored type is an object
+ // TODO: Check if the object has a conversion operator to a primitive value
+ *(asINT64*)value = 0;
+ }
+ return true;
+ }
+ else if( typeId > asTYPEID_DOUBLE && (m_typeId & asTYPEID_MASK_OBJECT) == 0 )
+ {
+ // The desired type is an enum. These are always 32bit integers
+ if( m_typeId == asTYPEID_DOUBLE )
+ *(int*)value = int(m_valueFlt);
+ else if( m_typeId == asTYPEID_INT64 )
+ *(int*)value = int(m_valueInt);
+ else if (m_typeId == asTYPEID_BOOL)
+ {
+ // Use memcpy instead of type cast to make sure the code is endianess agnostic
+ char localValue;
+ memcpy(&localValue, &m_valueInt, sizeof(char));
+ *(int*)value = localValue ? 1 : 0;
+ }
+ else if (m_typeId > asTYPEID_DOUBLE && (m_typeId & asTYPEID_MASK_OBJECT) == 0)
+ {
+ // Use memcpy instead of type cast to make sure the code is endianess agnostic
+ int localValue;
+ memcpy(&localValue, &m_valueInt, sizeof(int));
+ *(int*)value = localValue; // enums are 32bit
+ }
+ else
+ {
+ // The stored type is an object
+ // TODO: Check if the object has a conversion operator to a primitive value
+ *(int*)value = 0;
+ }
+ }
+ else if( typeId == asTYPEID_BOOL )
+ {
+ if (m_typeId & asTYPEID_OBJHANDLE)
+ {
+ // TODO: Check if the object has a conversion operator to a primitive value
+ *(bool*)value = m_valueObj ? true : false;
+ }
+ else if( m_typeId & asTYPEID_MASK_OBJECT )
+ {
+ // TODO: Check if the object has a conversion operator to a primitive value
+ *(bool*)value = true;
+ }
+ else
+ {
+ // Compare only the bytes that were actually set
+ asQWORD zero = 0;
+ int size = engine->GetSizeOfPrimitiveType(m_typeId);
+ *(bool*)value = memcmp(&m_valueInt, &zero, size) == 0 ? false : true;
+ }
+ }
+ }
+
+ // It was not possible to retrieve the value using the desired typeId
+ return false;
+}
+
+const void * CScriptDictValue::GetAddressOfValue() const
+{
+ if( (m_typeId & asTYPEID_MASK_OBJECT) && !(m_typeId & asTYPEID_OBJHANDLE) )
+ {
+ // Return the address to the object directly
+ return m_valueObj;
+ }
+
+ // Return the address of the primitive or the pointer to the object
+ return reinterpret_cast<const void*>(&m_valueObj);
+}
+
+bool CScriptDictValue::Get(asIScriptEngine *engine, asINT64 &value) const
+{
+ return Get(engine, &value, asTYPEID_INT64);
+}
+
+bool CScriptDictValue::Get(asIScriptEngine *engine, double &value) const
+{
+ return Get(engine, &value, asTYPEID_DOUBLE);
+}
+
+int CScriptDictValue::GetTypeId() const
+{
+ return m_typeId;
+}
+
+static void CScriptDictValue_Construct(void *mem)
+{
+ new(mem) CScriptDictValue();
+}
+
+static void CScriptDictValue_Destruct(CScriptDictValue *obj)
+{
+ asIScriptContext *ctx = asGetActiveContext();
+ if( ctx )
+ {
+ asIScriptEngine *engine = ctx->GetEngine();
+ obj->FreeValue(engine);
+ }
+ obj->~CScriptDictValue();
+}
+
+static CScriptDictValue &CScriptDictValue_opAssign(void *ref, int typeId, CScriptDictValue *obj)
+{
+ asIScriptContext *ctx = asGetActiveContext();
+ if( ctx )
+ {
+ asIScriptEngine *engine = ctx->GetEngine();
+ obj->Set(engine, ref, typeId);
+ }
+ return *obj;
+}
+
+static CScriptDictValue &CScriptDictValue_opAssign(const CScriptDictValue &other, CScriptDictValue *obj)
+{
+ asIScriptContext *ctx = asGetActiveContext();
+ if( ctx )
+ {
+ asIScriptEngine *engine = ctx->GetEngine();
+ obj->Set(engine, const_cast<CScriptDictValue&>(other));
+ }
+
+ return *obj;
+}
+
+static CScriptDictValue &CScriptDictValue_opAssign(double val, CScriptDictValue *obj)
+{
+ return CScriptDictValue_opAssign(&val, asTYPEID_DOUBLE, obj);
+}
+
+static CScriptDictValue &CScriptDictValue_opAssign(asINT64 val, CScriptDictValue *obj)
+{
+ return CScriptDictValue_opAssign(&val, asTYPEID_INT64, obj);
+}
+
+static void CScriptDictValue_opCast(void *ref, int typeId, CScriptDictValue *obj)
+{
+ asIScriptContext *ctx = asGetActiveContext();
+ if( ctx )
+ {
+ asIScriptEngine *engine = ctx->GetEngine();
+ obj->Get(engine, ref, typeId);
+ }
+}
+
+static asINT64 CScriptDictValue_opConvInt(CScriptDictValue *obj)
+{
+ asINT64 value;
+ CScriptDictValue_opCast(&value, asTYPEID_INT64, obj);
+ return value;
+}
+
+static double CScriptDictValue_opConvDouble(CScriptDictValue *obj)
+{
+ double value;
+ CScriptDictValue_opCast(&value, asTYPEID_DOUBLE, obj);
+ return value;
+}
+
+//-------------------------------------------------------------------
+// generic wrapper for CScriptDictValue
+
+static void CScriptDictValue_opConvDouble_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictValue *self = (CScriptDictValue*)gen->GetObject();
+ double value;
+ self->Get(gen->GetEngine(), value);
+ *(double*)gen->GetAddressOfReturnLocation() = value;
+}
+
+static void CScriptDictValue_opConvInt_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictValue *self = (CScriptDictValue*)gen->GetObject();
+ asINT64 value;
+ self->Get(gen->GetEngine(), value);
+ *(asINT64*)gen->GetAddressOfReturnLocation() = value;
+}
+
+static void CScriptDictValue_opCast_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictValue *self = (CScriptDictValue*)gen->GetObject();
+ self->Get(gen->GetEngine(), gen->GetArgAddress(0), gen->GetArgTypeId(0));
+}
+
+static void CScriptDictValue_opAssign_int64_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictValue *self = (CScriptDictValue*)gen->GetObject();
+ *(CScriptDictValue**)gen->GetAddressOfReturnLocation() = &CScriptDictValue_opAssign((asINT64)gen->GetArgQWord(0), self);
+}
+
+static void CScriptDictValue_opAssign_double_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictValue *self = (CScriptDictValue*)gen->GetObject();
+ *(CScriptDictValue**)gen->GetAddressOfReturnLocation() = &CScriptDictValue_opAssign(gen->GetArgDouble(0), self);
+}
+
+static void CScriptDictValue_opAssign_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictValue *self = (CScriptDictValue*)gen->GetObject();
+ *(CScriptDictValue**)gen->GetAddressOfReturnLocation() = &CScriptDictValue_opAssign(gen->GetArgAddress(0), gen->GetArgTypeId(0), self);
+}
+
+static void CScriptDictValue_opCopyAssign_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictValue *self = (CScriptDictValue*)gen->GetObject();
+ *(CScriptDictValue**)gen->GetAddressOfReturnLocation() = &CScriptDictValue_opAssign(*reinterpret_cast<CScriptDictValue*>(gen->GetArgAddress(0)), self);
+}
+
+static void CScriptDictValue_Construct_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictValue *self = (CScriptDictValue*)gen->GetObject();
+ CScriptDictValue_Construct(self);
+}
+
+static void CScriptDictValue_Destruct_Generic(asIScriptGeneric *gen)
+{
+ CScriptDictValue *self = (CScriptDictValue*)gen->GetObject();
+ CScriptDictValue_Destruct(self);
+}
+
+//--------------------------------------------------------------------------
+// Register the type
+
+void RegisterScriptDictionary(asIScriptEngine *engine)
+{
+ if( strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") )
+ RegisterScriptDictionary_Generic(engine);
+ else
+ RegisterScriptDictionary_Native(engine);
+}
+
+void RegisterScriptDictionary_Native(asIScriptEngine *engine)
+{
+ int r;
+
+ // The array<string> type must be available
+ assert( engine->GetTypeInfoByDecl("array<string>") );
+
+#if AS_CAN_USE_CPP11
+ // With C++11 it is possible to use asGetTypeTraits to automatically determine the correct flags that represents the C++ class
+ r = engine->RegisterObjectType("dictionaryValue", sizeof(CScriptDictValue), asOBJ_VALUE | asOBJ_ASHANDLE | asGetTypeTraits<CScriptDictValue>()); assert( r >= 0 );
+#else
+ r = engine->RegisterObjectType("dictionaryValue", sizeof(CScriptDictValue), asOBJ_VALUE | asOBJ_ASHANDLE | asOBJ_APP_CLASS_CD); assert( r >= 0 );
+#endif
+ r = engine->RegisterObjectBehaviour("dictionaryValue", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(CScriptDictValue_Construct), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("dictionaryValue", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(CScriptDictValue_Destruct), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionaryValue", "dictionaryValue &opAssign(const dictionaryValue &in)", asFUNCTIONPR(CScriptDictValue_opAssign, (const CScriptDictValue &, CScriptDictValue *), CScriptDictValue &), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionaryValue", "dictionaryValue &opHndlAssign(const ?&in)", asFUNCTIONPR(CScriptDictValue_opAssign, (void *, int, CScriptDictValue*), CScriptDictValue &), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionaryValue", "dictionaryValue &opAssign(const ?&in)", asFUNCTIONPR(CScriptDictValue_opAssign, (void *, int, CScriptDictValue*), CScriptDictValue &), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionaryValue", "dictionaryValue &opAssign(double)", asFUNCTIONPR(CScriptDictValue_opAssign, (double, CScriptDictValue*), CScriptDictValue &), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionaryValue", "dictionaryValue &opAssign(int64)", asFUNCTIONPR(CScriptDictValue_opAssign, (asINT64, CScriptDictValue*), CScriptDictValue &), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionaryValue", "void opCast(?&out)", asFUNCTIONPR(CScriptDictValue_opCast, (void *, int, CScriptDictValue*), void), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionaryValue", "void opConv(?&out)", asFUNCTIONPR(CScriptDictValue_opCast, (void *, int, CScriptDictValue*), void), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionaryValue", "int64 opConv()", asFUNCTIONPR(CScriptDictValue_opConvInt, (CScriptDictValue*), asINT64), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionaryValue", "double opConv()", asFUNCTIONPR(CScriptDictValue_opConvDouble, (CScriptDictValue*), double), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+
+ r = engine->RegisterObjectType("dictionary", sizeof(CScriptDictionary), asOBJ_REF | asOBJ_GC); assert( r >= 0 );
+ // Use the generic interface to construct the object since we need the engine pointer, we could also have retrieved the engine pointer from the active context
+ r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_FACTORY, "dictionary@ f()", asFUNCTION(ScriptDictionaryFactory_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_LIST_FACTORY, "dictionary @f(int &in) {repeat {string, ?}}", asFUNCTION(ScriptDictionaryListFactory_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_ADDREF, "void f()", asMETHOD(CScriptDictionary,AddRef), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_RELEASE, "void f()", asMETHOD(CScriptDictionary,Release), asCALL_THISCALL); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("dictionary", "dictionary &opAssign(const dictionary &in)", asMETHODPR(CScriptDictionary, operator=, (const CScriptDictionary &), CScriptDictionary&), asCALL_THISCALL); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("dictionary", "void set(const string &in, const ?&in)", asMETHODPR(CScriptDictionary,Set,(const dictKey_t&,void*,int),void), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionary", "bool get(const string &in, ?&out) const", asMETHODPR(CScriptDictionary,Get,(const dictKey_t&,void*,int) const,bool), asCALL_THISCALL); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("dictionary", "void set(const string &in, const int64&in)", asMETHODPR(CScriptDictionary,Set,(const dictKey_t&,const asINT64&),void), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionary", "bool get(const string &in, int64&out) const", asMETHODPR(CScriptDictionary,Get,(const dictKey_t&,asINT64&) const,bool), asCALL_THISCALL); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("dictionary", "void set(const string &in, const double&in)", asMETHODPR(CScriptDictionary,Set,(const dictKey_t&,const double&),void), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionary", "bool get(const string &in, double&out) const", asMETHODPR(CScriptDictionary,Get,(const dictKey_t&,double&) const,bool), asCALL_THISCALL); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("dictionary", "bool exists(const string &in) const", asMETHOD(CScriptDictionary,Exists), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionary", "bool isEmpty() const", asMETHOD(CScriptDictionary, IsEmpty), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionary", "uint getSize() const", asMETHOD(CScriptDictionary, GetSize), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionary", "bool delete(const string &in)", asMETHOD(CScriptDictionary,Delete), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionary", "void deleteAll()", asMETHOD(CScriptDictionary,DeleteAll), asCALL_THISCALL); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("dictionary", "array<string> @getKeys() const", asMETHOD(CScriptDictionary,GetKeys), asCALL_THISCALL); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("dictionary", "dictionaryValue &opIndex(const string &in)", asMETHODPR(CScriptDictionary, operator[], (const dictKey_t &), CScriptDictValue*), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionary", "const dictionaryValue &opIndex(const string &in) const", asMETHODPR(CScriptDictionary, operator[], (const dictKey_t &) const, const CScriptDictValue*), asCALL_THISCALL); assert( r >= 0 );
+
+ // Register GC behaviours
+ r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_GETREFCOUNT, "int f()", asMETHOD(CScriptDictionary,GetRefCount), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_SETGCFLAG, "void f()", asMETHOD(CScriptDictionary,SetGCFlag), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_GETGCFLAG, "bool f()", asMETHOD(CScriptDictionary,GetGCFlag), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_ENUMREFS, "void f(int&in)", asMETHOD(CScriptDictionary,EnumReferences), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_RELEASEREFS, "void f(int&in)", asMETHOD(CScriptDictionary,ReleaseAllReferences), asCALL_THISCALL); assert( r >= 0 );
+
+#if AS_USE_STLNAMES == 1
+ // Same as isEmpty
+ r = engine->RegisterObjectMethod("dictionary", "bool empty() const", asMETHOD(CScriptDictionary, IsEmpty), asCALL_THISCALL); assert( r >= 0 );
+ // Same as getSize
+ r = engine->RegisterObjectMethod("dictionary", "uint size() const", asMETHOD(CScriptDictionary, GetSize), asCALL_THISCALL); assert( r >= 0 );
+ // Same as delete
+ r = engine->RegisterObjectMethod("dictionary", "void erase(const string &in)", asMETHOD(CScriptDictionary,Delete), asCALL_THISCALL); assert( r >= 0 );
+ // Same as deleteAll
+ r = engine->RegisterObjectMethod("dictionary", "void clear()", asMETHOD(CScriptDictionary,DeleteAll), asCALL_THISCALL); assert( r >= 0 );
+#endif
+
+ // Cache some things the dictionary will need at runtime
+ SDictionaryCache::Setup(engine);
+}
+
+void RegisterScriptDictionary_Generic(asIScriptEngine *engine)
+{
+ int r;
+
+ // Register the cleanup callback for the object type cache
+ engine->SetEngineUserDataCleanupCallback(SDictionaryCache::Cleanup, DICTIONARY_CACHE);
+
+#if AS_CAN_USE_CPP11
+ // With C++11 it is possible to use asGetTypeTraits to automatically determine the correct flags that represents the C++ class
+ r = engine->RegisterObjectType("dictionaryValue", sizeof(CScriptDictValue), asOBJ_VALUE | asOBJ_ASHANDLE | asGetTypeTraits<CScriptDictValue>()); assert( r >= 0 );
+#else
+ r = engine->RegisterObjectType("dictionaryValue", sizeof(CScriptDictValue), asOBJ_VALUE | asOBJ_ASHANDLE | asOBJ_APP_CLASS_CD); assert( r >= 0 );
+#endif
+ r = engine->RegisterObjectBehaviour("dictionaryValue", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(CScriptDictValue_Construct_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("dictionaryValue", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(CScriptDictValue_Destruct_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionaryValue", "dictionaryValue &opAssign(const dictionaryValue &in)", asFUNCTION(CScriptDictValue_opCopyAssign_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionaryValue", "dictionaryValue &opHndlAssign(const ?&in)", asFUNCTION(CScriptDictValue_opAssign_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionaryValue", "dictionaryValue &opAssign(const ?&in)", asFUNCTION(CScriptDictValue_opAssign_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionaryValue", "dictionaryValue &opAssign(double)", asFUNCTION(CScriptDictValue_opAssign_double_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionaryValue", "dictionaryValue &opAssign(int64)", asFUNCTION(CScriptDictValue_opAssign_int64_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionaryValue", "void opCast(?&out)", asFUNCTION(CScriptDictValue_opCast_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionaryValue", "void opConv(?&out)", asFUNCTION(CScriptDictValue_opCast_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionaryValue", "int64 opConv()", asFUNCTION(CScriptDictValue_opConvInt_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionaryValue", "double opConv()", asFUNCTION(CScriptDictValue_opConvDouble_Generic), asCALL_GENERIC); assert( r >= 0 );
+
+ r = engine->RegisterObjectType("dictionary", sizeof(CScriptDictionary), asOBJ_REF | asOBJ_GC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_FACTORY, "dictionary@ f()", asFUNCTION(ScriptDictionaryFactory_Generic), asCALL_GENERIC); assert( r>= 0 );
+ r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_LIST_FACTORY, "dictionary @f(int &in) {repeat {string, ?}}", asFUNCTION(ScriptDictionaryListFactory_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_ADDREF, "void f()", asFUNCTION(ScriptDictionaryAddRef_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_RELEASE, "void f()", asFUNCTION(ScriptDictionaryRelease_Generic), asCALL_GENERIC); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("dictionary", "dictionary &opAssign(const dictionary &in)", asFUNCTION(ScriptDictionaryAssign_Generic), asCALL_GENERIC); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("dictionary", "void set(const string &in, const ?&in)", asFUNCTION(ScriptDictionarySet_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionary", "bool get(const string &in, ?&out) const", asFUNCTION(ScriptDictionaryGet_Generic), asCALL_GENERIC); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("dictionary", "void set(const string &in, const int64&in)", asFUNCTION(ScriptDictionarySetInt_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionary", "bool get(const string &in, int64&out) const", asFUNCTION(ScriptDictionaryGetInt_Generic), asCALL_GENERIC); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("dictionary", "void set(const string &in, const double&in)", asFUNCTION(ScriptDictionarySetFlt_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionary", "bool get(const string &in, double&out) const", asFUNCTION(ScriptDictionaryGetFlt_Generic), asCALL_GENERIC); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("dictionary", "bool exists(const string &in) const", asFUNCTION(ScriptDictionaryExists_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionary", "bool isEmpty() const", asFUNCTION(ScriptDictionaryIsEmpty_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionary", "uint getSize() const", asFUNCTION(ScriptDictionaryGetSize_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionary", "bool delete(const string &in)", asFUNCTION(ScriptDictionaryDelete_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionary", "void deleteAll()", asFUNCTION(ScriptDictionaryDeleteAll_Generic), asCALL_GENERIC); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("dictionary", "array<string> @getKeys() const", asFUNCTION(CScriptDictionaryGetKeys_Generic), asCALL_GENERIC); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("dictionary", "dictionaryValue &opIndex(const string &in)", asFUNCTION(CScriptDictionary_opIndex_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("dictionary", "const dictionaryValue &opIndex(const string &in) const", asFUNCTION(CScriptDictionary_opIndex_const_Generic), asCALL_GENERIC); assert( r >= 0 );
+
+ // Register GC behaviours
+ r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_GETREFCOUNT, "int f()", asFUNCTION(ScriptDictionaryGetRefCount_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_SETGCFLAG, "void f()", asFUNCTION(ScriptDictionarySetGCFlag_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_GETGCFLAG, "bool f()", asFUNCTION(ScriptDictionaryGetGCFlag_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_ENUMREFS, "void f(int&in)", asFUNCTION(ScriptDictionaryEnumReferences_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_RELEASEREFS, "void f(int&in)", asFUNCTION(ScriptDictionaryReleaseAllReferences_Generic), asCALL_GENERIC); assert( r >= 0 );
+
+ // Cache some things the dictionary will need at runtime
+ SDictionaryCache::Setup(engine);
+}
+
+//------------------------------------------------------------------
+// Iterator implementation
+
+CScriptDictionary::CIterator CScriptDictionary::begin() const
+{
+ return CIterator(*this, dict.begin());
+}
+
+CScriptDictionary::CIterator CScriptDictionary::end() const
+{
+ return CIterator(*this, dict.end());
+}
+
+CScriptDictionary::CIterator CScriptDictionary::find(const dictKey_t &key) const
+{
+ return CIterator(*this, dict.find(key));
+}
+
+CScriptDictionary::CIterator::CIterator(
+ const CScriptDictionary &dict,
+ dictMap_t::const_iterator it)
+ : m_it(it), m_dict(dict)
+{}
+
+void CScriptDictionary::CIterator::operator++()
+{
+ ++m_it;
+}
+
+void CScriptDictionary::CIterator::operator++(int)
+{
+ ++m_it;
+
+ // Normally the post increment would return a copy of the object with the original state,
+ // but it is rarely used so we skip this extra copy to avoid unnecessary overhead
+}
+
+CScriptDictionary::CIterator &CScriptDictionary::CIterator::operator*()
+{
+ return *this;
+}
+
+bool CScriptDictionary::CIterator::operator==(const CIterator &other) const
+{
+ return m_it == other.m_it;
+}
+
+bool CScriptDictionary::CIterator::operator!=(const CIterator &other) const
+{
+ return m_it != other.m_it;
+}
+
+const dictKey_t &CScriptDictionary::CIterator::GetKey() const
+{
+ return m_it->first;
+}
+
+int CScriptDictionary::CIterator::GetTypeId() const
+{
+ return m_it->second.m_typeId;
+}
+
+bool CScriptDictionary::CIterator::GetValue(asINT64 &value) const
+{
+ return m_it->second.Get(m_dict.engine, &value, asTYPEID_INT64);
+}
+
+bool CScriptDictionary::CIterator::GetValue(double &value) const
+{
+ return m_it->second.Get(m_dict.engine, &value, asTYPEID_DOUBLE);
+}
+
+bool CScriptDictionary::CIterator::GetValue(void *value, int typeId) const
+{
+ return m_it->second.Get(m_dict.engine, value, typeId);
+}
+
+const void *CScriptDictionary::CIterator::GetAddressOfValue() const
+{
+ return m_it->second.GetAddressOfValue();
+}
+
+END_AS_NAMESPACE
+
+
diff --git a/Source/Scripting/angelscript/add_on/scriptdictionary/scriptdictionary.h b/Source/Scripting/angelscript/add_on/scriptdictionary/scriptdictionary.h
new file mode 100644
index 00000000..1fa8aeea
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/scriptdictionary/scriptdictionary.h
@@ -0,0 +1,240 @@
+#ifndef SCRIPTDICTIONARY_H
+#define SCRIPTDICTIONARY_H
+
+// The dictionary class relies on the script string object, thus the script
+// string type must be registered with the engine before registering the
+// dictionary type
+
+#ifndef ANGELSCRIPT_H
+// Avoid having to inform include path if header is already include before
+#include <angelscript.h>
+#endif
+
+// By default the CScriptDictionary use the std::string for the keys.
+// If the application uses a custom string type, then this typedef
+// can be changed accordingly. Remember, if the application uses
+// a ref counted string type, then further changes will be needed,
+// for example in the code for GetKeys() and the constructor that
+// takes an initialization list.
+#include <string>
+typedef std::string dictKey_t;
+
+// Forward declare CScriptDictValue so we can typedef the internal map type
+BEGIN_AS_NAMESPACE
+class CScriptDictValue;
+END_AS_NAMESPACE
+
+// C++11 introduced the std::unordered_map which is a hash map which is
+// is generally more performatic for lookups than the std::map which is a
+// binary tree.
+// TODO: memory: The map allocator should use the asAllocMem and asFreeMem
+#if AS_CAN_USE_CPP11
+#include <unordered_map>
+typedef std::unordered_map<dictKey_t, AS_NAMESPACE_QUALIFIER CScriptDictValue> dictMap_t;
+#else
+#include <map>
+typedef std::map<dictKey_t, AS_NAMESPACE_QUALIFIER CScriptDictValue> dictMap_t;
+#endif
+
+
+#ifdef _MSC_VER
+// Turn off annoying warnings about truncated symbol names
+#pragma warning (disable:4786)
+#endif
+
+
+
+
+// Sometimes it may be desired to use the same method names as used by C++ STL.
+// This may for example reduce time when converting code from script to C++ or
+// back.
+//
+// 0 = off
+// 1 = on
+
+#ifndef AS_USE_STLNAMES
+#define AS_USE_STLNAMES 0
+#endif
+
+
+BEGIN_AS_NAMESPACE
+
+class CScriptArray;
+class CScriptDictionary;
+
+class CScriptDictValue
+{
+public:
+ // This class must not be declared as local variable in C++, because it needs
+ // to receive the script engine pointer in all operations. The engine pointer
+ // is not kept as member in order to keep the size down
+ CScriptDictValue();
+ CScriptDictValue(asIScriptEngine *engine, void *value, int typeId);
+
+ // Destructor must not be called without first calling FreeValue, otherwise a memory leak will occur
+ ~CScriptDictValue();
+
+ // Replace the stored value
+ void Set(asIScriptEngine *engine, void *value, int typeId);
+ void Set(asIScriptEngine *engine, const asINT64 &value);
+ void Set(asIScriptEngine *engine, const double &value);
+ void Set(asIScriptEngine *engine, CScriptDictValue &value);
+
+ // Gets the stored value. Returns false if the value isn't compatible with the informed typeId
+ bool Get(asIScriptEngine *engine, void *value, int typeId) const;
+ bool Get(asIScriptEngine *engine, asINT64 &value) const;
+ bool Get(asIScriptEngine *engine, double &value) const;
+
+ // Returns the address of the stored value for inspection
+ const void *GetAddressOfValue() const;
+
+ // Returns the type id of the stored value
+ int GetTypeId() const;
+
+ // Free the stored value
+ void FreeValue(asIScriptEngine *engine);
+
+protected:
+ friend class CScriptDictionary;
+
+ union
+ {
+ asINT64 m_valueInt;
+ double m_valueFlt;
+ void *m_valueObj;
+ };
+ int m_typeId;
+};
+
+class CScriptDictionary
+{
+public:
+ // Factory functions
+ static CScriptDictionary *Create(asIScriptEngine *engine);
+
+ // Called from the script to instantiate a dictionary from an initialization list
+ static CScriptDictionary *Create(asBYTE *buffer);
+
+ // Reference counting
+ void AddRef() const;
+ void Release() const;
+
+ // Reassign the dictionary
+ CScriptDictionary &operator =(const CScriptDictionary &other);
+
+ // Sets a key/value pair
+ void Set(const dictKey_t &key, void *value, int typeId);
+ void Set(const dictKey_t &key, const asINT64 &value);
+ void Set(const dictKey_t &key, const double &value);
+
+ // Gets the stored value. Returns false if the value isn't compatible with the informed typeId
+ bool Get(const dictKey_t &key, void *value, int typeId) const;
+ bool Get(const dictKey_t &key, asINT64 &value) const;
+ bool Get(const dictKey_t &key, double &value) const;
+
+ // Index accessors. If the dictionary is not const it inserts the value if it doesn't already exist
+ // If the dictionary is const then a script exception is set if it doesn't exist and a null pointer is returned
+ CScriptDictValue *operator[](const dictKey_t &key);
+ const CScriptDictValue *operator[](const dictKey_t &key) const;
+
+ // Returns the type id of the stored value, or negative if it doesn't exist
+ int GetTypeId(const dictKey_t &key) const;
+
+ // Returns true if the key is set
+ bool Exists(const dictKey_t &key) const;
+
+ // Returns true if there are no key/value pairs in the dictionary
+ bool IsEmpty() const;
+
+ // Returns the number of key/value pairs in the dictionary
+ asUINT GetSize() const;
+
+ // Deletes the key
+ bool Delete(const dictKey_t &key);
+
+ // Deletes all keys
+ void DeleteAll();
+
+ // Get an array of all keys
+ CScriptArray *GetKeys() const;
+
+ // STL style iterator
+ class CIterator
+ {
+ public:
+ void operator++(); // Pre-increment
+ void operator++(int); // Post-increment
+
+ // This is needed to support C++11 range-for
+ CIterator &operator*();
+
+ bool operator==(const CIterator &other) const;
+ bool operator!=(const CIterator &other) const;
+
+ // Accessors
+ const dictKey_t &GetKey() const;
+ int GetTypeId() const;
+ bool GetValue(asINT64 &value) const;
+ bool GetValue(double &value) const;
+ bool GetValue(void *value, int typeId) const;
+ const void * GetAddressOfValue() const;
+
+ protected:
+ friend class CScriptDictionary;
+
+ CIterator();
+ CIterator(const CScriptDictionary &dict,
+ dictMap_t::const_iterator it);
+
+ CIterator &operator=(const CIterator &) {return *this;} // Not used
+
+ dictMap_t::const_iterator m_it;
+ const CScriptDictionary &m_dict;
+ };
+
+ CIterator begin() const;
+ CIterator end() const;
+ CIterator find(const dictKey_t &key) const;
+
+ // Garbage collections behaviours
+ int GetRefCount();
+ void SetGCFlag();
+ bool GetGCFlag();
+ void EnumReferences(asIScriptEngine *engine);
+ void ReleaseAllReferences(asIScriptEngine *engine);
+
+protected:
+ // Since the dictionary uses the asAllocMem and asFreeMem functions to allocate memory
+ // the constructors are made protected so that the application cannot allocate it
+ // manually in a different way
+ CScriptDictionary(asIScriptEngine *engine);
+ CScriptDictionary(asBYTE *buffer);
+
+ // We don't want anyone to call the destructor directly, it should be called through the Release method
+ virtual ~CScriptDictionary();
+
+ // Cache the object types needed
+ void Init(asIScriptEngine *engine);
+
+ // Our properties
+ asIScriptEngine *engine;
+ mutable int refCount;
+ mutable bool gcFlag;
+ dictMap_t dict;
+};
+
+// This function will determine the configuration of the engine
+// and use one of the two functions below to register the dictionary object
+void RegisterScriptDictionary(asIScriptEngine *engine);
+
+// Call this function to register the math functions
+// using native calling conventions
+void RegisterScriptDictionary_Native(asIScriptEngine *engine);
+
+// Use this one instead if native calling conventions
+// are not supported on the target platform
+void RegisterScriptDictionary_Generic(asIScriptEngine *engine);
+
+END_AS_NAMESPACE
+
+#endif
diff --git a/Source/Scripting/angelscript/add_on/scriptfile/scriptfile.cpp b/Source/Scripting/angelscript/add_on/scriptfile/scriptfile.cpp
new file mode 100644
index 00000000..5c4e26c7
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/scriptfile/scriptfile.cpp
@@ -0,0 +1,660 @@
+#include "scriptfile.h"
+#include <new>
+#include <assert.h>
+#include <string>
+#include <string.h>
+#include <stdio.h>
+
+#ifdef _WIN32_WCE
+#include <windows.h> // For GetModuleFileName
+#ifdef GetObject
+#undef GetObject
+#endif
+#endif
+
+using namespace std;
+
+BEGIN_AS_NAMESPACE
+
+CScriptFile *ScriptFile_Factory()
+{
+ return new CScriptFile();
+}
+
+void ScriptFile_Factory_Generic(asIScriptGeneric *gen)
+{
+ *(CScriptFile**)gen->GetAddressOfReturnLocation() = ScriptFile_Factory();
+}
+
+void ScriptFile_AddRef_Generic(asIScriptGeneric *gen)
+{
+ CScriptFile *file = (CScriptFile*)gen->GetObject();
+ file->AddRef();
+}
+
+void ScriptFile_Release_Generic(asIScriptGeneric *gen)
+{
+ CScriptFile *file = (CScriptFile*)gen->GetObject();
+ file->Release();
+}
+
+void ScriptFile_Open_Generic(asIScriptGeneric *gen)
+{
+ CScriptFile *file = (CScriptFile*)gen->GetObject();
+ std::string *f = (std::string*)gen->GetArgAddress(0);
+ std::string *m = (std::string*)gen->GetArgAddress(1);
+ int r = file->Open(*f, *m);
+ gen->SetReturnDWord(r);
+}
+
+void ScriptFile_Close_Generic(asIScriptGeneric *gen)
+{
+ CScriptFile *file = (CScriptFile*)gen->GetObject();
+ int r = file->Close();
+ gen->SetReturnDWord(r);
+}
+
+void ScriptFile_GetSize_Generic(asIScriptGeneric *gen)
+{
+ CScriptFile *file = (CScriptFile*)gen->GetObject();
+ int r = file->GetSize();
+ gen->SetReturnDWord(r);
+}
+
+void ScriptFile_ReadString_Generic(asIScriptGeneric *gen)
+{
+ CScriptFile *file = (CScriptFile*)gen->GetObject();
+ int len = gen->GetArgDWord(0);
+ string str = file->ReadString(len);
+ gen->SetReturnObject(&str);
+}
+
+void ScriptFile_ReadLine_Generic(asIScriptGeneric *gen)
+{
+ CScriptFile *file = (CScriptFile*)gen->GetObject();
+ std::string str = file->ReadLine();
+ gen->SetReturnObject(&str);
+}
+
+void ScriptFile_ReadInt_Generic(asIScriptGeneric *gen)
+{
+ CScriptFile *file = (CScriptFile*)gen->GetObject();
+ asUINT bytes = *(asUINT*)gen->GetAddressOfArg(0);
+ *(asINT64*)gen->GetAddressOfReturnLocation() = file->ReadInt(bytes);
+}
+
+void ScriptFile_ReadUInt_Generic(asIScriptGeneric *gen)
+{
+ CScriptFile *file = (CScriptFile*)gen->GetObject();
+ asUINT bytes = *(asUINT*)gen->GetAddressOfArg(0);
+ *(asQWORD*)gen->GetAddressOfReturnLocation() = file->ReadUInt(bytes);
+}
+
+void ScriptFile_ReadFloat_Generic(asIScriptGeneric *gen)
+{
+ CScriptFile *file = (CScriptFile*)gen->GetObject();
+ *(float*)gen->GetAddressOfReturnLocation() = file->ReadFloat();
+}
+
+void ScriptFile_ReadDouble_Generic(asIScriptGeneric *gen)
+{
+ CScriptFile *file = (CScriptFile*)gen->GetObject();
+ *(double*)gen->GetAddressOfReturnLocation() = file->ReadDouble();
+}
+
+void ScriptFile_WriteString_Generic(asIScriptGeneric *gen)
+{
+ CScriptFile *file = (CScriptFile*)gen->GetObject();
+ std::string *str = (std::string*)gen->GetArgAddress(0);
+ gen->SetReturnDWord(file->WriteString(*str));
+}
+
+void ScriptFile_WriteInt_Generic(asIScriptGeneric *gen)
+{
+ CScriptFile *file = (CScriptFile*)gen->GetObject();
+ asINT64 val = *(asINT64*)gen->GetAddressOfArg(0);
+ asUINT bytes = *(asUINT*)gen->GetAddressOfArg(1);
+ *(int*)gen->GetAddressOfReturnLocation() = file->WriteInt(val, bytes);
+}
+
+void ScriptFile_WriteUInt_Generic(asIScriptGeneric *gen)
+{
+ CScriptFile *file = (CScriptFile*)gen->GetObject();
+ asQWORD val = *(asQWORD*)gen->GetAddressOfArg(0);
+ asUINT bytes = *(asUINT*)gen->GetAddressOfArg(1);
+ *(int*)gen->GetAddressOfReturnLocation() = file->WriteUInt(val, bytes);
+}
+
+void ScriptFile_WriteFloat_Generic(asIScriptGeneric *gen)
+{
+ CScriptFile *file = (CScriptFile*)gen->GetObject();
+ float val = *(float*)gen->GetAddressOfArg(0);
+ *(int*)gen->GetAddressOfReturnLocation() = file->WriteFloat(val);
+}
+
+void ScriptFile_WriteDouble_Generic(asIScriptGeneric *gen)
+{
+ CScriptFile *file = (CScriptFile*)gen->GetObject();
+ double val = *(double*)gen->GetAddressOfArg(0);
+ *(int*)gen->GetAddressOfReturnLocation() = file->WriteDouble(val);
+}
+
+void ScriptFile_IsEOF_Generic(asIScriptGeneric *gen)
+{
+ CScriptFile *file = (CScriptFile*)gen->GetObject();
+ bool r = file->IsEOF();
+ gen->SetReturnByte(r);
+}
+
+void ScriptFile_GetPos_Generic(asIScriptGeneric *gen)
+{
+ CScriptFile *file = (CScriptFile*)gen->GetObject();
+ gen->SetReturnDWord(file->GetPos());
+}
+
+void ScriptFile_SetPos_Generic(asIScriptGeneric *gen)
+{
+ CScriptFile *file = (CScriptFile*)gen->GetObject();
+ int pos = (int)gen->GetArgDWord(0);
+ gen->SetReturnDWord(file->SetPos(pos));
+}
+
+void ScriptFile_MovePos_Generic(asIScriptGeneric *gen)
+{
+ CScriptFile *file = (CScriptFile*)gen->GetObject();
+ int delta = (int)gen->GetArgDWord(0);
+ gen->SetReturnDWord(file->MovePos(delta));
+}
+
+void RegisterScriptFile_Native(asIScriptEngine *engine)
+{
+ int r;
+
+ r = engine->RegisterObjectType("file", 0, asOBJ_REF); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("file", asBEHAVE_FACTORY, "file @f()", asFUNCTION(ScriptFile_Factory), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("file", asBEHAVE_ADDREF, "void f()", asMETHOD(CScriptFile,AddRef), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("file", asBEHAVE_RELEASE, "void f()", asMETHOD(CScriptFile,Release), asCALL_THISCALL); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("file", "int open(const string &in, const string &in)", asMETHOD(CScriptFile,Open), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "int close()", asMETHOD(CScriptFile,Close), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "int getSize() const", asMETHOD(CScriptFile,GetSize), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "bool isEndOfFile() const", asMETHOD(CScriptFile,IsEOF), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "string readString(uint)", asMETHOD(CScriptFile,ReadString), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "string readLine()", asMETHOD(CScriptFile,ReadLine), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "int64 readInt(uint)", asMETHOD(CScriptFile,ReadInt), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "uint64 readUInt(uint)", asMETHOD(CScriptFile,ReadUInt), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "float readFloat()", asMETHOD(CScriptFile,ReadFloat), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "double readDouble()", asMETHOD(CScriptFile,ReadDouble), asCALL_THISCALL); assert( r >= 0 );
+#if AS_WRITE_OPS == 1
+ r = engine->RegisterObjectMethod("file", "int writeString(const string &in)", asMETHOD(CScriptFile,WriteString), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "int writeInt(int64, uint)", asMETHOD(CScriptFile,WriteInt), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "int writeUInt(uint64, uint)", asMETHOD(CScriptFile,WriteUInt), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "int writeFloat(float)", asMETHOD(CScriptFile,WriteFloat), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "int writeDouble(double)", asMETHOD(CScriptFile,WriteDouble), asCALL_THISCALL); assert( r >= 0 );
+#endif
+ r = engine->RegisterObjectMethod("file", "int getPos() const", asMETHOD(CScriptFile,GetPos), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "int setPos(int)", asMETHOD(CScriptFile,SetPos), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "int movePos(int)", asMETHOD(CScriptFile,MovePos), asCALL_THISCALL); assert( r >= 0 );
+
+ r = engine->RegisterObjectProperty("file", "bool mostSignificantByteFirst", asOFFSET(CScriptFile, mostSignificantByteFirst)); assert( r >= 0 );
+}
+
+void RegisterScriptFile_Generic(asIScriptEngine *engine)
+{
+ int r;
+
+ r = engine->RegisterObjectType("file", 0, asOBJ_REF); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("file", asBEHAVE_FACTORY, "file @f()", asFUNCTION(ScriptFile_Factory_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("file", asBEHAVE_ADDREF, "void f()", asFUNCTION(ScriptFile_AddRef_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("file", asBEHAVE_RELEASE, "void f()", asFUNCTION(ScriptFile_Release_Generic), asCALL_GENERIC); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("file", "int open(const string &in, const string &in)", asFUNCTION(ScriptFile_Open_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "int close()", asFUNCTION(ScriptFile_Close_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "int getSize() const", asFUNCTION(ScriptFile_GetSize_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "bool isEndOfFile() const", asFUNCTION(ScriptFile_IsEOF_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "string readString(uint)", asFUNCTION(ScriptFile_ReadString_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "string readLine()", asFUNCTION(ScriptFile_ReadLine_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "int64 readInt(uint)", asFUNCTION(ScriptFile_ReadInt_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "uint64 readUInt(uint)", asFUNCTION(ScriptFile_ReadUInt_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "float readFloat()", asFUNCTION(ScriptFile_ReadFloat_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "double readDouble()", asFUNCTION(ScriptFile_ReadDouble_Generic), asCALL_GENERIC); assert( r >= 0 );
+#if AS_WRITE_OPS == 1
+ r = engine->RegisterObjectMethod("file", "int writeString(const string &in)", asFUNCTION(ScriptFile_WriteString_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "int writeInt(int64, uint)", asFUNCTION(ScriptFile_WriteInt_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "int writeUInt(uint64, uint)", asFUNCTION(ScriptFile_WriteUInt_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "int writeFloat(float)", asFUNCTION(ScriptFile_WriteFloat_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "int writeDouble(double)", asFUNCTION(ScriptFile_WriteDouble_Generic), asCALL_GENERIC); assert( r >= 0 );
+#endif
+ r = engine->RegisterObjectMethod("file", "int getPos() const", asFUNCTION(ScriptFile_GetPos_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "int setPos(int)", asFUNCTION(ScriptFile_SetPos_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("file", "int movePos(int)", asFUNCTION(ScriptFile_MovePos_Generic), asCALL_GENERIC); assert( r >= 0 );
+
+ r = engine->RegisterObjectProperty("file", "bool mostSignificantByteFirst", asOFFSET(CScriptFile, mostSignificantByteFirst)); assert( r >= 0 );
+}
+
+void RegisterScriptFile(asIScriptEngine *engine)
+{
+ if( strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") )
+ RegisterScriptFile_Generic(engine);
+ else
+ RegisterScriptFile_Native(engine);
+}
+
+CScriptFile::CScriptFile()
+{
+ refCount = 1;
+ file = 0;
+ mostSignificantByteFirst = false;
+}
+
+CScriptFile::~CScriptFile()
+{
+ Close();
+}
+
+void CScriptFile::AddRef() const
+{
+ asAtomicInc(refCount);
+}
+
+void CScriptFile::Release() const
+{
+ if( asAtomicDec(refCount) == 0 )
+ delete this;
+}
+
+int CScriptFile::Open(const std::string &filename, const std::string &mode)
+{
+ // Close the previously opened file handle
+ if( file )
+ Close();
+
+ std::string myFilename = filename;
+
+ // Validate the mode
+ string m;
+#if AS_WRITE_OPS == 1
+ if( mode != "r" && mode != "w" && mode != "a" )
+#else
+ if( mode != "r" )
+#endif
+ return -1;
+ else
+ m = mode;
+
+#ifdef _WIN32_WCE
+ // no relative pathing on CE
+ char buf[MAX_PATH];
+ static TCHAR apppath[MAX_PATH] = TEXT("");
+ if (!apppath[0])
+ {
+ GetModuleFileName(NULL, apppath, MAX_PATH);
+
+ int appLen = _tcslen(apppath);
+ while (appLen > 1)
+ {
+ if (apppath[appLen-1] == TEXT('\\'))
+ break;
+ appLen--;
+ }
+
+ // Terminate the string after the trailing backslash
+ apppath[appLen] = TEXT('\0');
+ }
+#ifdef _UNICODE
+ wcstombs(buf, apppath, wcslen(apppath)+1);
+#else
+ memcpy(buf, apppath, strlen(apppath));
+#endif
+ myFilename = buf + myFilename;
+#endif
+
+
+ // By default windows translates "\r\n" to "\n", but we want to read the file as-is.
+ m += "b";
+
+ // Open the file
+#if _MSC_VER >= 1400 && !defined(__S3E__)
+ // MSVC 8.0 / 2005 introduced new functions
+ // Marmalade doesn't use these, even though it uses the MSVC compiler
+ fopen_s(&file, myFilename.c_str(), m.c_str());
+#else
+ file = fopen(myFilename.c_str(), m.c_str());
+#endif
+ if( file == 0 )
+ return -1;
+
+ return 0;
+}
+
+int CScriptFile::Close()
+{
+ if( file == 0 )
+ return -1;
+
+ fclose(file);
+ file = 0;
+
+ return 0;
+}
+
+int CScriptFile::GetSize() const
+{
+ if( file == 0 )
+ return -1;
+
+ int pos = ftell(file);
+ fseek(file, 0, SEEK_END);
+ int size = ftell(file);
+ fseek(file, pos, SEEK_SET);
+
+ return size;
+}
+
+int CScriptFile::GetPos() const
+{
+ if( file == 0 )
+ return -1;
+
+ return ftell(file);
+}
+
+int CScriptFile::SetPos(int pos)
+{
+ if( file == 0 )
+ return -1;
+
+ int r = fseek(file, pos, SEEK_SET);
+
+ // Return -1 on error
+ return r ? -1 : 0;
+}
+
+int CScriptFile::MovePos(int delta)
+{
+ if( file == 0 )
+ return -1;
+
+ int r = fseek(file, delta, SEEK_CUR);
+
+ // Return -1 on error
+ return r ? -1 : 0;
+}
+
+string CScriptFile::ReadString(unsigned int length)
+{
+ if( file == 0 )
+ return "";
+
+ // Read the string
+ string str;
+ str.resize(length);
+ int size = (int)fread(&str[0], 1, length, file);
+ str.resize(size);
+
+ return str;
+}
+
+string CScriptFile::ReadLine()
+{
+ if( file == 0 )
+ return "";
+
+ // Read until the first new-line character
+ string str;
+ char buf[256];
+
+ do
+ {
+ // Get the current position so we can determine how many characters were read
+ int start = ftell(file);
+
+ // Set the last byte to something different that 0, so that we can check if the buffer was filled up
+ buf[255] = 1;
+
+ // Read the line (or first 255 characters, which ever comes first)
+ char *r = fgets(buf, 256, file);
+ if( r == 0 ) break;
+
+ // Get the position after the read
+ int end = ftell(file);
+
+ // Add the read characters to the output buffer
+ str.append(buf, end-start);
+ }
+ while( !feof(file) && buf[255] == 0 && buf[254] != '\n' );
+
+ return str;
+}
+
+asINT64 CScriptFile::ReadInt(asUINT bytes)
+{
+ if( file == 0 )
+ return 0;
+
+ if( bytes > 8 ) bytes = 8;
+ if( bytes == 0 ) return 0;
+
+ unsigned char buf[8];
+ size_t r = fread(buf, bytes, 1, file);
+ if( r == 0 ) return 0;
+
+ asINT64 val = 0;
+ if( mostSignificantByteFirst )
+ {
+ unsigned int n = 0;
+ for( ; n < bytes; n++ )
+ val |= asQWORD(buf[n]) << ((bytes-n-1)*8);
+
+ // Check the most significant byte to determine if the rest
+ // of the qword must be filled to give a negative value
+ if( buf[0] & 0x80 )
+ for( ; n < 8; n++ )
+ val |= asQWORD(0xFF) << (n*8);
+ }
+ else
+ {
+ unsigned int n = 0;
+ for( ; n < bytes; n++ )
+ val |= asQWORD(buf[n]) << (n*8);
+
+ // Check the most significant byte to determine if the rest
+ // of the qword must be filled to give a negative value
+ if( buf[bytes-1] & 0x80 )
+ for( ; n < 8; n++ )
+ val |= asQWORD(0xFF) << (n*8);
+ }
+
+ return val;
+}
+
+asQWORD CScriptFile::ReadUInt(asUINT bytes)
+{
+ if( file == 0 )
+ return 0;
+
+ if( bytes > 8 ) bytes = 8;
+ if( bytes == 0 ) return 0;
+
+ unsigned char buf[8];
+ size_t r = fread(buf, bytes, 1, file);
+ if( r == 0 ) return 0;
+
+ asQWORD val = 0;
+ if( mostSignificantByteFirst )
+ {
+ unsigned int n = 0;
+ for( ; n < bytes; n++ )
+ val |= asQWORD(buf[n]) << ((bytes-n-1)*8);
+ }
+ else
+ {
+ unsigned int n = 0;
+ for( ; n < bytes; n++ )
+ val |= asQWORD(buf[n]) << (n*8);
+ }
+
+ return val;
+}
+
+float CScriptFile::ReadFloat()
+{
+ if( file == 0 )
+ return 0;
+
+ unsigned char buf[4];
+ size_t r = fread(buf, 4, 1, file);
+ if( r == 0 ) return 0;
+
+ asUINT val = 0;
+ if( mostSignificantByteFirst )
+ {
+ unsigned int n = 0;
+ for( ; n < 4; n++ )
+ val |= asUINT(buf[n]) << ((3-n)*8);
+ }
+ else
+ {
+ unsigned int n = 0;
+ for( ; n < 4; n++ )
+ val |= asUINT(buf[n]) << (n*8);
+ }
+
+ return *reinterpret_cast<float*>(&val);
+}
+
+double CScriptFile::ReadDouble()
+{
+ if( file == 0 )
+ return 0;
+
+ unsigned char buf[8];
+ size_t r = fread(buf, 8, 1, file);
+ if( r == 0 ) return 0;
+
+ asQWORD val = 0;
+ if( mostSignificantByteFirst )
+ {
+ unsigned int n = 0;
+ for( ; n < 8; n++ )
+ val |= asQWORD(buf[n]) << ((7-n)*8);
+ }
+ else
+ {
+ unsigned int n = 0;
+ for( ; n < 8; n++ )
+ val |= asQWORD(buf[n]) << (n*8);
+ }
+
+ return *reinterpret_cast<double*>(&val);
+}
+
+bool CScriptFile::IsEOF() const
+{
+ if( file == 0 )
+ return true;
+
+ return feof(file) ? true : false;
+}
+
+#if AS_WRITE_OPS == 1
+int CScriptFile::WriteString(const std::string &str)
+{
+ if( file == 0 )
+ return -1;
+
+ // Write the entire string
+ size_t r = fwrite(&str[0], 1, str.length(), file);
+
+ return int(r);
+}
+
+int CScriptFile::WriteInt(asINT64 val, asUINT bytes)
+{
+ if( file == 0 )
+ return 0;
+
+ unsigned char buf[8];
+ if( mostSignificantByteFirst )
+ {
+ for( unsigned int n = 0; n < bytes; n++ )
+ buf[n] = (val >> ((bytes-n-1)*8)) & 0xFF;
+ }
+ else
+ {
+ for( unsigned int n = 0; n < bytes; n++ )
+ buf[n] = (val >> (n*8)) & 0xFF;
+ }
+
+ size_t r = fwrite(&buf, bytes, 1, file);
+ return int(r);
+}
+
+int CScriptFile::WriteUInt(asQWORD val, asUINT bytes)
+{
+ if( file == 0 )
+ return 0;
+
+ unsigned char buf[8];
+ if( mostSignificantByteFirst )
+ {
+ for( unsigned int n = 0; n < bytes; n++ )
+ buf[n] = (val >> ((bytes-n-1)*8)) & 0xFF;
+ }
+ else
+ {
+ for( unsigned int n = 0; n < bytes; n++ )
+ buf[n] = (val >> (n*8)) & 0xFF;
+ }
+
+ size_t r = fwrite(&buf, bytes, 1, file);
+ return int(r);
+}
+
+int CScriptFile::WriteFloat(float f)
+{
+ if( file == 0 )
+ return 0;
+
+ unsigned char buf[4];
+ asUINT val = *reinterpret_cast<asUINT*>(&f);
+ if( mostSignificantByteFirst )
+ {
+ for( unsigned int n = 0; n < 4; n++ )
+ buf[n] = (val >> ((3-n)*4)) & 0xFF;
+ }
+ else
+ {
+ for( unsigned int n = 0; n < 4; n++ )
+ buf[n] = (val >> (n*8)) & 0xFF;
+ }
+
+ size_t r = fwrite(&buf, 4, 1, file);
+ return int(r);
+}
+
+int CScriptFile::WriteDouble(double d)
+{
+ if( file == 0 )
+ return 0;
+
+ unsigned char buf[8];
+ asQWORD val = *reinterpret_cast<asQWORD*>(&d);
+ if( mostSignificantByteFirst )
+ {
+ for( unsigned int n = 0; n < 8; n++ )
+ buf[n] = (val >> ((7-n)*8)) & 0xFF;
+ }
+ else
+ {
+ for( unsigned int n = 0; n < 8; n++ )
+ buf[n] = (val >> (n*8)) & 0xFF;
+ }
+
+ size_t r = fwrite(&buf, 8, 1, file);
+ return int(r);
+}
+#endif
+
+
+END_AS_NAMESPACE
diff --git a/Source/Scripting/angelscript/add_on/scriptfile/scriptfile.h b/Source/Scripting/angelscript/add_on/scriptfile/scriptfile.h
new file mode 100644
index 00000000..748bba77
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/scriptfile/scriptfile.h
@@ -0,0 +1,101 @@
+//
+// CScriptFile
+//
+// This class encapsulates a FILE pointer in a reference counted class for
+// use within AngelScript.
+//
+
+#ifndef SCRIPTFILE_H
+#define SCRIPTFILE_H
+
+//---------------------------
+// Compilation settings
+//
+
+// Set this flag to turn on/off write support
+// 0 = off
+// 1 = on
+
+#ifndef AS_WRITE_OPS
+#define AS_WRITE_OPS 1
+#endif
+
+
+
+
+//---------------------------
+// Declaration
+//
+
+#ifndef ANGELSCRIPT_H
+// Avoid having to inform include path if header is already include before
+#include <angelscript.h>
+#endif
+
+#include <string>
+#include <stdio.h>
+
+BEGIN_AS_NAMESPACE
+
+class CScriptFile
+{
+public:
+ CScriptFile();
+
+ void AddRef() const;
+ void Release() const;
+
+ // TODO: Implement the "r+", "w+" and "a+" modes
+ // mode = "r" -> open the file for reading
+ // "w" -> open the file for writing (overwrites existing file)
+ // "a" -> open the file for appending
+ int Open(const std::string &filename, const std::string &mode);
+ int Close();
+ int GetSize() const;
+ bool IsEOF() const;
+
+ // Reading
+ std::string ReadString(unsigned int length);
+ std::string ReadLine();
+ asINT64 ReadInt(asUINT bytes);
+ asQWORD ReadUInt(asUINT bytes);
+ float ReadFloat();
+ double ReadDouble();
+
+ // Writing
+ int WriteString(const std::string &str);
+ int WriteInt(asINT64 v, asUINT bytes);
+ int WriteUInt(asQWORD v, asUINT bytes);
+ int WriteFloat(float v);
+ int WriteDouble(double v);
+
+ // Cursor
+ int GetPos() const;
+ int SetPos(int pos);
+ int MovePos(int delta);
+
+ // Big-endian = most significant byte first
+ bool mostSignificantByteFirst;
+
+protected:
+ ~CScriptFile();
+
+ mutable int refCount;
+ FILE *file;
+};
+
+// This function will determine the configuration of the engine
+// and use one of the two functions below to register the file type
+void RegisterScriptFile(asIScriptEngine *engine);
+
+// Call this function to register the file type
+// using native calling conventions
+void RegisterScriptFile_Native(asIScriptEngine *engine);
+
+// Use this one instead if native calling conventions
+// are not supported on the target platform
+void RegisterScriptFile_Generic(asIScriptEngine *engine);
+
+END_AS_NAMESPACE
+
+#endif
diff --git a/Source/Scripting/angelscript/add_on/scriptfile/scriptfilesystem.cpp b/Source/Scripting/angelscript/add_on/scriptfile/scriptfilesystem.cpp
new file mode 100644
index 00000000..da1ceab6
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/scriptfile/scriptfilesystem.cpp
@@ -0,0 +1,267 @@
+#include "scriptfilesystem.h"
+
+#if defined(_WIN32)
+#include <direct.h> // _getcwd
+#include <Windows.h> // FindFirstFile, GetFileAttributes
+#else
+#include <unistd.h> // getcwd
+#include <dirent.h> // opendir, readdir, closedir
+#include <sys/stat.h> // stat
+#endif
+#include <assert.h> // assert
+
+using namespace std;
+
+BEGIN_AS_NAMESPACE
+
+CScriptFileSystem *ScriptFileSystem_Factory()
+{
+ return new CScriptFileSystem();
+}
+
+void RegisterScriptFileSystem_Native(asIScriptEngine *engine)
+{
+ int r;
+
+ r = engine->RegisterObjectType("filesystem", 0, asOBJ_REF); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("filesystem", asBEHAVE_FACTORY, "filesystem @f()", asFUNCTION(ScriptFileSystem_Factory), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("filesystem", asBEHAVE_ADDREF, "void f()", asMETHOD(CScriptFileSystem,AddRef), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("filesystem", asBEHAVE_RELEASE, "void f()", asMETHOD(CScriptFileSystem,Release), asCALL_THISCALL); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("filesystem", "bool changeCurrentPath(const string &in)", asMETHOD(CScriptFileSystem, ChangeCurrentPath), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("filesystem", "string getCurrentPath() const", asMETHOD(CScriptFileSystem, GetCurrentPath), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("filesystem", "array<string> @getDirs()", asMETHOD(CScriptFileSystem, GetDirs), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("filesystem", "array<string> @getFiles()", asMETHOD(CScriptFileSystem, GetFiles), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("filesystem", "bool isDir(const string &in)", asMETHOD(CScriptFileSystem, IsDir), asCALL_THISCALL); assert( r >= 0 );
+}
+
+void RegisterScriptFileSystem(asIScriptEngine *engine)
+{
+// if( strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") )
+// RegisterScriptFileSystem_Generic(engine);
+// else
+ RegisterScriptFileSystem_Native(engine);
+}
+
+CScriptFileSystem::CScriptFileSystem()
+{
+ refCount = 1;
+
+ // Gets the application's current working directory as the starting point
+ char buffer[1000];
+#if defined(_WIN32)
+ currentPath = _getcwd(buffer, 1000);
+#else
+ currentPath = getcwd(buffer, 1000);
+#endif
+}
+
+CScriptFileSystem::~CScriptFileSystem()
+{
+}
+
+void CScriptFileSystem::AddRef() const
+{
+ asAtomicInc(refCount);
+}
+
+void CScriptFileSystem::Release() const
+{
+ if( asAtomicDec(refCount) == 0 )
+ delete this;
+}
+
+CScriptArray *CScriptFileSystem::GetFiles() const
+{
+ // Obtain a pointer to the engine
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+
+ // TODO: This should only be done once
+ // TODO: This assumes that CScriptArray was already registered
+ asITypeInfo *arrayType = engine->GetTypeInfoByDecl("array<string>");
+
+ // Create the array object
+ CScriptArray *array = CScriptArray::Create(arrayType);
+
+#if defined(_WIN32)
+ // Windows uses UTF16 so it is necessary to convert the string
+ wchar_t bufUTF16[10000];
+ string searchPattern = currentPath + "/*";
+ MultiByteToWideChar(CP_UTF8, 0, searchPattern.c_str(), -1, bufUTF16, 10000);
+
+ WIN32_FIND_DATAW ffd;
+ HANDLE hFind = FindFirstFileW(bufUTF16, &ffd);
+ if( INVALID_HANDLE_VALUE == hFind )
+ return array;
+
+ do
+ {
+ // Skip directories
+ if( (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) )
+ continue;
+
+ // Convert the file name back to UTF8
+ char bufUTF8[10000];
+ WideCharToMultiByte(CP_UTF8, 0, ffd.cFileName, -1, bufUTF8, 10000, 0, 0);
+
+ // Add the file to the array
+ array->Resize(array->GetSize()+1);
+ ((string*)(array->At(array->GetSize()-1)))->assign(bufUTF8);
+ }
+ while( FindNextFileW(hFind, &ffd) != 0 );
+
+ FindClose(hFind);
+#else
+ dirent *ent = 0;
+ DIR *dir = opendir(currentPath.c_str());
+ while( (ent = readdir(dir)) != NULL )
+ {
+ const string filename = ent->d_name;
+
+ // Skip . and ..
+ if( filename[0] == '.' )
+ continue;
+
+ // Skip sub directories
+ const string fullname = currentPath + "/" + filename;
+ struct stat st;
+ if( stat(fullname.c_str(), &st) == -1 )
+ continue;
+ if( (st.st_mode & S_IFDIR) != 0 )
+ continue;
+
+ // Add the file to the array
+ array->Resize(array->GetSize()+1);
+ ((string*)(array->At(array->GetSize()-1)))->assign(filename);
+ }
+ closedir(dir);
+#endif
+
+ return array;
+}
+
+CScriptArray *CScriptFileSystem::GetDirs() const
+{
+ // Obtain a pointer to the engine
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+
+ // TODO: This should only be done once
+ // TODO: This assumes that CScriptArray was already registered
+ asITypeInfo *arrayType = engine->GetTypeInfoByDecl("array<string>");
+
+ // Create the array object
+ CScriptArray *array = CScriptArray::Create(arrayType);
+
+#if defined(_WIN32)
+ // Windows uses UTF16 so it is necessary to convert the string
+ wchar_t bufUTF16[10000];
+ string searchPattern = currentPath + "/*";
+ MultiByteToWideChar(CP_UTF8, 0, searchPattern.c_str(), -1, bufUTF16, 10000);
+
+ WIN32_FIND_DATAW ffd;
+ HANDLE hFind = FindFirstFileW(bufUTF16, &ffd);
+ if( INVALID_HANDLE_VALUE == hFind )
+ return array;
+
+ do
+ {
+ // Skip files
+ if( !(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) )
+ continue;
+
+ // Convert the file name back to UTF8
+ char bufUTF8[10000];
+ WideCharToMultiByte(CP_UTF8, 0, ffd.cFileName, -1, bufUTF8, 10000, 0, 0);
+
+ if( strcmp(bufUTF8, ".") == 0 || strcmp(bufUTF8, "..") == 0 )
+ continue;
+
+ // Add the dir to the array
+ array->Resize(array->GetSize()+1);
+ ((string*)(array->At(array->GetSize()-1)))->assign(bufUTF8);
+ }
+ while( FindNextFileW(hFind, &ffd) != 0 );
+
+ FindClose(hFind);
+#else
+ dirent *ent = 0;
+ DIR *dir = opendir(currentPath.c_str());
+ while( (ent = readdir(dir)) != NULL )
+ {
+ const string filename = ent->d_name;
+
+ // Skip . and ..
+ if( filename[0] == '.' )
+ continue;
+
+ // Skip files
+ const string fullname = currentPath + "/" + filename;
+ struct stat st;
+ if( stat(fullname.c_str(), &st) == -1 )
+ continue;
+ if( (st.st_mode & S_IFDIR) == 0 )
+ continue;
+
+ // Add the dir to the array
+ array->Resize(array->GetSize()+1);
+ ((string*)(array->At(array->GetSize()-1)))->assign(filename);
+ }
+ closedir(dir);
+#endif
+
+ return array;
+}
+
+bool CScriptFileSystem::ChangeCurrentPath(const string &path)
+{
+ if( path.find(":") != string::npos || path.find("/") == 0 || path.find("\\") == 0 )
+ currentPath = path;
+ else
+ currentPath += "/" + path;
+
+ // Remove trailing slashes from the path
+ while( currentPath.length() && (currentPath[currentPath.length()-1] == '/' || currentPath[currentPath.length()-1] == '\\') )
+ currentPath.resize(currentPath.length()-1);
+
+ return IsDir(currentPath);
+}
+
+bool CScriptFileSystem::IsDir(const string &path) const
+{
+ string search;
+ if( path.find(":") != string::npos || path.find("/") == 0 || path.find("\\") == 0 )
+ search = path;
+ else
+ search = currentPath + "/" + path;
+
+#if defined(_WIN32)
+ // Windows uses UTF16 so it is necessary to convert the string
+ wchar_t bufUTF16[10000];
+ MultiByteToWideChar(CP_UTF8, 0, search.c_str(), -1, bufUTF16, 10000);
+
+ // Check if the path exists and is a directory
+ DWORD attrib = GetFileAttributesW(bufUTF16);
+ if( attrib == INVALID_FILE_ATTRIBUTES ||
+ !(attrib & FILE_ATTRIBUTE_DIRECTORY) )
+ return false;
+#else
+ // Check if the path exists and is a directory
+ struct stat st;
+ if( stat(search.c_str(), &st) == -1 )
+ return false;
+ if( (st.st_mode & S_IFDIR) == 0 )
+ return false;
+#endif
+
+ return true;
+}
+
+string CScriptFileSystem::GetCurrentPath() const
+{
+ return currentPath;
+}
+
+
+END_AS_NAMESPACE
diff --git a/Source/Scripting/angelscript/add_on/scriptfile/scriptfilesystem.h b/Source/Scripting/angelscript/add_on/scriptfile/scriptfilesystem.h
new file mode 100644
index 00000000..923c192f
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/scriptfile/scriptfilesystem.h
@@ -0,0 +1,49 @@
+#ifndef AS_SCRIPTFILESYSTEM_H
+#define AS_SCRIPTFILESYSTEM_H
+
+#ifndef ANGELSCRIPT_H
+// Avoid having to inform include path if header is already include before
+#include <angelscript.h>
+#endif
+
+#include <string>
+#include <stdio.h>
+
+#include "../scriptarray/scriptarray.h"
+
+BEGIN_AS_NAMESPACE
+
+class CScriptFileSystem
+{
+public:
+ CScriptFileSystem();
+
+ void AddRef() const;
+ void Release() const;
+
+ // Sets the current path that should be used in other calls when using relative paths
+ // It can use relative paths too, so moving up a directory is used by passing in ".."
+ bool ChangeCurrentPath(const std::string &path);
+ std::string GetCurrentPath() const;
+
+ // Returns true if the path is a directory. Input can be either a full path or a relative path
+ bool IsDir(const std::string &path) const;
+
+ // Returns a list of the files in the current path
+ CScriptArray *GetFiles() const;
+
+ // Returns a list of the directories in the current path
+ CScriptArray *GetDirs() const;
+
+protected:
+ ~CScriptFileSystem();
+
+ mutable int refCount;
+ std::string currentPath;
+};
+
+void RegisterScriptFileSystem(asIScriptEngine *engine);
+
+END_AS_NAMESPACE
+
+#endif
diff --git a/Source/Scripting/angelscript/add_on/scriptgrid/scriptgrid.cpp b/Source/Scripting/angelscript/add_on/scriptgrid/scriptgrid.cpp
new file mode 100644
index 00000000..3f4a2d25
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/scriptgrid/scriptgrid.cpp
@@ -0,0 +1,792 @@
+#include <new>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdio.h> // sprintf
+
+#include "scriptgrid.h"
+
+using namespace std;
+
+BEGIN_AS_NAMESPACE
+
+// Set the default memory routines
+// Use the angelscript engine's memory routines by default
+static asALLOCFUNC_t userAlloc = asAllocMem;
+static asFREEFUNC_t userFree = asFreeMem;
+
+// Allows the application to set which memory routines should be used by the array object
+void CScriptGrid::SetMemoryFunctions(asALLOCFUNC_t allocFunc, asFREEFUNC_t freeFunc)
+{
+ userAlloc = allocFunc;
+ userFree = freeFunc;
+}
+
+static void RegisterScriptGrid_Native(asIScriptEngine *engine);
+
+struct SGridBuffer
+{
+ asDWORD width;
+ asDWORD height;
+ asBYTE data[1];
+};
+
+CScriptGrid *CScriptGrid::Create(asITypeInfo *ti)
+{
+ return CScriptGrid::Create(ti, 0, 0);
+}
+
+CScriptGrid *CScriptGrid::Create(asITypeInfo *ti, asUINT w, asUINT h)
+{
+ // Allocate the memory
+ void *mem = userAlloc(sizeof(CScriptGrid));
+ if( mem == 0 )
+ {
+ asIScriptContext *ctx = asGetActiveContext();
+ if( ctx )
+ ctx->SetException("Out of memory");
+
+ return 0;
+ }
+
+ // Initialize the object
+ CScriptGrid *a = new(mem) CScriptGrid(w, h, ti);
+
+ return a;
+}
+
+CScriptGrid *CScriptGrid::Create(asITypeInfo *ti, void *initList)
+{
+ // Allocate the memory
+ void *mem = userAlloc(sizeof(CScriptGrid));
+ if( mem == 0 )
+ {
+ asIScriptContext *ctx = asGetActiveContext();
+ if( ctx )
+ ctx->SetException("Out of memory");
+
+ return 0;
+ }
+
+ // Initialize the object
+ CScriptGrid *a = new(mem) CScriptGrid(ti, initList);
+
+ return a;
+}
+
+CScriptGrid *CScriptGrid::Create(asITypeInfo *ti, asUINT w, asUINT h, void *defVal)
+{
+ // Allocate the memory
+ void *mem = userAlloc(sizeof(CScriptGrid));
+ if( mem == 0 )
+ {
+ asIScriptContext *ctx = asGetActiveContext();
+ if( ctx )
+ ctx->SetException("Out of memory");
+
+ return 0;
+ }
+
+ // Initialize the object
+ CScriptGrid *a = new(mem) CScriptGrid(w, h, defVal, ti);
+
+ return a;
+}
+
+// This optional callback is called when the template type is first used by the compiler.
+// It allows the application to validate if the template can be instantiated for the requested
+// subtype at compile time, instead of at runtime. The output argument dontGarbageCollect
+// allow the callback to tell the engine if the template instance type shouldn't be garbage collected,
+// i.e. no asOBJ_GC flag.
+static bool ScriptGridTemplateCallback(asITypeInfo *ti, bool &dontGarbageCollect)
+{
+ // Make sure the subtype can be instantiated with a default factory/constructor,
+ // otherwise we won't be able to instantiate the elements.
+ int typeId = ti->GetSubTypeId();
+ if( typeId == asTYPEID_VOID )
+ return false;
+ if( (typeId & asTYPEID_MASK_OBJECT) && !(typeId & asTYPEID_OBJHANDLE) )
+ {
+ asITypeInfo *subtype = ti->GetEngine()->GetTypeInfoById(typeId);
+ asDWORD flags = subtype->GetFlags();
+ if( (flags & asOBJ_VALUE) && !(flags & asOBJ_POD) )
+ {
+ // Verify that there is a default constructor
+ bool found = false;
+ for( asUINT n = 0; n < subtype->GetBehaviourCount(); n++ )
+ {
+ asEBehaviours beh;
+ asIScriptFunction *func = subtype->GetBehaviourByIndex(n, &beh);
+ if( beh != asBEHAVE_CONSTRUCT ) continue;
+
+ if( func->GetParamCount() == 0 )
+ {
+ // Found the default constructor
+ found = true;
+ break;
+ }
+ }
+
+ if( !found )
+ {
+ // There is no default constructor
+ ti->GetEngine()->WriteMessage("array", 0, 0, asMSGTYPE_ERROR, "The subtype has no default constructor");
+ return false;
+ }
+ }
+ else if( (flags & asOBJ_REF) )
+ {
+ bool found = false;
+
+ // If value assignment for ref type has been disabled then the array
+ // can be created if the type has a default factory function
+ if( !ti->GetEngine()->GetEngineProperty(asEP_DISALLOW_VALUE_ASSIGN_FOR_REF_TYPE) )
+ {
+ // Verify that there is a default factory
+ for( asUINT n = 0; n < subtype->GetFactoryCount(); n++ )
+ {
+ asIScriptFunction *func = subtype->GetFactoryByIndex(n);
+ if( func->GetParamCount() == 0 )
+ {
+ // Found the default factory
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if( !found )
+ {
+ // No default factory
+ ti->GetEngine()->WriteMessage("array", 0, 0, asMSGTYPE_ERROR, "The subtype has no default factory");
+ return false;
+ }
+ }
+
+ // If the object type is not garbage collected then the array also doesn't need to be
+ if( !(flags & asOBJ_GC) )
+ dontGarbageCollect = true;
+ }
+ else if( !(typeId & asTYPEID_OBJHANDLE) )
+ {
+ // Arrays with primitives cannot form circular references,
+ // thus there is no need to garbage collect them
+ dontGarbageCollect = true;
+ }
+ else
+ {
+ assert( typeId & asTYPEID_OBJHANDLE );
+
+ // It is not necessary to set the array as garbage collected for all handle types.
+ // If it is possible to determine that the handle cannot refer to an object type
+ // that can potentially form a circular reference with the array then it is not
+ // necessary to make the array garbage collected.
+ asITypeInfo *subtype = ti->GetEngine()->GetTypeInfoById(typeId);
+ asDWORD flags = subtype->GetFlags();
+ if( !(flags & asOBJ_GC) )
+ {
+ if( (flags & asOBJ_SCRIPT_OBJECT) )
+ {
+ // Even if a script class is by itself not garbage collected, it is possible
+ // that classes that derive from it may be, so it is not possible to know
+ // that no circular reference can occur.
+ if( (flags & asOBJ_NOINHERIT) )
+ {
+ // A script class declared as final cannot be inherited from, thus
+ // we can be certain that the object cannot be garbage collected.
+ dontGarbageCollect = true;
+ }
+ }
+ else
+ {
+ // For application registered classes we assume the application knows
+ // what it is doing and don't mark the array as garbage collected unless
+ // the type is also garbage collected.
+ dontGarbageCollect = true;
+ }
+ }
+ }
+
+ // The type is ok
+ return true;
+}
+
+// Registers the template array type
+void RegisterScriptGrid(asIScriptEngine *engine)
+{
+ // TODO: Implement the generic calling convention
+ RegisterScriptGrid_Native(engine);
+}
+
+static void RegisterScriptGrid_Native(asIScriptEngine *engine)
+{
+ int r;
+
+ // Register the grid type as a template
+ r = engine->RegisterObjectType("grid<class T>", 0, asOBJ_REF | asOBJ_GC | asOBJ_TEMPLATE); assert( r >= 0 );
+
+ // Register a callback for validating the subtype before it is used
+ r = engine->RegisterObjectBehaviour("grid<T>", asBEHAVE_TEMPLATE_CALLBACK, "bool f(int&in, bool&out)", asFUNCTION(ScriptGridTemplateCallback), asCALL_CDECL); assert( r >= 0 );
+
+ // Templates receive the object type as the first parameter. To the script writer this is hidden
+ r = engine->RegisterObjectBehaviour("grid<T>", asBEHAVE_FACTORY, "grid<T>@ f(int&in)", asFUNCTIONPR(CScriptGrid::Create, (asITypeInfo*), CScriptGrid*), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("grid<T>", asBEHAVE_FACTORY, "grid<T>@ f(int&in, uint, uint)", asFUNCTIONPR(CScriptGrid::Create, (asITypeInfo*, asUINT, asUINT), CScriptGrid*), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("grid<T>", asBEHAVE_FACTORY, "grid<T>@ f(int&in, uint, uint, const T &in)", asFUNCTIONPR(CScriptGrid::Create, (asITypeInfo*, asUINT, asUINT, void *), CScriptGrid*), asCALL_CDECL); assert( r >= 0 );
+
+ // Register the factory that will be used for initialization lists
+ r = engine->RegisterObjectBehaviour("grid<T>", asBEHAVE_LIST_FACTORY, "grid<T>@ f(int&in type, int&in list) {repeat {repeat_same T}}", asFUNCTIONPR(CScriptGrid::Create, (asITypeInfo*, void*), CScriptGrid*), asCALL_CDECL); assert( r >= 0 );
+
+ // The memory management methods
+ r = engine->RegisterObjectBehaviour("grid<T>", asBEHAVE_ADDREF, "void f()", asMETHOD(CScriptGrid,AddRef), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("grid<T>", asBEHAVE_RELEASE, "void f()", asMETHOD(CScriptGrid,Release), asCALL_THISCALL); assert( r >= 0 );
+
+ // The index operator returns the template subtype
+ r = engine->RegisterObjectMethod("grid<T>", "T &opIndex(uint, uint)", asMETHODPR(CScriptGrid, At, (asUINT, asUINT), void*), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("grid<T>", "const T &opIndex(uint, uint) const", asMETHODPR(CScriptGrid, At, (asUINT, asUINT) const, const void*), asCALL_THISCALL); assert( r >= 0 );
+
+ // Other methods
+ r = engine->RegisterObjectMethod("grid<T>", "void resize(uint width, uint height)", asMETHODPR(CScriptGrid, Resize, (asUINT, asUINT), void), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("grid<T>", "uint width() const", asMETHOD(CScriptGrid, GetWidth), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("grid<T>", "uint height() const", asMETHOD(CScriptGrid, GetHeight), asCALL_THISCALL); assert( r >= 0 );
+
+ // Register GC behaviours in case the array needs to be garbage collected
+ r = engine->RegisterObjectBehaviour("grid<T>", asBEHAVE_GETREFCOUNT, "int f()", asMETHOD(CScriptGrid, GetRefCount), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("grid<T>", asBEHAVE_SETGCFLAG, "void f()", asMETHOD(CScriptGrid, SetFlag), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("grid<T>", asBEHAVE_GETGCFLAG, "bool f()", asMETHOD(CScriptGrid, GetFlag), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("grid<T>", asBEHAVE_ENUMREFS, "void f(int&in)", asMETHOD(CScriptGrid, EnumReferences), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("grid<T>", asBEHAVE_RELEASEREFS, "void f(int&in)", asMETHOD(CScriptGrid, ReleaseAllHandles), asCALL_THISCALL); assert( r >= 0 );
+}
+
+CScriptGrid::CScriptGrid(asITypeInfo *ti, void *buf)
+{
+ refCount = 1;
+ gcFlag = false;
+ objType = ti;
+ objType->AddRef();
+ buffer = 0;
+ subTypeId = objType->GetSubTypeId();
+
+ asIScriptEngine *engine = ti->GetEngine();
+
+ // Determine element size
+ if( subTypeId & asTYPEID_MASK_OBJECT )
+ elementSize = sizeof(asPWORD);
+ else
+ elementSize = engine->GetSizeOfPrimitiveType(subTypeId);
+
+ // Determine the initial size from the buffer
+ asUINT height = *(asUINT*)buf;
+ asUINT width = height ? *(asUINT*)((char*)(buf)+4) : 0;
+
+ // Make sure the grid size isn't too large for us to handle
+ if( !CheckMaxSize(width, height) )
+ {
+ // Don't continue with the initialization
+ return;
+ }
+
+ // Skip the height value at the start of the buffer
+ buf = (asUINT*)(buf)+1;
+
+ // Copy the values of the grid elements from the buffer
+ if( (ti->GetSubTypeId() & asTYPEID_MASK_OBJECT) == 0 )
+ {
+ CreateBuffer(&buffer, width, height);
+
+ // Copy the values of the primitive type into the internal buffer
+ for( asUINT y = 0; y < height; y++ )
+ {
+ // Skip the length value at the start of each row
+ buf = (asUINT*)(buf)+1;
+
+ // Copy the line
+ if( width > 0 )
+ memcpy(At(0,y), buf, width*elementSize);
+
+ // Move to next line
+ buf = (char*)(buf) + width*elementSize;
+
+ // Align to 4 byte boundary
+ if( asPWORD(buf) & 0x3 )
+ buf = (char*)(buf) + 4 - (asPWORD(buf) & 0x3);
+ }
+ }
+ else if( ti->GetSubTypeId() & asTYPEID_OBJHANDLE )
+ {
+ CreateBuffer(&buffer, width, height);
+
+ // Copy the handles into the internal buffer
+ for( asUINT y = 0; y < height; y++ )
+ {
+ // Skip the length value at the start of each row
+ buf = (asUINT*)(buf)+1;
+
+ // Copy the line
+ if( width > 0 )
+ memcpy(At(0,y), buf, width*elementSize);
+
+ // With object handles it is safe to clear the memory in the received buffer
+ // instead of increasing the ref count. It will save time both by avoiding the
+ // call the increase ref, and also relieve the engine from having to release
+ // its references too
+ memset(buf, 0, width*elementSize);
+
+ // Move to next line
+ buf = (char*)(buf) + width*elementSize;
+
+ // Align to 4 byte boundary
+ if( asPWORD(buf) & 0x3 )
+ buf = (char*)(buf) + 4 - (asPWORD(buf) & 0x3);
+ }
+ }
+ else if( ti->GetSubType()->GetFlags() & asOBJ_REF )
+ {
+ // Only allocate the buffer, but not the objects
+ subTypeId |= asTYPEID_OBJHANDLE;
+ CreateBuffer(&buffer, width, height);
+ subTypeId &= ~asTYPEID_OBJHANDLE;
+
+ // Copy the handles into the internal buffer
+ for( asUINT y = 0; y < height; y++ )
+ {
+ // Skip the length value at the start of each row
+ buf = (asUINT*)(buf)+1;
+
+ // Copy the line
+ if( width > 0 )
+ memcpy(At(0,y), buf, width*elementSize);
+
+ // With object handles it is safe to clear the memory in the received buffer
+ // instead of increasing the ref count. It will save time both by avoiding the
+ // call the increase ref, and also relieve the engine from having to release
+ // its references too
+ memset(buf, 0, width*elementSize);
+
+ // Move to next line
+ buf = (char*)(buf) + width*elementSize;
+
+ // Align to 4 byte boundary
+ if( asPWORD(buf) & 0x3 )
+ buf = (char*)(buf) + 4 - (asPWORD(buf) & 0x3);
+ }
+ }
+ else
+ {
+ // TODO: Optimize by calling the copy constructor of the object instead of
+ // constructing with the default constructor and then assigning the value
+ // TODO: With C++11 ideally we should be calling the move constructor, instead
+ // of the copy constructor as the engine will just discard the objects in the
+ // buffer afterwards.
+ CreateBuffer(&buffer, width, height);
+
+ // For value types we need to call the opAssign for each individual object
+ asITypeInfo *subType = ti->GetSubType();
+ asUINT subTypeSize = subType->GetSize();
+ for( asUINT y = 0;y < height; y++ )
+ {
+ // Skip the length value at the start of each row
+ buf = (asUINT*)(buf)+1;
+
+ // Call opAssign for each of the objects on the row
+ for( asUINT x = 0; x < width; x++ )
+ {
+ void *obj = At(x,y);
+ asBYTE *srcObj = (asBYTE*)(buf) + x*subTypeSize;
+ engine->AssignScriptObject(obj, srcObj, subType);
+ }
+
+ // Move to next line
+ buf = (char*)(buf) + width*subTypeSize;
+
+ // Align to 4 byte boundary
+ if( asPWORD(buf) & 0x3 )
+ buf = (char*)(buf) + 4 - (asPWORD(buf) & 0x3);
+ }
+ }
+
+ // Notify the GC of the successful creation
+ if( objType->GetFlags() & asOBJ_GC )
+ objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType);
+}
+
+CScriptGrid::CScriptGrid(asUINT width, asUINT height, asITypeInfo *ti)
+{
+ refCount = 1;
+ gcFlag = false;
+ objType = ti;
+ objType->AddRef();
+ buffer = 0;
+ subTypeId = objType->GetSubTypeId();
+
+ // Determine element size
+ if( subTypeId & asTYPEID_MASK_OBJECT )
+ elementSize = sizeof(asPWORD);
+ else
+ elementSize = objType->GetEngine()->GetSizeOfPrimitiveType(subTypeId);
+
+ // Make sure the array size isn't too large for us to handle
+ if( !CheckMaxSize(width, height) )
+ {
+ // Don't continue with the initialization
+ return;
+ }
+
+ CreateBuffer(&buffer, width, height);
+
+ // Notify the GC of the successful creation
+ if( objType->GetFlags() & asOBJ_GC )
+ objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType);
+}
+
+void CScriptGrid::Resize(asUINT width, asUINT height)
+{
+ // Make sure the size isn't too large for us to handle
+ if( !CheckMaxSize(width, height) )
+ return;
+
+ // Create a new buffer
+ SGridBuffer *tmpBuffer = 0;
+ CreateBuffer(&tmpBuffer, width, height);
+ if( tmpBuffer == 0 )
+ return;
+
+ if( buffer )
+ {
+ // Copy the existing values to the new buffer
+ asUINT w = width > buffer->width ? buffer->width : width;
+ asUINT h = height > buffer->height ? buffer->height : height;
+ for( asUINT y = 0; y < h; y++ )
+ for( asUINT x = 0; x < w; x++ )
+ SetValue(tmpBuffer, x, y, At(buffer, x, y));
+
+ // Replace the internal buffer
+ DeleteBuffer(buffer);
+ }
+
+ buffer = tmpBuffer;
+}
+
+CScriptGrid::CScriptGrid(asUINT width, asUINT height, void *defVal, asITypeInfo *ti)
+{
+ refCount = 1;
+ gcFlag = false;
+ objType = ti;
+ objType->AddRef();
+ buffer = 0;
+ subTypeId = objType->GetSubTypeId();
+
+ // Determine element size
+ if( subTypeId & asTYPEID_MASK_OBJECT )
+ elementSize = sizeof(asPWORD);
+ else
+ elementSize = objType->GetEngine()->GetSizeOfPrimitiveType(subTypeId);
+
+ // Make sure the array size isn't too large for us to handle
+ if( !CheckMaxSize(width, height) )
+ {
+ // Don't continue with the initialization
+ return;
+ }
+
+ CreateBuffer(&buffer, width, height);
+
+ // Notify the GC of the successful creation
+ if( objType->GetFlags() & asOBJ_GC )
+ objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType);
+
+ // Initialize the elements with the default value
+ for( asUINT y = 0; y < GetHeight(); y++ )
+ for( asUINT x = 0; x < GetWidth(); x++ )
+ SetValue(x, y, defVal);
+}
+
+void CScriptGrid::SetValue(asUINT x, asUINT y, void *value)
+{
+ SetValue(buffer, x, y, value);
+}
+
+void CScriptGrid::SetValue(SGridBuffer *buf, asUINT x, asUINT y, void *value)
+{
+ // At() will take care of the out-of-bounds checking, though
+ // if called from the application then nothing will be done
+ void *ptr = At(buf, x, y);
+ if( ptr == 0 ) return;
+
+ if( (subTypeId & ~asTYPEID_MASK_SEQNBR) && !(subTypeId & asTYPEID_OBJHANDLE) )
+ objType->GetEngine()->AssignScriptObject(ptr, value, objType->GetSubType());
+ else if( subTypeId & asTYPEID_OBJHANDLE )
+ {
+ void *tmp = *(void**)ptr;
+ *(void**)ptr = *(void**)value;
+ objType->GetEngine()->AddRefScriptObject(*(void**)value, objType->GetSubType());
+ if( tmp )
+ objType->GetEngine()->ReleaseScriptObject(tmp, objType->GetSubType());
+ }
+ else if( subTypeId == asTYPEID_BOOL ||
+ subTypeId == asTYPEID_INT8 ||
+ subTypeId == asTYPEID_UINT8 )
+ *(char*)ptr = *(char*)value;
+ else if( subTypeId == asTYPEID_INT16 ||
+ subTypeId == asTYPEID_UINT16 )
+ *(short*)ptr = *(short*)value;
+ else if( subTypeId == asTYPEID_INT32 ||
+ subTypeId == asTYPEID_UINT32 ||
+ subTypeId == asTYPEID_FLOAT ||
+ subTypeId > asTYPEID_DOUBLE ) // enums have a type id larger than doubles
+ *(int*)ptr = *(int*)value;
+ else if( subTypeId == asTYPEID_INT64 ||
+ subTypeId == asTYPEID_UINT64 ||
+ subTypeId == asTYPEID_DOUBLE )
+ *(double*)ptr = *(double*)value;
+}
+
+CScriptGrid::~CScriptGrid()
+{
+ if( buffer )
+ {
+ DeleteBuffer(buffer);
+ buffer = 0;
+ }
+ if( objType ) objType->Release();
+}
+
+asUINT CScriptGrid::GetWidth() const
+{
+ if( buffer )
+ return buffer->width;
+
+ return 0;
+}
+
+asUINT CScriptGrid::GetHeight() const
+{
+ if( buffer )
+ return buffer->height;
+
+ return 0;
+}
+
+// internal
+bool CScriptGrid::CheckMaxSize(asUINT width, asUINT height)
+{
+ // This code makes sure the size of the buffer that is allocated
+ // for the array doesn't overflow and becomes smaller than requested
+
+ asUINT maxSize = 0xFFFFFFFFul - sizeof(SGridBuffer) + 1;
+ if( elementSize > 0 )
+ maxSize /= elementSize;
+
+ asINT64 numElements = width * height;
+
+ if( (numElements >> 32) || numElements > maxSize )
+ {
+ asIScriptContext *ctx = asGetActiveContext();
+ if( ctx )
+ ctx->SetException("Too large grid size");
+
+ return false;
+ }
+
+ // OK
+ return true;
+}
+
+asITypeInfo *CScriptGrid::GetGridObjectType() const
+{
+ return objType;
+}
+
+int CScriptGrid::GetGridTypeId() const
+{
+ return objType->GetTypeId();
+}
+
+int CScriptGrid::GetElementTypeId() const
+{
+ return subTypeId;
+}
+
+void *CScriptGrid::At(asUINT x, asUINT y)
+{
+ return At(buffer, x, y);
+}
+
+// Return a pointer to the array element. Returns 0 if the index is out of bounds
+void *CScriptGrid::At(SGridBuffer *buf, asUINT x, asUINT y)
+{
+ if( buf == 0 || x >= buf->width || y >= buf->height )
+ {
+ // If this is called from a script we raise a script exception
+ asIScriptContext *ctx = asGetActiveContext();
+ if( ctx )
+ ctx->SetException("Index out of bounds");
+ return 0;
+ }
+
+ asUINT index = x+y*buf->width;
+ if( (subTypeId & asTYPEID_MASK_OBJECT) && !(subTypeId & asTYPEID_OBJHANDLE) )
+ return *(void**)(buf->data + elementSize*index);
+ else
+ return buf->data + elementSize*index;
+}
+const void *CScriptGrid::At(asUINT x, asUINT y) const
+{
+ return const_cast<CScriptGrid*>(this)->At(const_cast<SGridBuffer*>(buffer), x, y);
+}
+
+
+// internal
+void CScriptGrid::CreateBuffer(SGridBuffer **buf, asUINT w, asUINT h)
+{
+ asUINT numElements = w * h;
+
+ *buf = reinterpret_cast<SGridBuffer*>(userAlloc(sizeof(SGridBuffer)-1+elementSize*numElements));
+
+ if( *buf )
+ {
+ (*buf)->width = w;
+ (*buf)->height = h;
+ Construct(*buf);
+ }
+ else
+ {
+ // Oops, out of memory
+ asIScriptContext *ctx = asGetActiveContext();
+ if( ctx )
+ ctx->SetException("Out of memory");
+ }
+}
+
+// internal
+void CScriptGrid::DeleteBuffer(SGridBuffer *buf)
+{
+ assert( buf );
+
+ Destruct(buf);
+
+ // Free the buffer
+ userFree(buf);
+}
+
+// internal
+void CScriptGrid::Construct(SGridBuffer *buf)
+{
+ assert( buf );
+
+ if( subTypeId & asTYPEID_OBJHANDLE )
+ {
+ // Set all object handles to null
+ void *d = (void*)(buf->data);
+ memset(d, 0, (buf->width*buf->height)*sizeof(void*));
+ }
+ else if( subTypeId & asTYPEID_MASK_OBJECT )
+ {
+ void **max = (void**)(buf->data + (buf->width*buf->height) * sizeof(void*));
+ void **d = (void**)(buf->data);
+
+ asIScriptEngine *engine = objType->GetEngine();
+ asITypeInfo *subType = objType->GetSubType();
+
+ for( ; d < max; d++ )
+ {
+ *d = (void*)engine->CreateScriptObject(subType);
+ if( *d == 0 )
+ {
+ // Set the remaining entries to null so the destructor
+ // won't attempt to destroy invalid objects later
+ memset(d, 0, sizeof(void*)*(max-d));
+
+ // There is no need to set an exception on the context,
+ // as CreateScriptObject has already done that
+ return;
+ }
+ }
+ }
+}
+
+// internal
+void CScriptGrid::Destruct(SGridBuffer *buf)
+{
+ assert( buf );
+
+ if( subTypeId & asTYPEID_MASK_OBJECT )
+ {
+ asIScriptEngine *engine = objType->GetEngine();
+
+ void **max = (void**)(buf->data + (buf->width*buf->height) * sizeof(void*));
+ void **d = (void**)(buf->data);
+
+ for( ; d < max; d++ )
+ {
+ if( *d )
+ engine->ReleaseScriptObject(*d, objType->GetSubType());
+ }
+ }
+}
+
+// GC behaviour
+void CScriptGrid::EnumReferences(asIScriptEngine *engine)
+{
+ if( buffer == 0 ) return;
+
+ // If the array is holding handles, then we need to notify the GC of them
+ if( subTypeId & asTYPEID_MASK_OBJECT )
+ {
+ asUINT numElements = buffer->width * buffer->height;
+ void **d = (void**)buffer->data;
+ for( asUINT n = 0; n < numElements; n++ )
+ {
+ if( d[n] )
+ engine->GCEnumCallback(d[n]);
+ }
+ }
+}
+
+// GC behaviour
+void CScriptGrid::ReleaseAllHandles(asIScriptEngine*)
+{
+ if( buffer == 0 ) return;
+
+ DeleteBuffer(buffer);
+ buffer = 0;
+}
+
+void CScriptGrid::AddRef() const
+{
+ // Clear the GC flag then increase the counter
+ gcFlag = false;
+ asAtomicInc(refCount);
+}
+
+void CScriptGrid::Release() const
+{
+ // Clearing the GC flag then descrease the counter
+ gcFlag = false;
+ if( asAtomicDec(refCount) == 0 )
+ {
+ // When reaching 0 no more references to this instance
+ // exists and the object should be destroyed
+ this->~CScriptGrid();
+ userFree(const_cast<CScriptGrid*>(this));
+ }
+}
+
+// GC behaviour
+int CScriptGrid::GetRefCount()
+{
+ return refCount;
+}
+
+// GC behaviour
+void CScriptGrid::SetFlag()
+{
+ gcFlag = true;
+}
+
+// GC behaviour
+bool CScriptGrid::GetFlag()
+{
+ return gcFlag;
+}
+
+END_AS_NAMESPACE
diff --git a/Source/Scripting/angelscript/add_on/scriptgrid/scriptgrid.h b/Source/Scripting/angelscript/add_on/scriptgrid/scriptgrid.h
new file mode 100644
index 00000000..feeb167f
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/scriptgrid/scriptgrid.h
@@ -0,0 +1,82 @@
+#ifndef SCRIPTGRID_H
+#define SCRIPTGRID_H
+
+#ifndef ANGELSCRIPT_H
+// Avoid having to inform include path if header is already include before
+#include <angelscript.h>
+#endif
+
+BEGIN_AS_NAMESPACE
+
+struct SGridBuffer;
+
+class CScriptGrid
+{
+public:
+ // Set the memory functions that should be used by all CScriptGrids
+ static void SetMemoryFunctions(asALLOCFUNC_t allocFunc, asFREEFUNC_t freeFunc);
+
+ // Factory functions
+ static CScriptGrid *Create(asITypeInfo *ot);
+ static CScriptGrid *Create(asITypeInfo *ot, asUINT width, asUINT height);
+ static CScriptGrid *Create(asITypeInfo *ot, asUINT width, asUINT height, void *defaultValue);
+ static CScriptGrid *Create(asITypeInfo *ot, void *listBuffer);
+
+ // Memory management
+ void AddRef() const;
+ void Release() const;
+
+ // Type information
+ asITypeInfo *GetGridObjectType() const;
+ int GetGridTypeId() const;
+ int GetElementTypeId() const;
+
+ // Size
+ asUINT GetWidth() const;
+ asUINT GetHeight() const;
+ void Resize(asUINT width, asUINT height);
+
+ // Get a pointer to an element. Returns 0 if out of bounds
+ void *At(asUINT x, asUINT y);
+ const void *At(asUINT x, asUINT y) const;
+
+ // Set value of an element
+ // Remember, if the grid holds handles the value parameter should be the
+ // address of the handle. The refCount of the object will also be incremented
+ void SetValue(asUINT x, asUINT y, void *value);
+
+ // GC methods
+ int GetRefCount();
+ void SetFlag();
+ bool GetFlag();
+ void EnumReferences(asIScriptEngine *engine);
+ void ReleaseAllHandles(asIScriptEngine *engine);
+
+protected:
+ mutable int refCount;
+ mutable bool gcFlag;
+ asITypeInfo *objType;
+ SGridBuffer *buffer;
+ int elementSize;
+ int subTypeId;
+
+ // Constructors
+ CScriptGrid(asITypeInfo *ot, void *initBuf); // Called from script when initialized with list
+ CScriptGrid(asUINT w, asUINT h, asITypeInfo *ot);
+ CScriptGrid(asUINT w, asUINT h, void *defVal, asITypeInfo *ot);
+ virtual ~CScriptGrid();
+
+ bool CheckMaxSize(asUINT x, asUINT y);
+ void CreateBuffer(SGridBuffer **buf, asUINT w, asUINT h);
+ void DeleteBuffer(SGridBuffer *buf);
+ void Construct(SGridBuffer *buf);
+ void Destruct(SGridBuffer *buf);
+ void SetValue(SGridBuffer *buf, asUINT x, asUINT y, void *value);
+ void *At(SGridBuffer *buf, asUINT x, asUINT y);
+};
+
+void RegisterScriptGrid(asIScriptEngine *engine);
+
+END_AS_NAMESPACE
+
+#endif
diff --git a/Source/Scripting/angelscript/add_on/scripthandle/scripthandle.cpp b/Source/Scripting/angelscript/add_on/scripthandle/scripthandle.cpp
new file mode 100644
index 00000000..ce0650cd
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/scripthandle/scripthandle.cpp
@@ -0,0 +1,329 @@
+#include "scripthandle.h"
+#include <new>
+#include <assert.h>
+#include <string.h>
+
+BEGIN_AS_NAMESPACE
+
+static void Construct(CScriptHandle *self) { new(self) CScriptHandle(); }
+static void Construct(CScriptHandle *self, const CScriptHandle &o) { new(self) CScriptHandle(o); }
+// This one is not static because it needs to be friend with the CScriptHandle class
+void Construct(CScriptHandle *self, void *ref, int typeId) { new(self) CScriptHandle(ref, typeId); }
+static void Destruct(CScriptHandle *self) { self->~CScriptHandle(); }
+
+CScriptHandle::CScriptHandle()
+{
+ m_ref = 0;
+ m_type = 0;
+}
+
+CScriptHandle::CScriptHandle(const CScriptHandle &other)
+{
+ m_ref = other.m_ref;
+ m_type = other.m_type;
+
+ AddRefHandle();
+}
+
+CScriptHandle::CScriptHandle(void *ref, asITypeInfo *type)
+{
+ m_ref = ref;
+ m_type = type;
+
+ AddRefHandle();
+}
+
+// This constructor shouldn't be called from the application
+// directly as it requires an active script context
+CScriptHandle::CScriptHandle(void *ref, int typeId)
+{
+ m_ref = 0;
+ m_type = 0;
+
+ Assign(ref, typeId);
+}
+
+CScriptHandle::~CScriptHandle()
+{
+ ReleaseHandle();
+}
+
+void CScriptHandle::ReleaseHandle()
+{
+ if( m_ref && m_type )
+ {
+ asIScriptEngine *engine = m_type->GetEngine();
+ engine->ReleaseScriptObject(m_ref, m_type);
+
+ engine->Release();
+
+ m_ref = 0;
+ m_type = 0;
+ }
+}
+
+void CScriptHandle::AddRefHandle()
+{
+ if( m_ref && m_type )
+ {
+ asIScriptEngine *engine = m_type->GetEngine();
+ engine->AddRefScriptObject(m_ref, m_type);
+
+ // Hold on to the engine so it isn't destroyed while
+ // a reference to a script object is still held
+ engine->AddRef();
+ }
+}
+
+CScriptHandle &CScriptHandle::operator =(const CScriptHandle &other)
+{
+ Set(other.m_ref, other.m_type);
+
+ return *this;
+}
+
+void CScriptHandle::Set(void *ref, asITypeInfo *type)
+{
+ if( m_ref == ref ) return;
+
+ ReleaseHandle();
+
+ m_ref = ref;
+ m_type = type;
+
+ AddRefHandle();
+}
+
+void *CScriptHandle::GetRef()
+{
+ return m_ref;
+}
+
+asITypeInfo *CScriptHandle::GetType() const
+{
+ return m_type;
+}
+
+int CScriptHandle::GetTypeId() const
+{
+ if( m_type == 0 ) return 0;
+
+ return m_type->GetTypeId() | asTYPEID_OBJHANDLE;
+}
+
+// This method shouldn't be called from the application
+// directly as it requires an active script context
+CScriptHandle &CScriptHandle::Assign(void *ref, int typeId)
+{
+ // When receiving a null handle we just clear our memory
+ if( typeId == 0 )
+ {
+ Set(0, 0);
+ return *this;
+ }
+
+ // Dereference received handles to get the object
+ if( typeId & asTYPEID_OBJHANDLE )
+ {
+ // Store the actual reference
+ ref = *(void**)ref;
+ typeId &= ~asTYPEID_OBJHANDLE;
+ }
+
+ // Get the object type
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *type = engine->GetTypeInfoById(typeId);
+
+ // If the argument is another CScriptHandle, we should copy the content instead
+ if( type && strcmp(type->GetName(), "ref") == 0 )
+ {
+ CScriptHandle *r = (CScriptHandle*)ref;
+ ref = r->m_ref;
+ type = r->m_type;
+ }
+
+ Set(ref, type);
+
+ return *this;
+}
+
+bool CScriptHandle::operator==(const CScriptHandle &o) const
+{
+ if( m_ref == o.m_ref &&
+ m_type == o.m_type )
+ return true;
+
+ // TODO: If type is not the same, we should attempt to do a dynamic cast,
+ // which may change the pointer for application registered classes
+
+ return false;
+}
+
+bool CScriptHandle::operator!=(const CScriptHandle &o) const
+{
+ return !(*this == o);
+}
+
+bool CScriptHandle::Equals(void *ref, int typeId) const
+{
+ // Null handles are received as reference to a null handle
+ if( typeId == 0 )
+ ref = 0;
+
+ // Dereference handles to get the object
+ if( typeId & asTYPEID_OBJHANDLE )
+ {
+ // Compare the actual reference
+ ref = *(void**)ref;
+ typeId &= ~asTYPEID_OBJHANDLE;
+ }
+
+ // TODO: If typeId is not the same, we should attempt to do a dynamic cast,
+ // which may change the pointer for application registered classes
+
+ if( ref == m_ref ) return true;
+
+ return false;
+}
+
+// AngelScript: used as '@obj = cast<obj>(ref);'
+void CScriptHandle::Cast(void **outRef, int typeId)
+{
+ // If we hold a null handle, then just return null
+ if( m_type == 0 )
+ {
+ *outRef = 0;
+ return;
+ }
+
+ // It is expected that the outRef is always a handle
+ assert( typeId & asTYPEID_OBJHANDLE );
+
+ // Compare the type id of the actual object
+ typeId &= ~asTYPEID_OBJHANDLE;
+ asIScriptEngine *engine = m_type->GetEngine();
+ asITypeInfo *type = engine->GetTypeInfoById(typeId);
+
+ *outRef = 0;
+
+ // RefCastObject will increment the refCount of the returned object if successful
+ engine->RefCastObject(m_ref, m_type, type, outRef);
+}
+
+
+
+void RegisterScriptHandle_Native(asIScriptEngine *engine)
+{
+ int r;
+
+#if AS_CAN_USE_CPP11
+ // With C++11 it is possible to use asGetTypeTraits to automatically determine the flags that represent the C++ class
+ r = engine->RegisterObjectType("ref", sizeof(CScriptHandle), asOBJ_VALUE | asOBJ_ASHANDLE | asGetTypeTraits<CScriptHandle>()); assert( r >= 0 );
+#else
+ r = engine->RegisterObjectType("ref", sizeof(CScriptHandle), asOBJ_VALUE | asOBJ_ASHANDLE | asOBJ_APP_CLASS_CDAK); assert( r >= 0 );
+#endif
+ r = engine->RegisterObjectBehaviour("ref", asBEHAVE_CONSTRUCT, "void f()", asFUNCTIONPR(Construct, (CScriptHandle *), void), asCALL_CDECL_OBJFIRST); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("ref", asBEHAVE_CONSTRUCT, "void f(const ref &in)", asFUNCTIONPR(Construct, (CScriptHandle *, const CScriptHandle &), void), asCALL_CDECL_OBJFIRST); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("ref", asBEHAVE_CONSTRUCT, "void f(const ?&in)", asFUNCTIONPR(Construct, (CScriptHandle *, void *, int), void), asCALL_CDECL_OBJFIRST); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("ref", asBEHAVE_DESTRUCT, "void f()", asFUNCTIONPR(Destruct, (CScriptHandle *), void), asCALL_CDECL_OBJFIRST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("ref", "void opCast(?&out)", asMETHODPR(CScriptHandle, Cast, (void **, int), void), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("ref", "ref &opHndlAssign(const ref &in)", asMETHOD(CScriptHandle, operator=), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("ref", "ref &opHndlAssign(const ?&in)", asMETHOD(CScriptHandle, Assign), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("ref", "bool opEquals(const ref &in) const", asMETHODPR(CScriptHandle, operator==, (const CScriptHandle &) const, bool), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("ref", "bool opEquals(const ?&in) const", asMETHODPR(CScriptHandle, Equals, (void*, int) const, bool), asCALL_THISCALL); assert( r >= 0 );
+}
+
+void CScriptHandle_Construct_Generic(asIScriptGeneric *gen)
+{
+ CScriptHandle *self = reinterpret_cast<CScriptHandle*>(gen->GetObject());
+ new(self) CScriptHandle();
+}
+
+void CScriptHandle_ConstructCopy_Generic(asIScriptGeneric *gen)
+{
+ CScriptHandle *other = reinterpret_cast<CScriptHandle*>(gen->GetArgAddress(0));
+ CScriptHandle *self = reinterpret_cast<CScriptHandle*>(gen->GetObject());
+ new(self) CScriptHandle(*other);
+}
+
+void CScriptHandle_ConstructVar_Generic(asIScriptGeneric *gen)
+{
+ void *ref = gen->GetArgAddress(0);
+ int typeId = gen->GetArgTypeId(0);
+ CScriptHandle *self = reinterpret_cast<CScriptHandle*>(gen->GetObject());
+ Construct(self, ref, typeId);
+}
+
+void CScriptHandle_Destruct_Generic(asIScriptGeneric *gen)
+{
+ CScriptHandle *self = reinterpret_cast<CScriptHandle*>(gen->GetObject());
+ self->~CScriptHandle();
+}
+
+void CScriptHandle_Cast_Generic(asIScriptGeneric *gen)
+{
+ void **ref = reinterpret_cast<void**>(gen->GetArgAddress(0));
+ int typeId = gen->GetArgTypeId(0);
+ CScriptHandle *self = reinterpret_cast<CScriptHandle*>(gen->GetObject());
+ self->Cast(ref, typeId);
+}
+
+void CScriptHandle_Assign_Generic(asIScriptGeneric *gen)
+{
+ CScriptHandle *other = reinterpret_cast<CScriptHandle*>(gen->GetArgAddress(0));
+ CScriptHandle *self = reinterpret_cast<CScriptHandle*>(gen->GetObject());
+ *self = *other;
+ gen->SetReturnAddress(self);
+}
+
+void CScriptHandle_AssignVar_Generic(asIScriptGeneric *gen)
+{
+ void *ref = gen->GetArgAddress(0);
+ int typeId = gen->GetArgTypeId(0);
+ CScriptHandle *self = reinterpret_cast<CScriptHandle*>(gen->GetObject());
+ self->Assign(ref, typeId);
+ gen->SetReturnAddress(self);
+}
+
+void CScriptHandle_Equals_Generic(asIScriptGeneric *gen)
+{
+ CScriptHandle *other = reinterpret_cast<CScriptHandle*>(gen->GetArgAddress(0));
+ CScriptHandle *self = reinterpret_cast<CScriptHandle*>(gen->GetObject());
+ gen->SetReturnByte(*self == *other);
+}
+
+void CScriptHandle_EqualsVar_Generic(asIScriptGeneric *gen)
+{
+ void *ref = gen->GetArgAddress(0);
+ int typeId = gen->GetArgTypeId(0);
+ CScriptHandle *self = reinterpret_cast<CScriptHandle*>(gen->GetObject());
+ gen->SetReturnByte(self->Equals(ref, typeId));
+}
+
+void RegisterScriptHandle_Generic(asIScriptEngine *engine)
+{
+ int r;
+
+ r = engine->RegisterObjectType("ref", sizeof(CScriptHandle), asOBJ_VALUE | asOBJ_ASHANDLE | asOBJ_APP_CLASS_CDAK); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("ref", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(CScriptHandle_Construct_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("ref", asBEHAVE_CONSTRUCT, "void f(const ref &in)", asFUNCTION(CScriptHandle_ConstructCopy_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("ref", asBEHAVE_CONSTRUCT, "void f(const ?&in)", asFUNCTION(CScriptHandle_ConstructVar_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("ref", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(CScriptHandle_Destruct_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("ref", "void opCast(?&out)", asFUNCTION(CScriptHandle_Cast_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("ref", "ref &opHndlAssign(const ref &in)", asFUNCTION(CScriptHandle_Assign_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("ref", "ref &opHndlAssign(const ?&in)", asFUNCTION(CScriptHandle_AssignVar_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("ref", "bool opEquals(const ref &in) const", asFUNCTION(CScriptHandle_Equals_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("ref", "bool opEquals(const ?&in) const", asFUNCTION(CScriptHandle_EqualsVar_Generic), asCALL_GENERIC); assert( r >= 0 );
+}
+
+void RegisterScriptHandle(asIScriptEngine *engine)
+{
+ if( strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") )
+ RegisterScriptHandle_Generic(engine);
+ else
+ RegisterScriptHandle_Native(engine);
+}
+
+
+END_AS_NAMESPACE
diff --git a/Source/Scripting/angelscript/add_on/scripthandle/scripthandle.h b/Source/Scripting/angelscript/add_on/scripthandle/scripthandle.h
new file mode 100644
index 00000000..f98217c4
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/scripthandle/scripthandle.h
@@ -0,0 +1,65 @@
+#ifndef SCRIPTHANDLE_H
+#define SCRIPTHANDLE_H
+
+#ifndef ANGELSCRIPT_H
+// Avoid having to inform include path if header is already include before
+#include <angelscript.h>
+#endif
+
+
+BEGIN_AS_NAMESPACE
+
+class CScriptHandle
+{
+public:
+ // Constructors
+ CScriptHandle();
+ CScriptHandle(const CScriptHandle &other);
+ CScriptHandle(void *ref, asITypeInfo *type);
+ ~CScriptHandle();
+
+ // Copy the stored value from another any object
+ CScriptHandle &operator=(const CScriptHandle &other);
+
+ // Set the reference
+ void Set(void *ref, asITypeInfo *type);
+
+ // Compare equalness
+ bool operator==(const CScriptHandle &o) const;
+ bool operator!=(const CScriptHandle &o) const;
+ bool Equals(void *ref, int typeId) const;
+
+ // Dynamic cast to desired handle type
+ void Cast(void **outRef, int typeId);
+
+ // Returns the type of the reference held
+ asITypeInfo *GetType() const;
+ int GetTypeId() const;
+
+ // Get the reference
+ void *GetRef();
+
+protected:
+ // These functions need to have access to protected
+ // members in order to call them from the script engine
+ friend void Construct(CScriptHandle *self, void *ref, int typeId);
+ friend void RegisterScriptHandle_Native(asIScriptEngine *engine);
+ friend void CScriptHandle_AssignVar_Generic(asIScriptGeneric *gen);
+
+ void ReleaseHandle();
+ void AddRefHandle();
+
+ // These shouldn't be called directly by the
+ // application as they requires an active context
+ CScriptHandle(void *ref, int typeId);
+ CScriptHandle &Assign(void *ref, int typeId);
+
+ void *m_ref;
+ asITypeInfo *m_type;
+};
+
+void RegisterScriptHandle(asIScriptEngine *engine);
+
+END_AS_NAMESPACE
+
+#endif
diff --git a/Source/Scripting/angelscript/add_on/scripthelper/scripthelper.cpp b/Source/Scripting/angelscript/add_on/scripthelper/scripthelper.cpp
new file mode 100644
index 00000000..1880b5ed
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/scripthelper/scripthelper.cpp
@@ -0,0 +1,956 @@
+#include <string.h>
+#include "scripthelper.h"
+#include <assert.h>
+#include <stdio.h>
+#include <fstream>
+#include <set>
+#include <stdlib.h>
+
+using namespace std;
+
+BEGIN_AS_NAMESPACE
+
+int CompareRelation(asIScriptEngine *engine, void *lobj, void *robj, int typeId, int &result)
+{
+ // TODO: If a lot of script objects are going to be compared, e.g. when sorting an array,
+ // then the method id and context should be cached between calls.
+
+ int retval = -1;
+ asIScriptFunction *func = 0;
+
+ asITypeInfo *ti = engine->GetTypeInfoById(typeId);
+ if( ti )
+ {
+ // Check if the object type has a compatible opCmp method
+ for( asUINT n = 0; n < ti->GetMethodCount(); n++ )
+ {
+ asIScriptFunction *f = ti->GetMethodByIndex(n);
+ asDWORD flags;
+ if( strcmp(f->GetName(), "opCmp") == 0 &&
+ f->GetReturnTypeId(&flags) == asTYPEID_INT32 &&
+ flags == asTM_NONE &&
+ f->GetParamCount() == 1 )
+ {
+ int paramTypeId;
+ f->GetParam(0, &paramTypeId, &flags);
+
+ // The parameter must be an input reference of the same type
+ // If the reference is a inout reference, then it must also be read-only
+ if( !(flags & asTM_INREF) || typeId != paramTypeId || ((flags & asTM_OUTREF) && !(flags & asTM_CONST)) )
+ break;
+
+ // Found the method
+ func = f;
+ break;
+ }
+ }
+ }
+
+ if( func )
+ {
+ // Call the method
+ asIScriptContext *ctx = engine->CreateContext();
+ ctx->Prepare(func);
+ ctx->SetObject(lobj);
+ ctx->SetArgAddress(0, robj);
+ int r = ctx->Execute();
+ if( r == asEXECUTION_FINISHED )
+ {
+ result = (int)ctx->GetReturnDWord();
+
+ // The comparison was successful
+ retval = 0;
+ }
+ ctx->Release();
+ }
+
+ return retval;
+}
+
+int CompareEquality(asIScriptEngine *engine, void *lobj, void *robj, int typeId, bool &result)
+{
+ // TODO: If a lot of script objects are going to be compared, e.g. when searching for an
+ // entry in a set, then the method and context should be cached between calls.
+
+ int retval = -1;
+ asIScriptFunction *func = 0;
+
+ asITypeInfo *ti = engine->GetTypeInfoById(typeId);
+ if( ti )
+ {
+ // Check if the object type has a compatible opEquals method
+ for( asUINT n = 0; n < ti->GetMethodCount(); n++ )
+ {
+ asIScriptFunction *f = ti->GetMethodByIndex(n);
+ asDWORD flags;
+ if( strcmp(f->GetName(), "opEquals") == 0 &&
+ f->GetReturnTypeId(&flags) == asTYPEID_BOOL &&
+ flags == asTM_NONE &&
+ f->GetParamCount() == 1 )
+ {
+ int paramTypeId;
+ f->GetParam(0, &paramTypeId, &flags);
+
+ // The parameter must be an input reference of the same type
+ // If the reference is a inout reference, then it must also be read-only
+ if( !(flags & asTM_INREF) || typeId != paramTypeId || ((flags & asTM_OUTREF) && !(flags & asTM_CONST)) )
+ break;
+
+ // Found the method
+ func = f;
+ break;
+ }
+ }
+ }
+
+ if( func )
+ {
+ // Call the method
+ asIScriptContext *ctx = engine->CreateContext();
+ ctx->Prepare(func);
+ ctx->SetObject(lobj);
+ ctx->SetArgAddress(0, robj);
+ int r = ctx->Execute();
+ if( r == asEXECUTION_FINISHED )
+ {
+ result = ctx->GetReturnByte() ? true : false;
+
+ // The comparison was successful
+ retval = 0;
+ }
+ ctx->Release();
+ }
+ else
+ {
+ // If the opEquals method doesn't exist, then we try with opCmp instead
+ int relation;
+ retval = CompareRelation(engine, lobj, robj, typeId, relation);
+ if( retval >= 0 )
+ result = relation == 0 ? true : false;
+ }
+
+ return retval;
+}
+
+int ExecuteString(asIScriptEngine *engine, const char *code, asIScriptModule *mod, asIScriptContext *ctx)
+{
+ return ExecuteString(engine, code, 0, asTYPEID_VOID, mod, ctx);
+}
+
+int ExecuteString(asIScriptEngine *engine, const char *code, void *ref, int refTypeId, asIScriptModule *mod, asIScriptContext *ctx)
+{
+ // Wrap the code in a function so that it can be compiled and executed
+ string funcCode = " ExecuteString() {\n";
+ funcCode += code;
+ funcCode += "\n;}";
+
+ // Determine the return type based on the type of the ref arg
+ funcCode = engine->GetTypeDeclaration(refTypeId, true) + funcCode;
+
+ // GetModule will free unused types, so to be on the safe side we'll hold on to a reference to the type
+ asITypeInfo *type = 0;
+ if( refTypeId & asTYPEID_MASK_OBJECT )
+ {
+ type = engine->GetTypeInfoById(refTypeId);
+ if( type )
+ type->AddRef();
+ }
+
+ // If no module was provided, get a dummy from the engine
+ asIScriptModule *execMod = mod ? mod : engine->GetModule("ExecuteString", asGM_ALWAYS_CREATE);
+
+ // Now it's ok to release the type
+ if( type )
+ type->Release();
+
+ // Compile the function that can be executed
+ asIScriptFunction *func = 0;
+ int r = execMod->CompileFunction("ExecuteString", funcCode.c_str(), -1, 0, &func);
+ if( r < 0 )
+ return r;
+
+ // If no context was provided, request a new one from the engine
+ asIScriptContext *execCtx = ctx ? ctx : engine->RequestContext();
+ r = execCtx->Prepare(func);
+ if (r >= 0)
+ {
+ // Execute the function
+ r = execCtx->Execute();
+
+ // Unless the provided type was void retrieve it's value
+ if (ref != 0 && refTypeId != asTYPEID_VOID)
+ {
+ if (refTypeId & asTYPEID_OBJHANDLE)
+ {
+ // Expect the pointer to be null to start with
+ assert(*reinterpret_cast<void**>(ref) == 0);
+ *reinterpret_cast<void**>(ref) = *reinterpret_cast<void**>(execCtx->GetAddressOfReturnValue());
+ engine->AddRefScriptObject(*reinterpret_cast<void**>(ref), engine->GetTypeInfoById(refTypeId));
+ }
+ else if (refTypeId & asTYPEID_MASK_OBJECT)
+ {
+ // Expect the pointer to point to a valid object
+ assert(*reinterpret_cast<void**>(ref) != 0);
+ engine->AssignScriptObject(ref, execCtx->GetAddressOfReturnValue(), engine->GetTypeInfoById(refTypeId));
+ }
+ else
+ {
+ // Copy the primitive value
+ memcpy(ref, execCtx->GetAddressOfReturnValue(), engine->GetSizeOfPrimitiveType(refTypeId));
+ }
+ }
+ }
+
+ // Clean up
+ func->Release();
+ if( !ctx ) engine->ReturnContext(execCtx);
+
+ return r;
+}
+
+int WriteConfigToFile(asIScriptEngine *engine, const char *filename)
+{
+ ofstream strm;
+ strm.open(filename);
+
+ return WriteConfigToStream(engine, strm);
+}
+
+int WriteConfigToStream(asIScriptEngine *engine, ostream &strm)
+{
+ // A helper function for escaping quotes in default arguments
+ struct Escape
+ {
+ static string Quotes(const char *decl)
+ {
+ string str = decl;
+ size_t pos = 0;
+ for(;;)
+ {
+ // Find " characters
+ pos = str.find("\"",pos);
+ if( pos == string::npos )
+ break;
+
+ // Add a \ to escape them
+ str.insert(pos, "\\");
+ pos += 2;
+ }
+
+ return str;
+ }
+ };
+
+ int c, n;
+
+ asDWORD currAccessMask = 0;
+ string currNamespace = "";
+ engine->SetDefaultNamespace("");
+
+ // Export the engine version, just for info
+ strm << "// AngelScript " << asGetLibraryVersion() << "\n";
+ strm << "// Lib options " << asGetLibraryOptions() << "\n";
+
+ // Export the relevant engine properties
+ strm << "// Engine properties\n";
+ for( n = 0; n < asEP_LAST_PROPERTY; n++ )
+ strm << "ep " << n << " " << engine->GetEngineProperty(asEEngineProp(n)) << "\n";
+
+ // Make sure the default array type is expanded to the template form
+ bool expandDefArrayToTempl = engine->GetEngineProperty(asEP_EXPAND_DEF_ARRAY_TO_TMPL) ? true : false;
+ engine->SetEngineProperty(asEP_EXPAND_DEF_ARRAY_TO_TMPL, true);
+
+ // Write enum types and their values
+ strm << "\n// Enums\n";
+ c = engine->GetEnumCount();
+ for( n = 0; n < c; n++ )
+ {
+ asITypeInfo *ti = engine->GetEnumByIndex(n);
+ asDWORD accessMask = ti->GetAccessMask();
+ if( accessMask != currAccessMask )
+ {
+ strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n";
+ currAccessMask = accessMask;
+ }
+ const char *nameSpace = ti->GetNamespace();
+ if( nameSpace != currNamespace )
+ {
+ strm << "namespace \"" << nameSpace << "\"\n";
+ currNamespace = nameSpace;
+ engine->SetDefaultNamespace(currNamespace.c_str());
+ }
+ const char *enumName = ti->GetName();
+ strm << "enum " << enumName << "\n";
+ for( asUINT m = 0; m < ti->GetEnumValueCount(); m++ )
+ {
+ const char *valName;
+ int val;
+ valName = ti->GetEnumValueByIndex(m, &val);
+ strm << "enumval " << enumName << " " << valName << " " << val << "\n";
+ }
+ }
+
+ // Enumerate all types
+ strm << "\n// Types\n";
+
+ // Keep a list of the template types, as the methods for these need to be exported first
+ set<asITypeInfo*> templateTypes;
+
+ c = engine->GetObjectTypeCount();
+ for( n = 0; n < c; n++ )
+ {
+ asITypeInfo *type = engine->GetObjectTypeByIndex(n);
+ asDWORD accessMask = type->GetAccessMask();
+ if( accessMask != currAccessMask )
+ {
+ strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n";
+ currAccessMask = accessMask;
+ }
+ const char *nameSpace = type->GetNamespace();
+ if( nameSpace != currNamespace )
+ {
+ strm << "namespace \"" << nameSpace << "\"\n";
+ currNamespace = nameSpace;
+ engine->SetDefaultNamespace(currNamespace.c_str());
+ }
+ if( type->GetFlags() & asOBJ_SCRIPT_OBJECT )
+ {
+ // This should only be interfaces
+ assert( type->GetSize() == 0 );
+
+ strm << "intf " << type->GetName() << "\n";
+ }
+ else
+ {
+ // Only the type flags are necessary. The application flags are application
+ // specific and doesn't matter to the offline compiler. The object size is also
+ // unnecessary for the offline compiler
+ strm << "objtype \"" << engine->GetTypeDeclaration(type->GetTypeId()) << "\" " << (unsigned int)(type->GetFlags() & asOBJ_MASK_VALID_FLAGS) << "\n";
+
+ // Store the template types (but not template instances)
+ if( (type->GetFlags() & asOBJ_TEMPLATE) && type->GetSubType() && (type->GetSubType()->GetFlags() & asOBJ_TEMPLATE_SUBTYPE) )
+ templateTypes.insert(type);
+ }
+ }
+
+ c = engine->GetTypedefCount();
+ for( n = 0; n < c; n++ )
+ {
+ asITypeInfo *ti = engine->GetTypedefByIndex(n);
+ const char *nameSpace = ti->GetNamespace();
+ if( nameSpace != currNamespace )
+ {
+ strm << "namespace \"" << nameSpace << "\"\n";
+ currNamespace = nameSpace;
+ engine->SetDefaultNamespace(currNamespace.c_str());
+ }
+ asDWORD accessMask = ti->GetAccessMask();
+ if( accessMask != currAccessMask )
+ {
+ strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n";
+ currAccessMask = accessMask;
+ }
+ strm << "typedef " << ti->GetName() << " \"" << engine->GetTypeDeclaration(ti->GetTypedefTypeId()) << "\"\n";
+ }
+
+ c = engine->GetFuncdefCount();
+ for( n = 0; n < c; n++ )
+ {
+ asITypeInfo *funcDef = engine->GetFuncdefByIndex(n);
+ asDWORD accessMask = funcDef->GetAccessMask();
+ const char *nameSpace = funcDef->GetNamespace();
+ // Child funcdefs do not have any namespace, as they belong to the parent object
+ if( nameSpace && nameSpace != currNamespace )
+ {
+ strm << "namespace \"" << nameSpace << "\"\n";
+ currNamespace = nameSpace;
+ engine->SetDefaultNamespace(currNamespace.c_str());
+ }
+ if( accessMask != currAccessMask )
+ {
+ strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n";
+ currAccessMask = accessMask;
+ }
+ strm << "funcdef \"" << funcDef->GetFuncdefSignature()->GetDeclaration() << "\"\n";
+ }
+
+ // A helper for writing object type members
+ struct TypeWriter
+ {
+ static void Write(asIScriptEngine *engine, ostream &strm, asITypeInfo *type, string &currNamespace, asDWORD &currAccessMask)
+ {
+ const char *nameSpace = type->GetNamespace();
+ if( nameSpace != currNamespace )
+ {
+ strm << "namespace \"" << nameSpace << "\"\n";
+ currNamespace = nameSpace;
+ engine->SetDefaultNamespace(currNamespace.c_str());
+ }
+ string typeDecl = engine->GetTypeDeclaration(type->GetTypeId());
+ if( type->GetFlags() & asOBJ_SCRIPT_OBJECT )
+ {
+ for( asUINT m = 0; m < type->GetMethodCount(); m++ )
+ {
+ asIScriptFunction *func = type->GetMethodByIndex(m);
+ asDWORD accessMask = func->GetAccessMask();
+ if( accessMask != currAccessMask )
+ {
+ strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n";
+ currAccessMask = accessMask;
+ }
+ strm << "intfmthd " << typeDecl.c_str() << " \"" << Escape::Quotes(func->GetDeclaration(false)).c_str() << "\"\n";
+ }
+ }
+ else
+ {
+ asUINT m;
+ for( m = 0; m < type->GetFactoryCount(); m++ )
+ {
+ asIScriptFunction *func = type->GetFactoryByIndex(m);
+ asDWORD accessMask = func->GetAccessMask();
+ if( accessMask != currAccessMask )
+ {
+ strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n";
+ currAccessMask = accessMask;
+ }
+ strm << "objbeh \"" << typeDecl.c_str() << "\" " << asBEHAVE_FACTORY << " \"" << Escape::Quotes(func->GetDeclaration(false)).c_str() << "\"\n";
+ }
+ for( m = 0; m < type->GetBehaviourCount(); m++ )
+ {
+ asEBehaviours beh;
+ asIScriptFunction *func = type->GetBehaviourByIndex(m, &beh);
+
+ if( beh == asBEHAVE_CONSTRUCT )
+ // Prefix 'void'
+ strm << "objbeh \"" << typeDecl.c_str() << "\" " << beh << " \"void " << Escape::Quotes(func->GetDeclaration(false)).c_str() << "\"\n";
+ else if( beh == asBEHAVE_DESTRUCT )
+ // Prefix 'void' and remove ~
+ strm << "objbeh \"" << typeDecl.c_str() << "\" " << beh << " \"void " << Escape::Quotes(func->GetDeclaration(false)).c_str()+1 << "\"\n";
+ else
+ strm << "objbeh \"" << typeDecl.c_str() << "\" " << beh << " \"" << Escape::Quotes(func->GetDeclaration(false)).c_str() << "\"\n";
+ }
+ for( m = 0; m < type->GetMethodCount(); m++ )
+ {
+ asIScriptFunction *func = type->GetMethodByIndex(m);
+ asDWORD accessMask = func->GetAccessMask();
+ if( accessMask != currAccessMask )
+ {
+ strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n";
+ currAccessMask = accessMask;
+ }
+ strm << "objmthd \"" << typeDecl.c_str() << "\" \"" << Escape::Quotes(func->GetDeclaration(false)).c_str() << "\"\n";
+ }
+ for( m = 0; m < type->GetPropertyCount(); m++ )
+ {
+ asDWORD accessMask;
+ type->GetProperty(m, 0, 0, 0, 0, 0, 0, &accessMask);
+ if( accessMask != currAccessMask )
+ {
+ strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n";
+ currAccessMask = accessMask;
+ }
+ strm << "objprop \"" << typeDecl.c_str() << "\" \"" << type->GetPropertyDeclaration(m) << "\"";
+
+ // Save information about composite properties
+ int compositeOffset;
+ bool isCompositeIndirect;
+ type->GetProperty(m, 0, 0, 0, 0, 0, 0, 0, &compositeOffset, &isCompositeIndirect);
+ strm << " " << compositeOffset << " " << (isCompositeIndirect ? "1" : "0") << "\n";
+ }
+ }
+ }
+ };
+
+ // Write the members of the template types, so they can be fully registered before any other type uses them
+ // TODO: Order the template types based on dependency to avoid failure if one type uses instances of another
+ strm << "\n// Template type members\n";
+ for( set<asITypeInfo*>::iterator it = templateTypes.begin(); it != templateTypes.end(); ++it )
+ {
+ asITypeInfo *type = *it;
+ TypeWriter::Write(engine, strm, type, currNamespace, currAccessMask);
+ }
+
+ // Write the object types members
+ strm << "\n// Type members\n";
+
+ c = engine->GetObjectTypeCount();
+ for( n = 0; n < c; n++ )
+ {
+ asITypeInfo *type = engine->GetObjectTypeByIndex(n);
+ if( templateTypes.find(type) == templateTypes.end() )
+ TypeWriter::Write(engine, strm, type, currNamespace, currAccessMask);
+ }
+
+ // Write functions
+ strm << "\n// Functions\n";
+
+ c = engine->GetGlobalFunctionCount();
+ for( n = 0; n < c; n++ )
+ {
+ asIScriptFunction *func = engine->GetGlobalFunctionByIndex(n);
+ const char *nameSpace = func->GetNamespace();
+ if( nameSpace != currNamespace )
+ {
+ strm << "namespace \"" << nameSpace << "\"\n";
+ currNamespace = nameSpace;
+ engine->SetDefaultNamespace(currNamespace.c_str());
+ }
+ asDWORD accessMask = func->GetAccessMask();
+ if( accessMask != currAccessMask )
+ {
+ strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n";
+ currAccessMask = accessMask;
+ }
+ strm << "func \"" << Escape::Quotes(func->GetDeclaration()).c_str() << "\"\n";
+ }
+
+ // Write global properties
+ strm << "\n// Properties\n";
+
+ c = engine->GetGlobalPropertyCount();
+ for( n = 0; n < c; n++ )
+ {
+ const char *name;
+ int typeId;
+ bool isConst;
+ asDWORD accessMask;
+ const char *nameSpace;
+ engine->GetGlobalPropertyByIndex(n, &name, &nameSpace, &typeId, &isConst, 0, 0, &accessMask);
+ if( accessMask != currAccessMask )
+ {
+ strm << "access " << hex << (unsigned int)(accessMask) << dec << "\n";
+ currAccessMask = accessMask;
+ }
+ if( nameSpace != currNamespace )
+ {
+ strm << "namespace \"" << nameSpace << "\"\n";
+ currNamespace = nameSpace;
+ engine->SetDefaultNamespace(currNamespace.c_str());
+ }
+ strm << "prop \"" << (isConst ? "const " : "") << engine->GetTypeDeclaration(typeId) << " " << name << "\"\n";
+ }
+
+ // Write string factory
+ strm << "\n// String factory\n";
+
+ // Reset the namespace for the string factory and default array type
+ if ("" != currNamespace)
+ {
+ strm << "namespace \"\"\n";
+ currNamespace = "";
+ engine->SetDefaultNamespace("");
+ }
+
+ asDWORD flags = 0;
+ int typeId = engine->GetStringFactoryReturnTypeId(&flags);
+ if( typeId > 0 )
+ strm << "strfactory \"" << ((flags & asTM_CONST) ? "const " : "") << engine->GetTypeDeclaration(typeId) << ((flags & asTM_INOUTREF) ? "&" : "") << "\"\n";
+
+ // Write default array type
+ strm << "\n// Default array type\n";
+ typeId = engine->GetDefaultArrayTypeId();
+ if( typeId > 0 )
+ strm << "defarray \"" << engine->GetTypeDeclaration(typeId) << "\"\n";
+
+ // Restore original settings
+ engine->SetEngineProperty(asEP_EXPAND_DEF_ARRAY_TO_TMPL, expandDefArrayToTempl);
+
+ return 0;
+}
+
+int ConfigEngineFromStream(asIScriptEngine *engine, istream &strm, const char *configFile, asIStringFactory *stringFactory)
+{
+ int r;
+
+ // Some helper functions for parsing the configuration
+ struct in
+ {
+ static asETokenClass GetToken(asIScriptEngine *engine, string &token, const string &text, asUINT &pos)
+ {
+ asUINT len = 0;
+ asETokenClass t = engine->ParseToken(&text[pos], text.length() - pos, &len);
+ while( (t == asTC_WHITESPACE || t == asTC_COMMENT) && pos < text.length() )
+ {
+ pos += len;
+ t = engine->ParseToken(&text[pos], text.length() - pos, &len);
+ }
+
+ token.assign(&text[pos], len);
+
+ pos += len;
+
+ return t;
+ }
+
+ static void ReplaceSlashQuote(string &str)
+ {
+ size_t pos = 0;
+ for(;;)
+ {
+ // Search for \" in the string
+ pos = str.find("\\\"", pos);
+ if( pos == string::npos )
+ break;
+
+ // Remove the \ character
+ str.erase(pos, 1);
+ }
+ }
+
+ static asUINT GetLineNumber(const string &text, asUINT pos)
+ {
+ asUINT count = 1;
+ for( asUINT n = 0; n < pos; n++ )
+ if( text[n] == '\n' )
+ count++;
+
+ return count;
+ }
+ };
+
+ // Since we are only going to compile the script and never actually execute it,
+ // we turn off the initialization of global variables, so that the compiler can
+ // just register dummy types and functions for the application interface.
+ r = engine->SetEngineProperty(asEP_INIT_GLOBAL_VARS_AFTER_BUILD, false); assert( r >= 0 );
+
+ // Read the entire file
+ char buffer[1000];
+ string config;
+ do {
+ strm.getline(buffer, 1000);
+ config += buffer;
+ config += "\n";
+ } while( !strm.eof() && strm.good() );
+
+ // Process the configuration file and register each entity
+ asUINT pos = 0;
+ while( pos < config.length() )
+ {
+ string token;
+ // TODO: The position where the initial token is found should be stored for error messages
+ in::GetToken(engine, token, config, pos);
+ if( token == "ep" )
+ {
+ string tmp;
+ in::GetToken(engine, tmp, config, pos);
+
+ asEEngineProp ep = asEEngineProp(atol(tmp.c_str()));
+
+ // Only set properties that affect the compiler
+ if( ep != asEP_COPY_SCRIPT_SECTIONS &&
+ ep != asEP_MAX_STACK_SIZE &&
+ ep != asEP_INIT_GLOBAL_VARS_AFTER_BUILD &&
+ ep != asEP_EXPAND_DEF_ARRAY_TO_TMPL &&
+ ep != asEP_AUTO_GARBAGE_COLLECT )
+ {
+ // Get the value for the property
+ in::GetToken(engine, tmp, config, pos);
+ stringstream s(tmp);
+ asPWORD value;
+
+ s >> value;
+
+ engine->SetEngineProperty(ep, value);
+ }
+ }
+ else if( token == "namespace" )
+ {
+ string ns;
+ in::GetToken(engine, ns, config, pos);
+ ns = ns.substr(1, ns.length() - 2);
+
+ r = engine->SetDefaultNamespace(ns.c_str());
+ if( r < 0 )
+ {
+ engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to set namespace");
+ return -1;
+ }
+ }
+ else if( token == "access" )
+ {
+ string maskStr;
+ in::GetToken(engine, maskStr, config, pos);
+ asDWORD mask = strtoul(maskStr.c_str(), 0, 16);
+ engine->SetDefaultAccessMask(mask);
+ }
+ else if( token == "objtype" )
+ {
+ string name, flags;
+ in::GetToken(engine, name, config, pos);
+ name = name.substr(1, name.length() - 2);
+ in::GetToken(engine, flags, config, pos);
+
+ // The size of the value type doesn't matter, because the
+ // engine must adjust it anyway for different platforms
+ r = engine->RegisterObjectType(name.c_str(), (atol(flags.c_str()) & asOBJ_VALUE) ? 1 : 0, atol(flags.c_str()));
+ if( r < 0 )
+ {
+ engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register object type");
+ return -1;
+ }
+ }
+ else if( token == "objbeh" )
+ {
+ string name, behaviour, decl;
+ in::GetToken(engine, name, config, pos);
+ name = name.substr(1, name.length() - 2);
+ in::GetToken(engine, behaviour, config, pos);
+ in::GetToken(engine, decl, config, pos);
+ decl = decl.substr(1, decl.length() - 2);
+ in::ReplaceSlashQuote(decl);
+
+ // Remove the $ that the engine prefixes the behaviours with
+ size_t n = decl.find("$");
+ if( n != string::npos )
+ decl[n] = ' ';
+
+ asEBehaviours behave = static_cast<asEBehaviours>(atol(behaviour.c_str()));
+ if( behave == asBEHAVE_TEMPLATE_CALLBACK )
+ {
+ // TODO: How can we let the compiler register this? Maybe through a plug-in system? Or maybe by implementing the callback as a script itself
+ engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_WARNING, "Cannot register template callback without the actual implementation");
+ }
+ else
+ {
+ r = engine->RegisterObjectBehaviour(name.c_str(), behave, decl.c_str(), asFUNCTION(0), asCALL_GENERIC);
+ if( r < 0 )
+ {
+ engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register behaviour");
+ return -1;
+ }
+ }
+ }
+ else if( token == "objmthd" )
+ {
+ string name, decl;
+ in::GetToken(engine, name, config, pos);
+ name = name.substr(1, name.length() - 2);
+ in::GetToken(engine, decl, config, pos);
+ decl = decl.substr(1, decl.length() - 2);
+ in::ReplaceSlashQuote(decl);
+
+ r = engine->RegisterObjectMethod(name.c_str(), decl.c_str(), asFUNCTION(0), asCALL_GENERIC);
+ if( r < 0 )
+ {
+ engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register object method");
+ return -1;
+ }
+ }
+ else if( token == "objprop" )
+ {
+ string name, decl, compositeOffset, isCompositeIndirect;
+ in::GetToken(engine, name, config, pos);
+ name = name.substr(1, name.length() - 2);
+ in::GetToken(engine, decl, config, pos);
+ decl = decl.substr(1, decl.length() - 2);
+ in::GetToken(engine, compositeOffset, config, pos);
+ in::GetToken(engine, isCompositeIndirect, config, pos);
+
+ asITypeInfo *type = engine->GetTypeInfoById(engine->GetTypeIdByDecl(name.c_str()));
+ if( type == 0 )
+ {
+ engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Type doesn't exist for property registration");
+ return -1;
+ }
+
+ // All properties must have different offsets in order to make them
+ // distinct, so we simply register them with an incremental offset
+ r = engine->RegisterObjectProperty(name.c_str(), decl.c_str(), type->GetPropertyCount(), compositeOffset != "0" ? type->GetPropertyCount() : 0, isCompositeIndirect != "0");
+ if( r < 0 )
+ {
+ engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register object property");
+ return -1;
+ }
+ }
+ else if( token == "intf" )
+ {
+ string name, size, flags;
+ in::GetToken(engine, name, config, pos);
+
+ r = engine->RegisterInterface(name.c_str());
+ if( r < 0 )
+ {
+ engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register interface");
+ return -1;
+ }
+ }
+ else if( token == "intfmthd" )
+ {
+ string name, decl;
+ in::GetToken(engine, name, config, pos);
+ in::GetToken(engine, decl, config, pos);
+ decl = decl.substr(1, decl.length() - 2);
+ in::ReplaceSlashQuote(decl);
+
+ r = engine->RegisterInterfaceMethod(name.c_str(), decl.c_str());
+ if( r < 0 )
+ {
+ engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register interface method");
+ return -1;
+ }
+ }
+ else if( token == "func" )
+ {
+ string decl;
+ in::GetToken(engine, decl, config, pos);
+ decl = decl.substr(1, decl.length() - 2);
+ in::ReplaceSlashQuote(decl);
+
+ r = engine->RegisterGlobalFunction(decl.c_str(), asFUNCTION(0), asCALL_GENERIC);
+ if( r < 0 )
+ {
+ engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register global function");
+ return -1;
+ }
+ }
+ else if( token == "prop" )
+ {
+ string decl;
+ in::GetToken(engine, decl, config, pos);
+ decl = decl.substr(1, decl.length() - 2);
+
+ // All properties must have different offsets in order to make them
+ // distinct, so we simply register them with an incremental offset.
+ // The pointer must also be non-null so we add 1 to have a value.
+ r = engine->RegisterGlobalProperty(decl.c_str(), reinterpret_cast<void*>(asPWORD(engine->GetGlobalPropertyCount()+1)));
+ if( r < 0 )
+ {
+ engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register global property");
+ return -1;
+ }
+ }
+ else if( token == "strfactory" )
+ {
+ string type;
+ in::GetToken(engine, type, config, pos);
+ type = type.substr(1, type.length() - 2);
+
+ if (stringFactory == 0)
+ {
+ engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_WARNING, "Cannot register string factory without the actual implementation");
+ return -1;
+ }
+ else
+ {
+ r = engine->RegisterStringFactory(type.c_str(), stringFactory);
+ if (r < 0)
+ {
+ engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register string factory");
+ return -1;
+ }
+ }
+ }
+ else if( token == "defarray" )
+ {
+ string type;
+ in::GetToken(engine, type, config, pos);
+ type = type.substr(1, type.length() - 2);
+
+ r = engine->RegisterDefaultArrayType(type.c_str());
+ if( r < 0 )
+ {
+ engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register the default array type");
+ return -1;
+ }
+ }
+ else if( token == "enum" )
+ {
+ string type;
+ in::GetToken(engine, type, config, pos);
+
+ r = engine->RegisterEnum(type.c_str());
+ if( r < 0 )
+ {
+ engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register enum type");
+ return -1;
+ }
+ }
+ else if( token == "enumval" )
+ {
+ string type, name, value;
+ in::GetToken(engine, type, config, pos);
+ in::GetToken(engine, name, config, pos);
+ in::GetToken(engine, value, config, pos);
+
+ r = engine->RegisterEnumValue(type.c_str(), name.c_str(), atol(value.c_str()));
+ if( r < 0 )
+ {
+ engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register enum value");
+ return -1;
+ }
+ }
+ else if( token == "typedef" )
+ {
+ string type, decl;
+ in::GetToken(engine, type, config, pos);
+ in::GetToken(engine, decl, config, pos);
+ decl = decl.substr(1, decl.length() - 2);
+
+ r = engine->RegisterTypedef(type.c_str(), decl.c_str());
+ if( r < 0 )
+ {
+ engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register typedef");
+ return -1;
+ }
+ }
+ else if( token == "funcdef" )
+ {
+ string decl;
+ in::GetToken(engine, decl, config, pos);
+ decl = decl.substr(1, decl.length() - 2);
+
+ r = engine->RegisterFuncdef(decl.c_str());
+ if( r < 0 )
+ {
+ engine->WriteMessage(configFile, in::GetLineNumber(config, pos), 0, asMSGTYPE_ERROR, "Failed to register funcdef");
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+string GetExceptionInfo(asIScriptContext *ctx, bool showStack)
+{
+ if( ctx->GetState() != asEXECUTION_EXCEPTION ) return "";
+
+ stringstream text;
+
+ const asIScriptFunction *function = ctx->GetExceptionFunction();
+ text << "func: " << function->GetDeclaration() << "\n";
+ text << "modl: " << (function->GetModuleName() ? function->GetModuleName() : "") << "\n";
+ text << "sect: " << (function->GetScriptSectionName() ? function->GetScriptSectionName() : "") << "\n";
+ text << "line: " << ctx->GetExceptionLineNumber() << "\n";
+ text << "desc: " << ctx->GetExceptionString() << "\n";
+
+ if( showStack )
+ {
+ text << "--- call stack ---\n";
+ for( asUINT n = 1; n < ctx->GetCallstackSize(); n++ )
+ {
+ function = ctx->GetFunction(n);
+ if( function )
+ {
+ if( function->GetFuncType() == asFUNC_SCRIPT )
+ {
+ text << (function->GetScriptSectionName() ? function->GetScriptSectionName() : "") << " (" << ctx->GetLineNumber(n) << "): " << function->GetDeclaration() << "\n";
+ }
+ else
+ {
+ // The context is being reused by the application for a nested call
+ text << "{...application...}: " << function->GetDeclaration() << "\n";
+ }
+ }
+ else
+ {
+ // The context is being reused by the script engine for a nested call
+ text << "{...script engine...}\n";
+ }
+ }
+ }
+
+ return text.str();
+}
+
+END_AS_NAMESPACE
diff --git a/Source/Scripting/angelscript/add_on/scripthelper/scripthelper.h b/Source/Scripting/angelscript/add_on/scripthelper/scripthelper.h
new file mode 100644
index 00000000..5e42e5f8
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/scripthelper/scripthelper.h
@@ -0,0 +1,48 @@
+#ifndef SCRIPTHELPER_H
+#define SCRIPTHELPER_H
+
+#include <sstream>
+#include <string>
+
+#ifndef ANGELSCRIPT_H
+// Avoid having to inform include path if header is already include before
+#include <angelscript.h>
+#endif
+
+
+BEGIN_AS_NAMESPACE
+
+// Compare relation between two objects of the same type
+int CompareRelation(asIScriptEngine *engine, void *lobj, void *robj, int typeId, int &result);
+
+// Compare equality between two objects of the same type
+int CompareEquality(asIScriptEngine *engine, void *lobj, void *robj, int typeId, bool &result);
+
+// Compile and execute simple statements
+// The module is optional. If given the statements can access the entities compiled in the module.
+// The caller can optionally provide its own context, for example if a context should be reused.
+int ExecuteString(asIScriptEngine *engine, const char *code, asIScriptModule *mod = 0, asIScriptContext *ctx = 0);
+
+// Compile and execute simple statements with option of return value
+// The module is optional. If given the statements can access the entitites compiled in the module.
+// The caller can optionally provide its own context, for example if a context should be reused.
+int ExecuteString(asIScriptEngine *engine, const char *code, void *ret, int retTypeId, asIScriptModule *mod = 0, asIScriptContext *ctx = 0);
+
+// Write the registered application interface to a file for an offline compiler.
+// The format is compatible with the offline compiler in /sdk/samples/asbuild/.
+int WriteConfigToFile(asIScriptEngine *engine, const char *filename);
+
+// Write the registered application interface to a text stream.
+int WriteConfigToStream(asIScriptEngine *engine, std::ostream &strm);
+
+// Loads an interface from a text stream and configures the engine with it. This will not
+// set the correct function pointers, so it is not possible to use this engine to execute
+// scripts, but it can be used to compile scripts and save the byte code.
+int ConfigEngineFromStream(asIScriptEngine *engine, std::istream &strm, const char *nameOfStream = "config", asIStringFactory *stringFactory = 0);
+
+// Format the details of the script exception into a human readable text
+std::string GetExceptionInfo(asIScriptContext *ctx, bool showStack = false);
+
+END_AS_NAMESPACE
+
+#endif
diff --git a/Source/Scripting/angelscript/add_on/scriptmath/scriptmath.cpp b/Source/Scripting/angelscript/add_on/scriptmath/scriptmath.cpp
new file mode 100644
index 00000000..c5b86c38
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/scriptmath/scriptmath.cpp
@@ -0,0 +1,347 @@
+#include <assert.h>
+#include <math.h>
+#include <float.h>
+#include <string.h>
+#include "scriptmath.h"
+
+#ifdef __BORLANDC__
+#include <cmath>
+
+// The C++Builder RTL doesn't pull the *f functions into the global namespace per default.
+using namespace std;
+
+#if __BORLANDC__ < 0x580
+// C++Builder 6 and earlier don't come with any *f variants of the math functions at all.
+inline float cosf (float arg) { return std::cos (arg); }
+inline float sinf (float arg) { return std::sin (arg); }
+inline float tanf (float arg) { return std::tan (arg); }
+inline float atan2f (float y, float x) { return std::atan2 (y, x); }
+inline float logf (float arg) { return std::log (arg); }
+inline float powf (float x, float y) { return std::pow (x, y); }
+inline float sqrtf (float arg) { return std::sqrt (arg); }
+#endif
+
+// C++Builder doesn't define most of the non-standard float-specific math functions with
+// "*f" suffix; instead it provides overloads for the standard math functions which take
+// "float" arguments.
+inline float acosf (float arg) { return std::acos (arg); }
+inline float asinf (float arg) { return std::asin (arg); }
+inline float atanf (float arg) { return std::atan (arg); }
+inline float coshf (float arg) { return std::cosh (arg); }
+inline float sinhf (float arg) { return std::sinh (arg); }
+inline float tanhf (float arg) { return std::tanh (arg); }
+inline float log10f (float arg) { return std::log10 (arg); }
+inline float ceilf (float arg) { return std::ceil (arg); }
+inline float fabsf (float arg) { return std::fabs (arg); }
+inline float floorf (float arg) { return std::floor (arg); }
+
+// C++Builder doesn't define a non-standard "modff" function but rather an overload of "modf"
+// for float arguments. However, BCC's float overload of fmod() is broken (QC #74816; fixed
+// in C++Builder 2010).
+inline float modff (float x, float *y)
+{
+ double d;
+ float f = (float) modf((double) x, &d);
+ *y = (float) d;
+ return f;
+}
+#endif
+
+BEGIN_AS_NAMESPACE
+
+// Determine whether the float version should be registered, or the double version
+#ifndef AS_USE_FLOAT
+#if !defined(_WIN32_WCE) // WinCE doesn't have the float versions of the math functions
+#define AS_USE_FLOAT 1
+#endif
+#endif
+
+// The modf function doesn't seem very intuitive, so I'm writing this
+// function that simply returns the fractional part of the float value
+#if AS_USE_FLOAT
+float fractionf(float v)
+{
+ float intPart;
+ return modff(v, &intPart);
+}
+#else
+double fraction(double v)
+{
+ double intPart;
+ return modf(v, &intPart);
+}
+#endif
+
+// As AngelScript doesn't allow bitwise manipulation of float types we'll provide a couple of
+// functions for converting float values to IEEE 754 formatted values etc. This also allow us to
+// provide a platform agnostic representation to the script so the scripts don't have to worry
+// about whether the CPU uses IEEE 754 floats or some other representation
+float fpFromIEEE(asUINT raw)
+{
+ // TODO: Identify CPU family to provide proper conversion
+ // if the CPU doesn't natively use IEEE style floats
+ return *reinterpret_cast<float*>(&raw);
+}
+asUINT fpToIEEE(float fp)
+{
+ return *reinterpret_cast<asUINT*>(&fp);
+}
+double fpFromIEEE(asQWORD raw)
+{
+ return *reinterpret_cast<double*>(&raw);
+}
+asQWORD fpToIEEE(double fp)
+{
+ return *reinterpret_cast<asQWORD*>(&fp);
+}
+
+// closeTo() is used to determine if the binary representation of two numbers are
+// relatively close to each other. Numerical errors due to rounding errors build
+// up over many operations, so it is almost impossible to get exact numbers and
+// this is where closeTo() comes in.
+//
+// It shouldn't be used to determine if two numbers are mathematically close to
+// each other.
+//
+// ref: http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
+// ref: http://www.gamedev.net/topic/653449-scriptmath-and-closeto/
+bool closeTo(float a, float b, float epsilon)
+{
+ // Equal numbers and infinity will return immediately
+ if( a == b ) return true;
+
+ // When very close to 0, we can use the absolute comparison
+ float diff = fabsf(a - b);
+ if( (a == 0 || b == 0) && (diff < epsilon) )
+ return true;
+
+ // Otherwise we need to use relative comparison to account for precision
+ return diff / (fabs(a) + fabs(b)) < epsilon;
+}
+
+bool closeTo(double a, double b, double epsilon)
+{
+ if( a == b ) return true;
+
+ double diff = fabs(a - b);
+ if( (a == 0 || b == 0) && (diff < epsilon) )
+ return true;
+
+ return diff / (fabs(a) + fabs(b)) < epsilon;
+}
+
+void RegisterScriptMath_Native(asIScriptEngine *engine)
+{
+ int r;
+
+ // Conversion between floating point and IEEE bits representations
+ r = engine->RegisterGlobalFunction("float fpFromIEEE(uint)", asFUNCTIONPR(fpFromIEEE, (asUINT), float), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("uint fpToIEEE(float)", asFUNCTIONPR(fpToIEEE, (float), asUINT), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double fpFromIEEE(uint64)", asFUNCTIONPR(fpFromIEEE, (asQWORD), double), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("uint64 fpToIEEE(double)", asFUNCTIONPR(fpToIEEE, (double), asQWORD), asCALL_CDECL); assert( r >= 0 );
+
+ // Close to comparison with epsilon
+ r = engine->RegisterGlobalFunction("bool closeTo(float, float, float = 0.00001f)", asFUNCTIONPR(closeTo, (float, float, float), bool), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("bool closeTo(double, double, double = 0.0000000001)", asFUNCTIONPR(closeTo, (double, double, double), bool), asCALL_CDECL); assert( r >= 0 );
+
+#if AS_USE_FLOAT
+ // Trigonometric functions
+ r = engine->RegisterGlobalFunction("float cos(float)", asFUNCTIONPR(cosf, (float), float), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("float sin(float)", asFUNCTIONPR(sinf, (float), float), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("float tan(float)", asFUNCTIONPR(tanf, (float), float), asCALL_CDECL); assert( r >= 0 );
+
+ r = engine->RegisterGlobalFunction("float acos(float)", asFUNCTIONPR(acosf, (float), float), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("float asin(float)", asFUNCTIONPR(asinf, (float), float), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("float atan(float)", asFUNCTIONPR(atanf, (float), float), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("float atan2(float,float)", asFUNCTIONPR(atan2f, (float, float), float), asCALL_CDECL); assert( r >= 0 );
+
+ // Hyberbolic functions
+ r = engine->RegisterGlobalFunction("float cosh(float)", asFUNCTIONPR(coshf, (float), float), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("float sinh(float)", asFUNCTIONPR(sinhf, (float), float), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("float tanh(float)", asFUNCTIONPR(tanhf, (float), float), asCALL_CDECL); assert( r >= 0 );
+
+ // Exponential and logarithmic functions
+ r = engine->RegisterGlobalFunction("float log(float)", asFUNCTIONPR(logf, (float), float), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("float log10(float)", asFUNCTIONPR(log10f, (float), float), asCALL_CDECL); assert( r >= 0 );
+
+ // Power functions
+ r = engine->RegisterGlobalFunction("float pow(float, float)", asFUNCTIONPR(powf, (float, float), float), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("float sqrt(float)", asFUNCTIONPR(sqrtf, (float), float), asCALL_CDECL); assert( r >= 0 );
+
+ // Nearest integer, absolute value, and remainder functions
+ r = engine->RegisterGlobalFunction("float ceil(float)", asFUNCTIONPR(ceilf, (float), float), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("float abs(float)", asFUNCTIONPR(fabsf, (float), float), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("float floor(float)", asFUNCTIONPR(floorf, (float), float), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("float fraction(float)", asFUNCTIONPR(fractionf, (float), float), asCALL_CDECL); assert( r >= 0 );
+
+ // Don't register modf because AngelScript already supports the % operator
+#else
+ // double versions of the same
+ r = engine->RegisterGlobalFunction("double cos(double)", asFUNCTIONPR(cos, (double), double), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double sin(double)", asFUNCTIONPR(sin, (double), double), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double tan(double)", asFUNCTIONPR(tan, (double), double), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double acos(double)", asFUNCTIONPR(acos, (double), double), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double asin(double)", asFUNCTIONPR(asin, (double), double), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double atan(double)", asFUNCTIONPR(atan, (double), double), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double atan2(double,double)", asFUNCTIONPR(atan2, (double, double), double), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double cosh(double)", asFUNCTIONPR(cosh, (double), double), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double sinh(double)", asFUNCTIONPR(sinh, (double), double), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double tanh(double)", asFUNCTIONPR(tanh, (double), double), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double log(double)", asFUNCTIONPR(log, (double), double), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double log10(double)", asFUNCTIONPR(log10, (double), double), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double pow(double, double)", asFUNCTIONPR(pow, (double, double), double), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double sqrt(double)", asFUNCTIONPR(sqrt, (double), double), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double ceil(double)", asFUNCTIONPR(ceil, (double), double), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double abs(double)", asFUNCTIONPR(fabs, (double), double), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double floor(double)", asFUNCTIONPR(floor, (double), double), asCALL_CDECL); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double fraction(double)", asFUNCTIONPR(fraction, (double), double), asCALL_CDECL); assert( r >= 0 );
+#endif
+}
+
+#if AS_USE_FLOAT
+// This macro creates simple generic wrappers for functions of type 'float func(float)'
+#define GENERICff(x) \
+void x##_generic(asIScriptGeneric *gen) \
+{ \
+ float f = *(float*)gen->GetAddressOfArg(0); \
+ *(float*)gen->GetAddressOfReturnLocation() = x(f); \
+}
+
+GENERICff(cosf)
+GENERICff(sinf)
+GENERICff(tanf)
+GENERICff(acosf)
+GENERICff(asinf)
+GENERICff(atanf)
+GENERICff(coshf)
+GENERICff(sinhf)
+GENERICff(tanhf)
+GENERICff(logf)
+GENERICff(log10f)
+GENERICff(sqrtf)
+GENERICff(ceilf)
+GENERICff(fabsf)
+GENERICff(floorf)
+GENERICff(fractionf)
+
+void powf_generic(asIScriptGeneric *gen)
+{
+ float f1 = *(float*)gen->GetAddressOfArg(0);
+ float f2 = *(float*)gen->GetAddressOfArg(1);
+ *(float*)gen->GetAddressOfReturnLocation() = powf(f1, f2);
+}
+void atan2f_generic(asIScriptGeneric *gen)
+{
+ float f1 = *(float*)gen->GetAddressOfArg(0);
+ float f2 = *(float*)gen->GetAddressOfArg(1);
+ *(float*)gen->GetAddressOfReturnLocation() = atan2f(f1, f2);
+}
+
+#else
+// This macro creates simple generic wrappers for functions of type 'double func(double)'
+#define GENERICdd(x) \
+void x##_generic(asIScriptGeneric *gen) \
+{ \
+ double f = *(double*)gen->GetAddressOfArg(0); \
+ *(double*)gen->GetAddressOfReturnLocation() = x(f); \
+}
+
+GENERICdd(cos)
+GENERICdd(sin)
+GENERICdd(tan)
+GENERICdd(acos)
+GENERICdd(asin)
+GENERICdd(atan)
+GENERICdd(cosh)
+GENERICdd(sinh)
+GENERICdd(tanh)
+GENERICdd(log)
+GENERICdd(log10)
+GENERICdd(sqrt)
+GENERICdd(ceil)
+GENERICdd(fabs)
+GENERICdd(floor)
+GENERICdd(fraction)
+
+void pow_generic(asIScriptGeneric *gen)
+{
+ double f1 = *(double*)gen->GetAddressOfArg(0);
+ double f2 = *(double*)gen->GetAddressOfArg(1);
+ *(double*)gen->GetAddressOfReturnLocation() = pow(f1, f2);
+}
+void atan2_generic(asIScriptGeneric *gen)
+{
+ double f1 = *(double*)gen->GetAddressOfArg(0);
+ double f2 = *(double*)gen->GetAddressOfArg(1);
+ *(double*)gen->GetAddressOfReturnLocation() = atan2(f1, f2);
+}
+#endif
+void RegisterScriptMath_Generic(asIScriptEngine *engine)
+{
+ int r;
+
+#if AS_USE_FLOAT
+ // Trigonometric functions
+ r = engine->RegisterGlobalFunction("float cos(float)", asFUNCTION(cosf_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("float sin(float)", asFUNCTION(sinf_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("float tan(float)", asFUNCTION(tanf_generic), asCALL_GENERIC); assert( r >= 0 );
+
+ r = engine->RegisterGlobalFunction("float acos(float)", asFUNCTION(acosf_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("float asin(float)", asFUNCTION(asinf_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("float atan(float)", asFUNCTION(atanf_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("float atan2(float,float)", asFUNCTION(atan2f_generic), asCALL_GENERIC); assert( r >= 0 );
+
+ // Hyberbolic functions
+ r = engine->RegisterGlobalFunction("float cosh(float)", asFUNCTION(coshf_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("float sinh(float)", asFUNCTION(sinhf_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("float tanh(float)", asFUNCTION(tanhf_generic), asCALL_GENERIC); assert( r >= 0 );
+
+ // Exponential and logarithmic functions
+ r = engine->RegisterGlobalFunction("float log(float)", asFUNCTION(logf_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("float log10(float)", asFUNCTION(log10f_generic), asCALL_GENERIC); assert( r >= 0 );
+
+ // Power functions
+ r = engine->RegisterGlobalFunction("float pow(float, float)", asFUNCTION(powf_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("float sqrt(float)", asFUNCTION(sqrtf_generic), asCALL_GENERIC); assert( r >= 0 );
+
+ // Nearest integer, absolute value, and remainder functions
+ r = engine->RegisterGlobalFunction("float ceil(float)", asFUNCTION(ceilf_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("float abs(float)", asFUNCTION(fabsf_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("float floor(float)", asFUNCTION(floorf_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("float fraction(float)", asFUNCTION(fractionf_generic), asCALL_GENERIC); assert( r >= 0 );
+
+ // Don't register modf because AngelScript already supports the % operator
+#else
+ // double versions of the same
+ r = engine->RegisterGlobalFunction("double cos(double)", asFUNCTION(cos_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double sin(double)", asFUNCTION(sin_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double tan(double)", asFUNCTION(tan_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double acos(double)", asFUNCTION(acos_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double asin(double)", asFUNCTION(asin_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double atan(double)", asFUNCTION(atan_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double atan2(double,double)", asFUNCTION(atan2_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double cosh(double)", asFUNCTION(cosh_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double sinh(double)", asFUNCTION(sinh_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double tanh(double)", asFUNCTION(tanh_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double log(double)", asFUNCTION(log_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double log10(double)", asFUNCTION(log10_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double pow(double, double)", asFUNCTION(pow_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double sqrt(double)", asFUNCTION(sqrt_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double ceil(double)", asFUNCTION(ceil_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double abs(double)", asFUNCTION(fabs_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double floor(double)", asFUNCTION(floor_generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterGlobalFunction("double fraction(double)", asFUNCTION(fraction_generic), asCALL_GENERIC); assert( r >= 0 );
+#endif
+}
+
+void RegisterScriptMath(asIScriptEngine *engine)
+{
+ if( strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") )
+ RegisterScriptMath_Generic(engine);
+ else
+ RegisterScriptMath_Native(engine);
+}
+
+END_AS_NAMESPACE
+
+
diff --git a/Source/Scripting/angelscript/add_on/scriptmath/scriptmath.h b/Source/Scripting/angelscript/add_on/scriptmath/scriptmath.h
new file mode 100644
index 00000000..a239e38d
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/scriptmath/scriptmath.h
@@ -0,0 +1,26 @@
+#ifndef SCRIPTMATH_H
+#define SCRIPTMATH_H
+
+#ifndef ANGELSCRIPT_H
+// Avoid having to inform include path if header is already include before
+#include <angelscript.h>
+#endif
+
+
+BEGIN_AS_NAMESPACE
+
+// This function will determine the configuration of the engine
+// and use one of the two functions below to register the math functions
+void RegisterScriptMath(asIScriptEngine *engine);
+
+// Call this function to register the math functions
+// using native calling conventions
+void RegisterScriptMath_Native(asIScriptEngine *engine);
+
+// Use this one instead if native calling conventions
+// are not supported on the target platform
+void RegisterScriptMath_Generic(asIScriptEngine *engine);
+
+END_AS_NAMESPACE
+
+#endif
diff --git a/Source/Scripting/angelscript/add_on/scriptmath/scriptmathcomplex.cpp b/Source/Scripting/angelscript/add_on/scriptmath/scriptmathcomplex.cpp
new file mode 100644
index 00000000..4f10232b
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/scriptmath/scriptmathcomplex.cpp
@@ -0,0 +1,222 @@
+#include <assert.h>
+#include <string.h> // strstr
+#include <new> // new()
+#include <math.h>
+#include "scriptmathcomplex.h"
+
+#ifdef __BORLANDC__
+// C++Builder doesn't define a non-standard "sqrtf" function but rather an overload of "sqrt"
+// for float arguments.
+inline float sqrtf (float x) { return sqrt (x); }
+#endif
+
+BEGIN_AS_NAMESPACE
+
+Complex::Complex()
+{
+ r = 0;
+ i = 0;
+}
+
+Complex::Complex(const Complex &other)
+{
+ r = other.r;
+ i = other.i;
+}
+
+Complex::Complex(float _r, float _i)
+{
+ r = _r;
+ i = _i;
+}
+
+bool Complex::operator==(const Complex &o) const
+{
+ return (r == o.r) && (i == o.i);
+}
+
+bool Complex::operator!=(const Complex &o) const
+{
+ return !(*this == o);
+}
+
+Complex &Complex::operator=(const Complex &other)
+{
+ r = other.r;
+ i = other.i;
+ return *this;
+}
+
+Complex &Complex::operator+=(const Complex &other)
+{
+ r += other.r;
+ i += other.i;
+ return *this;
+}
+
+Complex &Complex::operator-=(const Complex &other)
+{
+ r -= other.r;
+ i -= other.i;
+ return *this;
+}
+
+Complex &Complex::operator*=(const Complex &other)
+{
+ *this = *this * other;
+ return *this;
+}
+
+Complex &Complex::operator/=(const Complex &other)
+{
+ *this = *this / other;
+ return *this;
+}
+
+float Complex::squaredLength() const
+{
+ return r*r + i*i;
+}
+
+float Complex::length() const
+{
+ return sqrtf(squaredLength());
+}
+
+Complex Complex::operator+(const Complex &other) const
+{
+ return Complex(r + other.r, i + other.i);
+}
+
+Complex Complex::operator-(const Complex &other) const
+{
+ return Complex(r - other.r, i + other.i);
+}
+
+Complex Complex::operator*(const Complex &other) const
+{
+ return Complex(r*other.r - i*other.i, r*other.i + i*other.r);
+}
+
+Complex Complex::operator/(const Complex &other) const
+{
+ float squaredLen = other.squaredLength();
+ if( squaredLen == 0 ) return Complex(0,0);
+
+ return Complex((r*other.r + i*other.i)/squaredLen, (i*other.r - r*other.i)/squaredLen);
+}
+
+//-----------------------
+// Swizzle operators
+//-----------------------
+
+Complex Complex::get_ri() const
+{
+ return *this;
+}
+Complex Complex::get_ir() const
+{
+ return Complex(r,i);
+}
+void Complex::set_ri(const Complex &o)
+{
+ *this = o;
+}
+void Complex::set_ir(const Complex &o)
+{
+ r = o.i;
+ i = o.r;
+}
+
+//-----------------------
+// AngelScript functions
+//-----------------------
+
+static void ComplexDefaultConstructor(Complex *self)
+{
+ new(self) Complex();
+}
+
+static void ComplexCopyConstructor(const Complex &other, Complex *self)
+{
+ new(self) Complex(other);
+}
+
+static void ComplexConvConstructor(float r, Complex *self)
+{
+ new(self) Complex(r);
+}
+
+static void ComplexInitConstructor(float r, float i, Complex *self)
+{
+ new(self) Complex(r,i);
+}
+
+static void ComplexListConstructor(float *list, Complex *self)
+{
+ new(self) Complex(list[0], list[1]);
+}
+
+//--------------------------------
+// Registration
+//-------------------------------------
+
+static void RegisterScriptMathComplex_Native(asIScriptEngine *engine)
+{
+ int r;
+
+ // Register the type
+#if AS_CAN_USE_CPP11
+ // With C++11 it is possible to use asGetTypeTraits to determine the correct flags to represent the C++ class, except for the asOBJ_APP_CLASS_ALLFLOATS
+ r = engine->RegisterObjectType("complex", sizeof(Complex), asOBJ_VALUE | asOBJ_POD | asGetTypeTraits<Complex>() | asOBJ_APP_CLASS_ALLFLOATS); assert( r >= 0 );
+#else
+ r = engine->RegisterObjectType("complex", sizeof(Complex), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS_CAK | asOBJ_APP_CLASS_ALLFLOATS); assert( r >= 0 );
+#endif
+
+ // Register the object properties
+ r = engine->RegisterObjectProperty("complex", "float r", asOFFSET(Complex, r)); assert( r >= 0 );
+ r = engine->RegisterObjectProperty("complex", "float i", asOFFSET(Complex, i)); assert( r >= 0 );
+
+ // Register the constructors
+ r = engine->RegisterObjectBehaviour("complex", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ComplexDefaultConstructor), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("complex", asBEHAVE_CONSTRUCT, "void f(const complex &in)", asFUNCTION(ComplexCopyConstructor), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("complex", asBEHAVE_CONSTRUCT, "void f(float)", asFUNCTION(ComplexConvConstructor), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("complex", asBEHAVE_CONSTRUCT, "void f(float, float)", asFUNCTION(ComplexInitConstructor), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("complex", asBEHAVE_LIST_CONSTRUCT, "void f(const int &in) {float, float}", asFUNCTION(ComplexListConstructor), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+
+ // Register the operator overloads
+ r = engine->RegisterObjectMethod("complex", "complex &opAddAssign(const complex &in)", asMETHODPR(Complex, operator+=, (const Complex &), Complex&), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("complex", "complex &opSubAssign(const complex &in)", asMETHODPR(Complex, operator-=, (const Complex &), Complex&), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("complex", "complex &opMulAssign(const complex &in)", asMETHODPR(Complex, operator*=, (const Complex &), Complex&), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("complex", "complex &opDivAssign(const complex &in)", asMETHODPR(Complex, operator/=, (const Complex &), Complex&), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("complex", "bool opEquals(const complex &in) const", asMETHODPR(Complex, operator==, (const Complex &) const, bool), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("complex", "complex opAdd(const complex &in) const", asMETHODPR(Complex, operator+, (const Complex &) const, Complex), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("complex", "complex opSub(const complex &in) const", asMETHODPR(Complex, operator-, (const Complex &) const, Complex), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("complex", "complex opMul(const complex &in) const", asMETHODPR(Complex, operator*, (const Complex &) const, Complex), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("complex", "complex opDiv(const complex &in) const", asMETHODPR(Complex, operator/, (const Complex &) const, Complex), asCALL_THISCALL); assert( r >= 0 );
+
+ // Register the object methods
+ r = engine->RegisterObjectMethod("complex", "float abs() const", asMETHOD(Complex,length), asCALL_THISCALL); assert( r >= 0 );
+
+ // Register the swizzle operators
+ r = engine->RegisterObjectMethod("complex", "complex get_ri() const", asMETHOD(Complex, get_ri), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("complex", "complex get_ir() const", asMETHOD(Complex, get_ir), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("complex", "void set_ri(const complex &in)", asMETHOD(Complex, set_ri), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("complex", "void set_ir(const complex &in)", asMETHOD(Complex, set_ir), asCALL_THISCALL); assert( r >= 0 );
+}
+
+void RegisterScriptMathComplex(asIScriptEngine *engine)
+{
+ if( strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") )
+ {
+ assert( false );
+ // TODO: implement support for generic calling convention
+ // RegisterScriptMathComplex_Generic(engine);
+ }
+ else
+ RegisterScriptMathComplex_Native(engine);
+}
+
+END_AS_NAMESPACE
+
+
diff --git a/Source/Scripting/angelscript/add_on/scriptmath/scriptmathcomplex.h b/Source/Scripting/angelscript/add_on/scriptmath/scriptmathcomplex.h
new file mode 100644
index 00000000..8d33915d
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/scriptmath/scriptmathcomplex.h
@@ -0,0 +1,61 @@
+#ifndef SCRIPTMATHCOMPLEX_H
+#define SCRIPTMATHCOMPLEX_H
+
+#ifndef ANGELSCRIPT_H
+// Avoid having to inform include path if header is already include before
+#include <angelscript.h>
+#endif
+
+
+BEGIN_AS_NAMESPACE
+
+// This class implements complex numbers and the common
+// operations that can be done with it.
+//
+// Ref: http://mathworld.wolfram.com/ComplexNumber.html
+
+struct Complex
+{
+ Complex();
+ Complex(const Complex &other);
+ Complex(float r, float i = 0);
+
+ // Assignment operator
+ Complex &operator=(const Complex &other);
+
+ // Compound assigment operators
+ Complex &operator+=(const Complex &other);
+ Complex &operator-=(const Complex &other);
+ Complex &operator*=(const Complex &other);
+ Complex &operator/=(const Complex &other);
+
+ float length() const;
+ float squaredLength() const;
+
+ // Swizzle operators
+ Complex get_ri() const;
+ void set_ri(const Complex &in);
+ Complex get_ir() const;
+ void set_ir(const Complex &in);
+
+ // Comparison
+ bool operator==(const Complex &other) const;
+ bool operator!=(const Complex &other) const;
+
+ // Math operators
+ Complex operator+(const Complex &other) const;
+ Complex operator-(const Complex &other) const;
+ Complex operator*(const Complex &other) const;
+ Complex operator/(const Complex &other) const;
+
+ float r;
+ float i;
+};
+
+// This function will determine the configuration of the engine
+// and use one of the two functions below to register the string type
+void RegisterScriptMathComplex(asIScriptEngine *engine);
+
+END_AS_NAMESPACE
+
+#endif
diff --git a/Source/Scripting/angelscript/add_on/scriptstdstring/scriptstdstring.cpp b/Source/Scripting/angelscript/add_on/scriptstdstring/scriptstdstring.cpp
new file mode 100644
index 00000000..86cfef55
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/scriptstdstring/scriptstdstring.cpp
@@ -0,0 +1,1299 @@
+#include "scriptstdstring.h"
+#include <assert.h> // assert()
+#include <sstream> // std::stringstream
+#include <string.h> // strstr()
+#include <stdio.h> // sprintf()
+#include <stdlib.h> // strtod()
+#ifndef __psp2__
+ #include <locale.h> // setlocale()
+#endif
+
+using namespace std;
+
+// This macro is used to avoid warnings about unused variables.
+// Usually where the variables are only used in debug mode.
+#define UNUSED_VAR(x) (void)(x)
+
+#ifdef AS_CAN_USE_CPP11
+// The string factory doesn't need to keep a specific order in the
+// cache, so the unordered_map is faster than the ordinary map
+#include <unordered_map> // std::unordered_map
+BEGIN_AS_NAMESPACE
+typedef unordered_map<string, int> map_t;
+END_AS_NAMESPACE
+#else
+#include <map> // std::map
+BEGIN_AS_NAMESPACE
+typedef map<string, int> map_t;
+END_AS_NAMESPACE
+#endif
+
+class CStdStringFactory : public asIStringFactory
+{
+public:
+ CStdStringFactory() {}
+ ~CStdStringFactory()
+ {
+ // The script engine must release each string
+ // constant that it has requested
+ assert(stringCache.size() == 0);
+ }
+
+ const void *GetStringConstant(const char *data, asUINT length)
+ {
+ string str(data, length);
+ map_t::iterator it = stringCache.find(str);
+ if (it != stringCache.end())
+ it->second++;
+ else
+ it = stringCache.insert(map_t::value_type(str, 1)).first;
+
+ return reinterpret_cast<const void*>(&it->first);
+ }
+
+ int ReleaseStringConstant(const void *str)
+ {
+ if (str == 0)
+ return asERROR;
+
+ map_t::iterator it = stringCache.find(*reinterpret_cast<const string*>(str));
+ if (it == stringCache.end())
+ return asERROR;
+
+ it->second--;
+ if (it->second == 0)
+ stringCache.erase(it);
+ return asSUCCESS;
+ }
+
+ int GetRawStringData(const void *str, char *data, asUINT *length) const
+ {
+ if (str == 0)
+ return asERROR;
+
+ if (length)
+ *length = (asUINT)reinterpret_cast<const string*>(str)->length();
+
+ if (data)
+ memcpy(data, reinterpret_cast<const string*>(str)->c_str(), reinterpret_cast<const string*>(str)->length());
+
+ return asSUCCESS;
+ }
+
+ // TODO: Make sure the access to the string cache is thread safe
+ map_t stringCache;
+};
+
+static CStdStringFactory stringFactory;
+
+
+static void ConstructString(string *thisPointer)
+{
+ new(thisPointer) string();
+}
+
+static void CopyConstructString(const string &other, string *thisPointer)
+{
+ new(thisPointer) string(other);
+}
+
+static void DestructString(string *thisPointer)
+{
+ thisPointer->~string();
+}
+
+static string &AddAssignStringToString(const string &str, string &dest)
+{
+ // We don't register the method directly because some compilers
+ // and standard libraries inline the definition, resulting in the
+ // linker being unable to find the declaration.
+ // Example: CLang/LLVM with XCode 4.3 on OSX 10.7
+ dest += str;
+ return dest;
+}
+
+// bool string::isEmpty()
+// bool string::empty() // if AS_USE_STLNAMES == 1
+static bool StringIsEmpty(const string &str)
+{
+ // We don't register the method directly because some compilers
+ // and standard libraries inline the definition, resulting in the
+ // linker being unable to find the declaration
+ // Example: CLang/LLVM with XCode 4.3 on OSX 10.7
+ return str.empty();
+}
+
+static string &AssignUInt64ToString(asQWORD i, string &dest)
+{
+ ostringstream stream;
+ stream << i;
+ dest = stream.str();
+ return dest;
+}
+
+static string &AddAssignUInt64ToString(asQWORD i, string &dest)
+{
+ ostringstream stream;
+ stream << i;
+ dest += stream.str();
+ return dest;
+}
+
+static string AddStringUInt64(const string &str, asQWORD i)
+{
+ ostringstream stream;
+ stream << i;
+ return str + stream.str();
+}
+
+static string AddInt64String(asINT64 i, const string &str)
+{
+ ostringstream stream;
+ stream << i;
+ return stream.str() + str;
+}
+
+static string &AssignInt64ToString(asINT64 i, string &dest)
+{
+ ostringstream stream;
+ stream << i;
+ dest = stream.str();
+ return dest;
+}
+
+static string &AddAssignInt64ToString(asINT64 i, string &dest)
+{
+ ostringstream stream;
+ stream << i;
+ dest += stream.str();
+ return dest;
+}
+
+static string AddStringInt64(const string &str, asINT64 i)
+{
+ ostringstream stream;
+ stream << i;
+ return str + stream.str();
+}
+
+static string AddUInt64String(asQWORD i, const string &str)
+{
+ ostringstream stream;
+ stream << i;
+ return stream.str() + str;
+}
+
+static string &AssignDoubleToString(double f, string &dest)
+{
+ ostringstream stream;
+ stream << f;
+ dest = stream.str();
+ return dest;
+}
+
+static string &AddAssignDoubleToString(double f, string &dest)
+{
+ ostringstream stream;
+ stream << f;
+ dest += stream.str();
+ return dest;
+}
+
+static string &AssignFloatToString(float f, string &dest)
+{
+ ostringstream stream;
+ stream << f;
+ dest = stream.str();
+ return dest;
+}
+
+static string &AddAssignFloatToString(float f, string &dest)
+{
+ ostringstream stream;
+ stream << f;
+ dest += stream.str();
+ return dest;
+}
+
+static string &AssignBoolToString(bool b, string &dest)
+{
+ ostringstream stream;
+ stream << (b ? "true" : "false");
+ dest = stream.str();
+ return dest;
+}
+
+static string &AddAssignBoolToString(bool b, string &dest)
+{
+ ostringstream stream;
+ stream << (b ? "true" : "false");
+ dest += stream.str();
+ return dest;
+}
+
+static string AddStringDouble(const string &str, double f)
+{
+ ostringstream stream;
+ stream << f;
+ return str + stream.str();
+}
+
+static string AddDoubleString(double f, const string &str)
+{
+ ostringstream stream;
+ stream << f;
+ return stream.str() + str;
+}
+
+static string AddStringFloat(const string &str, float f)
+{
+ ostringstream stream;
+ stream << f;
+ return str + stream.str();
+}
+
+static string AddFloatString(float f, const string &str)
+{
+ ostringstream stream;
+ stream << f;
+ return stream.str() + str;
+}
+
+static string AddStringBool(const string &str, bool b)
+{
+ ostringstream stream;
+ stream << (b ? "true" : "false");
+ return str + stream.str();
+}
+
+static string AddBoolString(bool b, const string &str)
+{
+ ostringstream stream;
+ stream << (b ? "true" : "false");
+ return stream.str() + str;
+}
+
+static char *StringCharAt(unsigned int i, string &str)
+{
+ if( i >= str.size() )
+ {
+ // Set a script exception
+ asIScriptContext *ctx = asGetActiveContext();
+ ctx->SetException("Out of range");
+
+ // Return a null pointer
+ return 0;
+ }
+
+ return &str[i];
+}
+
+// AngelScript signature:
+// int string::opCmp(const string &in) const
+static int StringCmp(const string &a, const string &b)
+{
+ int cmp = 0;
+ if( a < b ) cmp = -1;
+ else if( a > b ) cmp = 1;
+ return cmp;
+}
+
+// This function returns the index of the first position where the substring
+// exists in the input string. If the substring doesn't exist in the input
+// string -1 is returned.
+//
+// AngelScript signature:
+// int string::findFirst(const string &in sub, uint start = 0) const
+static int StringFindFirst(const string &sub, asUINT start, const string &str)
+{
+ // We don't register the method directly because the argument types change between 32bit and 64bit platforms
+ return (int)str.find(sub, (size_t)(start < 0 ? string::npos : start));
+}
+
+// This function returns the index of the first position where the one of the bytes in substring
+// exists in the input string. If the characters in the substring doesn't exist in the input
+// string -1 is returned.
+//
+// AngelScript signature:
+// int string::findFirstOf(const string &in sub, uint start = 0) const
+static int StringFindFirstOf(const string &sub, asUINT start, const string &str)
+{
+ // We don't register the method directly because the argument types change between 32bit and 64bit platforms
+ return (int)str.find_first_of(sub, (size_t)(start < 0 ? string::npos : start));
+}
+
+// This function returns the index of the last position where the one of the bytes in substring
+// exists in the input string. If the characters in the substring doesn't exist in the input
+// string -1 is returned.
+//
+// AngelScript signature:
+// int string::findLastOf(const string &in sub, uint start = -1) const
+static int StringFindLastOf(const string &sub, asUINT start, const string &str)
+{
+ // We don't register the method directly because the argument types change between 32bit and 64bit platforms
+ return (int)str.find_last_of(sub, (size_t)(start < 0 ? string::npos : start));
+}
+
+// This function returns the index of the first position where a byte other than those in substring
+// exists in the input string. If none is found -1 is returned.
+//
+// AngelScript signature:
+// int string::findFirstNotOf(const string &in sub, uint start = 0) const
+static int StringFindFirstNotOf(const string &sub, asUINT start, const string &str)
+{
+ // We don't register the method directly because the argument types change between 32bit and 64bit platforms
+ return (int)str.find_first_not_of(sub, (size_t)(start < 0 ? string::npos : start));
+}
+
+// This function returns the index of the last position where a byte other than those in substring
+// exists in the input string. If none is found -1 is returned.
+//
+// AngelScript signature:
+// int string::findLastNotOf(const string &in sub, uint start = -1) const
+static int StringFindLastNotOf(const string &sub, asUINT start, const string &str)
+{
+ // We don't register the method directly because the argument types change between 32bit and 64bit platforms
+ return (int)str.find_last_of(sub, (size_t)(start < 0 ? string::npos : start));
+}
+
+// This function returns the index of the last position where the substring
+// exists in the input string. If the substring doesn't exist in the input
+// string -1 is returned.
+//
+// AngelScript signature:
+// int string::findLast(const string &in sub, int start = -1) const
+static int StringFindLast(const string &sub, int start, const string &str)
+{
+ // We don't register the method directly because the argument types change between 32bit and 64bit platforms
+ return (int)str.rfind(sub, (size_t)(start < 0 ? string::npos : start));
+}
+
+// AngelScript signature:
+// void string::insert(uint pos, const string &in other)
+static void StringInsert(unsigned int pos, const string &other, string &str)
+{
+ // We don't register the method directly because the argument types change between 32bit and 64bit platforms
+ str.insert(pos, other);
+}
+
+// AngelScript signature:
+// void string::erase(uint pos, int count = -1)
+static void StringErase(unsigned int pos, int count, string &str)
+{
+ // We don't register the method directly because the argument types change between 32bit and 64bit platforms
+ str.erase(pos, (size_t)(count < 0 ? string::npos : count));
+}
+
+
+// AngelScript signature:
+// uint string::length() const
+static asUINT StringLength(const string &str)
+{
+ // We don't register the method directly because the return type changes between 32bit and 64bit platforms
+ return (asUINT)str.length();
+}
+
+
+// AngelScript signature:
+// void string::resize(uint l)
+static void StringResize(asUINT l, string &str)
+{
+ // We don't register the method directly because the argument types change between 32bit and 64bit platforms
+ str.resize(l);
+}
+
+// AngelScript signature:
+// string formatInt(int64 val, const string &in options, uint width)
+static string formatInt(asINT64 value, const string &options, asUINT width)
+{
+ bool leftJustify = options.find("l") != string::npos;
+ bool padWithZero = options.find("0") != string::npos;
+ bool alwaysSign = options.find("+") != string::npos;
+ bool spaceOnSign = options.find(" ") != string::npos;
+ bool hexSmall = options.find("h") != string::npos;
+ bool hexLarge = options.find("H") != string::npos;
+
+ string fmt = "%";
+ if( leftJustify ) fmt += "-";
+ if( alwaysSign ) fmt += "+";
+ if( spaceOnSign ) fmt += " ";
+ if( padWithZero ) fmt += "0";
+
+#ifdef _WIN32
+ fmt += "*I64";
+#else
+#ifdef _LP64
+ fmt += "*l";
+#else
+ fmt += "*ll";
+#endif
+#endif
+
+ if( hexSmall ) fmt += "x";
+ else if( hexLarge ) fmt += "X";
+ else fmt += "d";
+
+ string buf;
+ buf.resize(width+30);
+#if _MSC_VER >= 1400 && !defined(__S3E__)
+ // MSVC 8.0 / 2005 or newer
+ sprintf_s(&buf[0], buf.size(), fmt.c_str(), width, value);
+#else
+ sprintf(&buf[0], fmt.c_str(), width, value);
+#endif
+ buf.resize(strlen(&buf[0]));
+
+ return buf;
+}
+
+// AngelScript signature:
+// string formatUInt(uint64 val, const string &in options, uint width)
+static string formatUInt(asQWORD value, const string &options, asUINT width)
+{
+ bool leftJustify = options.find("l") != string::npos;
+ bool padWithZero = options.find("0") != string::npos;
+ bool alwaysSign = options.find("+") != string::npos;
+ bool spaceOnSign = options.find(" ") != string::npos;
+ bool hexSmall = options.find("h") != string::npos;
+ bool hexLarge = options.find("H") != string::npos;
+
+ string fmt = "%";
+ if( leftJustify ) fmt += "-";
+ if( alwaysSign ) fmt += "+";
+ if( spaceOnSign ) fmt += " ";
+ if( padWithZero ) fmt += "0";
+
+#ifdef _WIN32
+ fmt += "*I64";
+#else
+#ifdef _LP64
+ fmt += "*l";
+#else
+ fmt += "*ll";
+#endif
+#endif
+
+ if( hexSmall ) fmt += "x";
+ else if( hexLarge ) fmt += "X";
+ else fmt += "u";
+
+ string buf;
+ buf.resize(width+30);
+#if _MSC_VER >= 1400 && !defined(__S3E__)
+ // MSVC 8.0 / 2005 or newer
+ sprintf_s(&buf[0], buf.size(), fmt.c_str(), width, value);
+#else
+ sprintf(&buf[0], fmt.c_str(), width, value);
+#endif
+ buf.resize(strlen(&buf[0]));
+
+ return buf;
+}
+
+// AngelScript signature:
+// string formatFloat(double val, const string &in options, uint width, uint precision)
+static string formatFloat(double value, const string &options, asUINT width, asUINT precision)
+{
+ bool leftJustify = options.find("l") != string::npos;
+ bool padWithZero = options.find("0") != string::npos;
+ bool alwaysSign = options.find("+") != string::npos;
+ bool spaceOnSign = options.find(" ") != string::npos;
+ bool expSmall = options.find("e") != string::npos;
+ bool expLarge = options.find("E") != string::npos;
+
+ string fmt = "%";
+ if( leftJustify ) fmt += "-";
+ if( alwaysSign ) fmt += "+";
+ if( spaceOnSign ) fmt += " ";
+ if( padWithZero ) fmt += "0";
+
+ fmt += "*.*";
+
+ if( expSmall ) fmt += "e";
+ else if( expLarge ) fmt += "E";
+ else fmt += "f";
+
+ string buf;
+ buf.resize(width+precision+50);
+#if _MSC_VER >= 1400 && !defined(__S3E__)
+ // MSVC 8.0 / 2005 or newer
+ sprintf_s(&buf[0], buf.size(), fmt.c_str(), width, precision, value);
+#else
+ sprintf(&buf[0], fmt.c_str(), width, precision, value);
+#endif
+ buf.resize(strlen(&buf[0]));
+
+ return buf;
+}
+
+// AngelScript signature:
+// int64 parseInt(const string &in val, uint base = 10, uint &out byteCount = 0)
+static asINT64 parseInt(const string &val, asUINT base, asUINT *byteCount)
+{
+ // Only accept base 10 and 16
+ if( base != 10 && base != 16 )
+ {
+ if( byteCount ) *byteCount = 0;
+ return 0;
+ }
+
+ const char *end = &val[0];
+
+ // Determine the sign
+ bool sign = false;
+ if( *end == '-' )
+ {
+ sign = true;
+ end++;
+ }
+ else if( *end == '+' )
+ end++;
+
+ asINT64 res = 0;
+ if( base == 10 )
+ {
+ while( *end >= '0' && *end <= '9' )
+ {
+ res *= 10;
+ res += *end++ - '0';
+ }
+ }
+ else if( base == 16 )
+ {
+ while( (*end >= '0' && *end <= '9') ||
+ (*end >= 'a' && *end <= 'f') ||
+ (*end >= 'A' && *end <= 'F') )
+ {
+ res *= 16;
+ if( *end >= '0' && *end <= '9' )
+ res += *end++ - '0';
+ else if( *end >= 'a' && *end <= 'f' )
+ res += *end++ - 'a' + 10;
+ else if( *end >= 'A' && *end <= 'F' )
+ res += *end++ - 'A' + 10;
+ }
+ }
+
+ if( byteCount )
+ *byteCount = asUINT(size_t(end - val.c_str()));
+
+ if( sign )
+ res = -res;
+
+ return res;
+}
+
+// AngelScript signature:
+// uint64 parseUInt(const string &in val, uint base = 10, uint &out byteCount = 0)
+static asQWORD parseUInt(const string &val, asUINT base, asUINT *byteCount)
+{
+ // Only accept base 10 and 16
+ if (base != 10 && base != 16)
+ {
+ if (byteCount) *byteCount = 0;
+ return 0;
+ }
+
+ const char *end = &val[0];
+
+ asQWORD res = 0;
+ if (base == 10)
+ {
+ while (*end >= '0' && *end <= '9')
+ {
+ res *= 10;
+ res += *end++ - '0';
+ }
+ }
+ else if (base == 16)
+ {
+ while ((*end >= '0' && *end <= '9') ||
+ (*end >= 'a' && *end <= 'f') ||
+ (*end >= 'A' && *end <= 'F'))
+ {
+ res *= 16;
+ if (*end >= '0' && *end <= '9')
+ res += *end++ - '0';
+ else if (*end >= 'a' && *end <= 'f')
+ res += *end++ - 'a' + 10;
+ else if (*end >= 'A' && *end <= 'F')
+ res += *end++ - 'A' + 10;
+ }
+ }
+
+ if (byteCount)
+ *byteCount = asUINT(size_t(end - val.c_str()));
+
+ return res;
+}
+
+// AngelScript signature:
+// double parseFloat(const string &in val, uint &out byteCount = 0)
+double parseFloat(const string &val, asUINT *byteCount)
+{
+ char *end;
+
+ // WinCE doesn't have setlocale. Some quick testing on my current platform
+ // still manages to parse the numbers such as "3.14" even if the decimal for the
+ // locale is ",".
+#if !defined(_WIN32_WCE) && !defined(ANDROID) && !defined(__psp2__)
+ // Set the locale to C so that we are guaranteed to parse the float value correctly
+ char *tmp = setlocale(LC_NUMERIC, 0);
+ string orig = tmp ? tmp : "C";
+ setlocale(LC_NUMERIC, "C");
+#endif
+
+ double res = strtod(val.c_str(), &end);
+
+#if !defined(_WIN32_WCE) && !defined(ANDROID) && !defined(__psp2__)
+ // Restore the locale
+ setlocale(LC_NUMERIC, orig.c_str());
+#endif
+
+ if( byteCount )
+ *byteCount = asUINT(size_t(end - val.c_str()));
+
+ return res;
+}
+
+// This function returns a string containing the substring of the input string
+// determined by the starting index and count of characters.
+//
+// AngelScript signature:
+// string string::substr(uint start = 0, int count = -1) const
+static string StringSubString(asUINT start, int count, const string &str)
+{
+ // Check for out-of-bounds
+ string ret;
+ if( start < str.length() && count != 0 )
+ ret = str.substr(start, (size_t)(count < 0 ? string::npos : count));
+
+ return ret;
+}
+
+// String equality comparison.
+// Returns true iff lhs is equal to rhs.
+//
+// For some reason gcc 4.7 has difficulties resolving the
+// asFUNCTIONPR(operator==, (const string &, const string &)
+// makro, so this wrapper was introduced as work around.
+static bool StringEquals(const std::string& lhs, const std::string& rhs)
+{
+ return lhs == rhs;
+}
+
+void RegisterStdString_Native(asIScriptEngine *engine)
+{
+ int r = 0;
+ UNUSED_VAR(r);
+
+ // Register the string type
+#if AS_CAN_USE_CPP11
+ // With C++11 it is possible to use asGetTypeTraits to automatically determine the correct flags to use
+ r = engine->RegisterObjectType("string", sizeof(string), asOBJ_VALUE | asGetTypeTraits<string>()); assert( r >= 0 );
+#else
+ r = engine->RegisterObjectType("string", sizeof(string), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK); assert( r >= 0 );
+#endif
+
+ r = engine->RegisterStringFactory("string", &stringFactory);
+
+ // Register the object operator overloads
+ r = engine->RegisterObjectBehaviour("string", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ConstructString), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("string", asBEHAVE_CONSTRUCT, "void f(const string &in)", asFUNCTION(CopyConstructString), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("string", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(DestructString), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string &opAssign(const string &in)", asMETHODPR(string, operator =, (const string&), string&), asCALL_THISCALL); assert( r >= 0 );
+ // Need to use a wrapper on Mac OS X 10.7/XCode 4.3 and CLang/LLVM, otherwise the linker fails
+ r = engine->RegisterObjectMethod("string", "string &opAddAssign(const string &in)", asFUNCTION(AddAssignStringToString), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+// r = engine->RegisterObjectMethod("string", "string &opAddAssign(const string &in)", asMETHODPR(string, operator+=, (const string&), string&), asCALL_THISCALL); assert( r >= 0 );
+
+ // Need to use a wrapper for operator== otherwise gcc 4.7 fails to compile
+ r = engine->RegisterObjectMethod("string", "bool opEquals(const string &in) const", asFUNCTIONPR(StringEquals, (const string &, const string &), bool), asCALL_CDECL_OBJFIRST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "int opCmp(const string &in) const", asFUNCTION(StringCmp), asCALL_CDECL_OBJFIRST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string opAdd(const string &in) const", asFUNCTIONPR(operator +, (const string &, const string &), string), asCALL_CDECL_OBJFIRST); assert( r >= 0 );
+
+ // The string length can be accessed through methods or through virtual property
+ r = engine->RegisterObjectMethod("string", "uint length() const", asFUNCTION(StringLength), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "void resize(uint)", asFUNCTION(StringResize), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "uint get_length() const", asFUNCTION(StringLength), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "void set_length(uint)", asFUNCTION(StringResize), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ // Need to use a wrapper on Mac OS X 10.7/XCode 4.3 and CLang/LLVM, otherwise the linker fails
+// r = engine->RegisterObjectMethod("string", "bool isEmpty() const", asMETHOD(string, empty), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "bool isEmpty() const", asFUNCTION(StringIsEmpty), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+
+ // Register the index operator, both as a mutator and as an inspector
+ // Note that we don't register the operator[] directly, as it doesn't do bounds checking
+ r = engine->RegisterObjectMethod("string", "uint8 &opIndex(uint)", asFUNCTION(StringCharAt), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "const uint8 &opIndex(uint) const", asFUNCTION(StringCharAt), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+
+ // Automatic conversion from values
+ r = engine->RegisterObjectMethod("string", "string &opAssign(double)", asFUNCTION(AssignDoubleToString), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string &opAddAssign(double)", asFUNCTION(AddAssignDoubleToString), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string opAdd(double) const", asFUNCTION(AddStringDouble), asCALL_CDECL_OBJFIRST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string opAdd_r(double) const", asFUNCTION(AddDoubleString), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("string", "string &opAssign(float)", asFUNCTION(AssignFloatToString), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string &opAddAssign(float)", asFUNCTION(AddAssignFloatToString), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string opAdd(float) const", asFUNCTION(AddStringFloat), asCALL_CDECL_OBJFIRST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string opAdd_r(float) const", asFUNCTION(AddFloatString), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("string", "string &opAssign(int64)", asFUNCTION(AssignInt64ToString), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string &opAddAssign(int64)", asFUNCTION(AddAssignInt64ToString), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string opAdd(int64) const", asFUNCTION(AddStringInt64), asCALL_CDECL_OBJFIRST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string opAdd_r(int64) const", asFUNCTION(AddInt64String), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("string", "string &opAssign(uint64)", asFUNCTION(AssignUInt64ToString), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string &opAddAssign(uint64)", asFUNCTION(AddAssignUInt64ToString), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string opAdd(uint64) const", asFUNCTION(AddStringUInt64), asCALL_CDECL_OBJFIRST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string opAdd_r(uint64) const", asFUNCTION(AddUInt64String), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("string", "string &opAssign(bool)", asFUNCTION(AssignBoolToString), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string &opAddAssign(bool)", asFUNCTION(AddAssignBoolToString), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string opAdd(bool) const", asFUNCTION(AddStringBool), asCALL_CDECL_OBJFIRST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string opAdd_r(bool) const", asFUNCTION(AddBoolString), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+
+ // Utilities
+ r = engine->RegisterObjectMethod("string", "string substr(uint start = 0, int count = -1) const", asFUNCTION(StringSubString), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "int findFirst(const string &in, uint start = 0) const", asFUNCTION(StringFindFirst), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "int findFirstOf(const string &in, uint start = 0) const", asFUNCTION(StringFindFirstOf), asCALL_CDECL_OBJLAST); assert(r >= 0);
+ r = engine->RegisterObjectMethod("string", "int findFirstNotOf(const string &in, uint start = 0) const", asFUNCTION(StringFindFirstNotOf), asCALL_CDECL_OBJLAST); assert(r >= 0);
+ r = engine->RegisterObjectMethod("string", "int findLast(const string &in, int start = -1) const", asFUNCTION(StringFindLast), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "int findLastOf(const string &in, int start = -1) const", asFUNCTION(StringFindLastOf), asCALL_CDECL_OBJLAST); assert(r >= 0);
+ r = engine->RegisterObjectMethod("string", "int findLastNotOf(const string &in, int start = -1) const", asFUNCTION(StringFindLastNotOf), asCALL_CDECL_OBJLAST); assert(r >= 0);
+ r = engine->RegisterObjectMethod("string", "void insert(uint pos, const string &in other)", asFUNCTION(StringInsert), asCALL_CDECL_OBJLAST); assert(r >= 0);
+ r = engine->RegisterObjectMethod("string", "void erase(uint pos, int count = -1)", asFUNCTION(StringErase), asCALL_CDECL_OBJLAST); assert(r >= 0);
+
+
+ r = engine->RegisterGlobalFunction("string formatInt(int64 val, const string &in options = \"\", uint width = 0)", asFUNCTION(formatInt), asCALL_CDECL); assert(r >= 0);
+ r = engine->RegisterGlobalFunction("string formatUInt(uint64 val, const string &in options = \"\", uint width = 0)", asFUNCTION(formatUInt), asCALL_CDECL); assert(r >= 0);
+ r = engine->RegisterGlobalFunction("string formatFloat(double val, const string &in options = \"\", uint width = 0, uint precision = 0)", asFUNCTION(formatFloat), asCALL_CDECL); assert(r >= 0);
+ r = engine->RegisterGlobalFunction("int64 parseInt(const string &in, uint base = 10, uint &out byteCount = 0)", asFUNCTION(parseInt), asCALL_CDECL); assert(r >= 0);
+ r = engine->RegisterGlobalFunction("uint64 parseUInt(const string &in, uint base = 10, uint &out byteCount = 0)", asFUNCTION(parseUInt), asCALL_CDECL); assert(r >= 0);
+ r = engine->RegisterGlobalFunction("double parseFloat(const string &in, uint &out byteCount = 0)", asFUNCTION(parseFloat), asCALL_CDECL); assert(r >= 0);
+
+#if AS_USE_STLNAMES == 1
+ // Same as length
+ r = engine->RegisterObjectMethod("string", "uint size() const", asFUNCTION(StringLength), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ // Same as isEmpty
+ r = engine->RegisterObjectMethod("string", "bool empty() const", asFUNCTION(StringIsEmpty), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ // Same as findFirst
+ r = engine->RegisterObjectMethod("string", "int find(const string &in, uint start = 0) const", asFUNCTION(StringFindFirst), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ // Same as findLast
+ r = engine->RegisterObjectMethod("string", "int rfind(const string &in, int start = -1) const", asFUNCTION(StringFindLast), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+#endif
+
+ // TODO: Implement the following
+ // findAndReplace - replaces a text found in the string
+ // replaceRange - replaces a range of bytes in the string
+ // multiply/times/opMul/opMul_r - takes the string and multiplies it n times, e.g. "-".multiply(5) returns "-----"
+}
+
+static void ConstructStringGeneric(asIScriptGeneric * gen)
+{
+ new (gen->GetObject()) string();
+}
+
+static void CopyConstructStringGeneric(asIScriptGeneric * gen)
+{
+ string * a = static_cast<string *>(gen->GetArgObject(0));
+ new (gen->GetObject()) string(*a);
+}
+
+static void DestructStringGeneric(asIScriptGeneric * gen)
+{
+ string * ptr = static_cast<string *>(gen->GetObject());
+ ptr->~string();
+}
+
+static void AssignStringGeneric(asIScriptGeneric *gen)
+{
+ string * a = static_cast<string *>(gen->GetArgObject(0));
+ string * self = static_cast<string *>(gen->GetObject());
+ *self = *a;
+ gen->SetReturnAddress(self);
+}
+
+static void AddAssignStringGeneric(asIScriptGeneric *gen)
+{
+ string * a = static_cast<string *>(gen->GetArgObject(0));
+ string * self = static_cast<string *>(gen->GetObject());
+ *self += *a;
+ gen->SetReturnAddress(self);
+}
+
+static void StringEqualsGeneric(asIScriptGeneric * gen)
+{
+ string * a = static_cast<string *>(gen->GetObject());
+ string * b = static_cast<string *>(gen->GetArgAddress(0));
+ *(bool*)gen->GetAddressOfReturnLocation() = (*a == *b);
+}
+
+static void StringCmpGeneric(asIScriptGeneric * gen)
+{
+ string * a = static_cast<string *>(gen->GetObject());
+ string * b = static_cast<string *>(gen->GetArgAddress(0));
+
+ int cmp = 0;
+ if( *a < *b ) cmp = -1;
+ else if( *a > *b ) cmp = 1;
+
+ *(int*)gen->GetAddressOfReturnLocation() = cmp;
+}
+
+static void StringAddGeneric(asIScriptGeneric * gen)
+{
+ string * a = static_cast<string *>(gen->GetObject());
+ string * b = static_cast<string *>(gen->GetArgAddress(0));
+ string ret_val = *a + *b;
+ gen->SetReturnObject(&ret_val);
+}
+
+static void StringLengthGeneric(asIScriptGeneric * gen)
+{
+ string * self = static_cast<string *>(gen->GetObject());
+ *static_cast<asUINT *>(gen->GetAddressOfReturnLocation()) = (asUINT)self->length();
+}
+
+static void StringIsEmptyGeneric(asIScriptGeneric * gen)
+{
+ string * self = reinterpret_cast<string *>(gen->GetObject());
+ *reinterpret_cast<bool *>(gen->GetAddressOfReturnLocation()) = StringIsEmpty(*self);
+}
+
+static void StringResizeGeneric(asIScriptGeneric * gen)
+{
+ string * self = static_cast<string *>(gen->GetObject());
+ self->resize(*static_cast<asUINT *>(gen->GetAddressOfArg(0)));
+}
+
+static void StringInsert_Generic(asIScriptGeneric *gen)
+{
+ string * self = static_cast<string *>(gen->GetObject());
+ asUINT pos = gen->GetArgDWord(0);
+ string *other = reinterpret_cast<string*>(gen->GetArgAddress(1));
+ StringInsert(pos, *other, *self);
+}
+
+static void StringErase_Generic(asIScriptGeneric *gen)
+{
+ string * self = static_cast<string *>(gen->GetObject());
+ asUINT pos = gen->GetArgDWord(0);
+ int count = int(gen->GetArgDWord(1));
+ StringErase(pos, count, *self);
+}
+
+static void StringFindFirst_Generic(asIScriptGeneric * gen)
+{
+ string *find = reinterpret_cast<string*>(gen->GetArgAddress(0));
+ asUINT start = gen->GetArgDWord(1);
+ string *self = reinterpret_cast<string *>(gen->GetObject());
+ *reinterpret_cast<int *>(gen->GetAddressOfReturnLocation()) = StringFindFirst(*find, start, *self);
+}
+
+static void StringFindLast_Generic(asIScriptGeneric * gen)
+{
+ string *find = reinterpret_cast<string*>(gen->GetArgAddress(0));
+ asUINT start = gen->GetArgDWord(1);
+ string *self = reinterpret_cast<string *>(gen->GetObject());
+ *reinterpret_cast<int *>(gen->GetAddressOfReturnLocation()) = StringFindLast(*find, start, *self);
+}
+
+static void StringFindFirstOf_Generic(asIScriptGeneric * gen)
+{
+ string *find = reinterpret_cast<string*>(gen->GetArgAddress(0));
+ asUINT start = gen->GetArgDWord(1);
+ string *self = reinterpret_cast<string *>(gen->GetObject());
+ *reinterpret_cast<int *>(gen->GetAddressOfReturnLocation()) = StringFindFirstOf(*find, start, *self);
+}
+
+static void StringFindLastOf_Generic(asIScriptGeneric * gen)
+{
+ string *find = reinterpret_cast<string*>(gen->GetArgAddress(0));
+ asUINT start = gen->GetArgDWord(1);
+ string *self = reinterpret_cast<string *>(gen->GetObject());
+ *reinterpret_cast<int *>(gen->GetAddressOfReturnLocation()) = StringFindLastOf(*find, start, *self);
+}
+
+static void StringFindFirstNotOf_Generic(asIScriptGeneric * gen)
+{
+ string *find = reinterpret_cast<string*>(gen->GetArgAddress(0));
+ asUINT start = gen->GetArgDWord(1);
+ string *self = reinterpret_cast<string *>(gen->GetObject());
+ *reinterpret_cast<int *>(gen->GetAddressOfReturnLocation()) = StringFindFirstNotOf(*find, start, *self);
+}
+
+static void StringFindLastNotOf_Generic(asIScriptGeneric * gen)
+{
+ string *find = reinterpret_cast<string*>(gen->GetArgAddress(0));
+ asUINT start = gen->GetArgDWord(1);
+ string *self = reinterpret_cast<string *>(gen->GetObject());
+ *reinterpret_cast<int *>(gen->GetAddressOfReturnLocation()) = StringFindLastNotOf(*find, start, *self);
+}
+
+static void formatInt_Generic(asIScriptGeneric * gen)
+{
+ asINT64 val = gen->GetArgQWord(0);
+ string *options = reinterpret_cast<string*>(gen->GetArgAddress(1));
+ asUINT width = gen->GetArgDWord(2);
+ new(gen->GetAddressOfReturnLocation()) string(formatInt(val, *options, width));
+}
+
+static void formatUInt_Generic(asIScriptGeneric * gen)
+{
+ asQWORD val = gen->GetArgQWord(0);
+ string *options = reinterpret_cast<string*>(gen->GetArgAddress(1));
+ asUINT width = gen->GetArgDWord(2);
+ new(gen->GetAddressOfReturnLocation()) string(formatUInt(val, *options, width));
+}
+
+static void formatFloat_Generic(asIScriptGeneric *gen)
+{
+ double val = gen->GetArgDouble(0);
+ string *options = reinterpret_cast<string*>(gen->GetArgAddress(1));
+ asUINT width = gen->GetArgDWord(2);
+ asUINT precision = gen->GetArgDWord(3);
+ new(gen->GetAddressOfReturnLocation()) string(formatFloat(val, *options, width, precision));
+}
+
+static void parseInt_Generic(asIScriptGeneric *gen)
+{
+ string *str = reinterpret_cast<string*>(gen->GetArgAddress(0));
+ asUINT base = gen->GetArgDWord(1);
+ asUINT *byteCount = reinterpret_cast<asUINT*>(gen->GetArgAddress(2));
+ gen->SetReturnQWord(parseInt(*str,base,byteCount));
+}
+
+static void parseUInt_Generic(asIScriptGeneric *gen)
+{
+ string *str = reinterpret_cast<string*>(gen->GetArgAddress(0));
+ asUINT base = gen->GetArgDWord(1);
+ asUINT *byteCount = reinterpret_cast<asUINT*>(gen->GetArgAddress(2));
+ gen->SetReturnQWord(parseUInt(*str, base, byteCount));
+}
+
+static void parseFloat_Generic(asIScriptGeneric *gen)
+{
+ string *str = reinterpret_cast<string*>(gen->GetArgAddress(0));
+ asUINT *byteCount = reinterpret_cast<asUINT*>(gen->GetArgAddress(1));
+ gen->SetReturnDouble(parseFloat(*str,byteCount));
+}
+
+static void StringCharAtGeneric(asIScriptGeneric * gen)
+{
+ unsigned int index = gen->GetArgDWord(0);
+ string * self = static_cast<string *>(gen->GetObject());
+
+ if (index >= self->size())
+ {
+ // Set a script exception
+ asIScriptContext *ctx = asGetActiveContext();
+ ctx->SetException("Out of range");
+
+ gen->SetReturnAddress(0);
+ }
+ else
+ {
+ gen->SetReturnAddress(&(self->operator [](index)));
+ }
+}
+
+static void AssignInt2StringGeneric(asIScriptGeneric *gen)
+{
+ asINT64 *a = static_cast<asINT64*>(gen->GetAddressOfArg(0));
+ string *self = static_cast<string*>(gen->GetObject());
+ std::stringstream sstr;
+ sstr << *a;
+ *self = sstr.str();
+ gen->SetReturnAddress(self);
+}
+
+static void AssignUInt2StringGeneric(asIScriptGeneric *gen)
+{
+ asQWORD *a = static_cast<asQWORD*>(gen->GetAddressOfArg(0));
+ string *self = static_cast<string*>(gen->GetObject());
+ std::stringstream sstr;
+ sstr << *a;
+ *self = sstr.str();
+ gen->SetReturnAddress(self);
+}
+
+static void AssignDouble2StringGeneric(asIScriptGeneric *gen)
+{
+ double *a = static_cast<double*>(gen->GetAddressOfArg(0));
+ string *self = static_cast<string*>(gen->GetObject());
+ std::stringstream sstr;
+ sstr << *a;
+ *self = sstr.str();
+ gen->SetReturnAddress(self);
+}
+
+static void AssignFloat2StringGeneric(asIScriptGeneric *gen)
+{
+ float *a = static_cast<float*>(gen->GetAddressOfArg(0));
+ string *self = static_cast<string*>(gen->GetObject());
+ std::stringstream sstr;
+ sstr << *a;
+ *self = sstr.str();
+ gen->SetReturnAddress(self);
+}
+
+static void AssignBool2StringGeneric(asIScriptGeneric *gen)
+{
+ bool *a = static_cast<bool*>(gen->GetAddressOfArg(0));
+ string *self = static_cast<string*>(gen->GetObject());
+ std::stringstream sstr;
+ sstr << (*a ? "true" : "false");
+ *self = sstr.str();
+ gen->SetReturnAddress(self);
+}
+
+static void AddAssignDouble2StringGeneric(asIScriptGeneric * gen)
+{
+ double * a = static_cast<double *>(gen->GetAddressOfArg(0));
+ string * self = static_cast<string *>(gen->GetObject());
+ std::stringstream sstr;
+ sstr << *a;
+ *self += sstr.str();
+ gen->SetReturnAddress(self);
+}
+
+static void AddAssignFloat2StringGeneric(asIScriptGeneric * gen)
+{
+ float * a = static_cast<float *>(gen->GetAddressOfArg(0));
+ string * self = static_cast<string *>(gen->GetObject());
+ std::stringstream sstr;
+ sstr << *a;
+ *self += sstr.str();
+ gen->SetReturnAddress(self);
+}
+
+static void AddAssignInt2StringGeneric(asIScriptGeneric * gen)
+{
+ asINT64 * a = static_cast<asINT64 *>(gen->GetAddressOfArg(0));
+ string * self = static_cast<string *>(gen->GetObject());
+ std::stringstream sstr;
+ sstr << *a;
+ *self += sstr.str();
+ gen->SetReturnAddress(self);
+}
+
+static void AddAssignUInt2StringGeneric(asIScriptGeneric * gen)
+{
+ asQWORD * a = static_cast<asQWORD *>(gen->GetAddressOfArg(0));
+ string * self = static_cast<string *>(gen->GetObject());
+ std::stringstream sstr;
+ sstr << *a;
+ *self += sstr.str();
+ gen->SetReturnAddress(self);
+}
+
+static void AddAssignBool2StringGeneric(asIScriptGeneric * gen)
+{
+ bool * a = static_cast<bool *>(gen->GetAddressOfArg(0));
+ string * self = static_cast<string *>(gen->GetObject());
+ std::stringstream sstr;
+ sstr << (*a ? "true" : "false");
+ *self += sstr.str();
+ gen->SetReturnAddress(self);
+}
+
+static void AddString2DoubleGeneric(asIScriptGeneric * gen)
+{
+ string * a = static_cast<string *>(gen->GetObject());
+ double * b = static_cast<double *>(gen->GetAddressOfArg(0));
+ std::stringstream sstr;
+ sstr << *a << *b;
+ std::string ret_val = sstr.str();
+ gen->SetReturnObject(&ret_val);
+}
+
+static void AddString2FloatGeneric(asIScriptGeneric * gen)
+{
+ string * a = static_cast<string *>(gen->GetObject());
+ float * b = static_cast<float *>(gen->GetAddressOfArg(0));
+ std::stringstream sstr;
+ sstr << *a << *b;
+ std::string ret_val = sstr.str();
+ gen->SetReturnObject(&ret_val);
+}
+
+static void AddString2IntGeneric(asIScriptGeneric * gen)
+{
+ string * a = static_cast<string *>(gen->GetObject());
+ asINT64 * b = static_cast<asINT64 *>(gen->GetAddressOfArg(0));
+ std::stringstream sstr;
+ sstr << *a << *b;
+ std::string ret_val = sstr.str();
+ gen->SetReturnObject(&ret_val);
+}
+
+static void AddString2UIntGeneric(asIScriptGeneric * gen)
+{
+ string * a = static_cast<string *>(gen->GetObject());
+ asQWORD * b = static_cast<asQWORD *>(gen->GetAddressOfArg(0));
+ std::stringstream sstr;
+ sstr << *a << *b;
+ std::string ret_val = sstr.str();
+ gen->SetReturnObject(&ret_val);
+}
+
+static void AddString2BoolGeneric(asIScriptGeneric * gen)
+{
+ string * a = static_cast<string *>(gen->GetObject());
+ bool * b = static_cast<bool *>(gen->GetAddressOfArg(0));
+ std::stringstream sstr;
+ sstr << *a << (*b ? "true" : "false");
+ std::string ret_val = sstr.str();
+ gen->SetReturnObject(&ret_val);
+}
+
+static void AddDouble2StringGeneric(asIScriptGeneric * gen)
+{
+ double* a = static_cast<double *>(gen->GetAddressOfArg(0));
+ string * b = static_cast<string *>(gen->GetObject());
+ std::stringstream sstr;
+ sstr << *a << *b;
+ std::string ret_val = sstr.str();
+ gen->SetReturnObject(&ret_val);
+}
+
+static void AddFloat2StringGeneric(asIScriptGeneric * gen)
+{
+ float* a = static_cast<float *>(gen->GetAddressOfArg(0));
+ string * b = static_cast<string *>(gen->GetObject());
+ std::stringstream sstr;
+ sstr << *a << *b;
+ std::string ret_val = sstr.str();
+ gen->SetReturnObject(&ret_val);
+}
+
+static void AddInt2StringGeneric(asIScriptGeneric * gen)
+{
+ asINT64* a = static_cast<asINT64 *>(gen->GetAddressOfArg(0));
+ string * b = static_cast<string *>(gen->GetObject());
+ std::stringstream sstr;
+ sstr << *a << *b;
+ std::string ret_val = sstr.str();
+ gen->SetReturnObject(&ret_val);
+}
+
+static void AddUInt2StringGeneric(asIScriptGeneric * gen)
+{
+ asQWORD* a = static_cast<asQWORD *>(gen->GetAddressOfArg(0));
+ string * b = static_cast<string *>(gen->GetObject());
+ std::stringstream sstr;
+ sstr << *a << *b;
+ std::string ret_val = sstr.str();
+ gen->SetReturnObject(&ret_val);
+}
+
+static void AddBool2StringGeneric(asIScriptGeneric * gen)
+{
+ bool* a = static_cast<bool *>(gen->GetAddressOfArg(0));
+ string * b = static_cast<string *>(gen->GetObject());
+ std::stringstream sstr;
+ sstr << (*a ? "true" : "false") << *b;
+ std::string ret_val = sstr.str();
+ gen->SetReturnObject(&ret_val);
+}
+
+static void StringSubString_Generic(asIScriptGeneric *gen)
+{
+ // Get the arguments
+ string *str = (string*)gen->GetObject();
+ asUINT start = *(int*)gen->GetAddressOfArg(0);
+ int count = *(int*)gen->GetAddressOfArg(1);
+
+ // Return the substring
+ new(gen->GetAddressOfReturnLocation()) string(StringSubString(start, count, *str));
+}
+
+void RegisterStdString_Generic(asIScriptEngine *engine)
+{
+ int r = 0;
+ UNUSED_VAR(r);
+
+ // Register the string type
+ r = engine->RegisterObjectType("string", sizeof(string), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK); assert( r >= 0 );
+
+ r = engine->RegisterStringFactory("string", &stringFactory);
+
+ // Register the object operator overloads
+ r = engine->RegisterObjectBehaviour("string", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ConstructStringGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("string", asBEHAVE_CONSTRUCT, "void f(const string &in)", asFUNCTION(CopyConstructStringGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("string", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(DestructStringGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string &opAssign(const string &in)", asFUNCTION(AssignStringGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string &opAddAssign(const string &in)", asFUNCTION(AddAssignStringGeneric), asCALL_GENERIC); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("string", "bool opEquals(const string &in) const", asFUNCTION(StringEqualsGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "int opCmp(const string &in) const", asFUNCTION(StringCmpGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string opAdd(const string &in) const", asFUNCTION(StringAddGeneric), asCALL_GENERIC); assert( r >= 0 );
+
+ // Register the object methods
+ r = engine->RegisterObjectMethod("string", "uint length() const", asFUNCTION(StringLengthGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "void resize(uint)", asFUNCTION(StringResizeGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "uint get_length() const", asFUNCTION(StringLengthGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "void set_length(uint)", asFUNCTION(StringResizeGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "bool isEmpty() const", asFUNCTION(StringIsEmptyGeneric), asCALL_GENERIC); assert( r >= 0 );
+
+ // Register the index operator, both as a mutator and as an inspector
+ r = engine->RegisterObjectMethod("string", "uint8 &opIndex(uint)", asFUNCTION(StringCharAtGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "const uint8 &opIndex(uint) const", asFUNCTION(StringCharAtGeneric), asCALL_GENERIC); assert( r >= 0 );
+
+ // Automatic conversion from values
+ r = engine->RegisterObjectMethod("string", "string &opAssign(double)", asFUNCTION(AssignDouble2StringGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string &opAddAssign(double)", asFUNCTION(AddAssignDouble2StringGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string opAdd(double) const", asFUNCTION(AddString2DoubleGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string opAdd_r(double) const", asFUNCTION(AddDouble2StringGeneric), asCALL_GENERIC); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("string", "string &opAssign(float)", asFUNCTION(AssignFloat2StringGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string &opAddAssign(float)", asFUNCTION(AddAssignFloat2StringGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string opAdd(float) const", asFUNCTION(AddString2FloatGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string opAdd_r(float) const", asFUNCTION(AddFloat2StringGeneric), asCALL_GENERIC); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("string", "string &opAssign(int64)", asFUNCTION(AssignInt2StringGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string &opAddAssign(int64)", asFUNCTION(AddAssignInt2StringGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string opAdd(int64) const", asFUNCTION(AddString2IntGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string opAdd_r(int64) const", asFUNCTION(AddInt2StringGeneric), asCALL_GENERIC); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("string", "string &opAssign(uint64)", asFUNCTION(AssignUInt2StringGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string &opAddAssign(uint64)", asFUNCTION(AddAssignUInt2StringGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string opAdd(uint64) const", asFUNCTION(AddString2UIntGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string opAdd_r(uint64) const", asFUNCTION(AddUInt2StringGeneric), asCALL_GENERIC); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("string", "string &opAssign(bool)", asFUNCTION(AssignBool2StringGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string &opAddAssign(bool)", asFUNCTION(AddAssignBool2StringGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string opAdd(bool) const", asFUNCTION(AddString2BoolGeneric), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("string", "string opAdd_r(bool) const", asFUNCTION(AddBool2StringGeneric), asCALL_GENERIC); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("string", "string substr(uint start = 0, int count = -1) const", asFUNCTION(StringSubString_Generic), asCALL_GENERIC); assert(r >= 0);
+ r = engine->RegisterObjectMethod("string", "int findFirst(const string &in, uint start = 0) const", asFUNCTION(StringFindFirst_Generic), asCALL_GENERIC); assert(r >= 0);
+ r = engine->RegisterObjectMethod("string", "int findFirstOf(const string &in, uint start = 0) const", asFUNCTION(StringFindFirstOf_Generic), asCALL_GENERIC); assert(r >= 0);
+ r = engine->RegisterObjectMethod("string", "int findFirstNotOf(const string &in, uint start = 0) const", asFUNCTION(StringFindFirstNotOf_Generic), asCALL_GENERIC); assert(r >= 0);
+ r = engine->RegisterObjectMethod("string", "int findLast(const string &in, int start = -1) const", asFUNCTION(StringFindLast_Generic), asCALL_GENERIC); assert(r >= 0);
+ r = engine->RegisterObjectMethod("string", "int findLastOf(const string &in, int start = -1) const", asFUNCTION(StringFindLastOf_Generic), asCALL_GENERIC); assert(r >= 0);
+ r = engine->RegisterObjectMethod("string", "int findLastNotOf(const string &in, int start = -1) const", asFUNCTION(StringFindLastNotOf_Generic), asCALL_GENERIC); assert(r >= 0);
+ r = engine->RegisterObjectMethod("string", "void insert(uint pos, const string &in other)", asFUNCTION(StringInsert_Generic), asCALL_GENERIC); assert(r >= 0);
+ r = engine->RegisterObjectMethod("string", "void erase(uint pos, int count = -1)", asFUNCTION(StringErase_Generic), asCALL_GENERIC); assert(r >= 0);
+
+
+ r = engine->RegisterGlobalFunction("string formatInt(int64 val, const string &in options = \"\", uint width = 0)", asFUNCTION(formatInt_Generic), asCALL_GENERIC); assert(r >= 0);
+ r = engine->RegisterGlobalFunction("string formatUInt(uint64 val, const string &in options = \"\", uint width = 0)", asFUNCTION(formatUInt_Generic), asCALL_GENERIC); assert(r >= 0);
+ r = engine->RegisterGlobalFunction("string formatFloat(double val, const string &in options = \"\", uint width = 0, uint precision = 0)", asFUNCTION(formatFloat_Generic), asCALL_GENERIC); assert(r >= 0);
+ r = engine->RegisterGlobalFunction("int64 parseInt(const string &in, uint base = 10, uint &out byteCount = 0)", asFUNCTION(parseInt_Generic), asCALL_GENERIC); assert(r >= 0);
+ r = engine->RegisterGlobalFunction("uint64 parseUInt(const string &in, uint base = 10, uint &out byteCount = 0)", asFUNCTION(parseUInt_Generic), asCALL_GENERIC); assert(r >= 0);
+ r = engine->RegisterGlobalFunction("double parseFloat(const string &in, uint &out byteCount = 0)", asFUNCTION(parseFloat_Generic), asCALL_GENERIC); assert(r >= 0);
+}
+
+void RegisterStdString(asIScriptEngine * engine)
+{
+ if (strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY"))
+ RegisterStdString_Generic(engine);
+ else
+ RegisterStdString_Native(engine);
+}
+
+END_AS_NAMESPACE
+
+
+
+
diff --git a/Source/Scripting/angelscript/add_on/scriptstdstring/scriptstdstring.h b/Source/Scripting/angelscript/add_on/scriptstdstring/scriptstdstring.h
new file mode 100644
index 00000000..9d8ac53c
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/scriptstdstring/scriptstdstring.h
@@ -0,0 +1,43 @@
+//
+// Script std::string
+//
+// This function registers the std::string type with AngelScript to be used as the default string type.
+//
+// The string type is registered as a value type, thus may have performance issues if a lot of
+// string operations are performed in the script. However, for relatively few operations, this should
+// not cause any problem for most applications.
+//
+
+#ifndef SCRIPTSTDSTRING_H
+#define SCRIPTSTDSTRING_H
+
+#ifndef ANGELSCRIPT_H
+// Avoid having to inform include path if header is already include before
+#include <angelscript.h>
+#endif
+
+#include <string>
+
+//---------------------------
+// Compilation settings
+//
+
+// Sometimes it may be desired to use the same method names as used by C++ STL.
+// This may for example reduce time when converting code from script to C++ or
+// back.
+//
+// 0 = off
+// 1 = on
+
+#ifndef AS_USE_STLNAMES
+#define AS_USE_STLNAMES 0
+#endif
+
+BEGIN_AS_NAMESPACE
+
+void RegisterStdString(asIScriptEngine *engine);
+void RegisterStdStringUtils(asIScriptEngine *engine);
+
+END_AS_NAMESPACE
+
+#endif
diff --git a/Source/Scripting/angelscript/add_on/scriptstdstring/scriptstdstring_utils.cpp b/Source/Scripting/angelscript/add_on/scriptstdstring/scriptstdstring_utils.cpp
new file mode 100644
index 00000000..54662aa0
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/scriptstdstring/scriptstdstring_utils.cpp
@@ -0,0 +1,129 @@
+#include <assert.h>
+#include "scriptstdstring.h"
+#include "../scriptarray/scriptarray.h"
+#include <stdio.h>
+#include <string.h>
+
+using namespace std;
+
+BEGIN_AS_NAMESPACE
+
+// This function takes an input string and splits it into parts by looking
+// for a specified delimiter. Example:
+//
+// string str = "A|B||D";
+// array<string>@ array = str.split("|");
+//
+// The resulting array has the following elements:
+//
+// {"A", "B", "", "D"}
+//
+// AngelScript signature:
+// array<string>@ string::split(const string &in delim) const
+static CScriptArray *StringSplit(const string &delim, const string &str)
+{
+ // Obtain a pointer to the engine
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+
+ // TODO: This should only be done once
+ // TODO: This assumes that CScriptArray was already registered
+ asITypeInfo *arrayType = engine->GetTypeInfoByDecl("array<string>");
+
+ // Create the array object
+ CScriptArray *array = CScriptArray::Create(arrayType);
+
+ // Find the existence of the delimiter in the input string
+ int pos = 0, prev = 0, count = 0;
+ while( (pos = (int)str.find(delim, prev)) != (int)string::npos )
+ {
+ // Add the part to the array
+ array->Resize(array->GetSize()+1);
+ ((string*)array->At(count))->assign(&str[prev], pos-prev);
+
+ // Find the next part
+ count++;
+ prev = pos + (int)delim.length();
+ }
+
+ // Add the remaining part
+ array->Resize(array->GetSize()+1);
+ ((string*)array->At(count))->assign(&str[prev]);
+
+ return array;
+}
+
+static void StringSplit_Generic(asIScriptGeneric *gen)
+{
+ // Get the arguments
+ string *str = (string*)gen->GetObject();
+ string *delim = *(string**)gen->GetAddressOfArg(0);
+
+ // Return the array by handle
+ *(CScriptArray**)gen->GetAddressOfReturnLocation() = StringSplit(*delim, *str);
+}
+
+
+
+// This function takes as input an array of string handles as well as a
+// delimiter and concatenates the array elements into one delimited string.
+// Example:
+//
+// array<string> array = {"A", "B", "", "D"};
+// string str = join(array, "|");
+//
+// The resulting string is:
+//
+// "A|B||D"
+//
+// AngelScript signature:
+// string join(const array<string> &in array, const string &in delim)
+static string StringJoin(const CScriptArray &array, const string &delim)
+{
+ // Create the new string
+ string str = "";
+ if( array.GetSize() )
+ {
+ int n;
+ for( n = 0; n < (int)array.GetSize() - 1; n++ )
+ {
+ str += *(string*)array.At(n);
+ str += delim;
+ }
+
+ // Add the last part
+ str += *(string*)array.At(n);
+ }
+
+ return str;
+}
+
+static void StringJoin_Generic(asIScriptGeneric *gen)
+{
+ // Get the arguments
+ CScriptArray *array = *(CScriptArray**)gen->GetAddressOfArg(0);
+ string *delim = *(string**)gen->GetAddressOfArg(1);
+
+ // Return the string
+ new(gen->GetAddressOfReturnLocation()) string(StringJoin(*array, *delim));
+}
+
+// This is where the utility functions are registered.
+// The string type must have been registered first.
+void RegisterStdStringUtils(asIScriptEngine *engine)
+{
+ int r;
+
+ if( strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") )
+ {
+ r = engine->RegisterObjectMethod("string", "array<string>@ split(const string &in) const", asFUNCTION(StringSplit_Generic), asCALL_GENERIC); assert(r >= 0);
+ r = engine->RegisterGlobalFunction("string join(const array<string> &in, const string &in)", asFUNCTION(StringJoin_Generic), asCALL_GENERIC); assert(r >= 0);
+ }
+ else
+ {
+ r = engine->RegisterObjectMethod("string", "array<string>@ split(const string &in) const", asFUNCTION(StringSplit), asCALL_CDECL_OBJLAST); assert(r >= 0);
+ r = engine->RegisterGlobalFunction("string join(const array<string> &in, const string &in)", asFUNCTION(StringJoin), asCALL_CDECL); assert(r >= 0);
+ }
+}
+
+END_AS_NAMESPACE
diff --git a/Source/Scripting/angelscript/add_on/serializer/serializer.cpp b/Source/Scripting/angelscript/add_on/serializer/serializer.cpp
new file mode 100644
index 00000000..a359fcdd
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/serializer/serializer.cpp
@@ -0,0 +1,548 @@
+//
+// CSerializer
+//
+// This code was based on the CScriptReloader written by FDsagizi
+// http://www.gamedev.net/topic/604890-dynamic-reloading-script/
+//
+
+#include <assert.h>
+#include <string.h> // strstr
+#include <stdio.h> // sprintf
+#include "serializer.h"
+
+using namespace std;
+
+BEGIN_AS_NAMESPACE
+
+///////////////////////////////////////////////////////////////////////////////////
+
+CSerializer::CSerializer()
+{
+ m_engine = 0;
+}
+
+CSerializer::~CSerializer()
+{
+ // Extra objects need to be released, since they are not stored in
+ // the module and we cannot rely on the application releasing them
+ for( size_t i = 0; i < m_extraObjects.size(); i++ )
+ {
+ SExtraObject &o = m_extraObjects[i];
+ for( size_t i2 = 0; i2 < m_root.m_children.size(); i2++ )
+ {
+ if( m_root.m_children[i2]->m_originalPtr == o.originalObject && m_root.m_children[i2]->m_restorePtr )
+ reinterpret_cast<asIScriptObject*>(m_root.m_children[i2]->m_restorePtr)->Release();
+ }
+ }
+
+ // Clean the serialized values before we remove the user types
+ m_root.Uninit();
+
+ // Delete the user types
+ std::map<std::string, CUserType*>::iterator it;
+ for( it = m_userTypes.begin(); it != m_userTypes.end(); it++ )
+ delete it->second;
+
+ if( m_engine )
+ m_engine->Release();
+}
+
+void CSerializer::AddUserType(CUserType *ref, const std::string &name)
+{
+ m_userTypes[name] = ref;
+}
+
+int CSerializer::Store(asIScriptModule *mod)
+{
+ m_mod = mod;
+
+ // The engine must not be destroyed before we're completed, so we'll hold on to a reference
+ mod->GetEngine()->AddRef();
+ if( m_engine ) m_engine->Release();
+ m_engine = mod->GetEngine();
+
+ m_root.m_serializer = this;
+
+ // First store global variables
+ asUINT i;
+ for( i = 0; i < mod->GetGlobalVarCount(); i++ )
+ {
+ const char *name, *nameSpace;
+ int typeId;
+ mod->GetGlobalVar(i, &name, &nameSpace, &typeId);
+ m_root.m_children.push_back(new CSerializedValue(&m_root, name, nameSpace, mod->GetAddressOfGlobalVar(i), typeId));
+ }
+
+ // Second store extra objects
+ for( i = 0; i < m_extraObjects.size(); i++ )
+ m_root.m_children.push_back(new CSerializedValue(&m_root, "", "", m_extraObjects[i].originalObject, m_extraObjects[i].originalTypeId));
+
+ // For the handles that were stored, we need to substitute the stored pointer
+ // that is still pointing to the original object to an internal reference so
+ // it can be restored later on.
+ m_root.ReplaceHandles();
+
+ return 0;
+}
+
+// Retrieve all global variables after reload script.
+int CSerializer::Restore(asIScriptModule *mod)
+{
+ m_mod = mod;
+
+ // The engine must not be destroyed before we're completed, so we'll hold on to a reference
+ mod->GetEngine()->AddRef();
+ if( m_engine ) m_engine->Release();
+ m_engine = mod->GetEngine();
+
+ // First restore extra objects, i.e. the ones that are not directly seen from the module's global variables
+ asUINT i;
+ for( i = 0; i < m_extraObjects.size(); i++ )
+ {
+ SExtraObject &o = m_extraObjects[i];
+ asITypeInfo *type = m_mod->GetTypeInfoByName( o.originalClassName.c_str() );
+ if( type )
+ {
+ for( size_t i2 = 0; i2 < m_root.m_children.size(); i2++ )
+ {
+ if( m_root.m_children[i2]->m_originalPtr == o.originalObject )
+ {
+ // Create a new script object, but don't call its constructor as we will initialize the members.
+ // Calling the constructor may have unwanted side effects if for example the constructor changes
+ // any outside entities, such as setting global variables to point to new objects, etc.
+ void *newPtr = m_engine->CreateUninitializedScriptObject( type );
+ m_root.m_children[i2]->Restore( newPtr, type->GetTypeId() );
+ }
+ }
+ }
+ }
+
+ // Second restore the global variables
+ asUINT varCount = mod->GetGlobalVarCount();
+ for( i = 0; i < varCount; i++ )
+ {
+ const char *name, *nameSpace;
+ int typeId;
+ mod->GetGlobalVar(i, &name, &nameSpace, &typeId);
+
+ CSerializedValue *v = m_root.FindByName(name, nameSpace);
+ if( v )
+ v->Restore(mod->GetAddressOfGlobalVar(i), typeId);
+ }
+
+ // The handles that were restored needs to be
+ // updated to point to their final objects.
+ m_root.RestoreHandles();
+
+ return 0;
+}
+
+void *CSerializer::GetPointerToRestoredObject(void *ptr)
+{
+ return m_root.GetPointerToRestoredObject( ptr );
+}
+
+void CSerializer::AddExtraObjectToStore( asIScriptObject *object )
+{
+ if( !object )
+ return;
+
+ // Check if the object hasn't been included already
+ for( size_t i=0; i < m_extraObjects.size(); i++ )
+ if( m_extraObjects[i].originalObject == object )
+ return;
+
+ SExtraObject o;
+ o.originalObject = object;
+ o.originalClassName = object->GetObjectType()->GetName();
+ o.originalTypeId = object->GetTypeId();
+
+ m_extraObjects.push_back( o );
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////
+
+CSerializedValue::CSerializedValue()
+{
+ Init();
+}
+
+CSerializedValue::CSerializedValue(CSerializedValue *parent, const std::string &name, const std::string &nameSpace, void *ref, int typeId)
+{
+ Init();
+
+ m_name = name;
+ m_nameSpace = nameSpace;
+ m_serializer = parent->m_serializer;
+ Store(ref, typeId);
+}
+
+void CSerializedValue::Init()
+{
+ m_handlePtr = 0;
+ m_restorePtr = 0;
+ m_typeId = 0;
+ m_isInit = false;
+ m_serializer = 0;
+ m_userData = 0;
+ m_originalPtr = 0;
+}
+
+void CSerializedValue::Uninit()
+{
+ m_isInit = false;
+
+ ClearChildren();
+
+ if( m_userData )
+ {
+ CUserType *type = m_serializer->m_userTypes[m_typeName];
+ if( type )
+ type->CleanupUserData(this);
+ m_userData = 0;
+ }
+}
+
+void CSerializedValue::ClearChildren()
+{
+ // If this value is for an object handle that created an object during the restore
+ // then it is necessary to release the handle here, so we won't get a memory leak
+ if( (m_typeId & asTYPEID_OBJHANDLE) && m_children.size() == 1 && m_children[0]->m_restorePtr )
+ {
+ m_serializer->m_engine->ReleaseScriptObject(m_children[0]->m_restorePtr, m_serializer->m_engine->GetTypeInfoById(m_children[0]->m_typeId));
+ }
+
+ for( size_t n = 0; n < m_children.size(); n++ )
+ delete m_children[n];
+ m_children.clear();
+}
+
+CSerializedValue::~CSerializedValue()
+{
+ Uninit();
+}
+
+CSerializedValue *CSerializedValue::FindByName(const std::string &name, const std::string &nameSpace)
+{
+ for( size_t i = 0; i < m_children.size(); i++ )
+ if( m_children[i]->m_name == name &&
+ m_children[i]->m_nameSpace == nameSpace )
+ return m_children[i];
+
+ return 0;
+}
+
+void CSerializedValue::GetAllPointersOfChildren(std::vector<void*> *ptrs)
+{
+ ptrs->push_back(m_originalPtr);
+
+ for( size_t i = 0; i < m_children.size(); ++i )
+ m_children[i]->GetAllPointersOfChildren(ptrs);
+}
+
+CSerializedValue *CSerializedValue::FindByPtr(void *ptr)
+{
+ if( m_originalPtr == ptr )
+ return this;
+
+ for( size_t i = 0; i < m_children.size(); i++ )
+ {
+ CSerializedValue *find = m_children[i]->FindByPtr(ptr);
+ if( find )
+ return find;
+ }
+
+ return 0;
+}
+
+void *CSerializedValue::GetPointerToRestoredObject(void *ptr)
+{
+ if( m_originalPtr == ptr )
+ return m_restorePtr;
+
+ for( size_t i = 0; i < m_children.size(); ++i )
+ {
+ void *ret = m_children[i]->GetPointerToRestoredObject(ptr);
+ if( ret )
+ return ret;
+ }
+
+ return 0;
+}
+
+// find variable by ptr but looking only at those in the references, which will create a new object
+CSerializedValue *CSerializedValue::FindByPtrInHandles(void *ptr)
+{
+ // if this handle created object
+ if( (m_typeId & asTYPEID_OBJHANDLE) && m_children.size() == 1 )
+ {
+ if( m_children[0]->m_originalPtr == ptr )
+ return this;
+ }
+
+ if( !(m_typeId & asTYPEID_OBJHANDLE) )
+ {
+ for( size_t i = 0; i < m_children.size(); i++ )
+ {
+ CSerializedValue *find = m_children[i]->FindByPtrInHandles(ptr);
+ if( find )
+ return find;
+ }
+ }
+
+ return 0;
+}
+
+void CSerializedValue::Store(void *ref, int typeId)
+{
+ m_isInit = true;
+ SetType(typeId);
+ m_originalPtr = ref;
+
+ if( m_typeId & asTYPEID_OBJHANDLE )
+ {
+ m_handlePtr = *(void**)ref;
+ }
+ else if( m_typeId & asTYPEID_SCRIPTOBJECT )
+ {
+ asIScriptObject *obj = (asIScriptObject *)ref;
+ asITypeInfo *type = obj->GetObjectType();
+ SetType(type->GetTypeId());
+
+ // Store children
+ for( asUINT i = 0; i < type->GetPropertyCount(); i++ )
+ {
+ int childId;
+ const char *childName;
+ type->GetProperty(i, &childName, &childId);
+
+ m_children.push_back(new CSerializedValue(this, childName, "", obj->GetAddressOfProperty(i), childId));
+ }
+ }
+ else
+ {
+ int size = m_serializer->m_engine->GetSizeOfPrimitiveType(m_typeId);
+
+ if( size == 0 )
+ {
+ // if it is user type( string, array, etc ... )
+ if( m_serializer->m_userTypes[m_typeName] )
+ m_serializer->m_userTypes[m_typeName]->Store(this, m_originalPtr);
+ else
+ {
+ // POD-types can be stored without need for user type
+ asITypeInfo *type = GetType();
+ if( type && (type->GetFlags() & asOBJ_POD) )
+ size = GetType()->GetSize();
+
+ // It is not necessary to report an error here if it is not a POD-type as that will be done when restoring
+ }
+ }
+
+ if( size )
+ {
+ m_mem.resize(size);
+ memcpy(&m_mem[0], ref, size);
+ }
+ }
+}
+
+void CSerializedValue::Restore(void *ref, int typeId)
+{
+ if( !this || !m_isInit || !ref )
+ return;
+
+ // Verify that the stored type matched the new type of the value being restored
+ if( typeId <= asTYPEID_DOUBLE && typeId != m_typeId ) return; // TODO: We may try to do a type conversion for primitives
+ if( (typeId & ~asTYPEID_MASK_SEQNBR) ^ (m_typeId & ~asTYPEID_MASK_SEQNBR) ) return;
+ asITypeInfo *type = m_serializer->m_engine->GetTypeInfoById(typeId);
+ if( type && m_typeName != type->GetName() ) return;
+
+ // Set the new pointer and type
+ m_restorePtr = ref;
+ SetType(typeId);
+
+ // Restore the value
+ if( m_typeId & asTYPEID_OBJHANDLE )
+ {
+ // if need create objects
+ if( m_children.size() == 1 )
+ {
+ asITypeInfo *ctype = m_children[0]->GetType();
+
+ if( ctype->GetFactoryCount() == 0 )
+ {
+ // There are no factories, so assume the same pointer is going to be used
+ m_children[0]->m_restorePtr = m_handlePtr;
+
+ // Increase the refCount for the object as it will be released upon clean-up
+ m_serializer->m_engine->AddRefScriptObject(m_handlePtr, ctype);
+ }
+ else
+ {
+ // Create a new script object, but don't call its constructor as we will initialize the members.
+ // Calling the constructor may have unwanted side effects if for example the constructor changes
+ // any outside entities, such as setting global variables to point to new objects, etc.
+ void *newObject = m_serializer->m_engine->CreateUninitializedScriptObject(ctype);
+ m_children[0]->Restore(newObject, ctype->GetTypeId());
+ }
+ }
+ }
+ else if( m_typeId & asTYPEID_SCRIPTOBJECT )
+ {
+ asIScriptObject *obj = (asIScriptObject *)ref;
+
+ // Retrieve children
+ for( asUINT i = 0; i < type->GetPropertyCount() ; i++ )
+ {
+ const char *nameProperty;
+ int ptypeId;
+ type->GetProperty(i, &nameProperty, &ptypeId);
+
+ CSerializedValue *var = FindByName(nameProperty, "");
+ if( var )
+ var->Restore(obj->GetAddressOfProperty(i), ptypeId);
+ }
+ }
+ else
+ {
+ if( m_mem.size() )
+ {
+ // POD values can be restored with direct copy
+ memcpy(ref, &m_mem[0], m_mem.size());
+ }
+ else if( m_serializer->m_userTypes[m_typeName] )
+ {
+ // user type restore
+ m_serializer->m_userTypes[m_typeName]->Restore(this, m_restorePtr);
+ }
+ else
+ {
+ std::string str = "Cannot restore type '";
+ str += type->GetName();
+ str += "'";
+ m_serializer->m_engine->WriteMessage("", 0, 0, asMSGTYPE_ERROR, str.c_str());
+ }
+ }
+}
+
+void CSerializedValue::CancelDuplicates(CSerializedValue *from)
+{
+ std::vector<void*> ptrs;
+ from->GetAllPointersOfChildren(&ptrs);
+
+ for( size_t i = 0; i < ptrs.size(); ++i )
+ {
+ CSerializedValue *find = m_serializer->m_root.FindByPtrInHandles(ptrs[i]);
+
+ while( find )
+ {
+ // cancel create object
+ find->ClearChildren();
+
+ // Find next link to this ptr
+ find = m_serializer->m_root.FindByPtrInHandles(ptrs[i]);
+ }
+ }
+}
+
+void CSerializedValue::ReplaceHandles()
+{
+ if( m_handlePtr )
+ {
+ // Find the object that the handle is referring to
+ CSerializedValue *handle_to = m_serializer->m_root.FindByPtr(m_handlePtr);
+
+ // If the object hasn't been stored yet...
+ if( handle_to == 0 )
+ {
+ // Store the object now
+ asITypeInfo *type = GetType();
+ CSerializedValue *need_create = new CSerializedValue(this, m_name, m_nameSpace, m_handlePtr, type->GetTypeId());
+
+ // Make sure all other handles that point to the same object
+ // are updated, so we don't end up creating duplicates
+ CancelDuplicates(need_create);
+
+ m_children.push_back(need_create);
+ }
+ }
+
+ // Replace the handles in the children too
+ for( size_t i = 0; i < m_children.size(); ++i )
+ m_children[i]->ReplaceHandles();
+}
+
+void CSerializedValue::RestoreHandles()
+{
+ if( m_typeId & asTYPEID_OBJHANDLE )
+ {
+ if( m_handlePtr )
+ {
+ // Find the object the handle is supposed to point to
+ CSerializedValue *handleTo = m_serializer->m_root.FindByPtr(m_handlePtr);
+
+ if( m_restorePtr && handleTo && handleTo->m_restorePtr )
+ {
+ asITypeInfo *type = m_serializer->m_engine->GetTypeInfoById(m_typeId);
+
+ // If the handle is already pointing to something it must be released first
+ if( *(void**)m_restorePtr )
+ m_serializer->m_engine->ReleaseScriptObject(*(void**)m_restorePtr, type);
+
+ // Update the internal pointer
+ *(void**)m_restorePtr = handleTo->m_restorePtr;
+
+ // Increase the reference
+ m_serializer->m_engine->AddRefScriptObject(handleTo->m_restorePtr, type);
+ }
+ }
+ else
+ {
+ // If the handle is pointing to something, we must release it to restore the null pointer
+ if( m_restorePtr && *(void**)m_restorePtr )
+ {
+ m_serializer->m_engine->ReleaseScriptObject(*(void**)m_restorePtr, m_serializer->m_engine->GetTypeInfoById(m_typeId));
+ *(void**)m_restorePtr = 0;
+ }
+ }
+ }
+
+ // Do the same for the children
+ for( size_t i = 0; i < m_children.size(); ++i )
+ m_children[i]->RestoreHandles();
+}
+
+void CSerializedValue::SetType(int typeId)
+{
+ m_typeId = typeId;
+
+ asITypeInfo *type = m_serializer->m_engine->GetTypeInfoById(typeId);
+
+ if( type )
+ m_typeName = type->GetName();
+}
+
+asITypeInfo *CSerializedValue::GetType()
+{
+ if( !m_typeName.empty() )
+ {
+ int newTypeId = m_serializer->m_mod->GetTypeIdByDecl(m_typeName.c_str());
+ return m_serializer->m_engine->GetTypeInfoById(newTypeId);
+ }
+
+ return 0;
+}
+
+void CSerializedValue::SetUserData(void *data)
+{
+ m_userData = data;
+}
+
+void *CSerializedValue::GetUserData()
+{
+ return m_userData;
+}
+
+END_AS_NAMESPACE
diff --git a/Source/Scripting/angelscript/add_on/serializer/serializer.h b/Source/Scripting/angelscript/add_on/serializer/serializer.h
new file mode 100644
index 00000000..2d91cbe8
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/serializer/serializer.h
@@ -0,0 +1,193 @@
+//
+// CSerializer
+//
+// This code was based on the CScriptReloader written by FDsagizi
+// http://www.gamedev.net/topic/604890-dynamic-reloading-script/
+//
+
+#ifndef SERIALIZER_H
+#define SERIALIZER_H
+
+#ifndef ANGELSCRIPT_H
+// Avoid having to inform include path if header is already include before
+#include <angelscript.h>
+#endif
+
+#include <vector>
+#include <string>
+#include <map>
+
+BEGIN_AS_NAMESPACE
+
+class CSerializer;
+class CSerializedValue;
+
+// Need for register user types objects
+// string, any, array... for all object
+// user ref type.
+struct CUserType
+{
+ virtual ~CUserType() {};
+ virtual void Store(CSerializedValue *val, void *ptr) = 0;
+ virtual void Restore(CSerializedValue *val, void *ptr) = 0;
+ virtual void CleanupUserData(CSerializedValue * /*val*/) {}
+};
+
+
+class CSerializedValue
+{
+public:
+ CSerializedValue();
+ CSerializedValue(CSerializedValue *parent, const std::string &name, const std::string &nameSpace, void *ref, int typeId);
+ ~CSerializedValue();
+
+ // Save the object and its children
+ void Store(void *ref, int refTypeId);
+
+ // Restore the object and its children
+ void Restore(void *ref, int refTypeId);
+
+ // Set type of this var
+ void SetType(int typeId);
+
+ // Returns the object type for non-primitives
+ asITypeInfo *GetType();
+
+ // Get child by name variable
+ CSerializedValue *FindByName(const std::string &name, const std::string &nameSpace);
+
+ // Find variable by ptr
+ CSerializedValue *FindByPtr(void *ptr);
+
+ // User data
+ void *GetUserData();
+ void SetUserData(void *data);
+
+ // Children, e.g. properties of a script class, or elements
+ // of an array, or object pointed to by a handle unless it
+ // is already a variable)
+ std::vector<CSerializedValue*> m_children;
+
+protected:
+ friend class CSerializer;
+
+ void Init();
+ void Uninit();
+
+ // you first need to save all the objects before you can save references to objects
+ void ReplaceHandles();
+
+ // After the objects has been restored, the handles needs to
+ // be updated to point to the right objects
+ void RestoreHandles();
+
+ // Recursively get all ptrs of the children
+ void GetAllPointersOfChildren(std::vector<void*> *ptrs);
+
+ // may be that the two references refer to the same variable.
+ // But this variable is not available in the global list.
+ // According to this reference will be restores it.
+ // And so two links are not created 2 variables,
+ // it is necessary to cancel the creation of one of them.
+ void CancelDuplicates(CSerializedValue *from);
+
+ // Find variable by ptr but looking only at those in the references, which will create a new object
+ CSerializedValue *FindByPtrInHandles(void *ptr);
+
+ // ptr - is a handle to class
+ void *GetPointerToRestoredObject(void *ptr);
+
+ // Cleanup children
+ void ClearChildren();
+
+ // The serializer object
+ CSerializer *m_serializer;
+
+ // The user data can be used by CUserType to store extra information
+ void *m_userData;
+
+ // The type id of the stored value
+ int m_typeId;
+
+ // For non-primitives the typeId may change if the module is reloaded so
+ // it is necessary to store the type name to determine the new type id
+ std::string m_typeName;
+
+ // Name of variable or property
+ std::string m_name;
+ std::string m_nameSpace;
+
+ // Is initialized
+ bool m_isInit;
+
+ // 'this' pointer to variable.
+ // While storing, this points to the actual variable that was stored.
+ // While restoring, it is just a unique identifier.
+ void *m_originalPtr;
+
+ // where handle references
+ // While storing, this points to the actual object.
+ // While restoring, it is just a unique identifier.
+ void *m_handlePtr;
+
+ // new address object, ie address the restoration
+ // While storing this isn't used.
+ // While restoring it will point to the actual variable/object that is restored.
+ void *m_restorePtr;
+
+ // Serialized data for primitives
+ std::vector<char> m_mem;
+};
+
+
+// This class keeps a list of variables, then restores them after the script is rebuilt.
+// But you have to be careful with the change of signature in classes, or
+// changing the types of objects. You can remove or add variables, functions,
+// methods, but you can not (yet) change the type of variables.
+//
+// You also need to understand that after a rebuild you should get
+// new functions and typeids from the module.
+class CSerializer
+{
+public:
+ CSerializer();
+ ~CSerializer();
+
+ // Add implementation for serializing user types
+ void AddUserType(CUserType *ref, const std::string &name);
+
+ // Store all global variables in the module
+ int Store(asIScriptModule *mod);
+
+ // Restore all global variables after reloading script
+ int Restore(asIScriptModule *mod);
+
+ // Store extra objects that are not seen from the module's global variables
+ void AddExtraObjectToStore(asIScriptObject *object);
+
+ // Return new pointer to restored object
+ void *GetPointerToRestoredObject(void *originalObject);
+
+protected:
+ friend class CSerializedValue;
+
+ CSerializedValue m_root;
+ asIScriptEngine *m_engine;
+ asIScriptModule *m_mod;
+
+ std::map<std::string, CUserType*> m_userTypes;
+
+ struct SExtraObject
+ {
+ asIScriptObject *originalObject;
+ std::string originalClassName;
+ int originalTypeId;
+ };
+
+ std::vector<SExtraObject> m_extraObjects;
+};
+
+
+END_AS_NAMESPACE
+
+#endif
diff --git a/Source/Scripting/angelscript/add_on/weakref/weakref.cpp b/Source/Scripting/angelscript/add_on/weakref/weakref.cpp
new file mode 100644
index 00000000..c1fb11c7
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/weakref/weakref.cpp
@@ -0,0 +1,375 @@
+
+// The CScriptWeakRef class was originally implemented by vroad in March 2013
+
+#include "weakref.h"
+#include <new>
+#include <assert.h>
+#include <string.h> // strstr()
+
+BEGIN_AS_NAMESPACE
+
+static void ScriptWeakRefConstruct(asITypeInfo *type, void *mem)
+{
+ new(mem) CScriptWeakRef(type);
+}
+
+static void ScriptWeakRefConstruct2(asITypeInfo *type, void *ref, void *mem)
+{
+ new(mem) CScriptWeakRef(ref, type);
+
+ // It's possible the constructor raised a script exception, in which case we
+ // need to call the destructor in order to cleanup the memory before returning
+ asIScriptContext *ctx = asGetActiveContext();
+ if( ctx && ctx->GetState() == asEXECUTION_EXCEPTION )
+ reinterpret_cast<CScriptWeakRef*>(mem)->~CScriptWeakRef();
+}
+
+static void ScriptWeakRefDestruct(CScriptWeakRef *obj)
+{
+ obj->~CScriptWeakRef();
+}
+
+static bool ScriptWeakRefTemplateCallback(asITypeInfo *ti, bool &/*dontGarbageCollect*/)
+{
+ asITypeInfo *subType = ti->GetSubType();
+
+ // Weak references only work for reference types
+ if( subType == 0 ) return false;
+ if( !(subType->GetFlags() & asOBJ_REF) ) return false;
+
+ // The subtype shouldn't be a handle
+ if( ti->GetSubTypeId() & asTYPEID_OBJHANDLE )
+ return false;
+
+ // Make sure the type really supports weak references
+ asUINT cnt = subType->GetBehaviourCount();
+ for( asUINT n = 0; n < cnt; n++ )
+ {
+ asEBehaviours beh;
+ subType->GetBehaviourByIndex(n, &beh);
+ if( beh == asBEHAVE_GET_WEAKREF_FLAG )
+ return true;
+ }
+
+ ti->GetEngine()->WriteMessage("weakref", 0, 0, asMSGTYPE_ERROR, "The subtype doesn't support weak references");
+ return false;
+}
+
+CScriptWeakRef::CScriptWeakRef(asITypeInfo *type)
+{
+ m_ref = 0;
+ m_type = type;
+ m_type->AddRef();
+ m_weakRefFlag = 0;
+}
+
+CScriptWeakRef::CScriptWeakRef(const CScriptWeakRef &other)
+{
+ m_ref = other.m_ref;
+ m_type = other.m_type;
+ m_type->AddRef();
+ m_weakRefFlag = other.m_weakRefFlag;
+ if( m_weakRefFlag )
+ m_weakRefFlag->AddRef();
+}
+
+CScriptWeakRef::CScriptWeakRef(void *ref, asITypeInfo *type)
+{
+ m_ref = ref;
+ m_type = type;
+ m_type->AddRef();
+
+ // The given type should be the weakref template instance
+ assert( strcmp(type->GetName(), "weakref") == 0 ||
+ strcmp(type->GetName(), "const_weakref") == 0 );
+
+ // Get the shared flag that will tell us when the object has been destroyed
+ // This is threadsafe as we hold a strong reference to the object
+ m_weakRefFlag = m_type->GetEngine()->GetWeakRefFlagOfScriptObject(m_ref, m_type->GetSubType());
+ if( m_weakRefFlag )
+ m_weakRefFlag->AddRef();
+}
+
+CScriptWeakRef::~CScriptWeakRef()
+{
+ if( m_type )
+ m_type->Release();
+ if( m_weakRefFlag )
+ m_weakRefFlag->Release();
+}
+
+CScriptWeakRef &CScriptWeakRef::operator =(const CScriptWeakRef &other)
+{
+ // Don't do anything if it is the same reference
+ if( m_ref == other.m_ref )
+ return *this;
+
+ // Must not allow changing the type
+ if( m_type != other.m_type )
+ {
+ // We can allow a weakref to be assigned to a const_weakref
+ if( !(strcmp(m_type->GetName(), "const_weakref") == 0 &&
+ strcmp(other.m_type->GetName(), "weakref") == 0 &&
+ m_type->GetSubType() == other.m_type->GetSubType()) )
+ {
+ assert( false );
+ return *this;
+ }
+ }
+
+ m_ref = other.m_ref;
+
+ if( m_weakRefFlag )
+ m_weakRefFlag->Release();
+ m_weakRefFlag = other.m_weakRefFlag;
+ if( m_weakRefFlag )
+ m_weakRefFlag->AddRef();
+
+ return *this;
+}
+
+CScriptWeakRef &CScriptWeakRef::Set(void *newRef)
+{
+ // Release the previous weak ref
+ if( m_weakRefFlag )
+ m_weakRefFlag->Release();
+
+ // Retrieve the new weak ref
+ m_ref = newRef;
+ if( newRef )
+ {
+ m_weakRefFlag = m_type->GetEngine()->GetWeakRefFlagOfScriptObject(newRef, m_type->GetSubType());
+ m_weakRefFlag->AddRef();
+ }
+ else
+ m_weakRefFlag = 0;
+
+ // Release the newRef since we're only supposed to hold a weakref
+ m_type->GetEngine()->ReleaseScriptObject(newRef, m_type->GetSubType());
+
+ return *this;
+}
+
+asITypeInfo *CScriptWeakRef::GetRefType() const
+{
+ return m_type->GetSubType();
+}
+
+bool CScriptWeakRef::operator==(const CScriptWeakRef &o) const
+{
+ if( m_ref == o.m_ref &&
+ m_type == o.m_type )
+ return true;
+
+ // TODO: If type is not the same, we should attempt to do a dynamic cast,
+ // which may change the pointer for application registered classes
+
+ return false;
+}
+
+bool CScriptWeakRef::operator!=(const CScriptWeakRef &o) const
+{
+ return !(*this == o);
+}
+
+// AngelScript: used as '@obj = ref.get();'
+void *CScriptWeakRef::Get() const
+{
+ // If we hold a null handle, then just return null
+ if( m_ref == 0 || m_weakRefFlag == 0 )
+ return 0;
+
+ // Lock on the shared bool, so we can be certain it won't be changed to true
+ // between the inspection of the flag and the increase of the ref count in the
+ // owning object.
+ m_weakRefFlag->Lock();
+ if( !m_weakRefFlag->Get() )
+ {
+ m_type->GetEngine()->AddRefScriptObject(m_ref, m_type->GetSubType());
+ m_weakRefFlag->Unlock();
+ return m_ref;
+ }
+ m_weakRefFlag->Unlock();
+
+ return 0;
+}
+
+bool CScriptWeakRef::Equals(void *ref) const
+{
+ // Release the ref since we'll not keep it
+ m_type->GetEngine()->ReleaseScriptObject(ref, m_type->GetSubType());
+
+ if( m_ref == ref )
+ return true;
+
+ return false;
+}
+
+void RegisterScriptWeakRef_Native(asIScriptEngine *engine)
+{
+ int r;
+
+ // Register a type for non-const handles
+ r = engine->RegisterObjectType("weakref<class T>", sizeof(CScriptWeakRef), asOBJ_VALUE | asOBJ_ASHANDLE | asOBJ_TEMPLATE | asOBJ_APP_CLASS_DAK); assert( r >= 0 );
+
+ r = engine->RegisterObjectBehaviour("weakref<T>", asBEHAVE_CONSTRUCT, "void f(int&in)", asFUNCTION(ScriptWeakRefConstruct), asCALL_CDECL_OBJLAST); assert( r>= 0 );
+ r = engine->RegisterObjectBehaviour("weakref<T>", asBEHAVE_CONSTRUCT, "void f(int&in, T@+)", asFUNCTION(ScriptWeakRefConstruct2), asCALL_CDECL_OBJLAST); assert( r>= 0 );
+ r = engine->RegisterObjectBehaviour("weakref<T>", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(ScriptWeakRefDestruct), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("weakref<T>", asBEHAVE_TEMPLATE_CALLBACK, "bool f(int&in, bool&out)", asFUNCTION(ScriptWeakRefTemplateCallback), asCALL_CDECL); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("weakref<T>", "T@ opImplCast()", asMETHOD(CScriptWeakRef, Get), asCALL_THISCALL); assert(r >= 0);
+ r = engine->RegisterObjectMethod("weakref<T>", "T@ get() const", asMETHODPR(CScriptWeakRef, Get, () const, void*), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("weakref<T>", "weakref<T> &opHndlAssign(const weakref<T> &in)", asMETHOD(CScriptWeakRef, operator=), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("weakref<T>", "weakref<T> &opAssign(const weakref<T> &in)", asMETHOD(CScriptWeakRef, operator=), asCALL_THISCALL); assert(r >= 0);
+ r = engine->RegisterObjectMethod("weakref<T>", "bool opEquals(const weakref<T> &in) const", asMETHODPR(CScriptWeakRef, operator==, (const CScriptWeakRef &) const, bool), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("weakref<T>", "weakref<T> &opHndlAssign(T@)", asMETHOD(CScriptWeakRef, Set), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("weakref<T>", "bool opEquals(const T@) const", asMETHOD(CScriptWeakRef, Equals), asCALL_THISCALL); assert(r >= 0);
+
+ // Register another type for const handles
+ r = engine->RegisterObjectType("const_weakref<class T>", sizeof(CScriptWeakRef), asOBJ_VALUE | asOBJ_ASHANDLE | asOBJ_TEMPLATE | asOBJ_APP_CLASS_DAK); assert( r >= 0 );
+
+ r = engine->RegisterObjectBehaviour("const_weakref<T>", asBEHAVE_CONSTRUCT, "void f(int&in)", asFUNCTION(ScriptWeakRefConstruct), asCALL_CDECL_OBJLAST); assert( r>= 0 );
+ r = engine->RegisterObjectBehaviour("const_weakref<T>", asBEHAVE_CONSTRUCT, "void f(int&in, const T@+)", asFUNCTION(ScriptWeakRefConstruct2), asCALL_CDECL_OBJLAST); assert( r>= 0 );
+ r = engine->RegisterObjectBehaviour("const_weakref<T>", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(ScriptWeakRefDestruct), asCALL_CDECL_OBJLAST); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("const_weakref<T>", asBEHAVE_TEMPLATE_CALLBACK, "bool f(int&in, bool&out)", asFUNCTION(ScriptWeakRefTemplateCallback), asCALL_CDECL); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("const_weakref<T>", "const T@ opImplCast() const", asMETHOD(CScriptWeakRef, Get), asCALL_THISCALL); assert(r >= 0);
+ r = engine->RegisterObjectMethod("const_weakref<T>", "const T@ get() const", asMETHODPR(CScriptWeakRef, Get, () const, void*), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("const_weakref<T>", "const_weakref<T> &opHndlAssign(const const_weakref<T> &in)", asMETHOD(CScriptWeakRef, operator=), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("const_weakref<T>", "const_weakref<T> &opAssign(const const_weakref<T> &in)", asMETHOD(CScriptWeakRef, operator=), asCALL_THISCALL); assert(r >= 0);
+ r = engine->RegisterObjectMethod("const_weakref<T>", "bool opEquals(const const_weakref<T> &in) const", asMETHODPR(CScriptWeakRef, operator==, (const CScriptWeakRef &) const, bool), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("const_weakref<T>", "const_weakref<T> &opHndlAssign(const T@)", asMETHOD(CScriptWeakRef, Set), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("const_weakref<T>", "bool opEquals(const T@) const", asMETHOD(CScriptWeakRef, Equals), asCALL_THISCALL); assert(r >= 0);
+
+ // Allow non-const weak references to be converted to const weak references
+ r = engine->RegisterObjectMethod("const_weakref<T>", "const_weakref<T> &opHndlAssign(const weakref<T> &in)", asMETHOD(CScriptWeakRef, operator=), asCALL_THISCALL); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("const_weakref<T>", "bool opEquals(const weakref<T> &in) const", asMETHODPR(CScriptWeakRef, operator==, (const CScriptWeakRef &) const, bool), asCALL_THISCALL); assert( r >= 0 );
+}
+
+static void ScriptWeakRefConstruct_Generic(asIScriptGeneric *gen)
+{
+ asITypeInfo *ti = *reinterpret_cast<asITypeInfo**>(gen->GetAddressOfArg(0));
+
+ ScriptWeakRefConstruct(ti, gen->GetObject());
+}
+
+static void ScriptWeakRefConstruct2_Generic(asIScriptGeneric *gen)
+{
+ asITypeInfo *ti = *reinterpret_cast<asITypeInfo**>(gen->GetAddressOfArg(0));
+ void *ref = gen->GetArgAddress(1);
+
+ ScriptWeakRefConstruct2(ti, ref, gen->GetObject());
+}
+
+static void ScriptWeakRefDestruct_Generic(asIScriptGeneric *gen)
+{
+ CScriptWeakRef *self = reinterpret_cast<CScriptWeakRef*>(gen->GetObject());
+ self->~CScriptWeakRef();
+}
+
+void CScriptWeakRef_Get_Generic(asIScriptGeneric *gen)
+{
+ CScriptWeakRef *self = reinterpret_cast<CScriptWeakRef*>(gen->GetObject());
+ gen->SetReturnAddress(self->Get());
+}
+
+void CScriptWeakRef_Assign_Generic(asIScriptGeneric *gen)
+{
+ CScriptWeakRef *other = reinterpret_cast<CScriptWeakRef*>(gen->GetArgAddress(0));
+ CScriptWeakRef *self = reinterpret_cast<CScriptWeakRef*>(gen->GetObject());
+ *self = *other;
+ gen->SetReturnAddress(self);
+}
+
+void CScriptWeakRef_Assign2_Generic(asIScriptGeneric *gen)
+{
+ void *other = gen->GetArgAddress(0);
+ CScriptWeakRef *self = reinterpret_cast<CScriptWeakRef*>(gen->GetObject());
+
+ // Must increase the refcount of the object, since Set() will decrease it
+ // If this is not done, the object will be destroyed too early since the
+ // generic interface also automatically decreases the refcount of received handles
+ gen->GetEngine()->AddRefScriptObject(other, self->GetRefType());
+
+ self->Set(other);
+ gen->SetReturnAddress(self);
+}
+
+void CScriptWeakRef_Equals_Generic(asIScriptGeneric *gen)
+{
+ CScriptWeakRef *other = reinterpret_cast<CScriptWeakRef*>(gen->GetArgAddress(0));
+ CScriptWeakRef *self = reinterpret_cast<CScriptWeakRef*>(gen->GetObject());
+ gen->SetReturnByte(*self == *other);
+}
+
+void CScriptWeakRef_Equals2_Generic(asIScriptGeneric *gen)
+{
+ void *other = gen->GetArgAddress(0);
+ CScriptWeakRef *self = reinterpret_cast<CScriptWeakRef*>(gen->GetObject());
+
+ // Must increase the refcount of the object, since Equals() will decrease it
+ // If this is not done, the object will be destroyed too early since the
+ // generic interface also automatically decreases the refcount of received handles
+ gen->GetEngine()->AddRefScriptObject(other, self->GetRefType());
+
+ gen->SetReturnByte(self->Equals(other));
+}
+
+static void ScriptWeakRefTemplateCallback_Generic(asIScriptGeneric *gen)
+{
+ asITypeInfo *ti = *reinterpret_cast<asITypeInfo**>(gen->GetAddressOfArg(0));
+ bool *dontGarbageCollect = *reinterpret_cast<bool**>(gen->GetAddressOfArg(1));
+ *reinterpret_cast<bool*>(gen->GetAddressOfReturnLocation()) = ScriptWeakRefTemplateCallback(ti, *dontGarbageCollect);
+}
+
+void RegisterScriptWeakRef_Generic(asIScriptEngine *engine)
+{
+ int r;
+
+ // Register a type for non-const handles
+ r = engine->RegisterObjectType("weakref<class T>", sizeof(CScriptWeakRef), asOBJ_VALUE | asOBJ_ASHANDLE | asOBJ_TEMPLATE | asOBJ_APP_CLASS_DAK); assert( r >= 0 );
+
+ r = engine->RegisterObjectBehaviour("weakref<T>", asBEHAVE_CONSTRUCT, "void f(int&in)", asFUNCTION(ScriptWeakRefConstruct_Generic), asCALL_GENERIC); assert( r>= 0 );
+ r = engine->RegisterObjectBehaviour("weakref<T>", asBEHAVE_CONSTRUCT, "void f(int&in, T@)", asFUNCTION(ScriptWeakRefConstruct2_Generic), asCALL_GENERIC); assert( r>= 0 );
+ r = engine->RegisterObjectBehaviour("weakref<T>", asBEHAVE_TEMPLATE_CALLBACK, "bool f(int&in, bool&out)", asFUNCTION(ScriptWeakRefTemplateCallback_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("weakref<T>", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(ScriptWeakRefDestruct_Generic), asCALL_GENERIC); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("weakref<T>", "T@ opImplCast()", asFUNCTION(CScriptWeakRef_Get_Generic), asCALL_GENERIC); assert(r >= 0);
+ r = engine->RegisterObjectMethod("weakref<T>", "T@ get() const", asFUNCTION(CScriptWeakRef_Get_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("weakref<T>", "weakref<T> &opHndlAssign(const weakref<T> &in)", asFUNCTION(CScriptWeakRef_Assign_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("weakref<T>", "weakref<T> &opAssign(const weakref<T> &in)", asFUNCTION(CScriptWeakRef_Assign_Generic), asCALL_GENERIC); assert(r >= 0);
+ r = engine->RegisterObjectMethod("weakref<T>", "bool opEquals(const weakref<T> &in) const", asFUNCTION(CScriptWeakRef_Equals_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("weakref<T>", "weakref<T> &opHndlAssign(T@)", asFUNCTION(CScriptWeakRef_Assign2_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("weakref<T>", "bool opEquals(const T@) const", asFUNCTION(CScriptWeakRef_Equals2_Generic), asCALL_GENERIC); assert(r >= 0);
+
+ // Register another type for const handles
+ r = engine->RegisterObjectType("const_weakref<class T>", sizeof(CScriptWeakRef), asOBJ_VALUE | asOBJ_ASHANDLE | asOBJ_TEMPLATE | asOBJ_APP_CLASS_DAK); assert( r >= 0 );
+
+ r = engine->RegisterObjectBehaviour("const_weakref<T>", asBEHAVE_CONSTRUCT, "void f(int&in)", asFUNCTION(ScriptWeakRefConstruct_Generic), asCALL_GENERIC); assert( r>= 0 );
+ r = engine->RegisterObjectBehaviour("const_weakref<T>", asBEHAVE_CONSTRUCT, "void f(int&in, const T@)", asFUNCTION(ScriptWeakRefConstruct2_Generic), asCALL_GENERIC); assert( r>= 0 );
+ r = engine->RegisterObjectBehaviour("const_weakref<T>", asBEHAVE_TEMPLATE_CALLBACK, "bool f(int&in, bool&out)", asFUNCTION(ScriptWeakRefTemplateCallback_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectBehaviour("const_weakref<T>", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(ScriptWeakRefDestruct_Generic), asCALL_GENERIC); assert( r >= 0 );
+
+ r = engine->RegisterObjectMethod("const_weakref<T>", "const T@ opImplCast() const", asFUNCTION(CScriptWeakRef_Get_Generic), asCALL_GENERIC); assert(r >= 0);
+ r = engine->RegisterObjectMethod("const_weakref<T>", "const T@ get() const", asFUNCTION(CScriptWeakRef_Get_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("const_weakref<T>", "const_weakref<T> &opHndlAssign(const const_weakref<T> &in)", asFUNCTION(CScriptWeakRef_Assign_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("const_weakref<T>", "const_weakref<T> &opAssign(const const_weakref<T> &in)", asFUNCTION(CScriptWeakRef_Assign_Generic), asCALL_GENERIC); assert(r >= 0);
+ r = engine->RegisterObjectMethod("const_weakref<T>", "bool opEquals(const const_weakref<T> &in) const", asFUNCTION(CScriptWeakRef_Equals_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("const_weakref<T>", "const_weakref<T> &opHndlAssign(const T@)", asFUNCTION(CScriptWeakRef_Assign2_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("const_weakref<T>", "bool opEquals(const T@) const", asFUNCTION(CScriptWeakRef_Equals2_Generic), asCALL_GENERIC); assert(r >= 0);
+
+ // Allow non-const weak references to be converted to const weak references
+ r = engine->RegisterObjectMethod("const_weakref<T>", "const_weakref<T> &opHndlAssign(const weakref<T> &in)", asFUNCTION(CScriptWeakRef_Assign_Generic), asCALL_GENERIC); assert( r >= 0 );
+ r = engine->RegisterObjectMethod("const_weakref<T>", "bool opEquals(const weakref<T> &in) const", asFUNCTION(CScriptWeakRef_Equals_Generic), asCALL_GENERIC); assert( r >= 0 );
+}
+
+void RegisterScriptWeakRef(asIScriptEngine *engine)
+{
+ if( strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") )
+ RegisterScriptWeakRef_Generic(engine);
+ else
+ RegisterScriptWeakRef_Native(engine);
+}
+
+
+END_AS_NAMESPACE
diff --git a/Source/Scripting/angelscript/add_on/weakref/weakref.h b/Source/Scripting/angelscript/add_on/weakref/weakref.h
new file mode 100644
index 00000000..ac24ea95
--- /dev/null
+++ b/Source/Scripting/angelscript/add_on/weakref/weakref.h
@@ -0,0 +1,58 @@
+#ifndef SCRIPTWEAKREF_H
+#define SCRIPTWEAKREF_H
+
+// The CScriptWeakRef class was originally implemented by vroad in March 2013
+
+#ifndef ANGELSCRIPT_H
+// Avoid having to inform include path if header is already include before
+#include <angelscript.h>
+#endif
+
+
+BEGIN_AS_NAMESPACE
+
+class CScriptWeakRef
+{
+public:
+ // Constructors
+ CScriptWeakRef(asITypeInfo *type);
+ CScriptWeakRef(const CScriptWeakRef &other);
+ CScriptWeakRef(void *ref, asITypeInfo *type);
+
+ ~CScriptWeakRef();
+
+ // Copy the stored value from another weakref object
+ CScriptWeakRef &operator=(const CScriptWeakRef &other);
+
+ // Compare equalness
+ bool operator==(const CScriptWeakRef &o) const;
+ bool operator!=(const CScriptWeakRef &o) const;
+
+ // Sets a new reference
+ CScriptWeakRef &Set(void *newRef);
+
+ // Returns the object if it is still alive
+ // This will increment the refCount of the returned object
+ void *Get() const;
+
+ // Returns true if the contained reference is the same
+ bool Equals(void *ref) const;
+
+ // Returns the type of the reference held
+ asITypeInfo *GetRefType() const;
+
+protected:
+ // These functions need to have access to protected
+ // members in order to call them from the script engine
+ friend void RegisterScriptWeakRef_Native(asIScriptEngine *engine);
+
+ void *m_ref;
+ asITypeInfo *m_type;
+ asILockableSharedBool *m_weakRefFlag;
+};
+
+void RegisterScriptWeakRef(asIScriptEngine *engine);
+
+END_AS_NAMESPACE
+
+#endif \ No newline at end of file
diff --git a/Source/Scripting/angelscript/asarglist.h b/Source/Scripting/angelscript/asarglist.h
new file mode 100644
index 00000000..02a61af3
--- /dev/null
+++ b/Source/Scripting/angelscript/asarglist.h
@@ -0,0 +1,129 @@
+//-----------------------------------------------------------------------------
+// Name: asarglist.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <JSON/jsonhelper.h>
+
+#include <angelscript.h>
+
+#include <list>
+#include <vector>
+
+enum ASArgType {_as_char, _as_int, _as_unsigned, _as_float, _as_double,
+ _as_address, _as_object, _as_bool, _as_string, _as_json };
+
+struct ASArg {
+ ASArgType type;
+ void* data;
+ std::string strData;
+ SimpleJSONWrapper jsonData;
+};
+
+class ASArglist {
+ public:
+ std::vector<ASArg> args;
+ std::list<asBYTE> local_char_copies;
+ std::list<asDWORD> local_int_copies;
+ std::list<asDWORD> local_unsigned_copies;
+ std::list<float> local_float_copies;
+ std::list<double> local_double_copies;
+
+ ASArglist(){}
+
+ unsigned size() const {
+ return (unsigned) args.size();
+ }
+
+ const ASArg& operator[](int which) const{
+ return args[which];
+ }
+
+ void clear() {
+ args.clear();
+ local_char_copies.clear();
+ local_int_copies.clear();
+ local_unsigned_copies.clear();
+ local_float_copies.clear();
+ local_double_copies.clear();
+ }
+
+ void Add(const char &val) {
+ local_char_copies.push_back(val);
+ args.resize(args.size()+1);
+ args.back().type = _as_char;
+ args.back().data = (void*)&(local_char_copies.back());
+ }
+
+ void Add(const bool &val) {
+ local_char_copies.push_back(val);
+ args.resize(args.size()+1);
+ args.back().type = _as_bool;
+ args.back().data = (void*)&(local_char_copies.back());
+ }
+
+ void Add(const int &val) {
+ local_int_copies.push_back(val);
+ args.resize(args.size()+1);
+ args.back().type = _as_int;
+ args.back().data = (void*)&(local_int_copies.back());
+ }
+
+ void Add(const unsigned &val) {
+ local_unsigned_copies.push_back(val);
+ args.resize(args.size()+1);
+ args.back().type = _as_unsigned;
+ args.back().data = (void*)&(local_unsigned_copies.back());
+ }
+
+ void Add(const float &val) {
+ local_float_copies.push_back(val);
+ args.resize(args.size()+1);
+ args.back().type = _as_float;
+ args.back().data = (void*)&(local_float_copies.back());
+ }
+
+ void Add(const double &val) {
+ local_double_copies.push_back(val);
+ args.resize(args.size()+1);
+ args.back().type = _as_double;
+ args.back().data = (void*)&(local_double_copies.back());
+ }
+
+ void AddObject(void *val) {
+ args.resize(args.size()+1);
+ args.back().type = _as_object;
+ args.back().data = val;
+ }
+
+ void AddAddress(void *val) {
+ args.resize(args.size()+1);
+ args.back().type = _as_address;
+ args.back().data = val;
+ }
+
+ void AddString(std::string *str) {
+ args.resize(args.size()+1);
+ args.back().type = _as_string;
+ args.back().data = str;
+ }
+};
diff --git a/Source/Scripting/angelscript/ascollisions.cpp b/Source/Scripting/angelscript/ascollisions.cpp
new file mode 100644
index 00000000..e00cfb70
--- /dev/null
+++ b/Source/Scripting/angelscript/ascollisions.cpp
@@ -0,0 +1,493 @@
+//-----------------------------------------------------------------------------
+// Name: ascollisions.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "ascollisions.h"
+
+#include <Physics/bulletworld.h>
+#include <Physics/bulletcollision.h>
+#include <Physics/bulletcollision.h>
+
+#include <Math/enginemath.h>
+#include <Math/vec3math.h>
+
+#include <Scripting/angelscript/ascontext.h>
+#include <Main/scenegraph.h>
+#include <Objects/object.h>
+
+/*
+ Adapted from http://paulbourke.net/geometry/lineline3d/
+ Calculate the line segment PaPb that is the shortest route between
+ two lines P1P2 and P3P4. Calculate also the values of mua and mub where
+ Pa = P1 + mua (P2 - P1)
+ Pb = P3 + mub (P4 - P3)
+ Return FALSE if no solution exists.
+*/
+vec2 LineLineIntersect(
+ const vec3 &p1,const vec3 &p2,const vec3 &p3,const vec3 &p4)
+{
+ vec3 p13,p43,p21;
+ float d1343,d4321,d1321,d4343,d2121;
+ float numer,denom;
+ const float EPS = 0.000001f;
+
+ p13[0] = p1[0] - p3[0];
+ p13[1] = p1[1] - p3[1];
+ p13[2] = p1[2] - p3[2];
+ p43[0] = p4[0] - p3[0];
+ p43[1] = p4[1] - p3[1];
+ p43[2] = p4[2] - p3[2];
+ if (fabs(p43[0]) < EPS && fabs(p43[1]) < EPS && fabs(p43[2]) < EPS)
+ return(vec2(0.0f));
+ p21[0] = p2[0] - p1[0];
+ p21[1] = p2[1] - p1[1];
+ p21[2] = p2[2] - p1[2];
+ if (fabs(p21[0]) < EPS && fabs(p21[1]) < EPS && fabs(p21[2]) < EPS)
+ return(vec2(0.0f));
+
+ d1343 = p13[0] * p43[0] + p13[1] * p43[1] + p13[2] * p43[2];
+ d4321 = p43[0] * p21[0] + p43[1] * p21[1] + p43[2] * p21[2];
+ d1321 = p13[0] * p21[0] + p13[1] * p21[1] + p13[2] * p21[2];
+ d4343 = p43[0] * p43[0] + p43[1] * p43[1] + p43[2] * p43[2];
+ d2121 = p21[0] * p21[0] + p21[1] * p21[1] + p21[2] * p21[2];
+
+ denom = d2121 * d4343 - d4321 * d4321;
+ if (fabs(denom) < EPS)
+ return(vec2(0.0f));
+ numer = d1343 * d4321 - d1321 * d4343;
+
+ float mua = numer / denom;
+ float mub = (d1343 + d4321 * mua) / d4343;
+
+ return vec2(mua,mub);
+}
+
+vec3 ASLineLineIntersect(vec3 p1, vec3 p2, vec3 p3, vec3 p4)
+{
+ vec2 vec = LineLineIntersect(p1,p2,p3,p4);
+ return vec3(vec[0], vec[1], 0.0f);
+}
+
+
+void ASCollisions::ASGetSlidingSphereCollision(vec3 pos, float radius) {
+ GetSlidingSphereCollision(pos,radius,as_col,SINGLE_SIDED);
+}
+
+void ASCollisions::ASGetSlidingSphereCollisionDoubleSided(vec3 pos, float radius) {
+ GetSlidingSphereCollision(pos,radius,as_col,DOUBLE_SIDED);
+}
+
+void ASCollisions::GetSlidingSphereCollision(vec3 pos,
+ float radius,
+ SphereCollision& col,
+ CollisionSides sides)
+{
+ col.position = pos;
+ col.radius = radius;
+
+ ContactSlideCallback cb;
+ cb.m_collisionFilterMask = btBroadphaseProxy::StaticFilter;
+ if(sides == DOUBLE_SIDED){
+ cb.single_sided = false;
+ }
+
+ BulletWorld* bullet_world = scenegraph->bullet_world_;
+ bullet_world->GetSphereCollisions(col.position, col.radius, cb);
+ col.adjusted_position = bullet_world->ApplySphereSlide(col.position,
+ col.radius,
+ cb.collision_info);
+
+ col.SetFromCallback(cb);
+}
+
+void ASCollisions::GetSlidingScaledSphereCollision(vec3 pos,
+ float radius,
+ vec3 scale,
+ SphereCollision& col)
+{
+ col.position = pos;
+ col.radius = radius;
+
+ ContactSlideCallback cb;
+ cb.m_collisionFilterMask = btBroadphaseProxy::StaticFilter;
+
+ BulletWorld* bullet_world = scenegraph->bullet_world_;
+ bullet_world->GetScaledSphereCollisions(col.position, col.radius,scale, cb);
+ col.adjusted_position = bullet_world->ApplyScaledSphereSlide(col.position,
+ col.radius,
+ scale,
+ cb.collision_info);
+
+ col.SetFromCallback(cb);
+}
+
+void ASCollisions::ASGetScaledSphereCollision(vec3 pos,
+ float radius,
+ vec3 scale)
+{
+ GetScaledSphereCollision(pos,radius,scale,as_col);
+}
+
+void ASCollisions::ASGetScaledSpherePlantCollision(vec3 pos,
+ float radius,
+ vec3 scale)
+{
+ GetScaledSphereCollision(pos,radius,scale,as_col,true);
+ as_col.position = pos;
+ as_col.radius = radius;
+
+ ContactInfoCallback cb;
+ cb.m_collisionFilterMask = btBroadphaseProxy::StaticFilter;
+
+ BulletWorld* bw = scenegraph->plant_bullet_world_;
+ bw->GetScaledSphereCollisions(as_col.position, as_col.radius, scale, cb);
+
+ as_col.SetFromCallback(cb);
+}
+
+void ASCollisions::ASGetSlidingScaledSphereCollision(vec3 pos,
+ float radius,
+ vec3 scale)
+{
+ GetSlidingScaledSphereCollision(pos,radius,scale,as_col);
+}
+
+void ASCollisions::GetScaledSphereCollision(vec3 pos,
+ float radius,
+ vec3 scale,
+ SphereCollision& col,
+ bool plant_world)
+{
+ col.position = pos;
+ col.radius = radius;
+
+ ContactSlideCallback cb;
+ cb.m_collisionFilterMask = btBroadphaseProxy::StaticFilter;
+
+ BulletWorld* bullet_world =
+ plant_world?scenegraph->plant_bullet_world_:scenegraph->bullet_world_;
+ bullet_world->GetScaledSphereCollisions(col.position, col.radius, scale, cb);
+
+ col.SetFromCallback(cb);
+}
+
+void ASCollisions::ASGetCylinderCollision(vec3 pos,
+ float radius,
+ float height)
+{
+ GetCylinderCollision(pos,radius,height,as_col,SINGLE_SIDED);
+}
+
+void ASCollisions::ASGetCylinderCollisionDoubleSided(vec3 pos,
+ float radius,
+ float height)
+{
+ GetCylinderCollision(pos,radius,height,as_col,DOUBLE_SIDED);
+}
+
+void ASCollisions::GetCylinderCollision(vec3 pos,
+ float radius,
+ float height,
+ SphereCollision& col,
+ CollisionSides sides)
+{
+ col.position = pos;
+ col.radius = radius;
+
+ ContactSlideCallback cb;
+ cb.m_collisionFilterMask = btBroadphaseProxy::StaticFilter;
+ if(sides == DOUBLE_SIDED){
+ cb.single_sided = false;
+ }
+
+ BulletWorld* bullet_world = scenegraph->bullet_world_;
+ bullet_world->GetCylinderCollisions(col.position, col.radius, height, cb);
+
+ col.SetFromCallback(cb);
+}
+
+
+void ASCollisions::ASGetSweptSphereCollision(const vec3& pos,
+ const vec3& pos2,
+ float radius)
+{
+ GetSweptSphereCollision(pos,pos2,radius,as_col);
+}
+
+void ASCollisions::ASGetSweptBoxCollision(vec3 pos,
+ vec3 pos2,
+ vec3 dimensions)
+{
+ GetSweptBoxCollision(pos,pos2,dimensions,as_col);
+}
+
+void ASCollisions::GetSweptSphereCollision(const vec3& pos, const vec3& pos2, float radius, SphereCollision& col) {
+ col.radius = radius;
+ SweptSlideCallback cb;
+ cb.m_collisionFilterMask = btBroadphaseProxy::StaticFilter;
+ BulletWorld* bullet_world = scenegraph->bullet_world_;
+ bullet_world->GetSweptSphereCollisions(pos, pos2, col.radius, cb);
+ col.adjusted_position = bullet_world->ApplySphereSlide(pos2, col.radius, cb.collision_info);
+ col.SetFromCallback(pos, pos2, cb);
+}
+
+void ASCollisions::GetSweptBoxCollision(vec3 pos,
+ vec3 pos2,
+ vec3 dimensions,
+ SphereCollision& col)
+{
+ SweptSlideCallback cb;
+ cb.m_collisionFilterMask = btBroadphaseProxy::StaticFilter;
+
+ BulletWorld* bullet_world = scenegraph->bullet_world_;
+ bullet_world->GetSweptBoxCollisions(pos, pos2, dimensions, cb);
+
+ col.SetFromCallback(pos, pos2, cb);
+}
+
+
+void ASCollisions::GetSweptCylinderCollision( vec3 pos,
+ vec3 pos2,
+ float radius,
+ float height,
+ SphereCollision& col,
+ CollisionSides sides )
+{
+ SweptSlideCallback cb;
+ cb.m_collisionFilterMask = btBroadphaseProxy::StaticFilter;
+ if(sides == DOUBLE_SIDED){
+ cb.single_sided = false;
+ }
+
+ BulletWorld* bullet_world = scenegraph->bullet_world_;
+ bullet_world->GetSweptCylinderCollisions(pos, pos2, radius, height, cb);
+
+ col.SetFromCallback(pos, pos2, cb);
+}
+
+void ASCollisions::ASGetSweptCylinderCollision(vec3 pos,
+ vec3 pos2,
+ float radius,
+ float height)
+{
+ GetSweptCylinderCollision(pos,pos2,radius,height,as_col,SINGLE_SIDED);
+}
+
+
+void ASCollisions::ASGetSweptCylinderCollisionDoubleSided(vec3 pos,
+ vec3 pos2,
+ float radius,
+ float height)
+{
+ GetSweptCylinderCollision(pos,pos2,radius,height,as_col,DOUBLE_SIDED);
+}
+
+void SphereCollision::SetFromCallback(const SweptSlideCallback &cb) {
+ contacts.resize(cb.collision_info.contacts.size());
+ for(unsigned i=0; i<contacts.size(); i++){
+ contacts[i].position = cb.collision_info.contacts[i].point;
+ contacts[i].normal = cb.collision_info.contacts[i].normal;
+ contacts[i].custom_normal = cb.collision_info.contacts[i].custom_normal;
+ contacts[i].id = cb.collision_info.contacts[i].obj_id;
+ contacts[i].tri = cb.collision_info.contacts[i].tri;
+ }
+}
+
+void SphereCollision::SetFromCallback(const ContactSlideCallback &cb) {
+ contacts.resize(cb.collision_info.contacts.size());
+ for(unsigned i=0; i<contacts.size(); i++){
+ contacts[i].position = cb.collision_info.contacts[i].point;
+ contacts[i].normal = cb.collision_info.contacts[i].normal;
+ contacts[i].custom_normal = cb.collision_info.contacts[i].custom_normal;
+ contacts[i].id = cb.collision_info.contacts[i].obj_id;
+ contacts[i].tri = cb.collision_info.contacts[i].tri;
+ }
+}
+
+void SphereCollision::SetFromCallback( const ContactInfoCallback &cb) {
+ contacts.resize(cb.contact_info.size());
+ for(unsigned i=0; i<contacts.size(); i++){
+ contacts[i].position = ToVec3(cb.contact_info[i].point);
+ contacts[i].normal = vec3(0.0f);
+ contacts[i].id = -1;
+ BulletObject* obj = cb.contact_info[i].object;
+ if(obj && obj->owner_object){
+ contacts[i].id = obj->owner_object->GetID();
+ }
+ }
+}
+
+void SphereCollision::SetFromCallback( const SimpleRayResultCallbackInfo &cb) {
+ contacts.resize(cb.contact_info.size());
+ for(unsigned i=0; i<contacts.size(); i++){
+ contacts[i].position = cb.contact_info[i].point;
+ contacts[i].normal = vec3(0.0f);
+ contacts[i].id = -1;
+ BulletObject* obj = cb.contact_info[i].object;
+ if(obj && obj->owner_object){
+ contacts[i].id = obj->owner_object->GetID();
+ }
+ }
+}
+
+void SphereCollision::SetFromCallback(
+ const vec3 &pos,
+ const vec3 &pos2,
+ const SweptSlideCallback &cb)
+{
+ SetFromCallback(cb);
+
+ if(contacts.empty()){
+ position = pos2;
+ } else {
+ position = mix(pos,pos2,cb.true_closest_hit_fraction);
+ }
+}
+
+static void ASGetPlantRayCollision(ASCollisions* as_collisions, vec3 start, vec3 end) {
+ as_collisions->as_col.contacts.clear();
+
+ BulletWorld* bullet_world = as_collisions->scenegraph->plant_bullet_world_;
+ SimpleRayResultCallbackInfo cb;
+ bullet_world->CheckRayCollisionInfo(start,end,cb, true);
+ as_collisions->as_col.SetFromCallback(cb);
+}
+
+
+void ASCollisions::AttachToContext(ASContext *as_context) {
+ as_context->RegisterEnum("CollisionSides");
+ as_context->RegisterEnumValue("CollisionSides", "DOUBLE_SIDED", DOUBLE_SIDED);
+ as_context->RegisterEnumValue("CollisionSides", "SINGLE_SIDED", SINGLE_SIDED);
+ as_context->DocsCloseBrace();
+
+ as_context->RegisterObjectType("CollisionPoint", sizeof(CollisionPoint), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS);
+ as_context->RegisterObjectProperty("CollisionPoint","vec3 position",asOFFSET(CollisionPoint,position));
+ as_context->RegisterObjectProperty("CollisionPoint","vec3 normal",asOFFSET(CollisionPoint,normal));
+ as_context->RegisterObjectProperty("CollisionPoint","vec3 custom_normal",asOFFSET(CollisionPoint,custom_normal));
+ as_context->RegisterObjectProperty("CollisionPoint","int id",asOFFSET(CollisionPoint,id));
+ as_context->RegisterObjectProperty("CollisionPoint","int tri",asOFFSET(CollisionPoint,tri));
+ as_context->DocsCloseBrace();
+
+ as_context->RegisterObjectType("SphereCollision", 0, asOBJ_REF | asOBJ_NOHANDLE);
+ as_context->RegisterObjectProperty("SphereCollision","vec3 position",asOFFSET(SphereCollision,position));
+ as_context->RegisterObjectProperty("SphereCollision","vec3 adjusted_position",asOFFSET(SphereCollision,adjusted_position), "Position after collision response is applied");
+ as_context->RegisterObjectProperty("SphereCollision","float radius",asOFFSET(SphereCollision,radius));
+ as_context->RegisterObjectMethod("SphereCollision","int NumContacts()",asMETHOD(SphereCollision, NumContacts), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("SphereCollision","CollisionPoint GetContact(int)",asMETHOD(SphereCollision, GetContact), asCALL_THISCALL);
+ as_context->DocsCloseBrace();
+
+ as_context->RegisterObjectType("ASCollisions", 0, asOBJ_REF | asOBJ_NOHANDLE);
+ as_context->RegisterObjectMethod("ASCollisions","vec3 GetSlidingCapsuleCollision(vec3 start, vec3 end, float radius)",asMETHOD(ASCollisions, GetSlidingCapsuleCollision), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ASCollisions","void GetSlidingSphereCollision(vec3 pos, float radius)",asMETHOD(ASCollisions, ASGetSlidingSphereCollision), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ASCollisions","void GetSlidingSphereCollisionDoubleSided(vec3 pos, float radius)",asMETHOD(ASCollisions, ASGetSlidingSphereCollisionDoubleSided), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ASCollisions","void GetScaledSphereCollision(vec3 pos, float radius, vec3 scale)",asMETHOD(ASCollisions, ASGetScaledSphereCollision), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ASCollisions","void GetScaledSpherePlantCollision(vec3 pos, float radius, vec3 scale)",asMETHOD(ASCollisions, ASGetScaledSpherePlantCollision), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ASCollisions","void GetSlidingScaledSphereCollision(vec3 pos, float radius, vec3 scale)",asMETHOD(ASCollisions, ASGetSlidingScaledSphereCollision), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ASCollisions","void GetCylinderCollision(vec3 pos, float radius, float height)",asMETHOD(ASCollisions, ASGetCylinderCollision), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ASCollisions","void GetCylinderCollisionDoubleSided(vec3 pos, float radius, float height)",asMETHOD(ASCollisions, ASGetCylinderCollisionDoubleSided), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ASCollisions","void GetSweptSphereCollision(const vec3 &in start, const vec3 &in end, float radius)",asMETHOD(ASCollisions, ASGetSweptSphereCollision), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ASCollisions","void GetSweptSphereCollisionCharacters(vec3 start, vec3 end, float radius)",asMETHOD(ASCollisions, ASGetSweptSphereCollisionCharacters), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ASCollisions","void GetSweptCylinderCollision(vec3 start, vec3 end, float radius, float height)",asMETHOD(ASCollisions, ASGetSweptCylinderCollision), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ASCollisions","void GetSweptCylinderCollisionDoubleSided(vec3 start, vec3 end, float radius, float height)",asMETHOD(ASCollisions, ASGetSweptCylinderCollisionDoubleSided), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ASCollisions","void GetSweptBoxCollision(vec3 start, vec3 end, vec3 dimensions)",asMETHOD(ASCollisions, ASGetSweptBoxCollision), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ASCollisions","void CheckRayCollisionCharacters(vec3 start, vec3 end)",asMETHOD(ASCollisions, ASCheckRayCollisionCharacters), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ASCollisions","vec3 GetRayCollision(vec3 start, vec3 end)",asMETHOD(ASCollisions, GetRayCollision), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ASCollisions","void GetObjRayCollision(vec3 start, vec3 end)",asMETHOD(ASCollisions, ASGetObjRayCollision), asCALL_THISCALL);
+ as_context->RegisterObjectMethod("ASCollisions","void GetPlantRayCollision(vec3 start, vec3 end)",asFUNCTION(ASGetPlantRayCollision), asCALL_CDECL_OBJFIRST);
+ as_context->RegisterObjectMethod("ASCollisions","void GetObjectsInSphere(vec3 pos, float radius)",asMETHOD(ASCollisions, ASGetObjectsInSphere), asCALL_THISCALL);
+ as_context->DocsCloseBrace();
+ as_context->RegisterGlobalProperty("ASCollisions col", this, "Used to access collision functions");
+ as_context->RegisterGlobalProperty("SphereCollision sphere_col", &as_col, "Stores results of collision functions");
+
+ as_context->RegisterGlobalFunction("vec3 LineLineIntersect(vec3 start_a, vec3 end_a, vec3 start_b, vec3 end_b)",asFUNCTION(ASLineLineIntersect), asCALL_CDECL, "Get closest point between two line segments");
+}
+
+ASCollisions::ASCollisions( SceneGraph* _scenegraph ) :
+ scenegraph(_scenegraph)
+{}
+
+vec3 ASCollisions::GetSlidingCapsuleCollision( vec3 pos, vec3 pos2, float radius ) {
+ return scenegraph->bullet_world_->CheckCapsuleCollisionSlide(pos,pos2,radius);
+}
+
+vec3 ASCollisions::GetRayCollision( vec3 pos, vec3 pos2) {
+ vec3 point;
+ scenegraph->bullet_world_->CheckRayCollision(pos,pos2,&point);
+ return point;
+}
+
+void ASCollisions::ASGetObjRayCollision(vec3 start, vec3 end) {
+ as_col.contacts.clear();
+ GetObjRayCollision(start, end, as_col);
+}
+
+void ASCollisions::GetObjRayCollision( vec3 pos, vec3 pos2, SphereCollision &col) {
+ BulletWorld* bullet_world = scenegraph->bullet_world_;
+ SimpleRayResultCallbackInfo cb;
+ //SweptSlideCallback cb;
+ bullet_world->CheckRayCollisionInfo(pos,pos2,cb, true);
+ //bullet_world->GetSweptCylinderCollisions(pos, pos2, 1.0f, 1.0f, cb);
+
+
+ col.SetFromCallback(cb);
+}
+
+void ASCollisions::ASGetObjectsInSphere(vec3 pos, float radius){
+ as_col.contacts.clear();
+ GetObjectsInSphere(pos, radius, as_col);
+}
+
+void ASCollisions::GetObjectsInSphere(vec3 pos, float radius, SphereCollision &col){
+ ContactInfoCallback cb;
+ //scenegraph->abstract_bullet_world_->GetSphereCollisions(pos, radius, cb);
+ scenegraph->bullet_world_->GetSphereCollisions(pos, radius, cb);
+ SimpleRayResultCallbackInfo scb;
+
+ for(int i=0; i<cb.contact_info.size(); ++i){
+ //RayContactInfo &ci = scb.contact_info[i];
+ }
+ col.SetFromCallback(cb);
+}
+
+void ASCollisions::ASGetSweptSphereCollisionCharacters( vec3 pos, vec3 pos2, float radius ) {
+ return scenegraph->GetSweptSphereCollisionCharacters( pos, pos2, radius, as_col );
+}
+
+void ASCollisions::ASCheckRayCollisionCharacters( vec3 pos, vec3 pos2 ) {
+ as_col.contacts.clear();
+ as_col.position = pos2;
+ vec3 point, normal;
+ int bone = -1;
+ int hit = scenegraph->CheckRayCollisionCharacters( pos, pos2, &point, &normal, &bone );
+ if(hit != -1){
+ as_col.position = point;
+ as_col.contacts.resize(1);
+ as_col.contacts.back().position = point;
+ as_col.contacts.back().normal = normal;
+ as_col.contacts.back().id = hit;
+ as_col.contacts.back().tri = bone;
+ } else {
+ as_col.contacts.resize(0);
+ }
+}
+
+ASCollisions::~ASCollisions()
+{}
diff --git a/Source/Scripting/angelscript/ascollisions.h b/Source/Scripting/angelscript/ascollisions.h
new file mode 100644
index 00000000..8164f3f4
--- /dev/null
+++ b/Source/Scripting/angelscript/ascollisions.h
@@ -0,0 +1,98 @@
+//-----------------------------------------------------------------------------
+// Name: ascollisions.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+
+#include <vector>
+
+class SceneGraph;
+class ASContext;
+
+struct CollisionPoint {
+ vec3 position;
+ vec3 normal;
+ vec3 custom_normal;
+ int id;
+ int tri;
+};
+
+class SweptSlideCallback;
+class ContactSlideCallback;
+class ContactInfoCallback;
+struct SimpleRayResultCallbackInfo;
+
+struct SphereCollision {
+ float radius;
+ vec3 position;
+ vec3 adjusted_position;
+ std::vector<CollisionPoint> contacts;
+ size_t NumContacts() {return contacts.size();}
+ CollisionPoint GetContact(int which) {return contacts[which];}
+ void SetFromCallback( const vec3 &pos, const vec3 &pos2, const SweptSlideCallback &cb);
+ void SetFromCallback( const SweptSlideCallback &cb);
+ void SetFromCallback( const ContactSlideCallback &cb);
+ void SetFromCallback( const ContactInfoCallback &cb);
+ void SetFromCallback( const SimpleRayResultCallbackInfo &cb);
+};
+
+class ASCollisions {
+public:
+ enum CollisionSides {
+ SINGLE_SIDED,
+ DOUBLE_SIDED
+ };
+ void GetSlidingSphereCollision(vec3 pos, float radius, SphereCollision& col, CollisionSides sides);
+ vec3 GetSlidingCapsuleCollision( vec3 pos, vec3 pos2, float radius );
+ void ASGetSlidingSphereCollision(vec3 pos, float radius);
+ void ASGetSlidingSphereCollisionDoubleSided(vec3 pos, float radius);
+ void ASGetSweptSphereCollision(const vec3& pos, const vec3& pos2, float radius);
+ void ASGetSweptSphereCollisionCharacters(vec3 pos, vec3 pos2, float radius);
+ void GetSweptSphereCollision(const vec3& pos, const vec3& pos2, float radius, SphereCollision& col);
+ void GetSweptBoxCollision(vec3 pos, vec3 pos2, vec3 dimensions, SphereCollision& col);
+ void ASGetSweptBoxCollision(vec3 pos, vec3 pos2, vec3 dimensions);
+ void GetSweptCylinderCollision( vec3 pos, vec3 pos2, float radius, float height, SphereCollision& col, CollisionSides sides );
+ void ASGetSweptCylinderCollisionDoubleSided(vec3 pos, vec3 pos2, float radius, float height);
+ void ASGetSweptCylinderCollision(vec3 pos, vec3 pos2, float radius, float height);
+ void GetCylinderCollision(vec3 pos, float radius, float height, SphereCollision& col, CollisionSides sides);
+ void ASGetCylinderCollision(vec3 pos, float radius, float height);
+ void ASGetCylinderCollisionDoubleSided(vec3 pos, float radius, float height);
+ void GetScaledSphereCollision(vec3 pos, float radius, vec3 scale, SphereCollision& col, bool plant_world = false);
+ void ASGetScaledSphereCollision(vec3 pos, float radius, vec3 scale);
+ void ASGetScaledSpherePlantCollision(vec3 pos, float radius, vec3 scale);
+ void GetSlidingScaledSphereCollision(vec3 pos, float radius, vec3 scale, SphereCollision& col);
+ void ASGetSlidingScaledSphereCollision(vec3 pos, float radius, vec3 scale);
+ void ASCheckRayCollisionCharacters( vec3 pos, vec3 pos2 );
+ vec3 GetRayCollision( vec3 pos, vec3 pos2);
+ void ASGetObjRayCollision( vec3 pos, vec3 pos2);
+ void GetObjRayCollision( vec3 start, vec3 end, SphereCollision& col);
+ void ASGetObjectsInSphere(vec3 pos, float radius);
+ void GetObjectsInSphere(vec3 pos, float radius, SphereCollision& col);
+
+ SceneGraph *scenegraph;
+ SphereCollision as_col;
+ void AttachToContext(ASContext *as_context);
+
+ ASCollisions(SceneGraph* _scenegraph);
+ ~ASCollisions();
+};
diff --git a/Source/Scripting/angelscript/ascontext.cpp b/Source/Scripting/angelscript/ascontext.cpp
new file mode 100644
index 00000000..beffb0f7
--- /dev/null
+++ b/Source/Scripting/angelscript/ascontext.cpp
@@ -0,0 +1,866 @@
+//-----------------------------------------------------------------------------
+// Name: ascontext.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "ascontext.h"
+
+#include <Scripting/angelscript/asfuncs.h>
+#include <Scripting/angelscript/scriptstdstring_extend.h>
+#include <Scripting/angelscript/add_on/scriptstdstring/scriptstdstring.h>
+#include <Scripting/angelscript/add_on/scriptarray/scriptarray.h>
+#include <Scripting/angelscript/add_on/scriptdictionary/scriptdictionary.h>
+#include <Scripting/angelscript/add_on/scripthelper/scripthelper.h>
+#include <Scripting/angelscript/ascrashdump.h>
+#include <Scripting/scriptfile.h>
+#include <Scripting/scriptlogging.h>
+
+#include <Internal/common.h>
+#include <Internal/timer.h>
+#include <Internal/profiler.h>
+
+#include <Compat/fileio.h>
+#include <Utility/assert.h>
+#include <Logging/logdata.h>
+
+#include <angelscript.h>
+
+#include <stack>
+#include <sstream>
+#include <cassert>
+
+extern Timer game_timer;
+extern Timer ui_timer;
+bool asdebugger_enabled = false;
+bool asprofiler_enabled = false;
+
+#ifdef _DEBUG
+const bool kDebugLineCallback = false;
+#else
+const bool kDebugLineCallback = false;
+#endif
+
+int ASContext::CompileScript(const Path& path) {
+ return module.CompileScript(path);
+}
+
+int ASContext::CompileScriptFromText(const std::string &text) {
+ return module.CompileScriptFromText(text);
+}
+
+ASContext::ASContext(const char* name, const ASData& as_data):
+ scenegraph(as_data.scenegraph), gui(as_data.gui), activate_keyboard_events(false), context_name(name)
+{
+ engine = asCreateScriptEngine(ANGELSCRIPT_VERSION);
+ engine->SetEngineProperty(asEP_ALLOW_MULTILINE_STRINGS, true);
+ engine->SetEngineProperty(asEP_ALLOW_UNSAFE_REFERENCES, true);
+ engine->SetEngineProperty(asEP_OPTIMIZE_BYTECODE, true);
+
+ // Configure the script engine with all the functions,
+ // and variables that the script should be able to use.
+ RegisterStdString(engine);
+ RegisterScriptArray(engine,true);
+
+ // The script compiler will write any compiler messages to the callback.
+ engine->SetMessageCallback(asFUNCTION(MessageCallback), &angelscript_error_string, asCALL_CDECL);
+
+ RegisterObjectMethod("array<T>", "void push_back(const T&in)", asMETHOD(CScriptArray, InsertLast), asCALL_THISCALL);
+ RegisterObjectMethod("array<T>", "uint size() const", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL);
+
+ RegisterScriptDictionary(engine);
+ RegisterStdStringUtils(engine);
+
+ AttachStopwatch(this);
+ if(asprofiler_enabled)
+ AttachProfiler(this);
+ else
+ AttachTelemetry(this);
+ AttachMathFuncs(this);
+ Attach3DMathFuncs(this);
+ RegisterStdString_Extend(this);
+ AttachTimer(this);
+ AttachSound(this);
+ AttachParticles(this);
+ AttachSky(this);
+ AttachDebugDraw(this);
+ AttachDecals(this);
+ AttachError(this);
+ AttachLog(this);
+ AttachInfo(this);
+ AttachJSON(this);
+ AttachConfig(this);
+ AttachStringUtil(this);
+ AttachIO(this);
+ AttachLevelDetails(this);
+ AttachModding(this);
+ AttachStorage(this);
+ AttachDebug(this);
+
+ if(engine->RegisterGlobalFunction("void Print(string &in)", asFUNCTION(PrintString), asCALL_CDECL) < 0){
+ DisplayError("Error","Registration of Print function failed");
+ }
+
+ RegisterObjectType("ASContext", 0, asOBJ_REF | asOBJ_NOCOUNT);
+ RegisterObjectMethod("ASContext","void PrintGlobalVars()",asMETHOD(ASContext, PrintGlobalVars), asCALL_THISCALL);
+ DocsCloseBrace();
+ RegisterGlobalProperty("ASContext context", this);
+
+ module.AttachToEngine(engine);
+ module.SetErrorStringDestination(&angelscript_error_string);
+
+ // Create a context that will execute the script.
+ ctx = engine->CreateContext();
+ ctx->SetUserData(this, 0);
+ if( ctx == 0 ) {
+ FatalError("Error","Failed to create the context.");
+ return;
+ }
+
+ if(asdebugger_enabled) {
+ dbg.SetEngine(engine);
+ dbg.SetModule(&module);
+ ctx->SetLineCallback(asMETHOD(ASDebugger, LineCallback), &dbg, asCALL_THISCALL);
+ }
+
+ RegisterAngelscriptContext(name,this);
+}
+
+std::stack<ASContext*> active_context_stack;
+
+void ASContext::run( asIScriptFunction *func, const ASArglist *args_ptr, ASArg *return_val ) {
+ LOGS << "Pushing a new callstack " << this << std::endl;
+ active_context_stack.push(this);
+ ctx->PushState();
+
+ int r = ctx->Prepare(func);
+ if( r < 0 ) {
+ FatalError("Error", "Failed to prepare the context for function: \"%s\"", func->GetName());
+ }
+
+ //Set args
+ if(args_ptr){
+ const ASArglist& args = *args_ptr;
+ for(unsigned i=0; i<args.size(); i++){
+ const ASArgType& type = args[i].type;
+ switch(type){
+ case _as_char:
+ case _as_bool:
+ ctx->SetArgByte(i,*((asBYTE*)args[i].data));
+ break;
+ case _as_int:
+ ctx->SetArgDWord(i,*((asDWORD*)args[i].data));
+ break;
+ case _as_unsigned:
+ ctx->SetArgDWord(i,*((asDWORD*)args[i].data));
+ break;
+ case _as_float:
+ ctx->SetArgFloat(i,*((float*)args[i].data));
+ break;
+ case _as_double:
+ ctx->SetArgDouble(i,*((double*)args[i].data));
+ break;
+ case _as_address:
+ ctx->SetArgAddress(i,args[i].data);
+ break;
+ case _as_object:
+ ctx->SetArgObject(i,args[i].data);
+ break;
+ case _as_string:
+ ctx->SetArgObject(i,args[i].data);
+ //LOGE << "Unhandled _as_string case" << std::endl;
+ break;
+ case _as_json:
+ LOGE << "Unhandled _as_json case" << std::endl;
+ break;
+ }
+ }
+ }
+
+ r = ctx->Execute();
+ if( r != asEXECUTION_FINISHED ) {
+ // The execution didn't finish as we had planned. Determine why.
+ if( r == asEXECUTION_ABORTED )
+ {
+ LOGE << "The script was aborted before it could finish. Probably it timed out." << std::endl;
+ }
+ else if( r == asEXECUTION_EXCEPTION )
+ {
+ LOGE << "The script ended with an exception." << std::endl;
+
+ // Write some information about the script exception
+ asIScriptFunction *func = ctx->GetExceptionFunction();
+ LOGE << "context: " << context_name << std::endl;
+ LOGE << "func: " << func->GetDeclaration() << std::endl;
+ LOGE << "modl: " << func->GetModuleName() << std::endl;
+ LOGE << "sect: " << func->GetScriptSectionName() << std::endl;
+ LOGE << "About to get ctx line number" << std::endl;
+ int line_num = ctx->GetExceptionLineNumber();
+ LOGE << "uncorrected line: " << line_num << std::endl;
+ LineFile corrected_line = module.GetCorrectedLine(line_num);
+ LOGE << "line: " << corrected_line.line_number << std::endl;
+ LOGE << "desc: " << ctx->GetExceptionString() << std::endl;
+ }
+ else if( r == asCONTEXT_NOT_PREPARED )
+ {
+ LOGE << "Context is not prepared" << std::endl;
+ }
+ else if(r != asEXECUTION_SUSPENDED)
+ LOGE << "The script ended for some unforeseen reason (" << r << ")." << std::endl;
+ } else {
+ if(return_val) {
+ switch(return_val->type) {
+ case _as_char:
+ case _as_bool:
+ (*(asBYTE*)return_val->data) = ctx->GetReturnByte();
+ break;
+ case _as_int:
+ (*(asDWORD*)return_val->data) = ctx->GetReturnDWord();
+ break;
+ case _as_unsigned:
+ (*(asDWORD*)return_val->data) = ctx->GetReturnDWord();
+ break;
+ case _as_float:
+ (*(float*)return_val->data) = ctx->GetReturnFloat();
+ break;
+ case _as_double:
+ (*(double*)return_val->data) = ctx->GetReturnDouble();
+ break;
+ case _as_address:
+ return_val->data = ctx->GetReturnAddress();
+ break;
+ case _as_object:
+ return_val->data = ctx->GetReturnObject();
+ break;
+ case _as_string:
+ return_val->strData = *(std::string*)ctx->GetReturnObject();
+ break;
+ case _as_json:
+ return_val->jsonData = *(SimpleJSONWrapper*)ctx->GetReturnObject();
+ break;
+ default:
+ DisplayError("Error","Angelscript function has unknown return type");
+ break;
+ }
+ }
+ }
+ ctx->PopState();
+ LOGS << "Popping a callstack " << this << std::endl;
+ active_context_stack.pop();
+}
+
+ASContext::~ASContext() {
+ if(asdebugger_enabled)
+ ASDebugger::RemoveContext(this);
+ if(asprofiler_enabled)
+ ASProfiler::RemoveContext(this);
+ DeregisterAngelscriptContext(this);
+ angelscript_error_string.clear();
+
+ // We must release the contexts when no longer using them
+ ctx->Release();
+ // Release the engine
+ engine->ShutDownAndRelease();
+}
+
+bool ASContext::LoadScript( const Path& path ) {
+ // Compile the script code
+ int r = CompileScript(path);
+ if( r < 0 ) {
+ DisplayFormatError(_ok, true, "Error", "Could not compile script: %s", path.GetFullPath());
+ return false;
+ }
+
+ if( LoadExpectedFunctions() == false ) {
+ std::stringstream ss;
+ for(unsigned i = 0; i < expected_functions.size(); i++ ) {
+ if(expected_functions[i].mandatory && expected_functions[i].func_ptr == NULL ) {
+ ss << expected_functions[i].definition << std::endl;
+ }
+ }
+ DisplayFormatError(_ok_cancel, true, "Error", "Could not initialize script %s, missing expected functions:\n%s", path.GetFullPath(), ss.str().c_str());
+ return false;
+ }
+
+ if(kDebugLineCallback){
+ ctx->SetLineCallback(asFUNCTION(DebugLineCallback), &module, asCALL_CDECL);
+ }
+
+ current_script = path;
+ if(asdebugger_enabled)
+ ASDebugger::AddContext(this);
+ if(asprofiler_enabled) {
+ ASProfiler::AddContext(this);
+ profiler.SetContext(this);
+ }
+
+ return true;
+}
+
+void ASContext::LoadScriptFromText( const std::string &text ) {
+ // Compile the script code
+ int r = CompileScriptFromText(text);
+ if( r < 0 ) {
+ FatalError("Error","Could not compile script: hard-coded text");
+ return;
+ }
+
+ if( LoadExpectedFunctions() == false ) {
+ std::stringstream ss;
+ for(unsigned i = 0; i < expected_functions.size(); i++ ) {
+ if(expected_functions[i].mandatory && expected_functions[i].func_ptr == NULL ) {
+ ss << expected_functions[i].definition << std::endl;
+ }
+ }
+ DisplayFormatError(_ok_cancel, true, "Error", "Could not initialize script, missing expected functions:\n%s", ss.str().c_str());
+ return;
+ }
+
+ if(kDebugLineCallback){
+ ctx->SetLineCallback(asFUNCTION(DebugLineCallback), &module, asCALL_CDECL);
+ }
+}
+
+bool ASContext::Reload() {
+ PROFILER_ZONE(g_profiler_ctx, "Live update check");
+ if( module.SourceChanged() ) {
+ module.Recompile();
+
+ if( LoadExpectedFunctions() == false ) {
+ std::stringstream ss;
+ for(unsigned i = 0; i < expected_functions.size(); i++ ) {
+ if(expected_functions[i].mandatory && expected_functions[i].func_ptr == NULL ) {
+ ss << expected_functions[i].definition << std::endl;
+ }
+ }
+ DisplayFormatError(_ok_cancel, true, "Error", "Could not fully reload script, missing expected functions:\n%s", ss.str().c_str());
+ return false;
+ }
+ return true;
+ } else {
+ return false;
+ }
+
+}
+
+bool ASContext::LoadExpectedFunctions(){
+ bool ok = true;
+ for( unsigned i = 0; i < expected_functions.size(); i++ ) {
+ asIScriptFunction *func = module.GetFunctionID(expected_functions[i].definition);
+ expected_functions[i].func_ptr = NULL;
+ expected_functions[i].unloaded = false;
+ if(func){
+ expected_functions[i].func_ptr = func;
+ } else if (expected_functions[i].mandatory ) {
+ LOGF << "Could not load mandatory function " << expected_functions[i].definition << std::endl;
+ ok = false;
+ }
+ }
+ return ok;
+}
+
+bool ASContext::HasFunction(ASFunctionHandle handle) {
+ if(handle < 0) {
+ return false;
+ }
+
+ return expected_functions[handle].func_ptr;
+}
+
+bool ASContext::CallScriptFunction(ASFunctionHandle handle, const ASArglist *args, ASArg *return_val) {
+ if( handle >= 0 ) {
+ asIScriptFunction* func = expected_functions[handle].func_ptr;
+ const std::string& definition = expected_functions[handle].definition;
+ if( func ) {
+ profiler.CallScriptFunction(definition.c_str());
+ run(func,args,return_val);
+ profiler.LeaveScriptFunction();
+ return true;
+ } else {
+ if(expected_functions[handle].unloaded) {
+ LOGE << "Expected function " << expected_functions[handle].definition << " was not pre-loaded." << std::endl;
+ }
+ return false;
+ }
+ } else {
+ LOGF << "Invalid handle" << std::endl;
+ return false;
+ }
+}
+
+bool ASContext::HasFunction(const std::string &function_definition) {
+ asIScriptFunction* func = module.GetFunctionID(function_definition);
+ return func != NULL;
+}
+
+bool ASContext::CallScriptFunction( const std::string &function_name, const ASArglist *args, ASArg *return_val, bool fail_message) {
+ // Find the function id for the function we want to execute.
+ asIScriptFunction* func = module.GetFunctionID(function_name);
+ if( !func )
+ {
+ if(fail_message){
+ ErrorResponse response =
+ DisplayError("Error",("Function: "+function_name+" not found in file: \""+
+ module.GetScriptPath().GetFullPath()+"\".").c_str(), _ok_cancel_retry);
+
+ if(response == _retry){
+ if(module.SourceChanged()) {
+ while(Reload() == false) {
+ }
+ return CallScriptFunction(function_name, args, return_val, fail_message);
+ }
+ }
+ }
+ return false;
+ }
+
+ run(func, args, return_val);
+ return true;
+}
+
+void ASContext::Execute( const std::string &code, bool newContext ) {
+ if( newContext ) {
+ ExecuteString(engine, code.c_str(), module.GetInternalScriptModule(), (asIScriptContext*)0);
+ }
+ else {
+ ExecuteString(engine, code.c_str(), module.GetInternalScriptModule(), ctx);
+ }
+}
+
+void ASContext::RegisterGlobalFunction( const char* declaration, const asSFuncPtr& funcPointer, asDWORD callconv, const char* comment ) {
+ angelscript_error_string.clear();
+
+ int r = engine->RegisterGlobalFunction(declaration, funcPointer, callconv);
+ if(r<0){
+ FatalError("Error", "Error registering function: \"%s\"\n%s", declaration, angelscript_error_string.c_str());
+ }
+ const int BUF_SIZE = 512;
+ char doc_buf[BUF_SIZE];
+ if(comment){
+ FormatString(doc_buf, BUF_SIZE, "%s; // %s\n", declaration, comment);
+ } else {
+ FormatString(doc_buf, BUF_SIZE, "%s;\n", declaration);
+ }
+ documentation += doc_buf;
+}
+
+void ASContext::RegisterGlobalFunctionThis( const char* declaration, const asSFuncPtr& funcPointer, asDWORD callconv, void* ptr, const char* comment ) {
+ angelscript_error_string.clear();
+
+ int r = engine->RegisterGlobalFunction(declaration, funcPointer, callconv, ptr);
+ if(r<0){
+ FatalError("Error", "Error registering function: \"%s\"\n%s", declaration, angelscript_error_string.c_str());
+ }
+ const int BUF_SIZE = 512;
+ char doc_buf[BUF_SIZE];
+ if(comment){
+ FormatString(doc_buf, BUF_SIZE, "%s; // %s\n", declaration, comment);
+ } else {
+ FormatString(doc_buf, BUF_SIZE, "%s;\n", declaration);
+ }
+ documentation += doc_buf;
+}
+
+void ASContext::RegisterGlobalProperty( const char* declaration, void* pointer, const char* comment ) {
+ int r = engine->RegisterGlobalProperty(declaration, pointer);
+ if(r<0){
+ FatalError("Error", "Error registering property: \"%s\"", declaration);
+ }
+ const int BUF_SIZE = 512;
+ char doc_buf[BUF_SIZE];
+ if(comment){
+ FormatString(doc_buf, BUF_SIZE, "%s; // %s\n", declaration, comment);
+ } else {
+ FormatString(doc_buf, BUF_SIZE, "%s;\n", declaration);
+ }
+ documentation += doc_buf;
+}
+
+void ASContext::RegisterObjectType( const char *obj, int byteSize, asDWORD flags, const char *comment ) {
+ int r = engine->RegisterObjectType(obj, byteSize, flags);
+ if(r<0){
+ const char* error;
+ switch(r){
+ case asINVALID_ARG: error = "asINVALID_ARG"; break;
+ case asINVALID_NAME: error = "asINVALID_NAME"; break;
+ case asALREADY_REGISTERED: error = "asALREADY_REGISTERED"; break;
+ case asNAME_TAKEN: error = "asNAME_TAKEN"; break;
+ case asLOWER_ARRAY_DIMENSION_NOT_REGISTERED: error = "asLOWER_ARRAY_DIMENSION_NOT_REGISTERED"; break;
+ case asINVALID_TYPE: error = "asINVALID_TYPE"; break;
+ case asNOT_SUPPORTED: error = "asNOT_SUPPORTED"; break;
+ }
+ FatalError("Error", "Error registering type \"%s\" %s", obj, error);
+ }
+ const int BUF_SIZE = 512;
+ char doc_buf[BUF_SIZE];
+ if(comment){
+ FormatString(doc_buf, BUF_SIZE, "class %s { // %s\n", obj, comment);
+ } else {
+ FormatString(doc_buf, BUF_SIZE, "class %s {\n", obj);
+ }
+ documentation += doc_buf;
+}
+
+void ASContext::ExportDocs(const char* path) {
+ FILE *file = my_fopen(path, "w");
+ if(file) {
+ fprintf(file, "//Mandatory functions in script\n");
+ for( unsigned i = 0; i < expected_functions.size(); i++ ) {
+ if(expected_functions[i].mandatory) {
+ fprintf(file, "%s\n", expected_functions[i].definition.c_str());
+ }
+ }
+ fprintf(file, "\n//Optional functions in script\n");
+ for( unsigned i = 0; i < expected_functions.size(); i++ ) {
+ if(expected_functions[i].mandatory == false) {
+ fprintf(file, "%s\n", expected_functions[i].definition.c_str());
+ }
+ }
+ fprintf(file, "\n//Interface\n");
+ fwrite(documentation.c_str(), sizeof(char), documentation.length(), file);
+ fclose(file);
+ }
+}
+
+void ASContext::RegisterFuncdef(const char *declaration, const char* comment) {
+ int r = engine->RegisterFuncdef(declaration);
+ if(r<0){
+ const char* error;
+ switch(r){
+ case asWRONG_CONFIG_GROUP: error = "asWRONG_CONFIG_GROUP"; break;
+ case asNOT_SUPPORTED: error = "asNOT_SUPPORTED"; break;
+ case asINVALID_TYPE: error = "asINVALID_TYPE"; break;
+ case asINVALID_DECLARATION: error = "asINVALID_DECLARATION"; break;
+ case asNAME_TAKEN: error = "asNAME_TAKEN"; break;
+ case asWRONG_CALLING_CONV: error = "asWRONG_CALLING_CONV"; break;
+ }
+ FatalError("Error", "Error registering funcdef of \"%s\" %s", declaration, error);
+ }
+ const int BUF_SIZE = 512;
+ char doc_buf[BUF_SIZE];
+ if(comment){
+ FormatString(doc_buf, BUF_SIZE, " %s; // %s\n", declaration, comment);
+ } else {
+ FormatString(doc_buf, BUF_SIZE, " %s;\n", declaration);
+ }
+ documentation += doc_buf;
+}
+
+void ASContext::RegisterObjectMethod( const char *obj, const char *declaration, const asSFuncPtr &funcPointer, asDWORD callConv, const char* comment ) {
+ int r = engine->RegisterObjectMethod(obj, declaration, funcPointer, callConv);
+ if(r<0){
+ const char* error;
+ switch(r){
+ case asWRONG_CONFIG_GROUP: error = "asWRONG_CONFIG_GROUP"; break;
+ case asNOT_SUPPORTED: error = "asNOT_SUPPORTED"; break;
+ case asINVALID_TYPE: error = "asINVALID_TYPE"; break;
+ case asINVALID_DECLARATION: error = "asINVALID_DECLARATION"; break;
+ case asNAME_TAKEN: error = "asNAME_TAKEN"; break;
+ case asWRONG_CALLING_CONV: error = "asWRONG_CALLING_CONV"; break;
+ }
+ FatalError("Error", "Error registering method of \"%s\" \"%s\" %s", obj, declaration, error);
+ }
+ const int BUF_SIZE = 512;
+ char doc_buf[BUF_SIZE];
+ if(comment){
+ FormatString(doc_buf, BUF_SIZE, " %s; // %s\n", declaration, comment);
+ } else {
+ FormatString(doc_buf, BUF_SIZE, " %s;\n", declaration);
+ }
+ documentation += doc_buf;
+}
+
+void ASContext::RegisterObjectProperty( const char *obj, const char *declaration, int byteOffset, const char* comment ) {
+ int r = engine->RegisterObjectProperty(obj, declaration, byteOffset);
+ if(r<0){
+ const char* error;
+ switch(r){
+ case asWRONG_CONFIG_GROUP: error = "asWRONG_CONFIG_GROUP"; break;
+ case asINVALID_OBJECT: error = "asINVALID_OBJECT"; break;
+ case asINVALID_TYPE: error = "asINVALID_TYPE"; break;
+ case asINVALID_DECLARATION: error = "asINVALID_DECLARATION"; break;
+ case asNAME_TAKEN: error = "asNAME_TAKEN"; break;
+ }
+ FatalError("Error", "Error registering property of \"%s\" \"%s\" %s", obj, declaration, error);
+ }
+ const int BUF_SIZE = 512;
+ char doc_buf[BUF_SIZE];
+ FormatString(doc_buf, BUF_SIZE, " %s;\n", declaration);
+ documentation += doc_buf;
+}
+
+void ASContext::RegisterObjectBehaviour( const char *obj, asEBehaviours behaviour, const char *declaration, const asSFuncPtr &funcPointer, asDWORD callConv, const char* comment ) {
+ int r = engine->RegisterObjectBehaviour(obj, behaviour, declaration, funcPointer, callConv);
+ if(r<0){
+ FatalError("Error", "Error registering object behaviour: \"%s\"", obj);
+ }
+ const int BUF_SIZE = 512;
+ char doc_buf[BUF_SIZE];
+ if(comment){
+ FormatString(doc_buf, BUF_SIZE, " %s; // %s\n", declaration, comment);
+ } else {
+ FormatString(doc_buf, BUF_SIZE, " %s;\n", declaration);
+ }
+ documentation += doc_buf;
+}
+
+ASFunctionHandle ASContext::RegisterExpectedFunction( const std::string &function_decl, bool mandatory ) {
+ //First check to see if we already have this function registered.
+ for(unsigned i = 0; i < expected_functions.size(); i++) {
+ if( expected_functions[i].definition == function_decl ) {
+ if( expected_functions[i].mandatory == false ) {
+ expected_functions[i].mandatory = mandatory;
+ }
+ return i;
+ }
+ }
+
+ ASExpectedFunction f;
+
+ f.definition = function_decl;
+ f.mandatory = mandatory;
+ f.func_ptr = NULL;
+ f.unloaded = true;
+
+ int32_t id = expected_functions.push_back(f);
+ if(id >= 0) {
+ return id;
+ } else {
+ LOGF << "Not enough slots for external functions" << std::endl;
+ return -1;
+ }
+}
+
+void ASContext::ResetGlobals() {
+ module.ResetGlobals();
+}
+
+void ASContext::PrintGlobalVars() {
+ module.PrintGlobalVars();
+}
+
+void ASContext::SaveGlobalVars() {
+ module.SaveGlobalVars();
+}
+
+void ASContext::LoadGlobalVars() {
+ module.LoadGlobalVars();
+}
+
+void *ASContext::GetVarPtr(const char* name) {
+ return module.GetVarPtr(name);
+}
+
+void *ASContext::GetArrayVarPtr( const std::string& name, int index) {
+ CScriptArray* array = (CScriptArray*)GetVarPtr(name.c_str());
+ return array->At(index);
+}
+
+std::string ASContext::GetCallstack() {
+ std::ostringstream oss;
+ // Show the call stack
+ for( asUINT n = 0; n < ctx->GetCallstackSize(); n++ )
+ {
+ asIScriptFunction *func;
+ const char *scriptSection;
+ int line, column;
+ func = ctx->GetFunction(n);
+ line = ctx->GetLineNumber(n, &column, &scriptSection);
+ LineFile lf = module.GetCorrectedLine(line);
+ oss << lf.file << " line " << lf.line_number << " \"" << func->GetDeclaration() << "\"\n";
+ }
+ return oss.str();
+}
+
+void ASContext::DumpCallstack( std::ostream& out ) {
+ module.LogGlobalVars();
+ asUINT sz = ctx->GetCallstackSize();
+ for( asUINT n = 0; n < sz; n++ ) {
+ asIScriptFunction *func;
+ const char *scriptSection;
+ int line, column;
+ func = ctx->GetFunction(n);
+ line = ctx->GetLineNumber(n, &column, &scriptSection);
+ LineFile lf = module.GetCorrectedLine(line);
+
+ out << lf.file << " line " << lf.line_number << " \"" << func->GetDeclaration() << "\"\n";
+ }
+}
+
+void ASContext::LogCallstack( ) {
+}
+
+void ASContext::RegisterEnum( const char* declaration ) {
+ int r = engine->RegisterEnum(declaration);
+ if(r<0){
+ FatalError("Error", "Error registering enum: \"%s\"\n%s",declaration, angelscript_error_string.c_str());
+ }
+ const int BUF_SIZE = 512;
+ char doc_buf[BUF_SIZE];
+ FormatString(doc_buf, BUF_SIZE, "enum %s {\n", declaration);
+ documentation += doc_buf;
+}
+
+void ASContext::RegisterEnumValue( const char* enum_declaration, const char* enum_val_string, int enum_val ) {
+ int r = engine->RegisterEnumValue(enum_declaration, enum_val_string, enum_val);
+ if(r<0){
+ FatalError("Error", "Error registering enum: \"%s::%s\"\n%s",
+ enum_declaration, enum_val_string, angelscript_error_string.c_str());
+ }
+ const int BUF_SIZE = 512;
+ char doc_buf[BUF_SIZE];
+ FormatString(doc_buf, BUF_SIZE, " %s = %d,\n", enum_val_string, enum_val);
+ documentation += doc_buf;
+}
+
+bool ASContext::TypeExists( const std::string& decl ) {
+ return engine->GetTypeIdByDecl(decl.c_str()) >= 0;
+}
+
+void ASContext::DocsCloseBrace() {
+ documentation += "};\n";
+}
+
+void ASContext::ActivateKeyboardEvents() {
+ activate_keyboard_events = true;
+}
+
+void ASContext::DeactivateKeyboardEvents() {
+ activate_keyboard_events = false;
+}
+
+bool ASContext::IsKeyboardEventActivated() {
+ return activate_keyboard_events;
+}
+
+const int DEBUG_LINE_INFO_SIZE = 512;
+char debug_line_info[DEBUG_LINE_INFO_SIZE];
+
+void DebugLineCallback(asIScriptContext *ctx, ASModule *asmod) {
+ const char *scriptSection;
+ int line = ctx->GetLineNumber(0, 0, &scriptSection);
+ LineFile lf = asmod->GetCorrectedLine(line);
+ FormatString(debug_line_info, DEBUG_LINE_INFO_SIZE, "Executing line %d in %s\n", lf.line_number, lf.file.GetFullPath());
+}
+
+std::pair<Path,int> ASContext::GetCallFile()
+{
+ const char *scriptSection;
+ int line, column;
+ line = ctx->GetLineNumber(0, &column, &scriptSection);
+ LineFile lf = module.GetCorrectedLine(line);
+
+ return std::pair<Path,int>(lf.file,lf.line_number);
+}
+
+std::string ASContext::GetASFunctionNameFromMPState(uint32_t state) {
+ if (HasFunction(mpCallBacks[state])) {
+ ASFunctionHandle handle = mpCallBacks[state];
+
+ return expected_functions[handle].definition;
+ }
+
+ return "";
+}
+
+void ASContext::RegisterMPStateCallback(uint32_t state, const std::string & callbackFunction) {
+ if (mpCallBacks.find(state) != mpCallBacks.end()) {
+ return;
+ }
+
+
+ ASFunctionHandle handle = RegisterExpectedFunction(callbackFunction, false);
+
+ asIScriptFunction *func = module.GetFunctionID(expected_functions[handle].definition);
+
+ if (func != nullptr) {
+ mpCallBacks[state] = handle;
+ LOGI << " STATE: " << state << " is now connected to " << callbackFunction << " via expected function handle: " << handle << std::endl;
+ LoadExpectedFunctions();
+ }
+ else {
+ LOGE << "FAILED TO BIND YOUR STATE: " << state << " TO " << callbackFunction << ". CHECK DEFINITION OF FUNCTION AND SCOPE OF CONTEXT" << std::endl;
+ }
+}
+
+bool ASContext::CallMPCallBack(uint32_t state, const std::vector<char>& data) {
+ if (mpCallBacks.find(state) != mpCallBacks.end()) {
+ if (HasFunction(mpCallBacks[state])) {
+ ASArglist args;
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<uint8>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+ array->Reserve(data.size());
+ for (int i = 0, len = data.size(); i < len; ++i) {
+ array->InsertLast(const_cast<char*>(&data[i]));
+ }
+
+ args.AddAddress((void*)array);
+
+ CallScriptFunction(mpCallBacks[state], &args);
+
+ array->Release();
+
+ return true;
+ }
+ else {
+ LOGE << this << " Failed to call function, state does not have a binded function. Handle ID: " << mpCallBacks[state] << std::endl;
+ return false;
+ }
+
+ return false;
+ }
+
+ return false;
+}
+
+bool ASContext::CallMPCallBack(uint32_t state, const std::vector<unsigned int>& data) {
+ if (mpCallBacks.find(state) != mpCallBacks.end()) {
+ if (HasFunction(mpCallBacks[state])) {
+ ASArglist args;
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<uint>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+ array->Reserve(data.size());
+ for (int i = 0, len = data.size(); i < len; ++i) {
+ array->InsertLast((void*)&data[i]);
+ }
+
+ args.AddAddress((void*)array);
+
+ LOGD << "Calling function: " << GetASFunctionNameFromMPState(state) << std::endl;
+
+ CallScriptFunction(mpCallBacks[state], &args);
+
+ array->Release();
+
+ return true;
+ }
+ else {
+ LOGE << this << " Failed to call function, state does not have a binded function. Handle ID: " << mpCallBacks[state] << std::endl;
+ return false;
+ }
+ LOGI << "MPCALLBACKS are aware of:" << state << " but not ASCONTEXT" << std::endl;
+ return false;
+ }
+ LOGI << "MPCALLBACKS are NOT aware of:" << state << std::endl;
+ return false;
+}
+
+asIScriptEngine* ASContext::GetEngine() {
+ return engine;
+}
diff --git a/Source/Scripting/angelscript/ascontext.h b/Source/Scripting/angelscript/ascontext.h
new file mode 100644
index 00000000..037faef6
--- /dev/null
+++ b/Source/Scripting/angelscript/ascontext.h
@@ -0,0 +1,163 @@
+//-----------------------------------------------------------------------------
+// Name: ascontext.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Scripting/angelscript/asmodule.h>
+#include <Scripting/angelscript/asarglist.h>
+#include <Scripting/angelscript/asdebugger.h>
+#include <Scripting/angelscript/asprofiler.h>
+
+#include <Internal/integer.h>
+#include <Internal/path.h>
+
+#include <Utility/fixed_array.h>
+
+#include <angelscript.h>
+
+#include <unordered_map>
+#include <string>
+
+class asIScriptContext;
+class asIScriptEngine;
+class asIScriptFunction;
+class SceneGraph;
+class GUI;
+
+typedef int32_t ASFunctionHandle;
+
+struct ASExpectedFunction {
+ std::string definition;
+ asIScriptFunction* func_ptr;
+ bool mandatory;
+ bool unloaded;
+};
+
+struct ASData {
+ SceneGraph* scenegraph;
+ GUI* gui;
+ ASData():scenegraph(NULL), gui(NULL) {}
+};
+
+class ASContext {
+public:
+ std::string angelscript_error_string;
+
+ asIScriptContext *ctx;
+ asIScriptEngine *engine;
+ ASDebugger dbg;
+ ASProfiler profiler;
+ ASModule module;
+ bool activate_keyboard_events;
+
+ std::string documentation;
+
+ GUI* gui;
+private:
+ std::unordered_map<uint32_t, ASFunctionHandle> mpCallBacks;
+
+ SceneGraph* scenegraph;
+
+ std::string context_name;
+
+ fixed_array<ASExpectedFunction, 64> expected_functions;
+
+public:
+ bool LoadExpectedFunctions();
+ ASFunctionHandle RegisterExpectedFunction( const std::string &function_decl, bool mandatory );
+ Path current_script;
+
+ ASContext(const char* name, const ASData& as_data);
+ ~ASContext();
+ void run( asIScriptFunction *func, const ASArglist *args_ptr, ASArg *return_val );
+ bool LoadScript( const Path& path );
+
+ bool HasFunction(ASFunctionHandle handle);
+ bool CallScriptFunction(ASFunctionHandle handle, const ASArglist *args = NULL, ASArg *return_val = NULL);
+ bool HasFunction(const std::string& function_definition);
+ bool CallScriptFunction(const std::string &function_name, const ASArglist* args = NULL, ASArg *return_val = NULL, bool fail_message = true);
+
+ int CompileScript(const Path& path);
+
+ void RegisterEnum( const char* declaration );
+ void RegisterEnumValue( const char* enum_declaration, const char* enum_val_string, int enum_val );
+ void RegisterGlobalFunction( const char* declaration,
+ const asSFuncPtr& funcPointer,
+ asDWORD callconv,
+ const char* comment = NULL);
+ void RegisterGlobalFunctionThis( const char* declaration,
+ const asSFuncPtr& funcPointer,
+ asDWORD callconv,
+ void* ptr,
+ const char* comment = NULL );
+ void RegisterGlobalProperty( const char* declaration,
+ void* pointer,
+ const char* comment = NULL);
+ void RegisterObjectType( const char *obj,
+ int byteSize,
+ asDWORD flags,
+ const char* comment = NULL);
+ void RegisterFuncdef(const char *declaration, const char* comment = NULL);
+ void RegisterObjectMethod( const char *obj,
+ const char *declaration,
+ const asSFuncPtr &funcPointer,
+ asDWORD callConv,
+ const char* comment = NULL);
+ void RegisterObjectProperty( const char *obj,
+ const char *declaration,
+ int byteOffset,
+ const char* comment = NULL);
+ void RegisterObjectBehaviour( const char *obj,
+ asEBehaviours behaviour,
+ const char *declaration,
+ const asSFuncPtr &funcPointer,
+ asDWORD callConv,
+ const char* comment = NULL);
+ void ResetGlobals();
+ void Recompile();
+ void PrintGlobalVars();
+ void LoadGlobalVars();
+ void SaveGlobalVars();
+ std::string GetCallstack();
+ void DumpCallstack(std::ostream& out);
+ void LogCallstack();
+ void Execute( const std::string &code, bool newContext = false );
+ void *GetVarPtr(const char* name);
+ void *GetArrayVarPtr( const std::string& name, int index);
+ int CompileScriptFromText(const std::string &text);
+ void LoadScriptFromText( const std::string &text );
+ bool TypeExists( const std::string& decl );
+ bool Reload();
+ void ExportDocs(const char* path);
+ void DocsCloseBrace();
+ void ActivateKeyboardEvents();
+ void DeactivateKeyboardEvents();
+ bool IsKeyboardEventActivated();
+ std::pair<Path,int> GetCallFile();
+ std::string GetASFunctionNameFromMPState(uint32_t state);
+ void RegisterMPStateCallback(uint32_t state, const std::string &callbackFunction);
+ bool CallMPCallBack(uint32_t state, const std::vector<char> &data);
+ bool CallMPCallBack(uint32_t state, const std::vector<unsigned int> &data);
+ asIScriptEngine* GetEngine();
+};
+
+void DebugLineCallback(asIScriptContext *ctx, ASModule *asmod);
diff --git a/Source/Scripting/angelscript/ascrashdump.cpp b/Source/Scripting/angelscript/ascrashdump.cpp
new file mode 100644
index 00000000..4f43def0
--- /dev/null
+++ b/Source/Scripting/angelscript/ascrashdump.cpp
@@ -0,0 +1,95 @@
+//-----------------------------------------------------------------------------
+// Name: ascrashdump.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "ascrashdump.h"
+
+#include <Scripting/angelscript/ascontext.h>
+#include <Compat/fileio.h>
+#include <Logging/logdata.h>
+#include <Internal/filesystem.h>
+
+struct AngelScriptContext
+{
+ AngelScriptContext();
+ bool allocated;
+ char name[64];
+ ASContext *context;
+};
+
+AngelScriptContext::AngelScriptContext() : allocated(false)
+{
+
+}
+
+static const int CONTEXT_COUNT = 16;
+static AngelScriptContext contexts[CONTEXT_COUNT];
+
+void RegisterAngelscriptContext( const char* name, ASContext* ascontext )
+{
+ for( int i = 0; i < CONTEXT_COUNT; i++ )
+ {
+ if( contexts[i].allocated == false )
+ {
+ strncpy( contexts[i].name, name, 64 );
+ contexts[i].name[63] = '\0';
+ contexts[i].allocated = true;
+ contexts[i].context = ascontext;
+ return;
+ }
+ }
+}
+
+void DeregisterAngelscriptContext( ASContext* ascontext )
+{
+ for( int i = 0; i < CONTEXT_COUNT; i++ )
+ {
+ if( contexts[i].context == ascontext )
+ {
+ contexts[i].allocated = false;
+ }
+ }
+}
+
+void DumpAngelscriptStates()
+{
+ std::ofstream output;
+ char dump_path[kPathSize];
+ GetASDumpPath(dump_path, kPathSize);
+
+ my_ofstream_open(output,dump_path);
+
+ if( output.is_open() ) {
+ LOGI << "Dumping angelscript states to: " << dump_path << std::endl;
+ for( int i = 0; i < CONTEXT_COUNT; i++ )
+ {
+ if( contexts[i].allocated )
+ {
+ LOGI << "Dumping angelscript state for: " << contexts[i].name << std::endl;
+ if( output.good() ) {
+ contexts[i].context->DumpCallstack(output);
+ }
+ }
+ }
+ } else {
+ LOGE << "Unable to dump angelscript states to: " << dump_path << std::endl;
+ }
+}
diff --git a/Source/Scripting/angelscript/ascrashdump.h b/Source/Scripting/angelscript/ascrashdump.h
new file mode 100644
index 00000000..8eb56466
--- /dev/null
+++ b/Source/Scripting/angelscript/ascrashdump.h
@@ -0,0 +1,30 @@
+//-----------------------------------------------------------------------------
+// Name: ascrashdump.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+class ASContext;
+
+void RegisterAngelscriptContext( const char* name, ASContext* ascontext );
+void DeregisterAngelscriptContext( ASContext* ascontext );
+
+void DumpAngelscriptStates();
diff --git a/Source/Scripting/angelscript/asdebugger.cpp b/Source/Scripting/angelscript/asdebugger.cpp
new file mode 100644
index 00000000..43bd220d
--- /dev/null
+++ b/Source/Scripting/angelscript/asdebugger.cpp
@@ -0,0 +1,795 @@
+//-----------------------------------------------------------------------------
+// Name: asdebugger.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "asdebugger.h"
+
+#include <GUI/dimgui/dimgui.h>
+#include <GUI/dimgui/imgui_impl_sdl_gl3.h>
+
+#include <Scripting/angelscript/ascontext.h>
+#include <Scripting/angelscript/add_on/scriptarray/scriptarray.h>
+
+#include <Main/engine.h>
+#include <Graphics/graphics.h>
+#include <UserInput/input.h>
+
+#include <angelscript.h>
+
+#include <set>
+
+std::vector<ASContext*> ASDebugger::active_contexts;
+std::vector<std::pair<std::string, int> > ASDebugger::global_breakpoints;
+
+ASDebugger::ASDebugger()
+ : paused(false)
+ , break_next(false)
+ , current_line(-1)
+ , current_stack_level(0)
+ , ctx(NULL)
+ , auto_scroll(false)
+ , show_local_variables(false)
+ , show_global_variables(false)
+ , show_stack_trace(false)
+{
+ for(size_t i = 0; i < global_breakpoints.size(); ++i) {
+ ToggleBreakpoint(global_breakpoints[i].first.c_str(), global_breakpoints[i].second);
+ }
+}
+
+ASDebugger::~ASDebugger() {
+}
+
+void ASDebugger::AddContext(ASContext* context) {
+ bool add = true;
+ for(size_t i = 0; i < active_contexts.size(); ++i) {
+ if(active_contexts[i] == context) {
+ add = false;
+ break;
+ }
+ }
+
+ if(add)
+ active_contexts.push_back(context);
+}
+
+void ASDebugger::RemoveContext(ASContext* context) {
+ for(size_t i = 0; i < active_contexts.size(); ++i) {
+ if(active_contexts[i] == context) {
+ active_contexts.erase(active_contexts.begin() + i);
+ break;
+ }
+ }
+}
+
+const std::vector<ASContext*>& ASDebugger::GetActiveContexts() {
+ return active_contexts;
+}
+
+void ASDebugger::AddGlobalBreakpoint(const char* file, int line) {
+ bool found = false;
+ for(size_t i = 0; i < global_breakpoints.size(); ++i) {
+ if(strcmp(global_breakpoints[i].first.c_str(), file) == 0 && global_breakpoints[i].second == line) {
+ found = true;
+ break;
+ }
+ }
+
+ if(!found) {
+ global_breakpoints.push_back(std::pair<std::string, int>(file, line));
+ for(size_t i = 0; i < active_contexts.size(); ++i) {
+ active_contexts[i]->dbg.AddBreakpoint(file, line);
+ }
+ }
+}
+
+void ASDebugger::RemoveGlobalBreakpoint(const char* file, int line) {
+ for(size_t i = 0; i < global_breakpoints.size(); ++i) {
+ if(strcmp(global_breakpoints[i].first.c_str(), file) == 0 && global_breakpoints[i].second == line) {
+ global_breakpoints.erase(global_breakpoints.begin() + i);
+ for(size_t i = 0; i < active_contexts.size(); ++i) {
+ active_contexts[i]->dbg.RemoveBreakpoint(file, line);
+ }
+ break;
+ }
+ }
+}
+
+const std::vector<std::pair<std::string, int> >& ASDebugger::GetGlobalBreakpoints() {
+ return global_breakpoints;
+}
+
+void ASDebugger::SetModule(ASModule* module) {
+ this->module = module;
+}
+
+void ASDebugger::PrintCType(Variable& variable, asIScriptContext* ctx) {
+ void* ptr = variable.ptr;
+ if(variable.type_id & asTYPEID_OBJHANDLE) {
+ ptr = *(void**)ptr;
+ if(!ptr) {
+ ImGui::Text("%s points to null", variable.name);
+ return;
+ }
+ }
+
+ if(variable.type_id & asTYPEID_SCRIPTOBJECT) {
+ if(ImGui::TreeNodeEx(variable.name, 0)) {
+ asIScriptObject* obj = (asIScriptObject*)ptr;
+ for(asUINT i = 0; i < obj->GetPropertyCount(); ++i) {
+ Variable member;
+ member.type_id = obj->GetPropertyTypeId(i);
+ member.name = obj->GetPropertyName(i);
+ member.ptr = obj->GetAddressOfProperty(i);
+ PrintVar(member, ctx);
+ }
+ ImGui::TreePop();
+ }
+ return;
+ }
+ asITypeInfo* info = ctx->GetEngine()->GetTypeInfoById(variable.type_id);
+ if(strcmp(info->GetName(), "string") == 0) {
+ std::string* string = (std::string*)ptr;
+ if(string) {
+ ImGui::Text("%s = \"%s\"", variable.name, string->c_str());
+ } else {
+ ImGui::Text("%s = ?", variable.name);
+ }
+ } else if(strcmp(info->GetName(), "array") == 0) {
+ if(ImGui::TreeNodeEx(variable.name, 0)) {
+ CScriptArray* arr = (CScriptArray*)ptr;
+ int new_type_id = info->GetSubTypeId(0);
+ for(size_t i = 0; i < arr->GetSize(); ++i) {
+ char buffer[16];
+ sprintf(buffer, "[%d]", (int)i);
+ Variable variable;
+ variable.name = buffer;
+ variable.type_id = new_type_id;
+ variable.ptr = arr->At(i);
+ PrintVar(variable, ctx);
+ }
+ ImGui::TreePop();
+ }
+ } else {
+ if(info->GetPropertyCount() > 0) {
+ if(ImGui::TreeNodeEx(variable.name, 0)) {
+ for(size_t i = 0; i < info->GetPropertyCount(); ++i) {
+ int property_offset;
+ Variable property;
+ info->GetProperty(i, &property.name, &property.type_id, NULL, NULL, &property_offset);
+ property.ptr = (char*)ptr + property_offset;
+ PrintVar(property, ctx);
+ }
+
+ ImGui::TreePop();
+ }
+ } else {
+ ImGui::Text("%s is of unknown type %s and no members are registered", variable.name, info->GetName());
+ }
+ }
+}
+
+void ASDebugger::PrintVar(Variable& variable, asIScriptContext* ctx) {
+ switch(variable.type_id) {
+ case asTYPEID_BOOL:
+ ImGui::Text("%s = %d\n", variable.name, *(bool*)variable.ptr);
+ break;
+ case asTYPEID_INT8:
+ ImGui::Text("%s = %d\n", variable.name, (int)*(int8_t*)variable.ptr);
+ break;
+ case asTYPEID_INT16:
+ ImGui::Text("%s = %d\n", variable.name, (int)*(int16_t*)variable.ptr);
+ break;
+ case asTYPEID_INT32:
+ ImGui::Text("%s = %ld\n", variable.name, (long)(*(int32_t*)variable.ptr)); // These casts are done to avoid warnings on 32/64 bit builds
+ break;
+ case asTYPEID_INT64:
+ ImGui::Text("%s = %lld\n", variable.name, (long long)(*(int64_t*)variable.ptr));
+ break;
+ case asTYPEID_UINT8:
+ ImGui::Text("%s = %u\n", variable.name, (unsigned int)*(uint8_t*)variable.ptr);
+ break;
+ case asTYPEID_UINT16:
+ ImGui::Text("%s = %u\n", variable.name, (unsigned int)*(uint16_t*)variable.ptr);
+ break;
+ case asTYPEID_UINT32:
+ ImGui::Text("%s = %lu\n", variable.name, (unsigned long)(*(uint32_t*)variable.ptr));
+ break;
+ case asTYPEID_UINT64:
+ ImGui::Text("%s = %llu\n", variable.name, (unsigned long long)(*(uint64_t*)variable.ptr));
+ break;
+ case asTYPEID_FLOAT:
+ case asTYPEID_DOUBLE:
+ ImGui::Text("%s = %f\n", variable.name, *(float*)variable.ptr);
+ break;
+ default: {
+ if(variable.ptr == NULL)
+ return;
+ PrintCType(variable, ctx);
+ return;
+ }
+ }
+}
+
+void ASDebugger::Print() {
+ std::set<void*> printed_variables;
+ asUINT callstack_size = ctx->GetCallstackSize();
+ for(asUINT stack_level = 0; stack_level < callstack_size; ++stack_level) {
+ int variable_count = ctx->GetVarCount(stack_level);
+
+ for(int var_index = 0; var_index < variable_count; ++var_index) {
+ Variable variable;
+ variable.name = ctx->GetVarName(var_index, stack_level);
+ variable.type_id = ctx->GetVarTypeId(var_index, stack_level);
+ variable.ptr = ctx->GetAddressOfVar(var_index, stack_level);
+
+ if(printed_variables.find(variable.ptr) != printed_variables.end())
+ continue;
+ printed_variables.insert(variable.ptr);
+ local_variables.push_back(variable);
+ }
+ }
+
+ asIScriptFunction* function = ctx->GetFunction();
+ if(!function)
+ return;
+ asIScriptModule* script_mod = function->GetModule();
+ if(!script_mod)
+ return;
+
+ for(asUINT variable_index = 0; variable_index < script_mod->GetGlobalVarCount(); ++variable_index) {
+ Variable variable;
+ script_mod->GetGlobalVar(variable_index, &variable.name, NULL, &variable.type_id);
+ variable.ptr = script_mod->GetAddressOfGlobalVar(variable_index);
+ if(printed_variables.find(variable.ptr) != printed_variables.end())
+ continue;
+ printed_variables.insert(variable.ptr);
+ global_variables.push_back(variable);
+ }
+
+ asIScriptEngine* engine = ctx->GetEngine();
+ for(asUINT variable_index = 0; variable_index < engine->GetGlobalPropertyCount(); ++variable_index) {
+ Variable variable;
+ engine->GetGlobalPropertyByIndex(variable_index, &variable.name, NULL, &variable.type_id, NULL, NULL, &variable.ptr);
+ if(printed_variables.find(variable.ptr) != printed_variables.end())
+ continue;
+ printed_variables.insert(variable.ptr);
+ global_variables.push_back(variable);
+ }
+}
+
+void ASDebugger::Break() {
+ break_next = true;
+ auto_scroll = true;
+}
+
+void ASDebugger::ToggleBreakpoint(const char* file_name, int line) {
+ std::string file(file_name);
+
+ Breakpoint_iter breakpoint_iter = breakpoints.find(file);
+ if(breakpoint_iter == breakpoints.end())
+ breakpoints.insert(std::pair<std::string, std::vector<int> >(file, std::vector<int>(1, line)));
+ else
+ {
+ std::vector<int>& lines = breakpoint_iter->second;
+
+ for(size_t i = 0; i < lines.size(); ++i) {
+ if(lines[i] == line) {
+ lines.erase(lines.begin() + i);
+ return;
+ }
+ }
+
+ breakpoint_iter->second.push_back(line);
+ }
+}
+
+void ASDebugger::AddBreakpoint(const char* file_name, int line) {
+ std::string file(file_name);
+
+ Breakpoint_iter breakpoint_iter = breakpoints.find(file);
+ if(breakpoint_iter == breakpoints.end())
+ breakpoints.insert(std::pair<std::string, std::vector<int> >(file, std::vector<int>(1, line)));
+ else
+ {
+ std::vector<int>& lines = breakpoint_iter->second;
+
+ for(size_t i = 0; i < lines.size(); ++i) {
+ if(lines[i] == line) {
+ return;
+ }
+ }
+
+ breakpoint_iter->second.push_back(line);
+ }
+}
+
+void ASDebugger::RemoveBreakpoint(const char* file_name, int line) {
+ std::string file(file_name);
+
+ Breakpoint_iter breakpoint_iter = breakpoints.find(file);
+ if(breakpoint_iter != breakpoints.end())
+ {
+ std::vector<int>& lines = breakpoint_iter->second;
+
+ for(size_t i = 0; i < lines.size(); ++i) {
+ if(lines[i] == line) {
+ lines.erase(lines.begin() + i);
+ return;
+ }
+ }
+ }
+}
+
+const std::vector<int>* ASDebugger::GetBreakpoints(const char* file_name) {
+ std::string file(file_name);
+
+ Breakpoint_iter breakpoint_iter = breakpoints.find(file);
+ if(breakpoint_iter != breakpoints.end())
+ return &(breakpoint_iter->second);
+ else
+ return NULL;
+}
+
+void ASDebugger::LineCallback(asIScriptContext* ctx) {
+ if(paused) {
+ // After suspending, the line callback is called once again
+ local_variables.clear();
+ global_variables.clear();
+ Print();
+ DebugLoop();
+ return;
+ }
+ this->ctx = ctx;
+
+ LineFile lf = GetLF(ctx);
+
+ current_file = lf.file;
+ current_line = lf.line_number;
+
+ if(break_next) {
+ break_next = false;
+ paused = true;
+ ctx->Suspend();
+ return;
+ }
+
+ std::string file_path = lf.file.GetOriginalPathStr();
+ file_path = file_path.substr(file_path.find_last_of("\\/") + 1);
+
+ const std::vector<int>* breakpoints = GetBreakpoints(file_path.c_str());
+ if(breakpoints == NULL)
+ return;
+ else {
+ for(size_t i = 0; i < breakpoints->size(); ++i) {
+ if(breakpoints->at(i) == current_line) {
+ paused = true;
+ auto_scroll = true;
+ ctx->Suspend();
+ }
+ }
+ }
+}
+
+void ASDebugger::StepOver(asIScriptContext* ctx) {
+ LineFile lf = GetLF(ctx);
+
+ int stack_level = ctx->GetCallstackSize();
+
+ if(stack_level <= current_stack_level) {
+ current_file = lf.file;
+ current_line = lf.line_number;
+ ctx->Suspend();
+ paused = true;
+ auto_scroll = true;
+ } else {
+ Continue(ctx);
+ }
+}
+
+void ASDebugger::StepInto(asIScriptContext* ctx) {
+ LineFile lf = GetLF(ctx);
+
+ if((int)lf.line_number != current_line) {
+ current_file = lf.file;
+ current_line = lf.line_number;
+ ctx->Suspend();
+ paused = true;
+ auto_scroll = true;
+ }
+}
+
+void ASDebugger::StepOut(asIScriptContext* ctx) {
+ int stack_level = ctx->GetCallstackSize();
+
+ if(stack_level < current_stack_level) {
+ LineFile lf = GetLF(ctx);
+ current_file = lf.file;
+ current_line = lf.line_number;
+ ctx->Suspend();
+ paused = true;
+ auto_scroll = true;
+ }
+}
+
+void ASDebugger::Continue(asIScriptContext* ctx) {
+ LineFile lf = GetLF(ctx);
+ if((int)lf.line_number != current_line)
+ continue_break = true;
+
+ if(continue_break) {
+ std::string file_path = lf.file.GetOriginalPathStr();
+ file_path = file_path.substr(file_path.find_last_of("\\/") + 1);
+
+ const std::vector<int>* breakpoints = GetBreakpoints(file_path.c_str());
+ if(breakpoints == NULL)
+ return;
+ else {
+ for(size_t i = 0; i < breakpoints->size(); ++i) {
+ if(breakpoints->at(i) == (int)lf.line_number) {
+ current_file = lf.file;
+ current_line = lf.line_number;
+ ctx->Suspend();
+ paused = true;
+ auto_scroll = true;
+ }
+ }
+ }
+ }
+}
+
+void ASDebugger::DebugLoop() {
+ Graphics* graphics = Graphics::Instance();
+ Input* input = Input::Instance();
+
+ input->cursor->SetVisible(true);
+ input->SetGrabMouse(false);
+ while(paused) {
+ ctx->ClearLineCallback();
+ PollEvents();
+
+ bool imgui_begun = true; //ImGui::FrameBegun();
+ if(imgui_begun) {
+ ImGui::EndFrame();
+ }
+
+ ImGui_ImplSdlGL3_NewFrame(graphics->sdl_window_, input->GetGrabMouse());
+ bool update_variables = true;
+ switch(UpdateGUI())
+ {
+ case CONTINUE:
+ continue_break = false;
+ ctx->SetLineCallback(asMETHOD(ASDebugger, Continue), this, asCALL_THISCALL);
+ paused = false;
+ do {
+ int ret = ctx->Execute();
+ if(ret == asEXECUTION_FINISHED) {
+ ctx->SetLineCallback(asMETHOD(ASDebugger, LineCallback), this, asCALL_THISCALL);
+ ImGui::Render();
+ if(imgui_begun) {
+ ImGui_ImplSdlGL3_NewFrame(graphics->sdl_window_, input->GetGrabMouse());
+ }
+ return;
+ }
+ } while(!paused);
+ break;
+ case OVER:
+ current_stack_level = ctx->GetCallstackSize();
+ ctx->SetLineCallback(asMETHOD(ASDebugger, StepOver), this, asCALL_THISCALL);
+ paused = false;
+ do {
+ int ret = ctx->Execute();
+ if(ret == asEXECUTION_FINISHED) {
+ ctx->SetLineCallback(asMETHOD(ASDebugger, LineCallback), this, asCALL_THISCALL);
+ ImGui::Render();
+ if(imgui_begun) {
+ ImGui_ImplSdlGL3_NewFrame(graphics->sdl_window_, input->GetGrabMouse());
+ }
+ return;
+ }
+ } while(!paused);
+ break;
+ case INTO:
+ ctx->SetLineCallback(asMETHOD(ASDebugger, StepInto), this, asCALL_THISCALL);
+ paused = false;
+ do {
+ int ret = ctx->Execute();
+ if(ret == asEXECUTION_FINISHED) {
+ ctx->SetLineCallback(asMETHOD(ASDebugger, LineCallback), this, asCALL_THISCALL);
+ ImGui::Render();
+ if(imgui_begun) {
+ ImGui_ImplSdlGL3_NewFrame(graphics->sdl_window_, input->GetGrabMouse());
+ }
+ return;
+ }
+ } while(!paused);
+ break;
+ case LEAVE:
+ current_stack_level = ctx->GetCallstackSize();
+ ctx->SetLineCallback(asMETHOD(ASDebugger, StepOut), this, asCALL_THISCALL);
+ paused = false;
+ do {
+ int ret = ctx->Execute();
+ if(ret == asEXECUTION_FINISHED) {
+ ctx->SetLineCallback(asMETHOD(ASDebugger, LineCallback), this, asCALL_THISCALL);
+ ImGui::Render();
+ if(imgui_begun) {
+ ImGui_ImplSdlGL3_NewFrame(graphics->sdl_window_, input->GetGrabMouse());
+ }
+ return;
+ }
+ } while(!paused);
+ break;
+ default:
+ update_variables = false;
+ break;
+ }
+ if(update_variables) {
+ local_variables.clear();
+ global_variables.clear();
+ Print();
+ }
+
+ // Back up state
+ std::stack<ViewportDims> viewport_dims_stack;
+ std::stack<GLuint> fbo_stack;
+
+ graphics->PushViewport(); // Viewport is set to entire window
+ graphics->PushFramebuffer(); // Framebuffer is set to 0
+ // Back these up to temporarily so no assertions fail/states change
+ viewport_dims_stack = graphics->viewport_dims_stack;
+ fbo_stack = graphics->fbo_stack;
+
+ graphics->viewport_dims_stack = std::stack<ViewportDims>();
+ graphics->fbo_stack = std::stack<GLuint>();
+
+ // Changes states
+ graphics->setViewport(0, 0, graphics->window_dims[0], graphics->window_dims[1]);
+ graphics->bindFramebuffer(0);
+ graphics->Clear(true);
+
+ // Draw
+ ImGui::Render();
+ input->cursor->Draw();
+
+ // Swap
+ graphics->SwapToScreen();
+
+ // Restore state
+ graphics->viewport_dims_stack = viewport_dims_stack;
+ graphics->fbo_stack = fbo_stack;
+ graphics->PopFramebuffer();
+ graphics->PopViewport();
+
+ static uint32_t last_time = 0;
+ uint32_t diff = 16 - (SDL_TS_GetTicks() - last_time) - 1;
+ if( diff > 15 ){
+ diff = 15;
+ }
+ SDL_Delay(diff);
+ last_time = SDL_TS_GetTicks();
+ }
+
+ ctx->SetLineCallback(asMETHOD(ASDebugger, LineCallback), this, asCALL_THISCALL);
+}
+
+void ASDebugger::PollEvents() {
+ Graphics* graphics = Graphics::Instance();
+ Input* input = Input::Instance();
+ SDL_Event event;
+ while (SDL_PollEvent(&event)) {
+ ProcessEventImGui(&event);
+ //Redirect input events to the controller
+ switch( event.type ) {
+ case SDL_WINDOWEVENT:
+ switch(event.window.event){
+ case SDL_WINDOWEVENT_FOCUS_LOST:
+ input->HandleEvent(event);
+ break;
+ case SDL_WINDOWEVENT_FOCUS_GAINED:
+ input->HandleEvent(event);
+ break;
+ }
+ break;
+ case SDL_QUIT:
+ input->RequestQuit();
+ return;
+ case SDL_MOUSEBUTTONDOWN:
+ case SDL_MOUSEWHEEL:
+ if(!WantMouseImGui()){
+ input->HandleEvent(event);
+ }
+ break;
+ case SDL_KEYDOWN:
+ case SDL_TEXTINPUT:
+ if(!WantKeyboardImGui()){
+ input->HandleEvent(event);
+ }
+ break;
+ default:
+ input->HandleEvent(event);
+ }
+ }
+}
+
+void ASDebugger::VariablesMenu(const char* window_name, ImGuiTextFilter& filter, std::vector<Variable>& variables, bool& show_variables) {
+ if(show_variables) {
+ ImGui::SetNextWindowSize(ImVec2(640.0f, 480.0f), ImGuiCond_FirstUseEver);
+ ImGui::Begin(window_name, &show_variables);
+ if(ImGui::InputText("Filters:", filter.InputBuf, IM_ARRAYSIZE(filter.InputBuf))) {
+ filter.Build();
+ }
+
+ if(filter.IsActive()) {
+ for(size_t i = 0; i < variables.size(); ++i) {
+ if(filter.PassFilter(variables[i].name)) {
+ ImGui::PushID(i);
+ PrintVar(variables[i], ctx);
+ ImGui::PopID();
+ }
+ }
+ } else {
+ for(size_t i = 0; i < variables.size(); ++i) {
+ ImGui::PushID(i);
+ PrintVar(variables[i], ctx);
+ ImGui::PopID();
+ }
+ }
+ ImGui::End();
+ }
+}
+
+static char as_debugger_buffer[512];
+static ImGuiTextFilter local_filter;
+static ImGuiTextFilter global_filter;
+ASDebugger::BreakAction ASDebugger::UpdateGUI() {
+ BreakAction action = NONE;
+
+ ImGui::SetNextWindowSize(ImVec2(1024.0f, 768.0f), ImGuiCond_FirstUseEver);
+ ImGui::Begin("Angelscript debugger", &show_performance);
+
+ ImGui::Text("Currently executing %s", current_file.resolved);
+
+ if(ImGui::Button("Continue")) {
+ action = CONTINUE;
+ }
+ ImGui::SameLine();
+ if(ImGui::Button("Step over")) {
+ action = OVER;
+ }
+ ImGui::SameLine();
+ if(ImGui::Button("Step into")) {
+ action = INTO;
+ }
+ ImGui::SameLine();
+ if(ImGui::Button("Step out")) {
+ action = LEAVE;
+ }
+
+ ImGui::Checkbox("Show local variables", &show_local_variables);
+ ImGui::SameLine();
+ ImGui::Checkbox("Show global variables", &show_global_variables);
+ ImGui::SameLine();
+ ImGui::Checkbox("Show stack trace", &show_stack_trace);
+
+ const ScriptFile* script = ScriptFileUtil::GetScriptFile(current_file);
+ const char* file_name = script->file_path.GetFullPath();
+ int last_slash = 0;
+ for(size_t i = 0; i < std::strlen(file_name); ++i) {
+ if(file_name[i] == '\\' || file_name[i] == '/')
+ last_slash = i;
+ }
+ file_name += last_slash + 1;
+
+ const std::string& script_source = script->unexpanded_contents;
+ int start_index = 0;
+ int end_index = 0;
+
+ ImGui::BeginChild("ASSourceMain", ImVec2(0,0), false, 0);
+ ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
+ ImGui::Columns(2);
+
+ int line_nr = 1;
+
+ const int MAX_LINE_LENGTH = 255;
+ char line[MAX_LINE_LENGTH + 1];
+
+ const std::vector<int>* breakpoints = GetBreakpoints(file_name);
+
+ ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.5f));
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
+ while(end_index != (int)script_source.size()) {
+ end_index = std::min(script_source.find('\n', start_index), script_source.size());
+
+ int copy_length = std::min(MAX_LINE_LENGTH, end_index - start_index);
+ std::memcpy(line, script_source.c_str() + start_index, copy_length);
+ line[copy_length] = '\0';
+ start_index = end_index + 1;
+
+ bool is_breakpoint = false;
+ if(breakpoints != NULL) {
+ for(size_t i = 0; i < breakpoints->size(); ++i) {
+ if(breakpoints->at(i) == line_nr) {
+ is_breakpoint = true;
+ break;
+ }
+ }
+ }
+
+ bool is_current_line = false;
+ if(line_nr == GetCurrentLine()) {
+ is_current_line = true;
+ }
+
+ if(is_current_line) {
+ if(is_breakpoint)
+ ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.4f, 0.0f, 0.5f));
+ else
+ ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.8f, 0.0f, 0.5f));
+
+ if(auto_scroll) {
+ ImGui::SetScrollHereY();
+ auto_scroll = false;
+ }
+ }
+ else if(is_breakpoint)
+ ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.0f, 0.0f, 0.5f));
+
+ ImGui::Text("%d", line_nr);
+ ImGui::NextColumn();
+ ImGui::PushID(line_nr);
+ ImGui::SetColumnOffset(-1, 36);
+ if(ImGui::ButtonEx(line, ImVec2(ImGui::GetWindowWidth() - 36, 0), ImGuiButtonFlags_AlignTextBaseLine)) {
+ ToggleBreakpoint(file_name, line_nr);
+ }
+ ImGui::NextColumn();
+ ImGui::PopID();
+ if(is_current_line || is_breakpoint)
+ ImGui::PopStyleColor();
+
+ ++line_nr;
+ }
+
+ ImGui::PopStyleVar(2);
+ ImGui::PopStyleColor();
+ ImGui::EndChild();
+ ImGui::End();
+
+ if(show_stack_trace) {
+ const char* file;
+ for(asUINT i = 0; i < ctx->GetCallstackSize(); ++i) {
+ int line = ctx->GetLineNumber(i, 0, &file);
+ LineFile lineFile = module->GetCorrectedLine(line);
+ ImGui::Text("%s:%d %s", lineFile.file.GetOriginalPath(), lineFile.line_number, ctx->GetFunction(i)->GetDeclaration());
+ }
+ }
+
+ VariablesMenu("Angelscript local variables", local_filter, local_variables, show_local_variables);
+ VariablesMenu("Angelscript global variables", global_filter, global_variables, show_global_variables);
+
+ return action;
+}
+
+LineFile ASDebugger::GetLF(asIScriptContext* ctx) {
+ const char *scriptSection;
+ int line = ctx->GetLineNumber(0, 0, &scriptSection);
+ return module->GetCorrectedLine(line);
+}
diff --git a/Source/Scripting/angelscript/asdebugger.h b/Source/Scripting/angelscript/asdebugger.h
new file mode 100644
index 00000000..367b7e52
--- /dev/null
+++ b/Source/Scripting/angelscript/asdebugger.h
@@ -0,0 +1,112 @@
+//-----------------------------------------------------------------------------
+// Name: asdebugger.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Scripting/angelscript/add_on/debugger/debugger.h>
+#include <Scripting/angelscript/asmodule.h>
+
+#include <imgui.h>
+#include <imgui_internal.h>
+
+class ASContext;
+
+class ASDebugger : public CDebugger
+{
+private:
+ // LEAVE was named OUT, but on windows OUT is #defined
+ enum BreakAction { CONTINUE, OVER, INTO, LEAVE, NONE };
+
+ struct Variable
+ {
+ const char* name;
+ int type_id;
+ void* ptr;
+ };
+public:
+ ASDebugger();
+ ~ASDebugger();
+
+ void SetModule(ASModule* module);
+
+ void Print();
+
+ void Break();
+
+ void LineCallback(asIScriptContext* ctx);
+
+ void ToggleBreakpoint(const char* file_name, int line);
+ void AddBreakpoint(const char* file_name, int line);
+ void RemoveBreakpoint(const char* file_name, int line);
+ const std::vector<int>* GetBreakpoints(const char* file_name);
+
+ int GetCurrentLine() const { return current_line; }
+ const Path& GetCurrentFile() const { return current_file; }
+
+ static void AddContext(ASContext* context);
+ static void RemoveContext(ASContext* context);
+ static const std::vector<ASContext*>& GetActiveContexts();
+
+ static void AddGlobalBreakpoint(const char* file, int line);
+ static void RemoveGlobalBreakpoint(const char* file, int line);
+ static const std::vector<std::pair<std::string, int> >& GetGlobalBreakpoints();
+private:
+ typedef std::map<std::string, std::vector<int> >::iterator Breakpoint_iter;
+ std::map<std::string, std::vector<int> > breakpoints;
+
+ ASModule* module;
+
+ bool auto_scroll;
+ bool break_next;
+ bool paused;
+ bool continue_break;
+ bool show_local_variables;
+ bool show_global_variables;
+ bool show_stack_trace;
+ BreakAction break_action;
+
+ Path current_file;
+ int current_line;
+ int current_stack_level;
+
+ asIScriptContext* ctx;
+ std::vector<Variable> local_variables;
+ std::vector<Variable> global_variables;
+
+ static std::vector<ASContext*> active_contexts;
+ static std::vector<std::pair<std::string, int> > global_breakpoints;
+
+ LineFile GetLF(asIScriptContext* ctx);
+
+ void DebugLoop();
+ void PollEvents();
+ BreakAction UpdateGUI();
+
+ void StepOver(asIScriptContext* ctx);
+ void StepOut(asIScriptContext* ctx);
+ void StepInto(asIScriptContext* ctx);
+ void Continue(asIScriptContext* ctx);
+
+ void PrintCType(Variable& variable, asIScriptContext* ctx);
+ void PrintVar(Variable& variable, asIScriptContext* ctx);
+ void VariablesMenu(const char* window_name, ImGuiTextFilter& filter, std::vector<Variable>& variables, bool& show_variables);
+};
diff --git a/Source/Scripting/angelscript/asfuncs.cpp b/Source/Scripting/angelscript/asfuncs.cpp
new file mode 100644
index 00000000..eb307499
--- /dev/null
+++ b/Source/Scripting/angelscript/asfuncs.cpp
@@ -0,0 +1,7707 @@
+//-----------------------------------------------------------------------------
+// Name: asfuncs.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "asfuncs.h"
+
+#include <Objects/hotspot.h>
+#include <Objects/decalobject.h>
+#include <Objects/dynamiclightobject.h>
+#include <Objects/placeholderobject.h>
+#include <Objects/movementobject.h>
+#include <Objects/itemobject.h>
+#include <Objects/group.h>
+#include <Objects/prefab.h>
+#include <Objects/envobject.h>
+#include <Objects/pathpointobject.h>
+
+#include <Scripting/angelscript/asfuncs.h>
+#include <Scripting/angelscript/ascontext.h>
+#include <Scripting/angelscript/add_on/scriptarray/scriptarray.h>
+
+#include <UserInput/keyTranslator.h>
+#include <UserInput/input.h>
+
+#include <Graphics/textures.h>
+#include <Graphics/sky.h>
+#include <Graphics/particles.h>
+#include <Graphics/bonetransform.h>
+#include <Graphics/flares.h>
+
+#include <Math/enginemath.h>
+#include <Math/mat3.h>
+#include <Math/mat4.h>
+#include <Math/vec2math.h>
+#include <Math/vec3math.h>
+#include <Math/ivec2math.h>
+#include <Math/ivec3.h>
+#include <Math/ivec4.h>
+
+#include <Internal/common.h>
+#include <Internal/file_descriptor.h>
+#include <Internal/stopwatch.h>
+#include <Internal/dialogues.h>
+#include <Internal/config.h>
+#include <Internal/filesystem.h>
+#include <Internal/profiler.h>
+#include <Internal/modloading.h>
+#include <Internal/message.h>
+#include <Internal/varstring.h>
+
+#include <Online/online.h>
+#include <Online/online_utility.h>
+#include <Online/online_datastructures.h>
+
+#include <Scripting/scriptlogging.h>
+#include <Scripting/angelscript/add_on/scriptarray/scriptarray.h>
+
+#include <Main/scenegraph.h>
+#include <Main/engine.h>
+
+#include <GUI/dimgui/settings_screen.h>
+#include <GUI/IMUI/im_image.h>
+#include <GUI/gui.h>
+
+#include <Utility/strings.h>
+#include <Utility/assert.h>
+
+#include <Editors/map_editor.h>
+#include <Editors/actors_editor.h>
+
+#include <Compat/compat.h>
+#include <AI/navmesh.h>
+#include <Version/version.h>
+#include <Memory/allocation.h>
+#include <Steam/steamworks.h>
+#include <Asset/Asset/levelinfo.h>
+
+#include <angelscript.h>
+#include <imgui.h>
+
+#include <cmath>
+#include <iostream>
+#include <sstream>
+#include <stack>
+
+#ifdef max
+#undef max
+#endif
+
+SceneGraph *the_scenegraph = NULL;
+extern std::stack<ASContext*> active_context_stack;
+extern Timer game_timer;
+extern Timer ui_timer;
+extern Config default_config;
+
+static ASContext* GetActiveASContext() {
+ asIScriptContext* ctx = asGetActiveContext();
+ if(ctx) {
+ return (ASContext*)ctx->GetUserData(0);
+ } else {
+ return NULL;
+ }
+}
+
+void MessageCallback(const asSMessageInfo *msg, void *param) {
+ const char *type = "ERR ";
+ if( msg->type == asMSGTYPE_WARNING )
+ type = "WARN";
+ else if( msg->type == asMSGTYPE_INFORMATION )
+ type = "INFO";
+
+ std::ostringstream oss;
+ oss << msg->section << " (" << msg->row << ", " << msg->col << ") : ";
+ oss << type << " : " << msg->message << "\n";
+
+ *((std::string*)param) += oss.str();
+// printf("%s (%d, %d) : %s : %s\n", msg->section, msg->row, msg->col, type, msg->message);
+}
+
+static uint32_t socket_id_invalid = SOCKET_ID_INVALID;
+
+static uint32_t AS_CreateSocketTCP(std::string& host, uint16_t port) {
+ return Engine::Instance()->GetASNetwork()->CreateSocketTCP(host, port);
+}
+
+static void AS_DestroySocketTCP( uint32_t socket ) {
+ return Engine::Instance()->GetASNetwork()->DestroySocketTCP(socket);
+}
+
+static int AS_SocketTCPSend( uint32_t socket, const CScriptArray &array ) {
+ uint8_t* datc = (uint8_t*)alloc.stack.Alloc(array.GetSize());
+ for( unsigned i = 0; i < array.GetSize(); i++ ) {
+ datc[i] = *(uint8_t*)array.At(i);
+ }
+ int ret = Engine::Instance()->GetASNetwork()->SocketTCPSend(socket,datc,array.GetSize());
+ alloc.stack.Free(datc);
+ return ret;
+}
+
+static bool AS_IsValidSocketTCP( uint32_t socket )
+{
+ return Engine::Instance()->GetASNetwork()->IsValidSocketTCP(socket);
+}
+
+static CScriptArray* AS_GetPlayerStates() {
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<PlayerState>@"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+
+ std::vector<PlayerState> vals = Online::Instance()->GetPlayerStates();
+
+ array->Reserve(vals.size());
+
+ for(unsigned i = 0; i < vals.size(); i++) {
+ array->InsertLast((void*)&vals[i]);
+ }
+
+ return array;
+}
+
+static void PlayerStateDefaultConstructor(PlayerState *self) {
+ new(self) PlayerState();
+}
+
+void AttachASNetwork(ASContext* context) {
+ context->RegisterObjectType("PlayerState", sizeof(PlayerState), asOBJ_VALUE | asOBJ_POD );
+ context->RegisterObjectProperty("PlayerState", "string playername", asOFFSET(PlayerState, playername));
+ context->RegisterObjectProperty("PlayerState", "int avatar_id", asOFFSET(PlayerState, object_id));
+ context->RegisterObjectProperty("PlayerState", "int ping", asOFFSET(PlayerState, ping));
+ context->RegisterObjectBehaviour("PlayerState", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(PlayerStateDefaultConstructor), asCALL_CDECL_OBJLAST);
+
+ context->RegisterGlobalFunction("array<PlayerState>@ GetPlayerStates()", asFUNCTION(AS_GetPlayerStates), asCALL_CDECL );
+
+ context->RegisterGlobalProperty("uint SOCKET_ID_INVALID", &socket_id_invalid);
+ context->RegisterGlobalFunction("uint CreateSocketTCP(string& host, uint16 port)",
+ asFUNCTION(AS_CreateSocketTCP),
+ asCALL_CDECL );
+ context->RegisterGlobalFunction("void DestroySocketTCP(uint socket)",
+ asFUNCTION(AS_DestroySocketTCP),
+ asCALL_CDECL );
+ context->RegisterGlobalFunction("int SocketTCPSend(uint socket, const array<uint8>& data)",
+ asFUNCTION(AS_SocketTCPSend),
+ asCALL_CDECL );
+ context->RegisterGlobalFunction("bool IsValidSocketTCP(uint socket)",
+ asFUNCTION(AS_IsValidSocketTCP),
+ asCALL_CDECL );
+}
+
+// Function implementation with native calling convention
+void PrintString(std::string &str) {
+ printf( "%s", str.c_str() );
+ //LogSystem::LogData( LogSystem::info,"us","Print()",0) << " " << str;
+}
+
+float AS_GetMoveXAxis(int controller_id) {
+ PlayerInput* controller = Input::Instance()->GetController(controller_id);
+
+ if(controller == NULL) {
+ return 0.0f;
+ }
+
+ float left_depth = controller->key_down["left"].depth;
+ float right_depth = controller->key_down["right"].depth;
+ return right_depth - left_depth;
+}
+
+float AS_GetMoveYAxis(int controller_id) {
+ PlayerInput* controller = Input::Instance()->GetController(controller_id);
+
+ if(controller == NULL) {
+ return 0.0f;
+ }
+
+ float up_depth = controller->key_down["up"].depth;
+ float down_depth = controller->key_down["down"].depth;
+ return down_depth - up_depth;
+}
+
+static bool AS_GetInputDownFiltered( int controller_id, const std::string &which_key, uint32_t filter )
+{
+ PlayerInput* controller = Input::Instance()->GetController(controller_id);
+
+ if(controller == NULL) {
+ return false;
+ }
+
+ bool key_down = false;
+
+ PlayerInput::KeyDownMap::iterator iter = controller->key_down.find(which_key);
+ if(iter != controller->key_down.end()) {
+ return iter->second.depth_count != 0;
+ } else {
+ if(which_key == "move_left"){
+ key_down = controller->key_down["left"].count != 0;
+ } else if(which_key == "move_right") {
+ key_down = controller->key_down["right"].count != 0;
+ } else if(which_key == "move_up") {
+ key_down = controller->key_down["up"].count != 0;
+ } else if(which_key == "move_down") {
+ key_down = controller->key_down["down"].count != 0;
+ } else if(which_key == "skip_dialogue") {
+ key_down = controller->key_down["skip_dialogue"].count != 0;
+ } else if(which_key == "mouse0"){
+ key_down = Input::Instance()->getMouse().mouse_down_[Mouse::LEFT] != 0;
+ } else if(which_key == "mousescrollup"){
+ key_down = Input::Instance()->getMouse().wheel_delta_y_ > 0;
+ } else if(which_key == "mousescrolldown"){
+ key_down = Input::Instance()->getMouse().wheel_delta_y_ < 0;
+ } else if(which_key == "mousescrollleft"){
+ key_down = Input::Instance()->getMouse().wheel_delta_x_ < 0;
+ } else if(which_key == "mousescrollright"){
+ key_down = Input::Instance()->getMouse().wheel_delta_x_ > 0;
+ } else {
+ SDL_Scancode key = StringToSDLScancode(which_key);
+ if(key != SDL_SCANCODE_SYSREQ) {
+ key_down = Input::Instance()->getKeyboard().isScancodeDown(key,filter);
+ }
+ }
+ }
+
+ return key_down;
+}
+
+static bool AS_GetInputDown(int controller_id, const std::string &which_key ) {
+ return AS_GetInputDownFiltered(controller_id, which_key, KIMF_PLAYING);
+}
+
+static bool AS_GetInputPressedFiltered(int controller_id, const std::string &which_key, uint32_t filter ) {
+ PlayerInput* controller = Input::Instance()->GetController(controller_id);
+
+ if(controller == NULL) {
+ return false;
+ }
+
+ bool key_down = false;
+
+ PlayerInput::KeyDownMap::iterator iter = controller->key_down.find(which_key);
+ if(iter != controller->key_down.end()) {
+ return iter->second.depth_count == 1;
+ } else {
+ if(which_key == "move_left") {
+ key_down = controller->key_down["left"].count == 1;
+ } else if(which_key == "move_right") {
+ key_down = controller->key_down["right"].count == 1;
+ } else if(which_key == "move_up") {
+ key_down = controller->key_down["up"].count == 1;
+ } else if(which_key == "move_down") {
+ key_down = controller->key_down["down"].count == 1;
+ } else if(which_key == "mouse0"){
+ key_down = Input::Instance()->getMouse().mouse_down_[Mouse::LEFT] == 1;
+ } else {
+ SDL_Scancode key = StringToSDLScancode(which_key);
+ if(key != SDL_SCANCODE_SYSREQ) {
+ key_down = Input::Instance()->getKeyboard().wasScancodePressed(key,filter);
+ }
+ }
+ }
+
+ return key_down;
+}
+
+static bool AS_GetInputPressed(int controller_id, const std::string &which_key) {
+ return AS_GetInputPressedFiltered(controller_id, which_key, KIMF_PLAYING);
+}
+
+void AS_ActivateKeyboardEvents()
+{
+ ASContext* ctx = GetActiveASContext();
+
+ if( ctx )
+ {
+ ctx->ActivateKeyboardEvents();
+ }
+}
+
+void AS_DeactivateKeyboardEvents()
+{
+ ASContext* ctx = GetActiveASContext();
+
+ if( ctx )
+ {
+ ctx->DeactivateKeyboardEvents();
+ }
+}
+
+const std::string AS_GetClipboard() {
+ return SDL_GetClipboardText();
+}
+
+void AS_SetClipboard(const std::string &text) {
+ SDL_SetClipboardText(text.c_str());
+}
+
+void AS_StartTextInput() {
+ Input::Instance()->StartTextInput();
+}
+
+void AS_StopTextInput() {
+ Input::Instance()->StopTextInput();
+}
+
+static int AS_GetCodeForKey(std::string name) {
+ return StringToSDLScancode(name);
+}
+
+static std::string AS_GetLocaleStringForScancode(int scancode) {
+ return SDLLocaleAdjustedStringFromScancode((SDL_Scancode)scancode);
+}
+
+static std::string AS_GetStringForMouseButton(int button) {
+ return StringFromMouseButton(button);
+}
+
+static std::string AS_GetStringForControllerInput(int input) {
+ return StringFromControllerInput((ControllerInput::Input)input);
+}
+
+static std::string AS_GetStringForMouseString(const std::string& text) {
+ return StringFromMouseString(text);
+}
+
+static uint32_t AS_GetInputMode() {
+ return Input::Instance()->getKeyboard().GetModes();
+}
+
+static bool AS_IsKeyDown(int which_key) {
+ return Input::Instance()->getKeyboard().isScancodeDown((SDL_Scancode)which_key,KIMF_PLAYING);
+}
+
+float AS_GetLookXAxis(int controller_id) {
+ PlayerInput* controller = Input::Instance()->GetController(controller_id);
+
+ if(controller == NULL) {
+ return 0.0f;
+ }
+
+ float left_depth = controller->key_down["look_left"].depth;
+ float right_depth = controller->key_down["look_right"].depth;
+ return right_depth - left_depth;
+}
+
+float AS_GetLookYAxis(int controller_id) {
+ PlayerInput* controller = Input::Instance()->GetController(controller_id);
+
+ if(controller == NULL) {
+ return 0.0f;
+ }
+
+ float up_depth = controller->key_down["look_up"].depth;
+ float down_depth = controller->key_down["look_down"].depth;
+ return down_depth - up_depth;
+}
+
+void AS_SetGrabMouse(bool grab) {
+ return Input::Instance()->SetGrabMouse(grab);
+}
+
+bool AS_GetDebugKeysEnabled(){
+ return Input::Instance()->debug_keys && Engine::Instance()->current_engine_state_.type == kEngineEditorLevelState;
+}
+
+bool AS_EditorEnabled(){
+ return Engine::Instance()->current_engine_state_.type == kEngineEditorLevelState;
+}
+
+static void AS_LoadEditorLevel() {
+ Engine* engine = Engine::Instance();
+ engine->NewLevel();
+}
+
+std::string AS_GetStringDescriptionForBinding( const std::string& type, const std::string& name ) {
+ if(type == "xbox") // Backwards compat
+ return Input::Instance()->GetStringDescriptionForBinding( "controller", name );
+ else
+ return Input::Instance()->GetStringDescriptionForBinding( type, name );
+}
+
+static CScriptArray* AS_GetRawKeyboardInputs() {
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<KeyboardPress>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+
+ std::vector<Keyboard::KeyboardPress> vals = Input::Instance()->GetKeyboardInputs();
+
+ array->Reserve(vals.size());
+
+ for( unsigned i = 0; i < vals.size(); i++ ) {
+ array->InsertLast((void*)&vals[i]);
+ }
+
+ return array;
+}
+
+static CScriptArray* AS_GetRawMouseInputs() {
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<MousePress>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+
+ std::vector<Mouse::MousePress> vals = Input::Instance()->GetMouseInputs();
+
+ array->Reserve(vals.size());
+
+ for( unsigned i = 0; i < vals.size(); i++ ) {
+ array->InsertLast((void*)&vals[i]);
+ }
+
+ return array;
+}
+
+static CScriptArray* AS_GetRawJoystickInputs(int which) {
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<ControllerPress>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+
+ std::vector<Joystick::JoystickPress> vals = Input::Instance()->GetJoystickInputs(which);
+
+ array->Reserve(vals.size());
+
+ for( unsigned i = 0; i < vals.size(); i++ ) {
+ array->InsertLast((void*)&vals[i]);
+ }
+
+ return array;
+}
+
+static bool AS_IsControllerConnected() {
+ return Input::Instance()->IsControllerConnected();
+}
+
+void AttachUIQueries(ASContext *context) {
+ context->RegisterGlobalFunction("bool GetInputDown(int controller_id, const string &in input_label)",
+ asFUNCTION(AS_GetInputDown),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("bool GetInputDownFiltered(int controller_id, const string &in input_label, uint filter)",
+ asFUNCTION(AS_GetInputDownFiltered),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("bool GetInputPressed(int controller_id, const string &in input_label)",
+ asFUNCTION(AS_GetInputPressed),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("bool GetInputPressedFiltered(int controller_id, const string &in input_label, uint filter)",
+ asFUNCTION(AS_GetInputPressedFiltered),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("void ActivateKeyboardEvents()",
+ asFUNCTION(AS_ActivateKeyboardEvents),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("void DeactivateKeyboardEvents()",
+ asFUNCTION(AS_DeactivateKeyboardEvents),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("string GetClipboard()",
+ asFUNCTION(AS_GetClipboard),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetClipboard(string text)",
+ asFUNCTION(AS_SetClipboard),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("void StartTextInput()",
+ asFUNCTION(AS_StartTextInput),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("void StopTextInput()",
+ asFUNCTION(AS_StopTextInput),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("float GetLookXAxis(int controller_id)",
+ asFUNCTION(AS_GetLookXAxis),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("float GetLookYAxis(int controller_id)",
+ asFUNCTION(AS_GetLookYAxis),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("float GetMoveXAxis(int controller_id)",
+ asFUNCTION(AS_GetMoveXAxis),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("float GetMoveYAxis(int controller_id)",
+ asFUNCTION(AS_GetMoveYAxis),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetGrabMouse(bool)",
+ asFUNCTION(AS_SetGrabMouse),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("bool DebugKeysEnabled()",
+ asFUNCTION(AS_GetDebugKeysEnabled),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("bool EditorEnabled()",
+ asFUNCTION(AS_EditorEnabled),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("void LoadEditorLevel()",
+ asFUNCTION(AS_LoadEditorLevel),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("bool IsKeyDown(int key_code)",
+ asFUNCTION(AS_IsKeyDown),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("int GetCodeForKey(string key_name)",
+ asFUNCTION(AS_GetCodeForKey),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction( "string GetStringDescriptionForBinding( const string& in, const string& in )",
+ asFUNCTION(AS_GetStringDescriptionForBinding),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction( "string GetLocaleStringForScancode(int scancode)",
+ asFUNCTION(AS_GetLocaleStringForScancode),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction( "string GetStringForMouseButton(int button)",
+ asFUNCTION(AS_GetStringForMouseButton),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction( "string GetStringForControllerInput(int input)",
+ asFUNCTION(AS_GetStringForControllerInput),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction( "string GetStringForMouseString(const string& text)",
+ asFUNCTION(AS_GetStringForMouseString),
+ asCALL_CDECL);
+
+ context->RegisterEnum("KeyboardInputModeFlag");
+ context->RegisterEnumValue("KeyboardInputModeFlag","KIMF_NO",KIMF_NO);
+ context->RegisterEnumValue("KeyboardInputModeFlag","KIMF_MENU",KIMF_MENU);
+ context->RegisterEnumValue("KeyboardInputModeFlag","KIMF_PLAYING",KIMF_PLAYING);
+ context->RegisterEnumValue("KeyboardInputModeFlag","KIMF_LEVEL_EDITOR_GENERAL",KIMF_LEVEL_EDITOR_GENERAL);
+ context->RegisterEnumValue("KeyboardInputModeFlag","KIMF_LEVEL_EDITOR_DIALOGUE_EDITOR",KIMF_LEVEL_EDITOR_DIALOGUE_EDITOR);
+ context->RegisterEnumValue("KeyboardInputModeFlag","KIMF_GUI_GENERAL",KIMF_GUI_GENERAL);
+ context->RegisterEnumValue("KeyboardInputModeFlag","KIMF_ANY",KIMF_ANY);
+
+ context->RegisterEnum("SDLNumeric");
+ context->RegisterEnumValue("SDLNumeric", "K_ESCAPE", SDLK_ESCAPE);
+ context->RegisterEnumValue("SDLNumeric", "K_ENTER", SDLK_RETURN);
+ context->RegisterEnumValue("SDLNumeric", "KP_ENTER", SDLK_KP_ENTER);
+ context->RegisterEnumValue("SDLNumeric", "K_BACKSPACE", SDLK_BACKSPACE);
+ context->RegisterEnumValue("SDLNumeric", "K_TAB", SDLK_TAB);
+ context->RegisterEnumValue("SDLNumeric", "K_0", SDLK_0);
+ context->RegisterEnumValue("SDLNumeric", "K_1", SDLK_1);
+ context->RegisterEnumValue("SDLNumeric", "K_2", SDLK_2);
+ context->RegisterEnumValue("SDLNumeric", "K_3", SDLK_3);
+ context->RegisterEnumValue("SDLNumeric", "K_4", SDLK_4);
+ context->RegisterEnumValue("SDLNumeric", "K_5", SDLK_5);
+ context->RegisterEnumValue("SDLNumeric", "K_6", SDLK_6);
+ context->RegisterEnumValue("SDLNumeric", "K_7", SDLK_7);
+ context->RegisterEnumValue("SDLNumeric", "K_8", SDLK_8);
+ context->RegisterEnumValue("SDLNumeric", "K_9", SDLK_9);
+ context->RegisterEnumValue("SDLNumeric", "KP_0", SDLK_KP_0);
+ context->RegisterEnumValue("SDLNumeric", "KP_1", SDLK_KP_1);
+ context->RegisterEnumValue("SDLNumeric", "KP_2", SDLK_KP_2);
+ context->RegisterEnumValue("SDLNumeric", "KP_3", SDLK_KP_3);
+ context->RegisterEnumValue("SDLNumeric", "KP_4", SDLK_KP_4);
+ context->RegisterEnumValue("SDLNumeric", "KP_5", SDLK_KP_5);
+ context->RegisterEnumValue("SDLNumeric", "KP_6", SDLK_KP_6);
+ context->RegisterEnumValue("SDLNumeric", "KP_7", SDLK_KP_7);
+ context->RegisterEnumValue("SDLNumeric", "KP_8", SDLK_KP_8);
+ context->RegisterEnumValue("SDLNumeric", "KP_9", SDLK_KP_9);
+
+ context->RegisterGlobalFunction("uint GetInputMode()",
+ asFUNCTION(AS_GetInputMode),
+ asCALL_CDECL);
+
+ context->RegisterObjectType("KeyboardPress", sizeof(Keyboard::KeyboardPress), asOBJ_VALUE | asOBJ_POD );
+ context->RegisterObjectProperty("KeyboardPress", "uint16 s_id", asOFFSET(Keyboard::KeyboardPress, s_id));
+ context->RegisterObjectProperty("KeyboardPress", "uint32 keycode", asOFFSET(Keyboard::KeyboardPress, keycode));
+ context->RegisterObjectProperty("KeyboardPress", "uint32 scancode", asOFFSET(Keyboard::KeyboardPress, scancode));
+ context->RegisterObjectProperty("KeyboardPress", "uint16 mod", asOFFSET(Keyboard::KeyboardPress, mod));
+
+ context->RegisterGlobalProperty("float last_keyboard_event_time", &(Input::Instance()->last_keyboard_event_time));
+ context->RegisterGlobalProperty("float last_mouse_event_time", &(Input::Instance()->last_mouse_event_time));
+ context->RegisterGlobalProperty("float last_controller_event_time", &(Input::Instance()->last_controller_event_time));
+
+ context->RegisterGlobalFunction( "array<KeyboardPress>@ GetRawKeyboardInputs()",
+ asFUNCTION(AS_GetRawKeyboardInputs),
+ asCALL_CDECL);
+
+ context->RegisterEnum("MouseButton");
+ context->RegisterEnumValue("MouseButton", "LEFT", Mouse::MouseButton::LEFT);
+ context->RegisterEnumValue("MouseButton", "MIDDLE", Mouse::MouseButton::MIDDLE);
+ context->RegisterEnumValue("MouseButton", "RIGHT", Mouse::MouseButton::RIGHT);
+ context->RegisterEnumValue("MouseButton", "FOURTH", Mouse::MouseButton::FOURTH);
+ context->RegisterEnumValue("MouseButton", "FIFTH", Mouse::MouseButton::FIFTH);
+ context->RegisterEnumValue("MouseButton", "SIXTH", Mouse::MouseButton::SIXTH);
+ context->RegisterEnumValue("MouseButton", "SEVENTH", Mouse::MouseButton::SEVENTH);
+ context->RegisterEnumValue("MouseButton", "EIGHT", Mouse::MouseButton::EIGHT);
+ context->RegisterEnumValue("MouseButton", "NINTH", Mouse::MouseButton::NINTH);
+ context->RegisterEnumValue("MouseButton", "TENTH", Mouse::MouseButton::TENTH);
+ context->RegisterEnumValue("MouseButton", "TWELFTH", Mouse::MouseButton::TWELFTH);
+
+ context->RegisterObjectType("MousePress", sizeof(Mouse::MousePress), asOBJ_VALUE | asOBJ_POD );
+ context->RegisterObjectProperty("MousePress", "uint16 s_id", asOFFSET(Mouse::MousePress, s_id));
+ context->RegisterObjectProperty("MousePress", "MouseButton button", asOFFSET(Mouse::MousePress, button));
+
+ context->RegisterGlobalFunction( "array<MousePress>@ GetRawMouseInputs()",
+ asFUNCTION(AS_GetRawMouseInputs),
+ asCALL_CDECL);
+
+ context->RegisterEnum("ControllerInput");
+ context->RegisterEnumValue("ControllerInput", "A", ControllerInput::A);
+ context->RegisterEnumValue("ControllerInput", "B", ControllerInput::B);
+ context->RegisterEnumValue("ControllerInput", "X", ControllerInput::X);
+ context->RegisterEnumValue("ControllerInput", "Y", ControllerInput::Y);
+ context->RegisterEnumValue("ControllerInput", "D_UP", ControllerInput::D_UP);
+ context->RegisterEnumValue("ControllerInput", "D_RIGHT", ControllerInput::D_RIGHT);
+ context->RegisterEnumValue("ControllerInput", "D_DOWN", ControllerInput::D_DOWN);
+ context->RegisterEnumValue("ControllerInput", "D_LEFT", ControllerInput::D_LEFT);
+ context->RegisterEnumValue("ControllerInput", "START", ControllerInput::START);
+ context->RegisterEnumValue("ControllerInput", "BACK", ControllerInput::BACK);
+ context->RegisterEnumValue("ControllerInput", "GUIDE", ControllerInput::GUIDE);
+ context->RegisterEnumValue("ControllerInput", "L_STICK_PRESSED", ControllerInput::L_STICK_PRESSED);
+ context->RegisterEnumValue("ControllerInput", "R_STICK_PRESSED", ControllerInput::R_STICK_PRESSED);
+ context->RegisterEnumValue("ControllerInput", "LB", ControllerInput::LB);
+ context->RegisterEnumValue("ControllerInput", "RB", ControllerInput::RB);
+ context->RegisterEnumValue("ControllerInput", "L_STICK_XN", ControllerInput::L_STICK_XN);
+ context->RegisterEnumValue("ControllerInput", "L_STICK_XP", ControllerInput::L_STICK_XP);
+ context->RegisterEnumValue("ControllerInput", "L_STICK_YN", ControllerInput::L_STICK_YN);
+ context->RegisterEnumValue("ControllerInput", "L_STICK_YP", ControllerInput::L_STICK_YP);
+ context->RegisterEnumValue("ControllerInput", "R_STICK_XN", ControllerInput::R_STICK_XN);
+ context->RegisterEnumValue("ControllerInput", "R_STICK_XP", ControllerInput::R_STICK_XP);
+ context->RegisterEnumValue("ControllerInput", "R_STICK_YN", ControllerInput::R_STICK_YN);
+ context->RegisterEnumValue("ControllerInput", "R_STICK_YP", ControllerInput::R_STICK_YP);
+ context->RegisterEnumValue("ControllerInput", "L_TRIGGER", ControllerInput::L_TRIGGER);
+ context->RegisterEnumValue("ControllerInput", "R_TRIGGER", ControllerInput::R_TRIGGER);
+
+ context->RegisterObjectType("ControllerPress", sizeof(Joystick::JoystickPress), asOBJ_VALUE | asOBJ_POD);
+ context->RegisterObjectProperty("ControllerPress", "uint32 s_id", asOFFSET(Joystick::JoystickPress, s_id));
+ context->RegisterObjectProperty("ControllerPress", "ControllerInput input", asOFFSET(Joystick::JoystickPress, input));
+ context->RegisterObjectProperty("ControllerPress", "float depth", asOFFSET(Joystick::JoystickPress, depth));
+
+ context->RegisterGlobalFunction("array<ControllerPress>@ GetRawControllerInputs(int which)",
+ asFUNCTION(AS_GetRawJoystickInputs),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("bool IsControllerConnected()",
+ asFUNCTION(AS_IsControllerConnected),
+ asCALL_CDECL);
+}
+
+void ASDisplayError(const std::string& title, const std::string& contents){
+ DisplayError(title.c_str(), contents.c_str(), _ok);
+}
+
+void AttachError(ASContext *context) {
+ context->RegisterGlobalFunction("void DisplayError(const string &in title, const string &in contents)",
+ asFUNCTION(ASDisplayError),
+ asCALL_CDECL);
+}
+
+float as_min(float a, float b) {
+ return a<b?a:b;
+}
+
+float as_max(float a, float b) {
+ return a>b?a:b;
+}
+
+int as_min_int(int a, int b) {
+ return a<b?a:b;
+}
+
+int as_max_int(int a, int b) {
+ return a>b?a:b;
+}
+
+float asPow(float a, float b) {
+ return (float)pow(a, b);
+}
+
+// As AngelScript doesn't allow bitwise manipulation of float types we'll provide a couple of
+// functions for converting float values to IEEE 754 formatted values etc. This also allow us to
+// provide a platform agnostic representation to the script so the scripts don't have to worry
+// about whether the CPU uses IEEE 754 floats or some other representation
+float fpFromIEEE(asUINT raw)
+{
+ // TODO: Identify CPU family to provide proper conversion
+ // if the CPU doesn't natively use IEEE style floats
+ return *reinterpret_cast<float*>(&raw);
+}
+asUINT fpToIEEE(float fp)
+{
+ return *reinterpret_cast<asUINT*>(&fp);
+}
+double fpFromIEEE(asQWORD raw)
+{
+ return *reinterpret_cast<double*>(&raw);
+}
+asQWORD fpToIEEE(double fp)
+{
+ return *reinterpret_cast<asQWORD*>(&fp);
+}
+
+static uint32_t uint32max = UINT32MAX;
+
+void AttachMathFuncs( ASContext *context ) {
+ context->RegisterGlobalProperty("uint UINT32MAX", &uint32max);
+
+ // Conversion between floating point and IEEE bits representations
+ context->RegisterGlobalFunction("float fpFromIEEE(uint)", asFUNCTIONPR(fpFromIEEE, (asUINT), float), asCALL_CDECL);
+ context->RegisterGlobalFunction("uint fpToIEEE(float)", asFUNCTIONPR(fpToIEEE, (float), asUINT), asCALL_CDECL);
+ context->RegisterGlobalFunction("double fpFromIEEE(uint64)", asFUNCTIONPR(fpFromIEEE, (asQWORD), double), asCALL_CDECL);
+ context->RegisterGlobalFunction("uint64 fpToIEEE(double)", asFUNCTIONPR(fpToIEEE, (double), asQWORD), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("float min(float,float)", asFUNCTION(as_min), asCALL_CDECL);
+ context->RegisterGlobalFunction("float max(float,float)", asFUNCTION(as_max), asCALL_CDECL);
+ context->RegisterGlobalFunction("int min(int,int)", asFUNCTION(as_min_int), asCALL_CDECL);
+ context->RegisterGlobalFunction("int max(int,int)", asFUNCTION(as_max_int), asCALL_CDECL);
+ context->RegisterGlobalFunction("float mix(float a,float b,float amount)", asFUNCTIONPR(mix, (float,float,float), float), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("float cos(float)", asFUNCTIONPR(cosf, (float), float), asCALL_CDECL);
+ context->RegisterGlobalFunction("float sin(float)", asFUNCTIONPR(sinf, (float), float), asCALL_CDECL);
+ context->RegisterGlobalFunction("float tan(float)", asFUNCTIONPR(tanf, (float), float), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("float acos(float)", asFUNCTIONPR(acosf, (float), float), asCALL_CDECL);
+ context->RegisterGlobalFunction("float asin(float)", asFUNCTIONPR(asinf, (float), float), asCALL_CDECL);
+ context->RegisterGlobalFunction("float atan(float)", asFUNCTIONPR(atanf, (float), float), asCALL_CDECL);
+ context->RegisterGlobalFunction("float atan2(float,float)", asFUNCTIONPR(atan2f, (float, float), float), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("float log(float)", asFUNCTIONPR(logf, (float), float), asCALL_CDECL);
+ context->RegisterGlobalFunction("float log10(float)", asFUNCTIONPR(log10f, (float), float), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("float pow(float val, float exponent)", asFUNCTIONPR(asPow, (float, float), float), asCALL_CDECL);
+ context->RegisterGlobalFunction("float sqrt(float)", asFUNCTIONPR(sqrtf, (float), float), asCALL_CDECL);
+ context->RegisterGlobalFunction("int rand()", asFUNCTIONPR(rand, (void), int), asCALL_CDECL);
+ context->RegisterGlobalFunction("float RangedRandomFloat(float min, float max)", asFUNCTION(RangedRandomFloat), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("float ceil(float)", asFUNCTIONPR(ceilf, (float), float), asCALL_CDECL);
+ context->RegisterGlobalFunction("float abs(float)", asFUNCTIONPR(fabsf, (float), float), asCALL_CDECL);
+ context->RegisterGlobalFunction("float floor(float)", asFUNCTIONPR(floorf, (float), float), asCALL_CDECL);
+}
+
+#include "Math/quaternions.h"
+
+static void quaternionDefaultConstructor(quaternion *self) {
+ new(self) quaternion();
+}
+
+static void quaternionCopyConstructor(const quaternion &other, quaternion *self) {
+ new(self) quaternion(other);
+}
+
+static void quaternionInitConstructor(float x, float y, float z, float w, quaternion *self) {
+ new(self) quaternion(x,y,z,w);
+}
+
+static void quaternionInitConstructor2(vec4 vec, quaternion *self) {
+ new(self) quaternion(vec);
+}
+
+static void ASMult_Generic (asIScriptGeneric *gen) {
+ quaternion *a = (quaternion*)gen->GetArgObject(0);
+ vec3 *b = (vec3*)gen->GetArgObject(1);
+ vec3 prod = *b;
+ QuaternionMultiplyVector(a, &prod);
+ gen->SetReturnObject(&prod);
+}
+
+/*
+static void quatquatmultgeneric(asIScriptGeneric *gen) {
+ quaternion *a = (quaternion*)gen->GetObject();
+ quaternion *b = (quaternion*)gen->GetArgObject(0);
+ quaternion prod = (*a)*(*b);
+ gen->SetReturnObject(&prod);
+}
+*/
+
+static quaternion quatquatmult(quaternion *a, const quaternion& b) {
+ return *a * b;
+}
+
+static vec3 quatvecmult(quaternion *a, const vec3& b) {
+ vec3 prod = b;
+ QuaternionMultiplyVector(a, &prod);
+ return prod;
+}
+
+static void quatdestructor(void *memory) {
+ ((quaternion*)memory)->~quaternion();
+}
+
+static const quaternion& quatassign(quaternion *self, const quaternion& other) {
+ return (*self) = other;
+}
+
+quaternion ASQuatMix(const quaternion &a, const quaternion &b, float alpha) {
+ return mix(a, b, alpha);
+}
+
+void AttachQuaternionFuncs( ASContext *context ) {
+ // Register the type
+ context->RegisterObjectType("quaternion", sizeof(quaternion), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK);
+
+ // Register the object properties
+ context->RegisterObjectProperty("quaternion", "float x", asOFFSET(quaternion, entries));
+ context->RegisterObjectProperty("quaternion", "float y", asOFFSET(quaternion, entries)+sizeof(float));
+ context->RegisterObjectProperty("quaternion", "float z", asOFFSET(quaternion, entries)+sizeof(float)*2);
+ context->RegisterObjectProperty("quaternion", "float w", asOFFSET(quaternion, entries)+sizeof(float)*3);
+
+ // Register the constructors
+ context->RegisterObjectBehaviour("quaternion", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(quaternionDefaultConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("quaternion", asBEHAVE_CONSTRUCT, "void f(const quaternion &in)", asFUNCTION(quaternionCopyConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("quaternion", asBEHAVE_CONSTRUCT, "void f(float, float, float, float)", asFUNCTION(quaternionInitConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("quaternion", asBEHAVE_CONSTRUCT, "void f(vec4)", asFUNCTION(quaternionInitConstructor2), asCALL_CDECL_OBJLAST, "Axis-angle (axis.x, axis.y, axis.z, angle_radians)");
+ context->RegisterObjectBehaviour("quaternion", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(quatdestructor), asCALL_CDECL_OBJLAST);
+
+ // Register the operator overloads
+ context->RegisterObjectMethod("quaternion", "quaternion &opAssign(const quaternion &in)", asFUNCTION(quatassign), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("quaternion", "quaternion &opAddAssign(const quaternion &in)", asMETHOD(quaternion, operator+=), asCALL_THISCALL);
+ context->RegisterObjectMethod("quaternion", "bool opEquals(const quaternion &in) const", asFUNCTIONPR(operator==, (const quaternion&, const quaternion&), bool), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("quaternion", "quaternion opAdd(const quaternion &in) const", asFUNCTIONPR(operator+, (const quaternion&, const quaternion&), const quaternion), asCALL_CDECL_OBJFIRST);
+ //context->RegisterObjectMethod("quaternion", "quaternion opMul(const quaternion &in) const", asFUNCTIONPR(operator*, (const quaternion&, const quaternion&), const quaternion), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("quaternion", "quaternion opMul(const quaternion &in) const", asFUNCTION(quatquatmult), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("quaternion", "vec3 opMul(const vec3 &in) const", asFUNCTION(quatvecmult), asCALL_CDECL_OBJFIRST);
+ context->DocsCloseBrace();
+
+ // Register the object methods
+ context->RegisterGlobalFunction("vec3 Mult(quaternion, vec3)", asFUNCTION(ASMult_Generic), asCALL_GENERIC, "Applies a quaternion rotation to a vector");
+ context->RegisterGlobalFunction("quaternion mix(const quaternion &in a, const quaternion &in b, float alpha)", asFUNCTION(ASQuatMix), asCALL_CDECL);
+ context->RegisterGlobalFunction("quaternion invert(quaternion quat)", asFUNCTIONPR(invert_by_val, (quaternion), quaternion), asCALL_CDECL);
+ context->RegisterGlobalFunction("void GetRotationBetweenVectors(const vec3 &in start, const vec3 &in end, quaternion &out rotation)", asFUNCTION(GetRotationBetweenVectors), asCALL_CDECL);
+}
+
+static void ASSetSunPosition(vec3 pos){
+ the_scenegraph->primary_light.pos = normalize(pos);
+ the_scenegraph->flares.flares[0]->position = the_scenegraph->primary_light.pos;
+}
+
+static void ASSetSunColor(vec3 color){
+ the_scenegraph->primary_light.color = color;
+ the_scenegraph->flares.flares[0]->color = color;
+}
+
+static void ASSetSunAmbient(float ambient){
+ the_scenegraph->primary_light.intensity = ambient;
+}
+
+static vec3 ASGetSunPosition(){
+ return the_scenegraph->primary_light.pos;
+}
+
+static vec3 ASGetSunColor(){
+ return(the_scenegraph->primary_light.color);
+}
+
+static float ASGetSunAmbient(){
+ return the_scenegraph->primary_light.intensity;
+}
+
+static void ASSetSkyTint(vec3 color){
+ the_scenegraph->sky->sky_tint = color;
+}
+
+static vec3 ASGetSkyTint(){
+ return the_scenegraph->sky->sky_tint;
+}
+
+static vec3 ASGetBaseSkyTint(){
+ return the_scenegraph->sky->sky_base_tint;
+}
+
+static void ASSetFlareDiffuse(float diffuse){
+ the_scenegraph->flares.flares[0]->diffuse = diffuse;
+}
+
+static void ASSetHDRWhitePoint(float val){
+ Graphics::Instance()->hdr_white_point = val;
+}
+
+static void ASSetHDRBlackPoint(float val){
+ Graphics::Instance()->hdr_black_point = val;
+}
+
+static void ASSetHDRBloomMult(float val){
+ Graphics::Instance()->hdr_bloom_mult = val;
+}
+
+static float ASGetHDRWhitePoint(){
+ return Graphics::Instance()->hdr_white_point;
+}
+
+static float ASGetHDRBlackPoint(){
+ return Graphics::Instance()->hdr_black_point;
+}
+
+static float ASGetHDRBloomMult(){
+ return Graphics::Instance()->hdr_bloom_mult;
+}
+
+void AttachSky( ASContext *context ) {
+ // Register the object methods
+ context->RegisterGlobalFunction("void SetSunPosition(vec3)", asFUNCTION(ASSetSunPosition), asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetSunColor(vec3)", asFUNCTION(ASSetSunColor), asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetSunAmbient(float)", asFUNCTION(ASSetSunAmbient), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec3 GetSunPosition()", asFUNCTION(ASGetSunPosition), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec3 GetSunColor()", asFUNCTION(ASGetSunColor), asCALL_CDECL);
+ context->RegisterGlobalFunction("float GetSunAmbient()", asFUNCTION(ASGetSunAmbient), asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetFlareDiffuse(float)", asFUNCTION(ASSetFlareDiffuse), asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetSkyTint(vec3)", asFUNCTION(ASSetSkyTint), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec3 GetSkyTint()", asFUNCTION(ASGetSkyTint), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec3 GetBaseSkyTint()", asFUNCTION(ASGetBaseSkyTint), asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetHDRWhitePoint(float)", asFUNCTION(ASSetHDRWhitePoint), asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetHDRBlackPoint(float)", asFUNCTION(ASSetHDRBlackPoint), asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetHDRBloomMult(float)", asFUNCTION(ASSetHDRBloomMult), asCALL_CDECL);
+ context->RegisterGlobalFunction("float GetHDRWhitePoint(void)", asFUNCTION(ASGetHDRWhitePoint), asCALL_CDECL);
+ context->RegisterGlobalFunction("float GetHDRBlackPoint(void)", asFUNCTION(ASGetHDRBlackPoint), asCALL_CDECL);
+ context->RegisterGlobalFunction("float GetHDRBloomMult(void)", asFUNCTION(ASGetHDRBloomMult), asCALL_CDECL);
+}
+
+std::vector<char> file_buf;
+int file_index;
+
+bool ASLoadFile(const std::string &path){
+ DiskFileDescriptor file;
+ char abs_path[kPathSize];
+ if(-1 == FindFilePath(path.c_str(), abs_path, kPathSize, kModPaths|kDataPaths|kAbsPath|kWriteDir|kModWriteDirs)){
+ const int kBufSize = 512;
+ char err[kBufSize];
+ FormatString(err, kBufSize, "Could not find file: %s", path.c_str());
+ DisplayError("Error", err);
+ return false;
+ }
+ if(!file.Open(abs_path, "r")){
+ const int kBufSize = 512;
+ char err[kBufSize];
+ FormatString(err, kBufSize, "Could not open file: %s", abs_path);
+ DisplayError("Error", err);
+ return false;
+ }
+ int file_size = file.GetSize();
+ file_buf.resize(file_size);
+
+ int bytes_read = file.ReadBytesPartial(&file_buf[0], file_size);
+ file_buf.resize(bytes_read+1);
+ file_buf.back() = '\0';
+
+ file.Close();
+ file_index = 0;
+ return true;
+}
+
+std::string ASGetFileLine(){
+ std::string ret;
+ int str_max = (int)file_buf.size();
+ if(file_index == str_max || file_buf[file_index] == '\0'){
+ return "end";
+ }
+ int str_end = file_index;
+ for(int i=file_index; i<str_max; ++i){
+ str_end = i;
+ if(file_buf[i] == '\n' || file_buf[i] == '\r' || file_buf[i] == '\0'){
+ break;
+ }
+ }
+ ret.resize(str_end - file_index);
+ for(int i=file_index; i<str_end; ++i){
+ ret[i-file_index] = file_buf[i];
+ }
+ file_index = str_end+1;
+ return ret;
+}
+
+void ASStartWriteFile() {
+ file_buf.clear();
+}
+
+void ASAddFileString(const std::string &str){
+ int index = file_buf.size();
+ int str_len = str.length();
+ file_buf.resize(index + str_len);
+ for(int i=0; i<str_len; ++i){
+ file_buf[index] = str[i];
+ ++index;
+ }
+}
+
+bool ASWriteFile(const std::string &path){
+ DiskFileDescriptor file;
+ if(!file.Open(path, "w")){
+ return false;
+ }
+ file.WriteBytes(&file_buf[0], file_buf.size());
+ file.Close();
+ return true;
+}
+
+bool ASWriteFileKeepBackup(const std::string &path){
+ DiskFileDescriptor file;
+ CreateBackup(path.c_str());
+
+ if(!file.Open(path, "w")){
+ return false;
+ }
+ file.WriteBytes(&file_buf[0], file_buf.size());
+ file.Close();
+ return true;
+}
+
+bool ASWriteFileToWriteDir(const std::string &path){
+ DiskFileDescriptor file;
+ std::string full_path = std::string(GetWritePath(CoreGameModID).c_str())+"/"+path;
+
+ CreateParentDirs(full_path);
+
+ if(!file.Open(full_path, "w")){
+ return false;
+ }
+
+ file.WriteBytes(&file_buf[0], file_buf.size());
+ file.Close();
+
+ return true;
+}
+
+std::string ASGetLocalizedDialoguePath(const std::string& path) {
+ const std::string& locale = config["language"].str();
+ char buffer[kPathSize];
+ strcpy(buffer, path.c_str());
+ ApplicationPathSeparators(buffer);
+ if(memcmp(buffer, "Data/Dialogues/", strlen("Data/Dialogues/")) == 0) {
+ FormatString(buffer, kPathSize, "Data/DialoguesLocalized/%s/%s", locale.c_str(), path.c_str() + strlen("Data/Dialogues/"));
+ }
+
+ Path found_path = FindFilePath(buffer, kAnyPath, false);
+ if(found_path.isValid()) {
+ return found_path.GetFullPath();
+ } else {
+ FormatString(buffer, kPathSize, "Data/DialoguesLocalized/en_us/%s", path.c_str() + strlen("Data/Dialogues/"));
+ found_path = FindFilePath(buffer, kAnyPath, false);
+ if(found_path.isValid()) {
+ return found_path.GetFullPath();
+ }
+
+ return path;
+ }
+}
+
+void AttachSimpleFile( ASContext *context ) {
+ context->RegisterGlobalFunction("bool LoadFile(const string &in)", asFUNCTION(ASLoadFile), asCALL_CDECL);
+ context->RegisterGlobalFunction("string GetFileLine()", asFUNCTION(ASGetFileLine), asCALL_CDECL);
+ context->RegisterGlobalFunction("void StartWriteFile()", asFUNCTION(ASStartWriteFile), asCALL_CDECL);
+ context->RegisterGlobalFunction("void AddFileString(const string &in)", asFUNCTION(ASAddFileString), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool WriteFile(const string &in)", asFUNCTION(ASWriteFile), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool WriteFileKeepBackup(const string &in)", asFUNCTION(ASWriteFileKeepBackup), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool WriteFileToWriteDir(const string &in)", asFUNCTION(ASWriteFileToWriteDir), asCALL_CDECL);
+ context->RegisterGlobalFunction("string GetLocalizedDialoguePath(const string &in)", asFUNCTION(ASGetLocalizedDialoguePath), asCALL_CDECL);
+}
+
+static void BoneTransformDefaultConstructor(BoneTransform *self) {
+ new(self) BoneTransform();
+}
+
+static void BoneTransformCopyConstructor(BoneTransform *self, const mat4 &other) {
+ new(self) BoneTransform(other);
+}
+
+static void BoneTransformCopyConstructor2(BoneTransform *self, const BoneTransform &other) {
+ new(self) BoneTransform(other);
+}
+
+static BoneTransform BoneTransformOpMult(BoneTransform *self, const BoneTransform &other) {
+ return *self * other;
+}
+
+static bool BoneTransformOpEquals(BoneTransform *self, const BoneTransform &other) {
+ return (*self == other);
+}
+
+static BoneTransform BoneTransformOpMultQuat(quaternion *self, const BoneTransform &other) {
+ return *self * other;
+}
+
+static vec3 BoneTransformOpMult2(BoneTransform *self, const vec3 &other) {
+ return *self * other;
+}
+
+static BoneTransform BoneTransformInvert(const BoneTransform &transform) {
+ return invert(transform);
+}
+
+static BoneTransform BoneTransformMix(const BoneTransform &a, const BoneTransform &b, float alpha) {
+ return mix(a,b,alpha);
+}
+
+static mat4 BoneTransformGetMat4(BoneTransform *self){
+ return self->GetMat4();
+}
+
+static void AttachBoneTransform( ASContext *context ) {
+ // Register the type
+ context->RegisterObjectType("BoneTransform",
+ sizeof(BoneTransform),
+ asOBJ_VALUE | asOBJ_APP_CLASS | asOBJ_POD,
+ "An efficient way to define an unscaled transformation");
+ context->RegisterObjectProperty("BoneTransform", "quaternion rotation", asOFFSET(BoneTransform, rotation));
+ context->RegisterObjectProperty("BoneTransform", "vec3 origin", asOFFSET(BoneTransform, origin));
+
+ // Register the constructors
+ context->RegisterObjectBehaviour("BoneTransform", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(BoneTransformDefaultConstructor), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectBehaviour("BoneTransform", asBEHAVE_CONSTRUCT, "void f(const mat4 &in)", asFUNCTION(BoneTransformCopyConstructor), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectBehaviour("BoneTransform", asBEHAVE_CONSTRUCT, "void f(const BoneTransform &in)", asFUNCTION(BoneTransformCopyConstructor2), asCALL_CDECL_OBJFIRST);
+
+ // Register the operator overloads
+ context->RegisterObjectMethod("BoneTransform", "bool opEquals(const BoneTransform &in) const", asFUNCTION(BoneTransformOpEquals), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("BoneTransform", "BoneTransform opMul(const BoneTransform &in) const", asFUNCTION(BoneTransformOpMult), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("quaternion", "BoneTransform opMul(const BoneTransform &in) const", asFUNCTION(BoneTransformOpMultQuat), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("BoneTransform", "vec3 opMul(const vec3 &in) const", asFUNCTION(BoneTransformOpMult2), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("BoneTransform", "mat4 GetMat4() const", asFUNCTION(BoneTransformGetMat4), asCALL_CDECL_OBJFIRST);
+ context->DocsCloseBrace();
+ context->RegisterGlobalFunction("BoneTransform invert(const BoneTransform &in)", asFUNCTION(BoneTransformInvert), asCALL_CDECL);
+ context->RegisterGlobalFunction("BoneTransform mix(const BoneTransform &in a, const BoneTransform &in b, float alpha)", asFUNCTION(BoneTransformMix), asCALL_CDECL);
+}
+
+static void mat4DefaultConstructor(mat4 *self) {
+ new(self) mat4();
+}
+
+static void mat4CopyConstructor(const mat4 &other, mat4 *self) {
+ new(self) mat4(other);
+}
+
+void ASSetTranslationPart(vec3 val, mat4 *self) {
+ self->SetTranslationPart(val);
+}
+
+vec3 ASGetTranslationPart(mat4 *self) {
+ return self->GetTranslationPart();
+}
+
+void ASSetRotationPart(mat4 val, mat4 *self) {
+ self->SetRotationPart(val);
+}
+
+mat4 ASGetRotationPart(mat4 *self) {
+ return self->GetRotationPart();
+}
+
+void ASSetColumn(int which, vec3 val, mat4 *self) {
+ self->SetColumn(which,val);
+}
+
+vec3 ASGetColumn(int which, mat4 *self) {
+ return self->GetColumn(which).xyz();
+}
+
+mat4 AStranspose(mat4 mat) {
+ return transpose(mat);
+}
+
+mat4 ASinvert(mat4 mat) {
+ return invert(mat);
+}
+
+static void mat4multgeneric(asIScriptGeneric *gen) {
+ mat4 *a = (mat4*)gen->GetObject();
+ mat4 *b = (mat4*)gen->GetArgObject(0);
+ mat4 prod = (*a)*(*b);
+ gen->SetReturnObject(&prod);
+}
+
+static void mat4multvec3generic(asIScriptGeneric *gen) {
+ mat4 *a = (mat4*)gen->GetObject();
+ vec3 *b = (vec3*)gen->GetArgObject(0);
+ vec3 prod = (*a)*(*b);
+ gen->SetReturnObject(&prod);
+}
+
+static void mat4multvec4generic(asIScriptGeneric *gen) {
+ mat4 *a = (mat4*)gen->GetObject();
+ vec4 *b = (vec4*)gen->GetArgObject(0);
+ vec4 prod = (*a)*(*b);
+ gen->SetReturnObject(&prod);
+}
+
+static quaternion ASQuaternionFromMat4(const mat4& mat) {
+ return QuaternionFromMat4(mat);
+}
+
+static mat4 ASMatrixMix(const mat4& a, const mat4& b, float alpha){
+ return mix(a, b, alpha);
+}
+
+static float& mat4index(unsigned int which, mat4 *mat){
+ return mat->entries[which];
+}
+
+static const float& mat4indexconst(unsigned int which, mat4 *mat){
+ return mat->entries[which];
+}
+
+static void mat3DefaultConstructor(mat3 *self) {
+ new(self) mat3();
+}
+
+static void mat3CopyConstructor(const mat3 &other, mat3 *self) {
+ new(self) mat3(other);
+}
+
+static float& mat3index(unsigned int which, mat3 *mat){
+ return mat->entries[which];
+}
+
+static const float& mat3indexconst(unsigned int which, mat3 *mat){
+ return mat->entries[which];
+}
+
+static vec3 mat3multvec3(mat3 *mat, const vec3 &vec){
+ return *mat * vec;
+}
+
+void AttachMatrixFuncs( ASContext *context ) {
+ // Register the type
+ context->RegisterObjectType("mat4",
+ sizeof(mat4),
+ asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS_CDA | asOBJ_APP_CLASS_ALLFLOATS);
+
+ // Register the constructors
+ context->RegisterObjectBehaviour("mat4", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(mat4DefaultConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("mat4", asBEHAVE_CONSTRUCT, "void f(const mat4 &in)", asFUNCTION(mat4CopyConstructor), asCALL_CDECL_OBJLAST);
+
+ // Register the operator overloads
+ context->RegisterObjectMethod("mat4", "float &opIndex(uint)", asFUNCTION(mat4index), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectMethod("mat4", "const float &opIndex(uint) const", asFUNCTION(mat4indexconst), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectMethod("mat4", "mat4 opMul(mat4) const", asFUNCTION(mat4multgeneric), asCALL_GENERIC);
+ context->RegisterObjectMethod("mat4", "vec3 opMul(vec3) const", asFUNCTION(mat4multvec3generic), asCALL_GENERIC);
+ context->RegisterObjectMethod("mat4", "vec3 opMul(vec4) const", asFUNCTION(mat4multvec4generic), asCALL_GENERIC);
+ context->RegisterObjectMethod("mat4", "void SetTranslationPart(vec3)", asFUNCTION(ASSetTranslationPart), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectMethod("mat4", "vec3 GetTranslationPart() const", asFUNCTION(ASGetTranslationPart), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectMethod("mat4", "void SetRotationPart(mat4)", asFUNCTION(ASSetRotationPart), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectMethod("mat4", "mat4 GetRotationPart() const", asFUNCTION(ASGetRotationPart), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectMethod("mat4", "void SetRotationX(float)", asMETHOD(mat4, SetRotationX), asCALL_THISCALL);
+ context->RegisterObjectMethod("mat4", "void SetRotationY(float)", asMETHOD(mat4, SetRotationY), asCALL_THISCALL);
+ context->RegisterObjectMethod("mat4", "void SetRotationZ(float)", asMETHOD(mat4, SetRotationZ), asCALL_THISCALL);
+ context->RegisterObjectMethod("mat4", "void SetColumn(int, vec3)", asFUNCTION(ASSetColumn), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectMethod("mat4", "vec3 GetColumn(int)", asFUNCTION(ASGetColumn), asCALL_CDECL_OBJLAST);
+ context->DocsCloseBrace();
+ context->RegisterGlobalFunction("mat4 transpose(mat4)", asFUNCTION(AStranspose), asCALL_CDECL);
+ context->RegisterGlobalFunction("mat4 invert(mat4)", asFUNCTION(ASinvert), asCALL_CDECL);
+ context->RegisterGlobalFunction("mat4 mix(const mat4 &in a, const mat4 &in b, float alpha)", asFUNCTION(ASMatrixMix), asCALL_CDECL);
+
+
+ // Register the type
+ context->RegisterObjectType("mat3",
+ sizeof(mat3),
+ asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS_CDA | asOBJ_APP_CLASS_ALLFLOATS);
+
+ // Register the constructors
+ context->RegisterObjectBehaviour("mat3", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(mat3DefaultConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("mat3", asBEHAVE_CONSTRUCT, "void f(const mat3 &in)", asFUNCTION(mat3CopyConstructor), asCALL_CDECL_OBJLAST);
+
+ // Register the operator overloads
+ context->RegisterObjectMethod("mat3", "float &opIndex(uint)", asFUNCTION(mat3index), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectMethod("mat3", "const float &opIndex(uint) const", asFUNCTION(mat3indexconst), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectMethod("mat3", "vec3 opMul(const vec3 &in) const", asFUNCTION(mat3multvec3), asCALL_CDECL_OBJFIRST);
+ context->DocsCloseBrace();
+
+ AttachBoneTransform(context);
+}
+
+static void vec3DefaultConstructor(vec3 *self) {
+ new(self) vec3();
+}
+
+static void vec3CopyConstructor(const vec3 &other, vec3 *self) {
+ new(self) vec3(other);
+}
+
+static void vec3InitConstructor(float x, float y, float z, vec3 *self) {
+ new(self) vec3(x,y,z);
+}
+
+static void vec3InitConstructor2(float val, vec3 *self) {
+ new(self) vec3(val);
+}
+
+void AttachVec3Funcs( ASContext *context) {
+ // Register the type
+ context->RegisterObjectType("vec3",
+ sizeof(vec3),
+ asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS_CA | asOBJ_APP_CLASS_ALLFLOATS);
+
+ // Register the object properties
+ context->RegisterObjectProperty("vec3", "float x", asOFFSET(vec3, entries));
+ context->RegisterObjectProperty("vec3", "float y", asOFFSET(vec3, entries)+sizeof(float));
+ context->RegisterObjectProperty("vec3", "float z", asOFFSET(vec3, entries)+sizeof(float)*2);
+
+ // Register the constructors
+ context->RegisterObjectBehaviour("vec3", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(vec3DefaultConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("vec3", asBEHAVE_CONSTRUCT, "void f(const vec3 &in)", asFUNCTION(vec3CopyConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("vec3", asBEHAVE_CONSTRUCT, "void f(float, float, float)", asFUNCTION(vec3InitConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("vec3", asBEHAVE_CONSTRUCT, "void f(float)", asFUNCTION(vec3InitConstructor2), asCALL_CDECL_OBJLAST);
+
+ // Register the operator overloads
+ context->RegisterObjectMethod("vec3", "vec3 &opAddAssign(const vec3 &in)", asFUNCTIONPR(operator+=, (vec3&, const vec3 &), vec3&), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("vec3", "vec3 &opSubAssign(const vec3 &in)", asFUNCTIONPR(operator-=, (vec3&, const vec3 &), vec3&), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("vec3", "vec3 &opMulAssign(float)", asFUNCTIONPR(operator*=, (vec3&, float), vec3&), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("vec3", "vec3 &opDivAssign(float)", asFUNCTIONPR(operator/=, (vec3&, float), vec3&), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("vec3", "bool opEquals(const vec3 &in) const", asFUNCTIONPR(operator==, (const vec3&, const vec3&), bool), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("vec3", "vec3 opAdd(const vec3 &in) const", asFUNCTIONPR(operator+, (const vec3&, const vec3&), vec3), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("vec3", "vec3 opSub(const vec3 &in) const", asFUNCTIONPR(operator-, (const vec3&, const vec3&), vec3), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("vec3", "vec3 opMul(float) const", asFUNCTIONPR(operator*, (const vec3&, float), vec3), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("vec3", "vec3 opMul(const vec3& in) const", asFUNCTIONPR(operator*, (const vec3&, const vec3&), vec3), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("vec3", "vec3 opMul_r(float) const", asFUNCTIONPR(operator*, (float, const vec3&), vec3), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectMethod("vec3", "vec3 opDiv(float) const", asFUNCTIONPR(operator/, (const vec3&, float), vec3), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("vec3", "float &opIndex(int)", asMETHODPR(vec3, operator[], (const int), float&), asCALL_THISCALL);
+ context->DocsCloseBrace();
+
+ // Register the object methods
+ context->RegisterGlobalFunction("float length(const vec3 &in)", asFUNCTIONPR(length, (const vec3&), float), asCALL_CDECL);
+ context->RegisterGlobalFunction("float length_squared(const vec3 &in)", asFUNCTIONPR(length_squared, (const vec3&), float), asCALL_CDECL);
+ context->RegisterGlobalFunction("float dot(const vec3 &in, const vec3 &in)", asFUNCTIONPR(dot, (const vec3&, const vec3&), float), asCALL_CDECL);
+ context->RegisterGlobalFunction("float distance(const vec3 &in, const vec3 &in)", asFUNCTIONPR(distance, (const vec3&,const vec3&), float), asCALL_CDECL);
+ context->RegisterGlobalFunction("float distance_squared(const vec3 &in, const vec3 &in)", asFUNCTIONPR(distance_squared, (const vec3&,const vec3&), float), asCALL_CDECL);
+ context->RegisterGlobalFunction("float xz_distance(const vec3 &in, const vec3 &in)", asFUNCTIONPR(xz_distance, (const vec3&,const vec3&), float), asCALL_CDECL);
+ context->RegisterGlobalFunction("float xz_distance_squared(const vec3 &in, const vec3 &in)", asFUNCTIONPR(xz_distance_squared, (const vec3&,const vec3&), float), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec3 normalize(const vec3 &in)", asFUNCTIONPR(normalize, (const vec3&), vec3), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec3 cross(const vec3 &in, const vec3 &in)", asFUNCTIONPR(cross, (const vec3&, const vec3&), vec3), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec3 reflect(const vec3 &in vec, const vec3 &in normal)", asFUNCTIONPR(reflect, (const vec3&, const vec3&), vec3), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec3 mix(vec3 a,vec3 b,float alpha)", asFUNCTIONPR(mix, (vec3,vec3,float), vec3), asCALL_CDECL);
+}
+
+static void vec2DefaultConstructor(vec2 *self) {
+ new(self) vec2();
+}
+
+static void vec2CopyConstructor(const vec2 &other, vec2 *self) {
+ new(self) vec2(other);
+}
+
+static void vec2InitConstructor(float x, float y, vec2 *self) {
+ new(self) vec2(x,y);
+}
+
+static void vec2InitConstructor2(float val, vec2 *self) {
+ new(self) vec2(val);
+}
+
+void AttachVec2Funcs( ASContext *context) {
+ // Register the type
+ context->RegisterObjectType("vec2",
+ sizeof(vec2),
+ asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS_CA | asOBJ_APP_CLASS_ALLFLOATS);
+
+ // Register the object properties
+ context->RegisterObjectProperty("vec2", "float x", asOFFSET(vec2, entries));
+ context->RegisterObjectProperty("vec2", "float y", asOFFSET(vec2, entries)+sizeof(float));
+
+ // Register the constructors
+ context->RegisterObjectBehaviour("vec2", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(vec2DefaultConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("vec2", asBEHAVE_CONSTRUCT, "void f(const vec2 &in)", asFUNCTION(vec2CopyConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("vec2", asBEHAVE_CONSTRUCT, "void f(float, float)", asFUNCTION(vec2InitConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("vec2", asBEHAVE_CONSTRUCT, "void f(float)", asFUNCTION(vec2InitConstructor2), asCALL_CDECL_OBJLAST);
+
+ // Register the operator overloads
+ context->RegisterObjectMethod("vec2", "vec2 &opAddAssign(const vec2 &in)", asFUNCTIONPR(operator+=, (vec2&, const vec2 &), vec2&), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("vec2", "vec2 &opSubAssign(const vec2 &in)", asFUNCTIONPR(operator-=, (vec2&, const vec2 &), vec2&), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("vec2", "vec2 &opMulAssign(float)", asFUNCTIONPR(operator*=, (vec2&, float), vec2&), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("vec2", "vec2 &opDivAssign(float)", asFUNCTIONPR(operator/=, (vec2&, float), vec2&), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("vec2", "bool opEquals(const vec2 &in) const", asFUNCTIONPR(operator==, (const vec2&, const vec2&), bool), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("vec2", "vec2 opAdd(const vec2 &in) const", asFUNCTIONPR(operator+, (const vec2&, const vec2&), vec2), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("vec2", "vec2 opSub(const vec2 &in) const", asFUNCTIONPR(operator-, (const vec2&, const vec2&), vec2), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("vec2", "vec2 opMul(float) const", asFUNCTIONPR(operator*, (const vec2&, float), vec2), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("vec2", "vec2 opMul_r(float) const", asFUNCTIONPR(operator*, (float, const vec2&), vec2), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectMethod("vec2", "vec2 opDiv(float) const", asFUNCTIONPR(operator/, (const vec2&, float), vec2), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("vec2", "float &opIndex(int)", asMETHODPR(vec2, operator[], (const int), float&), asCALL_THISCALL);
+ context->DocsCloseBrace();
+
+ // Register the object methods
+ context->RegisterGlobalFunction("float length(const vec2 &in)", asFUNCTIONPR(length, (const vec2&), float), asCALL_CDECL);
+ context->RegisterGlobalFunction("float length_squared(const vec2 &in)", asFUNCTIONPR(length_squared, (const vec2&), float), asCALL_CDECL);
+ context->RegisterGlobalFunction("float dot(const vec2 &in, const vec2 &in)", asFUNCTIONPR(dot, (const vec2&, const vec2&), float), asCALL_CDECL);
+ context->RegisterGlobalFunction("float distance(const vec2 &in, const vec2 &in)", asFUNCTIONPR(distance, (const vec2&,const vec2&), float), asCALL_CDECL);
+ context->RegisterGlobalFunction("float distance_squared(const vec2 &in, const vec2 &in)", asFUNCTIONPR(distance_squared, (const vec2&,const vec2&), float), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec2 normalize(const vec2 &in)", asFUNCTIONPR(normalize, (const vec2&), vec2), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec2 reflect(const vec2 &in vec, const vec2 &in normal)", asFUNCTIONPR(reflect, (const vec2&, const vec2&), vec2), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec2 mix(vec2 a,vec2 b,float alpha)", asFUNCTIONPR(mix, (vec2,vec2,float), vec2), asCALL_CDECL);
+}
+
+static void vec4DefaultConstructor(vec4 *self) {
+ new(self) vec4();
+}
+
+static void vec4CopyConstructor(const vec4 &other, vec4 *self) {
+ new(self) vec4(other);
+}
+
+static void vec4InitConstructor(float x, float y, float z, float a, vec3 *self) {
+ new(self) vec4(x,y,z,a);
+}
+
+static void vec4InitConstructor2(float val, vec4 *self) {
+ new(self) vec4(val);
+}
+
+static void vec4InitConstructor3(const vec3 &vec, float val, vec4 *self) {
+ new(self) vec4(vec, val);
+}
+
+vec4 ASMix(vec4 a,vec4 b,float alpha){
+ vec4 result;
+ for(int i=0; i<4; ++i){
+ result[i] = a[i] * (1.0f-alpha) + b[i]*alpha;
+ }
+ return result;
+}
+
+void AttachVec4Funcs( ASContext *context) {
+ // Register the type
+ context->RegisterObjectType("vec4", sizeof(vec4), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS_CA | asOBJ_APP_CLASS_ALLFLOATS);
+
+ // Register the object properties
+ context->RegisterObjectProperty("vec4", "float x", asOFFSET(vec4, entries));
+ context->RegisterObjectProperty("vec4", "float y", asOFFSET(vec4, entries)+sizeof(float));
+ context->RegisterObjectProperty("vec4", "float z", asOFFSET(vec4, entries)+sizeof(float)*2);
+ context->RegisterObjectProperty("vec4", "float a", asOFFSET(vec4, entries)+sizeof(float)*3);
+
+ // Register the constructors
+ context->RegisterObjectBehaviour("vec4", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(vec4DefaultConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("vec4", asBEHAVE_CONSTRUCT, "void f(const vec4 &in)", asFUNCTION(vec4CopyConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("vec4", asBEHAVE_CONSTRUCT, "void f(float, float, float, float)", asFUNCTION(vec4InitConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("vec4", asBEHAVE_CONSTRUCT, "void f(const vec3 &in, float)", asFUNCTION(vec4InitConstructor3), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("vec4", asBEHAVE_CONSTRUCT, "void f(float)", asFUNCTION(vec4InitConstructor2), asCALL_CDECL_OBJLAST);
+ context->RegisterGlobalFunction("vec4 mix(vec4 a,vec4 b,float alpha)", asFUNCTION(ASMix), asCALL_CDECL);
+
+ // Register the operator overloads
+ context->RegisterObjectMethod("vec4", "float &opIndex(int)", asMETHODPR(vec4, operator[], (const int), float&), asCALL_THISCALL);
+ context->DocsCloseBrace();
+}
+
+static void ivec2DefaultConstructor(ivec2 *self) {
+ new(self) ivec2();
+}
+
+static void ivec2CopyConstructor(const ivec2 &other, ivec2 *self) {
+ new(self) ivec2(other);
+}
+
+static void ivec2InitConstructor(int x, int y, ivec2 *self) {
+ new(self) ivec2(x,y);
+}
+
+static void ivec2InitConstructor2(int val, ivec2 *self) {
+ new(self) ivec2(val);
+}
+
+void AttachIVec2Funcs( ASContext *context) {
+ // Register the type
+ context->RegisterObjectType("ivec2",
+ sizeof(ivec2),
+ asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS_CA | asOBJ_APP_CLASS_ALLINTS);
+
+ // Register the object properties
+ context->RegisterObjectProperty("ivec2", "int x", asOFFSET(ivec2, entries));
+ context->RegisterObjectProperty("ivec2", "int y", asOFFSET(ivec2, entries)+sizeof(int));
+
+ // Register the constructors
+ context->RegisterObjectBehaviour("ivec2", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ivec2DefaultConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("ivec2", asBEHAVE_CONSTRUCT, "void f(const ivec2 &in)", asFUNCTION(ivec2CopyConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("ivec2", asBEHAVE_CONSTRUCT, "void f(int, int)", asFUNCTION(ivec2InitConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("ivec2", asBEHAVE_CONSTRUCT, "void f(int)", asFUNCTION(ivec2InitConstructor2), asCALL_CDECL_OBJLAST);
+
+ context->RegisterObjectMethod("ivec2", "ivec2 &opAddAssign(const ivec2 &in)", asFUNCTIONPR(operator+=, (ivec2&, const ivec2 &), ivec2&), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("ivec2", "ivec2 &opSubAssign(const ivec2 &in)", asFUNCTIONPR(operator-=, (ivec2&, const ivec2 &), ivec2&), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("ivec2", "ivec2 &opMulAssign(int)", asFUNCTIONPR(operator*=, (ivec2&, int), ivec2&), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("ivec2", "ivec2 &opDivAssign(int)", asFUNCTIONPR(operator/=, (ivec2&, int), ivec2&), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("ivec2", "ivec2 opAdd(const ivec2 &in) const", asFUNCTIONPR(operator+, (const ivec2&, const ivec2&), ivec2), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("ivec2", "ivec2 opSub(const ivec2 &in) const", asFUNCTIONPR(operator-, (const ivec2&, const ivec2&), ivec2), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("ivec2", "ivec2 opMul(int) const", asFUNCTIONPR(operator*, (const ivec2&, int), ivec2), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("ivec2", "ivec2 opDiv(int) const", asFUNCTIONPR(operator/, (const ivec2&, int), ivec2), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("ivec2", "int &opIndex(int)", asMETHODPR(ivec2, operator[], (const int), int&), asCALL_THISCALL);
+ context->DocsCloseBrace();
+}
+
+static void ivec3DefaultConstructor(ivec3 *self) {
+ new(self) ivec3();
+}
+
+static void ivec3CopyConstructor(const ivec3 &other, ivec3 *self) {
+ new(self) ivec3(other);
+}
+
+static void ivec3InitConstructor(int x, int y, int z, ivec3 *self) {
+ new(self) ivec3(x,y,z);
+}
+
+static void ivec3InitConstructor2(int val, ivec3 *self) {
+ new(self) ivec3(val);
+}
+
+void AttachIVec3Funcs( ASContext *context) {
+ // Register the type
+ context->RegisterObjectType("ivec3",
+ sizeof(ivec3),
+ asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS_CA | asOBJ_APP_CLASS_ALLINTS);
+
+ // Register the object properties
+ context->RegisterObjectProperty("ivec3", "int x", asOFFSET(ivec3, entries));
+ context->RegisterObjectProperty("ivec3", "int y", asOFFSET(ivec3, entries)+sizeof(int));
+
+ // Register the constructors
+ context->RegisterObjectBehaviour("ivec3", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ivec3DefaultConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("ivec3", asBEHAVE_CONSTRUCT, "void f(const ivec3 &in)", asFUNCTION(ivec3CopyConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("ivec3", asBEHAVE_CONSTRUCT, "void f(int, int, int)", asFUNCTION(ivec3InitConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("ivec3", asBEHAVE_CONSTRUCT, "void f(int)", asFUNCTION(ivec3InitConstructor2), asCALL_CDECL_OBJLAST);
+
+ // Register the operator overloads
+ context->RegisterObjectMethod("ivec3", "int &opIndex(int)", asMETHODPR(ivec3, operator[], (const int), int&), asCALL_THISCALL);
+ context->DocsCloseBrace();
+}
+
+static void ivec4DefaultConstructor(ivec4 *self) {
+ new(self) ivec4();
+}
+
+static void ivec4CopyConstructor(const ivec4 &other, ivec4 *self) {
+ new(self) ivec4(other);
+}
+
+static void ivec4InitConstructor(int x, int y, int z, int w, ivec4 *self) {
+ new(self) ivec4(x,y,z,w);
+}
+
+static void ivec4InitConstructor2(int val, ivec4 *self) {
+ new(self) ivec4(val);
+}
+
+void AttachIVec4Funcs( ASContext *context) {
+ // Register the type
+ context->RegisterObjectType("ivec4",
+ sizeof(ivec4),
+ asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS_CA | asOBJ_APP_CLASS_ALLINTS);
+
+ // Register the object properties
+ context->RegisterObjectProperty("ivec4", "int x", asOFFSET(ivec4, entries));
+ context->RegisterObjectProperty("ivec4", "int y", asOFFSET(ivec4, entries)+sizeof(int));
+
+ // Register the constructors
+ context->RegisterObjectBehaviour("ivec4", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ivec4DefaultConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("ivec4", asBEHAVE_CONSTRUCT, "void f(const ivec4 &in)", asFUNCTION(ivec4CopyConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("ivec4", asBEHAVE_CONSTRUCT, "void f(int, int, int, int)", asFUNCTION(ivec4InitConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("ivec4", asBEHAVE_CONSTRUCT, "void f(int)", asFUNCTION(ivec4InitConstructor2), asCALL_CDECL_OBJLAST);
+
+ // Register the operator overloads
+ context->RegisterObjectMethod("ivec4", "int &opIndex(int)", asMETHODPR(ivec4, operator[], (const int), int&), asCALL_THISCALL);
+ context->DocsCloseBrace();
+}
+
+void Attach3DMathFuncs( ASContext *context ) {
+ AttachVec2Funcs(context);
+ AttachVec3Funcs(context);
+ AttachVec4Funcs(context);
+ AttachIVec2Funcs(context);
+ AttachIVec3Funcs(context);
+ AttachIVec4Funcs(context);
+ AttachQuaternionFuncs(context);
+ AttachMatrixFuncs(context);
+
+ context->RegisterGlobalFunction("mat4 Mat4FromQuaternion(const quaternion &in)", asFUNCTION(Mat4FromQuaternion), asCALL_CDECL);
+ context->RegisterGlobalFunction("mat3 Mat3FromQuaternion(const quaternion &in)", asFUNCTION(Mat3FromQuaternion), asCALL_CDECL);
+ context->RegisterGlobalFunction("quaternion QuaternionFromMat4(const mat4 &in)", asFUNCTION(ASQuaternionFromMat4), asCALL_CDECL);
+}
+
+#include "Graphics/camera.h"
+
+Camera* GetCamera(int* id) { return (*id == -1 ? ActiveCameras::Get() : ActiveCameras::GetCamera(*id)); }
+
+static vec3 AS_MetaCameraGetFacing(int* camera_id) {return GetCamera(camera_id)->GetFacing();}
+
+static void AS_MetaCameraFixDiscontinuity(int* camera_id) {GetCamera(camera_id)->FixDiscontinuity();}
+
+static vec3 AS_MetaCameraGetFlatFacing(int* camera_id) {return GetCamera(camera_id)->GetFlatFacing();}
+
+static vec3 AS_MetaCameraGetMouseRay(int* camera_id) {return GetCamera(camera_id)->GetMouseRay();}
+
+static float AS_MetaCameraGetXRotation(int* camera_id) {return GetCamera(camera_id)->GetXRotation();}
+
+static void AS_MetaCameraSetXRotation(int* camera_id,float val) {GetCamera(camera_id)->SetXRotation(val);}
+
+static float AS_MetaCameraGetYRotation(int* camera_id) {return GetCamera(camera_id)->GetYRotation();}
+
+static void AS_MetaCameraSetYRotation(int* camera_id,float val) {GetCamera(camera_id)->SetYRotation(val);}
+
+static float AS_MetaCameraGetZRotation(int* camera_id) {return GetCamera(camera_id)->GetZRotation();}
+
+static void AS_MetaCameraSetZRotation(int* camera_id,float val) {GetCamera(camera_id)->SetZRotation(val);}
+
+static vec3 AS_MetaCameraGetPos(int* camera_id) {return GetCamera(camera_id)->GetPos();}
+
+static vec3 AS_MetaCameraGetUpVector(int* camera_id) {return GetCamera(camera_id)->GetUpVector();}
+
+static void AS_MetaCameraSetPos(int* camera_id,vec3 val) {GetCamera(camera_id)->SetPos(val);}
+
+static void AS_MetaCameraSetFacing(int* camera_id,vec3 val) {GetCamera(camera_id)->SetFacing(val);}
+
+static void AS_MetaCameraSetUp(int* camera_id,vec3 val) {GetCamera(camera_id)->SetUp(val);}
+
+static void AS_MetaCameraCalcFacing(int* camera_id) {GetCamera(camera_id)->CalcFacing();}
+
+static void AS_MetaCameraCalcUp(int* camera_id) {GetCamera(camera_id)->calcUp();}
+
+static void AS_MetaCameraSetVelocity(int* camera_id,vec3 val) {GetCamera(camera_id)->SetVelocity(val);}
+
+static void AS_MetaCameraLookAt(int* camera_id,vec3 val) {GetCamera(camera_id)->LookAt(val);}
+
+static void AS_MetaCameraSetFOV(int* camera_id,float val) {GetCamera(camera_id)->SetFOV(val);}
+
+static float AS_MetaCameraGetFOV(int* camera_id,float val) {return GetCamera(camera_id)->GetFOV();}
+
+static bool AS_MetaCameraGetAutoCamera(int* camera_id) {return GetCamera(camera_id)->GetAutoCamera();}
+
+static void AS_MetaCameraSetDistance(int* camera_id,float val) {GetCamera(camera_id)->SetDistance(val);}
+
+static void AS_MetaCameraSetInterpSteps(int* camera_id,int val) {GetCamera(camera_id)->SetInterpSteps(val);}
+
+static int AS_MetaCameraGetFlags(int* camera_id) {return GetCamera(camera_id)->GetFlags();}
+
+static void AS_MetaCameraSetFlags(int* camera_id,int val) {return GetCamera(camera_id)->SetFlags(val);}
+
+static void AS_MetaCameraSetDOF(int* camera_id,float near_blur, float near_dist, float near_transition, float far_blur, float far_dist, float far_transition) {
+ Camera* camera = GetCamera(camera_id);
+ camera->near_blur_amount = near_blur;
+ camera->near_sharp_dist = near_dist;
+ camera->near_blur_transition_size = near_transition;
+ camera->far_blur_amount = far_blur;
+ camera->far_sharp_dist = pow(far_dist, 0.5f);
+ camera->far_blur_transition_size = far_transition;
+}
+
+static const vec3& ASGetTint(int* camera_id) { return GetCamera(camera_id)->tint; }
+
+static void ASSetTint(int* camera_id, const vec3& val) { GetCamera(camera_id)->tint = val; }
+
+static const vec3& ASGetVignetteTint(int* camera_id) { return GetCamera(camera_id)->vignette_tint; }
+
+static void ASSetVignetteTint(int* camera_id, const vec3& val) { GetCamera(camera_id)->vignette_tint = val; }
+
+static bool GetSplitscreen() {
+ return Engine::Instance()->GetSplitScreen();
+}
+
+static int GetScreenWidth() {
+ return Graphics::Instance()->window_dims[0];
+}
+
+static int GetScreenHeight() {
+ return Graphics::Instance()->window_dims[1];
+}
+
+static int GetBloodLevel() {
+ return Graphics::Instance()->config_.blood();
+}
+
+static const vec3& GetBloodTint() {
+ return Graphics::Instance()->config_.blood_color();
+}
+
+void AttachScreenWidth(ASContext *context) {
+ context->RegisterGlobalFunction("int GetScreenWidth()", asFUNCTION(GetScreenWidth), asCALL_CDECL);
+ context->RegisterGlobalFunction("int GetScreenHeight()", asFUNCTION(GetScreenHeight), asCALL_CDECL);
+}
+
+static int zero_camera_id = -1;
+void AttachMovementObjectCamera(ASContext *context, MovementObject* mo)
+{
+ context->RegisterObjectType("Camera", 0, asOBJ_REF | asOBJ_NOHANDLE);
+ context->RegisterObjectMethod("Camera","vec3 &GetTint()",asFUNCTION(ASGetTint), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","void SetTint(const vec3 &in)",asFUNCTION(ASSetTint), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","vec3 &GetVignetteTint()",asFUNCTION(ASGetVignetteTint), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","void SetVignetteTint(const vec3 &in)",asFUNCTION(ASSetVignetteTint), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectMethod("Camera","void FixDiscontinuity()",asFUNCTION(AS_MetaCameraFixDiscontinuity), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","vec3 GetFacing()",asFUNCTION(AS_MetaCameraGetFacing), asCALL_CDECL_OBJFIRST);
+ //context->RegisterObjectMethod("Camera","void SetFacing()",asFUNCTION(AS_MetaCameraSetFacing), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","vec3 GetFlatFacing()",asFUNCTION(AS_MetaCameraGetFlatFacing), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","vec3 GetMouseRay()",asFUNCTION(AS_MetaCameraGetMouseRay), asCALL_CDECL_OBJFIRST, "Direction that mouse cursor is pointing");
+ context->RegisterObjectMethod("Camera","float GetXRotation()",asFUNCTION(AS_MetaCameraGetXRotation), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","void SetXRotation(float)",asFUNCTION(AS_MetaCameraSetXRotation), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","float GetYRotation()",asFUNCTION(AS_MetaCameraGetYRotation), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","void SetYRotation(float)",asFUNCTION(AS_MetaCameraSetYRotation), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","float GetZRotation()",asFUNCTION(AS_MetaCameraGetZRotation), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","void SetZRotation(float)",asFUNCTION(AS_MetaCameraSetZRotation), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","vec3 GetPos()",asFUNCTION(AS_MetaCameraGetPos), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","vec3 GetUpVector()",asFUNCTION(AS_MetaCameraGetUpVector), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","void SetPos(vec3)",asFUNCTION(AS_MetaCameraSetPos), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","void SetFacing(vec3)",asFUNCTION(AS_MetaCameraSetFacing), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","void SetUp(vec3)",asFUNCTION(AS_MetaCameraSetUp), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","void CalcFacing()",asFUNCTION(AS_MetaCameraCalcFacing), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","void SetVelocity(vec3)",asFUNCTION(AS_MetaCameraSetVelocity), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","void LookAt(vec3 target)",asFUNCTION(AS_MetaCameraLookAt), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","void SetFOV(float)",asFUNCTION(AS_MetaCameraSetFOV), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","float GetFOV()",asFUNCTION(AS_MetaCameraGetFOV), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","bool GetAutoCamera()",asFUNCTION(AS_MetaCameraGetAutoCamera), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","void SetDistance(float)",asFUNCTION(AS_MetaCameraSetDistance), asCALL_CDECL_OBJFIRST, "From orbit point, for chase camera");
+ context->RegisterObjectMethod("Camera","void SetInterpSteps(int)",asFUNCTION(AS_MetaCameraSetInterpSteps), asCALL_CDECL_OBJFIRST, "Number of time steps between camera updates");
+ context->RegisterObjectMethod("Camera","int GetFlags()",asFUNCTION(AS_MetaCameraGetFlags), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","void SetFlags(int)",asFUNCTION(AS_MetaCameraSetFlags), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","void CalcUp()",asFUNCTION(AS_MetaCameraCalcUp), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Camera","void SetDOF(float near_blur, float near_dist, float near_transition, float far_blur, float far_dist, float far_transition)",asFUNCTION(AS_MetaCameraSetDOF), asCALL_CDECL_OBJFIRST);
+
+ context->DocsCloseBrace();
+ context->RegisterGlobalProperty("Camera camera", mo ? &mo->camera_id: &zero_camera_id);
+ context->RegisterGlobalFunction("bool GetSplitscreen()", asFUNCTION(GetSplitscreen), asCALL_CDECL);
+ context->RegisterGlobalFunction("int GetBloodLevel()", asFUNCTION(GetBloodLevel), asCALL_CDECL);
+ context->RegisterGlobalFunction("const vec3 &GetBloodTint()", asFUNCTION(GetBloodTint), asCALL_CDECL);
+ context->RegisterEnum("CameraFlags");
+ context->RegisterEnumValue("CameraFlags","kEditorCamera",Camera::kEditorCamera);
+ context->RegisterEnumValue("CameraFlags","kPreviewCamera",Camera::kPreviewCamera);
+ context->DocsCloseBrace();
+}
+
+void AttachActiveCamera( ASContext *context )
+{
+ AttachMovementObjectCamera(context, NULL);
+}
+
+#include "Internal/timer.h"
+static void ASTimedSlowMotion(float target_time_scale, float how_long, float delay) {
+ Online* online = Online::Instance();
+ if (online->IsHosting()) {
+ online->Send<OnlineMessages::TimedSlowMotion>(target_time_scale, how_long, delay);
+ }
+ game_timer.AddTimedSlowMotionLayer(target_time_scale, how_long, delay);
+}
+
+static uint64_t GetPerformanceCounter() {
+ return SDL_GetPerformanceCounter();
+}
+
+static uint64_t GetPerformanceFrequency() {
+ return SDL_GetPerformanceFrequency();
+}
+
+static bool GetMenuPaused() {
+ return Engine::Instance()->menu_paused;
+}
+
+void AttachTimer( ASContext *context ) {
+ context->RegisterGlobalProperty("float time_step", &game_timer.timestep, "Time in seconds between engine time steps");
+ context->RegisterGlobalProperty("float the_time", &game_timer.game_time, "The current time in seconds since engine started (in-game time)");
+ context->RegisterGlobalProperty("float ui_time", &ui_timer.game_time, "The current time in seconds since engine started (absolute time)");
+ context->RegisterGlobalFunction("void TimedSlowMotion(float target_time_scale, float how_long, float delay)", asFUNCTION(ASTimedSlowMotion), asCALL_CDECL, "Used to trigger brief periods of slow motion");
+ context->RegisterGlobalFunction("uint64 GetPerformanceCounter()", asFUNCTION(GetPerformanceCounter), asCALL_CDECL, "Get high precision time info for profiling");
+ context->RegisterGlobalFunction("uint64 GetPerformanceFrequency()", asFUNCTION(GetPerformanceFrequency), asCALL_CDECL, "Used to convert PerformanceCounter into seconds");
+ context->RegisterGlobalFunction("bool GetMenuPaused()", asFUNCTION(GetMenuPaused), asCALL_CDECL, "Is game paused by a menu");
+}
+
+#include "Physics/physics.h"
+void AttachPhysics( ASContext *context ) {
+ Physics *physics = Physics::Instance();
+
+ context->RegisterObjectType("Physics", 0, asOBJ_REF | asOBJ_NOHANDLE);
+ context->RegisterObjectProperty("Physics","vec3 gravity_vector",asOFFSET(Physics,gravity));
+ context->DocsCloseBrace();
+ context->RegisterGlobalProperty("Physics physics", physics);
+}
+
+#include "Sound/sound.h"
+static int ASPlaySound(std::string path) {
+ Online* online = Online::Instance();
+
+ if (online->IsHosting()) {
+ online->Send<OnlineMessages::AudioPlaySoundMessage>(path);
+ }
+
+ SoundPlayInfo spi;
+ spi.path = path;
+ spi.flags = spi.flags | SoundFlags::kRelative;
+ int handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->Play(handle, spi);
+ return handle;
+}
+
+static int ASPlaySoundLoop(const std::string &path, float gain) {
+ Online* online = Online::Instance();
+
+ if (online->IsHosting()) {
+ online->Send<OnlineMessages::AudioPlaySoundLoopMessage>(path, gain);
+ }
+
+ SoundPlayInfo spi;
+ spi.path = path;
+ spi.flags = spi.flags | SoundFlags::kRelative;
+ spi.looping = true;
+ spi.volume = gain;
+ int handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->Play(handle, spi);
+ return handle;
+}
+
+static int ASPlaySoundLoopAtLocation(const std::string &path, vec3 pos, float gain) {
+ Online* online = Online::Instance();
+
+ if (online->IsHosting()) {
+ online->Send<OnlineMessages::AudioPlaySoundLoopAtLocationMessage>(path, gain, pos);
+ }
+
+ SoundPlayInfo spi;
+ spi.path = path;
+ spi.looping = true;
+ spi.volume = gain;
+ spi.position = pos;
+ int handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->Play(handle, spi);
+ return handle;
+}
+
+static int ASPlaySoundAtLocation(std::string path, vec3 location) {
+ Online* online = Online::Instance();
+
+ if (online->IsHosting()) {
+ online->Send<OnlineMessages::AudioPlaySoundLocationMessage>(path, location);
+ }
+
+ SoundPlayInfo spi;
+ spi.path = path;
+ spi.position = location;
+ int handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->Play(handle, spi);
+ return handle;
+}
+
+static int ASPlaySoundGroupRelative(std::string path) {
+ //SoundGroupRef sgr = SoundGroups::Instance()->ReturnRef(path);
+ Online* online = Online::Instance();
+
+ if (online->IsHosting()) {
+ online->Send<OnlineMessages::AudioPlaySoundGroupRelativeMessage>(path);
+ }
+ SoundGroupRef sgr = Engine::Instance()->GetAssetManager()->LoadSync<SoundGroup>(path);
+ SoundGroupPlayInfo sgpi(SoundGroupPlayInfo(*sgr, vec3(0.0f)));
+ sgpi.flags = sgpi.flags | SoundFlags::kRelative;
+
+ int handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->PlayGroup(handle, sgpi);
+ return handle;
+}
+
+static int ASPlaySoundGroupRelativeGain(std::string path, float gain) {
+ //SoundGroupRef sgr = SoundGroups::Instance()->ReturnRef(path);
+ Online* online = Online::Instance();
+
+ if (online->IsHosting()) {
+ online->Send<OnlineMessages::AudioPlaySoundGroupRelativeGainMessage>(path, gain);
+ }
+
+ SoundGroupRef sgr = Engine::Instance()->GetAssetManager()->LoadSync<SoundGroup>(path);
+ SoundGroupPlayInfo sgpi(SoundGroupPlayInfo(*sgr, vec3(0.0f)));
+ sgpi.flags = sgpi.flags | SoundFlags::kRelative;
+ sgpi.gain = gain;
+
+ int handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->PlayGroup(handle, sgpi);
+ return handle;
+}
+
+static int ASPlaySoundGroup(std::string path, vec3 location) {
+ Online* online = Online::Instance();
+
+ if (online->IsHosting()) {
+ online->Send<OnlineMessages::AudioPlaySoundGroupMessage>(path, location);
+ }
+
+ int handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ //SoundGroupRef sgr = SoundGroups::Instance()->ReturnRef(path);
+ SoundGroupRef sgr = Engine::Instance()->GetAssetManager()->LoadSync<SoundGroup>(path);
+ SoundGroupPlayInfo sgpi(*sgr, location);
+ Engine::Instance()->GetSound()->PlayGroup(handle, sgpi);
+ return handle;
+}
+
+static int ASPlaySoundGroupPriority(std::string path, vec3 location, int priority) {
+ //SoundGroupRef sgr = SoundGroups::Instance()->ReturnRef(path);
+
+ Online* online = Online::Instance();
+
+ if (online->IsHosting()) {
+ online->Send<OnlineMessages::AudioPlayGroupPriorityMessage>(path, location, priority);
+ }
+
+ SoundGroupRef sgr = Engine::Instance()->GetAssetManager()->LoadSync<SoundGroup>(path);
+ SoundGroupPlayInfo sgpi(*sgr, location);
+ sgpi.priority = priority;
+ int handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->PlayGroup(handle, sgpi);
+ return handle;
+}
+
+static int ASPlaySoundGroupGain(std::string path, vec3 location, float gain) {
+ //SoundGroupRef sgr = SoundGroups::Instance()->ReturnRef(path);
+ Online* online = Online::Instance();
+
+ if (online->IsHosting()) {
+ online->Send<OnlineMessages::AudioPlaySoundGroupGainMessage>(path, location, gain);
+ }
+
+ SoundGroupRef sgr = Engine::Instance()->GetAssetManager()->LoadSync<SoundGroup>(path);
+ SoundGroupPlayInfo sgpi(*sgr, location);
+ sgpi.gain = gain;
+ int handle = Engine::Instance()->GetSound()->CreateHandle(__FUNCTION__);
+ Engine::Instance()->GetSound()->PlayGroup(handle,sgpi);
+ return handle;
+}
+
+static void ASSetRemoteAirWhoosh(int id, float volume, float pitch) {
+ // Send the whoosh to the client
+ for (const auto& it : Online::Instance()->online_session->player_states) {
+ if (it.second.object_id == id) { // TODO This assumes that player_id == peer_id!
+ Peer* peer = Online::Instance()->GetPeerFromID(it.first);
+ if (peer != nullptr) {
+ Online::Instance()->SendTo<OnlineMessages::WhooshSoundMessage>(peer->conn_id, volume, pitch);
+ } else {
+ LOGW << "Tried ASSetRemoteAirWhoosh(). We managed to find I PlayerState, but couldn't resolve a peer." << std::endl;
+ }
+ break;
+ }
+ }
+}
+
+static void ASSetAirWhoosh(float volume, float pitch){
+ Engine::Instance()->GetSound()->setAirWhoosh(volume, pitch);
+}
+
+static void ASUpdateListener(vec3 a, vec3 b, vec3 c, vec3 d){
+ Engine::Instance()->GetSound()->updateListener(a,b,c,d);
+}
+
+static void ASStopSound(int which) {
+ Engine::Instance()->GetSound()->Stop(which);
+}
+
+static void ASSetSoundGain(int which, float gain) {
+ Engine::Instance()->GetSound()->SetVolume(which, gain);
+}
+
+static void ASSetSoundPitch(int which, float pitch) {
+ Engine::Instance()->GetSound()->SetPitch(which, pitch);
+}
+
+static void ASSetSoundPosition(int which, vec3 pos) {
+ Engine::Instance()->GetSound()->SetPosition(which, pos);
+}
+
+static void ASPlaySong(const std::string& type) {
+ Engine::Instance()->GetSound()->TransitionToSong(type);
+}
+
+static void ASQueueSegment( const std::string& name ) {
+ Engine::Instance()->GetSound()->QueueSegment(name);
+}
+
+static void ASPlaySegment( const std::string& name ) {
+ Engine::Instance()->GetSound()->TransitionToSegment(name);
+}
+
+static bool ASAddMusic( const std::string& path ) {
+ Path p = FindFilePath(path);
+ if( p.valid )
+ {
+ Engine::Instance()->GetSound()->AddMusic(p);
+ return true;
+ }
+ else
+ {
+ LOGE << "Invalid path, can't load music" << p << std::endl;
+ return false;
+ }
+}
+
+static bool ASRemoveMusic( const std::string& path ) {
+ Path p = FindFilePath(path);
+ if( p.valid )
+ {
+ Engine::Instance()->GetSound()->RemoveMusic(p);
+ return true;
+ }
+ else
+ {
+ LOGE << "Invalid path, can't remove music" << p << std::endl;
+ return false;
+ }
+}
+
+static void ASSetSegment( const std::string& name ) {
+ Engine::Instance()->GetSound()->SetSegment(name);
+}
+
+static std::string ASGetSegment( ) {
+ return Engine::Instance()->GetSound()->GetSegment();
+}
+
+static void ASSetSong( const std::string& name ) {
+ Engine::Instance()->GetSound()->SetSong(name);
+}
+
+static std::string ASGetSong() {
+ return Engine::Instance()->GetSound()->GetSongName();
+}
+
+static CScriptArray* ASGetLayerNames() {
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<string>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+
+ std::map<std::string,float> layer_gains = Engine::Instance()->GetSound()->GetLayerGains();
+
+ array->Reserve(layer_gains.size());
+
+ std::map<std::string,float>::iterator layerit = layer_gains.begin();
+
+ for( ; layerit != layer_gains.end(); layerit++ ) {
+ array->InsertLast((void*)&(layerit->first));
+ }
+
+ return array;
+}
+
+static void ASSetLayerGain( const std::string& layer, float gain ) {
+ Engine::Instance()->GetSound()->SetLayerGain(layer,gain);
+}
+
+static float ASGetLayerGain( const std::string& layer ) {
+ std::map<std::string,float> layer_gains = Engine::Instance()->GetSound()->GetLayerGains();
+
+ std::map<std::string,float>::iterator layerit = layer_gains.find(layer);
+
+ if( layerit != layer_gains.end() ) {
+ return layerit->second;
+ } else {
+ return 0.0f;
+ }
+}
+
+void ASReloadMods() {
+ ModLoading::Instance().Reload();
+}
+
+const int as_sound_priority_max = _sound_priority_max;
+const int as_sound_priority_very_high = _sound_priority_very_high;
+const int as_sound_priority_high = _sound_priority_high;
+const int as_sound_priority_med = _sound_priority_med;
+const int as_sound_priority_low = _sound_priority_low;
+
+void AttachSound( ASContext *context ) {
+ context->RegisterGlobalFunction("void UpdateListener(vec3 pos, vec3 vel, vec3 facing, vec3 up)", asFUNCTION(ASUpdateListener), asCALL_CDECL);
+ context->RegisterGlobalFunction("int PlaySound(string path)", asFUNCTION(ASPlaySound), asCALL_CDECL);
+ context->RegisterGlobalFunction("int PlaySoundLoop(const string &in path, float gain)", asFUNCTION(ASPlaySoundLoop), asCALL_CDECL);
+ context->RegisterGlobalFunction("int PlaySoundLoopAtLocation(const string &in path, vec3 pos, float gain)", asFUNCTION(ASPlaySoundLoopAtLocation), asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetSoundGain(int handle, float gain)", asFUNCTION(ASSetSoundGain), asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetSoundPitch(int handle, float pitch)", asFUNCTION(ASSetSoundPitch), asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetSoundPosition(int handle, vec3 pos)", asFUNCTION(ASSetSoundPosition), asCALL_CDECL);
+ context->RegisterGlobalFunction("void StopSound(int handle)", asFUNCTION(ASStopSound), asCALL_CDECL);
+ context->RegisterGlobalFunction("int PlaySound(string path, vec3 position)", asFUNCTION(ASPlaySoundAtLocation), asCALL_CDECL);
+ context->RegisterGlobalFunction("int PlaySoundGroup(string path)", asFUNCTION(ASPlaySoundGroupRelative), asCALL_CDECL);
+ context->RegisterGlobalFunction("int PlaySoundGroup(string path, float gain)", asFUNCTION(ASPlaySoundGroupRelativeGain), asCALL_CDECL);
+ context->RegisterGlobalFunction("int PlaySoundGroup(string path, vec3 position)", asFUNCTION(ASPlaySoundGroup), asCALL_CDECL);
+ context->RegisterGlobalFunction("int PlaySoundGroup(string path, vec3 position, float gain)", asFUNCTION(ASPlaySoundGroupGain), asCALL_CDECL);
+ context->RegisterGlobalFunction("int PlaySoundGroup(string path, vec3 position, int priority)", asFUNCTION(ASPlaySoundGroupPriority), asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetAirWhoosh(float volume, float pitch)", asFUNCTION(ASSetAirWhoosh), asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetRemoteAirWhoosh(int id, float volume, float pitch)", asFUNCTION(ASSetRemoteAirWhoosh), asCALL_CDECL);
+ context->RegisterGlobalProperty("const int _sound_priority_max", (void*)&as_sound_priority_max);
+ context->RegisterGlobalProperty("const int _sound_priority_very_high", (void*)&as_sound_priority_very_high);
+ context->RegisterGlobalProperty("const int _sound_priority_high", (void*)&as_sound_priority_high);
+ context->RegisterGlobalProperty("const int _sound_priority_med", (void*)&as_sound_priority_med);
+ context->RegisterGlobalProperty("const int _sound_priority_low", (void*)&as_sound_priority_low);
+
+ context->RegisterGlobalFunction("bool AddMusic(const string& in)", asFUNCTION(ASAddMusic), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool RemoveMusic(const string& in)", asFUNCTION(ASRemoveMusic), asCALL_CDECL);
+
+ //Old music API (keep for backwards compatability)
+ context->RegisterGlobalFunction("void PlaySong(const string& in)", asFUNCTION(ASPlaySong), asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetSong(const string& in)", asFUNCTION(ASSetSong), asCALL_CDECL);
+ context->RegisterGlobalFunction("string GetSong()", asFUNCTION(ASGetSong), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("void SetSegment(const string& in)", asFUNCTION(ASSetSegment), asCALL_CDECL);
+ context->RegisterGlobalFunction("void QueueSegment(const string& in)", asFUNCTION(ASQueueSegment), asCALL_CDECL);
+ context->RegisterGlobalFunction("void PlaySegment(const string& in)", asFUNCTION(ASPlaySegment), asCALL_CDECL);
+ context->RegisterGlobalFunction("string GetSegment()", asFUNCTION(ASGetSegment), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("array<string>@ GetLayerNames()", asFUNCTION(ASGetLayerNames), asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetLayerGain(const string &in layer, float gain)", asFUNCTION(ASSetLayerGain), asCALL_CDECL);
+ context->RegisterGlobalFunction("float GetLayerGain(const string &in layer)", asFUNCTION(ASGetLayerGain), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("void ReloadMods()", asFUNCTION(ASReloadMods), asCALL_CDECL);
+}
+
+unsigned ASMakeParticle(std::string path, vec3 pos, vec3 vel) {
+ return the_scenegraph->particle_system->MakeParticle(the_scenegraph, path, pos, vel, vec3(1.0f));
+}
+
+unsigned ASMakeParticleColor(std::string path, vec3 pos, vec3 vel, vec3 color) {
+ return the_scenegraph->particle_system->MakeParticle(the_scenegraph, path, pos, vel, color);
+}
+
+void ASConnectParticles(unsigned a, unsigned b) {
+ the_scenegraph->particle_system->ConnectParticles(a,b);
+}
+
+void ASTintParticle(unsigned id, const vec3 &color) {
+ the_scenegraph->particle_system->TintParticle(id, color);
+}
+
+void AttachParticles( ASContext *context ) {
+ context->RegisterGlobalFunction("void ConnectParticles(uint32 id_a, uint32 id_b)", asFUNCTION(ASConnectParticles), asCALL_CDECL, "Used for ribbon particles, like throat-cut blood");
+ context->RegisterGlobalFunction("uint32 MakeParticle(string path, vec3 pos, vec3 vel)", asFUNCTION(ASMakeParticle), asCALL_CDECL);
+ context->RegisterGlobalFunction("uint32 MakeParticle(string path, vec3 pos, vec3 vel, vec3 color)", asFUNCTION(ASMakeParticleColor), asCALL_CDECL);
+ context->RegisterGlobalFunction("void TintParticle(uint32 id, const vec3 &in color)", asFUNCTION(ASTintParticle), asCALL_CDECL);
+}
+
+static PrecisionStopwatch stopwatch;
+
+void ASStartStopwatch() {
+ stopwatch.Start();
+}
+
+
+void ASStopAndReportStopwatch() {
+ stopwatch.StopAndReportNanoseconds();
+}
+
+void AttachStopwatch( ASContext *context ) {
+ context->RegisterGlobalFunction("void StartStopwatch()", asFUNCTION(ASStartStopwatch), asCALL_CDECL);
+ context->RegisterGlobalFunction("uint64 StopAndReportStopwatch()", asFUNCTION(ASStopAndReportStopwatch), asCALL_CDECL);
+}
+
+void ASEnterTelemetryZone( const std::string& str ) {
+ PROFILER_ENTER_DYNAMIC_STRING(g_profiler_ctx, str.c_str());
+}
+
+void ASLeaveTelemetryZone() {
+ PROFILER_LEAVE(g_profiler_ctx);
+}
+
+/*void ASEnterTelemetryZoneProfiler( const std::string& str ) {
+ LOGI << str << std::endl;
+}
+
+void ASLeaveTelemetryZoneProfiler() {
+ LOGI << "Leave" << std::endl;
+}*/
+
+void AttachProfiler( ASContext *context ) {
+ context->RegisterGlobalFunctionThis("void EnterTelemetryZone(const string& in name)", asMETHOD(ASProfiler, ASEnterTelemetryZone), asCALL_THISCALL_ASGLOBAL, &context->profiler);
+ context->RegisterGlobalFunctionThis("void LeaveTelemetryZone()", asMETHOD(ASProfiler, ASLeaveTelemetryZone), asCALL_THISCALL_ASGLOBAL, &context->profiler);
+}
+
+void AttachTelemetry( ASContext *context ) {
+ context->RegisterGlobalFunction("void EnterTelemetryZone(const string& in name)", asFUNCTION(ASEnterTelemetryZone), asCALL_CDECL);
+ context->RegisterGlobalFunction("void LeaveTelemetryZone()", asFUNCTION(ASLeaveTelemetryZone), asCALL_CDECL);
+}
+
+#include "Graphics/pxdebugdraw.h"
+
+const int _delete_on_draw_val = _delete_on_draw;
+const int _delete_on_update_val = _delete_on_update;
+const int _persistent_val = _persistent;
+const int _fade_val = _fade;
+
+int ASDebugDrawLine(vec3 start, vec3 end, vec3 color, int lifespan_int) {
+ DDLifespan lifespan = LifespanFromInt(lifespan_int);
+ return DebugDraw::Instance()->AddLine(start,end, color, lifespan);
+}
+
+int ASDebugDrawBillboard(const std::string& texture_path, vec3 center, float scale, vec4 color, int lifespan_int) {
+ DDLifespan lifespan = LifespanFromInt(lifespan_int);
+ TextureAssetRef tex_ref = Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(texture_path);
+ return DebugDraw::Instance()->AddBillboard(tex_ref->GetTextureRef(), center, scale, color, kStraightBlend, lifespan);
+}
+
+int ASDebugDrawLine2(vec3 start, vec3 end, vec3 color, vec3 color2, int lifespan_int) {
+ DDLifespan lifespan = LifespanFromInt(lifespan_int);
+ return DebugDraw::Instance()->AddLine(start,end, color, color2, lifespan);
+}
+
+int ASDebugDrawLine3(vec3 start, vec3 end, vec4 color, vec4 color2, int lifespan_int) {
+ DDLifespan lifespan = LifespanFromInt(lifespan_int);
+ return DebugDraw::Instance()->AddLine(start,end, color, color2, lifespan);
+}
+
+int ASDebugDrawRibbon(vec3 start, vec3 end, vec4 color, vec4 color2, float start_width, float end_width, int lifespan_int) {
+ DDLifespan lifespan = LifespanFromInt(lifespan_int);
+ return DebugDraw::Instance()->AddRibbon(start,end, color, color2, start_width, end_width, lifespan);
+}
+
+int ASDebugDrawRibbon2(int lifespan_int) {
+ DDLifespan lifespan = LifespanFromInt(lifespan_int);
+ return DebugDraw::Instance()->AddRibbon(lifespan);
+}
+
+void ASAddDebugDrawRibbonPoint(int which, vec3 pos, vec4 color, float width) {
+ DebugDrawElement* element = DebugDraw::Instance()->GetElement(which);
+ DebugDrawRibbon* ribbon = (DebugDrawRibbon*) element;
+ ribbon->AddPoint(pos, color, width);
+}
+
+int ASDebugDrawLines( const CScriptArray &array, vec4 color, int lifespan_int ) {
+
+ std::vector<vec3> data;
+
+ for( int n = 0; n < (int)array.GetSize(); n++ )
+ {
+ data.push_back(*((vec3*)array.At(n)));
+ }
+
+ DDLifespan lifespan = LifespanFromInt(lifespan_int);
+ return DebugDraw::Instance()->AddLines(data, color, lifespan, _DD_NO_FLAG);
+}
+
+int ASDebugDrawText(vec3 pos, std::string text, float scale, bool screen_space, int lifespan_int) {
+ DDLifespan lifespan = LifespanFromInt(lifespan_int);
+ return DebugDraw::Instance()->AddText(pos, text, scale, lifespan, screen_space ? _DD_SCREEN_SPACE : _DD_NO_FLAG );
+}
+
+bool ASDebugSetPosition( int id, vec3 pos )
+{
+ return DebugDraw::Instance()->SetPosition( id, pos );
+}
+
+int ASDebugDrawSphere(vec3 pos, float radius, vec3 color, int lifespan_int) {
+ DDLifespan lifespan = LifespanFromInt(lifespan_int);
+ return DebugDraw::Instance()->AddWireSphere(pos, radius, color, lifespan);
+}
+
+int ASDebugDrawWireMesh(std::string path, mat4 transform, vec4 color, int lifespan_int) {
+ DDLifespan lifespan = LifespanFromInt(lifespan_int);
+ return DebugDraw::Instance()->AddWireMesh(path, transform, color, lifespan);
+}
+
+int ASDebugDrawScaledSphere(vec3 pos, float radius, vec3 scale, vec3 color, int lifespan_int) {
+ DDLifespan lifespan = LifespanFromInt(lifespan_int);
+ return DebugDraw::Instance()->AddWireScaledSphere(pos, radius, scale, color, lifespan);
+}
+
+int ASDebugDrawScaledSphere2(vec3 pos, float radius, vec3 scale, vec4 color, int lifespan_int) {
+ DDLifespan lifespan = LifespanFromInt(lifespan_int);
+ return DebugDraw::Instance()->AddWireScaledSphere(pos, radius, scale, color, lifespan);
+}
+
+int ASDebugDrawCylinder(vec3 pos,
+ float radius,
+ float height,
+ vec3 color,
+ int lifespan_int)
+{
+ DDLifespan lifespan = LifespanFromInt(lifespan_int);
+ DebugDraw* dd = DebugDraw::Instance();
+ return dd->AddWireCylinder(pos,radius, height, color, lifespan);
+}
+
+int ASDebugDrawBox(vec3 pos, vec3 dimensions, vec3 color, int lifespan_int) {
+ DDLifespan lifespan = LifespanFromInt(lifespan_int);
+ return DebugDraw::Instance()->AddWireBox(pos, dimensions, color, lifespan);
+}
+
+int ASDebugDrawCircle(mat4 transform, vec4 color, int lifespan_int) {
+ DDLifespan lifespan = LifespanFromInt(lifespan_int);
+ return DebugDraw::Instance()->AddCircle(transform, color, lifespan);
+}
+
+void ASDebugDrawClear(int which) {
+ DebugDraw::Instance()->Remove(which);
+}
+
+void ASDebugText(std::string key, std::string text, float time) {
+ GetActiveASContext()->gui->AddDebugText(key,text,time);
+}
+
+int ASDebugDrawPoint(vec3 pos, vec4 color, int lifespan_int ){
+
+ DDLifespan lifespan = LifespanFromInt(lifespan_int);
+ return DebugDraw::Instance()->AddPoint(pos, color, lifespan, _DD_NO_FLAG);
+}
+
+void AttachDebugDraw( ASContext *context ) {
+ context->RegisterGlobalProperty("const int _delete_on_update",
+ (void*)&_delete_on_update_val);
+ context->RegisterGlobalProperty("const int _fade",
+ (void*)&_fade_val);
+ context->RegisterGlobalProperty("const int _delete_on_draw",
+ (void*)&_delete_on_draw_val);
+ context->RegisterGlobalProperty("const int _persistent",
+ (void*)&_persistent_val);
+ context->RegisterGlobalFunction("int DebugDrawLine(vec3 start, vec3 end, vec3 color, int lifespan)",
+ asFUNCTION(ASDebugDrawLine), asCALL_CDECL);
+ context->RegisterGlobalFunction("int DebugDrawBillboard(const string &in path, vec3 center, float scale, vec4 color, int lifespan)",
+ asFUNCTION(ASDebugDrawBillboard), asCALL_CDECL);
+ context->RegisterGlobalFunction("int DebugDrawLine(vec3 start, vec3 end, vec3 start_color, vec3 end_color, int lifespan)",
+ asFUNCTION(ASDebugDrawLine2), asCALL_CDECL);
+ context->RegisterGlobalFunction("int DebugDrawLine(vec3 start, vec3 end, vec4 start_color, vec4 end_color, int lifespan)",
+ asFUNCTION(ASDebugDrawLine3), asCALL_CDECL);
+ context->RegisterGlobalFunction("int DebugDrawRibbon(vec3 start, vec3 end, vec4 start_color, vec4 end_color, float start_width, float end_width, int lifespan)",
+ asFUNCTION(ASDebugDrawRibbon), asCALL_CDECL);
+ context->RegisterGlobalFunction("int DebugDrawRibbon(int lifespan)",
+ asFUNCTION(ASDebugDrawRibbon2), asCALL_CDECL);
+ context->RegisterGlobalFunction("void AddDebugDrawRibbonPoint(int which, vec3 pos, vec4 color, float width)",
+ asFUNCTION(ASAddDebugDrawRibbonPoint), asCALL_CDECL);
+ context->RegisterGlobalFunction("int DebugDrawLines(const array<vec3> &vertices, vec4 color, int lifespan)",
+ asFUNCTION(ASDebugDrawLines), asCALL_CDECL);
+ context->RegisterGlobalFunction("int DebugDrawText(vec3 pos, string text, float scale, bool screen_space, int lifespan)",
+ asFUNCTION(ASDebugDrawText), asCALL_CDECL);
+ context->RegisterGlobalFunction("int DebugSetPosition(int id, vec3 pos)",
+ asFUNCTION(ASDebugSetPosition), asCALL_CDECL);
+ context->RegisterGlobalFunction("int DebugDrawWireSphere(vec3 pos, float radius, vec3 color, int lifespan)",
+ asFUNCTION(ASDebugDrawSphere), asCALL_CDECL);
+ context->RegisterGlobalFunction("int DebugDrawWireMesh(string path, mat4 transform, vec4 color, int lifespan)",
+ asFUNCTION(ASDebugDrawWireMesh), asCALL_CDECL);
+ context->RegisterGlobalFunction("int DebugDrawWireScaledSphere(vec3 pos, float radius, vec3 scale, vec3 color, int lifespan)",
+ asFUNCTION(ASDebugDrawScaledSphere), asCALL_CDECL);
+ context->RegisterGlobalFunction("int DebugDrawWireScaledSphere(vec3 pos, float radius, vec3 scale, vec4 color, int lifespan)",
+ asFUNCTION(ASDebugDrawScaledSphere2), asCALL_CDECL);
+ context->RegisterGlobalFunction("int DebugDrawWireCylinder(vec3 pos, float radius, float height, vec3 color, int lifespan)",
+ asFUNCTION(ASDebugDrawCylinder), asCALL_CDECL);
+ context->RegisterGlobalFunction("int DebugDrawWireBox(vec3 pos, vec3 dimensions, vec3 color, int lifespan)",
+ asFUNCTION(ASDebugDrawBox), asCALL_CDECL);
+ context->RegisterGlobalFunction("int DebugDrawCircle(mat4 transform, vec4 color, int lifespan)",
+ asFUNCTION(ASDebugDrawCircle), asCALL_CDECL);
+ context->RegisterGlobalFunction("void DebugDrawRemove(int id)",
+ asFUNCTION(ASDebugDrawClear), asCALL_CDECL);
+ context->RegisterGlobalFunction("void DebugText(string key, string display_text, float lifetime)",
+ asFUNCTION(ASDebugText), asCALL_CDECL);
+ context->RegisterGlobalFunction("string FloatString(float val, int digits)",
+ asFUNCTION(FloatString), asCALL_CDECL);
+ context->RegisterGlobalFunction("int DebugDrawPoint(vec3 pos, vec4 color, int lifespan)",
+ asFUNCTION(ASDebugDrawPoint), asCALL_CDECL);
+}
+
+static bool ObjectExists(int id) {
+ bool result = false;
+
+ if(id >= 0) {
+ result = the_scenegraph->DoesObjectWithIdExist(id);
+ }
+
+ return result;
+}
+
+Object* ReadObjectFromID(int id) {
+ Object* obj = the_scenegraph->GetObjectFromID(id);
+ if(!obj) {
+ std::string callstack = active_context_stack.top()->GetCallstack();
+ std::ostringstream oss;
+ oss << "There is no object " << id << "\n Called from:\n" << callstack;
+ FatalError("Error", "There is no object %d\n Called from:\n%s", id, callstack.c_str());
+ }
+ return obj;
+}
+
+void ASSetTranslation(Object* obj, const vec3 &vec) {
+ switch(obj->GetType()){
+ case _env_object:
+ ((EnvObject*)obj)->SetTranslation(vec);
+ ((EnvObject*)obj)->UpdatePhysicsTransform();
+ break;
+ case _group:
+ ((Group*)obj)->SetTranslation(vec);
+ break;
+ case _prefab:
+ ((Prefab*)obj)->SetTranslation(vec);
+ break;
+ case _decal_object:
+ ((DecalObject*)obj)->SetTranslation(vec);
+ break;
+ case _movement_object:
+ ((MovementObject*)obj)->SetTranslation(vec);
+ break;
+ default:
+ obj->SetTranslation(vec);
+ break;
+ }
+}
+
+bool ASIsSelected(Object* obj){
+ if(obj){
+ return obj->Selected();
+ } else {
+ return false;
+ }
+}
+
+void ASSetSelected(Object* obj, bool val){
+ if(val && obj->permission_flags & Object::CAN_SELECT){
+ obj->Select(true);
+ } else {
+ obj->Select(false);
+ }
+}
+
+void ASSetEnabled(Object* obj, bool val){
+ obj->SetEnabled(val);
+}
+
+void ASSetCollisionEnabled(Object* obj, bool val){
+ obj->SetCollisionEnabled(val);
+}
+
+bool ASGetEnabled(Object* obj){
+ return obj->enabled_;
+}
+
+void ASDeselectAll(){
+ MapEditor::DeselectAll(the_scenegraph);
+}
+
+CScriptArray* ASGetSelected() {
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<int>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+ array->Reserve(the_scenegraph->objects_.size());
+
+ const SceneGraph::object_list &objects = the_scenegraph->objects_;
+ for(SceneGraph::object_list::const_iterator iter = objects.begin();
+ iter != objects.end(); ++iter)
+ {
+ Object* obj = (*iter);
+ if(obj->Selected()) {
+ int val = obj->GetID();
+ array->InsertLast(&val);
+ }
+ }
+ return array;
+}
+
+vec3 ASGetScale(Object* obj) {
+ return obj->GetScale();
+}
+
+void ASSetScale(Object* obj, const vec3 &vec) {
+ switch(obj->GetType()){
+ case _env_object:
+ ((EnvObject*)obj)->SetScale(vec);
+ ((EnvObject*)obj)->UpdatePhysicsTransform();
+ break;
+ case _decal_object:
+ ((DecalObject*)obj)->SetScale(vec);
+ break;
+ case _movement_object:
+ ((MovementObject*)obj)->SetScale(vec);
+ break;
+ default:
+ obj->SetScale(vec);
+ break;
+ }
+}
+
+vec3 ASGetTranslation(Object* obj) {
+ return obj->GetTranslation();
+}
+
+void ASSetRotation(Object* obj, const quaternion &quat) {
+ switch(obj->GetType()){
+ case _env_object:
+ ((EnvObject*)obj)->SetRotation(quat);
+ ((EnvObject*)obj)->UpdatePhysicsTransform();
+ break;
+ case _decal_object:
+ ((DecalObject*)obj)->SetRotation(quat);
+ break;
+ case _movement_object:
+ ((MovementObject*)obj)->SetRotation(quat);
+ break;
+ default:
+ obj->SetRotation(quat);
+ break;
+ }
+}
+
+void ASSetTranslationRotationFast(Object* obj, const vec3 &vec, const quaternion &quat) {
+ switch(obj->GetType()){
+ case _env_object:
+ ((EnvObject*)obj)->SetTranslationRotationFast(vec, quat);
+ break;
+ case _group:
+ ((Group*)obj)->SetTranslationRotationFast(vec, quat);
+ break;
+ default:
+ obj->SetTranslationRotationFast(vec, quat);
+ break;
+ }
+}
+
+
+quaternion ASGetRotation(Object* obj) {
+ return obj->GetRotation();
+}
+
+vec4 ASGetRotationVec4(Object* obj) {
+ quaternion q = obj->GetRotation();
+ float* e = q.entries;
+ return vec4(e[0], e[1], e[2], e[3]);
+}
+
+EntityType ASGetType(Object* obj) {
+ return obj->GetType();
+}
+
+CScriptArray *ASGetObjectIDArrayType(int type) {
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<int>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+ array->Reserve(the_scenegraph->objects_.size());
+
+ const SceneGraph::object_list &objects = the_scenegraph->objects_;
+ for(SceneGraph::object_list::const_iterator iter = objects.begin();
+ iter != objects.end(); ++iter)
+ {
+ Object* obj = (*iter);
+ if(obj->GetType() == type){
+ int val = obj->GetID();
+ if(val != -1){
+ array->InsertLast(&val);
+ }
+ }
+ }
+ return array;
+}
+
+CScriptArray *ASGetObjectIDArray() {
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<int>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+ array->Reserve(the_scenegraph->objects_.size());
+
+ const SceneGraph::object_list &objects = the_scenegraph->objects_;
+ for(SceneGraph::object_list::const_iterator iter = objects.begin();
+ iter != objects.end(); ++iter)
+ {
+ int val = (*iter)->GetID();
+ if(val != -1){
+ array->InsertLast(&val);
+ }
+ }
+ return array;
+}
+
+void ASDeleteObjectID(int val) {
+ LOGD << "Deleting id: " << val << std::endl;
+ the_scenegraph->map_editor->DeleteID(val);
+}
+
+void ASQueueDeleteObjectID(int val) {
+ the_scenegraph->object_ids_to_delete.push_back(val);
+}
+
+int ASCreateObject(const std::string& path, bool exclude_from_save) {
+ int id = -1;
+ EntityDescriptionList desc_list;
+ std::string file_type;
+ Path source;
+ ActorsEditor_LoadEntitiesFromFile(path, desc_list, &file_type, &source);
+ for(unsigned i=0; i<desc_list.size(); ++i){
+ Object* obj = MapEditor::AddEntityFromDesc(the_scenegraph, desc_list[i], false);
+ if( obj ) {
+ obj->exclude_from_undo = exclude_from_save;
+ obj->exclude_from_save = exclude_from_save;
+ obj->permission_flags = 0;
+ id = obj->GetID();
+ } else {
+ LOGE << "Failed at creating object from: " << path << std::endl;
+ }
+ }
+ return id;
+}
+
+int ASCreateObject2(const std::string& path) {
+ return ASCreateObject(path,true);
+}
+
+int ASDuplicateObject(const Object* obj){
+ return the_scenegraph->map_editor->DuplicateObject(obj);
+}
+
+void ASSetPlayer(Object* obj, bool val) {
+ if(obj->GetType() == _movement_object){
+ ((MovementObject*)obj)->is_player = val;
+ }
+}
+
+bool ASGetPlayer(Object* obj) {
+ if(obj->GetType() == _movement_object){
+ return static_cast<MovementObject*>(obj)->is_player;
+ }
+ return false;
+}
+
+int ASGetNumPaletteColors(Object* obj) {
+ OGPalette *palette = obj->GetPalette();
+ if(!palette){
+ return 0;
+ } else {
+ return palette->size();
+ }
+}
+
+vec3 GetPaletteColor(Object* obj, int which) {
+ OGPalette *palette = obj->GetPalette();
+ return palette->at(which).color;
+}
+
+void SetPaletteColor(Object* obj, int which, const vec3& color) {
+ OGPalette *palette = obj->GetPalette();
+ if(palette && (int)palette->size() > which){
+ palette->at(which).color = color;
+ obj->ApplyPalette(*palette);
+ }
+}
+
+vec3 GetTint(Object* obj) {
+ switch(obj->GetType()){
+ case _env_object:
+ return ((EnvObject*)obj)->GetColorTint();
+ break;
+ case _decal_object:
+ return ((DecalObject*)obj)->color_tint_component_.tint_;
+ break;
+ case _dynamic_light_object:
+ return ((DynamicLightObject*)obj)->GetTint();
+ break;
+ default:
+ return vec3(0.0f);
+ break;
+ }
+}
+
+void SetTint(Object* obj, const vec3& color) {
+ obj->ReceiveObjectMessage(OBJECT_MSG::SET_COLOR, &color);
+}
+
+bool ConnectTo(Object* obj, Object* other) {
+ if(obj && other)
+ return obj->ConnectTo(*other);
+
+ return false;
+}
+
+bool Disconnect(Object* obj, Object* other) {
+ if(obj && other)
+ return obj->Disconnect(*other);
+
+ return false;
+}
+
+void AttachItem(Object* movement_object_base, Object* item_object_base, int type, bool mirrored) {
+ ItemObject* item_object = (ItemObject*)item_object_base;
+ MovementObject* movement_object = (MovementObject*)movement_object_base;
+ AttachmentSlotList attachment_slots;
+ movement_object->rigged_object()->AvailableItemSlots(item_object->item_ref(), &attachment_slots);
+ for(AttachmentSlotList::iterator it = attachment_slots.begin(); it != attachment_slots.end(); ++it) {
+ AttachmentSlot& slot = (*it);
+ if(slot.mirrored == mirrored && slot.type == type){
+ movement_object->AttachItemToSlotEditor(item_object->GetID(), slot.type, slot.mirrored, slot.attachment_ref);
+ break;
+ }
+ }
+}
+
+ScriptParams* GetScriptParams(Object* obj) {
+ return obj->GetScriptParams();
+}
+
+std::string GetLabel(Object* obj){
+ switch(obj->GetType()){
+ case _env_object:
+ return ((EnvObject*)obj)->GetLabel();
+ break;
+ default:
+ return "";
+ break;
+ }
+}
+
+void ASUpdateScriptParams(Object* obj){
+ obj->UpdateScriptParams();
+}
+
+void SetBit(int* flags, int bit, bool val){
+ if(val){
+ *flags |= bit;
+ } else {
+ *flags &= ~bit;
+ }
+}
+
+void ASSetCopyable(Object* obj, bool val){
+ SetBit(&obj->permission_flags, Object::CAN_COPY, val);
+}
+
+void ASSetSelectable(Object* obj, bool val){
+ SetBit(&obj->permission_flags, Object::CAN_SELECT, val);
+}
+
+void ASSetDeletable(Object* obj, bool val){
+ SetBit(&obj->permission_flags, Object::CAN_DELETE, val);
+}
+
+void ASSetRotatable(Object* obj, bool val){
+ SetBit(&obj->permission_flags, Object::CAN_ROTATE, val);
+}
+
+void ASSetTranslatable(Object* obj, bool val){
+ SetBit(&obj->permission_flags, Object::CAN_TRANSLATE, val);
+}
+
+void ASSetScalable(Object* obj, bool val){
+ SetBit(&obj->permission_flags, Object::CAN_SCALE, val);
+}
+
+void ASSetEditorLabel(Object* obj, std::string value) {
+ obj->editor_label = value;
+}
+
+std::string ASGetEditorLabel( Object *obj ) {
+ return obj->editor_label;
+}
+
+void ASSetEditorLabelOffset(Object* obj, vec3 offset ) {
+ obj->editor_label_offset = offset;
+}
+
+vec3 ASGetEditorLabelOffset(Object *obj ) {
+ return obj->editor_label_offset;
+}
+
+void ASSetEditorLabelScale(Object* obj, float scale ) {
+ obj->editor_label_scale = scale;
+}
+
+float ASGetEditorLabelScale(Object *obj ) {
+ return obj->editor_label_scale;
+}
+
+template<class A, class B>
+B* refCast(A* a) {
+ if( !a ) return 0;
+ return dynamic_cast<B*>(a);
+}
+
+mat4 ASGetTransform(Object* object){
+ return object->GetTransform();
+}
+
+static vec3 ASGetBoundingBox(Object* object) {
+ if( object->GetType() == _env_object ) {
+ return static_cast<EnvObject*>(object)->GetBoundingBoxSize();
+ } else {
+ return vec3(0.0f);
+ }
+}
+
+static bool ASIsExcludedFromSave(Object* obj) {
+ if( obj ) {
+ return obj->exclude_from_save;
+ } else {
+ LOGE << "Got NULL" << std::endl;
+ return true;
+ }
+}
+
+static bool ASIsExcludedFromUndo(Object* obj) {
+ if( obj ) {
+ return obj->exclude_from_undo;
+ } else {
+ LOGE << "Got NULL" << std::endl;
+ return true;
+ }
+}
+
+static int ASGetParent(Object* obj) {
+ return obj->parent ? obj->parent->GetID() : -1;
+}
+
+CScriptArray* ASGetChildren(Object* obj) {
+ if(obj->GetType() != _group) {
+ return NULL;
+ }
+
+ Group* group = static_cast<Group*>(obj);
+
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<int>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+ array->Reserve(group->children.size());
+
+ for(std::vector<Group::Child>::const_iterator iter = group->children.begin();
+ iter != group->children.end(); ++iter)
+ {
+ int val = (*iter).direct_ptr->GetID();
+ if(val != -1){
+ array->InsertLast(&val);
+ }
+ }
+ return array;
+}
+
+static int ASObjectGetID( Object* obj ) {
+ return obj->GetID();
+}
+
+static std::string ASObjectGetName( Object* obj ) {
+ return obj->GetName();
+}
+
+static void ASObjectSetName(Object* obj, const std::string& string ) {
+ obj->SetName(string);
+}
+
+void AttachObject(ASContext *context) {
+ context->RegisterEnum("EntityType");
+ context->RegisterEnumValue("EntityType","_any_type",_any_type);
+ context->RegisterEnumValue("EntityType","_no_type",_no_type);
+ context->RegisterEnumValue("EntityType","_camera_type",_camera_type);
+ context->RegisterEnumValue("EntityType","_terrain_type",_terrain_type);
+ context->RegisterEnumValue("EntityType","_env_object",_env_object);
+ context->RegisterEnumValue("EntityType","_movement_object",_movement_object);
+ context->RegisterEnumValue("EntityType","_spawn_point",_spawn_point);
+ context->RegisterEnumValue("EntityType","_decal_object",_decal_object);
+ context->RegisterEnumValue("EntityType","_hotspot_object",_hotspot_object);
+ context->RegisterEnumValue("EntityType","_group",_group);
+ context->RegisterEnumValue("EntityType","_rigged_object",_rigged_object);
+ context->RegisterEnumValue("EntityType","_item_object",_item_object);
+ context->RegisterEnumValue("EntityType","_path_point_object",_path_point_object);
+ context->RegisterEnumValue("EntityType","_ambient_sound_object",_ambient_sound_object);
+ context->RegisterEnumValue("EntityType","_placeholder_object",_placeholder_object);
+ context->RegisterEnumValue("EntityType","_light_probe_object",_light_probe_object);
+ context->RegisterEnumValue("EntityType","_dynamic_light_object",_dynamic_light_object);
+ context->RegisterEnumValue("EntityType","_navmesh_hint_object",_navmesh_hint_object);
+ context->RegisterEnumValue("EntityType","_navmesh_region_object",_navmesh_region_object);
+ context->RegisterEnumValue("EntityType","_navmesh_connection_object",_navmesh_connection_object);
+ context->RegisterEnumValue("EntityType","_reflection_capture_object",_reflection_capture_object);
+ context->RegisterEnumValue("EntityType","_light_volume_object",_light_volume_object);
+ context->RegisterEnumValue("EntityType","_prefab",_prefab);
+
+ context->DocsCloseBrace();
+ context->RegisterEnum("AttachmentType");
+ context->RegisterEnumValue("AttachmentType","_at_attachment",_at_attachment);
+ context->RegisterEnumValue("AttachmentType","_at_grip",_at_grip);
+ context->RegisterEnumValue("AttachmentType","_at_sheathe",_at_sheathe);
+ context->RegisterEnumValue("AttachmentType","_at_unspecified",_at_unspecified);
+ context->DocsCloseBrace();
+ context->RegisterEnum("ConnectionType");
+ context->RegisterEnumValue("ConnectionType","kCTNone",Object::kCTNone);
+ context->RegisterEnumValue("ConnectionType","kCTMovementObjects",Object::kCTMovementObjects);
+ context->RegisterEnumValue("ConnectionType","kCTItemObjects",Object::kCTItemObjects);
+ context->RegisterEnumValue("ConnectionType","kCTEnvObjectsAndGroups",Object::kCTEnvObjectsAndGroups);
+ context->RegisterEnumValue("ConnectionType","kCTPathPoints",Object::kCTPathPoints);
+ context->RegisterEnumValue("ConnectionType","kCTPlaceholderObjects",Object::kCTPlaceholderObjects);
+ context->RegisterEnumValue("ConnectionType","kCTNavmeshConnections",Object::kCTNavmeshConnections);
+ context->RegisterEnumValue("ConnectionType","kCTHotspots",Object::kCTHotspots);
+ context->DocsCloseBrace();
+ context->RegisterObjectMethod("Object", "int GetID()", asFUNCTION(ASObjectGetID), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "string GetName()", asFUNCTION(ASObjectGetName), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "void SetName(const string &in)", asFUNCTION(ASObjectSetName), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "bool IsSelected()", asFUNCTION(ASIsSelected), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "void SetSelected(bool)", asFUNCTION(ASSetSelected), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "void SetEnabled(bool)", asFUNCTION(ASSetEnabled), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "void SetCollisionEnabled(bool)", asFUNCTION(ASSetCollisionEnabled), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "bool GetEnabled()", asFUNCTION(ASGetEnabled), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "void SetTranslation(const vec3 &in)", asFUNCTION(ASSetTranslation), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "vec3 GetTranslation()", asFUNCTION(ASGetTranslation), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "void SetScale(const vec3 &in)", asFUNCTION(ASSetScale), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "mat4 GetTransform()", asFUNCTION(ASGetTransform), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "vec3 GetScale()", asFUNCTION(ASGetScale), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "void SetRotation(const quaternion &in)", asFUNCTION(ASSetRotation), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "void SetTranslationRotationFast(const vec3 &in, const quaternion &in)", asFUNCTION(ASSetTranslationRotationFast), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "quaternion GetRotation()", asFUNCTION(ASGetRotation), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "vec4 GetRotationVec4()", asFUNCTION(ASGetRotationVec4), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "EntityType GetType()", asFUNCTION(ASGetType), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "void SetPlayer(bool)", asFUNCTION(ASSetPlayer), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "bool GetPlayer()", asFUNCTION(ASGetPlayer), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "int GetNumPaletteColors()", asFUNCTION(ASGetNumPaletteColors), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "vec3 GetPaletteColor(int which)", asFUNCTION(GetPaletteColor), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "void SetPaletteColor(int which, const vec3 &in color)", asFUNCTION(SetPaletteColor), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "vec3 GetTint()", asFUNCTION(GetTint), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "void SetTint(const vec3 &in)", asFUNCTION(SetTint), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "bool ConnectTo(Object@)", asFUNCTION(ConnectTo), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "bool Disconnect(Object@)", asFUNCTION(Disconnect), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "void AttachItem(Object@, AttachmentType, bool mirrored)", asFUNCTION(AttachItem), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "void ReceiveScriptMessage(const string &in)", asMETHOD(Object, ReceiveScriptMessage), asCALL_THISCALL);
+ context->RegisterObjectMethod("Object", "void QueueScriptMessage(const string &in)", asMETHOD(Object, QueueScriptMessage), asCALL_THISCALL);
+ context->RegisterObjectMethod("Object", "void UpdateScriptParams()", asFUNCTION(ASUpdateScriptParams), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "void SetCopyable(bool)", asFUNCTION(ASSetCopyable), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "void SetSelectable(bool)", asFUNCTION(ASSetSelectable), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "void SetDeletable(bool)", asFUNCTION(ASSetDeletable), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "void SetScalable(bool)", asFUNCTION(ASSetScalable), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "void SetTranslatable(bool)", asFUNCTION(ASSetTranslatable), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "void SetRotatable(bool)", asFUNCTION(ASSetRotatable), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "void SetEditorLabel(string)", asFUNCTION(ASSetEditorLabel), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "string GetEditorLabel()", asFUNCTION(ASGetEditorLabel), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "void SetEditorLabelOffset(vec3)", asFUNCTION(ASSetEditorLabelOffset), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "vec3 GetEditorLabelOffset()", asFUNCTION(ASGetEditorLabelOffset), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "void SetEditorLabelScale(float)", asFUNCTION(ASSetEditorLabelScale), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "float GetEditorLabelScale()", asFUNCTION(ASGetEditorLabelScale), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "ScriptParams@ GetScriptParams()", asFUNCTION(GetScriptParams), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "string GetLabel()", asFUNCTION(GetLabel), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "vec3 GetBoundingBox()", asFUNCTION(ASGetBoundingBox), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "bool IsExcludedFromSave()", asFUNCTION(ASIsExcludedFromSave), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "bool IsExcludedFromUndo()", asFUNCTION(ASIsExcludedFromUndo), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "int GetParent()", asFUNCTION(ASGetParent), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "array<int>@ GetChildren()", asFUNCTION(ASGetChildren), asCALL_CDECL_OBJFIRST);
+
+ context->DocsCloseBrace();
+ context->RegisterGlobalFunction("Object@ ReadObjectFromID(int)", asFUNCTION(ReadObjectFromID), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ObjectExists()", asFUNCTION(ObjectExists), asCALL_CDECL);
+ context->RegisterGlobalFunction("array<int> @GetObjectIDs()", asFUNCTION(ASGetObjectIDArray), asCALL_CDECL);
+ context->RegisterGlobalFunction("array<int> @GetObjectIDsType(int type)", asFUNCTION(ASGetObjectIDArrayType), asCALL_CDECL);
+ context->RegisterGlobalFunction("void DeleteObjectID(int)", asFUNCTION(ASDeleteObjectID), asCALL_CDECL);
+ context->RegisterGlobalFunction("void QueueDeleteObjectID(int)", asFUNCTION(ASQueueDeleteObjectID), asCALL_CDECL);
+ context->RegisterGlobalFunction("int CreateObject(const string &in path, bool exclude_from_save)", asFUNCTION(ASCreateObject), asCALL_CDECL);
+ context->RegisterGlobalFunction("int CreateObject(const string &in path)", asFUNCTION(ASCreateObject2), asCALL_CDECL);
+ context->RegisterGlobalFunction("int DuplicateObject(Object@ obj)", asFUNCTION(ASDuplicateObject), asCALL_CDECL);
+ context->RegisterGlobalFunction("void DeselectAll()", asFUNCTION(ASDeselectAll), asCALL_CDECL);
+ context->RegisterGlobalFunction("array<int> @GetSelected()", asFUNCTION(ASGetSelected), asCALL_CDECL);
+
+ PathPointObject::RegisterToScript(context);
+ context->RegisterObjectMethod("Object", "PathPointObject@ opCast()", asFUNCTION((refCast<Object,PathPointObject>)), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectMethod("Object", "Hotspot@ opCast()", asFUNCTION((refCast<Object, Hotspot>)), asCALL_CDECL_OBJLAST);
+
+}
+
+static void ASSetEditorDisplayName(PlaceholderObject* obj, const std::string& name){
+ obj->editor_display_name = name;
+}
+
+static void ASSetUnsavedChanges(PlaceholderObject* obj, bool changes){
+ obj->unsaved_changes = changes;
+}
+
+static bool ASGetUnsavedChanges(PlaceholderObject* obj){
+ return obj->unsaved_changes;
+}
+
+void AttachPlaceholderObject(ASContext *context) {
+ context->RegisterEnum("PlaceholderObjectType");
+ context->RegisterEnumValue("PlaceholderObjectType","kCamPreview",PlaceholderObject::kCamPreview);
+ context->RegisterEnumValue("PlaceholderObjectType","kPlayerConnect",PlaceholderObject::kPlayerConnect);
+ context->RegisterEnumValue("PlaceholderObjectType","kSpawn",PlaceholderObject::kSpawn);
+ context->RegisterObjectType("PlaceholderObject", 0, asOBJ_REF | asOBJ_NOCOUNT);
+ context->RegisterObjectMethod("PlaceholderObject", "void SetPreview(const string &in path)", asMETHOD(PlaceholderObject, SetPreview), asCALL_THISCALL);
+ context->RegisterObjectMethod("PlaceholderObject", "void SetBillboard(const string &in path)", asMETHOD(PlaceholderObject, SetBillboard), asCALL_THISCALL);
+ context->RegisterObjectMethod("PlaceholderObject", "void SetSpecialType(int)", asMETHOD(PlaceholderObject, SetSpecialType), asCALL_THISCALL);
+ context->RegisterObjectMethod("PlaceholderObject", "void SetEditorDisplayName(const string &in name)", asFUNCTION(ASSetEditorDisplayName), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("PlaceholderObject", "int GetConnectID()", asMETHOD(PlaceholderObject, GetConnectID), asCALL_THISCALL);
+ context->RegisterObjectMethod("PlaceholderObject", "uint64 GetConnectToTypeFilterFlags()", asMETHOD(PlaceholderObject, GetConnectToTypeFilterFlags), asCALL_THISCALL, "Returns a bit mask, with EntityType for bit indices");
+ context->RegisterObjectMethod("PlaceholderObject", "void SetConnectToTypeFilterFlags(uint64 flags)", asMETHOD(PlaceholderObject, SetConnectToTypeFilterFlags), asCALL_THISCALL, "Set a bit mask, with EntityType for bit indices");
+ context->RegisterObjectMethod("PlaceholderObject", "void SetUnsavedChanges(bool changes)", asFUNCTION(ASSetUnsavedChanges), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("PlaceholderObject", "bool GetUnsavedChanges()", asFUNCTION(ASGetUnsavedChanges), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Object", "PlaceholderObject@ opCast()", asFUNCTION((refCast<Object,PlaceholderObject>)), asCALL_CDECL_OBJLAST);
+ context->DocsCloseBrace();
+}
+
+void ASClearTemporaryDecals() {
+ //the_scenegraph->decals->ClearTemporary();
+ while(!the_scenegraph->dynamic_decals.empty()){
+ DecalObject *obj = the_scenegraph->dynamic_decals.front();
+ obj->SetParent(NULL);
+ the_scenegraph->UnlinkObject(obj);
+ obj->Dispose();
+ delete(obj);
+ }
+}
+
+void AttachDecals( ASContext *context ) {
+ context->RegisterGlobalFunction("void ClearTemporaryDecals()",
+ asFUNCTION(ASClearTemporaryDecals), asCALL_CDECL, "Like blood splats and footprints");
+}
+
+void ResetLevel() {
+ the_scenegraph->QueueLevelReset();
+}
+
+int ASGetNumCharacters() {
+ return the_scenegraph->movement_objects_.size();
+}
+
+int ASGetNumHotspots() {
+ return the_scenegraph->hotspots_.size();
+}
+
+struct TokenIterator {
+ int64_t token_start;
+ int64_t token_end;
+ int64_t last_token_end;
+ void Init() {
+ token_start = 0;
+ token_end = 0;
+ last_token_end = 0;
+ }
+ bool FindNextToken(const std::string &str){
+ bool in_quotes = false;
+ const char* cstr = str.c_str();
+ const char* chr = &cstr[last_token_end];
+ if(*chr == '\0'){
+ return false;
+ }
+ while(*chr == ' '){
+ ++chr;
+ }
+ if(*chr == '\"'){
+ in_quotes = true;
+ ++chr;
+ }
+ token_start = (int64_t) (chr - cstr);
+ bool escape_character = false;
+ while(true){
+ if((*chr == ' ' && !in_quotes) || (*chr == '\"' && !escape_character) || *chr == '\0'){
+ break;
+ }
+ escape_character = false;
+ if(*chr == '\\' && !escape_character){
+ escape_character = true;
+ }
+ ++chr;
+ }
+ token_end = (int64_t) (chr - cstr);
+ last_token_end = token_end;
+ if(in_quotes && *chr == '\"'){
+ ++chr;
+ ++last_token_end;
+ }
+ //printf("Tokenized: %s\n", str.substr(token_start, token_end-token_start).c_str());
+ return true;
+ }
+ std::string GetToken(const std::string &str){
+ int64_t len = token_end - token_start;
+ std::vector<char> token;
+ token.resize(len+1);
+ int64_t index = 0;
+ bool escape = false;
+ for(int i=0; i<len; ++i){
+ char c = str[token_start+i];
+ if(c == '\\' && !escape){
+ escape = true;
+ } else {
+ if(escape && c != '"' && c != '\\'){
+ token[index] = '\\';
+ ++index;
+ }
+ escape = false;
+ token[index] = c;
+ ++index;
+ }
+ }
+ token[index] = '\0';
+ std::string tok = &token[0];
+ return tok;
+ }
+};
+
+void Breakpoint(int val){
+ LOGI << "Breakpoint: " << val << std::endl;
+}
+
+void AttachTokenIterator(ASContext *context) {
+ context->RegisterObjectType("TokenIterator", sizeof(TokenIterator), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS);
+ context->RegisterObjectMethod("TokenIterator","void Init()", asMETHOD(TokenIterator, Init), asCALL_THISCALL);
+ context->RegisterObjectMethod("TokenIterator","bool FindNextToken(const string& in)", asMETHOD(TokenIterator, FindNextToken), asCALL_THISCALL);
+ context->RegisterObjectMethod("TokenIterator","string GetToken(const string& in)", asMETHOD(TokenIterator, GetToken), asCALL_THISCALL);
+ context->DocsCloseBrace();
+ context->RegisterGlobalFunction("void Breakpoint(int)", asFUNCTION(Breakpoint), asCALL_CDECL);
+}
+
+MovementObject* ASReadCharacter( int which ) {
+ if(which < 0 || which >= (int)the_scenegraph->movement_objects_.size()){
+ std::string callstack = active_context_stack.top()->GetCallstack();
+ FatalError("Error", "There is no movement object #%d\n Called from:\n%s", which, callstack.c_str());
+ }
+ return (MovementObject*)the_scenegraph->movement_objects_[which];
+}
+
+Hotspot* ASReadHotspot( int which ) {
+ if(which < 0 || which >= (int)the_scenegraph->hotspots_.size()){
+ std::string callstack = active_context_stack.top()->GetCallstack();
+ FatalError("Error", "There is no hotspot %d\n Called from:\n%s", which, callstack.c_str());
+ }
+ return (Hotspot*)the_scenegraph->hotspots_[which];
+}
+
+MovementObject* ASReadCharacterID( int which ) {
+ if(which==-1) {
+ std::string callstack = active_context_stack.top()->GetCallstack();
+ LOGW << "Called ReadCharacter ID with parameter -1 from\n " << callstack.c_str() << std::endl;
+ return NULL;
+ }
+ Object* obj = the_scenegraph->GetObjectFromID(which);
+ if(!obj || obj->GetType() != _movement_object){
+ std::string callstack = active_context_stack.top()->GetCallstack();
+ LOGE.Format("There is no movement object with id %d\n Called from:\n%s\n", which, callstack.c_str());
+ return NULL;
+ }
+ return (MovementObject*)obj;
+}
+
+int ASGetNumItems( ) {
+ return the_scenegraph->item_objects_.size();
+}
+
+ItemObject* ASReadItem( int which ) {
+ if(which < 0 || which >= (int)the_scenegraph->item_objects_.size()){
+ std::string callstack = active_context_stack.top()->GetCallstack();
+ FatalError("Error", "There is no item object %d\n Called from:\n%s", which, callstack.c_str());
+ }
+ return (ItemObject*)the_scenegraph->item_objects_[which];
+}
+
+ItemObject* ASReadItemID( int which ) {
+ Object* obj = the_scenegraph->GetObjectFromID(which);
+ if(!obj || obj->GetType() != _item_object){
+ std::string callstack = active_context_stack.top()->GetCallstack();
+ FatalError("Error", "There is no item object %d\n Called from:\n%s", which, callstack.c_str());
+ }
+ return (ItemObject*)obj;
+}
+
+EnvObject* ASReadEnvObjectID( int which ) {
+ Object* obj = the_scenegraph->GetObjectFromID(which);
+ if(!obj || obj->GetType() != _env_object){
+ FatalError("Error", "There is no env object %d", which);
+ }
+ return (EnvObject*)obj;
+}
+
+bool ASObjectExists( int which ) {
+ return the_scenegraph->DoesObjectWithIdExist(which);
+}
+
+bool ASMovementObjectExists( int id ) {
+ Object* obj = the_scenegraph->GetObjectFromID(id);
+ if(obj && obj->GetType() == _movement_object) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool ASIsGroupDerived( int id ) {
+ Object* obj = the_scenegraph->GetObjectFromID(id);
+ if(obj && obj->IsGroupDerived() ) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+#include "Scripting/angelscript/add_on/scriptarray/scriptarray.h"
+void GetCharactersInSphere( vec3 origin, float radius, CScriptArray *array ) {
+ ContactInfoCallback cb;
+ the_scenegraph->abstract_bullet_world_->GetSphereCollisions(origin, radius, cb);
+ for(int i=0; i<cb.contact_info.size();++i){
+ BulletObject* bo = cb.contact_info[i].object;
+ if(bo && bo->owner_object && the_scenegraph->IsObjectSane(bo->owner_object) && bo->owner_object->GetType() == _movement_object) {
+ int val = ((MovementObject*)bo->owner_object)->GetID();
+ array->InsertLast(&val);
+ }
+ }
+}
+
+void GetCharacters( CScriptArray *array ) {
+ for(int i=0, len=the_scenegraph->movement_objects_.size(); i<len; ++i){
+ int val = the_scenegraph->movement_objects_[i]->GetID();
+ array->InsertLast(&val);
+ }
+}
+
+void GetCharactersInHull( std::string path, mat4 transform, CScriptArray *array ) {
+ ContactInfoCallback cb;
+ the_scenegraph->abstract_bullet_world_->GetConvexHullCollisions(path, transform, cb);
+ for(int i=0; i<cb.contact_info.size();++i){
+ BulletObject* bo = cb.contact_info[i].object;
+ if(bo && bo->owner_object && the_scenegraph->IsObjectSane(bo->owner_object) && bo->owner_object->GetType() == _movement_object) {
+ MovementObject* mo = (MovementObject*)bo->owner_object;
+ if(mo->visible){
+ int val = mo->GetID();
+ array->InsertLast(&val);
+ }
+ }
+ }
+}
+
+void GetObjectsInHull( std::string path, mat4 transform, CScriptArray *array ) {
+ ContactInfoCallback cb;
+ the_scenegraph->abstract_bullet_world_->GetConvexHullCollisions(path, transform, cb);
+ for(int i=0; i<cb.contact_info.size();++i){
+ BulletObject* bo = cb.contact_info[i].object;
+ if(bo && bo->owner_object && the_scenegraph->IsObjectSane(bo->owner_object) ) {
+ int val = ((Object*)bo->owner_object)->GetID();
+ array->InsertLast(&val);
+ }
+ }
+}
+
+static void CreateCustomHull( const std::string& key, const CScriptArray &array ) {
+ std::vector<vec3> data;
+ for(int i=0, len=array.GetSize(); i<len ; ++i) {
+ data.push_back(*((vec3*)array.At(i)));
+ }
+ the_scenegraph->abstract_bullet_world_->CreateCustomHullShape(key, data);
+}
+
+#include "Objects/movementobject.h"
+#include "Objects/itemobject.h"
+
+bool ASDoesItemFitInItem( int a, int b ) {
+ Object* obj_a = the_scenegraph->GetObjectFromID(a);
+ Object* obj_b = the_scenegraph->GetObjectFromID(b);
+ if(!obj_a || obj_a->GetType() != _item_object){
+ std::string callstack = active_context_stack.top()->GetCallstack();
+ FatalError("Error", "There is no item object %d.\n Called from:\n%s", a, callstack.c_str());
+ };
+ if(!obj_b || obj_b->GetType() != _item_object){
+ std::string callstack = active_context_stack.top()->GetCallstack();
+ FatalError("Error", "There is no item object %d.\n Called from:\n%s", b, callstack.c_str());
+ }
+ ItemObject* item_obj_a = (ItemObject*) obj_a;
+ ItemObject* item_obj_b = (ItemObject*) obj_b;
+ return item_obj_b->item_ref()->GetContains() == item_obj_a->item_ref()->path_;
+}
+
+float ASGetFriction( const vec3 &pos ) {
+ return the_scenegraph->GetMaterialFriction(pos);
+}
+
+void ASSetPaused(bool paused){
+ Engine::Instance()->menu_paused = paused;
+ Engine::Instance()->CommitPause();
+}
+
+int ASFindFirstCharacterInGroup(int id) {
+ Object* obj = the_scenegraph->GetObjectFromID(id);
+ if(obj && obj->IsGroupDerived() ) {
+ Group* group = static_cast<Group*>(obj);
+ std::vector<Object*> children;
+ group->GetTopDownCompleteChildren(&children);
+ for( unsigned i = 0; i < children.size(); i++ ) {
+ if( children[i]->GetType() == _movement_object ) {
+ return children[i]->GetID();
+ }
+ }
+ }
+ return -1;
+}
+
+void AttachScenegraph( ASContext *context, SceneGraph *scenegraph ) {
+ the_scenegraph = scenegraph;
+
+ context->RegisterGlobalFunction("int GetNumCharacters()",
+ asFUNCTION(ASGetNumCharacters), asCALL_CDECL);
+ context->RegisterGlobalFunction("int GetNumHotspots()",
+ asFUNCTION(ASGetNumHotspots), asCALL_CDECL);
+ context->RegisterGlobalFunction("int GetNumItems()",
+ asFUNCTION(ASGetNumItems), asCALL_CDECL);
+ context->RegisterGlobalFunction("void GetCharactersInSphere(vec3 position, float radius, array<int>@ id_array)",
+ asFUNCTION(GetCharactersInSphere), asCALL_CDECL);
+ context->RegisterGlobalFunction("void GetCharacters(array<int>@ id_array)",
+ asFUNCTION(GetCharacters), asCALL_CDECL);
+ context->RegisterGlobalFunction("void GetCharactersInHull(string model_path, mat4, array<int>@ id_array)",
+ asFUNCTION(GetCharactersInHull), asCALL_CDECL);
+ context->RegisterGlobalFunction("void GetObjectsInHull(string model_path, mat4, array<int>@ id_array)",
+ asFUNCTION(GetObjectsInHull), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ResetLevel()",
+ asFUNCTION(ResetLevel), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool DoesItemFitInItem(int item_id, int holster_item_id)",
+ asFUNCTION(ASDoesItemFitInItem), asCALL_CDECL);
+ context->RegisterGlobalFunction("float GetFriction(const vec3 &in position)",
+ asFUNCTION(ASGetFriction), asCALL_CDECL);
+ context->RegisterGlobalFunction("void CreateCustomHull(const string &in key, const array<vec3> &vertices)",
+ asFUNCTION(CreateCustomHull), asCALL_CDECL);
+
+ context->RegisterObjectType("Object", 0, asOBJ_REF | asOBJ_NOCOUNT);
+
+ DefineRiggedObjectTypePublic(context);
+ if(!context->TypeExists("MovementObject")){
+ DefineMovementObjectTypePublic(context);
+ context->DocsCloseBrace();
+ }
+ DefineItemObjectTypePublic(context);
+ DefineEnvObjectTypePublic(context);
+ DefineHotspotTypePublic(context);
+
+ AttachObject(context);
+
+ context->RegisterGlobalFunction("MovementObject@ ReadCharacter(int index)",asFUNCTION( ASReadCharacter), asCALL_CDECL, "e.g. first character in scene");
+ context->RegisterGlobalFunction("MovementObject@ ReadCharacterID(int id)",asFUNCTION( ASReadCharacterID), asCALL_CDECL, "e.g. character with object ID 39");
+ context->RegisterGlobalFunction("Hotspot@ ReadHotspot(int index)",asFUNCTION( ASReadHotspot), asCALL_CDECL);
+ context->RegisterGlobalFunction("ItemObject@ ReadItem(int index)",asFUNCTION( ASReadItem), asCALL_CDECL);
+ context->RegisterGlobalFunction("ItemObject@ ReadItemID(int id)",asFUNCTION( ASReadItemID), asCALL_CDECL);
+ context->RegisterGlobalFunction("EnvObject@ ReadEnvObjectID(int id)",asFUNCTION( ASReadEnvObjectID), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ObjectExists(int id)",asFUNCTION( ASObjectExists), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool MovementObjectExists(int id)", asFUNCTION(ASMovementObjectExists), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool IsGroupDerived(int id)", asFUNCTION(ASIsGroupDerived), asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetPaused(bool paused)",asFUNCTION( ASSetPaused), asCALL_CDECL);
+ context->RegisterGlobalFunction("int FindFirstCharacterInGroup(int id)", asFUNCTION( ASFindFirstCharacterInGroup ), asCALL_CDECL);
+}
+
+#include "Game/level.h"
+
+void ASClearUndoHistory() {
+ the_scenegraph->map_editor->ClearUndoHistory();
+}
+
+void ASRibbonItemSetEnabled(const std::string& str, bool val){
+}
+
+void ASRibbonItemSetToggled(const std::string& str, bool val){
+}
+
+void ASRibbonItemFlash(const std::string& str){
+}
+
+bool ASMediaMode() {
+ return Graphics::Instance()->media_mode();
+}
+
+void ASSetMediaMode(bool val) {
+ Graphics::Instance()->SetMediaMode(val);
+}
+
+static void ASSetInterlevelData(const std::string& key, const std::string& val) {
+ Engine::Instance()->interlevel_data[key] = val;
+}
+
+static std::string ASGetInterlevelData(const std::string& key) {
+ return Engine::Instance()->interlevel_data[key];
+}
+
+void AttachLevel( ASContext *context ) {
+ Level::DefineLevelTypePublic(context);
+ context->RegisterGlobalProperty("Level level", the_scenegraph->level);
+ context->RegisterGlobalFunction("void ClearUndoHistory()",asFUNCTION( ASClearUndoHistory), asCALL_CDECL);
+ context->RegisterGlobalFunction("void RibbonItemSetEnabled(const string &in, bool)",asFUNCTION( ASRibbonItemSetEnabled), asCALL_CDECL);
+ context->RegisterGlobalFunction("void RibbonItemSetToggled(const string &in, bool)",asFUNCTION( ASRibbonItemSetToggled), asCALL_CDECL);
+ context->RegisterGlobalFunction("void RibbonItemFlash(const string &in)",asFUNCTION( ASRibbonItemFlash), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool MediaMode()",asFUNCTION( ASMediaMode), asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetMediaMode(bool)",asFUNCTION( ASSetMediaMode), asCALL_CDECL);
+}
+
+void AttachInterlevelData( ASContext *context ) {
+ context->RegisterGlobalFunction("void SetInterlevelData(const string &in, const string &in)",asFUNCTION( ASSetInterlevelData), asCALL_CDECL);
+ context->RegisterGlobalFunction("string GetInterlevelData(const string &in)",asFUNCTION( ASGetInterlevelData), asCALL_CDECL);
+}
+
+static void AS_TextureAssetRefConstructor(void* memory) {
+ new(memory) TextureAssetRef();
+}
+
+static void AS_TextureAssetRefCopyConstructor(void* memory, const TextureAssetRef& other) {
+ new(memory) TextureAssetRef(other);
+}
+
+static void AS_TextureAssetRefDestructor(void* memory) {
+ ((TextureAssetRef*)memory)->~TextureAssetRef();
+}
+
+static const TextureAssetRef& AS_TextureAssetRefOpAssign(TextureAssetRef* self, const TextureAssetRef& other) {
+ return (*self) = other;
+}
+
+static bool AS_TextureAssetRefOpEquals(TextureAssetRef* self, const TextureAssetRef& other) {
+ return (*self) == other;
+}
+
+static int AS_TextureAssetRefOpCmp(TextureAssetRef* self, const TextureAssetRef& other) {
+ return (*self) < other ?
+ -1 :
+ ((*self) == other ? 0 : 1);
+}
+
+static TextureAssetRef AS_LoadTexture(const std::string& path, uint32_t texture_load_flags) {
+ return Engine::Instance()->GetAssetManager()->LoadSync<TextureAsset>(path, texture_load_flags, 0x0);
+}
+
+bool AS_ImGui_Begin(const std::string& name, int flags) {
+ return ImGui::Begin(name.c_str(), NULL, flags);
+}
+
+bool AS_ImGui_Begin2(const std::string& name, bool& open, int flags) {
+ return ImGui::Begin(name.c_str(), &open, flags);
+}
+
+void AS_ImGui_End() {
+ ImGui::End();
+}
+
+bool AS_ImGui_BeginChild(const std::string& str_id, const vec2& size, bool border, int extra_flags) {
+ return ImGui::BeginChild(str_id.c_str(), ImVec2(size[0], size[1]), border, extra_flags);
+}
+
+bool AS_ImGui_BeginChild2(unsigned int id, const vec2& size, bool border, int extra_flags) {
+ return ImGui::BeginChild(id, ImVec2(size[0], size[1]), border, extra_flags);
+}
+
+void AS_ImGui_EndChild() {
+ return ImGui::EndChild();
+}
+
+vec2 AS_ImGui_GetContentRegionMax() {
+ ImVec2 result = ImGui::GetContentRegionMax();
+ return vec2(result.x, result.y);
+}
+
+vec2 AS_ImGui_GetContentRegionAvail() {
+ ImVec2 result = ImGui::GetContentRegionAvail();
+ return vec2(result.x, result.y);
+}
+
+float AS_ImGui_GetContentRegionAvailWidth() {
+ return ImGui::GetContentRegionAvailWidth();
+}
+
+vec2 AS_ImGui_GetWindowContentRegionMin() {
+ ImVec2 result = ImGui::GetWindowContentRegionMin();
+ return vec2(result.x, result.y);
+}
+
+vec2 AS_ImGui_GetWindowContentRegionMax() {
+ ImVec2 result = ImGui::GetWindowContentRegionMax();
+ return vec2(result.x, result.y);
+}
+
+float AS_ImGui_GetWindowContentRegionWidth() {
+ return ImGui::GetWindowContentRegionWidth();
+}
+
+vec2 AS_ImGui_GetWindowPos() {
+ ImVec2 result = ImGui::GetWindowPos();
+ return vec2(result.x, result.y);
+}
+
+vec2 AS_ImGui_GetWindowSize() {
+ ImVec2 result = ImGui::GetWindowSize();
+ return vec2(result.x, result.y);
+}
+
+float AS_ImGui_GetWindowWidth() {
+ return ImGui::GetWindowWidth();
+}
+
+float AS_ImGui_GetWindowHeight() {
+ return ImGui::GetWindowHeight();
+}
+
+bool AS_ImGui_IsWindowCollapsed() {
+ return ImGui::IsWindowCollapsed();
+}
+
+bool AS_ImGui_IsWindowAppearing() {
+ return ImGui::IsWindowAppearing();
+}
+
+void AS_ImGui_SetWindowFontScale(float scale) {
+ return ImGui::SetWindowFontScale(scale);
+}
+
+void AS_ImGui_SetNextWindowPos(const vec2& pos, int cond) {
+ ImGui::SetNextWindowPos(ImVec2(pos[0], pos[1]), cond);
+}
+
+void AS_ImGui_SetNextWindowPosCenter(int cond) {
+ ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x * 0.5f, ImGui::GetIO().DisplaySize.y * 0.5f), cond, ImVec2(0.5f, 0.5f));
+}
+
+void AS_ImGui_SetNextWindowSize(const vec2& size, int cond) {
+ ImGui::SetNextWindowSize(ImVec2(size[0], size[1]), cond);
+}
+
+void AS_ImGui_SetNextWindowContentSize(const vec2& size) {
+ ImGui::SetNextWindowContentSize(ImVec2(size[0], size[1]));
+}
+
+void AS_ImGui_SetNextWindowContentWidth(float width) {
+ ImGui::SetNextWindowContentSize(ImVec2(width, 0.0f));
+}
+
+void AS_ImGui_SetNextWindowCollapsed(bool collapsed, int cond) {
+ ImGui::SetNextWindowCollapsed(collapsed, cond);
+}
+
+void AS_ImGui_SetNextWindowFocus() {
+ ImGui::SetNextWindowFocus();
+}
+
+void AS_ImGui_SetWindowPos(const vec2& pos, int cond) {
+ ImGui::SetWindowPos(ImVec2(pos[0], pos[1]), cond);
+}
+
+void AS_ImGui_SetWindowSize(const vec2& size, int cond) {
+ ImGui::SetWindowSize(ImVec2(size[0], size[1]), cond);
+}
+
+void AS_ImGui_SetWindowCollapsed(bool collapsed, int cond) {
+ ImGui::SetWindowCollapsed(collapsed, cond);
+}
+
+void AS_ImGui_SetWindowFocus() {
+ ImGui::SetWindowFocus();
+}
+
+void AS_ImGui_SetWindowPos2(const std::string& name, const vec2& pos, int cond) {
+ ImGui::SetWindowPos(name.c_str(), ImVec2(pos[0], pos[1]), cond);
+}
+
+void AS_ImGui_SetWindowSize2(const std::string& name, const vec2& size, int cond) {
+ ImGui::SetWindowSize(name.c_str(), ImVec2(size[0], size[1]), cond);
+}
+
+void AS_ImGui_SetWindowCollapsed2(const std::string& name, bool collapsed, int cond) {
+ ImGui::SetWindowCollapsed(name.c_str(), collapsed, cond);
+}
+
+void AS_ImGui_SetWindowFocus2(const std::string& name) {
+ ImGui::SetWindowFocus(name.c_str());
+}
+
+float AS_ImGui_GetScrollX() {
+ return ImGui::GetScrollX();
+}
+
+float AS_ImGui_GetScrollY() {
+ return ImGui::GetScrollY();
+}
+
+float AS_ImGui_GetScrollMaxX() {
+ return ImGui::GetScrollMaxX();
+}
+
+float AS_ImGui_GetScrollMaxY() {
+ return ImGui::GetScrollMaxY();
+}
+
+void AS_ImGui_SetScrollX(float scroll_x) {
+ ImGui::SetScrollX(scroll_x);
+}
+
+void AS_ImGui_SetScrollY(float scroll_y) {
+ ImGui::SetScrollY(scroll_y);
+}
+
+void AS_ImGui_SetScrollHere(float center_y_ratio = 0.5f) {
+ ImGui::SetScrollHereY(center_y_ratio);
+}
+
+void AS_ImGui_SetScrollFromPosY(float pos_y, float center_y_ratio = 0.5f) {
+ ImGui::SetScrollFromPosY(pos_y, center_y_ratio);
+}
+
+void AS_ImGui_SetKeyboardFocusHere(int val) {
+ ImGui::SetKeyboardFocusHere(val);
+}
+
+void AS_ImGui_PushStyleColor(int idx, const vec4& color) {
+ ImVec4 col(color[0], color[1], color[2], color[3]);
+ ImGui::PushStyleColor(idx, col);
+}
+
+void AS_ImGui_PopStyleColor(int count) {
+ ImGui::PopStyleColor(count);
+}
+
+void AS_ImGui_PushStyleVar(int idx, float val) {
+ ImGui::PushStyleVar(idx, val);
+}
+
+void AS_ImGui_PushStyleVar2(int idx, const vec2& value) {
+ ImVec2 val(value[0], value[1]);
+ ImGui::PushStyleVar(idx, val);
+}
+
+void AS_ImGui_PushStyleVar3(int idx, bool value) {
+ ImGui::PushStyleVar(idx, value);
+}
+
+void AS_ImGui_PushStyleVar4(int idx, int value) {
+ ImGui::PushStyleVar(idx, (float) value);
+}
+
+void AS_ImGui_BeginDisabled(bool is_disabled) {
+ ImGui::BeginDisabled(is_disabled);
+}
+
+void AS_ImGui_EndDisabled(bool is_disabled) {
+ ImGui::EndDisabled();
+}
+
+void AS_ImGui_PopStyleVar(int count) {
+ ImGui::PopStyleVar(count);
+}
+
+vec4 AS_ImGui_GetStyleColorVec4(int idx) {
+ ImVec4 ret = ImGui::GetStyleColorVec4(idx);
+ return vec4(ret.x, ret.y, ret.z, ret.w);
+}
+
+uint32_t AS_ImGui_GetColorU32(uint32_t idx, float alpha_mul) {
+ return ImGui::GetColorU32(idx, alpha_mul);
+}
+
+uint32_t AS_ImGui_GetColorU32_2(const vec3& col) {
+ return ImGui::GetColorU32(ImVec4(col[0], col[1], col[2], 1.0f));
+}
+
+uint32_t AS_ImGui_GetColorU32_3(const vec4& col) {
+ return ImGui::GetColorU32(ImVec4(col[0], col[1], col[2], col[3]));
+}
+
+uint32_t AS_ImGui_GetColorU32_4(float r, float g, float b, float alpha_mul) {
+ return ImGui::GetColorU32(ImVec4(r, g, b, alpha_mul));
+}
+
+void AS_ImGui_PushItemWidth(float val) {
+ ImGui::PushItemWidth(val);
+}
+
+void AS_ImGui_PopItemWidth() {
+ ImGui::PopItemWidth();
+}
+
+float AS_ImGui_CalcItemWidth() {
+ return ImGui::CalcItemWidth();
+}
+
+void AS_ImGui_PushTextWrapPos(float wrap_pos_x) {
+ ImGui::PushTextWrapPos(wrap_pos_x);
+}
+
+void AS_ImGui_PopTextWrapPos() {
+ ImGui::PopTextWrapPos();
+}
+
+void AS_ImGui_PushAllowKeyboardFocus(bool allow_keyboard_focus) {
+ ImGui::PushAllowKeyboardFocus(allow_keyboard_focus);
+}
+
+void AS_ImGui_PopAllowKeyboardFocus() {
+ ImGui::PopAllowKeyboardFocus();
+}
+
+void AS_ImGui_PushButtonRepeat(bool repeat) {
+ ImGui::PushButtonRepeat(repeat);
+}
+
+void AS_ImGui_PopButtonRepeat() {
+ ImGui::PopButtonRepeat();
+}
+
+void AS_ImGui_Separator() {
+ ImGui::Separator();
+}
+
+void AS_ImGui_SameLine(float pos_x, float spacing_w) {
+ ImGui::SameLine(pos_x, spacing_w);
+}
+
+void AS_ImGui_NewLine() {
+ ImGui::NewLine();
+}
+
+void AS_ImGui_Spacing() {
+ ImGui::Spacing();
+}
+
+void AS_ImGui_Dummy(const vec2& size) {
+ ImVec2 temp_size(size[0], size[1]);
+ ImGui::Dummy(temp_size);
+}
+
+void AS_ImGui_Indent(float indent_w) {
+ ImGui::Indent(indent_w);
+}
+
+void AS_ImGui_Unindent(float indent_w) {
+ ImGui::Unindent(indent_w);
+}
+
+void AS_ImGui_BeginGroup() {
+ ImGui::BeginGroup();
+}
+
+void AS_ImGui_EndGroup() {
+ ImGui::EndGroup();
+}
+
+vec2 AS_ImGui_GetCursorPos() {
+ ImVec2 result = ImGui::GetCursorPos();
+ return vec2(result.x, result.y);
+}
+
+float AS_ImGui_GetCursorPosX() {
+ return ImGui::GetCursorPosX();
+}
+
+float AS_ImGui_GetCursorPosY() {
+ return ImGui::GetCursorPosY();
+}
+
+void AS_ImGui_SetCursorPos(const vec2& local_pos) {
+ ImVec2 temp_local_pos(local_pos[0], local_pos[1]);
+ ImGui::SetCursorPos(temp_local_pos);
+}
+
+void AS_ImGui_SetCursorPosX(float x) {
+ ImGui::SetCursorPosX(x);
+}
+
+void AS_ImGui_SetCursorPosY(float y) {
+ ImGui::SetCursorPosY(y);
+}
+
+vec2 AS_ImGui_GetCursorStartPos() {
+ ImVec2 result = ImGui::GetCursorStartPos();
+ return vec2(result.x, result.y);
+}
+
+vec2 AS_ImGui_GetCursorScreenPos() {
+ ImVec2 result = ImGui::GetCursorScreenPos();
+ return vec2(result.x, result.y);
+}
+
+void AS_ImGui_SetCursorScreenPos(const vec2& pos) {
+ ImVec2 temp_pos(pos[0], pos[1]);
+ ImGui::SetCursorScreenPos(temp_pos);
+}
+
+void AS_ImGui_AlignTextToFramePadding() {
+ return ImGui::AlignTextToFramePadding();
+}
+
+float AS_ImGui_GetTextLineHeight() {
+ return ImGui::GetTextLineHeight();
+}
+
+float AS_ImGui_GetTextLineHeightWithSpacing() {
+ return ImGui::GetTextLineHeightWithSpacing();
+}
+
+float AS_ImGui_GetFrameHeight() {
+ return ImGui::GetFrameHeight();
+}
+
+float AS_ImGui_GetFrameHeightWithSpacing() {
+ return ImGui::GetFrameHeightWithSpacing();
+}
+
+void AS_ImGui_Columns(int count, bool border) {
+ ImGui::Columns(count, NULL, border);
+}
+
+void AS_ImGui_Columns2(int count, const std::string& id, bool border) {
+ ImGui::Columns(count, id.c_str(), border);
+}
+
+void AS_ImGui_NextColumn() {
+ ImGui::NextColumn();
+}
+
+int AS_ImGui_GetColumnIndex() {
+ return ImGui::GetColumnIndex();
+}
+
+float AS_ImGui_GetColumnOffset(int column_index) {
+ return ImGui::GetColumnOffset(column_index);
+}
+
+void AS_ImGui_SetColumnOffset(int column_index, float offset_x) {
+ ImGui::SetColumnOffset(column_index, offset_x);
+}
+
+float AS_ImGui_GetColumnWidth(int column_index) {
+ return ImGui::GetColumnWidth(column_index);
+}
+
+void AS_ImGui_SetColumnWidth(int column_index, float width) {
+ return ImGui::SetColumnWidth(column_index, width);
+}
+
+int AS_ImGui_GetColumnsCount() {
+ return ImGui::GetColumnsCount();
+}
+
+void AS_ImGui_PushID(const std::string& str) {
+ ImGui::PushID(str.c_str());
+}
+
+void AS_ImGui_PushID2(const void* ptr_id) {
+ ImGui::PushID(ptr_id);
+}
+
+void AS_ImGui_PushID3(int int_id) {
+ ImGui::PushID(int_id);
+}
+
+void AS_ImGui_PopID() {
+ ImGui::PopID();
+}
+
+unsigned int AS_ImGui_GetID(const std::string& str_id) {
+ return ImGui::GetID(str_id.c_str());
+}
+
+unsigned int AS_ImGui_GetID2(const void* ptr_id) {
+ return ImGui::GetID(ptr_id);
+}
+
+void AS_ImGui_Text(const std::string& str) {
+ ImGui::Text("%s", str.c_str());
+}
+
+void AS_ImGui_TextColored(const vec4& col, const std::string& str) {
+ ImGui::TextColored(ImVec4(col[0], col[1], col[2], col[3]), "%s", str.c_str());
+}
+
+void AS_ImGui_TextDisabled(const std::string& label) {
+ ImGui::TextDisabled("%s", label.c_str());
+}
+
+void AS_ImGui_TextWrapped(const std::string& label) {
+ ImGui::TextWrapped("%s", label.c_str());
+}
+
+void AS_ImGui_LabelText(const std::string& str, const std::string& label) {
+ ImGui::LabelText(str.c_str(), "%s", label.c_str());
+}
+
+void AS_ImGui_Bullet() {
+ ImGui::Bullet();
+}
+
+void AS_ImGui_BulletText(const std::string& label) {
+ ImGui::BulletText("%s", label.c_str());
+}
+
+bool AS_ImGui_Button(const std::string& label, const vec2& size) {
+ ImVec2 size_temp(size[0], size[1]);
+ return ImGui::Button(label.c_str(), size_temp);
+}
+
+bool AS_ImGui_SmallButton(const std::string& label) {
+ return ImGui::SmallButton(label.c_str());
+}
+
+bool AS_ImGui_InvisibleButton(const std::string& str_id, const vec2& size) {
+ ImVec2 size_temp(size[0], size[1]);
+ return ImGui::InvisibleButton(str_id.c_str(), size_temp);
+}
+
+void AS_ImGui_Image(const TextureAssetRef& user_texture_ref, const vec2& size, const vec2& uv0, const vec2& uv1, const vec4& tint_color, const vec4& border_color) {
+ if(user_texture_ref.valid()) {
+ ImVec2 size_temp(size[0], size[1]);
+ ImVec2 uv0_temp(uv0[0], uv0[1]);
+ ImVec2 uv1_temp(uv1[0], uv1[1]);
+ ImVec4 tint_color_temp(tint_color[0], tint_color[1], tint_color[2], tint_color[3]);
+ ImVec4 border_color_temp(border_color[0], border_color[1], border_color[2], border_color[3]);
+ Textures::Instance()->EnsureInVRAM(user_texture_ref);
+ ImGui::Image(reinterpret_cast<ImTextureID>(Textures::Instance()->returnTexture(user_texture_ref)), size_temp, uv0_temp, uv1_temp, tint_color_temp, border_color_temp);
+ }
+}
+
+bool AS_ImGui_ImageButton(const TextureAssetRef& user_texture_ref, const vec2& size, const vec2& uv0, const vec2& uv1, int frame_padding, const vec4& background_color, const vec4& tint_color) {
+ if(user_texture_ref.valid()) {
+ ImVec2 size_temp(size[0], size[1]);
+ ImVec2 uv0_temp(uv0[0], uv0[1]);
+ ImVec2 uv1_temp(uv1[0], uv1[1]);
+ ImVec4 background_color_temp(background_color[0], background_color[1], background_color[2], background_color[3]);
+ ImVec4 tint_color_temp(tint_color[0], tint_color[1], tint_color[2], tint_color[3]);
+ Textures::Instance()->EnsureInVRAM(user_texture_ref);
+ return ImGui::ImageButton(reinterpret_cast<ImTextureID>(Textures::Instance()->returnTexture(user_texture_ref)), size_temp, uv0_temp, uv1_temp, frame_padding, background_color_temp, tint_color_temp);
+ }
+
+ return false;
+}
+
+bool AS_ImGui_Checkbox(const std::string& label, bool& value) {
+ return ImGui::Checkbox(label.c_str(), &value);
+}
+
+bool AS_ImGui_CheckboxFlags(const std::string& label, unsigned int& flags, unsigned int flags_value) {
+ return ImGui::CheckboxFlags(label.c_str(), &flags, flags_value);
+}
+
+bool AS_ImGui_RadioButton(const std::string& label, bool active) {
+ return ImGui::RadioButton(label.c_str(), active);
+}
+
+bool AS_ImGui_RadioButton2(const std::string& label, int& value, int v_button) {
+ return ImGui::RadioButton(label.c_str(), & value, v_button);
+}
+
+bool AS_ImGui_BeginCombo(const std::string& label, const std::string& preview_value, int flags) {
+ return ImGui::BeginCombo(label.c_str(), preview_value.c_str(), flags);
+}
+
+void AS_ImGui_EndCombo() {
+ ImGui::EndCombo();
+}
+
+bool AS_ImGui_Combo(const std::string& label, int& current_item, const CScriptArray& items, int height_in_items) {
+ const int items_count = items.GetSize();
+ std::vector<const char*> items_data;
+
+ for(int n = 0; n < items_count; ++n) {
+ items_data.push_back(reinterpret_cast<const std::string*>(items.At(n))->c_str());
+ }
+
+ return ImGui::Combo(label.c_str(), &current_item, &items_data[0], items_count, height_in_items);
+}
+
+bool AS_ImGui_Combo2(const std::string& label, int& current_item, const std::string& items_separated_by_zeros, int height_in_items) {
+ return ImGui::Combo(label.c_str(), &current_item, items_separated_by_zeros.c_str(), height_in_items);
+}
+
+bool AS_ImGui_ColorButton(const vec4& color, bool small_height, bool outline_border) {
+ ImVec4 temp_color(color[0], color[1], color[2], color[3]);
+ return ImGui::ColorButton("", temp_color, small_height);
+}
+
+bool AS_ImGui_ColorButton2(const std::string& desc, const vec4& color, ImGuiColorEditFlags flags, const vec2& size) {
+ ImVec4 temp_color(color[0], color[1], color[2], color[3]);
+ ImVec2 temp_size(size[0], size[1]);
+ return ImGui::ColorButton(desc.c_str(), temp_color, flags, temp_size);
+}
+
+bool AS_ImGui_ColorEdit3(const std::string& label, vec3& color) {
+ return ImGui::ColorEdit3(label.c_str(), color.entries);
+}
+
+bool AS_ImGui_ColorEdit4(const std::string& label, vec4& color, bool show_alpha) {
+ ImGuiColorEditFlags flags = 0x01;
+ if(!show_alpha) {
+ flags = ImGuiColorEditFlags_NoAlpha;
+ }
+ return ImGui::ColorEdit4(label.c_str(), color.entries, flags);
+}
+
+bool AS_ImGui_ColorEdit4_2(const std::string& label, vec4& color, ImGuiColorEditFlags flags) {
+ return ImGui::ColorEdit4(label.c_str(), color.entries, flags);
+}
+
+bool AS_ImGui_ColorPicker3(const std::string label, vec3& color, int flags) {
+ return ImGui::ColorPicker3(label.c_str(), color.entries, flags);
+}
+
+bool AS_ImGui_ColorPicker4(const std::string label, vec4& color, int flags) {
+ return ImGui::ColorPicker4(label.c_str(), color.entries, flags);
+}
+
+bool AS_ImGui_ColorPicker4_2(const std::string label, vec4& color, int flags, vec4 ref_color) {
+ return ImGui::ColorPicker4(label.c_str(), color.entries, flags, ref_color.entries);
+}
+
+void AS_ImGui_ColorEditMode(int mode) {
+ // From old imgui version
+ //ImGuiColorEditMode_UserSelect = -2;
+ //ImGuiColorEditMode_UserSelectShowButton = -1;
+ //ImGuiColorEditMode_RGB = 0
+ //ImGuiColorEditMode_HSV = 1;
+ //ImGuiColorEditMode_HEX = 2;
+
+ switch(mode) {
+ case 0:
+ ImGui::SetColorEditOptions(ImGuiColorEditFlags_RGB);
+ break;
+ case 1:
+ ImGui::SetColorEditOptions(ImGuiColorEditFlags_HSV);
+ break;
+ case 2:
+ ImGui::SetColorEditOptions(ImGuiColorEditFlags_HEX);
+ break;
+ default:
+ // UserSelect and UserSelectShowButton doesn't exist anymore, so default to RGB
+ ImGui::SetColorEditOptions(ImGuiColorEditFlags_RGB);
+ break;
+ }
+}
+
+bool AS_ImGui_DragFloat(const std::string& label, float& value, float v_speed, float v_min, float v_max, const std::string& display_format, float power) {
+ return ImGui::DragFloat(label.c_str(), &value, v_speed, v_min, v_max, display_format.c_str(), power);
+}
+
+bool AS_ImGui_DragFloat2_Vector(const std::string& label, vec2& value, float v_speed, float v_min, float v_max, const std::string& display_format, float power) {
+ return ImGui::DragFloat2(label.c_str(), value.entries, v_speed, v_min, v_max, display_format.c_str(), power);
+}
+
+bool AS_ImGui_DragFloat3_Vector(const std::string& label, vec3& value, float v_speed, float v_min, float v_max, const std::string& display_format, float power) {
+ return ImGui::DragFloat3(label.c_str(), value.entries, v_speed, v_min, v_max, display_format.c_str(), power);
+}
+
+bool AS_ImGui_DragFloat4_Vector(const std::string& label, vec4& value, float v_speed, float v_min, float v_max, const std::string& display_format, float power) {
+ return ImGui::DragFloat4(label.c_str(), value.entries, v_speed, v_min, v_max, display_format.c_str(), power);
+}
+
+bool AS_ImGui_DragFloatRange2_Vector(const std::string& label, float& v_current_min, float& v_current_max, float v_speed, float v_min, float v_max, const std::string& display_format) {
+ return ImGui::DragFloatRange2(label.c_str(), &v_current_min, &v_current_max, v_speed, v_min, v_max, display_format.c_str());
+}
+
+bool AS_ImGui_DragFloatRange2_Vector2(const std::string& label, float& v_current_min, float& v_current_max, float v_speed, float v_min, float v_max, const std::string& display_format, const std::string& display_format_max, float power) {
+ LOGW << "This version of \"ImGui_DragFloatRange2_Vector2\" is obsolete, use the version without the optional power parameter!" << std::endl;
+ return ImGui::DragFloatRange2(label.c_str(), &v_current_min, &v_current_max, v_speed, v_min, v_max, display_format.c_str(), display_format_max.c_str());
+}
+
+bool AS_ImGui_DragInt(const std::string& label, int& value, float v_speed, int v_min, int v_max, const std::string& display_format) {
+ return ImGui::DragInt(label.c_str(), &value, v_speed, v_min, v_max, display_format.c_str());
+}
+
+bool AS_ImGui_DragInt2_Vector(const std::string& label, ivec2& value, float v_speed, int v_min, int v_max, const std::string& display_format) {
+ return ImGui::DragInt2(label.c_str(), value.entries, v_speed, v_min, v_max, display_format.c_str());
+}
+
+bool AS_ImGui_DragInt3_Vector(const std::string& label, ivec3& value, float v_speed, int v_min, int v_max, const std::string& display_format) {
+ return ImGui::DragInt3(label.c_str(), value.entries, v_speed, v_min, v_max, display_format.c_str());
+}
+
+bool AS_ImGui_DragInt4_Vector(const std::string& label, ivec4& value, float v_speed, int v_min, int v_max, const std::string& display_format) {
+ return ImGui::DragInt4(label.c_str(), value.entries, v_speed, v_min, v_max, display_format.c_str());
+}
+
+bool AS_ImGui_DragIntRange2_Vector(const std::string& label, int& v_current_min, int& v_current_max, float v_speed, int v_min, int v_max, const std::string& display_format) {
+ return ImGui::DragIntRange2(label.c_str(), &v_current_min, &v_current_max, v_speed, v_min, v_max, display_format.c_str());
+}
+
+bool AS_ImGui_DragIntRange2_Vector2(const std::string& label, int& v_current_min, int& v_current_max, float v_speed, int v_min, int v_max, const std::string& display_format, const std::string& display_format_max) {
+ return ImGui::DragIntRange2(label.c_str(), &v_current_min, &v_current_max, v_speed, v_min, v_max, display_format.c_str(), display_format_max.c_str());
+}
+
+const int kBufSize = 262144;
+char buf[kBufSize];
+
+int imgui_text_input_CursorPos;
+int imgui_text_input_SelectionStart;
+int imgui_text_input_SelectionEnd;
+
+bool AS_ImGui_InputText(const std::string& label, int flags) {
+ return ImGui::InputText(label.c_str(), buf, kBufSize, flags);
+}
+
+std::string AS_ImGui_GetTextBuf() {
+ return std::string(buf);
+}
+
+void AS_ImGui_SetTextBuf(const std::string& contents) {
+ FormatString(buf, kBufSize, "%s", contents.c_str());
+}
+
+bool AS_ImGui_InputText2(const std::string& label, std::string& text_buffer, int buffer_size, int flags) {
+ text_buffer.reserve(buffer_size);
+ AS_ImGui_SetTextBuf(text_buffer);
+ bool ret_val = ImGui::InputText(label.c_str(), buf, buffer_size, flags);
+ if(ret_val) {
+ size_t new_size = strlen(buf);
+ if(text_buffer.size() < new_size) {
+ text_buffer.insert(text_buffer.size(), new_size - text_buffer.size(), 0);
+ } else if(text_buffer.size() > new_size) {
+ text_buffer.erase(text_buffer.end() - (text_buffer.size() - new_size), text_buffer.end());
+ }
+ strcpy(&text_buffer[0], buf);
+ }
+ return ret_val;
+}
+
+int ImGui_TextInputCallback(ImGuiInputTextCallbackData *data) {
+ imgui_text_input_CursorPos = data->CursorPos;
+ imgui_text_input_SelectionStart = data->SelectionStart;
+ imgui_text_input_SelectionEnd = data->SelectionEnd;
+ return 0;
+}
+
+bool AS_ImGui_InputTextMultiline(const std::string& label, const vec2& size, int flags) {
+ return ImGui::InputTextMultiline(label.c_str(), buf, kBufSize, ImVec2(size[0], size[1]), flags | ImGuiInputTextFlags_CallbackAlways, ImGui_TextInputCallback);
+}
+
+bool AS_ImGui_InputTextMultiline2(const std::string& label, std::string& text_buffer, int buffer_size, const vec2& size, int flags) {
+ text_buffer.reserve(buffer_size);
+ AS_ImGui_SetTextBuf(text_buffer);
+ bool ret_val = ImGui::InputTextMultiline(label.c_str(), buf, buffer_size, ImVec2(size[0], size[1]), flags | ImGuiInputTextFlags_CallbackAlways, ImGui_TextInputCallback);
+ if(ret_val) {
+ size_t new_size = strlen(buf);
+ if(text_buffer.size() < new_size) {
+ text_buffer.insert(text_buffer.size(), new_size - text_buffer.size(), 0);
+ } else if(text_buffer.size() > new_size) {
+ text_buffer.erase(text_buffer.end() - (text_buffer.size() - new_size), text_buffer.end());
+ }
+ strcpy(&text_buffer[0], buf);
+ }
+ return ret_val;
+}
+
+bool AS_ImGui_InputFloat(const std::string& label, float& value, float step, float step_fast, const char* format, int extra_flags) {
+ return ImGui::InputFloat(label.c_str(), &value, step, step_fast, format, extra_flags);
+}
+
+bool AS_ImGui_InputFloat2_Vector(const std::string& label, vec2& value, const char* format, int extra_flags) {
+ return ImGui::InputFloat2(label.c_str(), value.entries, format, extra_flags);
+}
+
+bool AS_ImGui_InputFloat3_Vector(const std::string& label, vec3& value, const char* format, int extra_flags) {
+ return ImGui::InputFloat3(label.c_str(), value.entries, format, extra_flags);
+}
+
+bool AS_ImGui_InputFloat4_Vector(const std::string& label, vec4& value, const char* format, int extra_flags) {
+ return ImGui::InputFloat4(label.c_str(), value.entries, format, extra_flags);
+}
+
+bool AS_ImGui_InputInt(const std::string& label, int& value, int step, int step_fast, int extra_flags) {
+ return ImGui::InputInt(label.c_str(), &value, step, step_fast, extra_flags);
+}
+
+bool AS_ImGui_SliderFloat(const std::string& label, float& v, float v_min, float v_max, const std::string& display_format, float power) {
+ return ImGui::SliderFloat(label.c_str(), &v, v_min, v_max, display_format.c_str(), power);
+}
+
+bool AS_ImGui_SliderFloat2(const std::string& label, vec2& v, float v_min, float v_max, const std::string& display_format, float power) {
+ return ImGui::SliderFloat2(label.c_str(), v.entries, v_min, v_max, display_format.c_str(), power);
+}
+
+bool AS_ImGui_SliderFloat3(const std::string& label, vec3& v, float v_min, float v_max, const std::string& display_format, float power) {
+ return ImGui::SliderFloat3(label.c_str(), v.entries, v_min, v_max, display_format.c_str(), power);
+}
+
+bool AS_ImGui_SliderFloat4(const std::string& label, vec4& v, float v_min, float v_max, const std::string& display_format, float power) {
+ return ImGui::SliderFloat4(label.c_str(), v.entries, v_min, v_max, display_format.c_str(), power);
+}
+
+bool AS_ImGui_SliderAngle(const std::string& label, float& v_rad, float v_degrees_min, float v_degrees_max) {
+ return ImGui::SliderAngle(label.c_str(), &v_rad, v_degrees_min, v_degrees_max);
+}
+
+bool AS_ImGui_SliderInt(const std::string& label, int& v, int v_min, int v_max, const std::string& display_format) {
+ return ImGui::SliderInt(label.c_str(), &v, v_min, v_max, display_format.c_str());
+}
+
+bool AS_ImGui_SliderInt2(const std::string& label, ivec2& v, int v_min, int v_max, const std::string& display_format) {
+ return ImGui::SliderInt2(label.c_str(), v.entries, v_min, v_max, display_format.c_str());
+}
+
+bool AS_ImGui_SliderInt3(const std::string& label, ivec3& v, int v_min, int v_max, const std::string& display_format) {
+ return ImGui::SliderInt3(label.c_str(), v.entries, v_min, v_max, display_format.c_str());
+}
+
+bool AS_ImGui_SliderInt4(const std::string& label, ivec4& v, int v_min, int v_max, const std::string& display_format) {
+ return ImGui::SliderInt4(label.c_str(), v.entries, v_min, v_max, display_format.c_str());
+}
+
+bool AS_ImGui_TreeNode(const std::string& label) {
+ return ImGui::TreeNode(label.c_str());
+}
+
+bool AS_ImGui_TreeNode2(const std::string& str_id, const std::string& label) {
+ return ImGui::TreeNode(str_id.c_str(), "%s", label.c_str());
+}
+
+bool AS_ImGui_TreeNode3(const void* ref, int typeId, const std::string& label) {
+ return ImGui::TreeNode(ref, "%s", label.c_str());
+}
+
+bool AS_ImGui_TreeNodeEx(const std::string& label, int flags) {
+ return ImGui::TreeNodeEx(label.c_str(), flags);
+}
+
+bool AS_ImGui_TreeNodeEx2(const std::string& str_id, int flags, const std::string& label) {
+ return ImGui::TreeNodeEx(str_id.c_str(), flags, "%s", label.c_str());
+}
+
+bool AS_ImGui_TreeNodeEx3(const void* ref, int flags, const std::string& label) {
+ return ImGui::TreeNodeEx(ref, flags, "%s", label.c_str());
+}
+
+void AS_ImGui_TreePush() {
+ ImGui::TreePush(static_cast<const void*>(NULL));
+}
+
+void AS_ImGui_TreePush2(const std::string& str_id) {
+ ImGui::TreePush(str_id.c_str());
+}
+
+void AS_ImGui_TreePush3(const void *ref, int typeId) {
+ ImGui::TreePush(ref);
+}
+
+void AS_ImGui_TreePop() {
+ ImGui::TreePop();
+}
+
+void AS_ImGui_TreeAdvanceToLabelPos() {
+ ImGui::TreeAdvanceToLabelPos();
+}
+
+float AS_ImGui_GetTreeNodeToLabelSpacing() {
+ return ImGui::GetTreeNodeToLabelSpacing();
+}
+
+void AS_ImGui_SetNextTreeNodeOpen(bool is_open, int cond) {
+ ImGui::SetNextTreeNodeOpen(is_open, cond);
+}
+
+bool AS_ImGui_CollapsingHeader(const std::string& label, int flags) {
+ return ImGui::CollapsingHeader(label.c_str(), flags);
+}
+
+bool AS_ImGui_CollapsingHeader2(const std::string& label, bool& is_open, int flags) {
+ return ImGui::CollapsingHeader(label.c_str(), &is_open, flags);
+}
+
+bool AS_ImGui_Selectable(const std::string& name, bool selected, int flags, const vec2& size) {
+ ImVec2 temp_size(size[0], size[1]);
+ return ImGui::Selectable(name.c_str(), selected, flags, temp_size);
+}
+
+bool AS_ImGui_SelectableToggle(const std::string& name, bool& selected, int flags, const vec2& size) {
+ ImVec2 temp_size(size[0], size[1]);
+ return ImGui::Selectable(name.c_str(), &selected, flags, temp_size);
+}
+
+bool AS_ImGui_ListBox(const std::string& label, int& current_item, const CScriptArray& items, int height_in_items) {
+ const int items_count = items.GetSize();
+ std::vector<const char*> items_data;
+
+ for(int n = 0; n < items_count; ++n) {
+ items_data.push_back(reinterpret_cast<const std::string*>(items.At(n))->c_str());
+ }
+
+ return ImGui::ListBox(label.c_str(), &current_item, items_data.data(), items_count, height_in_items);
+}
+
+bool AS_ImGui_ListBoxHeader(const std::string& label) {
+ ImVec2 size_temp(0, 0);
+ return ImGui::BeginListBox(label.c_str(), size_temp);
+}
+
+bool AS_ImGui_ListBoxHeader2(const std::string& label, const vec2& size) {
+ ImVec2 size_temp(size[0], size[1]);
+ return ImGui::BeginListBox(label.c_str(), size_temp);
+}
+
+bool AS_ImGui_ListBoxHeader3(const std::string& label, int items_count, int height_in_items) {
+ return ImGui::BeginListBox(label.c_str());// , items_count, height_in_items);
+}
+
+void AS_ImGui_ListBoxFooter() {
+ ImGui::ListBoxFooter();
+}
+
+void AS_ImGui_SetTooltip(const std::string& text) {
+ ImGui::SetTooltip("%s", text.c_str());
+}
+
+void AS_ImGui_BeginTooltip() {
+ ImGui::BeginTooltip();
+}
+
+void AS_ImGui_EndTooltip() {
+ ImGui::EndTooltip();
+}
+
+bool AS_ImGui_BeginMenuBar() {
+ return ImGui::BeginMenuBar();
+}
+
+void AS_ImGui_EndMenuBar() {
+ ImGui::EndMenuBar();
+}
+
+bool AS_ImGui_BeginMenu(const std::string& label, bool enabled) {
+ return ImGui::BeginMenu(label.c_str(), enabled);
+}
+
+void AS_ImGui_EndMenu() {
+ ImGui::EndMenu();
+}
+
+bool AS_ImGui_MenuItem(const std::string& label, bool selected, bool enabled) {
+ return ImGui::MenuItem(label.c_str(), NULL, &selected, enabled);
+}
+
+void AS_ImGui_OpenPopup(const std::string& str_id) {
+ ImGui::OpenPopup(str_id.c_str());
+}
+
+void AS_ImGui_OpenPopupOnItemClick(const std::string& label, int mouse_button) {
+ ImGui::OpenPopupOnItemClick(label.c_str(), mouse_button);
+}
+
+bool AS_ImGui_BeginPopup(const std::string& str_id) {
+ return ImGui::BeginPopup(str_id.c_str());
+}
+
+bool AS_ImGui_BeginPopupModal(const std::string& name, int flags) {
+ return ImGui::BeginPopupModal(name.c_str(), NULL, flags);
+}
+
+bool AS_ImGui_BeginPopupModal2(const std::string& name, bool& open, int flags) {
+ return ImGui::BeginPopupModal(name.c_str(), &open, flags);
+}
+
+bool AS_ImGui_BeginPopupContextItem(const std::string& str_id, int mouse_button) {
+ return ImGui::BeginPopupContextItem(str_id.c_str(), mouse_button);
+}
+
+bool AS_ImGui_BeginPopupContextWindow(bool also_over_items, int mouse_button) {
+ return ImGui::BeginPopupContextWindow(NULL, also_over_items, mouse_button);
+}
+
+bool AS_ImGui_BeginPopupContextWindow2(bool also_over_items, const std::string& str_id, int mouse_button) {
+ return ImGui::BeginPopupContextWindow(str_id.c_str(), also_over_items, mouse_button);
+}
+
+bool AS_ImGui_BeginPopupContextVoid(int mouse_button) {
+ return ImGui::BeginPopupContextVoid(NULL, mouse_button);
+}
+
+bool AS_ImGui_BeginPopupContextVoid2(const std::string& str_id, int mouse_button) {
+ return ImGui::BeginPopupContextVoid(str_id.c_str(), mouse_button);
+}
+
+void AS_ImGui_EndPopup() {
+ ImGui::EndPopup();
+}
+
+bool AS_ImGui_IsPopupOpen(const std::string& str_id) {
+ return ImGui::IsPopupOpen(str_id.c_str());
+}
+
+void AS_ImGui_CloseCurrentPopup() {
+ ImGui::CloseCurrentPopup();
+}
+
+bool AS_ImGui_IsItemHovered() {
+ return ImGui::IsItemHovered();
+}
+
+bool AS_ImGui_IsItemHoveredRect() {
+ return ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly);
+}
+
+bool AS_ImGui_IsItemActive() {
+ return ImGui::IsItemActive();
+}
+
+bool AS_ImGui_IsItemClicked(int mouse_button) {
+ return ImGui::IsItemClicked(mouse_button);
+}
+
+bool AS_ImGui_IsItemVisible() {
+ return ImGui::IsItemVisible();
+}
+
+bool AS_ImGui_IsAnyItemHovered() {
+ return ImGui::IsAnyItemHovered();
+}
+
+bool AS_ImGui_IsAnyItemActive() {
+ return ImGui::IsAnyItemActive();
+}
+
+vec2 AS_ImGui_GetItemRectMin() {
+ ImVec2 result = ImGui::GetItemRectMin();
+ return vec2(result.x, result.y);
+}
+
+vec2 AS_ImGui_GetItemRectMax() {
+ ImVec2 result = ImGui::GetItemRectMax();
+ return vec2(result.x, result.y);
+}
+
+vec2 AS_ImGui_GetItemRectSize() {
+ ImVec2 result = ImGui::GetItemRectSize();
+ return vec2(result.x, result.y);
+}
+
+void AS_ImGui_SetItemAllowOverlap() {
+ ImGui::SetItemAllowOverlap();
+}
+
+bool AS_ImGui_IsWindowHovered() {
+ return ImGui::IsWindowHovered();
+}
+
+bool AS_ImGui_IsWindowFocused() {
+ return ImGui::IsWindowFocused();
+}
+
+bool AS_ImGui_IsRootWindowFocused() {
+ return ImGui::IsWindowFocused(ImGuiFocusedFlags_RootWindow);
+}
+
+bool AS_ImGui_IsRootWindowOrAnyChildFocused() {
+ return ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows);
+}
+
+bool AS_ImGui_IsRootWindowOrAnyChildHovered() {
+ return ImGui::IsWindowHovered(ImGuiFocusedFlags_RootAndChildWindows);
+}
+
+bool AS_ImGui_IsRectVisible(const vec2& size) {
+ ImVec2 temp_size(size[0], size[1]);
+ return ImGui::IsRectVisible(temp_size);
+}
+
+bool AS_ImGui_IsRectVisible2(const vec2& rect_min, const vec2& rect_max) {
+ ImVec2 temp_rect_min(rect_min[0], rect_min[1]);
+ ImVec2 temp_rect_max(rect_max[0], rect_max[1]);
+ return ImGui::IsRectVisible(temp_rect_min, temp_rect_max);
+}
+
+bool AS_ImGui_WantCaptureMouse() {
+ return ImGui::GetIO().WantCaptureMouse;
+}
+
+bool AS_ImGui_IsPosHoveringAnyWindow(const vec2& pos) {
+ LOGW_ONCE("Don\'t use IsPosHoveringAnyWindow, use WantCaptureMouse instead. Doesn\'t work as expected? Send in a ticket");
+ return AS_ImGui_WantCaptureMouse();
+}
+
+float AS_ImGui_GetTime() {
+ return (float) ImGui::GetTime();
+}
+
+int AS_ImGui_GetFrameCount() {
+ return ImGui::GetFrameCount();
+}
+
+std::string AS_ImGui_GetStyleColorName(int idx) {
+ return std::string(ImGui::GetStyleColorName(idx)); // Note: Original returned const char*, not a copy
+}
+//
+//vec2 AS_ImGui_CalcItemRectClosestPoint(const vec2& pos, bool on_edge, float outward) {
+// ImVec2 temp_pos(pos[0], pos[1]);
+// ImVec2 result = ImGui::CalcItemRectClosestPoint(temp_pos, on_edge, outward);
+// return vec2(result.x, result.y);
+//}
+
+vec2 AS_ImGui_CalcTextSize(const std::string& text, bool hide_text_after_double_hash, float wrap_width) {
+ ImVec2 result = ImGui::CalcTextSize(text.c_str(), NULL, hide_text_after_double_hash, wrap_width);
+ return vec2(result.x, result.y);
+}
+
+void AS_ImGui_CalcListClipping(int items_count, float items_height, int& out_items_display_start, int& out_items_display_end) {
+ return ImGui::CalcListClipping(items_count, items_height, &out_items_display_start, &out_items_display_end);
+}
+
+bool AS_ImGui_BeginChildFrame(unsigned int id, const vec2& size, int extra_flags) {
+ ImVec2 temp_size(size[0], size[1]);
+ return ImGui::BeginChildFrame(id, temp_size, extra_flags);
+}
+
+void AS_ImGui_EndChildFrame() {
+ return ImGui::EndChildFrame();
+}
+
+vec4 AS_ImGui_ColorConvertU32ToFloat4(unsigned int value) {
+ ImVec4 result = ImGui::ColorConvertU32ToFloat4(value);
+ return vec4(result.x, result.y, result.z, result.w);
+}
+
+unsigned int AS_ImGui_ColorConvertFloat4ToU32(const vec4& value) {
+ ImVec4 temp_value(value[0], value[1], value[2], value[3]);
+ return ImGui::ColorConvertFloat4ToU32(temp_value);
+}
+
+void AS_ImGui_ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v) {
+ ImGui::ColorConvertRGBtoHSV(r, g, b, out_h, out_s, out_v);
+}
+
+void AS_ImGui_ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b) {
+ ImGui::ColorConvertHSVtoRGB(h, s, v, out_r, out_g, out_b);
+}
+
+int AS_ImGui_GetKeyIndex(int imgui_key) {
+ return ImGui::GetKeyIndex(imgui_key);
+}
+
+bool AS_ImGui_IsKeyDown(int key_index) {
+ return ImGui::IsKeyDown(key_index);
+}
+
+bool AS_ImGui_IsKeyPressed(int key_index, bool repeat) {
+ return ImGui::IsKeyPressed(key_index, repeat);
+}
+
+bool AS_ImGui_IsKeyReleased(int key_index) {
+ return ImGui::IsKeyReleased(key_index);
+}
+
+bool AS_ImGui_IsMouseDown(int button) {
+ return ImGui::IsMouseDown(button);
+}
+
+bool AS_ImGui_IsMouseClicked(int button, bool repeat) {
+ return ImGui::IsMouseClicked(button, repeat);
+}
+
+bool AS_ImGui_IsMouseDoubleClicked(int button) {
+ return ImGui::IsMouseDoubleClicked(button);
+}
+
+bool AS_ImGui_IsMouseReleased(int button) {
+ return ImGui::IsMouseReleased(button);
+}
+
+bool AS_ImGui_IsMouseHoveringWindow() {
+ return ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
+}
+
+bool AS_ImGui_IsMouseHoveringAnyWindow() {
+ return ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow);
+}
+
+bool AS_ImGui_IsMouseHoveringRect(const vec2& r_min, const vec2& r_max, bool clip) {
+ ImVec2 temp_r_min(r_min[0], r_min[1]);
+ ImVec2 temp_r_max(r_max[0], r_max[1]);
+ return ImGui::IsMouseHoveringRect(temp_r_min, temp_r_max, clip);
+}
+
+bool AS_ImGui_IsMouseDragging(int button, float lock_threshold) {
+ return ImGui::IsMouseDragging(button, lock_threshold);
+}
+
+vec2 AS_ImGui_GetMousePos() {
+ ImVec2 result = ImGui::GetMousePos();
+ return vec2(result.x, result.y);
+}
+
+vec2 AS_ImGui_GetMousePosOnOpeningCurrentPopup() {
+ ImVec2 result = ImGui::GetMousePosOnOpeningCurrentPopup();
+ return vec2(result.x, result.y);
+}
+
+vec2 AS_ImGui_GetMouseDragDelta(int button, float lock_threshold) {
+ ImVec2 result = ImGui::GetMouseDragDelta(button, lock_threshold);
+ return vec2(result.x, result.y);
+}
+
+void AS_ImGui_ResetMouseDragDelta(int button) {
+ ImGui::ResetMouseDragDelta(button);
+}
+
+int AS_ImGui_GetMouseCursor() {
+ return ImGui::GetMouseCursor();
+}
+
+void AS_ImGui_SetMouseCursor(int type) {
+ ImGui::SetMouseCursor(type);
+}
+
+void AS_ImGui_CaptureKeyboardFromApp(bool capture) {
+ ImGui::CaptureKeyboardFromApp(capture);
+}
+
+void AS_ImGui_CaptureMouseFromApp(bool capture) {
+ ImGui::CaptureMouseFromApp(capture);
+}
+
+std::string AS_ImGui_GetClipboardText() {
+ return std::string(ImGui::GetClipboardText());
+}
+
+void AS_ImGui_SetClipboardText(const std::string& text) {
+ ImGui::SetClipboardText(text.c_str());
+}
+
+bool ImGui_TryGetWindowDrawList(ImDrawList** draw_list) {
+ bool result = false;
+ ImGuiWindow* window = ImGui::GetCurrentWindow();
+
+ if(window && window->DrawList) {
+ *draw_list = window->DrawList;
+ result = true;
+ }
+
+ return result;
+}
+
+void AS_ImDrawList_AddLine(const vec2& a, const vec2& b, uint32_t col, float thickness) {
+ ImDrawList* draw_list;
+
+ if(ImGui_TryGetWindowDrawList(&draw_list)) {
+ draw_list->AddLine(ImVec2(a[0], a[1]), ImVec2(b[0], b[1]), col, thickness);
+ }
+}
+
+void AS_ImDrawList_AddRect(const vec2& a, const vec2& b, uint32_t col, float rounding, int rounding_corners_flags, float thickness) {
+ ImDrawList* draw_list;
+
+ if(ImGui_TryGetWindowDrawList(&draw_list)) {
+ draw_list->AddRect(ImVec2(a[0], a[1]), ImVec2(b[0], b[1]), col, rounding, rounding_corners_flags, thickness);
+ }
+}
+
+void AS_ImDrawList_AddRectFilled(const vec2& a, const vec2& b, uint32_t col, float rounding, int rounding_corners_flags) {
+ ImDrawList* draw_list;
+
+ if(ImGui_TryGetWindowDrawList(&draw_list)) {
+ draw_list->AddRectFilled(ImVec2(a[0], a[1]), ImVec2(b[0], b[1]), col, rounding, rounding_corners_flags);
+ }
+}
+
+void AS_ImDrawList_AddRectFilledMultiColor(const vec2& a, const vec2& b, uint32_t col_upr_left, uint32_t col_upr_right, uint32_t col_bot_right, uint32_t col_bot_left) {
+ ImDrawList* draw_list;
+
+ if(ImGui_TryGetWindowDrawList(&draw_list)) {
+ draw_list->AddRectFilledMultiColor(ImVec2(a[0], a[1]), ImVec2(b[0], b[1]), col_upr_left, col_upr_right, col_bot_right, col_bot_left);
+ }
+}
+
+void AS_ImDrawList_AddQuad(const vec2& a, const vec2& b, const vec2& c, const vec2& d, uint32_t col, float thickness) {
+ ImDrawList* draw_list;
+
+ if(ImGui_TryGetWindowDrawList(&draw_list)) {
+ draw_list->AddQuad(ImVec2(a[0], a[1]), ImVec2(b[0], b[1]), ImVec2(c[0], c[1]), ImVec2(d[0], d[1]), col, thickness);
+ }
+}
+
+void AS_ImDrawList_AddQuadFilled(const vec2& a, const vec2& b, const vec2& c, const vec2& d, uint32_t col) {
+ ImDrawList* draw_list;
+
+ if(ImGui_TryGetWindowDrawList(&draw_list)) {
+ draw_list->AddQuadFilled(ImVec2(a[0], a[1]), ImVec2(b[0], b[1]), ImVec2(c[0], c[1]), ImVec2(d[0], d[1]), col);
+ }
+}
+
+void AS_ImDrawList_AddTriangle(const vec2& a, const vec2& b, const vec2& c, uint32_t col, float thickness) {
+ ImDrawList* draw_list;
+
+ if(ImGui_TryGetWindowDrawList(&draw_list)) {
+ draw_list->AddTriangle(ImVec2(a[0], a[1]), ImVec2(b[0], b[1]), ImVec2(c[0], c[1]), col, thickness);
+ }
+}
+
+void AS_ImDrawList_AddTriangleFilled(const vec2& a, const vec2& b, const vec2& c, uint32_t col) {
+ ImDrawList* draw_list;
+
+ if(ImGui_TryGetWindowDrawList(&draw_list)) {
+ draw_list->AddTriangleFilled(ImVec2(a[0], a[1]), ImVec2(b[0], b[1]), ImVec2(c[0], c[1]), col);
+ }
+}
+
+void AS_ImDrawList_AddCircle(const vec2& center, float radius, uint32_t col, int num_segments, float thickness) {
+ ImDrawList* draw_list;
+
+ if(ImGui_TryGetWindowDrawList(&draw_list)) {
+ draw_list->AddCircle(ImVec2(center[0], center[1]), radius, col, num_segments, thickness);
+ }
+}
+
+void AS_ImDrawList_AddCircleFilled(const vec2& center, float radius, uint32_t col, int num_segments) {
+ ImDrawList* draw_list;
+
+ if(ImGui_TryGetWindowDrawList(&draw_list)) {
+ draw_list->AddCircleFilled(ImVec2(center[0], center[1]), radius, col, num_segments);
+ }
+}
+
+void AS_ImDrawList_AddText(const vec2& pos, uint32_t col, const std::string& text) {
+ ImDrawList* draw_list;
+
+ if(ImGui_TryGetWindowDrawList(&draw_list)) {
+ draw_list->AddText(ImVec2(pos[0], pos[1]), col, text.c_str());
+ }
+}
+
+void AS_ImDrawList_AddImage(const TextureAssetRef& user_texture_ref, const vec2& a, const vec2& b, const vec2& uv_a, const vec2& uv_b, uint32_t tint_color) {
+ ImDrawList* draw_list;
+
+ if(user_texture_ref.valid() && ImGui_TryGetWindowDrawList(&draw_list)) {
+ Textures::Instance()->EnsureInVRAM(user_texture_ref);
+ draw_list->AddImage(
+ reinterpret_cast<ImTextureID>(Textures::Instance()->returnTexture(user_texture_ref)),
+ ImVec2(a[0], a[1]),
+ ImVec2(b[0], b[1]),
+ ImVec2(uv_a[0], uv_a[1]),
+ ImVec2(uv_b[0], uv_b[1]),
+ tint_color);
+ }
+}
+
+void AS_ImDrawList_AddImageQuad(const TextureAssetRef& user_texture_ref, const vec2& a, const vec2& b, const vec2& c, const vec2& d, const vec2& uv_a, const vec2& uv_b, const vec2& uv_c, const vec2& uv_d, uint32_t tint_color) {
+ ImDrawList* draw_list;
+
+ if(user_texture_ref.valid() && ImGui_TryGetWindowDrawList(&draw_list)) {
+ Textures::Instance()->EnsureInVRAM(user_texture_ref);
+ draw_list->AddImageQuad(
+ reinterpret_cast<ImTextureID>(Textures::Instance()->returnTexture(user_texture_ref)),
+ ImVec2(a[0], a[1]),
+ ImVec2(b[0], b[1]),
+ ImVec2(c[0], c[1]),
+ ImVec2(d[0], d[1]),
+ ImVec2(uv_a[0], uv_a[1]),
+ ImVec2(uv_b[0], uv_b[1]),
+ ImVec2(uv_c[0], uv_c[1]),
+ ImVec2(uv_d[0], uv_d[1]),
+ tint_color);
+ }
+}
+
+void AS_ImDrawList_AddImageRounded(const TextureAssetRef& user_texture_ref, const vec2& a, const vec2& b, const vec2& uv_a, const vec2& uv_b, uint32_t tint_color, float rounding, int rounding_corners) {
+ ImDrawList* draw_list;
+
+ if(user_texture_ref.valid() && ImGui_TryGetWindowDrawList(&draw_list)) {
+ Textures::Instance()->EnsureInVRAM(user_texture_ref);
+ draw_list->AddImageRounded(
+ reinterpret_cast<ImTextureID>(Textures::Instance()->returnTexture(user_texture_ref)),
+ ImVec2(a[0], a[1]),
+ ImVec2(b[0], b[1]),
+ ImVec2(uv_a[0], uv_a[1]),
+ ImVec2(uv_b[0], uv_b[1]),
+ tint_color,
+ rounding,
+ rounding_corners);
+ }
+}
+
+void AS_ImDrawList_AddBezierCurve(const vec2& pos0, const vec2& cp0, const vec2& cp1, const vec2& pos1, uint32_t col, float thickness, int num_segments) {
+ ImDrawList* draw_list;
+
+ if(ImGui_TryGetWindowDrawList(&draw_list)) {
+ draw_list->AddBezierCurve(ImVec2(pos0[0], pos0[1]), ImVec2(cp0[0], cp0[1]), ImVec2(cp1[0], cp1[1]), ImVec2(pos1[0], pos1[1]), col, thickness, num_segments);
+ }
+}
+
+void AS_ImDrawList_PathClear() {
+ ImDrawList* draw_list;
+
+ if(ImGui_TryGetWindowDrawList(&draw_list)) {
+ draw_list->PathClear();
+ }
+}
+
+void AS_ImDrawList_PathLineTo(const vec2& pos) {
+ ImDrawList* draw_list;
+
+ if(ImGui_TryGetWindowDrawList(&draw_list)) {
+ draw_list->PathLineTo(ImVec2(pos[0], pos[1]));
+ }
+}
+
+void AS_ImDrawList_PathLineToMergeDuplicate(const vec2& pos) {
+ ImDrawList* draw_list;
+
+ if(ImGui_TryGetWindowDrawList(&draw_list)) {
+ draw_list->PathLineToMergeDuplicate(ImVec2(pos[0], pos[1]));
+ }
+}
+
+void AS_ImDrawList_PathFillConvex(uint32_t col) {
+ ImDrawList* draw_list;
+
+ if(ImGui_TryGetWindowDrawList(&draw_list)) {
+ draw_list->PathFillConvex(col);
+ }
+}
+
+void AS_ImDrawList_PathStroke(uint32_t col, bool closed, float thickness) {
+ ImDrawList* draw_list;
+
+ if(ImGui_TryGetWindowDrawList(&draw_list)) {
+ draw_list->PathStroke(col, closed, thickness);
+ }
+}
+
+void AS_ImDrawList_PathArcTo(const vec2& center, float radius, float a_min, float a_max, int num_segments) {
+ ImDrawList* draw_list;
+
+ if(ImGui_TryGetWindowDrawList(&draw_list)) {
+ draw_list->PathArcTo(ImVec2(center[0], center[1]), radius, a_min, a_max, num_segments);
+ }
+}
+
+void AS_ImDrawList_PathArcToFast(const vec2& center, float radius, int a_min_of_12, int a_max_of_12) {
+ ImDrawList* draw_list;
+
+ if(ImGui_TryGetWindowDrawList(&draw_list)) {
+ draw_list->PathArcToFast(ImVec2(center[0], center[1]), radius, a_min_of_12, a_max_of_12);
+ }
+}
+
+void AS_ImDrawList_PathBezierCurveTo(const vec2& p1, const vec2& p2, const vec2& p3, int num_segments) {
+ ImDrawList* draw_list;
+
+ if(ImGui_TryGetWindowDrawList(&draw_list)) {
+ draw_list->PathBezierCurveTo(ImVec2(p1[0], p1[1]), ImVec2(p2[0], p2[1]), ImVec2(p3[0], p3[1]), num_segments);
+ }
+}
+
+void AS_ImDrawList_PathRect(const vec2& rect_min, const vec2& rect_max, float rounding, int rounding_corners_flags) {
+ ImDrawList* draw_list;
+
+ if(ImGui_TryGetWindowDrawList(&draw_list)) {
+ draw_list->PathRect(ImVec2(rect_min[0], rect_min[1]), ImVec2(rect_max[0], rect_max[1]), rounding, rounding_corners_flags);
+ }
+}
+
+void AS_ImGui_DrawSettings() {
+ DrawSettingsImGui(the_scenegraph, IMGST_WINDOW);
+}
+
+std::string GetUserPickedWritePath(const std::string& suffix, const std::string& default_path) {
+ const int BUF_SIZE = 512;
+ char buf[BUF_SIZE];
+ Dialog::DialogErr err = Dialog::writeFile(suffix.c_str(), 1, default_path.c_str(), buf, BUF_SIZE);
+ if(!err){
+ return std::string(buf);
+ } else {
+ return std::string("");
+ }
+}
+
+std::string GetUserPickedReadPath(const std::string& suffix, const std::string& default_path) {
+ Input::Instance()->ignore_mouse_frame = true;
+ const int BUF_SIZE = 512;
+ char buffer[BUF_SIZE];
+ Dialog::DialogErr err = Dialog::readFile(suffix.c_str(), 1, default_path.c_str(), buffer, BUF_SIZE);
+
+ if( err != Dialog::NO_ERR ) {
+ LOGE << Dialog::DialogErrString( err ) << std::endl;
+ return std::string("");
+ } else {
+ std::string shortened = FindShortestPath(std::string(buffer));
+ return shortened;
+ }
+}
+
+std::string AS_FindShortestPath(const std::string& path) {
+ return FindShortestPath2(path).GetOriginalPathStr();
+}
+
+std::string AS_FindFilePath(const std::string& path) {
+ return FindFilePath(path).GetFullPathStr();
+}
+
+// Register Dear ImGui functions to be accessible to an angelscript context
+void AttachIMGUI( ASContext *context ) {
+ // TextureLoadFlags - This is a phoenix engine thing, not a Dear ImGui thing, but is necessary in order to feed images to Dear ImGui
+ context->RegisterEnum("TextureLoadFlags");
+ context->RegisterEnumValue("TextureLoadFlags", "TextureLoadFlags_SRGB", PX_SRGB);
+ context->RegisterEnumValue("TextureLoadFlags", "TextureLoadFlags_NoMipmap", PX_NOMIPMAP);
+ context->RegisterEnumValue("TextureLoadFlags", "TextureLoadFlags_NoConvert", PX_NOCONVERT);
+ context->RegisterEnumValue("TextureLoadFlags", "TextureLoadFlags_NoLiveUpdate", PX_NOLIVEUPDATE);
+ context->RegisterEnumValue("TextureLoadFlags", "TextureLoadFlags_NoReduce", PX_NOREDUCE);
+
+ // TextureAssetRef - This is a phoenix engine thing, not a Dear ImGui thing, but is necessary in order to feed images to Dear ImGui
+ context->RegisterObjectType("TextureAssetRef", sizeof(TextureAssetRef), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK);
+ context->RegisterObjectBehaviour("TextureAssetRef", asBEHAVE_CONSTRUCT, "void TextureAssetRef()", asFUNCTION(AS_TextureAssetRefConstructor), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectBehaviour("TextureAssetRef", asBEHAVE_CONSTRUCT, "void TextureAssetRef(const TextureAssetRef &in other)", asFUNCTION(AS_TextureAssetRefCopyConstructor), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectBehaviour("TextureAssetRef", asBEHAVE_DESTRUCT, "void TextureAssetRef()", asFUNCTION(AS_TextureAssetRefDestructor), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("TextureAssetRef", "bool IsValid()", asMETHOD(TextureAssetRef, valid), asCALL_THISCALL);
+ context->RegisterObjectMethod("TextureAssetRef", "void Clear()", asMETHOD(TextureAssetRef, clear), asCALL_THISCALL);
+ context->RegisterObjectMethod("TextureAssetRef", "TextureAssetRef& opAssign(const TextureAssetRef &in other)", asFUNCTION(AS_TextureAssetRefOpAssign), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("TextureAssetRef", "TextureAssetRef& opEquals(const TextureAssetRef &in other)", asFUNCTION(AS_TextureAssetRefOpEquals), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("TextureAssetRef", "TextureAssetRef& opCmp(const TextureAssetRef &in other)", asFUNCTION(AS_TextureAssetRefOpCmp), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterGlobalFunction("TextureAssetRef LoadTexture(const string &in name, uint32 texture_load_flags = 0)", asFUNCTION(AS_LoadTexture), asCALL_CDECL);
+
+ // Window
+ context->RegisterGlobalFunction("bool ImGui_Begin(const string &in name, int flags = 0)", asFUNCTION(AS_ImGui_Begin), asCALL_CDECL, "bool ImGui_Begin(const string &in name, ImGuiWindowFlags flags = 0)");
+ context->RegisterGlobalFunction("bool ImGui_Begin(const string &in name, bool &inout is_open, int flags = 0)", asFUNCTION(AS_ImGui_Begin2), asCALL_CDECL, "bool ImGui_Begin(const string &in name, bool &inout is_open, ImGuiWindowFlags flags = 0)");
+ context->RegisterGlobalFunction("void ImGui_End()", asFUNCTION(AS_ImGui_End), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_BeginChild(const string &in str_id, const vec2 &in size = vec2(0,0), bool border = false, int extra_flags = 0)", asFUNCTION(AS_ImGui_BeginChild), asCALL_CDECL, "bool ImGui_BeginChild(const string &in str_id, const vec2 &in size = vec2(0,0), bool border = false, ImGuiWindowFlags extra_flags = 0)");
+ context->RegisterGlobalFunction("bool ImGui_BeginChild(uint id, const vec2 &in size = vec2(0,0), bool border = false, int extra_flags = 0)", asFUNCTION(AS_ImGui_BeginChild2), asCALL_CDECL, "bool ImGui_BeginChild(uint id, const vec2 &in size = vec2(0,0), bool border = false, ImGuiWindowFlags extra_flags = 0)");
+ context->RegisterGlobalFunction("void ImGui_EndChild()", asFUNCTION(AS_ImGui_EndChild), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec2 ImGui_GetContentRegionMax()", asFUNCTION(AS_ImGui_GetContentRegionMax), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec2 ImGui_GetContentRegionAvail()", asFUNCTION(AS_ImGui_GetContentRegionAvail), asCALL_CDECL);
+ context->RegisterGlobalFunction("float ImGui_GetContentRegionAvailWidth()", asFUNCTION(AS_ImGui_GetContentRegionAvailWidth), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec2 ImGui_GetWindowContentRegionMin()", asFUNCTION(AS_ImGui_GetWindowContentRegionMin), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec2 ImGui_GetWindowContentRegionMax()", asFUNCTION(AS_ImGui_GetWindowContentRegionMax), asCALL_CDECL);
+ context->RegisterGlobalFunction("float ImGui_GetWindowContentRegionWidth()", asFUNCTION(AS_ImGui_GetWindowContentRegionWidth), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec2 ImGui_GetWindowPos()", asFUNCTION(AS_ImGui_GetWindowPos), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec2 ImGui_GetWindowSize()", asFUNCTION(AS_ImGui_GetWindowSize), asCALL_CDECL);
+ context->RegisterGlobalFunction("float ImGui_GetWindowWidth()", asFUNCTION(AS_ImGui_GetWindowWidth), asCALL_CDECL);
+ context->RegisterGlobalFunction("float ImGui_GetWindowHeight()", asFUNCTION(AS_ImGui_GetWindowHeight), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsWindowCollapsed()", asFUNCTION(AS_ImGui_IsWindowCollapsed), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsWindowAppearing()", asFUNCTION(AS_ImGui_IsWindowAppearing), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_SetWindowFontScale(float scale)", asFUNCTION(AS_ImGui_SetWindowFontScale), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("void ImGui_SetNextWindowPos(const vec2 &in pos, int cond = 0)", asFUNCTION(AS_ImGui_SetNextWindowPos), asCALL_CDECL, "void ImGui_SetNextWindowPos(const vec2 &in pos, ImGuiSetCond cond = 0)");
+ context->RegisterGlobalFunction("void ImGui_SetNextWindowPosCenter(int cond = 0)", asFUNCTION(AS_ImGui_SetNextWindowPosCenter), asCALL_CDECL, "void ImGui_SetNextWindowPosCenter(ImGuiSetCond cond = 0)");
+ context->RegisterGlobalFunction("void ImGui_SetNextWindowSize(const vec2 &in size, int cond = 0)", asFUNCTION(AS_ImGui_SetNextWindowSize), asCALL_CDECL, "void ImGui_SetNextWindowSize(const vec2 &in size, ImGuiSetCond cond = 0)");
+ context->RegisterGlobalFunction("void ImGui_SetNextWindowContentSize(const vec2 &in size)", asFUNCTION(AS_ImGui_SetNextWindowContentSize), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_SetNextWindowContentWidth(float width)", asFUNCTION(AS_ImGui_SetNextWindowContentWidth), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_SetNextWindowCollapsed(bool collapsed, int cond = 0)", asFUNCTION(AS_ImGui_SetNextWindowCollapsed), asCALL_CDECL, "void ImGui_SetNextWindowCollapsed(bool collapsed, ImGuiSetCond cond = 0)");
+ context->RegisterGlobalFunction("void ImGui_SetNextWindowFocus()", asFUNCTION(AS_ImGui_SetNextWindowFocus), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_SetWindowPos(const vec2 &in pos, int cond = 0)", asFUNCTION(AS_ImGui_SetWindowPos), asCALL_CDECL, "void ImGui_SetWindowPos(const vec2 &in pos, ImGuiSetCond cond = 0)");
+ context->RegisterGlobalFunction("void ImGui_SetWindowSize(const vec2 &in size, int cond = 0)", asFUNCTION(AS_ImGui_SetWindowSize), asCALL_CDECL, "void ImGui_SetWindowSize(const vec2 &in size, ImGuiSetCond cond = 0)");
+ context->RegisterGlobalFunction("void ImGui_SetWindowCollapsed(bool collapsed, int cond = 0)", asFUNCTION(AS_ImGui_SetWindowCollapsed), asCALL_CDECL, "void ImGui_SetWindowCollapsed(bool collapsed, ImGuiSetCond cond = 0)");
+ context->RegisterGlobalFunction("void ImGui_SetWindowFocus()", asFUNCTION(AS_ImGui_SetWindowFocus), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_SetWindowPos(const string &in name, const vec2 &in pos, int cond = 0)", asFUNCTION(AS_ImGui_SetWindowPos2), asCALL_CDECL, "void ImGui_SetWindowPos(const string &in name, const vec2 &in pos, ImGuiSetCond cond = 0)");
+ context->RegisterGlobalFunction("void ImGui_SetWindowSize(const string &in name, const vec2 &in size, int cond = 0)", asFUNCTION(AS_ImGui_SetWindowSize2), asCALL_CDECL, "void ImGui_SetWindowSize(const string &in name, const vec2 &in size, ImGuiSetCond cond = 0)");
+ context->RegisterGlobalFunction("void ImGui_SetWindowCollapsed(const string &in name, bool collapsed, int cond = 0)", asFUNCTION(AS_ImGui_SetWindowCollapsed2), asCALL_CDECL, "void ImGui_SetWindowCollapsed(const string &in name, bool collapsed, ImGuiSetCond cond = 0)");
+ context->RegisterGlobalFunction("void ImGui_SetWindowFocus(const string &in name)", asFUNCTION(AS_ImGui_SetWindowFocus2), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("float ImGui_GetScrollX()", asFUNCTION(AS_ImGui_GetScrollX), asCALL_CDECL);
+ context->RegisterGlobalFunction("float ImGui_GetScrollY()", asFUNCTION(AS_ImGui_GetScrollY), asCALL_CDECL);
+ context->RegisterGlobalFunction("float ImGui_GetScrollMaxX()", asFUNCTION(AS_ImGui_GetScrollMaxX), asCALL_CDECL);
+ context->RegisterGlobalFunction("float ImGui_GetScrollMaxY()", asFUNCTION(AS_ImGui_GetScrollMaxY), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_SetScrollX(float scroll_x)", asFUNCTION(AS_ImGui_SetScrollX), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_SetScrollY(float scroll_y)", asFUNCTION(AS_ImGui_SetScrollY), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_SetScrollHere(float center_y_ratio = 0.5f)", asFUNCTION(AS_ImGui_SetScrollHere), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_SetScrollFromPosY(float pos_y, float center_y_ratio = 0.5f)", asFUNCTION(AS_ImGui_SetScrollFromPosY), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_SetKeyboardFocusHere(int offset = 0)", asFUNCTION(AS_ImGui_SetKeyboardFocusHere), asCALL_CDECL);
+
+ // Parameters stacks (shared)
+ context->RegisterGlobalFunction("void ImGui_PushStyleColor(int idx, const vec4 &in col)", asFUNCTION(AS_ImGui_PushStyleColor), asCALL_CDECL, "void ImGui_PushStyleColor(ImGuiCol idx, const vec4 &in col)");
+ context->RegisterGlobalFunction("void ImGui_PopStyleColor(int count = 1)", asFUNCTION(AS_ImGui_PopStyleColor), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_PushStyleVar(int idx, float val)", asFUNCTION(AS_ImGui_PushStyleVar), asCALL_CDECL, "void ImGui_PushStyleVar(ImGuiStyleVar idx, float val)");
+ context->RegisterGlobalFunction("void ImGui_PushStyleVar(int idx, const vec2 &in val)", asFUNCTION(AS_ImGui_PushStyleVar2), asCALL_CDECL, "void ImGui_PushStyleVar(ImGuiStyleVar idx, const vec2 &in val)");
+ context->RegisterGlobalFunction("void ImGui_PushStyleVar(int idx, bool val)", asFUNCTION(AS_ImGui_PushStyleVar3), asCALL_CDECL, "void ImGui_PushStyleVar(ImGuiStyleVar idx, bool val)");
+ context->RegisterGlobalFunction("void ImGui_PushStyleVar(int idx, int val)", asFUNCTION(AS_ImGui_PushStyleVar4), asCALL_CDECL, "void ImGui_PushStyleVar(ImGuiStyleVar idx, int val)");
+ context->RegisterGlobalFunction("void ImGui_BeginDisabled(bool is_disabled)", asFUNCTION(AS_ImGui_BeginDisabled), asCALL_CDECL, "void ImGui_BeginDisabled(bool is_disabled)");
+ context->RegisterGlobalFunction("void ImGui_EndDisabled()", asFUNCTION(AS_ImGui_EndDisabled), asCALL_CDECL, "void ImGui_EndDisabled()");
+ context->RegisterGlobalFunction("void ImGui_PopStyleVar(int count = 1)", asFUNCTION(AS_ImGui_PopStyleVar), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec4 ImGui_GetStyleColorVec4(int idx)", asFUNCTION(AS_ImGui_GetStyleColorVec4), asCALL_CDECL, "string ImGui_GetStyleColName(ImGuiCol idx)"); // Note: Original returned const char*, not a copy
+ context->RegisterGlobalFunction("uint32 ImGui_GetColorU32(uint32 idx, float alpha_mul = 1.0f)", asFUNCTION(AS_ImGui_GetColorU32), asCALL_CDECL, "uint32 GetColorU32(ImGuiCol idx, float alpha_mul = 1.0f)");
+ context->RegisterGlobalFunction("uint32 ImGui_GetColorU32(const vec3 &in col)", asFUNCTION(AS_ImGui_GetColorU32_2), asCALL_CDECL);
+ context->RegisterGlobalFunction("uint32 ImGui_GetColorU32(const vec4 &in col)", asFUNCTION(AS_ImGui_GetColorU32_3), asCALL_CDECL);
+ context->RegisterGlobalFunction("uint32 ImGui_GetColorU32(float r, float g, float b, float alpha_mul = 1.0f)", asFUNCTION(AS_ImGui_GetColorU32_4), asCALL_CDECL);
+
+ // Parameters stacks (current window)
+ context->RegisterGlobalFunction("void ImGui_PushItemWidth(float item_width)", asFUNCTION(AS_ImGui_PushItemWidth), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_PopItemWidth()", asFUNCTION(AS_ImGui_PopItemWidth), asCALL_CDECL);
+ context->RegisterGlobalFunction("float ImGui_CalcItemWidth()", asFUNCTION(AS_ImGui_CalcItemWidth), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_PushTextWrapPos(float wrap_pos_x = 0.0f)", asFUNCTION(AS_ImGui_PushTextWrapPos), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_PopTextWrapPos()", asFUNCTION(AS_ImGui_PopTextWrapPos), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_PushAllowKeyboardFocus(bool allow_keyboard_focus)", asFUNCTION(AS_ImGui_PushAllowKeyboardFocus), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_PopAllowKeyboardFocus()", asFUNCTION(AS_ImGui_PopAllowKeyboardFocus), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_PushButtonRepeat(bool repeat)", asFUNCTION(AS_ImGui_PushButtonRepeat), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_PopButtonRepeat()", asFUNCTION(AS_ImGui_PopButtonRepeat), asCALL_CDECL);
+
+ // Cursor / Layout
+ context->RegisterGlobalFunction("void ImGui_Separator()", asFUNCTION(AS_ImGui_Separator), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_SameLine(float pos_x = 0.0, float spacing_w = -1.0)", asFUNCTION(AS_ImGui_SameLine), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_NewLine()", asFUNCTION(AS_ImGui_NewLine), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_Spacing()", asFUNCTION(AS_ImGui_Spacing), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_Dummy(const vec2 &in size)", asFUNCTION(AS_ImGui_Dummy), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_Indent(float indent_w = 0.0f)", asFUNCTION(AS_ImGui_Indent), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_Unindent(float indent_w = 0.0f)", asFUNCTION(AS_ImGui_Unindent), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_BeginGroup()", asFUNCTION(AS_ImGui_BeginGroup), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_EndGroup()", asFUNCTION(AS_ImGui_EndGroup), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec2 ImGui_GetCursorPos()", asFUNCTION(AS_ImGui_GetCursorPos), asCALL_CDECL);
+ context->RegisterGlobalFunction("float ImGui_GetCursorPosX()", asFUNCTION(AS_ImGui_GetCursorPosX), asCALL_CDECL);
+ context->RegisterGlobalFunction("float ImGui_GetCursorPosY()", asFUNCTION(AS_ImGui_GetCursorPosY), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_SetCursorPos(const vec2 &in local_pos)", asFUNCTION(AS_ImGui_SetCursorPos), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_SetCursorPosX(float x)", asFUNCTION(AS_ImGui_SetCursorPosX), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_SetCursorPosY(float y)", asFUNCTION(AS_ImGui_SetCursorPosY), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec2 ImGui_GetCursorStartPos()", asFUNCTION(AS_ImGui_GetCursorStartPos), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec2 ImGui_GetCursorScreenPos()", asFUNCTION(AS_ImGui_GetCursorScreenPos), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_SetCursorScreenPos(const vec2 &in pos)", asFUNCTION(AS_ImGui_SetCursorScreenPos), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_AlignFirstTextHeightToWidgets()", asFUNCTION(AS_ImGui_AlignTextToFramePadding), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_AlignTextToFramePadding()", asFUNCTION(AS_ImGui_AlignTextToFramePadding), asCALL_CDECL);
+ context->RegisterGlobalFunction("float ImGui_GetTextLineHeight()", asFUNCTION(AS_ImGui_GetTextLineHeight), asCALL_CDECL);
+ context->RegisterGlobalFunction("float ImGui_GetTextLineHeightWithSpacing()", asFUNCTION(AS_ImGui_GetTextLineHeightWithSpacing), asCALL_CDECL);
+ context->RegisterGlobalFunction("float ImGui_GetItemsLineHeightWithSpacing()", asFUNCTION(AS_ImGui_GetFrameHeightWithSpacing), asCALL_CDECL);
+ context->RegisterGlobalFunction("float ImGui_GetFrameHeight()", asFUNCTION(AS_ImGui_GetFrameHeight), asCALL_CDECL);
+ context->RegisterGlobalFunction("float ImGui_GetFrameHeightWithSpacing()", asFUNCTION(AS_ImGui_GetFrameHeightWithSpacing), asCALL_CDECL);
+
+ // Columns
+ context->RegisterGlobalFunction("bool ImGui_Columns(int count = 1, bool border = true)", asFUNCTION(AS_ImGui_Columns), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_Columns(int count, const string &in id, bool border = true)", asFUNCTION(AS_ImGui_Columns2), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_NextColumn()", asFUNCTION(AS_ImGui_NextColumn), asCALL_CDECL);
+ context->RegisterGlobalFunction("int ImGui_GetColumnIndex()", asFUNCTION(AS_ImGui_GetColumnIndex), asCALL_CDECL);
+ context->RegisterGlobalFunction("float ImGui_GetColumnOffset(int column_index = -1)", asFUNCTION(AS_ImGui_GetColumnOffset), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_SetColumnOffset(int column_index, float offset_x)", asFUNCTION(AS_ImGui_SetColumnOffset), asCALL_CDECL);
+ context->RegisterGlobalFunction("float ImGui_GetColumnWidth(int column_index = -1)", asFUNCTION(AS_ImGui_GetColumnWidth), asCALL_CDECL);
+ context->RegisterGlobalFunction("float ImGui_SetColumnWidth(int column_index, float width)", asFUNCTION(AS_ImGui_SetColumnWidth), asCALL_CDECL);
+ context->RegisterGlobalFunction("int ImGui_GetColumnsCount()", asFUNCTION(AS_ImGui_GetColumnsCount), asCALL_CDECL);
+
+ // ID scopes
+ context->RegisterGlobalFunction("void ImGui_PushID(const string &in str)", asFUNCTION(AS_ImGui_PushID), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_PushID(const ? &in ptr_id)", asFUNCTION(AS_ImGui_PushID2), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_PushID(int int_id)", asFUNCTION(AS_ImGui_PushID3), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_PopID()", asFUNCTION(AS_ImGui_PopID), asCALL_CDECL);
+ context->RegisterGlobalFunction("uint ImGui_GetID(const string &in str_id)", asFUNCTION(AS_ImGui_GetID), asCALL_CDECL);
+ context->RegisterGlobalFunction("uint ImGui_GetID(const ? &in ptr_id)", asFUNCTION(AS_ImGui_GetID2), asCALL_CDECL);
+
+ // Widgets: Text
+ context->RegisterGlobalFunction("void ImGui_Text(const string &in label)", asFUNCTION(AS_ImGui_Text), asCALL_CDECL, "Note: label is not a format string, unlike in Dear ImGui. Use string concatenation instead");
+ context->RegisterGlobalFunction("void ImGui_TextColored(const vec4 &in color, const string &in label)", asFUNCTION(AS_ImGui_TextColored), asCALL_CDECL, "Note: label is not a format string, unlike in Dear ImGui. Use string concatenation instead");
+ context->RegisterGlobalFunction("void ImGui_TextDisabled(const string &in label)", asFUNCTION(AS_ImGui_TextDisabled), asCALL_CDECL, "Note: label is not a format string, unlike in Dear ImGui. Use string concatenation instead");
+ context->RegisterGlobalFunction("void ImGui_TextWrapped(const string &in label)", asFUNCTION(AS_ImGui_TextWrapped), asCALL_CDECL, "Note: label is not a format string, unlike in Dear ImGui. Use string concatenation instead");
+ context->RegisterGlobalFunction("void ImGui_LabelText(const string &in str, const string &in label)", asFUNCTION(AS_ImGui_LabelText), asCALL_CDECL, "Note: label is not a format string, unlike in Dear ImGui. Use string concatenation instead");
+ context->RegisterGlobalFunction("void ImGui_Bullet()", asFUNCTION(AS_ImGui_Bullet), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_BulletText(const string &in label)", asFUNCTION(AS_ImGui_BulletText), asCALL_CDECL, "Note: label is not a format string, unlike in Dear ImGui. Use string concatenation instead");
+
+ // Widgets: Main
+ context->RegisterGlobalFunction("bool ImGui_Button(const string &in, const vec2 &in size = vec2(0,0))", asFUNCTION(AS_ImGui_Button), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_SmallButton(const string &in label)", asFUNCTION(AS_ImGui_SmallButton), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_InvisibleButton(const string &in str_id, const vec2 &in size)", asFUNCTION(AS_ImGui_InvisibleButton), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_Image(const TextureAssetRef &in texture, const vec2 &in size, const vec2 &in uv0 = vec2(0,0), const vec2 &in uv1 = vec2(1,1), const vec4 &in tint_color = vec4(1,1,1,1), const vec4 &in border_color = vec4(0,0,0,0))", asFUNCTION(AS_ImGui_Image), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_ImageButton(const TextureAssetRef &in texture, const vec2 &in size, const vec2 &in uv0 = vec2(0,0), const vec2 &in uv1 = vec2(1,1), int frame_padding = -1, const vec4 &in background_color = vec4(0,0,0,0), const vec4 &in tint_color = vec4(1,1,1,1))", asFUNCTION(AS_ImGui_ImageButton), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_Checkbox(const string &in label, bool &inout value)", asFUNCTION(AS_ImGui_Checkbox), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_CheckboxFlags(const string &in label, uint &inout flags, uint flags_value)", asFUNCTION(AS_ImGui_CheckboxFlags), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_RadioButton(const string &in label, bool active)", asFUNCTION(AS_ImGui_RadioButton), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_RadioButton(const string &in label, int &inout value, int v_button)", asFUNCTION(AS_ImGui_RadioButton2), asCALL_CDECL);
+
+ // Widgets: Combo box
+ context->RegisterGlobalFunction("bool ImGui_BeginCombo(const string &in label, const string& in preview_value, int flags = 0)", asFUNCTION(AS_ImGui_BeginCombo), asCALL_CDECL, "bool ImGui_BeginCombo(const string &in label, const string& in preview_value, ImGuiComboFlags flags = 0)");
+ context->RegisterGlobalFunction("void ImGui_EndCombo()", asFUNCTION(AS_ImGui_EndCombo), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_Combo(const string &in label, int &inout current_item, const array<string> &in items, int popup_max_height_in_items = -1)", asFUNCTION(AS_ImGui_Combo), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_Combo(const string &in label, int &inout current_item, const string &in items_separated_by_zeros, int popup_max_height_in_items = -1)", asFUNCTION(AS_ImGui_Combo2), asCALL_CDECL);
+
+ // Widgets: Drags
+ context->RegisterGlobalFunction("bool ImGui_DragFloat(const string &in label, float &inout value, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const string &in display_format = \"%.3f\", float power = 1.0f)", asFUNCTION(AS_ImGui_DragFloat), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_DragFloat2(const string &in label, vec2 &inout value, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const string &in display_format = \"%.3f\", float power = 1.0f)", asFUNCTION(AS_ImGui_DragFloat2_Vector), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_DragFloat3(const string &in label, vec3 &inout value, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const string &in display_format = \"%.3f\", float power = 1.0f)", asFUNCTION(AS_ImGui_DragFloat3_Vector), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_DragFloat4(const string &in label, vec4 &inout value, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const string &in display_format = \"%.3f\", float power = 1.0f)", asFUNCTION(AS_ImGui_DragFloat4_Vector), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_DragFloatRange2(const string &in label, float &inout v_current_min, float &inout v_current_max, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const string &in display_format = \"%.3f\")", asFUNCTION(AS_ImGui_DragFloatRange2_Vector), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_DragFloatRange2(const string &in label, float &inout v_current_min, float &inout v_current_max, float v_speed, float v_min, float v_max, const string &in display_format, const string &in display_format_max, float power = 1.0f)", asFUNCTION(AS_ImGui_DragFloatRange2_Vector2), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_DragInt(const string &in label, int &inout value, float v_speed = 1.0f, int v_min = 0, int v_max = 0, const string &in display_format = \"%.0f\")", asFUNCTION(AS_ImGui_DragInt), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_DragInt2(const string &in label, ivec2 &inout value, float v_speed = 1.0f, int v_min = 0, int v_max = 0, const string &in display_format = \"%.0f\")", asFUNCTION(AS_ImGui_DragInt2_Vector), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_DragInt3(const string &in label, ivec3 &inout value, float v_speed = 1.0f, int v_min = 0, int v_max = 0, const string &in display_format = \"%.0f\")", asFUNCTION(AS_ImGui_DragInt3_Vector), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_DragInt4(const string &in label, ivec4 &inout value, float v_speed = 1.0f, int v_min = 0, int v_max = 0, const string &in display_format = \"%.0f\")", asFUNCTION(AS_ImGui_DragInt4_Vector), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_DragIntRange2(const string &in label, int &inout v_current_min, int &inout v_current_max, float v_speed = 1.0f, int v_min = 0.0f, int v_max = 0.0f, const string &in display_format = \"%.3f\")", asFUNCTION(AS_ImGui_DragIntRange2_Vector), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_DragIntRange2(const string &in label, int &inout v_current_min, int &inout v_current_max, float v_speed, int v_min, int v_max, const string &in display_format, const string &in display_format_max)", asFUNCTION(AS_ImGui_DragIntRange2_Vector2), asCALL_CDECL);
+
+ // Widgets: Input with Keyboard
+ context->RegisterGlobalFunction("bool ImGui_InputText(const string &in label, int flags = 0)", asFUNCTION(AS_ImGui_InputText), asCALL_CDECL, "bool ImGui_InputText(const string &in label, ImGuiInputTextFlags flags = 0)");
+ context->RegisterGlobalFunction("bool ImGui_InputText(const string &in label, string &inout text_buffer, int buffer_size, int flags = 0)", asFUNCTION(AS_ImGui_InputText2), asCALL_CDECL, "bool ImGui_InputText(const string &in label, string &inout text_buffer, int buffer_size, ImGuiInputTextFlags flags = 0)");
+ context->RegisterGlobalFunction("bool ImGui_InputTextMultiline(const string &in label, const vec2 &in size = vec2(0,0), int flags = 0)", asFUNCTION(AS_ImGui_InputTextMultiline), asCALL_CDECL, "bool ImGui_InputTextMultiline(const string &in label, const vec2 &in size = vec2(0,0), ImGuiInputTextFlags flags = 0)");
+ context->RegisterGlobalFunction("bool ImGui_InputTextMultiline(const string &in label, string &inout text_buffer, int buffer_size, const vec2 &in size = vec2(0,0), int flags = 0)", asFUNCTION(AS_ImGui_InputTextMultiline2), asCALL_CDECL, "bool ImGui_InputTextMultiline(const string &in label, string &inout text_buffer, int buffer_size, const vec2 &in size = vec2(0,0), ImGuiInputTextFlags flags = 0)");
+ context->RegisterGlobalFunction("bool ImGui_InputFloat(const string &in label, float &inout value, float step = 0.0f, float step_fast = 0.0f, int decimal_precision = -1, int extra_flags = 0)", asFUNCTION(AS_ImGui_InputFloat), asCALL_CDECL, "bool ImGui_InputFloat(const string &in label, float &inout value, float step = 0.0f, float step_fast = 0.0f, int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0)");
+ context->RegisterGlobalFunction("bool ImGui_InputFloat2(const string &in label, vec2 &inout value, int decimal_precision = -1, int extra_flags = 0)", asFUNCTION(AS_ImGui_InputFloat2_Vector), asCALL_CDECL, "bool ImGui_InputFloat2(const string &in label, vec2 &inout value, int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0)");
+ context->RegisterGlobalFunction("bool ImGui_InputFloat3(const string &in label, vec3 &inout value, int decimal_precision = -1, int extra_flags = 0)", asFUNCTION(AS_ImGui_InputFloat3_Vector), asCALL_CDECL, "bool ImGui_InputFloat3(const string &in label, vec3 &inout value, int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0)");
+ context->RegisterGlobalFunction("bool ImGui_InputFloat4(const string &in label, vec4 &inout value, int decimal_precision = -1, int extra_flags = 0)", asFUNCTION(AS_ImGui_InputFloat4_Vector), asCALL_CDECL, "bool ImGui_InputFloat4(const string &in label, vec4 &inout value, int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0)");
+ context->RegisterGlobalFunction("bool ImGui_InputInt(const string &in label, int &inout value, int step = 1, int step_fast = 100, int extra_flags = 0)", asFUNCTION(AS_ImGui_InputInt), asCALL_CDECL, "bool ImGui_InputInt(const string &in label, int &inout value, int step = 1, int step_fast = 100, ImGuiInputTextFlags extra_flags = 0)");
+
+ // Widgets:: Sliders
+ context->RegisterGlobalFunction("bool ImGui_SliderFloat(const string &in label, float &inout v, float v_min, float v_max, const string &in display_format = \"%.3f\", float power = 1.0f)", asFUNCTION(AS_ImGui_SliderFloat), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_SliderFloat2(const string &in label, vec2 &inout v, float v_min, float v_max, const string &in display_format = \"%.3f\", float power = 1.0f)", asFUNCTION(AS_ImGui_SliderFloat2), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_SliderFloat3(const string &in label, vec3 &inout v, float v_min, float v_max, const string &in display_format = \"%.3f\", float power = 1.0f)", asFUNCTION(AS_ImGui_SliderFloat3), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_SliderFloat4(const string &in label, vec4 &inout v, float v_min, float v_max, const string &in display_format = \"%.3f\", float power = 1.0f)", asFUNCTION(AS_ImGui_SliderFloat4), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_SliderAngle(const string &in label, float &inout v_rad, float v_degrees_min = -360.0f, float v_degrees_max = +360.0f)", asFUNCTION(AS_ImGui_SliderAngle), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_SliderInt(const string &in label, int &inout v, int v_min, int v_max, const string &in display_format = \"%.0f\")", asFUNCTION(AS_ImGui_SliderInt), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_SliderInt2(const string &in label, ivec2 &inout v, int v_min, int v_max, const string &in display_format = \"%.0f\")", asFUNCTION(AS_ImGui_SliderInt2), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_SliderInt3(const string &in label, ivec3 &inout v, int v_min, int v_max, const string &in display_format = \"%.0f\")", asFUNCTION(AS_ImGui_SliderInt3), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_SliderInt4(const string &in label, ivec4 &inout v, int v_min, int v_max, const string &in display_format = \"%.0f\")", asFUNCTION(AS_ImGui_SliderInt4), asCALL_CDECL);
+
+ // Widgets: Color Editor/Picker
+ context->RegisterGlobalFunction("bool ImGui_ColorEdit3(const string &in label, vec3 &inout color, int flags = 0)", asFUNCTION(AS_ImGui_ColorEdit3), asCALL_CDECL, "bool ImGui_ColorEdit3(const string &in label, vec3 &inout color, ImGuiColorEditFlags flags = 0");
+ context->RegisterGlobalFunction("bool ImGui_ColorEdit4(const string &in label, vec4 &inout color, bool show_alpha = true)", asFUNCTION(AS_ImGui_ColorEdit4), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_ColorEdit4(const string &in label, vec4 &inout color, int flags)", asFUNCTION(AS_ImGui_ColorEdit4_2), asCALL_CDECL, "void ImGui_ColorEdit4(const string in& label, vec4 &inout color, ImGuiColorEditFlags flags = 0");
+ context->RegisterGlobalFunction("bool ImGui_ColorPicker3(const string &in label, vec3 &inout color, int flags = 0)", asFUNCTION(AS_ImGui_ColorPicker3), asCALL_CDECL, "bool ImGui_ColorPicker3(const string &in label, vec3 &inout color, ImGuiColorEditFlags flags = 0)");
+ context->RegisterGlobalFunction("bool ImGui_ColorPicker4(const string &in label, vec4 &inout color, int flags = 0)", asFUNCTION(AS_ImGui_ColorPicker4), asCALL_CDECL, "bool ImGui_ColorPicker4(const string &in label, vec4 &inout color, ImGuiColorEditFlags flags = 0)");
+ context->RegisterGlobalFunction("bool ImGui_ColorPicker4(const string &in label, vec4 &inout color, int flags, vec4 ref_col)", asFUNCTION(AS_ImGui_ColorPicker4_2), asCALL_CDECL, "bool ImGui_ColorPicker4(const string &in label, vec4 &inout color, ImGuiColorEditFlags flags, const vec4 &in ref_color)");
+ context->RegisterGlobalFunction("bool ImGui_ColorButton(const vec4 &in col, bool small_height = false, bool outline_border = true)", asFUNCTION(AS_ImGui_ColorButton), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_ColorButton(const string &in desc, const vec4 &in col, int flags = 0, const vec2 &in size = vec2(0, 0))", asFUNCTION(AS_ImGui_ColorButton2), asCALL_CDECL, "bool ImGui_ColorButton(const string &in desc, const vec4 &in col, ImGuiColorEditFlags flags = 0, const vec2 &in size = vec2(0, 0)");
+ context->RegisterGlobalFunction("void ImGui_ColorEditMode(int)", asFUNCTION(AS_ImGui_ColorEditMode), asCALL_CDECL, "void ImGui_ColorEditMode(ImGuiColorEditMode mode)");
+
+ // Widgets: Trees
+ context->RegisterGlobalFunction("bool ImGui_TreeNode(const string &in label)", asFUNCTION(AS_ImGui_TreeNode), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_TreeNode(const string &in str_id, const string &in label)", asFUNCTION(AS_ImGui_TreeNode2), asCALL_CDECL, "Note: label is not a format string, unlike in Dear ImGui. Use string concatenation instead");
+ context->RegisterGlobalFunction("bool ImGui_TreeNode(const ? &in ptr_id, const string &in label)", asFUNCTION(AS_ImGui_TreeNode3), asCALL_CDECL, "Note: label is not a format string, unlike in Dear ImGui. Use string concatenation instead");
+ context->RegisterGlobalFunction("bool ImGui_TreeNodeEx(const string &in label, int flags = 0)", asFUNCTION(AS_ImGui_TreeNodeEx), asCALL_CDECL, "bool ImGui_TreeNodeEx(const string &in label, ImGuiTreeNodeFlags_ flags = 0)");
+ context->RegisterGlobalFunction("bool ImGui_TreeNodeEx(const string &in str_id, int flags, const string &in label)", asFUNCTION(AS_ImGui_TreeNodeEx2), asCALL_CDECL, "bool ImGui_TreeNodeEx(const string &in str_id, ImGuiTreeNodeFlags_ flags, const string &in label) - Note: label is not a format string, unlike in Dear ImGui. Use string concatenation instead");
+ context->RegisterGlobalFunction("bool ImGui_TreeNodeEx(const ? &in ptr_id, int flags, const string &in label)", asFUNCTION(AS_ImGui_TreeNodeEx3), asCALL_CDECL, "bool ImGui_TreeNodeEx(const ? &in ptr_id, ImGuiTreeNodeFlags_ flags, const string &in label) - Note: label is not a format string, unlike in Dear ImGui. Use string concatenation instead");
+ context->RegisterGlobalFunction("void ImGui_TreePush()", asFUNCTION(AS_ImGui_TreePush), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_TreePush(const string &in str_id)", asFUNCTION(AS_ImGui_TreePush2), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_TreePush(const ? &in ptr_id)", asFUNCTION(AS_ImGui_TreePush3), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_TreePop()", asFUNCTION(AS_ImGui_TreePop), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_TreeAdvanceToLabelPos()", asFUNCTION(AS_ImGui_TreeAdvanceToLabelPos), asCALL_CDECL);
+ context->RegisterGlobalFunction("float ImGui_GetTreeNodeToLabelSpacing()", asFUNCTION(AS_ImGui_GetTreeNodeToLabelSpacing), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_SetNextTreeNodeOpen(bool is_open, int cond = 0)", asFUNCTION(AS_ImGui_SetNextTreeNodeOpen), asCALL_CDECL, "void ImGui_SetNextTreeNodeOpen(bool is_open, ImGuiSetCond cond = 0)");
+ context->RegisterGlobalFunction("bool ImGui_CollapsingHeader(const string &in label, int flags = 0)", asFUNCTION(AS_ImGui_CollapsingHeader), asCALL_CDECL, "bool ImGui_CollapsingHeader(const string &in label, ImGuiTreeNodeFlags flags = 0)");
+ context->RegisterGlobalFunction("bool ImGui_CollapsingHeader(const string &in label, bool &inout is_open, int flags = 0)", asFUNCTION(AS_ImGui_CollapsingHeader2), asCALL_CDECL, "bool ImGui_CollapsingHeader(const string &in label, bool &inout is_open, ImGuiTreeNodeFlags flags = 0)");
+
+ // Widgets: Selectable / Lists
+ context->RegisterGlobalFunction("bool ImGui_Selectable(const string &in label, bool selected = false, int flags = 0, const vec2 &in size = vec2(0,0))", asFUNCTION(AS_ImGui_Selectable), asCALL_CDECL, "ImGui_Selectable(const string &in label, bool selected = false, ImGuiSelectableFlags flags = 0, const vec2& size = vec2(0,0))");
+ context->RegisterGlobalFunction("bool ImGui_SelectableToggle(const string &in label, bool &inout selected, int flags = 0, const vec2 &in size = vec2(0,0))", asFUNCTION(AS_ImGui_SelectableToggle), asCALL_CDECL, "ImGui_SelectableToggle(const string &in label, bool &inout selected, ImGuiSelectableFlags flags = 0, const vec2& size = vec2(0,0))");
+ context->RegisterGlobalFunction("bool ImGui_ListBox(const string &in label, int &inout current_item, const array<string> &in items, int height_in_items = -1)", asFUNCTION(AS_ImGui_ListBox), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_ListBoxHeader(const string &in label)", asFUNCTION(AS_ImGui_ListBoxHeader), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_ListBoxHeader(const string &in label, const vec2 &in size)", asFUNCTION(AS_ImGui_ListBoxHeader2), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_ListBoxHeader(const string &in label, int items_count, int height_in_items = -1)", asFUNCTION(AS_ImGui_ListBoxHeader3), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_ListBoxFooter()", asFUNCTION(AS_ImGui_ListBoxFooter), asCALL_CDECL);
+
+ // Tooltips
+ context->RegisterGlobalFunction("void ImGui_SetTooltip(const string &in text)", asFUNCTION(AS_ImGui_SetTooltip), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_BeginTooltip()", asFUNCTION(AS_ImGui_BeginTooltip), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_EndTooltip()", asFUNCTION(AS_ImGui_EndTooltip), asCALL_CDECL);
+
+ // Menus
+ context->RegisterGlobalFunction("bool ImGui_BeginMenuBar()", asFUNCTION(AS_ImGui_BeginMenuBar), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_EndMenuBar()", asFUNCTION(AS_ImGui_EndMenuBar), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_BeginMenu(const string &in label, bool enabled = true)", asFUNCTION(AS_ImGui_BeginMenu), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_EndMenu()", asFUNCTION(AS_ImGui_EndMenu), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_MenuItem(const string &in label, bool selected = false, bool enabled = true)", asFUNCTION(AS_ImGui_MenuItem), asCALL_CDECL);
+
+ // Popups
+ context->RegisterGlobalFunction("void ImGui_OpenPopup(const string &in popup_id)", asFUNCTION(AS_ImGui_OpenPopup), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_OpenPopupOnItemClick(const string &in popup_id, int mouse_button = 1)", asFUNCTION(AS_ImGui_OpenPopupOnItemClick), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_BeginPopup(const string &in popup_id)", asFUNCTION(AS_ImGui_BeginPopup), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_BeginPopupModal(const string &in name, int extra_flags = 0)", asFUNCTION(AS_ImGui_BeginPopupModal), asCALL_CDECL, "bool ImGui_BeginPopupModal(const string &in name, ImGuiWindowFlags extra_flags = 0)");
+ context->RegisterGlobalFunction("bool ImGui_BeginPopupModal(const string &in name, bool &inout is_open, int extra_flags = 0)", asFUNCTION(AS_ImGui_BeginPopupModal2), asCALL_CDECL, "bool ImGui_BeginPopupModal(const string &in name, bool &inout is_open, ImGuiWindowFlags extra_flags = 0)");
+ context->RegisterGlobalFunction("bool ImGui_BeginPopupContextItem(const string &in popup_id, int mouse_button = 1)", asFUNCTION(AS_ImGui_BeginPopupContextItem), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_BeginPopupContextWindow(bool also_over_items = true, int mouse_button = 1)", asFUNCTION(AS_ImGui_BeginPopupContextWindow), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_BeginPopupContextWindow(bool also_over_items, const string &in popup_id, int mouse_button = 1)", asFUNCTION(AS_ImGui_BeginPopupContextWindow2), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_BeginPopupContextVoid(int mouse_button = 1)", asFUNCTION(AS_ImGui_BeginPopupContextVoid), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_BeginPopupContextVoid(const string &in popup_id, int mouse_button = 1)", asFUNCTION(AS_ImGui_BeginPopupContextVoid2), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_EndPopup()", asFUNCTION(AS_ImGui_EndPopup), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsPopupOpen(const string &in str_id)", asFUNCTION(AS_ImGui_IsPopupOpen), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_CloseCurrentPopup()", asFUNCTION(AS_ImGui_CloseCurrentPopup), asCALL_CDECL);
+
+ // Utilities
+ context->RegisterGlobalFunction("bool ImGui_IsItemHovered()", asFUNCTION(AS_ImGui_IsItemHovered), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsItemHoveredRect()", asFUNCTION(AS_ImGui_IsItemHoveredRect), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsItemActive()", asFUNCTION(AS_ImGui_IsItemActive), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsItemClicked(int mouse_button = 0)", asFUNCTION(AS_ImGui_IsItemClicked), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsItemVisible()", asFUNCTION(AS_ImGui_IsItemVisible), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsAnyItemHovered()", asFUNCTION(AS_ImGui_IsAnyItemHovered), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsAnyItemActive()", asFUNCTION(AS_ImGui_IsAnyItemActive), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec2 ImGui_GetItemRectMin()", asFUNCTION(AS_ImGui_GetItemRectMin), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec2 ImGui_GetItemRectMax()", asFUNCTION(AS_ImGui_GetItemRectMax), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec2 ImGui_GetItemRectSize()", asFUNCTION(AS_ImGui_GetItemRectSize), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_SetItemAllowOverlap()", asFUNCTION(AS_ImGui_SetItemAllowOverlap), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsWindowHovered()", asFUNCTION(AS_ImGui_IsWindowHovered), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsWindowFocused()", asFUNCTION(AS_ImGui_IsWindowFocused), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsRootWindowFocused()", asFUNCTION(AS_ImGui_IsRootWindowFocused), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsRootWindowOrAnyChildFocused()", asFUNCTION(AS_ImGui_IsRootWindowOrAnyChildFocused), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsRootWindowOrAnyChildHovered()", asFUNCTION(AS_ImGui_IsRootWindowOrAnyChildHovered), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsRectVisible(const vec2 &in size)", asFUNCTION(AS_ImGui_IsRectVisible), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsRectVisible(const vec2 &in rect_min, const vec2 &in rect_max)", asFUNCTION(AS_ImGui_IsRectVisible2), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsPosHoveringAnyWindow(const vec2 &in pos)", asFUNCTION(AS_ImGui_IsPosHoveringAnyWindow), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_WantCaptureMouse()", asFUNCTION(AS_ImGui_WantCaptureMouse), asCALL_CDECL);
+ context->RegisterGlobalFunction("float ImGui_GetTime()", asFUNCTION(AS_ImGui_GetTime), asCALL_CDECL);
+ context->RegisterGlobalFunction("int ImGui_GetFrameCount()", asFUNCTION(AS_ImGui_GetFrameCount), asCALL_CDECL);
+ context->RegisterGlobalFunction("string ImGui_GetStyleColName(int idx)", asFUNCTION(AS_ImGui_GetStyleColorName), asCALL_CDECL, "string ImGui_GetStyleColName(ImGuiCol idx)"); // Note: Original returned const char*, not a copy
+ context->RegisterGlobalFunction("string ImGui_GetStyleColorName(int idx)", asFUNCTION(AS_ImGui_GetStyleColorName), asCALL_CDECL, "string ImGui_GetStyleColorName(ImGuiCol idx)"); // Note: Original returned const char*, not a copy
+ //context->RegisterGlobalFunction("vec2 ImGui_CalcItemRectClosestPoint(const vec2 &in pos, bool on_edge = false, float outward = 0.0f)", asFUNCTION(AS_ImGui_CalcItemRectClosestPoint), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec2 ImGui_CalcTextSize(const string &in text, bool hide_text_after_double_hash = false, float wrap_width = -1.0f)", asFUNCTION(AS_ImGui_CalcTextSize), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_CalcListClipping(int items_count, float items_height, int &out out_items_display_start, int &out out_items_display_end)", asFUNCTION(AS_ImGui_CalcListClipping), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("bool ImGui_BeginChildFrame(uint id, const vec2 &in size, int extra_flags = 0)", asFUNCTION(AS_ImGui_BeginChildFrame), asCALL_CDECL, "bool ImGui_BeginChildFrame(uint id, const vec2 &in size, ImGuiWindowFlags extra_flags = 0)");
+ context->RegisterGlobalFunction("void ImGui_EndChildFrame()", asFUNCTION(AS_ImGui_EndChildFrame), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("vec4 ImGui_ColorConvertU32ToFloat4(uint value)", asFUNCTION(AS_ImGui_ColorConvertU32ToFloat4), asCALL_CDECL);
+ context->RegisterGlobalFunction("uint ImGui_ColorConvertFloat4ToU32(const vec4 &in value)", asFUNCTION(AS_ImGui_ColorConvertFloat4ToU32), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_ColorConvertRGBtoHSV(float r, float g, float b, float &out out_h, float &out out_s, float &out out_v)", asFUNCTION(AS_ImGui_ColorConvertRGBtoHSV), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_ColorConvertHSVtoRGB(float h, float s, float v, float &out out_r, float &out out_g, float &out out_b)", asFUNCTION(AS_ImGui_ColorConvertHSVtoRGB), asCALL_CDECL);
+
+ // Inputs
+ context->RegisterGlobalFunction("int ImGui_GetKeyIndex(int imgui_key)", asFUNCTION(AS_ImGui_GetKeyIndex), asCALL_CDECL, "int ImGui_GetKeyIndex(ImGuiKey key)");
+ context->RegisterGlobalFunction("bool ImGui_IsKeyDown(int key_index)", asFUNCTION(AS_ImGui_IsKeyDown), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsKeyPressed(int key_index, bool repeat = true)", asFUNCTION(AS_ImGui_IsKeyPressed), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsKeyReleased(int key_index)", asFUNCTION(AS_ImGui_IsKeyReleased), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsMouseDown(int button)", asFUNCTION(AS_ImGui_IsMouseDown), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsMouseClicked(int button, bool repeat = false)", asFUNCTION(AS_ImGui_IsMouseClicked), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsMouseDoubleClicked(int button)", asFUNCTION(AS_ImGui_IsMouseDoubleClicked), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsMouseReleased(int button)", asFUNCTION(AS_ImGui_IsMouseReleased), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsMouseHoveringWindow()", asFUNCTION(AS_ImGui_IsMouseHoveringWindow), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsMouseHoveringAnyWindow()", asFUNCTION(AS_ImGui_IsMouseHoveringAnyWindow), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsMouseHoveringRect(const vec2 &in r_min, const vec2 &in r_max, bool clip = true)", asFUNCTION(AS_ImGui_IsMouseHoveringRect), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ImGui_IsMouseDragging(int button = 0, float lock_threshold = -1.0f)", asFUNCTION(AS_ImGui_IsMouseDragging), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec2 ImGui_GetMousePos()", asFUNCTION(AS_ImGui_GetMousePos), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec2 ImGui_GetMousePosOnOpeningCurrentPopup()", asFUNCTION(AS_ImGui_GetMousePosOnOpeningCurrentPopup), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec2 ImGui_GetMouseDragDelta(int button = 0, float lock_threshold = -1.0f)", asFUNCTION(AS_ImGui_GetMouseDragDelta), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_ResetMouseDragDelta(int button = 0)", asFUNCTION(AS_ImGui_ResetMouseDragDelta), asCALL_CDECL);
+ context->RegisterGlobalFunction("int ImGui_GetMouseCursor()", asFUNCTION(AS_ImGui_GetMouseCursor), asCALL_CDECL, "ImGuiMouseCursor ImGui_GetMouseCursor()");
+ context->RegisterGlobalFunction("void ImGui_SetMouseCursor(int type)", asFUNCTION(AS_ImGui_SetMouseCursor), asCALL_CDECL, "void ImGui_SetMouseCursor(ImGuiMouseCursor type)");
+ context->RegisterGlobalFunction("void ImGui_CaptureKeyboardFromApp(bool capture = true)", asFUNCTION(AS_ImGui_CaptureKeyboardFromApp), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_CaptureMouseFromApp(bool capture = true)", asFUNCTION(AS_ImGui_CaptureMouseFromApp), asCALL_CDECL);
+
+ // Helpers functions to access functions pointers in ImGui::GetIO()
+ context->RegisterGlobalFunction("string ImGui_GetClipboardText()", asFUNCTION(AS_ImGui_GetClipboardText), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_SetClipboardText(const string &in value)", asFUNCTION(AS_ImGui_SetClipboardText), asCALL_CDECL);
+
+ // Flags for ImGui::Begin()
+ context->RegisterEnum("ImGuiWindowFlags_");
+ context->RegisterEnumValue("ImGuiWindowFlags_","ImGuiWindowFlags_NoTitleBar", ImGuiWindowFlags_NoTitleBar);
+ context->RegisterEnumValue("ImGuiWindowFlags_","ImGuiWindowFlags_NoResize", ImGuiWindowFlags_NoResize);
+ context->RegisterEnumValue("ImGuiWindowFlags_","ImGuiWindowFlags_NoMove", ImGuiWindowFlags_NoMove);
+ context->RegisterEnumValue("ImGuiWindowFlags_","ImGuiWindowFlags_NoScrollbar", ImGuiWindowFlags_NoScrollbar);
+ context->RegisterEnumValue("ImGuiWindowFlags_","ImGuiWindowFlags_NoScrollWithMouse", ImGuiWindowFlags_NoScrollWithMouse);
+ context->RegisterEnumValue("ImGuiWindowFlags_","ImGuiWindowFlags_NoCollapse", ImGuiWindowFlags_NoCollapse);
+ context->RegisterEnumValue("ImGuiWindowFlags_","ImGuiWindowFlags_AlwaysAutoResize", ImGuiWindowFlags_AlwaysAutoResize);
+ context->RegisterEnumValue("ImGuiWindowFlags_","ImGuiWindowFlags_NoSavedSettings", ImGuiWindowFlags_NoSavedSettings);
+ context->RegisterEnumValue("ImGuiWindowFlags_","ImGuiWindowFlags_NoInputs", ImGuiWindowFlags_NoInputs);
+ context->RegisterEnumValue("ImGuiWindowFlags_","ImGuiWindowFlags_MenuBar", ImGuiWindowFlags_MenuBar);
+ context->RegisterEnumValue("ImGuiWindowFlags_","ImGuiWindowFlags_HorizontalScrollbar", ImGuiWindowFlags_HorizontalScrollbar);
+ context->RegisterEnumValue("ImGuiWindowFlags_","ImGuiWindowFlags_NoFocusOnAppearing", ImGuiWindowFlags_NoFocusOnAppearing);
+ context->RegisterEnumValue("ImGuiWindowFlags_","ImGuiWindowFlags_NoBringToFrontOnFocus", ImGuiWindowFlags_NoBringToFrontOnFocus);
+ context->RegisterEnumValue("ImGuiWindowFlags_","ImGuiWindowFlags_AlwaysVerticalScrollbar", ImGuiWindowFlags_AlwaysVerticalScrollbar);
+ context->RegisterEnumValue("ImGuiWindowFlags_","ImGuiWindowFlags_AlwaysHorizontalScrollbar", ImGuiWindowFlags_AlwaysHorizontalScrollbar);
+ context->RegisterEnumValue("ImGuiWindowFlags_","ImGuiWindowFlags_AlwaysUseWindowPadding", ImGuiWindowFlags_AlwaysUseWindowPadding);
+ //context->RegisterEnumValue("ImGuiWindowFlags_","ImGuiWindowFlags_ResizeFromAnySide", ImGuiWindowFlags_ResizeFromAnySide); Obsolete
+
+ // Flags for ImGui::InputText()
+ context->RegisterEnum("ImGuiInputTextFlags_");
+ context->RegisterEnumValue("ImGuiInputTextFlags_","ImGuiInputTextFlags_CharsDecimal", ImGuiInputTextFlags_CharsDecimal);
+ context->RegisterEnumValue("ImGuiInputTextFlags_","ImGuiInputTextFlags_CharsHexadecimal", ImGuiInputTextFlags_CharsHexadecimal);
+ context->RegisterEnumValue("ImGuiInputTextFlags_","ImGuiInputTextFlags_CharsUppercase", ImGuiInputTextFlags_CharsUppercase);
+ context->RegisterEnumValue("ImGuiInputTextFlags_","ImGuiInputTextFlags_CharsNoBlank", ImGuiInputTextFlags_CharsNoBlank);
+ context->RegisterEnumValue("ImGuiInputTextFlags_","ImGuiInputTextFlags_AutoSelectAll", ImGuiInputTextFlags_AutoSelectAll);
+ context->RegisterEnumValue("ImGuiInputTextFlags_","ImGuiInputTextFlags_EnterReturnsTrue", ImGuiInputTextFlags_EnterReturnsTrue);
+ context->RegisterEnumValue("ImGuiInputTextFlags_","ImGuiInputTextFlags_CallbackCompletion", ImGuiInputTextFlags_CallbackCompletion);
+ context->RegisterEnumValue("ImGuiInputTextFlags_","ImGuiInputTextFlags_CallbackHistory", ImGuiInputTextFlags_CallbackHistory);
+ context->RegisterEnumValue("ImGuiInputTextFlags_","ImGuiInputTextFlags_CallbackAlways", ImGuiInputTextFlags_CallbackAlways);
+ context->RegisterEnumValue("ImGuiInputTextFlags_","ImGuiInputTextFlags_CallbackCharFilter", ImGuiInputTextFlags_CallbackCharFilter);
+ context->RegisterEnumValue("ImGuiInputTextFlags_","ImGuiInputTextFlags_AllowTabInput", ImGuiInputTextFlags_AllowTabInput);
+ context->RegisterEnumValue("ImGuiInputTextFlags_","ImGuiInputTextFlags_CtrlEnterForNewLine", ImGuiInputTextFlags_CtrlEnterForNewLine);
+ context->RegisterEnumValue("ImGuiInputTextFlags_","ImGuiInputTextFlags_NoHorizontalScroll", ImGuiInputTextFlags_NoHorizontalScroll);
+ context->RegisterEnumValue("ImGuiInputTextFlags_","ImGuiInputTextFlags_AlwaysInsertMode", ImGuiInputTextFlags_AlwaysInsertMode);
+ context->RegisterEnumValue("ImGuiInputTextFlags_","ImGuiInputTextFlags_ReadOnly", ImGuiInputTextFlags_ReadOnly);
+ context->RegisterEnumValue("ImGuiInputTextFlags_","ImGuiInputTextFlags_Password", ImGuiInputTextFlags_Password);
+ context->RegisterEnumValue("ImGuiInputTextFlags_","ImGuiInputTextFlags_NoUndoRedo", ImGuiInputTextFlags_NoUndoRedo);
+
+ // Flags for ImGui::TreeNodeEx(), ImGui::CollapsingHeader*()
+ context->RegisterEnum("ImGuiTreeNodeFlags_");
+ context->RegisterEnumValue("ImGuiTreeNodeFlags_", "ImGuiTreeNodeFlags_Selected", ImGuiTreeNodeFlags_Selected);
+ context->RegisterEnumValue("ImGuiTreeNodeFlags_", "ImGuiTreeNodeFlags_Framed", ImGuiTreeNodeFlags_Framed);
+ context->RegisterEnumValue("ImGuiTreeNodeFlags_", "ImGuiTreeNodeFlags_AllowOverlapMode", ImGuiTreeNodeFlags_AllowItemOverlap);
+ context->RegisterEnumValue("ImGuiTreeNodeFlags_", "ImGuiTreeNodeFlags_AllowItemOverlap", ImGuiTreeNodeFlags_AllowItemOverlap);
+ context->RegisterEnumValue("ImGuiTreeNodeFlags_", "ImGuiTreeNodeFlags_NoTreePushOnOpen", ImGuiTreeNodeFlags_NoTreePushOnOpen);
+ context->RegisterEnumValue("ImGuiTreeNodeFlags_", "ImGuiTreeNodeFlags_NoAutoOpenOnLog", ImGuiTreeNodeFlags_NoAutoOpenOnLog);
+ context->RegisterEnumValue("ImGuiTreeNodeFlags_", "ImGuiTreeNodeFlags_DefaultOpen", ImGuiTreeNodeFlags_DefaultOpen);
+ context->RegisterEnumValue("ImGuiTreeNodeFlags_", "ImGuiTreeNodeFlags_OpenOnDoubleClick", ImGuiTreeNodeFlags_OpenOnDoubleClick);
+ context->RegisterEnumValue("ImGuiTreeNodeFlags_", "ImGuiTreeNodeFlags_OpenOnArrow", ImGuiTreeNodeFlags_OpenOnArrow);
+ context->RegisterEnumValue("ImGuiTreeNodeFlags_", "ImGuiTreeNodeFlags_Leaf", ImGuiTreeNodeFlags_Leaf);
+ context->RegisterEnumValue("ImGuiTreeNodeFlags_", "ImGuiTreeNodeFlags_Bullet", ImGuiTreeNodeFlags_Bullet);
+ context->RegisterEnumValue("ImGuiTreeNodeFlags_", "ImGuiTreeNodeFlags_FramePadding", ImGuiTreeNodeFlags_FramePadding);
+ context->RegisterEnumValue("ImGuiTreeNodeFlags_", "ImGuiTreeNodeFlags_CollapsingHeader", ImGuiTreeNodeFlags_CollapsingHeader);
+
+ // Flags for ImGui::Selectable()
+ context->RegisterEnum("ImGuiSelectableFlags_");
+ context->RegisterEnumValue("ImGuiSelectableFlags_","ImGuiSelectableFlags_DontClosePopups", ImGuiSelectableFlags_DontClosePopups);
+ context->RegisterEnumValue("ImGuiSelectableFlags_","ImGuiSelectableFlags_SpanAllColumns", ImGuiSelectableFlags_SpanAllColumns);
+ context->RegisterEnumValue("ImGuiSelectableFlags_","ImGuiSelectableFlags_AllowDoubleClick", ImGuiSelectableFlags_AllowDoubleClick);
+
+ // Flags for ImGui::BeginCombo()
+ context->RegisterEnum("ImGuiComboFlags_");
+ context->RegisterEnumValue("ImGuiComboFlags_","ImGuiComboFlags_PopupAlignLeft", ImGuiComboFlags_PopupAlignLeft);
+ context->RegisterEnumValue("ImGuiComboFlags_","ImGuiComboFlags_HeightSmall", ImGuiComboFlags_HeightSmall);
+ context->RegisterEnumValue("ImGuiComboFlags_","ImGuiComboFlags_HeightRegular", ImGuiComboFlags_HeightRegular);
+ context->RegisterEnumValue("ImGuiComboFlags_","ImGuiComboFlags_HeightLarge", ImGuiComboFlags_HeightLarge);
+ context->RegisterEnumValue("ImGuiComboFlags_","ImGuiComboFlags_HeightLargest", ImGuiComboFlags_HeightLargest);
+
+ // Flags for ImGui::IsWindowFocused
+ // ...
+
+ // A key identifier (ImGui-side enum)
+ context->RegisterEnum("ImGuiKey_");
+ context->RegisterEnumValue("ImGuiKey_", "ImGuiKey_Tab", ImGuiKey_Tab);
+ context->RegisterEnumValue("ImGuiKey_", "ImGuiKey_LeftArrow", ImGuiKey_LeftArrow);
+ context->RegisterEnumValue("ImGuiKey_", "ImGuiKey_RightArrow", ImGuiKey_RightArrow);
+ context->RegisterEnumValue("ImGuiKey_", "ImGuiKey_UpArrow", ImGuiKey_UpArrow);
+ context->RegisterEnumValue("ImGuiKey_", "ImGuiKey_DownArrow", ImGuiKey_DownArrow);
+ context->RegisterEnumValue("ImGuiKey_", "ImGuiKey_PageUp", ImGuiKey_PageUp);
+ context->RegisterEnumValue("ImGuiKey_", "ImGuiKey_PageDown", ImGuiKey_PageDown);
+ context->RegisterEnumValue("ImGuiKey_", "ImGuiKey_Home", ImGuiKey_Home);
+ context->RegisterEnumValue("ImGuiKey_", "ImGuiKey_End", ImGuiKey_End);
+ context->RegisterEnumValue("ImGuiKey_", "ImGuiKey_Delete", ImGuiKey_Delete);
+ context->RegisterEnumValue("ImGuiKey_", "ImGuiKey_Backspace", ImGuiKey_Backspace);
+ context->RegisterEnumValue("ImGuiKey_", "ImGuiKey_Enter", ImGuiKey_Enter);
+ context->RegisterEnumValue("ImGuiKey_", "ImGuiKey_Escape", ImGuiKey_Escape);
+ context->RegisterEnumValue("ImGuiKey_", "ImGuiKey_A", ImGuiKey_A);
+ context->RegisterEnumValue("ImGuiKey_", "ImGuiKey_C", ImGuiKey_C);
+ context->RegisterEnumValue("ImGuiKey_", "ImGuiKey_V", ImGuiKey_V);
+ context->RegisterEnumValue("ImGuiKey_", "ImGuiKey_X", ImGuiKey_X);
+ context->RegisterEnumValue("ImGuiKey_", "ImGuiKey_Y", ImGuiKey_Y);
+ context->RegisterEnumValue("ImGuiKey_", "ImGuiKey_Z", ImGuiKey_Z);
+ context->RegisterEnumValue("ImGuiKey_", "ImGuiKey_COUNT", ImGuiKey_COUNT);
+
+ // Enumeration for PushStyleColor() / PopStyleColor()
+ context->RegisterEnum("ImGuiCol_");
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_Text", ImGuiCol_Text);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_TextDisabled", ImGuiCol_TextDisabled);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_WindowBg", ImGuiCol_WindowBg);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_ChildWindowBg", ImGuiCol_ChildBg);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_ChildBg", ImGuiCol_ChildBg);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_PopupBg", ImGuiCol_PopupBg);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_Border", ImGuiCol_Border);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_BorderShadow", ImGuiCol_BorderShadow);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_FrameBg", ImGuiCol_FrameBg);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_FrameBgHovered", ImGuiCol_FrameBgHovered);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_FrameBgActive", ImGuiCol_FrameBgActive);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_TitleBg", ImGuiCol_TitleBg);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_TitleBgCollapsed", ImGuiCol_TitleBgCollapsed);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_TitleBgActive", ImGuiCol_TitleBgActive);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_MenuBarBg", ImGuiCol_MenuBarBg);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_ScrollbarBg", ImGuiCol_ScrollbarBg);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_ScrollbarGrab", ImGuiCol_ScrollbarGrab);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_ScrollbarGrabHovered", ImGuiCol_ScrollbarGrabHovered);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_ScrollbarGrabActive", ImGuiCol_ScrollbarGrabActive);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_ComboBg", ImGuiCol_PopupBg);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_CheckMark", ImGuiCol_CheckMark);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_SliderGrab", ImGuiCol_SliderGrab);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_SliderGrabActive", ImGuiCol_SliderGrabActive);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_Button", ImGuiCol_Button);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_ButtonHovered", ImGuiCol_ButtonHovered);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_ButtonActive", ImGuiCol_ButtonActive);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_Header", ImGuiCol_Header);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_HeaderHovered", ImGuiCol_HeaderHovered);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_HeaderActive", ImGuiCol_HeaderActive);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_Column", ImGuiCol_Separator);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_ColumnHovered", ImGuiCol_SeparatorHovered);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_ColumnActive", ImGuiCol_SeparatorActive);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_ResizeGrip", ImGuiCol_ResizeGrip);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_ResizeGripHovered", ImGuiCol_ResizeGripHovered);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_ResizeGripActive", ImGuiCol_ResizeGripActive);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_CloseButton", ImGuiCol_Button); // Obsolete, Equal to "ImGuiCol_Button"!
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_CloseButtonHovered", ImGuiCol_ButtonHovered); // Obsolete, Equal to "ImGuiCol_ButtonHovered"!
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_CloseButtonActive", ImGuiCol_ButtonActive); // Obsolete, Equal to "ImGuiCol_ButtonActive"!
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_PlotLines", ImGuiCol_PlotLines);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_PlotLinesHovered", ImGuiCol_PlotLinesHovered);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_PlotHistogram", ImGuiCol_PlotHistogram);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_PlotHistogramHovered", ImGuiCol_PlotHistogramHovered);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_TextSelectedBg", ImGuiCol_TextSelectedBg);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_ModalWindowDarkening", ImGuiCol_ModalWindowDimBg);
+ context->RegisterEnumValue("ImGuiCol_","ImGuiCol_COUNT", ImGuiCol_COUNT);
+
+ // Enumeration for PushStyleVar() / PopStyleVar()
+ // NB: the enum only refers to fields of ImGuiStyle() which makes sense to be pushed/poped in UI code. Feel free to add others.
+ context->RegisterEnum("ImGuiStyleVar_");
+ context->RegisterEnumValue("ImGuiStyleVar_", "ImGuiStyleVar_Alpha", ImGuiStyleVar_Alpha); // float
+ context->RegisterEnumValue("ImGuiStyleVar_", "ImGuiStyleVar_WindowPadding", ImGuiStyleVar_WindowPadding); // ImVec2
+ context->RegisterEnumValue("ImGuiStyleVar_", "ImGuiStyleVar_WindowRounding", ImGuiStyleVar_WindowRounding); // float
+ context->RegisterEnumValue("ImGuiStyleVar_", "ImGuiStyleVar_WindowMinSize", ImGuiStyleVar_WindowMinSize); // ImVec2
+ context->RegisterEnumValue("ImGuiStyleVar_", "ImGuiStyleVar_ChildWindowRounding", ImGuiStyleVar_ChildRounding); // float
+ context->RegisterEnumValue("ImGuiStyleVar_", "ImGuiStyleVar_ChildRounding", ImGuiStyleVar_ChildRounding); // float
+ context->RegisterEnumValue("ImGuiStyleVar_", "ImGuiStyleVar_FramePadding", ImGuiStyleVar_FramePadding); // ImVec2
+ context->RegisterEnumValue("ImGuiStyleVar_", "ImGuiStyleVar_FrameRounding", ImGuiStyleVar_FrameRounding); // float
+ context->RegisterEnumValue("ImGuiStyleVar_", "ImGuiStyleVar_ItemSpacing", ImGuiStyleVar_ItemSpacing); // ImVec2
+ context->RegisterEnumValue("ImGuiStyleVar_", "ImGuiStyleVar_ItemInnerSpacing", ImGuiStyleVar_ItemInnerSpacing); // ImVec2
+ context->RegisterEnumValue("ImGuiStyleVar_", "ImGuiStyleVar_IndentSpacing", ImGuiStyleVar_IndentSpacing); // float
+ context->RegisterEnumValue("ImGuiStyleVar_", "ImGuiStyleVar_GrabMinSize", ImGuiStyleVar_GrabMinSize); // float
+ context->RegisterEnumValue("ImGuiStyleVar_", "ImGuiStyleVar_ButtonTextAlign", ImGuiStyleVar_ButtonTextAlign); // flags ImGuiAlign_*
+ // context->RegisterEnumValue("ImGuiStyleVar_", "ImGuiStyleVar_Enabled", ImGuiStyleVar_Enabled);
+ context->RegisterEnumValue("ImGuiStyleVar_", "ImGuiStyleVar_Count_", ImGuiStyleVar_COUNT);
+
+ // Enumeration for EditColor4/ColorPicker4
+ context->RegisterEnum("ImGuiColorEditFlags_");
+ context->RegisterEnumValue("ImGuiColorEditFlags_", "ImGuiColorEditFlags_NoAlpha", ImGuiColorEditFlags_NoAlpha);
+ context->RegisterEnumValue("ImGuiColorEditFlags_", "ImGuiColorEditFlags_NoPicker", ImGuiColorEditFlags_NoPicker);
+ context->RegisterEnumValue("ImGuiColorEditFlags_", "ImGuiColorEditFlags_NoOptions", ImGuiColorEditFlags_NoOptions);
+ context->RegisterEnumValue("ImGuiColorEditFlags_", "ImGuiColorEditFlags_NoSmallPreview", ImGuiColorEditFlags_NoSmallPreview);
+ context->RegisterEnumValue("ImGuiColorEditFlags_", "ImGuiColorEditFlags_NoInputs", ImGuiColorEditFlags_NoInputs);
+ context->RegisterEnumValue("ImGuiColorEditFlags_", "ImGuiColorEditFlags_NoTooltip", ImGuiColorEditFlags_NoTooltip);
+ context->RegisterEnumValue("ImGuiColorEditFlags_", "ImGuiColorEditFlags_NoLabel", ImGuiColorEditFlags_NoLabel);
+ context->RegisterEnumValue("ImGuiColorEditFlags_", "ImGuiColorEditFlags_NoSidePreview", ImGuiColorEditFlags_NoSidePreview);
+ context->RegisterEnumValue("ImGuiColorEditFlags_", "ImGuiColorEditFlags_AlphaBar", ImGuiColorEditFlags_AlphaBar);
+ context->RegisterEnumValue("ImGuiColorEditFlags_", "ImGuiColorEditFlags_AlphaPreview", ImGuiColorEditFlags_AlphaPreview);
+ context->RegisterEnumValue("ImGuiColorEditFlags_", "ImGuiColorEditFlags_AlphaPreviewHalf", ImGuiColorEditFlags_AlphaPreviewHalf);
+ context->RegisterEnumValue("ImGuiColorEditFlags_", "ImGuiColorEditFlags_HDR", ImGuiColorEditFlags_HDR);
+ context->RegisterEnumValue("ImGuiColorEditFlags_", "ImGuiColorEditFlags_RGB", ImGuiColorEditFlags_RGB);
+ context->RegisterEnumValue("ImGuiColorEditFlags_", "ImGuiColorEditFlags_HSV", ImGuiColorEditFlags_HSV);
+ context->RegisterEnumValue("ImGuiColorEditFlags_", "ImGuiColorEditFlags_HEX", ImGuiColorEditFlags_HEX);
+ context->RegisterEnumValue("ImGuiColorEditFlags_", "ImGuiColorEditFlags_Uint8", ImGuiColorEditFlags_Uint8);
+ context->RegisterEnumValue("ImGuiColorEditFlags_", "ImGuiColorEditFlags_Float", ImGuiColorEditFlags_Float);
+ context->RegisterEnumValue("ImGuiColorEditFlags_", "ImGuiColorEditFlags_PickerHueBar", ImGuiColorEditFlags_PickerHueBar);
+ context->RegisterEnumValue("ImGuiColorEditFlags_", "ImGuiColorEditFlags_PickerHueWheel", ImGuiColorEditFlags_PickerHueWheel);
+
+ // Enumeration for ColorEditMode()
+ // This is from an old imgui version, but kept for backwards compatibility
+ context->RegisterEnum("ImGuiColorEditMode_");
+ context->RegisterEnumValue("ImGuiColorEditMode_", "ImGuiColorEditMode_UserSelect", -2); //ImGuiColorEditMode_UserSelect
+ context->RegisterEnumValue("ImGuiColorEditMode_", "ImGuiColorEditMode_UserSelectShowButton", -1);//ImGuiColorEditMode_UserSelectShowButton
+ context->RegisterEnumValue("ImGuiColorEditMode_", "ImGuiColorEditMode_RGB", 0); //ImGuiColorEditMode_RGB
+ context->RegisterEnumValue("ImGuiColorEditMode_", "ImGuiColorEditMode_HSV", 1); //ImGuiColorEditMode_HSV
+ context->RegisterEnumValue("ImGuiColorEditMode_", "ImGuiColorEditMode_HEX", 2); //ImGuiColorEditMode_HEX
+
+ // Condition flags for ImGui::SetWindow***(), SetNextWindow***(), SetNextTreeNode***() functions
+ context->RegisterEnum("ImGuiSetCond_");
+ context->RegisterEnumValue("ImGuiSetCond_", "ImGuiCond_Always", ImGuiCond_Always);
+ context->RegisterEnumValue("ImGuiSetCond_", "ImGuiCond_Once", ImGuiCond_Once);
+ context->RegisterEnumValue("ImGuiSetCond_", "ImGuiCond_FirstUseEver", ImGuiCond_FirstUseEver);
+ context->RegisterEnumValue("ImGuiSetCond_", "ImGuiCond_Appearing", ImGuiCond_Appearing);
+
+ context->RegisterEnumValue("ImGuiSetCond_", "ImGuiSetCond_Always", ImGuiCond_Always); // Obsolete, Equal to "ImGuiCond_Always"!
+ context->RegisterEnumValue("ImGuiSetCond_", "ImGuiSetCond_Once", ImGuiCond_Once); // Obsolete, Equal to "ImGuiCond_Once"!
+ context->RegisterEnumValue("ImGuiSetCond_", "ImGuiSetCond_FirstUseEver", ImGuiCond_FirstUseEver); // Obsolete, Equal to "ImGuiCond_FirstUseEver"!
+ context->RegisterEnumValue("ImGuiSetCond_", "ImGuiSetCond_Appearing", ImGuiCond_Appearing); // Obsolete, Equal to "ImGuiCond_Appearing"!
+
+ // Primitives
+ context->RegisterGlobalFunction("void ImDrawList_AddLine(const vec2 &in a, const vec2 &in b, uint32 col, float thickness = 1.0f)", asFUNCTION(AS_ImDrawList_AddLine), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImDrawList_AddRect(const vec2 &in a, const vec2 &in b, uint32 col, float rounding = 0.0f, int rounding_corners_flags = 0xF, float thickness = 1.0f)", asFUNCTION(AS_ImDrawList_AddRect), asCALL_CDECL, "void ImDrawList_AddRect(const vec2 &in a, const vec2 &in b, uint32 col, float rounding = 0.0f, int rounding_corners_flags = ImDrawCornerFlags_All, float thickness = 1.0f) - a: upper-left, b: lower-right, rounding_corners_flags: 4-bits corresponding to which corner to round");
+ context->RegisterGlobalFunction("void ImDrawList_AddRectFilled(const vec2 &in a, const vec2 &in b, uint32 col, float rounding = 0.0f, int rounding_corners_flags = 0xF)", asFUNCTION(AS_ImDrawList_AddRectFilled), asCALL_CDECL, "void ImDrawList_AddRectFilled(const vec2 &in a, const vec2 &in b, uint32 col, float rounding = 0.0f, int rounding_corners_flags = ImDrawCornerFlags_All) - a: upper-left, b: lower-right");
+ context->RegisterGlobalFunction("void ImDrawList_AddRectFilledMultiColor(const vec2 &in a, const vec2 &in b, uint32 col_upr_left, uint32 col_upr_right, uint32 col_bot_right, uint32 col_bot_left)", asFUNCTION(AS_ImDrawList_AddRectFilledMultiColor), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImDrawList_AddQuad(const vec2 &in a, const vec2 &in b, const vec2 &in c, const vec2 &in d, uint32 col, float thickness = 1.0f)", asFUNCTION(AS_ImDrawList_AddQuad), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImDrawList_AddQuadFilled(const vec2 &in a, const vec2 &in b, const vec2 &in c, const vec2 &in d, uint32 col)", asFUNCTION(AS_ImDrawList_AddQuadFilled), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImDrawList_AddTriangle(const vec2 &in a, const vec2 &in b, const vec2 &in c, uint32 col, float thickness = 1.0f)", asFUNCTION(AS_ImDrawList_AddTriangle), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImDrawList_AddTriangleFilled(const vec2 &in a, const vec2 &in b, const vec2 &in c, uint32 col)", asFUNCTION(AS_ImDrawList_AddTriangleFilled), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImDrawList_AddCircle(const vec2 &in center, float radius, uint32 col, int num_segments = 12, float thickness = 1.0f)", asFUNCTION(AS_ImDrawList_AddCircle), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImDrawList_AddCircleFilled(const vec2 &in center, float radius, uint32 col, int num_segments = 12)", asFUNCTION(AS_ImDrawList_AddCircleFilled), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImDrawList_AddText(const vec2 &in pos, uint32 col, const string &in text)", asFUNCTION(AS_ImDrawList_AddText), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImDrawList_AddImage(const TextureAssetRef &in texture, const vec2 &in a, const vec2 &in b, const vec2 &in uv_a = vec2(0, 0), const vec2 &in uv_b = vec2(1, 1), uint32 tint_color = 0xFFFFFFFF)", asFUNCTION(AS_ImDrawList_AddImage), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImDrawList_AddImageQuad(const TextureAssetRef &in texture, const vec2 &in a, const vec2 &in b, const vec2 &in c, const vec2 &in d, const vec2 &in uv_a = vec2(0, 0), const vec2 &in uv_b = vec2(1, 0), const vec2 &in uv_c = vec2(1, 1), const vec2 &in uv_d = vec2(0, 1), uint32 tint_color = 0xFFFFFFFF)", asFUNCTION(AS_ImDrawList_AddImageQuad), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImDrawList_AddImageRounded(const TextureAssetRef &in texture, const vec2 &in a, const vec2 &in b, const vec2 &in uv_a, const vec2 &in uv_b, uint32 tint_color, float rounding, int rounding_corners = 0xF)", asFUNCTION(AS_ImDrawList_AddImageRounded), asCALL_CDECL, "void ImDrawList_AddImageRounded(const TextureAssetRef &in texture, const vec2 &in a, const vec2 &in b, const vec2 &in uv_a, const vec2 &in uv_b, uint32 tint_color, float rounding, int rounding_corners = ImDrawCornerFlags_All)");
+ context->RegisterGlobalFunction("void ImDrawList_AddBezierCurve(const vec2 &in pos0, const vec2 &in cp0, const vec2 &in cp1, const vec2 &in pos1, uint32 col, float thickness, int num_segments = 0)", asFUNCTION(AS_ImDrawList_AddBezierCurve), asCALL_CDECL);
+
+ // Stateful path API, add points then finish with PathFill() or PathStroke()
+ context->RegisterGlobalFunction("void ImDrawList_PathClear()", asFUNCTION(AS_ImDrawList_PathClear), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImDrawList_PathLineTo(const vec2 &in pos)", asFUNCTION(AS_ImDrawList_PathLineTo), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImDrawList_PathLineToMergeDuplicate(const vec2 &in pos)", asFUNCTION(AS_ImDrawList_PathLineToMergeDuplicate), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImDrawList_PathFillConvex(uint32 col)", asFUNCTION(AS_ImDrawList_PathFillConvex), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImDrawList_PathStroke(uint32 col, bool closed, float thickness = 1.0f)", asFUNCTION(AS_ImDrawList_PathStroke), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImDrawList_PathArcTo(const vec2 &in center, float radius, float a_min, float a_max, int num_segments = 10)", asFUNCTION(AS_ImDrawList_PathArcTo), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImDrawList_PathArcToFast(const vec2 &in center, float radius, int a_min_of_12, int a_max_of_12)", asFUNCTION(AS_ImDrawList_PathArcToFast), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImDrawList_PathBezierCurveTo(const vec2 &in p1, const vec2 &in p2, const vec2 &in p3, int num_segments = 0)", asFUNCTION(AS_ImDrawList_PathBezierCurveTo), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImDrawList_PathRect(const vec2 &in rect_min, const vec2 &in rect_max, float rounding = 0.0f, int rounding_corners_flags = 0xF)", asFUNCTION(AS_ImDrawList_PathRect), asCALL_CDECL, "void ImDrawList_PathRect(const vec2 &in rect_min, const vec2 &in rect_max, float rounding = 0.0f, ImDrawCornerFlags rounding_corners_flags = ImDrawCornerFlags_All)");
+
+ // flags: for ImDrawList::AddRect*() etc.
+ context->RegisterEnum("ImDrawCornerFlags_");
+ context->RegisterEnumValue("ImDrawCornerFlags_", "ImDrawCornerFlags_TopLeft", ImDrawCornerFlags_TopLeft);
+ context->RegisterEnumValue("ImDrawCornerFlags_", "ImDrawCornerFlags_TopRight", ImDrawCornerFlags_TopRight);
+ context->RegisterEnumValue("ImDrawCornerFlags_", "ImDrawCornerFlags_BotLeft", ImDrawCornerFlags_BotLeft);
+ context->RegisterEnumValue("ImDrawCornerFlags_", "ImDrawCornerFlags_BotRight", ImDrawCornerFlags_BotRight);
+ context->RegisterEnumValue("ImDrawCornerFlags_", "ImDrawCornerFlags_Top", ImDrawCornerFlags_Top);
+ context->RegisterEnumValue("ImDrawCornerFlags_", "ImDrawCornerFlags_Bot", ImDrawCornerFlags_Bot);
+ context->RegisterEnumValue("ImDrawCornerFlags_", "ImDrawCornerFlags_Left", ImDrawCornerFlags_Left);
+ context->RegisterEnumValue("ImDrawCornerFlags_", "ImDrawCornerFlags_Right", ImDrawCornerFlags_Right);
+ context->RegisterEnumValue("ImDrawCornerFlags_", "ImDrawCornerFlags_All", ImDrawCornerFlags_All);
+
+ // Phoenix engine custom state
+ context->RegisterGlobalFunction("string ImGui_GetTextBuf()", asFUNCTION(AS_ImGui_GetTextBuf), asCALL_CDECL);
+ context->RegisterGlobalFunction("void ImGui_SetTextBuf(const string &in value)", asFUNCTION(AS_ImGui_SetTextBuf), asCALL_CDECL);
+ context->RegisterGlobalProperty("int imgui_text_input_CursorPos", &imgui_text_input_CursorPos);
+ context->RegisterGlobalProperty("int imgui_text_input_SelectionStart", &imgui_text_input_SelectionStart);
+ context->RegisterGlobalProperty("int imgui_text_input_SelectionEnd", &imgui_text_input_SelectionEnd);
+
+ // Phoenix engine custom functions
+ context->RegisterGlobalFunction("void ImGui_DrawSettings()", asFUNCTION(AS_ImGui_DrawSettings), asCALL_CDECL);
+
+ // TODO: Move these elsewhere!
+ // These are not Dear ImGui functions, so do not belong here
+ context->RegisterGlobalFunction("string GetUserPickedWritePath(const string &in suffix, const string &in default_path)", asFUNCTION(GetUserPickedWritePath), asCALL_CDECL);
+ context->RegisterGlobalFunction("string GetUserPickedReadPath(const string &in suffix, const string &in default_path)", asFUNCTION(GetUserPickedReadPath), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("string FindShortestPath(const string &in path)", asFUNCTION(AS_FindShortestPath), asCALL_CDECL);
+ context->RegisterGlobalFunction("string FindFilePath(const string &in path)", asFUNCTION(AS_FindFilePath), asCALL_CDECL);
+}
+
+NavPath ASGetPath2(vec3 start, vec3 end, uint16_t inclusive_poly_flags, uint16_t exclusive_poly_flags ) {
+ if(the_scenegraph->GetNavMesh()) {
+ return the_scenegraph->GetNavMesh()->FindPath(start, end, inclusive_poly_flags, exclusive_poly_flags);
+ } else {
+ NavPath path;
+ path.waypoints.push_back(end);
+ return path;
+ }
+}
+
+NavPath ASGetPath(vec3 start, vec3 end) {
+ return ASGetPath2(
+ start,
+ end,
+ SAMPLE_POLYFLAGS_ALL ,
+ SAMPLE_POLYFLAGS_NONE
+ );
+}
+
+vec3 ASNavRaycast(vec3 start, vec3 end) {
+ if(the_scenegraph->GetNavMesh()) {
+ return the_scenegraph->GetNavMesh()->RayCast(start, end);
+ } else {
+ return end;
+ }
+}
+
+vec3 ASNavRaycastSlide(vec3 start, vec3 end, int depth) {
+ if(the_scenegraph->GetNavMesh()) {
+ return the_scenegraph->GetNavMesh()->RayCastSlide(start, end, depth);
+ } else {
+ return end;
+ }
+}
+
+static NavPoint ASGetNavPoint(vec3 point) {
+ if(the_scenegraph->GetNavMesh()) {
+ return the_scenegraph->GetNavMesh()->GetNavPoint(point);
+ } else {
+ return NavPoint(point);
+ }
+}
+
+static vec3 ASGetNavPointPos(vec3 point) {
+ if(the_scenegraph->GetNavMesh()) {
+ return the_scenegraph->GetNavMesh()->GetNavPoint(point).GetPoint();
+ } else {
+ return point;
+ }
+}
+
+static void NavPathConstructor(void *memory) {
+ new(memory) NavPath();
+}
+
+static void NavPathDestructor(void *memory) {
+ ((NavPath*)memory)->~NavPath();
+}
+
+static const NavPath& NavPathAssign(NavPath *self, const NavPath& other) {
+ return (*self) = other;
+}
+
+static void NavPathCopyConstructor(void *memory, const NavPath& other) {
+ new(memory) NavPath(other);
+}
+
+
+static void NavPointConstructor(void *memory) {
+ new(memory) NavPoint();
+}
+
+static void NavPointDestructor(void *memory) {
+ ((NavPoint*)memory)->~NavPoint();
+}
+
+static const NavPoint& NavPointAssign(NavPoint *self, const NavPoint& other) {
+ return (*self) = other;
+}
+
+static void NavPointCopyConstructor(void *memory, const NavPoint& other) {
+ new(memory) NavPoint(other);
+}
+
+void AttachNavMesh(ASContext *context) {
+ context->RegisterEnum("SamplePolyFlag");
+ context->RegisterEnumValue("SamplePolyFlag", "POLYFLAGS_NONE", SAMPLE_POLYFLAGS_NONE);
+ context->RegisterEnumValue("SamplePolyFlag", "POLYFLAGS_WALK", SAMPLE_POLYFLAGS_WALK);
+ context->RegisterEnumValue("SamplePolyFlag", "POLYFLAGS_SWIM", SAMPLE_POLYFLAGS_SWIM);
+ context->RegisterEnumValue("SamplePolyFlag", "POLYFLAGS_DOOR", SAMPLE_POLYFLAGS_DOOR);
+ context->RegisterEnumValue("SamplePolyFlag", "POLYFLAGS_JUMP1", SAMPLE_POLYFLAGS_JUMP1);
+ context->RegisterEnumValue("SamplePolyFlag", "POLYFLAGS_JUMP2", SAMPLE_POLYFLAGS_JUMP2);
+ context->RegisterEnumValue("SamplePolyFlag", "POLYFLAGS_JUMP3", SAMPLE_POLYFLAGS_JUMP3);
+ context->RegisterEnumValue("SamplePolyFlag", "POLYFLAGS_JUMP4", SAMPLE_POLYFLAGS_JUMP4);
+ context->RegisterEnumValue("SamplePolyFlag", "POLYFLAGS_JUMP5", SAMPLE_POLYFLAGS_JUMP5);
+ context->RegisterEnumValue("SamplePolyFlag", "POLYFLAGS_JUMP_ALL", SAMPLE_POLYFLAGS_JUMP_ALL);
+ context->RegisterEnumValue("SamplePolyFlag", "POLYFLAGS_DISABLED", SAMPLE_POLYFLAGS_DISABLED);
+ context->RegisterEnumValue("SamplePolyFlag", "POLYFLAGS_ALL", SAMPLE_POLYFLAGS_ALL);
+
+ context->RegisterEnum("NavPathFlag");
+ context->RegisterEnumValue("NavPathFlag", "DT_STRAIGHTPATH_START", DT_STRAIGHTPATH_START);
+ context->RegisterEnumValue("NavPathFlag", "DT_STRAIGHTPATH_END", DT_STRAIGHTPATH_END);
+ context->RegisterEnumValue("NavPathFlag", "DT_STRAIGHTPATH_OFFMESH_CONNECTION", DT_STRAIGHTPATH_OFFMESH_CONNECTION);
+
+ context->RegisterObjectType("NavPath", sizeof(NavPath), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK);
+ context->RegisterObjectBehaviour("NavPath", asBEHAVE_CONSTRUCT, "void NavPath()", asFUNCTION(NavPathConstructor), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectBehaviour("NavPath", asBEHAVE_CONSTRUCT, "void NavPath(const NavPath &in other)", asFUNCTION(NavPathCopyConstructor), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectBehaviour("NavPath", asBEHAVE_DESTRUCT, "void NavPath()", asFUNCTION(NavPathDestructor), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("NavPath", "NavPath& opAssign(const NavPath &in other)", asFUNCTION(NavPathAssign), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectProperty("NavPath","bool success",asOFFSET(NavPath,success));
+ context->RegisterObjectMethod("NavPath","int NumPoints()",asMETHOD(NavPath, NumPoints), asCALL_THISCALL);
+ context->RegisterObjectMethod("NavPath","vec3 GetPoint(int)",asMETHOD(NavPath, GetPoint), asCALL_THISCALL);
+ context->RegisterObjectMethod("NavPath","uint8 GetFlag(int)",asMETHOD(NavPath, GetFlag), asCALL_THISCALL);
+ context->DocsCloseBrace();
+
+ context->RegisterObjectType("NavPoint", sizeof(NavPoint), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK);
+ context->RegisterObjectBehaviour("NavPoint", asBEHAVE_CONSTRUCT, "void NavPoint()", asFUNCTION(NavPointConstructor), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectBehaviour("NavPoint", asBEHAVE_CONSTRUCT, "void NavPoint(const NavPoint &in other)", asFUNCTION(NavPointCopyConstructor), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectBehaviour("NavPoint", asBEHAVE_DESTRUCT, "void NavPoint()", asFUNCTION(NavPointDestructor), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("NavPoint", "NavPoint& opAssign(const NavPoint &in other)", asFUNCTION(NavPointAssign), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectMethod("NavPoint","bool IsSuccess()",asMETHOD(NavPoint, IsSuccess), asCALL_THISCALL);
+ context->RegisterObjectMethod("NavPoint","vec3 GetPoint()",asMETHOD(NavPoint, GetPoint), asCALL_THISCALL);
+ context->DocsCloseBrace();
+
+ context->RegisterGlobalFunction("NavPath GetPath(vec3 start, vec3 end)",asFUNCTION(ASGetPath), asCALL_CDECL);
+ context->RegisterGlobalFunction("NavPath GetPath(vec3 start, vec3 end, uint16 include_poly_flags, uint16 exclude_poly_flags)",asFUNCTION(ASGetPath2), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec3 NavRaycast(vec3 start, vec3 end)",asFUNCTION(ASNavRaycast), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec3 NavRaycastSlide(vec3 start, vec3 end, int depth)",asFUNCTION(ASNavRaycastSlide), asCALL_CDECL);
+ context->RegisterGlobalFunction("NavPoint GetNavPoint(vec3)",asFUNCTION(ASGetNavPoint), asCALL_CDECL);
+ context->RegisterGlobalFunction("vec3 GetNavPointPos(vec3)",asFUNCTION(ASGetNavPointPos), asCALL_CDECL);
+}
+
+const int as_plant_movement_msg = _plant_movement_msg;
+const int as_editor_msg = _editor_msg;
+
+void ASPrintCallstack() {
+ std::string callstack = active_context_stack.top()->GetCallstack();
+ LOGI << "Callstack: \n" << callstack << std::endl;
+}
+
+void ASSendMessage( int target, int type, vec3 vec_a, vec3 vec_b ) {
+ Object* obj = the_scenegraph->GetObjectFromID(target);
+ if(!obj){
+ std::string callstack = active_context_stack.top()->GetCallstack();
+ std::ostringstream oss;
+ oss << "No object with id: " << target << "\n";
+ oss << "Called from:\n" << callstack;
+ DisplayError("Error", oss.str().c_str());
+ return;
+ }
+ obj->ReceiveASVec3Message(type, vec_a, vec_b);
+}
+
+void ASSendMessageString( int type, std::string msg ) {
+ the_scenegraph->map_editor->ReceiveMessage(msg);
+}
+
+static void ASSendGlobalMessage( std::string msg ) {
+ SceneGraph* s = Engine::Instance()->GetSceneGraph();
+ if( s ) {
+ s->SendScriptMessageToAllObjects(msg);
+ s->level->Message(msg);
+ }
+
+ ScriptableCampaign *sc = Engine::Instance()->GetCurrentCampaign();
+
+ if( sc ) {
+ sc->ReceiveMessage(msg);
+ }
+}
+
+void AttachMessages( ASContext *context ) {
+ context->RegisterGlobalFunction("void SendMessage(int target, int type, vec3 vec_a, vec3 vec_b)",asFUNCTION(ASSendMessage), asCALL_CDECL);
+ context->RegisterGlobalFunction("void SendMessage(int type, string msg)",asFUNCTION(ASSendMessageString), asCALL_CDECL);
+ context->RegisterGlobalFunction("void SendGlobalMessage(string msg)", asFUNCTION(ASSendGlobalMessage), asCALL_CDECL);
+ context->RegisterGlobalProperty("int _plant_movement_msg", (void*)&as_plant_movement_msg);
+ context->RegisterGlobalProperty("int _editor_msg", (void*)&as_editor_msg);
+}
+
+float ASatof(const std::string& str) {
+ return (float)atof(str.c_str());
+}
+
+int ASatoi(const std::string& str) {
+ return atoi(str.c_str());
+}
+
+static void ASLoadLevel(std::string path) {
+ Engine* engine = Engine::Instance();
+ engine->ScriptableUICallback(path);
+}
+
+static void ASLoadLevelID(std::string id) {
+ Engine* engine = Engine::Instance();
+ Online* online = Online::Instance();
+
+ if (online->IsHosting()) {
+ //mp.changeLevel(id);
+ }
+
+ engine->ScriptableUICallback("load_campaign_level " + id);
+}
+
+static void ASSetCampaignID(std::string id) {
+ Engine* engine = Engine::Instance();
+ engine->ScriptableUICallback("set_campaign " + id);
+}
+
+static std::string ASGetCurrLevelAbsPath() {
+ SceneGraph *s = Engine::Instance()->GetSceneGraph();
+ if( s ) {
+ return s->level_path_.GetAbsPathStr();
+ } else {
+ return "";
+ }
+}
+
+static std::string ASGetCurrLevel() {
+ SceneGraph *s = Engine::Instance()->GetSceneGraph();
+ if( s ) {
+ return s->level_path_.GetFullPath();
+ } else {
+ return "";
+ }
+}
+
+static std::string ASGetCurrLevelID() {
+ return Engine::Instance()->GetCurrentLevelID();
+}
+
+static std::string ASGetCurrCampaignID() {
+ ScriptableCampaign* sc = Engine::Instance()->GetCurrentCampaign();
+ if( sc ) {
+ return sc->GetCampaignID();
+ } else {
+ return "";
+ }
+}
+
+static std::string ASGetCurrLevelRelPath() {
+ SceneGraph *s = Engine::Instance()->GetSceneGraph();
+ if( s ) {
+ return s->level_path_.GetOriginalPath();
+ } else {
+ return "";
+ }
+}
+
+static std::string ASGetLevelName( const std::string& path) {
+ if(FileExists(path,kAnyPath)){
+ LevelInfoAssetRef levelinfo = Engine::Instance()->GetAssetManager()->LoadSync<LevelInfoAsset>(path);
+
+ if( levelinfo.valid() ) {
+ return levelinfo->GetLevelName();
+ } else {
+ LOGW << "Failed to load levelinfo for " << path << std::endl;
+ }
+ } else {
+ LOGW << "Failed to load levelinfo for " << path << std::endl;
+ }
+ return "";
+}
+
+static std::string ASGetCurrLevelName() {
+ return ASGetLevelName(ASGetCurrLevelAbsPath());
+}
+
+static std::string ASGetCurrentMenuModsourceID() {
+ return ModLoading::Instance().GetModID(Engine::Instance()->GetLatestMenuPath().GetModsource());
+}
+
+static std::string ASGetCurrentLevelModsourceID() {
+ return ModLoading::Instance().GetModID(Engine::Instance()->GetLatestLevelPath().GetModsource());
+}
+
+static std::string ASGetCurrentCampaignModsourceID() {
+// return ModLoading::Instance().GetModID(Engine::Instnace()->GetLatestCampaign
+ return "";
+}
+
+static bool ASEditorModeActive() {
+ SceneGraph* s = Engine::Instance()->GetSceneGraph();
+ if( s ) {
+ return s->map_editor->state_ != MapEditor::kInGame;
+ } else {
+ return false;
+ }
+}
+
+static void ASAssert(asIScriptGeneric *gen) {
+ bool arg0 = gen->GetArgByte(0);
+ if(arg0 == 0) {
+ LOGE << "Failed assert in angelscript" << std::endl;
+ }
+}
+
+static void ASSetSplitScreenMode(ForcedSplitScreenMode mode) {
+ Engine::Instance()->SetForcedSplitScreenMode(mode);
+}
+
+void AttachEngine(ASContext *context)
+{
+ context->RegisterEnum("EngineState");
+ context->RegisterEnumValue("EngineState", "kEngineNoState", kEngineNoState);
+ context->RegisterEnumValue("EngineState", "kEngineLevelState", kEngineLevelState);
+ context->RegisterEnumValue("EngineState", "kEngineEditorLevelState", kEngineEditorLevelState);
+ context->RegisterEnumValue("EngineState", "kEngineEngineScriptableUIState", kEngineScriptableUIState);
+ context->RegisterEnumValue("EngineState", "kEngineCampaignState", kEngineCampaignState);
+
+ context->RegisterEnum("SplitScreenMode");
+ context->RegisterEnumValue("SplitScreenMode", "kModeNone", kForcedModeNone);
+ context->RegisterEnumValue("SplitScreenMode", "kModeFull", kForcedModeFull);
+ context->RegisterEnumValue("SplitScreenMode", "kModeSplit", kForcedModeSplit);
+
+ context->RegisterGlobalFunction("void LoadLevel(string level_path)",
+ asFUNCTION(ASLoadLevel), asCALL_CDECL);
+ context->RegisterGlobalFunction("void LoadLevelID(string id)",
+ asFUNCTION(ASLoadLevelID), asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetCampaignID(string id)",
+ asFUNCTION(ASSetCampaignID), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("string GetCurrLevelAbsPath()",
+ asFUNCTION(ASGetCurrLevelAbsPath), asCALL_CDECL);
+ context->RegisterGlobalFunction("string GetCurrLevel()",
+ asFUNCTION(ASGetCurrLevel), asCALL_CDECL);
+ context->RegisterGlobalFunction("string GetCurrLevelRelPath()",
+ asFUNCTION(ASGetCurrLevelRelPath), asCALL_CDECL);
+ context->RegisterGlobalFunction("string GetLevelName(const string& path)",
+ asFUNCTION(ASGetLevelName), asCALL_CDECL);
+ context->RegisterGlobalFunction("string GetCurrLevelName()",
+ asFUNCTION(ASGetCurrLevelName), asCALL_CDECL);
+ context->RegisterGlobalFunction("string GetCurrLevelID()",
+ asFUNCTION(ASGetCurrLevelID), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("string GetCurrCampaignID()",
+ asFUNCTION(ASGetCurrCampaignID), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("string GetCurrentMenuModsourceID()",
+ asFUNCTION(ASGetCurrentMenuModsourceID), asCALL_CDECL);
+ context->RegisterGlobalFunction("string GetCurrentLevelModsourceID()",
+ asFUNCTION(ASGetCurrentLevelModsourceID), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("bool EditorModeActive()", asFUNCTION(ASEditorModeActive), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("void assert(bool val)", asFUNCTION(ASAssert), asCALL_GENERIC);
+
+ context->RegisterGlobalFunction("void SetSplitScreenMode(SplitScreenMode mode)", asFUNCTION(ASSetSplitScreenMode), asCALL_CDECL);
+}
+
+bool AS_Online_IsActive() {
+ return Online::Instance()->IsActive();
+}
+
+void AS_Online_SendKillMessage(uint32_t victim_id, uint32_t killer_id) {
+ Online* online = Online::Instance();
+
+ PlayerState victim;
+ PlayerState killer;
+ if (online->TryGetPlayerState(victim, victim_id)) {
+ if (online->TryGetPlayerState(killer, killer_id)) {
+ online->SendRawChatMessage(victim.playername + " was killed by " + killer.playername);
+ } else {
+ online->SendRawChatMessage(victim.playername + " died");
+ }
+ }
+}
+
+void AS_Online_SendChatMessage(const std::string& message) {
+ Online::Instance()->BroadcastChatMessage(message);
+}
+
+void AS_Online_SendChatSystemMessage(const std::string& system_message) {
+ Online::Instance()->SendRawChatMessage(system_message);
+}
+
+void AS_Online_Host() {
+ Online::Instance()->StartHostingMultiplayer();
+}
+
+void AS_Online_Connect(const std::string& ip) {
+ Online::Instance()->ConnectByIPAddress(ip.c_str());
+}
+
+bool AS_Online_IsClient() {
+ return Online::Instance()->IsClient();
+}
+
+void AS_Online_SetAvatarCameraAttachedMode(bool mode) {
+ Online::Instance()->SetAvatarCameraAttachedMode(mode);
+}
+
+bool AS_Online_IsAvatarCameraAttached() {
+ return Online::Instance()->IsAvatarCameraAttached();
+}
+
+bool AS_Online_IsHosting() {
+ return Online::Instance()->IsHosting();
+}
+
+void AS_Online_Close() {
+ Online::Instance()->QueueStopMultiplayer();
+}
+
+void AS_Online_IsLobbyFull() {
+ Online::Instance()->IsLobbyFull();
+}
+
+uint32_t AS_Online_RegisterState(std::string state) {
+ return Online::Instance()->RegisterMPState(state);
+}
+
+void AS_Online_SendStateToPeer(uint32_t state, uint32_t avatar_id, const CScriptArray &data) {
+ const int items_count = data.GetSize();
+ std::vector<uint32_t> array_data;
+ array_data.resize(items_count);
+
+ for (int n = 0; n < items_count; n++) {
+ array_data[n] = (*static_cast<const unsigned int*>(data.At(n)));
+ }
+
+ ASContext * ctx = GetActiveASContext();
+
+ LOGD << "Calling: " << ctx->GetASFunctionNameFromMPState(state) << " on client with state:" << state << std::endl;
+
+ Online::Instance()->Send<OnlineMessages::AngelscriptObjectData>(avatar_id, state, array_data);
+}
+
+void AS_Online_RegisterStateCallback(uint32_t state, std::string function) {
+ ASContext * ctx = GetActiveASContext();
+ ctx->RegisterMPStateCallback(state, function);
+}
+
+void ASCallStateMPCallback(uint32_t state, std::vector<char>& data) {
+ ASContext *ctx = GetActiveASContext();
+ ctx->CallMPCallBack(state, data);
+}
+
+bool AS_Online_IsValidPlayerName(const std::string& name) {
+ return OnlineUtility::IsValidPlayerName(name);
+}
+
+void AS_Online_NetFrameworkHasFriendInviteOverlay() {
+ Online::Instance()->NetFrameworkHasFriendInviteOverlay();
+}
+
+void AS_Online_ActivateInviteDialog() {
+ Online::Instance()->ActivateGameOverlayInviteDialog();
+}
+
+void AS_Online_AddSyncState(uint32_t state, const CScriptArray &data) {
+ // This is the same as bellow, please write helper function instead of copy code
+ const int items_count = data.GetSize();
+ std::vector<char> array_data;
+ array_data.resize(items_count);
+
+ for (int n = 0; n < items_count; n++) {
+ array_data[n] = (*reinterpret_cast<const char*>(data.At(n)));
+ }
+
+ Online::Instance()->AddSyncState(state, array_data);
+}
+
+void AS_Online_SendState(uint32_t state, const CScriptArray &data) {
+ const int items_count = data.GetSize();
+ std::vector<char> array_data;
+ array_data.resize(items_count);
+
+ for (int n = 0; n < items_count; n++) {
+ array_data[n] = (*reinterpret_cast<const char*>(data.At(n)));
+ }
+
+ Online::Instance()->Send<OnlineMessages::AngelscriptData>(state, array_data, false);
+}
+
+bool AS_Online_HasActiveIncompatibleMods() {
+ return OnlineUtility::HasActiveIncompatibleMods();
+}
+
+std::string AS_Online_GetActiveIncompatibleModsString() {
+ return OnlineUtility::GetActiveIncompatibleModsString();
+}
+
+void AttachOnline(ASContext *context) {
+ context->RegisterGlobalFunction("void Online_Connect(string ip)", asFUNCTION(AS_Online_Connect), asCALL_CDECL);
+ context->RegisterGlobalFunction("void Online_Host()", asFUNCTION(AS_Online_Host), asCALL_CDECL);
+ context->RegisterGlobalFunction("void Online_SendKillMessage(uint victim, uint killer)", asFUNCTION(AS_Online_SendKillMessage), asCALL_CDECL);
+ context->RegisterGlobalFunction("void Online_SendChatMessage(string message)", asFUNCTION(AS_Online_SendChatMessage), asCALL_CDECL);
+ context->RegisterGlobalFunction("void Online_SendChatSystemMessage(string system_message)", asFUNCTION(AS_Online_SendChatSystemMessage), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool Online_IsActive()", asFUNCTION(AS_Online_IsActive), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool Online_IsClient(void)", asFUNCTION(AS_Online_IsClient), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool Online_IsHosting(void)", asFUNCTION(AS_Online_IsHosting), asCALL_CDECL);
+ context->RegisterGlobalFunction("void Online_SetAvatarCameraAttachedMode(bool mode)", asFUNCTION(AS_Online_SetAvatarCameraAttachedMode), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool Online_IsAvatarCameraAttached()", asFUNCTION(AS_Online_IsAvatarCameraAttached), asCALL_CDECL);
+ context->RegisterGlobalFunction("void Online_SendStateToPeer(uint state, uint avatar_id, const array<uint>& data)", asFUNCTION(AS_Online_SendStateToPeer), asCALL_CDECL);
+ context->RegisterGlobalFunction("uint Online_RegisterState(string state)", asFUNCTION(AS_Online_RegisterState), asCALL_CDECL);
+ context->RegisterGlobalFunction("uint Online_RegisterStateCallback(uint state, string callback)", asFUNCTION(AS_Online_RegisterStateCallback), asCALL_CDECL);
+ context->RegisterGlobalFunction("void Online_SendState(uint state, const array<uint8>& data)", asFUNCTION(AS_Online_SendState), asCALL_CDECL);
+ context->RegisterGlobalFunction("void Online_AddSyncState(uint state, const array<uint8>& data)", asFUNCTION(AS_Online_AddSyncState), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool Online_IsValidPlayerName(string name)", asFUNCTION(AS_Online_IsValidPlayerName), asCALL_CDECL);
+ context->RegisterGlobalFunction("void Online_Close()", asFUNCTION(AS_Online_Close), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool Online_IsLobbyFull()", asFUNCTION(AS_Online_IsLobbyFull), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool Online_HasActiveIncompatibleMods()", asFUNCTION(AS_Online_HasActiveIncompatibleMods), asCALL_CDECL);
+ context->RegisterGlobalFunction("string Online_GetActiveIncompatibleModsString()", asFUNCTION(AS_Online_GetActiveIncompatibleModsString), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("bool Online_HasFriendInviteOverlay()", asFUNCTION(AS_Online_NetFrameworkHasFriendInviteOverlay), asCALL_CDECL);
+ context->RegisterGlobalFunction("void Online_ActivateInviteDialog()", asFUNCTION(AS_Online_ActivateInviteDialog), asCALL_CDECL);
+}
+
+void AttachStringConvert( ASContext *context ) {
+ context->RegisterGlobalFunction("float atof(const string &in str)", asFUNCTION(ASatof), asCALL_CDECL);
+ context->RegisterGlobalFunction("int atoi(const string &in str)", asFUNCTION(ASatoi), asCALL_CDECL);
+}
+
+void ASWriteString(SavedChunk* chunk, const std::string &str){
+ chunk->desc.AddString(EDF_SCRIPT_PARAMS, str);
+}
+
+std::string ASReadString(SavedChunk* chunk){
+ EntityDescriptionField *edf = chunk->desc.GetField(EDF_SCRIPT_PARAMS);
+ LOG_ASSERT(edf);
+ std::string str;
+ edf->ReadString(&str);
+ return str;
+}
+
+void AttachUndo( ASContext *context ) {
+ context->RegisterObjectType("SavedChunk", 0, asOBJ_REF | asOBJ_NOCOUNT);
+ context->RegisterObjectMethod("SavedChunk", "void WriteString(const string &in str)", asFUNCTION(ASWriteString), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("SavedChunk", "string ReadString()", asFUNCTION(ASReadString), asCALL_CDECL_OBJFIRST);
+}
+
+static void ASLog( LogSystem::LogType level, const std::string& str )
+{
+ if( active_context_stack.size() > 0 )
+ {
+ std::pair<Path,int> d = active_context_stack.top()->GetCallFile();
+
+ if(memcmp(d.first.GetOriginalPath(), "Data/Scripts/", 13) == 0)
+ LogSystem::LogData(level, "us",d.first.GetOriginalPath() + 13,d.second) << str << std::endl;
+ else
+ LogSystem::LogData(level, "us",d.first.GetOriginalPath(),d.second) << str << std::endl;
+ }
+ else
+ {
+ LogSystem::LogData(level, "us","NO_CONTEXT",0) << str << std::endl;
+ }
+
+}
+
+void AttachLog( ASContext *context )
+{
+ context->RegisterEnum("LogType");
+
+ context->RegisterEnumValue("LogType","fatal", LogSystem::fatal);
+ context->RegisterEnumValue("LogType","error", LogSystem::error);
+ context->RegisterEnumValue("LogType","warning", LogSystem::warning);
+ context->RegisterEnumValue("LogType","info", LogSystem::info);
+ context->RegisterEnumValue("LogType","debug", LogSystem::debug);
+ context->RegisterEnumValue("LogType","spam", LogSystem::spam);
+
+ context->RegisterGlobalFunction( "void Log( LogType level, const string &in str )", asFUNCTION( ASLog ), asCALL_CDECL );
+}
+
+static std::string ASGetBuildVersionShort()
+{
+ return std::string(GetBuildVersion());
+}
+
+static std::string ASGetBuildVersionFull()
+{
+ return std::string(GetBuildVersion()) + "_" + std::string(GetBuildIDString());
+}
+
+static std::string ASGetBuildTimestamp()
+{
+ return std::string(GetBuildTimestamp());
+}
+
+void AttachInfo( ASContext *context )
+{
+ context->RegisterGlobalFunction( "string GetBuildVersionShort( )", asFUNCTION( ASGetBuildVersionShort ), asCALL_CDECL );
+ context->RegisterGlobalFunction( "string GetBuildVersionFull( )", asFUNCTION( ASGetBuildVersionFull ), asCALL_CDECL );
+ context->RegisterGlobalFunction( "string GetBuildTimestamp( )", asFUNCTION( ASGetBuildTimestamp ), asCALL_CDECL );
+}
+
+
+/*******
+ *
+ * JSON suport
+ *
+ */
+
+#include "JSON/jsonhelper.h"
+
+
+static void JSONValueConstructor(void *memory) {
+ new(memory) Json::Value();
+}
+
+static void JSONValueTypeConstructor( void *memory, Json::ValueType& type ) {
+ new(memory) Json::Value( type );
+}
+
+static void JSONValueIntConstructor(void *memory, Json::Int& value ) {
+ new(memory) Json::Value( value );
+}
+
+static void JSONValueUIntConstructor(void *memory, Json::UInt& value ) {
+ new(memory) Json::Value( value );
+}
+
+static void JSONValueInt64Constructor(void *memory, Json::Int64& value ) {
+ new(memory) Json::Value( value );
+}
+
+static void JSONValueUInt64Constructor(void *memory, Json::UInt64& value ) {
+ new(memory) Json::Value( value );
+}
+
+static void JSONValueDoubleConstructor(void *memory, double& value ) {
+ new(memory) Json::Value( value );
+}
+
+static void JSONValueStringConstructor(void *memory, std::string& value ) {
+ new(memory) Json::Value( value );
+}
+
+static void JSONValueBoolConstructor(void *memory, bool& value ) {
+ new(memory) Json::Value( value );
+}
+
+static void JSONValueValueConstructor(void *memory, Json::Value& value ) {
+ new(memory) Json::Value( value );
+}
+
+static void JSONValueDestructor(void *memory) {
+ ((Json::Value*)memory)->~Value();
+}
+
+static Json::Value& JSONValueAssign(Json::Value *self, const Json::Value& other) {
+ return (*self) = other;
+}
+
+static Json::Value& JSONValueConstStringIndex(Json::Value *self, const std::string &key) {
+ return (*self)[ key ];
+}
+
+static Json::Value& JSONValueConstIntIndex(Json::Value *self, const int &key) {
+ return (*self)[ key ];
+}
+
+static bool JSONValueRemoveIndex(Json::Value *self, const unsigned int i) {
+ Json::Value temp;
+ return self->removeIndex(i, &temp);
+}
+
+static bool JSONValueRemoveMember(Json::Value *self, const std::string &key) {
+ Json::Value temp;
+ return self->removeMember(key, &temp);
+}
+
+static bool JSONValueIsMember(Json::Value *self, const std::string &key) {
+ return self->isMember(key);
+}
+
+static CScriptArray* JSONValueGetMembers(Json::Value *self) {
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<string>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+
+ std::vector<std::string> members = self->getMemberNames();
+
+ array->Reserve(members.size());
+
+ for(std::vector<std::string>::const_iterator iter = members.begin();
+ iter != members.end(); ++iter)
+ {
+ array->InsertLast((void*)(&(*iter)));
+ }
+ return array;
+}
+
+static std::string JSONValueTypeName(Json::Value *self) {
+
+ switch ( self->type() )
+ {
+ case Json::nullValue:
+ {
+ return "null";
+ }
+ break;
+ case Json::intValue:
+ {
+ return "int";
+ }
+ break;
+ case Json::uintValue:
+ {
+ return "uint";
+ }
+ break;
+ case Json::realValue:
+ {
+ return "real";
+ }
+ break;
+ case Json::stringValue:
+ {
+ return "string";
+ }
+ break;
+ case Json::booleanValue:
+ {
+ return "boolean";
+ }
+ break;
+ case Json::arrayValue:
+ {
+ return "array";
+ }
+ break;
+ case Json::objectValue:
+ {
+ return "object";
+ }
+ break;
+
+ default:
+ {
+ return "unknown";
+ }
+ break;
+ }
+}
+
+static void SimpleJSONWrapperConstructor(void *memory) {
+ new(memory) SimpleJSONWrapper();
+}
+
+static void SimpleJSONWrapperDestructor(void *memory) {
+ ((SimpleJSONWrapper*)memory)->~SimpleJSONWrapper();
+}
+
+static SimpleJSONWrapper& SimpleJSONWrapperAssign(SimpleJSONWrapper *self, const SimpleJSONWrapper& other) {
+ return (*self) = other;
+}
+
+void AttachJSON( ASContext *context )
+{
+ // Register the JSON value type enumeration
+ context->RegisterEnum("JsonValueType");
+ context->RegisterEnumValue("JsonValueType","JSONnullValue",Json::nullValue);
+
+ context->RegisterEnumValue("JsonValueType","JSONintValue",Json::intValue);
+ context->RegisterEnumValue("JsonValueType","JSONuintValue",Json::uintValue);
+ context->RegisterEnumValue("JsonValueType","JSONrealValue",Json::realValue);
+ context->RegisterEnumValue("JsonValueType","JSONstringValue",Json::stringValue);
+ context->RegisterEnumValue("JsonValueType","JSONbooleanValue",Json::booleanValue);
+ context->RegisterEnumValue("JsonValueType","JSONarrayValue",Json::arrayValue);
+ context->RegisterEnumValue("JsonValueType","JSONobjectValue",Json::objectValue);
+
+ //Attach the JSON Value
+ context->RegisterObjectType("JSONValue", sizeof(Json::Value), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK);
+
+ context->RegisterObjectBehaviour("JSONValue", asBEHAVE_CONSTRUCT, "void JSONValue()", asFUNCTION(JSONValueConstructor), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectBehaviour("JSONValue", asBEHAVE_CONSTRUCT, "void JSONValue( JsonValueType &in )", asFUNCTION(JSONValueTypeConstructor), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectBehaviour("JSONValue", asBEHAVE_CONSTRUCT, "void JSONValue( int &in )", asFUNCTION(JSONValueIntConstructor), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectBehaviour("JSONValue", asBEHAVE_CONSTRUCT, "void JSONValue( uint &in)", asFUNCTION(JSONValueUIntConstructor), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectBehaviour("JSONValue", asBEHAVE_CONSTRUCT, "void JSONValue( int64 &in)", asFUNCTION(JSONValueInt64Constructor), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectBehaviour("JSONValue", asBEHAVE_CONSTRUCT, "void JSONValue( uint64 &in )", asFUNCTION(JSONValueUInt64Constructor), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectBehaviour("JSONValue", asBEHAVE_CONSTRUCT, "void JSONValue( double &in )", asFUNCTION(JSONValueDoubleConstructor), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectBehaviour("JSONValue", asBEHAVE_CONSTRUCT, "void JSONValue( string &in )", asFUNCTION(JSONValueStringConstructor), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectBehaviour("JSONValue", asBEHAVE_CONSTRUCT, "void JSONValue( bool &in )", asFUNCTION(JSONValueBoolConstructor), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectBehaviour("JSONValue", asBEHAVE_CONSTRUCT, "void JSONValue( JSONValue &in )", asFUNCTION(JSONValueValueConstructor), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectBehaviour("JSONValue", asBEHAVE_DESTRUCT, "void JSONValue()", asFUNCTION(JSONValueDestructor), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectMethod("JSONValue", "JSONValue& opAssign(const JSONValue &in other)", asFUNCTION(JSONValueAssign), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectMethod("JSONValue", "JSONValue& opIndex( const string &in )", asFUNCTION(JSONValueConstStringIndex), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectMethod("JSONValue", "JSONValue& opIndex( const int &in )", asFUNCTION(JSONValueConstIntIndex), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectMethod("JSONValue", "string asString()",asMETHOD(Json::Value, asString), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "JsonValueType type()",asMETHOD(Json::Value, type), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "string typeName()", asFUNCTION(JSONValueTypeName), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectMethod("JSONValue", "int asInt()",asMETHOD(Json::Value, asInt), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "uint asUInt()",asMETHOD(Json::Value, asUInt), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "int64 asInt64()",asMETHOD(Json::Value, asInt64), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "uint64 asUInt64()",asMETHOD(Json::Value, asUInt64 ), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "float asFloat()",asMETHOD(Json::Value, asFloat ), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "double asDouble()",asMETHOD(Json::Value, asDouble ), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "bool asBool()",asMETHOD(Json::Value, asBool ), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "bool isNull()",asMETHOD(Json::Value, isNull ), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "bool isBool()",asMETHOD(Json::Value, isBool ), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "bool isInt()",asMETHOD(Json::Value, isInt ), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "bool isInt64()",asMETHOD(Json::Value, isInt64 ), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "bool isUInt()",asMETHOD(Json::Value, isUInt ), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "bool isUInt64()",asMETHOD(Json::Value, isUInt64 ), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "bool isIntegral()",asMETHOD(Json::Value, isIntegral ), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "bool isDouble()",asMETHOD(Json::Value, isDouble ), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "bool isNumeric()",asMETHOD(Json::Value, isNumeric ), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "bool isString()",asMETHOD(Json::Value, isString ), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "bool isArray()",asMETHOD(Json::Value, isArray ), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "bool isObject()",asMETHOD(Json::Value, isObject ), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "bool isConvertibleTo(JsonValueType type)",asMETHOD(Json::Value, isConvertibleTo ), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "uint size()",asMETHOD(Json::Value, size ), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "bool empty()",asMETHOD(Json::Value, empty ), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "void clear()",asMETHOD(Json::Value, empty ), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "void resize(uint64)",asMETHOD(Json::Value, resize ), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "bool isValidIndex(uint64)",asMETHOD(Json::Value, isValidIndex ), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "JSONValue& append(const JSONValue &in)",asMETHOD(Json::Value, append ), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSONValue", "bool removeMember( const string &in )", asFUNCTION(JSONValueRemoveMember), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectMethod("JSONValue", "bool removeIndex( uint i )", asFUNCTION(JSONValueRemoveIndex), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectMethod("JSONValue", "bool isMember(const string &in)",asFUNCTION( JSONValueIsMember ), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectMethod("JSONValue", "array<string>@ getMemberNames()",asFUNCTION( JSONValueGetMembers ), asCALL_CDECL_OBJFIRST);
+
+ // Attach the wrapper
+ context->RegisterObjectType("JSON", sizeof(SimpleJSONWrapper), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK);
+
+ context->RegisterObjectBehaviour("JSON", asBEHAVE_CONSTRUCT, "void JSON()", asFUNCTION(SimpleJSONWrapperConstructor), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectBehaviour("JSON", asBEHAVE_DESTRUCT, "void JSON()", asFUNCTION(SimpleJSONWrapperDestructor), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectMethod("JSON", "JSON& opAssign(const JSON &in other)", asFUNCTION(SimpleJSONWrapperAssign), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectMethod("JSON", "bool parseString(string &in)", asMETHOD(SimpleJSONWrapper, parseString), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSON", "bool parseFile(string &in)", asMETHOD(SimpleJSONWrapper, parseFile), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSON", "string writeString(bool=false)",asMETHOD(SimpleJSONWrapper, writeString), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("JSON", "JSONValue& getRoot()",asMETHOD(SimpleJSONWrapper, getRoot), asCALL_THISCALL);
+}
+
+std::string AS_GetConfigValueString( std::string string )
+{
+ if(string == "overall"){
+ return config.GetSettingsPreset();
+ } else if(string == "difficulty_preset") {
+ return config.GetClosestDifficulty();
+ } else {
+ return config[string].str();
+ }
+}
+
+static CScriptArray* AS_GetConfigValueOptions( std::string string ) {
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<string>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+
+ std::vector<std::string> values;
+
+
+ if(string == "overall"){
+ values = config.GetSettingsPresets();
+ } else if(string == "difficulty_preset") {
+ values = config.GetDifficultyPresets();
+ } else{
+ }
+
+ array->Reserve(values.size());
+
+ std::vector<std::string>::iterator valueit = values.begin();
+
+ for(;valueit != values.end(); valueit++) {
+ array->InsertLast((void*)&(*valueit));
+ }
+
+ return array;
+
+}
+
+float AS_GetConfigValueFloat( std::string string )
+{
+ return config[string].toNumber<float>();
+}
+
+bool AS_GetConfigValueBool( std::string string )
+{
+ return config[string].toBool();
+}
+
+void AS_SetConfigValueString( std::string key, std::string value )
+{
+ if(key == "overall"){
+ config.SetSettingsToPreset(value);
+ config.ReloadStaticSettings();
+ config.ReloadDynamicSettings();
+ } else if(key == "difficulty_preset") {
+ config.SetDifficultyPreset(value);
+ config.ReloadDynamicSettings();
+ }else{
+ config.GetRef(key) = value;
+ config.ReloadDynamicSettings();
+ }
+}
+
+void AS_SetConfigValueBool( std::string key, bool value )
+{
+ config.GetRef(key) = value;
+ config.ReloadDynamicSettings();
+}
+
+void AS_SetConfigValueInt( std::string key, int value )
+{
+ config.GetRef(key) = value;
+ config.ReloadDynamicSettings();
+}
+
+int AS_GetConfigValueInt( std::string string )
+{
+ return config[string].toNumber<int>();
+}
+
+void AS_SetConfigValueFloat( std::string key, float value )
+{
+ config.GetRef(key) = value;
+ config.ReloadDynamicSettings();
+}
+
+int AS_GetMonitorCount()
+{
+ return SDL_GetNumVideoDisplays();
+}
+
+static CScriptArray* AS_GetPossibleResolutions() {
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<vec2>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+
+ std::vector<Resolution> resolutions = config.GetPossibleResolutions();
+
+ array->Reserve(resolutions.size());
+
+ for( unsigned i = 0; i < resolutions.size(); i++ ) {
+ vec2 cur_res = vec2(resolutions[i].w, resolutions[i].h);
+ array->InsertLast((void*)&(cur_res));
+ }
+ return array;
+}
+
+void AS_ReloadStaticValues() {
+ config.ReloadStaticSettings();
+}
+
+static CScriptArray* ASGetAvailableBindingCategories() {
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<string>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+
+ std::vector<std::string> descs = Input::Instance()->GetAvailableBindingCategories();
+
+ array->Reserve(descs.size());
+
+ std::vector<std::string>::iterator descit = descs.begin();
+
+ for(;descit != descs.end(); descit++) {
+ array->InsertLast((void*)&(*descit));
+ }
+
+ return array;
+}
+
+static CScriptArray* ASGetAvailableBindings( const std::string& category ) {
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<string>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+
+ std::vector<std::string> descs = Input::Instance()->GetAvailableBindings(category);
+
+ array->Reserve(descs.size());
+
+ std::vector<std::string>::iterator descit = descs.begin();
+
+ for( ; descit != descs.end(); descit++ ) {
+ array->InsertLast((void*)&(*descit));
+ }
+
+ return array;
+}
+
+static std::string ASGetBindingValue( std::string binding_category, std::string binding ) {
+ return Input::Instance()->GetBindingValue( binding_category, binding );
+}
+
+static void ASSetBindingValue( std::string binding_category, std::string binding, std::string value ) {
+ Input::Instance()->SetBindingValue( binding_category, binding, value );
+}
+
+static void ASSetKeyboardBindingValue( std::string binding_category, std::string binding, uint32_t scancode ) {
+ Input::Instance()->SetKeyboardBindingValue( binding_category, binding, (SDL_Scancode)scancode );
+}
+
+static void ASSetMouseBindingValue( std::string binding_category, std::string binding, uint32_t button ) {
+ Input::Instance()->SetMouseBindingValue( binding_category, binding, button );
+}
+
+static void ASSetMouseBindingValueString( std::string binding_category, std::string binding, std::string text ) {
+ Input::Instance()->SetMouseBindingValue( binding_category, binding, text );
+}
+
+static void ASSetControllerBindingValue( std::string binding_category, std::string binding, uint32_t input ) {
+ Input::Instance()->SetControllerBindingValue( binding_category, binding, (ControllerInput::Input)input );
+}
+
+static void ASSaveConfig() {
+ std::string config_path = GetConfigPath();
+ if( config.HasChangedSinceLastSave() ) {
+ LOGI << "Saving config at request from angel script" << std::endl;
+ config.Save(config_path);
+ }
+}
+
+static bool ASConfigHasKey(std::string key) {
+ return config.HasKey(key);
+}
+
+static void ASResetBinding( std::string category, std::string binding) {
+ const std::string config_value = category + "[" + binding + "]";
+ if(memcmp(category.c_str(), "gamepad_", strlen("gamepad_")) == 0) {
+ config.RemoveConfig(config_value);
+ } else {
+ config.GetRef(config_value) = default_config.GetRef(config_value);
+ }
+ config.ReloadStaticSettings();
+}
+
+void AttachConfig( ASContext *context )
+{
+ context->RegisterGlobalFunction("string GetConfigValueString(string index)",
+ asFUNCTION(AS_GetConfigValueString),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("array<string>@ GetConfigValueOptions(string index)",
+ asFUNCTION(AS_GetConfigValueOptions),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("float GetConfigValueFloat(string index)",
+ asFUNCTION(AS_GetConfigValueFloat),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetConfigValueFloat(string key, float value)",
+ asFUNCTION(AS_SetConfigValueFloat),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("bool GetConfigValueBool(string index)",
+ asFUNCTION(AS_GetConfigValueBool),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetConfigValueString(string key, string value)",
+ asFUNCTION(AS_SetConfigValueString),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetConfigValueBool(string key, bool value)",
+ asFUNCTION(AS_SetConfigValueBool),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetConfigValueInt(string key, int value)",
+ asFUNCTION(AS_SetConfigValueInt),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("int GetConfigValueInt(string key)",
+ asFUNCTION(AS_GetConfigValueInt),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("int GetMonitorCount()",
+ asFUNCTION(AS_GetMonitorCount),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("array<vec2>@ GetPossibleResolutions()",
+ asFUNCTION(AS_GetPossibleResolutions),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("void ReloadStaticValues()",
+ asFUNCTION(AS_ReloadStaticValues),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("array<string>@ GetAvailableBindingCategories()",
+ asFUNCTION(ASGetAvailableBindingCategories),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("array<string>@ GetAvailableBindings(const string& in)",
+ asFUNCTION(ASGetAvailableBindings),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("string GetBindingValue(string binding_category, string binding)",
+ asFUNCTION(ASGetBindingValue),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetBindingValue(string binding_category, string binding, string value)",
+ asFUNCTION(ASSetBindingValue),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetKeyboardBindingValue(string binding_category, string binding, uint32 scancode)",
+ asFUNCTION(ASSetKeyboardBindingValue),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetMouseBindingValue(string binding_category, string binding, uint32 button)",
+ asFUNCTION(ASSetMouseBindingValue),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetMouseBindingValue(string binding_category, string binding, string text)",
+ asFUNCTION(ASSetMouseBindingValueString),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("void SetControllerBindingValue(string binding_category, string binding, uint32 input)",
+ asFUNCTION(ASSetControllerBindingValue),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("void SaveConfig()",
+ asFUNCTION(ASSaveConfig),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ConfigHasKey(string key)",
+ asFUNCTION(ASConfigHasKey),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("void ResetBinding(string binding_category, string binding)",
+ asFUNCTION(ASResetBinding),
+ asCALL_CDECL);
+}
+
+static std::string AS_ToUpper( std::string& in )
+{
+ size_t len = in.size()+1+(in.size()/10)+10;
+ char* output = (char*)alloca(len);//We pad for the trailing null sign and potential codepoint expansion of some characters.
+ UTF8ToUpper( output, len, in.c_str() );
+ return std::string(output);
+}
+
+uint32_t AS_GetLengthInBytesForNCodepoints( const std::string& utf8in, uint32_t codepoint_index ) {
+ return GetLengthInBytesForNCodepoints( utf8in,codepoint_index );
+}
+
+uint32_t AS_GetCodepointCount( const std::string &utf8in ) {
+ return GetCodepointCount( utf8in );
+}
+
+void AttachStringUtil( ASContext *context )
+{
+ context->RegisterGlobalFunction("string ToUpper(string &in)",
+ asFUNCTION(AS_ToUpper),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("uint GetLengthInBytesForNCodepoints( const string& in, uint codepoint_index )",
+ asFUNCTION(AS_GetLengthInBytesForNCodepoints),
+ asCALL_CDECL);
+ context->RegisterGlobalFunction("uint GetCodepointCount( const string& in )",
+ asFUNCTION(AS_GetCodepointCount),
+ asCALL_CDECL);
+}
+
+bool AS_DirectoryExists( std::string& in )
+{
+ char out[kPathSize];
+ return 0 == FindFilePath( in.c_str(), out, kPathSize, kModPaths | kDataPaths | kWriteDir | kModWriteDirs, false ) && !isFile(out);
+}
+
+bool AS_FileExists( std::string& in )
+{
+ char out[kPathSize];
+ return 0 == FindFilePath( in.c_str(), out, kPathSize, kModPaths | kDataPaths | kWriteDir | kModWriteDirs, false ) && isFile(out);
+}
+
+void AttachIO( ASContext *context ) {
+ context->RegisterGlobalFunction("bool DirectoryExists(string& in)", asFUNCTION(AS_DirectoryExists), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool FileExists(string& in)", asFUNCTION(AS_FileExists), asCALL_CDECL);
+}
+
+void AttachDebug( ASContext *context ) {
+ context->RegisterGlobalFunction("void PrintCallstack()",asFUNCTION(ASPrintCallstack), asCALL_CDECL);
+}
+
+static void ASParameterConstructor( void* m ) {
+ new (m) ModInstance::Parameter();
+}
+
+static void ASParameterCopyConstructor( const ModInstance::Parameter& other, void * m ) {
+ new (m) ModInstance::Parameter(other);
+}
+
+static void ASParameterDestructor( void* m ) {
+ ((ModInstance::Parameter*)m)->~Parameter();
+}
+
+static ModInstance::Parameter& ASParameterOpAssign( ModInstance::Parameter* self, const ModInstance::Parameter& other ) {
+ *self = other;
+ return *self;
+}
+
+static ModInstance::Parameter ParameterConstStringIndex(ModInstance::Parameter *self, const std::string &key) {
+ if(strmtch(self->type,"table")) {
+ for(uint32_t i = 0; i < self->parameters.size(); i++) {
+ if(strmtch(self->parameters[i].name,key.c_str())) {
+ return self->parameters[i];
+ }
+ }
+ }
+ return ModInstance::Parameter();
+}
+
+static ModInstance::Parameter ParameterConstIntIndex(ModInstance::Parameter *self, const int &key) {
+ if( key >= 0 && key < (int)self->parameters.size() ) {
+ return self->parameters[key];
+ }
+ return ModInstance::Parameter();
+}
+
+static bool ASParameterIsEmpty(ModInstance::Parameter *self) {
+ return strmtch(self->type,"empty");
+}
+
+static bool ASParameterIsString(ModInstance::Parameter *self) {
+ return strmtch(self->type,"string");
+}
+
+static bool ASParameterIsArray(ModInstance::Parameter *self) {
+ return strmtch(self->type,"array");
+}
+
+static bool ASParameterIsTable(ModInstance::Parameter *self) {
+ return strmtch(self->type,"table");
+}
+
+static uint32_t ASParameterSize(ModInstance::Parameter *self) {
+ return self->parameters.size();
+}
+
+static std::string ASParameterAsString(ModInstance::Parameter *self) {
+ return std::string(self->value);
+}
+
+static std::string ASParameterGetName(ModInstance::Parameter *self) {
+ return std::string(self->name);
+}
+
+static bool ASParameterContains(ModInstance::Parameter *self, const std::string& val) {
+ for(uint32_t i = 0; i < self->parameters.size(); i++ ) {
+ if(strmtch(self->parameters[i].value, val.c_str())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool ASParameterContainsName(ModInstance::Parameter *self, const std::string& val) {
+ for(uint32_t i = 0; i < self->parameters.size(); i++ ) {
+ if(strmtch(self->parameters[i].name, val.c_str())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+//Data parsed from the level structure
+struct ASLevelDetails {
+ char name[128];
+};
+
+static void ASLevelDetailsConstruct( ASLevelDetails* obj ) {
+ obj->name[0] = '\0';
+}
+
+static std::string ASLevelDetailsGetName( ASLevelDetails* obj ) {
+ return std::string(obj->name);
+}
+
+void AttachLevelDetails(ASContext *context) {
+ context->RegisterObjectType("LevelDetails", sizeof(ASLevelDetails), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS_C );
+ context->RegisterObjectBehaviour("LevelDetails", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ASLevelDetailsConstruct), asCALL_CDECL_OBJLAST);
+
+ context->RegisterObjectMethod("LevelDetails", "string GetName()", asFUNCTION(ASLevelDetailsGetName), asCALL_CDECL_OBJFIRST);
+
+ context->DocsCloseBrace();
+}
+
+static void ASModIDConstructor(void *memory) {
+ new(memory) ModID();
+}
+
+static void ASModIDDestructor(void *memory) {
+ ((ModID*)memory)->~ModID();
+}
+
+static bool ASModIDValid(void *memory) {
+ return ((ModID*)memory)->Valid();
+}
+
+static void ASMenuItemConstructor(void *memory) {
+ new(memory) ModInstance::MenuItem();
+}
+
+static void ASMenuItemDestructor(void *memory) {
+ ((ModInstance::MenuItem*)memory)->~MenuItem();
+}
+
+static std::string ASMenuItemGetTitle(void *memory) {
+ return std::string(((ModInstance::MenuItem*)memory)->title);
+}
+
+static std::string ASMenuItemGetCategory(void *memory) {
+ return std::string(((ModInstance::MenuItem*)memory)->category);
+}
+
+static std::string ASMenuItemGetPath(void *memory) {
+ return std::string(((ModInstance::MenuItem*)memory)->path);
+}
+
+static std::string ASMenuItemGetThumbnail(void *memory) {
+ return std::string(((ModInstance::MenuItem*)memory)->thumbnail);
+}
+
+static void ASSpawnerItemConstructor(void* memory) {
+ new(memory) ModInstance::Item();
+}
+
+static void ASSpawnerItemDestructor(void* memory) {
+ ((ModInstance::Item*)memory)->~Item();
+}
+
+static std::string ASSpawnerItemGetTitle(void* memory) {
+ return std::string(((ModInstance::Item*)memory)->title);
+}
+
+static std::string ASSpawnerItemGetCategory(void* memory) {
+ return std::string(((ModInstance::Item*)memory)->category);
+}
+
+static std::string ASSpawnerItemGetPath(void* memory) {
+ return std::string(((ModInstance::Item*)memory)->path);
+}
+
+static std::string ASSpawnerItemGetThumbnail(void* memory) {
+ return std::string(((ModInstance::Item*)memory)->thumbnail);
+}
+static bool ASModIsActive(ModID& sid) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if(mod) {
+ return mod->IsActive();
+ } else {
+ return false;
+ }
+}
+
+static bool ASModNeedsRestart(ModID& sid) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if(mod) {
+ return mod->NeedsRestart();
+ } else {
+ return false;
+ }
+}
+
+static bool ASModIsValid(ModID& sid) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if( mod ) {
+ return mod->IsValid();
+ } else {
+ return false;
+ }
+}
+
+static bool ASModIsCore(ModID& sid) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if( mod ) {
+ return mod->IsCore();
+ } else {
+ return false;
+ }
+}
+
+static bool ASModCanActivate(ModID& sid) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if( mod ) {
+ return mod->CanActivate();
+ } else {
+ return false;
+ }
+}
+
+static int ASModGetSource(ModID& sid) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if(mod) {
+ return mod->modsource;
+ } else {
+ return 0;
+ }
+}
+
+static std::string ASModGetID(ModID& sid) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if(mod) {
+ return std::string(mod->id);
+ } else {
+ return std::string();
+ }
+}
+
+static std::string ASModGetName(ModID& sid) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if(mod) {
+ return std::string(mod->name);
+ } else {
+ return std::string();
+ }
+}
+
+static bool ASModGetSupportsOnline(ModID& sid) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if(mod) {
+ return mod->SupportsOnline();
+ } else {
+ return false;
+ }
+}
+
+static bool ASModGetSupportsCurrentVersion(ModID& sid) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if(mod) {
+ return mod->ExplicitVersionSupport();
+ } else {
+ return false;
+ }
+}
+
+static std::string ASModGetAuthor(ModID& sid) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if(mod) {
+ return std::string(mod->author);
+ } else {
+ return std::string();
+ }
+}
+
+static std::string ASModGetCategory(ModID& sid) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if(mod) {
+ return std::string(mod->category);
+ } else {
+ return std::string();
+ }
+}
+
+static std::string ASModGetDescription(ModID& sid) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if(mod) {
+ return std::string(mod->description);
+ } else {
+ return std::string();
+ }
+}
+
+static std::string ASModGetVersion(ModID& sid) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if(mod) {
+ return std::string(mod->version);
+ } else {
+ return std::string();
+ }
+}
+
+static std::string ASModGetTags(ModID& sid) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if(mod) {
+ return std::string(mod->GetTagsListString());
+ } else {
+ return std::string();
+ }
+}
+
+static bool ASModActivation(ModID& sid, bool active) {
+ ModInstance *inst = ModLoading::Instance().GetMod(sid);
+ if( inst ) {
+ return inst->Activate(active);
+ } else {
+ return false;
+ }
+}
+
+static std::string ASGetModPath(ModID& sid) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if(mod) {
+ return mod->path;
+ } else {
+ return std::string();
+ }
+}
+
+
+static std::string ASGetModValidityString(ModID& sid) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if(mod) {
+ return mod->GetValidityErrors();
+ } else {
+ return "";
+ }
+}
+
+static std::string ASGetModThumbnail(ModID& sid) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if(mod) {
+ return mod->thumbnail.str();
+ } else {
+ return "";
+ }
+}
+
+static IMImage* ASGetModThumbnailImage( ModID& sid ) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ IMImage* image = NULL;
+ if(mod) {
+ Path thumb = mod->GetFullAbsThumbnailPath();
+ if(thumb.isValid() && IsImageFile(thumb)) {
+ return IMImage::ASFactoryPath(thumb);
+ } else {
+ return IMImage::ASFactory("Images/thumb_fallback.png");
+ }
+ } else {
+ return IMImage::ASFactory("Images/thumb_fallback.png");
+ }
+}
+
+static CScriptArray* ASGetModSids() {
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<ModID>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+
+ std::vector<ModID> sids = ModLoading::Instance().GetModsSid();
+
+ array->Reserve(sids.size());
+
+ for( unsigned i = 0; i < sids.size(); i++ ) {
+ array->InsertLast((void*)&(sids[i]));
+ }
+ return array;
+}
+
+static CScriptArray* ASGetActiveModSids() {
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<ModID>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+
+ std::vector<ModID> sids = ModLoading::Instance().GetModsSid();
+
+ array->Reserve(sids.size());
+
+ for( unsigned i = 0; i < sids.size(); i++ ) {
+ if( ModLoading::Instance().IsActive(sids[i]) ) {
+ array->InsertLast((void*)&(sids[i]));
+ }
+ }
+ return array;
+}
+
+static CScriptArray* ASModGetMenuItems(ModID& sid) {
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<MenuItem>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+
+ ModInstance *mod = ModLoading::Instance().GetMod(sid);
+
+ if(mod) {
+ std::vector<ModInstance::MenuItem>& mmi = mod->main_menu_items;
+
+ array->Reserve(mmi.size());
+
+ for( unsigned i = 0; i < mmi.size(); i++ ) {
+ array->InsertLast((void*)&(mmi[i]));
+ }
+ }
+ return array;
+}
+
+static CScriptArray* ASModGetSpawnerItems(ModID& sid) {
+ asIScriptContext* ctx = asGetActiveContext();
+ asIScriptEngine* engine = ctx->GetEngine();
+ asITypeInfo* arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<SpawnerItem>"));
+ CScriptArray* array = CScriptArray::Create(arrayType, (asUINT)0);
+
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+
+ if(mod) {
+ std::vector<ModInstance::Item>& msi = mod->items;
+
+ array->Reserve(msi.size());
+
+ for(unsigned i = 0; i < msi.size(); i++) {
+ array->InsertLast((void*)&(msi[i]));
+ }
+ }
+
+ return array;
+}
+
+static CScriptArray* ASModGetAllSpawnerItems(bool only_include_active) {
+ asIScriptContext* ctx = asGetActiveContext();
+ asIScriptEngine* engine = ctx->GetEngine();
+ asITypeInfo* arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<SpawnerItem>"));
+ CScriptArray* array = CScriptArray::Create(arrayType, (asUINT)0);
+
+ const std::vector<ModInstance*>& mods = ModLoading::Instance().GetMods();
+
+ unsigned item_count = 0;
+
+ for(unsigned i = 0; i < mods.size(); i++) {
+ if(!only_include_active || mods[i]->IsActive() ) {
+ item_count += mods[i]->items.size();
+ }
+ }
+
+ array->Reserve(item_count);
+
+ for(unsigned i = 0; i < mods.size(); i++) {
+ if(!only_include_active || mods[i]->IsActive() ) {
+ const std::vector<ModInstance::Item>& msi = mods[i]->items;
+
+ for(unsigned i = 0; i < msi.size(); i++) {
+ array->InsertLast((void*)&(msi[i]));
+ }
+ }
+ }
+
+ return array;
+}
+
+static ModInstance::Campaign AsModGetCampaign( ModID& sid ) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+
+ if(mod && !mod->campaigns.empty()) {
+ return mod->campaigns[0];
+ } else {
+ return ModInstance::Campaign();
+ }
+}
+
+static CScriptArray* ASModGetCampaigns( ModID& sid ) {
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<Campaign>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+
+ if(mod) {
+ std::vector<ModInstance::Campaign>& campaigns = mod->campaigns;
+
+ array->Reserve(campaigns.size());
+
+ for(unsigned i = 0; i < campaigns.size(); i++) {
+ array->InsertLast((void*)&(campaigns[i]));
+ }
+ }
+ return array;
+}
+
+static ModInstance::Campaign ASGetCampaign( std::string& campaign_id ) {
+ return ModLoading::Instance().GetCampaign(campaign_id);
+}
+
+static CScriptArray* ASGetCampaigns() {
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<Campaign>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+
+ std::vector<ModInstance::Campaign> campaigns = ModLoading::Instance().GetCampaigns();
+
+ array->Reserve(campaigns.size());
+
+ for( unsigned i = 0; i < campaigns.size(); i++ ) {
+ array->InsertLast((void*)&(campaigns[i]));
+ }
+
+ return array;
+}
+
+static void ASLevelConstructor( void* m ) {
+ new (m) ModInstance::Level();
+}
+
+static void ASLevelCopyConstructor( const ModInstance::Level& other, void * m ) {
+ new (m) ModInstance::Level(other);
+}
+
+static void ASLevelDestructor( void* m ) {
+ ((ModInstance::Level*)m)->~Level();
+}
+
+static ModInstance::Level& ASLevelOpAssign( ModInstance::Level* self, const ModInstance::Level& other ) {
+ *self = other;
+ return *self;
+}
+
+static std::string ASLevelGetTitle( void* m ) {
+ return std::string(((ModInstance::Level*)m)->title);
+}
+
+static std::string ASLevelGetID( void* m ) {
+ return std::string(((ModInstance::Level*)m)->id);
+}
+
+static bool ASLevelGetSupportsOnline(void* m) {
+ return ((ModInstance::Level*)m)->supports_online;
+}
+
+static bool ASLevelGetRequiresOnline(void* m) {
+ return ((ModInstance::Level*)m)->requires_online;
+}
+
+static std::string ASLevelGetThumbnail( void* m ) {
+ return std::string(((ModInstance::Level*)m)->thumbnail);
+}
+
+static std::string ASLevelGetPath( void* m ) {
+ return std::string(((ModInstance::Level*)m)->path);
+}
+
+static ASLevelDetails ASLevelGetLevelDetails( void* m ) {
+ ASLevelDetails li;
+ ASLevelDetailsConstruct(&li);
+
+ std::string path = AssemblePath("Data/Levels/",((ModInstance::Level*)m)->path);
+ if( FileExists(path, kAnyPath) ) {
+ LevelInfoAssetRef levelinfo = Engine::Instance()->GetAssetManager()->LoadSync<LevelInfoAsset>(AssemblePath("Data/Levels/",((ModInstance::Level*)m)->path));
+
+ if( levelinfo.valid() ) {
+ strscpy(li.name, levelinfo->GetLevelName().c_str(), 128);
+ } else {
+ LOGW << "Failed to load levelinfo for " << ((ModInstance::Level*)m)->path << std::endl;
+ }
+ } else {
+ LOGW << "Unable to load level details for " << path << std::endl;
+ }
+
+ return li;
+}
+
+static bool ASLevelCompletionOptional( void* m ) {
+ return ((ModInstance::Level*)m)->completion_optional;
+}
+
+static ModInstance::Parameter ASLevelGetParameter( void* m) {
+ ModInstance::Level* level = static_cast<ModInstance::Level*>(m);
+ return level->parameter;
+}
+
+static void ASCampaignConstructor( void* m ) {
+ new (m) ModInstance::Campaign();
+}
+
+static void ASCampaignCopyConstructor( const ModInstance::Campaign& other, void * m ) {
+ new (m) ModInstance::Campaign(other);
+}
+
+static void ASCampaignDestructor( void * m ) {
+ ((ModInstance::Campaign*)m)->~Campaign();
+}
+
+static ModInstance::Parameter ASCampaignGetParameter( void* m) {
+ ModInstance::Campaign* level = static_cast<ModInstance::Campaign*>(m);
+ return level->parameter;
+}
+
+static ModInstance::Campaign& ASCampaignOpAssign( ModInstance::Campaign* self, const ModInstance::Campaign& other ) {
+ *self = other;
+ return *self;
+}
+
+static std::string ASCampaignGetID( void *m ) {
+ return std::string(((ModInstance::Campaign*)m)->id);
+}
+
+static std::string ASCampaignGetTitle( void *m ) {
+ return std::string(((ModInstance::Campaign*)m)->title);
+}
+
+static bool ASCampaignGetSupportsOnline(void *m) {
+ return (((ModInstance::Campaign*)m)->supports_online);
+}
+
+static bool ASCampaignGetRequiresOnline(void *m) {
+ return (((ModInstance::Campaign*)m)->requires_online);
+}
+
+static std::string ASCampaignGetThumbnail( void *m ) {
+ return std::string(((ModInstance::Campaign*)m)->thumbnail);
+}
+
+static std::string ASCampaignGetMainScript( void *m ) {
+ return std::string(((ModInstance::Campaign*)m)->main_script);
+}
+
+static std::string ASCampaignGetMenuScript( void *m ) {
+ return std::string(((ModInstance::Campaign*)m)->menu_script);
+}
+
+static std::string ASCampaignGetAttribute( void *m, std::string& id ) {
+ std::vector<ModInstance::Attribute>& attributes = ((ModInstance::Campaign*)m)->attributes;
+ for( unsigned int i = 0; i < attributes.size(); i++ ) {
+ if( strmtch( attributes[i].id, id.c_str()) ){
+ return attributes[i].value.str();
+ }
+ }
+ return "";
+}
+
+static CScriptArray* ASCampaignGetLevels(void *m) {
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<ModLevel>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+
+ std::vector<ModInstance::Level> mmi = ((ModInstance::Campaign*)m)->levels;
+
+ array->Reserve(mmi.size());
+
+ for( unsigned i = 0; i < mmi.size(); i++ ) {
+ array->InsertLast((void*)&(mmi[i]));
+ }
+
+ return array;
+}
+
+static ModInstance::Level ASCampaignGetLevel(ModInstance::Campaign *m, const std::string& id) {
+ for( unsigned i = 0; i < m->levels.size(); i++ ) {
+ if( strmtch(id, m->levels[i].id) ) {
+ return m->levels[i];
+ }
+ }
+ return ModInstance::Level();
+}
+
+static CScriptArray* ASModGetModCampaignLevels(ModID& sid) {
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<ModLevel>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+
+ ModInstance *mod = ModLoading::Instance().GetMod(sid);
+
+ if(mod && !mod->campaigns.empty()) {
+ std::vector<ModInstance::Level>& mmi = mod->campaigns[0].levels;
+
+ array->Reserve(mmi.size());
+
+ for( unsigned i = 0; i < mmi.size(); i++ ) {
+ array->InsertLast((void*)&(mmi[i]));
+ }
+ }
+ return array;
+}
+
+static CScriptArray* ASModGetModSingleLevels(ModID& sid) {
+ asIScriptContext *ctx = asGetActiveContext();
+ asIScriptEngine *engine = ctx->GetEngine();
+ asITypeInfo *arrayType = engine->GetTypeInfoById(engine->GetTypeIdByDecl("array<ModLevel>"));
+ CScriptArray *array = CScriptArray::Create(arrayType, (asUINT)0);
+
+ ModInstance *mod = ModLoading::Instance().GetMod(sid);
+
+ if(mod) {
+ std::vector<ModInstance::Level>& mmi = mod->levels;
+
+ array->Reserve(mmi.size());
+
+ for( unsigned i = 0; i < mmi.size(); i++ ) {
+ array->InsertLast((void*)&(mmi[i]));
+ }
+ }
+ return array;
+}
+
+static ModInstance::UserVote ASModGetModUserVote( ModID& sid ) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if(mod) {
+ return mod->GetUserVote();
+ }
+ return ModInstance::k_VoteUnknown;
+}
+
+static void ASRequestModSetUserVote( ModID& sid, bool voteup ) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if(mod) {
+ mod->RequestVoteSet(voteup);
+ }
+}
+
+static void ASRequestModSetFavorite( ModID& sid, bool fav ) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if(mod) {
+ mod->RequestFavoriteSet(fav);
+ }
+}
+
+static bool ASModIsFavorite( ModID& sid ) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if(mod) {
+ return mod->IsFavorite();
+ } else {
+ return false;
+ }
+}
+
+static void ASRequestWorkshopSubscribe( ModID& sid ) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if(mod) {
+ mod->RequestSubscribe();
+ }
+}
+
+static void ASRequestWorkshopUnSubscribe( ModID& sid ) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if(mod) {
+ mod->RequestUnsubscribe();
+ }
+}
+
+static bool ASIsWorkshopSubscribed( ModID& sid ) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if(mod) {
+ return mod->IsSubscribed();
+ } else {
+ return true;
+ }
+}
+
+static bool ASIsWorkshopMod( ModID& sid ) {
+ ModInstance* mod = ModLoading::Instance().GetMod(sid);
+ if(mod) {
+ return mod->modsource == ModSourceSteamworks;
+ } else {
+ return true;
+ }
+}
+
+static void ASOpenModWorkshopPage( ModID& id ) {
+#if ENABLE_STEAMWORKS
+ Steamworks::Instance()->OpenWebPageToMod(id);
+#endif
+}
+
+static void ASOpenModAuthorWorkshopPage( ModID& id ) {
+#if ENABLE_STEAMWORKS
+ Steamworks::Instance()->OpenWebPageToModAuthor(id);
+#endif
+}
+
+static void ASOpenWorkshop() {
+#if ENABLE_STEAMWORKS
+ Steamworks::Instance()->OpenWebPageToWorkshop();
+#endif
+}
+
+static void ASDeactivateAllMods() {
+ std::vector<ModInstance*> mods = ModLoading::Instance().GetAllMods();
+ for( unsigned i = 0; i < mods.size(); i++ ) {
+ mods[i]->Activate(false);
+ }
+}
+
+static bool ASIsWorkshopAvailable() {
+#if ENABLE_STEAMWORKS
+ return Steamworks::Instance()->UserCanAccessWorkshop();
+#else
+ return false;
+#endif
+}
+
+static void ASSaveModConfig() {
+ LOGI << "Saving mod config" << std::endl;
+ ModLoading::Instance().SaveModConfig();
+}
+
+uint32_t ASWorkshopSubscribedNotInstalledCount() {
+#if ENABLE_STEAMWORKS
+ if( Steamworks::Instance()->GetUGC() ) {
+ return Steamworks::Instance()->GetUGC()->SubscribedNotInstalledCount();
+ }
+#endif
+
+ return 0;
+}
+
+uint32_t ASWorkshopDownloadingCount() {
+#if ENABLE_STEAMWORKS
+ if( Steamworks::Instance()->GetUGC() ) {
+ return Steamworks::Instance()->GetUGC()->DownloadingCount();
+ }
+#endif
+ return 0;
+}
+
+uint32_t ASWorkshopDownloadPendingCount() {
+#if ENABLE_STEAMWORKS
+ if( Steamworks::Instance()->GetUGC() ) {
+ return Steamworks::Instance()->GetUGC()->DownloadPendingCount();
+ }
+#endif
+ return 0;
+}
+
+uint32_t ASWorkshopNeedsUpdateCount() {
+#if ENABLE_STEAMWORKS
+ if( Steamworks::Instance()->GetUGC() ) {
+ return Steamworks::Instance()->GetUGC()->NeedsUpdateCount();
+ }
+#endif
+ return 0;
+}
+
+float ASWorkshopTotalDownloadProgress() {
+#if ENABLE_STEAMWORKS
+ if( Steamworks::Instance()->GetUGC() ) {
+ return Steamworks::Instance()->GetUGC()->TotalDownloadProgress();
+ }
+#endif
+ return 0.0f;
+}
+
+void AttachModding( ASContext *context ) {
+ context->RegisterEnum("UserVote");
+
+ context->RegisterEnumValue("UserVote","k_VoteUnknown",ModInstance::k_VoteUnknown);
+ context->RegisterEnumValue("UserVote","k_VoteNone",ModInstance::k_VoteNone);
+ context->RegisterEnumValue("UserVote","k_VoteUp",ModInstance::k_VoteUp);
+ context->RegisterEnumValue("UserVote","k_VoteDown",ModInstance::k_VoteDown);
+
+ context->RegisterObjectType("Parameter", sizeof(ModInstance::Parameter), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK );
+ context->RegisterObjectBehaviour("Parameter", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ASParameterConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("Parameter", asBEHAVE_CONSTRUCT, "void f(const Parameter &in other)", asFUNCTION(ASParameterCopyConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("Parameter", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(ASParameterDestructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectMethod("Parameter", "Parameter& opAssign(const Parameter &in other)", asFUNCTION(ASParameterOpAssign), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectMethod("Parameter", "Parameter opIndex( const string &in )", asFUNCTION(ParameterConstStringIndex), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Parameter", "Parameter opIndex( const int &in )", asFUNCTION(ParameterConstIntIndex), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectMethod("Parameter", "string getName()", asFUNCTION(ASParameterGetName), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectMethod("Parameter", "bool isEmpty()", asFUNCTION(ASParameterIsEmpty), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Parameter", "bool isString()", asFUNCTION(ASParameterIsString), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Parameter", "bool isArray()", asFUNCTION(ASParameterIsArray), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Parameter", "bool isTable()", asFUNCTION(ASParameterIsTable), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Parameter", "uint size()", asFUNCTION(ASParameterSize), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectMethod("Parameter", "string asString()", asFUNCTION(ASParameterAsString), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectMethod("Parameter", "bool contains(const string &in value)",asFUNCTION(ASParameterContains), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Parameter", "bool containsName(const string &in value)",asFUNCTION(ASParameterContainsName), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectType("ModID", sizeof(ModID), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS_CD );
+
+ context->RegisterObjectBehaviour("ModID", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ASModIDConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("ModID", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(ASModIDDestructor), asCALL_CDECL_OBJLAST);
+
+ context->RegisterObjectMethod("ModID", "bool Valid()", asFUNCTION(ASModIDValid), asCALL_CDECL_OBJFIRST);
+ context->DocsCloseBrace();
+
+ context->RegisterObjectType("MenuItem", sizeof(ModInstance::MenuItem), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS_CD );
+ context->RegisterObjectBehaviour("MenuItem", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ASMenuItemConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("MenuItem", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(ASMenuItemDestructor), asCALL_CDECL_OBJLAST);
+
+ context->RegisterObjectMethod("MenuItem", "string GetTitle()", asFUNCTION(ASMenuItemGetTitle), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("MenuItem", "string GetCategory()", asFUNCTION(ASMenuItemGetCategory), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("MenuItem", "string GetPath()", asFUNCTION(ASMenuItemGetPath), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("MenuItem", "string GetThumbnail()", asFUNCTION(ASMenuItemGetThumbnail), asCALL_CDECL_OBJFIRST);
+ context->DocsCloseBrace();
+
+ context->RegisterObjectType("SpawnerItem", sizeof(ModInstance::Item), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS_CD );
+ context->RegisterObjectBehaviour("SpawnerItem", asBEHAVE_CONSTRUCT, "void SpawnerItem()", asFUNCTION(ASSpawnerItemConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("SpawnerItem", asBEHAVE_DESTRUCT, "void SpawnerItem()", asFUNCTION(ASSpawnerItemDestructor), asCALL_CDECL_OBJLAST);
+
+ context->RegisterObjectMethod("SpawnerItem", "string GetTitle()", asFUNCTION(ASSpawnerItemGetTitle), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("SpawnerItem", "string GetCategory()", asFUNCTION(ASSpawnerItemGetCategory), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("SpawnerItem", "string GetPath()", asFUNCTION(ASSpawnerItemGetPath), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("SpawnerItem", "string GetThumbnail()", asFUNCTION(ASSpawnerItemGetThumbnail), asCALL_CDECL_OBJFIRST);
+ context->DocsCloseBrace();
+
+ context->RegisterObjectType("ModLevel", sizeof(ModInstance::Level), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK );
+
+ context->RegisterObjectBehaviour("ModLevel", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ASLevelConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("ModLevel", asBEHAVE_CONSTRUCT, "void f(const ModLevel &in other)", asFUNCTION(ASLevelCopyConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("ModLevel", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(ASLevelDestructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectMethod("ModLevel", "ModLevel& opAssign(const ModLevel &in other)", asFUNCTION(ASLevelOpAssign), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectMethod("ModLevel", "string GetTitle()", asFUNCTION(ASLevelGetTitle), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("ModLevel", "string GetID()", asFUNCTION(ASLevelGetID), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("ModLevel", "string GetThumbnail()", asFUNCTION(ASLevelGetThumbnail), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("ModLevel", "bool GetSupportsOnline()", asFUNCTION(ASLevelGetSupportsOnline), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("ModLevel", "bool GetRequiresOnline()", asFUNCTION(ASLevelGetRequiresOnline), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("ModLevel", "string GetPath()", asFUNCTION(ASLevelGetPath), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("ModLevel", "LevelDetails GetDetails()", asFUNCTION(ASLevelGetLevelDetails), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("ModLevel", "bool CompletionOptional()", asFUNCTION(ASLevelCompletionOptional), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterObjectMethod("ModLevel", "Parameter GetParameter()", asFUNCTION(ASLevelGetParameter), asCALL_CDECL_OBJFIRST);
+ context->DocsCloseBrace();
+
+ context->RegisterObjectType("Campaign", sizeof(ModInstance::Campaign), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK);
+ context->RegisterObjectBehaviour("Campaign", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ASCampaignConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("Campaign", asBEHAVE_CONSTRUCT, "void f(const Campaign &in other)", asFUNCTION(ASCampaignCopyConstructor), asCALL_CDECL_OBJLAST);
+ context->RegisterObjectBehaviour("Campaign", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(ASCampaignDestructor), asCALL_CDECL_OBJLAST);
+
+ context->RegisterObjectMethod("Campaign", "Campaign& opAssign(const Campaign &in other)", asFUNCTION(ASCampaignOpAssign), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Campaign", "string GetID()", asFUNCTION(ASCampaignGetID), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Campaign", "string GetTitle()", asFUNCTION(ASCampaignGetTitle), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Campaign", "bool GetSupportsOnline()", asFUNCTION(ASCampaignGetSupportsOnline), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Campaign", "bool GetRequiresOnline()", asFUNCTION(ASCampaignGetRequiresOnline), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Campaign", "string GetThumbnail()", asFUNCTION(ASCampaignGetThumbnail), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Campaign", "string GetMainScript()", asFUNCTION(ASCampaignGetMainScript), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Campaign", "string GetMenuScript()", asFUNCTION(ASCampaignGetMenuScript), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Campaign", "string GetAttribute(string &in id)", asFUNCTION(ASCampaignGetAttribute), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Campaign", "array<ModLevel>@ GetLevels()", asFUNCTION(ASCampaignGetLevels), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Campaign", "ModLevel GetLevel(string &in id)", asFUNCTION(ASCampaignGetLevel), asCALL_CDECL_OBJFIRST);
+ context->RegisterObjectMethod("Campaign", "Parameter GetParameter()", asFUNCTION(ASCampaignGetParameter), asCALL_CDECL_OBJFIRST);
+
+ context->RegisterGlobalFunction("Campaign GetCampaign(string& campaign_id)", asFUNCTION(ASGetCampaign), asCALL_CDECL);
+ context->RegisterGlobalFunction("array<Campaign>@ GetCampaigns()", asFUNCTION(ASGetCampaigns), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("bool ModIsActive(ModID& id)", asFUNCTION(ASModIsActive), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ModNeedsRestart(ModID& id)", asFUNCTION(ASModNeedsRestart), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ModIsValid(ModID& id)", asFUNCTION(ASModIsValid), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ModIsCore(ModID& id)", asFUNCTION(ASModIsCore), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ModCanActivate(ModID& id)", asFUNCTION(ASModCanActivate), asCALL_CDECL);
+ context->RegisterGlobalFunction("int ModGetSource(ModID& id)", asFUNCTION(ASModGetSource), asCALL_CDECL);
+ context->RegisterGlobalFunction("string ModGetID(ModID& id)", asFUNCTION(ASModGetID), asCALL_CDECL);
+ context->RegisterGlobalFunction("string ModGetName(ModID& id)", asFUNCTION(ASModGetName), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ModGetSupportsOnline(ModID& id)", asFUNCTION(ASModGetSupportsOnline), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ModGetSupportsCurrentVersion(ModID& id)", asFUNCTION(ASModGetSupportsCurrentVersion), asCALL_CDECL);
+ context->RegisterGlobalFunction("string ModGetAuthor(ModID& id)", asFUNCTION(ASModGetAuthor), asCALL_CDECL);
+ context->RegisterGlobalFunction("string ModGetVersion(ModID& id)", asFUNCTION(ASModGetVersion), asCALL_CDECL);
+ context->RegisterGlobalFunction("string ModGetTags(ModID& id)", asFUNCTION(ASModGetTags), asCALL_CDECL);
+ context->RegisterGlobalFunction("string ModGetPath(ModID& sid)", asFUNCTION(ASGetModPath), asCALL_CDECL);
+ context->RegisterGlobalFunction("string ModGetValidityString(ModID& sid)", asFUNCTION(ASGetModValidityString), asCALL_CDECL);
+ context->RegisterGlobalFunction("string ModGetDescription(ModID& id)", asFUNCTION(ASModGetDescription), asCALL_CDECL);
+ context->RegisterGlobalFunction("string ModGetThumbnail(ModID& sid)", asFUNCTION(ASGetModThumbnail), asCALL_CDECL);
+ context->RegisterGlobalFunction("array<MenuItem>@ ModGetMenuItems(ModID& sid)", asFUNCTION(ASModGetMenuItems), asCALL_CDECL);
+ context->RegisterGlobalFunction("array<SpawnerItem>@ ModGetSpawnerItems(ModID& sid)", asFUNCTION(ASModGetSpawnerItems), asCALL_CDECL);
+ context->RegisterGlobalFunction("array<SpawnerItem>@ ModGetAllSpawnerItems(bool only_include_active = true)", asFUNCTION(ASModGetAllSpawnerItems), asCALL_CDECL);
+ context->RegisterGlobalFunction("array<ModLevel>@ ModGetCampaignLevels(ModID& sid)", asFUNCTION(ASModGetModCampaignLevels), asCALL_CDECL);
+ context->RegisterGlobalFunction("array<ModLevel>@ ModGetSingleLevels(ModID& sid)", asFUNCTION(ASModGetModSingleLevels), asCALL_CDECL);
+ context->RegisterGlobalFunction("UserVote ModGetUserVote(ModID& sid)", asFUNCTION(ASModGetModUserVote), asCALL_CDECL);
+ context->RegisterGlobalFunction("void RequestModSetUserVote(ModID& id, bool voteup)", asFUNCTION(ASRequestModSetUserVote), asCALL_CDECL);
+ context->RegisterGlobalFunction("void RequestModSetFavorite(ModID& id, bool fav)", asFUNCTION(ASRequestModSetFavorite), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ModIsFavorite(ModID& id)", asFUNCTION(ASModIsFavorite), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("array<ModID>@ GetModSids()", asFUNCTION(ASGetModSids), asCALL_CDECL);
+ context->RegisterGlobalFunction("array<ModID>@ GetActiveModSids()", asFUNCTION(ASGetActiveModSids), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool ModActivation(ModID& sid, bool active)", asFUNCTION(ASModActivation), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("void RequestWorkshopSubscribe(ModID& id)", asFUNCTION(ASRequestWorkshopSubscribe), asCALL_CDECL);
+ context->RegisterGlobalFunction("void RequestWorkshopUnSubscribe(ModID& id)", asFUNCTION(ASRequestWorkshopUnSubscribe), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool IsWorkshopSubscribed(ModID& id)", asFUNCTION(ASIsWorkshopSubscribed), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool IsWorkshopMod(ModID& id)", asFUNCTION(ASIsWorkshopMod), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("bool IsWorkshopAvailable()", asFUNCTION(ASIsWorkshopAvailable), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("void SaveModConfig()", asFUNCTION(ASSaveModConfig), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("void OpenModWorkshopPage(ModID& id)", asFUNCTION(ASOpenModWorkshopPage), asCALL_CDECL);
+ context->RegisterGlobalFunction("void OpenModAuthorWorkshopPage(ModID& id)", asFUNCTION(ASOpenModAuthorWorkshopPage), asCALL_CDECL);
+ context->RegisterGlobalFunction("void OpenWorkshop()", asFUNCTION(ASOpenWorkshop), asCALL_CDECL);
+ context->RegisterGlobalFunction("void DeactivateAllMods()", asFUNCTION(ASDeactivateAllMods), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("uint WorkshopSubscribedNotInstalledCount()", asFUNCTION(ASWorkshopSubscribedNotInstalledCount), asCALL_CDECL);
+ context->RegisterGlobalFunction("uint WorkshopDownloadingCount()", asFUNCTION(ASWorkshopDownloadingCount), asCALL_CDECL);
+ context->RegisterGlobalFunction("uint WorkshopDownloadPendingCount()", asFUNCTION(ASWorkshopDownloadPendingCount), asCALL_CDECL);
+ context->RegisterGlobalFunction("uint WorkshopNeedsUpdateCount()", asFUNCTION(ASWorkshopNeedsUpdateCount), asCALL_CDECL);
+ context->RegisterGlobalFunction("float WorkshopTotalDownloadProgress()", asFUNCTION(ASWorkshopTotalDownloadProgress), asCALL_CDECL);
+}
+
+void AttachIMGUIModding(ASContext *context) {
+ context->RegisterGlobalFunction("IMImage@ ModGetThumbnailImage(ModID& sid)", asFUNCTION(ASGetModThumbnailImage), asCALL_CDECL);
+}
+
+static std::map<std::string,std::string> storage_string;
+static std::map<std::string,int32_t> storage_int32;
+
+void ASStorageSetString( std::string index, std::string value ) {
+ storage_string[index] = value;
+}
+
+bool ASStorageHasString( std::string index ) {
+ return storage_string.find(index) != storage_string.end();
+}
+
+std::string ASStorageGetString( std::string index ) {
+ return storage_string[index];
+}
+
+void ASStorageSetInt32( std::string index, int32_t value ) {
+ storage_int32[index] = value;
+}
+
+bool ASStorageHasInt32( std::string index ) {
+ return storage_int32.find(index) != storage_int32.end();
+}
+
+int32_t ASStorageGetInt32( std::string index ) {
+ return storage_int32[index];
+}
+
+//Routine for storing angelscript data over the course of single run.
+void AttachStorage(ASContext *context) {
+ context->RegisterGlobalFunction("void StorageSetString(string index, string value)", asFUNCTION(ASStorageSetString), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool StorageHasString(string index)", asFUNCTION(ASStorageHasString), asCALL_CDECL);
+ context->RegisterGlobalFunction("string StorageGetString(string index)", asFUNCTION(ASStorageGetString), asCALL_CDECL);
+
+ context->RegisterGlobalFunction("void StorageSetInt32(string index, int value)", asFUNCTION(ASStorageSetInt32), asCALL_CDECL);
+ context->RegisterGlobalFunction("bool StorageHasInt32(string index)", asFUNCTION(ASStorageHasInt32), asCALL_CDECL);
+ context->RegisterGlobalFunction("int StorageGetInt32(string index)", asFUNCTION(ASStorageGetInt32), asCALL_CDECL);
+}
diff --git a/Source/Scripting/angelscript/asfuncs.h b/Source/Scripting/angelscript/asfuncs.h
new file mode 100644
index 00000000..f2055b3b
--- /dev/null
+++ b/Source/Scripting/angelscript/asfuncs.h
@@ -0,0 +1,88 @@
+//-----------------------------------------------------------------------------
+// Name: asfuncs.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <string>
+#include <map>
+#include <cstdint>
+
+class asIScriptContext;
+struct asSMessageInfo;
+
+void PrintString(std::string &str);
+void MessageCallback(const asSMessageInfo *msg, void *param);
+
+class SceneGraph;
+class Engine;
+class ASContext;
+class MovementObject;
+
+void AttachASNetwork(ASContext* context);
+void AttachStringConvert(ASContext *context);
+void AttachUIQueries(ASContext *context);
+void AttachMathFuncs(ASContext *context);
+void Attach3DMathFuncs(ASContext *context);
+void AttachActiveCamera(ASContext *context);
+void AttachMovementObjectCamera(ASContext *context, MovementObject* mo);
+void AttachTimer(ASContext *context);
+void AttachPhysics(ASContext *context);
+void AttachSound( ASContext *context );
+void AttachParticles( ASContext *context );
+void AttachDebugDraw( ASContext *context );
+void AttachDecals( ASContext *context );
+void AttachEngine( ASContext *context );
+void AttachScenegraph( ASContext *context, SceneGraph *scenegraph );
+void AttachLevel( ASContext *context );
+void AttachInterlevelData( ASContext *context );
+void AttachIMGUI( ASContext *context );
+void AttachIMGUIModding( ASContext *context );
+void AttachNavMesh(ASContext *context);
+void AttachMessages(ASContext *context);
+void AttachScreenWidth(ASContext *context);
+void AttachObject(ASContext *context);
+void AttachPlaceholderObject(ASContext *context);
+void AttachTokenIterator(ASContext *context);
+void AttachError(ASContext *context);
+void AttachSky( ASContext *context );
+void AttachSimpleFile( ASContext *context );
+void AttachStopwatch( ASContext *context );
+void AttachProfiler( ASContext* context );
+void AttachTelemetry( ASContext *context );
+void AttachUndo( ASContext *context );
+void AttachLog( ASContext *context );
+void AttachInfo( ASContext *context );
+void AttachJSON( ASContext *context );
+void AttachConfig( ASContext *context );
+void AttachStringUtil( ASContext *context );
+void AttachIO( ASContext *context );
+void AttachLevelDetails(ASContext *context);
+void AttachModding( ASContext* context );
+void AttachStorage( ASContext* context );
+void AttachDebug( ASContext *context );
+void AttachOnline( ASContext * context);
+
+void ReloadConfigValues();
+void SetSettingsToPreset( std::string preset_name );
+
+int ASCreateObject(const std::string& path, bool exclude_from_save);
+class Object* ReadObjectFromID(int id);
diff --git a/Source/Scripting/angelscript/asmodule.cpp b/Source/Scripting/angelscript/asmodule.cpp
new file mode 100644
index 00000000..524f4afc
--- /dev/null
+++ b/Source/Scripting/angelscript/asmodule.cpp
@@ -0,0 +1,1993 @@
+//-----------------------------------------------------------------------------
+// Name: asmodule.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "asmodule.h"
+
+#include <Scripting/scriptfile.h>
+#include <Scripting/angelscript/add_on/scriptarray/scriptarray.h>
+#include <Scripting/angelscript/add_on/scriptdictionary/scriptdictionary.h>
+
+#include <Math/vec2.h>
+#include <Math/vec3.h>
+#include <Math/vec4.h>
+#include <Math/ivec2.h>
+#include <Math/ivec3.h>
+#include <Math/ivec4.h>
+
+#include <Internal/config.h>
+#include <Internal/profiler.h>
+#include <Internal/modloading.h>
+#include <Internal/common.h>
+#include <Internal/filesystem.h>
+
+#include <GUI/IMUI/imgui.h>
+#include <GUI/IMUI/imui.h>
+#include <GUI/IMUI/im_container.h>
+#include <GUI/IMUI/im_divider.h>
+#include <GUI/IMUI/im_element.h>
+#include <GUI/IMUI/im_image.h>
+#include <GUI/IMUI/im_message.h>
+#include <GUI/IMUI/im_selection_list.h>
+#include <GUI/IMUI/im_spacer.h>
+#include <GUI/IMUI/im_text.h>
+#include <GUI/IMUI/im_behaviors.h>
+#include <GUI/IMUI/imui_state.h>
+#include <GUI/IMUI/im_support.h>
+#include <GUI/IMUI/im_tween.h>
+#include <GUI/IMUI/im_behaviors.h>
+#include <GUI/IMUI/imui_state.h>
+
+#include <AI/navmesh.h>
+#include <Compat/fileio.h>
+#include <Version/version.h>
+#include <Logging/logdata.h>
+#include <Utility/strings.h>
+#include <Game/attackscript.h>
+#include <Graphics/bonetransform.h>
+
+#include <sstream>
+#include <cstring>
+
+enum ASErrType {
+ _ERR = 0,
+ _WARN = 1,
+ _INFO = 2
+};
+
+std::string StringFromErrType(ASErrType type){
+ switch(type){
+ case _ERR:
+ return "ERR";
+ case _WARN:
+ return "WARN";
+ case _INFO:
+ return "INFO";
+ }
+ return "";
+}
+
+struct ASErrReport {
+ ASErrType type;
+ unsigned line_number;
+ unsigned column_number;
+ std::string message;
+ std::string file_name;
+
+ ASErrReport(const std::string &err_line);
+};
+
+ASErrReport::ASErrReport(const std::string &err_line) {
+ //Lines are like this:
+ // script (544, 20) : INFO : Compiling const float offset
+ size_t open_paren = err_line.find('(');
+ size_t comma = err_line.find(',');
+ std::string first_num = err_line.substr(open_paren+1,comma-open_paren-1);
+ size_t close_paren = err_line.find(')');
+ std::string second_num = err_line.substr(comma+2,close_paren-comma-2);
+ size_t first_colon = err_line.find(':');
+ size_t second_colon = err_line.find(':',first_colon+1);
+ std::string type_str = err_line.substr(first_colon+2,
+ second_colon-first_colon-3);
+ message = err_line.substr(second_colon+2);
+ line_number = atoi(first_num.c_str());
+ column_number = atoi(second_num.c_str());
+ if(type_str == "ERR "){
+ type = _ERR;
+ } else if(type_str == "WARN"){
+ type = _WARN;
+ } else if(type_str == "INFO"){
+ type = _INFO;
+ }
+}
+
+typedef std::list<ASErrReport> ErrList;
+
+std::string CorrectASError(const std::string& input_error,
+ const ScriptFile &script_file) {
+ // Break down error message into a list of errors
+ ErrList errors;
+ {
+ size_t line_break = 0;
+ unsigned input_error_size = input_error.size();
+ do {
+ size_t next_line_break = input_error.find('\n',line_break+1);
+ if(next_line_break == std::string::npos){
+ next_line_break = input_error_size;
+ }
+ if(next_line_break - line_break < 10){
+ break;
+ }
+ std::string err_line = input_error.substr(line_break,
+ next_line_break-line_break);
+ errors.push_back(ASErrReport(err_line));
+ line_break = next_line_break;
+ } while(line_break != input_error_size);
+ }
+
+ // Correct line numbers and file names
+ {
+ ErrList::iterator iter = errors.begin();
+ for(;iter != errors.end(); ++iter){
+ ASErrReport &err = (*iter);
+ LineFile line_file = script_file.GetCorrectedLine(err.line_number);
+ err.line_number = line_file.line_number;
+ err.file_name = line_file.file.GetFullPath();
+ err.file_name = err.file_name.substr(err.file_name.rfind('/')+1);
+ }
+ }
+
+ // Create new error message string
+ std::string err_message;
+ {
+ std::ostringstream oss;
+ ErrList::iterator iter = errors.begin();
+ for(;iter != errors.end(); ++iter){
+ ASErrReport &err = (*iter);
+ if(err.type != _INFO){
+ oss << " " << StringFromErrType(err.type) << ": "
+ << err.file_name << " (" << err.line_number
+ << ", " << err.column_number << ") "
+ << err.message << "\n";
+ } else {
+ oss << err.message << "\n";
+ }
+ }
+ err_message = oss.str();
+ }
+
+ return err_message;
+}
+
+int ASModule::CompileScriptFromText(const std::string &text) {
+ func_map_.clear();
+ fast_var_ptr_map.clear();
+ bool retry = true;
+ while(retry){
+ const std::string &script = text;
+
+ std::string filename = "Hard-coded text";
+ // Add the script sections that will be compiled into executable code.
+ // If we want to combine more than one file into the same script, then
+ // we can call AddScriptSection() several times for the same module and
+ // the script engine will treat them all as if they were one. The script
+ // section name, will allow us to localize any errors in the script code.
+ int r = module_->AddScriptSection("script", &script[0], script.length());
+ if( r < 0 )
+ {
+ static const int kBufSize = 512;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "Error in \"%s\"", filename.c_str());
+ FatalError(buf,"AddScriptSection() failed");
+ return -1;
+ }
+
+ // Compile the script. If there are any compiler messages they will
+ // be written to the message stream that we set right after creating the
+ // script engine. If there are no errors, and no warnings, nothing will
+ // be written to the stream.
+ active_angelscript_error_string->clear();
+ r = module_->Build();
+ if( r < 0)
+ {
+ ErrorResponse err;
+ err = DisplayError(("Error in \""+filename+"\"").c_str(),
+ active_angelscript_error_string->c_str(),
+ _ok_cancel_retry);
+ if(err != _retry){
+ return -1;
+ }
+ } else if(!active_angelscript_error_string->empty())
+ {
+ ErrorResponse err;
+ err = DisplayError(("Warnings in \""+filename+"\"").c_str(),
+ active_angelscript_error_string->c_str(),
+ _ok_cancel_retry);
+ if(err == _continue){
+ retry = false;
+ }
+ } else {
+ retry = false;
+ }
+
+ script_path_ = Path();
+ }
+
+ // The engine doesn't keep a copy of the script sections after Build() has
+ // returned. So if the script needs to be recompiled, then all the script
+ // sections must be added again.
+
+ // If we want to have several scripts executing at different times but
+ // that have no direct relation with each other, then we can compile them
+ // into separate script modules. Each module use their own namespace and
+ // scope, so function names, and global variables will not conflict with
+ // each other.
+
+ return 0;
+}
+
+class CBytecodeStream : public asIBinaryStream {
+public:
+ CBytecodeStream(FILE *fp) : f(fp) {}
+
+ int Write(const void *ptr, asUINT size) {
+ if( size == 0 ) return -1;
+ return fwrite(ptr, size, 1, f);
+ }
+ int Read(void *ptr, asUINT size) {
+ if( size == 0 ) return - 1;
+ return fread(ptr, size, 1, f);
+ }
+
+protected:
+ FILE *f;
+};
+
+const uint32_t script_bytecode_ver = 10;
+const uint32_t script_bytecode_program_build_length = 512;
+
+int ASModule::CompileScript(const Path& path) {
+ fast_var_ptr_map.clear();
+ func_map_.clear();
+ bool retry = true;
+ while(retry){
+ if(module_->GetEngine()){
+ asIScriptEngine* engine = module_->GetEngine();
+ module_->Discard();
+ AttachToEngine(engine);
+ }
+ // Load the script file, along with all its include files
+ const ScriptFile &script_file = *ScriptFileUtil::GetScriptFile(path);
+ script_file_ptr_ = &script_file;
+ modified_ = script_file.latest_modification;
+
+ // Hash the contents of the script, and use that to find the storage location
+ // of the script bytecode
+ bool bytecode_loaded = false;
+ int last_slash = 0;
+ std::string original_path = path.GetOriginalPathStr();
+ for(int i=original_path.length()-2; i>=0; --i){
+ if(original_path[i] == '/' || original_path[i] == '\\'){
+ last_slash = i+1;
+ break;
+ }
+ }
+ const char* script_name = &original_path[last_slash];
+
+ if( config["dump_include_scripts"].toBool() )
+ {
+ std::stringstream debug_script_path;
+ debug_script_path << GetWritePath(script_file.file_path.GetModsource()).c_str() << "Data/ScriptDebug/" << script_name << ".full.as";
+ FILE *debug_script_file = my_fopen( debug_script_path.str().c_str(), "wb" );
+ LOGI << "Dumping script " << script_name << std::endl;
+ if( debug_script_file )
+ {
+ fwrite( script_file.contents.c_str(), sizeof(char), script_file.contents.size(), debug_script_file );
+ fclose( debug_script_file );
+ }
+ }
+
+ static const int kBufSize = 512;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "%sData/ScriptBytecode/%s.bytecode",GetWritePath(script_file.file_path.GetModsource()).c_str(), script_name) ;
+ FILE *file = my_fopen(buf, "rb");
+ if( file ) {
+ // Compare the bytecode hash to the script hash to make sure it matches
+
+ // We check the base version first, because this allows us to change anything that comes after.
+ uint32_t ver;
+ fread(&ver, sizeof(uint32_t), 1, file);
+ if( script_bytecode_ver == ver )
+ {
+ char build_id_string[script_bytecode_program_build_length+1];
+ memset(build_id_string, '\0' , script_bytecode_program_build_length+1);
+ fread(build_id_string, sizeof(char) * script_bytecode_program_build_length, 1, file);
+
+ unsigned long file_hash;
+ fread(&file_hash, sizeof(unsigned long), 1, file);
+ if( file_hash == script_file.hash && strcmp( build_id_string,GetFullBuildString().c_str() ) == 0 ) {
+ // Hash matches, so load the bytecode into the module
+ CBytecodeStream byte_code_stream(file);
+ if(module_->LoadByteCode(&byte_code_stream) < 0) {
+ LOGE << "Problem loading saved script bytecode from file \"" << file << "\", recompiling." << std::endl;
+ } else {
+ bytecode_loaded = true;
+ retry = false;
+ }
+ }
+ else
+ {
+ LOGI << "Script byte code for " << buf << " appears outdated, recompiling." << std::endl;
+ }
+ }
+ else
+ {
+ LOGI << "Script byte code base version for " << buf << " appears outdated, recompiling." << std::endl;
+ }
+ fclose(file);
+ }
+ if(!bytecode_loaded){
+ std::string script = script_file.contents;
+ // Get the last section of the path and store it for pretty filename display
+ int last_slash_pos = path.GetFullPathStr().rfind('/');
+ std::string filename = path.GetFullPathStr().substr(last_slash_pos+1);
+
+ // Add the script contents to the module
+ int r = module_->AddScriptSection("script", &script[0], script.length());
+ if(r < 0){
+ static const int kBufSize = 512;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "Error in \"%s\"", filename.c_str());
+ FatalError(buf,"AddScriptSection() failed");
+ return -1;
+ }
+
+ // Clear error messages
+ active_angelscript_error_string->clear();
+
+ // Compile the module
+ r = module_->Build();
+ if(r < 0) {
+ // There was an error! Print error, and retry
+ std::string title = "Error in \""+filename+"\" from mod "+ModLoading::Instance().GetModName(script_file.file_path.GetModsource());
+ bool showModPollution = !config.HasKey("list_include_files_when_logging_mod_errors") ||
+ config["list_include_files_when_logging_mod_errors"].toBool();
+ std::string corrected_error =
+ title + ":\n\n" +
+ (showModPollution ? (script_file.GetModPollutionInformation() + "\n") : "") +
+ CorrectASError(*(active_angelscript_error_string),
+ script_file);
+ ErrorResponse err;
+ err = DisplayError(title.c_str(),
+ corrected_error.c_str(),
+ _ok_cancel_retry);
+ if(err != _retry){
+ return -1;
+ }
+ } else if(!active_angelscript_error_string->empty()) {
+ // There were warnings! Print warnings, and retry or continue
+ std::string title = "Warnings in \""+filename+"\" from mod "+ModLoading::Instance().GetModName(script_file.file_path.GetModsource());
+ bool showModPollution = !config.HasKey("list_include_files_when_logging_mod_errors") ||
+ config["list_include_files_when_logging_mod_errors"].toBool();
+ std::string corrected_error =
+ title + ":\n\n" +
+ (showModPollution ? (script_file.GetModPollutionInformation() + "\n") : "") +
+ CorrectASError(*(active_angelscript_error_string),
+ script_file);
+ ErrorResponse err;
+ err = DisplayError(title.c_str(),
+ corrected_error.c_str(),
+ _ok_cancel_retry);
+ if(err == _continue){
+ retry = false;
+ }
+ } else {
+ // Compile successful!
+ retry = false;
+ }
+
+ // If compile was successful, save bytecode
+
+
+ //Disabling on linux amd64 platoform because this routine does not work on that platform atm.
+ //waiting for feedback on issue
+ #if !(PLATFORM_LINUX && PLATFORM_64)
+ if(!retry){
+ FILE *file = my_fopen(buf, "wb");
+ if(file){
+ fwrite(&script_bytecode_ver, sizeof(uint32_t), 1, file);
+
+ char build_id_string[script_bytecode_program_build_length+1];
+ memset(build_id_string, '\0' , script_bytecode_program_build_length+1);
+ memcpy(build_id_string, GetFullBuildString().c_str(), sizeof(char) * GetFullBuildString().length());
+ fwrite(build_id_string, sizeof(char) * script_bytecode_program_build_length, 1, file);
+
+ unsigned long temp = script_file.hash;
+ fwrite(&temp, sizeof(unsigned long), 1, file);
+ CBytecodeStream byte_code_stream(file);
+ if(module_->SaveByteCode(&byte_code_stream) < 0){
+ DisplayError("Error", "Problem saving script bytecode: ");
+ }
+ fclose(file);
+ } else {
+ char err_msg[kBufSize];
+ FormatString(err_msg, kBufSize, "Problem saving script bytecode to %s",buf);
+ DisplayError("Error", err_msg);
+ }
+ }
+ #endif
+ }
+
+ script_path_ = path;
+ }
+ return 0;
+}
+
+bool ASModule::SourceChanged() {
+ if(ScriptFileUtil::GetLatestModification(script_path_) > modified_){
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void ASModule::PrintScriptClassVars(asIScriptObject* obj) {
+ printf("Script class vars:\n");
+ int c = obj->GetPropertyCount();
+ for(int n=0; n<c; ++n){
+ const char *name = obj->GetPropertyName(n);
+ printf("%s\n",name);
+ }
+}
+
+void FillVar( VarStorage & new_var, const char * name, int type_id, void* ptr, std::list<ScriptObjectInstance>& handle_var_list, asIScriptModule* module );
+
+void ScriptObject::Populate( asIScriptObject * obj, std::list<ScriptObjectInstance>& handle_var_list, asIScriptModule *module )
+{
+ int c = obj->GetPropertyCount();
+ for( int n = 0; n < c; n++ )
+ {
+ const char *name = obj->GetPropertyName(n);
+ int type_id = obj->GetPropertyTypeId(n);
+ void* ptr = obj->GetAddressOfProperty(n);
+ VarStorage new_var;
+ FillVar(new_var, name, type_id, ptr, handle_var_list, module);
+ storage.push_back(new_var);
+ }
+}
+
+void ScriptObject::destroy() {
+ std::list<VarStorage>::iterator iter = storage.begin();
+ for(; iter != storage.end(); ++iter) {
+ iter->destroy();
+ }
+}
+
+std::string VarStorage::GetString(unsigned depth)
+{
+ std::string type_str;
+ std::string val_str;
+ std::ostringstream oss;
+
+ if(type == _vs_bool){
+ type_str = "bool";
+ val_str = *(bool*)var?"true":"false";
+ } else if(type == _vs_int8){
+ type_str = "int8";
+ oss << *(char*)var;
+ val_str = oss.str();
+ } else if(type == _vs_int16){
+ type_str = "int16";
+ oss << *(short*)var;
+ val_str = oss.str();
+ } else if(type == _vs_int32){
+ type_str = "int32";
+ oss << *(int*)var;
+ val_str = oss.str();
+ } else if(type == _vs_int64){
+ type_str = "int64";
+ oss << *(int64_t*)var;
+ val_str = oss.str();
+ } else if(type == _vs_uint8){
+ type_str = "uint8";
+ oss << *(unsigned char*)var;
+ val_str = oss.str();
+ } else if(type == _vs_uint16){
+ type_str = "uint16";
+ oss << *(unsigned short*)var;
+ val_str = oss.str();
+ } else if(type == _vs_uint32){
+ type_str = "uint32";
+ oss << *(unsigned*)var;
+ val_str = oss.str();
+ } else if(type == _vs_uint64){
+ type_str = "uint64";
+ oss << *(uint64_t*)var;
+ val_str = oss.str();
+ } else if(type == _vs_float){
+ type_str = "float";
+ oss << *(float*)var;
+ val_str = oss.str();
+ } else if(type == _vs_enum){
+ type_str = "enum";
+ for( int i = 0; i < size; i++ ) {
+ oss << ((char*)var)[i] << " ";
+ }
+ val_str = oss.str();
+ } else if(type == _vs_app_obj){
+ AppObject &ao = *(AppObject*)var;
+ if(ao.type == _ao_vec3){
+ type_str = "vec3";
+ vec3 &vec = *(vec3*)ao.var;
+ oss << "[" << vec[0] << ", " << vec[1] << ", " << vec[2] << "]";
+ val_str = oss.str();
+ } else if(ao.type == _ao_string){
+ type_str = "string";
+ std::string &str = *(std::string*)ao.var;
+ val_str = "\""+str+"\"";
+ } else {
+ type_str = "unknown app object";
+ }
+ } else if(type == _vs_script_obj){
+ type_str = "script object";
+ std::string total;
+ for(unsigned i=0; i<depth; ++i){
+ total += " ";
+ }
+ ScriptObject &so = *(ScriptObject*)var;
+ total += so.type_name+" "+name+":";
+ std::list<VarStorage>::iterator iter = so.storage.begin();
+ for(; iter!=so.storage.end(); ++iter){
+ total += "\n";
+ total += (*iter).GetString(depth+1);
+ }
+ return total;
+ } else if(type == _vs_app_obj_handle ) {
+ type_str = "app obj handle";
+ } else if(type == _vs_template){
+ TemplateObject &to = *(TemplateObject*)var;
+ if(to.type == _to_array){
+ type_str = "array";
+ type_str += "<"+to.sub_type_name+">";
+ std::string total;
+ for(unsigned i=0; i<depth; ++i){
+ total += " ";
+ }
+ total += type_str + " " + name + ":";
+ std::list<VarStorage>& storage = *(std::list<VarStorage>*)to.var;
+ std::list<VarStorage>::iterator iter = storage.begin();
+ for(; iter!=storage.end(); ++iter){
+ total += "\n";
+ total += (*iter).GetString(depth+1);
+ }
+ return total;
+ } else {
+ type_str = "template";
+ }
+ type_str += "<"+to.sub_type_name+">";
+
+ } else {
+ type_str = "unknown";
+ }
+ std::string total;
+ for(unsigned i=0; i<depth; ++i){
+ total += " ";
+ }
+ total += type_str + " " + name + " = " + val_str;
+ return total;
+}
+
+void VarStorage::destroy()
+{
+ switch(type){
+ case _vs_bool: delete (bool*)var; break;
+ case _vs_int8: delete (char*)var; break;
+ case _vs_int16: delete (short*)var; break;
+ case _vs_int32: delete (int*)var; break;
+ case _vs_int64: delete (int64_t*)var; break;
+ case _vs_uint8: delete (unsigned char*)var; break;
+ case _vs_uint16: delete (unsigned short*)var; break;
+ case _vs_uint32: delete (unsigned int*)var; break;
+ case _vs_uint64: delete (uint64_t*)var; break;
+ case _vs_float: delete (float*)var; break;
+ case _vs_enum: delete [] (char*)var; break;
+ case _vs_app_obj: {
+ AppObject* ao = (AppObject*)var;
+ ao->destroy();
+ delete ao;
+ break;
+ }
+ case _vs_script_obj: {
+ ScriptObject* so = (ScriptObject*)var;
+ so->destroy();
+ delete so;
+ break;
+ }
+ case _vs_template: {
+ TemplateObject* to = (TemplateObject*)var;
+ to->destroy();
+ delete to;
+ break;
+ }
+ case _vs_script_obj_handle: {
+ //We don't do nuthing, because we have a handle to a _vs_script_obj
+ break;
+ }
+ case _vs_app_obj_handle: {
+ AppObjectHandle* sao = (AppObjectHandle*)var;
+ sao->destroy();
+ delete sao;
+ break;
+ }
+ case _vs_template_handle: {
+ break;
+ }
+
+ case _vs_unknown: {
+ break;
+ }
+
+ default: {
+ LOGE << "(Leak) No destroy() routine for VarStorage type: " << type << std::endl;
+ break;
+ }
+ }
+}
+
+VarStorage::VarStorage() : var(NULL), type(_vs_unknown) {
+}
+
+ScriptObjectInstance::ScriptObjectInstance(uintptr_t ptr, VarStorage& vari, bool _from_script_obj_value) : ptr(ptr), var(vari), new_ptr(NULL), reconstructed(false), from_script_obj_value(_from_script_obj_value) {
+}
+
+void ScriptObjectInstance::destroy() {
+ if( new_ptr ) {
+ new_ptr->Release();
+ }
+ var.destroy();
+}
+
+void FillVar( VarStorage & new_var, const char * name, int type_id, void* ptr, std::list<ScriptObjectInstance>& handle_var_list, asIScriptModule* module )
+{
+ new_var.name = name;
+ new_var.type = _vs_unknown;
+ switch(type_id){
+ case asTYPEID_BOOL:
+ new_var.type = _vs_bool;
+ new_var.var = new bool;
+ *(bool*)new_var.var = *(bool*)ptr;
+ break;
+ case asTYPEID_INT8:
+ new_var.type = _vs_int8;
+ new_var.var = new char;
+ *(char*)new_var.var = *(char*)ptr;
+ break;
+ case asTYPEID_INT16:
+ new_var.type = _vs_int16;
+ new_var.var = new short;
+ *(short*)new_var.var = *(short*)ptr;
+ break;
+ case asTYPEID_INT32:
+ new_var.type = _vs_int32;
+ new_var.var = new int;
+ *(int*)new_var.var = *(int*)ptr;
+ break;
+ case asTYPEID_INT64:
+ new_var.type = _vs_int64;
+ new_var.var = new int64_t;
+ *(int64_t*)new_var.var = *(int64_t*)ptr;
+ break;
+ case asTYPEID_UINT8:
+ new_var.type = _vs_uint8;
+ new_var.var = new unsigned char;
+ *(unsigned char*)new_var.var = *(unsigned char*)ptr;
+ break;
+ case asTYPEID_UINT16:
+ new_var.type = _vs_uint16;
+ new_var.var = new unsigned short;
+ *(unsigned short*)new_var.var = *(unsigned short*)ptr;
+ break;
+ case asTYPEID_UINT32:
+ new_var.type = _vs_uint32;
+ new_var.var = new unsigned;
+ *(unsigned*)new_var.var = *(unsigned*)ptr;
+ break;
+ case asTYPEID_UINT64:
+ new_var.type = _vs_uint64;
+ new_var.var = new uint64_t;
+ *(uint64_t*)new_var.var = *(uint64_t*)ptr;
+ break;
+ case asTYPEID_FLOAT:
+ new_var.type = _vs_float;
+ new_var.var = new float;
+ *(float*)new_var.var = *(float*)ptr;
+ break;
+ default:
+ asIScriptEngine* engine = module->GetEngine();
+ asITypeInfo* type = engine->GetTypeInfoById(type_id);
+ if( type_id & asTYPEID_APPOBJECT ) {
+ if( type_id & asTYPEID_OBJHANDLE ) {
+ new_var.type = _vs_app_obj_handle;
+ const char* type_name = type->GetName();
+ AppObjectHandle* oh = new AppObjectHandle();
+ oh->var = *(void**)ptr;
+ new_var.var = oh;
+
+ if(strmtch(type_name, "IMFadeIn")) {
+ IMFadeIn* d = *(static_cast<IMFadeIn**>(ptr));
+ oh->type = _ao_IMFadeIn;
+ if(d) {
+ d->AddRef();
+ }
+ } else if(strmtch(type_name, "IMMoveIn")) {
+ IMMoveIn* d = *(static_cast<IMMoveIn**>(ptr));
+ oh->type = _ao_IMMoveIn;
+ if(d) {
+ d->AddRef();
+ }
+ } else if(strmtch(type_name, "IMChangeTextFadeOutIn")) {
+ IMChangeTextFadeOutIn* d = *(static_cast<IMChangeTextFadeOutIn**>(ptr));
+ oh->type = _ao_IMChangeTextFadeOutIn;
+ if(d) {
+ d->AddRef();
+ }
+ } else if(strmtch(type_name, "IMChangeImageFadeOutIn")) {
+ IMChangeImageFadeOutIn* d = *(static_cast<IMChangeImageFadeOutIn**>(ptr));
+ oh->type = _ao_IMChangeImageFadeOutIn;
+ if(d) {
+ d->AddRef();
+ }
+ } else if(strmtch(type_name, "IMPulseAlpha")) {
+ IMPulseAlpha* d = *(static_cast<IMPulseAlpha**>(ptr));
+ oh->type = _ao_IMPulseAlpha;
+ if(d) {
+ d->AddRef();
+ }
+ } else if(strmtch(type_name, "IMPulseBorderAlpha")) {
+ IMPulseBorderAlpha* d = *(static_cast<IMPulseBorderAlpha**>(ptr));
+ oh->type = _ao_IMPulseBorderAlpha;
+ if(d) {
+ d->AddRef();
+ }
+ } else if(strmtch(type_name, "IMPulseBorderAlpha")) {
+ IMPulseBorderAlpha* d = *(static_cast<IMPulseBorderAlpha**>(ptr));
+ oh->type = _ao_IMPulseBorderAlpha;
+ if(d) {
+ d->AddRef();
+ }
+ } else if(strmtch(type_name, "IMMouseOverMove")) {
+ IMMouseOverMove* d = *(static_cast<IMMouseOverMove**>(ptr));
+ oh->type = _ao_IMMouseOverMove;
+ if(d) {
+ d->AddRef();
+ }
+ } else if(strmtch(type_name, "IMMouseOverScale")) {
+ IMMouseOverScale* d = *(static_cast<IMMouseOverScale**>(ptr));
+ oh->type = _ao_IMMouseOverScale;
+ if(d) {
+ d->AddRef();
+ }
+ } else if(strmtch(type_name, "IMMouseOverShowBorder")) {
+ IMMouseOverShowBorder* d = *(static_cast<IMMouseOverShowBorder**>(ptr));
+ oh->type = _ao_IMMouseOverShowBorder;
+ if(d) {
+ d->AddRef();
+ }
+ } else if(strmtch(type_name, "IMMouseOverShowBorder")) {
+ IMMouseOverShowBorder* d = *(static_cast<IMMouseOverShowBorder**>(ptr));
+ oh->type = _ao_IMMouseOverShowBorder;
+ if(d) {
+ d->AddRef();
+ }
+ } else if(strmtch(type_name, "IMMouseOverPulseColor")) {
+ IMMouseOverPulseColor* d = *(static_cast<IMMouseOverPulseColor**>(ptr));
+ oh->type = _ao_IMMouseOverPulseColor;
+ if(d) {
+ d->AddRef();
+ }
+ } else if(strmtch(type_name, "IMMouseOverPulseBorder")) {
+ IMMouseOverPulseBorder* d = *(static_cast<IMMouseOverPulseBorder**>(ptr));
+ oh->type = _ao_IMMouseOverPulseBorder;
+ if(d) {
+ d->AddRef();
+ }
+ } else if(strmtch(type_name, "IMMouseOverPulseBorderAlpha")) {
+ IMMouseOverPulseBorderAlpha* d = *(static_cast<IMMouseOverPulseBorderAlpha**>(ptr));
+ oh->type = _ao_IMMouseOverPulseBorderAlpha;
+ if(d) {
+ d->AddRef();
+ }
+ } else if(strmtch(type_name, "IMMouseOverFadeIn")) {
+ IMMouseOverFadeIn* d = *(static_cast<IMMouseOverFadeIn**>(ptr));
+ oh->type = _ao_IMMouseOverFadeIn;
+ if(d) {
+ d->AddRef();
+ }
+ } else if(strmtch(type_name, "IMFixedMessageOnMouseOver")) {
+ IMFixedMessageOnMouseOver* d = *(static_cast<IMFixedMessageOnMouseOver**>(ptr));
+ oh->type = _ao_IMFixedMessageOnMouseOver;
+ if(d) {
+ d->AddRef();
+ }
+ } else if(strmtch(type_name, "IMFixedMessageOnClick")) {
+ IMFixedMessageOnClick* d = *(static_cast<IMFixedMessageOnClick**>(ptr));
+ oh->type = _ao_IMFixedMessageOnClick;
+ if(d) {
+ d->AddRef();
+ }
+ } else if(strmtch(type_name, "IMMessage")) {
+ IMMessage* d = *(static_cast<IMMessage**>(ptr));
+ oh->type = _ao_IMMessage;
+ if(d) {
+ d->AddRef();
+ }
+ } else if(strmtch(type_name, "IMGUI")) {
+ IMGUI* d = *(static_cast<IMGUI**>(ptr));
+ oh->type = _ao_IMGUI;
+ if(d) {
+ d->AddRef();
+ }
+ } else if(strmtch(type_name, "IMElement")) {
+ IMElement* d = *(static_cast<IMElement**>(ptr));
+ oh->type = _ao_IMElement;
+ if(d) {
+ d->AddRef();
+ }
+ } else if(strmtch(type_name, "IMContainer")) {
+ IMContainer* d = *(static_cast<IMContainer**>(ptr));
+ oh->type = _ao_IMContainer;
+ if(d) {
+ d->AddRef();
+ }
+ } else if(strmtch(type_name, "IMDivider")) {
+ IMDivider* d = *(static_cast<IMDivider**>(ptr));
+ oh->type = _ao_IMDivider;
+ if(d) {
+ d->AddRef();
+ }
+ } else if(strmtch(type_name, "IMImage")) {
+ IMImage* d = *(static_cast<IMImage**>(ptr));
+ oh->type = _ao_IMImage;
+ if(d) {
+ d->AddRef();
+ }
+ } else if(strmtch(type_name, "IMText")) {
+ IMText* d = *(static_cast<IMText**>(ptr));
+ oh->type = _ao_IMText;
+ if(d) {
+ d->AddRef();
+ }
+ } else if(strmtch(type_name, "IMTextSelectionList")) {
+ IMTextSelectionList* d = *(static_cast<IMTextSelectionList**>(ptr));
+ oh->type = _ao_IMTextSelectionList;
+ if(d) {
+ d->AddRef();
+ }
+ } else if(strmtch(type_name, "IMSpacer")) {
+ IMSpacer* d = *(static_cast<IMSpacer**>(ptr));
+ oh->type = _ao_IMSpacer;
+ if(d) {
+ d->AddRef();
+ }
+ // -- Begin scenegraph objects --
+ } else if(strmtch(type_name, "Object")) {
+ oh->type = _ao_game_object;
+ } else if(strmtch(type_name, "AmbientSoundObject")) {
+ oh->type = _ao_ambient_sound_object;
+ } else if(strmtch(type_name, "CameraObject")) {
+ oh->type = _ao_camera_object;
+ } else if(strmtch(type_name, "DecalObject")) {
+ oh->type = _ao_decal_object;
+ } else if(strmtch(type_name, "DynamicLightObject")) {
+ oh->type = _ao_dynamic_light_object;
+ } else if(strmtch(type_name, "EnvObject")) {
+ oh->type = _ao_env_object;
+ } else if(strmtch(type_name, "Group")) {
+ oh->type = _ao_group_object;
+ } else if(strmtch(type_name, "Hotspot")) {
+ oh->type = _ao_hotspot_object;
+ } else if(strmtch(type_name, "ItemObject")) {
+ oh->type = _ao_item_object;
+ } else if(strmtch(type_name, "LightProbeObject")) {
+ oh->type = _ao_light_probe_object;
+ } else if(strmtch(type_name, "LightVolumeObject")) {
+ oh->type = _ao_light_volume_object;
+ } else if(strmtch(type_name, "MovementObject")) {
+ oh->type = _ao_movement_object;
+ } else if(strmtch(type_name, "NavmeshConnectionObject")) {
+ oh->type = _ao_navmesh_connection_object;
+ } else if(strmtch(type_name, "NavmeshHintObject")) {
+ oh->type = _ao_navmesh_hint_object;
+ } else if(strmtch(type_name, "NavmeshRegionObject")) {
+ oh->type = _ao_navmesh_region_object;
+ } else if(strmtch(type_name, "PathPointObject")) {
+ oh->type = _ao_path_point_object;
+ } else if(strmtch(type_name, "PlaceholderObject")) {
+ oh->type = _ao_placeholder_object;
+ } else if(strmtch(type_name, "ReflectionCaptureObject")) {
+ oh->type = _ao_reflection_capture_object;
+ } else if(strmtch(type_name, "RiggedObject")) {
+ oh->type = _ao_rigged_object;
+ } else if(strmtch(type_name, "TerrainObject")) {
+ oh->type = _ao_terrain_object;
+ // -- End scenegraph objects --
+ } else {
+ LOGE << "Unhandled handle for name " << name << std::endl;
+ LOGE << "Typename " << type_name << std::endl;
+ delete oh;
+ new_var.var = NULL;
+ assert(false);
+ }
+ } else { //Assumed a direct value.
+ new_var.type = _vs_app_obj;
+ new_var.var = new AppObject();
+ AppObject& ao = *(AppObject*)new_var.var;
+ ao.type = _ao_ignored;
+ const char* type_name = type->GetName();
+ const char* name_space = type->GetNamespace();
+ if(strcmp(type_name, "vec3") == 0){
+ ao.type = _ao_vec3;
+ ao.var = new vec3();
+ *(vec3*)ao.var = *(vec3*)ptr;
+ } else if(strcmp(type_name, "ivec2") == 0){
+ ao.type = _ao_ivec2;
+ ao.var = new ivec2();
+ *(ivec2*)ao.var = *(ivec2*)ptr;
+ } else if(strcmp(type_name, "ivec3") == 0){
+ ao.type = _ao_ivec3;
+ ao.var = new ivec3();
+ *(ivec3*)ao.var = *(ivec3*)ptr;
+ } else if(strcmp(type_name, "ivec4") == 0){
+ ao.type = _ao_ivec4;
+ ao.var = new ivec4();
+ *(ivec4*)ao.var = *(ivec4*)ptr;
+ } else if(strcmp(type_name, "string") == 0){
+ ao.type = _ao_string;
+ ao.var = new std::string();
+ *(std::string*)ao.var = *(std::string*)ptr;
+ } else if(strcmp(type_name, "AttackScriptGetter") == 0){
+ ao.type = _ao_attack_script_getter;
+ ao.var = new AttackScriptGetter();
+ *(AttackScriptGetter*)ao.var = *(AttackScriptGetter*)ptr;
+ } else if(strcmp(type_name, "BoneTransform") == 0){
+ ao.type = _ao_bone_transform;
+ ao.var = new BoneTransform();
+ *(BoneTransform*)ao.var = *(BoneTransform*)ptr;
+ } else if(strcmp(type_name, "quaternion") == 0){
+ ao.type = _ao_quaternion;
+ ao.var = new quaternion();
+ *(quaternion*)ao.var = *(quaternion*)ptr;
+ } else if(strcmp(type_name, "vec2") == 0){
+ ao.type = _ao_vec2;
+ ao.var = new vec2();
+ *(vec2*)ao.var = *(vec2*)ptr;
+ } else if(strcmp(type_name, "vec4") == 0){
+ ao.type = _ao_vec4;
+ ao.var = new vec4();
+ *(vec4*)ao.var = *(vec4*)ptr;
+ } else if(strcmp(type_name, "NavPath") == 0){
+ ao.type = _ao_nav_path;
+ ao.var = new NavPath();
+ } else if(strcmp(type_name, "mat4") == 0){
+ ao.type = _ao_mat4;
+ ao.var = new mat4();
+ *(mat4*)ao.var = *(mat4*)ptr;
+ } else if(strcmp(type_name, "JSON") == 0) {
+ ao.type = _ao_json;
+ ao.var = new SimpleJSONWrapper();
+ *(SimpleJSONWrapper*)ao.var = *(SimpleJSONWrapper*)ptr;
+ } else if(strcmp(type_name, "FontSetup") == 0) {
+ ao.type = _ao_fontsetup;
+ ao.var = new FontSetup();
+ *(FontSetup*)ao.var = *(FontSetup*)ptr;
+ } else if(strcmp(type_name, "ModLevel") == 0) {
+ ao.type = _ao_modlevel;
+ ao.var = new ModInstance::Level();
+ *(ModInstance::Level*)ao.var = *(ModInstance::Level*)ptr;
+ } else if(strcmp(type_name, "TextureAssetRef") == 0) {
+ ao.type = _ao_textureassetref;
+ ao.var = new TextureAssetRef();
+ *(TextureAssetRef*)ao.var = *(TextureAssetRef*)ptr;
+ } else if(strcmp(type_name, "ModID") == 0) {
+ ao.type = _ao_modid;
+ ao.var = new ModID();
+ *(ModID*)ao.var = *(ModID*)ptr;
+ } else if(strcmp(type_name, "SpawnerItem") == 0) {
+ ao.type = _ao_spawneritem;
+ ao.var = new ModInstance::Item();
+ *(ModInstance::Item*)ao.var = *(ModInstance::Item*)ptr;
+ } else if(strcmp(type_name, "IMFadeIn") == 0) {
+ LOGW << "IMFadeIn can not be stored as a value in global space and reloaded, it will be reset to initial values. Variable name: " << name << std::endl;
+ } else if(strcmp(type_name, "IMMoveIn") == 0) {
+ LOGW << "IMMoveIn can not be stored as a value in global space and reloaded, it will be reset to initial values. Variable name: " << name << std::endl;
+ } else if(strcmp(type_name, "IMChangeTextFadeOutIn") == 0) {
+ LOGW << "IMChangeTextFadeOutIn can not be stored as a value in global space and reloaded, it will be reset to initial values. Variable name: " << name << std::endl;
+ } else if(strcmp(type_name, "IMChangeImageFadeOutIn") == 0) {
+ LOGW << "IMChangeImageFadeOutIn can not be stored as a value in global space and reloaded, it will be reset to initial values. Variable name: " << name << std::endl;
+ } else if(strcmp(type_name, "IMPulseAlpha") == 0) {
+ LOGW << "IMPulseAlpha can not be stored as a value in global space and reloaded, it will be reset to initial values. Variable name: " << name << std::endl;
+ } else if(strcmp(type_name, "IMPulseBorderAlpha") == 0) {
+ LOGW << "IMPulseBorderAlpha can not be stored as a value in global space and reloaded, it will be reset to initial values. Variable name: " << name << std::endl;
+ } else if(strcmp(type_name, "IMMouseOverScale") == 0) {
+ LOGW << "IMMouseOverScale can not be stored as a value in global space and reloaded, it will be reset to initial values. Variable name: " << name << std::endl;
+ } else if(strcmp(type_name, "IMMouseOverMove") == 0) {
+ LOGW << "IMMouseOverMove can not be stored as a value in global space and reloaded, it will be reset to initial values. Variable name: " << name << std::endl;
+ } else if(strcmp(type_name, "IMMouseOverShowBorder") == 0) {
+ LOGW << "IMMouseOverShowBorder can not be stored as a value in global space and reloaded, it will be reset to initial values. Variable name: " << name << std::endl;
+ } else if(strcmp(type_name, "IMMouseOverPulseColor") == 0) {
+ LOGW << "IMMouseOverPulseColor can not be stored as a value in global space and reloaded, it will be reset to initial values. Variable name: " << name << std::endl;
+ } else if(strcmp(type_name, "IMMouseOverPulseBorder") == 0) {
+ LOGW << "IMMouseOverPulseBorder can not be stored as a value in global space and reloaded, it will be reset to initial values. Variable name: " << name << std::endl;
+ } else if(strcmp(type_name, "IMMouseOverPulseBorderAlpha") == 0) {
+ LOGW << "IMMouseOverPulseBorderAlpha can not be stored as a value in global space and reloaded, it will be reset to initial values. Variable name: " << name << std::endl;
+ } else if(strcmp(type_name, "IMMouseOverFadeIn") == 0) {
+ LOGW << "IMMouseOverFadeIn can not be stored as a value in global space and reloaded, it will be reset to initial values. Variable name: " << name << std::endl;
+ } else if(strcmp(type_name, "IMFixedMessageOnMouseOver") == 0) {
+ LOGW << "IMFixedMessageOnMouseOver can not be stored as a value in global space and reloaded, it will be reset to initial values. Variable name: " << name << std::endl;
+ } else if(strcmp(type_name, "IMFixedMessageOnClick") == 0) {
+ LOGW << "IMFixedMessageOnClick can not be stored as a value in global space and reloaded, it will be reset to initial values. Variable name: " << name << std::endl;
+ } else if(strcmp(type_name, "IMMessage") == 0) {
+ LOGW << "IMMessage can not be stored as a value in global space and reloaded, it will be reset to initial values. Variable name: " << name << std::endl;
+ } else if(strcmp(type_name, "IMElement") == 0) {
+ LOGW << "IMElements can not be stored as a value in global space and reloaded, it will be reset to initial values. Variable name: " << name << std::endl;
+ } else if(strcmp(type_name, "IMContainer") == 0) {
+ LOGW << "IMContainer can not be stored by value in global space and reloaded, it will be reset to initial values. Variable name: " << name << std::endl;
+ } else if(strcmp(type_name, "IMDivider") == 0) {
+ LOGW << "IMDivider can not be stored by value in global space and reloaded, it will be reset to initial values. Variable name: " << name << std::endl;
+ } else if(strcmp(type_name, "IMImage") == 0) {
+ LOGW << "IMImage can not be stored by value in global space and reloaded, it will be reset to initial values. Variable name: " << name << std::endl;
+ } else if(strcmp(type_name, "IMText") == 0) {
+ LOGW << "IMText can not be stored by value in global space and reloaded, it will be reset to initial values. Variable name: " << name << std::endl;
+ } else if(strcmp(type_name, "IMSelectionList") == 0) {
+ LOGW << "IMSelectionList can not be stored by value in global space and reloaded, it will be reset to initial values. Variable name: " << name << std::endl;
+ } else if(strcmp(type_name, "IMSpacer") == 0) {
+ LOGW << "IMSpacer can not be stored by value in global space and reloaded, it will be reset to initial values. Variable name: " << name << std::endl;
+ } else if(strcmp(type_name, "IMUIText") == 0) {
+ LOGW << "IMUIText can not be stored by value in global space and reloaded, it will be reset to initial values. Variable name: " << name << std::endl;
+ } else if(strcmp(type_name, "IMUIImage") == 0) {
+ LOGW << "IMUIImage can not be stored by value in global space and reloaded, it will be reset to initial values. Variable name: " << name << std::endl;
+ } else if(strcmp(type_name, "dictionary") == 0) {
+ DisplayError("Warning", "Hotloading for dictionaries are not supported due to their construction complexity.", _ok_cancel, false);
+ /*
+ ao.type = _ao_dictionary;
+ ao.original = ptr;
+ std::list<std::pair<std::string,VarStorage> > *storage
+ = new std::list<std::pair<std::string,VarStorage> >();
+ ao.var = storage;
+ CScriptDictionary* dictionary = (CScriptDictionary*)ptr;
+ CScriptDictionary::CIterator dictit = dictionary->begin();
+ for( ;dictit != dictionary->end(); dictit++ ) {
+ storage->resize(storage->size()+1);
+ storage->back().first = dictit.GetKey();
+ uintptr_t vadr = reinterpret_cast<uintptr_t>(dictit.GetAddressOfValue());
+ FillVar(storage->back().second,"",dictit.GetTypeId(),reinterpret_cast<void*>(vadr),handle_var_list,module);
+ }
+ */
+ } else {
+ DisplayError("Warning", ("Unknown app object type: "+std::string(type_name)).c_str(), _ok_cancel, false);
+ ao.type = _ao_unknown;
+ }
+ }
+ } else if( type_id & asTYPEID_SCRIPTOBJECT ) {
+ if( type_id & asTYPEID_OBJHANDLE ) {
+ asIScriptObject* script_object_ptr = *(static_cast<asIScriptObject**>(ptr));
+ if( ((uintptr_t)script_object_ptr) != 0 ) {
+ bool found_prev_constructed = false;
+ std::list<ScriptObjectInstance>::iterator hvs_it = handle_var_list.begin();
+ for( ;hvs_it != handle_var_list.end(); hvs_it++ ){
+ if( hvs_it->ptr == ((uintptr_t)script_object_ptr) ){
+ found_prev_constructed = true;
+ }
+ }
+
+ if( found_prev_constructed == false ) {
+ VarStorage var_storage;
+
+ asITypeInfo* actual_type = script_object_ptr->GetObjectType();
+ var_storage.name = "handle_source";
+ var_storage.type = _vs_script_obj;
+ var_storage.var = new ScriptObject();
+ ScriptObject& so = *(ScriptObject*)var_storage.var;
+ so.type_name = actual_type->GetName();
+ so.orig_ptr = (uintptr_t)script_object_ptr;
+
+ if( actual_type->GetNamespace() ) {
+ so.name_space = std::string( actual_type->GetNamespace() );
+ }
+
+ //LOGI << "TYPENAME: " << so.type_name << std::endl;
+ handle_var_list.push_back(
+ ScriptObjectInstance(
+ (uintptr_t)*(static_cast<asIScriptObject**>(ptr)),
+ var_storage,
+ false
+ )
+ );
+ asIScriptObject *obj = *(static_cast<asIScriptObject**>(ptr));
+ so.Populate(obj, handle_var_list, module);
+ }
+ }
+
+ new_var.type = _vs_script_obj_handle;
+ new_var.var = (void*)*(static_cast<asIScriptObject**>(ptr));
+ } else {
+ bool found_prev_constructed = false;
+ asIScriptObject* script_object_ptr = static_cast<asIScriptObject*>(ptr);
+ std::list<ScriptObjectInstance>::iterator hvs_it = handle_var_list.begin();
+ for( ;hvs_it != handle_var_list.end(); hvs_it++ ){
+ if( hvs_it->ptr == ((uintptr_t)script_object_ptr) ){
+ found_prev_constructed = true;
+ hvs_it->from_script_obj_value = true;
+ hvs_it->var.name = name;
+ }
+ }
+
+ new_var.type = _vs_script_obj;
+ new_var.var = new ScriptObject();
+ ScriptObject& so = *(ScriptObject*)new_var.var;
+ so.type_name = type->GetName();
+ so.orig_ptr = (uintptr_t)ptr;
+
+ if( type->GetNamespace() ) {
+ so.name_space = std::string( type->GetNamespace() );
+ }
+
+ if( found_prev_constructed == false ) {
+ VarStorage vs;
+ handle_var_list.push_back(
+ ScriptObjectInstance(
+ (uintptr_t)ptr,
+ vs,
+ true
+ )
+ );
+ }
+
+ so.Populate(script_object_ptr, handle_var_list, module);
+ }
+ } else if( type_id & asTYPEID_TEMPLATE ) {
+ if( type_id & asTYPEID_OBJHANDLE ) {
+ //TODO: Implement this similar to app_objects
+ LOGW << "We can't recover a reference to a template type from global variable storage, consider refactoring to avoid this, variable name: " << name << " it will be default/invalid." << std::endl;
+ new_var.type = _vs_template_handle;
+ new_var.var = NULL;
+ } else {
+ new_var.type = _vs_template;
+ new_var.var = new TemplateObject();
+ TemplateObject& to = *(TemplateObject*)new_var.var;
+ const char* type_name = type->GetName();
+ int sub_type_id = type->GetSubTypeId();
+ asITypeInfo* sub_type = engine->GetTypeInfoById(sub_type_id);
+ if(sub_type) {
+ to.sub_type_name = sub_type->GetName();
+ } else {
+ if(sub_type_id == asTYPEID_BOOL){
+ to.sub_type_name = "bool";
+ } else if(sub_type_id == asTYPEID_INT8){
+ to.sub_type_name = "int8";
+ } else if(sub_type_id == asTYPEID_INT16){
+ to.sub_type_name = "int16";
+ } else if(sub_type_id == asTYPEID_INT32){
+ to.sub_type_name = "int32";
+ } else if(sub_type_id == asTYPEID_UINT8){
+ to.sub_type_name = "uint8";
+ } else if(sub_type_id == asTYPEID_UINT16){
+ to.sub_type_name = "uint16";
+ } else if(sub_type_id == asTYPEID_UINT32){
+ to.sub_type_name = "uint32";
+ } else if(sub_type_id == asTYPEID_FLOAT){
+ to.sub_type_name = "float";
+ } else {
+ to.sub_type_name = "unknown";
+ }
+ }
+ if(strcmp(type_name, "array")==0) {
+ to.type = _to_array;
+ to.var = new std::list<VarStorage>();
+ std::list<VarStorage> &storage = *(std::list<VarStorage>*)to.var;
+ CScriptArray* array = (CScriptArray*)ptr;
+ unsigned size = array->GetSize();
+ for(unsigned i=0; i<size; ++i) {
+ storage.resize(i+1);
+ FillVar(storage.back(),"",sub_type_id,array->At(i), handle_var_list,module);
+ }
+ } else {
+ LOGE << "Unknown template type name: " << type_name << std::endl;
+ to.type = _to_unknown;
+ }
+ }
+ } else {
+ if( type ) {
+ asDWORD type_flags = type->GetFlags();
+ if( type_flags & asOBJ_ENUM ) {
+ new_var.type = _vs_enum;
+ new_var.var = new char[type->GetSize()];
+ new_var.size = type->GetSize();
+ memcpy(new_var.var,ptr,type->GetSize());
+ } else {
+ LOGW << "Is unknown type " << std::endl;
+ }
+ } else {
+ LOGE << "Type is null for some reason" << std::endl;
+ }
+ }
+ break;
+ }
+}
+
+ASModule::~ASModule() {
+ std::list<VarStorage>::iterator iter = saved_vars_.begin();
+ for(; iter != saved_vars_.end(); ++iter) {
+ iter->destroy();
+ }
+
+ std::list<ScriptObjectInstance>::iterator iterh = saved_handle_vars_.begin();
+ for(; iterh != saved_handle_vars_.end(); ++iterh) {
+ iterh->destroy();
+ }
+}
+
+void ASModule::SetErrorStringDestination(std::string* error_string) {
+ active_angelscript_error_string = error_string;
+}
+
+void ASModule::PrintGlobalVars() {
+ std::list<VarStorage> storage;
+ std::list<ScriptObjectInstance> handle_var_storage;
+ GetGlobalVars(storage, handle_var_storage);
+ std::list<VarStorage>::iterator iter = storage.begin();
+ for(; iter != storage.end(); ++iter){
+ VarStorage& vs = (*iter);
+ printf("%s\n", vs.GetString().c_str());
+ }
+}
+
+void ASModule::PrintAllTypes() {
+ /*
+ asIScriptEngine engine = module_->GetEngine();
+ for( int i = 0; i < engine->GetTypedefCount(); i++ ) {
+ asITypeInfo *typeinfo = engine->GetTypeInfoByIndex(i);
+ }
+ */
+}
+
+void ASModule::LogGlobalVars() {
+ std::list<VarStorage> storage;
+ std::list<ScriptObjectInstance> handle_var_storage;
+ GetGlobalVars(storage, handle_var_storage);
+ std::list<VarStorage>::iterator iter = storage.begin();
+ for(; iter != storage.end(); ++iter){
+ VarStorage& vs = (*iter);
+ LOGE.Format("%s\n", vs.GetString().c_str());
+ }
+}
+
+void *ASModule::GetVarPtr(const char* name){
+ int index = module_->GetGlobalVarIndexByName(name);
+ return module_->GetAddressOfGlobalVar(index);
+}
+
+void *ASModule::GetVarPtrCache(const char* name){
+ std::map<void*, void*>::iterator iter = fast_var_ptr_map.find((void*)name);
+ if(iter != fast_var_ptr_map.end()){
+ return iter->second;
+ } else {
+ int index = module_->GetGlobalVarIndexByName(name);
+ void* addr = module_->GetAddressOfGlobalVar(index);
+ fast_var_ptr_map[(void*)name] = addr;
+ return addr;
+ }
+}
+
+void ASModule::GetGlobalVars(std::list<VarStorage> &storage, std::list<ScriptObjectInstance>& handle_var_list) {
+ int c = module_->GetGlobalVarCount();
+ for( int n = 0; n < c; n++ )
+ {
+ const char *name;
+ const char **name_hdl = &name;
+ int type_id;
+ bool is_const;
+ module_->GetGlobalVar(n,name_hdl,0,&type_id,&is_const);
+ if(is_const){
+ continue;
+ }
+ void* ptr = module_->GetAddressOfGlobalVar(n);
+ VarStorage new_var;
+ FillVar(new_var, name, type_id, ptr, handle_var_list, module_);
+ storage.push_back(new_var);
+ }
+}
+
+void ASModule::SaveGlobalVars() {
+ GetGlobalVars(saved_vars_, saved_handle_vars_);
+}
+
+void ASModule::LoadVar(ValueParent var_type, void* parent_ptr, int parent_index, const VarStorage &vs, void *ptr, std::list<ScriptObjectInstance>& handle_var_list, std::list<ScriptObjectInstanceHandleRemap>& app_obj_handle_offset, asIScriptModule* module ) {
+ if(vs.type == _vs_bool){
+ *(bool*)ptr = *(bool*)vs.var;
+ } else if(vs.type == _vs_int8){
+ *(char*)ptr = *(char*)vs.var;
+ } else if(vs.type == _vs_int16){
+ *(short*)ptr = *(short*)vs.var;
+ } else if(vs.type == _vs_int32){
+ *(int*)ptr = *(int*)vs.var;
+ } else if(vs.type == _vs_int64){
+ *(int64_t*)ptr = *(int64_t*)vs.var;
+ } else if(vs.type == _vs_uint8){
+ *(unsigned char*)ptr = *(unsigned char*)vs.var;
+ } else if(vs.type == _vs_uint16){
+ *(unsigned short*)ptr = *(unsigned short*)vs.var;
+ } else if(vs.type == _vs_uint32){
+ *(unsigned int*)ptr = *(unsigned int*)vs.var;
+ } else if(vs.type == _vs_uint64){
+ *(uint64_t*)ptr = *(uint64_t*)vs.var;
+ } else if(vs.type == _vs_float){
+ *(float*)ptr = *(float*)vs.var;
+ } else if(vs.type == _vs_enum) {
+ memcpy(ptr,vs.var,vs.size);
+ } else if(vs.type == _vs_app_obj){
+ AppObject &ao = *(AppObject*)vs.var;
+ if(ao.type == _ao_vec3){
+ *(vec3*)ptr = *(vec3*)ao.var;
+ } else if(ao.type == _ao_ivec2){
+ *(ivec2*)ptr = *(ivec2*)ao.var;
+ } else if(ao.type == _ao_ivec3){
+ *(ivec3*)ptr = *(ivec3*)ao.var;
+ } else if(ao.type == _ao_ivec4){
+ *(ivec4*)ptr = *(ivec4*)ao.var;
+ } else if(ao.type == _ao_string){
+ *(std::string*)ptr = *(std::string*)ao.var;
+ } else if(ao.type == _ao_attack_script_getter){
+ *(AttackScriptGetter*)ptr = *(AttackScriptGetter*)ao.var;
+ } else if(ao.type == _ao_bone_transform){
+ *(BoneTransform*)ptr = *(BoneTransform*)ao.var;
+ } else if(ao.type == _ao_quaternion){
+ *(quaternion*)ptr = *(quaternion*)ao.var;
+ } else if(ao.type == _ao_vec2){
+ *(vec2*)ptr = *(vec2*)ao.var;
+ } else if(ao.type == _ao_vec4){
+ *(vec4*)ptr = *(vec4*)ao.var;
+ } else if(ao.type == _ao_nav_path){
+ *(NavPath*)ptr = *(NavPath*)ao.var;
+ } else if(ao.type == _ao_mat4){
+ *(mat4*)ptr = *(mat4*)ao.var;
+ } else if(ao.type == _ao_json){
+ *(SimpleJSONWrapper*)ptr = *(SimpleJSONWrapper*)ao.var;
+ } else if(ao.type == _ao_fontsetup){
+ *(FontSetup*)ptr = *(FontSetup*)ao.var;
+ } else if(ao.type == _ao_modlevel){
+ *(ModInstance::Level*)ptr = *(ModInstance::Level*)ao.var;
+ } else if(ao.type == _ao_textureassetref){
+ *(TextureAssetRef*)ptr = *(TextureAssetRef*)ao.var;
+ } else if(ao.type == _ao_modid){
+ *(ModID*)ptr = *(ModID*)ao.var;
+ } else if(ao.type == _ao_spawneritem){
+ *(ModInstance::Item*)ptr = *(ModInstance::Item*)ao.var;
+ } else if(ao.type == _ao_dictionary) {
+ /*
+ std::list<std::pair<int,VarStorage> >* storage = static_cast<std::list<std::pair<int,VarStorage> >* >(ao.var);
+ CScriptDictionary* dictionary = (CScriptDictionary*)ptr;
+
+ std::list<std::pair<int,VarStorage> >::iterator storit = storage->begin();
+ for( ; storit != storage->end(); storit++ ) {
+
+ }
+ */
+ } else if( ao.type == _ao_ignored) {
+
+ } else {
+ DisplayError("Error", "Unknown ao.type");
+ }
+ } else if(vs.type == _vs_script_obj) {
+ bool found = false;
+ ScriptObject &so = *(ScriptObject*)vs.var;
+ asIScriptObject *obj = (asIScriptObject *)ptr;
+
+ std::list<ScriptObjectInstance>::iterator hvs_it = handle_var_list.begin();
+ for( ;hvs_it != handle_var_list.end(); hvs_it++ ) {
+ if( hvs_it->ptr == so.orig_ptr ) {
+ hvs_it->new_ptr = obj;
+ hvs_it->new_ptr->AddRef();
+ hvs_it->reconstructed = true;
+ found = true;
+ }
+ }
+
+ if( found == false ) {
+ LOGE << "Could not find a handle for this script_obj." << std::endl;
+ }
+
+ unsigned count = 0;
+ std::list<VarStorage>::const_iterator iter2 = so.storage.begin();
+ for(;iter2!=so.storage.end(); ++iter2){
+ void *so_ptr = obj->GetAddressOfProperty(count);
+ const VarStorage &so_vs = (*iter2);
+ LoadVar(_vp_ScriptObject, obj, count, so_vs, so_ptr,handle_var_list,app_obj_handle_offset,module);
+ ++count;
+ }
+ } else if(vs.type == _vs_script_obj_handle) {
+ std::list<ScriptObjectInstance>::iterator hvs_it = handle_var_list.begin();
+ for( ;hvs_it != handle_var_list.end(); hvs_it++ ) {
+ if( hvs_it->ptr == (uintptr_t)vs.var ) {
+ if( hvs_it->from_script_obj_value == false ) {
+ if( hvs_it->reconstructed == false ) {
+ hvs_it->reconstructed = true;
+
+ ScriptObject &so = *(ScriptObject*)hvs_it->var.var;
+ asIScriptEngine* engine = module->GetEngine();
+
+ if( so.name_space.empty() == false ) {
+ module->SetDefaultNamespace(so.name_space.c_str());
+ }
+
+ asITypeInfo* script_object_type_info = static_cast<asITypeInfo*>(module->GetTypeInfoByName(so.type_name.c_str()));
+ if( script_object_type_info != NULL ) {
+ //Create a new instance of asIScriptObject
+ asIScriptObject *obj = static_cast<asIScriptObject*>(engine->CreateUninitializedScriptObject(script_object_type_info));
+ hvs_it->new_ptr = obj;
+ hvs_it->new_ptr->AddRef();
+ if( obj != NULL )
+ {
+ unsigned count = 0;
+ std::list<VarStorage>::const_iterator iter2 = so.storage.begin();
+ for(;iter2!=so.storage.end(); ++iter2){
+ void *so_ptr = obj->GetAddressOfProperty(count);
+ const VarStorage &so_vs = (*iter2);
+ LoadVar(_vp_ScriptObject, obj, count, so_vs, so_ptr,handle_var_list,app_obj_handle_offset,module);
+ ++count;
+ }
+ }
+ } else {
+ LOGE << "No matching typename " << so.type_name << " in recompiled module."<< std::endl;
+ }
+
+ module->SetDefaultNamespace("");
+ }
+ }
+
+ if( hvs_it->reconstructed ) {
+ if( hvs_it->new_ptr ) {
+ (*(asIScriptObject **)ptr) = hvs_it->new_ptr;
+ (*(asIScriptObject **)ptr)->AddRef();
+ } else {
+ LOGE << "No pointer created for ScriptObjectType" << std::endl;
+ }
+ } else {
+ ScriptObjectInstanceHandleRemap soihr;
+
+ soihr.var_type = var_type;
+ soihr.parent_ptr = parent_ptr;
+ soihr.parent_index = parent_index;
+ soihr.orig_ptr = (uintptr_t)vs.var;
+
+ app_obj_handle_offset.push_back(soihr);
+ }
+ }
+ }
+ } else if(vs.type == _vs_app_obj_handle) {
+ AppObjectHandle *aoh = static_cast<AppObjectHandle*>(vs.var);
+ if(aoh->var) {
+ *(void**)ptr = aoh->var;
+ if(aoh->type == _ao_IMFadeIn) {
+ IMFadeIn* v = static_cast<IMFadeIn*>(aoh->var);
+ v->AddRef();
+ } else if(aoh->type == _ao_IMMoveIn) {
+ IMMoveIn* v = static_cast<IMMoveIn*>(aoh->var);
+ v->AddRef();
+ } else if(aoh->type == _ao_IMChangeTextFadeOutIn) {
+ IMChangeTextFadeOutIn* v = static_cast<IMChangeTextFadeOutIn*>(aoh->var);
+ v->AddRef();
+ } else if(aoh->type == _ao_IMChangeImageFadeOutIn) {
+ IMChangeImageFadeOutIn* v = static_cast<IMChangeImageFadeOutIn*>(aoh->var);
+ v->AddRef();
+ } else if(aoh->type == _ao_IMPulseAlpha) {
+ IMPulseAlpha* v = static_cast<IMPulseAlpha*>(aoh->var);
+ v->AddRef();
+ } else if(aoh->type == _ao_IMPulseBorderAlpha) {
+ IMPulseBorderAlpha* v = static_cast<IMPulseBorderAlpha*>(aoh->var);
+ v->AddRef();
+ } else if(aoh->type == _ao_IMMouseOverMove) {
+ IMMouseOverMove* v = static_cast<IMMouseOverMove*>(aoh->var);
+ v->AddRef();
+ } else if(aoh->type == _ao_IMMouseOverScale) {
+ IMMouseOverScale* v = static_cast<IMMouseOverScale*>(aoh->var);
+ v->AddRef();
+ } else if(aoh->type == _ao_IMMouseOverShowBorder) {
+ IMMouseOverShowBorder* v = static_cast<IMMouseOverShowBorder*>(aoh->var);
+ v->AddRef();
+ } else if(aoh->type == _ao_IMMouseOverPulseColor) {
+ IMMouseOverPulseColor* v = static_cast<IMMouseOverPulseColor*>(aoh->var);
+ v->AddRef();
+ } else if(aoh->type == _ao_IMMouseOverPulseBorder) {
+ IMMouseOverPulseBorder* v = static_cast<IMMouseOverPulseBorder*>(aoh->var);
+ v->AddRef();
+ } else if(aoh->type == _ao_IMMouseOverPulseBorderAlpha) {
+ IMMouseOverPulseBorderAlpha* v = static_cast<IMMouseOverPulseBorderAlpha*>(aoh->var);
+ v->AddRef();
+ } else if(aoh->type == _ao_IMMouseOverFadeIn) {
+ IMMouseOverFadeIn* v = static_cast<IMMouseOverFadeIn*>(aoh->var);
+ v->AddRef();
+ } else if(aoh->type == _ao_IMFixedMessageOnMouseOver) {
+ IMFixedMessageOnMouseOver* v = static_cast<IMFixedMessageOnMouseOver*>(aoh->var);
+ v->AddRef();
+ } else if(aoh->type == _ao_IMFixedMessageOnClick) {
+ IMFixedMessageOnClick* v = static_cast<IMFixedMessageOnClick*>(aoh->var);
+ v->AddRef();
+ } else if(aoh->type == _ao_IMMessage) {
+ IMMessage* v = static_cast<IMMessage*>(aoh->var);
+ v->AddRef();
+ } else if(aoh->type == _ao_IMGUI) {
+ IMGUI* v = static_cast<IMGUI*>(aoh->var);
+ v->AddRef();
+ } else if(aoh->type == _ao_IMElement) {
+ IMElement* v = static_cast<IMElement*>(aoh->var);
+ v->AddRef();
+ } else if(aoh->type == _ao_IMContainer) {
+ IMContainer* v = static_cast<IMContainer*>(aoh->var);
+ v->AddRef();
+ } else if(aoh->type == _ao_IMDivider) {
+ IMDivider* v = static_cast<IMDivider*>(aoh->var);
+ v->AddRef();
+ } else if(aoh->type == _ao_IMImage) {
+ IMImage* v = static_cast<IMImage*>(aoh->var);
+ v->AddRef();
+ } else if(aoh->type == _ao_IMText) {
+ IMText* v = static_cast<IMText*>(aoh->var);
+ v->AddRef();
+ } else if(aoh->type == _ao_IMTextSelectionList) {
+ IMTextSelectionList* v = static_cast<IMTextSelectionList*>(aoh->var);
+ v->AddRef();
+ } else if(aoh->type == _ao_IMSpacer) {
+ IMSpacer* v = static_cast<IMSpacer*>(aoh->var);
+ v->AddRef();
+ // -- Begin scenegraph objects --
+ } else if(aoh->type == _ao_game_object) {
+ // Nothing to do here
+ } else if(aoh->type == _ao_ambient_sound_object) {
+ // Nothing to do here
+ } else if(aoh->type == _ao_camera_object) {
+ // Nothing to do here
+ } else if(aoh->type == _ao_decal_object) {
+ // Nothing to do here
+ } else if(aoh->type == _ao_dynamic_light_object) {
+ // Nothing to do here
+ } else if(aoh->type == _ao_env_object) {
+ // Nothing to do here
+ } else if(aoh->type == _ao_group_object) {
+ // Nothing to do here
+ } else if(aoh->type == _ao_hotspot_object) {
+ // Nothing to do here
+ } else if(aoh->type == _ao_item_object) {
+ // Nothing to do here
+ } else if(aoh->type == _ao_light_probe_object) {
+ // Nothing to do here
+ } else if(aoh->type == _ao_light_volume_object) {
+ // Nothing to do here
+ } else if(aoh->type == _ao_movement_object) {
+ // Nothing to do here
+ } else if(aoh->type == _ao_navmesh_connection_object) {
+ // Nothing to do here
+ } else if(aoh->type == _ao_navmesh_hint_object) {
+ // Nothing to do here
+ } else if(aoh->type == _ao_navmesh_region_object) {
+ // Nothing to do here
+ } else if(aoh->type == _ao_path_point_object) {
+ // Nothing to do here
+ } else if(aoh->type == _ao_placeholder_object) {
+ // Nothing to do here
+ } else if(aoh->type == _ao_reflection_capture_object) {
+ // Nothing to do here
+ } else if(aoh->type == _ao_rigged_object) {
+ // Nothing to do here
+ } else if(aoh->type == _ao_terrain_object) {
+ // Nothing to do here
+ // -- End scenegraph objects --
+ } else {
+ LOGE << "Unhandled _ao_ type " << std::endl;
+ assert(false);
+ }
+ } else {
+ *(void**)ptr = NULL;
+ }
+ } else if(vs.type == _vs_template){
+ TemplateObject &to = *(TemplateObject*)vs.var;
+ if(to.type == _to_array){
+ const std::list<VarStorage>& storage = *(std::list<VarStorage>*)to.var;
+ CScriptArray* array = (CScriptArray*)ptr;
+ array->Resize(storage.size());
+ unsigned count = 0;
+ std::list<VarStorage>::const_iterator iter = storage.begin();
+ for(; iter!=storage.end(); ++iter){
+ LoadVar(_vp_Array, array, count, *iter, array->At(count),handle_var_list,app_obj_handle_offset,module);
+ ++count;
+ }
+ }
+ }
+}
+
+void ASModule::LoadGlobalVars() {
+ const std::list<VarStorage> &storage = saved_vars_;
+ std::list<ScriptObjectInstanceHandleRemap> app_obj_handle_offset;
+
+ std::list<VarStorage>::const_iterator iter = storage.begin();
+ for(;iter!=storage.end(); ++iter){
+ const VarStorage& vs = (*iter);
+ int index = module_->GetGlobalVarIndexByName(vs.name.c_str());
+ void *ptr = module_->GetAddressOfGlobalVar(index);
+ if(ptr) {
+ LoadVar(_vp_Global, module_, index, vs, ptr, saved_handle_vars_, app_obj_handle_offset, module_);
+ }
+ }
+
+ std::list<ScriptObjectInstanceHandleRemap>::iterator soihr_it;
+ std::list<ScriptObjectInstance>::iterator hvs_it;
+ for( soihr_it = app_obj_handle_offset.begin();
+ soihr_it != app_obj_handle_offset.end();
+ soihr_it++ ) {
+ for( hvs_it = saved_handle_vars_.begin();
+ hvs_it != saved_handle_vars_.end();
+ hvs_it++ ) {
+ if( hvs_it->ptr == soihr_it->orig_ptr ) {
+ void* ptr = NULL;
+ switch( soihr_it->var_type ) {
+ case _vp_Global:
+ ptr = static_cast<asIScriptModule*>(soihr_it->parent_ptr)->GetAddressOfGlobalVar(soihr_it->parent_index);
+ break;
+ case _vp_ScriptObject:
+ ptr = static_cast<asIScriptObject*>(soihr_it->parent_ptr)->GetAddressOfProperty(soihr_it->parent_index);
+ break;
+ case _vp_Array:
+ ptr = static_cast<CScriptArray*>(soihr_it->parent_ptr)->At(soihr_it->parent_index);
+ break;
+ }
+
+ if( hvs_it->new_ptr && ptr ) {
+ (*(asIScriptObject **)ptr) = hvs_it->new_ptr;
+ (*(asIScriptObject **)ptr)->AddRef();
+ } else {
+ LOGE << "No pointer created for ScriptObjectType, it should have." << std::endl;
+ }
+ }
+ }
+ }
+}
+
+void ASModule::Recompile()
+{
+ PROFILER_ZONE(g_profiler_ctx, "Recompile");
+ SaveGlobalVars();
+ int r = CompileScript(script_path_);
+ if( r < 0 )
+ {
+ FatalError("Error","Could not compile script: %s", script_path_.GetFullPath());
+ return;
+ }
+ LoadGlobalVars();
+
+ std::list<VarStorage>::iterator iter = saved_vars_.begin();
+ for(; iter != saved_vars_.end(); ++iter) {
+ iter->destroy();
+ }
+ saved_vars_.clear();
+}
+
+void ASModule::AttachToEngine( asIScriptEngine* engine )
+{
+ module_ = engine->GetModule(0, asGM_ALWAYS_CREATE);
+}
+
+const Path& ASModule::GetScriptPath()
+{
+ return script_path_;
+}
+
+LineFile ASModule::GetCorrectedLine(int line){
+ return script_file_ptr_->GetCorrectedLine(line);
+}
+
+void ASModule::ResetGlobals()
+{
+ module_->ResetGlobalVars();
+ fast_var_ptr_map.clear();
+}
+
+asIScriptFunction* ASModule::GetFunctionID( const std::string &function_name )
+{
+ std::map<std::string, asIScriptFunction*>::const_iterator iter = func_map_.find(function_name);
+ asIScriptFunction* func;
+ if(iter == func_map_.end()){
+ func = module_->GetFunctionByDecl(function_name.c_str());
+ func_map_.insert(std::pair<std::string, asIScriptFunction*>(function_name, func));
+ } else {
+ func = iter->second;
+ }
+ return func;
+}
+
+asIScriptModule* ASModule::GetInternalScriptModule()
+{
+ return module_;
+}
+
+AppObject::AppObject()
+{
+ var = NULL;
+}
+
+void AppObject::destroy()
+{
+ switch(type){
+ case _ao_vec3: delete (vec3*)var; break;
+ case _ao_ivec2: delete (ivec2*)var; break;
+ case _ao_ivec3: delete (ivec3*)var; break;
+ case _ao_ivec4: delete (ivec4*)var; break;
+ case _ao_string: delete (std::string*)var; break;
+ case _ao_attack_script_getter: delete (AttackScriptGetter*)var; break;
+ case _ao_bone_transform: delete (BoneTransform*)var; break;
+ case _ao_quaternion: delete (quaternion*)var; break;
+ case _ao_vec2: delete (vec2*)var; break;
+ case _ao_vec4: delete (vec4*)var; break;
+ case _ao_nav_path: delete (NavPath*)var; break;
+ case _ao_mat4: delete (mat4*)var; break;
+ case _ao_json: delete (SimpleJSONWrapper*)var; break;
+ case _ao_IMUIImage: delete (IMUIImage*)var; break;
+ case _ao_IMUIText: delete (IMUIText*)var; break;
+ case _ao_modlevel: delete (ModInstance::Level*)var; break;
+ case _ao_textureassetref: delete (TextureAssetRef*)var; break;
+ case _ao_modid: delete (ModID*)var; break;
+ case _ao_spawneritem: delete (ModInstance::Item*)var; break;
+ case _ao_unknown: break;
+ case _ao_ignored: break;
+ default: LOGE << "No destroy for unhandled type: " << AppObjectTypeString(type) << " in AppObject" << std::endl; break;
+ }
+ var = NULL;
+}
+
+AppObjectHandle::AppObjectHandle() {
+ type = _ao_unknown;
+ var = NULL;
+}
+
+void AppObjectHandle::destroy() {
+ if( var ) {
+ switch(type){
+ case _ao_IMFadeIn:
+ static_cast<IMFadeIn*>(var)->Release();
+ break;
+ case _ao_IMMoveIn:
+ static_cast<IMMoveIn*>(var)->Release();
+ break;
+ case _ao_IMChangeTextFadeOutIn:
+ static_cast<IMChangeTextFadeOutIn*>(var)->Release();
+ break;
+ case _ao_IMChangeImageFadeOutIn:
+ static_cast<IMChangeImageFadeOutIn*>(var)->Release();
+ break;
+ case _ao_IMPulseAlpha:
+ static_cast<IMPulseAlpha*>(var)->Release();
+ break;
+ case _ao_IMPulseBorderAlpha:
+ static_cast<IMPulseBorderAlpha*>(var)->Release();
+ break;
+ case _ao_IMMouseOverMove:
+ static_cast<IMMouseOverMove*>(var)->Release();
+ break;
+ case _ao_IMMouseOverScale:
+ static_cast<IMMouseOverScale*>(var)->Release();
+ break;
+ case _ao_IMMouseOverShowBorder:
+ static_cast<IMMouseOverShowBorder*>(var)->Release();
+ break;
+ case _ao_IMMouseOverPulseColor:
+ static_cast<IMMouseOverPulseColor*>(var)->Release();
+ break;
+ case _ao_IMMouseOverPulseBorder:
+ static_cast<IMMouseOverPulseBorder*>(var)->Release();
+ break;
+ case _ao_IMMouseOverPulseBorderAlpha:
+ static_cast<IMMouseOverPulseBorderAlpha*>(var)->Release();
+ break;
+ case _ao_IMMouseOverFadeIn:
+ static_cast<IMMouseOverFadeIn*>(var)->Release();
+ break;
+ case _ao_IMFixedMessageOnMouseOver:
+ static_cast<IMFixedMessageOnMouseOver*>(var)->Release();
+ break;
+ case _ao_IMFixedMessageOnClick:
+ static_cast<IMFixedMessageOnClick*>(var)->Release();
+ break;
+ case _ao_IMMessage:
+ static_cast<IMMessage*>(var)->Release();
+ break;
+ case _ao_IMGUI:
+ static_cast<IMGUI*>(var)->Release();
+ break;
+ case _ao_IMElement:
+ static_cast<IMElement*>(var)->Release();
+ break;
+ case _ao_IMContainer:
+ static_cast<IMContainer*>(var)->Release();
+ break;
+ case _ao_IMDivider:
+ static_cast<IMDivider*>(var)->Release();
+ break;
+ case _ao_IMImage:
+ static_cast<IMImage*>(var)->Release();
+ break;
+ case _ao_IMText:
+ static_cast<IMText*>(var)->Release();
+ break;
+ case _ao_IMTextSelectionList:
+ static_cast<IMTextSelectionList*>(var)->Release();
+ break;
+ case _ao_IMSpacer:
+ static_cast<IMSpacer*>(var)->Release();
+ break;
+ // -- Begin scenegraph objects --
+ case _ao_game_object:
+ case _ao_ambient_sound_object:
+ case _ao_camera_object:
+ case _ao_decal_object:
+ case _ao_dynamic_light_object:
+ case _ao_env_object:
+ case _ao_group_object:
+ case _ao_hotspot_object:
+ case _ao_item_object:
+ case _ao_light_probe_object:
+ case _ao_light_volume_object:
+ case _ao_movement_object:
+ case _ao_navmesh_connection_object:
+ case _ao_navmesh_hint_object:
+ case _ao_navmesh_region_object:
+ case _ao_path_point_object:
+ case _ao_placeholder_object:
+ case _ao_reflection_capture_object:
+ case _ao_rigged_object:
+ case _ao_terrain_object:
+ // Nothing to do here
+ break;
+ // -- End scenegraph objects
+ default:
+ LOGE << "No destroy for unhandled type: " << AppObjectTypeString(type) << " in AppObject" << std::endl;
+ break;
+ }
+ }
+ type = _ao_unknown;
+ var = NULL;
+}
+
+TemplateObject::TemplateObject() {
+ var = NULL;
+}
+
+void TemplateObject::destroy() {
+ switch(type) {
+ case _to_array: {
+ std::list<VarStorage>* varlist = (std::list<VarStorage>*)var;
+ std::list<VarStorage>::iterator iter = varlist->begin();
+ for(; iter != varlist->end(); ++iter) {
+ iter->destroy();
+ }
+ delete varlist;
+ break;
+ }
+ default: break;
+ }
+}
+
+const char* AppObjectTypeString(enum AppObjectType aot) {
+ switch( aot ) {
+ case _ao_vec3: return "_ao_vec3"; break;
+ case _ao_vec4: return "_ao_vec4"; break;
+ case _ao_ivec2: return "_ao_ivec2"; break;
+ case _ao_ivec3: return "_ao_ivec3"; break;
+ case _ao_ivec4: return "_ao_ivec4"; break;
+ case _ao_string: return "_ao_string"; break;
+ // -- Begin scenegraph objects --
+ case _ao_ambient_sound_object: return "_ao_ambient_sound_object"; break;
+ case _ao_camera_object: return "_ao_camera_object"; break;
+ case _ao_decal_object: return "_ao_decal_object"; break;
+ case _ao_dynamic_light_object: return "_ao_dynamic_light_object"; break;
+ case _ao_env_object: return "_ao_env_object"; break;
+ case _ao_game_object: return "_ao_game_object"; break;
+ case _ao_group_object: return "_ao_group_object"; break;
+ case _ao_hotspot_object: return "_ao_hotspot_object"; break;
+ case _ao_item_object: return "_ao_item_object"; break;
+ case _ao_light_probe_object: return "_ao_light_probe_object"; break;
+ case _ao_light_volume_object: return "_ao_light_volume_object"; break;
+ case _ao_movement_object: return "_ao_movement_object"; break;
+ case _ao_navmesh_connection_object: return "_ao_navmesh_connection_object"; break;
+ case _ao_navmesh_hint_object: return "_ao_navmesh_hint_object"; break;
+ case _ao_navmesh_region_object: return "_ao_navmesh_region_object"; break;
+ case _ao_path_point_object: return "_ao_path_point_object"; break;
+ case _ao_placeholder_object: return "_ao_placeholder_object"; break;
+ case _ao_reflection_capture_object: return "_ao_reflection_capture_object"; break;
+ case _ao_rigged_object: return "_ao_rigged_object"; break;
+ case _ao_terrain_object: return "_ao_terrain_object"; break;
+ // -- End scenegraph objects --
+ case _ao_attack_script_getter: return "_ao_attack_script_getter"; break;
+ case _ao_bone_transform: return "_ao_bone_transform"; break;
+ case _ao_quaternion: return "_ao_quaternion"; break;
+ case _ao_vec2: return "_ao_vec2"; break;
+ case _ao_nav_path: return "_ao_nav_path"; break;
+ case _ao_mat4: return "_ao_mat4"; break;
+ case _ao_json: return "_ao_json"; break;
+ case _ao_fontsetup: return "_ao_fontsetup"; break;
+ case _ao_modlevel: return "_ao_modlevel"; break;
+ case _ao_textureassetref: return "_ao_texetureassetref"; break;
+ case _ao_modid: return "_ao_modid"; break;
+ case _ao_spawneritem: return "_ao_spawneritem"; break;
+ case _ao_IMFadeIn: return "_ao_IMFadeIn"; break;
+ case _ao_IMMoveIn: return "_ao_IMMoveIn"; break;
+ case _ao_IMChangeTextFadeOutIn: return "_ao_IMChangeTextFadeOutIn"; break;
+ case _ao_IMChangeImageFadeOutIn: return "_ao_IMChangeImageFadeOutIn"; break;
+ case _ao_IMPulseAlpha: return "_ao_IMPulseAlpha"; break;
+ case _ao_IMPulseBorderAlpha: return "_ao_IMPulseBorderAlpha"; break;
+ case _ao_IMMouseOverMove: return "_ao_IMMouseOverMove"; break;
+ case _ao_IMMouseOverScale: return "_ao_IMMouseOverScale"; break;
+ case _ao_IMMouseOverShowBorder: return "_ao_IMMouseOverShowBorder"; break;
+ case _ao_IMMouseOverPulseColor: return "_ao_IMMouseOverPulseColor"; break;
+ case _ao_IMMouseOverPulseBorder: return "_ao_IMMouseOverPulseBorder"; break;
+ case _ao_IMMouseOverPulseBorderAlpha: return "_ao_IMMouseOverPulseBorderAlpha"; break;
+ case _ao_IMMouseOverFadeIn: return "_ao_IMMouseOverFadeIn"; break;
+ case _ao_IMFixedMessageOnMouseOver: return "_ao_IMFixedMessageOnMouseOver"; break;
+ case _ao_IMFixedMessageOnClick: return "_ao_IMFixedMessageOnClick"; break;
+ case _ao_IMMessage: return "_ao_IMMessage"; break;
+ case _ao_IMGUI: return "_ao_IMGUI"; break;
+ case _ao_IMElement: return "_ao_IMElement"; break;
+ case _ao_IMContainer: return "_ao_IMContainer"; break;
+ case _ao_IMDivider: return "_ao_IMDivider"; break;
+ case _ao_IMImage: return "_ao_IMImage"; break;
+ case _ao_IMText: return "_ao_IMText"; break;
+ case _ao_IMTextSelectionList: return "_ao_IMTextSelectionList"; break;
+ case _ao_IMSpacer: return "_ao_IMSpacer"; break;
+ case _ao_IMUIImage: return "_ao_IMUIImage"; break;
+ case _ao_IMUIText: return "_ao_IMUIText"; break;
+ case _ao_dictionary: return "_ao_dictionary"; break;
+ case _ao_unknown: return "_ao_unknown"; break;
+ case _ao_ignored: return "_ao_ignored"; break;
+ default: return "UNKNOWN TYPE"; break;
+ }
+}
+
+std::string GetTypeIDString(int type_id) {
+ std::stringstream ss;
+ if( type_id & asTYPEID_VOID) { ss << " asTYPEID_VOID"; }
+ if( type_id & asTYPEID_BOOL) { ss << " asTYPEID_BOOL"; }
+ if( type_id & asTYPEID_INT8) { ss << " asTYPEID_INT8"; }
+ if( type_id & asTYPEID_INT16) { ss << " asTYPEID_INT16"; }
+ if( type_id & asTYPEID_INT32) { ss << " asTYPEID_INT32"; }
+ if( type_id & asTYPEID_INT64) { ss << " asTYPEID_INT64"; }
+ if( type_id & asTYPEID_UINT8) { ss << " asTYPEID_UINT8"; }
+ if( type_id & asTYPEID_UINT16) { ss << " asTYPEID_UINT16"; }
+ if( type_id & asTYPEID_UINT32) { ss << " asTYPEID_UINT32"; }
+ if( type_id & asTYPEID_UINT64) { ss << " asTYPEID_UINT64"; }
+ if( type_id & asTYPEID_FLOAT) { ss << " asTYPEID_FLOAT"; }
+ if( type_id & asTYPEID_DOUBLE) { ss << " asTYPEID_DOUBLE"; }
+ if( type_id & asTYPEID_OBJHANDLE) { ss << " asTYPEID_OBJHANDLE"; }
+ if( type_id & asTYPEID_HANDLETOCONST) { ss << " asTYPEID_HANDLETOCONST"; }
+ if( type_id & asTYPEID_MASK_OBJECT) { ss << " asTYPEID_MASK_OBJECT"; }
+ if( type_id & asTYPEID_APPOBJECT) { ss << " asTYPEID_APPOBJECT"; }
+ if( type_id & asTYPEID_SCRIPTOBJECT) { ss << " asTYPEID_SCRIPTOBJECT"; }
+ if( type_id & asTYPEID_TEMPLATE) { ss << " asTYPEID_TEMPLATE"; }
+ if( type_id & asTYPEID_MASK_SEQNBR) { ss << " asTYPEID_MASK_SEQNBR"; }
+ return ss.str();
+}
diff --git a/Source/Scripting/angelscript/asmodule.h b/Source/Scripting/angelscript/asmodule.h
new file mode 100644
index 00000000..6c900ddf
--- /dev/null
+++ b/Source/Scripting/angelscript/asmodule.h
@@ -0,0 +1,255 @@
+//-----------------------------------------------------------------------------
+// Name: asmodule.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Asset/assets.h>
+#include <Scripting/scriptfile.h>
+#include <JSON/jsonhelper.h>
+#include <Internal/integer.h>
+#include <Logging/logdata.h>
+
+#include <angelscript.h>
+
+#include <string>
+#include <vector>
+#include <list>
+#include <map>
+
+class asIScriptModule;
+class asIScriptEngine;
+class asIScriptFunction;
+
+struct AppObject;
+struct ScriptObject;
+struct TemplateObject;
+
+enum VarStorageType {
+ _vs_unknown = 1,
+ _vs_bool = 2,
+ _vs_int8 = 3,
+ _vs_int32 = 4,
+ _vs_int64 = 5,
+ _vs_uint8 = 6,
+ _vs_uint32 = 7,
+ _vs_uint64 = 8,
+ _vs_float = 9,
+ _vs_enum = 10,
+ _vs_app_obj = 11,
+ _vs_script_obj = 12,
+ _vs_template = 13,
+ _vs_app_obj_handle = 14,
+ _vs_script_obj_handle = 15,
+ _vs_template_handle = 16,
+ _vs_int16 = 17,
+ _vs_uint16 = 18
+};
+
+enum ValueParent {
+ _vp_Global,
+ _vp_ScriptObject,
+ _vp_Array
+};
+
+enum AppObjectType {
+ _ao_vec3,
+ _ao_vec4,
+ _ao_ivec2,
+ _ao_ivec3,
+ _ao_ivec4,
+ _ao_string,
+ // -- Begin scenegraph objects --
+ _ao_ambient_sound_object,
+ _ao_camera_object,
+ _ao_decal_object,
+ _ao_dynamic_light_object,
+ _ao_env_object,
+ _ao_game_object,
+ _ao_group_object,
+ _ao_hotspot_object,
+ _ao_item_object,
+ _ao_light_probe_object,
+ _ao_light_volume_object,
+ _ao_movement_object,
+ _ao_navmesh_connection_object,
+ _ao_navmesh_hint_object,
+ _ao_navmesh_region_object,
+ _ao_path_point_object,
+ _ao_placeholder_object,
+ _ao_reflection_capture_object,
+ _ao_rigged_object,
+ _ao_terrain_object,
+ // -- End scenegraph objects --
+ _ao_attack_script_getter,
+ _ao_bone_transform,
+ _ao_quaternion,
+ _ao_vec2,
+ _ao_nav_path,
+ _ao_mat4,
+ _ao_json,
+ _ao_fontsetup,
+ _ao_modlevel,
+ _ao_textureassetref,
+ _ao_modid,
+ _ao_spawneritem,
+ _ao_IMFadeIn,
+ _ao_IMMoveIn,
+ _ao_IMChangeTextFadeOutIn,
+ _ao_IMChangeImageFadeOutIn,
+ _ao_IMPulseAlpha,
+ _ao_IMPulseBorderAlpha,
+ _ao_IMMouseOverMove,
+ _ao_IMMouseOverScale,
+ _ao_IMMouseOverShowBorder,
+ _ao_IMMouseOverPulseColor,
+ _ao_IMMouseOverPulseBorder,
+ _ao_IMMouseOverPulseBorderAlpha,
+ _ao_IMMouseOverFadeIn,
+ _ao_IMFixedMessageOnMouseOver,
+ _ao_IMFixedMessageOnClick,
+ _ao_IMMessage,
+ _ao_IMGUI,
+ _ao_IMElement,
+ _ao_IMContainer,
+ _ao_IMDivider,
+ _ao_IMImage,
+ _ao_IMText,
+ _ao_IMTextSelectionList,
+ _ao_IMSpacer,
+ _ao_IMUIImage,
+ _ao_IMUIText,
+ _ao_dictionary,
+ _ao_ignored,
+ _ao_unknown
+};
+
+struct VarStorage {
+ VarStorageType type;
+ int size;
+ std::string name;
+ std::string type_name;
+ void* var;
+ VarStorage();
+ void destroy();
+ std::string GetString(unsigned depth=0);
+};
+
+struct ScriptObjectInstance {
+ ScriptObjectInstance(uintptr_t ptr, VarStorage& var, bool from_script_obj_value);
+ void destroy();
+ uintptr_t ptr;
+ bool reconstructed;
+ bool from_script_obj_value; //This means there's a value version of this app object that all handles should re-refer to.
+ asIScriptObject* new_ptr; //New value generated
+ VarStorage var;
+};
+
+struct AppObject {
+ AppObjectType type;
+ void* original;
+ void* var;
+ AppObject();
+ void destroy();
+};
+
+struct AppObjectHandle {
+ AppObjectType type;
+ void* var;
+ AppObjectHandle();
+ void destroy();
+};
+
+enum TemplateObjectType {_to_array, _to_unknown};
+
+struct TemplateObject {
+ TemplateObjectType type;
+ std::string sub_type_name;
+ void* var;
+ TemplateObject();
+ void destroy();
+};
+
+struct ScriptObject {
+ uintptr_t orig_ptr; //Original ptr.
+ std::string type_name;
+ std::string name_space;
+ std::list<VarStorage> storage;
+ void Populate( asIScriptObject * obj, std::list<ScriptObjectInstance>& handle_var_list, asIScriptModule *module );
+ void destroy();
+};
+
+struct ScriptObjectInstanceHandleRemap {
+ ValueParent var_type;
+ void* parent_ptr;
+ int parent_index;
+ uintptr_t orig_ptr;
+};
+
+class ASModule {
+ asIScriptModule *module_;
+ const ScriptFile* script_file_ptr_;
+
+ std::map<void*, void*> fast_var_ptr_map;
+ std::map<std::string, asIScriptFunction*> func_map_;
+ Path script_path_;
+ std::list<VarStorage> saved_vars_;
+ std::list<ScriptObjectInstance> saved_handle_vars_;
+ int64_t modified_;
+
+ std::string* active_angelscript_error_string;
+
+ ASModule(const ASModule&);
+ ASModule& operator=(const ASModule&);
+
+public:
+ ASModule()
+ : active_angelscript_error_string(NULL)
+ {}
+ ~ASModule();
+
+ void SetErrorStringDestination(std::string* error_string);
+
+ asIScriptModule* GetInternalScriptModule();
+ void AttachToEngine(asIScriptEngine* engine);
+ int CompileScript(const Path& path);
+ bool SourceChanged();
+ const Path& GetScriptPath();
+ LineFile GetCorrectedLine(int line);
+ void ResetGlobals();
+ void Recompile();
+ void PrintGlobalVars();
+ void PrintAllTypes();
+ void LogGlobalVars();
+ void PrintScriptClassVars(asIScriptObject* obj);
+ void GetGlobalVars(std::list<VarStorage> &storage,std::list<ScriptObjectInstance>& handle_var_list);
+ void SaveGlobalVars();
+ void LoadGlobalVars();
+ void LoadVar(ValueParent var_type, void* parent_ptr, int parent_index, const VarStorage &vs, void *ptr, std::list<ScriptObjectInstance>& handle_var_list, std::list<ScriptObjectInstanceHandleRemap>& app_obj_handle_offset, asIScriptModule* module);
+ asIScriptFunction* GetFunctionID( const std::string &function_name );
+ void *GetVarPtr(const char* name);
+ void *GetVarPtrCache(const char* name); // Only use if passing fixed const char*, not converted from string
+ int CompileScriptFromText(const std::string &text);
+};
+
+const char* AppObjectTypeString(enum AppObjectType aot);
+
+std::string GetTypeIDString(int type_id);
diff --git a/Source/Scripting/angelscript/asprofiler.cpp b/Source/Scripting/angelscript/asprofiler.cpp
new file mode 100644
index 00000000..09338480
--- /dev/null
+++ b/Source/Scripting/angelscript/asprofiler.cpp
@@ -0,0 +1,255 @@
+//-----------------------------------------------------------------------------
+// Name: asprofiler.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "asprofiler.h"
+
+#include <Compat/time.h>
+#include <Scripting/angelscript/ascontext.h>
+#include <Logging/logdata.h>
+
+#include <algorithm>
+
+std::vector<ASContext*> ASProfiler::active_contexts;
+
+ASProfiler::ASProfiler()
+ : enabled(false)
+ , last_active_zone(NULL)
+ , active_zone(NULL)
+ , id_counter(0)
+ , window_counter(0)
+{}
+
+ASProfiler::~ASProfiler() {
+ for(size_t i = 0; i < zone_times.size(); ++i)
+ DeleteZone(zone_times[i]);
+}
+
+void ASProfiler::Update() {
+ if(window_counter == kMaxWindowSize) {
+ for(size_t i = 0; i < zone_times.size(); ++i) {
+ MoveForward(zone_times[i]);
+ }
+ for(size_t i = 0; i < script_function_times.size(); ++i) {
+ MoveForward(&script_function_times[i]);
+ }
+ window_counter = 0;
+ } else {
+ window_counter++;
+ }
+}
+
+static float TimeGetter(void* data, int idx)
+{
+ return (float)(((uint64_t*)data)[idx]) * 0.001f;
+}
+
+static char buffer[256];
+void ASProfiler::Draw() {
+ if(active_zone != NULL) {
+ LOGW << active_zone->name << " isn't closed before drawing the AS profiler, have you forgotten to close it? It will be automatically closed" << std::endl;
+ active_zone = NULL;
+ }
+
+ uint64_t zone_time = 0;
+ for(size_t i = 0; i < zone_times.size(); ++i) {
+ zone_time += zone_times[i]->times.back();
+ }
+
+ sprintf(buffer, "%s: %f###%p", context->current_script.GetOriginalPath(), zone_time * 0.001f, (void*)context);
+ if(ImGui::TreeNode(buffer)) {
+ for(size_t i = 0; i < script_function_times.size(); ++i) {
+ ZoneTime* zone = &script_function_times[i];
+ ImGui::Text("%s", zone->name.c_str());
+ ImGui::PlotHistogram("", &TimeGetter, zone->times.data(), kTimeHistorySize, 0, NULL, 0.0f, 5000.0f, ImVec2(0,40));
+ }
+
+ for(size_t i = 0; i < zone_times.size(); ++i) {
+ DrawZone(zone_times[i]);
+ }
+ ImGui::TreePop();
+ }
+}
+
+void ASProfiler::AddContext(ASContext* context) {
+ bool add = true;
+ for(size_t i = 0; i < active_contexts.size(); ++i) {
+ if(active_contexts[i] == context) {
+ add = false;
+ break;
+ }
+ }
+
+ if(add)
+ active_contexts.push_back(context);
+}
+
+void ASProfiler::RemoveContext(ASContext* context) {
+ for(size_t i = 0; i < active_contexts.size(); ++i) {
+ if(active_contexts[i] == context) {
+ active_contexts.erase(active_contexts.begin() + i);
+ break;
+ }
+ }
+}
+
+const std::vector<ASContext*>& ASProfiler::GetActiveContexts() {
+ return active_contexts;
+}
+
+void ASProfiler::SetContext(ASContext* context) {
+ this->context = context;
+}
+
+void ASProfiler::ASEnterTelemetryZone( const std::string& str ) {
+ if(!enabled)
+ return;
+
+ bool found = false;
+ if(active_zone == NULL) {
+ for(size_t i = 0; i < zone_times.size(); ++i) {
+ if(zone_times[i]->name == str) {
+ active_zone = zone_times[i];
+ found = true;
+ break;
+ }
+ }
+
+ if(!found) {
+ ZoneTime* newZone = new ZoneTime;
+ newZone->name = str;
+ newZone->parent = NULL;
+ newZone->times.resize(kTimeHistorySize, 0);
+ newZone->id = id_counter++;
+ zone_times.push_back(newZone);
+ active_zone = zone_times.back();
+ }
+ } else {
+ for(size_t i = 0; i < active_zone->children.size(); ++i) {
+ if(active_zone->children[i]->name == str) {
+ active_zone = active_zone->children[i];
+ found = true;
+ break;
+ }
+ }
+
+ if(!found) {
+ ZoneTime* newZone = new ZoneTime;
+ newZone->name = str;
+ newZone->parent = active_zone;
+ newZone->times.resize(kTimeHistorySize, 0);
+ newZone->id = id_counter++;
+ active_zone->children.push_back(newZone);
+ active_zone = active_zone->children.back();
+ }
+ }
+
+ active_zone->temp_time = GetPrecisionTime();
+}
+
+void ASProfiler::ASLeaveTelemetryZone() {
+ uint64_t end_time = GetPrecisionTime();
+ if(!enabled) {
+ return;
+ } else if(active_zone == NULL) {
+ if(last_active_zone == NULL)
+ LOGE << "LeaveTelemetryZone called from script file without calling EnterTelemetryZone first, no last active zone" << std::endl;
+ else
+ LOGE << "LeaveTelemetryZone called from script file without calling EnterTelemetryZone first, last active zone was " << last_active_zone->name << std::endl;
+
+ return;
+ }
+
+ uint64_t duration = ToNanoseconds(end_time) - ToNanoseconds(active_zone->temp_time);
+ active_zone->times[kTimeHistorySize - 1] = std::max(duration, active_zone->times[kTimeHistorySize - 1]);
+
+ last_active_zone = active_zone;
+ active_zone = active_zone->parent;
+}
+
+void ASProfiler::CallScriptFunction(const char* str) {
+ if(!enabled)
+ return;
+
+ ZoneTime* zone = NULL;
+ for(size_t i = 0; i < script_function_times.size(); ++i) {
+ if(strcmp(script_function_times[i].name.c_str(), str) == 0) {
+ zone = &script_function_times[i];
+ break;
+ }
+ }
+
+ if(!zone) {
+ ZoneTime newZone;
+ newZone.name = str;
+ newZone.parent = NULL;
+ newZone.id = -1;
+ newZone.times.resize(kTimeHistorySize);
+ script_function_times.push_back(newZone);
+ zone = &script_function_times.back();
+ }
+
+ last_script_function_zone = zone;
+ zone->temp_time = GetPrecisionTime();
+}
+
+void ASProfiler::LeaveScriptFunction() {
+ uint64_t end_time = GetPrecisionTime();
+ if(!enabled)
+ return;
+
+ if(!last_script_function_zone)
+ return; // Shouldn't happen
+
+ uint64_t duration = ToNanoseconds(end_time) - ToNanoseconds(last_script_function_zone->temp_time);
+ last_script_function_zone->times[kTimeHistorySize - 1] = std::max(duration, last_script_function_zone->times[kTimeHistorySize - 1]);
+ last_script_function_zone = NULL;
+}
+
+void ASProfiler::DeleteZone(ZoneTime* zone) {
+ for(size_t i = 0; i < zone->children.size(); ++i) {
+ DeleteZone(zone->children[i]);
+ }
+
+ delete zone;
+}
+
+void ASProfiler::MoveForward(ZoneTime* zone) {
+ memmove(zone->times.data(), zone->times.data() + 1, sizeof(uint64_t) * (kTimeHistorySize - 1));
+ zone->times[kTimeHistorySize - 1] = 0;
+
+ for(size_t i = 0; i < zone->children.size(); ++i) {
+ MoveForward(zone->children[i]);
+ }
+}
+
+void ASProfiler::DrawZone(ZoneTime* zone) {
+ uint64_t zone_time = zone->times.back();
+
+ sprintf(buffer, "%s: %f###%d", zone->name.c_str(), zone_time * 0.001f, zone->id);
+ if(ImGui::TreeNode(buffer)) {
+ ImGui::PlotHistogram("", &TimeGetter, zone->times.data(), kTimeHistorySize, 0, NULL, 0.0f, 5000.0f, ImVec2(0,40));
+ for(size_t i = 0; i < zone->children.size(); ++i) {
+ DrawZone(zone->children[i]);
+ }
+ ImGui::TreePop();
+ }
+}
diff --git a/Source/Scripting/angelscript/asprofiler.h b/Source/Scripting/angelscript/asprofiler.h
new file mode 100644
index 00000000..b96f9803
--- /dev/null
+++ b/Source/Scripting/angelscript/asprofiler.h
@@ -0,0 +1,89 @@
+//-----------------------------------------------------------------------------
+// Name: asprofiler.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/integer.h>
+
+#include <imgui.h>
+#include <imgui_internal.h>
+
+#include <vector>
+#include <string>
+#include <map>
+
+class ASContext;
+
+class ASProfiler
+{
+public:
+ ASProfiler();
+ ~ASProfiler();
+
+ bool enabled;
+
+ void Update();
+ void Draw();
+
+ static void AddContext(ASContext* context);
+ static void RemoveContext(ASContext* context);
+ static const std::vector<ASContext*>& GetActiveContexts();
+
+ void SetContext(ASContext* context);
+
+ void ASEnterTelemetryZone(const std::string& str);
+ void ASLeaveTelemetryZone();
+
+ void CallScriptFunction(const char* str);
+ void LeaveScriptFunction();
+
+ static int GetMaxWindowSize() { return kMaxWindowSize; }
+ static int GetMaxTimeHistorySize() { return kTimeHistorySize; }
+private:
+ const static int kMaxWindowSize = 60;
+ const static int kTimeHistorySize = 10;
+ struct ZoneTime {
+ std::string name;
+ std::vector<uint64_t> times;
+ uint64_t temp_time;
+ ZoneTime* parent;
+ std::vector<ZoneTime*> children;
+ int id;
+ };
+ static std::vector<ASContext*> active_contexts;
+
+ std::vector<ZoneTime> script_function_times;
+ ZoneTime* last_script_function_zone;
+
+ int id_counter;
+ int window_counter;
+
+ ASContext* context;
+
+ ZoneTime* last_active_zone;
+ ZoneTime* active_zone;
+ std::vector<ZoneTime*> zone_times;
+
+ void DeleteZone(ZoneTime* zone);
+ void MoveForward(ZoneTime* zone);
+ void DrawZone(ZoneTime* zone);
+};
diff --git a/Source/Scripting/angelscript/scriptstdstring_extend.cpp b/Source/Scripting/angelscript/scriptstdstring_extend.cpp
new file mode 100644
index 00000000..bd997c8b
--- /dev/null
+++ b/Source/Scripting/angelscript/scriptstdstring_extend.cpp
@@ -0,0 +1,73 @@
+//-----------------------------------------------------------------------------
+// Name: scriptstdstring_extend.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "scriptstdstring_extend.h"
+
+#include <Scripting/angelscript/ascontext.h>
+#include <Math/vec3.h>
+
+#include <string>
+#include <sstream>
+
+using namespace std;
+
+static void AssignVec32StringGeneric(asIScriptGeneric *gen) {
+ vec3 *a = static_cast<vec3*>(gen->GetAddressOfArg(0));
+ string *self = static_cast<string*>(gen->GetObject());
+ std::stringstream sstr;
+ sstr << *a;
+ *self = sstr.str();
+ gen->SetReturnAddress(self);
+}
+
+static void AddAssignVec32StringGeneric(asIScriptGeneric * gen) {
+ vec3 *a = static_cast<vec3*>(gen->GetAddressOfArg(0));
+ string * self = static_cast<string *>(gen->GetObject());
+ std::stringstream sstr;
+ sstr << *a;
+ *self += sstr.str();
+ gen->SetReturnAddress(self);
+}
+static void AddString2Vec3Generic(asIScriptGeneric * gen) {
+ string * a = static_cast<string *>(gen->GetObject());
+ vec3 *b = static_cast<vec3*>(gen->GetAddressOfArg(0));
+ std::stringstream sstr;
+ sstr << *a << *b;
+ std::string ret_val = sstr.str();
+ gen->SetReturnObject(&ret_val);
+}
+
+static void AddVec32StringGeneric(asIScriptGeneric * gen) {
+ vec3 *a = static_cast<vec3*>(gen->GetAddressOfArg(0));
+ string *b = static_cast<string *>(gen->GetObject());
+ std::stringstream sstr;
+ sstr << *a << *b;
+ std::string ret_val = sstr.str();
+ gen->SetReturnObject(&ret_val);
+}
+
+void RegisterStdString_Extend(ASContext* ctx) {
+ ctx->RegisterObjectMethod("string", "string &opAssign(vec3)", asFUNCTION(AssignVec32StringGeneric), asCALL_GENERIC, "These 4 functions allow us to append vec3s to strings");
+ ctx->RegisterObjectMethod("string", "string &opAddAssign(vec3)", asFUNCTION(AddAssignVec32StringGeneric), asCALL_GENERIC);
+ ctx->RegisterObjectMethod("string", "string opAdd(vec3) const", asFUNCTION(AddString2Vec3Generic), asCALL_GENERIC);
+ ctx->RegisterObjectMethod("string", "string opAdd_r(vec3) const", asFUNCTION(AddVec32StringGeneric), asCALL_GENERIC);
+}
diff --git a/Source/Scripting/angelscript/scriptstdstring_extend.h b/Source/Scripting/angelscript/scriptstdstring_extend.h
new file mode 100644
index 00000000..9360162d
--- /dev/null
+++ b/Source/Scripting/angelscript/scriptstdstring_extend.h
@@ -0,0 +1,26 @@
+//-----------------------------------------------------------------------------
+// Name: scriptstdstring_extend.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+class ASContext;
+void RegisterStdString_Extend(ASContext* ctx);
diff --git a/Source/Scripting/scriptfile.cpp b/Source/Scripting/scriptfile.cpp
new file mode 100644
index 00000000..40e3b169
--- /dev/null
+++ b/Source/Scripting/scriptfile.cpp
@@ -0,0 +1,429 @@
+//-----------------------------------------------------------------------------
+// Name: scriptfile.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "scriptfile.h"
+
+#include <Internal/error.h>
+#include <Internal/filesystem.h>
+#include <Internal/stopwatch.h>
+#include <Internal/returnpathutil.h>
+#include <Internal/checksum.h>
+#include <Internal/config.h>
+#include <Internal/profiler.h>
+
+#include <Scripting/scriptlogging.h>
+#include <Compat/fileio.h>
+#include <Main/engine.h>
+
+#include <errno.h>
+#include <algorithm>
+
+using std::min;
+using std::max;
+
+extern std::string script_dir_path;
+
+const int _num_script_open_tries = 10;
+
+std::string ScriptFile::GetModPollutionInformation() const {
+ std::stringstream ss;
+ for( unsigned i = 0; i < dependencies.size(); i++ ) {
+ if( dependencies[i].path.GetModsource() != CoreGameModID ) {
+ ss << "Includes " << dependencies[i].path << " from mod " << ModLoading::Instance().GetModName(dependencies[i].path.GetModsource()) << "." << std::endl;
+ }
+ }
+ return ss.str();
+}
+
+static bool AlreadyAddedIncludeFileInParentHierarchy(const ScriptFile* parent_script_file, const Path &path) {
+ bool already_loaded = false;
+ while(parent_script_file != NULL){
+ if(path == parent_script_file->file_path){
+ already_loaded = true;
+ break;
+ }
+ parent_script_file = parent_script_file->parent;
+ }
+ return already_loaded;
+}
+
+bool ScriptFile::AlreadyAddedIncludeFile(const Path &path) {
+ bool already_loaded = false;
+ for(unsigned i=0; i<dependencies.size(); ++i){
+ if(path == dependencies[i].path){
+ already_loaded = true;
+ break;
+ }
+ }
+ return already_loaded;
+}
+
+unsigned GetLineNumber(std::string &script, unsigned char_pos){
+ unsigned line_number = 1;
+ for(unsigned i=0; i<char_pos; i++){
+ if(script[i] == '\n'){
+ line_number++;
+ }
+ }
+ return line_number;
+}
+
+unsigned GetNumLines(std::string &script){
+ unsigned line_number = 1;
+ for(unsigned i=0; i<script.size(); i++){
+ if(script[i] == '\n'){
+ line_number++;
+ }
+ }
+ return line_number;
+}
+
+FileRangeList::iterator GetFileRange(FileRangeList& ranges, unsigned line) {
+ FileRangeList::iterator iter = ranges.begin();
+ FileRangeList::iterator good_iter = ranges.end();
+ for(;iter != ranges.end(); ++iter){
+ IncludeFileRange& range = (*iter);
+ if(line >= range.start){
+ good_iter = iter;
+ }
+ }
+ return good_iter;
+}
+
+FileRangeList::const_iterator GetFileRange(const FileRangeList& ranges,
+ unsigned line) {
+ FileRangeList::const_iterator iter = ranges.begin();
+ FileRangeList::const_iterator good_iter = ranges.end();
+ for(;iter != ranges.end(); ++iter){
+ const IncludeFileRange& range = (*iter);
+ if(line >= range.start){
+ good_iter = iter;
+ }
+ }
+ return good_iter == ranges.end() ? ranges.begin() : good_iter;
+}
+
+static FILE *robust_fopen(const char* path, const char* mode, int max_tries = 10) {
+ int tries = 0;
+ FILE *file = my_fopen(path, mode);
+ while(file == NULL && tries < max_tries){
+ file = my_fopen(path, mode);
+ if(file == NULL){
+ tries++;
+ BusyWaitMilliseconds(50);
+ }
+ }
+ return file;
+}
+
+static std::string ReadScriptFile(const Path &path) {
+ int max_tries = 10;
+ int tries = 0;
+ int len = 0;
+ bool file_exists = false;
+ FILE *file;
+
+ while(len == 0 && tries < max_tries){
+ file = robust_fopen(path.GetFullPath(), "rb");
+
+ if( file == NULL ) {
+ FatalError("Error","Failed to open the script file: %s\n%s",
+ path.GetFullPath(), strerror(errno));
+ }
+
+ file_exists = true;
+
+ // Determine the size of the file
+ fseek(file, 0, SEEK_END);
+ len = ftell(file);
+ fseek(file, 0, SEEK_SET);
+
+ if(len == 0){
+ fclose(file);
+ tries++;
+ BusyWaitMilliseconds(50);
+ }
+ }
+
+ // Read the entire file
+ std::string script;
+ script.resize(len);
+ int c = fread(&script[0], len, 1, file);
+
+ if(file_exists) {
+ if( len == 0 ) {
+ FatalError("Error","Script file was present, but empty:\n%s\n%s",
+ path.GetFullPath(), strerror(errno));
+ } else if( c == 0 ) {
+ FatalError("Error","Failed to load script file:\n%s\n%s",
+ path.GetFullPath(), strerror(errno));
+ }
+ } else {
+ FatalError("Error","Failed to load script file:\n%s\n%s",
+ path.GetFullPath(), strerror(errno));
+ }
+
+ fclose(file);
+
+ return script;
+}
+
+static void UpdateFile( const ScriptFile* parent_script_file, ScriptFile &file, const std::string &source, const Path& path ) {
+ file.parent = parent_script_file;
+ file.unexpanded_contents = source;
+ file.contents = source;
+ file.file_path = path;
+ file.dependencies.clear();
+ file.dependencies.resize(1);
+ file.dependencies[0].path = path;
+ file.dependencies[0].modified = GetDateModifiedInt64(path);
+ file.ExpandIncludePaths();
+ file.hash = djb2_string_hash(file.contents.c_str());
+ std::vector<Dependency>::iterator iter = file.dependencies.begin();
+ file.latest_modification = (*iter).modified;
+ ++iter;
+ for(; iter != file.dependencies.end(); ++iter){
+ file.latest_modification = max(file.latest_modification, (*iter).modified);
+ }
+}
+
+static const ScriptFile* GetScriptFileRecursive(const ScriptFile* parent_script_file, const Path& path) {
+ // Look in script file cache for desired script file
+ ScriptFileMap& file_map = ScriptFileCache::Instance()->files;
+ ScriptFileMap::iterator iter;
+ iter = file_map.find(path.GetFullPathStr());
+ ModID modsource;
+ if(iter == file_map.end()){
+ // Desired script file not found, so load it
+ std::string source = ReadScriptFile(path);
+ if (!source.empty()) {
+ ScriptFile& file = file_map[path.GetFullPathStr()];
+ UpdateFile(parent_script_file, file, source, path);
+ return &file;
+ }
+ } else {
+ // Script file found -- make sure it hasn't changed
+ ScriptFile &script_file = (*iter).second;
+ if(ScriptFileUtil::DetectScriptFileChanged(path)){
+ std::string source = ReadScriptFile(path);
+ if (!source.empty()) {
+ UpdateFile(parent_script_file, script_file, source, path);
+ return &script_file;
+ }
+ } else {
+ return &script_file;
+ }
+ }
+
+ return NULL;
+}
+
+static void InsertFileRange(FileRangeList& ranges,
+ unsigned start,
+ unsigned length,
+ const Path& path)
+{
+ FileRangeList::iterator parent_iter = GetFileRange(ranges, start);
+
+ // Split parent in half
+ IncludeFileRange& parent = (*parent_iter);
+ unsigned local_cut = start - parent.start;
+
+ if(parent.length-local_cut>0){
+ FileRangeList::iterator after_parent_iter = parent_iter;
+ after_parent_iter++;
+ ranges.insert(after_parent_iter,
+ IncludeFileRange(parent.start+local_cut,
+ parent.length-local_cut,
+ parent.offset,
+ parent.file_path));
+ parent.length = local_cut;
+ }
+
+ // Shift all elements to the right of the cut
+ {
+ FileRangeList::iterator shift_iter = parent_iter;
+ shift_iter++;
+ for(;shift_iter != ranges.end(); ++shift_iter){
+ IncludeFileRange& range = (*shift_iter);
+ range.start += length;
+ range.offset += length-1;
+ }
+ }
+
+ // Add new range in between parent halves
+ {
+ FileRangeList::iterator after_parent_iter = parent_iter;
+ after_parent_iter++;
+ ranges.insert(after_parent_iter,
+ IncludeFileRange(start,length,start-1,path));
+ }
+
+ // Delete original parent half if length is now zero
+ if(parent.length == 0){
+ ranges.erase(parent_iter);
+ }
+}
+
+void ScriptFile::ExpandIncludePaths(){
+ std::string &script = contents;
+
+ file_range.clear();
+ file_range.push_back(IncludeFileRange(1,GetNumLines(script),0,file_path));
+
+ // Get the position of the first letter of the first "#include"
+ // directive in the script
+ size_t found_pos = script.find("#include");
+ while (found_pos != std::string::npos){
+ bool disabled_include = false;
+ for( int i = found_pos-1; i >= 0; i-- ) {
+ if(script[i] != ' ' && script[i] != '\t') {
+ if( script[i] == '\n' || script[i] == '\r' ) {
+ break;
+ } else {
+ if( script[i] != '/' ) {
+ LOGE << "Unexpected character \'" << script[i] << "\' before #include in " << file_path << std::endl;
+ int line_count = 0;
+ for( int k = 0; k < i; k++ ){
+ if( script[k] == '\n' ){
+ line_count++;
+ }
+ }
+ LOGE << " line " << line_count << std::endl;
+ } else {
+ disabled_include = true;
+ }
+ }
+ }
+ }
+ size_t path_start = found_pos + 10; // Get pos of first letter of path
+ size_t path_end = script.find('\"',path_start); // Find end of path
+ std::string path = std::string(script_dir_path) +
+ script.substr(path_start, path_end-path_start);
+
+ // If include file has not already been added, then replace #include
+ // directive with contents of included file. Otherwise, just remove
+ // the directive.
+ Path include_path = FindFilePath(path, kDataPaths | kModPaths);
+ if(disabled_include == false && !AlreadyAddedIncludeFileInParentHierarchy(this, include_path) && !AlreadyAddedIncludeFile(include_path)){
+ dependencies.resize(dependencies.size()+1);
+ dependencies.back().path = include_path;
+ if( include_path.isValid() ) {
+ dependencies.back().modified = GetDateModifiedInt64(include_path.GetFullPath());
+
+ std::string new_script = GetScriptFileRecursive(this, include_path)->unexpanded_contents;
+
+ if( config["dump_include_scripts"].toBool() )
+ {
+ new_script =
+ + "/*include - START - " + path + " */\n"
+ + new_script
+ + "/*include - END - " + path + " */\n";
+ }
+
+ script.replace(found_pos,path_end+1-found_pos,new_script);
+
+ unsigned line_number = GetLineNumber(script, found_pos);
+ unsigned include_length = GetNumLines(new_script);
+ InsertFileRange(file_range,line_number,include_length,include_path);
+ } else {
+ LOGE << "Could not resolve script include: " << path << std::endl;
+ }
+ } else {
+ script.replace(found_pos,path_end+1-found_pos,"");
+ }
+
+ found_pos = script.find("#include",found_pos);
+ }
+}
+
+LineFile ScriptFile::GetCorrectedLine( unsigned line ) const
+{
+ LOGD << "Getting corrected line A." << std::endl;
+ FileRangeList::const_iterator iter = GetFileRange(file_range, line);
+ LOGD << "Getting corrected line B." << std::endl;
+ const IncludeFileRange& range = (*iter);
+ LOGD << "Getting corrected line C." << std::endl;
+
+ return LineFile(line - range.offset, range.file_path);
+}
+
+bool ScriptFileUtil::DetectScriptFileChanged(const Path &path) {
+ ScriptFileMap& file_map = ScriptFileCache::Instance()->files;
+ ScriptFileMap::iterator iter;
+ iter = file_map.find(path.GetFullPathStr());
+ if(iter == file_map.end()){
+ return true;
+ } else {
+ ScriptFile &file = (*iter).second;
+ return GetLatestModification(path) > file.latest_modification;
+ }
+}
+
+int64_t ScriptFileUtil::GetLatestModification(const Path &path) {
+ PROFILER_ZONE(g_profiler_ctx, "GetLatestModification");
+ ScriptFileMap& file_map = ScriptFileCache::Instance()->files;
+ ScriptFileMap::iterator iter;
+ iter = file_map.find(path.GetFullPathStr());
+ if(iter == file_map.end()){
+ DisplayError("Error", "Getting latest modification of unloaded script");
+ return 0;
+ } else {
+ ScriptFile &file = (*iter).second;
+ std::vector<Dependency>::iterator iter = file.dependencies.begin();
+ int64_t latest_modification = GetDateModifiedInt64((*iter).path);
+ ++iter;
+ for(; iter != file.dependencies.end(); ++iter){
+ latest_modification = max(latest_modification, GetDateModifiedInt64((*iter).path));
+ }
+ return latest_modification;
+ }
+}
+
+const ScriptFile* ScriptFileUtil::GetScriptFile(const Path& path) {
+ return GetScriptFileRecursive(NULL, path);
+}
+
+void ScriptFileUtil::ReturnPaths(const Path &script_path, PathSet &path_set) {
+ path_set.insert("script "+script_path.GetOriginalPathStr());
+ const ScriptFile &script_file = *ScriptFileUtil::GetScriptFile(script_path);
+ const std::string &script = script_file.contents;
+ std::string load_path;
+ std::string type;
+ std::string::size_type index = 0;
+ while(1){
+ index = script.find("\"Data/", index); // Set index to first quote of "Data/" path
+ if(index == std::string::npos){
+ break;
+ }
+ ++index; // Set index to first character of Data/ path
+ std::string::size_type end_quote = script.find('\"', index); // Find last quote
+ load_path = script.substr(index, end_quote - index);
+ ReturnPathUtil::ReturnPathsFromPath(load_path, path_set);
+ }
+}
+
+void ScriptFileCache::Dispose()
+{
+ files.clear();
+}
+
diff --git a/Source/Scripting/scriptfile.h b/Source/Scripting/scriptfile.h
new file mode 100644
index 00000000..dfcd5b18
--- /dev/null
+++ b/Source/Scripting/scriptfile.h
@@ -0,0 +1,108 @@
+//-----------------------------------------------------------------------------
+// Name: scriptfile.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/datemodified.h>
+#include <Internal/path.h>
+#include <Internal/modid.h>
+#include <Internal/path.h>
+
+#include <Asset/assets.h>
+
+#include <map>
+#include <string>
+#include <list>
+#include <vector>
+
+struct Dependency {
+ Path path;
+ int64_t modified;
+};
+
+struct IncludeFileRange {
+ unsigned start;
+ Path file_path;
+ unsigned offset;
+ unsigned length;
+ IncludeFileRange(unsigned _start,
+ unsigned _length,
+ unsigned _offset,
+ const Path &_file_path)
+ :start(_start),
+ file_path(_file_path),
+ offset(_offset),
+ length(_length)
+ {}
+};
+
+typedef std::list<IncludeFileRange> FileRangeList;
+
+struct LineFile {
+ unsigned line_number;
+ const Path file;
+
+ LineFile(unsigned _line_number,
+ const Path &_file)
+ :line_number(_line_number),
+ file(_file)
+ {}
+};
+
+struct ScriptFile {
+ const ScriptFile* parent;
+ std::string unexpanded_contents;
+ std::string contents;
+ Path file_path;
+ std::vector<Dependency> dependencies;
+ FileRangeList file_range;
+ int64_t latest_modification;
+ unsigned long hash;
+
+ void ExpandIncludePaths();
+ LineFile GetCorrectedLine(unsigned line) const;
+
+ std::string GetModPollutionInformation() const;
+
+private:
+ bool AlreadyAddedIncludeFile(const Path &path);
+};
+
+typedef std::map<std::string, ScriptFile> ScriptFileMap;
+
+struct ScriptFileCache {
+ ScriptFileMap files;
+
+ static ScriptFileCache* Instance() {
+ static ScriptFileCache instance;
+ return &instance;
+ }
+
+ void Dispose();
+};
+
+namespace ScriptFileUtil {
+ const ScriptFile* GetScriptFile(const Path& path);
+ bool DetectScriptFileChanged(const Path &path);
+ int64_t GetLatestModification(const Path &path);
+ void ReturnPaths(const Path &path, PathSet &path_set);
+}
diff --git a/Source/Scripting/scriptlogging.h b/Source/Scripting/scriptlogging.h
new file mode 100644
index 00000000..a9afd52e
--- /dev/null
+++ b/Source/Scripting/scriptlogging.h
@@ -0,0 +1,55 @@
+//-----------------------------------------------------------------------------
+// Name: scriptlogging.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Logging/logdata.h>
+
+#if LOG_LEVEL > 0
+#undef LOGF
+#define LOGF LogSystem::LogData( LogSystem::fatal, "as",__FILE__,__LINE__)
+#endif
+
+#if LOG_LEVEL > 1
+#undef LOGE
+#define LOGE LogSystem::LogData( LogSystem::error, "as",__FILE__,__LINE__)
+#endif
+
+#if LOG_LEVEL > 2
+#undef LOGW
+#define LOGW LogSystem::LogData( LogSystem::warning, "as",__FILE__,__LINE__)
+#endif
+
+#if LOG_LEVEL > 3
+#undef LOGI
+#define LOGI LogSystem::LogData( LogSystem::info, "as",__FILE__,__LINE__)
+#endif
+
+#if LOG_LEVEL > 4
+#undef LOGD
+#define LOGD LogSystem::LogData( LogSystem::debug, "as",__FILE__,__LINE__)
+#endif
+
+#if LOG_LEVEL > 5
+#undef LOGS
+#define LOGS LogSystem::LogData( LogSystem::spam, "as",__FILE__,__LINE__)
+#endif
diff --git a/Source/Scripting/scriptparams.cpp b/Source/Scripting/scriptparams.cpp
new file mode 100644
index 00000000..61938633
--- /dev/null
+++ b/Source/Scripting/scriptparams.cpp
@@ -0,0 +1,713 @@
+//-----------------------------------------------------------------------------
+// Name: scriptparams.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "scriptparams.h"
+
+#include <Scripting/angelscript/ascontext.h>
+#include <Internal/memwrite.h>
+#include <Logging/logdata.h>
+#include <Utility/assert.h>
+#include <Main/engine.h>
+
+#include <tinyxml.h>
+
+#include <sstream>
+
+void ScriptParams::RegisterScriptInstance( ASContext* context )
+{
+ context->RegisterGlobalProperty("ScriptParams params", this);
+}
+
+void ScriptParams::RegisterScriptType( ASContext* context )
+{
+ if(context->TypeExists("ScriptParams")){
+ return;
+ }
+ context->RegisterObjectType("ScriptParams", 0, asOBJ_REF | asOBJ_NOCOUNT);
+ context->RegisterObjectMethod("ScriptParams", "const string &GetString (const string &in key)", asMETHOD(ScriptParams,GetStringVal), asCALL_THISCALL);
+ context->RegisterObjectMethod("ScriptParams", "float GetFloat (const string &in key)", asMETHOD(ScriptParams,ASGetFloat), asCALL_THISCALL);
+ context->RegisterObjectMethod("ScriptParams", "int GetInt (const string &in key)", asMETHOD(ScriptParams,ASGetInt), asCALL_THISCALL);
+ context->RegisterObjectMethod("ScriptParams", "float SetFloat (const string &in key, float val)", asMETHOD(ScriptParams,ASSetFloat), asCALL_THISCALL);
+ context->RegisterObjectMethod("ScriptParams", "int SetInt (const string &in key, int)", asMETHOD(ScriptParams,ASSetInt), asCALL_THISCALL);
+ context->RegisterObjectMethod("ScriptParams", "void SetString (const string &in key, const string &in val)", asMETHOD(ScriptParams,ASSetString), asCALL_THISCALL);
+ context->RegisterObjectMethod("ScriptParams", "void AddInt (const string &in key, int default_val)", asMETHOD(ScriptParams,ASAddInt), asCALL_THISCALL);
+ context->RegisterObjectMethod("ScriptParams", "void AddIntSlider (const string &in key, int default_val, const string &in bounds)", asMETHOD(ScriptParams,ASAddIntSlider), asCALL_THISCALL);
+ context->RegisterObjectMethod("ScriptParams", "void AddFloatSlider (const string &in key, float default_val, const string &in bounds)", asMETHOD(ScriptParams,ASAddFloatSlider), asCALL_THISCALL);
+ context->RegisterObjectMethod("ScriptParams", "void AddIntCheckbox (const string &in key, bool default_val)", asMETHOD(ScriptParams,ASAddIntCheckbox), asCALL_THISCALL);
+ context->RegisterObjectMethod("ScriptParams", "void AddFloat (const string &in key, float default_val)", asMETHOD(ScriptParams,ASAddFloat), asCALL_THISCALL);
+ context->RegisterObjectMethod("ScriptParams", "void AddString (const string &in key, const string &in default_val)", asMETHOD(ScriptParams,ASAddString), asCALL_THISCALL);
+
+ context->RegisterObjectMethod("ScriptParams", "void ASAddJSONFromString (const string &in key, const string &in default_val)", asMETHOD(ScriptParams,ASAddJSONFromString), asCALL_THISCALL);
+ context->RegisterObjectMethod("ScriptParams", "void AddJSON (const string &in key, JSON &in default_val)", asMETHOD(ScriptParams,ASAddJSON), asCALL_THISCALL);
+ context->RegisterObjectMethod("ScriptParams", "JSON GetJSON (const string &in key)", asMETHOD(ScriptParams,ASGetJSON), asCALL_THISCALL);
+
+
+ context->RegisterObjectMethod("ScriptParams", "void Remove (const string &in key)", asMETHOD(ScriptParams,ASRemove), asCALL_THISCALL);
+ context->RegisterObjectMethod("ScriptParams", "bool HasParam (const string &in key)", asMETHOD(ScriptParams,HasParam), asCALL_THISCALL);
+ context->RegisterObjectMethod("ScriptParams", "bool IsParamInt(const string &in key)", asMETHOD(ScriptParams, IsParamInt), asCALL_THISCALL);
+ context->RegisterObjectMethod("ScriptParams", "bool IsParamFloat(const string &in key)", asMETHOD(ScriptParams, IsParamFloat), asCALL_THISCALL);
+ context->RegisterObjectMethod("ScriptParams", "bool IsParamString(const string &in key)", asMETHOD(ScriptParams, IsParamString), asCALL_THISCALL);
+ context->RegisterObjectMethod("ScriptParams", "bool IsParamJSON(const string &in key)", asMETHOD(ScriptParams, IsParamJSON), asCALL_THISCALL);
+ context->DocsCloseBrace();
+}
+
+namespace {
+ std::string null_str;
+}
+
+const std::string &ScriptParams::GetStringVal(const std::string &val) const
+{
+ ScriptParamMap::const_iterator iter = parameter_map_.find(val);
+ if(iter != parameter_map_.end()) {
+ return iter->second.GetString();
+ } else {
+ DisplayError("Error",("No parameter \""+val+"\" of correct type.").c_str());
+ return null_str;
+ }
+}
+
+const std::string &ScriptParams::GetJSONValAsString(const std::string &val) const
+{
+ ScriptParamMap::const_iterator iter = parameter_map_.find(val);
+ if(iter != parameter_map_.end() && iter->second.IsJSON() ) {
+ return iter->second.GetString();
+ } else {
+ DisplayError("Error",("No parameter \""+val+"\" of correct type.").c_str());
+ return null_str;
+ }
+}
+
+SimpleJSONWrapper ScriptParams::GetJSONVal(const std::string &val)
+{
+ ScriptParamMap::iterator iter = parameter_map_.find(val);
+ if(iter != parameter_map_.end() && iter->second.IsJSON() ) {
+ return iter->second.GetJSON();
+ } else {
+ DisplayError("Error",("No parameter \""+val+"\" of correct type.").c_str());
+ SimpleJSONWrapper nullWrapper;
+ return nullWrapper;
+ }
+
+}
+
+bool ScriptParams::HasParam(const std::string &val) const {
+ return parameter_map_.find(val) != parameter_map_.end();
+}
+
+bool ScriptParams::IsParamString(const std::string &val) const {
+
+ ScriptParamMap::const_iterator iter = parameter_map_.find(val);
+
+ if(iter == parameter_map_.end() || !(iter->second.IsString()) )
+ {
+ return false;
+ }
+ else {
+ return true;
+ }
+
+}
+
+bool ScriptParams::IsParamFloat(const std::string &val) const {
+
+ ScriptParamMap::const_iterator iter = parameter_map_.find(val);
+
+ if(iter == parameter_map_.end() || !(iter->second.IsFloat()) )
+ {
+ return false;
+ }
+ else {
+ return true;
+ }
+
+}
+
+bool ScriptParams::IsParamInt(const std::string &val) const {
+
+ ScriptParamMap::const_iterator iter = parameter_map_.find(val);
+
+ if(iter == parameter_map_.end() || !(iter->second.IsInt()) )
+ {
+ return false;
+ }
+ else {
+ return true;
+ }
+
+}
+
+bool ScriptParams::IsParamJSON(const std::string &val) const {
+
+ ScriptParamMap::const_iterator iter = parameter_map_.find(val);
+
+ if(iter == parameter_map_.end() || !(iter->second.IsJSON()) )
+ {
+ return false;
+ }
+ else {
+ return true;
+ }
+}
+
+float ScriptParams::ASGetFloat(const std::string &key)
+{
+ ScriptParamMap::iterator iter = parameter_map_.find(key);
+ float ret_val = -1.0f;
+ if(iter != parameter_map_.end()){
+ const ScriptParam &sp = iter->second;
+ ret_val = sp.GetFloat();
+ } else {
+ DisplayError("Error",("No parameter \""+key+"\" of correct type.").c_str());
+ }
+ return ret_val;
+}
+
+int ScriptParams::ASGetInt(const std::string &key)
+{
+ ScriptParamMap::iterator iter = parameter_map_.find(key);
+ int ret_val = -1;
+ if(iter != parameter_map_.end()) {
+ const ScriptParam &sp = iter->second;
+ ret_val = sp.GetInt();
+ } else {
+ DisplayError("Error",("No parameter \""+key+"\" of correct type.").c_str());
+ }
+ return ret_val;
+}
+
+void ScriptParams::ASRemove(const std::string &key) {
+ ScriptParamMap::iterator iter = parameter_map_.find(key);
+ parameter_map_.erase(key);
+}
+
+void ScriptParams::SetObjectID(uint32_t id) {
+ obj_id = id;
+}
+
+uint32_t ScriptParams::GetObjectID() const {
+ return obj_id;
+}
+
+bool ScriptParams::RenameParameterKey(const std::string & curr_name, const std::string & next_name) {
+ if (HasParam(curr_name)) {
+
+ ScriptParam sp = parameter_map_[curr_name];
+ parameter_map_.erase(curr_name);
+ parameter_map_[next_name] = sp;
+
+ return true;
+ }
+ return false;
+}
+
+void ScriptParams::ASSetFloat(const std::string &key, float num)
+{
+ parameter_map_[key].SetFloat(num);
+
+ if (Online::Instance()->IsActive()) {
+ Online::Instance()->SendIntFloatScriptParam(obj_id, key, parameter_map_[key]);
+ }
+
+}
+
+void ScriptParams::ASSetInt(const std::string &key, int num)
+{
+ parameter_map_[key].SetInt(num);
+
+ if (Online::Instance()->IsActive()) {
+ Online::Instance()->SendIntFloatScriptParam(obj_id, key, parameter_map_[key]);
+ }
+}
+
+void ScriptParams::ASAddInt(const std::string &key, int default_val)
+{
+ if(parameter_map_.find(key) != parameter_map_.end()){
+ return;
+ }
+ ScriptParam sp;
+ sp.SetInt(default_val);
+ parameter_map_.insert(std::pair<std::string, ScriptParam>(key, sp));
+}
+
+void ScriptParams::ASAddIntSlider(const std::string &key, int default_val, const std::string &details)
+{
+ ASAddInt(key, default_val);
+ ScriptParam &sp = parameter_map_[key];
+ sp.editor().SetDisplaySlider(details);
+}
+
+void ScriptParams::ASAddFloatSlider(const std::string &key, float default_val, const std::string &details)
+{
+ ASAddFloat(key, default_val);
+ ScriptParam &sp = parameter_map_[key];
+ sp.editor().SetDisplaySlider(details);
+}
+
+void ScriptParams::ASAddIntCheckbox(const std::string &key, bool default_val)
+{
+ ASAddInt(key, default_val);
+ ScriptParam &sp = parameter_map_[key];
+ sp.editor().SetCheckbox();
+}
+
+void ScriptParams::ASAddFloat(const std::string &key, float default_val)
+{
+ if(parameter_map_.find(key) != parameter_map_.end()){
+ return;
+ }
+ ScriptParam sp;
+ sp.SetFloat(default_val);
+ parameter_map_.insert(std::pair<std::string, ScriptParam>(key, sp));
+}
+
+void ScriptParams::ASAddString(const std::string &key, const std::string &default_val) {
+ if(parameter_map_.find(key) != parameter_map_.end()){
+ return;
+ }
+ ScriptParam sp;
+ sp.SetString(default_val);
+ parameter_map_.insert(std::pair<std::string, ScriptParam>(key, sp));
+}
+
+void ScriptParams::InsertScriptParam(const std::string & key, ScriptParam sp) {
+ if (parameter_map_.find(key) != parameter_map_.end()) {
+ parameter_map_[key].UpdateValuesFromSocket(sp);
+ } else {
+ parameter_map_[key] = sp;
+ }
+}
+
+void ScriptParams::InsertNewScriptParam(const std::string& key, ScriptParam& param) {
+ parameter_map_[key] = param;
+ if (Online::Instance()->IsActive()) {
+ Online::Instance()->SendStringScriptParam(obj_id, key, parameter_map_[key]);
+ }
+}
+
+void ScriptParams::ASAddJSONFromString(const std::string &key, const std::string &default_val) {
+ if(parameter_map_.find(key) != parameter_map_.end()){
+ return;
+ }
+ ScriptParam sp;
+ sp.SetJSONFromString(default_val);
+ parameter_map_.insert(std::pair<std::string, ScriptParam>(key, sp));
+}
+
+void ScriptParams::ASAddJSON(const std::string &key, SimpleJSONWrapper& details) {
+ if(parameter_map_.find(key) != parameter_map_.end()){
+ return;
+ }
+ ScriptParam sp;
+ sp.SetJSON(details);
+ parameter_map_.insert(std::pair<std::string, ScriptParam>(key, sp));
+}
+
+SimpleJSONWrapper ScriptParams::ASGetJSON(const std::string &key) {
+
+ ScriptParamMap::iterator iter = parameter_map_.find(key);
+ SimpleJSONWrapper ret_val;
+ if(iter != parameter_map_.end()) {
+ ScriptParam &sp = iter->second;
+ ret_val = sp.GetJSON();
+ } else {
+ DisplayError("Error",("No parameter \""+key+"\" of correct type.").c_str());
+ }
+ return ret_val;
+}
+
+const ScriptParamMap & ScriptParams::GetParameterMap() const
+{
+ return parameter_map_;
+}
+
+void ScriptParams::SetParameterMap( const ScriptParamMap& spm )
+{
+ parameter_map_ = spm;
+}
+
+void ScriptParams::ASSetString( const std::string &key, const std::string& val ) {
+ parameter_map_[key].SetString(val);
+
+ if (Online::Instance()->IsActive()) {
+ Online::Instance()->SendStringScriptParam(obj_id, key, parameter_map_[key]);
+ }
+}
+
+std::ostream & operator<<(std::ostream & os, const ScriptParam & data) {
+ os << "float " << data.f_val_ << " int: " << data.i_val_ << " string: " << data.str_val_ << std::endl;
+ return os;
+}
+
+void ReadScriptParametersFromXML( ScriptParamMap &spm, const TiXmlElement* params )
+{
+ LOG_ASSERT(params);
+ const TiXmlElement* param = params->FirstChildElement("parameter");
+ while(param){
+ std::string name = param->Attribute("name");
+ std::string type_str = param->Attribute("type");
+ ScriptParam sp;
+ if(type_str == "int"){
+ int val;
+ param->QueryIntAttribute("val", &val);
+ sp.SetInt(val);
+ } else if(type_str == "float"){
+ float val = 0.0f;
+ param->QueryFloatAttribute("val", &val);
+ sp.SetFloat(val);
+ } else if(type_str == "string"){
+ sp.SetString(param->Attribute("val"));
+ } else if(type_str == "json"){
+ sp.SetJSONFromString(param->Attribute("val"));
+ }
+ spm[name] = sp;
+ param = param->NextSiblingElement();
+ }
+}
+
+void WriteScriptParamsToXML( const ScriptParamMap & pm, TiXmlElement* params )
+{
+ for(ScriptParamMap::const_iterator iter = pm.begin(); iter != pm.end(); ++iter){
+ TiXmlElement* param = new TiXmlElement("parameter");
+ param->SetAttribute("name",iter->first.c_str());
+ const ScriptParam &sp = iter->second;
+ sp.WriteToXML(param);
+ params->LinkEndChild(param);
+ }
+}
+
+void ReadScriptParametersFromRAM( ScriptParamMap &spm, const std::vector<char> &data )
+{
+ int index = 0;
+ int num_params;
+ memread(&num_params, sizeof(int), 1, data, index);
+ for(int i=0; i<num_params; ++i){
+ std::string name;
+ {
+ int str_len;
+ memread(&str_len, sizeof(int), 1, data, index);
+ name.resize(str_len);
+ memread(&name[0], sizeof(char), str_len, data, index);
+ }
+ ScriptParam sp;
+ sp.ReadFromRAM(data, index);
+ spm[name] = sp;
+ }
+}
+
+void WriteScriptParamsToRAM( const ScriptParamMap& spm, std::vector<char> &data )
+{
+ int num_params = spm.size();
+ memwrite(&num_params, sizeof(int), 1, data);
+ ScriptParamMap::const_iterator iter = spm.begin();
+ for(; iter != spm.end(); ++iter){
+ {
+ const std::string &str = iter->first;
+ int str_len = str.size();
+ memwrite(&str_len, sizeof(int), 1, data);
+ memwrite(&str[0], sizeof(char), str_len, data);
+ }
+ const ScriptParam& sp = iter->second;
+ sp.WriteToRAM(data);
+ }
+}
+
+const std::string& ScriptParam::GetString() const {
+ if(type_ != STRING && type_ != JSON ){
+ DisplayError("Error", "Calling GetString() on parameter of invalid type.");
+ }
+ return str_val_;
+}
+
+float ScriptParam::GetFloat() const {
+ switch(type_){
+ case FLOAT:
+ return f_val_;
+ case INT:
+ return (float)i_val_;
+ case STRING:
+ return (float)atof(str_val_.c_str());
+ default:
+ DisplayError("Error", "Calling GetFloat() on parameter of invalid type.");
+ return -1.0f;
+ }
+}
+
+int ScriptParam::GetInt() const {
+ switch(type_){
+ case FLOAT:
+ return (int)f_val_;
+ case INT:
+ return i_val_;
+ case STRING:
+ if(str_val_ == "true"){
+ return 1;
+ } else if(str_val_ == "false"){
+ return 0;
+ }
+ return atoi(str_val_.c_str());
+ default:
+ DisplayError("Error", "Calling GetInt() on parameter of invalid type.");
+ return -1;
+ }
+}
+
+void ScriptParam::SetInt( int val ) {
+ type_ = INT;
+ i_val_ = val;
+ editor_.SetTextField();
+}
+
+void ScriptParam::SetFloat( float val ) {
+ type_ = FLOAT;
+ f_val_ = val;
+ editor_.SetTextField();
+}
+
+void ScriptParam::SetString( const std::string &val ) {
+ type_ = STRING;
+ str_val_ = val;
+ editor_.SetTextField();
+}
+
+void ScriptParam::SetJSONFromString( const std::string &val ) {
+ type_ = JSON;
+ str_val_ = val;
+ editor_.SetCustomWindowLauncher();
+}
+
+void ScriptParam::SetJSON( SimpleJSONWrapper& JSONVal ) {
+ type_ = JSON;
+ str_val_ = JSONVal.writeString(false);
+ editor_.SetCustomWindowLauncher();
+}
+
+SimpleJSONWrapper ScriptParam::GetJSON() {
+ if(type_ != JSON ){
+ DisplayError("Error", "Calling GetJSON() on parameter of invalid type.");
+ }
+
+ SimpleJSONWrapper JSONVal;
+
+ if( JSONVal.parseString( str_val_ ) ) {
+ return JSONVal;
+ }
+ else {
+ DisplayError("Error", "Cannot parse JSON value.");
+ return JSONVal;
+ }
+}
+
+void ScriptParam::WriteToXML( TiXmlElement* param ) const {
+ switch(type_){
+ case INT:
+ param->SetAttribute("type", "int");
+ param->SetAttribute("val", i_val_);
+ break;
+ case FLOAT:
+ param->SetAttribute("type", "float");
+ param->SetDoubleAttribute("val", f_val_);
+ break;
+ case STRING:
+ param->SetAttribute("type", "string");
+ param->SetAttribute("val", str_val_.c_str());
+ break;
+ case JSON:
+ param->SetAttribute("type", "json");
+ param->SetAttribute("val", str_val_.c_str());
+ break;
+ case OTHER:
+ LOGW << "Got unhandled type OTHER" <<std::endl;
+ break;
+ }
+}
+
+void ScriptParam::ReadFromRAM( const std::vector<char> & data, int &index ) {
+ memread(&type_, sizeof(int), 1, data, index);
+ switch(type_){
+ case INT:
+ memread(&i_val_, sizeof(int), 1, data, index);
+ break;
+ case FLOAT:
+ memread(&f_val_, sizeof(float), 1, data, index);
+ break;
+ case STRING:
+ case JSON:
+ int str_len;
+ memread(&str_len, sizeof(int), 1, data, index);
+ str_val_.resize(str_len);
+ memread(&str_val_[0], sizeof(char), str_len, data, index);
+ break;
+ case OTHER:
+ LOGW << "Got unhandled type OTHER" <<std::endl;
+ break;
+
+ }
+ editor_.ReadFromRAM(data, index);
+}
+
+void ScriptParam::WriteToRAM( std::vector<char> & data ) const {
+ int int_type = type_;
+ memwrite(&int_type, sizeof(int), 1, data);
+ switch(int_type){
+ case INT:
+ memwrite(&i_val_, sizeof(int), 1, data);
+ break;
+ case FLOAT:
+ memwrite(&f_val_, sizeof(float), 1, data);
+ break;
+ case STRING:
+ case JSON:
+ int str_len = str_val_.size();
+ memwrite(&str_len, sizeof(int), 1, data);
+ memwrite(&str_val_[0], sizeof(char), str_len, data);
+ break;
+ }
+ editor_.WriteToRAM(data);
+}
+
+std::string ScriptParam::AsString() const {
+ std::ostringstream oss;
+ switch(type_){
+ case INT:
+ oss << i_val_;
+ break;
+ case FLOAT:
+ oss << f_val_;
+ break;
+ case STRING:
+ oss << "\"" << str_val_ << "\"";
+ break;
+ case JSON:
+ oss << str_val_;
+ break;
+ case OTHER:
+ LOGW << "Got unhandled type OTHER" <<std::endl;
+ break;
+ }
+ return oss.str();
+}
+
+std::string ScriptParam::GetStringForSocket() const {
+ return str_val_;
+}
+
+void ScriptParam::UpdateValuesFromSocket(const ScriptParam & data) {
+ this->f_val_ = data.f_val_;
+ this->str_val_ = data.str_val_;
+ this->type_ = data.type_;
+}
+
+ScriptParam::ScriptParam():
+ type_(OTHER), i_val_(0)
+{}
+
+bool ScriptParam::operator==( const ScriptParam& other ) const {
+ return ( type_ == other.type_ ) && ( i_val_ == other.i_val_ ) &&
+ ( f_val_ == other.f_val_ ) && ( str_val_ == other.str_val_ ) &&
+ ( editor_ == other.editor_ );
+}
+
+bool ScriptParam::operator!=( const ScriptParam& other ) const {
+ return !(*this == other);
+}
+
+void ScriptParamParts::Editor::SetTextField() {
+ type_ = ScriptParamEditorType::TEXTFIELD;
+}
+
+void ScriptParamParts::Editor::SetDisplaySlider( const std::string &details ) {
+ type_ = ScriptParamEditorType::DISPLAY_SLIDER;
+ details_ = details;
+}
+
+void ScriptParamParts::Editor::SetColorPicker( ) {
+ type_ = ScriptParamEditorType::COLOR_PICKER;
+}
+
+void ScriptParamParts::Editor::SetCheckbox() {
+ type_ = ScriptParamEditorType::CHECKBOX;
+}
+
+void ScriptParamParts::Editor::SetDropdown( const std::string &details ) {
+ type_ = ScriptParamEditorType::DROPDOWN_MENU;
+ details_ = details;
+}
+
+void ScriptParamParts::Editor::SetMultiSelect( const std::string &details ) {
+ type_ = ScriptParamEditorType::MULTI_SELECT;
+ details_ = details;
+}
+
+void ScriptParamParts::Editor::SetDetails(const std::string & details) {
+ this->details_ = details;
+}
+
+void ScriptParamParts::Editor::SetCustomWindowLauncher(){
+ type_ = ScriptParamEditorType::CUSTOM_WINDOW_LAUNCHER;
+}
+
+ScriptParamEditorType::Type ScriptParamParts::Editor::type() const {
+ return type_;
+}
+
+bool ScriptParamParts::Editor::operator==( const Editor& other ) const {
+ return ( type_ == other.type_ ) && ( details_ == other.details_ );
+}
+
+bool ScriptParamParts::Editor::operator!=( const Editor& other ) const {
+ return !(*this == other);
+}
+
+
+void ScriptParamParts::Editor::WriteToRAM( std::vector<char> & data ) const {
+ int int_type = type_;
+ memwrite(&int_type, sizeof(int), 1, data);
+ int str_len = details_.size();
+ memwrite(&str_len, sizeof(int), 1, data);
+ memwrite(&details_[0], sizeof(char), str_len, data);
+}
+
+void ScriptParamParts::Editor::ReadFromRAM( const std::vector<char> & data, int & index ) {
+ memread(&type_, sizeof(int), 1, data, index);
+ int str_len;
+ memread(&str_len, sizeof(int), 1, data, index);
+ details_.resize(str_len);
+ memread(&details_[0], sizeof(char), str_len, data, index);
+}
+
+
+bool testScriptParamsEqual( const ScriptParamMap& spmA, const ScriptParamMap& spmB ) {
+
+ //proceed to compare maps here
+ if(spmA.size() != spmB.size())
+ return false; // differing sizes, they are not the same
+
+ ScriptParamMap::const_iterator i, j;
+ for(i = spmA.begin(), j = spmB.begin(); i != spmA.end(); ++i, ++j)
+ {
+ if(*i != *j)
+ return false;
+ }
+
+ return true;
+
+}
diff --git a/Source/Scripting/scriptparams.h b/Source/Scripting/scriptparams.h
new file mode 100644
index 00000000..b62bd8b8
--- /dev/null
+++ b/Source/Scripting/scriptparams.h
@@ -0,0 +1,172 @@
+//-----------------------------------------------------------------------------
+// Name: scriptparams.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/integer.h>
+#include <JSON/jsonhelper.h>
+
+#include <map>
+#include <string>
+#include <vector>
+#include <ostream>
+#include <iostream>
+
+class TiXmlElement;
+
+namespace ScriptParamEditorType {
+ enum Type {
+ UNDEFINED,
+ TEXTFIELD,
+ DISPLAY_SLIDER,
+ COLOR_PICKER,
+ CHECKBOX,
+ DROPDOWN_MENU,
+ MULTI_SELECT,
+ CUSTOM_WINDOW_LAUNCHER
+ };
+}
+
+namespace ScriptParamParts {
+ class Editor {
+ public:
+ void SetTextField();
+ void SetDisplaySlider( const std::string &details );
+ void SetColorPicker( );
+ void SetCheckbox();
+ void SetCustomWindowLauncher();
+ void SetDropdown( const std::string &details );
+ ScriptParamEditorType::Type type() const;
+ Editor():type_(ScriptParamEditorType::UNDEFINED){}
+ void SetType(ScriptParamEditorType::Type type) { type_ = type; };
+ void WriteToRAM( std::vector<char> & data ) const;
+ void ReadFromRAM( const std::vector<char> & data, int & index );
+ const std::string& GetDetails() const {return details_;}
+ void SetMultiSelect( const std::string &details );
+ void SetDetails(const std::string& details);
+ bool operator==( const Editor& ) const;
+ bool operator!=( const Editor& ) const;
+
+
+ private:
+ ScriptParamEditorType::Type type_;
+ std::string details_;
+ };
+}
+
+class ScriptParam {
+public:
+ int GetInt() const;
+ void SetInt( int val );
+ float GetFloat() const;
+ void SetFloat( float default_val );
+ const std::string& GetString() const;
+ void SetString( const std::string &default_val );
+ void SetJSONFromString( const std::string &default_val );
+ void SetJSON( SimpleJSONWrapper &JSON );
+ SimpleJSONWrapper GetJSON();
+ void WriteToXML( TiXmlElement* params ) const;
+ void ReadFromRAM( const std::vector<char> & data, int &index );
+ void WriteToRAM( std::vector<char> & data ) const;
+ const ScriptParamParts::Editor& editor() const {return editor_;}
+ ScriptParamParts::Editor& editor() {return editor_;}
+ std::string AsString() const;
+ std::string GetStringForSocket() const;
+ void UpdateValuesFromSocket(const ScriptParam& data);
+ ScriptParam();
+
+ bool IsString() const { return type_ == STRING; }
+ bool IsFloat() const { return type_ == FLOAT; }
+ bool IsInt() const { return type_ == INT; }
+ bool IsJSON() const { return type_ == JSON; }
+ friend std::ostream& operator<<(std::ostream & os, const ScriptParam& data);
+ bool operator==( const ScriptParam& ) const;
+ bool operator!=( const ScriptParam& ) const;
+
+ enum ScriptParamType {
+ STRING,
+ FLOAT,
+ INT,
+ JSON,
+ OTHER
+ };
+private:
+
+ ScriptParamParts::Editor editor_;
+ ScriptParamType type_;
+ std::string str_val_;
+ union {
+ float f_val_;
+ int32_t i_val_;
+ };
+};
+
+typedef std::map<std::string, ScriptParam> ScriptParamMap;
+
+class ASContext;
+class ScriptParams {
+public:
+ const ScriptParamMap &GetParameterMap() const;
+ void SetParameterMap( const ScriptParamMap& spm );
+ const std::string& GetStringVal(const std::string &val) const;
+ const std::string& GetJSONValAsString(const std::string &val) const;
+ SimpleJSONWrapper GetJSONVal(const std::string &val);
+ static void RegisterScriptType( ASContext* context );
+ void RegisterScriptInstance( ASContext* context );
+ bool HasParam(const std::string &val) const;
+ bool IsParamString(const std::string &val) const;
+ bool IsParamFloat(const std::string &val) const;
+ bool IsParamInt(const std::string &val) const;
+ bool IsParamJSON(const std::string &val) const;
+ float ASGetFloat(const std::string &key);
+ int ASGetInt(const std::string &key);
+ void ASAddInt(const std::string &key, int default_val);
+ void ASAddFloat(const std::string &key, float default_val);
+ void ASAddString(const std::string &key, const std::string &default_val);
+ void InsertScriptParam(const std::string& key, ScriptParam sp);
+ void InsertNewScriptParam(const std::string& key, ScriptParam& param);
+ void ASAddJSONFromString(const std::string &key, const std::string& details);
+ void ASAddJSON(const std::string &key, SimpleJSONWrapper& details);
+ SimpleJSONWrapper ASGetJSON(const std::string &key);
+
+ void ASAddIntSlider(const std::string &key, int default_val, const std::string &details);
+ void ASAddIntCheckbox(const std::string &key, bool default_val);
+ void ASAddFloatSlider(const std::string &key, float default_val, const std::string &details);
+
+ void ASSetInt(const std::string &key, int num);
+ void ASSetFloat(const std::string &key, float num);
+ void ASSetString(const std::string& key, const std::string& val);
+ void ASRemove(const std::string &key);
+ void SetObjectID(uint32_t id);
+ uint32_t GetObjectID() const;
+ bool RenameParameterKey(const std::string& curr_name, const std::string& new_name);
+private:
+ uint32_t obj_id;
+ ScriptParamMap parameter_map_;
+};
+
+class TiXmlElement;
+void ReadScriptParametersFromXML( ScriptParamMap &spm, const TiXmlElement* params );
+void WriteScriptParamsToXML( const ScriptParamMap & pm, TiXmlElement* params );
+void ReadScriptParametersFromRAM( ScriptParamMap &spm, const std::vector<char> &data );
+void WriteScriptParamsToRAM( const ScriptParamMap& spm, std::vector<char> &data );
+bool testScriptParamsEqual( const ScriptParamMap& spmA, const ScriptParamMap& spmB );
diff --git a/Source/Sound/AudioFilters/limiter_audio_filter.cpp b/Source/Sound/AudioFilters/limiter_audio_filter.cpp
new file mode 100644
index 00000000..8750e8d2
--- /dev/null
+++ b/Source/Sound/AudioFilters/limiter_audio_filter.cpp
@@ -0,0 +1,126 @@
+//-----------------------------------------------------------------------------
+// Name: limiter_audio_filter.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "limiter_audio_filter.h"
+
+#include <Logging/logdata.h>
+
+#include <algorithm>
+#include <cmath>
+
+LimiterAudioFilter::LimiterAudioFilter(): target_limit_gain(1.0f), current_limit_gain(0.5f) {
+ Reset();
+}
+
+bool LimiterAudioFilter::Step( HighResBufferSegment* buffer ) {
+ int peak_limit = 30000;
+
+ int32_t peak = 0;
+ int32_t peak_limit_count = 0;
+
+ HighResBufferSegment in = *buffer;
+
+ buffer->data_size = next_f.data_size;
+ buffer->sample_rate = next_f.sample_rate;
+ buffer->channels = next_f.channels;
+
+ for( unsigned k = 0; k < in.FlatSampleCount(); k++ ) {
+ int32_t buf1;
+ char* buf1a = (char*)&buf1;
+
+ buf1a[0] = in.buf[k*4+0];
+ buf1a[1] = in.buf[k*4+1];
+ buf1a[2] = in.buf[k*4+2];
+ buf1a[3] = in.buf[k*4+3];
+
+ if( abs(buf1) > peak ) {
+ peak = abs(buf1);
+ }
+
+ if( abs(buf1) > peak_limit ) {
+ peak_limit_count++;
+ }
+ }
+
+ if(peak > peak_limit && peak_limit_count > 0 ) {
+ target_limit_gain = peak_limit/(float)peak;
+ frame_delay_limit = 3; //number of frames buffered
+ //LOGI << "Detected peaking, target_limit_gain:" << target_limit_gain << std::endl;
+ } else {
+ target_limit_gain = 1.0f;
+ }
+
+ for( unsigned k = 0; k < next_f.FlatSampleCount(); k++ ) {
+ int32_t buf1;
+ char* buf1a = (char*)&buf1;
+
+ if( (k % next_f.channels) == 0 ) {
+ if( target_limit_gain < current_limit_gain ) {
+ current_limit_gain -= 0.01f;
+ if( target_limit_gain > current_limit_gain ) {
+ current_limit_gain = target_limit_gain;
+ }
+ } else if( target_limit_gain > current_limit_gain ) {
+ if( frame_delay_limit <= 0 ) {
+ current_limit_gain += 0.005f;
+ if( target_limit_gain < current_limit_gain ) {
+ current_limit_gain = target_limit_gain;
+ }
+ }
+ }
+ }
+
+ buf1a[0] = next_f.buf[k*4+0];
+ buf1a[1] = next_f.buf[k*4+1];
+ buf1a[2] = next_f.buf[k*4+2];
+ buf1a[3] = next_f.buf[k*4+3];
+
+ buf1 = buf1 * (int32_t) current_limit_gain;
+
+ buffer->buf[k*4+0] = buf1a[0];
+ buffer->buf[k*4+1] = buf1a[1];
+ buffer->buf[k*4+2] = buf1a[2];
+ buffer->buf[k*4+3] = buf1a[3];
+ }
+
+ next1 = in;
+ next_f = next1;
+
+ if( frame_delay_limit > 0 ) {
+ frame_delay_limit--;
+ }
+
+ return true;
+}
+
+void LimiterAudioFilter::Reset() {
+ current_limit_gain = 0.5f;
+ target_limit_gain = 1.0f;
+
+ memset(next1.buf,0,HighResBufferSegment::BUF_SIZE);
+ next1.data_size = 0;
+ next1.channels = 0;
+
+ memset(next_f.buf,0,HighResBufferSegment::BUF_SIZE);
+ next_f.data_size = 0;
+ next_f.channels = 0;
+}
diff --git a/Source/Sound/AudioFilters/limiter_audio_filter.h b/Source/Sound/AudioFilters/limiter_audio_filter.h
new file mode 100644
index 00000000..02748a8b
--- /dev/null
+++ b/Source/Sound/AudioFilters/limiter_audio_filter.h
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------------
+// Name: limiter_audio_filter.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Sound/buffer_segment.h>
+#include <Sound/high_res_buffer_segment.h>
+
+
+class LimiterAudioFilter {
+private:
+ float current_limit_gain;
+ float target_limit_gain;
+ int frame_delay_limit;
+
+ HighResBufferSegment next1;
+ HighResBufferSegment next_f;
+public:
+ LimiterAudioFilter();
+
+ bool Step( HighResBufferSegment* buf );
+ void Reset();
+};
diff --git a/Source/Sound/AudioFilters/transition_mixer.cpp b/Source/Sound/AudioFilters/transition_mixer.cpp
new file mode 100644
index 00000000..19d2f97b
--- /dev/null
+++ b/Source/Sound/AudioFilters/transition_mixer.cpp
@@ -0,0 +1,141 @@
+//-----------------------------------------------------------------------------
+// Name: transition_mixer.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "transition_mixer.h"
+
+#include <Logging/logdata.h>
+#include <Utility/assert.h>
+
+#include <cmath>
+#include <algorithm>
+
+#define PI 3.141592653589793238462643383279502884197169399375105820974944592308
+
+TransitionMixer::TransitionMixer( TransitionType trans_type, float transition_length_seconds ) :
+transition_type( trans_type ),
+transition_length_sec( transition_length_seconds ),
+transition_position(0)
+{
+
+}
+
+bool TransitionMixer::Step( HighResBufferSegment* out, const HighResBufferSegment* first, const HighResBufferSegment* second )
+{
+ LOG_ASSERT( first->sample_rate == second->sample_rate );
+ LOG_ASSERT( first->channels == second->channels );
+
+ out->sample_rate = first->sample_rate;
+ out->channels = first->channels;
+ out->data_size = out->SampleSize() * std::max(first->FlatSampleCount(),second->FlatSampleCount());
+
+ size_t start = transition_position;
+ //How many bytes for a second of transition times the time of transition.
+ size_t end = (size_t) (transition_length_sec * first->sample_rate * first->channels);
+
+ size_t i = 0;
+
+ //This casting union is possible because we take endianess into account when we get the first from the underlying loader.
+ //If we use architecture inspecific endianess we have to bitshift this data instead.
+ //Also note that use of a union for aliasing is not strictly allowed until C99, but generally implemented and faster than shifting.
+ union casterunion
+ {
+ int32_t full;
+ char part[4];
+ };
+
+ casterunion out1;
+ casterunion buf1;
+ casterunion buf2;
+
+ //Crossfade the two firsts together.
+ for( i = 0; i < out->FlatSampleCount(); i++ )
+ {
+ float d = (float)(start+i)/(float)(end);
+ if( d > 1.0f ) {
+ d = 1.0f;
+ }
+
+ float dl;
+ float dr;
+ if( transition_type == Sinusoid )
+ {
+ //Crossfade in a sinusoid to preserve the energy.
+ float dp = d*(float)PI/2;
+ dl = sin(dp);
+ dr = cos(dp);
+ }
+ else if( transition_type == Linear )
+ {
+ dl = d;
+ dr = 1.0f-d;
+ }
+ else
+ {
+ LOGE << "No transition function used" << std::endl;
+
+ dl = 1.0f;
+ dr = 0.0f;
+ }
+
+ if( i < first->FlatSampleCount() ) {
+ buf1.part[0] = first->buf[i*4+0];
+ buf1.part[1] = first->buf[i*4+1];
+ buf1.part[2] = first->buf[i*4+2];
+ buf1.part[3] = first->buf[i*4+3];
+ } else {
+ buf1.full = 0;
+ }
+
+ if( i < second->FlatSampleCount() ) {
+ buf2.part[0] = second->buf[i*4+0];
+ buf2.part[1] = second->buf[i*4+1];
+ buf2.part[2] = second->buf[i*4+2];
+ buf2.part[3] = second->buf[i*4+3];
+ } else {
+ buf2.full = 0;
+ }
+
+ out1.full = (int32_t) (buf1.full*dr+buf2.full*dl);
+
+ out->buf[i*4+0] = out1.part[0];
+ out->buf[i*4+1] = out1.part[1];
+ out->buf[i*4+2] = out1.part[2];
+ out->buf[i*4+3] = out1.part[3];
+ }
+
+ //If it happened that the second first had more data than the first, indicate that
+ // we continued writing into the main first so we don't miss that segment of audio
+ // that was streamed.
+ transition_position += i;
+
+ if( transition_position >= end )
+ {
+ transition_position = end;
+ return true;
+ }
+ return false;
+}
+
+void TransitionMixer::Reset()
+{
+ transition_position = 0;
+}
diff --git a/Source/Sound/AudioFilters/transition_mixer.h b/Source/Sound/AudioFilters/transition_mixer.h
new file mode 100644
index 00000000..cdb74067
--- /dev/null
+++ b/Source/Sound/AudioFilters/transition_mixer.h
@@ -0,0 +1,46 @@
+//-----------------------------------------------------------------------------
+// Name: transition_mixer.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Sound/buffer_segment.h>
+#include <Sound/high_res_buffer_segment.h>
+
+
+class TransitionMixer
+{
+public:
+ enum TransitionType
+ {
+ Linear,
+ Sinusoid
+ };
+private:
+ TransitionType transition_type;
+ float transition_length_sec;
+ size_t transition_position;
+public:
+ TransitionMixer( TransitionType trans_type, float transition_length_seconds );
+
+ bool Step( HighResBufferSegment* out, const HighResBufferSegment* first, const HighResBufferSegment* second );
+ void Reset();
+};
diff --git a/Source/Sound/Loader/base_loader.cpp b/Source/Sound/Loader/base_loader.cpp
new file mode 100644
index 00000000..2ec5ff33
--- /dev/null
+++ b/Source/Sound/Loader/base_loader.cpp
@@ -0,0 +1,28 @@
+//-----------------------------------------------------------------------------
+// Name: base_loader.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "base_loader.h"
+
+baseLoader::~baseLoader()
+{
+
+}
diff --git a/Source/Sound/Loader/base_loader.h b/Source/Sound/Loader/base_loader.h
new file mode 100644
index 00000000..7b245c34
--- /dev/null
+++ b/Source/Sound/Loader/base_loader.h
@@ -0,0 +1,45 @@
+//-----------------------------------------------------------------------------
+// Name: base_loader.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/referencecounter.h>
+#include <Internal/integer.h>
+
+class baseLoader
+{
+public:
+ virtual ~baseLoader();
+
+ //If two channels, PCM data is interleaved left then right.
+ virtual int stream_buffer_int16(char *buffer, int size) = 0;
+ virtual unsigned long get_sample_count() = 0;
+ virtual unsigned long get_channels() = 0;
+ virtual int get_sample_rate() = 0;
+ virtual int rewind() = 0;
+ virtual bool is_at_end() = 0;
+
+ virtual int64_t get_pcm_pos() = 0;
+ virtual void set_pcm_pos( int64_t pos ) = 0;
+};
+
+typedef ReferenceCounter<baseLoader> rc_baseLoader;
diff --git a/Source/Sound/Loader/ogg_loader.cpp b/Source/Sound/Loader/ogg_loader.cpp
new file mode 100644
index 00000000..3c283438
--- /dev/null
+++ b/Source/Sound/Loader/ogg_loader.cpp
@@ -0,0 +1,168 @@
+//-----------------------------------------------------------------------------
+// Name: ogg_loader.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include <Internal/error.h>
+#include <Internal/filesystem.h>
+
+#include <Sound/Loader/ogg_loader.h>
+#include <Compat/fileio.h>
+#include <Logging/logdata.h>
+
+#include <cstring>
+#include <cerrno>
+
+#ifdef WIN32
+static int _fseek64_wrap(FILE *f,ogg_int64_t off,int whence){
+ if(f==NULL)return(-1);
+ return fseek(f,(long)off,whence);
+}
+
+static size_t do_read(void *a, size_t b, size_t c, void *d)
+{
+ return fread(a, b, c, (FILE *)d);
+}
+
+#endif
+
+oggLoader::oggLoader(Path path) :
+ m_ogg_file(NULL),
+ m_vorbis_info(NULL),
+ m_vorbis_comment(NULL)
+{
+ int result;
+
+ ended = false;
+
+ if(!(m_ogg_file = my_fopen(path.GetFullPath(), "rb"))) {
+ DisplayError("Error",("Could not open file: " + path.GetFullPathStr() + " " + strerror(errno) ).c_str());
+ ended = true;
+ return;
+ }
+
+
+#ifdef WIN32
+ ov_callbacks callbacks = {
+ (size_t (*)(void *, size_t, size_t, void *)) fread,
+ (int (*)(void *, ogg_int64_t, int)) _fseek64_wrap,
+ (int (*)(void *)) fclose,
+ (long (*)(void *)) ftell};
+
+ if((result = ov_open_callbacks(m_ogg_file, &m_ogg_stream, NULL, 0, callbacks)) < 0)
+#else
+ if((result = ov_open(m_ogg_file, &m_ogg_stream, NULL, 0)) < 0)
+#endif
+ {
+ DisplayError("Error","Problem with ogg streaming (ov_open failed)");
+ // only close file pointers if ov_open fails.
+ fclose(m_ogg_file);
+ m_ogg_file = NULL;
+ ended = true;
+ return;
+ }
+
+ m_vorbis_info = ov_info(&m_ogg_stream, -1);
+ m_vorbis_comment = ov_comment(&m_ogg_stream, -1);
+}
+
+oggLoader::~oggLoader()
+{
+ if (m_ogg_file != NULL)
+ ov_clear(&m_ogg_stream);
+}
+
+int oggLoader::stream_buffer_int16(char *buffer, int size)
+{
+ // ehh, error condition...
+ if (m_vorbis_info == NULL)
+ {
+ ::memset(buffer, 0, size);
+ return 0;
+ }
+
+ int section;
+
+#ifdef __BIG_ENDIAN__
+ int bigendianp = 1;
+#else
+ int bigendianp = 0;
+#endif
+
+ int length = ov_read(&m_ogg_stream, buffer, size, bigendianp, 2, 1, &section);
+ if( length == 0 )
+ ended = true;
+ return length;
+}
+
+unsigned long oggLoader::get_sample_count()
+{
+ if (m_ogg_file != NULL)
+ return (unsigned long)ov_pcm_total(&m_ogg_stream, 0);
+ else
+ return 0;
+}
+
+unsigned long oggLoader::get_channels()
+{
+ if (m_vorbis_info)
+ return m_vorbis_info->channels;
+ else
+ return 0;
+}
+
+int oggLoader::get_sample_rate()
+{
+ if (m_vorbis_info)
+ return m_vorbis_info->rate;
+ else
+ return 0;
+}
+
+int oggLoader::rewind()
+{
+ if (m_ogg_file != NULL)
+ {
+ ended = false;
+ return ov_raw_seek_lap(&m_ogg_stream, 0);
+ }
+
+ return -1;
+}
+
+bool oggLoader::is_at_end()
+{
+ return ended;
+}
+
+int64_t oggLoader::get_pcm_pos()
+{
+ return ov_pcm_tell( &m_ogg_stream );
+}
+
+void oggLoader::set_pcm_pos( int64_t pos )
+{
+ int ret = ov_pcm_seek( &m_ogg_stream, pos );
+ if( ret != 0 )
+ {
+ LOGE << "Error seeking in ogg stream " << std::endl;
+ }
+}
diff --git a/Source/Sound/Loader/ogg_loader.h b/Source/Sound/Loader/ogg_loader.h
new file mode 100644
index 00000000..41ceecf8
--- /dev/null
+++ b/Source/Sound/Loader/ogg_loader.h
@@ -0,0 +1,61 @@
+//-----------------------------------------------------------------------------
+// Name: ogg_loader.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+#include <Sound/Loader/base_loader.h>
+
+#include <Wrappers/vorbis.h>
+#include <Internal/filesystem.h>
+
+#ifdef __APPLE__
+#include <OpenAL/al.h>
+#else
+#include <al.h>
+#endif
+
+#include <string>
+
+class oggLoader : public baseLoader
+{
+private:
+ FILE* m_ogg_file;
+ OggVorbis_File m_ogg_stream;
+ vorbis_info* m_vorbis_info;
+ vorbis_comment* m_vorbis_comment;
+ std::string m_path;
+ unsigned long m_channels;
+ bool ended;
+
+public:
+ oggLoader(Path rel_path);
+ virtual ~oggLoader();
+
+ virtual int stream_buffer_int16(char *buffer, int size);
+ virtual unsigned long get_sample_count();
+ virtual unsigned long get_channels();
+ virtual int get_sample_rate();
+ virtual int rewind();
+ virtual bool is_at_end();
+
+ virtual int64_t get_pcm_pos();
+ virtual void set_pcm_pos( int64_t pos );
+};
diff --git a/Source/Sound/Loader/void_loader.cpp b/Source/Sound/Loader/void_loader.cpp
new file mode 100644
index 00000000..1e7b87a9
--- /dev/null
+++ b/Source/Sound/Loader/void_loader.cpp
@@ -0,0 +1,76 @@
+//-----------------------------------------------------------------------------
+// Name: void_loader.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "void_loader.h"
+
+#include <cstring>
+
+voidLoader::voidLoader()
+{
+}
+
+voidLoader::~voidLoader()
+{
+
+}
+
+int voidLoader::stream_buffer_int16( char* buffer, int size )
+{
+ ::memset(buffer, 0, size);
+ return size;
+}
+
+unsigned long voidLoader::get_sample_count()
+{
+ return 22000;
+}
+
+unsigned long voidLoader::get_channels()
+{
+ return 2;
+}
+
+int voidLoader::get_sample_rate()
+{
+ return 44100;
+}
+
+int voidLoader::rewind()
+{
+ return 0;
+}
+
+bool voidLoader::is_at_end()
+{
+ return true;
+}
+
+int64_t voidLoader::get_pcm_pos()
+{
+ return 0;
+}
+
+void voidLoader::set_pcm_pos( int64_t pos )
+{
+
+}
+
diff --git a/Source/Sound/Loader/void_loader.h b/Source/Sound/Loader/void_loader.h
new file mode 100644
index 00000000..1c964ba5
--- /dev/null
+++ b/Source/Sound/Loader/void_loader.h
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------------
+// Name: void_loader.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include "base_loader.h"
+
+class voidLoader : public baseLoader
+{
+public:
+ voidLoader();
+ virtual ~voidLoader();
+
+ virtual int stream_buffer_int16(char *buffer, int size);
+ virtual unsigned long get_sample_count();
+ virtual unsigned long get_channels();
+ virtual int get_sample_rate();
+ virtual int rewind();
+ virtual bool is_at_end();
+
+ virtual int64_t get_pcm_pos();
+ virtual void set_pcm_pos( int64_t pos );
+};
diff --git a/Source/Sound/al_audio.cpp b/Source/Sound/al_audio.cpp
new file mode 100644
index 00000000..f494994d
--- /dev/null
+++ b/Source/Sound/al_audio.cpp
@@ -0,0 +1,1610 @@
+//-----------------------------------------------------------------------------
+// Name: al_audio.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "al_audio.h"
+
+#include <Sound/soundlogging.h>
+#include <Sound/Loader/ogg_loader.h>
+
+#include <Internal/error.h>
+#include <Internal/timer.h>
+#include <Internal/common.h>
+#include <Internal/filesystem.h>
+
+#include <Compat/fileio.h>
+#include <Memory/allocation.h>
+#include <Math/vec3math.h>
+#include <Threading/sdl_wrapper.h>
+#include <Utility/strings.h>
+
+#ifdef WIN32
+#define NOMINMAX
+#include <windows.h>
+#endif
+
+#ifndef WIN32
+#include <unistd.h>
+#endif
+
+#include <memory.h>
+#include <cstdio>
+#include <cstdlib>
+#include <cstdint>
+#include <cmath>
+#include <cerrno>
+
+
+/*******************************************************************
+ staticEmitter class - internal class for playing sounds.
+*******************************************************************/
+class StaticEmitter : public AudioEmitter {
+public:
+ StaticEmitter(bool loop, const vec3 &position, float volume, float pitch_mul = 1.0f, uint8_t flags = 0, float _max_distance = 100.0f, unsigned char _priority = _default_priority) :
+ m_looping(loop),
+ m_position(position),
+ m_velocity(0.0f),
+ m_volume(volume),
+ m_pitch_mul(pitch_mul),
+ m_volume_mult(1.0),
+ m_max_distance(_max_distance),
+ m_occlusion_position(position),
+ m_priority(_priority)
+ {flags_ = flags;}
+
+ ~StaticEmitter()
+ {}
+
+ virtual bool KeepPlaying() {
+ return m_looping?false:true;
+ }
+
+ virtual bool GetPosition(vec3 &p) {
+ p = m_position;
+ return ((flags_ & SoundFlags::kRelative) != 0);
+ }
+
+ virtual const vec3 GetPosition() {
+ return m_position;
+ }
+
+ virtual const vec3 GetOcclusionPosition() {
+ return m_occlusion_position;
+ }
+
+ virtual float GetMaxDistance() {
+ return m_max_distance;
+ }
+
+ virtual void GetDirection(vec3 &p) {
+ p.x() = 0.0f; p.y() = 0.0f; p.z() = 0.0f;
+ return;
+ }
+
+ virtual const vec3& GetVelocity() {
+ return m_velocity;
+ }
+
+ virtual float GetVolume() {
+ return m_volume * m_volume_mult;
+ }
+
+ virtual float GetPitchMultiplier() {
+ return m_pitch_mul;
+ }
+
+ virtual void SetPitchMultiplier(float mul) {
+ m_pitch_mul = mul;
+ }
+
+ virtual void SetVolume(float mul) {
+ if( mul > 1.0f )
+ mul = 1.0f;
+ if( mul < 0.0f )
+ mul = 0.0f;
+
+ m_volume = mul;
+ }
+
+ virtual void SetVolumeMult(float mul) {
+ m_volume_mult = mul;
+ }
+
+ virtual void SetPosition(const vec3 &pos) {
+ m_position = pos;
+ }
+
+ virtual void SetOcclusionPosition(const vec3 &pos) {
+ m_occlusion_position = pos;
+ }
+
+ virtual void SetVelocity(const vec3 &vel) {
+ m_velocity = vel;
+ }
+
+ virtual void Unsubscribe() {
+ // should call the audioEmitter destructor, all should be cleaned.
+ delete this;
+ }
+
+ virtual unsigned char GetPriority() {
+ return m_priority;
+ }
+
+private:
+ bool m_looping;
+ vec3 m_position;
+ vec3 m_velocity;
+ float m_volume;
+ float m_pitch_mul;
+ float m_volume_mult;
+ float m_max_distance;
+ vec3 m_occlusion_position;
+ unsigned char m_priority;
+};
+
+void AudioEmitter::link(alAudio &a)
+{
+ m_audio = &a;
+}
+
+void AudioEmitter::Unsubscribe()
+{
+ if (m_audio)
+ {
+ m_audio->unsubscribe(*this);
+ m_audio = NULL;
+ }
+}
+
+AudioEmitter::~AudioEmitter()
+{
+ if (m_audio)
+ {
+ m_audio->unsubscribe(*this);
+ m_audio = NULL;
+ }
+}
+
+uint8_t AudioEmitter::flags() {
+ return flags_;
+}
+
+int AudioEmitter::uid_counter = 1;
+
+
+/*******************************************************************
+ audioStreamer class - base interface for stream subscribers
+*******************************************************************/
+audioStreamer::audioStreamer()
+{
+}
+
+audioStreamer::~audioStreamer()
+{
+}
+
+alAudio::alAudio(const char* preferred_device, float volume, float reference_distance) :
+ m_device(NULL),
+ m_context(NULL),
+ priority_levels(0),
+ m_handle_ctr(0),
+ m_reference_distance(reference_distance),
+ m_gain(volume),
+ m_pitch(1.0f)
+{
+ //Sending NULL to alcOpenDevice will result in fallback to default.
+ const char* open_device_name = NULL;
+
+ if( preferred_device == NULL ) {
+ preferred_device = "";
+ }
+
+ this->preferred_device = std::string(preferred_device);
+
+ if( alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT") == AL_TRUE ) {
+ LOGI << "ALC_ENUMERATION_EXT is available" << std::endl;
+
+ const char* device_list = alcGetString(NULL, ALC_DEVICE_SPECIFIER);
+
+ size_t cur_d_start = 0;
+ if( strlen(preferred_device) > 0 ) {
+ LOGI << "Trying to open preferred device: " << preferred_device << std::endl;
+ }
+
+ LOGI << "=== Available sound devices ===" << std::endl;
+ while( device_list ) {
+ if( device_list[cur_d_start] == '\0' ) {
+ device_list = NULL;
+ } else {
+ LOGI << &device_list[cur_d_start] << std::endl;
+ available_device_list.push_back(std::string(&device_list[cur_d_start]));
+ if(strmtch(preferred_device, &device_list[cur_d_start])) {
+ open_device_name = &device_list[cur_d_start];
+ }
+ cur_d_start += strlen(&device_list[cur_d_start]) + 1;
+ }
+ }
+ LOGI << "=== ===" << std::endl;
+
+ if( open_device_name == NULL ) {
+ open_device_name = alcGetString( NULL, ALC_DEFAULT_DEVICE_SPECIFIER );
+ LOGI << "Opening default audio device: " << open_device_name << "." << std::endl;
+ } else {
+ LOGI << "Opening preferred audio device: " << open_device_name << "." << std::endl;
+ }
+
+ used_device = std::string(open_device_name);
+ } else {
+ LOGI << "ALC_ENUMERATION_EXT is not available, will open default device." << std::endl;
+ }
+
+ m_device = alcOpenDevice( open_device_name );
+
+ if( m_device )
+ {
+ const char* actual_device = alcGetString(m_device,ALC_DEVICE_SPECIFIER);
+
+ if( actual_device ) {
+ LOGI << "Opened and using audio device: " << actual_device << std::endl;
+ }
+
+ m_context = alcCreateContext( m_device, NULL );
+
+ if( m_context )
+ {
+ alcMakeContextCurrent( m_context );
+ alGetError(); // Clear Error Code
+
+ reset_listener();
+
+ const unsigned long _ideal_max_sources = 32;
+
+ for( unsigned i = 0; i < _ideal_max_sources; i++ )
+ {
+ rc_alAudioSource as;
+ as->Allocate();
+
+ if( as->IsValid() )
+ {
+ m_sources.push_back(as);
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ LOGI << "We have " << m_sources.size() << " audio sources available." << std::endl;
+ }
+ else
+ {
+ LOGF << "Failed to load audio context, this means no sound, and probably crashing." << alcErrString(alcGetError(m_device)) << std::endl;
+ }
+ }
+ else
+ {
+ LOGF << "Failed to load audio device, this means no sound, and probably crashing." << std::endl;
+ }
+}
+
+void alAudio::Dispose() {
+
+ m_sources.clear();
+
+ m_buffers.clear();
+
+ // unsubscribe to all the audioEmitters,
+ // this will also allow the staticEmitters that are still running to be deleted
+ streamer_subscribers::iterator ssi;
+ for (ssi = m_streamers.begin(); ssi != m_streamers.end(); ++ssi)
+ {
+ if (!ssi->second->get_discard())
+ ssi->second->get_audioEmitter()->Unsubscribe();
+ }
+
+ for (ssi = m_streamers.begin(); ssi != m_streamers.end(); ++ssi)
+ {
+ delete ssi->second;
+ }
+
+ alcDestroyContext( m_context );
+
+ ALCenum err = alcGetError( m_device );
+ m_context = NULL;
+ if( err == ALC_NO_ERROR )
+ {
+ if( alcCloseDevice( m_device ) == false )
+ {
+ LOGE << "Unable to shut down alc device: \"" << alcErrString(err) << "\"" << std::endl;
+ }
+
+ m_device = NULL;
+ }
+ else
+ {
+ LOGE << "Error shutting down alc context: \"" << alcErrString(err) << "\"" << std::endl;
+ }
+}
+
+void alAudio::reset_listener()
+{
+ listener_pos = vec3(0.0f);
+ ALfloat listenerPos[]={0.0f,0.0f,0.0f};
+ ALfloat listenerVel[]={0.0f,0.0f,0.0f};
+ ALfloat listenerOri[]={0.0f,0.0f,-1.0f,0.0f,1.0f,0.0f}; // at, then up
+ // Set Listener attributes
+ alListenerfv(AL_POSITION,listenerPos);
+ alListenerfv(AL_VELOCITY,listenerVel);
+ alListenerfv(AL_ORIENTATION,listenerOri);
+}
+
+void alAudio::set_listener_position(vec3 &position)
+{
+ listener_pos = position;
+ alListener3f(AL_POSITION, position.x(), position.y(), position.z());
+}
+
+void alAudio::set_listener_orientation(vec3 &facing, vec3 &up)
+{
+ ALfloat listenerOri[6];
+ listenerOri[0] = facing.x();
+ listenerOri[1] = facing.y();
+ listenerOri[2] = facing.z();
+ listenerOri[3] = up.x();
+ listenerOri[4] = up.y();
+ listenerOri[5] = up.z();
+
+ alListenerfv(AL_ORIENTATION, listenerOri);
+}
+
+void alAudio::set_listener_velocity(vec3 &velocity)
+{
+ alListener3f(AL_VELOCITY, velocity.x(), velocity.y(), velocity.z());
+}
+
+void alAudio::update(float timestep)
+{
+
+ update_subscribers(timestep);
+
+ //Disabling because this system is now runnign in separate thread and needs to be fully contained.
+ //TODO: This rendering has to be moved to an external site.
+ /*
+ if( config["visible_sound_spheres"].toBool() )
+ {
+ static_handles::iterator iter = m_handles.begin();
+ for(; iter != m_handles.end(); ++iter){
+ DebugDraw::Instance()->AddWireSphere(iter->second->GetPosition(),
+ iter->second->GetMaxDistance()*0.4f, vec4(1.0f), _delete_on_update);
+ }
+ }
+ */
+
+}
+
+void alAudio::subscribe(audioStreamer &streamer)
+{
+ rc_alAudioSource source;
+
+ if (m_sources.empty())
+ {
+ //printf("Implement sound prioritization");
+ source = find_free_source();
+ }
+ else
+ {
+ source = m_sources.front();
+ m_sources.pop_front();
+ }
+
+ if( source->IsValid() )
+ {
+ streamer.link(*this);
+
+ // set current values in source
+ source->Sourcef(AL_REFERENCE_DISTANCE, m_reference_distance);
+ source->Sourcef(AL_GAIN, m_gain * streamer.GetVolume());
+ source->Sourcef(AL_PITCH, m_pitch * streamer.GetPitchMultiplier());
+ source->Sourcef(AL_MIN_GAIN, 0.0f);
+ source->Sourcef(AL_MAX_GAIN, 1.0f);
+
+ streamerLink *link = new streamerLink(source, streamer);
+ m_streamers.insert(streamer_subscribers::value_type(&streamer, link));
+ }
+}
+
+void alAudio::unsubscribe(AudioEmitter &streamer)
+{
+ const unsigned long &handle = streamer.handle;
+ m_handles.erase(handle);
+ //printf("Erasing %u from handles\n", handle);
+
+ streamer_subscribers::iterator it = m_streamers.find(&streamer);
+
+ if (it != m_streamers.end())
+ {
+ it->second->signal_discard();
+ }
+}
+
+
+void alAudio::update_subscribers(float timestep) {
+ unsigned int current_tick = SDL_TS_GetTicks();
+ streamer_subscribers::iterator it = m_streamers.begin();
+ while(it != m_streamers.end()) {
+ if (it->second->get_discard()){
+ ++it;
+ continue;
+ }
+
+ // set current values in source
+ it->second->get_source()->Sourcef(AL_REFERENCE_DISTANCE, m_reference_distance);
+ it->second->get_source()->Sourcef(AL_GAIN, m_gain * it->second->get_audioEmitter()->GetVolume());
+ it->second->get_source()->Sourcef(AL_PITCH, m_pitch * it->second->get_audioEmitter()->GetPitchMultiplier());
+ const vec3 &vel = it->second->get_audioEmitter()->GetVelocity();
+ it->second->get_source()->Source3f(AL_VELOCITY, vel[0], vel[1], vel[2]);
+ //alSourcef(it->second->get_source(), AL_MIN_GAIN, 0.0f);
+ //alSourcef(it->second->get_source(), AL_MAX_GAIN, 1.0f);
+
+ it->second->update(timestep,current_tick);
+
+ ++it;
+ }
+
+ // sweep and remove discarded members
+ it = m_streamers.begin();
+ while(it != m_streamers.end())
+ {
+ if (it->second->get_discard())
+ {
+ it->second->get_source()->Stop();
+ m_sources.push_front(it->second->get_source());
+
+ delete it->second;
+ // verify
+ m_streamers.erase(it++);
+ }
+ else
+ {
+ ++it;
+ }
+ }
+}
+
+bool alAudio::load(const std::string& rel_path, const FilterInfo &filter_info) {
+ // prexisting file found, don't reload
+ if (m_buffers.end() != m_buffers.find(std::pair<std::string, std::string>(rel_path, filter_info.path))) {
+ return true;
+ }
+
+ if (rel_path[rel_path.length() - 1] == 'g' || rel_path[rel_path.length() - 1] == 'G')
+ return load_ogg(rel_path, filter_info);
+ else
+ return load_wav(rel_path, filter_info);
+}
+
+bool alAudio::load_ogg(const std::string& rel_path, const FilterInfo &filter_info)
+{
+ bool retval = true;
+
+ Path abs_path = FindFilePath( rel_path.c_str(), kAnyPath );
+
+ oggLoader ol(abs_path);
+
+ char *data = new char[ol.get_sample_count() * 2];
+
+ int max_size = ol.get_sample_count() * 2;
+ int size = 0;
+ int result = 0;
+ while(size < max_size)
+ {
+ result = ol.stream_buffer_int16(data + size, max_size);
+ if (result <= 0) {
+ delete [] data;
+ return false;
+ }
+
+ size += result;
+ }
+
+ ALenum format;
+
+ if(ol.get_channels() == 1)
+ format = AL_FORMAT_MONO16;
+ else
+ format = AL_FORMAT_STEREO16;
+
+ if (format != AL_FORMAT_MONO16)
+ {
+ LOGW << "Stereo format handed to a mono reader" << std::endl;
+ retval = false;
+ }
+
+ if (retval)
+ {
+ rc_alAudioBuffer buffer;
+ buffer->Allocate();
+
+ // Copy ogg data into AL Buffer
+ buffer->BufferData( format, data, ol.get_sample_count() * 2, ol.get_sample_rate());
+
+ if (AL_NO_ERROR == alGetError())
+ {
+ m_buffers.insert(buffer_map::value_type(
+ std::pair<std::string, std::string>(rel_path, filter_info.path),
+ buffer));
+ }
+ else
+ {
+ LOGE << "Failed to buffer data" << std::endl;
+ retval = false;
+ }
+ }
+
+ delete [] data;
+
+ return retval;
+}
+
+void LowPassFilter(ALenum format, ALvoid* data, ALsizei size) {
+ unsigned bytes_per_channel_sample = 1;
+ if(format == AL_FORMAT_MONO16 || format == AL_FORMAT_STEREO16){
+ bytes_per_channel_sample = 2;
+ }
+
+ unsigned channels = 1;
+ if(format == AL_FORMAT_STEREO8 || format == AL_FORMAT_STEREO16){
+ channels = 2;
+ }
+
+ if(bytes_per_channel_sample == 2 && channels == 1){
+ int16_t* data_dbyte = (int16_t*)data;
+ for(int i=0; i<size/2-3; i++){
+ data_dbyte[i] = data_dbyte[i]/4 + data_dbyte[i+1]/4 + data_dbyte[i+2]/4 + data_dbyte[i+3]/4;
+ }
+ }
+
+ if(bytes_per_channel_sample == 2 && channels == 2){
+ int16_t* data_dbyte = (int16_t*)data;
+ for(int i=0; i<size/2-6; i++){
+ data_dbyte[i] = data_dbyte[i]/4 + data_dbyte[i+2]/4 + data_dbyte[i+4]/4 + data_dbyte[i+6]/4;
+ }
+ }
+}
+
+bool ParseWAV(const char* file_data, ALenum &format, ALvoid* &data, ALsizei &size, ALsizei &freq)
+{
+ const char* data_index = &file_data[0];
+ char xbuffer[5];
+ xbuffer[4] = '\0';
+ memcpy(xbuffer, data_index, 4);
+ data_index += 4;
+ if(strcmp(xbuffer, "RIFF") != 0){
+ LOGE << "WAV file has invalid header" << std::endl;
+ return false;
+ }
+ data_index += 4;
+ memcpy(xbuffer, data_index, 4);
+ data_index += 4;
+ if(strcmp(xbuffer, "WAVE") != 0){
+ LOGE << "WAV file has invalid header" << std::endl;
+ return false;
+ }
+ bool found_format_chunk = false;
+ do {
+ memcpy(xbuffer, data_index, 4);
+ data_index += 4;
+ if(strcmp(xbuffer, "fmt ") == 0){
+ found_format_chunk = true;
+ } else {
+ int32_t chunk_size = *((int32_t*)data_index);
+ data_index += 4 + chunk_size;
+ }
+ } while(!found_format_chunk);
+
+ data_index += 4;
+ int16_t audioFormat = *((int16_t*)data_index);
+ data_index += 2;
+ int16_t channels = *((int16_t*)data_index);
+ data_index += 2;
+ int32_t sampleRate = *((int32_t*)data_index);
+ data_index += 4;
+ //int32_t byteRate = *((int32_t*)data_index);
+ data_index += 6;
+ int16_t bitsPerSample = *((int16_t*)data_index);
+ data_index += 2;
+
+ if (audioFormat != 1) {
+ // Note: This early exit was added because the original code assumed there'd be a 16bit int here.
+ // In the case of an example 32 bit float file, audioFormat == 3, this was a "fact" chunk instead,
+ // and it would convert "fa" to an int16, and then skip 24+kb and hit an access violation
+ LOGE << "WAV file has unsupported non-PCM format" << std::endl;
+ return false;
+ }
+
+ bool found_data_chunk = false;
+ do {
+ memcpy(xbuffer, data_index, 4);
+ data_index += 4;
+ if(strcmp(xbuffer, "data") == 0){
+ found_data_chunk = true;
+ } else {
+ int32_t chunk_size = *((int32_t*)data_index);
+ data_index += 4 + chunk_size;
+ }
+ } while(!found_data_chunk);
+
+ int32_t dataChunkSize = *((int32_t*)data_index);
+ data_index += 4;
+
+ if(channels == 1 && bitsPerSample == 8){
+ format = AL_FORMAT_MONO8;
+ } else if(channels == 2 && bitsPerSample == 8){
+ format = AL_FORMAT_STEREO8;
+ } else if(channels == 1 && bitsPerSample == 16){
+ format = AL_FORMAT_MONO16;
+ } else if(channels == 2 && bitsPerSample == 16){
+ format = AL_FORMAT_STEREO16;
+ } else {
+ LOGE << "WAV file has unknown format" << std::endl;
+ return false;
+ }
+
+ freq = sampleRate;
+ data = OG_MALLOC(dataChunkSize);
+ size = dataChunkSize;
+ memcpy(data, data_index, dataChunkSize);
+
+ return true;
+}
+
+bool GenerateEmptyData(ALenum &format, ALvoid* &data, ALsizei &size, ALsizei &freq) {
+ freq = 44100;
+ format = AL_FORMAT_MONO8;
+ size = freq*5;
+ data = OG_MALLOC(size);
+ memset(data, 0, size);
+ return true;
+}
+
+bool LoadWAVFromFile(const char* file_path, ALenum &format, ALvoid* &data, ALsizei &size, ALsizei &freq) {
+ FILE* file = my_fopen(file_path, "rb");
+ if(!file){
+ LOGE << "Could not open WAV file (fopen): " << file_path << " " << strerror(errno) << std::endl;
+ return false;
+ }
+
+ fseek (file, 0, SEEK_END);
+ int file_size = ftell(file);
+ rewind (file);
+
+ void* mem = OG_MALLOC(file_size);
+ if(!mem){
+ LOGE << "Could not allocate memory for file: " << file_path << std::endl;
+ fclose(file);
+ return false;
+ }
+ fread(mem, 1, file_size, file);
+ fclose(file);
+
+ bool ret = ParseWAV((char*)mem, format, data, size, freq);
+
+ OG_FREE(mem);
+ return ret;
+}
+
+bool alAudio::load_wav(const std::string& rel_path, const FilterInfo &filter_info) {
+ bool retval = true;
+
+ ALint error;
+ ALsizei size,freq;
+ ALenum format;
+ ALvoid *data = NULL;
+
+ char abs_path[kPathSize];
+ if(FindFilePath(rel_path.c_str(), abs_path, kPathSize, kModPaths|kDataPaths) == -1){
+ LOGE << "Could not find sound file: " << rel_path.c_str() << ". Generating empty placeholder." << std::endl;
+ GenerateEmptyData(format,data,size,freq);
+ } else {
+ if( LoadWAVFromFile(abs_path,format,data,size,freq) == false ) {
+ LOGE << "Failed loading data, generating empty placeholder for " << rel_path.c_str() << std::endl;
+ GenerateEmptyData(format,data,size,freq);
+ }
+ }
+
+ if (data == NULL) {
+ retval = false;
+ } else {
+ rc_alAudioBuffer buffer;
+ buffer->Allocate();
+
+ //LowPassFilter(format, data, size);
+
+ AudioBufferData abd;
+ abd.bytes_per_channel_sample = 1;
+ if(format == AL_FORMAT_MONO16 || format == AL_FORMAT_STEREO16){
+ abd.bytes_per_channel_sample = 2;
+ }
+ abd.channels = 1;
+ if(format == AL_FORMAT_STEREO8 || format == AL_FORMAT_STEREO16){
+ abd.channels = 2;
+ }
+ abd.num_bytes = size;
+ abd.data = (void*)data;
+
+ if(!filter_info.path.empty()){
+ if(filter_info.type == _simple_filter) {
+ SimpleFIRFilter filter;
+ if( filter.Load(filter_info.path) ) {
+ filter.Apply(abd);
+ } else {
+ LOGE << "Failed loading fir filter: " << filter_info.path << " will not apply to sound of " << rel_path << std::endl;
+ }
+ buffer->BufferData(format,data,size,freq);
+ } else if(filter_info.type == _convolution_filter) {
+ LOGE << "Game does not currently support _convolution_filter for audio, and the developer never expected it to be used, it was disabled because underlying library was removed after being deemed not in use." << std::endl;
+
+ buffer->BufferData(format,data,size,freq);
+ /*
+ FFTConvolutionFilter filter;
+ std::vector<int16_t> filtered;
+ if( filter.Load(filter_info.path) ) {
+ filter.Apply(abd, &filtered);
+ } else {
+ LOGE << "Failed loading FFTConvolution filter: " << filter_info.path << " will not apply to sound of " << rel_path << std::endl;
+ }
+ if(!filtered.empty()){
+ buffer->BufferData(format,&filtered[0],filtered.size()*2,freq);
+ } else {
+ buffer->BufferData(format,data,size,freq);
+ }
+ */
+ }
+ } else {
+ buffer->BufferData(format,data,size,freq);
+ }
+
+ if ((error = alGetError()) == AL_NO_ERROR) {
+ m_buffers.insert(buffer_map::value_type(
+ std::pair<std::string, std::string>(rel_path, filter_info.path),
+ buffer));
+ } else {
+ LOGE << "Failed to buffer data: \"" << alErrString(error) << "\"" << std::endl;
+ retval = false;
+ }
+ }
+
+ OG_FREE(data);
+
+ return retval;
+}
+
+void alAudio::set_volume(float volume)
+{
+ m_gain = volume;
+
+ streamer_subscribers::iterator it = m_streamers.begin();
+ while(it != m_streamers.end())
+ {
+ if (!it->second->get_discard())
+ {
+ it->second->get_source()->Sourcef(AL_GAIN, m_gain * it->second->get_audioEmitter()->GetVolume());
+ }
+ ++it;
+ }
+}
+
+void alAudio::set_master_volume( float volume )
+{
+ if( volume > 1.0f )
+ volume = 1.0f;
+ if( volume < 0.0f )
+ volume = 0.0f;
+
+ alListenerf(AL_GAIN, volume);
+}
+
+void alAudio::set_pitch(float pitch)
+{
+ m_pitch = pitch;
+
+ streamer_subscribers::iterator it = m_streamers.begin();
+ while(it != m_streamers.end())
+ {
+ if (!it->second->get_discard())
+ {
+ it->second->get_source()->Sourcef(AL_PITCH, m_pitch * it->second->get_audioEmitter()->GetPitchMultiplier());
+ }
+ ++it;
+ }
+}
+
+void alAudio::set_reference_distance(float distance)
+{
+ m_reference_distance = distance;
+
+ streamer_subscribers::iterator it = m_streamers.begin();
+ while(it != m_streamers.end())
+ {
+ if (!it->second->get_discard())
+ {
+ it->second->get_source()->Sourcef( AL_REFERENCE_DISTANCE, m_reference_distance);
+ }
+ ++it;
+ }
+}
+
+void alAudio::set_distance_model(ALenum model)
+{
+ alDistanceModel(model);
+}
+
+void alAudio::play(const unsigned long& handle, const SoundPlayInfo& spi, AudioEmitter *owner )
+{
+ set_sound(handle,spi, owner);
+}
+
+std::vector<AudioEmitter*> alAudio::GetActiveSounds() {
+ std::vector<AudioEmitter*> active_sounds(m_handles.size());
+ alAudio::static_handles::iterator iter;
+ unsigned i=0;
+ for(iter = m_handles.begin(); iter != m_handles.end(); ++iter){
+ active_sounds[i] = iter->second;
+ ++i;
+ }
+ return active_sounds;
+}
+
+float GetRolloffFromMaxDistance(float max_distance){
+ static const float inv_threshold_vol = 100.0f;
+ return (inv_threshold_vol - 1.0f) / (1+max_distance);
+}
+
+int alAudio::set_sound(const unsigned long& handle, const SoundPlayInfo& spi, AudioEmitter *owner)
+{
+ if(!spi.looping && !(spi.flags & SoundFlags::kRelative) && distance_squared(spi.position, listener_pos) >
+ spi.max_distance * spi.max_distance)
+ {
+ return -1;
+ }
+ const char *rel_path = spi.path.c_str();
+
+ buffer_map::iterator it = m_buffers.find(
+ std::pair<std::string, std::string>(rel_path, spi.filter_info.path));
+
+ // if buffer isn't found, need to load.
+ if (m_buffers.end() == it) {
+ size_t str_size = strlen(rel_path);
+ char *buffer = new char [str_size + 1];
+ ::memset(buffer, 0, str_size + 1);
+ ::memcpy(buffer, rel_path, str_size);
+
+ if (load(buffer, spi.filter_info)) {
+ it = m_buffers.find(
+ std::pair<std::string, std::string>(
+ rel_path, spi.filter_info.path)); // this can be made faster.
+ } else {
+ it = m_buffers.end();
+ }
+
+ ALenum err = alGetError();
+
+ if( err != AL_NO_ERROR )
+ {
+ LOGE << alErrString(err) << std::endl;
+ }
+
+ delete [] buffer;
+ }
+
+ if (m_buffers.end() != it) {
+ //need to manage this better than dropping out.
+ rc_alAudioSource source;
+
+ if (m_sources.empty())
+ {
+ //printf("Implement sound prioritization");
+ source = find_free_source();
+ }
+ else
+ {
+ source = m_sources.front();
+ m_sources.pop_front();
+ }
+
+ if( source->IsValid() )
+ {
+ priority_levels = max(priority_levels, spi.priority);
+
+ AudioEmitter *emitter = NULL;
+ if (owner == NULL)
+ {
+ //printf("Creating %u %s\n",handle, spi.path.c_str());
+ // this is not a leak due to the unsubscribe deleting the object.
+ //staticEmitter *emitter = new staticEmitter(looping, position);
+ emitter = new StaticEmitter(spi.looping, spi.position, spi.volume, spi.pitch, spi.flags, spi.max_distance, spi.priority);
+ emitter->handle = handle;
+ m_handles.insert(static_handles::value_type(handle, emitter));
+ }
+ else
+ {
+ emitter = owner;
+ }
+ CheckALError(__LINE__, __FILE__);
+ emitter->display_name = spi.path.substr(spi.path.rfind('/')+1);
+ emitter->SetOcclusionPosition(spi.occlusion_position);
+ emitter->SetVolumeMult(spi.volume_mult);
+
+ // set current values in source
+ source->Sourcef(AL_REFERENCE_DISTANCE, m_reference_distance);
+ //alSourcef(source, AL_ROLLOFF_FACTOR, GetRolloffFromMaxDistance(spi.max_distance));
+ source->Sourcef(AL_GAIN, m_gain * emitter->GetVolume());
+ source->Sourcef(AL_PITCH, m_pitch * emitter->GetPitchMultiplier());
+ source->Sourcef(AL_MIN_GAIN, 0.0f);
+ source->Sourcef(AL_MAX_GAIN, spi.max_gain);
+ CheckALError(__LINE__, __FILE__);
+
+ vec3 p;
+ source->Sourcei (AL_SOURCE_RELATIVE, emitter->GetPosition(p)?AL_TRUE:AL_FALSE);
+ source->Source3f(AL_POSITION, p.x(), p.y(), p.z());
+ emitter->GetDirection(p);
+ source->Source3f(AL_DIRECTION, p.x(), p.y(), p.z());
+ p = emitter->GetVelocity();
+ source->Source3f(AL_VELOCITY, p.x(), p.y(), p.z());
+
+ emitter->link(*this);
+ staticLink *link = new staticLink(source, *emitter, it->second, spi);
+ m_streamers.insert(streamer_subscribers::value_type(emitter, link));
+
+ CheckALError(__LINE__, __FILE__);
+ }
+ }
+ else
+ {
+ LOGE << "Unable to load sound file: " << rel_path << std::endl;
+ }
+
+ return handle;
+}
+
+rc_alAudioSource alAudio::find_free_source()
+{
+ int i = priority_levels;
+ do {
+ streamer_subscribers::iterator it = m_streamers.begin();
+ while(it != m_streamers.end())
+ {
+ if (it->second->get_discard() || (it->second->get_audioEmitter()->GetPriority() == i))
+ {
+ rc_alAudioSource source = it->second->get_source();
+ source->Stop();
+
+ // nuke handles.
+ static_handles::iterator it_handle = m_handles.begin();
+ while(it_handle != m_handles.end())
+ {
+ if (it->second->get_audioEmitter() == it_handle->second) {
+ /*it_handle = */m_handles.erase(it_handle++);
+ } else {
+ ++it_handle;
+ }
+ }
+
+ delete it->second;
+ m_streamers.erase(it++);
+
+ return source;
+ }
+ ++it;
+ }
+ --i;
+ } while (i >= 0);
+
+ return rc_alAudioSource();
+}
+
+std::string GetALErrorString(ALenum err)
+{
+ switch(err)
+ {
+ case AL_NO_ERROR:
+ return std::string("AL_NO_ERROR");
+ break;
+
+ case AL_INVALID_NAME:
+ return std::string("AL_INVALID_NAME");
+ break;
+
+ case AL_INVALID_ENUM:
+ return std::string("AL_INVALID_ENUM");
+ break;
+
+ case AL_INVALID_VALUE:
+ return std::string("AL_INVALID_VALUE");
+ break;
+
+ case AL_INVALID_OPERATION:
+ return std::string("AL_INVALID_OPERATION");
+ break;
+
+ case AL_OUT_OF_MEMORY:
+ return std::string("AL_OUT_OF_MEMORY");
+ break;
+ };
+
+ return std::string("Unknown OpenAL error");
+}
+
+void CheckALError(int line, const char* file){
+ ALenum err;
+ err = alGetError();
+ if (err != AL_NO_ERROR) {
+ char error_msg[1024];
+ int i = 0;
+ int last_slash = 0;
+ while(file[i] != '\0') {
+ if(file[i] == '\\' || file[i] == '/') last_slash = i+1;
+ i++;
+ }
+ FormatString(error_msg, 1024, "On line %d of %s: \n%s", line, &file[last_slash], GetALErrorString(err).c_str());
+ DisplayError("OpenAL error", error_msg);
+ }
+}
+
+alAudio::streamerLink::streamerLink(rc_alAudioSource source, audioStreamer &streamer) :
+ basicLink(source), m_streamer(&streamer)
+{
+ unsigned long i = 0;
+ for (i = 0; i < streamer.required_buffers(); ++i)
+ {
+ rc_alAudioBuffer buf;
+ buf->Allocate();
+ m_buffers.push_back(buf);
+ }
+
+ for (i = 0; i < m_buffers.size(); ++i)
+ {
+ update(m_buffers[i]);
+ }
+
+
+ CheckALError(__LINE__, __FILE__);
+ m_source->QueueBuffers( std::vector<rc_alAudioBuffer>(m_buffers.begin(),m_buffers.begin() + streamer.required_buffers()));
+ CheckALError(__LINE__, __FILE__);
+ m_source->Play();
+ CheckALError(__LINE__, __FILE__);
+}
+
+alAudio::streamerLink::~streamerLink()
+{
+ // may not be a safe assumption in the future
+ m_source->Stop();
+}
+
+void alAudio::streamerLink::update(rc_alAudioBuffer buffer)
+{
+ m_streamer->update(buffer);
+ update_position();
+}
+
+void alAudio::streamerLink::update(float timestep, unsigned int current_tick)
+{
+ update_position();
+
+ std::vector<rc_alAudioBuffer> buffers = m_source->DequeueBuffers();
+
+ for( unsigned i = 0; i < buffers.size(); i++ )
+ {
+ m_streamer->update(buffers[i]);
+ }
+
+ m_source->QueueBuffers(buffers);
+
+ // check if playback stopped due to buffer timeout...
+ ALint state = m_source->GetSourcei(AL_SOURCE_STATE);
+
+ if (AL_STOPPED == state)
+ {
+ if (m_streamer->KeepPlaying())
+ {
+ m_source->Play();
+ }
+ else
+ {
+ m_streamer->Unsubscribe();
+ }
+ }
+}
+
+void alAudio::streamerLink::update_position()
+{
+ vec3 p;
+
+ m_source->Sourcei (AL_SOURCE_RELATIVE, m_streamer->GetPosition(p)?AL_TRUE:AL_FALSE);
+ m_source->Source3f(AL_POSITION, p.x(), p.y(), p.z());
+
+ m_streamer->GetDirection(p);
+ m_source->Source3f(AL_DIRECTION, p.x(), p.y(), p.z());
+ p = m_streamer->GetVelocity();
+ m_source->Source3f(AL_VELOCITY, p.x(), p.y(), p.z());
+}
+
+void alAudio::streamerLink::stop()
+{
+ m_streamer->Stop();
+ m_source->Stop();
+
+ std::vector<rc_alAudioBuffer> buffers = m_source->DequeueBuffers();
+
+ for( unsigned i = 0; i < buffers.size(); i++ )
+ {
+ m_streamer->update(buffers[i]);
+ }
+
+ m_source->QueueBuffers(buffers);
+
+ m_source->Play();
+}
+
+
+alAudio::staticLink::staticLink( rc_alAudioSource source, AudioEmitter &emitter, rc_alAudioBuffer buffer, const SoundPlayInfo &_spi) :
+ basicLink(source),
+ m_buffer(buffer),
+ m_emitter(&emitter),
+ played(false),
+ play_on_tick(_spi.play_past_tick)
+{
+ spi = _spi;
+ source->SetBuffer( m_buffer);
+ source->Sourcei(AL_LOOPING, spi.looping?AL_TRUE:AL_FALSE);
+ m_source->Stop();
+}
+
+alAudio::staticLink::~staticLink() {
+}
+
+void alAudio::staticLink::update(float timestep, unsigned int current_tick) {
+ update_position();
+
+ vec3 p;
+ m_emitter->GetPosition(p);
+
+ /*
+ TODO: Move to external routine, calling it from here would break the thread boundery
+ if( config["visible_sound_spheres"].toBool() )
+ {
+ DebugDraw::Instance()->AddLine(p, p+vec3(0.0f,1.0f,0.0f), 0.1f, vec4(1.0f), _delete_on_draw);
+ }
+ */
+
+ if( current_tick >= play_on_tick && played == false) {
+ m_source->Play();
+ played = true;
+ }
+
+ ALint state = m_source->GetSourcei(AL_SOURCE_STATE);
+ if (played == true && AL_STOPPED == state )
+ {
+ m_emitter->Unsubscribe();
+ }
+ // Do not put any code after this spot, unsubscribe WILL destroy this object.
+}
+
+void alAudio::staticLink::update_position() {
+ vec3 p;
+
+ m_source->Sourcei (AL_SOURCE_RELATIVE, m_emitter->GetPosition(p)?AL_TRUE:AL_FALSE);
+ m_source->Source3f(AL_POSITION, p.x(), p.y(), p.z());
+
+ m_emitter->GetDirection(p);
+ m_source->Source3f(AL_DIRECTION, p.x(), p.y(), p.z());
+ p = m_emitter->GetVelocity();
+ m_source->Source3f(AL_VELOCITY, p.x(), p.y(), p.z());
+}
+
+void alAudio::staticLink::stop() {
+ m_emitter->Unsubscribe();
+ // Do not put any code after this spot, unsubscribe WILL destroy this object.
+}
+
+unsigned long alAudio::get_next_handle() {
+ return ++m_handle_ctr;
+}
+
+unsigned long alAudio::get_invalid_handle() {
+ return 0;
+}
+
+std::string alAudio::GetPreferredDevice() {
+ return preferred_device;
+}
+
+std::string alAudio::GetUsedDevice() {
+ return used_device;
+}
+
+std::vector<std::string> alAudio::GetAvailableDevices() {
+ return available_device_list;
+}
+
+void alAudio::change_pitch(unsigned long handle, float pitch) {
+ static_handles::iterator it = m_handles.find(handle);
+
+ if (it != m_handles.end()) {
+ it->second->SetPitchMultiplier(pitch);
+ }
+}
+
+void alAudio::change_volume(unsigned long handle, float volume) {
+ static_handles::iterator it = m_handles.find(handle);
+
+ if (it != m_handles.end()) {
+ it->second->SetVolume(volume);
+ }
+}
+
+void alAudio::SetPosition(const unsigned long &handle, const vec3 &new_pos) {
+ static_handles::iterator it = m_handles.find(handle);
+
+ if (it != m_handles.end()) {
+ it->second->SetPosition(new_pos);
+ }
+}
+
+void alAudio::TranslatePosition(const unsigned long &handle, const vec3 &new_pos) {
+ static_handles::iterator it = m_handles.find(handle);
+
+ if (it != m_handles.end()) {
+ it->second->SetPosition(it->second->GetPosition()+new_pos);
+ }
+}
+
+void alAudio::SetOcclusionPosition(const unsigned long &handle, const vec3 &new_pos) {
+ static_handles::iterator it = m_handles.find(handle);
+
+ if (it != m_handles.end()) {
+ it->second->SetOcclusionPosition(new_pos);
+ }
+}
+
+void alAudio::SetVelocity(const unsigned long &handle, const vec3 &new_vel) {
+ static_handles::iterator it = m_handles.find(handle);
+
+ if (it != m_handles.end()) {
+ it->second->SetVelocity(new_vel);
+ }
+}
+
+bool alAudio::IsHandleValid(const unsigned long &handle) {
+ static_handles::iterator it = m_handles.find(handle);
+ if (it != m_handles.end()) {
+ return true;
+ }
+ return false;
+}
+
+const vec3 alAudio::GetPosition( const unsigned long &handle ) {
+ static_handles::iterator it = m_handles.find(handle);
+ if (it != m_handles.end()) {
+ return(it->second->GetPosition());
+ }
+ return vec3(0.0f);
+}
+
+void alAudio::SetPitch( const unsigned long &handle, float pitch ) {
+ static_handles::iterator it = m_handles.find(handle);
+ if (it != m_handles.end()) {
+ it->second->SetPitchMultiplier(pitch);
+ }
+}
+
+void alAudio::SetVolume( const unsigned long &handle, float volume ) {
+ static_handles::iterator it = m_handles.find(handle);
+ if (it != m_handles.end()) {
+ it->second->SetVolume(volume);
+ }
+}
+
+void alAudio::Stop( const unsigned long & handle ) {
+ static_handles::iterator it = m_handles.find(handle);
+ if (it != m_handles.end()) {
+ //printf("Stopping %u\n",handle);
+ it->second->Unsubscribe();
+ }
+}
+
+void alAudio::StopAll() {
+ streamer_subscribers::iterator it = m_streamers.begin();
+ while(it != m_streamers.end()) {
+ if (it->second->get_discard()){
+ ++it;
+ continue;
+ }
+
+ it->second->stop();
+
+ ++it;
+ }
+}
+
+void alAudio::StopAllTransient()
+{
+ streamer_subscribers::iterator it = m_streamers.begin();
+ while(it != m_streamers.end()) {
+ if (it->second->get_discard()){
+ ++it;
+ continue;
+ }
+
+ if( it->second->is_transient() )
+ {
+ it->second->stop();
+ }
+
+ ++it;
+ }
+}
+
+bool SimpleFIRFilter::Load( const std::string &path )
+{
+ ALsizei size,freq;
+ ALenum format;
+ ALvoid *data = NULL;
+
+ char abs_path[kPathSize];
+ FindFilePath(path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths);
+
+ if( LoadWAVFromFile(abs_path, format, data, size, freq) ) {
+ if( data ) {
+ unsigned bytes_per_channel_sample = 1;
+ if(format == AL_FORMAT_MONO16 || format == AL_FORMAT_STEREO16){
+ bytes_per_channel_sample = 2;
+ }
+
+ unsigned channels = 1;
+ if(format == AL_FORMAT_STEREO8 || format == AL_FORMAT_STEREO16){
+ channels = 2;
+ }
+
+ if(bytes_per_channel_sample == 2 && channels == 1){
+ int16_t* data_dbyte = (int16_t*)data;
+ filter.resize(size/2);
+ for(int i=0; i<size/2; i++){
+ filter[i] = data_dbyte[i] / 32767.0f;
+ }
+ } else {
+ OG_FREE(data);
+ data = NULL;
+ LOGE << "Filter is not 16-bit mono: " << path.c_str() << std::endl;
+ return false;
+ }
+ } else {
+ LOGE << "Unable to load fir filter from " << path << std::endl;
+ return false;
+ }
+ } else {
+ LOGE << "Unable to load wav file for FIR filter" << std::endl;
+ return false;
+ }
+
+ OG_FREE( data );
+ return true;
+}
+
+void SimpleFIRFilter::Apply( AudioBufferData &abd, std::vector<int16_t> *output_new)
+{
+ unsigned filter_mid = unsigned(((float)filter.size())*0.5f);
+ unsigned filter_size = filter.size();
+ unsigned filter_tip = filter_size - filter_mid;
+
+ if(abd.bytes_per_channel_sample == 2){
+ for(unsigned k=0; k<abd.channels; ++k){
+ int16_t* data_dbyte = (int16_t*)abd.data;
+ unsigned end = abd.num_bytes/2/abd.channels;
+
+ std::vector<int16_t> temp(filter_size);
+ for(unsigned i=0; i<filter_mid; ++i){
+ temp[i] = 0;
+ }
+ unsigned next_step = min(filter_size,end+filter_mid);
+ for(unsigned i=filter_mid; i<next_step; ++i){
+ temp[i] = data_dbyte[(i-filter_mid)*abd.channels+k];
+ }
+ for(unsigned i=end+filter_mid; i<filter_size; ++i){
+ temp[i] = 0;
+ }
+
+ unsigned offset = 0;
+ float total;
+ for(unsigned i=0; i<end; i++){
+ total = 0.0f;
+ for(unsigned j=0; j<filter.size(); ++j){
+ total += (float)temp[(j+offset)%filter_size] * filter[j];
+ }
+ data_dbyte[i*abd.channels+k] = (int16_t)total;
+ if(i+filter_tip < end){
+ temp[offset] = data_dbyte[(i+filter_tip)*abd.channels+k];
+ } else {
+ temp[offset] = 0;
+ }
+ ++offset;
+ offset = offset%filter_size;
+ }
+ }
+ }
+}
+
+/*
+bool FFTConvolutionFilter::Load( const std::string &path )
+{
+ ALsizei size,freq;
+ ALenum format;
+ ALvoid *data = NULL;
+
+ char abs_path[kPathSize];
+ FindFilePath(path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths);
+
+ if( LoadWAVFromFile(abs_path, format, data, size, freq) ) {
+ if( data )
+ {
+ unsigned bytes_per_channel_sample = 1;
+ if(format == AL_FORMAT_MONO16 || format == AL_FORMAT_STEREO16){
+ bytes_per_channel_sample = 2;
+ }
+
+ unsigned channels = 1;
+ if(format == AL_FORMAT_STEREO8 || format == AL_FORMAT_STEREO16){
+ channels = 2;
+ }
+
+ if(bytes_per_channel_sample == 2 && channels == 1){
+ int16_t* data_dbyte = (int16_t*)data;
+ filter.resize(size/2);
+ for(int i=0; i<size/2; i++){
+ filter[i] = data_dbyte[i];// / 32767.0f;
+ }
+ } else {
+ LOGE << "Filter is not 16-bit mono: " << path.c_str() << std::endl;
+ OG_FREE(data);
+ data = NULL;
+ return false;
+ }
+ } else {
+ LOGE << "Unable to load convolution filter from " << path << std::endl;
+ return false;
+ }
+ } else {
+ LOGE << "Unable to load wav file for FFTConvolution filter" << std::endl;
+ return false;
+ }
+
+ OG_FREE(data);
+ return true;
+}
+
+void FFTConvolutionFilter::Apply( AudioBufferData &abd, std::vector<int16_t> *output_new)
+{
+ if(abd.channels > 1){
+ return;
+ }
+ int16_t* samples = (int16_t*)abd.data;
+ int length1 = abd.num_bytes/abd.bytes_per_channel_sample;
+ int length2 = filter.size();
+
+ float gain = 0.2f;
+
+ int i;
+ float *in;
+ float *in2;
+ int n;
+ int nc;
+ fftwf_complex *out1;
+ fftwf_complex *out2;
+ fftwf_plan plan_backward;
+ fftwf_plan plan_forward;
+
+ int long_length = length1+length2-1;
+
+ n = long_length;
+ nc = ( n / 2 ) + 1;
+ in = (float*)fftwf_malloc ( sizeof ( float ) * n );
+ for ( i = 0; i < length1; i++ )
+ {
+ in[i] = samples[i];
+ }
+ for ( i = length1; i < n; i++ )
+ {
+ in[i] = 0;
+ }
+ out1 = (fftwf_complex*)fftwf_malloc ( sizeof ( fftwf_complex ) * nc );
+ plan_forward = fftwf_plan_dft_r2c_1d ( n, in, out1, FFTW_ESTIMATE );
+ fftwf_execute ( plan_forward );
+ fftwf_destroy_plan ( plan_forward );
+ fftwf_free ( in );
+
+ in = (float*)fftwf_malloc ( sizeof ( float ) * n );
+ for ( i = 0; i < length2; i++ )
+ {
+ in[i] = filter[i];
+ }
+ for ( i = length2; i < n; i++ )
+ {
+ in[i] = 0;
+ }
+ out2 = (fftwf_complex*)fftwf_malloc ( sizeof ( fftwf_complex ) * nc );
+ plan_forward = fftwf_plan_dft_r2c_1d ( n, in, out2, FFTW_ESTIMATE );
+ fftwf_execute ( plan_forward );
+ fftwf_destroy_plan ( plan_forward );
+ fftwf_free ( in );
+
+ float new_r;
+ float new_i;
+ float scale = 1/(float)n*gain;
+ for (i = 0;i<nc;i++){
+ new_r = (out1[i][0]*out2[i][0] - out1[i][1]*out2[i][1])*scale;
+ new_i = (out1[i][1]*out2[i][0] + out1[i][0]*out2[i][1])*scale;
+
+ out1[i][0] = new_r;
+ out1[i][1] = new_i;
+ }
+
+
+ //Set up an array to hold the backtransformed data IN2,
+ //get a "plan", and execute the plan to backtransform the OUT
+ //FFT coefficients to IN2.
+
+
+ in2 = (float*)fftwf_malloc ( sizeof ( float ) * n );
+ plan_backward = fftwf_plan_dft_c2r_1d ( n, out1, in2, FFTW_ESTIMATE );
+ fftwf_execute ( plan_backward );
+
+ for (i = 0; i < length1; i++ )
+ {
+ samples[i] = (int16_t)(in2[i]/n);
+ }
+
+ if(output_new){
+ std::vector<int16_t> &output = (*output_new);
+ output.resize(long_length);
+ for (i = 0; i < long_length; i++ )
+ {
+ output[i] = (int16_t)(in2[i]/n);
+ }
+ }
+
+ fftwf_destroy_plan ( plan_backward );
+ fftwf_free ( in2 );
+ fftwf_free ( out1 );
+ fftwf_free ( out2 );
+
+ return;
+}
+*/
+
+void alAudio::SetVolumeMult( const unsigned long &handle, float volume )
+{
+ static_handles::iterator it = m_handles.find(handle);
+ if (it != m_handles.end())
+ {
+ it->second->SetVolumeMult(volume);
+ }
+}
diff --git a/Source/Sound/al_audio.h b/Source/Sound/al_audio.h
new file mode 100644
index 00000000..df5e0541
--- /dev/null
+++ b/Source/Sound/al_audio.h
@@ -0,0 +1,342 @@
+//-----------------------------------------------------------------------------
+// Name: al_audio.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#ifdef WIN32
+#pragma warning(disable:4786)
+#endif
+
+#include <Sound/soundplayinfo.h>
+#include <Sound/al_audio_buffer.h>
+#include <Sound/al_audio_source.h>
+
+#include <Internal/integer.h>
+#include <Internal/referencecounter.h>
+
+#include <Wrappers/openal.h>
+#include <Math/enginemath.h>
+
+#include <map>
+#include <string>
+#include <vector>
+#include <deque>
+#include <list>
+#include <algorithm>
+#include <string>
+
+namespace SoundFlags {
+ enum {
+ kNoOcclusion = 1<<0,
+ kRelative = 1<<1
+ };
+}
+
+struct AudioBufferData {
+ unsigned channels;
+ unsigned bytes_per_channel_sample;
+ void *data;
+ unsigned num_bytes;
+};
+
+class AudioFilter {
+public:
+ virtual bool Load(const std::string &path)=0;
+ virtual void Apply( AudioBufferData &abd, std::vector<int16_t> *output_new = NULL)=0;
+ virtual ~AudioFilter() {}
+};
+
+class SimpleFIRFilter : public AudioFilter {
+ std::vector<float> filter;
+public:
+ bool Load(const std::string &path);
+ void Apply( AudioBufferData &abd, std::vector<int16_t> *output_new = NULL);
+};
+
+/*
+class FFTConvolutionFilter : public AudioFilter {
+ std::vector<float> filter;
+public:
+ bool Load(const std::string &path);
+ void Apply( AudioBufferData &abd, std::vector<int16_t> *output_new = NULL);
+};
+*/
+
+class alAudio;
+
+/**
+* Base class for all subscribers
+*/
+class AudioEmitter
+{
+public:
+ //Unique id for this emitter instance, used to reference between undefined slices of time.
+ int uid;
+
+ uint8_t flags_;
+ unsigned long handle;
+ std::string display_name;
+
+ AudioEmitter() : uid(++uid_counter), m_audio(NULL), handle(0) {}
+ virtual ~AudioEmitter();
+
+ /// Set to false to allow the emitter to time out (useful only on non-looping sounds)
+ virtual bool KeepPlaying() = 0;
+ /// if true is returned, this will be a relative-to-listener sound
+ virtual bool GetPosition(vec3 &p) = 0;
+ virtual void GetDirection(vec3 &p) = 0;
+ virtual const vec3& GetVelocity() = 0;
+ virtual const vec3 GetPosition() = 0;
+ virtual const vec3 GetOcclusionPosition() = 0;
+ virtual float GetMaxDistance() {return 0.0f;}
+ uint8_t flags();
+
+ /// allows you to change the global alAudio pitch multiplier for this sound
+ virtual float GetPitchMultiplier() {return 1.0f;}
+
+ /// allows you to change the global alAudio gain multiplier for this sound
+ virtual float GetVolume() {return 1.0f;}
+
+ /// For use by alAudio to build subscriber chains
+ void link(alAudio &a);
+ /// For use by alAudio to eliminate this object from the subscriber chain
+ virtual void Unsubscribe();
+
+ /// Indicate the priority of this effect to make room for newer/higher priority effects
+ virtual unsigned char GetPriority() = 0;
+
+ virtual void SetPitchMultiplier(float mul) {;}
+ virtual void SetVolume(float mul) {;}
+ virtual void SetVolumeMult(float mul) {;}
+ virtual void SetVelocity(const vec3 &vel) {;}
+ virtual void SetPosition(const vec3 &pos) {;}
+ virtual void SetOcclusionPosition(const vec3 &pos) {;}
+ virtual void SetFlags(uint8_t flags) {flags_ = flags;}
+ //Am i a transient sound? That means, short lived and shouldn't remain between major state changes.
+ virtual bool IsTransient() { return true; }
+
+protected:
+ alAudio *m_audio;
+ static int uid_counter;
+};
+
+
+
+
+/**
+* Subscriber specifically for streaming audio (oggSoundtracks, network voice, etc)
+*/
+class audioStreamer : public AudioEmitter
+{
+public:
+ audioStreamer();
+ virtual ~audioStreamer();
+
+ /// Request to fill a buffer with data
+ virtual void update(rc_alAudioBuffer buffer) = 0;
+
+ /// Return the number of buffers this streaming class requires (usually 2)
+ virtual unsigned long required_buffers() = 0;
+
+ /// Stop the current stream (reset to empty)
+ virtual void Stop() = 0;
+};
+
+
+/**
+* Interface to OpenAL
+*/
+class alAudio
+{
+public:
+ alAudio(const char* preferred_device, float volume, float reference_distance = 2000.0f);
+
+ /// Add a streaming subscriber to the alAudio subscriber chain
+ void subscribe(audioStreamer &streamer);
+
+ /// Remove any subscriber from the alAudio subscriber chain
+ void unsubscribe(AudioEmitter &streamer);
+
+ void set_listener_position(vec3 &position);
+ void set_listener_orientation(vec3 &facing, vec3 &up);
+ void set_listener_velocity(vec3 &velocity);
+
+ /// Preload a sound into the buffer. Accepts OGG or WAV.
+ bool load(const std::string& file, const FilterInfo &filter_info);
+
+ void play(const unsigned long& handle, const SoundPlayInfo& spi, AudioEmitter *owner = NULL);
+
+ /// This must be called frequently, it is responsible for updating every source in the link chain - optimally called once a frame.
+ void update(float timestep);
+
+ /// Set global volume
+ void set_volume(float volume);
+
+ void set_master_volume( float volume );
+
+ /// Set global pitch
+ void set_pitch(float pitch);
+
+ /// Set reference distance for sound cutoff
+ void set_reference_distance(float distance);
+
+ void set_distance_model(ALenum model);
+
+private:
+ ALCdevice* m_device;
+ ALCcontext* m_context;
+ unsigned char priority_levels;
+
+ int set_sound(const unsigned long& handle, const SoundPlayInfo& spi, AudioEmitter *owner);
+ void update_subscribers(float timestep);
+ void reset_listener();
+
+ bool load_wav(const std::string& file, const FilterInfo &filter_info);
+ bool load_ogg(const std::string& file, const FilterInfo &filter_info);
+
+ rc_alAudioSource find_free_source();
+public:
+
+ typedef std::deque<rc_alAudioSource> source_deque;
+ source_deque m_sources;
+
+ typedef std::map<std::pair<std::string, std::string>, rc_alAudioBuffer> buffer_map;
+ buffer_map m_buffers;
+
+ /**
+ * Base class of the subscriber chain
+ */
+ class basicLink
+ {
+ protected:
+ rc_alAudioSource m_source;
+
+ public:
+ basicLink(rc_alAudioSource source) : m_source(source), m_discard(false) {}
+ virtual ~basicLink() {}
+ virtual void update(float timestep, unsigned int current_tick) = 0;
+ virtual void update_position() = 0;
+ virtual void stop() = 0;
+ virtual bool is_transient() { return get_audioEmitter()->IsTransient(); };
+
+ rc_alAudioSource get_source() {return m_source;}
+
+ bool get_discard() { return m_discard; }
+ void signal_discard() { m_discard = true; }
+
+ virtual AudioEmitter *get_audioEmitter() = 0;
+
+ SoundPlayInfo spi;
+ private:
+ bool m_discard;
+ };
+
+ /**
+ * Static audio subscriber link
+ */
+ class staticLink : public basicLink
+ {
+ private:
+ //alAudio m_audio;
+ rc_alAudioBuffer m_buffer;
+ AudioEmitter *m_emitter;
+ unsigned int play_on_tick;
+ bool played;
+
+ public:
+ staticLink(rc_alAudioSource source, AudioEmitter &emitter, rc_alAudioBuffer buffer, const SoundPlayInfo& spi);
+ ~staticLink();
+
+ void update(float timestep,unsigned int current_tick);
+ void update_position();
+ void stop();
+ virtual AudioEmitter *get_audioEmitter() {return m_emitter;}
+ };
+
+
+ /**
+ * Streaming audio subscriber link
+ */
+ class streamerLink : public basicLink
+ {
+ private:
+ typedef std::vector<rc_alAudioBuffer> buffer_vector;
+ buffer_vector m_buffers;
+ audioStreamer *m_streamer;
+
+ public:
+ streamerLink(rc_alAudioSource source, audioStreamer &streamer);
+ ~streamerLink();
+
+ void update(float timestep, unsigned int current_tick);
+ void update(rc_alAudioBuffer buffer);
+ void update_position();
+ void stop();
+
+ rc_alAudioSource get_source() {return m_source;}
+ virtual AudioEmitter *get_audioEmitter() {return m_streamer;}
+ };
+
+ typedef std::map<AudioEmitter *, basicLink *> streamer_subscribers;
+
+ typedef std::map<unsigned long, AudioEmitter *> static_handles;
+
+ std::vector<std::string> available_device_list;
+ std::string used_device;
+ std::string preferred_device;
+
+ unsigned long get_next_handle();
+ unsigned long get_invalid_handle();
+private:
+ unsigned long m_handle_ctr;
+public:
+ static_handles m_handles;
+
+ std::string GetUsedDevice();
+ std::vector<std::string> GetAvailableDevices();
+ std::string GetPreferredDevice();
+
+ void change_pitch(unsigned long handle, float pitch);
+ void change_volume(unsigned long handle, float volume);
+ void Dispose();
+ bool IsHandleValid(const unsigned long &handle);
+ void SetPosition(const unsigned long &handle, const vec3 &new_pos);
+ void TranslatePosition( const unsigned long &handle, const vec3 &trans);
+ void SetVelocity(const unsigned long &handle, const vec3 &new_vel);
+ void SetPitch(const unsigned long &handle, float pitch);
+ void SetVolume(const unsigned long &handle, float volume);
+ void SetVolumeMult(const unsigned long &handle, float volume);
+ const vec3 GetPosition(const unsigned long &handle);
+ std::vector<AudioEmitter*> GetActiveSounds();
+ void Stop( const unsigned long & handle );
+ void StopAll();
+ void StopAllTransient();
+ void SetOcclusionPosition(const unsigned long &handle, const vec3 &new_pos);
+ streamer_subscribers m_streamers;
+ float m_reference_distance;
+ float m_gain;
+ float m_pitch;
+ vec3 listener_pos;
+};
+
+void CheckALError(int line, const char* file);
+
diff --git a/Source/Sound/al_audio_buffer.cpp b/Source/Sound/al_audio_buffer.cpp
new file mode 100644
index 00000000..01bc5253
--- /dev/null
+++ b/Source/Sound/al_audio_buffer.cpp
@@ -0,0 +1,85 @@
+//-----------------------------------------------------------------------------
+// Name: al_audio_buffer.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "al_audio_buffer.h"
+
+#include <Sound/soundlogging.h>
+
+alAudioBuffer::alAudioBuffer( )
+: buf( INVALID_BUF )
+{
+}
+
+void alAudioBuffer::Allocate()
+{
+ ALenum e = alGetError();
+ if( e != AL_NO_ERROR ) {
+ LOGW << "Entering alAudioBuffer with error: " << e << " " << alGetString(e) << std::endl;
+ }
+
+ alGenBuffers(1, &buf);
+
+ if( !IsValid() )
+ {
+ e = alGetError();
+ LOGW << "Unable to generate audio buffer: " << e << " " << alGetString(e) << std::endl;
+ buf = INVALID_BUF;
+ }
+}
+
+alAudioBuffer::~alAudioBuffer()
+{
+ if( IsValid() )
+ {
+ alDeleteBuffers(1, &buf);
+ buf = INVALID_BUF;
+ }
+}
+
+void alAudioBuffer::BufferData(ALenum format, const ALvoid *data, ALsizei size, ALsizei freq)
+{
+ if( !IsValid() )
+ {
+ Allocate();
+ }
+
+ if( IsValid() )
+ {
+ alBufferData(buf, format, data, size, freq );
+
+ ALenum err = alGetError();
+
+ if( err != AL_NO_ERROR )
+ {
+ LOGE << alErrString(err) << std::endl;
+ }
+ }
+ else
+ {
+ LOGE << "Can't buffer, buffer is invalid after attempted (re)allocation" << std::endl;
+ }
+}
+
+bool alAudioBuffer::IsValid()
+{
+ return buf != INVALID_BUF && alIsBuffer(buf);
+}
diff --git a/Source/Sound/al_audio_buffer.h b/Source/Sound/al_audio_buffer.h
new file mode 100644
index 00000000..889a43e3
--- /dev/null
+++ b/Source/Sound/al_audio_buffer.h
@@ -0,0 +1,45 @@
+//-----------------------------------------------------------------------------
+// Name: al_audio_buffer.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Wrappers/openal.h>
+#include <Internal/referencecounter.h>
+
+#include <cstdlib>
+
+class alAudioBuffer
+{
+public:
+ alAudioBuffer();
+ ~alAudioBuffer();
+ void BufferData(ALenum format, const ALvoid *data, ALsizei size, ALsizei freq);
+
+ void Allocate();
+ bool IsValid();
+private:
+ static const ALuint INVALID_BUF = 0xFFFFFFFF;
+ ALuint buf;
+ friend class alAudioSource;
+};
+
+typedef ReferenceCounter<alAudioBuffer> rc_alAudioBuffer;
diff --git a/Source/Sound/al_audio_source.cpp b/Source/Sound/al_audio_source.cpp
new file mode 100644
index 00000000..62fb67de
--- /dev/null
+++ b/Source/Sound/al_audio_source.cpp
@@ -0,0 +1,182 @@
+//-----------------------------------------------------------------------------
+// Name: al_audio_source.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "al_audio_source.h"
+
+#include <Sound/soundlogging.h>
+#include <Utility/assert.h>
+
+#include <cassert>
+
+alAudioSource::alAudioSource( )
+: src( INVALID_SRC )
+{
+}
+
+alAudioSource::~alAudioSource()
+{
+ if( IsValid() )
+ {
+ alSourceStop(src);
+ alDeleteSources(1, &src);
+ }
+}
+
+void alAudioSource::Allocate()
+{
+ alGenSources(1,&src);
+
+ if( !IsValid() )
+ {
+ ALenum e = alGetError();
+ LOGW << "Unable to generate audio source: " << e << std::endl;
+ src = INVALID_SRC;
+ }
+}
+
+void alAudioSource::SetBuffer( rc_alAudioBuffer buf )
+{
+ LOG_ASSERT( queuedBuffers.size() == 0 ); //Verify that we aren't using this for buffered audio.
+
+ boundBuffer = buf;
+
+ if( boundBuffer->IsValid() )
+ {
+ alSourcei( src, AL_BUFFER, buf->buf );
+ }
+}
+
+void alAudioSource::QueueBuffers( std::vector<rc_alAudioBuffer> d )
+{
+ LOG_ASSERT( !boundBuffer->IsValid() ); //Verify that we haven't bound anything else.
+
+ if( d.size() > 0 )
+ {
+ ALuint* buffers = (ALuint*)alloca( sizeof( ALuint ) * d.size() );
+
+ for( unsigned i = 0; i < d.size(); i++ )
+ {
+ buffers[i] = d[i]->buf;
+ }
+
+ alSourceQueueBuffers( src, d.size(), buffers );
+
+ ALenum err = alGetError();
+
+ if( err != AL_NO_ERROR )
+ {
+ LOGW << "Unable to queue sound buffer: " << alErrString(err) << std::endl;
+ std::copy( d.begin(), d.end(), back_inserter(failedQueuedBuffers) );
+ }
+ else
+ {
+ std::copy( d.begin(), d.end(), back_inserter(queuedBuffers) );
+ }
+ }
+}
+
+std::vector<rc_alAudioBuffer> alAudioSource::DequeueBuffers()
+{
+ ALint finished_count = GetSourcei( AL_BUFFERS_PROCESSED );
+ std::vector<rc_alAudioBuffer> retbuf;
+ retbuf.reserve(finished_count);
+
+ if( finished_count > 0 )
+ {
+ ALuint *finished_ids = (ALuint*)alloca( sizeof( ALuint ) * finished_count );
+ alSourceUnqueueBuffers( src, finished_count, finished_ids );
+
+ for( int i = 0; i < finished_count; i++ )
+ {
+ for( unsigned k = 0; k < queuedBuffers.size(); k++ )
+ {
+ if( queuedBuffers[k]->buf == finished_ids[i] )
+ {
+ retbuf.push_back(queuedBuffers[k]);
+ queuedBuffers.erase(queuedBuffers.begin() + k);
+ break;
+ }
+ }
+ }
+ }
+
+ if( failedQueuedBuffers.size() > 0 )
+ {
+ std::copy( failedQueuedBuffers.begin(), failedQueuedBuffers.end(), back_inserter(retbuf) );
+ failedQueuedBuffers.clear();
+ }
+
+ ALenum err = alGetError();
+
+ if( err != AL_NO_ERROR )
+ {
+ LOGE << alErrString(err) << ":" << src << std::endl;
+ }
+
+ return retbuf;
+}
+
+void alAudioSource::Sourcef( ALenum param, ALfloat value )
+{
+ alSourcef( src, param, value );
+}
+
+void alAudioSource::Source3f( ALenum param, ALfloat v1, ALfloat v2, ALfloat v3 )
+{
+ alSource3f( src, param, v1, v2, v3 );
+}
+
+void alAudioSource::Sourcei( ALenum param, ALint value )
+{
+ LOG_ASSERT( param != AL_BUFFER );
+ alSourcei( src, param, value );
+}
+
+ALint alAudioSource::GetSourcei( ALenum pname )
+{
+ ALint value;
+ alGetSourcei( src, pname, &value );
+ return value;
+}
+
+void alAudioSource::Play()
+{
+ alSourcePlay(src);
+
+ ALenum err = alGetError();
+
+ if( err != AL_NO_ERROR )
+ {
+ LOGE << alErrString(err) << std::endl;
+ }
+}
+
+void alAudioSource::Stop()
+{
+ alSourceStop(src);
+}
+
+bool alAudioSource::IsValid()
+{
+ return src != INVALID_SRC && alIsSource(src);
+}
+
diff --git a/Source/Sound/al_audio_source.h b/Source/Sound/al_audio_source.h
new file mode 100644
index 00000000..6b3b606c
--- /dev/null
+++ b/Source/Sound/al_audio_source.h
@@ -0,0 +1,60 @@
+//-----------------------------------------------------------------------------
+// Name: al_audio_source.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Sound/al_audio_buffer.h>
+#include <Wrappers/openal.h>
+#include <Internal/referencecounter.h>
+
+#include <vector>
+
+class alAudioSource
+{
+public:
+ alAudioSource();
+ ~alAudioSource();
+
+ void Sourcef( ALenum param, ALfloat value );
+ void Source3f( ALenum param, ALfloat v1, ALfloat v2, ALfloat v3 );
+ void Sourcei( ALenum param, ALint value );
+
+ ALint GetSourcei( ALenum pname );
+
+ void Allocate();
+ void SetBuffer( rc_alAudioBuffer buf );
+ void QueueBuffers( std::vector<rc_alAudioBuffer> d );
+ std::vector<rc_alAudioBuffer> DequeueBuffers();
+
+ void Play();
+ void Stop();
+
+ bool IsValid();
+private:
+ static const ALuint INVALID_SRC = 0xFFFFFFFF;
+ ALuint src;
+ rc_alAudioBuffer boundBuffer;
+ std::vector<rc_alAudioBuffer> queuedBuffers;
+ std::vector<rc_alAudioBuffer> failedQueuedBuffers;
+};
+
+typedef ReferenceCounter<alAudioSource> rc_alAudioSource;
diff --git a/Source/Sound/ambient_sound.cpp b/Source/Sound/ambient_sound.cpp
new file mode 100644
index 00000000..0bc0a71a
--- /dev/null
+++ b/Source/Sound/ambient_sound.cpp
@@ -0,0 +1,87 @@
+//-----------------------------------------------------------------------------
+// Name: ambient_sound.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "ambient_sound.h"
+
+ambientSound::ambientSound(float volume) : m_volume(volume), m_pitch(1.0)
+{
+}
+
+/// Set to false to allow the emitter to time out (useful only on non-looping sounds)
+bool ambientSound::KeepPlaying()
+{
+ return true;
+}
+
+/// if true is returned, this will be a relative-to-listener sound
+bool ambientSound::GetPosition(vec3 &p)
+{
+ return true;
+}
+
+const vec3 ambientSound::GetPosition()
+{
+ return vec3(0.0f);
+}
+
+void ambientSound::GetDirection(vec3 &p)
+{
+ p.x() = 0.0f; p.y() = 0.0f; p.y() = 0.0f;
+}
+
+const vec3 &ambientSound::GetVelocity()
+{
+ return m_velocity;
+}
+
+/// allows you to change the global alAudio pitch multiplier for this sound
+float ambientSound::GetPitchMultiplier()
+{
+ return m_pitch;
+}
+
+/// allows you to change the global alAudio gain multiplier for this sound
+float ambientSound::GetVolume()
+{
+ return m_volume;
+}
+
+/// Indicate the priority of this effect to make room for newer/higher priority effects
+unsigned char ambientSound::GetPriority()
+{
+ return _sound_priority_max;
+}
+
+void ambientSound::SetVolume(float volume)
+{
+ m_volume = volume;
+}
+
+void ambientSound::SetPitch(float pitch)
+{
+ m_pitch = pitch;
+}
+
+const vec3 ambientSound::GetOcclusionPosition()
+{
+ return vec3(0.0f);
+}
diff --git a/Source/Sound/ambient_sound.h b/Source/Sound/ambient_sound.h
new file mode 100644
index 00000000..2f96c3b4
--- /dev/null
+++ b/Source/Sound/ambient_sound.h
@@ -0,0 +1,58 @@
+//-----------------------------------------------------------------------------
+// Name: ambient_sound.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Sound/al_audio.h>
+
+class ambientSound : public AudioEmitter
+{
+public:
+ ambientSound(float volume = 1.0f);
+
+ /// Set to false to allow the emitter to time out (useful only on non-looping sounds)
+ virtual bool KeepPlaying();
+ /// if true is returned, this will be a relative-to-listener sound
+ virtual bool GetPosition(vec3 &p);
+ virtual const vec3 GetPosition();
+ virtual const vec3 GetOcclusionPosition();
+ virtual void GetDirection(vec3 &p);
+ virtual const vec3& GetVelocity();
+
+ /// allows you to change the global alAudio pitch multiplier for this sound
+ virtual float GetPitchMultiplier();
+
+ /// allows you to change the global alAudio gain multiplier for this sound
+ virtual float GetVolume();
+
+ /// Indicate the priority of this effect to make room for newer/higher priority effects
+ virtual unsigned char GetPriority();
+
+ void SetVolume(float volume);
+ void SetPitch(float pitch);
+ virtual bool IsTransient() { return false; }
+
+private:
+ float m_volume;
+ float m_pitch;
+ vec3 m_velocity;
+};
diff --git a/Source/Sound/ambienttriangle.h b/Source/Sound/ambienttriangle.h
new file mode 100644
index 00000000..3ff27769
--- /dev/null
+++ b/Source/Sound/ambienttriangle.h
@@ -0,0 +1,34 @@
+//-----------------------------------------------------------------------------
+// Name: ambienttriangle.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+
+#include <string>
+
+struct AmbientTriangle {
+ std::string path;
+ unsigned long handles[3];
+ vec3 rel_dir[3];
+ float vol[3];
+};
diff --git a/Source/Sound/buffer_segment.cpp b/Source/Sound/buffer_segment.cpp
new file mode 100644
index 00000000..7ffcf55f
--- /dev/null
+++ b/Source/Sound/buffer_segment.cpp
@@ -0,0 +1,44 @@
+//-----------------------------------------------------------------------------
+// Name: buffer_segment.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "buffer_segment.h"
+
+BufferSegment::BufferSegment(): sample_rate(0), channels(0), data_size(0), error_code(0) {
+
+}
+
+size_t BufferSegment::FullSampleCount() const {
+ if( channels > 0 ) {
+ return data_size/SAMPLE_SIZE/channels;
+ } else {
+ return 0;
+ }
+}
+
+size_t BufferSegment::FlatSampleCount() const {
+ return data_size/SAMPLE_SIZE;
+}
+
+size_t BufferSegment::SampleSize() const {
+ return SAMPLE_SIZE;
+}
+
diff --git a/Source/Sound/buffer_segment.h b/Source/Sound/buffer_segment.h
new file mode 100644
index 00000000..45157b3f
--- /dev/null
+++ b/Source/Sound/buffer_segment.h
@@ -0,0 +1,48 @@
+//-----------------------------------------------------------------------------
+// Name: buffer_segment.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/integer.h>
+
+#include <cmath>
+#include <cstring>
+
+class BufferSegment
+{
+public:
+ BufferSegment();
+
+ size_t FullSampleCount() const;
+ size_t FlatSampleCount() const;
+ size_t SampleSize() const;
+
+ static const size_t SAMPLE_SIZE = 2;
+ static const size_t BUF_SIZE = 4096*2*SAMPLE_SIZE;
+
+ size_t sample_rate;
+ size_t channels;
+
+ size_t data_size;
+ int error_code;
+ char buf[BUF_SIZE];
+};
diff --git a/Source/Sound/high_res_buffer_segment.cpp b/Source/Sound/high_res_buffer_segment.cpp
new file mode 100644
index 00000000..86f5d946
--- /dev/null
+++ b/Source/Sound/high_res_buffer_segment.cpp
@@ -0,0 +1,44 @@
+//-----------------------------------------------------------------------------
+// Name: high_res_buffer_segment.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "high_res_buffer_segment.h"
+
+HighResBufferSegment::HighResBufferSegment(): sample_rate(0), channels(0), data_size(0), error_code(0) {
+
+}
+
+size_t HighResBufferSegment::FullSampleCount() const {
+ if( channels > 0 ) {
+ return data_size/SAMPLE_SIZE/channels;
+ } else {
+ return 0;
+ }
+}
+
+size_t HighResBufferSegment::FlatSampleCount() const {
+ return data_size/SAMPLE_SIZE;
+}
+
+size_t HighResBufferSegment::SampleSize() const {
+ return SAMPLE_SIZE;
+}
+
diff --git a/Source/Sound/high_res_buffer_segment.h b/Source/Sound/high_res_buffer_segment.h
new file mode 100644
index 00000000..d73f4365
--- /dev/null
+++ b/Source/Sound/high_res_buffer_segment.h
@@ -0,0 +1,48 @@
+//-----------------------------------------------------------------------------
+// Name: high_res_buffer_segment.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/integer.h>
+
+#include <cmath>
+#include <cstring>
+
+class HighResBufferSegment
+{
+public:
+ HighResBufferSegment();
+
+ size_t FullSampleCount() const;
+ size_t FlatSampleCount() const;
+ size_t SampleSize() const;
+
+ static const size_t SAMPLE_SIZE = 4;
+ static const size_t BUF_SIZE = 4096*2*SAMPLE_SIZE;
+
+ size_t sample_rate;
+ size_t channels;
+
+ size_t data_size;
+ int error_code;
+ char buf[BUF_SIZE];
+};
diff --git a/Source/Sound/sound.cpp b/Source/Sound/sound.cpp
new file mode 100644
index 00000000..45114fb3
--- /dev/null
+++ b/Source/Sound/sound.cpp
@@ -0,0 +1,436 @@
+//-----------------------------------------------------------------------------
+// Name: sound.cpp
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: The sound class loads, manages, and plays sound effects
+// Not that this class might be run in a separate thread from the main
+// application, meaning thay any modifications have to be made with case, ensuring
+// that all calls to the outside application or third part libraries are thread-safe.
+//
+// Additionally, this class, and system takes ownership of OpenAL. Meaning that no other
+// Subsystem is allowed to directly communicate with the OpenAL API as it's not thread-safe.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include <Math/enginemath.h>
+#include <Math/vec3math.h>
+
+#include <Internal/datemodified.h>
+#include <Internal/timer.h>
+#include <Internal/error.h>
+#include <Internal/filesystem.h>
+#include <Internal/common.h>
+
+#include <Sound/sound.h>
+#include <Asset/Asset/soundgroup.h>
+#include <Physics/bulletworld.h>
+#include <GUI/gui.h>
+#include <Logging/logdata.h>
+#include <Threading/sdl_wrapper.h>
+
+#ifdef _WIN32
+#define NOMINMAX
+#include <windows.h>
+#endif
+
+#include <sstream>
+
+const bool kSoundOcclusionEnabled = false; // Disabled for now because of a problem with ambient triangles getting occluded (e.g. crowd on stucco arena)
+
+bool g_sound_enable_layered_soundtrack_limiter = false;
+
+//-----------------------------------------------------------------------------
+//Functions
+//-----------------------------------------------------------------------------
+
+#define REF_DISTANCE 1.0f
+//#define REF_DISTANCE 8.0f
+
+Sound::Sound(const char* preferred_device) :
+ m_last_game_time_scale(1.0f),
+ m_last_game_timestep(0.0f),
+ m_accum_game_timestep(0.0f),
+ m_audio(preferred_device, 1.0f, REF_DISTANCE),
+ m_soundtrack(0.5f),
+ whoosh_active_countdown(0)
+{
+ m_audio.set_distance_model(AL_INVERSE_DISTANCE_CLAMPED);
+ m_audio.subscribe(m_soundtrack);
+ pcg32_srandom_r(&play_group_rand_state, 0xDEADBEEF, 0xFACEFEED);
+}
+
+void Sound::Dispose() {
+ m_audio.Dispose();
+ m_soundtrack.Dispose();
+}
+
+void Sound::Update() {
+ for(unsigned i=0; i<ambient_triangles.size(); ++i){
+ AmbientTriangle& a_t = ambient_triangles[i];
+ for(unsigned j=0; j<3; ++j){
+ a_t.vol[j] = min(1.0f, a_t.vol[j] + m_accum_game_timestep);
+ SetVolume(a_t.handles[j], a_t.vol[j]);
+ }
+ }
+
+ if(m_last_game_time_scale == 1.0f){
+ m_audio.set_pitch(1.0f);
+ } else {
+ m_audio.set_pitch(m_last_game_time_scale);
+ }
+
+ m_audio.update(m_accum_game_timestep);
+
+ m_accum_game_timestep = 0.0f;
+
+ if( whoosh_active_countdown > 0 ) {
+ whoosh_active_countdown--;
+ if( whoosh_active_countdown == 0 ) {
+ setAirWhoosh(0);
+ }
+ }
+}
+
+void Sound::UpdateGameTimestep( float timestep )
+{
+ m_last_game_timestep = timestep;
+ m_accum_game_timestep += timestep;
+}
+
+void Sound::UpdateGameTimescale( float time_scale )
+{
+ m_last_game_time_scale = time_scale;
+}
+
+void Sound::updateListener(vec3 pos, vec3 vel, vec3 facing, vec3 up) {
+ m_audio.set_listener_position(pos);
+ m_audio.set_listener_velocity(vel);
+ m_audio.set_listener_orientation(facing, up);
+
+ //std::ostringstream oss;
+ //oss << "Ambient triangles: " << ambient_triangles.size();
+ //GUI::Instance()->AddDebugText("ambient_triangles", oss.str(), 0.5f);
+
+ for(unsigned i=0; i<ambient_triangles.size(); ++i){
+ AmbientTriangle& a_t = ambient_triangles[i];
+ for(unsigned j=0; j<3; ++j){
+ //vec3 new_dir(dot(a_t.rel_dir[j], cross(facing, up)),
+ // dot(a_t.rel_dir[j], up),
+ // dot(a_t.rel_dir[j], facing));
+ //SetPosition(a_t.handles[j],
+ // new_dir);
+ //printf("%f %f %f\n", new_dir[0], new_dir[1], new_dir[2]);
+ SetPosition(a_t.handles[j],
+ pos+a_t.rel_dir[j]);
+ SetVelocity(a_t.handles[j],
+ vel);
+ //DebugDraw::Instance()->AddWireSphere(pos+a_t.rel_dir[j], 0.3f, vec3(1.0f), _delete_on_update);
+ }
+ }
+}
+
+unsigned long Sound::CreateHandle()
+{
+ return m_audio.get_next_handle();
+}
+
+void Sound::Play(const unsigned long& handle, SoundPlayInfo spi) {
+ char abs_path[kPathSize];
+ if(FindFilePath(spi.path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths)==-1) {
+ std::string error_text = "Could not find sound file: "+std::string(spi.path);
+ DisplayError("Error",error_text.c_str());
+ }
+
+ /*
+ * Disabled until we're done threading and can move this out of this class.
+ if(kSoundOcclusionEnabled){
+ if(!(spi.flags & SoundFlags::kRelative)){
+ const vec3 &listener_pos = m_audio.listener_pos;
+ bool occluded = false;
+ if(bullet_world){
+ occluded = bullet_world->CheckRayCollision(listener_pos, spi.occlusion_position);
+ }
+ if(occluded){
+ spi.volume_mult *= _occluded_sound_mult;
+ }
+ }
+ }
+ */
+
+ if(spi.volume >= 0.0){
+ m_audio.play(handle,spi);
+ } else {
+ const int kBufSize = 256;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "Playing sound with negative volume: %s", spi.path.c_str());
+ DisplayError("Error",buf);
+ }
+}
+
+void Sound::PlayGroup(const unsigned long& handle, SoundGroupPlayInfo& sgpi) {
+ //DebugDraw::Instance()->AddWireSphere(pos, 0.1f, vec4(1.0f), _persistent);
+ //SoundGroupRef sgr = SoundGroups::Instance()->ReturnRef(sgpi.path);
+ SoundPlayInfo sound_info;
+ if(sgpi.specific_path.empty()){
+ sound_info.path = sgpi.GetARandomOrderedSoundPath(&play_group_rand_state);
+ } else {
+ sound_info.path = sgpi.specific_path;
+ }
+ sound_info.position = sgpi.position;
+ sound_info.occlusion_position = sgpi.occlusion_position;
+ sound_info.volume = sgpi.volume*sgpi.gain;
+ sound_info.pitch = 1.0f*sgpi.pitch_shift;
+ sound_info.created_by_id = sgpi.created_by_id;
+ sound_info.filter_info = sgpi.filter_info;
+ sound_info.max_gain = sgpi.max_gain;
+ sound_info.priority = sgpi.priority;
+ sound_info.flags = sgpi.flags;
+ sound_info.max_distance = sgpi.max_distance;
+ sound_info.play_past_tick = sgpi.play_past_tick;
+ return Play(handle, sound_info);
+}
+
+void Sound::setAirWhoosh(float amount, float pitch) {
+ whoosh_active_countdown = 60;
+ amount = max(0.0f,min(1.0f,amount));
+
+ if (m_air_whoosh_sound.get() == NULL) {
+ m_air_whoosh_sound = ambient_ptr(new ambientSound(amount));
+ SoundPlayInfo spi;
+ spi.path = "Data/Sounds/fall_whoosh.wav";
+ spi.looping = true;
+ unsigned long handle = m_audio.get_next_handle();
+ m_audio.play(handle, spi, m_air_whoosh_sound.get());
+ } else {
+ m_air_whoosh_sound->SetVolume(amount);
+ m_air_whoosh_sound->SetPitch(pitch);
+ }
+}
+
+std::vector<AudioEmitter*> Sound::GetActiveSounds()
+{
+ return m_audio.GetActiveSounds();
+}
+
+/*
+std::string Sound::GetGroupPath(const std::string &path){
+ SoundGroupRef sgr = SoundGroups::Instance()->ReturnRef(path);
+ return sgr->GetSoundPath();
+}
+*/
+
+
+bool Sound::IsHandleValid( const unsigned long &handle ) {
+ return m_audio.IsHandleValid(handle);
+}
+
+void Sound::SetPosition( const unsigned long &handle, const vec3 &new_pos ) {
+ m_audio.SetPosition(handle, new_pos);
+}
+
+void Sound::TranslatePosition( const unsigned long &handle, const vec3 &trans ) {
+ m_audio.TranslatePosition(handle, trans);
+}
+
+void Sound::SetOcclusionPosition( const unsigned long &handle, const vec3 &new_pos ) {
+ m_audio.SetOcclusionPosition(handle, new_pos);
+}
+
+void Sound::SetVelocity( const unsigned long &handle, const vec3 &new_vel ) {
+ m_audio.SetVelocity(handle, new_vel);
+}
+
+const vec3 Sound::GetPosition( const unsigned long &handle ) {
+ return m_audio.GetPosition(handle);
+}
+
+void Sound::SetPitch( const unsigned long &handle, float pitch ) {
+ m_audio.SetPitch(handle, pitch);
+}
+
+void Sound::SetVolume( const unsigned long &handle, float volume ) {
+ m_audio.SetVolume(handle, volume);
+}
+
+void Sound::Stop( const unsigned long &handle ) {
+ m_audio.Stop(handle);
+}
+
+void Sound::AddMusic( const Path &path ) {
+ m_soundtrack.AddMusic( path );
+}
+
+void Sound::RemoveMusic( const Path &path ) {
+ m_soundtrack.RemoveMusic( path );
+}
+
+void Sound::QueueSegment( const std::string& string ) {
+ m_soundtrack.QueueSegment( string );
+}
+
+void Sound::TransitionToSegment( const std::string& string ) {
+ m_soundtrack.TransitionIntoSegment( string );
+}
+
+void Sound::SetSegment( const std::string& string ) {
+ m_soundtrack.SetSegment( string );
+}
+
+void Sound::TransitionToSong( const std::string& string ) {
+ m_soundtrack.TransitionToSong( string );
+}
+
+void Sound::SetSong( const std::string& string ) {
+ m_soundtrack.SetSong( string );
+}
+
+void Sound::SetLayerGain(const std::string& layer, float v) {
+ m_soundtrack.SetLayerGain(layer,v);
+}
+
+const std::string Sound::GetSegment() const
+{
+ return m_soundtrack.GetSegment();
+}
+
+const std::map<std::string,float> Sound::GetLayerGains() {
+ return m_soundtrack.GetLayerGains();
+}
+
+const std::vector<std::string> Sound::GetLayerNames() const {
+ return m_soundtrack.GetLayerNames();
+}
+
+const std::string Sound::GetSongName() const
+{
+ return m_soundtrack.GetSongName();
+}
+
+const std::string Sound::GetSongType() const
+{
+ return m_soundtrack.GetSongType();
+}
+
+const std::string Sound::GetNextSongName() const
+{
+ return m_soundtrack.GetNextSongName();
+}
+
+const std::string Sound::GetNextSongType() const
+{
+ return m_soundtrack.GetNextSongType();
+}
+
+void Sound::SetMusicVolume( float val ) {
+ m_soundtrack.SetVolume(val);
+}
+
+void Sound::SetMasterVolume( float val ) {
+ m_audio.set_master_volume(val);
+}
+
+void Sound::AddAmbientTriangle( const std::string &path ) {
+ char paths[3][kPathSize];
+ for(unsigned i=0; i<3; ++i){
+ char abs_path[kPathSize];
+ FormatString(paths[i], kPathSize, "%s_%d.wav", path.c_str(), i+1);
+ if(FindFilePath(paths[i], abs_path, kPathSize, kDataPaths | kModPaths) == -1){
+ FatalError("Error","Could not find sound: %s", paths[i]);
+ }
+ }
+ ambient_triangles.resize(ambient_triangles.size()+1);
+ AmbientTriangle &ambient_triangle = ambient_triangles.back();
+ ambient_triangle.path = path;
+ ambient_triangle.rel_dir[0] = vec3(1.0f,0.0f,0.0f);
+ ambient_triangle.rel_dir[1] = vec3(-0.5f,0.0f,0.866f);
+ ambient_triangle.rel_dir[2] = vec3(-0.5f,0.0f,-0.866f);
+ SoundPlayInfo spi;
+ spi.looping = true;
+ spi.volume = 0.0f;
+ spi.flags = spi.flags | SoundFlags::kNoOcclusion;
+ for(unsigned i=0; i<3; ++i){
+ spi.path = paths[i];
+ spi.position = ambient_triangle.rel_dir[i];
+ spi.occlusion_position = spi.position;
+ unsigned long handle = CreateHandle();
+ Play(handle,spi);
+ ambient_triangle.handles[i] = handle;
+ ambient_triangle.vol[i] = 0.0f;
+ }
+}
+
+const std::vector<AmbientTriangle> & Sound::GetAmbientTriangles() {
+ return ambient_triangles;
+}
+
+void Sound::ClearTransient() {
+ m_audio.StopAllTransient();
+ ambient_triangles.clear();
+}
+
+void Sound::Clear() {
+ m_audio.StopAll();
+ ambient_triangles.clear();
+}
+
+/*
+ * For this function to work correctly in the future it'll have to use the bullet_world reference indirectly
+ * (as the sound system is run in a separate thread).
+ *
+void Sound::UpdateSoundOcclusion(void (OcclusionCheck*) (int sound_uid, vec3 from, vec3 to)) {
+ if(kSoundOcclusionEnabled){
+ const vec3 &listener_pos = m_audio.listener_pos;
+
+ std::vector<AudioEmitter*> sounds = m_audio.GetActiveSounds();
+
+ for(unsigned i=0; i<sounds.size(); ++i){
+ if(!(sounds[i]->flags() & SoundFlags::kNoOcclusion)){
+ const vec3& sound_pos = sounds[i]->GetOcclusionPosition();
+ bool occluded = bullet_world->CheckRayCollision(listener_pos, sound_pos);
+ if(occluded){
+ sounds[i]->SetVolumeMult(_occluded_sound_mult);
+ } else {
+ sounds[i]->SetVolumeMult(1.0f);
+ }
+ }
+ }
+ }
+}
+*/
+
+void Sound::LoadSoundFile( const std::string & path ) {
+ FilterInfo filter_info;
+ m_audio.load(path, filter_info);
+}
+
+std::string Sound::GetPreferredDevice() {
+ return m_audio.GetPreferredDevice();
+}
+
+std::string Sound::GetUsedDevice() {
+ return m_audio.GetUsedDevice();
+}
+
+std::vector<std::string> Sound::GetAvailableDevices() {
+ return m_audio.GetAvailableDevices();
+}
+
+void Sound::EnableLayeredSoundtrackLimiter(bool val) {
+ g_sound_enable_layered_soundtrack_limiter = val;
+}
diff --git a/Source/Sound/sound.h b/Source/Sound/sound.h
new file mode 100644
index 00000000..8b425d4b
--- /dev/null
+++ b/Source/Sound/sound.h
@@ -0,0 +1,155 @@
+//-----------------------------------------------------------------------------
+// Name: sound.h
+// Developer: Wolfire Games LLC
+// Author: David Rosen
+// Description: The sound class loads, manages, and plays sound effects
+// Not that this class might be run in a separate thread from the main
+// application, meaning thay any modifications have to be made with case, ensuring
+// that all calls to the outside application or third part libraries are thread-safe.
+//
+// Additionally, this class, and system takes ownership of OpenAL. Meaning that no other
+// Subsystem is allowed to directly communicate with the OpenAL API as it's not thread-safe.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/enginemath.h>
+
+#include <Sound/al_audio.h>
+#include <Sound/ambient_sound.h>
+#include <Sound/soundtrack.h>
+#include <Asset/Asset/soundgroup.h>
+#include <Sound/ambienttriangle.h>
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <memory>
+#include <map>
+
+//-----------------------------------------------------------------------------
+// Class Definition
+//-----------------------------------------------------------------------------
+
+class BulletWorld;
+
+const float _occluded_sound_mult = 0.5f;
+
+extern bool g_sound_enable_layered_soundtrack_limiter;
+
+class Sound {
+ public:
+ Sound(const char* preferred_device);
+
+ void Update();
+ void UpdateGameTimestep( float timestep );
+ void UpdateGameTimescale( float time_scale );
+
+ void setAirWhoosh(float amount, float pitch = 1.0f);
+
+ void updateListener(vec3 pos, vec3 vel, vec3 facing, vec3 up);
+
+ unsigned long CreateHandle();
+
+ void Play(const unsigned long &handle, SoundPlayInfo spi);
+
+ void PlayGroup(const unsigned long &handle, SoundGroupPlayInfo& sgpi);
+ const vec3 GetPosition(const unsigned long &handle);
+ void SetPosition(const unsigned long &handle, const vec3 &new_pos);
+ //Safer variant of SetPosition(GetPosition()+translation) as sound can be threaded and
+ //there's a potential delay of the position being updated.
+ void TranslatePosition(const unsigned long& handle, const vec3 &movement);
+ void SetVelocity(const unsigned long &handle, const vec3 &new_vel);
+ void SetPitch(const unsigned long &handle, float pitch);
+ void SetVolume(const unsigned long &handle, float volume);
+ void Stop(const unsigned long &handle);
+ bool IsHandleValid(const unsigned long &handle);
+
+ void AddMusic(const Path& path);
+ void RemoveMusic(const Path& path);
+
+ void QueueSegment(const std::string& string);
+ void TransitionToSegment(const std::string& string);
+ void TransitionToSong(const std::string& string);
+
+ void SetSegment(const std::string& string);
+ void SetSong(const std::string& string);
+ void SetLayerGain(const std::string& layer, float v);
+
+ const std::string GetSegment() const;
+ const std::map<std::string,float> GetLayerGains();
+ const std::vector<std::string> GetLayerNames() const;
+ const std::string GetSongName() const;
+ const std::string GetSongType() const;
+
+ const std::string GetNextSongName() const;
+ const std::string GetNextSongType() const;
+
+ void AddAmbientTriangle(const std::string &path);
+ const std::vector<AmbientTriangle> &GetAmbientTriangles();
+ void Clear();
+ //Clear all sounds that are considered transient (that is sound effects in a level and not music)
+ //This is called when a level is unloaded, or loaded. Basically between major state changes.
+ void ClearTransient();
+
+ std::vector<AudioEmitter*> GetActiveSounds();
+ //alAudio &getAlAudio();
+ //void changeSoundtrack(char *name, char *type)
+
+ void Dispose();
+ void SetMusicVolume( float val );
+ void SetMasterVolume( float val );
+
+ //void UpdateSoundOcclusion( );
+ //void SetSoundOcclusionState( int id, bool occluded );
+ void SetOcclusionPosition( const unsigned long &handle, const vec3 &new_pos );
+ void LoadSoundFile( const std::string & path );
+
+ void SetPreferredDevice(const std::string& name);
+ std::string GetPreferredDevice();
+
+ void SetUsedDevice(const std::string& name);
+ std::string GetUsedDevice();
+
+ void SetAvailableDevices(std::vector<std::string> devices);
+ std::vector<std::string> GetAvailableDevices();
+
+ void EnableLayeredSoundtrackLimiter(bool val);
+ private:
+
+ //Value containint the games time_scale value.
+ float m_last_game_time_scale;
+ //Value containing the latests game thread update timestep.
+ float m_last_game_timestep;
+ //In case the game does multiple frames on one audio frame, we will know the total amount
+ //of time passed since last sound update in the main game thread.
+ float m_accum_game_timestep;
+
+ alAudio m_audio;
+
+ Soundtrack m_soundtrack;
+
+ typedef std::auto_ptr<ambientSound> ambient_ptr;
+ ambient_ptr m_air_whoosh_sound;
+ int whoosh_active_countdown; //Kill whoosh if not updated in given frameperiod.
+
+ std::vector<AmbientTriangle> ambient_triangles;
+
+ pcg32_random_t play_group_rand_state;
+};
diff --git a/Source/Sound/soundlogging.h b/Source/Sound/soundlogging.h
new file mode 100644
index 00000000..98e44662
--- /dev/null
+++ b/Source/Sound/soundlogging.h
@@ -0,0 +1,55 @@
+//-----------------------------------------------------------------------------
+// Name: soundlogging.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Logging/logdata.h>
+
+#if LOG_LEVEL > 0
+#undef LOGF
+#define LOGF LogSystem::LogData( LogSystem::fatal, "al",__FILE__,__LINE__)
+#endif
+
+#if LOG_LEVEL > 1
+#undef LOGE
+#define LOGE LogSystem::LogData( LogSystem::error, "al",__FILE__,__LINE__)
+#endif
+
+#if LOG_LEVEL > 2
+#undef LOGW
+#define LOGW LogSystem::LogData( LogSystem::warning, "al",__FILE__,__LINE__)
+#endif
+
+#if LOG_LEVEL > 3
+#undef LOGI
+#define LOGI LogSystem::LogData( LogSystem::info, "al",__FILE__,__LINE__)
+#endif
+
+#if LOG_LEVEL > 4
+#undef LOGD
+#define LOGD LogSystem::LogData( LogSystem::debug, "al",__FILE__,__LINE__)
+#endif
+
+#if LOG_LEVEL > 5
+#undef LOGS
+#define LOGS LogSystem::LogData( LogSystem::spam, "al",__FILE__,__LINE__)
+#endif
diff --git a/Source/Sound/soundplayinfo.cpp b/Source/Sound/soundplayinfo.cpp
new file mode 100644
index 00000000..ccf8ca24
--- /dev/null
+++ b/Source/Sound/soundplayinfo.cpp
@@ -0,0 +1,97 @@
+//-----------------------------------------------------------------------------
+// Name: soundplayinfo.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "soundplayinfo.h"
+
+#include <Threading/rand.h>
+#include <Threading/sdl_wrapper.h>
+
+#include <Math/enginemath.h>
+#include <Logging/logdata.h>
+
+#include <sstream>
+
+SoundPlayInfo::SoundPlayInfo(): position(0.0f),
+ occlusion_position(0.0f),
+ pitch(1.0f),
+ volume(1.0f),
+ volume_mult(1.0f),
+ max_gain(1.0f),
+ max_distance(30.0f),
+ looping(false),
+ flags(0),
+ priority(_default_priority),
+ created_by_id(-1),
+ play_past_tick(0)
+{
+}
+
+SoundGroupPlayInfo::SoundGroupPlayInfo( const SoundGroup& sg, const vec3 &_pos ) :
+ position(_pos),
+ occlusion_position(_pos),
+ gain(1.0f),
+ max_gain(1.0f),
+ volume_mult(1.0f),
+ pitch_shift(1.0f),
+ path(sg.GetPath()),
+ created_by_id(-1),
+ flags(0),
+ priority(_sound_priority_low),
+ num_variants(sg.GetNumVariants()),
+ volume(sg.GetVolume()),
+ max_distance(sg.GetMaxDistance()),
+ play_past_tick()
+{
+ SetNumVariants(num_variants);
+ play_past_tick = SDL_TS_GetTicks() + 1000 * (unsigned int)sg.GetDelay();
+ sound_base_path = path.substr(0,path.size()-4);
+}
+
+std::string SoundGroupPlayInfo::GetARandomOrderedSoundPath( pcg32_random_t* reseed )
+{
+ const char* fstring = "%s_%d.wav";
+ char target_s[kPathSize];
+
+ int i = 0;
+ if( num_variants > 0 ) {
+ i = pcg32_random_r(reseed) % num_variants;
+
+ if( previous_sound_id == i ) {
+ i = (i + 1) % num_variants;
+ }
+ }
+
+ previous_sound_id = i;
+
+ FormatString(target_s, kPathSize, fstring, sound_base_path.c_str(), i+1);
+
+ return std::string(target_s);
+}
+
+void SoundGroupPlayInfo::SetNumVariants(int _num_variants)
+{
+ num_variants = _num_variants;
+}
+
+const std::string& SoundGroupPlayInfo::GetPath() const {
+ return path;
+}
diff --git a/Source/Sound/soundplayinfo.h b/Source/Sound/soundplayinfo.h
new file mode 100644
index 00000000..c69a7a63
--- /dev/null
+++ b/Source/Sound/soundplayinfo.h
@@ -0,0 +1,108 @@
+//-----------------------------------------------------------------------------
+// Name: soundplayinfo.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+#include <Internal/integer.h>
+#include <Asset/Asset/soundgroup.h>
+#include <Utility/pcg_basic.h>
+
+#include <string>
+
+enum FilterType {
+ _simple_filter = 0,
+ _convolution_filter = 1
+};
+
+enum SoundPriority {
+ _sound_priority_max = 0,
+ _sound_priority_very_high = 1,
+ _sound_priority_high = 2,
+ _sound_priority_med = 3,
+ _sound_priority_low = 4};
+
+const unsigned char _default_priority = _sound_priority_med;
+
+struct FilterInfo {
+ std::string path;
+ float wet;
+ FilterType type;
+ FilterInfo():wet(0.0f)
+ {}
+};
+
+struct SoundPlayInfo {
+ vec3 position;
+ vec3 occlusion_position;
+ float pitch;
+ float volume;
+ float volume_mult;
+ float max_gain;
+ float max_distance;
+ bool looping;
+ uint8_t flags;
+ unsigned char priority;
+ std::string path;
+ int created_by_id;
+ FilterInfo filter_info;
+
+ unsigned int play_past_tick;
+
+ SoundPlayInfo();
+};
+
+struct SoundGroupPlayInfo {
+ vec3 position;
+ vec3 occlusion_position;
+ float gain;
+ float max_gain;
+ float volume_mult;
+ float pitch_shift;
+private:
+ std::string path;
+ int previous_sound_id;
+public:
+ std::string specific_path;
+ std::string sound_base_path;
+ int created_by_id;
+ uint8_t flags;
+ unsigned char priority;
+ FilterInfo filter_info;
+
+ //Values from SoundGroup
+ float volume;
+ float max_distance;
+
+ SoundGroupPlayInfo(const SoundGroup& sg, const vec3 &_pos);
+
+ std::string GetARandomOrderedSoundPath(pcg32_random_t* reseed);
+
+ void SetNumVariants(int _num_variants);
+ inline int GetNumVariants() { return num_variants; };
+
+ unsigned int play_past_tick;
+
+ const std::string& GetPath() const;
+private:
+ int num_variants;
+};
diff --git a/Source/Sound/soundtrack.cpp b/Source/Sound/soundtrack.cpp
new file mode 100644
index 00000000..add5d819
--- /dev/null
+++ b/Source/Sound/soundtrack.cpp
@@ -0,0 +1,1448 @@
+//-----------------------------------------------------------------------------
+// Name: soundtrack.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "soundtrack.h"
+
+#include <Sound/Loader/void_loader.h>
+#include <Sound/Loader/ogg_loader.h>
+
+#include <Logging/logdata.h>
+#include <Utility/strings.h>
+
+#include <algorithm>
+#include <cmath>
+
+static void ToHighResBufferSegment( HighResBufferSegment& out, const BufferSegment& in ) {
+ out.sample_rate = in.sample_rate;
+ out.channels = in.channels;
+ out.data_size = in.FlatSampleCount() * out.SampleSize();
+
+ for( size_t i = 0; i < in.FlatSampleCount(); i++ ) {
+ int32_t hbuf;
+ char* hbuf_c = (char*)&hbuf;
+ int16_t lbuf;
+ char* lbuf_c = (char*)&lbuf;
+
+ lbuf_c[0] = in.buf[i*2+0];
+ lbuf_c[1] = in.buf[i*2+1];
+
+ hbuf = lbuf;
+
+ out.buf[i*4+0] = hbuf_c[0];
+ out.buf[i*4+1] = hbuf_c[1];
+ out.buf[i*4+2] = hbuf_c[2];
+ out.buf[i*4+3] = hbuf_c[3];
+ }
+}
+
+static void ToBufferSegment( BufferSegment& out, const HighResBufferSegment& in ) {
+ out.sample_rate = in.sample_rate;
+ out.channels = in.channels;
+ out.data_size = in.FlatSampleCount() * out.SampleSize();
+ for( size_t i = 0; i < in.FlatSampleCount(); i++ ) {
+ int32_t hbuf;
+ char* hbuf_c = (char*)&hbuf;
+ int16_t lbuf;
+ char* lbuf_c = (char*)&lbuf;
+
+ hbuf_c[0] = in.buf[i*4+0];
+ hbuf_c[1] = in.buf[i*4+1];
+ hbuf_c[2] = in.buf[i*4+2];
+ hbuf_c[3] = in.buf[i*4+3];
+
+ lbuf = hbuf;
+
+ out.buf[i*2+0] = lbuf_c[0];
+ out.buf[i*2+1] = lbuf_c[1];
+ }
+}
+
+PlayedInterface::PlayedInterface( Soundtrack* _owner ) : owner(_owner) {
+
+}
+
+PlayedSongInterface::PlayedSongInterface( Soundtrack* _owner ) : PlayedInterface(_owner), song() {
+
+}
+
+PlayedSongInterface::PlayedSongInterface(Soundtrack* _owner, const MusicXMLParser::Song& _song) : PlayedInterface(_owner), song(_song) {
+
+}
+
+const std::string PlayedSongInterface::GetSongName() const {
+ return std::string(song.name);
+}
+
+const std::string PlayedSongInterface::GetSongType() const {
+ return std::string(song.type);
+}
+
+const MusicXMLParser::Song& PlayedSongInterface::GetSong() const {
+ return song;
+}
+
+Soundtrack::PlayedSegment::PlayedSegment(Soundtrack* _owner ) :
+PlayedInterface(_owner),
+data(rc_baseLoader(new voidLoader()) ),
+overlapped_transition(false),
+started(false) {
+}
+
+Soundtrack::PlayedSegment::PlayedSegment( Soundtrack* _owner, const MusicXMLParser::Segment& _segment, bool overlapped_transition ) :
+PlayedInterface(_owner),
+data(rc_baseLoader(new voidLoader()) ),
+segment( _segment ),
+overlapped_transition( overlapped_transition ),
+started(false) {
+ if( strlen( segment.path ) > 0 ) {
+ Path p = FindFilePath( segment.path, kAnyPath );
+ if( p.isValid() ) {
+ data = rc_baseLoader(new oggLoader(p));
+ } else {
+ LOGE << "Segment " << segment << " doesn't refer to a valid resource" << std::endl;
+ }
+ }
+}
+
+void Soundtrack::PlayedSegment::update( HighResBufferSegment* out ) {
+ int result = 1;
+
+ BufferSegment buffer;
+
+ buffer.channels = data->get_channels();
+ buffer.sample_rate = data->get_sample_rate();
+
+ while( result > 0 && buffer.data_size < buffer.BUF_SIZE ) {
+ result = data->stream_buffer_int16( buffer.buf + buffer.data_size, (int) (buffer.BUF_SIZE - buffer.data_size) );
+
+ if( result > 0 ) {
+ buffer.data_size += (size_t)result;
+ } else if( result < 0 ) {
+ LOGE << "Got error from underlying stream_buffer_int16 call" << std::endl;
+ buffer.error_code = result;
+ } else {
+ //End of stream.
+ }
+ }
+
+ ToHighResBufferSegment(*out, buffer);
+}
+
+const MusicXMLParser::Segment& Soundtrack::PlayedSegment::GetSegment()
+{
+ return segment;
+}
+
+bool Soundtrack::PlayedSegment::IsAtEnd()
+{
+ return data->is_at_end();
+}
+
+void Soundtrack::PlayedSegment::Rewind()
+{
+ data->rewind();
+}
+
+int64_t Soundtrack::PlayedSegment::GetPCMPos()
+{
+ return data->get_pcm_pos();
+}
+
+void Soundtrack::PlayedSegment::SetPCMPos( int64_t pos )
+{
+ data->set_pcm_pos(pos);
+}
+
+int64_t Soundtrack::PlayedSegment::GetPCMCount()
+{
+ return data->get_sample_count();
+}
+
+int Soundtrack::PlayedSegment::SampleRate()
+{
+ return data->get_sample_rate();
+}
+
+int Soundtrack::PlayedSegment::Channels()
+{
+ return data->get_channels();
+}
+
+const std::string Soundtrack::PlayedSegment::GetSegmentName() const
+{
+ return segment.name;
+}
+
+Soundtrack::PlayedSegmentedSong::PlayedSegmentedSong(Soundtrack* _owner, const MusicXMLParser::Song& _song) :
+PlayedSongInterface(_owner, _song),
+currentSegment(_owner, _song.GetStartSegment(), false),
+mixer(TransitionMixer::Linear,1.0f)
+{
+
+}
+
+Soundtrack::PlayedSegmentedSong::~PlayedSegmentedSong( ) {
+}
+
+
+void Soundtrack::PlayedSegmentedSong::SetGain(float v) {
+ //nil
+}
+
+float Soundtrack::PlayedSegmentedSong::GetGain() {
+ return 1.0f;
+}
+
+void Soundtrack::PlayedSegmentedSong::update( HighResBufferSegment* buffer ) {
+ //If we _just_ started playing this song, set position of segment to be what it was last this song played.
+ if( currentSegment.started == false ) {
+ currentSegment.started = true;
+ }
+
+ //Aligned transition is a quick transition where the next song is set to the same raw position
+ //And we mix together the two buffers.
+ if( segmentQueue.size() > 0 && segmentQueue.front().overlapped_transition )
+ {
+ HighResBufferSegment first;
+ HighResBufferSegment second;
+
+ if( segmentQueue.front().started == false )
+ {
+ int64_t pos = currentSegment.GetPCMPos();
+ segmentQueue.front().SetPCMPos(pos);
+ segmentQueue.front().started = true;
+ mixer.Reset();
+ }
+
+ currentSegment.update(&first);
+ segmentQueue.front().update(&second);
+
+ if( currentSegment.IsAtEnd() || segmentQueue.front().IsAtEnd() )
+ {
+ currentSegment.Rewind();
+ segmentQueue.front().Rewind();
+ }
+
+ //If we're done transitioning, go to next.
+ if( mixer.Step( buffer, &first, &second ) )
+ {
+ currentSegment = segmentQueue.front();
+ segmentQueue.pop_front();
+ }
+ }
+ else
+ {
+ currentSegment.update(buffer);
+
+ if( currentSegment.IsAtEnd() )
+ {
+ if( segmentQueue.size() > 0 )
+ {
+ currentSegment = segmentQueue.front();
+ segmentQueue.pop_front();
+ }
+ else
+ {
+ currentSegment.Rewind();
+ }
+ }
+ }
+
+ if( buffer->data_size == 0 ) {
+ LOGE << "Buffer empty in PlayedSegmentedSong" << std::endl;
+ }
+
+}
+
+bool Soundtrack::PlayedSegmentedSong::QueueSegment( const MusicXMLParser::Segment& nextSeg )
+{
+ if( (segmentQueue.empty() && currentSegment.GetSegment() != nextSeg ) || (segmentQueue.size() > 0 && segmentQueue.back().GetSegment() != nextSeg) )
+ {
+ LOGI << "Queueing segment " << nextSeg << std::endl;
+
+ while( segmentQueue.size() > 1 )
+ {
+ segmentQueue.pop_back();
+ }
+
+ segmentQueue.push_back(PlayedSegment(owner,nextSeg,false));
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ return false;
+}
+
+
+bool Soundtrack::PlayedSegmentedSong::TransitionIntoSegment( const MusicXMLParser::Segment& newSeg )
+{
+ if( (segmentQueue.empty() && currentSegment.GetSegment() != newSeg ) || (segmentQueue.size() > 0 && segmentQueue.back().GetSegment() != newSeg) )
+ {
+ LOGI << "Transitioning into segment " << newSeg << std::endl;
+ while( segmentQueue.size() > 1 )
+ {
+ segmentQueue.pop_back();
+ }
+
+ segmentQueue.push_back(PlayedSegment(owner, newSeg,true));
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+
+}
+
+bool Soundtrack::PlayedSegmentedSong::SetSegment( const MusicXMLParser::Segment& nextSeg ) {
+ LOGI << "Setting segment " << nextSeg << std::endl;
+ segmentQueue.clear();
+ currentSegment = PlayedSegment(owner, nextSeg,false);
+ return true;
+}
+
+const std::string Soundtrack::PlayedSegmentedSong::GetSegmentName() const {
+ return currentSegment.GetSegmentName();
+}
+
+int Soundtrack::PlayedSegmentedSong::SampleRate() {
+ return currentSegment.SampleRate();
+}
+
+int Soundtrack::PlayedSegmentedSong::Channels() {
+ return currentSegment.Channels();
+}
+
+void Soundtrack::PlayedSegmentedSong::Rewind() {
+ //Nil
+}
+
+bool Soundtrack::PlayedSegmentedSong::IsAtEnd() {
+ return false;
+}
+
+void Soundtrack::PlayedSegmentedSong::SetPCMPos( int64_t c ) {
+ currentSegment.SetPCMPos(c);
+}
+
+int64_t Soundtrack::PlayedSegmentedSong::GetPCMPos() {
+ return currentSegment.GetPCMPos();
+}
+
+int64_t Soundtrack::PlayedSegmentedSong::GetPCMCount() {
+ return currentSegment.GetPCMCount();
+}
+
+Soundtrack::PlayedSingleSong::PlayedSingleSong(Soundtrack* _owner) :
+PlayedSongInterface(_owner),
+data(rc_baseLoader(new voidLoader()) ),
+started(false),
+target_gain(1.0f),
+current_gain(0.0f),
+from_gain(0.0f),
+current_volume_step(0),
+volume_change_per_second(1.0f)
+{
+
+}
+
+Soundtrack::PlayedSingleSong::PlayedSingleSong(Soundtrack* _owner ,const MusicXMLParser::Song& _song ) :
+PlayedSongInterface(_owner, _song),
+data(rc_baseLoader(new voidLoader())),
+started(false) ,
+target_gain(1.0f),
+from_gain(0.0f),
+current_gain(0.0f),
+current_volume_step(0),
+volume_change_per_second(1.0f)
+{
+ if( strlen(song.file_path) > 0 ) {
+ Path p = FindFilePath( song.file_path, kAnyPath );
+ if( p.isValid() ) {
+ data = rc_baseLoader(new oggLoader(p));
+ } else {
+ LOGE << "Segment " << song << " doesn't refer to a valid resource" << std::endl;
+ }
+ }
+}
+
+void Soundtrack::PlayedSingleSong::update( HighResBufferSegment* buffer ) {
+ int result = 1;
+
+ BufferSegment songbuf;
+ songbuf.channels = data->get_channels();
+ songbuf.sample_rate = data->get_sample_rate();
+
+ while( result > 0 && songbuf.data_size < songbuf.BUF_SIZE ) {
+ result = data->stream_buffer_int16( songbuf.buf + songbuf.data_size, (int)(songbuf.BUF_SIZE - songbuf.data_size) );
+
+ if( result > 0 ) {
+ songbuf.data_size += (size_t)result;
+ } else if( result < 0 ) {
+ LOGE << "Got error from underlying stream_buffer_int16 call" << std::endl;
+ songbuf.error_code = result;
+ } else {
+ //End of stream.
+ }
+ }
+
+ if( songbuf.data_size > 0 ) {
+ const size_t start = current_volume_step;
+ //How many bytes for a second of transition times the time of transition.
+ const size_t end = (size_t) std::abs(((from_gain-target_gain)*volume_change_per_second) * songbuf.sample_rate * songbuf.channels * 2);
+
+ for( size_t i = 0; i < songbuf.data_size/songbuf.channels; i += 2 ) {
+ float d = 1.0f;
+
+ if( end > 0 ) {
+ d = (start+i*songbuf.channels)/(float)end;
+ if( d > 1.0f ) {
+ d = 1.0f;
+ }
+ if( d < 0.0f ) {
+ d = 0.0f;
+ }
+ }
+
+ current_gain = from_gain*(1.0f-d)+target_gain*d;
+
+ union {
+ int16_t full;
+ char part[2];
+ } cast;
+
+ for( size_t c = 0; c < (size_t)songbuf.channels; c++ ) {
+ cast.part[0] = songbuf.buf[i*songbuf.channels+(c*2)+0];
+ cast.part[1] = songbuf.buf[i*songbuf.channels+(c*2)+1];
+
+ cast.full = (int16_t) (cast.full * current_gain);
+
+ songbuf.buf[i*songbuf.channels+(c*2)+0] = cast.part[0];
+ songbuf.buf[i*songbuf.channels+(c*2)+1] = cast.part[1];
+ }
+ }
+
+ current_volume_step += songbuf.data_size;
+ if( (size_t)current_volume_step > end ) {
+ current_volume_step = end;
+ from_gain = target_gain;
+ }
+ }
+
+ if( IsAtEnd() ) {
+ Rewind();
+ }
+
+ ToHighResBufferSegment( *buffer, songbuf );
+}
+
+bool Soundtrack::PlayedSingleSong::IsAtEnd() {
+ return data->is_at_end();
+}
+
+void Soundtrack::PlayedSingleSong::Rewind() {
+ data->rewind();
+}
+
+int64_t Soundtrack::PlayedSingleSong::GetPCMPos() {
+ return data->get_pcm_pos();
+}
+
+void Soundtrack::PlayedSingleSong::SetPCMPos( int64_t pos ) {
+ data->set_pcm_pos(pos);
+}
+
+int64_t Soundtrack::PlayedSingleSong::GetPCMCount() {
+ return data->get_sample_count();
+}
+
+void Soundtrack::PlayedSingleSong::SetGain( float v ) {
+ from_gain = current_gain;
+ target_gain = v;
+}
+
+float Soundtrack::PlayedSingleSong::GetGain() {
+ return current_gain;
+}
+
+int Soundtrack::PlayedSingleSong::SampleRate() {
+ return data->get_sample_rate();
+}
+
+int Soundtrack::PlayedSingleSong::Channels() {
+ return data->get_channels();
+}
+
+Soundtrack::PlayedLayeredSong::PlayedLayeredSong( Soundtrack* _owner ) :
+PlayedSongInterface(_owner),
+pcm_count(0),
+pcm_pos(0),
+sample_rate(0),
+channels(0) {
+
+}
+
+Soundtrack::PlayedLayeredSong::PlayedLayeredSong(Soundtrack* _owner, const MusicXMLParser::Song& _song) :
+PlayedSongInterface(_owner, _song),
+pcm_pos(0),
+pcm_count(0),
+sample_rate(0),
+channels(0) {
+ bool first_creation = true;
+ for( unsigned i = 0; i < song.songrefs.size(); i++ ) {
+ PlayedSongInterface *ps = NULL;
+ MusicXMLParser::Song ns = owner->GetSong(song.songrefs[i].name);
+
+ LOGI << "Creating a PlayedLayeredSong layer: " << ns.name << std::endl;
+
+ if( strmtch( ns.type, "segmented" ) ) {
+ LOGE << "No support for segmented songs in layered songs" << std::endl;
+ } else if( strmtch( ns.type, "single" ) ) {
+ ps = new PlayedSingleSong( owner, ns );
+ } else if( strmtch( ns.type, "layered" ) ) {
+ LOGE << "No support for layered songs in layered songs" << std::endl;
+ } else {
+ LOGE << "Unknown song type in " << ns << std::endl;
+ }
+
+ if( ps ) {
+ if( channels == 0 ) {
+ channels = ps->Channels();
+ } else if( channels != ps->Channels() ) {
+ LOGE << "Mimatching sample rates in layered song " << song << std::endl;
+ }
+
+ if( sample_rate == 0 ) {
+ sample_rate = ps->SampleRate();
+ } else if( sample_rate != ps->SampleRate() ) {
+ LOGE << "Misamtching sample rates in layered Song " << song << std::endl;
+ }
+
+ if( pcm_count < ps->GetPCMCount() ) {
+ pcm_count = ps->GetPCMCount();
+ }
+
+ if( first_creation == false ) {
+ ps->SetGain(0.0f);
+ } else {
+ first_creation = false;
+ }
+
+ subsongs.push_back(rc_PlayedSongInterface(ps));
+ }
+ }
+}
+
+Soundtrack::PlayedLayeredSong::~PlayedLayeredSong() {
+
+}
+
+void Soundtrack::PlayedLayeredSong::SetGain( float v ) {
+ //nil
+}
+
+float Soundtrack::PlayedLayeredSong::GetGain() {
+ return 1.0f;
+}
+
+void Soundtrack::PlayedLayeredSong::update( HighResBufferSegment* buffer ) {
+ HighResBufferSegment& first = *buffer;
+ HighResBufferSegment second;
+
+ memset(first.buf,0,HighResBufferSegment::BUF_SIZE);
+
+ first.data_size = HighResBufferSegment::BUF_SIZE;
+
+ int64_t max_pcm_pos = 0;
+ int16_t active_layer_count = 0;
+ unsigned max_sample_count = 0;
+
+ for( unsigned i = 0; i < subsongs.size(); i++ ) {
+ second = HighResBufferSegment();
+ subsongs[i]->update(&second);
+
+ if( i != 0 ) {
+ LOG_ASSERT(first.sample_rate == second.sample_rate);
+ LOG_ASSERT(first.channels == second.channels);
+ } else {
+ first.sample_rate = second.sample_rate;
+ first.channels = second.channels;
+ }
+
+ unsigned first_sample_count = first.FlatSampleCount();
+ unsigned second_sample_count = second.FlatSampleCount();
+
+ if( second_sample_count > max_sample_count ) {
+ max_sample_count = second_sample_count;
+ }
+
+ if( subsongs[i]->GetGain() > 0.0f ) {
+ active_layer_count++;
+
+ for( unsigned k = 0; k < second_sample_count; k++ ) {
+ int32_t buf1;
+ char* buf1a = (char*)&buf1;
+
+ int32_t buf2;
+ char* buf2a = (char*)&buf2;
+
+ buf1a[0] = first.buf[k*4+0];
+ buf1a[1] = first.buf[k*4+1];
+ buf1a[2] = first.buf[k*4+2];
+ buf1a[3] = first.buf[k*4+3];
+
+ buf2a[0] = second.buf[k*4+0];
+ buf2a[1] = second.buf[k*4+1];
+ buf2a[2] = second.buf[k*4+2];
+ buf2a[3] = second.buf[k*4+3];
+
+ buf1 += buf2;
+
+ first.buf[k*4+0] = buf1a[0];
+ first.buf[k*4+1] = buf1a[1];
+ first.buf[k*4+2] = buf1a[2];
+ first.buf[k*4+3] = buf1a[3];
+ }
+ }
+
+ int64_t pcm_pos = subsongs[i]->GetPCMPos();
+ if( max_pcm_pos < pcm_pos ) {
+ max_pcm_pos = pcm_pos;
+ }
+ }
+
+ this->pcm_pos = max_pcm_pos;
+
+ first.data_size = max_sample_count*first.SampleSize();
+
+ if( IsAtEnd() ) {
+ Rewind();
+ }
+}
+
+bool Soundtrack::PlayedLayeredSong::IsAtEnd() {
+ bool is_at_end = true;
+ for( unsigned i = 0; i < subsongs.size(); i++ ) {
+ is_at_end = is_at_end && subsongs[i]->IsAtEnd();
+ }
+ return is_at_end;
+}
+
+void Soundtrack::PlayedLayeredSong::Rewind() {
+ pcm_pos = 0;
+ for( unsigned i = 0; i < subsongs.size(); i++ ) {
+ subsongs[i]->Rewind();
+ }
+}
+
+int64_t Soundtrack::PlayedLayeredSong::GetPCMPos() {
+ return pcm_pos;
+}
+
+void Soundtrack::PlayedLayeredSong::SetPCMPos( int64_t pos ) {
+ pcm_pos = pos;
+ for( unsigned i = 0; i < subsongs.size(); i++ ) {
+ if( pos < subsongs[i]->GetPCMCount() ) {
+ subsongs[i]->SetPCMPos(pos);
+ }
+ }
+}
+
+int64_t Soundtrack::PlayedLayeredSong::GetPCMCount() {
+ int64_t max = 0;
+ for( unsigned i = 0; i < subsongs.size(); i++ ) {
+ if( max < subsongs[i]->GetPCMCount() ) {
+ max = subsongs[i]->GetPCMCount();
+ }
+ }
+ return max;
+}
+
+int Soundtrack::PlayedLayeredSong::SampleRate() {
+ return sample_rate;
+}
+
+int Soundtrack::PlayedLayeredSong::Channels() {
+ return channels;
+}
+
+bool Soundtrack::PlayedLayeredSong::SetLayerGain( const std::string& name, float gain ) {
+ if( gain > 1.0f ) {
+ gain = 1.0f;
+ }
+ if( gain < 0.0f ) {
+ gain = 0.0f;
+ }
+
+ for( unsigned i = 0; i < subsongs.size(); i++ ) {
+ if( subsongs[i]->GetSongName() == name ) {
+ subsongs[i]->SetGain(gain);
+ return true;
+ }
+ }
+ return false;
+}
+
+float Soundtrack::PlayedLayeredSong::GetLayerGain( const std::string& name ) {
+ for( unsigned i = 0; i < subsongs.size(); i++ ) {
+ if( subsongs[i]->GetSongName() == name ) {
+ return subsongs[i]->GetGain();
+ }
+ }
+ return 0.0f;
+}
+
+const std::map<std::string,float> Soundtrack::PlayedLayeredSong::GetLayerGains() {
+ std::map<std::string,float> gains;
+ for( unsigned i = 0; i < subsongs.size(); i++ ) {
+ gains[subsongs[i]->GetSongName()] = subsongs[i]->GetGain();
+ }
+ return gains;
+}
+
+std::vector<std::string> Soundtrack::PlayedLayeredSong::GetLayerNames() const {
+ std::vector<std::string> layers;
+ for( unsigned i = 0; i < song.songrefs.size(); i++ ) {
+ layers.push_back(song.songrefs[i].name);
+ }
+ return layers;
+}
+
+Soundtrack::TransitionPlayer::TransitionPlayer(Soundtrack* _owner) :
+PlayedInterface(_owner),
+playing(true),
+mixer(TransitionMixer::Sinusoid,2.0f),
+currentSong(new PlayedSingleSong(owner))
+{
+
+}
+
+Soundtrack::TransitionPlayer::~TransitionPlayer() {
+
+}
+
+void Soundtrack::TransitionPlayer::update( HighResBufferSegment* buffer ) {
+ //Play normally.
+ if( songQueue.empty() ) {
+ currentSong->update(buffer);
+
+ if( buffer->data_size <= 0 ) {
+ LOGE << "Empty buffer in transition" << std::endl;
+ }
+ mixer.Reset();
+ }
+ else //Transition until we have reached end.
+ {
+ if( currentSong->SampleRate() == songQueue.front()->SampleRate() )
+ {
+ if( currentSong->Channels() == songQueue.front()->Channels() )
+ {
+ HighResBufferSegment first;
+ HighResBufferSegment second;
+
+ currentSong->update(&first);
+ songQueue.front()->update(&second);
+
+ if( mixer.Step(buffer,&first,&second) )
+ {
+ stored_pcm_pos[currentSong->GetSongName()] = currentSong->GetPCMPos();
+
+ currentSong = songQueue.front();
+ songQueue.pop_front();
+ mixer.Reset();
+ }
+ }
+ else
+ {
+ LOGE << "Unable to transition as the two segments "
+ << currentSong->GetSongName() << "(" << currentSong->Channels() << ")"
+ << " and "
+ << songQueue.front()->GetSongName() << "(" << songQueue.front()->Channels() << ")"
+ << " don't have the same number of Channels, skipping transition" << std::endl;
+
+ currentSong = songQueue.front();
+ songQueue.pop_front();
+ mixer.Reset();
+ }
+ }
+ else
+ {
+ LOGE << "Unable to transition as the two segments "
+ << currentSong->GetSongName() << "(" << currentSong->SampleRate() << ")"
+ << " and "
+ << songQueue.front()->GetSongName() << "(" << songQueue.front()->SampleRate() << ")"
+ << " don't have the same sample rate, skipping transition" << std::endl;
+
+ currentSong = songQueue.front();
+ songQueue.pop_front();
+ mixer.Reset();
+ }
+ }
+}
+
+bool Soundtrack::TransitionPlayer::TransitionToSong( const MusicXMLParser::Song& ns )
+{
+ if( (songQueue.size() == 0 && currentSong->GetSong() != ns)
+ || (songQueue.size() > 0 && songQueue.back()->GetSong() != ns ) ) {
+
+ while( songQueue.size() > 1 ) {
+ songQueue.pop_back();
+ }
+
+ PlayedSongInterface *ps = NULL;
+
+ if( strmtch( ns.type, "segmented" ) ) {
+ ps = new PlayedSegmentedSong( owner, ns );
+ } else if( strmtch( ns.type, "single" ) ) {
+ ps = new PlayedSingleSong( owner, ns );
+ } else if( strmtch( ns.type, "layered" ) ) {
+ ps = new PlayedLayeredSong( owner, ns );
+ } else {
+ LOGE << "Unknown song type " << ns.type << std::endl;
+ }
+
+ if( ps ) {
+ std::map<std::string, int64_t>::iterator it = stored_pcm_pos.find(ps->GetSongName());
+ if( it != stored_pcm_pos.end() ) {
+ int64_t size = ps->GetPCMCount();
+ if( size > 0 ) {
+ int64_t v = it->second % size;
+ ps->SetPCMPos(v);
+ }
+ }
+
+ songQueue.push_back(rc_PlayedSongInterface(ps));
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+}
+
+bool Soundtrack::TransitionPlayer::SetSong( const MusicXMLParser::Song& ns ) {
+ mixer.Reset();
+
+ LOGI << "Setting song to " << ns << std::endl;
+
+ if( strmtch( ns.type, "segmented" ) ) {
+ currentSong.Set(new PlayedSegmentedSong(owner,ns));
+ } else if( strmtch( ns.type, "single" ) ) {
+ currentSong.Set(new PlayedSingleSong(owner,ns));
+ } else if( strmtch( ns.type, "layered" ) ) {
+ currentSong.Set(new PlayedLayeredSong(owner,ns));
+ } else {
+ LOGE << "Unknown song type " << ns.type << std::endl;
+ }
+
+ while( songQueue.size() > 0 ) {
+ songQueue.pop_front();
+ }
+ return true;
+}
+
+const std::string Soundtrack::TransitionPlayer::GetSegmentName() const {
+ if( strmtch( GetSongType().c_str(), "segmented" ) ) {
+ const PlayedSegmentedSong *pss = currentSong.GetConstPtr<PlayedSegmentedSong>();
+ return pss->GetSegmentName();
+ } else {
+ return std::string();
+ }
+}
+
+const std::string Soundtrack::TransitionPlayer::GetSongName() const
+{
+ return currentSong.GetConst().GetSongName();
+}
+
+const std::string Soundtrack::TransitionPlayer::GetSongType() const
+{
+ return currentSong.GetConst().GetSongType();
+}
+
+const std::string Soundtrack::TransitionPlayer::GetNextSongName() const
+{
+ if( songQueue.size() > 0 ) {
+ return songQueue[0].GetConst().GetSongName();
+ } else {
+ return std::string();
+ }
+}
+
+const std::string Soundtrack::TransitionPlayer::GetNextSongType() const
+{
+ if( songQueue.size() > 0 ) {
+ return songQueue[0].GetConst().GetSongType();
+ } else {
+ return std::string();
+ }
+}
+
+bool Soundtrack::TransitionPlayer::QueueSegment( const std::string& segment_name )
+{
+ bool found_match = false;
+ std::vector<MusicXMLParser::Segment>::const_iterator segmentit;
+ for( segmentit = currentSong->GetSong().segments.begin();
+ segmentit != currentSong->GetSong().segments.end();
+ segmentit++ )
+ {
+ if( segmentit->name == segment_name ) {
+ if( strmtch(currentSong->GetSongType(), "segmented") ) {
+ PlayedSegmentedSong *pss = currentSong.GetPtr<PlayedSegmentedSong>();
+ pss->QueueSegment(*segmentit);
+ found_match = true;
+ }
+ }
+ }
+
+ std::deque<rc_PlayedSongInterface>::const_iterator songit;
+ for( songit = songQueue.begin();
+ songit != songQueue.end();
+ songit++ )
+ {
+ std::vector<MusicXMLParser::Segment>::const_iterator segmentit;
+ for( segmentit = (*songit).GetConst().GetSong().segments.begin();
+ segmentit != (*songit).GetConst().GetSong().segments.end();
+ segmentit++ ) {
+ if( segmentit->name == segment_name ) {
+ if( strmtch(currentSong->GetSongType(), "segmented") ) {
+ PlayedSegmentedSong *pss = currentSong.GetPtr<PlayedSegmentedSong>();
+ pss->QueueSegment(*segmentit);
+ found_match = true;
+ }
+ }
+ }
+ }
+ if( !found_match )
+ LOGE << "Unable to find matching segment " << segment_name << " in current song: " << currentSong->GetSong() << std::endl;
+ return found_match;
+}
+
+bool Soundtrack::TransitionPlayer::TransitionIntoSegment( const std::string& segment_name )
+{
+ bool found_match = false;
+ std::vector<MusicXMLParser::Segment>::const_iterator segmentit;
+ for( segmentit = currentSong->GetSong().segments.begin();
+ segmentit != currentSong->GetSong().segments.end();
+ segmentit++ ) {
+ if( segmentit->name == segment_name ) {
+ if( strmtch(currentSong->GetSongType(), "segmented") ) {
+ PlayedSegmentedSong *pss = currentSong.GetPtr<PlayedSegmentedSong>();
+ pss->TransitionIntoSegment(*segmentit);
+ found_match = true;
+ }
+ }
+ }
+
+ std::deque<rc_PlayedSongInterface>::const_iterator songit;
+ for( songit = songQueue.begin();
+ songit != songQueue.end();
+ songit++ )
+ {
+ std::vector<MusicXMLParser::Segment>::const_iterator segmentit;
+ for( segmentit = (*songit).GetConst().GetSong().segments.begin();
+ segmentit != (*songit).GetConst().GetSong().segments.end();
+ segmentit++ ) {
+ if( segmentit->name == segment_name ) {
+ if( strmtch(currentSong->GetSongType(), "segmented") ) {
+ PlayedSegmentedSong *pss = currentSong.GetPtr<PlayedSegmentedSong>();
+ pss->TransitionIntoSegment(*segmentit);
+ found_match = true;
+ }
+ }
+ }
+ }
+
+ if( !found_match )
+ LOGE << "Unable to find matching segment " << segment_name << " in current song: " << currentSong->GetSong() << std::endl;
+ return found_match;
+}
+
+bool Soundtrack::TransitionPlayer::SetSegment( const std::string& segment_name )
+{
+ bool found_match = false;
+ std::vector<MusicXMLParser::Segment>::const_iterator segmentit;
+ for( segmentit = currentSong->GetSong().segments.begin();
+ segmentit != currentSong->GetSong().segments.end();
+ segmentit++ )
+ {
+ if( segmentit->name == segment_name ) {
+ if( strmtch(currentSong->GetSongType(), "segmented") ) {
+ PlayedSegmentedSong *pss = currentSong.GetPtr<PlayedSegmentedSong>();
+ pss->SetSegment(*segmentit);
+ found_match = true;
+ }
+ }
+ }
+
+ std::deque<rc_PlayedSongInterface>::const_iterator songit;
+ for( songit = songQueue.begin();
+ songit != songQueue.end();
+ songit++ )
+ {
+ std::vector<MusicXMLParser::Segment>::const_iterator segmentit;
+ for( segmentit = (*songit).GetConst().GetSong().segments.begin();
+ segmentit != (*songit).GetConst().GetSong().segments.end();
+ segmentit++ )
+ {
+ if( segmentit->name == segment_name )
+ {
+ if( strmtch(currentSong->GetSongType(), "segmented") ) {
+ PlayedSegmentedSong *pss = currentSong.GetPtr<PlayedSegmentedSong>();
+ pss->SetSegment(*segmentit);
+ found_match = true;
+ }
+ }
+ }
+ }
+
+ if( !found_match ) {
+ LOGE << "Unable to find matching segment " << segment_name << " in current song: " << currentSong->GetSong() << std::endl;
+ }
+ return found_match;
+}
+
+void Soundtrack::TransitionPlayer::SetPCMPos( int64_t c ) {
+ currentSong->SetPCMPos(c);
+}
+
+int64_t Soundtrack::TransitionPlayer::GetPCMCount() {
+ return currentSong->GetPCMCount();
+}
+
+int64_t Soundtrack::TransitionPlayer::GetPCMPos() {
+ return currentSong->GetPCMPos();
+}
+
+int Soundtrack::TransitionPlayer::SampleRate() {
+ return currentSong->SampleRate();
+}
+
+int Soundtrack::TransitionPlayer::Channels() {
+ return currentSong->Channels();
+}
+
+bool Soundtrack::TransitionPlayer::SetLayerGain(const std::string& name, float gain) {
+ if( songQueue.size() > 0 && strmtch(songQueue[0]->GetSongType(), "layered") ) {
+ PlayedLayeredSong *pss = songQueue[0].GetPtr<PlayedLayeredSong>();
+ return pss->SetLayerGain(name,gain);
+ } else if( strmtch(currentSong->GetSongType(), "layered") ) {
+ PlayedLayeredSong *pss = currentSong.GetPtr<PlayedLayeredSong>();
+ return pss->SetLayerGain(name,gain);
+ } else {
+ return false;
+ }
+}
+
+float Soundtrack::TransitionPlayer::GetLayerGain(const std::string& name) {
+ if( songQueue.size() > 0 && strmtch(songQueue[0]->GetSongType(), "layered") ) {
+ PlayedLayeredSong *pss = songQueue[0].GetPtr<PlayedLayeredSong>();
+ return pss->GetLayerGain(name);
+ } else if( strmtch(currentSong->GetSongType(), "layered") ) {
+ PlayedLayeredSong *pss = currentSong.GetPtr<PlayedLayeredSong>();
+ return pss->GetLayerGain(name);
+ }
+ return 0.0f;
+}
+
+std::vector<std::string> Soundtrack::TransitionPlayer::GetLayerNames() const {
+ if( songQueue.size() > 0 && strmtch(songQueue[0].GetConst().GetSongType(), "layered") ) {
+ const PlayedLayeredSong *pss = songQueue[0].GetConstPtr<PlayedLayeredSong>();
+ return pss->GetLayerNames();
+ } else if( strmtch(currentSong.GetConst().GetSongType(), "layered") ) {
+ const PlayedLayeredSong *pss = currentSong.GetConstPtr<PlayedLayeredSong>();
+ return pss->GetLayerNames();
+ }
+ return std::vector<std::string>();
+}
+
+const std::map<std::string,float> Soundtrack::TransitionPlayer::GetLayerGains() {
+ if( songQueue.size() > 0 && strmtch(songQueue[0]->GetSongType(), "layered") ) {
+ PlayedLayeredSong *pss = songQueue[0].GetPtr<PlayedLayeredSong>();
+ return pss->GetLayerGains();
+ } else if( strmtch(currentSong->GetSongType(), "layered") ) {
+ PlayedLayeredSong *pss = currentSong.GetPtr<PlayedLayeredSong>();
+ return pss->GetLayerGains();
+ }
+ return std::map<std::string,float>();
+}
+
+bool Soundtrack::QueueSegment( const std::string& name )
+{
+ return transitionPlayer.QueueSegment(name);
+}
+
+bool Soundtrack::TransitionIntoSegment( const std::string& name )
+{
+ return transitionPlayer.TransitionIntoSegment(name);
+}
+
+bool Soundtrack::SetSegment( const std::string& name )
+{
+ return transitionPlayer.SetSegment(name);
+}
+
+const std::string Soundtrack::GetSegment( ) const
+{
+ return transitionPlayer.GetSegmentName();
+}
+
+bool Soundtrack::TransitionToSong( const std::string& name )
+{
+ std::map<std::string,MusicXMLParser::Music>::iterator musicit;
+
+ if(name == "overgrowth_silence") {
+ MusicXMLParser::Song song;
+ strcpy(song.type, "single");
+ strcpy(song.file_path, "Data/Music/silence.ogg");
+ return transitionPlayer.TransitionToSong(song);
+ }
+
+ for( musicit = music.begin(); musicit != music.end(); musicit++ )
+ {
+ std::vector<MusicXMLParser::Song>::iterator songit ;
+
+ for( songit = musicit->second.songs.begin();
+ songit != musicit->second.songs.end();
+ songit++ )
+ {
+ if( songit->name == name )
+ {
+ return transitionPlayer.TransitionToSong(*songit);
+ }
+ }
+
+ }
+ LOGW_ONCE("Did not find song \"" << name << "\"");
+ return false;
+}
+
+bool Soundtrack::SetSong( const std::string& name )
+{
+ std::map<std::string,MusicXMLParser::Music>::iterator musicit;
+
+ for( musicit = music.begin(); musicit != music.end(); musicit++ )
+ {
+ std::vector<MusicXMLParser::Song>::iterator songit ;
+
+ for( songit = musicit->second.songs.begin();
+ songit != musicit->second.songs.end();
+ songit++ )
+ {
+ if( songit->name == name )
+ {
+ return transitionPlayer.SetSong(*songit);
+ }
+ }
+
+ }
+ return false;
+}
+
+void Soundtrack::SetLayerGain(const std::string& layer, float v)
+{
+ transitionPlayer.SetLayerGain(layer, v);
+}
+
+const std::string Soundtrack::GetSongName() const
+{
+ return transitionPlayer.GetSongName();
+}
+
+const std::string Soundtrack::GetSongType() const
+{
+ return transitionPlayer.GetSongType();
+}
+
+const std::string Soundtrack::GetNextSongName() const
+{
+ return transitionPlayer.GetNextSongName();
+}
+
+const std::string Soundtrack::GetNextSongType() const
+{
+ return transitionPlayer.GetNextSongType();
+}
+
+const MusicXMLParser::Song Soundtrack::GetSong(const std::string& name) {
+ std::map<std::string,MusicXMLParser::Music>::iterator musicit;
+ for( musicit = music.begin(); musicit != music.end(); musicit++ )
+ {
+ std::vector<MusicXMLParser::Song>::iterator songit ;
+
+ for( songit = musicit->second.songs.begin();
+ songit != musicit->second.songs.end();
+ songit++ )
+ {
+ if( songit->name == name )
+ {
+ return *songit;
+ }
+ }
+
+ }
+ LOGW_ONCE("Did not find song \"" << name << "\"");
+ return MusicXMLParser::Song();
+}
+
+const std::map<std::string,float> Soundtrack::GetLayerGains() {
+ return transitionPlayer.GetLayerGains();
+}
+
+const std::vector<std::string> Soundtrack::GetLayerNames() const {
+ return transitionPlayer.GetLayerNames();
+}
+
+void Soundtrack::PostProcessVolume( HighResBufferSegment* buffer )
+{
+ const size_t start = current_volume_step;
+ //How many bytes for a second of transition times the time of transition.
+ const size_t end = (size_t) std::abs(((volume_start-volume_target)*volume_change_per_second) * buffer->sample_rate);
+
+ for( size_t i = 0; i < buffer->FullSampleCount(); i++ )
+ {
+ float d = 1.0f;
+
+ if( end > 0 )
+ {
+ d = (start+i*buffer->channels)/(float)end;
+ if( d > 1.0f )
+ {
+ d = 1.0f;
+ }
+
+ if( d < 0.0f )
+ {
+ d = 0.0f;
+ }
+ }
+
+ float current_volume = volume_start*(1.0f-d)+volume_target*d;
+
+ union
+ {
+ int32_t full;
+ char part[4];
+ } cast;
+
+ for( size_t c = 0; c < buffer->channels; c++ ) {
+ cast.part[0] = buffer->buf[i*buffer->channels*4+c*4+0];
+ cast.part[1] = buffer->buf[i*buffer->channels*4+c*4+1];
+ cast.part[2] = buffer->buf[i*buffer->channels*4+c*4+2];
+ cast.part[3] = buffer->buf[i*buffer->channels*4+c*4+3];
+
+ cast.full = (int32_t) (cast.full * current_volume);
+
+ buffer->buf[i*buffer->channels*4+c*4+0] = cast.part[0];
+ buffer->buf[i*buffer->channels*4+c*4+1] = cast.part[1];
+ buffer->buf[i*buffer->channels*4+c*4+2] = cast.part[2];
+ buffer->buf[i*buffer->channels*4+c*4+3] = cast.part[3];
+ }
+ }
+
+ current_volume_step += buffer->data_size;
+ if( (size_t)current_volume_step > end )
+ {
+ current_volume_step = end;
+ volume_start = volume_target;
+ }
+}
+
+void Soundtrack::Dispose()
+{
+
+}
+
+void Soundtrack::update(rc_alAudioBuffer buffer)
+{
+ HighResBufferSegment hbs;
+
+ transitionPlayer.update(&hbs);
+
+ PostProcessVolume(&hbs);
+ if( g_sound_enable_layered_soundtrack_limiter ) {
+ limiter.Step(&hbs);
+ }
+
+ if( hbs.data_size > 0 && hbs.channels > 0 )
+ {
+ BufferSegment bs;
+
+ ToBufferSegment(bs,hbs);
+ if( bs.FullSampleCount() == 0 ) {
+ LOGI << "Feeding a null buffer to soundtrack" << std::endl;
+ memset(bs.buf,0,BufferSegment::BUF_SIZE);
+ bs.channels = 2;
+ bs.sample_rate = 44100;
+ bs.data_size = BufferSegment::BUF_SIZE;
+ }
+
+ buffer->BufferData( ( bs.channels == 1)?AL_FORMAT_MONO16:AL_FORMAT_STEREO16, bs.buf, bs.data_size, bs.sample_rate);
+ }
+ else
+ {
+ LOGE << "Unable to stream data from file" << std::endl;
+ }
+}
+
+unsigned long Soundtrack::required_buffers()
+{
+ return 4;
+}
+
+void Soundtrack::Stop()
+{
+ LOGI << "Stopping playing" << std::endl;
+ transitionPlayer = TransitionPlayer(this);
+}
+
+Soundtrack::Soundtrack(float volume) :
+audioStreamer(),
+volume_change_per_second(1.0f),
+volume_start(volume),
+volume_target(volume),
+current_volume_step(0),
+transitionPlayer(this)
+{
+
+}
+
+Soundtrack::~Soundtrack()
+{
+
+}
+
+void Soundtrack::AddMusic( const Path &path )
+{
+ LOGI << "Adding music sheet:" << path << std::endl;
+ MusicXMLParser parser;
+
+ if( music.find( path.GetOriginalPathStr() ) == music.end() )
+ {
+ if( parser.Load( path.GetFullPathStr() ) == MusicXMLParser::kErrorNoError )
+ {
+ std::map<std::string,MusicXMLParser::Music>::iterator musit;
+ for(musit = music.begin(); musit != music.end(); ++musit) {
+ bool replace = false;
+ for(size_t new_i = 0; new_i < parser.music.songs.size(); ++new_i) {
+ for(size_t old_i = 0; old_i < musit->second.songs.size(); ++old_i) {
+ if(strmtch(parser.music.songs[new_i].name, musit->second.songs[old_i].name)) {
+ LOGW << "Track " << parser.music.songs[new_i].name << " already found in " << musit->first << ", old music set will be overwritten" << std::endl;
+ replace = true;
+ if(transitionPlayer.GetSongName() == std::string(parser.music.songs[new_i].name))
+ Stop();
+ break;
+ }
+ }
+ }
+
+ // Loop to print all track collisions, but break here
+ if(replace) {
+ music.erase(musit);
+ break;
+ }
+ }
+
+ music[path.GetOriginalPathStr()] = parser.music;
+ LOGD << "Loaded music sheet " << path << std::endl;
+ }
+ else
+ {
+ LOGE << "Unable to load music file " << path << std::endl;
+ }
+ }
+ else
+ {
+ LOGW << "Music is already loaded, will not reload: " << path << std::endl;
+ }
+}
+
+void Soundtrack::RemoveMusic( const Path &path )
+{
+ std::map<std::string,MusicXMLParser::Music>::iterator musit = music.find( path.GetOriginalPathStr() );
+
+ if( musit != music.end() )
+ {
+ music.erase( musit );
+ }
+ else
+ {
+ LOGW << "Tried to remove not loaded music file: " << path << std::endl;
+ }
+}
+
+bool Soundtrack::KeepPlaying()
+{
+ return true;
+}
+
+bool Soundtrack::GetPosition( vec3 &p )
+{
+ p = vec3(0.0f);
+ return false;
+}
+
+void Soundtrack::GetDirection( vec3 &p )
+{
+ p = vec3(0.0f);
+}
+
+const vec3& Soundtrack::GetVelocity()
+{
+ static vec3 v(0.0f);
+ return v;
+}
+
+const vec3 Soundtrack::GetPosition()
+{
+ return vec3(0.0f);
+}
+
+const vec3 Soundtrack::GetOcclusionPosition()
+{
+ return vec3(0.0f);
+}
+
+unsigned char Soundtrack::GetPriority()
+{
+ return -1;
+}
+
+void Soundtrack::SetVolume(float volume)
+{
+ if( volume > 1.0f )
+ volume = 1.0f;
+ if( volume < 0.0f )
+ volume = 0.0f;
+ current_volume_step = 0;
+ volume_target = volume;
+}
+
+bool Soundtrack::IsTransient()
+{
+ return false;
+}
diff --git a/Source/Sound/soundtrack.h b/Source/Sound/soundtrack.h
new file mode 100644
index 00000000..7a8f2c33
--- /dev/null
+++ b/Source/Sound/soundtrack.h
@@ -0,0 +1,350 @@
+//-----------------------------------------------------------------------------
+// Name: soundtrack.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Sound/al_audio.h>
+#include <Sound/Loader/base_loader.h>
+#include <Sound/high_res_buffer_segment.h>
+#include <Sound/AudioFilters/transition_mixer.h>
+#include <Sound/AudioFilters/limiter_audio_filter.h>
+
+#include <XML/Parsers/musicxmlparser.h>
+#include <Sound/buffer_segment.h>
+
+#include <Internal/filesystem.h>
+#include <Internal/referencecounter.h>
+
+#include <map>
+#include <set>
+#include <deque>
+
+class Soundtrack;
+
+extern bool g_sound_enable_layered_soundtrack_limiter;
+
+class PlayedInterface
+{
+public:
+ PlayedInterface( Soundtrack* _owner );
+ virtual ~PlayedInterface() {};
+ virtual void update( HighResBufferSegment* buffer ) = 0;
+
+ virtual int64_t GetPCMPos() = 0;
+ virtual void SetPCMPos( int64_t pos ) = 0;
+ virtual int64_t GetPCMCount() = 0;
+
+ virtual int SampleRate() = 0;
+ virtual int Channels() = 0;
+
+protected:
+ Soundtrack* owner;
+};
+
+class PlayedSongInterface : public PlayedInterface
+{
+public:
+ PlayedSongInterface( Soundtrack* _owner );
+ PlayedSongInterface( Soundtrack* _owner, const MusicXMLParser::Song& song);
+ virtual ~PlayedSongInterface() {};
+
+ virtual void Rewind() = 0;
+ virtual bool IsAtEnd() = 0;
+
+ virtual void SetGain(float v) = 0;
+ virtual float GetGain() = 0;
+
+ const std::string GetSongName() const;
+ const std::string GetSongType() const;
+ const MusicXMLParser::Song& GetSong() const;
+
+protected:
+ MusicXMLParser::Song song;
+};
+
+typedef ReferenceCounter<PlayedSongInterface> rc_PlayedSongInterface;
+
+class Soundtrack : public audioStreamer
+{
+private:
+
+ class PlayedSegment : public PlayedInterface
+ {
+ public:
+ PlayedSegment( Soundtrack* _owner );
+ PlayedSegment( Soundtrack* _owner, const MusicXMLParser::Segment& segment, bool overlapped_transition );
+ virtual ~PlayedSegment() {};
+ void update( HighResBufferSegment* buffer );
+ const MusicXMLParser::Segment& GetSegment();
+ bool IsAtEnd();
+ void Rewind();
+
+ int64_t GetPCMPos();
+ void SetPCMPos( int64_t pos );
+ int64_t GetPCMCount();
+
+ int SampleRate();
+ int Channels();
+
+ const std::string GetSegmentName() const;
+ private:
+ rc_baseLoader data;
+ MusicXMLParser::Segment segment;
+ public:
+ bool overlapped_transition;
+ bool started;
+ };
+
+ class PlayedSegmentedSong : public PlayedSongInterface
+ {
+ public:
+ PlayedSegmentedSong( Soundtrack* _owner, const MusicXMLParser::Song& _song );
+ virtual ~PlayedSegmentedSong( );
+ void update( HighResBufferSegment* buffer );
+ bool QueueSegment( const MusicXMLParser::Segment& nextSeg );
+ bool TransitionIntoSegment( const MusicXMLParser::Segment& newSeg );
+ bool SetSegment( const MusicXMLParser::Segment& nextSeg );
+
+ bool IsAtEnd();
+ void Rewind();
+ int SampleRate();
+ int Channels();
+
+ void SetGain(float v);
+ float GetGain();
+
+ const std::string GetSegmentName() const;
+
+ void SetPCMPos( int64_t c );
+ int64_t GetPCMPos();
+ int64_t GetPCMCount();
+
+ friend bool operator==( const Soundtrack::PlayedSegmentedSong &lhs, const Soundtrack::PlayedSegmentedSong &rhs );
+ private:
+ PlayedSegment currentSegment;
+
+ std::deque<PlayedSegment> segmentQueue;
+
+ TransitionMixer mixer;
+ };
+
+ class PlayedSingleSong : public PlayedSongInterface
+ {
+ public:
+ PlayedSingleSong( Soundtrack* _owner );
+ PlayedSingleSong( Soundtrack* _owner, const MusicXMLParser::Song& song );
+ virtual ~PlayedSingleSong() {};
+ void update( HighResBufferSegment* buffer );
+ bool IsAtEnd();
+ void Rewind();
+
+ int64_t GetPCMPos();
+ void SetPCMPos( int64_t pos );
+ int64_t GetPCMCount();
+
+ void SetGain(float v);
+ float GetGain();
+
+ int SampleRate();
+ int Channels();
+ private:
+ rc_baseLoader data;
+ public:
+ bool started;
+
+ int current_volume_step;
+ float volume_change_per_second;
+
+ float target_gain;
+ float from_gain;
+ float current_gain;
+ };
+
+ class PlayedLayeredSong : public PlayedSongInterface
+ {
+ public:
+ PlayedLayeredSong( Soundtrack* _owner );
+ PlayedLayeredSong( Soundtrack* _owner, const MusicXMLParser::Song& song );
+ virtual ~PlayedLayeredSong();
+ void update( HighResBufferSegment* buffer );
+ bool IsAtEnd();
+ void Rewind();
+
+ int64_t GetPCMPos();
+ void SetPCMPos( int64_t pos );
+ int64_t GetPCMCount();
+
+ void SetGain(float v);
+ float GetGain();
+
+ int SampleRate();
+ int Channels();
+
+ //Specialized
+ bool SetLayerGain(const std::string& name, float gain);
+ float GetLayerGain(const std::string& name);
+ std::vector<std::string> GetLayerNames() const;
+ const std::map<std::string,float> GetLayerGains();
+ private:
+ std::vector<rc_PlayedSongInterface> subsongs;
+
+ int64_t pcm_count;
+ int64_t pcm_pos;
+ int sample_rate;
+ int channels;
+ bool started;
+ };
+
+ class TransitionPlayer : public PlayedInterface
+ {
+ public:
+ TransitionPlayer(Soundtrack* _owner);
+ virtual ~TransitionPlayer();
+
+ void update( HighResBufferSegment* buffer );
+ void SetTransitionPeriod( float sec );
+
+ bool TransitionToSong( const MusicXMLParser::Song& nextSong );
+ bool SetSong( const MusicXMLParser::Song& nextSong );
+
+ bool QueueSegment( const std::string& segment_name );
+ bool TransitionIntoSegment( const std::string& segment_name );
+ bool SetSegment( const std::string& segment_name );
+ const std::string GetSegmentName( ) const;
+
+ bool SetLayerGain( const std::string& layer, float gain );
+ float GetLayerGain( const std::string& layer );
+ std::vector<std::string> GetLayerNames() const;
+ const std::map<std::string,float> GetLayerGains();
+
+ void SetPCMPos( int64_t c );
+ int64_t GetPCMPos();
+ int64_t GetPCMCount();
+
+ virtual int SampleRate();
+ virtual int Channels();
+
+ const std::string GetSongName() const;
+ const std::string GetSongType() const;
+
+ const std::string GetNextSongName() const;
+ const std::string GetNextSongType() const;
+
+ private:
+ std::map<std::string, int64_t> stored_pcm_pos;
+ bool playing;
+ TransitionMixer mixer;
+
+ rc_PlayedSongInterface currentSong;
+
+ std::deque<rc_PlayedSongInterface> songQueue;
+ };
+
+ friend bool operator==( const Soundtrack::PlayedSegmentedSong &lhs, const Soundtrack::PlayedSegmentedSong &rhs );
+ friend bool operator!=( const Soundtrack::PlayedSegmentedSong &lhs, const Soundtrack::PlayedSegmentedSong &rhs );
+
+ std::map<std::string,MusicXMLParser::Music> music;
+
+ LimiterAudioFilter limiter;
+ TransitionPlayer transitionPlayer;
+
+ int current_volume_step;
+ float volume_change_per_second;
+
+ float volume_start;
+ float volume_target;
+
+ void PostProcessVolume(HighResBufferSegment* buffer);
+public:
+
+ void Dispose();
+
+ //Audiostreamer API
+ /// Request to fill a buffer with data
+ virtual void update(rc_alAudioBuffer buffer);
+
+ /// Return the number of buffers this streaming class requires (usually 2)
+ virtual unsigned long required_buffers();
+
+ /// Stop the current stream (reset to empty)
+ virtual void Stop();
+
+ //AudioEmitter API
+ //
+ /// Set to false to allow the emitter to time out (useful only on non-looping sounds)
+ virtual bool KeepPlaying();
+ /// if true is returned, this will be a relative-to-listener sound
+ virtual bool GetPosition(vec3 &p);
+ virtual void GetDirection(vec3 &p);
+ virtual const vec3& GetVelocity();
+ virtual const vec3 GetPosition();
+ virtual const vec3 GetOcclusionPosition();
+
+ /// Indicate the priority of this effect to make room for newer/higher priority effects
+ virtual unsigned char GetPriority();
+
+ virtual void SetVolume(float vol);
+
+ virtual bool IsTransient();
+
+public: //Control API
+ Soundtrack( float volume );
+
+ virtual ~Soundtrack();
+
+ void AddMusic( const Path& file );
+ void RemoveMusic( const Path& file );
+
+ //Set what segment to play after current is finished
+ bool QueueSegment( const std::string& name );
+ bool TransitionIntoSegment( const std::string& name );
+ //Abruptly change segment
+ bool SetSegment( const std::string& name );
+
+ const std::string GetSegment( ) const;
+ const std::map<std::string,float> GetLayerGains();
+
+ const std::vector<std::string> GetLayerNames() const;
+ const std::string GetSongName() const;
+ const std::string GetSongType() const;
+ const std::string GetNextSongName() const;
+ const std::string GetNextSongType() const;
+ const MusicXMLParser::Song GetSong(const std::string& name);
+
+ //Make a soft transition to a different song.
+ bool TransitionToSong( const std::string& name );
+ //Abruptly change song.
+ bool SetSong( const std::string& name );
+
+ void SetLayerGain(const std::string& layer, float v);
+};
+
+
+inline bool operator==( const Soundtrack::PlayedSegmentedSong &lhs, const Soundtrack::PlayedSegmentedSong &rhs )
+{
+ return lhs.song == rhs.song;
+}
+
+inline bool operator!=( const Soundtrack::PlayedSegmentedSong &lhs, const Soundtrack::PlayedSegmentedSong &rhs )
+{
+ return !(lhs == rhs);
+}
diff --git a/Source/Sound/threaded_sound_wrapper.cpp b/Source/Sound/threaded_sound_wrapper.cpp
new file mode 100644
index 00000000..49380d83
--- /dev/null
+++ b/Source/Sound/threaded_sound_wrapper.cpp
@@ -0,0 +1,1113 @@
+//-----------------------------------------------------------------------------
+// Name: threaded_sound_wrapper.cpp
+// Developer: Wolfire Games LLC
+// Author: Max Danielsson
+// Description: Wrapper around the entire sound system, allowing all sound to be run
+// in a separate thread. This is done using a message queue structure and a
+// synchronized data structure for immediate reads.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "threaded_sound_wrapper.h"
+
+#include <Threading/thread_name.h>
+#include <Threading/sdl_wrapper.h>
+
+#include <Utility/assert.h>
+#include <Utility/strings.h>
+
+#include <Memory/allocation.h>
+#include <Sound/sound.h>
+#include <Logging/logdata.h>
+#include <Internal/profiler.h>
+
+static ThreadedSoundMessageQueue message_queue_in;
+static ThreadedSoundMessageQueue message_queue_out;
+static ThreadedSoundDataBridge tsdb;
+
+void ThreadedSoundMessage::Allocate( size_t size ) {
+ void* new_ptr = NULL;
+
+ //Calling Allocate to shrink the data store might result in truncation, which is not handled.
+ LOG_ASSERT(datalen <= size);
+
+ if( size > LOCAL_DATA_SIZE ) {
+ new_ptr = OG_MALLOC( size );
+ } else {
+ new_ptr = local_data;
+ }
+
+ if( data != NULL ) {
+ if( data != new_ptr && new_ptr != NULL ) {
+ memcpy(new_ptr,data,min(datalen,size));
+ }
+
+ if( data != local_data ) {
+ OG_FREE(data);
+ }
+ }
+
+ data = new_ptr;
+
+ if( size < LOCAL_DATA_SIZE ) {
+ datalen = LOCAL_DATA_SIZE;
+ } else {
+ datalen = size;
+ }
+}
+
+void ThreadedSoundMessage::Free( ) {
+ if( data != NULL ) {
+ if( datalen > LOCAL_DATA_SIZE ) {
+ OG_FREE( data );
+ } else {
+ //Nothing
+ }
+
+ data = NULL;
+ string_data = false;
+ }
+}
+
+ThreadedSoundMessage::ThreadedSoundMessage() :
+ type(None),
+ data(NULL),
+ datalen(0),
+ has_handle(false),
+ wsh(0),
+ string_data(false) {
+
+}
+
+ThreadedSoundMessage::ThreadedSoundMessage(Type t) :
+ type(t),
+ data(NULL),
+ datalen(0),
+ has_handle(false),
+ wsh(0),
+ string_data(false) {
+
+}
+
+ThreadedSoundMessage::~ThreadedSoundMessage() {
+ Free();
+}
+
+ThreadedSoundMessage::ThreadedSoundMessage(const ThreadedSoundMessage& tsm ) :
+type(tsm.type),
+data(NULL),
+datalen(0),
+has_handle(tsm.has_handle),
+wsh(tsm.wsh),
+string_data(tsm.string_data) {
+ if( tsm.data != NULL ) {
+ Allocate( tsm.datalen );
+ memcpy( data, tsm.data, tsm.datalen );
+ }
+}
+
+ThreadedSoundMessage& ThreadedSoundMessage::operator=(const ThreadedSoundMessage& rhs ) {
+ Free();
+
+ type = rhs.type;
+ has_handle = rhs.has_handle;
+ wsh = rhs.wsh;
+ string_data = rhs.string_data;
+
+ Allocate( rhs.datalen );
+ memcpy( data, rhs.data, rhs.datalen );
+
+ return *this;
+}
+
+ThreadedSoundMessage::Type ThreadedSoundMessage::GetType() {
+ return type;
+}
+
+void ThreadedSoundMessage::SetHandle( wrapper_sound_handle _wsh ) {
+ has_handle = true;
+ wsh = _wsh;
+}
+
+wrapper_sound_handle ThreadedSoundMessage::GetWrapperHandle() {
+ LOG_ASSERT(has_handle);
+ return wsh;
+}
+
+real_sound_handle ThreadedSoundMessage::GetRealHandle() {
+ LOG_ASSERT(has_handle);
+ return tsdb.GetHandle(wsh);
+}
+
+void ThreadedSoundMessage::SetData( const void* _data_ptr, size_t offset, size_t _datalen ) {
+ if( offset + _datalen > datalen ) {
+ Allocate( offset + _datalen );
+ }
+ memcpy( (char*)data + offset, _data_ptr, _datalen );
+}
+
+void ThreadedSoundMessage::SetData( const void* _data_ptr, size_t _datalen ) {
+ if( _datalen > datalen ) {
+ Allocate( _datalen );
+ }
+ memcpy( data, _data_ptr, _datalen );
+}
+
+void ThreadedSoundMessage::GetData( void* _data_ptr, size_t _datalen ) {
+ LOG_ASSERT(_datalen <= datalen);
+ memcpy(_data_ptr, data, _datalen );
+}
+
+void ThreadedSoundMessage::GetData( void* _data_ptr, size_t _offset, size_t _datalen ) {
+ LOG_ASSERT( (_offset + _datalen) <= datalen);
+ memcpy(_data_ptr, (char*)data + _offset, _datalen);
+}
+
+void ThreadedSoundMessage::SetStringData( const std::string &str ) {
+ size_t str_size = str.size() * sizeof( char ) + 1;
+ Allocate(str_size);
+ memcpy(data, str.c_str(), str_size);
+ string_data = true;
+}
+
+std::string ThreadedSoundMessage::GetStringData( ) {
+ LOG_ASSERT( string_data );
+ std::string s((const char*)data);
+ return s;
+}
+
+ThreadedSoundDataBridge::ThreadedSoundDataBridge() : handle_counter(0) {
+
+}
+
+wrapper_sound_handle ThreadedSoundDataBridge::CreateWrapperHandle() {
+ wrapper_sound_handle val = 0;
+ mutex.lock();
+ while( val == 0 )
+ {
+ val = ++handle_counter;
+ }
+ //Zero is an invalid map name and good default.
+ handle_map[val] = 0;
+ mutex.unlock();
+ return val;
+}
+
+void ThreadedSoundDataBridge::SetHandle( wrapper_sound_handle wsh, real_sound_handle rsh ) {
+ mutex.lock();
+ handle_map[wsh] = rsh;
+ mutex.unlock();
+}
+
+void ThreadedSoundDataBridge::SetValues( wrapper_sound_handle wsh, const char* name, const char* type ) {
+ mutex.lock();
+ if( data_copy_map.find(wsh) != data_copy_map.end() ) {
+ SoundInstanceDataCopy &d = data_copy_map[wsh];
+ strscpy(d.name, name, SoundInstanceDataCopy::NAME_SIZE);
+ strscpy(d.type, type, SoundInstanceDataCopy::TYPE_SIZE);
+ }
+ mutex.unlock();
+}
+
+void ThreadedSoundDataBridge::SetIdentifier( wrapper_sound_handle wsh, const char* id ) {
+ mutex.lock();
+ strscpy(data_copy_map[wsh].id,id,SoundInstanceDataCopy::ID_SIZE);
+ mutex.unlock();
+}
+
+void ThreadedSoundDataBridge::SetPosition( wrapper_sound_handle wsh, const vec3& position ) {
+ mutex.lock();
+ if( data_copy_map.find(wsh) != data_copy_map.end() ) {
+ data_copy_map[wsh].position = position;
+ }
+ mutex.unlock();
+}
+
+void ThreadedSoundDataBridge::RemoveHandle( wrapper_sound_handle _wsh ) {
+ mutex.lock();
+
+ {
+ std::map<wrapper_sound_handle,real_sound_handle>::iterator hmit = handle_map.find(_wsh);
+ if( hmit != handle_map.end() )
+ handle_map.erase(hmit);
+ }
+
+ {
+ std::map<wrapper_sound_handle,SoundInstanceDataCopy>::iterator dcpit = data_copy_map.find(_wsh);
+ if( dcpit != data_copy_map.end() )
+ data_copy_map.erase(dcpit);
+ }
+
+ mutex.unlock();
+}
+
+real_sound_handle ThreadedSoundDataBridge::GetHandle( wrapper_sound_handle wsh ) {
+ real_sound_handle rsh = 0;
+ mutex.lock();
+ if( handle_map.find(wsh) != handle_map.end() ) {
+ rsh = handle_map[wsh];
+ }
+ mutex.unlock();
+ return rsh;
+}
+
+bool ThreadedSoundDataBridge::IsHandleValid( wrapper_sound_handle wsh ) {
+ bool out;
+ mutex.lock();
+ std::map<wrapper_sound_handle,real_sound_handle>::iterator it = handle_map.find(wsh);
+
+ if( it != handle_map.end() )
+ out = true;
+ else
+ out = false;
+ mutex.unlock();
+ return out;
+}
+
+std::map<wrapper_sound_handle,real_sound_handle> ThreadedSoundDataBridge::GetAllHandles() {
+ std::map<wrapper_sound_handle,real_sound_handle> v;
+ mutex.lock();
+ v = handle_map;
+ mutex.unlock();
+ return v;
+}
+
+SoundInstanceDataCopy ThreadedSoundDataBridge::GetHandleData( wrapper_sound_handle wsh ) {
+ SoundInstanceDataCopy v;
+ mutex.lock();
+ if( data_copy_map.find(wsh) != data_copy_map.end() ) {
+ v = data_copy_map[wsh];
+ }
+ mutex.unlock();
+ return v;
+}
+
+//void ThreadedSoundDataBridge::SetHandleData( wrapper_sound_handle wsh, const SoundInstanceDataCopy &sidc ) {
+// mutex.lock();
+// data_copy_map[wsh] = sidc;
+// mutex.unlock();
+//}
+
+SoundDataCopy ThreadedSoundDataBridge::GetData() {
+ SoundDataCopy out;
+ mutex.lock();
+ out = sound_data;
+ mutex.unlock();
+ return out;
+}
+
+size_t ThreadedSoundDataBridge::GetSoundHandleCount() {
+ size_t i;
+ mutex.lock();
+ i = handle_map.size();
+ mutex.unlock();
+ return i;
+}
+
+size_t ThreadedSoundDataBridge::GetSoundInstanceCount() {
+ size_t i;
+ mutex.lock();
+ i = data_copy_map.size();
+ mutex.unlock();
+ return i;
+}
+
+void ThreadedSoundDataBridge::SetData( const SoundDataCopy& dat ) {
+ mutex.lock();
+ sound_data = dat;
+ mutex.unlock();
+}
+
+void ThreadedSoundDataBridge::SetPreferredDevice(const std::string& name) {
+ mutex.lock();
+ preferred_device = name;
+ mutex.unlock();
+}
+
+std::string ThreadedSoundDataBridge::GetPreferredDevice() {
+ std::string return_value;
+ mutex.lock();
+ return_value = preferred_device;
+ mutex.unlock();
+ return return_value;
+}
+
+void ThreadedSoundDataBridge::SetUsedDevice(const std::string& name) {
+ mutex.lock();
+ used_device = name;
+ mutex.unlock();
+}
+
+std::string ThreadedSoundDataBridge::GetUsedDevice() {
+ std::string return_value;
+ mutex.lock();
+ return_value = used_device;
+ mutex.unlock();
+ return return_value;
+}
+
+std::vector<std::string> ThreadedSoundDataBridge::GetAvailableDevices() {
+ std::vector<std::string> ret;
+ mutex.lock();
+ ret = available_devices;
+ mutex.unlock();
+ return ret;
+}
+
+void ThreadedSoundDataBridge::SetAvailableDevices(std::vector<std::string> devices) {
+ mutex.lock();
+ available_devices = devices;
+ mutex.unlock();
+}
+
+void ThreadedSoundMessageQueue::Queue( const ThreadedSoundMessage &ev ) {
+ mutex.lock();
+ messages.push(ev);
+ mutex.unlock();
+}
+
+ThreadedSoundMessage ThreadedSoundMessageQueue::Pop( ) {
+ mutex.lock();
+ ThreadedSoundMessage val = messages.front();
+ messages.pop();
+ mutex.unlock();
+
+ return val;
+}
+
+size_t ThreadedSoundMessageQueue::Count()
+{
+ size_t c = 0;
+ mutex.lock();
+ c = messages.size();
+ mutex.unlock();
+ return c;
+}
+
+static void ThreadedSoundLoop()
+{
+ bool running = true;
+ bool initialized = false;
+ Sound* sound = NULL;
+ const unsigned int minimum_tick = 16;
+
+ PROFILER_NAME_THREAD(g_profiler_ctx, "Sound Thread");
+
+ while( running )
+ {
+ PROFILER_ENTER(g_profiler_ctx, "Sound Loop");
+
+ unsigned int start_time = SDL_TS_GetTicks();
+
+ std::queue<ThreadedSoundMessage> reinsertion;
+
+ PROFILER_ENTER(g_profiler_ctx, "Polling events");
+
+ //Run until the message queue is empty
+ while( message_queue_in.Count() > 0 )
+ {
+ ThreadedSoundMessage msg = message_queue_in.Pop();
+
+ if( msg.GetType() == ThreadedSoundMessage::Initialize )
+ {
+ if( false == initialized )
+ {
+ NameCurrentThread("Sound thread");
+ std::string preferred_device = tsdb.GetPreferredDevice();
+ sound = new Sound(preferred_device.c_str());
+ initialized = true;
+ tsdb.SetUsedDevice(sound->GetUsedDevice());
+ tsdb.SetAvailableDevices(sound->GetAvailableDevices());
+ }
+ else
+ {
+ LOGE << "Trying to double initialize threaded sound system" << std::endl;
+ }
+ }
+ else if( initialized )
+ {
+ if( msg.GetType() == ThreadedSoundMessage::Dispose )
+ {
+ if( sound )
+ {
+ sound->Dispose();
+ delete sound;
+ sound = NULL;
+ running = false;
+ initialized = false;
+ }
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFUpdateGameTimestep )
+ {
+ float f;
+ msg.GetData(&f,sizeof(float));
+ sound->UpdateGameTimestep(f);
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFUpdateGameTimescale )
+ {
+ float f;
+ msg.GetData(&f,sizeof(float));
+ sound->UpdateGameTimescale(f);
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFSetAirWhoosh )
+ {
+ float v[2];
+ msg.GetData(v,sizeof(v));
+ sound->setAirWhoosh(v[0],v[1]);
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFUpdateListener )
+ {
+ vec3 v[4];
+ msg.GetData(v,sizeof(v));
+ sound->updateListener(v[0],v[1],v[2],v[3]);
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFPlay )
+ {
+ SoundPlayInfo* v;
+ msg.GetData(&v,sizeof(SoundPlayInfo*));
+
+ real_sound_handle rsh = msg.GetRealHandle();
+ sound->Play(rsh, *v);
+
+ delete v;
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFPlayGroup )
+ {
+ SoundGroupPlayInfo* v;
+ msg.GetData(&v, sizeof(SoundGroupPlayInfo*));
+
+ sound->PlayGroup(msg.GetRealHandle(),*v);
+ delete v;
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InRCreateHandle )
+ {
+ wrapper_sound_handle wsh;
+ msg.GetData(&wsh, sizeof(wrapper_sound_handle));
+ real_sound_handle rsh = sound->CreateHandle();
+
+ tsdb.SetHandle( wsh, rsh );
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFSetPosition )
+ {
+ vec3 v;
+
+ msg.GetData(v.entries, sizeof(v.entries));
+
+ sound->SetPosition(msg.GetRealHandle(),v);
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFTranslatePosition )
+ {
+ vec3 v;
+
+ msg.GetData(v.entries, sizeof(v.entries));
+
+ sound->TranslatePosition(msg.GetRealHandle(),v);
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFSetVelocity )
+ {
+ vec3 v;
+
+ msg.GetData(v.entries, sizeof(v.entries));
+
+ sound->SetVelocity(msg.GetRealHandle(),v);
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFSetPitch )
+ {
+ float v;
+
+ msg.GetData(&v,sizeof(float));
+
+ sound->SetPitch(msg.GetRealHandle(),v);
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFSetVolume )
+ {
+ float v;
+
+ msg.GetData(&v, sizeof(float));
+
+ sound->SetVolume(msg.GetRealHandle(),v);
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFStop )
+ {
+ sound->Stop(msg.GetRealHandle());
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFAddMusic )
+ {
+ Path* p;
+ msg.GetData(&p,sizeof(Path*));
+ sound->AddMusic(*p);
+ delete p;
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFRemoveMusic )
+ {
+ Path *p;
+ msg.GetData(&p,sizeof(Path*));
+ sound->RemoveMusic(*p);
+ delete p;
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFQueueSegment )
+ {
+ sound->QueueSegment(msg.GetStringData());
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFTransitionToSegment )
+ {
+ sound->TransitionToSegment(msg.GetStringData());
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFTransitionToSong )
+ {
+ sound->TransitionToSong(msg.GetStringData());
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFSetSegment )
+ {
+ sound->SetSegment(msg.GetStringData());
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFSetLayerGain )
+ {
+ char layer_name[MusicXMLParser::NAME_MAX_LENGTH];
+ float layer_gain;
+
+ msg.GetData(layer_name, 0, MusicXMLParser::NAME_MAX_LENGTH);
+ msg.GetData(&layer_gain, MusicXMLParser::NAME_MAX_LENGTH, sizeof(float));
+
+ sound->SetLayerGain(std::string(layer_name),layer_gain);
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFSetSong )
+ {
+ sound->SetSong(msg.GetStringData());
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFAddAmbientTriangle )
+ {
+ sound->AddAmbientTriangle(msg.GetStringData());
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFClear )
+ {
+ sound->Clear();
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFClearTransient )
+ {
+ sound->ClearTransient();
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFSetMusicVolume )
+ {
+ float v;
+ msg.GetData(&v,sizeof(float));
+ sound->SetMusicVolume(v);
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFSetMasterVolume )
+ {
+ float v;
+ msg.GetData(&v,sizeof(float));
+ sound->SetMasterVolume(v);
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFSetOcclusionPosition )
+ {
+ vec3 v;
+ msg.GetData(&v,sizeof(vec3));
+ sound->SetOcclusionPosition(msg.GetRealHandle(),v);
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFLoadSoundFile )
+ {
+ sound->LoadSoundFile(msg.GetStringData());
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::InFEnableLayeredSoundtrackLimiter)
+ {
+ uint8_t d;
+ msg.GetData(&d,sizeof(uint8_t));
+ sound->EnableLayeredSoundtrackLimiter(d);
+ }
+ else if( msg.GetType() == ThreadedSoundMessage::None )
+ {
+ LOGI << "Got None. Invalid" << std::endl;
+ }
+ else
+ {
+ LOGE << "Function type is not handled in ThreadedSoundWrapper, needs to be implemented: " << msg.GetType() << std::endl;
+ }
+ }
+ else
+ {
+ LOGW << "Sound system isn't initialized yet, putting message into the queue." << std::endl;
+ reinsertion.push(msg);
+ }
+ }
+
+
+ while( reinsertion.size() > 0 )
+ {
+ message_queue_in.Queue( reinsertion.front() );
+ reinsertion.pop();
+ }
+
+ PROFILER_LEAVE(g_profiler_ctx);
+
+
+ if( initialized )
+ {
+ PROFILER_ENTER(g_profiler_ctx, "Sound Update");
+ sound->Update();
+ PROFILER_LEAVE(g_profiler_ctx);
+
+ PROFILER_ENTER(g_profiler_ctx, "Post Data Poll");
+ //Now we update the data from sound to a protected structure for some get functions.
+ std::map<wrapper_sound_handle, real_sound_handle> sounds = tsdb.GetAllHandles();
+ std::map<wrapper_sound_handle, real_sound_handle>::iterator sounds_it = sounds.begin();
+ for(;sounds_it != sounds.end(); sounds_it++)
+ {
+ if( sounds_it->second != 0 )
+ {
+ if( sound->IsHandleValid( sounds_it->second ) )
+ {
+ tsdb.SetPosition(sounds_it->first, sound->GetPosition(sounds_it->second));
+ }
+ else
+ {
+ if( sounds_it->second != 0 )
+ {
+ tsdb.RemoveHandle( sounds_it->first );
+ }
+ }
+ }
+ }
+
+ SoundDataCopy sdc;
+ sdc.current_segment = sound->GetSegment();
+
+ sdc.current_song_name = sound->GetSongName();
+ sdc.current_song_type = sound->GetSongType();
+ sdc.current_layer_names = sound->GetLayerNames();
+ sdc.current_layer_gains = sound->GetLayerGains();
+
+ sdc.next_song_name = sound->GetNextSongName();
+ sdc.next_song_type = sound->GetNextSongType();
+ sdc.ambient_triangles = sound->GetAmbientTriangles();
+
+ std::vector<AudioEmitter*> emitters = sound->GetActiveSounds();
+
+ for( uint32_t i = 0; i < emitters.size(); i++ ) {
+ SoundSourceInfo ss;
+
+ strscpy(ss.name, emitters[i]->display_name.c_str(), sizeof(ss.name));
+ ss.pos = emitters[i]->GetPosition();
+
+ sdc.sound_source.push_back(ss);
+ }
+
+ tsdb.SetData(sdc);
+
+ PROFILER_LEAVE(g_profiler_ctx);
+ }
+
+ unsigned int duration = SDL_TS_GetTicks() - start_time;
+
+ if( running )
+ {
+ PROFILER_ZONE_IDLE(g_profiler_ctx, "Sound Loop Sleep");
+ if( duration < minimum_tick )
+ {
+ std::this_thread::sleep_for(std::chrono::milliseconds(minimum_tick-duration));
+ }
+ else
+ {
+ std::this_thread::yield();
+ }
+ }
+ PROFILER_LEAVE(g_profiler_ctx);//Sound Loop;
+ }
+
+ if( sound )
+ {
+ delete sound;
+ }
+}
+
+ThreadedSound::ThreadedSound() : initialized(false)
+{
+}
+
+void ThreadedSound::Initialize(const char* preferred_device) {
+ tsdb.SetPreferredDevice(preferred_device);
+ thread = std::thread( ThreadedSoundLoop );
+ ThreadedSoundMessage tsm(ThreadedSoundMessage::Initialize);
+ message_queue_in.Queue(tsm);
+ initialized = true;
+}
+
+ThreadedSound::~ThreadedSound()
+{
+ Dispose();
+}
+
+/*** Sound interface message implementation ***/
+
+//Because this function is called every frame, it's a perfect candidate for
+//dealing with return messages from the sound system into the main program thread.
+void ThreadedSound::Update( )
+{
+
+}
+
+void ThreadedSound::UpdateGameTimestep( float timestep )
+{
+ ThreadedSoundMessage tsm(ThreadedSoundMessage::InFUpdateGameTimestep);
+ tsm.SetData(&timestep,sizeof(float));
+ message_queue_in.Queue(tsm);
+}
+
+void ThreadedSound::UpdateGameTimescale( float time_scale )
+{
+ ThreadedSoundMessage tsm(ThreadedSoundMessage::InFUpdateGameTimescale);
+ tsm.SetData(&time_scale,sizeof(float));
+ message_queue_in.Queue(tsm);
+}
+
+void ThreadedSound::setAirWhoosh( float amount, float pitch )
+{
+ ThreadedSoundMessage tsm(ThreadedSoundMessage::InFSetAirWhoosh);
+ float f[2];
+ f[0] = amount;
+ f[1] = pitch;
+ tsm.SetData(f, sizeof(f));
+ message_queue_in.Queue(tsm);
+}
+
+void ThreadedSound::updateListener( vec3 pos, vec3 vel, vec3 facing, vec3 up ) {
+ ThreadedSoundMessage tsm(ThreadedSoundMessage::InFUpdateListener);
+ vec3 v[4];
+ v[0] = pos;
+ v[1] = vel;
+ v[2] = facing;
+ v[3] = up;
+ tsm.SetData(v,sizeof(v));
+ message_queue_in.Queue(tsm);
+}
+
+unsigned long ThreadedSound::CreateHandle(const char* ident) {
+ wrapper_sound_handle wsh = tsdb.CreateWrapperHandle();
+
+ tsdb.SetIdentifier(wsh,ident);
+
+ ThreadedSoundMessage tsm(ThreadedSoundMessage::InRCreateHandle);
+
+ tsm.SetData(&wsh, sizeof(wrapper_sound_handle));
+
+ message_queue_in.Queue(tsm);
+
+ return wsh;
+}
+
+void ThreadedSound::Play( const unsigned long &handle, SoundPlayInfo spi ) {
+ tsdb.SetValues(handle, spi.path.c_str(), "Play");
+
+ ThreadedSoundMessage tsm(ThreadedSoundMessage::InFPlay);
+
+ SoundPlayInfo* v = new SoundPlayInfo(spi);
+
+ tsm.SetHandle(handle);
+ tsm.SetData(&v, sizeof(SoundPlayInfo*));
+
+ message_queue_in.Queue(tsm);
+}
+
+void ThreadedSound::PlayGroup(const unsigned long &handle, const SoundGroupPlayInfo& sgpi) {
+ tsdb.SetValues(handle, sgpi.GetPath().c_str(), "Group");
+
+ ThreadedSoundMessage tsm(ThreadedSoundMessage::InFPlayGroup);
+
+ SoundGroupPlayInfo* v;
+
+ v = new SoundGroupPlayInfo(sgpi);
+
+ v->play_past_tick = sgpi.play_past_tick;
+
+ tsm.SetHandle(handle);
+ tsm.SetData(&v, sizeof(SoundGroupPlayInfo*));
+
+ message_queue_in.Queue(tsm);
+}
+
+const vec3 ThreadedSound::GetPosition( const unsigned long& handle ) {
+ return tsdb.GetHandleData(handle).position;
+}
+
+const std::string ThreadedSound::GetName( const unsigned long& handle ) {
+ return tsdb.GetHandleData(handle).name;
+}
+
+const std::string ThreadedSound::GetType( const unsigned long& handle ) {
+ return tsdb.GetHandleData(handle).type;
+}
+
+const std::string ThreadedSound::GetID( const unsigned long& handle ) {
+ return tsdb.GetHandleData(handle).id;
+}
+
+
+void ThreadedSound::SetPosition( const unsigned long& handle, const vec3 &new_pos ) {
+ ThreadedSoundMessage tsm(ThreadedSoundMessage::InFSetPosition);
+ tsm.SetHandle(handle);
+ tsm.SetData(new_pos.entries,sizeof(new_pos.entries));
+ message_queue_in.Queue(tsm);
+}
+
+void ThreadedSound::TranslatePosition( const unsigned long& handle, const vec3 &trans ) {
+ ThreadedSoundMessage tsm( ThreadedSoundMessage::InFTranslatePosition );
+ tsm.SetHandle(handle);
+ tsm.SetData(trans.entries,sizeof(trans.entries));
+ message_queue_in.Queue(tsm);
+}
+
+void ThreadedSound::SetVelocity(const unsigned long &handle, const vec3 &new_vel) {
+ ThreadedSoundMessage tsm( ThreadedSoundMessage::InFSetVelocity );
+ tsm.SetHandle(handle);
+ tsm.SetData(new_vel.entries,sizeof(new_vel.entries));
+ message_queue_in.Queue(tsm);
+}
+
+void ThreadedSound::SetPitch( const unsigned long &handle, float pitch ) {
+ ThreadedSoundMessage tsm( ThreadedSoundMessage::InFSetPitch );
+ tsm.SetHandle(handle);
+ tsm.SetData(&pitch,sizeof(float));
+ message_queue_in.Queue(tsm);
+}
+
+void ThreadedSound::SetVolume( const unsigned long& handle, float volume )
+{
+ ThreadedSoundMessage tsm( ThreadedSoundMessage::InFSetVolume );
+ tsm.SetHandle(handle);
+ tsm.SetData(&volume,sizeof(float));
+ message_queue_in.Queue(tsm);
+}
+
+void ThreadedSound::Stop( const unsigned long& handle )
+{
+ ThreadedSoundMessage tsm( ThreadedSoundMessage::InFStop );
+ tsm.SetHandle(handle);
+ message_queue_in.Queue(tsm);
+}
+
+bool ThreadedSound::IsHandleValid(const unsigned long &handle)
+{
+ return tsdb.IsHandleValid(handle);
+}
+
+void ThreadedSound::AddMusic(const Path& path)
+{
+ ThreadedSoundMessage tsm( ThreadedSoundMessage::InFAddMusic );
+
+ Path *p = new Path(path);
+ tsm.SetData(&p,sizeof(Path*));
+
+ message_queue_in.Queue(tsm);
+}
+
+void ThreadedSound::RemoveMusic(const Path& path)
+{
+ ThreadedSoundMessage tsm( ThreadedSoundMessage::InFRemoveMusic );
+
+ Path *p = new Path(path);
+ tsm.SetData(&p,sizeof(Path*));
+
+ message_queue_in.Queue(tsm);
+}
+
+void ThreadedSound::QueueSegment(const std::string& string)
+{
+ ThreadedSoundMessage tsm( ThreadedSoundMessage::InFQueueSegment );
+ tsm.SetStringData( string );
+ message_queue_in.Queue(tsm);
+}
+
+void ThreadedSound::TransitionToSegment(const std::string& string)
+{
+ ThreadedSoundMessage tsm( ThreadedSoundMessage::InFTransitionToSegment );
+ tsm.SetStringData( string );
+ message_queue_in.Queue(tsm);
+}
+
+void ThreadedSound::TransitionToSong(const std::string& string)
+{
+ ThreadedSoundMessage tsm( ThreadedSoundMessage::InFTransitionToSong );
+ tsm.SetStringData( string );
+ message_queue_in.Queue(tsm);
+}
+
+void ThreadedSound::SetSegment(const std::string& string)
+{
+ ThreadedSoundMessage tsm( ThreadedSoundMessage::InFSetSegment );
+ tsm.SetStringData( string );
+ message_queue_in.Queue(tsm);
+}
+
+void ThreadedSound::SetLayerGain( const std::string& string, float v )
+{
+ //LOGI << "Queueing call to SetLayerGain(" << string << "," << v << ")" << std::endl;
+ ThreadedSoundMessage tsm( ThreadedSoundMessage::InFSetLayerGain );
+ char str[MusicXMLParser::NAME_MAX_LENGTH];
+ strscpy(str,string.c_str(),MusicXMLParser::NAME_MAX_LENGTH);
+ tsm.SetData(str,0,MusicXMLParser::NAME_MAX_LENGTH);
+ tsm.SetData(&v,MusicXMLParser::NAME_MAX_LENGTH,sizeof(float));
+ message_queue_in.Queue(tsm);
+}
+
+void ThreadedSound::SetSong(const std::string& string)
+{
+ ThreadedSoundMessage tsm( ThreadedSoundMessage::InFSetSong );
+ tsm.SetStringData( string );
+ message_queue_in.Queue(tsm);
+}
+
+const std::string ThreadedSound::GetSegment() const
+{
+ return tsdb.GetData().current_segment;
+}
+
+const std::vector<std::string> ThreadedSound::GetLayerNames() const
+{
+ return tsdb.GetData().current_layer_names;
+}
+
+const std::map<std::string,float> ThreadedSound::GetLayerGains() const
+{
+ return tsdb.GetData().current_layer_gains;
+}
+
+const float ThreadedSound::GetLayerGain(const std::string& layer) const
+{
+ return tsdb.GetData().current_layer_gains[layer];
+}
+
+const std::string ThreadedSound::GetSongName() const
+{
+ return tsdb.GetData().current_song_name;
+}
+
+const std::string ThreadedSound::GetSongType() const
+{
+ return tsdb.GetData().current_song_type;
+}
+
+const std::string ThreadedSound::GetNextSongName() const
+{
+ return tsdb.GetData().next_song_name;
+}
+
+const std::string ThreadedSound::GetNextSongType() const
+{
+ return tsdb.GetData().next_song_type;
+}
+
+void ThreadedSound::AddAmbientTriangle(const std::string &path)
+{
+ ThreadedSoundMessage tsm( ThreadedSoundMessage::InFAddAmbientTriangle );
+ tsm.SetStringData( path );
+ message_queue_in.Queue( tsm );
+}
+
+std::vector<AmbientTriangle> ThreadedSound::GetAmbientTriangles()
+{
+ std::vector<AmbientTriangle> d = tsdb.GetData().ambient_triangles;
+ return d;
+}
+
+void ThreadedSound::Clear()
+{
+ ThreadedSoundMessage tsm( ThreadedSoundMessage::InFClear );
+ message_queue_in.Queue( tsm );
+}
+
+void ThreadedSound::ClearTransient()
+{
+ ThreadedSoundMessage tsm( ThreadedSoundMessage::InFClearTransient );
+ message_queue_in.Queue( tsm );
+}
+
+void ThreadedSound::Dispose()
+{
+ if( initialized ) {
+ ThreadedSoundMessage tsm( ThreadedSoundMessage::Dispose );
+ message_queue_in.Queue( tsm );
+ thread.join();
+ initialized = false;
+ }
+}
+
+void ThreadedSound::SetMusicVolume( float val )
+{
+ ThreadedSoundMessage tsm( ThreadedSoundMessage::InFSetMusicVolume );
+ tsm.SetData( &val, sizeof( float ) );
+ message_queue_in.Queue( tsm );
+}
+
+void ThreadedSound::SetMasterVolume( float val )
+{
+ ThreadedSoundMessage tsm( ThreadedSoundMessage::InFSetMasterVolume );
+ tsm.SetData( &val, sizeof( float ) );
+ message_queue_in.Queue( tsm );
+}
+
+void ThreadedSound::SetOcclusionPosition( const unsigned long &handle, const vec3 &new_pos )
+{
+ ThreadedSoundMessage tsm( ThreadedSoundMessage::InFSetOcclusionPosition );
+ tsm.SetHandle( handle );
+ tsm.SetData(&new_pos, sizeof(vec3));
+ message_queue_in.Queue( tsm );
+}
+
+void ThreadedSound::LoadSoundFile( const std::string & path )
+{
+ ThreadedSoundMessage tsm( ThreadedSoundMessage::InFLoadSoundFile );
+ tsm.SetStringData(path);
+ message_queue_in.Queue(tsm);
+}
+
+std::vector<SoundSourceInfo> ThreadedSound::GetCurrentSoundSources()
+{
+ SoundDataCopy sdc = tsdb.GetData();
+ return sdc.sound_source;
+}
+
+size_t ThreadedSound::GetSoundHandleCount() {
+ return tsdb.GetSoundHandleCount();
+}
+
+size_t ThreadedSound::GetSoundInstanceCount() {
+ return tsdb.GetSoundInstanceCount();
+}
+
+std::map<wrapper_sound_handle,real_sound_handle> ThreadedSound::GetAllHandles() {
+ return tsdb.GetAllHandles();
+}
+
+std::string ThreadedSound::GetPreferredDevice() {
+ return tsdb.GetPreferredDevice();
+}
+
+std::string ThreadedSound::GetUsedDevice() {
+ return tsdb.GetUsedDevice();
+}
+
+std::vector<std::string> ThreadedSound::GetAvailableDevices() {
+ return tsdb.GetAvailableDevices();
+}
+
+void ThreadedSound::EnableLayeredSoundtrackLimiter(bool val) {
+ ThreadedSoundMessage tsm(ThreadedSoundMessage::InFEnableLayeredSoundtrackLimiter);
+ uint8_t d = val ? 1 : 0;
+ tsm.SetData(&d,sizeof(uint8_t));
+ message_queue_in.Queue(tsm);
+}
diff --git a/Source/Sound/threaded_sound_wrapper.h b/Source/Sound/threaded_sound_wrapper.h
new file mode 100644
index 00000000..d8ee5b8b
--- /dev/null
+++ b/Source/Sound/threaded_sound_wrapper.h
@@ -0,0 +1,309 @@
+//-----------------------------------------------------------------------------
+// Name: threaded_sound_wrapper.h
+// Developer: Wolfire Games LLC
+// Author: Max Danielsson
+// Description: Wrapper around the entire sound system, allowing all sound to be run
+// in a separate thread. This is done using a message queue structure and a
+// synchronized data structure for immediate reads.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Sound/soundplayinfo.h>
+#include <Sound/al_audio.h>
+#include <Sound/ambienttriangle.h>
+
+#include <Math/vec3.h>
+#include <Internal/filesystem.h>
+
+#include <queue>
+#include <thread>
+#include <mutex>
+#include <chrono>
+
+
+typedef unsigned long wrapper_sound_handle;
+typedef unsigned long real_sound_handle;
+
+class ThreadedSoundMessage {
+
+public:
+ static const unsigned char LOCAL_DATA_SIZE = 64;
+
+ enum Type
+ {
+ None,
+ Initialize,
+ Dispose,
+
+ InFUpdateGameTimestep,
+ InFUpdateGameTimescale,
+ InFSetAirWhoosh,
+ InFUpdateListener,
+ InFPlay,
+ InFPlayGroup,
+ InFSetPosition,
+ InFTranslatePosition,
+ InFSetVelocity,
+ InFSetPitch,
+ InFSetVolume,
+ InFStop,
+ InFAddMusic,
+ InFRemoveMusic,
+ InFQueueSegment,
+ InFTransitionToSegment,
+ InFTransitionToSong,
+ InFSetSegment,
+ InFSetLayerGain,
+ InFSetSong,
+ InFAddAmbientTriangle,
+ InFClear,
+ InFClearTransient,
+ InFSetMusicVolume,
+ InFSetMasterVolume,
+ InFSetOcclusionPosition,
+ InFLoadSoundFile,
+ InFEnableLayeredSoundtrackLimiter,
+
+ InRCreateHandle
+ };
+
+private:
+ Type type;
+
+ unsigned char local_data[LOCAL_DATA_SIZE];
+
+ void Free();
+
+ void *data;
+ size_t datalen;
+
+ bool has_handle;
+ wrapper_sound_handle wsh;
+
+ bool string_data;
+public:
+ void Allocate(size_t size);
+
+ ThreadedSoundMessage();
+ ThreadedSoundMessage( Type t );
+ ~ThreadedSoundMessage();
+
+ ThreadedSoundMessage( const ThreadedSoundMessage& tsm );
+ ThreadedSoundMessage& operator=(const ThreadedSoundMessage& tsm);
+
+ Type GetType();
+
+ void SetHandle( wrapper_sound_handle _wsh );
+ wrapper_sound_handle GetWrapperHandle();
+ real_sound_handle GetRealHandle();
+
+ //Different ways of storing data in same memory chunk.
+ void SetData( const void* mem_ptr, size_t data_len );
+ void SetData( const void* mem_ptr, size_t offset, size_t data_len );
+ void GetData( void* mem_ptr, size_t max_len );
+ void GetData( void* mem_ptr, size_t offset, size_t data_len );
+
+ void SetStringData( const std::string &str );
+ std::string GetStringData( );
+};
+
+class ThreadedSoundMessageQueue {
+ std::queue<ThreadedSoundMessage> messages;
+ std::mutex mutex;
+public:
+
+ void Queue( const ThreadedSoundMessage &ev );
+ ThreadedSoundMessage Pop( );
+ size_t Count( );
+};
+
+struct SoundSourceInfo {
+ vec3 pos;
+ char name[128];
+};
+
+struct SoundDataCopy {
+ std::string current_segment;
+ std::string current_song_name;
+ std::string current_song_type;
+
+ std::string next_song_name;
+ std::string next_song_type;
+
+ std::vector<AmbientTriangle> ambient_triangles;
+
+ std::map<std::string,float> current_layer_gains;
+ std::vector<std::string> current_layer_names;
+
+ std::vector<SoundSourceInfo> sound_source;
+};
+
+struct SoundInstanceDataCopy {
+ static const int ID_SIZE = 8;
+ static const int NAME_SIZE = 32;
+ static const int TYPE_SIZE = 8;
+ char id[ID_SIZE];
+ char name[NAME_SIZE];
+ char type[TYPE_SIZE];
+ vec3 position;
+};
+
+/*
+ * Class containing data that is used for copies, reads and mapping internal to external handles.
+ */
+class ThreadedSoundDataBridge {
+
+private:
+ std::mutex mutex;
+
+ wrapper_sound_handle handle_counter;
+ std::map<wrapper_sound_handle,real_sound_handle> handle_map;
+ std::map<wrapper_sound_handle,SoundInstanceDataCopy> data_copy_map;
+
+ SoundDataCopy sound_data;
+
+ std::vector<std::string> available_devices;
+ std::string preferred_device;
+ std::string used_device;
+public:
+ ThreadedSoundDataBridge();
+
+ wrapper_sound_handle CreateWrapperHandle();
+
+ void SetHandle( wrapper_sound_handle wsh, real_sound_handle rsh );
+ void SetIdentifier( wrapper_sound_handle wsh, const char* id );
+ void SetValues( wrapper_sound_handle wsh, const char* name, const char* type );
+ void SetPosition( wrapper_sound_handle wsh, const vec3& position );
+
+ void RemoveHandle( wrapper_sound_handle wsh );
+ real_sound_handle GetHandle( wrapper_sound_handle wsh );
+ bool IsHandleValid( wrapper_sound_handle wsh );
+
+ std::map<wrapper_sound_handle,real_sound_handle> GetAllHandles();
+ SoundInstanceDataCopy GetHandleData( wrapper_sound_handle wsh );
+ //void SetHandleData( wrapper_sound_handle wsh, const SoundInstanceDataCopy &sidc );
+
+ SoundDataCopy GetData();
+ size_t GetSoundHandleCount();
+ size_t GetSoundInstanceCount();
+ void SetData( const SoundDataCopy& dat );
+
+ void SetPreferredDevice(const std::string& name);
+ std::string GetPreferredDevice();
+
+ void SetUsedDevice(const std::string& name);
+ std::string GetUsedDevice();
+
+ void SetAvailableDevices(std::vector<std::string> devices);
+ std::vector<std::string> GetAvailableDevices();
+};
+
+class ThreadedSound {
+private:
+
+ std::thread thread;
+ bool initialized;
+
+public:
+ ThreadedSound();
+ ~ThreadedSound();
+
+ void Initialize(const char* preferred_device);
+
+ //Following is a copy of the Sound interface
+ void Update();
+ void UpdateGameTimestep( float timestep );
+ void UpdateGameTimescale( float time_scale );
+
+ void setAirWhoosh(float amount, float pitch = 1.0f);
+
+ void updateListener(vec3 pos, vec3 vel, vec3 facing, vec3 up);
+
+ unsigned long CreateHandle(const char* ident);
+
+ void Play(const unsigned long &handle, SoundPlayInfo spi);
+
+ void PlayGroup(const unsigned long &handle, const SoundGroupPlayInfo& sgpi);
+ const vec3 GetPosition(const unsigned long &handle);
+ const std::string GetName(const unsigned long& handle);
+ const std::string GetType(const unsigned long& handle);
+ const std::string GetID(const unsigned long& handle);
+ void SetPosition(const unsigned long &handle, const vec3 &new_pos);
+ void TranslatePosition(const unsigned long& handle, const vec3 &movement);
+ void SetVelocity(const unsigned long &handle, const vec3 &new_vel);
+ void SetPitch(const unsigned long &handle, float pitch);
+ void SetVolume(const unsigned long &handle, float volume);
+ void Stop(const unsigned long &handle);
+ bool IsHandleValid(const unsigned long &handle);
+
+ void AddMusic(const Path& path);
+ void RemoveMusic(const Path& path);
+
+ void QueueSegment(const std::string& string);
+ void TransitionToSegment(const std::string& string);
+ void TransitionToSong(const std::string& string);
+
+ void SetSegment(const std::string& string);
+ void SetLayerGain(const std::string& layer, float gain);
+ void SetSong(const std::string& string);
+
+ const std::string GetSegment() const;
+ const std::map<std::string,float> GetLayerGains() const;
+ const std::vector<std::string> GetLayerNames() const;
+ const float GetLayerGain( const std::string& layer ) const;
+ const std::string GetSongName() const;
+ const std::string GetSongType() const;
+
+ const std::string GetNextSongName() const;
+ const std::string GetNextSongType() const;
+
+ void AddAmbientTriangle(const std::string &path);
+ std::vector<AmbientTriangle> GetAmbientTriangles();
+ void Clear();
+ //Clear all sounds that are considered transient (that is sound effects in a level and not music)
+ //This is called when a level is unloaded, or loaded. Basically between major state changes.
+ void ClearTransient();
+
+ //std::vector<AudioEmitter*> GetActiveSounds();
+ //alAudio &getAlAudio();
+ //void changeSoundtrack(char *name, char *type)
+
+ void Dispose();
+ void SetMusicVolume( float val );
+ void SetMasterVolume( float val );
+
+ //void UpdateSoundOcclusion( );
+ //void SetSoundOcclusionState( int id, bool occluded );
+ void SetOcclusionPosition( const unsigned long &handle, const vec3 &new_pos );
+ void LoadSoundFile( const std::string & path );
+ //End of Sound interface.
+
+ std::vector<SoundSourceInfo> GetCurrentSoundSources();
+ size_t GetSoundHandleCount();
+ size_t GetSoundInstanceCount();
+
+ std::map<wrapper_sound_handle,real_sound_handle> GetAllHandles();
+
+ std::string GetPreferredDevice();
+ std::string GetUsedDevice();
+ std::vector<std::string> GetAvailableDevices();
+
+ void EnableLayeredSoundtrackLimiter(bool val);
+};
diff --git a/Source/Steam/steam_appid.h b/Source/Steam/steam_appid.h
new file mode 100644
index 00000000..ee844ec0
--- /dev/null
+++ b/Source/Steam/steam_appid.h
@@ -0,0 +1,26 @@
+//-----------------------------------------------------------------------------
+// Name: steam_appid.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#define OVERGROWTH_APP_ID 25000
+#define OVERGROWTH_APP_ID_STR "25000"
diff --git a/Source/Steam/steamworks.cpp b/Source/Steam/steamworks.cpp
new file mode 100644
index 00000000..a1973af3
--- /dev/null
+++ b/Source/Steam/steamworks.cpp
@@ -0,0 +1,242 @@
+//-----------------------------------------------------------------------------
+// Name: steamworks.cpp
+// Developer: Wolfire Games LLC
+// Description: Steamworks wrapper for Overgrowth, to simplify use and
+// minimize state.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "steamworks.h"
+
+#if ENABLE_STEAMWORKS
+#include "steam_api.h"
+
+#include <Logging/logdata.h>
+#include <Internal/integer.h>
+#include <Memory/allocation.h>
+#include <Internal/modloading.h>
+#include <Steam/ugc_item.h>
+#include <Utility/assert.h>
+#include <Internal/snprintf.h>
+#include <Internal/profiler.h>
+#include <Main/engine.h>
+
+#include <sstream>
+
+const int Steamworks::poll_freq = 60 * 10;
+
+Steamworks::Steamworks() :
+m_SteamGameOverlayActivated(this, &Steamworks::OnGameOverlayActivated),
+connected(false),
+ugc(NULL),
+friends(NULL),
+matchmaking(NULL),
+poll_counter(0),
+can_use_workshop(false),
+needs_to_accept_license(false) {
+ //Should be empty
+}
+
+Steamworks* Steamworks::Instance() {
+ static Steamworks instance;
+ return &instance;
+}
+
+SteamworksUGC* Steamworks::GetUGC() {
+ return ugc;
+}
+
+SteamworksFriends* Steamworks::GetFriends() {
+ return friends;
+}
+
+SteamworksMatchmaking* Steamworks::GetMatchmaking() {
+ return matchmaking;
+}
+
+void Steamworks::Initialize() {
+ bool steam_init_success = SteamAPI_Init();
+ connected = false;
+ if(steam_init_success){
+ LOGI << "Successfully connected to Steam API." << std::endl;
+ if( SteamUGC() ) {
+ connected = true;
+ ugc = new SteamworksUGC();
+ friends = new SteamworksFriends();
+ matchmaking = new SteamworksMatchmaking();
+ } else {
+ LOGE << "SteamUGC() is null, not initializing steamworks, this is an error." << std::endl;
+ }
+ } else {
+ LOGI << "Could not connect to Steam API." << std::endl;
+ }
+}
+
+static const CSteamID overgrowth_tester_group(103582791457175036ULL);
+void Steamworks::Update(bool updateUGC) {
+ {
+ PROFILER_ZONE(g_profiler_ctx, "RunCallbacks");
+ SteamAPI_RunCallbacks();
+ }
+ if(updateUGC) {
+ if( GetUGC() ) {
+ PROFILER_ZONE(g_profiler_ctx, "UGC->Update");
+ GetUGC()->Update();
+ }
+
+ if( friends ) {
+ PROFILER_ZONE(g_profiler_ctx, "Friends");
+ if( poll_counter <= 0 ) {
+ if( config["check_for_workshop_membership"].toBool() ) {
+ std::vector<CSteamID> ids = friends->GetClans();
+ can_use_workshop = false;
+ for( unsigned i = 0; i < ids.size(); i++ ) {
+ if( ids[i] == overgrowth_tester_group ) {
+ can_use_workshop = true;
+ }
+ }
+ } else {
+ can_use_workshop = true;
+ }
+ poll_counter = poll_freq;
+ }
+ poll_counter--;
+ } else {
+ can_use_workshop = false;
+ }
+ }
+}
+
+void Steamworks::Dispose() {
+ while(WaitingForResults()) {
+ Update(true);
+ }
+
+ if (matchmaking) {
+ delete matchmaking;
+ matchmaking = NULL;
+ }
+
+ SteamAPI_Shutdown();
+ delete ugc;
+ delete friends;
+ ugc = NULL;
+ friends = NULL;
+ connected = false;
+}
+
+//This function indicates of any of the steamworks systems are waiting
+//for data to be returned from the underlying api. If this is the case,
+//disposing of the systems aren't safe;
+bool Steamworks::WaitingForResults() {
+ if( ugc ) {
+ return ugc->WaitingForResults();
+ } else {
+ return false;
+ }
+}
+
+bool Steamworks::UserNeedsToAcceptWorkshopAgreement() {
+ return needs_to_accept_license;
+}
+
+bool Steamworks::UserCanAccessWorkshop() {
+ if( IsConnected() ) {
+ if( can_use_workshop ) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void Steamworks::OpenWebPageToMod(ModID &id) {
+ ModInstance* mod = ModLoading::Instance().GetMod(id);
+ if( mod ) {
+ SteamworksUGCItem* ugci = mod->GetUGCItem();
+ if( ugci ) {
+ if( friends ) {
+ LOGI << "Opening workshop mod overlay page for " << id << std::endl;
+
+ std::stringstream ss;
+ ss << "steamcommunity.com/sharedfiles/filedetails/?id=" << ugci->steamworks_id << std::endl;
+ std::string url = ss.str();
+ friends->ActivateGameOverlayToWebPage( url.c_str() );
+ } else {
+ LOGE << "Couldn't open mod page overlay, mod " << id << " no friends steamworks instance" << std::endl;
+ }
+ } else {
+ LOGE << "Couldn't open mod page overlay, mod " << id << " doesn't have a ugc" << std::endl;
+ }
+ } else {
+ LOGE << "Couldn't open mod page overlay, mod " << id << " no such mod" << std::endl;
+ }
+}
+
+void Steamworks::OpenWebPageToModAuthor(ModID &id) {
+ ModInstance* mod = ModLoading::Instance().GetMod(id);
+
+ if( mod ) {
+ SteamworksUGCItem* ugci = mod->GetUGCItem();
+ if( ugci ) {
+ if( friends ) {
+ LOGI << "Opening workshop mod author overlay page for " << id << std::endl;
+ friends->ActivateGameOverlayToUser( "steamid", ugci->owner_id );
+ } else {
+ LOGE << "Couldn't open mod author page overlay, mod " << id << " no friends steamworks instance" << std::endl;
+ }
+ } else {
+ LOGE << "Couldn't open mod author page overlay, mod " << id << " doesn't have a ugc" << std::endl;
+ }
+ } else {
+ LOGE << "Couldn't open mod author page overlay, mod " << id << " no such mod" << std::endl;
+ }
+}
+
+void Steamworks::OpenWebPageToWorkshop() {
+ const char* url = "steamcommunity.com/app/" OVERGROWTH_APP_ID_STR "/workshop/";
+ if( friends ) {
+ LOGI << "Opening workshop overlay" << std::endl;
+ friends->ActivateGameOverlayToWebPage( url );
+ } else {
+ LOGE << "Couldn't open workshop overlay page" << std::endl;
+ }
+}
+
+void Steamworks::OpenWebPage(const char* url) {
+ if( friends ) {
+ LOGI << "Opening workshop overlay" << std::endl;
+ friends->ActivateGameOverlayToWebPage( url );
+ } else {
+ LOGE << "Couldn't open workshop overlay url:" << url << std::endl;
+ }
+}
+
+bool Steamworks::IsConnected() {
+ return connected;
+}
+
+void Steamworks::OnGameOverlayActivated( GameOverlayActivated_t *callback ) {
+ if( callback->m_bActive ) {
+ LOGI << "Overlay activated" << std::endl;
+ UIShowCursor(1);
+ } else {
+ LOGI << "Overlay deactivated" << std::endl;
+ UIShowCursor(0);
+ }
+}
+#endif
diff --git a/Source/Steam/steamworks.h b/Source/Steam/steamworks.h
new file mode 100644
index 00000000..7f2bb300
--- /dev/null
+++ b/Source/Steam/steamworks.h
@@ -0,0 +1,75 @@
+//-----------------------------------------------------------------------------
+// Name: steamworks.h
+// Developer: Wolfire Games LLC
+// Description: Steamworks wrapper for Overgrowth, to simplify use and
+// minimize state.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Steam/ugc.h>
+
+#if ENABLE_STEAMWORKS
+#include <Steam/steamworks_friends.h>
+#include <Steam/steamworks_matchmaking.h>
+
+class SteamworksUGCItem;
+
+class Steamworks {
+private:
+ bool connected;
+ bool can_use_workshop;
+ bool needs_to_accept_license;
+
+ int poll_counter;
+ const static int poll_freq;
+
+
+ friend class SteamworksUGCItem;
+ SteamworksUGC* ugc;
+ SteamworksFriends* friends;
+ SteamworksMatchmaking* matchmaking;
+
+ STEAM_CALLBACK(Steamworks, OnGameOverlayActivated, GameOverlayActivated_t, m_SteamGameOverlayActivated);
+public:
+ static Steamworks* Instance();
+
+ Steamworks();
+
+ SteamworksFriends* GetFriends();
+ SteamworksUGC* GetUGC();
+ SteamworksMatchmaking* GetMatchmaking();
+ void Initialize();
+
+ void Update(bool updateUGC);
+ void Dispose();
+
+ bool WaitingForResults();
+
+ bool UserNeedsToAcceptWorkshopAgreement();
+ bool UserCanAccessWorkshop();
+
+ void OpenWebPageToMod(ModID &id);
+ void OpenWebPageToModAuthor(ModID &id);
+ void OpenWebPageToWorkshop();
+ void OpenWebPage(const char* url);
+
+ bool IsConnected();
+};
+#endif
diff --git a/Source/Steam/steamworks_friends.cpp b/Source/Steam/steamworks_friends.cpp
new file mode 100644
index 00000000..662ae74f
--- /dev/null
+++ b/Source/Steam/steamworks_friends.cpp
@@ -0,0 +1,60 @@
+//-----------------------------------------------------------------------------
+// Name: steamworks_friends.cpp
+// Developer: Wolfire Games LLC
+// Description: Steamworks wrapper for Overgrowth, to simplify use and
+// minimize state.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "steamworks_friends.h"
+
+#if ENABLE_STEAMWORKS
+SteamworksFriends::SteamworksFriends() {
+
+}
+
+SteamworksFriends::~SteamworksFriends() {
+
+}
+
+void SteamworksFriends::ActivateGameOverlay( const char *pchDialog ) {
+ SteamFriends()->ActivateGameOverlay(pchDialog);
+}
+
+void SteamworksFriends::ActivateGameOverlayToUser( const char *pchDialog, CSteamID steamID ) {
+ SteamFriends()->ActivateGameOverlayToUser(pchDialog, steamID);
+}
+
+void SteamworksFriends::ActivateGameOverlayToWebPage( const char *pchURL ) {
+ SteamFriends()->ActivateGameOverlayToWebPage(pchURL);
+}
+
+std::vector<CSteamID> SteamworksFriends::GetClans() {
+ std::vector<CSteamID> ret;
+ for( int i = 0; i < SteamFriends()->GetClanCount(); i++ ) {
+ CSteamID clanid = SteamFriends()->GetClanByIndex(i);
+ ret.push_back(clanid);
+ }
+ return ret;
+}
+
+
+std::string SteamworksFriends::GetPersonaName() {
+ return SteamFriends()->GetPersonaName();
+}
+#endif
diff --git a/Source/Steam/steamworks_friends.h b/Source/Steam/steamworks_friends.h
new file mode 100644
index 00000000..c225f1b4
--- /dev/null
+++ b/Source/Steam/steamworks_friends.h
@@ -0,0 +1,71 @@
+//-----------------------------------------------------------------------------
+// Name: steamworks_friends.h
+// Developer: Wolfire Games LLC
+// Description: Steamworks wrapper for Overgrowth, to simplify use and
+// minimize state.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Logging/logdata.h>
+#include <Internal/config.h>
+
+#if ENABLE_STEAMWORKS
+#include <steam_api.h>
+#endif
+
+#include <vector>
+
+#if ENABLE_STEAMWORKS
+
+class SteamworksFriends {
+private:
+ friend class Steamworks;
+ SteamworksFriends();
+
+public:
+ ~SteamworksFriends();
+
+ // activates the game overlay, with an optional dialog to open
+ // valid options are "Friends", "Community", "Players", "Settings", "OfficialGameGroup", "Stats", "Achievements"
+ void ActivateGameOverlay( const char *pchDialog );
+
+ // activates game overlay to a specific place
+ // valid options are
+ // "steamid" - opens the overlay web browser to the specified user or groups profile
+ // "chat" - opens a chat window to the specified user, or joins the group chat
+ // "jointrade" - opens a window to a Steam Trading session that was started with the ISteamEconomy/StartTrade Web API
+ // "stats" - opens the overlay web browser to the specified user's stats
+ // "achievements" - opens the overlay web browser to the specified user's achievements
+ // "friendadd" - opens the overlay in minimal mode prompting the user to add the target user as a friend
+ // "friendremove" - opens the overlay in minimal mode prompting the user to remove the target friend
+ // "friendrequestaccept" - opens the overlay in minimal mode prompting the user to accept an incoming friend invite
+ // "friendrequestignore" - opens the overlay in minimal mode prompting the user to ignore an incoming friend invite
+ void ActivateGameOverlayToUser( const char *pchDialog, CSteamID steamID );
+
+ // activates game overlay web browser directly to the specified URL
+ // full address with protocol type is required, e.g. http://www.steamgames.com/
+ void ActivateGameOverlayToWebPage( const char *pchURL );
+
+ std::vector<CSteamID> GetClans();
+
+ std::string GetPersonaName();
+};
+
+#endif
diff --git a/Source/Steam/steamworks_matchmaking.cpp b/Source/Steam/steamworks_matchmaking.cpp
new file mode 100644
index 00000000..58d5e869
--- /dev/null
+++ b/Source/Steam/steamworks_matchmaking.cpp
@@ -0,0 +1,300 @@
+//-----------------------------------------------------------------------------
+// Name: steamworks_matchmaking.cpp
+// Developer: Wolfire Games LLC
+// Description: Steamworks wrapper for Overgrowth, to simplify use and
+// minimize state.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "steamworks_matchmaking.h"
+
+#include <Online/online.h>
+#include <Internal/config.h>
+#include <Logging/logdata.h>
+#include <Steam/steamworks_util.h>
+
+#if ENABLE_STEAMWORKS
+#include <isteamnetworkingutils.h>
+#endif
+
+#include <algorithm>
+
+
+static const char* LEVEL_PATH = "levelpath";
+
+#if ENABLE_STEAMWORKS
+const char *SteamworksMatchmaking::StatusToString(ESteamNetworkingConnectionState state) {
+ switch (state) {
+ case k_ESteamNetworkingConnectionState_None:
+ return "None";
+ break;
+
+ case k_ESteamNetworkingConnectionState_Connecting:
+ return "Connecting";
+ break;
+
+ case k_ESteamNetworkingConnectionState_FindingRoute:
+ return "FindingRoute";
+ break;
+
+ case k_ESteamNetworkingConnectionState_Connected:
+ return "Connected";
+ break;
+
+ case k_ESteamNetworkingConnectionState_ClosedByPeer:
+ return "ClosedByPeer";
+ break;
+
+ case k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
+ return "ProblemDetectedLocally";
+ break;
+
+ default:
+ return "impossible internal state";
+ break;
+
+ }
+
+ return "unknown";
+}
+
+
+SteamworksMatchmaking::SteamworksMatchmaking() {
+ // initializing relay access on startup helps to start multiplayer faster
+ // but if the game is closed before it has finished initializing
+ // it will crash inside steam libraries
+ // so we don't do it
+ // FIXME: enable if steam fixes their lib
+#if 0
+ LOGI << "Initializing steam relay network access ..." << std::endl;
+ SteamNetworkingUtils()->InitRelayNetworkAccess();
+#endif // 0
+}
+
+
+SteamworksMatchmaking::~SteamworksMatchmaking() {
+ // if we free these it can cause crashes on exit on linux
+ // apparently steam api doesn't properly wait for its internal threads
+ // to shut down
+
+ if (!lobbies.empty()) {
+ LOGW << lobbies.size() << " lobbies remain at shutdown" << std::endl;
+#if 0
+ for (unsigned int i = 0; i < lobbies.size(); i++) {
+ SteamMatchmaking()->LeaveLobby(lobbies[i]);
+ }
+#endif // 0
+
+ lobbies.clear();
+ }
+}
+
+
+HSteamListenSocket SteamworksMatchmaking::ActivateGameOverlayInviteDialog() {
+ LOGI << "SteamworksMatchmaking::ActivateGameOverlayInviteDialog()" << std::endl;
+
+ SteamMatchmaking()->CreateLobby(k_ELobbyTypePrivate, 10);
+
+ ISteamNetworkingSockets *isns = SteamNetworkingSockets();
+ assert(isns);
+
+ HSteamListenSocket listen = isns->CreateListenSocketP2P(0, 0, nullptr);
+
+ return listen;
+}
+
+void SteamworksMatchmaking::OpenInviteDialog()
+{
+ if (lobbies.begin() != lobbies.end()) {
+ if (SteamMatchmaking != nullptr) {
+ ISteamFriends * friends = SteamFriends();
+ if (friends) {
+ friends->ActivateGameOverlayInviteDialog(*lobbies.begin());
+ }
+ }
+ }
+
+}
+
+
+void SteamworksMatchmaking::JoinLobby(const std::string &lobbyIDStr) {
+ LOGI << "SteamworksMatchmaking::JoinLobby " << lobbyIDStr << std::endl;
+
+ char *end = NULL;
+ // We presume uint64 to be the same as the unsigned long returned by strtoul
+ uint64 l = strtoul(lobbyIDStr.c_str(), &end, 10);
+
+ CSteamID lobbyID(l);
+
+ LOGI << "parsed lobby id: \"" << lobbyID.ConvertToUint64() << "\"" << std::endl;
+
+ SteamMatchmaking()->JoinLobby(lobbyID);
+}
+
+
+void SteamworksMatchmaking::LeaveLobby(CSteamID lobbyID) {
+ SteamMatchmaking()->LeaveLobby(lobbyID);
+ auto it = std::find(lobbies.begin(), lobbies.end(), lobbyID);
+ if (it != lobbies.end()) {
+ lobbies.erase(it);
+ }
+}
+
+
+void SteamworksMatchmaking::OnLobbyEnter(LobbyEnter_t *data) {
+ Online* online = Online::Instance();
+
+ assert(data);
+
+ LOGI << "OnLobbyEnter " << data->m_ulSteamIDLobby << std::endl;
+
+ lobbies.insert(data->m_ulSteamIDLobby);
+
+ if (online->IsHosting()) {
+ // we are the host
+ // set lobby metadata
+ SteamMatchmaking()->SetLobbyData(data->m_ulSteamIDLobby, LEVEL_PATH, "");
+
+ // activate overlay
+ SteamFriends()->ActivateGameOverlayInviteDialog(data->m_ulSteamIDLobby);
+ } else {
+ // we are client
+ // get lobby metadata
+
+ // Start multiplayer with host
+ CSteamID lobbyHost = SteamMatchmaking()->GetLobbyOwner(data->m_ulSteamIDLobby);
+ ISteamNetworkingSockets *isns = SteamNetworkingSockets();
+ assert(isns);
+
+ SteamNetworkingIdentity id;
+ id.SetSteamID(lobbyHost);
+
+ HSteamNetConnection connection = isns->ConnectP2P(id, 0, 0, nullptr);
+ std::vector<char> temp(1024, 0);
+ int ret = isns->GetDetailedConnectionStatus(connection, &temp[0], temp.size());
+ if (ret >= 0) {
+ LOGI << "connection status: " << &temp[0] << std::endl;
+ }
+ else {
+ LOGE << "GetDetailedConnectionStatus failed" << std::endl;
+ }
+ steamPendingConnections.insert(connection);
+
+ // Leave lobby - maybe not?
+ LeaveLobby(data->m_ulSteamIDLobby);
+
+ if (!online->IsActive())
+ online->StartMultiplayer(MultiplayerMode::Client);
+ }
+}
+
+
+void SteamworksMatchmaking::OnLobbyDataUpdate(LobbyDataUpdate_t *data) {
+ assert(data);
+
+ LOGI << "OnLobbyDataUpdate " << data->m_ulSteamIDLobby << std::endl;
+
+ if (data->m_bSuccess) {
+ LOGI << "Transition was a success" << std::endl;
+ }
+ }
+
+
+void SteamworksMatchmaking::OnLobbyJoinRequested(GameLobbyJoinRequested_t *data) {
+ assert(data);
+
+ LOGI << "OnLobbyJoinRequested" << std::endl;
+
+ SteamMatchmaking()->JoinLobby(data->m_steamIDLobby);
+}
+
+
+void SteamworksMatchmaking::OnLobbyChatUpdate(LobbyChatUpdate_t *data) {
+ Online* online = Online::Instance();
+ assert(data);
+
+ LOGI << "OnLobbyChatUpdate " << data->m_ulSteamIDLobby << std::endl;
+ if (data->m_rgfChatMemberStateChange & k_EChatMemberStateChangeEntered) {
+ LOGI << "User " << data->m_ulSteamIDUserChanged << " joined lobby" << std::endl;
+ if (online->IsHosting()) {
+ // Friend accepted invite
+ // Leave lobby
+ // For single player mode host should maybe stay in lobby and only clients leave as they enter game
+ //LeaveLobby(data->m_ulSteamIDLobby);
+ // Steam doesn't offer way to close overlay from app, user needs to do that
+ }
+ } else {
+ LOGI << "User " << data->m_ulSteamIDUserChanged << " left lobby" << std::endl;
+ }
+}
+
+
+void SteamworksMatchmaking::OnConnectionChange(SteamNetConnectionStatusChangedCallback_t *data) {
+
+ ISteamNetworkingSockets *isns = SteamNetworkingSockets();
+
+ assert(data);
+ LOGI << "SteamworksMatchmaking::OnConnectionChange " << data->m_hConn << " old:" << StatusToString(data->m_eOldState) << " new: " << StatusToString(data->m_info.m_eState) << std::endl;
+
+
+
+ /*/
+ switch (data->m_info.m_eState) {
+ case k_ESteamNetworkingConnectionState_Connecting: {
+ //EResult result = isns->AcceptConnection(data->m_hConn);
+ break;
+ }
+
+ default: {
+
+ }
+ }
+ */
+
+
+ auto it = steamPendingConnections.find(data->m_hConn);
+ if (it != steamPendingConnections.end()) {
+ // This is a Steam connection
+ LOGI << "Steam connection" << std::endl;
+ steamPendingConnections.erase(it);
+ }
+}
+
+
+std::string SteamworksMatchmaking::GetSteamNickname(const CSteamID &friendID) {
+ std::string name = "<unknown>";
+
+ ISteamFriends *friends = SteamFriends();
+ assert(friends);
+
+ if (friendID.IsValid()) {
+ // query for friend nickname, might not exist
+ const char *nickname = friends->GetPlayerNickname(friendID);
+ if (nickname) {
+ name = nickname;
+ } else {
+ // TODO: might not exist immediately when joining lobby
+ // need to wait for PersonaStateChange_t
+ // when not exists guaranteed to be non-NULL but possibly empty
+ name = friends->GetFriendPersonaName(friendID);
+ }
+ }
+
+ return name;
+}
+#endif
diff --git a/Source/Steam/steamworks_matchmaking.h b/Source/Steam/steamworks_matchmaking.h
new file mode 100644
index 00000000..c3eefb0a
--- /dev/null
+++ b/Source/Steam/steamworks_matchmaking.h
@@ -0,0 +1,96 @@
+//-----------------------------------------------------------------------------
+// Name: steamworks_matchmaking.h
+// Developer: Wolfire Games LLC
+// Description: Steamworks wrapper for Overgrowth, to simplify use and
+// minimize state.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+// steam_api.h requires cstddef for offsetof but doesn't include it itself
+#include <cstddef>
+
+#if ENABLE_STEAMWORKS || ENABLE_GAMENETWORKINGSOCKETS
+#include <isteamnetworkingsockets.h>
+#endif
+
+#if ENABLE_STEAMWORKS
+#include <steam_api.h>
+#endif
+
+#include <string>
+#include <unordered_set>
+
+#if ENABLE_STEAMWORKS
+
+namespace std {
+
+
+ template <> struct hash<CSteamID> {
+ size_t operator()(const CSteamID &id) const {
+ return hash<uint64_t>()(id.ConvertToUint64());
+ }
+ };
+
+
+} // namespace std
+
+
+class SteamworksMatchmaking {
+private:
+ friend class Steamworks;
+ SteamworksMatchmaking();
+
+ std::unordered_set<CSteamID> lobbies; // we are we assuming multiple??
+ std::unordered_set<HSteamNetConnection> steamPendingConnections;
+
+
+public:
+ ~SteamworksMatchmaking();
+
+ // activate game overlay to the invite dialog
+ // invitations sent from this dialog will be for the provided lobby
+
+ // This function has a ton of side effects and requires refactor
+ // it basically inits an entire mp state.
+ HSteamListenSocket ActivateGameOverlayInviteDialog();
+
+ //
+ void OpenInviteDialog();
+
+ // for command-line jobby loining
+ void JoinLobby(const std::string &lobbyIDStr);
+
+ // leave lobby
+ void LeaveLobby(CSteamID lobbyID);
+
+ // get Steam friend name
+ std::string GetSteamNickname(const CSteamID &friendID);
+
+ static const char *StatusToString(ESteamNetworkingConnectionState state);
+
+
+ STEAM_CALLBACK(SteamworksMatchmaking, OnLobbyEnter, LobbyEnter_t);
+ STEAM_CALLBACK(SteamworksMatchmaking, OnLobbyDataUpdate, LobbyDataUpdate_t);
+ STEAM_CALLBACK(SteamworksMatchmaking, OnLobbyJoinRequested, GameLobbyJoinRequested_t);
+ STEAM_CALLBACK(SteamworksMatchmaking, OnLobbyChatUpdate, LobbyChatUpdate_t);
+ STEAM_CALLBACK(SteamworksMatchmaking, OnConnectionChange, SteamNetConnectionStatusChangedCallback_t);
+
+};
+#endif
diff --git a/Source/Steam/steamworks_util.cpp b/Source/Steam/steamworks_util.cpp
new file mode 100644
index 00000000..d633063d
--- /dev/null
+++ b/Source/Steam/steamworks_util.cpp
@@ -0,0 +1,141 @@
+//-----------------------------------------------------------------------------
+// Name: steamworks_util.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "steamworks_util.h"
+
+#if ENABLE_STEAMWORKS
+const char* GetEResultString( const EResult& error ) {
+ switch( error ) {
+ case k_EResultOK: return "success";
+ case k_EResultFail: return "generic failure ";
+ case k_EResultNoConnection: return "no/failed network connection";
+ case k_EResultInvalidPassword: return "password/ticket is invalid";
+ case k_EResultLoggedInElsewhere: return "same user logged in elsewhere";
+ case k_EResultInvalidProtocolVer: return "protocol version is incorrect";
+ case k_EResultInvalidParam: return "a parameter is incorrect";
+ case k_EResultFileNotFound: return "file was not found";
+ case k_EResultBusy: return "called method busy - action not taken";
+ case k_EResultInvalidState: return "called object was in an invalid state";
+ case k_EResultInvalidName: return "name is invalid";
+ case k_EResultInvalidEmail: return "email is invalid";
+ case k_EResultDuplicateName: return "name is not unique";
+ case k_EResultAccessDenied: return "access is denied";
+ case k_EResultTimeout: return "operation timed out";
+ case k_EResultBanned: return "VAC2 banned";
+ case k_EResultAccountNotFound: return "account not found";
+ case k_EResultInvalidSteamID: return "steamID is invalid";
+ case k_EResultServiceUnavailable: return "The requested service is currently unavailable";
+ case k_EResultNotLoggedOn: return "The user is not logged on";
+ case k_EResultPending: return "Request is pending (may be in process, or waiting on third party)";
+ case k_EResultEncryptionFailure: return "Encryption or Decryption failed";
+ case k_EResultInsufficientPrivilege: return "Insufficient privilege";
+ case k_EResultLimitExceeded: return "Too much of a good thing";
+ case k_EResultRevoked: return "Access has been revoked (used for revoked guest passes)";
+ case k_EResultExpired: return "License/Guest pass the user is trying to access is expired";
+ case k_EResultAlreadyRedeemed: return "Guest pass has already been redeemed by account, cannot be acked again";
+ case k_EResultDuplicateRequest: return "The request is a duplicate and the action has already occurred in the past, ignored this time";
+ case k_EResultAlreadyOwned: return "All the games in this guest pass redemption request are already owned by the user";
+ case k_EResultIPNotFound: return "IP address not found";
+ case k_EResultPersistFailed: return "failed to write change to the data store";
+ case k_EResultLockingFailed: return "failed to acquire access lock for this operation";
+ case k_EResultLogonSessionReplaced: return "Logon Session Replaced";
+ case k_EResultConnectFailed: return "Connect Failed";
+ case k_EResultHandshakeFailed: return "Handshake Failed";
+ case k_EResultIOFailure: return "IO Failure";
+ case k_EResultRemoteDisconnect: return "Remote Disconnect";
+ case k_EResultShoppingCartNotFound: return "failed to find the shopping cart requested";
+ case k_EResultBlocked: return "a user didn't allow it";
+ case k_EResultIgnored: return "target is ignoring sender";
+ case k_EResultNoMatch: return "nothing matching the request found";
+ case k_EResultAccountDisabled: return "Account Disabled";
+ case k_EResultServiceReadOnly: return "this service is not accepting content changes right now";
+ case k_EResultAccountNotFeatured: return "account doesn't have value, so this feature isn't available";
+ case k_EResultAdministratorOK: return "allowed to take this action, but only because requester is admin";
+ case k_EResultContentVersion: return "A Version mismatch in content transmitted within the Steam protocol.";
+ case k_EResultTryAnotherCM: return "The current CM can't service the user making a request, user should try another.";
+ case k_EResultPasswordRequiredToKickSession: return "You are already logged in elsewhere, this cached credential login has failed.";
+ case k_EResultAlreadyLoggedInElsewhere: return "You are already logged in elsewhere, you must wait";
+ case k_EResultSuspended: return "Long running operation (content download) suspended/paused";
+ case k_EResultCancelled: return "Operation canceled (typically by user: content download)";
+ case k_EResultDataCorruption: return "Operation canceled because data is ill formed or unrecoverable";
+ case k_EResultDiskFull: return "Operation canceled - not enough disk space.";
+ case k_EResultRemoteCallFailed: return "an remote call or IPC call failed";
+ case k_EResultPasswordUnset: return "Password could not be verified as it's unset server side";
+ case k_EResultExternalAccountUnlinked: return "External account (PSN, Facebook...) is not linked to a Steam account";
+ case k_EResultPSNTicketInvalid: return "PSN ticket was invalid";
+ case k_EResultExternalAccountAlreadyLinked: return "External account (PSN, Facebook...) is already linked to some other account, must explicitly request to replace/delete the link first";
+ case k_EResultRemoteFileConflict: return "The sync cannot resume due to a conflict between the local and remote files";
+ case k_EResultIllegalPassword: return "The requested new password is not legal";
+ case k_EResultSameAsPreviousValue: return "new value is the same as the old one ( secret question and answer )";
+ case k_EResultAccountLogonDenied: return "account login denied due to 2nd factor authentication failure";
+ case k_EResultCannotUseOldPassword: return "The requested new password is not legal";
+ case k_EResultInvalidLoginAuthCode: return "account login denied due to auth code invalid";
+ case k_EResultAccountLogonDeniedNoMail: return "account login denied due to 2nd factor auth failure - and no mail has been sent";
+ case k_EResultHardwareNotCapableOfIPT: return "Hardware Not Capable Of IPT";
+ case k_EResultIPTInitError: return "IPT Init Error";
+ case k_EResultParentalControlRestricted: return "operation failed due to parental control restrictions for current user";
+ case k_EResultFacebookQueryError: return "Facebook query returned an error";
+ case k_EResultExpiredLoginAuthCode: return "account login denied due to auth code expired";
+ case k_EResultIPLoginRestrictionFailed: return "IP Login Restriction Failed";
+ case k_EResultAccountLockedDown: return "Account Locked Down";
+ case k_EResultAccountLogonDeniedVerifiedEmailRequired: return "Account Logon Denied Verified Email Required";
+ case k_EResultNoMatchingURL: return "No Matching URL";
+ case k_EResultBadResponse: return "parse failure, missing field, etc.";
+ case k_EResultRequirePasswordReEntry: return "The user cannot complete the action until they re-enter their password";
+ case k_EResultValueOutOfRange: return "the value entered is outside the acceptable range";
+ case k_EResultUnexpectedError: return "something happened that we didn't expect to ever happen";
+ case k_EResultDisabled: return "The requested service has been configured to be unavailable";
+ case k_EResultInvalidCEGSubmission: return "The set of files submitted to the CEG server are not valid !";
+ case k_EResultRestrictedDevice: return "The device being used is not allowed to perform this action";
+ case k_EResultRegionLocked: return "The action could not be complete because it is region restricted";
+ case k_EResultRateLimitExceeded: return "Temporary rate limit exceeded, try again later, different from k_EResultLimitExceeded which may be permanent";
+ case k_EResultAccountLoginDeniedNeedTwoFactor: return "Need two-factor code to login";
+ case k_EResultItemDeleted: return "The thing we're trying to access has been deleted";
+ case k_EResultAccountLoginDeniedThrottle: return "login attempt failed, try to throttle response to possible attacker";
+ case k_EResultTwoFactorCodeMismatch: return "two factor code mismatch";
+ case k_EResultTwoFactorActivationCodeMismatch: return "activation code for two-factor didn't match";
+ case k_EResultAccountAssociatedToMultiplePartners: return "account has been associated with multiple partners";
+ case k_EResultNotModified: return "data not modified";
+ case k_EResultNoMobileDevice: return "the account does not have a mobile device associated with it";
+ case k_EResultTimeNotSynced: return "the time presented is out of range or tolerance";
+ case k_EResultSmsCodeFailed: return "SMS code failure (no match, none pending, etc.)";
+ case k_EResultAccountLimitExceeded: return "Too many accounts access this resource";
+ case k_EResultAccountActivityLimitExceeded: return "Too many changes to this account";
+ case k_EResultPhoneActivityLimitExceeded: return "Too many changes to this phone";
+ case k_EResultRefundToWallet: return "Cannot refund to payment method, must use wallet";
+ case k_EResultEmailSendFailure: return "Cannot send an email";
+ case k_EResultNotSettled: return "Can't perform operation till payment has settled";
+ case k_EResultNeedCaptcha: return "Needs to provide a valid captcha";
+ case k_EResultGSLTDenied: return "a game server login token owned by this token's owner has been banned";
+ case k_EResultGSOwnerDenied: return "game server owner is denied for other reason (account lock, community ban, vac ban, missing phone)";
+ case k_EResultInvalidItemType: return "the type of thing we were requested to act on is invalid";
+ case k_EResultIPBanned: return "the ip address has been banned from taking this action";
+ case k_EResultGSLTExpired: return "this token has expired from disuse; can be reset for use";
+ default: return "";
+ }
+}
+
+std::ostream& operator<< ( std::ostream& data, const EResult& obj ) {
+ data << "\"Steamworks Error: " << GetEResultString( obj ) << "\"";
+ return data;
+}
+#endif
diff --git a/Source/Steam/steamworks_util.h b/Source/Steam/steamworks_util.h
new file mode 100644
index 00000000..d223f578
--- /dev/null
+++ b/Source/Steam/steamworks_util.h
@@ -0,0 +1,40 @@
+//-----------------------------------------------------------------------------
+// Name: steamworks_util.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Logging/logdata.h>
+
+
+#if ENABLE_STEAMWORKS
+#include <steam_api.h>
+#endif
+
+#include <ostream>
+
+
+#if ENABLE_STEAMWORKS
+const char* GetEResultString( const EResult& error );
+
+std::ostream& operator<< ( std::ostream& data, const EResult& obj );
+#endif
+
diff --git a/Source/Steam/ugc.cpp b/Source/Steam/ugc.cpp
new file mode 100644
index 00000000..aeb02426
--- /dev/null
+++ b/Source/Steam/ugc.cpp
@@ -0,0 +1,320 @@
+//-----------------------------------------------------------------------------
+// Name: ugc.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "ugc.h"
+
+#include <Internal/integer.h>
+#include <Internal/modid.h>
+
+#include <Memory/allocation.h>
+#include <Steam/ugc_item.h>
+#include <Logging/logdata.h>
+
+#if ENABLE_STEAMWORKS
+#include <steam_api.h>
+#endif
+
+#include <cmath>
+
+#if ENABLE_STEAMWORKS
+
+SteamworksUGC::SteamworksUGC() :
+m_callRemoteStoragePublishedFileSubscribed(this, &SteamworksUGC::OnUGCRemoteStoragePublishedFileSubscribed),
+m_callRemoteStoragePublishedFileUnsubscribed(this, &SteamworksUGC::OnUGCRemoteStoragePublishedFileUnsubscribed),
+m_callRemoteStoragePublishedFileDeleted(this, &SteamworksUGC::OnUGCRemoteStoragePublishedFileDeleted),
+update_item(0)
+{
+ uint32_t nr_items = SteamUGC()->GetNumSubscribedItems();
+
+ PublishedFileId_t* published_file_ids = static_cast<PublishedFileId_t*>(alloc.stack.Alloc(nr_items*sizeof(PublishedFileId_t)));
+
+ uint32_t ret_items = SteamUGC()->GetSubscribedItems( published_file_ids, nr_items );
+
+ for( unsigned i = 0; i < ret_items; i++ ) {
+ LoadModIntoGame(published_file_ids[i]);
+ }
+
+ m_callRemoteStoragePublishedFileSubscribed.Register(this, &SteamworksUGC::OnUGCRemoteStoragePublishedFileSubscribed);
+ m_callRemoteStoragePublishedFileUnsubscribed.Register(this, &SteamworksUGC::OnUGCRemoteStoragePublishedFileUnsubscribed);
+ m_callRemoteStoragePublishedFileDeleted.Register(this, &SteamworksUGC::OnUGCRemoteStoragePublishedFileDeleted);
+ alloc.stack.Free(published_file_ids);
+
+ QueryPersonalWorkshopItems();
+ QueryFavoritesWorkshopItems();
+}
+
+void SteamworksUGC::Update() {
+ if(!items.empty()) {
+ for(int end = update_item + int(ceilf(items.size() / (float)MAX_UPDATE_FRAMES)); update_item < end; ++update_item) {
+ items[update_item % items.size()]->Update();
+ }
+
+ update_item %= items.size();
+ }
+}
+
+UGCID SteamworksUGC::TryUploadMod( ModID sid ) {
+ std::vector<SteamworksUGCItem*>::iterator pre_item = GetItem( sid );
+ if( pre_item == GetItemEnd() ) {
+ SteamworksUGCItem *item = new SteamworksUGCItem(sid);
+ items.push_back(item);
+ return item->GetUGCID();
+ } else {
+ LOGE << "Already have a mod with this sid, tied to a steamworks mod" << std::endl;
+ return UGCID();
+ }
+}
+
+std::vector<SteamworksUGCItem*>::iterator SteamworksUGC::LoadModIntoGame( PublishedFileId_t steamworks_id ) {
+ std::vector<SteamworksUGCItem*>::iterator retit = GetItem( steamworks_id );
+ if( retit == GetItemEnd() ) {
+ uint32_t is = SteamUGC()->GetItemState(steamworks_id);
+ if( is & k_EItemStateLegacyItem ) {
+ LOGE << "Mod is a legacy mod, missing support for such, won't load" << std::endl;
+ } else {
+ items.push_back(new SteamworksUGCItem(steamworks_id));
+ retit = items.begin()+(items.size()-1);
+ }
+ }
+ return retit;
+}
+
+std::vector<SteamworksUGCItem*>::iterator SteamworksUGC::GetItem( PublishedFileId_t steamworks_id ) {
+ std::vector<SteamworksUGCItem*>::iterator it = items.begin();
+ for( ; it != items.end(); it++ ) {
+ if( (*it)->steamworks_id == steamworks_id ) {
+ break;
+ }
+ }
+ return it;
+}
+
+std::vector<SteamworksUGCItem*>::iterator SteamworksUGC::GetItem( ModID sid ) {
+ std::vector<SteamworksUGCItem*>::iterator it = items.begin();
+ for( ; it != items.end(); it++ ) {
+ if( (*it)->sid == sid ) {
+ break;
+ }
+ }
+ return it;
+}
+
+std::vector<SteamworksUGCItem*>::iterator SteamworksUGC::GetItem( UGCID ugc_id ) {
+ std::vector<SteamworksUGCItem*>::iterator it = items.begin();
+ for( ; it != items.end(); it++ ) {
+ if( (*it)->ugc_id == ugc_id) {
+ break;
+ }
+ }
+ return it;
+}
+
+std::vector<SteamworksUGCItem*>::iterator SteamworksUGC::GetItemEnd() {
+ return items.end();
+}
+
+std::vector<SteamworksUGCItem*>::iterator SteamworksUGC::GetItemBegin() {
+ return items.begin();
+}
+
+size_t SteamworksUGC::SubscribedNotInstalledCount() {
+ size_t ret = 0;
+ for( unsigned i = 0; i < items.size(); i++ ) {
+ if( items[i]->IsSubscribed() && items[i]->IsInstalled() == false ) {
+ ret++;
+ }
+ }
+ return ret;
+}
+
+size_t SteamworksUGC::DownloadingCount() {
+ size_t ret = 0;
+ for( unsigned i = 0; i < items.size(); i++ ) {
+ if( items[i]->IsDownloading() ) {
+ ret++;
+ }
+ }
+ return ret;
+}
+
+size_t SteamworksUGC::DownloadPendingCount() {
+ size_t ret = 0;
+ for( unsigned i = 0; i < items.size(); i++ ) {
+ if( items[i]->IsDownloadPending() ) {
+ ret++;
+ }
+ }
+ return ret;
+}
+
+size_t SteamworksUGC::NeedsUpdateCount() {
+ size_t ret = 0;
+ for( unsigned i = 0; i < items.size(); i++ ) {
+ if( items[i]->NeedsUpdate() ) {
+ ret++;
+ }
+ }
+ return ret;
+}
+
+float SteamworksUGC::TotalDownloadProgress() {
+ float total = 0.0f;
+ int count = 0;
+ for( unsigned i = 0; i < items.size(); i++ ) {
+ if( items[i]->NeedsUpdate() ) {
+ total += items[i]->ItemDownloadProgress();
+ count++;
+ }
+ }
+ return total/(float)count;
+}
+
+bool SteamworksUGC::WaitingForResults() {
+ for( unsigned i = 0; i < items.size() ; i++ ) {
+ if( items[i]->WaitingForResults() )
+ return true;
+ }
+ return false;
+}
+
+void SteamworksUGC::QueryPersonalWorkshopItems() {
+ CSteamID userid = SteamUser()->GetSteamID();
+
+ UGCQueryHandle_t query_handle = SteamUGC()->CreateQueryUserUGCRequest(
+ userid.GetAccountID(),
+ k_EUserUGCList_Published,
+ k_EUGCMatchingUGCType_All,
+ k_EUserUGCListSortOrder_TitleAsc,
+ OVERGROWTH_APP_ID,
+ OVERGROWTH_APP_ID,
+ 1
+ );
+
+
+ SteamAPICall_t call = SteamUGC()->SendQueryUGCRequest( query_handle );
+ m_callSteamUGCQueryCompleted.Set(call, this, &SteamworksUGC::OnUGCSteamUGCQueryCompleted);
+}
+
+void SteamworksUGC::QueryFavoritesWorkshopItems() {
+ CSteamID userid = SteamUser()->GetSteamID();
+
+ std::vector<SteamworksUGCItem*>::iterator itemit;
+
+ for( itemit = GetItemBegin(); itemit != GetItemEnd(); itemit++ ) {
+ (*itemit)->favorite_clean_for_query = true;
+ }
+
+ UGCQueryHandle_t query_handle = SteamUGC()->CreateQueryUserUGCRequest(
+ userid.GetAccountID(),
+ k_EUserUGCList_Favorited,
+ k_EUGCMatchingUGCType_All,
+ k_EUserUGCListSortOrder_TitleAsc,
+ OVERGROWTH_APP_ID,
+ OVERGROWTH_APP_ID,
+ 1
+ );
+
+ SteamAPICall_t call = SteamUGC()->SendQueryUGCRequest( query_handle );
+ m_callSteamUGCQueryFavoritesCompleted.Set(call, this, &SteamworksUGC::OnUGCSteamUGCQueryFavoritesCompleted);
+}
+
+SteamworksUGC::~SteamworksUGC() {
+
+}
+
+void SteamworksUGC::OnUGCRemoteStoragePublishedFileSubscribed( RemoteStoragePublishedFileSubscribed_t *pCallback ) {
+ LOGI << "Got notified about user subscribing to: " << pCallback->m_nPublishedFileId << std::endl;
+ std::vector<SteamworksUGCItem*>::iterator pre_item = GetItem( pCallback->m_nPublishedFileId );
+ if( pre_item == GetItemEnd() ) {
+ LoadModIntoGame(pCallback->m_nPublishedFileId);
+ } else {
+ (*pre_item)->Reload();
+ }
+}
+
+void SteamworksUGC::OnUGCRemoteStoragePublishedFileUnsubscribed( RemoteStoragePublishedFileUnsubscribed_t *pCallback ) {
+ LOGI << "Got notified about user unsubscribing to: " << pCallback->m_nPublishedFileId << std::endl;
+
+ std::vector<SteamworksUGCItem*>::iterator pre_item = GetItem( pCallback->m_nPublishedFileId );
+ if( pre_item != GetItemEnd() ){
+ (*pre_item)->Reload();
+ }
+}
+
+void SteamworksUGC::OnUGCRemoteStoragePublishedFileDeleted( RemoteStoragePublishedFileDeleted_t *pCallback ) {
+ LOGI << "Got notified deleted mod: " << pCallback->m_nPublishedFileId << std::endl;
+
+ std::vector<SteamworksUGCItem*>::iterator pre_item = GetItem( pCallback->m_nPublishedFileId );
+ if( pre_item != GetItemEnd() ){
+ (*pre_item)->Reload();
+ }
+}
+
+void SteamworksUGC::OnUGCSteamUGCQueryFavoritesCompleted( SteamUGCQueryCompleted_t *pResult, bool failed ) {
+ LOGI << "OnUGCSteamUGCQueryFavoriteCompleted() " << pResult->m_eResult << std::endl;
+ if( failed == false ) {
+ std::vector<SteamworksUGCItem*>::iterator itemit;
+
+ for( itemit = GetItemBegin(); itemit != GetItemEnd(); itemit++ ) {
+ if( (*itemit)->favorite_clean_for_query ) {
+ (*itemit)->favorite = false;
+ }
+ }
+
+ for( unsigned i = 0; i < pResult->m_unNumResultsReturned; i++ ) {
+ SteamUGCDetails_t pDetails;
+ SteamUGC()->GetQueryUGCResult(pResult->m_handle, i, &pDetails);
+ std::vector<SteamworksUGCItem*>::iterator modit = LoadModIntoGame(pDetails.m_nPublishedFileId);
+
+ if( modit != GetItemEnd() ) {
+ if( (*modit)->favorite_clean_for_query ) {
+ (*modit)->favorite = true;
+ }
+ }
+ }
+ } else {
+ LOGE << "OnUGCSteamUGCQueryFavoritesCompleted() error " << pResult->m_eResult << std::endl;
+ }
+
+ if( pResult ) {
+ SteamUGC()->ReleaseQueryUGCRequest(pResult->m_handle);
+ }
+}
+
+void SteamworksUGC::OnUGCSteamUGCQueryCompleted( SteamUGCQueryCompleted_t *pResult, bool failed ) {
+ LOGI << "OnUGCSteamUGCQueryCompleted() " << pResult->m_eResult << std::endl;
+
+ if( failed == false ) {
+ for( unsigned i = 0; i < pResult->m_unNumResultsReturned; i++ ) {
+ SteamUGCDetails_t pDetails;
+ SteamUGC()->GetQueryUGCResult(pResult->m_handle, i, &pDetails);
+ LoadModIntoGame(pDetails.m_nPublishedFileId);
+ }
+ } else {
+ LOGE << "OnUGCSteamUGCQueryCompleted() error " << pResult->m_eResult << std::endl;
+ }
+
+ if( pResult ) {
+ SteamUGC()->ReleaseQueryUGCRequest(pResult->m_handle);
+ }
+}
+#endif
diff --git a/Source/Steam/ugc.h b/Source/Steam/ugc.h
new file mode 100644
index 00000000..d871b570
--- /dev/null
+++ b/Source/Steam/ugc.h
@@ -0,0 +1,91 @@
+//-----------------------------------------------------------------------------
+// Name: ugc.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#if ENABLE_STEAMWORKS
+#include <Steam/ugc_id.h>
+#include <Internal/modid.h>
+
+#include <steam_api.h>
+
+#include <vector>
+#include <string>
+#include <set>
+
+class SteamworksUGCItem;
+
+
+class SteamworksUGC {
+private:
+ friend class Steamworks;
+ SteamworksUGC();
+
+ // After this many update calls, every item should be updated
+ // So if 120 mods are activated, 2 will be updated each frame when this is 60
+ const static int MAX_UPDATE_FRAMES = 60;
+ int update_item;
+public:
+ ~SteamworksUGC();
+
+ void Update();
+
+ UGCID TryUploadMod( ModID sid );
+
+ std::vector<SteamworksUGCItem*>::iterator LoadModIntoGame( PublishedFileId_t steamworks_id );
+
+ std::vector<SteamworksUGCItem*>::iterator GetItem( PublishedFileId_t steamworks_id );
+ std::vector<SteamworksUGCItem*>::iterator GetItem( ModID sid );
+ std::vector<SteamworksUGCItem*>::iterator GetItem( UGCID id );
+ std::vector<SteamworksUGCItem*>::iterator GetItemBegin();
+ std::vector<SteamworksUGCItem*>::iterator GetItemEnd();
+
+ size_t SubscribedNotInstalledCount();
+ size_t DownloadingCount();
+ size_t DownloadPendingCount();
+ size_t NeedsUpdateCount();
+
+ float TotalDownloadProgress();
+
+ bool WaitingForResults();
+
+ void QueryPersonalWorkshopItems();
+ void QueryFavoritesWorkshopItems();
+
+ std::vector<SteamworksUGCItem*> items;
+
+ void OnUGCRemoteStoragePublishedFileSubscribed( RemoteStoragePublishedFileSubscribed_t *pCallback );
+ CCallback<SteamworksUGC, RemoteStoragePublishedFileSubscribed_t, false> m_callRemoteStoragePublishedFileSubscribed;
+
+ void OnUGCRemoteStoragePublishedFileUnsubscribed( RemoteStoragePublishedFileUnsubscribed_t *pCallback );
+ CCallback<SteamworksUGC, RemoteStoragePublishedFileUnsubscribed_t, false> m_callRemoteStoragePublishedFileUnsubscribed;
+
+ void OnUGCRemoteStoragePublishedFileDeleted( RemoteStoragePublishedFileDeleted_t *pCallback );
+ CCallback<SteamworksUGC, RemoteStoragePublishedFileDeleted_t, false> m_callRemoteStoragePublishedFileDeleted;
+
+ void OnUGCSteamUGCQueryCompleted( SteamUGCQueryCompleted_t *pResult, bool failed );
+ CCallResult<SteamworksUGC, SteamUGCQueryCompleted_t> m_callSteamUGCQueryCompleted;
+
+ void OnUGCSteamUGCQueryFavoritesCompleted( SteamUGCQueryCompleted_t *pResult, bool failed );
+ CCallResult<SteamworksUGC, SteamUGCQueryCompleted_t> m_callSteamUGCQueryFavoritesCompleted;
+};
+#endif
diff --git a/Source/Steam/ugc_id.cpp b/Source/Steam/ugc_id.cpp
new file mode 100644
index 00000000..47d21cb1
--- /dev/null
+++ b/Source/Steam/ugc_id.cpp
@@ -0,0 +1,47 @@
+//-----------------------------------------------------------------------------
+// Name: ugc_id.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "ugc_id.h"
+
+UGCID::UGCID() : id(-1) {
+
+}
+
+UGCID::UGCID( int _id ) : id(_id) {
+}
+
+bool UGCID::Valid() {
+ return id != -1;
+}
+
+bool UGCID::operator==( const UGCID& other ) const {
+ return this->id == other.id;
+}
+
+bool UGCID::operator!=( const UGCID& other ) const {
+ return this->id != other.id;
+}
+
+std::ostream& operator<<(std::ostream& os, const UGCID &mi ) {
+ os << mi.id;
+ return os;
+}
diff --git a/Source/Steam/ugc_id.h b/Source/Steam/ugc_id.h
new file mode 100644
index 00000000..ebc0b895
--- /dev/null
+++ b/Source/Steam/ugc_id.h
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------------
+// Name: ugc_id.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#if ENABLE_STEAMWORKS
+#include <steam_api.h>
+#endif
+
+#include <ostream>
+
+struct UGCID {
+ UGCID( );
+ UGCID( int id );
+ int id;
+
+ bool Valid();
+
+ bool operator==( const UGCID& other ) const;
+ bool operator!=( const UGCID& other ) const;
+};
+
+std::ostream& operator<<(std::ostream& os, const UGCID &mi );
diff --git a/Source/Steam/ugc_item.cpp b/Source/Steam/ugc_item.cpp
new file mode 100644
index 00000000..d2d5e772
--- /dev/null
+++ b/Source/Steam/ugc_item.cpp
@@ -0,0 +1,642 @@
+//-----------------------------------------------------------------------------
+// Name: ugc_item.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "ugc_item.h"
+
+#include <Internal/integer.h>
+#include <Internal/modloading.h>
+#include <Internal/filesystem.h>
+
+#include <JSON/json.h>
+#include <JSON/jsonhelper.h>
+
+#include <Steam/steamworks_util.h>
+#include <Steam/steamworks.h>
+
+#include <Logging/logdata.h>
+#include <Utility/assert.h>
+#include <Memory/allocation.h>
+#include <Compat/compat.h>
+
+#if ENABLE_STEAMWORKS
+int SteamworksUGCItem::ugc_id_counter = 1;
+
+SteamworksUGCItem::SteamworksUGCItem( ModID _upload_sid_source ) :
+ugc_id(ugc_id_counter++),
+steamworks_id(0),
+upload_sid_source(_upload_sid_source),
+waiting_for_details(false),
+waiting_for_create(false),
+waiting_for_update(false),
+was_installed(false),
+has_eresult_error(false),
+visibility(k_ERemoteStoragePublishedFileVisibilityPrivate),
+user_vote(k_VoteUnknown),
+favorite(false),
+favorite_clean_for_query(true),
+mod_data_size(0),
+need_update_installed_size(true),
+update_progress(0.0f)
+{
+ RequestCreation();
+}
+
+SteamworksUGCItem::SteamworksUGCItem( PublishedFileId_t s_id ) :
+ugc_id(ugc_id_counter++),
+steamworks_id(s_id),
+waiting_for_details(false),
+waiting_for_create(false),
+waiting_for_update(false),
+was_installed(false),
+has_eresult_error(false),
+visibility(k_ERemoteStoragePublishedFileVisibilityPrivate),
+user_vote(k_VoteUnknown),
+favorite(false),
+favorite_clean_for_query(true),
+mod_data_size(0),
+need_update_installed_size(true),
+update_progress(0.0f)
+{
+ RequestDetails();
+}
+
+UGCID SteamworksUGCItem::GetUGCID() {
+ return ugc_id;
+}
+
+void SteamworksUGCItem::RequestCreation() {
+ if( waiting_for_create == false ) {
+ if( steamworks_id == 0 ) {
+ SteamAPICall_t hSteamAPICall = SteamUGC()->CreateItem(OVERGROWTH_APP_ID,k_EWorkshopFileTypeCommunity);
+ m_callResultsCreateItemResult.Set( hSteamAPICall, this, &SteamworksUGCItem::OnUGCCreateItemResult);
+
+ waiting_for_create = true;
+ } else {
+ LOGE << "Requested creation of an already created steamworks item" << std::endl;
+ }
+ } else {
+ LOGE << "Already creating, can't queue another attempt" << std::endl;
+ }
+}
+
+void SteamworksUGCItem::RequestDetails() {
+ if( waiting_for_details == false ) {
+ LOGI << "Requesting detail info for id: " << id << std::endl;
+ LOGI << "Item state:" << ItemStateString( ItemState() ) << std::endl;
+ //SteamAPICall_t hSteamAPICall = SteamUGC()->RequestUGCDetails(steamworks_id, 60);
+ //m_callResultSteamUGCRequestUGCDetailsResult.Set( hSteamAPICall, this, &SteamworksUGCItem::OnFindUGCDetailsResult );
+
+ UGCQueryHandle_t handle = SteamUGC()->CreateQueryUGCDetailsRequest( &steamworks_id, 1 );
+
+ SteamUGC()->SetReturnMetadata( handle, true );
+
+ SteamAPICall_t apicall = SteamUGC()->SendQueryUGCRequest(handle);
+
+ waiting_for_details = true;
+
+ m_callSteamUGCQueryCompleted.Set(apicall, this, &SteamworksUGCItem::OnUGCSteamUGCQueryCompleted);
+
+ apicall = SteamUGC()->GetUserItemVote(steamworks_id);
+
+ m_callResultGetUserItemVoteResult.Set(apicall, this, &SteamworksUGCItem::OnUGCGetUserItemVoteResult);
+ } else {
+ LOGE << "Already waiting for a response on details" << std::endl;
+ }
+}
+
+uint32_t SteamworksUGCItem::VerifyForUpload() {
+ uint32_t status = 0;
+
+ ModInstance* mi = ModLoading::Instance().GetMod( upload_sid_source );
+
+ if( mi ) {
+ if( mi->preview_images.size() > 0 ) {
+ Path previewpath = FindFilePath( AssemblePath( mi->path, mi->preview_images[0] ).c_str(), kAbsPath, true );
+ if( previewpath.valid == false) {
+ status |= k_UploadVerifyInvalidPreviewImage;
+ }
+ } else {
+ status |= k_UploadVerifyMissingPreview;
+ }
+
+ Path modpath = FindFilePath( mi->path.c_str(), kAbsPath, true );
+
+ if( modpath.valid == false ) {
+ status |= k_UploadVerifyInvalidModFolder;
+ }
+
+ if( mi->version == version ) {
+ status |= k_UploadVerifyUnchangedVersion;
+ }
+ } else {
+ status |= k_UploadVerifyMissingModSource;
+ }
+
+ if( waiting_for_update ) {
+ status |= k_UploadVerifyWaitingForUpload;
+ }
+
+ return status;
+}
+
+void SteamworksUGCItem::RequestUpload(const char* update_message, ERemoteStoragePublishedFileVisibility new_visibility, uint32_t upload_control) {
+ bool accept_upload = false;
+ uint32_t verify = VerifyForUpload();
+
+ if( verify == k_UploadVerifyOk ) {
+ if( waiting_for_update == false ) {
+ ModInstance* mi = ModLoading::Instance().GetMod( upload_sid_source );
+ if( mi ) {
+ LOGI << "Doing upload" << std::endl;
+ id.set( mi->id);
+ version.set( mi->version);
+ name.set( mi->name);
+ description.set( mi->description);
+ author.set( mi->author);
+ tags = mi->tags;
+
+ update_handle = SteamUGC()->StartItemUpdate( OVERGROWTH_APP_ID, steamworks_id );
+
+ SteamUGC()->SetItemTitle( update_handle, name );
+
+ if( upload_control & k_UploadDataControl_UploadDescription ) {
+ SteamUGC()->SetItemDescription( update_handle, description );
+ }
+
+ SteamUGC()->SetItemUpdateLanguage( update_handle, "english" );
+
+ SimpleJSONWrapper json;
+
+ Json::Value& meta_root = json.getRoot();
+ meta_root["id"] = Json::Value(id);
+ meta_root["version"] = Json::Value(version);
+ meta_root["author"] = Json::Value(author);
+
+ LOG_ASSERT( k_cchDeveloperMetadataMax > json.writeString().length()+1 );
+ SteamUGC()->SetItemMetadata( update_handle, json.writeString().c_str() );
+
+ SteamUGC()->SetItemVisibility( update_handle, new_visibility );
+
+ if( tags.size() > 0 ) {
+ const char** tagsc = (const char**)alloc.stack.Alloc(tags.size());
+
+ std::set<std::string>::iterator tagit = tags.begin();
+ int count = 0;
+ for(; tagit != tags.end(); tagit++ ) {
+ tagsc[count] = tagit->c_str();
+ count++;
+ }
+
+ SteamParamStringArray_t pTags;
+ pTags.m_ppStrings = tagsc;
+ pTags.m_nNumStrings = count;
+ SteamUGC()->SetItemTags( update_handle, &pTags );
+
+ alloc.stack.Free(tagsc);
+ }
+
+ if( mi->preview_images.size() > 0 ) {
+ Path previewpath = FindFilePath( AssemblePath( mi->path, mi->preview_images[0] ).c_str(), kAbsPath, true );
+ if( previewpath.valid ) {
+ SteamUGC()->SetItemPreview( update_handle, previewpath.GetAbsPathStr().c_str() );
+ }
+ }
+
+ Path modpath = FindFilePath( mi->path.c_str(), kAbsPath, true );
+ if( modpath.valid ) {
+ SteamUGC()->SetItemContent( update_handle, modpath.GetAbsPathStr().c_str() );
+ }
+
+ SteamAPICall_t hSteamAPICall = SteamUGC()->SubmitItemUpdate( update_handle, update_message );
+
+ m_callResultSubmitItemUpdateResults.Set( hSteamAPICall, this, &SteamworksUGCItem::OnUGCSubmitItemUpdateResult );
+ waiting_for_update = true;
+ }
+ }
+ } else {
+ LOGE << "Will not perform upload, error when verifying upload status: " << verify << std::endl;
+ }
+}
+
+void SteamworksUGCItem::RequestUnsubscribe() {
+ ModInstance *mod = ModLoading::Instance().GetMod(sid);
+ if( mod ) {
+ mod->Activate(false);
+ mod->PurgeActiveSettings();
+ }
+ SteamUGC()->UnsubscribeItem(steamworks_id);
+}
+
+void SteamworksUGCItem::RequestSubscribe() {
+ SteamUGC()->SubscribeItem(steamworks_id);
+}
+
+void SteamworksUGCItem::RequestUserVoteSet(bool voteup) {
+ SteamAPICall_t apicall = SteamUGC()->SetUserItemVote(steamworks_id, voteup);
+ m_callResultSetUserItemVoteResult.Set(apicall, this, &SteamworksUGCItem::OnUGCSetUserItemVoteResult);
+}
+
+void SteamworksUGCItem::RequestFavoriteSet(bool v) {
+ SteamAPICall_t hSteamAPICall;
+ favorite_clean_for_query = false;
+ if( v ) {
+ hSteamAPICall = SteamUGC()->AddItemToFavorites(OVERGROWTH_APP_ID,steamworks_id);
+ } else {
+ hSteamAPICall = SteamUGC()->RemoveItemFromFavorites(OVERGROWTH_APP_ID,steamworks_id);
+ }
+ m_callResultUserFavoriteItemsListChanged.Set(hSteamAPICall, this, &SteamworksUGCItem::OnUGCUserFavoriteItemsListChanged);
+}
+
+void SteamworksUGCItem::Reload() {
+ if( sid.Valid() ) {
+ ModInstance* mi = ModLoading::Instance().GetMod( sid );
+ if( mi ) {
+ LOGI << "Requesting that the ModInstance reload data" << std::endl;
+ mi->Reload();
+ } else {
+ LOGE << "There is no engine mod matching the steamworks mod" << std::endl;
+ }
+ }
+}
+
+void SteamworksUGCItem::UpdateModInstallationInfo() {
+ uint64 total;
+ uint32 timestamp;
+ char path[kPathSize];
+
+ bool res = SteamUGC()->GetItemInstallInfo( steamworks_id, &total, path, kPathSize, &timestamp );
+ if(res) {
+ mod_path.set(path);
+ mod_data_size = total;
+ LOGI << "Updated install info for " << id << std::endl;
+ } else {
+ LOGE << "Unable to fetch mod size info for " << id << std::endl;
+ }
+}
+
+void SteamworksUGCItem::Update() {
+ if( steamworks_id != 0 ) {
+ if( was_installed == false && IsInstalled() ) {
+ OnBecameInstalled();
+ }
+
+ if( was_installed == true && IsInstalled() == false ) {
+ OnBecameUninstalled();
+ }
+
+ was_installed = IsInstalled();
+
+ if( IsSubscribed() ) {
+ if( NeedsUpdate() ) {
+ uint64 processed, total;
+ SteamUGC()->GetItemDownloadInfo(steamworks_id, &processed, &total);
+ if( total > 0 ) {
+ download_progress = (float)((double)processed/(double)total);
+ mod_data_size = total;
+ } else {
+ download_progress = 0.0f;
+ }
+ need_update_installed_size = true;
+ } else if( IsInstalled() ) {
+ if( need_update_installed_size ) {
+ UpdateModInstallationInfo();
+ need_update_installed_size = false;
+ }
+ download_progress = 1.0f;
+ } else {
+ download_progress = 0.0f;
+ }
+ } else {
+ download_progress = 0.0f;
+ }
+
+ if( waiting_for_update ) {
+ uint64 processed, total;
+ update_status = SteamUGC()->GetItemUpdateProgress( update_handle, &processed, &total );
+ update_progress = (float)((double)processed/(double)total);
+ } else {
+ update_status = k_EItemUpdateStatusInvalid;
+ }
+ }
+}
+
+void SteamworksUGCItem::OnBecameInstalled() {
+ Reload();
+}
+
+void SteamworksUGCItem::OnBecameUninstalled() {
+ Reload();
+}
+
+void SteamworksUGCItem::OnUGCSteamUGCQueryCompleted( SteamUGCQueryCompleted_t *pResult, bool failed ) {
+ waiting_for_details = false;
+
+ LOGI << "Got Details for steamworks mod " << id << std::endl;
+ if( failed == false ) {
+ if( pResult->m_eResult == k_EResultOK ) {
+ has_eresult_error = false;
+ } else {
+ has_eresult_error = true;
+ eresult_error = pResult->m_eResult;
+ }
+
+ SteamUGCDetails_t m_details;
+ SteamUGC()->GetQueryUGCResult( pResult->m_handle, 0, &m_details );
+
+ if( pResult->m_unNumResultsReturned == 1 ) {
+ //If we get this error i'm assuming the mod doesn't exist anymore.
+ //As this is what local testing has implied.
+ if( m_details.m_eResult == k_EResultFileNotFound ) {
+ LOGW << "Steamworks mod " << id << " returned error which implies mod doesn't exist anymore" << std::endl;
+ } else {
+ if( sid.Valid() == false ) {
+ sid = ModLoading::Instance().CreateSteamworksMod(ugc_id);
+ }
+
+ name.set( m_details.m_rgchTitle);
+ description.set( m_details.m_rgchDescription);
+ owner_id.SetFromUint64(m_details.m_ulSteamIDOwner);
+ visibility = m_details.m_eVisibility;
+
+ char* metadata = (char*)alloc.stack.Alloc( k_cchDeveloperMetadataMax + 1 );
+
+ SteamUGC()->GetQueryUGCMetadata( pResult->m_handle, 0, metadata, k_cchDeveloperMetadataMax + 1 );
+
+ SimpleJSONWrapper json;
+
+ std::string metadatastr(metadata);
+
+ LOGI << "Metadata string:" << metadata;
+
+ json.parseString(metadatastr);
+ Json::Value root = json.getRoot();
+
+ id.set( root["id"].asString().c_str());
+ version.set( root["version"].asString().c_str());
+ author.set( root["author"].asString().c_str());
+
+ char tagbuf[k_cchTagListMax+1];
+ char *tagbuf_mem = NULL;
+ char *curtag = NULL;
+ strncpy(tagbuf, m_details.m_rgchTags, k_cchTagListMax );
+ curtag = strtok_r(tagbuf,",",&tagbuf_mem);
+ tags.clear();
+ while( curtag != NULL ) {
+ if( strlen(curtag) > 0 ) {
+ tags.insert(std::string(curtag));
+ }
+ curtag = strtok_r(NULL,",",&tagbuf_mem);
+ }
+
+ if (m_details.m_eResult != k_EResultOK) {
+ LOGE << "OnUGCDetailsResult() " << m_details.m_eResult << std::endl;
+ }
+
+ alloc.stack.Free(metadata);
+ }
+ } else {
+ LOGE << "Got more/less results than expected for " << steamworks_id << std::endl;
+ }
+ } else {
+
+ }
+
+ Reload();
+}
+
+void SteamworksUGCItem::OnUGCCreateItemResult( CreateItemResult_t *pResult, bool failed ) {
+ waiting_for_create = false;
+
+ LOGI << "OnUGCCreateItemResult() " << pResult->m_eResult << std::endl;
+
+ if( failed == false ) {
+ if( pResult->m_eResult == k_EResultOK ) {
+ has_eresult_error = false;
+ } else {
+ has_eresult_error = true;
+ eresult_error = pResult->m_eResult;
+ }
+ steamworks_id = pResult->m_nPublishedFileId;
+ if( sid.Valid() == false ) {
+ sid = ModLoading::Instance().CreateSteamworksMod(ugc_id);
+ }
+
+ Steamworks::Instance()->needs_to_accept_license = pResult->m_bUserNeedsToAcceptWorkshopLegalAgreement;
+ if( pResult->m_bUserNeedsToAcceptWorkshopLegalAgreement ) {
+ LOGW << "User needs to accept workshop legal agreement" << std::endl;
+ }
+ } else {
+ LOGE << "Error creating Steam Workshop item" << std::endl;
+ }
+
+ ModInstance* mi = ModLoading::Instance().GetMod( sid );
+
+ Reload();
+
+ RequestUpload("Initial Upload", visibility, k_UploadDataControl_UploadAll);
+}
+
+void SteamworksUGCItem::OnUGCSubmitItemUpdateResult( SubmitItemUpdateResult_t *pResult, bool failed ) {
+ waiting_for_update = false;
+ LOGI << "OnUGCSubmitItemUpdateResult() " << pResult->m_eResult << std::endl;
+ if ( failed == false ) {
+ if( pResult->m_eResult == k_EResultOK ) {
+ has_eresult_error = false;
+ } else {
+ has_eresult_error = true;
+ eresult_error = pResult->m_eResult;
+ }
+
+ Steamworks::Instance()->needs_to_accept_license = pResult->m_bUserNeedsToAcceptWorkshopLegalAgreement;
+ if( pResult->m_bUserNeedsToAcceptWorkshopLegalAgreement ) {
+ LOGW << "User needs to accept workshop legal agreement" << std::endl;
+ }
+ } else {
+ LOGE << "Error on item update" << std::endl;
+ }
+
+ ModInstance* mi = ModLoading::Instance().GetMod( sid );
+
+ Reload();
+
+ RequestDetails();
+}
+
+void SteamworksUGCItem::OnUGCGetUserItemVoteResult( GetUserItemVoteResult_t *pResult, bool failed ) {
+ if( failed == false ) {
+ if( pResult->m_eResult == k_EResultOK ) {
+ if( pResult->m_bVotedUp ) {
+ user_vote = k_VoteUp;
+ } else if( pResult->m_bVotedDown ) {
+ user_vote = k_VoteDown;
+ } else if( pResult->m_bVoteSkipped ) {
+ user_vote = k_VoteNone;
+ }
+ } else {
+ LOGE << "Failed getting item vote result" << std::endl;
+ }
+ } else {
+ LOGE << "Failed getting item vote result" << std::endl;
+ }
+}
+
+void SteamworksUGCItem::OnUGCUserFavoriteItemsListChanged( UserFavoriteItemsListChanged_t *pResult, bool failed ) {
+ if(pResult) {
+ if( failed == false ) {
+ favorite = pResult->m_bWasAddRequest;
+ } else {
+ LOGE << "Error when settings favorite: " << pResult->m_eResult << std::endl;
+ }
+ } else {
+ LOGE << "pResult was null in favorite change callback." << std::endl;
+ }
+}
+
+void SteamworksUGCItem::OnUGCSetUserItemVoteResult( SetUserItemVoteResult_t *pResult, bool failed ) {
+ if( failed == false ) {
+ if( pResult->m_eResult == k_EResultOK ) {
+ if( pResult->m_bVoteUp ) {
+ user_vote = k_VoteUp;
+ } else {
+ user_vote = k_VoteDown;
+ }
+ } else {
+ LOGE << "Failed getting item vote result" << std::endl;
+ }
+ } else {
+ LOGE << "Failed getting item vote result" << std::endl;
+ }
+}
+
+std::string SteamworksUGCItem::ItemStateString( uint32_t flag ) {
+ std::stringstream ss;
+ if( flag & k_EItemStateSubscribed )
+ ss << "k_EItemStateSubscribed ";
+ if( flag & k_EItemStateLegacyItem )
+ ss << "k_EItemStateLegacyItem ";
+ if( flag & k_EItemStateInstalled )
+ ss << "k_EItemStateInstalled ";
+ if( flag & k_EItemStateNeedsUpdate )
+ ss << "k_EItemStateNeedsUpdate ";
+ if( flag & k_EItemStateDownloading )
+ ss << "k_EItemStateDownloading ";
+ if( flag & k_EItemStateDownloadPending )
+ ss << "k_EItemStateDownloadPending ";
+ if( k_EItemStateNone == flag )
+ ss << "k_EItemStateNone";
+ return ss.str();
+}
+
+float SteamworksUGCItem::ItemDownloadProgress() {
+ return download_progress;
+}
+
+uint64_t SteamworksUGCItem::ItemDataSize() {
+ return mod_data_size;
+}
+
+float SteamworksUGCItem::ItemUploadProgress() {
+ return update_progress;
+}
+
+EItemUpdateStatus SteamworksUGCItem::GetUpdateStatus() {
+ return update_status;
+}
+
+const char* SteamworksUGCItem::UpdateStatusString() {
+ switch( update_status ) {
+ case k_EItemUpdateStatusInvalid: return "No Status";
+ case k_EItemUpdateStatusPreparingConfig: return "Preparing Config";
+ case k_EItemUpdateStatusPreparingContent: return "Preparing Content";
+ case k_EItemUpdateStatusUploadingContent: return "Uploading Content";
+ case k_EItemUpdateStatusUploadingPreviewFile: return "Uploading Preview File";
+ case k_EItemUpdateStatusCommittingChanges: return "Commiting Changes";
+ default: return "UNKNOWN";
+ }
+}
+
+uint32_t SteamworksUGCItem::ItemState() {
+ return SteamUGC()->GetItemState( steamworks_id );
+}
+
+bool SteamworksUGCItem::WaitingForResults() {
+ return waiting_for_details || waiting_for_create || waiting_for_update;
+}
+
+const char* SteamworksUGCItem::GetLastResultError() {
+ if( has_eresult_error ) {
+ return GetEResultString( eresult_error );
+ } else {
+ return "";
+ }
+}
+
+bool SteamworksUGCItem::IsSubscribed() {
+ return ItemState() & k_EItemStateSubscribed;
+}
+
+bool SteamworksUGCItem::IsInstalled() {
+ return ItemState() & k_EItemStateInstalled;
+}
+
+bool SteamworksUGCItem::IsDownloading() {
+ return ItemState() & k_EItemStateDownloading;
+}
+
+bool SteamworksUGCItem::IsDownloadPending() {
+ return ItemState() & k_EItemStateDownloadPending;
+}
+
+bool SteamworksUGCItem::NeedsUpdate() {
+ return ItemState() & k_EItemStateNeedsUpdate;
+}
+
+bool SteamworksUGCItem::IsFavorite() {
+ return favorite;
+}
+
+SteamworksUGCItem::UserVote SteamworksUGCItem::GetUserVote() {
+ return user_vote;
+}
+
+void SteamworksUGCItem::SetIntendedUpdateModSource(ModID mod) {
+ upload_sid_source = mod;
+}
+
+std::string SteamworksUGCItem::GetPath() {
+ if( ItemState() & k_EItemStateInstalled ) {
+ if( need_update_installed_size ) {
+ UpdateModInstallationInfo();
+ need_update_installed_size = false;
+ }
+ return std::string(mod_path);
+ } else {
+ return std::string("");
+ }
+}
+
+std::string SteamworksUGCItem::GetSteamworksIDString() {
+ std::stringstream ss;
+ ss << steamworks_id;
+ return ss.str();
+}
+#endif
diff --git a/Source/Steam/ugc_item.h b/Source/Steam/ugc_item.h
new file mode 100644
index 00000000..1fe1a619
--- /dev/null
+++ b/Source/Steam/ugc_item.h
@@ -0,0 +1,193 @@
+//-----------------------------------------------------------------------------
+// Name: ugc_item.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#if ENABLE_STEAMWORKS
+#include <Internal/integer.h>
+#include <Internal/modid.h>
+#include <Internal/path.h>
+
+#include <Steam/steam_appid.h>
+#include <Steam/ugc_id.h>
+#include <Steam/steamworks_util.h>
+
+#include <Utility/fixed_string.h>
+
+#include <steam_api.h>
+
+#include <string>
+#include <set>
+
+class SteamworksUGCItem {
+public:
+ enum UploadVerifyStatus {
+ k_UploadVerifyOk = 0,
+ k_UploadVerifyInvalidPreviewImage = 1UL << 0,
+ k_UploadVerifyMissingPreview = 1UL << 1,
+ k_UploadVerifyMissingModSource = 1UL << 2,
+ k_UploadVerifyWaitingForUpload = 1UL << 3,
+ k_UploadVerifyInvalidModFolder = 1UL << 4,
+ k_UploadVerifyUnchangedVersion = 1UL << 5
+ };
+
+ enum UploadDataControl {
+ k_UploadDataControl_UploadDescription = 1UL << 0,
+ k_UploadDataControl_UploadAll = 1UL << 0
+ };
+
+ enum ItemWrapperType {
+ Normal,
+ Upload
+ };
+
+ enum UserVote {
+ k_VoteUnknown,
+ k_VoteNone,
+ k_VoteUp,
+ k_VoteDown
+ };
+
+ PublishedFileId_t steamworks_id;
+ static int ugc_id_counter;
+ UGCID ugc_id;
+ ModID sid;
+ ModID upload_sid_source;
+ ItemWrapperType item_wrapper_type;
+ CSteamID owner_id;
+ ERemoteStoragePublishedFileVisibility visibility;
+
+ bool has_eresult_error;
+ EResult eresult_error;
+
+ //Stored in the metadata block
+ fixed_string<MOD_ID_MAX_LENGTH> id;
+ fixed_string<MOD_VERSION_MAX_LENGTH> version;
+ fixed_string<MOD_AUTHOR_MAX_LENGTH> author;
+
+ //Not in the metadata block
+ fixed_string<MOD_NAME_MAX_LENGTH> name;
+ fixed_string<MOD_DESCRIPTION_MAX_LENGTH> description;
+
+ UserVote user_vote;
+
+ std::set<std::string> tags;
+
+ SteamworksUGCItem( ModID sid );
+ SteamworksUGCItem( PublishedFileId_t id );
+
+ std::string GetSteamworksIDString();
+
+ UGCID GetUGCID();
+
+ uint32_t VerifyForUpload();
+
+ void RequestCreation();
+ void RequestDetails();
+ void RequestUpload(const char* update_message, ERemoteStoragePublishedFileVisibility new_visibility, uint32_t upload_control);
+ void RequestUnsubscribe();
+ void RequestSubscribe();
+ void RequestUserVoteSet(bool voteup);
+ void RequestFavoriteSet(bool favorite);
+
+ void Reload();
+
+ //Function that keeps tabs on some state changes.
+ void Update();
+
+ std::string ItemStateString(uint32_t flag);
+ uint32_t ItemState();
+
+ float ItemDownloadProgress();
+ uint64_t ItemDataSize();
+
+ float ItemUploadProgress();
+ EItemUpdateStatus GetUpdateStatus();
+ const char* UpdateStatusString();
+
+ const char* GetLastResultError();
+
+ bool WaitingForResults();
+
+ bool IsSubscribed();
+ bool IsInstalled();
+ bool IsDownloading();
+ bool IsDownloadPending();
+ bool NeedsUpdate();
+ bool IsFavorite();
+
+ UserVote GetUserVote();
+
+ void SetIntendedUpdateModSource(ModID modid);
+
+ std::string GetPath();
+
+ void OnBecameInstalled();
+ void OnBecameUninstalled();
+
+ void OnUGCSteamUGCQueryCompleted( SteamUGCQueryCompleted_t *pResult, bool failed );
+ CCallResult<SteamworksUGCItem, SteamUGCQueryCompleted_t> m_callSteamUGCQueryCompleted;
+
+ void OnUGCCreateItemResult( CreateItemResult_t *pResult, bool failed );
+ CCallResult<SteamworksUGCItem, CreateItemResult_t> m_callResultsCreateItemResult;
+
+ void OnUGCSubmitItemUpdateResult( SubmitItemUpdateResult_t *pResult, bool failed );
+ CCallResult<SteamworksUGCItem, SubmitItemUpdateResult_t> m_callResultSubmitItemUpdateResults;
+
+ void OnUGCGetUserItemVoteResult( GetUserItemVoteResult_t *pResult, bool failed );
+ CCallResult<SteamworksUGCItem, GetUserItemVoteResult_t> m_callResultGetUserItemVoteResult;
+
+ void OnUGCSetUserItemVoteResult( SetUserItemVoteResult_t *pResult, bool failed );
+ CCallResult<SteamworksUGCItem, SetUserItemVoteResult_t> m_callResultSetUserItemVoteResult;
+
+ void OnUGCUserFavoriteItemsListChanged( UserFavoriteItemsListChanged_t *pResult, bool failed );
+ CCallResult<SteamworksUGCItem, UserFavoriteItemsListChanged_t> m_callResultUserFavoriteItemsListChanged;
+
+
+private:
+ //Disable copying.
+ SteamworksUGCItem& operator=(const SteamworksUGCItem& item);
+ SteamworksUGCItem( SteamworksUGCItem& other );
+
+ void UpdateModInstallationInfo();
+
+ bool waiting_for_details;
+ bool waiting_for_create;
+
+ bool waiting_for_update;
+ UGCUpdateHandle_t update_handle;
+ EItemUpdateStatus update_status;
+ float update_progress;
+
+ bool was_installed;
+
+ bool favorite;
+ bool favorite_clean_for_query; //Marker when querying for favorite list, gets set to false when user changes value via game.
+
+ float download_progress;
+ fixed_string<kPathSize> mod_path;
+ uint64_t mod_data_size;
+ bool need_update_installed_size;
+
+ friend class SteamworksUGC;
+};
+#endif
diff --git a/Source/Threading/rand.cpp b/Source/Threading/rand.cpp
new file mode 100644
index 00000000..593d89f1
--- /dev/null
+++ b/Source/Threading/rand.cpp
@@ -0,0 +1,51 @@
+//-----------------------------------------------------------------------------
+// Name: rand.cpp
+// Developer: Wolfire Games LLC
+// Author: Max Danielsson
+// Description: This is a threadsafe variant of pcg rand(), which doesn't
+// require a local reseed variable.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "rand.h"
+
+#include <Utility/pcg_basic.h>
+#include <Internal/integer.h>
+
+#include <cstdlib>
+#include <mutex>
+
+static std::mutex mutex;
+static unsigned int reseed;
+static pcg32_random_t seedr;
+
+void rand_ts_seed(unsigned int seed)
+{
+ mutex.lock();
+ pcg32_srandom_r(&seedr,seed,seed);
+ mutex.unlock();
+}
+
+uint32_t rand_ts()
+{
+ uint32_t v;
+ mutex.lock();
+ v = pcg32_random_r(&seedr);
+ mutex.unlock();
+ return v;
+}
diff --git a/Source/Threading/rand.h b/Source/Threading/rand.h
new file mode 100644
index 00000000..dd070bf2
--- /dev/null
+++ b/Source/Threading/rand.h
@@ -0,0 +1,29 @@
+//-----------------------------------------------------------------------------
+// Name: rand.h
+// Developer: Wolfire Games LLC
+// Author: Max Danielsson
+// Description: This is a threadsafe variant of pcg rand(), which doesn't
+// require a local reseed variable.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include <Internal/integer.h>
+
+void rand_ts_seed(unsigned int seed);
+uint32_t rand_ts();
diff --git a/Source/Threading/sdl_wrapper.cpp b/Source/Threading/sdl_wrapper.cpp
new file mode 100644
index 00000000..85ed868d
--- /dev/null
+++ b/Source/Threading/sdl_wrapper.cpp
@@ -0,0 +1,40 @@
+//-----------------------------------------------------------------------------
+// Name: sdl_wrapper.cpp
+// Developer: Wolfire Games LLC
+// Author: Max Danielsson
+// Description: This is a threadsafe wrapper around functions in SDL known
+// to be used from mutiple threads.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "sdl_wrapper.h"
+
+#include <SDL.h>
+
+#include <mutex>
+
+static std::mutex GetTicks_mutex;
+
+unsigned int SDL_TS_GetTicks()
+{
+ unsigned int v = 0;
+ GetTicks_mutex.lock();
+ v = SDL_GetTicks();
+ GetTicks_mutex.unlock();
+ return v;
+}
diff --git a/Source/Threading/sdl_wrapper.h b/Source/Threading/sdl_wrapper.h
new file mode 100644
index 00000000..eb3bf4a1
--- /dev/null
+++ b/Source/Threading/sdl_wrapper.h
@@ -0,0 +1,29 @@
+//-----------------------------------------------------------------------------
+// Name: sdl_wrapper.h
+// Developer: Wolfire Games LLC
+// Author: Max Danielsson
+// Description: This is a threadsafe wrapper around functions in SDL known
+// to be used from mutiple threads.
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <Internal/integer.h>
+
+
+
+unsigned int SDL_TS_GetTicks();
diff --git a/Source/Threading/thread_name.cpp b/Source/Threading/thread_name.cpp
new file mode 100644
index 00000000..226dd8a6
--- /dev/null
+++ b/Source/Threading/thread_name.cpp
@@ -0,0 +1,64 @@
+//-----------------------------------------------------------------------------
+// Name: thread_name.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <Threading/thread_name.h>
+
+#ifdef _MSC_VER
+// Adapted from https://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx
+#define NOMINMAX
+#include <windows.h>
+const DWORD MS_VC_EXCEPTION = 0x406D1388;
+#pragma pack(push,8)
+typedef struct tagTHREADNAME_INFO
+{
+ DWORD dwType; // Must be 0x1000.
+ LPCSTR szName; // Pointer to name (in user addr space).
+ DWORD dwThreadID; // Thread ID (-1=caller thread).
+ DWORD dwFlags; // Reserved for future use, must be zero.
+} THREADNAME_INFO;
+#pragma pack(pop)
+void NameCurrentThread(const char* threadName) {
+ THREADNAME_INFO info;
+ info.dwType = 0x1000;
+ info.szName = threadName;
+ info.dwThreadID = GetCurrentThreadId();
+ info.dwFlags = 0;
+#pragma warning(push)
+#pragma warning(disable: 6320 6322)
+ __try{
+ RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info);
+ }
+ __except (EXCEPTION_EXECUTE_HANDLER){
+ }
+#pragma warning(pop)
+}
+#elif PLATFORM_LINUX
+#include <sys/prctl.h>
+void NameCurrentThread(const char* name)
+{
+ prctl(PR_SET_NAME,(long unsigned)name,0,0,0);
+}
+#else // _MSC_VER
+void NameCurrentThread(const char* name)
+{
+}
+#endif // _MSC_VER
diff --git a/Source/Threading/thread_name.h b/Source/Threading/thread_name.h
new file mode 100644
index 00000000..77013f8d
--- /dev/null
+++ b/Source/Threading/thread_name.h
@@ -0,0 +1,25 @@
+//-----------------------------------------------------------------------------
+// Name: thread_name.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+void NameCurrentThread(const char* name);
diff --git a/Source/Threading/thread_sanity.cpp b/Source/Threading/thread_sanity.cpp
new file mode 100644
index 00000000..8d586b36
--- /dev/null
+++ b/Source/Threading/thread_sanity.cpp
@@ -0,0 +1,56 @@
+//-----------------------------------------------------------------------------
+// Name: thread_sanity.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "thread_sanity.h"
+
+#include <Logging/logdata.h>
+
+#include <thread>
+#include <cassert>
+
+using std::thread;
+using std::endl;
+
+static thread::id main_thread_id;
+
+//Record which thread is the main one so that we can assert that we are in the main thread going forward.
+void RegisterMainThreadID()
+{
+ main_thread_id = std::this_thread::get_id();
+}
+
+bool AssertMainThread()
+{
+ if( main_thread_id != std::this_thread::get_id() )
+ {
+ LOGE << "It appears a thread has entered a code block that is limited only to the main thread" << endl;
+ assert(false);
+ return false;
+ }
+ return true;
+}
+
+
+bool IsMainThread()
+{
+ return main_thread_id == std::this_thread::get_id();
+}
diff --git a/Source/Threading/thread_sanity.h b/Source/Threading/thread_sanity.h
new file mode 100644
index 00000000..93512577
--- /dev/null
+++ b/Source/Threading/thread_sanity.h
@@ -0,0 +1,27 @@
+//-----------------------------------------------------------------------------
+// Name: thread_sanity.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+void RegisterMainThreadID();
+bool AssertMainThread();
+bool IsMainThread();
diff --git a/Source/Threading/threadsafequeue.h b/Source/Threading/threadsafequeue.h
new file mode 100644
index 00000000..68dd2797
--- /dev/null
+++ b/Source/Threading/threadsafequeue.h
@@ -0,0 +1,57 @@
+//-----------------------------------------------------------------------------
+// Name: threadsafequeue.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <queue>
+#include <mutex>
+
+template<class T>
+class ThreadSafeQueue {
+
+ std::queue<T> messages;
+ std::mutex mutex;
+public:
+
+ void Queue( const T &v ) {
+ mutex.lock();
+ messages.push(v);
+ mutex.unlock();
+ }
+
+ T Pop( ) {
+ mutex.lock();
+ T val = messages.front();
+ messages.pop();
+ mutex.unlock();
+
+ return val;
+ }
+
+ size_t Count( ) {
+ size_t c = 0;
+ mutex.lock();
+ c = messages.size();
+ mutex.unlock();
+ return c;
+ }
+};
diff --git a/Source/Timing/gl_query_perf.cpp b/Source/Timing/gl_query_perf.cpp
new file mode 100644
index 00000000..fd1db798
--- /dev/null
+++ b/Source/Timing/gl_query_perf.cpp
@@ -0,0 +1,166 @@
+//-----------------------------------------------------------------------------
+// Name: gl_query_perf.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "gl_query_perf.h"
+
+#ifdef TIMER_QUERY_TIMING
+
+#include <Logging/logdata.h>
+#include <Graphics/graphics.h>
+#include <Utility/assert.h>
+#include <Internal/filesystem.h>
+
+#include <opengl.h>
+#include <string>
+
+
+extern GLTimerQueryPerf* glTimingQuery;
+
+void GLTimerQueryPerf::Init() {
+ if( GLEW_ARB_timer_query ) {
+ perf_available = true;
+
+ query_ids_used = 0;
+ query_count = 0;
+ frame_counter = 0;
+
+ memset( queries, 0x0, sizeof(PerfQuery)*MAX_QUERY_COUNT );
+
+ glGenQueries(MAX_QUERY_COUNT, query_ids );
+ CHECK_GL_ERROR();
+
+ std::string path = std::string(GetWritePath(CoreGameModID).c_str()) + "/gl_query_perf.perf.dat";
+ my_ofstream_open( csv_output, path.c_str() );
+
+ begun_perf = false;
+ }
+}
+
+void GLTimerQueryPerf::Finalize(){
+ if( perf_available ) {
+ glDeleteQueries(MAX_QUERY_COUNT,query_ids);
+ CHECK_GL_ERROR();
+ csv_output.close();
+
+ std::string path = std::string(GetWritePath(CoreGameModID).c_str()) + "/gl_query_perf_filenames.txt";
+ LOGI << "Saved perf output names to " << path << std::endl;
+ std::ofstream f;
+ my_ofstream_open(f, path);
+
+ std::set<const char*>::iterator nm_it;
+
+ for( nm_it = file_names.begin();
+ nm_it != file_names.end();
+ nm_it++ ) {
+ f << (uint64_t)(*nm_it) << "," << *nm_it << std::endl;
+ }
+ f.close();
+ }
+}
+
+void GLTimerQueryPerf::PerfGPUBegin(const char* file, const int line){
+ if( perf_available ) {
+ if( queries[query_count].query_id == 0 )
+ {
+ GLuint query_id = query_ids[query_ids_used];
+ LOG_ASSERT(begun_perf == false);
+ glBeginQuery(GL_TIME_ELAPSED, query_id);
+ CHECK_GL_ERROR();
+
+ queries[query_count].query_id = query_id;
+ queries[query_count].line = line;
+ queries[query_count].file = file;
+ queries[query_count].frame = frame_counter;
+
+ query_ids_used++;
+ query_count++;
+ begun_perf = true;
+
+ if( query_ids_used >= MAX_QUERY_COUNT )
+ {
+ query_ids_used = 0;
+ query_count = 0;
+ }
+ }
+ else
+ {
+ LOGW << "Ran out of queries" << std::endl;
+ }
+ }
+}
+
+void GLTimerQueryPerf::PerfGPUEnd( ){
+ if( perf_available ) {
+ if( begun_perf ) {
+ glEndQuery(GL_TIME_ELAPSED);
+ CHECK_GL_ERROR();
+ begun_perf = false;
+ }
+ }
+}
+
+void GLTimerQueryPerf::PostFrameSwap(){
+ if( perf_available ) {
+ GLint available = 0;
+
+ /*
+ while (!available) {
+ glGetQueryObjectiv(query_ids[query_ids_used-1], GL_QUERY_RESULT_AVAILABLE, &available);
+ CHECK_GL_ERROR();
+ }
+ */
+ static int i = 0;
+ do {
+ PerfQuery& query = queries[i];
+
+ if( query.query_id != 0 ) {
+ glGetQueryObjectiv(query.query_id, GL_QUERY_RESULT_AVAILABLE, &available);
+ } else {
+ available = false;
+ }
+
+ if( available ) {
+ uint64_t result;
+ glGetQueryObjectui64v( query.query_id, GL_QUERY_RESULT, &result );
+ CHECK_GL_ERROR();
+
+ //LOGI << "Query " << i << " " << query.file << ":" << query.line << " " << result << std::endl;
+
+ file_names.insert(query.file);
+ csv_output << query.frame << "," << 0 << "," << (uint64_t)query.file << "," << query.line << "," << result << std::endl;
+
+ query.query_id = 0;
+
+ i++;
+ if( i >= MAX_QUERY_COUNT ) {
+ i = 0;
+ }
+ }
+ } while(available);
+ frame_counter++;
+ }
+}
+
+GLTimerQueryPerf GlTimingQuery;
+GLTimerQueryPerf* glTimingQuery = &GlTimingQuery;
+
+#endif
diff --git a/Source/Timing/gl_query_perf.h b/Source/Timing/gl_query_perf.h
new file mode 100644
index 00000000..8f257aca
--- /dev/null
+++ b/Source/Timing/gl_query_perf.h
@@ -0,0 +1,108 @@
+//-----------------------------------------------------------------------------
+// Name: gl_query_perf.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#ifdef TIMER_QUERY_TIMING
+#include <Compat/fileio.h>
+#include <Internal/integer.h>
+#include <Utility/disallow_copy_and_assign.h>
+
+#include <vector>
+#include <map>
+#include <queue>
+#include <string>
+#include <opengl.h>
+#include <stack>
+#include <set>
+
+/*
+ * This class creates a structure around using the GL_ARB_timer_query extension
+ * avaiable in OpenGL 3.2+
+ */
+
+struct PerfQuery {
+ int line;
+ GLuint frame;
+ const char* file;
+ GLuint query_id;
+};
+
+
+class GLTimerQueryPerf {
+ static const size_t MAX_QUERY_COUNT = 2048*16;
+ bool perf_available;
+
+ bool begun_perf;
+ GLuint frame_counter;
+ GLuint query_ids[MAX_QUERY_COUNT];
+ GLuint query_ids_used;
+
+ PerfQuery queries[MAX_QUERY_COUNT];
+ GLuint query_count;
+
+ std::set<const char*> file_names;
+
+ std::ofstream csv_output;
+public:
+
+ void Init();
+ void Finalize();
+ void PerfGPUBegin(const char* file, const int line);
+ void PerfGPUEnd( );
+ void PostFrameSwap();
+};
+
+extern GLTimerQueryPerf* glTimingQuery;
+
+
+#define GL_TIMER_QUERY_INIT( ) \
+glTimingQuery->Init()
+
+#define GL_TIMER_QUERY_DISPOSE( ) \
+glTimingQuery->Dispose()
+
+#define GL_TIMER_QUERY_START( ) \
+ if( glTimingQuery ) glTimingQuery->PerfGPUBegin( __FILE__, __LINE__ )
+
+#define GL_TIMER_QUERY_END( ) \
+ if( glTimingQuery ) glTimingQuery->PerfGPUEnd()
+
+#define GL_TIMER_QUERY_SWAP() \
+ glTimingQuery->PostFrameSwap()
+
+#define GL_TIMER_QUERY_FINALIZE() \
+ glTimingQuery->Finalize()
+
+#else
+
+#define GL_TIMER_QUERY_INIT()
+
+#define GL_TIMER_QUERY_START( )
+
+#define GL_TIMER_QUERY_END( )
+
+#define GL_TIMER_QUERY_SWAP()
+
+#define GL_TIMER_QUERY_FINALIZE()
+
+#endif
diff --git a/Source/Timing/intel_gl_perf.cpp b/Source/Timing/intel_gl_perf.cpp
new file mode 100644
index 00000000..87dde426
--- /dev/null
+++ b/Source/Timing/intel_gl_perf.cpp
@@ -0,0 +1,293 @@
+//-----------------------------------------------------------------------------
+// Name: intel_gl_perf.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "intel_gl_perf.h"
+#ifdef INTEL_TIMING
+
+#include <Internal/integer.h>
+#include <Internal/filesystem.h>
+#include <Internal/snprintf.h>
+
+#include <Memory/allocation.h>
+#include <Logging/logdata.h>
+#include <Utility/assert.h>
+
+#include <opengl.h>
+
+#include <cstdlib>
+
+IntelGLPerfRequest::IntelGLPerfRequest( std::string query_name, int type_id, std::vector<std::string> query_counter_names ) :
+query_name(query_name),
+type_id(type_id),
+query_counter_names(query_counter_names)
+{
+}
+
+IntelGLPerf::IntelGLPerf() :
+max_query_size(0),
+data_dest(NULL)
+{
+ std::vector<std::string> query_counter_names;
+ query_counter_names.push_back("GpuTime");
+
+ perf_requests.push_back(
+ IntelGLPerfRequest(
+ "Intel_HD_MD_Render_Basic_Hardware_Counters",
+ kIntelGLPerf_TypeId_Standard,
+ query_counter_names
+ )
+ );
+}
+
+IntelGLPerf::~IntelGLPerf() {
+ if( data_dest )
+ OG_FREE(data_dest);
+
+ if( csv_output.is_open() ) {
+ csv_output.close();
+ }
+
+ std::map<std::string,GLuint>::iterator query_it = active_query_handles.begin();
+
+ for( ;query_it != active_query_handles.end(); query_it++ ) {
+ glDeletePerfQueryINTEL(query_it->second);
+ }
+}
+
+void IntelGLPerf::Init() {
+ LOGI << "Initializing IntelGLPerf" << std::endl;
+
+ //Check if this plugin is available.
+ if( GLEW_INTEL_performance_query ) {
+ perf_available = true;
+
+ GLuint query_id;
+ glGetFirstPerfQueryIdINTEL(&query_id);
+ while( query_id != 0 ) {
+ const GLuint query_name_len = 64;
+ GLchar query_name[query_name_len];
+ GLuint data_size;
+ GLuint no_counters;
+ GLuint no_instances;
+ GLuint caps_mask;
+
+ glGetPerfQueryInfoINTEL(query_id,
+ query_name_len, query_name,
+ &data_size, &no_counters,
+ &no_instances, &caps_mask);
+
+ for( unsigned i = 0; i < perf_requests.size(); i++ ){
+ IntelGLPerfRequest &pr = perf_requests[i];
+ if( pr.query_name == std::string(query_name) ) {
+ IntelGLPerfQuery iglpq;
+
+ iglpq.id = query_id;
+ iglpq.name = std::string(query_name);
+ iglpq.size = data_size;
+ iglpq.type_id = pr.type_id;
+ queries.push_back(iglpq);
+
+ if( data_size > max_query_size )
+ max_query_size = data_size;
+ }
+ }
+
+ data_dest = static_cast<char*>(OG_MALLOC( max_query_size ));
+
+ LOGI << "Have query id: " << query_id << std::endl
+ << "name:" << query_name << std::endl
+ << "data_size:" << data_size << std::endl
+ << "no_counters: " << no_counters << std::endl
+ << "no_instances: " << no_instances << std::endl
+ << "caps_mask: " << caps_mask << std::endl
+ << std::endl;
+
+ for(unsigned counter_id = 1; counter_id <= no_counters; counter_id++) {
+ const GLuint counter_name_len = 32;
+ GLchar counter_name[counter_name_len];
+ const GLuint counter_desc_len = 256;
+ GLchar counter_desc[counter_desc_len];
+ GLuint counter_offset;
+ GLuint counter_data_size;
+ GLuint counter_type_enum;
+ GLuint counter_data_type_enum;
+ uint64_t raw_counter_max_value;
+
+ glGetPerfCounterInfoINTEL(
+ query_id,
+ counter_id,
+ counter_name_len,
+ counter_name,
+ counter_desc_len,
+ counter_desc,
+ &counter_offset,
+ &counter_data_size,
+ &counter_type_enum,
+ &counter_data_type_enum,
+ &raw_counter_max_value);
+
+ for( unsigned i = 0; i < perf_requests.size(); i++ ){
+ IntelGLPerfRequest &pr = perf_requests[i];
+ if( pr.query_name == std::string(query_name) ) {
+ for( unsigned k = 0; k < pr.query_counter_names.size(); k++ ) {
+ if( pr.query_counter_names[k] == std::string(counter_name) ) {
+ IntelGLPerfQueryCounter iglpqc;
+
+ iglpqc.query_id = query_id;
+ iglpqc.id = counter_id;
+ iglpqc.data_type = counter_data_type_enum;
+ iglpqc.name = std::string(counter_name);
+ iglpqc.offset = counter_offset;
+ iglpqc.size = counter_data_size;
+
+ query_counters.push_back(iglpqc);
+ }
+ }
+ }
+ }
+
+ LOGI << "Query counter info, query_id: " << query_id << std::endl
+ << "counter_id: " << counter_id << std::endl
+ << "counter_name: " << counter_name << std::endl
+ << "counter_desc: " << counter_desc << std::endl
+ << "counter_offset: " << counter_offset << std::endl
+ << "counter_data_size: " << counter_data_size << std::endl
+ << "counter_type_enum: " << std::hex << counter_type_enum << std::endl
+ << "counter_dta_type_enum: " << std::hex << counter_data_type_enum << std::endl
+ << "raw_counter_max_value: " << raw_counter_max_value << std::endl;
+ }
+
+ glGetNextPerfQueryIdINTEL(query_id, &query_id);
+ }
+ std::string path = std::string(GetWritePath(CoreGameModID).c_str())+ "/intel_gl_perf.csv";
+ my_ofstream_open( csv_output, path.c_str() );
+ } else {
+ LOGI << "Intel performance query is not available on this machine" << std::endl;
+ }
+}
+
+void IntelGLPerf::Finalize() {
+ if( perf_available )
+ {
+ //TODO: Dispose all available handles
+ //TODO: dispose of all actively used query handles
+ }
+}
+
+void IntelGLPerf::PerfGPUBegin(int type, int group, const char* file, const int line) {
+ if( perf_available )
+ {
+ GLuint query_id = 0;
+
+ int offset = 0;
+ int slash_pos = 0;
+ while( *(file + offset) != '\0' ){
+ if( *(file + offset) == '/' || *(file+offset) == '\\')
+ slash_pos = offset;
+ offset++;
+ }
+
+ char counter_name[256];
+ snprintf( counter_name, 256, "%s_%d", file+slash_pos+1, line );
+
+ std::map<std::string,GLuint>::iterator query_it = active_query_handles.find(std::string(counter_name));
+
+ for( unsigned i = 0; i < queries.size(); i++) {
+ if( queries[i].type_id == type ) {
+ query_id = queries[i].id;
+ }
+ }
+
+ GLuint query_handle;
+ if( query_it != active_query_handles.end() ) {
+ query_handle = query_it->second;
+ } else {
+ glCreatePerfQueryINTEL(query_id,&query_handle);
+ active_query_handles[std::string(counter_name)] = query_handle;
+ }
+
+ PerfInstance perf;
+ perf.instance_id = query_handle;
+ glBeginPerfQueryINTEL(perf.instance_id);
+
+ perf.group = group;
+ perf.file = file+slash_pos+1;
+ perf.line = line;
+ perf.query_id = query_id;
+
+ perf_query_instances.push_back(perf);
+ query_handle_stack.push(perf.instance_id);
+ }
+}
+
+void IntelGLPerf::PerfGPUEnd() {
+ if( perf_available )
+ {
+ glEndPerfQueryINTEL(query_handle_stack.top());
+ query_handle_stack.pop();
+ }
+}
+
+void IntelGLPerf::PostFrameSwap() {
+ if( perf_available ) {
+ for( unsigned i = 0; i < perf_query_instances.size(); i++ ) {
+ PerfInstance& p = perf_query_instances[i];
+ GLuint bytes_written;
+ glGetPerfQueryDataINTEL(
+ p.instance_id,
+ GL_PERFQUERY_WAIT_INTEL,
+ max_query_size,
+ data_dest,
+ &bytes_written);
+
+ for( unsigned k = 0; k < query_counters.size(); k++ ) {
+ if( query_counters[k].query_id == p.query_id ) {
+ char* data = data_dest + query_counters[k].offset;
+ switch( query_counters[k].data_type ){
+ case GL_PERFQUERY_COUNTER_DATA_UINT32_INTEL:
+ csv_output << p.file << ":" << p.line << "," << query_counters[k].name << "," << *((uint32_t*)data) << std::endl;
+ break;
+ case GL_PERFQUERY_COUNTER_DATA_UINT64_INTEL:
+ csv_output << p.file << ":" << p.line << "," << query_counters[k].name << "," << *((uint64_t*)data) << std::endl;
+ break;
+ case GL_PERFQUERY_COUNTER_DATA_FLOAT_INTEL:
+ csv_output << p.file << ":" << p.line << "," << query_counters[k].name << "," << *((float*)data) << std::endl;
+ break;
+ case GL_PERFQUERY_COUNTER_DATA_DOUBLE_INTEL:
+ csv_output << p.file << ":" << p.line << "," << query_counters[k].name << "," << *((double*)data) << std::endl;
+ break;
+ case GL_PERFQUERY_COUNTER_DATA_BOOL32_INTEL:
+ csv_output << p.file << ":" << p.line << "," << query_counters[k].name << "," << (*((uint32_t*)data) ? "true" : "false") << std::endl;
+ break;
+ }
+ }
+ }
+
+ }
+ perf_query_instances.clear();
+ }
+}
+
+IntelGLPerf IntelGLTiming;
+IntelGLPerf* intelGLTiming = &IntelGLTiming;
+
+#endif
diff --git a/Source/Timing/intel_gl_perf.h b/Source/Timing/intel_gl_perf.h
new file mode 100644
index 00000000..333b773c
--- /dev/null
+++ b/Source/Timing/intel_gl_perf.h
@@ -0,0 +1,140 @@
+//-----------------------------------------------------------------------------
+// Name: intel_gl_perf.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#ifdef INTEL_TIMING
+#include <Internal/integer.h>
+#include <Utility/disallow_copy_and_assign.h>
+
+#include <opengl.h>
+
+#include <vector>
+#include <map>
+#include <queue>
+#include <string>
+#include <stack>
+
+//const char IntelGLPerf_basic_counters_name[] = "Intel_HD_MD_Render_Basic_Hardware_Counters";
+
+//const char* IntelGLPerf_basic_counters[] = {"GpuTime", NULL};
+
+const int kIntelGLPerf_TypeId_Standard = 1;
+
+struct IntelGLPerfQuery
+{
+ std::string name;
+ int type_id;
+ GLuint id;
+ GLuint size;
+};
+
+struct IntelGLPerfQueryCounter
+{
+ GLuint query_id;
+ std::string name;
+ GLuint id;
+ GLuint offset;
+ GLenum data_type;
+ GLuint size;
+};
+
+class IntelGLPerfRequest {
+public:
+ IntelGLPerfRequest( std::string query_name, int type_id, std::vector<std::string> query_counter_names );
+
+ std::string query_name;
+ int type_id;
+ std::vector<std::string> query_counter_names;
+};
+
+struct PerfInstance {
+ GLuint instance_id;
+ GLuint query_id;
+ int group;
+ const char* file;
+ int line;
+};
+
+class IntelGLPerf {
+ bool perf_available;
+ char* data_dest;
+
+ std::vector<IntelGLPerfRequest> perf_requests;
+
+ std::vector<IntelGLPerfQuery> queries;
+ std::vector<IntelGLPerfQueryCounter> query_counters;
+
+ std::map<std::string,GLuint> active_query_handles;
+
+ std::vector<PerfInstance> perf_query_instances;
+
+ GLuint max_query_size;
+
+ std::stack<GLuint> query_handle_stack;
+
+ std::ofstream csv_output;
+public:
+ IntelGLPerf();
+ ~IntelGLPerf();
+
+ void Init();
+ void Finalize();
+
+ void PerfGPUBegin(int type, int group, const char* file, const int line);
+ void PerfGPUEnd( );
+
+ void PostFrameSwap();
+};
+
+extern IntelGLPerf* intelGLTiming;
+
+
+#define INTEL_GL_PERF_INIT( ) \
+ intelGLTiming->Init()
+
+#define INTEL_GL_PERF_START( ) \
+ if( intelGLTiming ) intelGLTiming->PerfGPUBegin( kIntelGLPerf_TypeId_Standard, 0, __FILE__, __LINE__ )
+
+#define INTEL_GL_PERF_END( ) \
+ if( intelGLTiming ) intelGLTiming->PerfGPUEnd()
+
+#define INTEL_GL_PERF_SWAP() \
+ intelGLTiming->PostFrameSwap()
+
+#define INTEL_GL_PERF_FINALIZE() \
+ intelGLTiming->Finalize()
+
+#else
+
+#define INTEL_GL_PERF_INIT()
+
+#define INTEL_GL_PERF_START( )
+
+#define INTEL_GL_PERF_END( )
+
+#define INTEL_GL_PERF_SWAP()
+
+#define INTEL_GL_PERF_FINALIZE()
+
+#endif
+
diff --git a/Source/Timing/timestamp.h b/Source/Timing/timestamp.h
new file mode 100644
index 00000000..598b1274
--- /dev/null
+++ b/Source/Timing/timestamp.h
@@ -0,0 +1,44 @@
+//-----------------------------------------------------------------------------
+// Name: timestamp.h
+// Developer: Wolfire Games LLC
+// Author: Micah J Best
+// Date: Thursday, April 1, 2016
+// Description: Lightweight timing tools
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+inline uint64_t GetTimestamp() {
+ // Use rdtsc instruction to get the tsc or Time Stamp Counter
+#if (defined(PLATFORM_LINUX) || defined(PLATFORM_MACOSX)) && (defined(__i386__) || defined(__x86_64__))
+ uint32_t rax, rdx;
+ asm volatile ( "lfence" ::: "memory" ); //Fence memory load to everything is executed up to this point.
+ asm volatile ( "rdtsc\n" : "=a" (rax), "=d" (rdx) : : );
+ return ((uint64_t)rdx << 32) + rax;
+#elif defined(PLATFORM_WINDOWS) && _MSC_VER >= 1600
+ unsigned __int64 i;
+ _ReadBarrier();
+ i = __rdtsc();
+ return (uint64_t)i;
+#else
+
+#error "No timer implementation for current target platform"
+
+#endif
+}
diff --git a/Source/Timing/timingevent.cpp b/Source/Timing/timingevent.cpp
new file mode 100644
index 00000000..7763de82
--- /dev/null
+++ b/Source/Timing/timingevent.cpp
@@ -0,0 +1,521 @@
+//-----------------------------------------------------------------------------
+// Name: timingevent.cpp
+// Developer: Wolfire Games LLC
+// Author: Micah J Best
+// Date: Thursday, April 1, 2016
+// Description: Lightweight timing tools
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#ifdef SLIM_TIMING
+
+#include <Graphics/graphics.h>
+#include <Graphics/shaders.h>
+#include <Graphics/vbocontainer.h>
+
+#include <Timing/timingevent.h>
+#include <Logging/logdata.h>
+#include <Compat/fileio.h>
+#include <Threading/sdl_wrapper.h>
+
+#include <sstream>
+
+EventRecorder::EventRecorder() :
+ frameCount( 0 ),
+ showVisualization(false),
+ visData( NULL ),
+ visIndices( NULL ),
+ data_vbo( V_KIBIBYTE,kVBOFloat | kVBODynamic ),
+ index_vbo( V_KIBIBYTE/4,kVBOElement | kVBODynamic )
+{
+ setVisualizationScale( 16.0, 32.0 );
+
+ gl_state.blend = true;
+ gl_state.blend_src = GL_SRC_ALPHA;
+ gl_state.blend_dst = GL_ONE_MINUS_SRC_ALPHA;
+ gl_state.cull_face = false;
+ gl_state.depth_test = false;
+ gl_state.depth_write = false;
+
+ resetData();
+}
+
+void EventRecorder::resetData() {
+ memset( &events, 0, ST_MAX_EVENTS * sizeof(Event) );
+ nextIndex = 0;
+}
+
+
+void EventRecorder::init( std::string filePath ) {
+
+ // See if we have a file to open
+ if( filePath == "" ) {
+ writeFile = false;
+ }
+ else {
+
+ writeFile = true;
+
+ // Open the .csv file and write out the heading
+ outputFileName = filePath + "/frameTimings.csv";
+
+ LOGI << "Opening timing file: " << filePath << std::endl;
+
+ fp = my_fopen(outputFileName.c_str(), "w");
+
+ if( fp == NULL ) {
+ LOGF << "Unable to open timing file " << outputFileName << std::endl;
+ exit(1);
+ }
+
+ fprintf( fp, "frame,ms,cycles" );
+ for( int i = 0; i < STLastEvent; ++i ) {
+ fprintf(fp, ",%s", EVENT_NAMES[i].c_str() );
+ }
+ fprintf(fp,"\n");
+ }
+}
+
+
+void EventRecorder::startFrame() {
+ frameStartCycle = GetTimestamp();
+ frameStartTick = SDL_TS_GetTicks();
+}
+
+void EventRecorder::endFrame() {
+
+
+#ifndef SLIM_TIMING
+ // If we're not doing fine grained timing and not displaying anything, we don't need to do anything
+ if( !showVisualization ) {
+ // Just make sure that there's no overrun
+ nextIndex = 0;
+ frameCount++;
+ // .. and get out of here
+ return;
+ }
+#endif //not SLIM_TIMING
+
+
+ const int historyIndex = frameCount % HISTORY_FRAMES;
+
+ timingHistory[historyIndex].frameTimeCycles = GetTimestamp() - frameStartCycle;
+ timingHistory[historyIndex].frameTimeMS = SDL_TS_GetTicks() - frameStartTick;
+
+ frameCount++;
+
+ // For now we're just doing totals
+ // (note -- I'm aware that this is not the most effecient way to do this
+ // if it becomes a burden it can always be optimized)
+
+ // Do each event class in order
+ for( int eventIndex = 0; eventIndex < STLastEvent; ++eventIndex ) {
+ uint64_t totalTime = 0;
+ uint64_t startTime = 0;
+ uint64_t endTime = 0;
+ int openCount = 0; // In the case of nested timings, we ignore everything but the outermost
+
+ for( int i = 0; i < nextIndex; ++i ) {
+ if( events[ i ].event_id != eventIndex ) continue;
+
+ if( events[ i ].eventType == ST_EVENT_START_CODE ) {
+ openCount++;
+ if( openCount > 1 ) continue; // Skip over nested events
+
+ startTime = events[ i ].tsc;
+
+ continue;
+ }
+
+ if( events[ i ].eventType == ST_EVENT_END_CODE ) {
+
+ openCount--;
+ if( openCount > 0 ) continue; // Skip over nested events
+
+ if( openCount < 0 ) {
+ std::string errorMsg = "Unterminated or extra timing event: " + EVENT_NAMES[eventIndex];
+
+ LOGW << errorMsg << std::endl;
+ }
+ else {
+ endTime = events[ i ].tsc;
+ // I'm also aware that there is a small small chance of wraparound here, but it's more than 1 in a billion and
+ // I'm in a hurry
+
+ totalTime += (endTime - startTime);
+ }
+
+ }
+
+ }
+ timingHistory[historyIndex].eventTimes[ eventIndex ] = totalTime;
+
+ }
+
+ // finally write out the times, if nesseary
+ if( writeFile && frameCount > WARMUP_FRAMES ) {
+ fprintf(fp, "%d", frameCount );
+ fprintf(fp, ",%d", timingHistory[historyIndex].frameTimeMS );
+ fprintf(fp, ",%llu", (long long unsigned int)timingHistory[historyIndex].frameTimeCycles );
+
+ for( int eventIndex = 0; eventIndex < STLastEvent; ++eventIndex ) {
+ fprintf( fp,",%llu", (long long unsigned int)timingHistory[historyIndex].eventTimes[eventIndex] );
+ }
+
+ fprintf( fp,"\n" );
+ }
+
+ resetData();
+}
+
+void EventRecorder::finalize() {
+ if( fp )
+ {
+ fclose(fp);
+ fp = NULL;
+ }
+}
+
+
+int EventRecorder::getAvailableHistorySize() {
+ if( frameCount > HISTORY_FRAMES ) {
+ return HISTORY_FRAMES;
+ }
+ else {
+ return frameCount;
+ }
+}
+
+void EventRecorder::setVisualizationScale( float baseMS, float highMS ) {
+ visBaseMS = baseMS;
+ visHighMS = highMS;
+
+ std::ostringstream ss;
+ ss << baseMS << "ms";
+ baseMSLabel = ss.str();
+ ss.str("");
+ ss.clear();
+ ss << highMS << "ms";
+ highMSLabel = ss.str();
+
+}
+
+
+void EventRecorder::addNewVisualization( uint16_t event1, vec3 color1,
+ int32_t event2, vec3 color2,
+ int32_t event3, vec3 color3,
+ int32_t event4, vec3 color4,
+ int32_t event5, vec3 color5 ) {
+
+ // I'm aware this isn't the most effecient code -- but it's called a few times per program execution
+
+ std::vector<VisualizerEvent> newVisualization;
+
+ newVisualization.push_back( VisualizerEvent( event1, color1 ) );
+
+ if( event2 != -1 ) {
+ newVisualization.push_back( VisualizerEvent( event2, color2 ) );
+ }
+
+ if( event3 != -1 ) {
+ newVisualization.push_back( VisualizerEvent( event3, color3 ) );
+ }
+
+ if( event4 != -1 ) {
+ newVisualization.push_back( VisualizerEvent( event4, color4 ) );
+ }
+
+ if( event5 != -1 ) {
+ newVisualization.push_back( VisualizerEvent( event5, color5 ) );
+ }
+
+ visualizerEvents.push_back( newVisualization );
+
+}
+
+void EventRecorder::initVisualization() {
+ // Now we allocate the memory, etc for our visualization based on this new count
+
+ // See if we already have buffers allocated
+ if( visData != NULL && visIndices != NULL ) {
+ delete visData;
+ delete visIndices;
+ }
+
+ int boxCount = 0;
+ for( std::vector< std::vector<VisualizerEvent> >::iterator it = visualizerEvents.begin();
+ it != visualizerEvents.end();
+ ++it )
+ {
+ boxCount += (*it).size();
+ }
+
+ visDataSize = 6 * ( ( (HISTORY_FRAMES * boxCount) + 4 ) * 4 );
+ visIndexSize = 6 * ( ( (HISTORY_FRAMES * boxCount) + 4 ) );
+
+ visData = new GLfloat[ visDataSize ];
+ visIndices = new GLuint[ visIndexSize ];
+
+}
+
+
+// Fills in the box coordinates and color in the buffer
+// Box number starts at 0
+void EventRecorder::buildBoxAt( vec2 const& UL, vec2 const& LR, vec4 const& color, int boxNumber ) {
+
+ int vertOffset = boxNumber * 6 * 4;
+
+ visData[vertOffset] = UL.x();
+ visData[vertOffset+1] = LR.y();
+
+ visData[vertOffset+2] = color.r();
+ visData[vertOffset+3] = color.g();
+ visData[vertOffset+4] = color.b();
+ visData[vertOffset+5] = color.a();
+
+ visData[vertOffset+6] = LR.x();
+ visData[vertOffset+7] = LR.y();
+
+ visData[vertOffset+8] = color.r();
+ visData[vertOffset+9] = color.g();
+ visData[vertOffset+10] = color.b();
+ visData[vertOffset+11] = color.a();
+
+ visData[vertOffset+12] = LR.x();
+ visData[vertOffset+13] = UL.y();
+
+ visData[vertOffset+14] = color.r();
+ visData[vertOffset+15] = color.g();
+ visData[vertOffset+16] = color.b();
+ visData[vertOffset+17] = color.a();
+
+ visData[vertOffset+18] = UL.x();
+ visData[vertOffset+19] = UL.y();
+
+ visData[vertOffset+20] = color.r();
+ visData[vertOffset+21] = color.g();
+ visData[vertOffset+22] = color.b();
+ visData[vertOffset+23] = color.a();
+
+ int indexOffset = boxNumber * 6;
+ int startIndex = boxNumber * 4;
+
+ visIndices[indexOffset] = startIndex;
+ visIndices[indexOffset+1] = startIndex+1;
+ visIndices[indexOffset+2] = startIndex+2;
+
+ visIndices[indexOffset+3] = startIndex;
+ visIndices[indexOffset+4] = startIndex+2;
+ visIndices[indexOffset+5] = startIndex+3;
+
+}
+
+void EventRecorder::drawVisualization( TextAtlasRenderer& text_atlas_renderer,
+ FontRenderer& font_renderer,
+ TextAtlas& text_atlas ) {
+
+ // See if we're displaying and do a sanity check
+ if( !showVisualization || visualizerEvents.empty() || visIndices == NULL || visData == NULL ) return;
+
+ Graphics* graphics = Graphics::Instance();
+ Shaders* shaders = Shaders::Instance();
+
+ const float winWidth = (float)graphics->window_dims[0];
+ const float winHeight = (float)graphics->window_dims[1];
+
+ // use non-premultiplied alpha
+ graphics->SetBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ // Prep the data
+
+ // Figure out the (rough) factor betweeen ms and cycles on this timing
+ int currentHistory = (frameCount ) % HISTORY_FRAMES;
+
+ float cyclesInBaseBar;
+
+ // prevent a divide by zero possibility (it'll look wonky... but it won't crash)
+ if( timingHistory[ currentHistory ].frameTimeMS == 0 ) {
+ cyclesInBaseBar = visBaseMS;
+ }
+ else {
+ cyclesInBaseBar = (timingHistory[ currentHistory ].frameTimeCycles / timingHistory[ currentHistory ].frameTimeMS) * visBaseMS;
+ }
+
+ // Now figure out the 'normalized' version of the current timings ( in terms of a factor of the timing bar)
+ for( int eventID = 0; eventID < STLastEvent; ++eventID ) {
+ normalizedFrames[currentHistory].eventTimes[eventID] = (float)timingHistory[currentHistory].eventTimes[eventID]/cyclesInBaseBar;
+ }
+
+ // Now we get to build some triangles!
+
+ int shader_id = shaders->returnProgram("simple_2d #COLOREDVERTICES");
+ shaders->setProgram(shader_id);
+ int vert_coord_id = shaders->returnShaderAttrib("vert_coord", shader_id);
+ int color_id = shaders->returnShaderAttrib("vert_color", shader_id);
+ int mvp_mat_id = shaders->returnShaderVariable("mvp_mat", shader_id);
+
+ graphics->setGLState(gl_state);
+ glm::mat4 proj_mat;
+
+ const vec4 backgroundColor( 0.26, 0.27, 0.29, 0.5 );
+ const vec4 backgroundBarColor( 0.5, 0.27, 0.29, 0.9 );
+ const vec4 highareaColor( 0.26, 0.27, 0.29, 0.3 );
+ const vec4 highareaBarColor( 0.5, 0.27, 0.29, 0.7 );
+
+ const float backgroundHeight = winHeight * 0.20 ;
+ const float highmarkHeight = (visHighMS/visBaseMS)*backgroundHeight;
+
+ // Set up the background panel
+ buildBoxAt( vec2( 0.0, backgroundHeight ), vec2( winWidth ,0.0 ), backgroundColor, 0 );
+ buildBoxAt( vec2( 0.0, backgroundHeight + 2 ), vec2( winWidth, backgroundHeight ), backgroundBarColor, 1 );
+
+ // Set up the 'high' background area (if needed)
+ if( visHighMS > visBaseMS ) {
+ buildBoxAt( vec2( 0.0, highmarkHeight ), vec2( winWidth, backgroundHeight ), highareaColor, 2 );
+ buildBoxAt( vec2( 0.0, highmarkHeight + 2 ), vec2( winWidth, highmarkHeight ), highareaBarColor, 3 );
+ }
+ else {
+ // Just draw nothing ( just to keep the buffer the same size )
+ buildBoxAt( vec2( 0.0, backgroundHeight ), vec2( winWidth ,0.0 ), vec4(0.0), 2 );
+ buildBoxAt( vec2( 0.0, backgroundHeight + 2 ), vec2( winWidth, backgroundHeight ), vec4(0.0), 3 );
+ }
+
+ // Figure out how wide the bars should be
+ float barWidth = ((winWidth - 10) - (float)( 10.0 * visualizerEvents.size() )) /
+ (float)(visualizerEvents.size() * HISTORY_FRAMES );
+
+ float xOffset = 10.0;
+ int boxCount = 4;
+
+ for( std::vector< std::vector<VisualizerEvent> >::iterator outerIt = visualizerEvents.begin();
+ outerIt != visualizerEvents.end();
+ ++outerIt )
+ {
+ // Go through all the available history
+ int histSize = getAvailableHistorySize();
+ int histIndex;
+
+ if( histSize < HISTORY_FRAMES ) {
+ histIndex = 0;
+ }
+ else {
+ histIndex = (frameCount + 1) % HISTORY_FRAMES;
+ }
+
+ float alpha = 0.3;
+ float alphaStep = 0.7 / (float)histSize;
+
+ for( int i = 0; i < histSize; ++i ) {
+
+ float yOffset = 0; // For stacking
+
+ for( std::vector<VisualizerEvent>::iterator innerIt = (*outerIt).begin();
+ innerIt != (*outerIt).end();
+ ++innerIt ) {
+
+ const uint16_t eventID = innerIt->eventID;
+
+ float upperHeight = yOffset + (backgroundHeight * normalizedFrames[ histIndex ].eventTimes[ eventID ]);
+
+ vec2 UL( xOffset, upperHeight );
+ vec2 LR( (xOffset+barWidth), yOffset );
+ vec4 color( innerIt->color, alpha );
+
+ buildBoxAt( UL, LR, color, boxCount );
+
+ boxCount++;
+ yOffset = upperHeight;
+
+
+ }
+ alpha += alphaStep;
+ histIndex = (histIndex + 1) % HISTORY_FRAMES;
+ xOffset += barWidth;
+ }
+ xOffset += 10.0;
+ }
+
+
+ // Set up the model matrix
+ glm::mat4 mvp_mat = glm::ortho(0.0f, winWidth, 0.0f, winHeight);
+
+ glUniformMatrix4fv(mvp_mat_id, 1, false, (GLfloat*)&mvp_mat);
+ Graphics::Instance()->EnableVertexAttribArray(vert_coord_id);
+ Graphics::Instance()->EnableVertexAttribArray(color_id);
+
+ index_vbo.SetHint(boxCount * 6 * sizeof(GLuint),kVBODynamic);
+ index_vbo.Fill(boxCount * 6 * sizeof(GLuint), (void*)visIndices);
+
+ data_vbo.SetHint(boxCount * 4 * 6 * sizeof(GLfloat),kVBODynamic);
+ data_vbo.Fill(boxCount * 4 * 6 * sizeof(GLfloat), (void*)visData);
+ index_vbo.Bind();
+ data_vbo.Bind();
+
+ glVertexAttribPointer(vert_coord_id, 2, GL_FLOAT, false, 6*sizeof(GLfloat), (const void*)(data_vbo.offset()));
+ glVertexAttribPointer(color_id, 4, GL_FLOAT, false, 6*sizeof(GLfloat), (const void*)(data_vbo.offset()+2*sizeof(GLfloat)));
+
+ Graphics::Instance()->DrawElements(GL_TRIANGLES, boxCount * 6, GL_UNSIGNED_INT, (const void*)index_vbo.offset());
+
+ Graphics::Instance()->ResetVertexAttribArrays();
+
+ int basepos[] = {5, (int)(graphics->window_dims[1] - (backgroundHeight+4))};
+ text_atlas_renderer.num_characters = 0;
+ text_atlas_renderer.AddText(&text_atlas, baseMSLabel.c_str(), basepos, &font_renderer,UINT32MAX);
+
+ if( visHighMS > visBaseMS ) {
+ int highpos[] = {5, (int)(graphics->window_dims[1] - (highmarkHeight+4))};
+ text_atlas_renderer.AddText(&text_atlas, highMSLabel.c_str(), highpos, &font_renderer,UINT32MAX);
+ }
+
+ text_atlas_renderer.Draw(&text_atlas, graphics, TextAtlasRenderer::kTextShadow, vec4(0.96, 0.91, 0.59, 0.8 ));
+ Textures::Instance()->InvalidateBindCache();
+
+
+}
+
+void EventRecorder::displayVisualization( bool show ) {
+
+ // See if we're switching from not displaying to displaying
+ if( !showVisualization && show )
+ {
+ // Set all the history structures back to zero
+ memset( &normalizedFrames, 0, HISTORY_FRAMES * sizeof(NormalizedFrameTotals) );
+ memset( &timingHistory, 0, HISTORY_FRAMES * sizeof(FrameTotals) );
+ }
+
+ showVisualization = show;
+}
+
+void EventRecorder::clearVisualization() {
+
+ if( visData != NULL && visIndices != NULL ) {
+ delete visData;
+ delete visIndices;
+ }
+
+ visData = NULL;
+ visIndices = NULL;
+
+ visualizerEvents.clear();
+}
+
+EventRecorder TimingEvents;
+EventRecorder* slimTimingEvents = &TimingEvents;
+
+#endif //SLIM_TIMING
diff --git a/Source/Timing/timingevent.h b/Source/Timing/timingevent.h
new file mode 100644
index 00000000..422508a7
--- /dev/null
+++ b/Source/Timing/timingevent.h
@@ -0,0 +1,479 @@
+//-----------------------------------------------------------------------------
+// Name: timingevent.h
+// Developer: Wolfire Games LLC
+// Author: Micah J Best
+// Date: Thursday, April 1, 2016
+// Description: Lightweight timing tools
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+/*******************************************************************************************/
+/**
+ * "Slim Timing" is a system for taking high accuracy timings of various events in the system
+ * and reporting them by various means. It has too modes: fine and coarse grained.
+ * Coarse grained is always compiled in and is basically for measuring fairly large engine
+ * features such as the time it takes to update all the AI agents and feed the realtime
+ * visualization. Fine grained mode needs to be compiled in (-DSLIM_TIMING) and is
+ * designed to take significantly finer grained timings and then output them in a nice
+ * easy to crunch .csv file. Both systems use the same commands with a different macro for
+ * each timing mode.
+ *
+ * The system revolves around a set of discrete event categories. Like most other timing
+ * systems the code is marked up with a 'begin event' and 'end event' macro. The list of
+ * timing events is in the SlimTimingEvents enum below (there is also a corresponding array
+ * of strings (EVENT_NAMES) for data output). The total amount of time spent inside these
+ * events is tabulated at the end of the frame. The system deals with nested timing of the
+ * same event type by disregarding all but the outermost, so there is no need to worry
+ * about double counting with reentrant code.
+ *
+ * An example setup would look something like this:
+ * STIMING_INIT( GetWritePath(CoreGameModID).c_str() );
+ *
+ * STIMING_ADDVISUALIZATION( STUpdate, vec3( 0.7, 0.3, 0.3 ),
+ * STDraw, vec3( 0.3, 0.7, 0.3 ) );
+ *
+ * STIMING_ADDVISUALIZATION( STDrawSwap, vec3( 0.3, 0.3, 0.7 ) );
+ *
+ * STIMING_SETVISUALIZATIONSCALE(16,32);
+ *
+ * STIMING_INITVISUALIZATION();
+ *
+ * This should be called sometime during program initialization (something like this should
+ * already be in the main program). The STIMING_INIT starts things off and specifies where
+ * the output (if any) should be written. The STIMING_ADDVISUALIZATION lines add events
+ * to the realtime visualization (which is bound to the "show_timing" key which at the time
+ * of this writing is given the default value of F2). These lines above say to display the
+ * STUpdata and STDraw event times in a stacked bar plot and the STDrawSwap in single bar
+ * plot. Each will be rendered with the RGB color values provided in the second parameter
+ * The system keeps a certain number of values as history (number determined by the
+ * HISTORY_FRAMES #define below) and displays all those values to show how the timing is changing
+ * frame by frame.
+ *
+ * The STIMING_SETVISUALIZATIONSCALE macro sets up the visual scale of the timing. Essentially
+ * this is saying "put markers at 16ms and 32ms in the visualization" and scale the results
+ * appropriately. (If you were timing something smaller, you may for example use 2,4). The
+ * defaults are 16 and 32 as these are the thresholds for 60 and 30 FPS respectively.
+ *
+ * Finally, STIMING_INITVISUALIZATION, allocated memory and gets ready (you can change what
+ * is being visualized dynamically, but that's out of the scope of this text).
+ *
+ * To demonstrate actual timings, let's look at simplified version of the main game loop:
+ *
+ * while( !engine->quitting_ ) {
+ *
+ * STIMING_STARTFRAME();
+ *
+ * STIMING_START_COARSE( STUpdate );
+ * engine->Update();
+ * STIMING_END_COARSE( STUpdate );
+ *
+ * if(!engine->quitting_){
+ * STIMING_START_COARSE( STDraw );
+ * engine->Draw();
+ * STIMING_END_COARSE( STDraw );
+ * }
+ * STIMING_START_COARSE( STDrawSwap );
+ * Graphics::Instance()->SwapToScreen();
+ * STIMING_END_COARSE( STDrawSwap );
+ *
+ * STIMING_ENDFRAME();
+ *
+ * }
+ *
+ * The STIMING_STARTFRAME and STIMING_ENDFRAME should be self-explanatory (note that the
+ * system assumes that the visualization is done *during* a frame I thought about timing this
+ * to compensate, but I think that might be going a little too far).
+ *
+ * Similarly, the STIMING_START_COARSE and STIMING_END_COARSE should also be self explanatory.
+ * This is where the difference between find and coarse mode come into play. There is the
+ * corresponding pair STIMING_START and STIMING_END which take exactly the same parameters,
+ * but without being in fine grained mode (compiling with -DSLIM_TIMING) they will be compiled
+ * to no-ops. The timings from the _COARSE markup will be used in *both* modes.
+ *
+ * UPDATE: due to strange crashing on Windows 7 machines, I'm going to
+ * disable this unless SLIM_TIMING is explicitly defined
+ *
+ *
+ */
+#pragma once
+
+//These needs to be included even when slim timing is disabled, because they contain zeroed macros.
+#include <Timing/gl_query_perf.h>
+#include <Timing/intel_gl_perf.h>
+#include <Timing/timestamp.h>
+
+#ifdef SLIM_TIMING
+
+#ifndef MEASURE_TIME_LEVEL
+#define MEASURE_TIME_LEVEL 1
+#endif
+
+#include <Internal/integer.h>
+#include <Wrappers/glm.h>
+#include <Graphics/text.h>
+#include <Graphics/vboringcontainer.h>
+
+#include <SDL.h>
+
+#if defined(PLATFORM_WINDOWS) && _MSC_VER >= 1600
+#include <intrin.h>
+#endif
+
+#include <string>
+#include <vector>
+#include <stdint.h>
+#include <stdio.h>
+
+// Total hack, but add new timing event classes here
+// (and don't forgot to add their name as a string below)
+enum SlimTimingEvents {
+ STUpdate,
+ STDraw,
+ STDrawSwap,
+ STLastEvent // Leave this last or Bad Things will happen
+};
+
+const std::string EVENT_NAMES[STLastEvent] = {
+ "Update",
+ "Draw",
+ "DrawSwap"
+};
+
+#ifdef SLIM_TIMING
+#define ST_MAX_EVENTS 16384
+#else
+#define ST_MAX_EVENTS 256
+#endif //SLIM_TIMING
+
+// How man frames should we skip to avoid throwing our averages off with setup/warming
+#define WARMUP_FRAMES 10
+#define HISTORY_FRAMES 120
+
+#define ST_MAX_THREADS 4
+
+#define ST_EVENT_START_CODE 0
+#define ST_EVENT_END_CODE 1
+
+class EventRecorder {
+
+ // Internal structure for timing events
+ struct Event {
+ uint64_t tsc; // Cycle count at the time of this event
+ uint16_t eventType; // Start, end, etc
+ uint16_t event_id; // Which event is being recorded?
+ uint32_t thread_id; // For future use -- and to pad out the structure for better reading
+ };
+
+ // Holding the derived values
+ struct FrameTotals {
+ uint64_t eventTimes[STLastEvent]; // Total for each event
+ uint64_t frameTimeCycles; // Total cycles used in this frame
+ uint32_t frameTimeMS; // Elapsed frame time in ms
+ };
+
+ struct NormalizedFrameTotals {
+ float eventTimes[ STLastEvent ];
+ };
+
+ int nextIndex; // Where to write the next event?
+ Event events[ ST_MAX_EVENTS ]; // Storage for all events
+
+ uint64_t frameStartCycle; // When did we start this frame (cycles)
+ uint32_t frameStartTick; // When did we start this frame (ms)?
+
+ FrameTotals timingHistory[HISTORY_FRAMES]; // Aggregate time for all events
+ NormalizedFrameTotals normalizedFrames[HISTORY_FRAMES];
+
+ bool writeFile; // Are we writing to a file?
+ std::string outputFileName; // What file are we writing to/if any?
+ FILE * fp; // File handle for the above
+ int frameCount; // Which frame are we processing
+
+ struct VisualizerEvent {
+ uint16_t eventID;
+ vec3 color;
+
+ VisualizerEvent() {}
+
+ VisualizerEvent( uint16_t _eventID, vec3 _color ) :
+ eventID( _eventID ),
+ color( _color )
+ {}
+ };
+
+ bool showVisualization; // Should we be displaying the visualizationx
+
+ GLfloat* visData; // OpenGL vertex data for the visualization
+ GLuint* visIndices; // OpenGL index data for the visualization
+
+ VBORingContainer data_vbo; //VBOs for the above
+ VBORingContainer index_vbo;
+
+ int visDataSize; // Sizes of the above (in bytes)
+ int visIndexSize;
+
+ float visBaseMS; // How many miliseconds does the 'main' area of the visualization represent
+ float visHighMS; // How many miliseconds does should the 'high' area of the visualization represent
+
+ std::string baseMSLabel; // Label for the base level maker
+ std::string highMSLabel; // Label for the high marker
+
+
+ std::vector< std::vector<VisualizerEvent> > visualizerEvents; // What events are being visualized
+
+ GLState gl_state; // State for the visualization
+
+ /*******************************************************************************************/
+ /**
+ * @brief Clear all current records
+ *
+ */
+ void resetData();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Get the number of history frames available
+ *
+ */
+ int getAvailableHistorySize();
+
+
+ /*******************************************************************************************/
+ /**
+ * @brief Internal — fill in the coordinates for a box
+ *
+ */
+ void buildBoxAt( vec2 const& UL, vec2 const& LR, vec4 const& color, int boxNumber );
+
+public:
+
+ EventRecorder();
+
+ void init( std::string filePath );
+
+
+ inline void startEvent( int const& event_id ) {
+ events[ nextIndex ].tsc = GetTimestamp();
+ events[ nextIndex ].eventType = ST_EVENT_START_CODE;
+ events[ nextIndex ].event_id = event_id;
+ events[ nextIndex ].thread_id = 0;
+ nextIndex++;
+ }
+
+ inline void endEvent( int const& event_id ) {
+ events[ nextIndex ].tsc = GetTimestamp();
+ events[ nextIndex ].eventType = ST_EVENT_END_CODE;
+ events[ nextIndex ].event_id = event_id;
+ events[ nextIndex ].thread_id = 0;
+ nextIndex++;
+ }
+
+ void startFrame();
+
+ void endFrame();
+
+ void finalize();
+
+
+ /*******************************************************************************************/
+ /**
+ * @brief Determine the scale of the presentation by giving a ms value to the ‘main box’ and an optional hight marker
+ *
+ *
+ * @param baseMS how many ms does the 'base' part of the visualization represent?
+ * @param highMS how many ms does the 'high' part of the visualization represent? (optional)
+ *
+ */
+ void setVisualizationScale( float baseMS, float highMS = -1.0 );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Adds a new timing event to the visualziation (add multiple events to stack)
+ *
+ *
+ * @param event1 ID of the event we want to visualization (required)
+ * @param color1 color for the first event (required)
+ * @param event2 ID of the event we want to visualization (optional)
+ * @param color2 color for the first event (optional)
+ * @param event3 ID of the event we want to visualization (optional)
+ * @param color3 color for the first event (optional)
+ * @param event4 ID of the event we want to visualization (optional)
+ * @param color4 color for the first event (optional)
+ * @param event5 ID of the event we want to visualization (optional)
+ * @param color5 color for the first event (optional)
+ *
+ */
+ void addNewVisualization( uint16_t event1, vec3 color1,
+ int32_t event2 = -1, vec3 color2 = vec3(0.0),
+ int32_t event3 = -1, vec3 color3 = vec3(0.0),
+ int32_t event4 = -1, vec3 color4 = vec3(0.0),
+ int32_t event5 = -1, vec3 color5 = vec3(0.0) );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Allocate memory, etc for the visualization
+ *
+ */
+ void initVisualization();
+
+ /*******************************************************************************************/
+ /**
+ * @brief Draw the various graphs requested for timing visualizing
+ *
+ * @param text_atlas_renderer Reference to a text atlas renderer for drawing text
+ * @param font_renderer Reference to a font renderer for drawing text
+ * @param text_atlas Text atlas to use for displaying text
+ *
+ */
+ void drawVisualization( TextAtlasRenderer& text_atlas_renderer,
+ FontRenderer& font_renderer,
+ TextAtlas& text_atlas );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Display (or not) the visualization
+ *
+ * @param show Should we show the visualization or not?
+ *
+ */
+ void displayVisualization( bool show );
+
+ /*******************************************************************************************/
+ /**
+ * @brief Toggles displaying the visualization
+ *
+ */
+ void toggleVisualization() {
+ displayVisualization( !showVisualization );
+ }
+
+ /*******************************************************************************************/
+ /**
+ * @brief Get rid of the current visualization parameters, you'll have to reset it up if you
+ * want it again
+ *
+ */
+ void clearVisualization();
+
+
+};
+
+extern EventRecorder* slimTimingEvents;
+
+#ifdef SLIM_TIMING
+
+
+#define STIMING_INIT( FILEPATH ) \
+slimTimingEvents->init( FILEPATH )
+
+#else
+
+// Don't write out a file if only doing coarse timing
+#define STIMING_INIT( FILEPATH ) \
+slimTimingEvents->init( "" )
+
+#endif //SLIM_TIMING
+
+#define STIMING_ADDVISUALIZATION(...)\
+slimTimingEvents->addNewVisualization(__VA_ARGS__)
+
+#define STIMING_SETVISUALIZATIONSCALE(... )\
+slimTimingEvents->setVisualizationScale(__VA_ARGS__)
+
+#define STIMING_INITVISUALIZATION() \
+slimTimingEvents->initVisualization()
+
+#define STIMING_STARTFRAME() \
+slimTimingEvents->startFrame()
+
+#define STIMING_START_COARSE( EVENT ) \
+slimTimingEvents->startEvent( EVENT )
+
+#define STIMING_END_COARSE( EVENT ) \
+slimTimingEvents->endEvent( EVENT )
+
+#ifdef SLIM_TIMING
+
+#define STIMING_START( EVENT ) \
+slimTimingEvents->startEvent( EVENT )
+
+#define STIMING_END( EVENT ) \
+slimTimingEvents->endEvent( EVENT )
+
+#else
+
+// Don't take the timing of events not marked 'coarse' unless asked
+#define STIMING_START( EVENT )
+
+#define STIMING_END( EVENT )
+
+#endif //SLIM_TIMING
+
+#define STIMING_ENDFRAME() \
+slimTimingEvents->endFrame()
+
+#define STIMING_FINALIZE() \
+slimTimingEvents->finalize()
+
+#else
+
+// To prevent an error on Windows, just disable everything if not defined
+
+#define STIMING_INIT( FILEPATH )
+
+#define STIMING_ADDVISUALIZATION(...)
+
+#define STIMING_SETVISUALIZATIONSCALE(... )
+
+#define STIMING_INITVISUALIZATION()
+
+#define STIMING_STARTFRAME()
+
+#define STIMING_START_COARSE( EVENT )
+
+#define STIMING_END_COARSE( EVENT )
+
+#define STIMING_START( EVENT )
+
+#define STIMING_END( EVENT )
+
+#define STIMING_ENDFRAME()
+
+#define STIMING_FINALIZE()
+
+#endif //SLIM_TIMING
+
+#define GL_PERF_INIT( ) \
+INTEL_GL_PERF_INIT(); \
+GL_TIMER_QUERY_INIT()
+
+#define GL_PERF_START( ) \
+INTEL_GL_PERF_START(); \
+GL_TIMER_QUERY_START()
+
+#define GL_PERF_END( ) \
+INTEL_GL_PERF_END(); \
+GL_TIMER_QUERY_END()
+
+#define GL_SWAP() \
+INTEL_GL_PERF_SWAP(); \
+GL_TIMER_QUERY_SWAP()
+
+#define GL_PERF_FINALIZE() \
+INTEL_GL_PERF_FINALIZE(); \
+GL_TIMER_QUERY_FINALIZE()
diff --git a/Source/UnitTests/atlastest.cpp b/Source/UnitTests/atlastest.cpp
new file mode 100644
index 00000000..81ba31ca
--- /dev/null
+++ b/Source/UnitTests/atlastest.cpp
@@ -0,0 +1,186 @@
+//-----------------------------------------------------------------------------
+// Name: atlastest.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <Graphics/atlasnodetree.h>
+#include <Logging/logdata.h>
+
+#include <tut/tut.hpp>
+
+#include <cmath>
+#include <cstdlib>
+
+#include <set>
+
+namespace tut
+{
+ struct data //
+ {
+ int hej;
+ };
+
+ typedef test_group<data> tg;
+ tg test_groupt("AtlasNodeTree tests");
+
+ typedef tg::object testobject;
+
+ template<>
+ template<>
+ void testobject::test<1>()
+ {
+ AtlasNodeTree tree(ivec2(1024,1024), 64);
+
+ /*
+ LOGI << "Size: " << tree.nodecount << std::endl;
+ for( int i = 0; i < tree.nodecount; i++ )
+ {
+ LOGI << tree.data[i] << std::endl;
+ }
+ */
+
+ for( int i = 1; i < tree.GetNodeCount()-1; i++ )
+ {
+ ensure_equals("Verify depth first memory", tree.GetNodeRoot()[i].id+1, tree.GetNodeRoot()[i+1].id);
+ }
+ }
+
+ template<>
+ template<>
+ void testobject::test<2>()
+ {
+ AtlasNodeTree tree(ivec2(1024,1024),64);
+
+ ensure("Verify correct flat allocation",tree.RetrieveNode(ivec2(512,512)).valid());
+ ensure("Verify correct flat allocation",tree.RetrieveNode(ivec2(512,512)).valid());
+ ensure("Verify correct flat allocation",tree.RetrieveNode(ivec2(512,512)).valid());
+ ensure("Verify correct flat allocation",tree.RetrieveNode(ivec2(512,512)).valid());
+ ensure("Verify correct flat allocation",tree.RetrieveNode(ivec2(512,512)).valid()==false);
+
+ }
+
+ template<>
+ template<>
+ void testobject::test<3>()
+ {
+ AtlasNodeTree tree(ivec2(1024,1024),64);
+
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(256,256)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(256,256)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(512,512)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(512,512)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(512,512)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(512,512)).valid()==false);
+
+ }
+
+ template<>
+ template<>
+ void testobject::test<4>()
+ {
+ AtlasNodeTree tree(ivec2(1024,1024),64);
+
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(512,512)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(256,256)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(256,256)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(512,512)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(512,512)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(512,512)).valid()==false);
+ }
+
+ template<>
+ template<>
+ void testobject::test<5>()
+ {
+ AtlasNodeTree tree(ivec2(1024,1024),64);
+
+ ensure("Verify allocation, mixxed sizes",tree.RetrieveNode(ivec2(512,512)).valid());
+ ensure("Verify allocation, mixxed sizes",tree.RetrieveNode(ivec2(128,128)).valid());
+ ensure("Verify allocation, mixxed sizes",tree.RetrieveNode(ivec2(128,128)).valid());
+ ensure("Verify allocation, mixxed sizes",tree.RetrieveNode(ivec2(256,256)).valid());
+ ensure("Verify allocation, mixxed sizes",tree.RetrieveNode(ivec2(512,512)).valid());
+ ensure("Verify allocation, mixxed sizes",tree.RetrieveNode(ivec2(512,512)).valid());
+ ensure("Verify allocation, mixxed sizes",tree.RetrieveNode(ivec2(512,512)).valid()==false);
+ }
+
+ template<>
+ template<>
+ void testobject::test<6>()
+ {
+ AtlasNodeTree tree(ivec2(1024,1024),64);
+
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(512,512)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(256,256)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(256,256)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(256,256)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(256,256)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(512,512)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(512,512)).valid());
+ ensure("Verify last is invalid",tree.RetrieveNode(ivec2(64,64)).valid()==false);
+ }
+
+ template<>
+ template<>
+ void testobject::test<7>()
+ {
+ AtlasNodeTree tree(ivec2(1024,1024),64);
+
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(512,512)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(64,128)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(128,64)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(128,128)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(512,512)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(512,512)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(512,512)).valid() == false);
+ }
+
+ template<>
+ template<>
+ void testobject::test<8>()
+ {
+ AtlasNodeTree tree(ivec2(1024,1024),64);
+
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(500,512)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(64,128)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(100,64)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(128,100)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(512,500)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(512,512)).valid());
+ ensure("Verify allocation",tree.RetrieveNode(ivec2(512,512)).valid()==false);
+ }
+
+ template<>
+ template<>
+ void testobject::test<9>()
+ {
+ AtlasNodeTree tree(ivec2(1024,1024),64);
+
+ std::set<AtlasNodeTree::AtlasNode*> pset;
+
+ pset.insert(tree.RetrieveNode(ivec2(512,512)).node);
+ pset.insert(tree.RetrieveNode(ivec2(64,128)).node);
+ pset.insert(tree.RetrieveNode(ivec2(128,64)).node);
+ pset.insert(tree.RetrieveNode(ivec2(128,128)).node);
+ pset.insert(tree.RetrieveNode(ivec2(512,512)).node);
+ pset.insert(tree.RetrieveNode(ivec2(512,512)).node);
+
+ ensure("Verify that we don't get the same object twice", pset.size()==6);
+ }
+}
diff --git a/Source/UnitTests/block_allocator_tester.cpp b/Source/UnitTests/block_allocator_tester.cpp
new file mode 100644
index 00000000..a6ee5f93
--- /dev/null
+++ b/Source/UnitTests/block_allocator_tester.cpp
@@ -0,0 +1,253 @@
+//-----------------------------------------------------------------------------
+// Name: block_allocator_tester.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include <Logging/logdata.h>
+#include <Memory/block_allocator.h>
+#include <Wrappers/tut.h>
+#include <Math/enginemath.h>
+
+#include <cmath>
+#include <cstdlib>
+
+#include <set>
+#include <vector>
+
+namespace tut
+{
+ struct BlockAllocatorTestData //
+ {
+ };
+
+ typedef test_group<BlockAllocatorTestData> tg;
+ tg test_group_i("Block Allocation tests");
+
+ typedef tg::object block_allocation_test;
+
+ template<>
+ template<>
+ void block_allocation_test::test<1>()
+ {
+ const size_t block_count = 2048;
+
+ size_t count = 128;
+ size_t blocksize = sizeof(uint64_t)*count;
+
+ void* mem = malloc( block_count * blocksize );
+
+ ensure( "Non null mem", mem != NULL );
+
+ BlockAllocator ba;
+ ba.Init( mem, block_count, blocksize );
+
+ uint64_t* ptrs[block_count];
+
+ for(uint32_t i = 0; i < block_count; i++ )
+ {
+ ptrs[i] = (uint64_t*)ba.Alloc(blocksize);
+
+ ensure( "NULL Return", ptrs[i] != NULL );
+ }
+ free(mem);
+ }
+
+ template<>
+ template<>
+ void block_allocation_test::test<2>()
+ {
+ const size_t block_count = 2048;
+
+ size_t count = 128;
+ size_t blocksize = sizeof(uint64_t)*count;
+
+ void* mem = malloc( block_count * blocksize );
+
+ BlockAllocator ba;
+ ba.Init( mem, block_count, blocksize );
+
+ uint64_t* ptrs[block_count];
+
+ for(uint32_t i = 0; i < block_count; i++ )
+ {
+ ptrs[i] = (uint64_t*)ba.Alloc(blocksize);
+
+ if( i > 0 )
+ {
+ ensure_equals( "Expected Pointer Distance", (uintptr_t)ptrs[i]-blocksize, (uintptr_t)ptrs[i-1] );
+ }
+ }
+ free(mem);
+ }
+
+ template<>
+ template<>
+ void block_allocation_test::test<3>()
+ {
+ const size_t block_count = 2048;
+
+ size_t count = 128;
+ size_t blocksize = sizeof(uint64_t)*count;
+
+ void* mem = malloc( block_count * blocksize );
+
+ BlockAllocator ba;
+ ba.Init( mem, block_count, blocksize );
+
+ uint64_t* ptrs[block_count];
+
+ for(uint32_t i = 0; i < block_count; i++ )
+ {
+ ptrs[i] = (uint64_t*)ba.Alloc(blocksize);
+
+ for( uint32_t k = 0; k < i; k++ )
+ {
+ ensure( "Non-repeating addresse", ptrs[k] != ptrs[i] );
+ }
+ }
+ free(mem);
+ }
+
+ template<>
+ template<>
+ void block_allocation_test::test<4>()
+ {
+ const size_t block_count = 2048;
+
+ size_t count = 128;
+ size_t blocksize = sizeof(uint64_t)*count;
+
+ void* mem = malloc( block_count * blocksize );
+
+ BlockAllocator ba;
+ ba.Init( mem, block_count, blocksize );
+
+ uint64_t* ptrs[block_count];
+
+ for( uint32_t i = 0; i < block_count; i++ )
+ {
+ ptrs[i] = (uint64_t*)ba.Alloc(1024);
+
+ for( uint32_t k = 0; k < count; k++ )
+ {
+ ptrs[i][k] = i;
+ }
+ }
+
+ for( uint32_t i = 0; i < block_count; i++ )
+ {
+ for( uint32_t k = 0; k < count; k++ )
+ {
+ ensure_equals( "Linear non-overlap", ptrs[i][k], i );
+ }
+ }
+ free(mem);
+ }
+
+ struct TestData
+ {
+ std::vector<uint8_t> ref_data;
+ uint8_t *ptr;
+ size_t count;
+
+ bool VerifyData()
+ {
+ for( uint32_t i = 0; i < count; i++ )
+ {
+ if( ptr[i] != ref_data[i] )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void GenerateData()
+ {
+ ref_data.resize(count);
+ for( uint32_t i = 0; i < count; i++ )
+ {
+ uint8_t v = RangedRandomInt(0,255);
+ ptr[i] = v;
+ ref_data[i] = v;
+ }
+ }
+ };
+
+ template<>
+ template<>
+ void block_allocation_test::test<5>()
+ {
+ const size_t block_count = 2048;
+
+ size_t count = 128;
+ size_t blocksize = sizeof(uint64_t)*count;
+
+ void* mem = malloc( block_count * blocksize );
+
+ BlockAllocator ba;
+ ba.Init( mem, block_count, blocksize );
+
+ std::vector<TestData> ptrs;
+
+ for( uint32_t attempt = 0; attempt < 32; attempt++ )
+ {
+ while(true)
+ {
+ size_t size = sizeof(uint8_t)*RangedRandomInt(1,2048*8);
+
+ if( ba.CanAlloc(size) )
+ {
+ uint8_t *ptr = (uint8_t*)ba.Alloc(size);
+
+ ensure( "Not NULL", ptr != NULL );
+ TestData t;
+
+ t.ptr = ptr;
+ t.count = size;
+ t.GenerateData();
+
+ ptrs.push_back(t);
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ uint32_t number_of_removes = RangedRandomInt(0,ptrs.size()-1);
+
+ for( uint32_t i = 0; i < number_of_removes; i++ )
+ {
+ int index = RangedRandomInt(0,ptrs.size()-1);
+
+ std::vector<TestData>::iterator it = ptrs.begin()+index;
+
+ ensure( "Data Overlap Persistance", it->VerifyData() );
+
+ ba.Free(it->ptr);
+ it->ptr = NULL;
+ ptrs.erase(it);
+ }
+ }
+ free(mem);
+ }
+}
diff --git a/Source/UnitTests/modloading_test.cpp b/Source/UnitTests/modloading_test.cpp
new file mode 100644
index 00000000..f492df31
--- /dev/null
+++ b/Source/UnitTests/modloading_test.cpp
@@ -0,0 +1,143 @@
+//-----------------------------------------------------------------------------
+// Name: modloading_test.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <Internal/modloading.h>
+
+#include <tut/tut.hpp>
+
+#include <cmath>
+#include <cstdlib>
+#include <set>
+
+namespace tut
+{
+ struct datamodloading //
+ {
+ int placeholder;
+ };
+
+ typedef test_group<datamodloading> tg;
+ tg test_group_m("Modloading");
+
+ typedef tg::object modloading;
+
+ template<>
+ template<>
+ void modloading::test<1>()
+ {
+ ensure("ModInstance::kValidityValid has no error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityValid).size() == 0);
+
+ ensure("ModInstance::kValidityBrokenXml has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityBrokenXml).size() == 1);
+ ensure("ModInstance::kValidityInvalidVersion has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityInvalidVersion).size() == 1);
+ ensure("ModInstance::kValidityMissingReadRights has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityMissingReadRights).size() == 1);
+ ensure("ModInstance::kValidityMissingXml has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityMissingXml).size() == 1);
+ ensure("ModInstance::kValidityUnloaded has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityUnloaded).size() == 1);
+ ensure("ModInstance::kValidityUnsupportedOvergrowthVersion has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityUnsupportedOvergrowthVersion).size() == 1);
+ ensure("ModInstance::kValidityNoDataOnDisk has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityNoDataOnDisk).size() == 1);
+ ensure("ModInstance::kValiditySteamworksError has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValiditySteamworksError).size() == 1);
+ ensure("ModInstance::kValidityNotInstalled has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityNotInstalled).size() == 1);
+ ensure("ModInstance::kValidityNotSubscribed has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityNotSubscribed).size() == 1);
+ ensure("ModInstance::kValidityIDTooLong has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityIDTooLong).size() == 1);
+ ensure("ModInstance::kValidityIDMissing has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityIDMissing).size() == 1);
+ ensure("ModInstance::kValidityIDInvalid has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityIDInvalid).size() == 1);
+ ensure("ModInstance::kValidityNameTooLong has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityNameTooLong).size() == 1);
+ ensure("ModInstance::kValidityVersionTooLong has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityVersionTooLong).size() == 1);
+ ensure("ModInstance::kValidityAuthorTooLong has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityAuthorTooLong).size() == 1);
+ ensure("ModInstance::kValidityDescriptionTooLong has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityDescriptionTooLong).size() == 1);
+ ensure("ModInstance::kValidityThumbnailMaxLength has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityThumbnailMaxLength).size() == 1);
+ ensure("ModInstance::kValidityTagsListTooLong has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityTagsListTooLong).size() == 1);
+ ensure("ModInstance::kValidityMenuItemTitleTooLong has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityMenuItemTitleTooLong).size() == 1);
+ ensure("ModInstance::kValidityMenuItemTitleMissing has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityMenuItemTitleMissing).size() == 1);
+ ensure("ModInstance::kValidityMenuItemCategoryTooLong has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityMenuItemCategoryTooLong).size() == 1);
+ ensure("ModInstance::kValidityMenuItemCategoryMissing has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityMenuItemCategoryMissing).size() == 1);
+ ensure("ModInstance::kValidityMenuItemPathTooLong has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityMenuItemPathTooLong).size() == 1);
+ ensure("ModInstance::kValidityMenuItemPathMissing has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityMenuItemPathMissing).size() == 1);
+ ensure("ModInstance::kValidityMenuItemPathInvalid has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityMenuItemPathInvalid).size() == 1);
+ ensure("ModInstance::kValidityInvalidTag has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityInvalidTag).size() == 1);
+ ensure("ModInstance::kValidityIDCollision has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityIDCollision).size() == 1);
+ ensure("ModInstance::kValidityActiveModCollision has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityActiveModCollision).size() == 1);
+ ensure("ModInstance::kValidityMissingName has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityMissingName).size() == 1);
+ ensure("ModInstance::kValidityMissingAuthor has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityMissingAuthor).size() == 1);
+ ensure("ModInstance::kValidityMissingDescription has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityMissingDescription).size() == 1);
+ ensure("ModInstance::kValidityMissingThumbnail has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityMissingThumbnail).size() == 1);
+ ensure("ModInstance::kValidityMissingThumbnailFile has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityMissingThumbnailFile).size() == 1);
+ ensure("ModInstance::kValidityMissingVersion has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityMissingVersion).size() == 1);
+ ensure("ModInstance::kValidityCampaignTitleTooLong has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityCampaignTitleTooLong).size() == 1);
+ ensure("ModInstance::kValidityCampaignTitleMissing has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityCampaignTitleMissing).size() == 1);
+ ensure("ModInstance::kValidityCampaignTypeTooLong has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityCampaignTypeTooLong).size() == 1);
+ ensure("ModInstance::kValidityCampaignTypeMissing has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityCampaignTypeMissing).size() == 1);
+
+ ensure("ModInstance::kValidityLevelTitleTooLong has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityLevelTitleTooLong).size() == 1);
+ ensure("ModInstance::kValidityLevelTitleMissing has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityLevelTitleMissing).size() == 1);
+
+ ensure("ModInstance::kValidityLevelPathTooLong has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityLevelPathTooLong).size() == 1);
+ ensure("ModInstance::kValidityLevelPathMissing has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityLevelPathMissing).size() == 1);
+ ensure("ModInstance::kValidityLevelPathInvalid has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityLevelPathInvalid).size() == 1);
+
+ ensure("ModInstance::kValidityLevelThumbnailTooLong has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityLevelThumbnailTooLong).size() == 1);
+ ensure("ModInstance::kValidityLevelThumbnailMissing has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityLevelThumbnailMissing).size() == 1);
+ ensure("ModInstance::kValidityLevelThumbnailInvalid has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityLevelThumbnailInvalid).size() == 1);
+ ensure("ModInstance::kValidityInvalidPreviewImage has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityInvalidPreviewImage).size() == 1);
+ ensure("ModInstance::kValidityInvalidSupportedVersion has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityInvalidSupportedVersion).size() == 1);
+ ensure("ModInstance::kValidityInvalidLevelHookFile has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityInvalidLevelHookFile).size() == 1);
+ ensure("ModInstance::kValidityInvalidNeedRestart has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityInvalidNeedRestart).size() == 1);
+
+ ensure("ModInstance::kValidityItemTitleTooLong has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityItemTitleTooLong).size() == 1);
+ ensure("ModInstance::kValidityItemTitleMissing has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityItemTitleMissing).size() == 1);
+
+ ensure("ModInstance::kValidityItemCategoryTooLong has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityItemCategoryTooLong).size() == 1);
+ ensure("ModInstance::kValidityItemCategoryMissing has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityItemCategoryMissing).size() == 1);
+
+ ensure("ModInstance::kValidityItemPathTooLong has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityItemPathTooLong).size() == 1);
+ ensure("ModInstance::kValidityItemPathMissing has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityItemPathMissing).size() == 1);
+ ensure("ModInstance::kValidityItemPathFileMissing has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityItemPathFileMissing).size() == 1);
+
+ ensure("ModInstance::kValidityItemThumbnailTooLong has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityItemThumbnailTooLong).size() == 1);
+ ensure("ModInstance::kValidityItemThumbnailMissing has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityItemThumbnailMissing).size() == 1);
+ ensure("ModInstance::kValidityItemThumbnailFileMissing has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityItemThumbnailFileMissing).size() == 1);
+
+ ensure("ModInstance::kValidityCategoryTooLong has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityCategoryTooLong).size() == 1);
+ ensure("ModInstance::kValidityCategoryMissing has error string", ModInstance::GenerateValidityErrorsArr(ModInstance::kValidityCategoryMissing).size() == 1);
+
+ for( unsigned i = 0; i < ModInstance::kValidityFlagCount; i++ ) {
+ ModValidity m;
+ if( i < 64 ) {
+ m = ModValidity( 0ULL, 1ULL << i );
+ } else {
+ m = ModValidity( 1ULL << (i-64), 0ULL );
+ }
+ ensure( "Ensure used flags have error string", ModInstance::GenerateValidityErrorsArr(m).size() == 1 );
+ }
+
+ for( int i = ModInstance::kValidityFlagCount; i < 128; i++ ) {
+ ModValidity m;
+ if( i < 64 ) {
+ m = ModValidity( 0ULL, 1ULL << i );
+ } else {
+ m = ModValidity( 1ULL << (i-64), 0ULL );
+ }
+ ensure( "Ensure unused flags lack error string", ModInstance::GenerateValidityErrorsArr(m).size() == 0 );
+ }
+
+ ensure( "Ensure validity 64 bit", sizeof(ModInstance::kValidityValid) == 16 );
+ }
+}
diff --git a/Source/UnitTests/string_test.cpp b/Source/UnitTests/string_test.cpp
new file mode 100644
index 00000000..147b73af
--- /dev/null
+++ b/Source/UnitTests/string_test.cpp
@@ -0,0 +1,89 @@
+//-----------------------------------------------------------------------------
+// Name: string_test.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include <Utility/strings.h>
+#include <Utility/serialize.h>
+
+#include <tut/tut.hpp>
+
+#include <cmath>
+#include <cstdlib>
+#include <set>
+
+static uint32_t hexflip( uint32_t v ) {
+ char str[9];
+ uint32_t vout;
+ flags_to_string(str,v);
+ string_flags_to_uint32(&vout, str);
+ return vout;
+}
+
+namespace tut
+{
+ struct datastrings //
+ {
+ int placeholder;
+ };
+
+ typedef test_group<datastrings> tg;
+ tg test_group_k("Strings");
+
+ typedef tg::object strings;
+
+ template<>
+ template<>
+ void strings::test<1>()
+ {
+ ensure( "ending test", endswith("my/cool/string.png", ".png") == true );
+ ensure( "ending test", endswith("my/cool/string.png", ".pnk") == false );
+
+ char buffer[6];
+ ensure( "strscpy exact match", strscpy(buffer,"false",6) == 0);
+ ensure( "strscpy source too long", strscpy(buffer,"false",5) == SOURCE_TOO_LONG);
+ ensure( "strscpy source is null", strscpy(buffer,NULL,5) == SOURCE_IS_NULL);
+ ensure( "strscpy zero length, null source", strscpy(buffer,NULL,0) == DESTINATION_IS_ZERO_LENGTH);
+
+ ensure( "saysTrue valid true", saysTrue("true") == 1 );
+ ensure( "saysTrue valid false", saysTrue("false") == 0 );
+ ensure( "saysTrue invalid", saysTrue("falseeeee") == -1 );
+ ensure( "saysTrue null", saysTrue(NULL) == -2 );
+ }
+
+ template<>
+ template<>
+ void strings::test<2>()
+ {
+ ensure( "hex code 0", hexflip(0) == 0);
+ for( uint32_t i = 1; i < 32; i++ ) {
+ ensure( "hex code powers", hexflip(1U << i) == (1U << i));
+ }
+
+ for( uint32_t i = 1; i < 0xFF; i++ ) {
+ ensure( "hex code powers", hexflip(i) == i);
+ }
+
+ ensure( "hex code radom", hexflip(0x12345678) == 0x12345678);
+ ensure( "hex code radom", hexflip(0xFEDCBA98) == 0xFEDCBA98);
+ ensure( "hex code radom", hexflip(0xDEADBEEF) == 0xDEADBEEF);
+ }
+}
diff --git a/Source/UnitTests/testmain.cpp b/Source/UnitTests/testmain.cpp
new file mode 100644
index 00000000..3d55ca65
--- /dev/null
+++ b/Source/UnitTests/testmain.cpp
@@ -0,0 +1,47 @@
+//-----------------------------------------------------------------------------
+// Name: testmain.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "testmain.h"
+
+#include <tut/tut.hpp>
+#include <tut/tut_reporter.hpp>
+
+#include <iostream>
+
+using std::exception;
+using std::cerr;
+using std::endl;
+
+namespace tut
+{
+ test_runner_singleton runner;
+}
+
+int RunUnitTests()
+{
+ tut::reporter reporter;
+ tut::runner.get().set_callback(&reporter);
+
+ tut::runner.get().run_tests();
+
+ return !reporter.all_ok();
+}
diff --git a/Source/UnitTests/testmain.h b/Source/UnitTests/testmain.h
new file mode 100644
index 00000000..1173f349
--- /dev/null
+++ b/Source/UnitTests/testmain.h
@@ -0,0 +1,25 @@
+//-----------------------------------------------------------------------------
+// Name: testmain.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+int RunUnitTests();
diff --git a/Source/UserInput/input.cpp b/Source/UserInput/input.cpp
new file mode 100644
index 00000000..b369e15d
--- /dev/null
+++ b/Source/UserInput/input.cpp
@@ -0,0 +1,1077 @@
+//-----------------------------------------------------------------------------
+// Name: testmain.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "input.h"
+
+#include <Graphics/graphics.h>
+#include <Graphics/Cursor.h>
+
+#include <UserInput/keyTranslator.h>
+#include <UserInput/keycommands.h>
+
+#include <Internal/config.h>
+#include <Internal/timer.h>
+#include <Internal/profiler.h>
+
+#include <GUI/gui.h>
+#include <Internal/file_descriptor.h>
+#include <Logging/logdata.h>
+
+#include <SDL.h>
+
+#include <cmath>
+#include <set>
+#include <cctype>
+
+
+extern Timer ui_timer;
+
+Input::JoystickStruct::JoystickStruct( )
+ : joystick( 1.0f )
+{
+
+}
+
+namespace {
+ void StringFromBind(std::string* str, const SDL_GameControllerButtonBind &bind) {
+ std::ostringstream oss;
+ switch(bind.bindType){
+ case SDL_CONTROLLER_BINDTYPE_NONE:
+ oss << "none"; break;
+ case SDL_CONTROLLER_BINDTYPE_BUTTON:
+ oss << "button" << bind.value.button + 1; break;
+ case SDL_CONTROLLER_BINDTYPE_AXIS:
+ oss << "axis" << bind.value.axis + 1; break;
+ case SDL_CONTROLLER_BINDTYPE_HAT:
+ oss << "hat" << bind.value.hat.hat << " " << bind.value.hat.hat_mask; break;
+ }
+ *str = oss.str();
+ }
+
+ void PrintControllers(const Input::JoystickMap &open_joysticks){
+ LOGI << "Current controllers:" << std::endl;
+ for(Input::JoystickMap::const_iterator iter=open_joysticks.begin(); iter!=open_joysticks.end(); ++iter){
+ const Input::RC_JoystickStruct& js = iter->second;
+ SDL_Joystick* joystick = (SDL_Joystick*)js.GetConst().sdl_joystick;
+ LOGI << "Joystick" << iter->first << "," << SDL_JoystickName(joystick) << std::endl;
+ }
+ }
+} // namespace ""
+
+Input::Input() :
+ ignore_mouse_frame(false),
+ num_players_(0),
+ quit_requested_(false),
+ in_focus_(true),
+ grab_mouse_(false),
+ invert_x_mouse_look_(false),
+ invert_y_mouse_look_(false),
+ use_raw_input(false),
+ last_controller_event_time(0.0f),
+ last_mouse_event_time(0.0f),
+ last_keyboard_event_time(0.0f),
+ joystick_sequence_id(0),
+ allow_controller_input_(true),
+ use_controller_input_(false)
+{
+ player_inputs_.resize(4);
+
+ //Default to only having one active player input.
+ player_inputs_[0].enabled = true;
+ player_inputs_[1].enabled = false;
+ player_inputs_[2].enabled = false;
+ player_inputs_[3].enabled = false;
+}
+
+void Input::SetInvertXMouseLook(bool val) {
+ invert_x_mouse_look_ = val;
+}
+
+void Input::SetInvertYMouseLook(bool val) {
+ invert_y_mouse_look_ = val;
+}
+
+void Input::UpdateGamepadLookSensitivity() {
+ for(JoystickMap::iterator iter=open_joysticks_.begin(); iter!=open_joysticks_.end(); ++iter)
+ {
+ char buffer[64];
+ sprintf(buffer, "gamepad_%i_look_sensitivity", iter->second->player_input);
+ iter->second->joystick.look_sensitivity_ = config[buffer].toNumber<float>();
+ }
+}
+
+void Input::UpdateGamepadDeadzone() {
+ for(JoystickMap::iterator iter=open_joysticks_.begin(); iter!=open_joysticks_.end(); ++iter)
+ {
+ char buffer[64];
+ sprintf(buffer, "gamepad_%i_deadzone", iter->second->player_input);
+ iter->second->joystick.deadzone = config[buffer].toNumber<float>();
+ }
+}
+
+void Input::OpenJoystick(int index){
+ //int num_joysticks = SDL_NumJoysticks();
+
+ // If joystick has a GameController definition, extract mapping
+ SDL_GameControllerButtonBind null_bind;
+ null_bind.bindType = SDL_CONTROLLER_BINDTYPE_NONE;
+ null_bind.value.axis = 0;
+ std::vector<SDL_GameControllerButtonBind> gamepad_binding;
+
+ if(SDL_IsGameController(index)){
+ const char* name = SDL_GameControllerNameForIndex(index);
+ LOGI << "Attached controller " << index << ":" << (name ? name : "Unknown Controller") << std::endl;
+ SDL_GameController* controller = SDL_GameControllerOpen(index);
+
+ if(!controller){
+ LOGW << "Could not open GameController: " << SDL_GetError() << std::endl;
+ return;
+ }
+
+ gamepad_binding.resize(ControllerInput::NUM_INPUTS, null_bind);
+
+ for(int i=0; i<SDL_CONTROLLER_AXIS_MAX; ++i){
+ SDL_GameControllerButtonBind bind = SDL_GameControllerGetBindForAxis(controller, (SDL_GameControllerAxis)i);
+ ControllerInput::Input input = SDLControllerAxisToController((SDL_GameControllerAxis)i);
+ if(input != ControllerInput::NONE) {
+ gamepad_binding[input] = bind;
+ }
+ }
+
+ for(int i=0; i<SDL_CONTROLLER_BUTTON_MAX; ++i){
+ SDL_GameControllerButtonBind bind = SDL_GameControllerGetBindForButton(controller, (SDL_GameControllerButton)i);
+ ControllerInput::Input input = SDLControllerButtonToController((SDL_GameControllerButton)i);
+ if(input != ControllerInput::NONE) {
+ gamepad_binding[input] = bind;
+ }
+ }
+
+ SDL_GameControllerClose(controller);
+ } else {
+ return; // Only handle joysticks that have a GameController entry for now
+ }
+
+ const char* name = SDL_JoystickNameForIndex(index);
+ LOGI << "Attached joystick " << index << ":" << (name ? name : "Unknown Joystick") << std::endl;
+ SDL_Joystick* joystick = SDL_JoystickOpen(index);
+
+ if(!joystick){
+ DisplayError("Error","Could not open Joystick");
+ return;
+ }
+
+ // Add configured bindings to binding internal map
+ SDL_JoystickID id = SDL_JoystickInstanceID(joystick);
+ RC_JoystickStruct& js = open_joysticks_[id];
+ js->sdl_joystick = joystick;
+ js->gamepad_bind = gamepad_binding;
+ //char buffer[64];
+ //sprintf(buffer, "gamepad%d_deadzone", id);
+ js->player_input = 0;
+ js->joystick.deadzone = config["gamepad_0_deadzone"].toNumber<float>();
+
+ /*SDL_Haptic* haptic = SDL_HapticOpenFromJoystick(joystick);
+ SDL_Haptic* haptic2 = SDL_HapticOpen(0);
+ js.sdl_haptic = haptic;
+ if(js.sdl_haptic){
+ if (SDL_HapticRumbleInit(haptic) == 0) {
+ if (SDL_HapticRumblePlay(haptic, 0.5, 2000) == 0) {
+ }
+ }
+ }*/
+}
+
+void Input::CloseJoystick(int instance_id){
+ JoystickMap::iterator iter = open_joysticks_.find(instance_id);
+ if(iter != open_joysticks_.end()){
+ RC_JoystickStruct js = iter->second;
+ SDL_Joystick* joystick = (SDL_Joystick*)js->sdl_joystick;
+ SDL_JoystickClose(joystick);
+ open_joysticks_.erase(iter);
+ }
+}
+
+static void AddGameControllerDB() {
+ // Load mappings from crowd-sourced controller database
+ // Update at:
+ // https://github.com/gabomdq/SDL_GameControllerDB/blob/master/gamecontrollerdb.txt
+ DiskFileDescriptor file;
+ std::vector<char> file_buf;
+ if(!file.Open("Data/gamecontrollerdb.txt", "r")){
+ return;
+ }
+ int file_size = file.GetSize();
+ file_buf.resize(file_size);
+
+ int bytes_read = file.ReadBytesPartial(&file_buf[0], file_size);
+ file_buf.resize(bytes_read+1);
+ file_buf.back() = '\0';
+ file.Close();
+
+ SDL_RWops* SDL_file = SDL_RWFromConstMem(&file_buf[0], file_buf.size());
+ if(SDL_file){
+ int mappings = SDL_GameControllerAddMappingsFromRW(SDL_file, 1);
+ LOGI << "Added " << mappings << " controller mappings." << std::endl;
+ }
+}
+
+void Input::Initialize() {
+ InitKeyTranslator();
+ if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) < 0) {
+ FatalError("Error", "Could not initialize SDL_INIT_GAMECONTROLLER");
+ }
+ if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0) {
+ FatalError("Error", "Could not initialize SDL_INIT_JOYSTICK");
+ }
+ // if (SDL_InitSubSystem(SDL_INIT_HAPTIC) < 0) {
+ // FatalError("Error", "Could not initialize SDL_INIT_HAPTIC");
+ // }
+ AddGameControllerDB();
+ SDL_JoystickEventState(SDL_ENABLE);
+ for (int i = 0; i < SDL_NumJoysticks(); i++) {
+ OpenJoystick(i);
+ }
+ PrintControllers(open_joysticks_);
+ ProcessBindings();
+ SetUpForXPlayers(0);
+}
+
+void Input::Dispose() {
+ for(JoystickMap::iterator iter=open_joysticks_.begin(); iter!=open_joysticks_.end(); ++iter){
+ RC_JoystickStruct js = iter->second;
+ SDL_Joystick* joystick = (SDL_Joystick*)js->sdl_joystick;
+ SDL_JoystickClose(joystick);
+ }
+
+ // SDL_QuitSubSystem(SDL_INIT_HAPTIC);
+ SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
+ SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
+}
+
+bool Input::GetGrabMouse() {
+ return grab_mouse_;
+}
+
+void UIShowCursor(bool show) {
+ if(show) {
+ SDL_ShowCursor(SDL_ENABLE);
+ } else {
+ SDL_ShowCursor(SDL_DISABLE);
+ }
+}
+
+bool UICursorVisible() {
+ int v = SDL_ShowCursor(SDL_QUERY);
+ if( v == SDL_ENABLE ) {
+ return true;
+ } else if( v == SDL_ENABLE ) {
+ return false;
+ } else {
+ LOGE << "Mouse show error" << std::endl;
+ return false;
+ }
+}
+
+void Input::SetGrabMouse(bool grab) {
+ if(grab_mouse_ == grab) return;
+ if(grab && in_focus_) {
+ grab_mouse_ = true;
+ Graphics::Instance()->SetWindowGrab(true);
+ cursor->SetVisible(false);
+ if( use_raw_input )
+ {
+ //If we further wish to do this "correctly" we can use this hint: https://wiki.libsdl.org/SDL_HINT_MOUSE_RELATIVE_MODE_WARP
+ SDL_SetRelativeMouseMode(SDL_TRUE);
+ }
+ else
+ {
+ SDL_SetRelativeMouseMode(SDL_FALSE);
+ }
+ } else {
+ grab_mouse_ = false;
+ Graphics::Instance()->SetWindowGrab(false);
+ cursor->SetVisible(true);
+ SDL_SetRelativeMouseMode(SDL_FALSE);
+ }
+}
+
+void Input::HandleEvent(const SDL_Event& event) {
+ PROFILER_ZONE(g_profiler_ctx, "Input::HandleEvent");
+ //Redirect input events to the controller
+ switch( event.type ) {
+ case SDL_WINDOWEVENT:
+ switch(event.window.event){
+ case SDL_WINDOWEVENT_FOCUS_LOST:
+ in_focus_ = false;
+ break;
+ case SDL_WINDOWEVENT_FOCUS_GAINED:
+ if(grab_mouse_) {
+ Graphics::Instance()->SetWindowGrab(true);
+ cursor->SetVisible(false);
+ }
+ in_focus_ = true;
+ break;
+ } break;
+ case SDL_MOUSEMOTION:
+ if(in_focus_){
+ last_mouse_event_time = (float) event.common.timestamp;
+ }
+ if( use_raw_input ) {
+ mouse_.delta_[0] += event.motion.xrel;
+ mouse_.delta_[1] += event.motion.yrel;
+ mouse_.pos_[0] = event.motion.x;
+ mouse_.pos_[1] = event.motion.y;
+ } else {
+ int width;
+ int height;
+ SDL_GetWindowSize(Graphics::Instance()->sdl_window_, &width, &height);
+ if(!grab_mouse_ || event.motion.x != width/2 || event.motion.y != height/2){
+ mouse_.delta_[0] += event.motion.xrel;
+ mouse_.delta_[1] += event.motion.yrel;
+ mouse_.pos_[0] = event.motion.x;
+ mouse_.pos_[1] = event.motion.y;
+ }
+ if(grab_mouse_ && in_focus_ && (event.motion.x < 100 || event.motion.y < 100 || event.motion.x > width - 100 || event.motion.y > height - 100) ) {
+ PROFILER_ZONE(g_profiler_ctx, "WarpMouse");
+ SDL_WarpMouseInWindow(Graphics::Instance()->sdl_window_, width/2, height/2);
+ }
+ }
+ break;
+ case SDL_MOUSEBUTTONDOWN:
+ if(in_focus_){
+ last_mouse_event_time = (float) event.common.timestamp;
+ }
+ if(!ignore_mouse_frame){
+ HandleMouseButtonDown(event.button.button, event.button.clicks);
+ }
+ break;
+ case SDL_MOUSEWHEEL:
+ if(in_focus_){
+ last_mouse_event_time = (float) event.common.timestamp;
+ }
+ if(!ignore_mouse_frame){
+ mouse_.MouseWheelEvent(event.wheel.x, event.wheel.y);
+ }
+ break;
+ case SDL_MOUSEBUTTONUP:{
+ if(in_focus_){
+ last_mouse_event_time = (float) event.common.timestamp;
+ }
+ if(!ignore_mouse_frame){
+ HandleMouseButtonUp(event.button.button);
+ }
+ break;}
+ case SDL_KEYDOWN: {
+ if(in_focus_){
+ last_keyboard_event_time = (float) event.common.timestamp;
+ }
+ const SDL_Keysym &keysym = event.key.keysym;
+ const SDL_Scancode &scancode = keysym.scancode;
+ const SDL_Keycode &sdl_key = keysym.sym;
+
+ if(scancode == StringToSDLScancode(bindings_["key"]["rclick"])){
+ HandleMouseButtonDown(3, 1);
+ break;
+ }
+
+ if( event.key.repeat == false ) {
+ keyboard_.handleKeyDownFirst( keysym );
+ }
+ keyboard_.handleKeyDown( keysym );
+ KeyCommand::HandleKeyDownEvent(keyboard_, keysym);
+ //printf("Keydown: %s\n", SDLKeyToString(sdl_key));
+ break;}
+ case SDL_KEYUP: {
+ if(in_focus_){
+ last_keyboard_event_time = (float) event.common.timestamp;
+ }
+ const SDL_Keysym &keysym = event.key.keysym;
+ const SDL_Scancode &scancode = keysym.scancode;
+ const SDL_Keycode &sdl_key = keysym.sym;
+ if(scancode == StringToSDLScancode(bindings_["key"]["rclick"])){
+ HandleMouseButtonUp(3);
+ break;
+ }
+ keyboard_.handleKeyUp( keysym );
+ break;}
+ case SDL_JOYAXISMOTION:{
+ if(in_focus_){
+ last_controller_event_time = (float) event.common.timestamp;
+ }
+ SDL_JoyAxisEvent *sdl_joy = (SDL_JoyAxisEvent *) &event.jaxis;
+ JoystickMap::iterator iter = open_joysticks_.find(sdl_joy->which);
+ if(iter != open_joysticks_.end()){
+ const std::vector<SDL_GameControllerButtonBind>& gamepad_bind = iter->second->gamepad_bind;
+ // TODO: Replace for-loop with constant-time lookup?
+ for(size_t i = 0; i < gamepad_bind.size(); ++i) {
+ if(gamepad_bind[i].bindType == SDL_CONTROLLER_BINDTYPE_AXIS && gamepad_bind[i].value.axis == sdl_joy->axis) {
+ ControllerInput::Input input = (ControllerInput::Input)i;
+ ControllerInput::Input opposite_input = ControllerInput::NONE;
+ switch(input) {
+ case ControllerInput::L_STICK_X:
+ input = (sdl_joy->value >= 0.0f ? ControllerInput::L_STICK_XP : ControllerInput::L_STICK_XN);
+ opposite_input = (sdl_joy->value < 0.0f ? ControllerInput::L_STICK_XP : ControllerInput::L_STICK_XN);
+ break;
+ case ControllerInput::L_STICK_Y:
+ input = (sdl_joy->value >= 0.0f ? ControllerInput::L_STICK_YP : ControllerInput::L_STICK_YN);
+ opposite_input = (sdl_joy->value < 0.0f ? ControllerInput::L_STICK_YP : ControllerInput::L_STICK_YN);
+ break;
+ case ControllerInput::R_STICK_X:
+ input = (sdl_joy->value >= 0.0f ? ControllerInput::R_STICK_XP : ControllerInput::R_STICK_XN);
+ opposite_input = (sdl_joy->value < 0.0f ? ControllerInput::R_STICK_XP : ControllerInput::R_STICK_XN);
+ break;
+ case ControllerInput::R_STICK_Y:
+ input = (sdl_joy->value >= 0.0f ? ControllerInput::R_STICK_YP : ControllerInput::R_STICK_YN);
+ opposite_input = (sdl_joy->value < 0.0f ? ControllerInput::R_STICK_YP : ControllerInput::R_STICK_YN);
+ break;
+ case ControllerInput::L_TRIGGER:
+ case ControllerInput::R_TRIGGER:
+ // Remap triggers from {-X, X} to { 0, X }
+ sdl_joy->value = ((sdl_joy->value + 32767) / 2);
+ break;
+ default:
+ break;
+ }
+ joystick_sequence_id += iter->second->joystick.HandleInputChange(input, sdl_joy->value, joystick_sequence_id);
+ if(opposite_input != ControllerInput::NONE) {
+ joystick_sequence_id += iter->second->joystick.HandleInputChange(opposite_input, 0.0f, joystick_sequence_id);
+ }
+ break;
+ }
+ }
+ }
+ break;}
+ case SDL_JOYBUTTONDOWN:
+ case SDL_JOYBUTTONUP:{
+ if(in_focus_){
+ last_controller_event_time = (float) event.common.timestamp;
+ }
+ SDL_JoyButtonEvent *button = (SDL_JoyButtonEvent *) &event.jbutton;
+ JoystickMap::iterator iter = open_joysticks_.find(button->which);
+ if(iter != open_joysticks_.end()){
+ // TODO: Replace for-loop with constant-time lookup?
+ const std::vector<SDL_GameControllerButtonBind>& gamepad_bind = iter->second->gamepad_bind;
+ for(size_t i = 0; i < gamepad_bind.size(); ++i) {
+ if(gamepad_bind[i].bindType == SDL_CONTROLLER_BINDTYPE_BUTTON && gamepad_bind[i].value.button == button->button){
+ joystick_sequence_id += iter->second->joystick.HandleInputChange((ControllerInput::Input)i, (button->state == SDL_PRESSED), joystick_sequence_id);
+ break;
+ }
+ }
+ }
+ break;}
+ case SDL_JOYHATMOTION:{
+ if(in_focus_){
+ last_controller_event_time = (float) event.common.timestamp;
+ }
+ SDL_JoyHatEvent *hat = (SDL_JoyHatEvent*)&event.jhat;
+ JoystickMap::iterator iter = open_joysticks_.find(hat->which);
+ if(iter != open_joysticks_.end()){
+ // TODO: Replace for-loop with constant-time lookup?
+ const std::vector<SDL_GameControllerButtonBind>& gamepad_bind = iter->second->gamepad_bind;
+ for(size_t i = 0; i < gamepad_bind.size(); ++i) {
+ if(gamepad_bind[i].bindType == SDL_CONTROLLER_BINDTYPE_HAT && gamepad_bind[i].value.hat.hat == hat->hat){
+ switch(hat->value) {
+ case SDL_HAT_LEFTUP:
+ if(gamepad_bind[i].value.hat.hat_mask == SDL_HAT_LEFT ||
+ gamepad_bind[i].value.hat.hat_mask == SDL_HAT_UP) {
+ joystick_sequence_id += iter->second->joystick.HandleInputChange((ControllerInput::Input)i, 1.0f, joystick_sequence_id);
+ } else {
+ joystick_sequence_id += iter->second->joystick.HandleInputChange((ControllerInput::Input)i, 0.0f, joystick_sequence_id);
+ }
+ break;
+ case SDL_HAT_RIGHTUP:
+ if(gamepad_bind[i].value.hat.hat_mask == SDL_HAT_RIGHT ||
+ gamepad_bind[i].value.hat.hat_mask == SDL_HAT_UP) {
+ joystick_sequence_id += iter->second->joystick.HandleInputChange((ControllerInput::Input)i, 1.0f, joystick_sequence_id);
+ } else {
+ joystick_sequence_id += iter->second->joystick.HandleInputChange((ControllerInput::Input)i, 0.0f, joystick_sequence_id);
+ }
+ break;
+ case SDL_HAT_LEFTDOWN:
+ if(gamepad_bind[i].value.hat.hat_mask == SDL_HAT_LEFT ||
+ gamepad_bind[i].value.hat.hat_mask == SDL_HAT_DOWN) {
+ joystick_sequence_id += iter->second->joystick.HandleInputChange((ControllerInput::Input)i, 1.0f, joystick_sequence_id);
+ } else {
+ joystick_sequence_id += iter->second->joystick.HandleInputChange((ControllerInput::Input)i, 0.0f, joystick_sequence_id);
+ }
+ break;
+ case SDL_HAT_RIGHTDOWN:
+ if(gamepad_bind[i].value.hat.hat_mask == SDL_HAT_RIGHT ||
+ gamepad_bind[i].value.hat.hat_mask == SDL_HAT_DOWN) {
+ joystick_sequence_id += iter->second->joystick.HandleInputChange((ControllerInput::Input)i, 1.0f, joystick_sequence_id);
+ } else {
+ joystick_sequence_id += iter->second->joystick.HandleInputChange((ControllerInput::Input)i, 0.0f, joystick_sequence_id);
+ }
+ break;
+ default:
+ if(gamepad_bind[i].value.hat.hat_mask == hat->value) {
+ joystick_sequence_id += iter->second->joystick.HandleInputChange((ControllerInput::Input)i, 1.0f, joystick_sequence_id);
+ } else {
+ joystick_sequence_id += iter->second->joystick.HandleInputChange((ControllerInput::Input)i, 0.0f, joystick_sequence_id);
+ }
+ break;
+ }
+ }
+ }
+ }
+ break;}
+ case SDL_JOYDEVICEADDED:
+ LOGI << "Added device:" << event.jdevice.which << std::endl;
+ OpenJoystick(event.jdevice.which);
+ PrintControllers(open_joysticks_);
+ SetUpForXPlayers(num_players_);
+ ProcessBindings();
+ break;
+ case SDL_JOYDEVICEREMOVED:
+ LOGI << "Removed device:" << event.jdevice.which << std::endl;
+ CloseJoystick(event.jdevice.which);
+ PrintControllers(open_joysticks_);
+ SetUpForXPlayers(num_players_);
+ break;
+ }
+}
+
+namespace {
+ void PlayerInputKeyDown(KeyState* keyState) {
+ if(keyState->count <= 0){
+ keyState->count *= -1;
+ keyState->count++;
+ keyState->depth_count++;
+ keyState->depth = 1.0f;
+ }
+ }
+} // namespace ""
+
+void Input::ProcessController(int controller_id, float timestep) {
+ PlayerInput &control = player_inputs_[controller_id];
+
+ //Early out if we're remote controlled via network.
+ if(control.remote_controlled) {
+ return;
+ }
+
+ //Early out if we're disabled.
+ if(control.enabled == false) {
+ return;
+ }
+
+ // Set all keydown counts to negative
+ PlayerInput::KeyDownMap &kd = control.key_down;
+ for(PlayerInput::KeyDownMap::iterator iter = kd.begin(); iter != kd.end(); ++iter){
+ iter->second.count *= -1;
+ iter->second.depth = 0.0f;
+ }
+ std::set<std::string> active_buttons;
+ // If any bound controller button is pressed more than halfway in
+ // (disregarding sensitivity settings), controller input is accepted until
+ // any mouse/keyboard bind is pressed.
+ // This is to avoid misconfigured deadzone settings to spook around
+ if(!use_controller_input_) {
+ bool run = true;
+ for(JoystickMap::iterator iter = open_joysticks_.begin(); run && iter != open_joysticks_.end(); ++iter){
+ const RC_JoystickStruct js = iter->second;
+ if(js.GetConst().player_input != controller_id){
+ continue;
+ }
+ const Joystick& joystick = js.GetConst().joystick;
+ const Joystick::ButtonMap& bm = joystick.buttons_down_;
+ for(Joystick::ButtonMap::const_iterator iter2 = bm.begin(); iter2 != bm.end(); ++iter2){
+ if(iter2->second){
+ float depth = iter2->second;
+ if(depth > 0.5f) {
+ use_controller_input_ = true;
+ run = false;
+ break;
+ }
+ }
+ }
+ }
+ }
+ // Joystick input
+ if(allow_controller_input_ && use_controller_input_) {
+ for(JoystickMap::iterator iter = open_joysticks_.begin(); iter != open_joysticks_.end(); ++iter){
+ const RC_JoystickStruct js = iter->second;
+ if(js.GetConst().player_input != controller_id){
+ continue;
+ }
+ const Joystick& joystick = js.GetConst().joystick;
+ const Joystick::ButtonMap& bm = joystick.buttons_down_;
+ for(Joystick::ButtonMap::const_iterator iter2 = bm.begin(); iter2 != bm.end(); ++iter2){
+ if(iter2->second){
+ float sensitivity = 1.0f;
+ if(memcmp(iter2->first.c_str(), "look", 4) == 0) {
+ sensitivity = joystick.look_sensitivity_;
+ }
+ float depth = iter2->second * sensitivity;
+
+ int &count = control.key_down[iter2->first].count;
+ int &depth_count = control.key_down[iter2->first].depth_count;
+ if(count <= 0){
+ count *= -1;
+ ++count;
+ if(depth > KeyState::kDepthThreshold)
+ ++depth_count;
+ }
+ control.key_down[iter2->first].depth = depth;
+ }
+ }
+ }
+ }
+ // Keyboard input, only bound to player one if in split screen.
+ bool catch_kbmouse = (controller_id == 0);
+ if(catch_kbmouse){
+ static const std::string kKey = "key";
+ const StrMap &key_map = bindings_[kKey];
+ for(StrMap::const_iterator iter = key_map.begin(); iter != key_map.end(); ++iter) {
+ if(IsKeyDown(iter->second.c_str())) {
+ PlayerInputKeyDown(&control.key_down[iter->first]);
+ use_controller_input_ = false;
+ }
+ }
+ keyboard_.Update(timestep);
+
+ if (in_focus_) {
+ control.key_down["look_right"].depth += mouse_.delta_[0] * mouse_sensitivity_ * (invert_x_mouse_look_?-1.0f:1.0f);
+ control.key_down["look_down"].depth += mouse_.delta_[1] * mouse_sensitivity_ * (invert_y_mouse_look_?-1.0f:1.0f);
+ mouse_.delta_[0]=0;
+ mouse_.delta_[1]=0;
+ }
+ }
+ // Zero all negative keydown counts
+ for(PlayerInput::KeyDownMap::iterator iter = kd.begin(); iter != kd.end(); ++iter){
+ if(iter->second.count < 0){
+ iter->second.count = 0;
+ iter->second.depth_count = 0;
+ iter->second.depth = 0.0f;
+ }
+ }
+}
+
+Keyboard &Input::getKeyboard() {
+ return keyboard_;
+}
+
+Mouse &Input::getMouse() {
+ return mouse_;
+}
+
+void Input::UseRawInput(bool val )
+{
+ use_raw_input = val;
+}
+
+void Input::StartTextInput()
+{
+ SDL_StartTextInput();
+}
+
+void Input::StopTextInput()
+{
+ SDL_StopTextInput();
+}
+
+std::string Input::GetStringDescriptionForBinding( const std::string& type, const std::string& name ) {
+ StrMap& keyboard_map = bindings_[type];
+ StrMap::iterator keyit = keyboard_map.find(name) ;
+
+ if( keyit != keyboard_map.end() ) {
+ return StringFromInput(keyit->second.c_str());
+ } else {
+ return name;
+ }
+}
+
+std::vector<std::string> Input::GetAvailableBindingCategories() {
+ std::vector<std::string> categs;
+ BindMap::iterator bindit = bindings_.begin();
+ while( bindit != bindings_.end() ) {
+ categs.push_back(bindit->first);
+
+ bindit++;
+ }
+ return categs;
+}
+
+
+std::vector<std::string> Input::GetAvailableBindings(const std::string& binding_category) {
+ std::vector<std::string> binds;
+ StrMap bindings = bindings_[binding_category];
+ StrMap::iterator bindit = bindings.begin();
+ while( bindit != bindings.end() ) {
+ binds.push_back(bindit->first);
+
+ bindit++;
+ }
+ return binds;
+}
+
+std::set<std::string> Input::GetAllAvailableBindings() {
+ std::set<std::string> binds;
+
+ for(auto binding_cat : bindings_) {
+ for(auto binding : binding_cat.second) {
+ binds.insert(binding.first);
+ }
+ }
+
+ return binds;
+}
+
+std::string Input::GetBindingValue( const std::string& binding_category, const std::string& binding ) {
+ // If the category is "gamepad_[0-9][...]", default to "gamepad_[...] if the bind doesn't exist
+ if(binding_category.size() > 10 && memcmp(binding_category.c_str(), "gamepad_", 8) == 0 && isdigit(binding_category.c_str()[8]) && binding_category.c_str()[9] == '[') {
+ BindMap::iterator iter = bindings_.find(binding_category);
+ if(iter != bindings_.end()) {
+ return iter->second[binding];
+ }
+ return bindings_["gamepad"][binding];
+ } else {
+ return bindings_[binding_category][binding];
+ }
+}
+
+void Input::SetBindingValue( std::string binding_category, std::string binding, std::string value ) {
+ config.GetRef(binding_category + "[" + binding + "]") = value;
+ config.ReloadStaticSettings();
+}
+
+void Input::SetKeyboardBindingValue( std::string binding_category, std::string binding, SDL_Scancode value ) {
+ SetBindingValue(binding_category, binding, std::string(SDLScancodeToString(value)));
+}
+
+void Input::SetMouseBindingValue( std::string binding_category, std::string binding, uint32_t button ) {
+ char buffer[32];
+ sprintf(buffer, "mouse%d", button);
+ SetBindingValue(binding_category, binding, buffer);
+}
+
+void Input::SetMouseBindingValue( std::string binding_category, std::string binding, std::string text ) {
+ SetBindingValue(binding_category, binding, text.c_str());
+}
+
+void Input::SetControllerBindingValue( std::string binding_category, std::string binding, ControllerInput::Input input ) {
+ SetBindingValue(binding_category, binding, StringFromControllerInput(input));
+}
+
+void Input::HandleMouseButtonDown( int sdl_button_index, int clicks ) {
+ mouse_.MouseDownEvent(sdl_button_index);
+}
+
+void Input::HandleMouseButtonUp( int sdl_button_index ) {
+ mouse_.MouseUpEvent(sdl_button_index);
+}
+
+PlayerInput* Input::GetController( int id ) {
+ return id >= 0 && id < player_inputs_.size() ? &player_inputs_[id] : NULL;
+}
+
+void Input::ProcessBindings() {
+ for(JoystickMap::iterator js_iter = open_joysticks_.begin(); js_iter != open_joysticks_.end(); ++js_iter){
+ RC_JoystickStruct js = js_iter->second;
+ js->joystick.ClearBinding();
+ char gamepad_name[32];
+ FormatString(gamepad_name, 32, "gamepad_%i", js->player_input);
+ if(!js->gamepad_bind.empty()){
+ StrMap& gamepad_map = bindings_[gamepad_name];
+ for(StrMap::iterator xb_iter = gamepad_map.begin(); xb_iter != gamepad_map.end(); ++xb_iter){
+ const std::string& input_str = xb_iter->second;
+ ControllerInput::Input input = SDLStringToController(input_str.c_str());
+ if(input != ControllerInput::NONE){
+ SDL_GameControllerButtonBind bind;
+ // Input comes as an internal value, so map it to a physical
+ // (SDL) input, but send the internal input to ProcessBinding
+ switch(input) {
+ case ControllerInput::L_STICK_XN:
+ case ControllerInput::L_STICK_XP:
+ bind = js->gamepad_bind[ControllerInput::L_STICK_X];
+ break;
+ case ControllerInput::L_STICK_YN:
+ case ControllerInput::L_STICK_YP:
+ bind = js->gamepad_bind[ControllerInput::L_STICK_Y];
+ break;
+ case ControllerInput::R_STICK_XN:
+ case ControllerInput::R_STICK_XP:
+ bind = js->gamepad_bind[ControllerInput::R_STICK_X];
+ break;
+ case ControllerInput::R_STICK_YN:
+ case ControllerInput::R_STICK_YP:
+ bind = js->gamepad_bind[ControllerInput::R_STICK_Y];
+ break;
+ default:
+ bind = js->gamepad_bind[input];
+ break;
+ }
+ if(bind.bindType != SDL_CONTROLLER_BINDTYPE_NONE){
+ js->joystick.ProcessBinding(input, xb_iter->first);
+ }
+ }
+ }
+ } else {
+ // TODO: Generic controller support
+ /*const StrMap &map = bindings_["controller"];
+ for( StrMap::const_iterator iter = map.begin(); iter != map.end(); ++iter ) {
+ js->joystick.ProcessBinding(iter->second, iter->first);
+ }*/
+ }
+ }
+}
+
+void Input::ProcessControllers(float timestep) {
+ PROFILER_ZONE(g_profiler_ctx, "ProcessControllers");
+ for(unsigned i=0; i<player_inputs_.size(); ++i){
+ ProcessController(i, timestep);
+ }
+}
+
+/*
+ * Poll all KeyboardListeners, asking if they want to the exclusive input this frame.
+ */
+void Input::UpdateKeyboardFocus() {
+ uint32_t mask = 0U;
+ if( mask == 0U ) {
+ mask = KIMF_ANY;
+ }
+ keyboard_.SetMode(mask);
+}
+
+void Input::SetUpForXPlayers( unsigned num_players ) {
+ if(num_players_ != num_players) {
+ num_players_ = num_players;
+ //Setup the enabled number of local controllers to match local player count.
+ for(unsigned i = 0; i < 4; i++) {
+ player_inputs_[i].enabled = (i < num_players);
+ }
+
+ if(num_players <= 1){
+ for(JoystickMap::iterator iter = open_joysticks_.begin(); iter != open_joysticks_.end(); ++iter){
+ RC_JoystickStruct js = iter->second;
+ js->player_input = 0;
+ std::map<std::string, float>::iterator buttons_iter = js->joystick.buttons_down_.begin();
+ for(; buttons_iter != js->joystick.buttons_down_.end(); ++buttons_iter) {
+ buttons_iter->second = 0.0f;
+ }
+ }
+ }
+ int num_joysticks = open_joysticks_.size();
+ if(num_players >= 2){
+ if(num_joysticks < (int)num_players){
+ int index = 1;
+ for(JoystickMap::iterator iter = open_joysticks_.begin(); iter != open_joysticks_.end(); ++iter){
+ RC_JoystickStruct js = iter->second;
+ js->player_input = index;
+ ++index;
+ std::map<std::string, float>::iterator buttons_iter = js->joystick.buttons_down_.begin();
+ for(; buttons_iter != js->joystick.buttons_down_.end(); ++buttons_iter) {
+ buttons_iter->second = 0.0f;
+ }
+ }
+ } else {
+ int index = 0;
+ for(JoystickMap::iterator iter = open_joysticks_.begin(); iter != open_joysticks_.end(); ++iter){
+ RC_JoystickStruct js = iter->second;
+ js->player_input = index;
+ ++index;
+ std::map<std::string, float>::iterator buttons_iter = js->joystick.buttons_down_.begin();
+ for(; buttons_iter != js->joystick.buttons_down_.end(); ++buttons_iter) {
+ buttons_iter->second = 0.0f;
+ }
+ }
+ }
+ }
+ ProcessBindings();
+ }
+}
+
+void Input::RequestQuit() {
+ quit_requested_ = true;
+}
+
+bool Input::WasQuitRequested() {
+ return quit_requested_;
+}
+
+void CheckBinding(const Config::Map::const_iterator &iter, const std::string &type, BindMap &bind_map) {
+ std::string find_str = type+"[";
+ size_t start = iter->first.find(find_str, 0);
+ if(start != std::string::npos){
+ size_t end = iter->first.find("]", 0);
+ start += find_str.size();
+ std::string label = iter->first.substr(start, end-start);
+ std::string binding = iter->second.data.str();
+ bind_map[type][label] = binding;
+ }
+}
+
+static void CompleteGamepadBindings(const std::string &type, BindMap &bind_map) {
+ StrMap& gamepad = bind_map[type];
+ for(StrMap::iterator iter = bind_map["gamepad"].begin(), end = bind_map["gamepad"].end(); iter != end; ++iter) {
+ StrMap::iterator gamepad_iter = gamepad.find(iter->first);
+ if(gamepad_iter == gamepad.end()) {
+ gamepad[iter->first] = iter->second;
+ }
+ }
+ char buffer[128];
+ FormatString(buffer, 128, "%s_deadzone", type.c_str());
+ if(!config.HasKey(buffer)) {
+ config.GetRef(std::string(buffer)) = config["gamepad_deadzone"];
+ }
+ FormatString(buffer, 128, "%s_look_sensitivity", type.c_str());
+ if(!config.HasKey(buffer)) {
+ config.GetRef(std::string(buffer)) = config["gamepad_look_sensitivity"];
+ }
+}
+
+
+void Input::SetFromConfig( const Config &config ) {
+ bindings_.erase("gamepad_0");
+ bindings_.erase("gamepad_1");
+ bindings_.erase("gamepad_2");
+ bindings_.erase("gamepad_3");
+ for(Config::Map::const_iterator iter = config.map_.begin(); iter != config.map_.end(); ++iter){
+ CheckBinding(iter, "key", bindings_);
+ CheckBinding(iter, "gamepad", bindings_);
+ CheckBinding(iter, "gamepad_0", bindings_);
+ CheckBinding(iter, "gamepad_1", bindings_);
+ CheckBinding(iter, "gamepad_2", bindings_);
+ CheckBinding(iter, "gamepad_3", bindings_);
+ CheckBinding(iter, "controller", bindings_);
+ CheckBinding(iter, "bind", bindings_);
+ CheckBinding(iter, "bind_win", bindings_);
+ CheckBinding(iter, "bind_unix", bindings_);
+ }
+ CompleteGamepadBindings("gamepad_0", bindings_);
+ CompleteGamepadBindings("gamepad_1", bindings_);
+ CompleteGamepadBindings("gamepad_2", bindings_);
+ CompleteGamepadBindings("gamepad_3", bindings_);
+ use_raw_input = config["use_raw_input"].toNumber<bool>();
+ // Process hotkey bindings
+ StrMap &binds = bindings_["bind"];
+ for(StrMap::const_iterator iter = binds.begin(); iter != binds.end(); ++iter){
+ KeyCommand::BindString((iter->second+": "+iter->first).c_str());
+ }
+ #ifdef WIN32
+ StrMap &platform_binds = bindings_["bind_win"];
+ #else
+ StrMap &platform_binds = bindings_["bind_unix"];
+ #endif
+ for(StrMap::const_iterator iter = platform_binds.begin(); iter != platform_binds.end(); ++iter){
+ KeyCommand::BindString((iter->second+": "+iter->first).c_str());
+ }
+ KeyCommand::FinalizeBindings();
+ mouse_sensitivity_ = config["mouse_sensitivity"].toNumber<float>();
+ UpdateGamepadLookSensitivity();
+ UpdateGamepadDeadzone();
+ SetInvertXMouseLook(config["invert_x_mouse_look"].toNumber<bool>());
+ SetInvertYMouseLook(config["invert_y_mouse_look"].toNumber<bool>());
+ debug_keys = config["debug_keys"].toNumber<bool>();
+ if(!open_joysticks_.empty()){
+ ProcessBindings();
+ }
+
+}
+
+void Input::ClearQuitRequested() {
+ quit_requested_ = false;
+}
+
+int Input::PlayerOpenedMenu() {
+ for(size_t i = 0; i < player_inputs_.size(); ++i) {
+ if(player_inputs_[i].key_down["quit"].count == 1) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+void Input::AllowControllerInput(bool allow) {
+ allow_controller_input_ = allow;
+}
+
+int Input::AllocateRemotePlayerInput() {
+ int index = player_inputs_.size();
+ player_inputs_.push_back(PlayerInput());
+ player_inputs_[index].remote_controlled = true;
+ player_inputs_[index].enabled = true;
+ return index;
+}
+
+bool Input::IsKeyDown(const char* name) {
+ if(strlen(name) <= 5 || memcmp(name, "mouse", 5) != 0) {
+ return keyboard_.isScancodeDown(StringToSDLScancode(name), KIMF_PLAYING);
+ } else if(strcmp(name, "mousescrollup") == 0) {
+ return mouse_.wheel_delta_y_ > 0;
+ } else if(strcmp(name, "mousescrolldown") == 0) {
+ return mouse_.wheel_delta_y_ < 0;
+ } else if(strcmp(name, "mousescrollleft") == 0) {
+ return mouse_.wheel_delta_x_ < 0;
+ } else if(strcmp(name, "mousescrollright") == 0) {
+ return mouse_.wheel_delta_x_ > 0;
+ } else {
+ int button = atoi(name + 5);
+ if(button >= 0 && button < Mouse::MouseButton::NUM_BUTTONS)
+ return mouse_.mouse_down_[button] == Mouse::ClickState::HELD;
+ else {
+ LOGW << "Unknown mouse button \"" << name << "\"" << std::endl;
+ return false;
+ }
+ }
+}
+
+bool Input::IsControllerConnected() {
+ return !open_joysticks_.empty();
+}
+
+Input::JoystickMap& Input::GetOpenJoysticks() {
+ return this->open_joysticks_;
+}
+
+std::vector<Keyboard::KeyboardPress> Input::GetKeyboardInputs() {
+ return keyboard_.GetKeyboardInputs();
+}
+
+std::vector<Mouse::MousePress> Input::GetMouseInputs() {
+ return mouse_.GetMouseInputs();
+}
+
+std::vector<Joystick::JoystickPress> Input::GetJoystickInputs(int player_index) {
+ std::vector<Joystick::JoystickPress> ret_inputs;
+ for(JoystickMap::iterator iter = open_joysticks_.begin(); iter != open_joysticks_.end(); ++iter) {
+ if(iter->second->player_input == player_index) {
+ std::vector<Joystick::JoystickPress> inputs = iter->second->joystick.GetJoystickInputs();
+ ret_inputs.reserve(ret_inputs.size() + inputs.size());
+ for(size_t i = 0; i < inputs.size(); ++i) {
+ ret_inputs.push_back(inputs[i]);
+ }
+ }
+ }
+ // Some insertion sort variant
+ for(int i = 1; i < (int)ret_inputs.size(); ++i) {
+ Joystick::JoystickPress curr = ret_inputs[i];
+ int j = i - 1;
+ for(; j >= 0 && ret_inputs[j].s_id > curr.s_id; --j) {
+ ret_inputs[j + 1] = ret_inputs[j];
+ }
+ ret_inputs[j + 1] = curr;
+ }
+ return ret_inputs;
+}
diff --git a/Source/UserInput/input.h b/Source/UserInput/input.h
new file mode 100644
index 00000000..62bee315
--- /dev/null
+++ b/Source/UserInput/input.h
@@ -0,0 +1,163 @@
+//-----------------------------------------------------------------------------
+// Name: input.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <UserInput/keyboard.h>
+#include <UserInput/mouse.h>
+#include <UserInput/joystick.h>
+
+#include <Editors/editor_utilities.h>
+#include <Internal/referencecounter.h>
+
+#include <SDL_joystick.h>
+#include <SDL_gamecontroller.h>
+#include <SDL_haptic.h>
+#include <SDL_events.h>
+
+#include <vector>
+#include <map>
+#include <set>
+
+class SceneGraph;
+class Config;
+struct SDL_Keysym;
+class GameCursor;
+
+typedef std::map<std::string, std::string> StrMap;
+typedef std::map<std::string, StrMap> BindMap; // Key binding map, e.g. bindings["gamepad"]["jump"] = "rightshoulder"
+
+class Input {
+public:
+ Input();
+ void Initialize();
+ void InitializeStringDescriptionMap();
+ void Dispose();
+ // take a control element and fill it based on the values we have.
+ void ProcessController(int controller_id, float timestep);
+ void UpdateKeyboardFocus();
+ Keyboard &getKeyboard();
+ Mouse &getMouse();
+ bool GetGrabMouse();
+ void SetGrabMouse(bool grab);
+ void HandleEvent(const SDL_Event& event);
+ void RequestQuit();
+ bool WasQuitRequested();
+ void SetInvertXMouseLook(bool val);
+ void SetInvertYMouseLook(bool val);
+
+ PlayerInput* GetController( int id );
+ void ProcessControllers(float timestep);
+ void SetUpForXPlayers( unsigned num_players );
+ void SetFromConfig( const Config &config );
+ float mouse_sensitivity() {return mouse_sensitivity_;}
+ void SetMouseSensitivity(float val) {mouse_sensitivity_ = val;}
+ void UpdateGamepadLookSensitivity();
+ void UpdateGamepadDeadzone();
+ void ClearQuitRequested();
+ int PlayerOpenedMenu();
+ void AllowControllerInput(bool allow);
+
+ int AllocateRemotePlayerInput();
+
+ bool IsKeyDown(const char* name);
+
+ bool IsControllerConnected();
+
+ bool live_updated;
+ bool debug_keys;
+
+ float last_controller_event_time;
+ float last_mouse_event_time;
+ float last_keyboard_event_time;
+
+ bool ignore_mouse_frame;
+ GameCursor* cursor;
+
+ static Input* Instance() {
+ static Input instance;
+ return &instance;
+ }
+
+ StrMap key_string_description_map;
+ struct JoystickStruct {
+ JoystickStruct( );
+ SDL_Joystick *sdl_joystick;
+ SDL_Haptic *sdl_haptic;
+ std::vector<SDL_GameControllerButtonBind> gamepad_bind;
+ Joystick joystick;
+ int player_input;
+ };
+ typedef ReferenceCounter<JoystickStruct> RC_JoystickStruct;
+ typedef std::map<int, RC_JoystickStruct> JoystickMap;
+
+ void UseRawInput( bool val );
+
+ void StartTextInput();
+ void StopTextInput();
+
+ std::string GetStringDescriptionForBinding( const std::string& type, const std::string& name );
+
+ std::vector<std::string> GetAvailableBindingCategories();
+ std::vector<std::string> GetAvailableBindings(const std::string& binding_category);
+ std::set<std::string> GetAllAvailableBindings();
+ std::string GetBindingValue(const std::string& binding_category, const std::string& binding);
+ void SetBindingValue( std::string binding_category, std::string binding, std::string value );
+ void SetKeyboardBindingValue( std::string binding_category, std::string binding, SDL_Scancode value );
+ void SetMouseBindingValue( std::string binding_category, std::string binding, uint32_t button );
+ void SetMouseBindingValue( std::string binding_category, std::string binding, std::string value );
+ void SetControllerBindingValue( std::string binding_category, std::string binding, ControllerInput::Input input);
+
+ JoystickMap& GetOpenJoysticks();
+
+ std::vector<Keyboard::KeyboardPress> GetKeyboardInputs();
+ std::vector<Mouse::MousePress> GetMouseInputs();
+ std::vector<Joystick::JoystickPress> GetJoystickInputs(int player_index);
+private:
+ unsigned num_players_;
+ BindMap bindings_;
+ std::vector<PlayerInput> player_inputs_;
+ bool quit_requested_;
+ JoystickMap open_joysticks_;
+ Keyboard keyboard_;
+ Mouse mouse_;
+
+ bool allow_controller_input_;
+ bool use_controller_input_;
+
+ bool in_focus_;
+ bool grab_mouse_;
+ bool invert_x_mouse_look_;
+ bool invert_y_mouse_look_;
+ float mouse_sensitivity_;
+
+ bool use_raw_input;
+ uint32_t joystick_sequence_id;
+
+ void HandleMouseButtonDown( int the_button, int clicks );
+ void HandleMouseButtonUp( int the_button );
+ void ProcessBindings();
+ void OpenJoystick(int which);
+ void CloseJoystick(int instance_id);
+};
+
+void UIShowCursor(bool show);
diff --git a/Source/UserInput/joystick.cpp b/Source/UserInput/joystick.cpp
new file mode 100644
index 00000000..b6815b4f
--- /dev/null
+++ b/Source/UserInput/joystick.cpp
@@ -0,0 +1,163 @@
+//-----------------------------------------------------------------------------
+// Name: joystick.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "joystick.h"
+
+#include <Logging/logdata.h>
+
+#include <sstream>
+#include <map>
+#include <algorithm>
+#include <cstdlib>
+#include <cmath>
+
+Joystick::Joystick(float look_sensitivity) :
+ look_sensitivity_(look_sensitivity),
+ deadzone(0.1f),
+ button_input_buffer_count(0)
+{
+}
+
+bool Joystick::HandleInputChange( ControllerInput::Input input, float val, uint32_t sequence_id ) {
+ float depth = val;
+ std::ostringstream oss;
+ switch(input) {
+ case ControllerInput::L_STICK_XN:
+ case ControllerInput::L_STICK_XP:
+ case ControllerInput::L_STICK_YN:
+ case ControllerInput::L_STICK_YP:
+ case ControllerInput::R_STICK_XN:
+ case ControllerInput::R_STICK_XP:
+ case ControllerInput::R_STICK_YN:
+ case ControllerInput::R_STICK_YP:
+ case ControllerInput::L_TRIGGER:
+ case ControllerInput::R_TRIGGER:
+ depth = NormalizeJoystick(std::fabs(val));
+ break;
+ default:
+ break;
+ }
+
+ bool ret_val = false;
+ if(depth > 0.0f) {
+ AddInputBufferItem(input, depth, sequence_id);
+ ret_val = true;
+ }
+
+ std::pair<BindingMap::iterator, BindingMap::iterator> iter_pair = binding_.equal_range(input);
+ if(iter_pair.first != binding_.end()) {
+ for(BindingMap::iterator iter = iter_pair.first; iter != iter_pair.second; ++iter) {
+ SetButtonDown(iter->second, depth);
+ }
+ }
+
+ return ret_val;
+}
+
+void Joystick::ProcessBinding( ControllerInput::Input input, const std::string command) {
+ binding_.insert(std::pair<ControllerInput::Input, std::string>(input, command));
+}
+
+float Joystick::GetButtonDown( const std::string &name ) const {
+ ButtonMap::const_iterator iter(buttons_down_.find(name));
+ if(iter != buttons_down_.end()){
+ return iter->second;
+ } else {
+ return false;
+ }
+}
+
+void Joystick::SetButtonDown( const std::string &name, float depth ) {
+ ButtonMap::iterator iter(buttons_down_.find(name));
+ if(iter != buttons_down_.end()){
+ iter->second = depth;
+ } else {
+ buttons_down_.insert(std::pair<std::string, bool>(name, depth));
+ }
+}
+
+void Joystick::ClearBinding() {
+ binding_.clear();
+}
+
+void Joystick::AddInputBufferItem(ControllerInput::Input input, float depth, uint32_t sequence_id)
+{
+ if( button_input_buffer_count >= button_input_buffer_size ) {
+ for( unsigned i = 1; i < button_input_buffer_count; i++ ) {
+ button_input_buffer[i-1] = button_input_buffer[i];
+ }
+ button_input_buffer_count--;
+ }
+
+ button_input_buffer[button_input_buffer_count].s_id = sequence_id;
+ button_input_buffer[button_input_buffer_count].input = input;
+ button_input_buffer[button_input_buffer_count].depth = depth;
+ button_input_buffer_count++;
+}
+
+std::vector<Joystick::JoystickPress> Joystick::GetJoystickInputs()
+{
+ std::vector<Joystick::JoystickPress> presses;
+ presses.resize(button_input_buffer_count);
+ for( unsigned i = 0; i < button_input_buffer_count; i++ ) {
+ presses[i] = button_input_buffer[i];
+ }
+ return presses;
+}
+
+float Joystick::NormalizeJoystick(float value) {
+ // Do not let kDeadzone become 32767.0f, or divide by 0 will occur
+ const float kDeadzone = std::min(32767.0f * deadzone, 32000.0f); // Not actually a constant, but sort of
+ // Deadzone
+ if(value < kDeadzone && value > -kDeadzone) {
+ return 0;
+ }
+ // Subtract deadzone from value and divisor
+ value -= kDeadzone;
+ float axis = 0.0f;
+ axis = pow(value / (32767.0f - kDeadzone), 2.0f);
+ // Clamp each axis to {-1,1}
+ if (axis < -1.0f) {
+ axis = -1.0f;
+ }
+ if (axis > 1.0f) {
+ axis = 1.0f;
+ }
+ return axis;
+}
+
+const float KeyState::kDepthThreshold = 0.5f;
+
+KeyState::KeyState()
+ : count(0)
+ , depth_count(0)
+ , depth(0)
+{ }
+
+bool KeyState::operator!=(const KeyState & other) const {
+ return count != other.count || depth != other.depth || depth != other.depth_count;
+}
+
+PlayerInput::PlayerInput() :
+ enabled(true),
+ remote_controlled(false)
+{ }
diff --git a/Source/UserInput/joystick.h b/Source/UserInput/joystick.h
new file mode 100644
index 00000000..1e021190
--- /dev/null
+++ b/Source/UserInput/joystick.h
@@ -0,0 +1,91 @@
+//-----------------------------------------------------------------------------
+// Name: joystick.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <UserInput/keyTranslator.h>
+
+#include <string>
+#include <map>
+#include <vector>
+
+class Joystick {
+public:
+ static const size_t button_input_buffer_size = 16;
+ struct JoystickPress {
+ uint32_t s_id;
+ ControllerInput::Input input;
+ float depth;
+ };
+ JoystickPress button_input_buffer[button_input_buffer_size];
+ size_t button_input_buffer_count;
+
+ float look_sensitivity_;
+ float deadzone;
+
+ Joystick(float look_sensitivity);
+ bool HandleInputChange(ControllerInput::Input input, float val, uint32_t sequence_id);
+ float GetButtonDown(const std::string &name) const;
+ void ProcessBinding( ControllerInput::Input input, const std::string command);
+ void SetButtonDown( const std::string &name, float depth );
+ void ClearBinding();
+
+ std::vector<JoystickPress> GetJoystickInputs();
+
+ typedef std::map<std::string, float> ButtonMap;
+ ButtonMap buttons_down_;
+private:
+ typedef std::multimap<ControllerInput::Input, std::string> BindingMap;
+
+ BindingMap binding_;
+
+ void AddInputBufferItem(ControllerInput::Input input, float depth, uint32_t sequence_id);
+ float NormalizeJoystick(float value);
+};
+
+class KeyState {
+public:
+ const static float kDepthThreshold;
+ KeyState();
+
+ bool operator!=(const KeyState& other) const;
+
+ int count;
+ int depth_count; // This is only incremented when depth is greater than kDepthThreshold
+ float depth;
+};
+
+struct PlayerInput {
+ typedef std::map<std::string, KeyState> KeyDownMap;
+
+ //Set depending on if there is a purpose of polling for a local character,
+ //Usually set to false for controller 2-4 if split-screen is disabled.
+ bool enabled;
+
+ //Disable local processing of input for this PlayerInput, and instead expect input to
+ //come from the Multiplayer system.
+ bool remote_controlled;
+
+ KeyDownMap key_down;
+
+ PlayerInput();
+};
diff --git a/Source/UserInput/keyTranslator.cpp b/Source/UserInput/keyTranslator.cpp
new file mode 100644
index 00000000..0b4786ed
--- /dev/null
+++ b/Source/UserInput/keyTranslator.cpp
@@ -0,0 +1,1106 @@
+//-----------------------------------------------------------------------------
+// Name: keyTranslator.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <UserInput/keyTranslator.h>
+#include <UserInput/keyboard.h>
+
+#include <Logging/logdata.h>
+#include <Utility/strings.h>
+
+#include <SDL.h>
+
+#include <map>
+#include <vector>
+//#ifdef WIN32
+ //#define NOMINMAX
+ //#include <windows.h>
+ //#include <XInput.h>
+//#endif
+
+struct CStrCmp
+{
+ bool operator()(const char* lhs, const char* rhs) const {
+ return std::strcmp(lhs, rhs) < 0;
+ }
+};
+
+typedef std::pair<const char*, SDL_Scancode> KeyPair;
+typedef std::map<const char*, ControllerInput::Input, CStrCmp> StrToControllerMap;
+typedef std::map<const char*, const char*, CStrCmp> InputToStrMap;
+
+static std::vector<KeyPair> keys;
+static std::map<std::string, SDL_Scancode> str_to_key_map;
+static std::map<SDL_Scancode, const char*> key_to_str_map;
+static InputToStrMap input_to_string_map;
+static StrToControllerMap str_to_controller_map;
+
+/*#ifdef WIN32
+#include <XInput.h>
+int XBoxToXInput(const std::string &s)
+{
+ static std::map<std::string, WORD> xbox_map;
+ if(xbox_map.empty()){
+ xbox_map["A"] = XINPUT_GAMEPAD_A;
+ xbox_map["B"] = XINPUT_GAMEPAD_B;
+ xbox_map["X"] = XINPUT_GAMEPAD_X;
+ xbox_map["Y"] = XINPUT_GAMEPAD_Y;
+ xbox_map["LB"] = XINPUT_GAMEPAD_LEFT_SHOULDER;
+ xbox_map["RB"] = XINPUT_GAMEPAD_RIGHT_SHOULDER;
+ xbox_map["L3"] = XINPUT_GAMEPAD_LEFT_THUMB;
+ xbox_map["R3"] = XINPUT_GAMEPAD_RIGHT_THUMB;
+ xbox_map["LT"] = 0x0400;
+ xbox_map["RT"] = 0x0800;
+ xbox_map["DPAD_L"] = XINPUT_GAMEPAD_DPAD_LEFT;
+ xbox_map["DPAD_U"] = XINPUT_GAMEPAD_DPAD_UP;
+ xbox_map["DPAD_R"] = XINPUT_GAMEPAD_DPAD_RIGHT;
+ xbox_map["DPAD_D"] = XINPUT_GAMEPAD_DPAD_DOWN;
+ xbox_map["START"] = XINPUT_GAMEPAD_START;
+ xbox_map["BACK"] = XINPUT_GAMEPAD_BACK;
+ }
+
+ std::map<std::string, WORD>::iterator iter = xbox_map.find(s);
+ if(iter != xbox_map.end()){
+ return iter->second;
+ } else {
+ return 0;
+ }
+}
+#endif*/
+
+uint32_t SDL_SCANCODES[] = {
+ SDL_SCANCODE_A,
+ SDL_SCANCODE_B,
+ SDL_SCANCODE_C,
+ SDL_SCANCODE_D,
+ SDL_SCANCODE_E,
+ SDL_SCANCODE_F,
+ SDL_SCANCODE_G,
+ SDL_SCANCODE_H,
+ SDL_SCANCODE_I,
+ SDL_SCANCODE_J,
+ SDL_SCANCODE_K,
+ SDL_SCANCODE_L,
+ SDL_SCANCODE_M,
+ SDL_SCANCODE_N,
+ SDL_SCANCODE_O,
+ SDL_SCANCODE_P,
+ SDL_SCANCODE_Q,
+ SDL_SCANCODE_R,
+ SDL_SCANCODE_S,
+ SDL_SCANCODE_T,
+ SDL_SCANCODE_U,
+ SDL_SCANCODE_V,
+ SDL_SCANCODE_W,
+ SDL_SCANCODE_X,
+ SDL_SCANCODE_Y,
+ SDL_SCANCODE_Z,
+
+ SDL_SCANCODE_1,
+ SDL_SCANCODE_2,
+ SDL_SCANCODE_3,
+ SDL_SCANCODE_4,
+ SDL_SCANCODE_5,
+ SDL_SCANCODE_6,
+ SDL_SCANCODE_7,
+ SDL_SCANCODE_8,
+ SDL_SCANCODE_9,
+ SDL_SCANCODE_0,
+
+ SDL_SCANCODE_RETURN,
+ SDL_SCANCODE_ESCAPE,
+ SDL_SCANCODE_BACKSPACE,
+ SDL_SCANCODE_TAB,
+ SDL_SCANCODE_SPACE,
+
+ SDL_SCANCODE_MINUS,
+ SDL_SCANCODE_EQUALS,
+ SDL_SCANCODE_LEFTBRACKET,
+ SDL_SCANCODE_RIGHTBRACKET,
+ SDL_SCANCODE_BACKSLASH,
+ SDL_SCANCODE_NONUSHASH,
+ SDL_SCANCODE_SEMICOLON,
+ SDL_SCANCODE_APOSTROPHE,
+ SDL_SCANCODE_GRAVE,
+ SDL_SCANCODE_COMMA,
+ SDL_SCANCODE_PERIOD,
+ SDL_SCANCODE_SLASH,
+ SDL_SCANCODE_CAPSLOCK,
+ SDL_SCANCODE_F1,
+ SDL_SCANCODE_F2,
+ SDL_SCANCODE_F3,
+ SDL_SCANCODE_F4,
+ SDL_SCANCODE_F5,
+ SDL_SCANCODE_F6,
+ SDL_SCANCODE_F7,
+ SDL_SCANCODE_F8,
+ SDL_SCANCODE_F9,
+ SDL_SCANCODE_F10,
+ SDL_SCANCODE_F11,
+ SDL_SCANCODE_F12,
+ SDL_SCANCODE_PRINTSCREEN,
+ SDL_SCANCODE_SCROLLLOCK,
+ SDL_SCANCODE_PAUSE,
+ SDL_SCANCODE_INSERT,
+ SDL_SCANCODE_HOME,
+ SDL_SCANCODE_PAGEUP,
+ SDL_SCANCODE_DELETE,
+ SDL_SCANCODE_END,
+ SDL_SCANCODE_PAGEDOWN,
+ SDL_SCANCODE_RIGHT,
+ SDL_SCANCODE_LEFT,
+ SDL_SCANCODE_DOWN,
+ SDL_SCANCODE_UP,
+ SDL_SCANCODE_NUMLOCKCLEAR,
+ SDL_SCANCODE_KP_DIVIDE,
+ SDL_SCANCODE_KP_MULTIPLY,
+ SDL_SCANCODE_KP_MINUS,
+ SDL_SCANCODE_KP_PLUS,
+ SDL_SCANCODE_KP_ENTER,
+ SDL_SCANCODE_KP_1,
+ SDL_SCANCODE_KP_2,
+ SDL_SCANCODE_KP_3,
+ SDL_SCANCODE_KP_4,
+ SDL_SCANCODE_KP_5,
+ SDL_SCANCODE_KP_6,
+ SDL_SCANCODE_KP_7,
+ SDL_SCANCODE_KP_8,
+ SDL_SCANCODE_KP_9,
+ SDL_SCANCODE_KP_0,
+ SDL_SCANCODE_KP_PERIOD,
+ SDL_SCANCODE_NONUSBACKSLASH,
+ SDL_SCANCODE_APPLICATION,
+ SDL_SCANCODE_POWER,
+ SDL_SCANCODE_KP_EQUALS,
+ SDL_SCANCODE_F13,
+ SDL_SCANCODE_F14,
+ SDL_SCANCODE_F15,
+ SDL_SCANCODE_F16,
+ SDL_SCANCODE_F17,
+ SDL_SCANCODE_F18,
+ SDL_SCANCODE_F19,
+ SDL_SCANCODE_F20,
+ SDL_SCANCODE_F21,
+ SDL_SCANCODE_F22,
+ SDL_SCANCODE_F23,
+ SDL_SCANCODE_F24,
+ SDL_SCANCODE_EXECUTE,
+ SDL_SCANCODE_HELP,
+ SDL_SCANCODE_MENU,
+ SDL_SCANCODE_SELECT,
+ SDL_SCANCODE_STOP,
+ SDL_SCANCODE_AGAIN,
+ SDL_SCANCODE_UNDO,
+ SDL_SCANCODE_CUT,
+ SDL_SCANCODE_COPY,
+ SDL_SCANCODE_PASTE,
+ SDL_SCANCODE_FIND,
+ SDL_SCANCODE_MUTE,
+ SDL_SCANCODE_VOLUMEUP,
+ SDL_SCANCODE_VOLUMEDOWN,
+
+ SDL_SCANCODE_KP_COMMA,
+ SDL_SCANCODE_KP_EQUALSAS400,
+
+ SDL_SCANCODE_INTERNATIONAL1,
+
+ SDL_SCANCODE_INTERNATIONAL2,
+ SDL_SCANCODE_INTERNATIONAL3,
+ SDL_SCANCODE_INTERNATIONAL4,
+ SDL_SCANCODE_INTERNATIONAL5,
+ SDL_SCANCODE_INTERNATIONAL6,
+ SDL_SCANCODE_INTERNATIONAL7,
+ SDL_SCANCODE_INTERNATIONAL8,
+ SDL_SCANCODE_INTERNATIONAL9,
+ SDL_SCANCODE_LANG1,
+ SDL_SCANCODE_LANG2,
+ SDL_SCANCODE_LANG3,
+ SDL_SCANCODE_LANG4,
+ SDL_SCANCODE_LANG5,
+ SDL_SCANCODE_LANG6,
+ SDL_SCANCODE_LANG7,
+ SDL_SCANCODE_LANG8,
+ SDL_SCANCODE_LANG9,
+
+ SDL_SCANCODE_ALTERASE,
+ SDL_SCANCODE_SYSREQ,
+ SDL_SCANCODE_CANCEL,
+ SDL_SCANCODE_CLEAR,
+ SDL_SCANCODE_PRIOR,
+ SDL_SCANCODE_RETURN2,
+ SDL_SCANCODE_SEPARATOR,
+ SDL_SCANCODE_OUT,
+ SDL_SCANCODE_OPER,
+ SDL_SCANCODE_CLEARAGAIN,
+ SDL_SCANCODE_CRSEL,
+ SDL_SCANCODE_EXSEL,
+
+ SDL_SCANCODE_KP_00,
+ SDL_SCANCODE_KP_000,
+ SDL_SCANCODE_THOUSANDSSEPARATOR,
+ SDL_SCANCODE_DECIMALSEPARATOR,
+ SDL_SCANCODE_CURRENCYUNIT,
+ SDL_SCANCODE_CURRENCYSUBUNIT,
+ SDL_SCANCODE_KP_LEFTPAREN,
+ SDL_SCANCODE_KP_RIGHTPAREN,
+ SDL_SCANCODE_KP_LEFTBRACE,
+ SDL_SCANCODE_KP_RIGHTBRACE,
+ SDL_SCANCODE_KP_TAB,
+ SDL_SCANCODE_KP_BACKSPACE,
+ SDL_SCANCODE_KP_A,
+ SDL_SCANCODE_KP_B,
+ SDL_SCANCODE_KP_C,
+ SDL_SCANCODE_KP_D,
+ SDL_SCANCODE_KP_E,
+ SDL_SCANCODE_KP_F,
+ SDL_SCANCODE_KP_XOR,
+ SDL_SCANCODE_KP_POWER,
+ SDL_SCANCODE_KP_PERCENT,
+ SDL_SCANCODE_KP_LESS,
+ SDL_SCANCODE_KP_GREATER,
+ SDL_SCANCODE_KP_AMPERSAND,
+ SDL_SCANCODE_KP_DBLAMPERSAND,
+ SDL_SCANCODE_KP_VERTICALBAR,
+ SDL_SCANCODE_KP_DBLVERTICALBAR,
+ SDL_SCANCODE_KP_COLON,
+ SDL_SCANCODE_KP_HASH,
+ SDL_SCANCODE_KP_SPACE,
+ SDL_SCANCODE_KP_AT,
+ SDL_SCANCODE_KP_EXCLAM,
+ SDL_SCANCODE_KP_MEMSTORE,
+ SDL_SCANCODE_KP_MEMRECALL,
+ SDL_SCANCODE_KP_MEMCLEAR,
+ SDL_SCANCODE_KP_MEMADD,
+ SDL_SCANCODE_KP_MEMSUBTRACT,
+ SDL_SCANCODE_KP_MEMMULTIPLY,
+ SDL_SCANCODE_KP_MEMDIVIDE,
+ SDL_SCANCODE_KP_PLUSMINUS,
+ SDL_SCANCODE_KP_CLEAR,
+ SDL_SCANCODE_KP_CLEARENTRY,
+ SDL_SCANCODE_KP_BINARY,
+ SDL_SCANCODE_KP_OCTAL,
+ SDL_SCANCODE_KP_DECIMAL,
+ SDL_SCANCODE_KP_HEXADECIMAL,
+
+ SDL_SCANCODE_LCTRL,
+ SDL_SCANCODE_LSHIFT,
+ SDL_SCANCODE_LALT,
+ SDL_SCANCODE_LGUI,
+ SDL_SCANCODE_RCTRL,
+ SDL_SCANCODE_RSHIFT,
+ SDL_SCANCODE_RALT,
+ SDL_SCANCODE_RGUI,
+
+ SDL_SCANCODE_MODE,
+
+ SDL_SCANCODE_AUDIONEXT,
+ SDL_SCANCODE_AUDIOPREV,
+ SDL_SCANCODE_AUDIOSTOP,
+ SDL_SCANCODE_AUDIOPLAY,
+ SDL_SCANCODE_AUDIOMUTE,
+ SDL_SCANCODE_MEDIASELECT,
+ SDL_SCANCODE_WWW,
+ SDL_SCANCODE_MAIL,
+ SDL_SCANCODE_CALCULATOR,
+ SDL_SCANCODE_COMPUTER,
+ SDL_SCANCODE_AC_SEARCH,
+ SDL_SCANCODE_AC_HOME,
+ SDL_SCANCODE_AC_BACK,
+ SDL_SCANCODE_AC_FORWARD,
+ SDL_SCANCODE_AC_STOP,
+ SDL_SCANCODE_AC_REFRESH,
+ SDL_SCANCODE_AC_BOOKMARKS,
+
+ SDL_SCANCODE_BRIGHTNESSDOWN,
+ SDL_SCANCODE_BRIGHTNESSUP,
+ SDL_SCANCODE_DISPLAYSWITCH,
+
+ SDL_SCANCODE_KBDILLUMTOGGLE,
+ SDL_SCANCODE_KBDILLUMDOWN,
+ SDL_SCANCODE_KBDILLUMUP,
+ SDL_SCANCODE_EJECT,
+ SDL_SCANCODE_SLEEP,
+
+ SDL_SCANCODE_APP1,
+ SDL_SCANCODE_APP2
+};
+
+/*
+uint32_t SDLKS[] = {
+ SDLK_RETURN,
+ SDLK_ESCAPE,
+ SDLK_BACKSPACE,
+ SDLK_TAB,
+ SDLK_SPACE,
+ SDLK_EXCLAIM,
+ SDLK_QUOTEDBL,
+ SDLK_HASH,
+ SDLK_PERCENT,
+ SDLK_DOLLAR,
+ SDLK_AMPERSAND,
+ SDLK_QUOTE,
+ SDLK_LEFTPAREN,
+ SDLK_RIGHTPAREN,
+ SDLK_ASTERISK,
+ SDLK_PLUS,
+ SDLK_COMMA,
+ SDLK_MINUS,
+ SDLK_PERIOD,
+ SDLK_SLASH,
+ SDLK_0,
+ SDLK_1,
+ SDLK_2,
+ SDLK_3,
+ SDLK_4,
+ SDLK_5,
+ SDLK_6,
+ SDLK_7,
+ SDLK_8,
+ SDLK_9,
+ SDLK_COLON,
+ SDLK_SEMICOLON,
+ SDLK_LESS,
+ SDLK_EQUALS,
+ SDLK_GREATER,
+ SDLK_QUESTION,
+ SDLK_AT,
+
+ SDLK_LEFTBRACKET,
+ SDLK_BACKSLASH,
+ SDLK_RIGHTBRACKET,
+ SDLK_CARET,
+ SDLK_UNDERSCORE,
+ SDLK_BACKQUOTE,
+ SDLK_a,
+ SDLK_b,
+ SDLK_c,
+ SDLK_d,
+ SDLK_e,
+ SDLK_f,
+ SDLK_g,
+ SDLK_h,
+ SDLK_i,
+ SDLK_j,
+ SDLK_k,
+ SDLK_l,
+ SDLK_m,
+ SDLK_n,
+ SDLK_o,
+ SDLK_p,
+ SDLK_q,
+ SDLK_r,
+ SDLK_s,
+ SDLK_t,
+ SDLK_u,
+ SDLK_v,
+ SDLK_w,
+ SDLK_x,
+ SDLK_y,
+ SDLK_z,
+
+ SDLK_CAPSLOCK,
+
+ SDLK_F1,
+ SDLK_F2,
+ SDLK_F3,
+ SDLK_F4,
+ SDLK_F5,
+ SDLK_F6,
+ SDLK_F7,
+ SDLK_F8,
+ SDLK_F9,
+ SDLK_F10,
+ SDLK_F11,
+ SDLK_F12,
+
+ SDLK_PRINTSCREEN,
+ SDLK_SCROLLLOCK,
+ SDLK_PAUSE,
+ SDLK_INSERT,
+ SDLK_HOME,
+ SDLK_PAGEUP,
+ SDLK_DELETE,
+ SDLK_END,
+ SDLK_PAGEDOWN,
+ SDLK_RIGHT,
+ SDLK_LEFT,
+ SDLK_DOWN,
+ SDLK_UP,
+
+ SDLK_NUMLOCKCLEAR,
+ SDLK_KP_DIVIDE,
+ SDLK_KP_MULTIPLY,
+ SDLK_KP_MINUS,
+ SDLK_KP_PLUS,
+ SDLK_KP_ENTER,
+ SDLK_KP_1,
+ SDLK_KP_2,
+ SDLK_KP_3,
+ SDLK_KP_4,
+ SDLK_KP_5,
+ SDLK_KP_6,
+ SDLK_KP_7,
+ SDLK_KP_8,
+ SDLK_KP_9,
+ SDLK_KP_0,
+ SDLK_KP_PERIOD,
+
+ SDLK_APPLICATION,
+ SDLK_POWER,
+ SDLK_KP_EQUALS,
+ SDLK_F13,
+ SDLK_F14,
+ SDLK_F15,
+ SDLK_F16,
+ SDLK_F17,
+ SDLK_F18,
+ SDLK_F19,
+ SDLK_F20,
+ SDLK_F21,
+ SDLK_F22,
+ SDLK_F23,
+ SDLK_F24,
+ SDLK_EXECUTE,
+ SDLK_HELP,
+ SDLK_MENU,
+ SDLK_SELECT,
+ SDLK_STOP,
+ SDLK_AGAIN,
+ SDLK_UNDO,
+ SDLK_CUT,
+ SDLK_COPY,
+ SDLK_PASTE,
+ SDLK_FIND,
+ SDLK_MUTE,
+ SDLK_VOLUMEUP,
+ SDLK_VOLUMEDOWN,
+ SDLK_KP_COMMA,
+ SDLK_KP_EQUALSAS400,
+
+ SDLK_ALTERASE,
+ SDLK_SYSREQ,
+ SDLK_CANCEL,
+ SDLK_CLEAR,
+ SDLK_PRIOR,
+// SDLK_RETURN2,
+ SDLK_SEPARATOR,
+ SDLK_OUT,
+ SDLK_OPER,
+ SDLK_CLEARAGAIN,
+ SDLK_CRSEL,
+ SDLK_EXSEL,
+
+ SDLK_KP_00,
+ SDLK_KP_000,
+ SDLK_THOUSANDSSEPARATOR,
+ SDLK_DECIMALSEPARATOR,
+ SDLK_CURRENCYUNIT,
+ SDLK_CURRENCYSUBUNIT,
+ SDLK_KP_LEFTPAREN,
+ SDLK_KP_RIGHTPAREN,
+ SDLK_KP_LEFTBRACE,
+ SDLK_KP_RIGHTBRACE,
+ SDLK_KP_TAB,
+ SDLK_KP_BACKSPACE,
+ SDLK_KP_A,
+ SDLK_KP_B,
+ SDLK_KP_C,
+ SDLK_KP_D,
+ SDLK_KP_E,
+ SDLK_KP_F,
+ SDLK_KP_XOR,
+ SDLK_KP_POWER,
+ SDLK_KP_PERCENT,
+ SDLK_KP_LESS,
+ SDLK_KP_GREATER,
+ SDLK_KP_AMPERSAND,
+ SDLK_KP_DBLAMPERSAND,
+ SDLK_KP_VERTICALBAR,
+ SDLK_KP_DBLVERTICALBAR,
+ SDLK_KP_COLON,
+ SDLK_KP_HASH,
+ SDLK_KP_SPACE,
+ SDLK_KP_AT,
+ SDLK_KP_EXCLAM,
+ SDLK_KP_MEMSTORE,
+ SDLK_KP_MEMRECALL,
+ SDLK_KP_MEMCLEAR,
+ SDLK_KP_MEMADD,
+ SDLK_KP_MEMSUBTRACT,
+ SDLK_KP_MEMMULTIPLY,
+ SDLK_KP_MEMDIVIDE,
+ SDLK_KP_PLUSMINUS,
+ SDLK_KP_CLEAR,
+ SDLK_KP_CLEARENTRY,
+ SDLK_KP_BINARY,
+ SDLK_KP_OCTAL,
+ SDLK_KP_DECIMAL,
+ SDLK_KP_HEXADECIMAL,
+
+ SDLK_LCTRL,
+ SDLK_LSHIFT,
+ SDLK_LALT,
+ SDLK_LGUI,
+ SDLK_RCTRL,
+ SDLK_RSHIFT,
+ SDLK_RALT,
+ SDLK_RGUI,
+
+ SDLK_MODE,
+
+ SDLK_AUDIONEXT,
+ SDLK_AUDIOPREV,
+ SDLK_AUDIOSTOP,
+ SDLK_AUDIOPLAY,
+ SDLK_AUDIOMUTE,
+ SDLK_MEDIASELECT,
+ SDLK_WWW,
+ SDLK_MAIL,
+ SDLK_CALCULATOR,
+ SDLK_COMPUTER,
+ SDLK_AC_SEARCH,
+ SDLK_AC_HOME,
+ SDLK_AC_BACK,
+ SDLK_AC_FORWARD,
+ SDLK_AC_STOP,
+ SDLK_AC_REFRESH,
+ SDLK_AC_BOOKMARKS,
+
+ SDLK_BRIGHTNESSDOWN,
+ SDLK_BRIGHTNESSUP,
+ SDLK_DISPLAYSWITCH,
+ SDLK_KBDILLUMTOGGLE,
+ SDLK_KBDILLUMDOWN,
+ SDLK_KBDILLUMUP,
+ SDLK_EJECT,
+ SDLK_SLEEP
+};
+*/
+
+static std::vector<char> key_translation_memory;
+
+void InitKeyTranslator() {
+ keys.clear();
+ key_translation_memory.resize(1024);
+ std::vector<std::pair<int,uint32_t> > offsets;
+
+ size_t cur_index = 0;
+ for( size_t i = 0; i < sizeof(SDL_SCANCODES)/sizeof(uint32_t); i++ ) {
+ const char* scancodename = SDL_GetScancodeName((SDL_Scancode)SDL_SCANCODES[i]);
+ size_t memlen = strlen(scancodename) + 1;
+ if(cur_index + memlen > key_translation_memory.size()) {
+ key_translation_memory.resize(key_translation_memory.size()+1024);
+ }
+ memcpy(&key_translation_memory[cur_index], scancodename, memlen);
+ UTF8InPlaceLower(&key_translation_memory[cur_index]);
+ offsets.push_back(std::pair<int,uint32_t>(cur_index,SDL_SCANCODES[i]));
+ cur_index += memlen;
+ }
+
+ for( size_t i = 0; i < offsets.size(); i++ ) {
+ keys.push_back(KeyPair(&key_translation_memory[offsets[i].first], (SDL_Scancode)offsets[i].second));
+ }
+
+ keys.push_back(KeyPair("backspace", SDL_SCANCODE_BACKSPACE));
+ keys.push_back(KeyPair("tab",SDL_SCANCODE_TAB));
+ keys.push_back(KeyPair("clear",SDL_SCANCODE_CLEAR));
+ keys.push_back(KeyPair("return",SDL_SCANCODE_RETURN));
+ keys.push_back(KeyPair("pause",SDL_SCANCODE_PAUSE));
+ keys.push_back(KeyPair("esc",SDL_SCANCODE_ESCAPE));
+ keys.push_back(KeyPair("space",SDL_SCANCODE_SPACE));
+ //keys.push_back(KeyPair("!",SDL_SCANCODE_EXCLAIM));
+ //keys.push_back(KeyPair("\"",SDL_SCANCODE_QUOTEDBL));
+ //keys.push_back(KeyPair("#",SDL_SCANCODE_HASH));
+ //keys.push_back(KeyPair("$",SDL_SCANCODE_DOLLAR));
+ //keys.push_back(KeyPair("&",SDL_SCANCODE_AMPERSAND));
+ //keys.push_back(KeyPair("\'",SDL_SCANCODE_QUOTE));
+ //keys.push_back(KeyPair("(",SDL_SCANCODE_LEFTPAREN));
+ //keys.push_back(KeyPair(")",SDL_SCANCODE_RIGHTPAREN));
+ //keys.push_back(KeyPair("*",SDL_SCANCODE_ASTERISK));
+ //keys.push_back(KeyPair("+",SDL_SCANCODE_PLUS));
+ keys.push_back(KeyPair(",",SDL_SCANCODE_COMMA));
+ keys.push_back(KeyPair("-",SDL_SCANCODE_MINUS));
+ keys.push_back(KeyPair(".",SDL_SCANCODE_PERIOD));
+ keys.push_back(KeyPair("/",SDL_SCANCODE_SLASH));
+ keys.push_back(KeyPair("0",SDL_SCANCODE_0));
+ keys.push_back(KeyPair("1",SDL_SCANCODE_1));
+ keys.push_back(KeyPair("2",SDL_SCANCODE_2));
+ keys.push_back(KeyPair("3",SDL_SCANCODE_3));
+ keys.push_back(KeyPair("4",SDL_SCANCODE_4));
+ keys.push_back(KeyPair("5",SDL_SCANCODE_5));
+ keys.push_back(KeyPair("6",SDL_SCANCODE_6));
+ keys.push_back(KeyPair("7",SDL_SCANCODE_7));
+ keys.push_back(KeyPair("8",SDL_SCANCODE_8));
+ keys.push_back(KeyPair("9",SDL_SCANCODE_9));
+ //keys.push_back(KeyPair(":",SDL_SCANCODE_COLON));
+ keys.push_back(KeyPair(";",SDL_SCANCODE_SEMICOLON));
+ //keys.push_back(KeyPair("<",SDL_SCANCODE_LESS));
+ keys.push_back(KeyPair("=",SDL_SCANCODE_EQUALS));
+ //keys.push_back(KeyPair(">",SDL_SCANCODE_GREATER));
+ //keys.push_back(KeyPair("?",SDL_SCANCODE_QUESTION));
+ //keys.push_back(KeyPair("@",SDL_SCANCODE_AT));
+ keys.push_back(KeyPair("[",SDL_SCANCODE_LEFTBRACKET));
+ keys.push_back(KeyPair("\\",SDL_SCANCODE_BACKSLASH));
+ keys.push_back(KeyPair("]",SDL_SCANCODE_RIGHTBRACKET));
+ //keys.push_back(KeyPair("^",SDL_SCANCODE_CARET));
+ //keys.push_back(KeyPair("_",SDL_SCANCODE_UNDERSCORE));
+ keys.push_back(KeyPair("`",SDL_SCANCODE_GRAVE));
+ keys.push_back(KeyPair("a",SDL_SCANCODE_A));
+ keys.push_back(KeyPair("b",SDL_SCANCODE_B));
+ keys.push_back(KeyPair("c",SDL_SCANCODE_C));
+ keys.push_back(KeyPair("d",SDL_SCANCODE_D));
+ keys.push_back(KeyPair("e",SDL_SCANCODE_E));
+ keys.push_back(KeyPair("f",SDL_SCANCODE_F));
+ keys.push_back(KeyPair("g",SDL_SCANCODE_G));
+ keys.push_back(KeyPair("h",SDL_SCANCODE_H));
+ keys.push_back(KeyPair("i",SDL_SCANCODE_I));
+ keys.push_back(KeyPair("j",SDL_SCANCODE_J));
+ keys.push_back(KeyPair("k",SDL_SCANCODE_K));
+ keys.push_back(KeyPair("l",SDL_SCANCODE_L));
+ keys.push_back(KeyPair("m",SDL_SCANCODE_M));
+ keys.push_back(KeyPair("n",SDL_SCANCODE_N));
+ keys.push_back(KeyPair("o",SDL_SCANCODE_O));
+ keys.push_back(KeyPair("p",SDL_SCANCODE_P));
+ keys.push_back(KeyPair("q",SDL_SCANCODE_Q));
+ keys.push_back(KeyPair("r",SDL_SCANCODE_R));
+ keys.push_back(KeyPair("s",SDL_SCANCODE_S));
+ keys.push_back(KeyPair("t",SDL_SCANCODE_T));
+ keys.push_back(KeyPair("u",SDL_SCANCODE_U));
+ keys.push_back(KeyPair("v",SDL_SCANCODE_V));
+ keys.push_back(KeyPair("w",SDL_SCANCODE_W));
+ keys.push_back(KeyPair("x",SDL_SCANCODE_X));
+ keys.push_back(KeyPair("y",SDL_SCANCODE_Y));
+ keys.push_back(KeyPair("z",SDL_SCANCODE_Z));
+ keys.push_back(KeyPair("delete",SDL_SCANCODE_DELETE));
+ keys.push_back(KeyPair("keypad0",SDL_SCANCODE_KP_0));
+ keys.push_back(KeyPair("keypad1",SDL_SCANCODE_KP_1));
+ keys.push_back(KeyPair("keypad2",SDL_SCANCODE_KP_2));
+ keys.push_back(KeyPair("keypad3",SDL_SCANCODE_KP_3));
+ keys.push_back(KeyPair("keypad4",SDL_SCANCODE_KP_4));
+ keys.push_back(KeyPair("keypad5",SDL_SCANCODE_KP_5));
+ keys.push_back(KeyPair("keypad6",SDL_SCANCODE_KP_6));
+ keys.push_back(KeyPair("keypad7",SDL_SCANCODE_KP_7));
+ keys.push_back(KeyPair("keypad8",SDL_SCANCODE_KP_8));
+ keys.push_back(KeyPair("keypad9",SDL_SCANCODE_KP_9));
+ keys.push_back(KeyPair("keypad.",SDL_SCANCODE_KP_PERIOD));
+ keys.push_back(KeyPair("keypad/",SDL_SCANCODE_KP_DIVIDE));
+ keys.push_back(KeyPair("keypad*",SDL_SCANCODE_KP_MULTIPLY));
+ keys.push_back(KeyPair("keypad-",SDL_SCANCODE_KP_MINUS));
+ keys.push_back(KeyPair("keypad+",SDL_SCANCODE_KP_PLUS));
+ keys.push_back(KeyPair("keypadenter",SDL_SCANCODE_KP_ENTER));
+ keys.push_back(KeyPair("keypad=",SDL_SCANCODE_KP_EQUALS));
+ keys.push_back(KeyPair("up",SDL_SCANCODE_UP));
+ keys.push_back(KeyPair("down",SDL_SCANCODE_DOWN));
+ keys.push_back(KeyPair("right",SDL_SCANCODE_RIGHT));
+ keys.push_back(KeyPair("left",SDL_SCANCODE_LEFT));
+ keys.push_back(KeyPair("insert",SDL_SCANCODE_INSERT));
+ keys.push_back(KeyPair("home",SDL_SCANCODE_HOME));
+ keys.push_back(KeyPair("end",SDL_SCANCODE_END));
+ keys.push_back(KeyPair("pageup",SDL_SCANCODE_PAGEUP));
+ keys.push_back(KeyPair("pagedown",SDL_SCANCODE_PAGEDOWN));
+ keys.push_back(KeyPair("f1",SDL_SCANCODE_F1));
+ keys.push_back(KeyPair("f2",SDL_SCANCODE_F2));
+ keys.push_back(KeyPair("f3",SDL_SCANCODE_F3));
+ keys.push_back(KeyPair("f4",SDL_SCANCODE_F4));
+ keys.push_back(KeyPair("f5",SDL_SCANCODE_F5));
+ keys.push_back(KeyPair("f6",SDL_SCANCODE_F6));
+ keys.push_back(KeyPair("f7",SDL_SCANCODE_F7));
+ keys.push_back(KeyPair("f8",SDL_SCANCODE_F8));
+ keys.push_back(KeyPair("f9",SDL_SCANCODE_F9));
+ keys.push_back(KeyPair("f10",SDL_SCANCODE_F10));
+ keys.push_back(KeyPair("f11",SDL_SCANCODE_F11));
+ keys.push_back(KeyPair("f12",SDL_SCANCODE_F12));
+ keys.push_back(KeyPair("f13",SDL_SCANCODE_F13));
+ keys.push_back(KeyPair("f14",SDL_SCANCODE_F14));
+ keys.push_back(KeyPair("f15",SDL_SCANCODE_F15));
+ keys.push_back(KeyPair("numlock",SDL_SCANCODE_NUMLOCKCLEAR));
+ keys.push_back(KeyPair("capslock",SDL_SCANCODE_CAPSLOCK));
+ keys.push_back(KeyPair("scrollock",SDL_SCANCODE_SCROLLLOCK));
+ keys.push_back(KeyPair("rshift",SDL_SCANCODE_RSHIFT));
+ keys.push_back(KeyPair("lshift",SDL_SCANCODE_LSHIFT));
+ //keys.push_back(KeyPair("shift",SDL_SCANCODE_SHIFT));
+ keys.push_back(KeyPair("rctrl",SDL_SCANCODE_RCTRL));
+ keys.push_back(KeyPair("lctrl",SDL_SCANCODE_LCTRL));
+ //keys.push_back(KeyPair("ctrl",SDL_SCANCODE_CTRL));
+ keys.push_back(KeyPair("ralt",SDL_SCANCODE_RALT));
+ keys.push_back(KeyPair("lalt",SDL_SCANCODE_LALT));
+ keys.push_back(KeyPair("rgui",SDL_SCANCODE_RGUI));
+ keys.push_back(KeyPair("lgui",SDL_SCANCODE_LGUI));
+ keys.push_back(KeyPair("mode",SDL_SCANCODE_MODE));
+ keys.push_back(KeyPair("help",SDL_SCANCODE_HELP));
+ keys.push_back(KeyPair("print",SDL_SCANCODE_PRINTSCREEN));
+ keys.push_back(KeyPair("sysreq",SDL_SCANCODE_SYSREQ));
+ keys.push_back(KeyPair("menu",SDL_SCANCODE_MENU));
+ keys.push_back(KeyPair("power",SDL_SCANCODE_POWER));
+
+ int num_keys = keys.size();
+ for(int i=0; i<num_keys; ++i){
+ const KeyPair &key_pair = keys[i];
+ str_to_key_map[key_pair.first] = key_pair.second;
+ key_to_str_map[key_pair.second] = key_pair.first;
+ }
+
+ str_to_controller_map["RB"] = ControllerInput::RB;
+ str_to_controller_map["LB"] = ControllerInput::LB;
+ str_to_controller_map["A"] = ControllerInput::A;
+ str_to_controller_map["B"] = ControllerInput::B;
+ str_to_controller_map["X"] = ControllerInput::X;
+ str_to_controller_map["Y"] = ControllerInput::Y;
+ str_to_controller_map["START"] = ControllerInput::START;
+ str_to_controller_map["BACK"] = ControllerInput::BACK;
+ str_to_controller_map["GUIDE"] = ControllerInput::GUIDE;
+ str_to_controller_map["L_STICK_X+"] = ControllerInput::L_STICK_XP;
+ str_to_controller_map["L_STICK_X-"] = ControllerInput::L_STICK_XN;
+ str_to_controller_map["L_STICK_Y+"] = ControllerInput::L_STICK_YP;
+ str_to_controller_map["L_STICK_Y-"] = ControllerInput::L_STICK_YN;
+ str_to_controller_map["R_STICK_X+"] = ControllerInput::R_STICK_XP;
+ str_to_controller_map["R_STICK_X-"] = ControllerInput::R_STICK_XN;
+ str_to_controller_map["R_STICK_Y+"] = ControllerInput::R_STICK_YP;
+ str_to_controller_map["R_STICK_Y-"] = ControllerInput::R_STICK_YN;
+ str_to_controller_map["L_TRIGGER"] = ControllerInput::L_TRIGGER;
+ str_to_controller_map["R_TRIGGER"] = ControllerInput::R_TRIGGER;
+ str_to_controller_map["RT"] = ControllerInput::R_TRIGGER; // Backwards compat
+ str_to_controller_map["LT"] = ControllerInput::L_TRIGGER; // Backwards compat
+ str_to_controller_map["R_STICK_PRESSED"] = ControllerInput::R_STICK_PRESSED;
+ str_to_controller_map["L_STICK_PRESSED"] = ControllerInput::L_STICK_PRESSED;
+ str_to_controller_map["D_UP"] = ControllerInput::D_UP;
+ str_to_controller_map["D_DOWN"] = ControllerInput::D_DOWN;
+ str_to_controller_map["D_RIGHT"] = ControllerInput::D_RIGHT;
+ str_to_controller_map["D_LEFT"] = ControllerInput::D_LEFT;
+
+ input_to_string_map["lshift"] = "Shift";
+ input_to_string_map["rshift"] = "Shift";
+ input_to_string_map["space"] = "Space";
+ input_to_string_map["tab"] = "Tab";
+ input_to_string_map["q"] = "Q";
+ input_to_string_map["e"] = "E";
+ input_to_string_map["w"] = "W";
+ input_to_string_map["a"] = "A";
+ input_to_string_map["s"] = "S";
+ input_to_string_map["d"] = "D";
+ input_to_string_map["mouse0"] = "Left mouse button";
+ input_to_string_map["mouse1"] = "Middle mouse button";
+ input_to_string_map["mouse2"] = "Right mouse button";
+}
+
+SDL_Scancode StringToSDLScancode(const std::string &s) {
+ std::map<std::string, SDL_Scancode>::const_iterator iter = str_to_key_map.find(UTF8ToLower(s));
+ if(iter != str_to_key_map.end()){
+ return iter->second;
+ } else {
+ return (SDL_Scancode)SDL_SCANCODE_SYSREQ;
+ }
+}
+
+const char* SDLScancodeToString(SDL_Scancode key) {
+ std::map<SDL_Scancode, const char*>::const_iterator iter = key_to_str_map.find(key);
+ if(iter != key_to_str_map.end()){
+ return iter->second;
+ } else {
+ return NULL;
+ }
+}
+
+const char* SDLKeycodeToString(SDL_Keycode keycode) {
+ SDL_Scancode key = SDL_GetScancodeFromKey(keycode);
+ std::map<SDL_Scancode, const char*>::const_iterator iter = key_to_str_map.find(key);
+ if(iter != key_to_str_map.end()){
+ return iter->second;
+ } else {
+ return NULL;
+ }
+}
+
+std::string StringFromInput(const std::string& input) {
+ InputToStrMap::iterator iter = input_to_string_map.find(input.c_str());
+ if(iter != input_to_string_map.end()) {
+ return iter->second;
+ }
+
+ return input;
+}
+
+std::string SDLLocaleAdjustedStringFromScancode( SDL_Scancode scancode ) {
+ std::string str = std::string(SDL_GetKeyName(SDL_GetKeyFromScancode(scancode)));
+ if( str.empty() ) {
+ str = std::string(SDL_GetScancodeName(scancode));
+ if( str.empty() ) {
+ str = std::string(SDLScancodeToString(scancode));
+ }
+ }
+ return str;
+}
+
+std::string StringFromMouseButton(int button) {
+ switch(button) {
+ case 0:
+ return "Left mouse";
+ case 1:
+ return "Middle mouse";
+ case 2:
+ return "Right mouse";
+ default: {
+ char buffer[32];
+ sprintf(buffer, "Mouse%d", button);
+ return buffer;
+ }
+ }
+}
+
+std::string StringFromMouseString(const std::string& text) {
+ if(text == "mousescrollup") {
+ return "scroll up";
+ } else if(text == "mousescrolldown") {
+ return "scroll down";
+ } else if(text == "mousescrollleft") {
+ return "scroll left";
+ } else if(text == "mousescrollright") {
+ return "scroll right";
+ } else {
+ int button = std::atoi(text.c_str() + 5);
+ return StringFromMouseButton(button);
+ }
+ return "";
+}
+
+std::string StringFromControllerInput(ControllerInput::Input input) {
+ switch(input) {
+ case ControllerInput::A:
+ case ControllerInput::B:
+ case ControllerInput::X:
+ case ControllerInput::Y:
+ case ControllerInput::D_UP:
+ case ControllerInput::D_RIGHT:
+ case ControllerInput::D_DOWN:
+ case ControllerInput::D_LEFT:
+ case ControllerInput::START:
+ case ControllerInput::BACK:
+ case ControllerInput::GUIDE:
+ case ControllerInput::L_STICK_PRESSED:
+ case ControllerInput::R_STICK_PRESSED:
+ case ControllerInput::LB:
+ case ControllerInput::RB:
+ return SDL_GameControllerGetStringForButton(ControllerToSDLControllerButton(input));
+ case ControllerInput::L_STICK_XN: {
+ const static std::string value = std::string(StringFromControllerInput(ControllerInput::L_STICK_X)) + "-";
+ return value.c_str();
+ }
+ case ControllerInput::L_STICK_XP: {
+ const static std::string value = std::string(StringFromControllerInput(ControllerInput::L_STICK_X)) + "+";
+ return value.c_str();
+ }
+ case ControllerInput::L_STICK_YN: {
+ const static std::string value = std::string(StringFromControllerInput(ControllerInput::L_STICK_Y)) + "-";
+ return value.c_str();
+ }
+ case ControllerInput::L_STICK_YP: {
+ const static std::string value = std::string(StringFromControllerInput(ControllerInput::L_STICK_Y)) + "+";
+ return value.c_str();
+ }
+ case ControllerInput::R_STICK_XN: {
+ const static std::string value = std::string(StringFromControllerInput(ControllerInput::R_STICK_X)) + "-";
+ return value.c_str();
+ }
+ case ControllerInput::R_STICK_XP: {
+ const static std::string value = std::string(StringFromControllerInput(ControllerInput::R_STICK_X)) + "+";
+ return value.c_str();
+ }
+ case ControllerInput::R_STICK_YN: {
+ const static std::string value = std::string(StringFromControllerInput(ControllerInput::R_STICK_Y)) + "-";
+ return value.c_str();
+ }
+ case ControllerInput::R_STICK_YP: {
+ const static std::string value = std::string(StringFromControllerInput(ControllerInput::R_STICK_Y)) + "+";
+ return value.c_str();
+ }
+ case ControllerInput::L_STICK_X:
+ case ControllerInput::L_STICK_Y:
+ case ControllerInput::R_STICK_X:
+ case ControllerInput::R_STICK_Y:
+ case ControllerInput::L_TRIGGER:
+ case ControllerInput::R_TRIGGER:
+ return SDL_GameControllerGetStringForAxis(ControllerToSDLControllerAxis(input));
+ default:
+ return "UNKNOWN KEY";
+ }
+}
+
+ControllerInput::Input SDLControllerButtonToController(SDL_GameControllerButton button) {
+ switch(button) {
+ case SDL_CONTROLLER_BUTTON_A: return ControllerInput::A;
+ case SDL_CONTROLLER_BUTTON_B: return ControllerInput::B;
+ case SDL_CONTROLLER_BUTTON_X: return ControllerInput::X;
+ case SDL_CONTROLLER_BUTTON_Y: return ControllerInput::Y;
+ case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: return ControllerInput::LB;
+ case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: return ControllerInput::RB;
+ case SDL_CONTROLLER_BUTTON_BACK: return ControllerInput::BACK;
+ case SDL_CONTROLLER_BUTTON_GUIDE: return ControllerInput::GUIDE;
+ case SDL_CONTROLLER_BUTTON_START: return ControllerInput::START;
+ case SDL_CONTROLLER_BUTTON_LEFTSTICK: return ControllerInput::L_STICK_PRESSED;
+ case SDL_CONTROLLER_BUTTON_RIGHTSTICK: return ControllerInput::R_STICK_PRESSED;
+ case SDL_CONTROLLER_BUTTON_DPAD_UP: return ControllerInput::D_UP;
+ case SDL_CONTROLLER_BUTTON_DPAD_DOWN: return ControllerInput::D_DOWN;
+ case SDL_CONTROLLER_BUTTON_DPAD_LEFT: return ControllerInput::D_LEFT;
+ case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return ControllerInput::D_RIGHT;
+ case SDL_CONTROLLER_BUTTON_INVALID:
+ LOGW << "Got unhandled SDL_CONTROLLER_BUTTON_INVALID" << std::endl;
+ return ControllerInput::NONE;
+ case SDL_CONTROLLER_BUTTON_MAX:
+ LOGW << "Got unhandled SDL_CONTROLLER_BUTTON_MAX" << std::endl;
+ return ControllerInput::NONE;
+ default:
+ LOGW << "Got default" << std::endl;
+ return ControllerInput::NONE;
+ }
+}
+
+ControllerInput::Input SDLControllerAxisToController(SDL_GameControllerAxis axis) {
+ switch(axis) {
+ case SDL_CONTROLLER_AXIS_LEFTX: return ControllerInput::L_STICK_X;
+ case SDL_CONTROLLER_AXIS_LEFTY: return ControllerInput::L_STICK_Y;
+ case SDL_CONTROLLER_AXIS_RIGHTX: return ControllerInput::R_STICK_X;
+ case SDL_CONTROLLER_AXIS_RIGHTY: return ControllerInput::R_STICK_Y;
+ case SDL_CONTROLLER_AXIS_TRIGGERLEFT: return ControllerInput::L_TRIGGER;
+ case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: return ControllerInput::R_TRIGGER;
+ case SDL_CONTROLLER_AXIS_INVALID:
+ LOGW << "Got unhandled SDL_CONTROLLER_AXIS_INVALID" << std::endl;
+ return ControllerInput::NONE;
+ case SDL_CONTROLLER_AXIS_MAX:
+ LOGW << "Got unhandled SDL_CONTROLLER_AXIS_MAX" << std::endl;
+ return ControllerInput::NONE;
+ default:
+ LOGW << "Got default" << std::endl;
+ return ControllerInput::NONE;
+ }
+}
+
+SDL_GameControllerButton ControllerToSDLControllerButton(ControllerInput::Input input) {
+ switch(input) {
+ case ControllerInput::A: return SDL_CONTROLLER_BUTTON_A;
+ case ControllerInput::B: return SDL_CONTROLLER_BUTTON_B;
+ case ControllerInput::X: return SDL_CONTROLLER_BUTTON_X;
+ case ControllerInput::Y: return SDL_CONTROLLER_BUTTON_Y;
+ case ControllerInput::LB: return SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
+ case ControllerInput::RB: return SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
+ case ControllerInput::BACK: return SDL_CONTROLLER_BUTTON_BACK;
+ case ControllerInput::GUIDE: return SDL_CONTROLLER_BUTTON_GUIDE;
+ case ControllerInput::START: return SDL_CONTROLLER_BUTTON_START;
+ case ControllerInput::L_STICK_PRESSED: return SDL_CONTROLLER_BUTTON_LEFTSTICK;
+ case ControllerInput::R_STICK_PRESSED: return SDL_CONTROLLER_BUTTON_RIGHTSTICK;
+ case ControllerInput::D_UP: return SDL_CONTROLLER_BUTTON_DPAD_UP;
+ case ControllerInput::D_DOWN: return SDL_CONTROLLER_BUTTON_DPAD_DOWN;
+ case ControllerInput::D_LEFT: return SDL_CONTROLLER_BUTTON_DPAD_LEFT;
+ case ControllerInput::D_RIGHT: return SDL_CONTROLLER_BUTTON_DPAD_RIGHT;
+ case ControllerInput::NONE:
+ LOGW << "Got unhandled NONE" << std::endl;
+ return SDL_CONTROLLER_BUTTON_INVALID;
+ default:
+ LOGW << "Got unhandled default" << std::endl;
+ return SDL_CONTROLLER_BUTTON_INVALID;
+ }
+}
+
+SDL_GameControllerAxis ControllerToSDLControllerAxis(ControllerInput::Input input) {
+ switch(input) {
+ case ControllerInput::L_STICK_XN:
+ case ControllerInput::L_STICK_XP:
+ case ControllerInput::L_STICK_X:
+ return SDL_CONTROLLER_AXIS_LEFTX;
+ case ControllerInput::L_STICK_YN:
+ case ControllerInput::L_STICK_YP:
+ case ControllerInput::L_STICK_Y:
+ return SDL_CONTROLLER_AXIS_LEFTY;
+ case ControllerInput::R_STICK_XN:
+ case ControllerInput::R_STICK_XP:
+ case ControllerInput::R_STICK_X:
+ return SDL_CONTROLLER_AXIS_RIGHTX;
+ case ControllerInput::R_STICK_YN:
+ case ControllerInput::R_STICK_YP:
+ case ControllerInput::R_STICK_Y:
+ return SDL_CONTROLLER_AXIS_RIGHTY;
+ case ControllerInput::L_TRIGGER: return SDL_CONTROLLER_AXIS_TRIGGERLEFT;
+ case ControllerInput::R_TRIGGER: return SDL_CONTROLLER_AXIS_TRIGGERRIGHT;
+ case ControllerInput::NONE:
+ LOGW << "Got unhandled NONE" << std::endl;
+ return SDL_CONTROLLER_AXIS_INVALID;
+ default:
+ LOGW << "Got unhandled default" << std::endl;
+ return SDL_CONTROLLER_AXIS_INVALID;
+ }
+}
+
+ControllerInput::Input SDLStringToController(const char* string) {
+ SDL_GameControllerButton button = SDL_GameControllerGetButtonFromString(string);
+ if(button != SDL_CONTROLLER_BUTTON_INVALID) {
+ return SDLControllerButtonToController(button);
+ }
+ char buffer[128];
+ strcpy(buffer, string);
+ int last = strlen(buffer) - 1;
+ if(last > 0) {
+ if(buffer[last] == '+' || buffer[last] == '-') {
+ buffer[last] = '\0';
+ }
+ SDL_GameControllerAxis axis = SDL_GameControllerGetAxisFromString(buffer);
+ if(axis != SDL_CONTROLLER_AXIS_INVALID) {
+ ControllerInput::Input input = SDLControllerAxisToController(axis);
+ switch(input) {
+ case ControllerInput::L_STICK_X:
+ if(string[last] == '+') {
+ return ControllerInput::L_STICK_XP;
+ } else if(string[last] == '-') {
+ return ControllerInput::L_STICK_XN;
+ } else {
+ return input;
+ }
+ case ControllerInput::L_STICK_Y:
+ if(string[last] == '+') {
+ return ControllerInput::L_STICK_YP;
+ } else if(string[last] == '-') {
+ return ControllerInput::L_STICK_YN;
+ } else {
+ return input;
+ }
+ case ControllerInput::R_STICK_X:
+ if(string[last] == '+') {
+ return ControllerInput::R_STICK_XP;
+ } else if(string[last] == '-') {
+ return ControllerInput::R_STICK_XN;
+ } else {
+ return input;
+ }
+ case ControllerInput::R_STICK_Y:
+ if(string[last] == '+') {
+ return ControllerInput::R_STICK_YP;
+ } else if(string[last] == '-') {
+ return ControllerInput::R_STICK_YN;
+ } else {
+ return input;
+ }
+ default:
+ return input;
+ }
+ }
+ }
+
+ LOGW << "Tried converting an invalid string \"" << string << "\" to a controller input" << std::endl;
+ return ControllerInput::NONE;
+}
diff --git a/Source/UserInput/keyTranslator.h b/Source/UserInput/keyTranslator.h
new file mode 100644
index 00000000..c61dbd5e
--- /dev/null
+++ b/Source/UserInput/keyTranslator.h
@@ -0,0 +1,92 @@
+//-----------------------------------------------------------------------------
+// Name: keyTranslator.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <SDL.h>
+
+#include <string>
+
+// Represents a combination of controller axes and buttons
+namespace ControllerInput {
+ enum Input {
+ A = 0,
+ B,
+ X,
+ Y,
+ D_UP,
+ D_RIGHT,
+ D_DOWN,
+ D_LEFT,
+ START,
+ BACK,
+ GUIDE,
+ L_STICK_PRESSED,
+ R_STICK_PRESSED,
+ LB,
+ RB,
+ L_STICK_X,
+ L_STICK_Y,
+ R_STICK_X,
+ R_STICK_Y,
+ L_TRIGGER,
+ R_TRIGGER,
+ NUM_INPUTS,
+ // There are constraints upon existing inputs, therefore they should not
+ // count to the number of available inputs.
+ L_STICK_XN,
+ L_STICK_XP,
+ L_STICK_YN,
+ L_STICK_YP,
+ R_STICK_XN,
+ R_STICK_XP,
+ R_STICK_YN,
+ R_STICK_YP,
+ NONE
+ };
+}
+
+void InitKeyTranslator();
+// Translate to human readable strings
+std::string SDLLocaleAdjustedStringFromScancode( SDL_Scancode scancode );
+std::string StringFromMouseButton(int button);
+std::string StringFromMouseString(const std::string& text); // e.g. mouse0 -> Left Mouse Button
+std::string StringFromControllerInput(ControllerInput::Input input);
+std::string StringFromInput(const std::string& input); // e.g. lshift -> Shift
+
+SDL_Scancode StringToSDLScancode(const std::string &s);
+/*#ifdef WIN32
+ int XBoxToXInput(const std::string &s);
+#endif*/
+const char* SDLScancodeToString(SDL_Scancode key);
+const char* SDLKeycodeToString(SDL_Keycode key);
+
+// Convert between internal values
+ControllerInput::Input SDLControllerButtonToController(SDL_GameControllerButton button);
+ControllerInput::Input SDLControllerAxisToController(SDL_GameControllerAxis axis);
+
+SDL_GameControllerButton ControllerToSDLControllerButton(ControllerInput::Input input);
+SDL_GameControllerAxis ControllerToSDLControllerAxis(ControllerInput::Input input);
+
+// SDL string, sort of. Converts leftx+, leftx-, etc. as well, even though these
+// are not directory SDL strings.
+ControllerInput::Input SDLStringToController(const char* string);
diff --git a/Source/UserInput/keyboard.cpp b/Source/UserInput/keyboard.cpp
new file mode 100644
index 00000000..3cb4caf9
--- /dev/null
+++ b/Source/UserInput/keyboard.cpp
@@ -0,0 +1,439 @@
+//-----------------------------------------------------------------------------
+// Name: keyboard.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "keyboard.h"
+
+#include <Internal/timer.h>
+#include <Logging/logdata.h>
+
+#include <SDL.h>
+
+//-----------------------------------------------------------------------------
+//Functions
+//-----------------------------------------------------------------------------
+
+InputModeStackElement::InputModeStackElement() : id(-1),mask(0U)
+{
+
+}
+
+Keyboard::Keyboard() :
+ key_queue_length(0),
+ keycode_input_buffer_sequence_counter(0),
+ keycode_input_buffer_count(0),
+ last_key_delay(0.0f),
+ polled(true),
+ input_mode(KIMF_ANY)
+{
+ ::memset(key_queue, 0, sizeof(key_queue));
+ last_key.sym = SDLK_UNKNOWN;
+ last_key.scancode = SDL_SCANCODE_UNKNOWN;
+}
+
+void Keyboard::addKeyQueue( SDL_Keysym which_key ) {
+ SDL_Keycode keycode = which_key.sym;
+ if(strlen(key_queue)<250){
+ if(strlen(SDL_GetKeyName(keycode))==1){
+ key_queue[key_queue_length]=SDL_GetKeyName(keycode)[0];
+ if(keys[SDL_SCANCODE_LSHIFT].down||keys[SDL_SCANCODE_RSHIFT].down){
+ if(key_queue[key_queue_length]>='a'
+ &&key_queue[key_queue_length]<='z'){
+ key_queue[key_queue_length]-=32;
+ }
+ if(key_queue[key_queue_length]=='1'){
+ key_queue[key_queue_length]='!';
+ }
+ if(key_queue[key_queue_length]=='2'){
+ key_queue[key_queue_length]='@';
+ }
+ if(key_queue[key_queue_length]=='3'){
+ key_queue[key_queue_length]='#';
+ }
+ if(key_queue[key_queue_length]=='4'){
+ key_queue[key_queue_length]='$';
+ }
+ if(key_queue[key_queue_length]=='5'){
+ key_queue[key_queue_length]='%';
+ }
+ if(key_queue[key_queue_length]=='6'){
+ key_queue[key_queue_length]='^';
+ }
+ if(key_queue[key_queue_length]=='7'){
+ key_queue[key_queue_length]='&';
+ }
+ if(key_queue[key_queue_length]=='8'){
+ key_queue[key_queue_length]='*';
+ }
+ if(key_queue[key_queue_length]=='9'){
+ key_queue[key_queue_length]='(';
+ }
+ if(key_queue[key_queue_length]=='0'){
+ key_queue[key_queue_length]=')';
+ }
+ if(key_queue[key_queue_length]=='`'){
+ key_queue[key_queue_length]='~';
+ }
+ if(key_queue[key_queue_length]=='-'){
+ key_queue[key_queue_length]='_';
+ }
+ if(key_queue[key_queue_length]=='='){
+ key_queue[key_queue_length]='+';
+ }
+ if(key_queue[key_queue_length]=='['){
+ key_queue[key_queue_length]='{';
+ }
+ if(key_queue[key_queue_length]==']'){
+ key_queue[key_queue_length]='}';
+ }
+ if(key_queue[key_queue_length]=='\\'){
+ key_queue[key_queue_length]='|';
+ }
+ if(key_queue[key_queue_length]==';'){
+ key_queue[key_queue_length]=':';
+ }
+ if(key_queue[key_queue_length]=='\''){
+ key_queue[key_queue_length]='"';
+ }
+ if(key_queue[key_queue_length]==','){
+ key_queue[key_queue_length]='<';
+ }
+ if(key_queue[key_queue_length]=='.'){
+ key_queue[key_queue_length]='>';
+ }
+ if(key_queue[key_queue_length]=='/'){
+ key_queue[key_queue_length]='?';
+ }
+ }
+ if(key_queue[key_queue_length]=='\\'){
+ key_queue_length++;
+ key_queue[key_queue_length]='\\';
+ }
+ key_queue_length++;
+ }
+ else if(keycode == SDLK_SPACE){
+ key_queue[key_queue_length]=' ';
+ key_queue_length++;
+ }
+ else if(keycode == SDLK_KP_0){
+ key_queue[key_queue_length]='0';
+ key_queue_length++;
+ }
+ else if(keycode == SDLK_KP_1){
+ key_queue[key_queue_length]='1';
+ key_queue_length++;
+ }
+ else if(keycode == SDLK_KP_2){
+ key_queue[key_queue_length]='2';
+ key_queue_length++;
+ }
+ else if(keycode == SDLK_KP_3){
+ key_queue[key_queue_length]='3';
+ key_queue_length++;
+ }
+ else if(keycode == SDLK_KP_4){
+ key_queue[key_queue_length]='4';
+ key_queue_length++;
+ }
+ else if(keycode == SDLK_KP_5){
+ key_queue[key_queue_length]='5';
+ key_queue_length++;
+ }
+ else if(keycode == SDLK_KP_6){
+ key_queue[key_queue_length]='6';
+ key_queue_length++;
+ }
+ else if(keycode == SDLK_KP_7){
+ key_queue[key_queue_length]='7';
+ key_queue_length++;
+ }
+ else if(keycode == SDLK_KP_8){
+ key_queue[key_queue_length]='8';
+ key_queue_length++;
+ }
+ else if(keycode == SDLK_KP_9){
+ key_queue[key_queue_length]='9';
+ key_queue_length++;
+ }
+ else if(keycode == SDLK_KP_PERIOD){
+ key_queue[key_queue_length]='.';
+ key_queue_length++;
+ }
+ else if(keycode == SDLK_KP_DIVIDE){
+ key_queue[key_queue_length]='/';
+ key_queue_length++;
+ }
+ else if(keycode == SDLK_KP_MULTIPLY){
+ key_queue[key_queue_length]='*';
+ key_queue_length++;
+ }
+ else if(keycode == SDLK_KP_MINUS){
+ key_queue[key_queue_length]='-';
+ key_queue_length++;
+ }
+ else if(keycode == SDLK_KP_PLUS){
+ key_queue[key_queue_length]='+';
+ key_queue_length++;
+ }
+ else if(keycode == SDLK_KP_EQUALS){
+ key_queue[key_queue_length]='=';
+ key_queue_length++;
+ }
+ else if(keycode == SDLK_BACKSPACE){
+ key_queue[key_queue_length]='\\';
+ key_queue_length++;
+ key_queue[key_queue_length]='<';
+ key_queue_length++;
+ }
+ else if(keycode == SDLK_DELETE){
+ key_queue[key_queue_length]='\\';
+ key_queue_length++;
+ key_queue[key_queue_length]='>';
+ key_queue_length++;
+ }
+ else if(keycode == SDLK_LEFT){
+ key_queue[key_queue_length]='\\';
+ key_queue_length++;
+ key_queue[key_queue_length]='a';
+ key_queue_length++;
+ }
+ else if(keycode == SDLK_UP){
+ key_queue[key_queue_length]='\\';
+ key_queue_length++;
+ key_queue[key_queue_length]='w';
+ key_queue_length++;
+ }
+ else if(keycode == SDLK_DOWN){
+ key_queue[key_queue_length]='\\';
+ key_queue_length++;
+ key_queue[key_queue_length]='s';
+ key_queue_length++;
+ }
+ else if(keycode == SDLK_RIGHT){
+ key_queue[key_queue_length]='\\';
+ key_queue_length++;
+ key_queue[key_queue_length]='d';
+ key_queue_length++;
+ }
+ else if(keycode == SDLK_RETURN){
+ key_queue[key_queue_length]='\\';
+ key_queue_length++;
+ key_queue[key_queue_length]='n';
+ key_queue_length++;
+ }
+ }
+}
+
+void Keyboard::addKeyCodeBufferItem( SDL_Keysym which_key ) {
+ if( keycode_input_buffer_count >= keycode_input_buffer_size ) {
+ for( unsigned i = 1; i < keycode_input_buffer_count; i++ ) {
+ keycode_input_buffer[i-1] = keycode_input_buffer[i];
+ }
+ keycode_input_buffer_count--;
+ }
+
+ keycode_input_buffer[keycode_input_buffer_count].s_id = keycode_input_buffer_sequence_counter++;
+ keycode_input_buffer[keycode_input_buffer_count].keycode = which_key.sym;
+ keycode_input_buffer[keycode_input_buffer_count].scancode = which_key.scancode;
+ keycode_input_buffer[keycode_input_buffer_count].mod = which_key.mod;
+ keycode_input_buffer_count++;
+}
+
+//Handle key presses
+void Keyboard::handleKeyDown( SDL_Keysym the_key ) {
+ KeyStatus &key = keys[the_key.scancode];
+ if(!key.down){
+ key.down = true;
+ key.pressed = true;
+ }
+ last_key = the_key;
+ last_key_delay = 0.6f;
+ addKeyQueue(the_key);
+}
+
+void Keyboard::handleKeyDownFirst( SDL_Keysym the_key ) {
+ addKeyCodeBufferItem(the_key);
+}
+
+void Keyboard::Update(float timestep) {
+ KeyStatus &key = keys[last_key.scancode];
+ if(key.down){
+ last_key_delay -= timestep;
+ while(last_key_delay<0){
+ addKeyQueue(last_key);
+ last_key_delay+=.05f;
+ }
+ }
+}
+
+//Handle key releases
+void Keyboard::handleKeyUp( SDL_Keysym the_key ) {
+ KeyStatus &key = keys[the_key.scancode];
+ key.down = false;
+}
+
+//Check if a key is pressed
+bool Keyboard::isKeycodeDown(SDL_Keycode which_key, const uint32_t kimf ) const {
+ SDL_Scancode sc = SDL_GetScancodeFromKey(which_key);
+ if( kimf & input_mode ) {
+ switch(which_key){
+ case SDLK_CTRL:
+ return isKeycodeDown(SDLK_LCTRL,kimf) || isKeycodeDown(SDLK_RCTRL,kimf);
+ case SDLK_ALT:
+ return isKeycodeDown(SDLK_LALT,kimf) || isKeycodeDown(SDLK_RALT,kimf);
+ case SDLK_GUI:
+ return isKeycodeDown(SDLK_LGUI,kimf) || isKeycodeDown(SDLK_RGUI,kimf);
+ case SDLK_SHIFT:
+ return isKeycodeDown(SDLK_LSHIFT,kimf) || isKeycodeDown(SDLK_RSHIFT,kimf);
+ default: {
+ KeyStatusMap::const_iterator iter = keys.find(sc);
+ if(iter != keys.end()) {
+ const KeyStatus &key = iter->second;
+ return key.down;
+ } else {
+ return false;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+bool Keyboard::isScancodeDown(SDL_Scancode which_key, const uint32_t kimf ) const {
+ KeyStatusMap::const_iterator iter = keys.find(which_key);
+ if(iter != keys.end()) {
+ const KeyStatus &key = iter->second;
+ return key.down;
+ } else {
+ return false;
+ }
+}
+
+bool Keyboard::isKeycodeCombinationDown(int key_combo, SDL_Keycode which_key, const uint32_t kimf) const {
+ bool shift = false;
+ bool ctrl = false;
+ bool gui = false;
+ if (key_combo & SHIFT) shift = true;
+ if (key_combo & CTRL) ctrl = true;
+ if (key_combo & GUI) gui = true;
+
+ return (!ctrl || isKeycodeDown(SDLK_CTRL,kimf)) &&
+ (!shift || isKeycodeDown(SDLK_SHIFT,kimf)) &&
+ (!gui || isKeycodeDown(SDLK_GUI,kimf)) &&
+ isKeycodeDown(which_key, kimf);
+}
+
+// Was the key combination pressed since we last checked?
+// note: only zeros which_key
+bool Keyboard::wasKeycodeCombinationPressed(int key_combo, SDL_Keycode which_key, const uint32_t kimf) const {
+ bool shift = false;
+ bool ctrl = false;
+ bool gui = false;
+ if (key_combo & SHIFT) shift = true;
+ if (key_combo & CTRL) ctrl = true;
+ if (key_combo & GUI) gui = true;
+
+ return (!ctrl || isKeycodeDown(SDLK_CTRL,kimf)) &&
+ (!shift || isKeycodeDown(SDLK_SHIFT,kimf)) &&
+ (!gui || isKeycodeDown(SDLK_GUI,kimf)) &&
+ wasKeycodePressed(which_key,kimf);
+}
+
+bool Keyboard::isScancodeCombinationDown(int key_combo, SDL_Scancode which_key, const uint32_t kimf) const {
+ bool shift = false;
+ bool ctrl = false;
+ bool gui = false;
+ if (key_combo & SHIFT) shift = true;
+ if (key_combo & CTRL) ctrl = true;
+ if (key_combo & GUI) gui = true;
+
+ return (!ctrl || isKeycodeDown(SDLK_CTRL,kimf)) &&
+ (!shift || isKeycodeDown(SDLK_SHIFT,kimf)) &&
+ (!gui || isKeycodeDown(SDLK_GUI,kimf)) &&
+ isScancodeDown(which_key, kimf);
+}
+
+//Was the key pressed since we last checked?
+bool Keyboard::wasKeycodePressed(SDL_Keycode the_key, const uint32_t kimf) const {
+ SDL_Scancode sc = SDL_GetScancodeFromKey(the_key);
+ if(kimf & input_mode) {
+ KeyStatusMap::const_iterator iter = keys.find(sc);
+ if(iter != keys.end()){
+ const KeyStatus &key = iter->second;
+ return key.pressed;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+}
+
+//Was the key pressed since we last checked?
+bool Keyboard::wasScancodePressed(SDL_Scancode the_key, const uint32_t kimf) const {
+ if(kimf & input_mode) {
+ KeyStatusMap::const_iterator iter = keys.find(the_key);
+ if(iter != keys.end()){
+ const KeyStatus &key = iter->second;
+ return key.pressed;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+}
+
+void Keyboard::clearKeyPresses() {
+ for (KeyStatusMap::iterator iter = keys.begin(); iter != keys.end(); ++iter){
+ KeyStatus &key = iter->second;
+ key.pressed=0;
+ }
+}
+
+void Keyboard::clearBasicKeyPresses() {
+ if(!isKeycodeDown(SDLK_CTRL, KIMF_ANY) &&
+ !isKeycodeDown(SDLK_ALT, KIMF_ANY) &&
+ !isKeycodeDown(SDLK_GUI, KIMF_ANY))
+ {
+ clearKeyPresses();
+ }
+}
+
+void Keyboard::SetMode( const uint32_t kimf )
+{
+ input_mode = kimf;
+}
+
+uint32_t Keyboard::GetModes() const
+{
+ return input_mode;
+}
+
+std::vector<Keyboard::KeyboardPress> Keyboard::GetKeyboardInputs() {
+ std::vector<Keyboard::KeyboardPress> presses;
+ presses.resize(keycode_input_buffer_count);
+ for( unsigned i = 0; i < keycode_input_buffer_count; i++ ) {
+ presses[i] = keycode_input_buffer[i];
+ }
+ return presses;
+}
+
diff --git a/Source/UserInput/keyboard.h b/Source/UserInput/keyboard.h
new file mode 100644
index 00000000..781293ca
--- /dev/null
+++ b/Source/UserInput/keyboard.h
@@ -0,0 +1,130 @@
+//-----------------------------------------------------------------------------
+// Name: keyboard.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/integer.h>
+
+#include <SDL.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+//-----------------------------------------------------------------------------
+// Class Definition
+//-----------------------------------------------------------------------------
+
+enum KeyboardInputModeFlag
+{
+ KIMF_NO = 0,
+ KIMF_MENU = 1UL << 0,
+ KIMF_PLAYING = 1UL << 1,
+ KIMF_LEVEL_EDITOR_GENERAL = 1UL << 2,
+ KIMF_LEVEL_EDITOR_DIALOGUE_EDITOR = 1UL << 3,
+ KIMF_GUI_GENERAL = 1UL << 4,
+ KIMF_ANY = 0x0FFFFFFF
+};
+
+const int SDLK_CTRL = 1000;
+const int SDLK_SHIFT = 1001;
+const int SDLK_GUI = 1002;
+const int SDLK_ALT = 1003;
+
+struct KeyStatus {
+ bool down;
+ bool pressed;
+ KeyStatus():
+ down(false),
+ pressed(false)
+ {}
+};
+
+struct InputModeStackElement
+{
+ InputModeStackElement();
+
+ int32_t id;
+ uint32_t mask;
+};
+
+class Keyboard
+{
+ private:
+ uint32_t input_mode;
+ public:
+ typedef std::map<SDL_Scancode, KeyStatus> KeyStatusMap;
+ KeyStatusMap keys;
+
+ enum KeyComboFlags {
+ SHIFT = 0x10000000,
+ CTRL = 0x01000000,
+ GUI = 0x00100000
+ };
+ char key_queue[256];
+ int key_queue_length;
+
+ static const size_t keycode_input_buffer_size = 16;
+ uint16_t keycode_input_buffer_sequence_counter;
+ struct KeyboardPress {
+ uint16_t s_id;
+ uint32_t keycode;
+ uint32_t scancode;
+ uint16_t mod;
+ };
+ KeyboardPress keycode_input_buffer[keycode_input_buffer_size];
+ size_t keycode_input_buffer_count;
+
+ std::vector<KeyboardPress> GetKeyboardInputs();
+ SDL_Keysym last_key;
+ float last_key_delay;
+ bool polled;
+
+ Keyboard();
+
+ void addKeyQueue( SDL_Keysym which_key );
+ void addKeyCodeBufferItem( SDL_Keysym which_key );
+ void Update(float timestep);
+
+ void handleKeyDown( SDL_Keysym the_key );
+ void handleKeyDownFirst( SDL_Keysym the_key );
+ void handleKeyUp( SDL_Keysym the_key );
+
+ bool isKeycodeDown(SDL_Keycode which_key, const uint32_t kimf ) const;
+ bool isScancodeDown(SDL_Scancode which_key, const uint32_t kimf) const;
+
+ bool isKeycodeCombinationDown(int key_combo, SDL_Keycode which_key, const uint32_t kimf) const;
+ bool isScancodeCombinationDown(int key_combo, SDL_Scancode which_key, const uint32_t kimf) const;
+
+ bool wasKeycodeCombinationPressed(int key_combo, SDL_Keycode which_key, const uint32_t kimf) const;
+ bool wasScancodeCombinationPressed(int key_combo, SDL_Scancode which_key, const uint32_t kimf) const;
+
+ bool wasKeycodePressed(SDL_Keycode which_key,const uint32_t kimf) const;
+ bool wasScancodePressed(SDL_Scancode which_key,const uint32_t kimf) const;
+
+ void clearKeyPresses();
+ void clearBasicKeyPresses();
+
+ void SetMode( const uint32_t kimf );
+ uint32_t GetModes() const;
+
+};
diff --git a/Source/UserInput/keycommands.cpp b/Source/UserInput/keycommands.cpp
new file mode 100644
index 00000000..9a66ca82
--- /dev/null
+++ b/Source/UserInput/keycommands.cpp
@@ -0,0 +1,406 @@
+//-----------------------------------------------------------------------------
+// Name: keycommands.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "keycommands.h"
+
+#include <Internal/error.h>
+#include <Internal/checksum.h>
+#include <Internal/common.h>
+
+#include <UserInput/keyboard.h>
+#include <UserInput/keyTranslator.h>
+
+#include <Logging/logdata.h>
+
+#include <SDL.h>
+
+#include <algorithm>
+
+using namespace KeyCommand;
+
+// Array of key commands, one for each label
+static Command commands[kNumLabels];
+static Command *commands_sorted_by_length[kNumLabels];
+typedef std::pair<unsigned long, int> HashInt;
+typedef std::vector<HashInt> HashIntVector;
+HashIntVector hash_to_command;
+
+struct HashIntCompare {
+ bool operator()(const HashInt& a, const HashInt& b){
+ return a.first < b.first;
+ }
+ bool operator()(const HashInt& a, unsigned long b){
+ return a.first < b;
+ }
+};
+
+Command::Command(): num_key_events(0), state(kUp)
+{
+ display_text[0] = '\0';
+}
+
+bool KeyCommand::CheckPressed( const Keyboard& keyboard, int label, const uint32_t kimf ) {
+ if( kimf & keyboard.GetModes() )
+ {
+ if(commands[label].num_key_events == 0){
+ return false;
+ } else {
+ return commands[label].state == kPressed;
+ }
+ }
+ else
+ {
+ return false;
+ }
+}
+
+#ifdef PLATFORM_MACOSX
+ const int command_key = Keyboard::GUI;
+ const SDL_Scancode command_SDL_SCANCODE_LEFT_key = SDL_SCANCODE_LGUI;
+ const SDL_Scancode command_SDL_SCANCODE_RIGHT_key = SDL_SCANCODE_RGUI;
+#else
+ const int command_key = Keyboard::CTRL;
+ const SDL_Scancode command_SDL_SCANCODE_LEFT_key = SDL_SCANCODE_LCTRL;
+ const SDL_Scancode command_SDL_SCANCODE_RIGHT_key = SDL_SCANCODE_RCTRL;
+#endif
+
+bool KeyCommand::CheckDown( const Keyboard& keyboard, int label, const uint32_t kimf ) {
+ if( kimf & keyboard.GetModes() )
+ {
+ if(commands[label].num_key_events == 0){
+ return false;
+ } else {
+ const Command &cmd = commands[label];
+ for(int i=0, len=cmd.num_key_events; i<len; ++i){
+ const KeyEvent &key_event = cmd.key_events[i];
+ bool key_valid = false;
+ switch(key_event.type_){
+ case kPressed:
+ case kDown:
+ if( key_event.sc_ == key_event.sc2_ ) {
+ key_valid = keyboard.isScancodeDown(key_event.sc_, KIMF_ANY);
+ } else {
+ key_valid = keyboard.isScancodeDown(key_event.sc_, KIMF_ANY) || keyboard.isScancodeDown(key_event.sc2_, KIMF_ANY);
+ }
+ break;
+ case kUp:
+ if( key_event.sc_ == key_event.sc2_ ) {
+ key_valid = !keyboard.isScancodeDown(key_event.sc_, KIMF_ANY);
+ } else {
+ key_valid = !keyboard.isScancodeDown(key_event.sc_, KIMF_ANY) && !keyboard.isScancodeDown(key_event.sc2_, KIMF_ANY);
+ }
+ break;
+ }
+ if(!key_valid){
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ else
+ {
+ return false;
+ }
+}
+
+const char* KeyCommand::GetDisplayText(int label) {
+ return commands[label].display_text;
+}
+
+bool CompareSubstr(const char* str, const char* substr, int substr_len){
+ int i=0;
+ while(str[i] != '\0' && substr[i] != '\0' && i < substr_len){
+ if(str[i] != substr[i]){
+ return false;
+ }
+ ++i;
+ }
+ return str[i] == '\0' && i == substr_len;
+}
+
+void KeyCommand::BindString(const char* bind_str) {
+ KeyEvent events[kMaxKeyEvents];
+ enum ParseStage {kInputs, kCommand};
+ ParseStage parse_stage = kInputs;
+ int num_events = 0;
+ int i = 0;
+ int start_token = 0;
+ std::string str;
+ std::string display;
+ str.reserve(255);
+ display.reserve(255);
+ while(true){
+ char c = bind_str[i];
+ if(parse_stage == kInputs){
+ if(c == '+' || c == ':'){
+ if(c == ':'){
+ parse_stage = kCommand;
+ }
+ if(num_events >= kMaxKeyEvents){
+ FatalError("Error", "Too many key events in %s", bind_str);
+ }
+ // ignore white space at start or end
+ while(bind_str[start_token] == ' ' || bind_str[start_token] == '\t'){
+ ++start_token;
+ }
+ int end_token = i-1;
+ while(bind_str[end_token] == ' ' || bind_str[end_token] == '\t'){
+ --end_token;
+ }
+ // Read token and attempt to parse key name
+ int token_len = end_token-start_token+1;
+ if(CompareSubstr("cmd", &bind_str[start_token], token_len)){
+#ifdef __MACOSX__
+ display += "Cmd";
+#else
+ display += "Ctrl";
+#endif
+ events[num_events].sc_ = command_SDL_SCANCODE_LEFT_key;
+ events[num_events].sc2_ = command_SDL_SCANCODE_RIGHT_key;
+ events[num_events].type_ = kDown;
+ ++num_events;
+ } else if(CompareSubstr("shift", &bind_str[start_token], token_len)){
+ display += "Shift";
+ events[num_events].sc_ = SDL_SCANCODE_LSHIFT;
+ events[num_events].sc2_ = SDL_SCANCODE_RSHIFT;
+ events[num_events].type_ = kDown;
+ ++num_events;
+ } else if(CompareSubstr("alt", &bind_str[start_token], token_len)){
+#ifdef __MACOSX__
+ display += "Option";
+#else
+ display += "Alt";
+#endif
+ events[num_events].sc_ = SDL_SCANCODE_LALT;
+ events[num_events].sc2_ = SDL_SCANCODE_RALT;
+ events[num_events].type_ = kDown;
+ ++num_events;
+ } else if(CompareSubstr("ctrl", &bind_str[start_token], token_len)){
+ display += "Ctrl";
+ events[num_events].sc_ = SDL_SCANCODE_LCTRL;
+ events[num_events].sc2_ = SDL_SCANCODE_RCTRL;
+ events[num_events].type_ = kDown;
+ ++num_events;
+ } else {
+ str.clear();
+ for(int j=start_token; j<=end_token; ++j){
+ str += bind_str[j];
+ }
+ if(str.length() == 1 && str[0] >= 'a' && str[0] <= 'z'){
+ display += (str[0] + ('A' - 'a'));
+ } else {
+ display += str;
+ }
+ SDL_Scancode key = StringToSDLScancode(str);
+ if(key == SDL_SCANCODE_SYSREQ){
+ FatalError("Error", "Unknown key: %s", str.c_str());
+ } else {
+ events[num_events].sc_ = key;
+ events[num_events].sc2_ = key;
+ events[num_events].type_ = kPressed;
+ ++num_events;
+ }
+ }
+ if(c == '+'){
+ display = display + "+";
+ }
+ start_token = i+1;
+ }
+ } else if(parse_stage == kCommand) {
+ if(c == '\0'){
+ while(bind_str[start_token] == ' ' || bind_str[start_token] == '\t'){
+ ++start_token;
+ }
+ int end_token = i-1;
+ while(bind_str[end_token] == ' ' || bind_str[end_token] == '\t'){
+ --end_token;
+ }
+ str.clear();
+ for(int j=start_token; j<=end_token; ++j){
+ str += bind_str[j];
+ }
+ int id = -1;
+ unsigned long hash = djb2_string_hash(str.c_str());
+ HashIntVector::iterator iter = std::lower_bound(hash_to_command.begin(), hash_to_command.end(), hash, HashIntCompare());
+ if(iter->first != hash) {
+ LOGW << "Could not find " << str << " in hash map" << std::endl;
+ } else {
+ id = iter->second;
+ Command& kc = commands[id];
+ kc.num_key_events = num_events;
+ for(int j=0; j<num_events; ++j){
+ kc.key_events[j] = events[j];
+ }
+ FormatString(kc.display_text, KeyCommand::kDisplayTextBufSize, "%s", display.c_str());
+ }
+ }
+ }
+ if(c == '\0'){
+ break;
+ }
+ ++i;
+ }
+}
+
+void AddPairToHash(const char* cstr, int id){
+ hash_to_command.resize(hash_to_command.size()+1);
+ HashInt &new_pair = hash_to_command.back();
+ new_pair.first = djb2_string_hash(cstr);
+ new_pair.second = id;
+}
+
+int CommandLengthCompare (const void* a, const void* b){
+ return (*((Command**)b))->num_key_events - (*((Command**)a))->num_key_events;
+}
+
+void KeyCommand::FinalizeBindings() {
+ // Prepare list of commands sorted by length (so they can be evaluated from most-specific to least)
+ for(int i=0; i<kNumLabels; ++i){
+ commands_sorted_by_length[i] = &commands[i];
+ }
+ qsort(commands_sorted_by_length, kNumLabels, sizeof(commands_sorted_by_length[0]), CommandLengthCompare);
+}
+
+void KeyCommand::Initialize() {
+ // Create structure to map string hashes to command labels
+ hash_to_command.reserve(kNumLabels);
+ AddPairToHash("toggle_level_load_stress", kToggleLevelLoadStress);
+ AddPairToHash("pause",kPause);
+ AddPairToHash("refresh",kRefresh);
+ AddPairToHash("quit",kQuit);
+ AddPairToHash("back",kBack);
+ AddPairToHash("clone_transform",kCloneTransform);
+ AddPairToHash("snap_transform",kSnapTransform);
+ AddPairToHash("force_rotate",kForceRotate);
+ AddPairToHash("force_scale",kForceScale);
+ AddPairToHash("force_translate",kForceTranslate);
+ AddPairToHash("normal_transform",kNormalTransform);
+ AddPairToHash("edit_script_params",kEditScriptParams);
+ AddPairToHash("single_selected", kSingleSelected);
+ AddPairToHash("edit_color",kEditColor);
+ AddPairToHash("search_scenegraph",kSearchScenegraph);
+ AddPairToHash("scenegraph",kScenegraph);
+ AddPairToHash("toggle_player",kTogglePlayer);
+ AddPairToHash("toggle_decal_editing",kToggleDecalEditing);
+ AddPairToHash("toggle_object_editing",kToggleObjectEditing);
+ AddPairToHash("toggle_hotspot_editing",kToggleHotspotEditing);
+ AddPairToHash("open_spawner",kOpenSpawner);
+ AddPairToHash("save_selected_items",kSaveSelectedItems);
+ AddPairToHash("save_level",kSaveLevel);
+ AddPairToHash("save_level_as",kSaveLevelAs);
+ AddPairToHash("undo",kUndo);
+ AddPairToHash("redo",kRedo);
+ AddPairToHash("cut",kCut);
+ AddPairToHash("paste",kPaste);
+ AddPairToHash("copy",kCopy);
+ AddPairToHash("enable_imposter",kEnableImposter);
+ AddPairToHash("disable_imposter",kDisableImposter);
+ AddPairToHash("connect",kConnect);
+ AddPairToHash("disconnect",kDisconnect);
+ AddPairToHash("group",kGroup);
+ AddPairToHash("ungroup",kUngroup);
+ AddPairToHash("box_select",kBoxSelect);
+ AddPairToHash("deselect_all",kDeselectAll);
+ AddPairToHash("select_all",kSelectAll);
+ AddPairToHash("select_similar",kSelectSimilar);
+ AddPairToHash("add_to_selection",kAddToSelection);
+ AddPairToHash("bake_gi",kBakeGI);
+ AddPairToHash("test1",kTest1);
+ AddPairToHash("print_objects", kPrintObjects);
+ AddPairToHash("kill_selected", kKillSelected);
+ AddPairToHash("new_level", kNewLevel);
+ AddPairToHash("open_level", kOpenLevel);
+ AddPairToHash("open_custom_editor", kOpenCustomEditor);
+ AddPairToHash("frame_selected", kFrameSelected);
+ AddPairToHash("frame_selected_force", kFrameSelectedForce);
+ AddPairToHash("load_item_search", kLoadItemSearch);
+ AddPairToHash("make_selected_character_saved_corpse", kMakeSelectedCharacterSavedCorpse);
+ AddPairToHash("revive_selected_character_and_unsave_corpse", kReviveSelectedCharacterAndUnsaveCorpse);
+
+ std::sort(hash_to_command.begin(), hash_to_command.end(), HashIntCompare());
+
+ // Make sure there are no hash collisions
+ for(int i=0, len=hash_to_command.size()-1; i<len; ++i) {
+ if(hash_to_command[i].first == hash_to_command[i+1].first) {
+ FatalError("Error", "Hash collision in KeyCommand hash map");
+ }
+ }
+ for(int i=0; i<kNumLabels; ++i){
+ commands[i].num_key_events = 0;
+ }
+}
+
+void KeyCommand::HandleKeyDownEvent( const Keyboard &keyboard, SDL_Keysym key_id ) {
+ // Check for key_pressed events from longest command to shortest
+ int cmd_length = -1;
+ SDL_Scancode sc = key_id.scancode;
+ for(int i=0; i<kNumLabels; ++i){
+ Command &cmd = *(commands_sorted_by_length[i]);
+ if(cmd.num_key_events < cmd_length) {
+ return;
+ }
+ bool cmd_valid = true;
+ for(int j=0; j<cmd.num_key_events; ++j){
+ const KeyEvent &key_event = cmd.key_events[j];
+ bool key_valid = false;
+
+ switch(key_event.type_){
+ case kPressed:
+ if( key_event.sc_ == key_event.sc2_ ) {
+ key_valid = (sc == key_event.sc_);
+ } else {
+ key_valid = (sc == key_event.sc_) || (sc == key_event.sc2_);
+ }
+ break;
+ case kDown:
+ if( key_event.sc_ == key_event.sc2_ ) {
+ key_valid = keyboard.isScancodeDown(key_event.sc_,KIMF_ANY);
+ } else {
+ key_valid = keyboard.isScancodeDown(key_event.sc_,KIMF_ANY) || keyboard.isScancodeDown(key_event.sc2_,KIMF_ANY);
+ }
+ break;
+ case kUp:
+ if( key_event.sc_ == key_event.sc2_ ) {
+ key_valid = !keyboard.isScancodeDown(key_event.sc_,KIMF_ANY);
+ } else {
+ key_valid = !keyboard.isScancodeDown(key_event.sc_,KIMF_ANY) && !keyboard.isScancodeDown(key_event.sc2_,KIMF_ANY);
+ }
+ break;
+ }
+ if(!key_valid){
+ cmd_valid = false;
+ break;
+ }
+ }
+ if(cmd_valid){
+ cmd_length = cmd.num_key_events;
+ cmd.state = kPressed;
+ }
+ }
+}
+
+void KeyCommand::ClearKeyPresses() {
+ for(int i=0; i<kNumLabels; ++i){
+ commands[i].state = kUp;
+ }
+}
diff --git a/Source/UserInput/keycommands.h b/Source/UserInput/keycommands.h
new file mode 100644
index 00000000..420c5f42
--- /dev/null
+++ b/Source/UserInput/keycommands.h
@@ -0,0 +1,133 @@
+//-----------------------------------------------------------------------------
+// Name: keycommands.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/integer.h>
+
+#include <SDL.h>
+
+#include <vector>
+
+class Keyboard;
+
+namespace KeyCommand {
+ enum EventType { kPressed, kDown, kUp };
+
+ // A KeyEvent is a key id and an event type
+ // e.g. "q was pressed this frame" or "alt key is currently down"
+ struct KeyEvent {
+ EventType type_;
+ SDL_Scancode sc_;
+ SDL_Scancode sc2_;
+ KeyEvent(EventType type, SDL_Scancode key_id) :
+ type_(type), sc_(key_id)
+ {}
+ KeyEvent(){};
+ };
+
+ const int kMaxKeyEvents = 4;
+ const int kDisplayTextBufSize = 32;
+
+ // A command is a number of key events that must be simultaneously occurring
+ struct Command {
+ char display_text[kDisplayTextBufSize];
+ KeyEvent key_events[kMaxKeyEvents];
+ int num_key_events;
+ EventType state;
+
+ Command();
+ };
+
+ // Loop through each KeyEvent in the KeyCommand, and return false if any of
+ // the necessary inputs are not reported by the keyboard, otherwise true
+ bool CheckPressed(const Keyboard& keyboard, int label, const uint32_t kimf);
+ bool CheckDown(const Keyboard& keyboard, int label, const uint32_t kimf);
+
+ const char* GetDisplayText(int label);
+
+ void HandleKeyDownEvent( const Keyboard &keyboard, SDL_Keysym key_id );
+ void ClearKeyPresses();
+
+ // Set up all of the default key commands
+ void Initialize();
+
+ enum Label {
+ kQuit,
+ kBack,
+ kCloneTransform,
+ kSnapTransform,
+ kForceTranslate,
+ kForceScale,
+ kForceRotate,
+ kNormalTransform,
+ kEditScriptParams,
+ kSingleSelected,
+ kEditColor,
+ kSearchScenegraph,
+ kScenegraph,
+ kTogglePlayer,
+ kToggleDecalEditing,
+ kToggleObjectEditing,
+ kToggleHotspotEditing,
+ kOpenSpawner,
+ kSaveSelectedItems,
+ kSaveLevel,
+ kSaveLevelAs,
+ kUndo,
+ kRedo,
+ kCut,
+ kPaste,
+ kCopy,
+ kEnableImposter,
+ kDisableImposter,
+ kConnect,
+ kDisconnect,
+ kGroup,
+ kUngroup,
+ kBoxSelect,
+ kDeselectAll,
+ kSelectAll,
+ kSelectSimilar,
+ kAddToSelection,
+ kBakeGI,
+ kTest1,
+ kKillSelected,
+ kPrintObjects,
+ kPause,
+ kRefresh,
+ kToggleLevelLoadStress,
+ kNewLevel,
+ kOpenLevel,
+ kFrameSelected,
+ kFrameSelectedForce,
+ kLoadItemSearch,
+ kMakeSelectedCharacterSavedCorpse,
+ kReviveSelectedCharacterAndUnsaveCorpse,
+ kOpenCustomEditor,
+
+ kNumLabels
+ };
+
+ void BindString(const char* bind_str);
+ void FinalizeBindings();
+}
diff --git a/Source/UserInput/mouse.cpp b/Source/UserInput/mouse.cpp
new file mode 100644
index 00000000..bdd28f86
--- /dev/null
+++ b/Source/UserInput/mouse.cpp
@@ -0,0 +1,126 @@
+//-----------------------------------------------------------------------------
+// Name: mouse.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "mouse.h"
+
+#include <Threading/sdl_wrapper.h>
+#include <Logging/logdata.h>
+
+#include <SDL.h>
+
+#include <memory>
+#include <iostream>
+
+Mouse::Mouse()
+ : button_input_buffer_sequence_counter(0)
+ , button_input_buffer_count(0)
+{
+ for(int i=0; i<2; ++i){
+ pos_[i] = 0;
+ delta_[i] = 0;
+ }
+ wheel_delta_x_ = 0;
+ wheel_delta_y_ = 0;
+ for(int i=0; i<NUM_BUTTONS; i++) {
+ double_click_timer_[i] = SDL_TS_GetTicks();
+ mouse_click_count_[i] = 0;
+ mouse_double_click_[i] = false;
+ mouse_down_[i] = UP;
+ }
+}
+
+void Mouse::Update() {
+ wheel_delta_x_ = 0;
+ wheel_delta_y_ = 0;
+ for(int i=0; i<2; ++i){
+ delta_[i] = 0;
+ }
+ for (int i = 0; i < NUM_BUTTONS; ++i) {
+ mouse_double_click_[i] = false;
+ if (mouse_down_[i] == CLICKED) {
+ mouse_down_[i] = HELD;
+ }
+ }
+}
+
+void Mouse::MouseWheelEvent(int x_amount, int y_amount) {
+ wheel_delta_x_ += x_amount;
+ wheel_delta_y_ += y_amount;
+}
+
+void Mouse::MouseDownEvent( int sdl_button_index ) {
+ AddKeyCodeBufferItem(sdl_button_index);
+ const int _double_click_threshold_ticks = 300;
+ int array_button_index = sdl_button_index-1;
+
+ if( array_button_index < NUM_BUTTONS ) {
+ mouse_down_[array_button_index]++;
+ // handle double clicks
+ if(mouse_down_[array_button_index] == CLICKED) {
+ int double_click_time = SDL_TS_GetTicks() - double_click_timer_[array_button_index];
+ if (double_click_time > _double_click_threshold_ticks) {
+ mouse_click_count_[array_button_index] = 1;
+ } else {
+ mouse_click_count_[array_button_index]++;
+ }
+ if (mouse_click_count_[array_button_index] == 1) {
+ double_click_timer_[array_button_index] = SDL_TS_GetTicks();
+ } else if (mouse_click_count_[array_button_index] == 2) {
+ mouse_double_click_[array_button_index] = true;
+ mouse_click_count_[array_button_index] = 0;
+ }
+ }
+ } else {
+ LOGE << "Pressed button index exceeds expected possible range" << std::endl;
+ }
+}
+
+void Mouse::MouseUpEvent( int sdl_button_index ) {
+ int array_button_index = sdl_button_index - 1;
+
+ if( array_button_index < NUM_BUTTONS ) {
+ mouse_down_[array_button_index]=0;
+ mouse_double_click_[array_button_index] = false;
+ }
+}
+
+void Mouse::AddKeyCodeBufferItem( int sdl_button_index ) {
+ if( button_input_buffer_count >= button_input_buffer_size ) {
+ for( unsigned i = 1; i < button_input_buffer_count; i++ ) {
+ button_input_buffer[i-1] = button_input_buffer[i];
+ }
+ button_input_buffer_count--;
+ }
+
+ button_input_buffer[button_input_buffer_count].s_id = button_input_buffer_sequence_counter++;
+ button_input_buffer[button_input_buffer_count].button = (MouseButton)(sdl_button_index - 1);
+ button_input_buffer_count++;
+}
+
+std::vector<Mouse::MousePress> Mouse::GetMouseInputs() {
+ std::vector<Mouse::MousePress> presses;
+ presses.resize(button_input_buffer_count);
+ for( unsigned i = 0; i < button_input_buffer_count; i++ ) {
+ presses[i] = button_input_buffer[i];
+ }
+ return presses;
+}
diff --git a/Source/UserInput/mouse.h b/Source/UserInput/mouse.h
new file mode 100644
index 00000000..a846b3b6
--- /dev/null
+++ b/Source/UserInput/mouse.h
@@ -0,0 +1,79 @@
+//-----------------------------------------------------------------------------
+// Name: mouse.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <cstdio>
+#include <cstdint>
+#include <vector>
+
+class Mouse {
+public:
+ enum ClickState{
+ UP,
+ CLICKED,
+ HELD
+ };
+ enum MouseButton {
+ LEFT,
+ MIDDLE,
+ RIGHT,
+ FOURTH,
+ FIFTH,
+ SIXTH,
+ SEVENTH,
+ EIGHT,
+ NINTH,
+ TENTH,
+ TWELFTH,
+ NUM_BUTTONS
+ };
+
+ static const size_t button_input_buffer_size = 16;
+ uint16_t button_input_buffer_sequence_counter;
+ struct MousePress {
+ uint16_t s_id;
+ MouseButton button;
+ };
+ MousePress button_input_buffer[button_input_buffer_size];
+ size_t button_input_buffer_count;
+
+ int pos_[2];
+ int delta_[2];
+ int wheel_delta_x_;
+ int wheel_delta_y_;
+ bool mouse_double_click_[NUM_BUTTONS];
+ int mouse_down_[NUM_BUTTONS];
+
+ Mouse();
+ void Update();
+ void MouseDownEvent( int which_button );
+ void MouseUpEvent( int sdl_button_index );
+ void MouseWheelEvent(int x_amount, int y_amount);
+
+ void AddKeyCodeBufferItem( int sdl_button_index );
+ std::vector<MousePress> GetMouseInputs();
+
+private:
+ int double_click_timer_[NUM_BUTTONS];
+ int mouse_click_count_[NUM_BUTTONS];
+};
diff --git a/Source/Utility/assert.h b/Source/Utility/assert.h
new file mode 100644
index 00000000..53c10ccd
--- /dev/null
+++ b/Source/Utility/assert.h
@@ -0,0 +1,76 @@
+//-----------------------------------------------------------------------------
+// Name: assert.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Logging/logdata.h>
+
+#include <cassert>
+
+#if FATAL_LOG_ASSERTS
+
+#define LOG_ASSERT_MSG(value,message) if(!(value)){LOGF << "Failed assert \"" #value "\"" << " Message: " << message << std::endl;} assert(value)
+
+#define LOG_ASSERT(value) if(!(value)){LOGF << "Failed assert \"" #value "\"" << std::endl;} assert(value)
+#define LOG_ASSERT2(value, actual) if(!(value)){LOGF << "Failed assert \"" #value "\"" << ". Actual " #actual << actual << std::endl;} assert(value)
+
+#define LOG_ASSERT_EQ(v1,v2) if(!(v1==v2)){LOGF << "Failed assert: \"" #v1 "(value: " << v1 << ")" << " == " << #v2 << "(value:" << v2 << ")\"" << std::endl;} assert(v1==v2)
+
+#define LOG_ASSERT_LT(v1,v2) if(!(v1<v2)){LOGF << "Failed assert: \"" #v1 "(value: " << v1 << ")" << " < " << #v2 << "(value:" << v2 << ")\"" << std::endl;} assert(v1<v2)
+#define LOG_ASSERT_GT(v1,v2) if(!(v1>v2)){LOGF << "Failed assert: \"" #v1 "(value: " << v1 << ")" << " > " << #v2 << "(value:" << v2 << ")\"" << std::endl;} assert(v1>v2)
+
+#define LOG_ASSERT_LTEQ(v1,v2) if(!(v1<=v2)){LOGF << "Failed assert: \"" #v1 "(value: " << v1 << ")" << " <= " << #v2 << "(value:" << v2 << ")\"" << std::endl;} assert(v1<=v2)
+#define LOG_ASSERT_GTEQ(v1,v2) if(!(v1>=v2)){LOGF << "Failed assert: \"" #v1 "(value: " << v1 << ")" << " >= " << #v2 << "(value:" << v2 << ")\"" << std::endl;} assert(v1>=v2)
+
+#elif SINGLE_SHOT_ASSERTS
+
+#define LOG_ASSERT_MSG(value,message) if(!(value)){static bool print = true; if(print){LOGF << "Failed assert \"" #value "\"" << " Message: " << message << " Will mute repeating errors." << std::endl; print = false;}}
+
+#define LOG_ASSERT(value) if(!(value)){static bool print = true; if(print){LOGF << "Failed assert \"" #value "\" Will mute repeating errors." << std::endl; print = false;}}
+#define LOG_ASSERT2(value, actual) if(!(value)){static bool print = true; if(print){LOGF << "Failed assert \"" #value "\"" << ". Actual " #actual << actual << " Will mute repeating errors." << std::endl; print = false;}}
+
+#define LOG_ASSERT_EQ(v1,v2) if(!(v1==v2)){static bool print = true; if(print){LOGF << "Failed assert: \"" #v1 "(value: " << v1 << ")" << " == " << #v2 << "(value:" << v2 << ")\" Will mute repeating errors." << std::endl; print = false;}}
+
+#define LOG_ASSERT_LT(v1,v2) if(!(v1<v2)){static bool print = true; if(print){LOGF << "Failed assert: \"" #v1 "(value: " << v1 << ")" << " < " << #v2 << "(value:" << v2 << ")\" Will mute repeating errors." << std::endl; print = false;}}
+#define LOG_ASSERT_GT(v1,v2) if(!(v1>v2)){static bool print = true; if(print){LOGF << "Failed assert: \"" #v1 "(value: " << v1 << ")" << " > " << #v2 << "(value:" << v2 << ")\" Will mute repeating errors." << std::endl; print = false;}}
+
+#define LOG_ASSERT_LTEQ(v1,v2) if(!(v1<=v2)){static bool print = true; if(print){LOGF << "Failed assert: \"" #v1 "(value: " << v1 << ")" << " <= " << #v2 << "(value:" << v2 << ")\" Will mute repeating errors." << std::endl; print = false;}}
+#define LOG_ASSERT_GTEQ(v1,v2) if(!(v1>=v2)){static bool print = true; if(print){LOGF << "Failed assert: \"" #v1 "(value: " << v1 << ")" << " >= " << #v2 << "(value:" << v2 << ")\" Will mute repeating errors." << std::endl; print = false;}}
+
+#else
+
+#define LOG_ASSERT_MSG(value,message) if(!(value)){LOGF << "Failed assert \"" #value "\"" << " Message: " << message << std::endl;}
+
+#define LOG_ASSERT(value) if(!(value)){LOGF << "Failed assert \"" #value "\"" << std::endl;}
+#define LOG_ASSERT2(value, actual) if(!(value)){LOGF << "Failed assert \"" #value "\"" << ". Actual " #actual << actual << std::endl;}
+
+#define LOG_ASSERT_EQ(v1,v2) if(!(v1==v2)){LOGF << "Failed assert: \"" #v1 "(value: " << v1 << ")" << " == " << #v2 << "(value:" << v2 << ")\"" << std::endl;}
+
+#define LOG_ASSERT_LT(v1,v2) if(!(v1<v2)){LOGF << "Failed assert: \"" #v1 "(value: " << v1 << ")" << " < " << #v2 << "(value:" << v2 << ")\"" << std::endl;}
+#define LOG_ASSERT_GT(v1,v2) if(!(v1>v2)){LOGF << "Failed assert: \"" #v1 "(value: " << v1 << ")" << " > " << #v2 << "(value:" << v2 << ")\"" << std::endl;}
+
+#define LOG_ASSERT_LTEQ(v1,v2) if(!(v1<=v2)){LOGF << "Failed assert: \"" #v1 "(value: " << v1 << ")" << " <= " << #v2 << "(value:" << v2 << ")\"" << std::endl;}
+#define LOG_ASSERT_GTEQ(v1,v2) if(!(v1>=v2)){LOGF << "Failed assert: \"" #v1 "(value: " << v1 << ")" << " >= " << #v2 << "(value:" << v2 << ")\"" << std::endl;}
+
+#endif
+
+#define LOG_ASSERT_ONCE(value) if(!(value)){static bool print = true; if(print){LOGF << "Failed assert \"" #value "\" Will mute repeating errors." << std::endl; print = false;}}
diff --git a/Source/Utility/binn_util.cpp b/Source/Utility/binn_util.cpp
new file mode 100644
index 00000000..ca5e0c54
--- /dev/null
+++ b/Source/Utility/binn_util.cpp
@@ -0,0 +1,154 @@
+//-----------------------------------------------------------------------------
+// Name: binn_util.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "binn_util.h"
+
+void binn_object_set_vec3(binn* l, const char* key, const vec3& v){
+ binn* l_vec3 = binn_list();
+
+ binn_list_add_float(l_vec3, v.x());
+ binn_list_add_float(l_vec3, v.y());
+ binn_list_add_float(l_vec3, v.z());
+
+ binn_object_set_list(l, key, l_vec3);
+
+ binn_free(l_vec3);
+}
+
+void binn_object_set_quat(binn* l, const char* key, const quaternion& v) {
+ binn* l_quat = binn_list();
+
+ binn_list_add_float(l_quat, v[0]);
+ binn_list_add_float(l_quat, v[1]);
+ binn_list_add_float(l_quat, v[2]);
+ binn_list_add_float(l_quat, v[3]);
+
+ binn_object_set_list(l, key, l_quat);
+
+ binn_free(l_quat);
+}
+
+void binn_object_set_mat4(binn* l, const char* key, const mat4& v) {
+ binn* l_mat4 = binn_list();
+
+ for(int i = 0; i < 4; i++) {
+ binn_list_add_float(l_mat4, v[i*4 + 0]);
+ binn_list_add_float(l_mat4, v[i*4 + 1]);
+ binn_list_add_float(l_mat4, v[i*4 + 2]);
+ binn_list_add_float(l_mat4, v[i*4 + 3]);
+ }
+
+ binn_object_set_list(l, key, l_mat4);
+
+ binn_free(l_mat4);
+}
+
+void binn_object_set_std_string(binn* l, const char* key, const string& v) {
+ binn_object_set_str(l, key, v.c_str());
+}
+
+void binn_object_set_vector_int(binn* l, const char* key, const vector<int>& arr) {
+ binn* l_vec_int = binn_list();
+
+ for(int i = 0; i < arr.size(); i++) {
+ binn_list_add_int32(l_vec_int, arr[i]);
+ }
+
+ binn_object_set_list(l, key, l_vec_int);
+ binn_free(l_vec_int);
+}
+
+bool binn_object_get_vec3(binn* l, const char* key, vec3* v) {
+ void* l_vec3;
+ if(binn_object_get_list(l, key, &l_vec3) == false) return false;
+
+ if(binn_list_get_float(l_vec3, 1, &v->entries[0]) == false) return false;
+ if(binn_list_get_float(l_vec3, 2, &v->entries[1]) == false) return false;
+ if(binn_list_get_float(l_vec3, 3, &v->entries[2]) == false) return false;
+
+ return true;
+}
+
+bool binn_object_get_quat(binn* l, const char* key, quaternion* v) {
+ void* l_quat;
+
+ if(binn_object_get_list(l, key, &l_quat) == false) return false;
+
+ if(binn_list_get_float(l_quat, 1, &v->entries[0]) == false) return false;
+ if(binn_list_get_float(l_quat, 2, &v->entries[1]) == false) return false;
+ if(binn_list_get_float(l_quat, 3, &v->entries[2]) == false) return false;
+ if(binn_list_get_float(l_quat, 4, &v->entries[3]) == false) return false;
+
+ return true;
+}
+
+bool binn_object_get_mat4(binn* l, const char* key, mat4* v) {
+ void* l_mat4;
+
+ if(binn_object_get_list(l, key, &l_mat4) == false) return false;
+
+ for(int i = 0; i < 4; i++) {
+ if(binn_list_get_float(l_mat4, i*4 + 1, &v->entries[i*4 + 0]) == false) return false;
+ if(binn_list_get_float(l_mat4, i*4 + 2, &v->entries[i*4 + 1]) == false) return false;
+ if(binn_list_get_float(l_mat4, i*4 + 3, &v->entries[i*4 + 2]) == false) return false;
+ if(binn_list_get_float(l_mat4, i*4 + 4, &v->entries[i*4 + 3]) == false) return false;
+ }
+
+ return true;
+}
+
+bool binn_object_get_std_string(binn* l, const char* key, string* v) {
+ char* c_str;
+ if(binn_object_get_str(l, key, &c_str) == false) return false;
+
+ *v = string(c_str);
+
+ return true;
+}
+
+bool binn_object_get_vector_int(binn* l, const char* key, vector<int>* arr) {
+ void* l_vec_int;
+ binn_object_get_list(l, key, &l_vec_int);
+
+ arr->clear();
+ binn_iter iter;
+ binn value;
+ binn_list_foreach(l_vec_int, value) {
+ int32_t v;
+ binn_get_int32(&value, &v);
+ arr->push_back(v);
+ }
+
+ return true;
+}
+
+void binn_list_add_std_string(binn* l, const string& v) {
+ binn_list_add_str(l, const_cast<char*>(v.c_str()));
+}
+
+bool binn_list_get_std_string(binn* l, const int pos, string* v) {
+ char* c_str;
+ if(binn_list_get_str(l, pos, &c_str) == false) return false;
+
+ *v = string(c_str);
+ return true;
+}
diff --git a/Source/Utility/binn_util.h b/Source/Utility/binn_util.h
new file mode 100644
index 00000000..6484e085
--- /dev/null
+++ b/Source/Utility/binn_util.h
@@ -0,0 +1,49 @@
+//-----------------------------------------------------------------------------
+// Name: binn_util.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Math/vec3.h>
+#include <Math/quaternions.h>
+
+#include <binn.h>
+
+#include <string>
+#include <vector>
+
+using std::string;
+using std::vector;
+
+void binn_object_set_vec3(binn* l, const char* key, const vec3& v);
+void binn_object_set_quat(binn* l, const char* key, const quaternion& v);
+void binn_object_set_mat4(binn* l, const char* key, const mat4& v);
+void binn_object_set_std_string(binn* l, const char* key, const string& v);
+void binn_object_set_vector_int(binn* l, const char* key, const vector<int>& arr);
+
+bool binn_object_get_vec3(binn* l, const char* key, vec3* v);
+bool binn_object_get_quat(binn* l, const char* key, quaternion* v);
+bool binn_object_get_mat4(binn* l, const char* key, mat4* v);
+bool binn_object_get_std_string(binn* l, const char* key, string* v);
+bool binn_object_get_vector_int(binn* l, const char* key, vector<int>* arr);
+
+void binn_list_add_std_string(binn* l, const string& v);
+bool binn_list_get_std_string(binn* l, const int key, string* v);
diff --git a/Source/Utility/block_allocator.cpp b/Source/Utility/block_allocator.cpp
new file mode 100644
index 00000000..ca08ca1e
--- /dev/null
+++ b/Source/Utility/block_allocator.cpp
@@ -0,0 +1,143 @@
+//-----------------------------------------------------------------------------
+// Name: block_allocator.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "block_allocator.h"
+
+BlockAlloctorThreadSafe::BlockAlloctorThreadSafe(uint32_t size, uint16_t blockSize)
+{
+ // register inside engine.cpp so that we can easily track all active allocators
+ this->size = size;
+ this->blockSize = blockSize;
+ pool = new uint8_t[size];
+
+
+ this->nrOfBlocks = size / blockSize;// + 1 * ((size % blockSize) > 0); // this is really stupid
+ allocations = new DebugAllocationData[nrOfBlocks];
+
+ bitmap = new bool[nrOfBlocks];
+
+ nr_of_allocations = 0;
+}
+
+BlockAlloctorThreadSafe::~BlockAlloctorThreadSafe()
+{
+ delete bitmap;
+ delete allocations;
+ delete pool;
+}
+
+uint32_t BlockAlloctorThreadSafe::GetSize() const
+{
+ return size;
+}
+
+uint32_t BlockAlloctorThreadSafe::GetNrOfBlocks() const
+{
+ return nrOfBlocks;
+}
+/*
+uint8_t * BlockAllactor::Allocate(uint32_t size) {
+ std::lock_guard<std::mutex> guard(bitmapLock);
+ uint32_t sizeOfAllocation = size;
+ uint16_t nrOfBlocksNeeded = sizeOfAllocation / blockSize + (1 * ((sizeOfAllocation % blockSize) > 0));
+
+ int32_t pos = FindFirstFreeBlock(nrOfBlocksNeeded);
+ if (pos != -1) {
+ DebugAllocationData debugData;
+ debugData.allocationSize = nrOfBlocksNeeded * blockSize;
+
+ allocations[pos] = debugData;// nrOfBlocksNeeded * blockSize;// +(blockSize % sizeOfAllocation);
+ std::cout << (uint32_t)&pool[pos * blockSize] << std::endl;
+ return &pool[pos * blockSize];
+ }
+ else {
+ // This will lead to undefined behavior, but thats OK. We want to crash when this happens.
+ std::cout << "Failed to get any memory" << std::endl;
+ return nullptr;
+ }
+}
+*/
+uint8_t* BlockAlloctorThreadSafe::Allocate(uint32_t size, uint32_t line, const char* file, bool watched)
+{
+ std::lock_guard<std::mutex> guard(bitmapLock);
+ uint32_t sizeOfAllocation = size;
+ uint16_t nrOfBlocksNeeded = sizeOfAllocation / blockSize + (1 * ((sizeOfAllocation % blockSize) > 0));
+
+ int32_t pos = FindFirstFreeBlock(nrOfBlocksNeeded);
+ if (pos != -1) {
+
+ allocations[pos].taken = true;
+ allocations[pos].filename = file;
+ allocations[pos].line = line;
+ allocations[pos].allocationSize = nrOfBlocksNeeded * blockSize;
+ allocations[pos].watched = watched;
+
+ //debugData.addr = (uint32_t)&pool[pos * blockSize];
+
+ if (watched) {
+ std::cout << "Allocted: " << " size: " << size << " bytes - " << file << " on line:" << line << std::endl;
+ }
+
+ return &pool[pos * blockSize];
+ }
+ else {
+ // This will lead to undefined behavior, but thats OK. We want to crash when this happens.
+ std::cout << "Failed to get any memory - crash imminenet - dumping state" << std::endl;
+ for (uint32_t i = 0; i < nrOfBlocks; i++) {
+ if (allocations[i].taken) {
+ std::cout << allocations[i] << std::endl;
+ }
+ }
+
+ return nullptr;
+ }
+}
+
+
+int32_t BlockAlloctorThreadSafe::FindFirstFreeBlock(uint32_t blocksNeeded)
+{
+ uint32_t available = 0; // socket thread will call allocate, main thread will call deallocate
+
+ uint32_t startPos = 0;
+
+ for (uint32_t i = 0; i < nrOfBlocks && available != blocksNeeded; i++) {
+ if (bitmap[i] != true) {
+ available++;
+ }
+ else {
+ available = 0;
+ startPos = i + 1;
+ }
+ }
+
+ if (available == blocksNeeded) {
+ for (uint32_t i = startPos; i < blocksNeeded + startPos && i < nrOfBlocks; i++) {
+ bitmap[i] = true; // this can happen in several threads at once. V bad
+ //std::cout << "allocated: " << i << std::endl;
+ }
+ }
+ else {
+ return -1;
+ }
+
+ return startPos;
+}
diff --git a/Source/Utility/block_allocator.h b/Source/Utility/block_allocator.h
new file mode 100644
index 00000000..49d8d5eb
--- /dev/null
+++ b/Source/Utility/block_allocator.h
@@ -0,0 +1,153 @@
+//-----------------------------------------------------------------------------
+// Name: block_allocator.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <iostream>
+#include <string>
+#include <thread>
+#include <mutex>
+#include <cstdint>
+#include <cstring>
+
+class BlockAlloctorThreadSafe {
+private:
+ struct DebugAllocationData {
+ uint32_t line;
+ const char* filename;
+ uint32_t allocationSize;
+ bool watched;
+ bool taken;
+
+
+
+ friend std::ostream& operator<<(std::ostream& os, const DebugAllocationData& data) {
+ os << data.filename << "\t: " << data.line << " size: " << data.allocationSize;
+
+ return os;
+ }
+ };
+ uint32_t size;
+ uint16_t blockSize;
+ uint32_t nrOfBlocks;
+ uint32_t nr_of_allocations;
+ DebugAllocationData * allocations; // tracks allocations from block number in size. Rounded to blocksize
+ bool * bitmap; // tracks free blocks
+ std::mutex bitmapLock;
+ uint8_t * pool;
+public:
+ BlockAlloctorThreadSafe(uint32_t size = 4096, uint16_t blockSize = 256); // size is initalize size of the avaiable pool of bytes
+ ~BlockAlloctorThreadSafe();
+
+ uint32_t GetSize() const;
+ uint32_t GetNrOfBlocks() const;
+
+ //uint8_t * Allocate(uint32_t size);
+
+ uint8_t* Allocate(uint32_t size, uint32_t line, const char * file, bool watched = false);
+
+ template <class T>
+ void DeAllocate(T* addr);
+
+ template<class T>
+ void DeAllocate(T* addr, uint32_t line, const char* file);
+
+ template <class T>
+ T* ReAllocate(T* addr, uint32_t size);
+
+private:
+ int32_t FindFirstFreeBlock(uint32_t size);
+
+};
+
+template<class T>
+void BlockAlloctorThreadSafe::DeAllocate(T * addr) {
+ std::lock_guard<std::mutex> guard(bitmapLock); // we can probably do without this in overgrowth.
+
+
+ nr_of_allocations--;
+
+ int32_t bytesFromStart = (int32_t)((uint8_t*)addr - pool); // we need to do some magic to make sure it's on block start.
+
+ if (bytesFromStart > size || bytesFromStart < 0) {
+ std::cout << "YOU DE-ALLOCATED AN ADDRES THAT WAS NEVER ALLOCATED" << std::endl;
+ return;
+ }
+
+
+
+ uint32_t startOfBlock = bytesFromStart / blockSize; // maybe warning here instead of solving a potential user error
+
+ if (allocations[startOfBlock].watched) {
+ std::cout << " deallocated from: " << allocations[startOfBlock].filename << " line: " << allocations[startOfBlock].line << std::endl;
+ }
+
+ if (allocations[startOfBlock].taken) {
+ for (uint32_t i = 0; i < allocations[startOfBlock].allocationSize / blockSize ; i++)
+ bitmap[((i * blockSize) + bytesFromStart) / blockSize] = false;
+
+ allocations[startOfBlock].taken = false;
+ }
+}
+
+template<class T>
+void BlockAlloctorThreadSafe::DeAllocate(T* addr, uint32_t line, const char* file) {
+
+ std::lock_guard<std::mutex> guard(bitmapLock); // we can probably do without this in overgrowth.
+ int32_t bytesFromStart = (int32_t)((uint8_t*)addr - pool); // we need to do some magic to make sure it's on block start.
+
+ if (bytesFromStart > size || bytesFromStart < 0) {
+ std::cout << "Deallocated line: " << line << " in file: " << file << " which where never allocated." << std::endl;
+ return;
+ }
+
+ nr_of_allocations--;
+
+ uint32_t startOfBlock = bytesFromStart / blockSize; // maybe warning here instead of solving a potential user error
+
+ if (allocations[startOfBlock].taken) {
+
+ DebugAllocationData temp = allocations[startOfBlock];
+
+ if (temp.watched) {
+ std::cout << " allocated from: " << temp.filename << " line: " << temp.line <<
+ " deallocated from: " << file << " on line " << line << std::endl;
+ }
+
+
+
+ for (uint32_t i = 0; i < allocations[startOfBlock].allocationSize / blockSize; i++)
+ bitmap[((i * blockSize) + bytesFromStart) / blockSize] = false;
+
+ allocations[startOfBlock].taken = false;
+ }
+ else {
+ std::cout << "No allocation where deallocated" << std::endl;
+ }
+}
+
+template<class T>
+T* BlockAlloctorThreadSafe::ReAllocate(T * addr, uint32_t size) {
+ DeAllocate(addr);
+
+ return Allocate(addr, size);
+}
diff --git a/Source/Utility/commonregex.cpp b/Source/Utility/commonregex.cpp
new file mode 100644
index 00000000..6c70e3e7
--- /dev/null
+++ b/Source/Utility/commonregex.cpp
@@ -0,0 +1,83 @@
+//-----------------------------------------------------------------------------
+// Name: commonregex.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "commonregex.h"
+
+#include <trex/trex.h>
+
+#include <string>
+
+static const std::string true_regex_string("^[Tt]rue|[Yy]es$");
+static TRexpp true_regex;
+static const std::string false_regex_string("^[Ff]alse|[Nn]o$");
+static TRexpp false_regex;
+
+static bool compiled_regexes = false;
+
+static void try_compile()
+{
+ if( !compiled_regexes )
+ {
+ true_regex.Compile(true_regex_string.c_str());
+ false_regex.Compile(false_regex_string.c_str());
+ compiled_regexes = true;
+ }
+}
+
+CommonRegex::CommonRegex()
+{
+ try_compile();
+}
+
+bool CommonRegex::saysTrue(const std::string& str)
+{
+ return true_regex.Match(str.c_str());
+}
+
+bool CommonRegex::saysFalse(const std::string& str)
+{
+ return false_regex.Match(str.c_str());
+}
+
+bool CommonRegex::saysTrue(const char* str)
+{
+ if( str )
+ {
+ return saysTrue(std::string(str));
+ }
+ else
+ {
+ return false;
+ }
+}
+
+bool CommonRegex::saysFalse(const char* str)
+{
+ if( str )
+ {
+ return saysFalse(std::string(str));
+ }
+ else
+ {
+ return false;
+ }
+}
diff --git a/Source/Utility/commonregex.h b/Source/Utility/commonregex.h
new file mode 100644
index 00000000..1274cdef
--- /dev/null
+++ b/Source/Utility/commonregex.h
@@ -0,0 +1,34 @@
+//-----------------------------------------------------------------------------
+// Name: commonregex.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+#include <string>
+
+class CommonRegex
+{
+public:
+ CommonRegex();
+ bool saysTrue(const std::string& str);
+ bool saysFalse(const std::string& str);
+ bool saysTrue(const char* str);
+ bool saysFalse(const char* str);
+};
diff --git a/Source/Utility/compiler_macros.h b/Source/Utility/compiler_macros.h
new file mode 100644
index 00000000..35fff26e
--- /dev/null
+++ b/Source/Utility/compiler_macros.h
@@ -0,0 +1,33 @@
+//-----------------------------------------------------------------------------
+// Name: compiler_macros.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+
+#ifndef __GNUC__
+
+
+#define __builtin_unreachable()
+#define __attribute__(x)
+
+
+#endif // __GNUC__
diff --git a/Source/Utility/disallow_copy_and_assign.h b/Source/Utility/disallow_copy_and_assign.h
new file mode 100644
index 00000000..906d3ed2
--- /dev/null
+++ b/Source/Utility/disallow_copy_and_assign.h
@@ -0,0 +1,32 @@
+//-----------------------------------------------------------------------------
+// Name: disallow_copy_and_assign.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+// From Google Style Guide
+// A macro to disallow the copy constructor and operator= functions
+// This should be used in the private: declarations for a class
+#ifndef DISALLOW_COPY_AND_ASSIGN
+#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
+ TypeName(const TypeName&); \
+ void operator=(const TypeName&)
+#endif
diff --git a/Source/Utility/fixed_array.h b/Source/Utility/fixed_array.h
new file mode 100644
index 00000000..3a78dd28
--- /dev/null
+++ b/Source/Utility/fixed_array.h
@@ -0,0 +1,53 @@
+//-----------------------------------------------------------------------------
+// Name: fixed_array.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+template<class T, int cap>
+class fixed_array {
+ size_t s;
+ T values[cap];
+public:
+ fixed_array() {
+ s = 0;
+ }
+
+ size_t size() {
+ return s;
+ }
+
+ size_t capacity() {
+ return cap;
+ }
+
+ T& operator[](size_t index) {
+ return values[index];
+ }
+
+ int push_back( const T& v ) {
+ if( s < cap ) {
+ values[s++] = v;
+ return s-1;
+ }
+ return -1;
+ }
+};
diff --git a/Source/Utility/fixed_heap_string.h b/Source/Utility/fixed_heap_string.h
new file mode 100644
index 00000000..3ea9a1d4
--- /dev/null
+++ b/Source/Utility/fixed_heap_string.h
@@ -0,0 +1,106 @@
+//-----------------------------------------------------------------------------
+// Name: fixed_heap_string.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <string>
+
+#include <Utility/strings.h>
+#include <Memory/allocation.h>
+
+template<int d_size>
+class fixed_heap_string {
+ char* data;
+
+public:
+ fixed_heap_string() {
+ data = static_cast<char*>(OG_MALLOC(d_size));
+ data[0] = '\0';
+ }
+
+ fixed_heap_string(const char* v) {
+ data = static_cast<char*>(OG_MALLOC(d_size));
+ set(v);
+ }
+
+ fixed_heap_string(const fixed_heap_string<d_size>* rhs) {
+ data = static_cast<char*>(OG_MALLOC(d_size));
+ set(rhs->c_str());
+ }
+
+ fixed_heap_string(const fixed_heap_string<d_size>& rhs) {
+ data = static_cast<char*>(OG_MALLOC(d_size));
+ set(rhs.c_str());
+ }
+
+ ~fixed_heap_string() {
+ OG_FREE(data);
+ }
+
+ std::string str() const {
+ return std::string(data);
+ }
+
+ const char* c_str() const {
+ return data;
+ }
+
+ char* ptr() {
+ return data;
+ }
+
+ size_t size() const {
+ return strlen(data);
+ }
+
+ size_t max_size() {
+ return d_size-1;
+ }
+
+ int set(const char* str) {
+ return strscpy(data,str,d_size);
+ }
+
+ int set(const std::string& other) {
+ return strscpy(data,other.c_str(),d_size);
+ }
+
+ operator const char*() const
+ {
+ return data;
+ }
+
+ bool operator==( const std::string& rh ) const
+ {
+ return strmtch(data,rh.c_str());
+ }
+
+ fixed_heap_string<d_size>& operator=(const fixed_heap_string<d_size>* rhs) {
+ set(rhs->c_str());
+ return *this;
+ }
+
+ fixed_heap_string<d_size>& operator=(const fixed_heap_string<d_size>& rhs) {
+ set(rhs.c_str());
+ return *this;
+ }
+};
diff --git a/Source/Utility/fixed_string.h b/Source/Utility/fixed_string.h
new file mode 100644
index 00000000..c7a9b1b8
--- /dev/null
+++ b/Source/Utility/fixed_string.h
@@ -0,0 +1,79 @@
+//-----------------------------------------------------------------------------
+// Name: fixed_string.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Utility/strings.h>
+
+#include <string>
+
+
+template<int d_size>
+class fixed_string {
+ char data[d_size];
+
+public:
+ fixed_string() {
+ data[0] = '\0';
+ }
+ fixed_string(const char* v) {
+ set(v);
+ }
+
+ std::string str() const {
+ return std::string(data);
+ }
+
+ const char* c_str() const {
+ return data;
+ }
+
+ char* ptr() {
+ return data;
+ }
+
+ size_t size() const {
+ return strlen(data);
+ }
+
+ size_t max_size() {
+ return d_size-1;
+ }
+
+ int set(const char* str) {
+ return strscpy(data,str,d_size);
+ }
+
+ int set(const std::string& other) {
+ return strscpy(data,other.c_str(),d_size);
+ }
+
+ operator const char*() const
+ {
+ return data;
+ }
+
+ bool operator==( const std::string& rh ) const
+ {
+ return strmtch(data,rh.c_str());
+ }
+};
diff --git a/Source/Utility/flat_hash_map.hpp b/Source/Utility/flat_hash_map.hpp
new file mode 100644
index 00000000..588651ea
--- /dev/null
+++ b/Source/Utility/flat_hash_map.hpp
@@ -0,0 +1,1507 @@
+//-----------------------------------------------------------------------------
+// Name: fixed_string.h
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+// Copyright Malte Skarupke 2017.
+// Distributed under the Boost Software License, Version 1.0.
+// (See http://www.boost.org/LICENSE_1_0.txt)
+
+#pragma once
+
+#include <cstdint>
+#include <cstddef>
+#include <functional>
+#include <cmath>
+#include <algorithm>
+#include <iterator>
+#include <utility>
+#include <type_traits>
+
+#ifdef _MSC_VER
+#define SKA_NOINLINE(...) __declspec(noinline) __VA_ARGS__
+#else
+#define SKA_NOINLINE(...) __VA_ARGS__ __attribute__((noinline))
+#endif
+
+namespace ska
+{
+struct prime_number_hash_policy;
+struct power_of_two_hash_policy;
+struct fibonacci_hash_policy;
+
+namespace detailv3
+{
+template<typename Result, typename Functor>
+struct functor_storage : Functor
+{
+ functor_storage() = default;
+ functor_storage(const Functor & functor)
+ : Functor(functor)
+ {
+ }
+ template<typename... Args>
+ Result operator()(Args &&... args)
+ {
+ return static_cast<Functor &>(*this)(std::forward<Args>(args)...);
+ }
+ template<typename... Args>
+ Result operator()(Args &&... args) const
+ {
+ return static_cast<const Functor &>(*this)(std::forward<Args>(args)...);
+ }
+};
+template<typename Result, typename... Args>
+struct functor_storage<Result, Result (*)(Args...)>
+{
+ typedef Result (*function_ptr)(Args...);
+ function_ptr function;
+ functor_storage(function_ptr function)
+ : function(function)
+ {
+ }
+ Result operator()(Args... args) const
+ {
+ return function(std::forward<Args>(args)...);
+ }
+ operator function_ptr &()
+ {
+ return function;
+ }
+ operator const function_ptr &()
+ {
+ return function;
+ }
+};
+template<typename key_type, typename value_type, typename hasher>
+struct KeyOrValueHasher : functor_storage<size_t, hasher>
+{
+ typedef functor_storage<size_t, hasher> hasher_storage;
+ KeyOrValueHasher() = default;
+ KeyOrValueHasher(const hasher & hash)
+ : hasher_storage(hash)
+ {
+ }
+ size_t operator()(const key_type & key)
+ {
+ return static_cast<hasher_storage &>(*this)(key);
+ }
+ size_t operator()(const key_type & key) const
+ {
+ return static_cast<const hasher_storage &>(*this)(key);
+ }
+ size_t operator()(const value_type & value)
+ {
+ return static_cast<hasher_storage &>(*this)(value.first);
+ }
+ size_t operator()(const value_type & value) const
+ {
+ return static_cast<const hasher_storage &>(*this)(value.first);
+ }
+ template<typename F, typename S>
+ size_t operator()(const std::pair<F, S> & value)
+ {
+ return static_cast<hasher_storage &>(*this)(value.first);
+ }
+ template<typename F, typename S>
+ size_t operator()(const std::pair<F, S> & value) const
+ {
+ return static_cast<const hasher_storage &>(*this)(value.first);
+ }
+};
+template<typename key_type, typename value_type, typename key_equal>
+struct KeyOrValueEquality : functor_storage<bool, key_equal>
+{
+ typedef functor_storage<bool, key_equal> equality_storage;
+ KeyOrValueEquality() = default;
+ KeyOrValueEquality(const key_equal & equality)
+ : equality_storage(equality)
+ {
+ }
+ bool operator()(const key_type & lhs, const key_type & rhs)
+ {
+ return static_cast<equality_storage &>(*this)(lhs, rhs);
+ }
+ bool operator()(const key_type & lhs, const value_type & rhs)
+ {
+ return static_cast<equality_storage &>(*this)(lhs, rhs.first);
+ }
+ bool operator()(const value_type & lhs, const key_type & rhs)
+ {
+ return static_cast<equality_storage &>(*this)(lhs.first, rhs);
+ }
+ bool operator()(const value_type & lhs, const value_type & rhs)
+ {
+ return static_cast<equality_storage &>(*this)(lhs.first, rhs.first);
+ }
+ template<typename F, typename S>
+ bool operator()(const key_type & lhs, const std::pair<F, S> & rhs)
+ {
+ return static_cast<equality_storage &>(*this)(lhs, rhs.first);
+ }
+ template<typename F, typename S>
+ bool operator()(const std::pair<F, S> & lhs, const key_type & rhs)
+ {
+ return static_cast<equality_storage &>(*this)(lhs.first, rhs);
+ }
+ template<typename F, typename S>
+ bool operator()(const value_type & lhs, const std::pair<F, S> & rhs)
+ {
+ return static_cast<equality_storage &>(*this)(lhs.first, rhs.first);
+ }
+ template<typename F, typename S>
+ bool operator()(const std::pair<F, S> & lhs, const value_type & rhs)
+ {
+ return static_cast<equality_storage &>(*this)(lhs.first, rhs.first);
+ }
+ template<typename FL, typename SL, typename FR, typename SR>
+ bool operator()(const std::pair<FL, SL> & lhs, const std::pair<FR, SR> & rhs)
+ {
+ return static_cast<equality_storage &>(*this)(lhs.first, rhs.first);
+ }
+};
+static constexpr int8_t min_lookups = 4;
+template<typename T>
+struct sherwood_v3_entry
+{
+ sherwood_v3_entry()
+ {
+ }
+ sherwood_v3_entry(int8_t distance_from_desired)
+ : distance_from_desired(distance_from_desired)
+ {
+ }
+ ~sherwood_v3_entry()
+ {
+ }
+ static sherwood_v3_entry * empty_default_table()
+ {
+ static sherwood_v3_entry result[min_lookups] = { {}, {}, {}, {special_end_value} };
+ return result;
+ }
+
+ bool has_value() const
+ {
+ return distance_from_desired >= 0;
+ }
+ bool is_empty() const
+ {
+ return distance_from_desired < 0;
+ }
+ bool is_at_desired_position() const
+ {
+ return distance_from_desired <= 0;
+ }
+ template<typename... Args>
+ void emplace(int8_t distance, Args &&... args)
+ {
+ new (std::addressof(value)) T(std::forward<Args>(args)...);
+ distance_from_desired = distance;
+ }
+
+ void destroy_value()
+ {
+ value.~T();
+ distance_from_desired = -1;
+ }
+
+ int8_t distance_from_desired = -1;
+ static constexpr int8_t special_end_value = 0;
+ union { T value; };
+};
+
+inline int8_t log2(size_t value)
+{
+ static constexpr int8_t table[64] =
+ {
+ 63, 0, 58, 1, 59, 47, 53, 2,
+ 60, 39, 48, 27, 54, 33, 42, 3,
+ 61, 51, 37, 40, 49, 18, 28, 20,
+ 55, 30, 34, 11, 43, 14, 22, 4,
+ 62, 57, 46, 52, 38, 26, 32, 41,
+ 50, 36, 17, 19, 29, 10, 13, 21,
+ 56, 45, 25, 31, 35, 16, 9, 12,
+ 44, 24, 15, 8, 23, 7, 6, 5
+ };
+ value |= value >> 1;
+ value |= value >> 2;
+ value |= value >> 4;
+ value |= value >> 8;
+ value |= value >> 16;
+ value |= value >> 32;
+ return table[((value - (value >> 1)) * 0x07EDD5E59A4E28C2) >> 58];
+}
+
+template<typename T, bool>
+struct AssignIfTrue
+{
+ void operator()(T & lhs, const T & rhs)
+ {
+ lhs = rhs;
+ }
+ void operator()(T & lhs, T && rhs)
+ {
+ lhs = std::move(rhs);
+ }
+};
+template<typename T>
+struct AssignIfTrue<T, false>
+{
+ void operator()(T &, const T &)
+ {
+ }
+ void operator()(T &, T &&)
+ {
+ }
+};
+
+inline size_t next_power_of_two(size_t i)
+{
+ --i;
+ i |= i >> 1;
+ i |= i >> 2;
+ i |= i >> 4;
+ i |= i >> 8;
+ i |= i >> 16;
+ i |= i >> 32;
+ ++i;
+ return i;
+}
+
+//template<typename...> using void_t = void;
+
+template<typename...>
+struct voider { using type = void; };
+
+template <typename... Ts>
+using void_t = typename voider<Ts...>::type;
+
+template<typename T, typename = void>
+struct HashPolicySelector
+{
+ typedef fibonacci_hash_policy type;
+};
+template<typename T>
+struct HashPolicySelector<T, void_t<typename T::hash_policy>>
+{
+ typedef typename T::hash_policy type;
+};
+
+template<typename T, typename FindKey, typename ArgumentHash, typename Hasher, typename ArgumentEqual, typename Equal, typename ArgumentAlloc, typename EntryAlloc>
+class sherwood_v3_table : private EntryAlloc, private Hasher, private Equal
+{
+ using Entry = detailv3::sherwood_v3_entry<T>;
+ using AllocatorTraits = std::allocator_traits<EntryAlloc>;
+ using EntryPointer = typename AllocatorTraits::pointer;
+ struct convertible_to_iterator;
+
+public:
+
+ using value_type = T;
+ using size_type = size_t;
+ using difference_type = std::ptrdiff_t;
+ using hasher = ArgumentHash;
+ using key_equal = ArgumentEqual;
+ using allocator_type = EntryAlloc;
+ using reference = value_type &;
+ using const_reference = const value_type &;
+ using pointer = value_type *;
+ using const_pointer = const value_type *;
+
+ sherwood_v3_table()
+ {
+ }
+ explicit sherwood_v3_table(size_type bucket_count, const ArgumentHash & hash = ArgumentHash(), const ArgumentEqual & equal = ArgumentEqual(), const ArgumentAlloc & alloc = ArgumentAlloc())
+ : EntryAlloc(alloc), Hasher(hash), Equal(equal)
+ {
+ rehash(bucket_count);
+ }
+ sherwood_v3_table(size_type bucket_count, const ArgumentAlloc & alloc)
+ : sherwood_v3_table(bucket_count, ArgumentHash(), ArgumentEqual(), alloc)
+ {
+ }
+ sherwood_v3_table(size_type bucket_count, const ArgumentHash & hash, const ArgumentAlloc & alloc)
+ : sherwood_v3_table(bucket_count, hash, ArgumentEqual(), alloc)
+ {
+ }
+ explicit sherwood_v3_table(const ArgumentAlloc & alloc)
+ : EntryAlloc(alloc)
+ {
+ }
+ template<typename It>
+ sherwood_v3_table(It first, It last, size_type bucket_count = 0, const ArgumentHash & hash = ArgumentHash(), const ArgumentEqual & equal = ArgumentEqual(), const ArgumentAlloc & alloc = ArgumentAlloc())
+ : sherwood_v3_table(bucket_count, hash, equal, alloc)
+ {
+ insert(first, last);
+ }
+ template<typename It>
+ sherwood_v3_table(It first, It last, size_type bucket_count, const ArgumentAlloc & alloc)
+ : sherwood_v3_table(first, last, bucket_count, ArgumentHash(), ArgumentEqual(), alloc)
+ {
+ }
+ template<typename It>
+ sherwood_v3_table(It first, It last, size_type bucket_count, const ArgumentHash & hash, const ArgumentAlloc & alloc)
+ : sherwood_v3_table(first, last, bucket_count, hash, ArgumentEqual(), alloc)
+ {
+ }
+ sherwood_v3_table(std::initializer_list<T> il, size_type bucket_count = 0, const ArgumentHash & hash = ArgumentHash(), const ArgumentEqual & equal = ArgumentEqual(), const ArgumentAlloc & alloc = ArgumentAlloc())
+ : sherwood_v3_table(bucket_count, hash, equal, alloc)
+ {
+ if (bucket_count == 0)
+ rehash(il.size());
+ insert(il.begin(), il.end());
+ }
+ sherwood_v3_table(std::initializer_list<T> il, size_type bucket_count, const ArgumentAlloc & alloc)
+ : sherwood_v3_table(il, bucket_count, ArgumentHash(), ArgumentEqual(), alloc)
+ {
+ }
+ sherwood_v3_table(std::initializer_list<T> il, size_type bucket_count, const ArgumentHash & hash, const ArgumentAlloc & alloc)
+ : sherwood_v3_table(il, bucket_count, hash, ArgumentEqual(), alloc)
+ {
+ }
+ sherwood_v3_table(const sherwood_v3_table & other)
+ : sherwood_v3_table(other, AllocatorTraits::select_on_container_copy_construction(other.get_allocator()))
+ {
+ }
+ sherwood_v3_table(const sherwood_v3_table & other, const ArgumentAlloc & alloc)
+ : EntryAlloc(alloc), Hasher(other), Equal(other), _max_load_factor(other._max_load_factor)
+ {
+ rehash_for_other_container(other);
+ try
+ {
+ insert(other.begin(), other.end());
+ }
+ catch(...)
+ {
+ clear();
+ deallocate_data(entries, num_slots_minus_one, max_lookups);
+ throw;
+ }
+ }
+ sherwood_v3_table(sherwood_v3_table && other) noexcept
+ : EntryAlloc(std::move(other)), Hasher(std::move(other)), Equal(std::move(other))
+ {
+ swap_pointers(other);
+ }
+ sherwood_v3_table(sherwood_v3_table && other, const ArgumentAlloc & alloc) noexcept
+ : EntryAlloc(alloc), Hasher(std::move(other)), Equal(std::move(other))
+ {
+ swap_pointers(other);
+ }
+ sherwood_v3_table & operator=(const sherwood_v3_table & other)
+ {
+ if (this == std::addressof(other))
+ return *this;
+
+ clear();
+ if (AllocatorTraits::propagate_on_container_copy_assignment::value)
+ {
+ if (static_cast<EntryAlloc &>(*this) != static_cast<const EntryAlloc &>(other))
+ {
+ reset_to_empty_state();
+ }
+ AssignIfTrue<EntryAlloc, AllocatorTraits::propagate_on_container_copy_assignment::value>()(*this, other);
+ }
+ _max_load_factor = other._max_load_factor;
+ static_cast<Hasher &>(*this) = other;
+ static_cast<Equal &>(*this) = other;
+ rehash_for_other_container(other);
+ insert(other.begin(), other.end());
+ return *this;
+ }
+ sherwood_v3_table & operator=(sherwood_v3_table && other) noexcept
+ {
+ if (this == std::addressof(other))
+ return *this;
+ else if (AllocatorTraits::propagate_on_container_move_assignment::value)
+ {
+ clear();
+ reset_to_empty_state();
+ AssignIfTrue<EntryAlloc, AllocatorTraits::propagate_on_container_move_assignment::value>()(*this, std::move(other));
+ swap_pointers(other);
+ }
+ else if (static_cast<EntryAlloc &>(*this) == static_cast<EntryAlloc &>(other))
+ {
+ swap_pointers(other);
+ }
+ else
+ {
+ clear();
+ _max_load_factor = other._max_load_factor;
+ rehash_for_other_container(other);
+ for (T & elem : other)
+ emplace(std::move(elem));
+ other.clear();
+ }
+ static_cast<Hasher &>(*this) = std::move(other);
+ static_cast<Equal &>(*this) = std::move(other);
+ return *this;
+ }
+ ~sherwood_v3_table()
+ {
+ clear();
+ deallocate_data(entries, num_slots_minus_one, max_lookups);
+ }
+
+ const allocator_type & get_allocator() const
+ {
+ return static_cast<const allocator_type &>(*this);
+ }
+ const ArgumentEqual & key_eq() const
+ {
+ return static_cast<const ArgumentEqual &>(*this);
+ }
+ const ArgumentHash & hash_function() const
+ {
+ return static_cast<const ArgumentHash &>(*this);
+ }
+
+ template<typename ValueType>
+ struct templated_iterator
+ {
+ templated_iterator() = default;
+ templated_iterator(EntryPointer current)
+ : current(current)
+ {
+ }
+ EntryPointer current = EntryPointer();
+
+ using iterator_category = std::forward_iterator_tag;
+ using value_type = ValueType;
+ using difference_type = ptrdiff_t;
+ using pointer = ValueType *;
+ using reference = ValueType &;
+
+ friend bool operator==(const templated_iterator & lhs, const templated_iterator & rhs)
+ {
+ return lhs.current == rhs.current;
+ }
+ friend bool operator!=(const templated_iterator & lhs, const templated_iterator & rhs)
+ {
+ return !(lhs == rhs);
+ }
+
+ templated_iterator & operator++()
+ {
+ do
+ {
+ ++current;
+ }
+ while(current->is_empty());
+ return *this;
+ }
+ templated_iterator operator++(int)
+ {
+ templated_iterator copy(*this);
+ ++*this;
+ return copy;
+ }
+
+ ValueType & operator*() const
+ {
+ return current->value;
+ }
+ ValueType * operator->() const
+ {
+ return std::addressof(current->value);
+ }
+
+ operator templated_iterator<const value_type>() const
+ {
+ return { current };
+ }
+ };
+ using iterator = templated_iterator<value_type>;
+ using const_iterator = templated_iterator<const value_type>;
+
+ iterator begin()
+ {
+ for (EntryPointer it = entries;; ++it)
+ {
+ if (it->has_value())
+ return { it };
+ }
+ }
+ const_iterator begin() const
+ {
+ for (EntryPointer it = entries;; ++it)
+ {
+ if (it->has_value())
+ return { it };
+ }
+ }
+ const_iterator cbegin() const
+ {
+ return begin();
+ }
+ iterator end()
+ {
+ return { entries + static_cast<ptrdiff_t>(num_slots_minus_one + max_lookups) };
+ }
+ const_iterator end() const
+ {
+ return { entries + static_cast<ptrdiff_t>(num_slots_minus_one + max_lookups) };
+ }
+ const_iterator cend() const
+ {
+ return end();
+ }
+
+ iterator find(const FindKey & key)
+ {
+ size_t index = hash_policy.index_for_hash(hash_object(key), num_slots_minus_one);
+ EntryPointer it = entries + ptrdiff_t(index);
+ for (int8_t distance = 0; it->distance_from_desired >= distance; ++distance, ++it)
+ {
+ if (compares_equal(key, it->value))
+ return { it };
+ }
+ return end();
+ }
+ const_iterator find(const FindKey & key) const
+ {
+ return const_cast<sherwood_v3_table *>(this)->find(key);
+ }
+ size_t count(const FindKey & key) const
+ {
+ return find(key) == end() ? 0 : 1;
+ }
+ std::pair<iterator, iterator> equal_range(const FindKey & key)
+ {
+ iterator found = find(key);
+ if (found == end())
+ return { found, found };
+ else
+ return { found, std::next(found) };
+ }
+ std::pair<const_iterator, const_iterator> equal_range(const FindKey & key) const
+ {
+ const_iterator found = find(key);
+ if (found == end())
+ return { found, found };
+ else
+ return { found, std::next(found) };
+ }
+
+ template<typename Key, typename... Args>
+ std::pair<iterator, bool> emplace(Key && key, Args &&... args)
+ {
+ size_t index = hash_policy.index_for_hash(hash_object(key), num_slots_minus_one);
+ EntryPointer current_entry = entries + ptrdiff_t(index);
+ int8_t distance_from_desired = 0;
+ for (; current_entry->distance_from_desired >= distance_from_desired; ++current_entry, ++distance_from_desired)
+ {
+ if (compares_equal(key, current_entry->value))
+ return { { current_entry }, false };
+ }
+ return emplace_new_key(distance_from_desired, current_entry, std::forward<Key>(key), std::forward<Args>(args)...);
+ }
+
+ std::pair<iterator, bool> insert(const value_type & value)
+ {
+ return emplace(value);
+ }
+ std::pair<iterator, bool> insert(value_type && value)
+ {
+ return emplace(std::move(value));
+ }
+ template<typename... Args>
+ iterator emplace_hint(const_iterator, Args &&... args)
+ {
+ return emplace(std::forward<Args>(args)...).first;
+ }
+ iterator insert(const_iterator, const value_type & value)
+ {
+ return emplace(value).first;
+ }
+ iterator insert(const_iterator, value_type && value)
+ {
+ return emplace(std::move(value)).first;
+ }
+
+ template<typename It>
+ void insert(It begin, It end)
+ {
+ for (; begin != end; ++begin)
+ {
+ emplace(*begin);
+ }
+ }
+ void insert(std::initializer_list<value_type> il)
+ {
+ insert(il.begin(), il.end());
+ }
+
+ void rehash(size_t num_buckets)
+ {
+ num_buckets = std::max(num_buckets, static_cast<size_t>(std::ceil(num_elements / static_cast<double>(_max_load_factor))));
+ if (num_buckets == 0)
+ {
+ reset_to_empty_state();
+ return;
+ }
+ auto new_prime_index = hash_policy.next_size_over(num_buckets);
+ if (num_buckets == bucket_count())
+ return;
+ int8_t new_max_lookups = compute_max_lookups(num_buckets);
+ EntryPointer new_buckets(AllocatorTraits::allocate(*this, num_buckets + new_max_lookups));
+ EntryPointer special_end_item = new_buckets + static_cast<ptrdiff_t>(num_buckets + new_max_lookups - 1);
+ for (EntryPointer it = new_buckets; it != special_end_item; ++it)
+ it->distance_from_desired = -1;
+ special_end_item->distance_from_desired = Entry::special_end_value;
+ std::swap(entries, new_buckets);
+ std::swap(num_slots_minus_one, num_buckets);
+ --num_slots_minus_one;
+ hash_policy.commit(new_prime_index);
+ int8_t old_max_lookups = max_lookups;
+ max_lookups = new_max_lookups;
+ num_elements = 0;
+ for (EntryPointer it = new_buckets, end = it + static_cast<ptrdiff_t>(num_buckets + old_max_lookups); it != end; ++it)
+ {
+ if (it->has_value())
+ {
+ emplace(std::move(it->value));
+ it->destroy_value();
+ }
+ }
+ deallocate_data(new_buckets, num_buckets, old_max_lookups);
+ }
+
+ void reserve(size_t num_elements)
+ {
+ size_t required_buckets = num_buckets_for_reserve(num_elements);
+ if (required_buckets > bucket_count())
+ rehash(required_buckets);
+ }
+
+ // the return value is a type that can be converted to an iterator
+ // the reason for doing this is that it's not free to find the
+ // iterator pointing at the next element. if you care about the
+ // next iterator, turn the return value into an iterator
+ convertible_to_iterator erase(const_iterator to_erase)
+ {
+ EntryPointer current = to_erase.current;
+ current->destroy_value();
+ --num_elements;
+ for (EntryPointer next = current + ptrdiff_t(1); !next->is_at_desired_position(); ++current, ++next)
+ {
+ current->emplace(next->distance_from_desired - 1, std::move(next->value));
+ next->destroy_value();
+ }
+ return { to_erase.current };
+ }
+
+ iterator erase(const_iterator begin_it, const_iterator end_it)
+ {
+ if (begin_it == end_it)
+ return { begin_it.current };
+ for (EntryPointer it = begin_it.current, end = end_it.current; it != end; ++it)
+ {
+ if (it->has_value())
+ {
+ it->destroy_value();
+ --num_elements;
+ }
+ }
+ if (end_it == this->end())
+ return this->end();
+ ptrdiff_t num_to_move = std::min(static_cast<ptrdiff_t>(end_it.current->distance_from_desired), end_it.current - begin_it.current);
+ EntryPointer to_return = end_it.current - num_to_move;
+ for (EntryPointer it = end_it.current; !it->is_at_desired_position();)
+ {
+ EntryPointer target = it - num_to_move;
+ target->emplace(it->distance_from_desired - num_to_move, std::move(it->value));
+ it->destroy_value();
+ ++it;
+ num_to_move = std::min(static_cast<ptrdiff_t>(it->distance_from_desired), num_to_move);
+ }
+ return { to_return };
+ }
+
+ size_t erase(const FindKey & key)
+ {
+ auto found = find(key);
+ if (found == end())
+ return 0;
+ else
+ {
+ erase(found);
+ return 1;
+ }
+ }
+
+ void clear()
+ {
+ for (EntryPointer it = entries, end = it + static_cast<ptrdiff_t>(num_slots_minus_one + max_lookups); it != end; ++it)
+ {
+ if (it->has_value())
+ it->destroy_value();
+ }
+ num_elements = 0;
+ }
+
+ void shrink_to_fit()
+ {
+ rehash_for_other_container(*this);
+ }
+
+ void swap(sherwood_v3_table & other)
+ {
+ using std::swap;
+ swap_pointers(other);
+ swap(static_cast<ArgumentHash &>(*this), static_cast<ArgumentHash &>(other));
+ swap(static_cast<ArgumentEqual &>(*this), static_cast<ArgumentEqual &>(other));
+ if (AllocatorTraits::propagate_on_container_swap::value)
+ swap(static_cast<EntryAlloc &>(*this), static_cast<EntryAlloc &>(other));
+ }
+
+ size_t size() const
+ {
+ return num_elements;
+ }
+ size_t max_size() const
+ {
+ return (AllocatorTraits::max_size(*this)) / sizeof(Entry);
+ }
+ size_t bucket_count() const
+ {
+ return num_slots_minus_one ? num_slots_minus_one + 1 : 0;
+ }
+ size_type max_bucket_count() const
+ {
+ return (AllocatorTraits::max_size(*this) - min_lookups) / sizeof(Entry);
+ }
+ size_t bucket(const FindKey & key) const
+ {
+ return hash_policy.index_for_hash(hash_object(key), num_slots_minus_one);
+ }
+ float load_factor() const
+ {
+ size_t buckets = bucket_count();
+ if (buckets)
+ return static_cast<float>(num_elements) / bucket_count();
+ else
+ return 0;
+ }
+ void max_load_factor(float value)
+ {
+ _max_load_factor = value;
+ }
+ float max_load_factor() const
+ {
+ return _max_load_factor;
+ }
+
+ bool empty() const
+ {
+ return num_elements == 0;
+ }
+
+private:
+ EntryPointer entries = Entry::empty_default_table();
+ size_t num_slots_minus_one = 0;
+ typename HashPolicySelector<ArgumentHash>::type hash_policy;
+ int8_t max_lookups = detailv3::min_lookups - 1;
+ float _max_load_factor = 0.5f;
+ size_t num_elements = 0;
+
+ static int8_t compute_max_lookups(size_t num_buckets)
+ {
+ int8_t desired = detailv3::log2(num_buckets);
+ return std::max(detailv3::min_lookups, desired);
+ }
+
+ size_t num_buckets_for_reserve(size_t num_elements) const
+ {
+ return static_cast<size_t>(std::ceil(num_elements / std::min(0.5, static_cast<double>(_max_load_factor))));
+ }
+ void rehash_for_other_container(const sherwood_v3_table & other)
+ {
+ rehash(std::min(num_buckets_for_reserve(other.size()), other.bucket_count()));
+ }
+
+ void swap_pointers(sherwood_v3_table & other)
+ {
+ using std::swap;
+ swap(hash_policy, other.hash_policy);
+ swap(entries, other.entries);
+ swap(num_slots_minus_one, other.num_slots_minus_one);
+ swap(num_elements, other.num_elements);
+ swap(max_lookups, other.max_lookups);
+ swap(_max_load_factor, other._max_load_factor);
+ }
+
+ template<typename Key, typename... Args>
+ SKA_NOINLINE(std::pair<iterator, bool>) emplace_new_key(int8_t distance_from_desired, EntryPointer current_entry, Key && key, Args &&... args)
+ {
+ using std::swap;
+ if (num_slots_minus_one == 0 || distance_from_desired == max_lookups || num_elements + 1 > (num_slots_minus_one + 1) * static_cast<double>(_max_load_factor))
+ {
+ grow();
+ return emplace(std::forward<Key>(key), std::forward<Args>(args)...);
+ }
+ else if (current_entry->is_empty())
+ {
+ current_entry->emplace(distance_from_desired, std::forward<Key>(key), std::forward<Args>(args)...);
+ ++num_elements;
+ return { { current_entry }, true };
+ }
+ value_type to_insert(std::forward<Key>(key), std::forward<Args>(args)...);
+ swap(distance_from_desired, current_entry->distance_from_desired);
+ swap(to_insert, current_entry->value);
+ iterator result = { current_entry };
+ for (++distance_from_desired, ++current_entry;; ++current_entry)
+ {
+ if (current_entry->is_empty())
+ {
+ current_entry->emplace(distance_from_desired, std::move(to_insert));
+ ++num_elements;
+ return { result, true };
+ }
+ else if (current_entry->distance_from_desired < distance_from_desired)
+ {
+ swap(distance_from_desired, current_entry->distance_from_desired);
+ swap(to_insert, current_entry->value);
+ ++distance_from_desired;
+ }
+ else
+ {
+ ++distance_from_desired;
+ if (distance_from_desired == max_lookups)
+ {
+ swap(to_insert, result.current->value);
+ grow();
+ return emplace(std::move(to_insert));
+ }
+ }
+ }
+ }
+
+ void grow()
+ {
+ rehash(std::max(size_t(4), 2 * bucket_count()));
+ }
+
+ void deallocate_data(EntryPointer begin, size_t num_slots_minus_one, int8_t max_lookups)
+ {
+ if (begin != Entry::empty_default_table())
+ {
+ AllocatorTraits::deallocate(*this, begin, num_slots_minus_one + max_lookups + 1);
+ }
+ }
+
+ void reset_to_empty_state()
+ {
+ deallocate_data(entries, num_slots_minus_one, max_lookups);
+ entries = Entry::empty_default_table();
+ num_slots_minus_one = 0;
+ hash_policy.reset();
+ max_lookups = detailv3::min_lookups - 1;
+ }
+
+ template<typename U>
+ size_t hash_object(const U & key)
+ {
+ return static_cast<Hasher &>(*this)(key);
+ }
+ template<typename U>
+ size_t hash_object(const U & key) const
+ {
+ return static_cast<const Hasher &>(*this)(key);
+ }
+ template<typename L, typename R>
+ bool compares_equal(const L & lhs, const R & rhs)
+ {
+ return static_cast<Equal &>(*this)(lhs, rhs);
+ }
+
+ struct convertible_to_iterator
+ {
+ EntryPointer it;
+
+ operator iterator()
+ {
+ if (it->has_value())
+ return { it };
+ else
+ return ++iterator{it};
+ }
+ operator const_iterator()
+ {
+ if (it->has_value())
+ return { it };
+ else
+ return ++const_iterator{it};
+ }
+ };
+
+};
+}
+
+struct prime_number_hash_policy
+{
+ static size_t mod0(size_t) { return 0llu; }
+ static size_t mod2(size_t hash) { return hash % 2llu; }
+ static size_t mod3(size_t hash) { return hash % 3llu; }
+ static size_t mod5(size_t hash) { return hash % 5llu; }
+ static size_t mod7(size_t hash) { return hash % 7llu; }
+ static size_t mod11(size_t hash) { return hash % 11llu; }
+ static size_t mod13(size_t hash) { return hash % 13llu; }
+ static size_t mod17(size_t hash) { return hash % 17llu; }
+ static size_t mod23(size_t hash) { return hash % 23llu; }
+ static size_t mod29(size_t hash) { return hash % 29llu; }
+ static size_t mod37(size_t hash) { return hash % 37llu; }
+ static size_t mod47(size_t hash) { return hash % 47llu; }
+ static size_t mod59(size_t hash) { return hash % 59llu; }
+ static size_t mod73(size_t hash) { return hash % 73llu; }
+ static size_t mod97(size_t hash) { return hash % 97llu; }
+ static size_t mod127(size_t hash) { return hash % 127llu; }
+ static size_t mod151(size_t hash) { return hash % 151llu; }
+ static size_t mod197(size_t hash) { return hash % 197llu; }
+ static size_t mod251(size_t hash) { return hash % 251llu; }
+ static size_t mod313(size_t hash) { return hash % 313llu; }
+ static size_t mod397(size_t hash) { return hash % 397llu; }
+ static size_t mod499(size_t hash) { return hash % 499llu; }
+ static size_t mod631(size_t hash) { return hash % 631llu; }
+ static size_t mod797(size_t hash) { return hash % 797llu; }
+ static size_t mod1009(size_t hash) { return hash % 1009llu; }
+ static size_t mod1259(size_t hash) { return hash % 1259llu; }
+ static size_t mod1597(size_t hash) { return hash % 1597llu; }
+ static size_t mod2011(size_t hash) { return hash % 2011llu; }
+ static size_t mod2539(size_t hash) { return hash % 2539llu; }
+ static size_t mod3203(size_t hash) { return hash % 3203llu; }
+ static size_t mod4027(size_t hash) { return hash % 4027llu; }
+ static size_t mod5087(size_t hash) { return hash % 5087llu; }
+ static size_t mod6421(size_t hash) { return hash % 6421llu; }
+ static size_t mod8089(size_t hash) { return hash % 8089llu; }
+ static size_t mod10193(size_t hash) { return hash % 10193llu; }
+ static size_t mod12853(size_t hash) { return hash % 12853llu; }
+ static size_t mod16193(size_t hash) { return hash % 16193llu; }
+ static size_t mod20399(size_t hash) { return hash % 20399llu; }
+ static size_t mod25717(size_t hash) { return hash % 25717llu; }
+ static size_t mod32401(size_t hash) { return hash % 32401llu; }
+ static size_t mod40823(size_t hash) { return hash % 40823llu; }
+ static size_t mod51437(size_t hash) { return hash % 51437llu; }
+ static size_t mod64811(size_t hash) { return hash % 64811llu; }
+ static size_t mod81649(size_t hash) { return hash % 81649llu; }
+ static size_t mod102877(size_t hash) { return hash % 102877llu; }
+ static size_t mod129607(size_t hash) { return hash % 129607llu; }
+ static size_t mod163307(size_t hash) { return hash % 163307llu; }
+ static size_t mod205759(size_t hash) { return hash % 205759llu; }
+ static size_t mod259229(size_t hash) { return hash % 259229llu; }
+ static size_t mod326617(size_t hash) { return hash % 326617llu; }
+ static size_t mod411527(size_t hash) { return hash % 411527llu; }
+ static size_t mod518509(size_t hash) { return hash % 518509llu; }
+ static size_t mod653267(size_t hash) { return hash % 653267llu; }
+ static size_t mod823117(size_t hash) { return hash % 823117llu; }
+ static size_t mod1037059(size_t hash) { return hash % 1037059llu; }
+ static size_t mod1306601(size_t hash) { return hash % 1306601llu; }
+ static size_t mod1646237(size_t hash) { return hash % 1646237llu; }
+ static size_t mod2074129(size_t hash) { return hash % 2074129llu; }
+ static size_t mod2613229(size_t hash) { return hash % 2613229llu; }
+ static size_t mod3292489(size_t hash) { return hash % 3292489llu; }
+ static size_t mod4148279(size_t hash) { return hash % 4148279llu; }
+ static size_t mod5226491(size_t hash) { return hash % 5226491llu; }
+ static size_t mod6584983(size_t hash) { return hash % 6584983llu; }
+ static size_t mod8296553(size_t hash) { return hash % 8296553llu; }
+ static size_t mod10453007(size_t hash) { return hash % 10453007llu; }
+ static size_t mod13169977(size_t hash) { return hash % 13169977llu; }
+ static size_t mod16593127(size_t hash) { return hash % 16593127llu; }
+ static size_t mod20906033(size_t hash) { return hash % 20906033llu; }
+ static size_t mod26339969(size_t hash) { return hash % 26339969llu; }
+ static size_t mod33186281(size_t hash) { return hash % 33186281llu; }
+ static size_t mod41812097(size_t hash) { return hash % 41812097llu; }
+ static size_t mod52679969(size_t hash) { return hash % 52679969llu; }
+ static size_t mod66372617(size_t hash) { return hash % 66372617llu; }
+ static size_t mod83624237(size_t hash) { return hash % 83624237llu; }
+ static size_t mod105359939(size_t hash) { return hash % 105359939llu; }
+ static size_t mod132745199(size_t hash) { return hash % 132745199llu; }
+ static size_t mod167248483(size_t hash) { return hash % 167248483llu; }
+ static size_t mod210719881(size_t hash) { return hash % 210719881llu; }
+ static size_t mod265490441(size_t hash) { return hash % 265490441llu; }
+ static size_t mod334496971(size_t hash) { return hash % 334496971llu; }
+ static size_t mod421439783(size_t hash) { return hash % 421439783llu; }
+ static size_t mod530980861(size_t hash) { return hash % 530980861llu; }
+ static size_t mod668993977(size_t hash) { return hash % 668993977llu; }
+ static size_t mod842879579(size_t hash) { return hash % 842879579llu; }
+ static size_t mod1061961721(size_t hash) { return hash % 1061961721llu; }
+ static size_t mod1337987929(size_t hash) { return hash % 1337987929llu; }
+ static size_t mod1685759167(size_t hash) { return hash % 1685759167llu; }
+ static size_t mod2123923447(size_t hash) { return hash % 2123923447llu; }
+ static size_t mod2675975881(size_t hash) { return hash % 2675975881llu; }
+ static size_t mod3371518343(size_t hash) { return hash % 3371518343llu; }
+ static size_t mod4247846927(size_t hash) { return hash % 4247846927llu; }
+ static size_t mod5351951779(size_t hash) { return hash % 5351951779llu; }
+ static size_t mod6743036717(size_t hash) { return hash % 6743036717llu; }
+ static size_t mod8495693897(size_t hash) { return hash % 8495693897llu; }
+ static size_t mod10703903591(size_t hash) { return hash % 10703903591llu; }
+ static size_t mod13486073473(size_t hash) { return hash % 13486073473llu; }
+ static size_t mod16991387857(size_t hash) { return hash % 16991387857llu; }
+ static size_t mod21407807219(size_t hash) { return hash % 21407807219llu; }
+ static size_t mod26972146961(size_t hash) { return hash % 26972146961llu; }
+ static size_t mod33982775741(size_t hash) { return hash % 33982775741llu; }
+ static size_t mod42815614441(size_t hash) { return hash % 42815614441llu; }
+ static size_t mod53944293929(size_t hash) { return hash % 53944293929llu; }
+ static size_t mod67965551447(size_t hash) { return hash % 67965551447llu; }
+ static size_t mod85631228929(size_t hash) { return hash % 85631228929llu; }
+ static size_t mod107888587883(size_t hash) { return hash % 107888587883llu; }
+ static size_t mod135931102921(size_t hash) { return hash % 135931102921llu; }
+ static size_t mod171262457903(size_t hash) { return hash % 171262457903llu; }
+ static size_t mod215777175787(size_t hash) { return hash % 215777175787llu; }
+ static size_t mod271862205833(size_t hash) { return hash % 271862205833llu; }
+ static size_t mod342524915839(size_t hash) { return hash % 342524915839llu; }
+ static size_t mod431554351609(size_t hash) { return hash % 431554351609llu; }
+ static size_t mod543724411781(size_t hash) { return hash % 543724411781llu; }
+ static size_t mod685049831731(size_t hash) { return hash % 685049831731llu; }
+ static size_t mod863108703229(size_t hash) { return hash % 863108703229llu; }
+ static size_t mod1087448823553(size_t hash) { return hash % 1087448823553llu; }
+ static size_t mod1370099663459(size_t hash) { return hash % 1370099663459llu; }
+ static size_t mod1726217406467(size_t hash) { return hash % 1726217406467llu; }
+ static size_t mod2174897647073(size_t hash) { return hash % 2174897647073llu; }
+ static size_t mod2740199326961(size_t hash) { return hash % 2740199326961llu; }
+ static size_t mod3452434812973(size_t hash) { return hash % 3452434812973llu; }
+ static size_t mod4349795294267(size_t hash) { return hash % 4349795294267llu; }
+ static size_t mod5480398654009(size_t hash) { return hash % 5480398654009llu; }
+ static size_t mod6904869625999(size_t hash) { return hash % 6904869625999llu; }
+ static size_t mod8699590588571(size_t hash) { return hash % 8699590588571llu; }
+ static size_t mod10960797308051(size_t hash) { return hash % 10960797308051llu; }
+ static size_t mod13809739252051(size_t hash) { return hash % 13809739252051llu; }
+ static size_t mod17399181177241(size_t hash) { return hash % 17399181177241llu; }
+ static size_t mod21921594616111(size_t hash) { return hash % 21921594616111llu; }
+ static size_t mod27619478504183(size_t hash) { return hash % 27619478504183llu; }
+ static size_t mod34798362354533(size_t hash) { return hash % 34798362354533llu; }
+ static size_t mod43843189232363(size_t hash) { return hash % 43843189232363llu; }
+ static size_t mod55238957008387(size_t hash) { return hash % 55238957008387llu; }
+ static size_t mod69596724709081(size_t hash) { return hash % 69596724709081llu; }
+ static size_t mod87686378464759(size_t hash) { return hash % 87686378464759llu; }
+ static size_t mod110477914016779(size_t hash) { return hash % 110477914016779llu; }
+ static size_t mod139193449418173(size_t hash) { return hash % 139193449418173llu; }
+ static size_t mod175372756929481(size_t hash) { return hash % 175372756929481llu; }
+ static size_t mod220955828033581(size_t hash) { return hash % 220955828033581llu; }
+ static size_t mod278386898836457(size_t hash) { return hash % 278386898836457llu; }
+ static size_t mod350745513859007(size_t hash) { return hash % 350745513859007llu; }
+ static size_t mod441911656067171(size_t hash) { return hash % 441911656067171llu; }
+ static size_t mod556773797672909(size_t hash) { return hash % 556773797672909llu; }
+ static size_t mod701491027718027(size_t hash) { return hash % 701491027718027llu; }
+ static size_t mod883823312134381(size_t hash) { return hash % 883823312134381llu; }
+ static size_t mod1113547595345903(size_t hash) { return hash % 1113547595345903llu; }
+ static size_t mod1402982055436147(size_t hash) { return hash % 1402982055436147llu; }
+ static size_t mod1767646624268779(size_t hash) { return hash % 1767646624268779llu; }
+ static size_t mod2227095190691797(size_t hash) { return hash % 2227095190691797llu; }
+ static size_t mod2805964110872297(size_t hash) { return hash % 2805964110872297llu; }
+ static size_t mod3535293248537579(size_t hash) { return hash % 3535293248537579llu; }
+ static size_t mod4454190381383713(size_t hash) { return hash % 4454190381383713llu; }
+ static size_t mod5611928221744609(size_t hash) { return hash % 5611928221744609llu; }
+ static size_t mod7070586497075177(size_t hash) { return hash % 7070586497075177llu; }
+ static size_t mod8908380762767489(size_t hash) { return hash % 8908380762767489llu; }
+ static size_t mod11223856443489329(size_t hash) { return hash % 11223856443489329llu; }
+ static size_t mod14141172994150357(size_t hash) { return hash % 14141172994150357llu; }
+ static size_t mod17816761525534927(size_t hash) { return hash % 17816761525534927llu; }
+ static size_t mod22447712886978529(size_t hash) { return hash % 22447712886978529llu; }
+ static size_t mod28282345988300791(size_t hash) { return hash % 28282345988300791llu; }
+ static size_t mod35633523051069991(size_t hash) { return hash % 35633523051069991llu; }
+ static size_t mod44895425773957261(size_t hash) { return hash % 44895425773957261llu; }
+ static size_t mod56564691976601587(size_t hash) { return hash % 56564691976601587llu; }
+ static size_t mod71267046102139967(size_t hash) { return hash % 71267046102139967llu; }
+ static size_t mod89790851547914507(size_t hash) { return hash % 89790851547914507llu; }
+ static size_t mod113129383953203213(size_t hash) { return hash % 113129383953203213llu; }
+ static size_t mod142534092204280003(size_t hash) { return hash % 142534092204280003llu; }
+ static size_t mod179581703095829107(size_t hash) { return hash % 179581703095829107llu; }
+ static size_t mod226258767906406483(size_t hash) { return hash % 226258767906406483llu; }
+ static size_t mod285068184408560057(size_t hash) { return hash % 285068184408560057llu; }
+ static size_t mod359163406191658253(size_t hash) { return hash % 359163406191658253llu; }
+ static size_t mod452517535812813007(size_t hash) { return hash % 452517535812813007llu; }
+ static size_t mod570136368817120201(size_t hash) { return hash % 570136368817120201llu; }
+ static size_t mod718326812383316683(size_t hash) { return hash % 718326812383316683llu; }
+ static size_t mod905035071625626043(size_t hash) { return hash % 905035071625626043llu; }
+ static size_t mod1140272737634240411(size_t hash) { return hash % 1140272737634240411llu; }
+ static size_t mod1436653624766633509(size_t hash) { return hash % 1436653624766633509llu; }
+ static size_t mod1810070143251252131(size_t hash) { return hash % 1810070143251252131llu; }
+ static size_t mod2280545475268481167(size_t hash) { return hash % 2280545475268481167llu; }
+ static size_t mod2873307249533267101(size_t hash) { return hash % 2873307249533267101llu; }
+ static size_t mod3620140286502504283(size_t hash) { return hash % 3620140286502504283llu; }
+ static size_t mod4561090950536962147(size_t hash) { return hash % 4561090950536962147llu; }
+ static size_t mod5746614499066534157(size_t hash) { return hash % 5746614499066534157llu; }
+ static size_t mod7240280573005008577(size_t hash) { return hash % 7240280573005008577llu; }
+ static size_t mod9122181901073924329(size_t hash) { return hash % 9122181901073924329llu; }
+ static size_t mod11493228998133068689(size_t hash) { return hash % 11493228998133068689llu; }
+ static size_t mod14480561146010017169(size_t hash) { return hash % 14480561146010017169llu; }
+ static size_t mod18446744073709551557(size_t hash) { return hash % 18446744073709551557llu; }
+
+ using mod_function = size_t (*)(size_t);
+
+ mod_function next_size_over(size_t & size) const
+ {
+ // prime numbers generated by the following method:
+ // 1. start with a prime p = 2
+ // 2. go to wolfram alpha and get p = NextPrime(2 * p)
+ // 3. repeat 2. until you overflow 64 bits
+ // you now have large gaps which you would hit if somebody called reserve() with an unlucky number.
+ // 4. to fill the gaps for every prime p go to wolfram alpha and get ClosestPrime(p * 2^(1/3)) and ClosestPrime(p * 2^(2/3)) and put those in the gaps
+ // 5. get PrevPrime(2^64) and put it at the end
+ static constexpr const size_t prime_list[] =
+ {
+ 2llu, 3llu, 5llu, 7llu, 11llu, 13llu, 17llu, 23llu, 29llu, 37llu, 47llu,
+ 59llu, 73llu, 97llu, 127llu, 151llu, 197llu, 251llu, 313llu, 397llu,
+ 499llu, 631llu, 797llu, 1009llu, 1259llu, 1597llu, 2011llu, 2539llu,
+ 3203llu, 4027llu, 5087llu, 6421llu, 8089llu, 10193llu, 12853llu, 16193llu,
+ 20399llu, 25717llu, 32401llu, 40823llu, 51437llu, 64811llu, 81649llu,
+ 102877llu, 129607llu, 163307llu, 205759llu, 259229llu, 326617llu,
+ 411527llu, 518509llu, 653267llu, 823117llu, 1037059llu, 1306601llu,
+ 1646237llu, 2074129llu, 2613229llu, 3292489llu, 4148279llu, 5226491llu,
+ 6584983llu, 8296553llu, 10453007llu, 13169977llu, 16593127llu, 20906033llu,
+ 26339969llu, 33186281llu, 41812097llu, 52679969llu, 66372617llu,
+ 83624237llu, 105359939llu, 132745199llu, 167248483llu, 210719881llu,
+ 265490441llu, 334496971llu, 421439783llu, 530980861llu, 668993977llu,
+ 842879579llu, 1061961721llu, 1337987929llu, 1685759167llu, 2123923447llu,
+ 2675975881llu, 3371518343llu, 4247846927llu, 5351951779llu, 6743036717llu,
+ 8495693897llu, 10703903591llu, 13486073473llu, 16991387857llu,
+ 21407807219llu, 26972146961llu, 33982775741llu, 42815614441llu,
+ 53944293929llu, 67965551447llu, 85631228929llu, 107888587883llu,
+ 135931102921llu, 171262457903llu, 215777175787llu, 271862205833llu,
+ 342524915839llu, 431554351609llu, 543724411781llu, 685049831731llu,
+ 863108703229llu, 1087448823553llu, 1370099663459llu, 1726217406467llu,
+ 2174897647073llu, 2740199326961llu, 3452434812973llu, 4349795294267llu,
+ 5480398654009llu, 6904869625999llu, 8699590588571llu, 10960797308051llu,
+ 13809739252051llu, 17399181177241llu, 21921594616111llu, 27619478504183llu,
+ 34798362354533llu, 43843189232363llu, 55238957008387llu, 69596724709081llu,
+ 87686378464759llu, 110477914016779llu, 139193449418173llu,
+ 175372756929481llu, 220955828033581llu, 278386898836457llu,
+ 350745513859007llu, 441911656067171llu, 556773797672909llu,
+ 701491027718027llu, 883823312134381llu, 1113547595345903llu,
+ 1402982055436147llu, 1767646624268779llu, 2227095190691797llu,
+ 2805964110872297llu, 3535293248537579llu, 4454190381383713llu,
+ 5611928221744609llu, 7070586497075177llu, 8908380762767489llu,
+ 11223856443489329llu, 14141172994150357llu, 17816761525534927llu,
+ 22447712886978529llu, 28282345988300791llu, 35633523051069991llu,
+ 44895425773957261llu, 56564691976601587llu, 71267046102139967llu,
+ 89790851547914507llu, 113129383953203213llu, 142534092204280003llu,
+ 179581703095829107llu, 226258767906406483llu, 285068184408560057llu,
+ 359163406191658253llu, 452517535812813007llu, 570136368817120201llu,
+ 718326812383316683llu, 905035071625626043llu, 1140272737634240411llu,
+ 1436653624766633509llu, 1810070143251252131llu, 2280545475268481167llu,
+ 2873307249533267101llu, 3620140286502504283llu, 4561090950536962147llu,
+ 5746614499066534157llu, 7240280573005008577llu, 9122181901073924329llu,
+ 11493228998133068689llu, 14480561146010017169llu, 18446744073709551557llu
+ };
+ static constexpr size_t (* const mod_functions[])(size_t) =
+ {
+ &mod0, &mod2, &mod3, &mod5, &mod7, &mod11, &mod13, &mod17, &mod23, &mod29, &mod37,
+ &mod47, &mod59, &mod73, &mod97, &mod127, &mod151, &mod197, &mod251, &mod313, &mod397,
+ &mod499, &mod631, &mod797, &mod1009, &mod1259, &mod1597, &mod2011, &mod2539, &mod3203,
+ &mod4027, &mod5087, &mod6421, &mod8089, &mod10193, &mod12853, &mod16193, &mod20399,
+ &mod25717, &mod32401, &mod40823, &mod51437, &mod64811, &mod81649, &mod102877,
+ &mod129607, &mod163307, &mod205759, &mod259229, &mod326617, &mod411527, &mod518509,
+ &mod653267, &mod823117, &mod1037059, &mod1306601, &mod1646237, &mod2074129,
+ &mod2613229, &mod3292489, &mod4148279, &mod5226491, &mod6584983, &mod8296553,
+ &mod10453007, &mod13169977, &mod16593127, &mod20906033, &mod26339969, &mod33186281,
+ &mod41812097, &mod52679969, &mod66372617, &mod83624237, &mod105359939, &mod132745199,
+ &mod167248483, &mod210719881, &mod265490441, &mod334496971, &mod421439783,
+ &mod530980861, &mod668993977, &mod842879579, &mod1061961721, &mod1337987929,
+ &mod1685759167, &mod2123923447, &mod2675975881, &mod3371518343, &mod4247846927,
+ &mod5351951779, &mod6743036717, &mod8495693897, &mod10703903591, &mod13486073473,
+ &mod16991387857, &mod21407807219, &mod26972146961, &mod33982775741, &mod42815614441,
+ &mod53944293929, &mod67965551447, &mod85631228929, &mod107888587883, &mod135931102921,
+ &mod171262457903, &mod215777175787, &mod271862205833, &mod342524915839,
+ &mod431554351609, &mod543724411781, &mod685049831731, &mod863108703229,
+ &mod1087448823553, &mod1370099663459, &mod1726217406467, &mod2174897647073,
+ &mod2740199326961, &mod3452434812973, &mod4349795294267, &mod5480398654009,
+ &mod6904869625999, &mod8699590588571, &mod10960797308051, &mod13809739252051,
+ &mod17399181177241, &mod21921594616111, &mod27619478504183, &mod34798362354533,
+ &mod43843189232363, &mod55238957008387, &mod69596724709081, &mod87686378464759,
+ &mod110477914016779, &mod139193449418173, &mod175372756929481, &mod220955828033581,
+ &mod278386898836457, &mod350745513859007, &mod441911656067171, &mod556773797672909,
+ &mod701491027718027, &mod883823312134381, &mod1113547595345903, &mod1402982055436147,
+ &mod1767646624268779, &mod2227095190691797, &mod2805964110872297, &mod3535293248537579,
+ &mod4454190381383713, &mod5611928221744609, &mod7070586497075177, &mod8908380762767489,
+ &mod11223856443489329, &mod14141172994150357, &mod17816761525534927,
+ &mod22447712886978529, &mod28282345988300791, &mod35633523051069991,
+ &mod44895425773957261, &mod56564691976601587, &mod71267046102139967,
+ &mod89790851547914507, &mod113129383953203213, &mod142534092204280003,
+ &mod179581703095829107, &mod226258767906406483, &mod285068184408560057,
+ &mod359163406191658253, &mod452517535812813007, &mod570136368817120201,
+ &mod718326812383316683, &mod905035071625626043, &mod1140272737634240411,
+ &mod1436653624766633509, &mod1810070143251252131, &mod2280545475268481167,
+ &mod2873307249533267101, &mod3620140286502504283, &mod4561090950536962147,
+ &mod5746614499066534157, &mod7240280573005008577, &mod9122181901073924329,
+ &mod11493228998133068689, &mod14480561146010017169, &mod18446744073709551557
+ };
+ const size_t * found = std::lower_bound(std::begin(prime_list), std::end(prime_list) - 1, size);
+ size = *found;
+ return mod_functions[1 + found - prime_list];
+ }
+ void commit(mod_function new_mod_function)
+ {
+ current_mod_function = new_mod_function;
+ }
+ void reset()
+ {
+ current_mod_function = &mod0;
+ }
+
+ size_t index_for_hash(size_t hash, size_t /*num_slots_minus_one*/) const
+ {
+ return current_mod_function(hash);
+ }
+ size_t keep_in_range(size_t index, size_t num_slots_minus_one) const
+ {
+ return index > num_slots_minus_one ? current_mod_function(index) : index;
+ }
+
+private:
+ mod_function current_mod_function = &mod0;
+};
+
+struct power_of_two_hash_policy
+{
+ size_t index_for_hash(size_t hash, size_t num_slots_minus_one) const
+ {
+ return hash & num_slots_minus_one;
+ }
+ size_t keep_in_range(size_t index, size_t num_slots_minus_one) const
+ {
+ return index_for_hash(index, num_slots_minus_one);
+ }
+ int8_t next_size_over(size_t & size) const
+ {
+ size = detailv3::next_power_of_two(size);
+ return 0;
+ }
+ void commit(int8_t)
+ {
+ }
+ void reset()
+ {
+ }
+
+};
+
+struct fibonacci_hash_policy
+{
+ size_t index_for_hash(size_t hash, size_t /*num_slots_minus_one*/) const
+ {
+ return (11400714819323198485ull * hash) >> shift;
+ }
+ size_t keep_in_range(size_t index, size_t num_slots_minus_one) const
+ {
+ return index & num_slots_minus_one;
+ }
+
+ int8_t next_size_over(size_t & size) const
+ {
+ size = std::max(size_t(2), detailv3::next_power_of_two(size));
+ return 64 - detailv3::log2(size);
+ }
+ void commit(int8_t shift)
+ {
+ this->shift = shift;
+ }
+ void reset()
+ {
+ shift = 63;
+ }
+
+private:
+ int8_t shift = 63;
+};
+
+template<typename K, typename V, typename H = std::hash<K>, typename E = std::equal_to<K>, typename A = std::allocator<std::pair<K, V> > >
+class flat_hash_map
+ : public detailv3::sherwood_v3_table
+ <
+ std::pair<K, V>,
+ K,
+ H,
+ detailv3::KeyOrValueHasher<K, std::pair<K, V>, H>,
+ E,
+ detailv3::KeyOrValueEquality<K, std::pair<K, V>, E>,
+ A,
+ typename std::allocator_traits<A>::template rebind_alloc<detailv3::sherwood_v3_entry<std::pair<K, V>>>
+ >
+{
+ using Table = detailv3::sherwood_v3_table
+ <
+ std::pair<K, V>,
+ K,
+ H,
+ detailv3::KeyOrValueHasher<K, std::pair<K, V>, H>,
+ E,
+ detailv3::KeyOrValueEquality<K, std::pair<K, V>, E>,
+ A,
+ typename std::allocator_traits<A>::template rebind_alloc<detailv3::sherwood_v3_entry<std::pair<K, V>>>
+ >;
+public:
+
+ using key_type = K;
+ using mapped_type = V;
+
+ using Table::Table;
+ flat_hash_map()
+ {
+ }
+
+ inline V & operator[](const K & key)
+ {
+ return emplace(key, convertible_to_value()).first->second;
+ }
+ inline V & operator[](K && key)
+ {
+ return emplace(std::move(key), convertible_to_value()).first->second;
+ }
+ V & at(const K & key)
+ {
+ auto found = this->find(key);
+ if (found == this->end())
+ throw std::out_of_range("Argument passed to at() was not in the map.");
+ return found->second;
+ }
+ const V & at(const K & key) const
+ {
+ auto found = this->find(key);
+ if (found == this->end())
+ throw std::out_of_range("Argument passed to at() was not in the map.");
+ return found->second;
+ }
+
+ using Table::emplace;
+ std::pair<typename Table::iterator, bool> emplace()
+ {
+ return emplace(key_type(), convertible_to_value());
+ }
+ template<typename M>
+ std::pair<typename Table::iterator, bool> insert_or_assign(const key_type & key, M && m)
+ {
+ auto emplace_result = emplace(key, std::forward<M>(m));
+ if (!emplace_result.second)
+ emplace_result.first->second = std::forward<M>(m);
+ return emplace_result;
+ }
+ template<typename M>
+ std::pair<typename Table::iterator, bool> insert_or_assign(key_type && key, M && m)
+ {
+ auto emplace_result = emplace(std::move(key), std::forward<M>(m));
+ if (!emplace_result.second)
+ emplace_result.first->second = std::forward<M>(m);
+ return emplace_result;
+ }
+ template<typename M>
+ typename Table::iterator insert_or_assign(typename Table::const_iterator, const key_type & key, M && m)
+ {
+ return insert_or_assign(key, std::forward<M>(m)).first;
+ }
+ template<typename M>
+ typename Table::iterator insert_or_assign(typename Table::const_iterator, key_type && key, M && m)
+ {
+ return insert_or_assign(std::move(key), std::forward<M>(m)).first;
+ }
+
+ friend bool operator==(const flat_hash_map & lhs, const flat_hash_map & rhs)
+ {
+ if (lhs.size() != rhs.size())
+ return false;
+ for (const typename Table::value_type & value : lhs)
+ {
+ auto found = rhs.find(value.first);
+ if (found == rhs.end())
+ return false;
+ else if (value.second != found->second)
+ return false;
+ }
+ return true;
+ }
+ friend bool operator!=(const flat_hash_map & lhs, const flat_hash_map & rhs)
+ {
+ return !(lhs == rhs);
+ }
+
+private:
+ struct convertible_to_value
+ {
+ operator V() const
+ {
+ return V();
+ }
+ };
+};
+
+template<typename T, typename H = std::hash<T>, typename E = std::equal_to<T>, typename A = std::allocator<T> >
+class flat_hash_set
+ : public detailv3::sherwood_v3_table
+ <
+ T,
+ T,
+ H,
+ detailv3::functor_storage<size_t, H>,
+ E,
+ detailv3::functor_storage<bool, E>,
+ A,
+ typename std::allocator_traits<A>::template rebind_alloc<detailv3::sherwood_v3_entry<T>>
+ >
+{
+ using Table = detailv3::sherwood_v3_table
+ <
+ T,
+ T,
+ H,
+ detailv3::functor_storage<size_t, H>,
+ E,
+ detailv3::functor_storage<bool, E>,
+ A,
+ typename std::allocator_traits<A>::template rebind_alloc<detailv3::sherwood_v3_entry<T>>
+ >;
+public:
+
+ using key_type = T;
+
+ using Table::Table;
+ flat_hash_set()
+ {
+ }
+
+ template<typename... Args>
+ std::pair<typename Table::iterator, bool> emplace(Args &&... args)
+ {
+ return Table::emplace(T(std::forward<Args>(args)...));
+ }
+ std::pair<typename Table::iterator, bool> emplace(const key_type & arg)
+ {
+ return Table::emplace(arg);
+ }
+ std::pair<typename Table::iterator, bool> emplace(key_type & arg)
+ {
+ return Table::emplace(arg);
+ }
+ std::pair<typename Table::iterator, bool> emplace(const key_type && arg)
+ {
+ return Table::emplace(std::move(arg));
+ }
+ std::pair<typename Table::iterator, bool> emplace(key_type && arg)
+ {
+ return Table::emplace(std::move(arg));
+ }
+
+ friend bool operator==(const flat_hash_set & lhs, const flat_hash_set & rhs)
+ {
+ if (lhs.size() != rhs.size())
+ return false;
+ for (const T & value : lhs)
+ {
+ if (rhs.find(value) == rhs.end())
+ return false;
+ }
+ return true;
+ }
+ friend bool operator!=(const flat_hash_set & lhs, const flat_hash_set & rhs)
+ {
+ return !(lhs == rhs);
+ }
+};
+
+
+template<typename T>
+struct power_of_two_std_hash : std::hash<T>
+{
+ typedef ska::power_of_two_hash_policy hash_policy;
+};
+
+} // end namespace ska
diff --git a/Source/Utility/fqms_simplify.hpp b/Source/Utility/fqms_simplify.hpp
new file mode 100644
index 00000000..3e6b68a2
--- /dev/null
+++ b/Source/Utility/fqms_simplify.hpp
@@ -0,0 +1,1053 @@
+//-----------------------------------------------------------------------------
+// Name: fqms_simplify.hpp
+// Developer:
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+/////////////////////////////////////////////
+//
+// Mesh Simplification Tutorial
+//
+// (C) by Sven Forstmann in 2014
+//
+// License : MIT
+// http://opensource.org/licenses/MIT
+//
+//https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification
+//
+// 5/2016: Chris Rorden created minimal version for OSX/Linux/Windows compile
+
+//#include <iostream>
+//#include <stddef.h>
+//#include <functional>
+//#include <sys/stat.h>
+//#include <stdbool.h>
+#include <string.h>
+//#include <ctype.h>
+//#include <float.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <map>
+#include <vector>
+#include <string>
+#include <math.h>
+#include <float.h> //FLT_EPSILON, DBL_EPSILON
+
+#define loopi(start_l,end_l) for ( int i=start_l;i<end_l;++i )
+#define loopi(start_l,end_l) for ( int i=start_l;i<end_l;++i )
+#define loopj(start_l,end_l) for ( int j=start_l;j<end_l;++j )
+#define loopk(start_l,end_l) for ( int k=start_l;k<end_l;++k )
+
+struct vector3
+{
+double x, y, z;
+};
+
+struct vec3f
+{
+ double x, y, z;
+
+ inline vec3f( void ) {}
+
+ //inline vec3f operator =( vector3 a )
+ // { vec3f b ; b.x = a.x; b.y = a.y; b.z = a.z; return b;}
+
+ inline vec3f( vector3 a )
+ { x = a.x; y = a.y; z = a.z; }
+
+ inline vec3f( const double X, const double Y, const double Z )
+ { x = X; y = Y; z = Z; }
+
+ inline vec3f operator + ( const vec3f& a ) const
+ { return vec3f( x + a.x, y + a.y, z + a.z ); }
+
+ inline vec3f operator += ( const vec3f& a ) const
+ { return vec3f( x + a.x, y + a.y, z + a.z ); }
+
+ inline vec3f operator * ( const double a ) const
+ { return vec3f( x * a, y * a, z * a ); }
+
+ inline vec3f operator * ( const vec3f a ) const
+ { return vec3f( x * a.x, y * a.y, z * a.z ); }
+
+ inline vec3f v3 () const
+ { return vec3f( x , y, z ); }
+
+ inline vec3f operator = ( const vector3 a )
+ { x=a.x;y=a.y;z=a.z;return *this; }
+
+ inline vec3f operator = ( const vec3f a )
+ { x=a.x;y=a.y;z=a.z;return *this; }
+
+ inline vec3f operator / ( const vec3f a ) const
+ { return vec3f( x / a.x, y / a.y, z / a.z ); }
+
+ inline vec3f operator - ( const vec3f& a ) const
+ { return vec3f( x - a.x, y - a.y, z - a.z ); }
+
+ inline vec3f operator / ( const double a ) const
+ { return vec3f( x / a, y / a, z / a ); }
+
+ inline double dot( const vec3f& a ) const
+ { return a.x*x + a.y*y + a.z*z; }
+
+ inline vec3f cross( const vec3f& a , const vec3f& b )
+ {
+ x = a.y * b.z - a.z * b.y;
+ y = a.z * b.x - a.x * b.z;
+ z = a.x * b.y - a.y * b.x;
+ return *this;
+ }
+
+ inline double angle( const vec3f& v )
+ {
+ vec3f a = v , b = *this;
+ double dot = v.x*x + v.y*y + v.z*z;
+ double len = a.length() * b.length();
+ if(len==0)len=0.00001f;
+ double input = dot / len;
+ if (input<-1) input=-1;
+ if (input>1) input=1;
+ return (double) acos ( input );
+ }
+
+ inline double angle2( const vec3f& v , const vec3f& w )
+ {
+ vec3f a = v , b= *this;
+ double dot = a.x*b.x + a.y*b.y + a.z*b.z;
+ double len = a.length() * b.length();
+ if(len==0)len=1;
+
+ vec3f plane; plane.cross( b,w );
+
+ if ( plane.x * a.x + plane.y * a.y + plane.z * a.z > 0 )
+ return (double) -acos ( dot / len );
+
+ return (double) acos ( dot / len );
+ }
+
+ inline vec3f rot_x( double a )
+ {
+ double yy = cos ( a ) * y + sin ( a ) * z;
+ double zz = cos ( a ) * z - sin ( a ) * y;
+ y = yy; z = zz;
+ return *this;
+ }
+ inline vec3f rot_y( double a )
+ {
+ double xx = cos ( -a ) * x + sin ( -a ) * z;
+ double zz = cos ( -a ) * z - sin ( -a ) * x;
+ x = xx; z = zz;
+ return *this;
+ }
+ inline void clamp( double min, double max )
+ {
+ if (x<min) x=min;
+ if (y<min) y=min;
+ if (z<min) z=min;
+ if (x>max) x=max;
+ if (y>max) y=max;
+ if (z>max) z=max;
+ }
+ inline vec3f rot_z( double a )
+ {
+ double yy = cos ( a ) * y + sin ( a ) * x;
+ double xx = cos ( a ) * x - sin ( a ) * y;
+ y = yy; x = xx;
+ return *this;
+ }
+ inline vec3f invert()
+ {
+ x=-x;y=-y;z=-z;return *this;
+ }
+ inline vec3f frac()
+ {
+ return vec3f(
+ x-double(int(x)),
+ y-double(int(y)),
+ z-double(int(z))
+ );
+ }
+
+ inline vec3f integer()
+ {
+ return vec3f(
+ double(int(x)),
+ double(int(y)),
+ double(int(z))
+ );
+ }
+
+ inline double length() const
+ {
+ return (double)sqrt(x*x + y*y + z*z);
+ }
+
+ inline vec3f normalize( double desired_length = 1 )
+ {
+ double square = sqrt(x*x + y*y + z*z);
+ /*
+ if (square <= 0.00001f )
+ {
+ x=1;y=0;z=0;
+ return *this;
+ }*/
+ //double len = desired_length / square;
+ x/=square;y/=square;z/=square;
+
+ return *this;
+ }
+ static vec3f normalize( vec3f a );
+
+ static void random_init();
+ static double random_double();
+ static vec3f random();
+
+ static int random_number;
+
+ double random_double_01(double a){
+ double rnf=a*14.434252+a*364.2343+a*4213.45352+a*2341.43255+a*254341.43535+a*223454341.3523534245+23453.423412;
+ int rni=((int)rnf)%100000;
+ return double(rni)/(100000.0f-1.0f);
+ }
+
+ vec3f random01_fxyz(){
+ x=(double)random_double_01(x);
+ y=(double)random_double_01(y);
+ z=(double)random_double_01(z);
+ return *this;
+ }
+
+};
+
+vec3f barycentric(const vec3f &p, const vec3f &a, const vec3f &b, const vec3f &c){
+ vec3f v0 = b-a;
+ vec3f v1 = c-a;
+ vec3f v2 = p-a;
+ double d00 = v0.dot(v0);
+ double d01 = v0.dot(v1);
+ double d11 = v1.dot(v1);
+ double d20 = v2.dot(v0);
+ double d21 = v2.dot(v1);
+ double denom = d00*d11-d01*d01;
+ double v = (d11 * d20 - d01 * d21) / denom;
+ double w = (d00 * d21 - d01 * d20) / denom;
+ double u = 1.0 - v - w;
+ return vec3f(u,v,w);
+}
+
+vec3f interpolate(const vec3f &p, const vec3f &a, const vec3f &b, const vec3f &c, const vec3f attrs[3])
+{
+ vec3f bary = barycentric(p,a,b,c);
+ vec3f out = vec3f(0,0,0);
+ out = out + attrs[0] * bary.x;
+ out = out + attrs[1] * bary.y;
+ out = out + attrs[2] * bary.z;
+ return out;
+}
+
+double min(double v1, double v2) {
+ return fmin(v1,v2);
+}
+
+
+class SymetricMatrix {
+
+ public:
+
+ // Constructor
+
+ SymetricMatrix(double c=0) { loopi(0,10) m[i] = c; }
+
+ SymetricMatrix( double m11, double m12, double m13, double m14,
+ double m22, double m23, double m24,
+ double m33, double m34,
+ double m44) {
+ m[0] = m11; m[1] = m12; m[2] = m13; m[3] = m14;
+ m[4] = m22; m[5] = m23; m[6] = m24;
+ m[7] = m33; m[8] = m34;
+ m[9] = m44;
+ }
+
+ // Make plane
+
+ SymetricMatrix(double a,double b,double c,double d)
+ {
+ m[0] = a*a; m[1] = a*b; m[2] = a*c; m[3] = a*d;
+ m[4] = b*b; m[5] = b*c; m[6] = b*d;
+ m[7 ] =c*c; m[8 ] = c*d;
+ m[9 ] = d*d;
+ }
+
+ double operator[](int c) const { return m[c]; }
+
+ // Determinant
+
+ double det( int a11, int a12, int a13,
+ int a21, int a22, int a23,
+ int a31, int a32, int a33)
+ {
+ double det = m[a11]*m[a22]*m[a33] + m[a13]*m[a21]*m[a32] + m[a12]*m[a23]*m[a31]
+ - m[a13]*m[a22]*m[a31] - m[a11]*m[a23]*m[a32]- m[a12]*m[a21]*m[a33];
+ return det;
+ }
+
+ const SymetricMatrix operator+(const SymetricMatrix& n) const
+ {
+ return SymetricMatrix( m[0]+n[0], m[1]+n[1], m[2]+n[2], m[3]+n[3],
+ m[4]+n[4], m[5]+n[5], m[6]+n[6],
+ m[ 7]+n[ 7], m[ 8]+n[8 ],
+ m[ 9]+n[9 ]);
+ }
+
+ SymetricMatrix& operator+=(const SymetricMatrix& n)
+ {
+ m[0]+=n[0]; m[1]+=n[1]; m[2]+=n[2]; m[3]+=n[3];
+ m[4]+=n[4]; m[5]+=n[5]; m[6]+=n[6]; m[7]+=n[7];
+ m[8]+=n[8]; m[9]+=n[9];
+ return *this;
+ }
+
+ double m[10];
+};
+///////////////////////////////////////////
+
+namespace Simplify
+{
+ // Global Variables & Strctures
+ enum Attributes {
+ NONE,
+ NORMAL = 2,
+ TEXCOORD = 4,
+ COLOR = 8
+ };
+ struct Triangle { int v[3];double err[4];int deleted,dirty,attr;vec3f n;vec3f uvs[3];int material; };
+ struct Vertex { vec3f p;int tstart,tcount;SymetricMatrix q;int border;};
+ struct Ref { int tid,tvertex; };
+ std::vector<Triangle> triangles;
+ std::vector<Vertex> vertices;
+ std::vector<Ref> refs;
+ std::string mtllib;
+ std::vector<std::string> materials;
+
+ // Helper functions
+
+ double vertex_error(SymetricMatrix q, double x, double y, double z);
+ double calculate_error(int id_v1, int id_v2, vec3f &p_result);
+ bool flipped(vec3f p,int i0,int i1,Vertex &v0,Vertex &v1,std::vector<int> &deleted);
+ void update_uvs(int i0,const Vertex &v,const vec3f &p,std::vector<int> &deleted);
+ void update_triangles(int i0,Vertex &v,std::vector<int> &deleted,int &deleted_triangles);
+ void update_mesh(int iteration);
+ void compact_mesh();
+
+ //Get the number of active triangles. Useful for getting an idea of the results after simplification.
+ int triangle_count() {
+ int count = 0;
+ for(int i = 0; i < triangles.size(); i++) {
+ if(triangles[i].deleted == false) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ //
+ // Main simplification function
+ //
+ // target_count : target nr. of triangles
+ // agressiveness : sharpness to increase the threshold.
+ // 5..8 are good numbers
+ // more iterations yield higher quality
+ //
+
+ void simplify_mesh(int target_count, double agressiveness=7, bool verbose=false)
+ {
+ // init
+ loopi(0,triangles.size())
+ {
+ triangles[i].deleted=0;
+ }
+
+ // main iteration loop
+ int deleted_triangles=0;
+ std::vector<int> deleted0,deleted1;
+ int triangle_count=triangles.size();
+ //int iteration = 0;
+ //loop(iteration,0,100)
+ for (int iteration = 0; iteration < 100; iteration ++)
+ {
+ if(triangle_count-deleted_triangles<=target_count)break;
+
+ // update mesh once in a while
+ if(iteration%5==0)
+ {
+ update_mesh(iteration);
+ }
+
+ // clear dirty flag
+ loopi(0,triangles.size()) triangles[i].dirty=0;
+
+ //
+ // All triangles with edges below the threshold will be removed
+ //
+ // The following numbers works well for most models.
+ // If it does not, try to adjust the 3 parameters
+ //
+ double threshold = 0.000000001*pow(double(iteration+3),agressiveness);
+
+ // target number of triangles reached ? Then break
+ if ((verbose) && (iteration%5==0)) {
+ printf("iteration %d - triangles %d threshold %g\n",iteration,triangle_count-deleted_triangles, threshold);
+ }
+
+ // remove vertices & mark deleted triangles
+ loopi(0,triangles.size())
+ {
+ Triangle &t=triangles[i];
+ if(t.err[3]>threshold) continue;
+ if(t.deleted) continue;
+ if(t.dirty) continue;
+
+ loopj(0,3)if(t.err[j]<threshold)
+ {
+
+ int i0=t.v[ j ]; Vertex &v0 = vertices[i0];
+ int i1=t.v[(j+1)%3]; Vertex &v1 = vertices[i1];
+ // Border check
+ if(v0.border != v1.border) continue;
+
+ // Compute vertex to collapse to
+ vec3f p;
+ calculate_error(i0,i1,p);
+ deleted0.resize(v0.tcount); // normals temporarily
+ deleted1.resize(v1.tcount); // normals temporarily
+ // don't remove if flipped
+ if( flipped(p,i0,i1,v0,v1,deleted0) ) continue;
+
+ if( flipped(p,i1,i0,v1,v0,deleted1) ) continue;
+
+ if ( (t.attr & TEXCOORD) == TEXCOORD )
+ {
+ update_uvs(i0,v0,p,deleted0);
+ update_uvs(i0,v1,p,deleted1);
+ }
+
+ // not flipped, so remove edge
+ v0.p=p;
+ v0.q=v1.q+v0.q;
+ int tstart=refs.size();
+
+ update_triangles(i0,v0,deleted0,deleted_triangles);
+ update_triangles(i0,v1,deleted1,deleted_triangles);
+
+ int tcount=refs.size()-tstart;
+
+ if(tcount<=v0.tcount)
+ {
+ // save ram
+ if(tcount)memcpy(&refs[v0.tstart],&refs[tstart],tcount*sizeof(Ref));
+ }
+ else
+ // append
+ v0.tstart=tstart;
+
+ v0.tcount=tcount;
+ break;
+ }
+ // done?
+ if(triangle_count-deleted_triangles<=target_count)break;
+ }
+ }
+ // clean up mesh
+ compact_mesh();
+ } //simplify_mesh()
+
+ void simplify_mesh_lossless(bool verbose=false)
+ {
+ // init
+ loopi(0,triangles.size()) triangles[i].deleted=0;
+
+ // main iteration loop
+ int deleted_triangles=0;
+ std::vector<int> deleted0,deleted1;
+ int triangle_count=triangles.size();
+ //int iteration = 0;
+ //loop(iteration,0,100)
+ for (int iteration = 0; iteration < 9999; iteration ++)
+ {
+ // update mesh constantly
+ update_mesh(iteration);
+ // clear dirty flag
+ loopi(0,triangles.size()) triangles[i].dirty=0;
+ //
+ // All triangles with edges below the threshold will be removed
+ //
+ // The following numbers works well for most models.
+ // If it does not, try to adjust the 3 parameters
+ //
+ double threshold = DBL_EPSILON; //1.0E-3 EPS;
+ if (verbose) {
+ printf("lossless iteration %d\n", iteration);
+ }
+
+ // remove vertices & mark deleted triangles
+ loopi(0,triangles.size())
+ {
+ Triangle &t=triangles[i];
+ if(t.err[3]>threshold) continue;
+ if(t.deleted) continue;
+ if(t.dirty) continue;
+
+ loopj(0,3)if(t.err[j]<threshold)
+ {
+ int i0=t.v[ j ]; Vertex &v0 = vertices[i0];
+ int i1=t.v[(j+1)%3]; Vertex &v1 = vertices[i1];
+
+ // Border check
+ if(v0.border != v1.border) continue;
+
+ // Compute vertex to collapse to
+ vec3f p;
+ calculate_error(i0,i1,p);
+
+ deleted0.resize(v0.tcount); // normals temporarily
+ deleted1.resize(v1.tcount); // normals temporarily
+
+ // don't remove if flipped
+ if( flipped(p,i0,i1,v0,v1,deleted0) ) continue;
+ if( flipped(p,i1,i0,v1,v0,deleted1) ) continue;
+
+ if ( (t.attr & TEXCOORD) == TEXCOORD )
+ {
+ update_uvs(i0,v0,p,deleted0);
+ update_uvs(i0,v1,p,deleted1);
+ }
+
+ // not flipped, so remove edge
+ v0.p=p;
+ v0.q=v1.q+v0.q;
+ int tstart=refs.size();
+
+ update_triangles(i0,v0,deleted0,deleted_triangles);
+ update_triangles(i0,v1,deleted1,deleted_triangles);
+
+ int tcount=refs.size()-tstart;
+
+ if(tcount<=v0.tcount)
+ {
+ // save ram
+ if(tcount)memcpy(&refs[v0.tstart],&refs[tstart],tcount*sizeof(Ref));
+ }
+ else
+ // append
+ v0.tstart=tstart;
+
+ v0.tcount=tcount;
+ break;
+ }
+ }
+ if(deleted_triangles<=0)break;
+ deleted_triangles=0;
+ } //for each iteration
+ // clean up mesh
+ compact_mesh();
+ } //simplify_mesh_lossless()
+
+
+ // Check if a triangle flips when this edge is removed
+
+ bool flipped(vec3f p,int i0,int i1,Vertex &v0,Vertex &v1,std::vector<int> &deleted)
+ {
+
+ loopk(0,v0.tcount)
+ {
+ Triangle &t=triangles[refs[v0.tstart+k].tid];
+ if(t.deleted)continue;
+
+ int s=refs[v0.tstart+k].tvertex;
+ int id1=t.v[(s+1)%3];
+ int id2=t.v[(s+2)%3];
+
+ if(id1==i1 || id2==i1) // delete ?
+ {
+
+ deleted[k]=1;
+ continue;
+ }
+ vec3f d1 = vertices[id1].p-p; d1.normalize();
+ vec3f d2 = vertices[id2].p-p; d2.normalize();
+ if(fabs(d1.dot(d2))>0.999) return true;
+ vec3f n;
+ n.cross(d1,d2);
+ n.normalize();
+ deleted[k]=0;
+ if(n.dot(t.n)<0.2) return true;
+ }
+ return false;
+ }
+
+ // update_uvs
+
+ void update_uvs(int i0,const Vertex &v,const vec3f &p,std::vector<int> &deleted)
+ {
+ loopk(0,v.tcount)
+ {
+ Ref &r=refs[v.tstart+k];
+ Triangle &t=triangles[r.tid];
+ if(t.deleted)continue;
+ if(deleted[k])continue;
+ vec3f p1=vertices[t.v[0]].p;
+ vec3f p2=vertices[t.v[1]].p;
+ vec3f p3=vertices[t.v[2]].p;
+ t.uvs[r.tvertex] = interpolate(p,p1,p2,p3,t.uvs);
+ }
+ }
+
+ // Update triangle connections and edge error after a edge is collapsed
+
+ void update_triangles(int i0,Vertex &v,std::vector<int> &deleted,int &deleted_triangles)
+ {
+ vec3f p;
+ loopk(0,v.tcount)
+ {
+ Ref &r=refs[v.tstart+k];
+ Triangle &t=triangles[r.tid];
+ if(t.deleted)continue;
+ if(deleted[k])
+ {
+ t.deleted=1;
+ deleted_triangles++;
+ continue;
+ }
+ t.v[r.tvertex]=i0;
+ t.dirty=1;
+ t.err[0]=calculate_error(t.v[0],t.v[1],p);
+ t.err[1]=calculate_error(t.v[1],t.v[2],p);
+ t.err[2]=calculate_error(t.v[2],t.v[0],p);
+ t.err[3]=min(t.err[0],min(t.err[1],t.err[2]));
+ refs.push_back(r);
+ }
+ }
+
+ // compact triangles, compute edge error and build reference list
+
+ void update_mesh(int iteration)
+ {
+ if(iteration>0) // compact triangles
+ {
+ int dst=0;
+ loopi(0,triangles.size())
+ if(!triangles[i].deleted)
+ {
+ triangles[dst++]=triangles[i];
+ }
+ triangles.resize(dst);
+ }
+ //
+
+ // Init Reference ID list
+ loopi(0,vertices.size())
+ {
+ vertices[i].tstart=0;
+ vertices[i].tcount=0;
+ }
+ loopi(0,triangles.size())
+ {
+ Triangle &t=triangles[i];
+ loopj(0,3) vertices[t.v[j]].tcount++;
+ }
+ int tstart=0;
+ loopi(0,vertices.size())
+ {
+ Vertex &v=vertices[i];
+ v.tstart=tstart;
+ tstart+=v.tcount;
+ v.tcount=0;
+ }
+
+ // Write References
+ refs.resize(triangles.size()*3);
+ loopi(0,triangles.size())
+ {
+ Triangle &t=triangles[i];
+ loopj(0,3)
+ {
+ Vertex &v=vertices[t.v[j]];
+ refs[v.tstart+v.tcount].tid=i;
+ refs[v.tstart+v.tcount].tvertex=j;
+ v.tcount++;
+ }
+ }
+
+ // Init Quadrics by Plane & Edge Errors
+ //
+ // required at the beginning ( iteration == 0 )
+ // recomputing during the simplification is not required,
+ // but mostly improves the result for closed meshes
+ //
+ if( iteration == 0 )
+ {
+ // Identify boundary : vertices[].border=0,1
+
+ std::vector<int> vcount,vids;
+
+ loopi(0,vertices.size())
+ vertices[i].border=0;
+
+ loopi(0,vertices.size())
+ {
+ Vertex &v=vertices[i];
+ vcount.clear();
+ vids.clear();
+ loopj(0,v.tcount)
+ {
+ int k=refs[v.tstart+j].tid;
+ Triangle &t=triangles[k];
+ loopk(0,3)
+ {
+ int ofs=0,id=t.v[k];
+ while(ofs<vcount.size())
+ {
+ if(vids[ofs]==id)break;
+ ofs++;
+ }
+ if(ofs==vcount.size())
+ {
+ vcount.push_back(1);
+ vids.push_back(id);
+ }
+ else
+ vcount[ofs]++;
+ }
+ }
+ loopj(0,vcount.size()) if(vcount[j]==1)
+ vertices[vids[j]].border=1;
+ }
+ //initialize errors
+ loopi(0,vertices.size())
+ vertices[i].q=SymetricMatrix(0.0);
+
+ loopi(0,triangles.size())
+ {
+ Triangle &t=triangles[i];
+ vec3f n,p[3];
+ loopj(0,3) p[j]=vertices[t.v[j]].p;
+ n.cross(p[1]-p[0],p[2]-p[0]);
+ n.normalize();
+ t.n=n;
+ loopj(0,3) vertices[t.v[j]].q =
+ vertices[t.v[j]].q+SymetricMatrix(n.x,n.y,n.z,-n.dot(p[0]));
+ }
+ loopi(0,triangles.size())
+ {
+ // Calc Edge Error
+ Triangle &t=triangles[i];vec3f p;
+ loopj(0,3) t.err[j]=calculate_error(t.v[j],t.v[(j+1)%3],p);
+ t.err[3]=min(t.err[0],min(t.err[1],t.err[2]));
+ }
+ }
+ }
+
+ // Finally compact mesh before exiting
+
+ void compact_mesh()
+ {
+ int dst=0;
+ loopi(0,vertices.size())
+ {
+ vertices[i].tcount=0;
+ }
+ loopi(0,triangles.size())
+ if(!triangles[i].deleted)
+ {
+ Triangle &t=triangles[i];
+ triangles[dst++]=t;
+ loopj(0,3)vertices[t.v[j]].tcount=1;
+ }
+ triangles.resize(dst);
+ dst=0;
+ loopi(0,vertices.size())
+ if(vertices[i].tcount)
+ {
+ vertices[i].tstart=dst;
+ vertices[dst].p=vertices[i].p;
+ dst++;
+ }
+ loopi(0,triangles.size())
+ {
+ Triangle &t=triangles[i];
+ loopj(0,3)t.v[j]=vertices[t.v[j]].tstart;
+ }
+ vertices.resize(dst);
+ }
+
+ // Error between vertex and Quadric
+
+ double vertex_error(SymetricMatrix q, double x, double y, double z)
+ {
+ return q[0]*x*x + 2*q[1]*x*y + 2*q[2]*x*z + 2*q[3]*x + q[4]*y*y
+ + 2*q[5]*y*z + 2*q[6]*y + q[7]*z*z + 2*q[8]*z + q[9];
+ }
+
+ // Error for one edge
+
+ double calculate_error(int id_v1, int id_v2, vec3f &p_result)
+ {
+ // compute interpolated vertex
+
+ SymetricMatrix q = vertices[id_v1].q + vertices[id_v2].q;
+ bool border = vertices[id_v1].border & vertices[id_v2].border;
+ double error=0;
+ double det = q.det(0, 1, 2, 1, 4, 5, 2, 5, 7);
+ if ( det != 0 && !border )
+ {
+
+ // q_delta is invertible
+ p_result.x = -1/det*(q.det(1, 2, 3, 4, 5, 6, 5, 7 , 8)); // vx = A41/det(q_delta)
+ p_result.y = 1/det*(q.det(0, 2, 3, 1, 5, 6, 2, 7 , 8)); // vy = A42/det(q_delta)
+ p_result.z = -1/det*(q.det(0, 1, 3, 1, 4, 6, 2, 5, 8)); // vz = A43/det(q_delta)
+
+ error = vertex_error(q, p_result.x, p_result.y, p_result.z);
+ }
+ else
+ {
+ // det = 0 -> try to find best result
+ vec3f p1=vertices[id_v1].p;
+ vec3f p2=vertices[id_v2].p;
+ vec3f p3=(p1+p2)/2;
+ double error1 = vertex_error(q, p1.x,p1.y,p1.z);
+ double error2 = vertex_error(q, p2.x,p2.y,p2.z);
+ double error3 = vertex_error(q, p3.x,p3.y,p3.z);
+ error = min(error1, min(error2, error3));
+ if (error1 == error) p_result=p1;
+ if (error2 == error) p_result=p2;
+ if (error3 == error) p_result=p3;
+ }
+ return error;
+ }
+
+ char *trimwhitespace(char *str)
+ {
+ char *end;
+
+ // Trim leading space
+ while(isspace((unsigned char)*str)) str++;
+
+ if(*str == 0) // All spaces?
+ return str;
+
+ // Trim trailing space
+ end = str + strlen(str) - 1;
+ while(end > str && isspace((unsigned char)*end)) end--;
+
+ // Write new null terminator
+ *(end+1) = 0;
+
+ return str;
+ }
+
+ //Option : Load OBJ
+ void load_obj(const char* filename, bool process_uv=false){
+ vertices.clear();
+ triangles.clear();
+ //printf ( "Loading Objects %s ... \n",filename);
+ FILE* fn;
+ if(filename==NULL) return ;
+ if((char)filename[0]==0) return ;
+ if ((fn = fopen(filename, "rb")) == NULL)
+ {
+ printf ( "File %s not found!\n" ,filename );
+ return;
+ }
+ char line[1000];
+ memset ( line,0,1000 );
+ int vertex_cnt = 0;
+ int material = -1;
+ std::map<std::string, int> material_map;
+ std::vector<vec3f> uvs;
+ std::vector<std::vector<int> > uvMap;
+
+ while(fgets( line, 1000, fn ) != NULL)
+ {
+ Vertex v;
+ vec3f uv;
+
+ if (strncmp(line, "mtllib", 6) == 0)
+ {
+ mtllib = trimwhitespace(&line[7]);
+ }
+ if (strncmp(line, "usemtl", 6) == 0)
+ {
+ std::string usemtl = trimwhitespace(&line[7]);
+ if (material_map.find(usemtl) == material_map.end())
+ {
+ material_map[usemtl] = materials.size();
+ materials.push_back(usemtl);
+ }
+ material = material_map[usemtl];
+ }
+
+ if ( line[0] == 'v' && line[1] == 't' )
+ {
+ if ( line[2] == ' ' )
+ if(sscanf(line,"vt %lf %lf",
+ &uv.x,&uv.y)==2)
+ {
+ uv.z = 0;
+ uvs.push_back(uv);
+ } else
+ if(sscanf(line,"vt %lf %lf %lf",
+ &uv.x,&uv.y,&uv.z)==3)
+ {
+ uvs.push_back(uv);
+ }
+ }
+ else if ( line[0] == 'v' )
+ {
+ if ( line[1] == ' ' )
+ if(sscanf(line,"v %lf %lf %lf",
+ &v.p.x, &v.p.y, &v.p.z)==3)
+ {
+ vertices.push_back(v);
+ }
+ }
+ int integers[9];
+ if ( line[0] == 'f' )
+ {
+ Triangle t;
+ bool tri_ok = false;
+ bool has_uv = false;
+
+ if(sscanf(line,"f %d %d %d",
+ &integers[0],&integers[1],&integers[2])==3)
+ {
+ tri_ok = true;
+ }else
+ if(sscanf(line,"f %d// %d// %d//",
+ &integers[0],&integers[1],&integers[2])==3)
+ {
+ tri_ok = true;
+ }else
+ if(sscanf(line,"f %d//%d %d//%d %d//%d",
+ &integers[0],&integers[3],
+ &integers[1],&integers[4],
+ &integers[2],&integers[5])==6)
+ {
+ tri_ok = true;
+ }else
+ if(sscanf(line,"f %d/%d/%d %d/%d/%d %d/%d/%d",
+ &integers[0],&integers[6],&integers[3],
+ &integers[1],&integers[7],&integers[4],
+ &integers[2],&integers[8],&integers[5])==9)
+ {
+ tri_ok = true;
+ has_uv = true;
+ }else // Add Support for v/vt only meshes
+ if (sscanf(line, "f %d/%d %d/%d %d/%d",
+ &integers[0], &integers[6],
+ &integers[1], &integers[7],
+ &integers[2], &integers[8]) == 6)
+ {
+ tri_ok = true;
+ has_uv = true;
+ }
+ else
+ {
+ printf("unrecognized sequence\n");
+ printf("%s\n",line);
+ while(1);
+ }
+ if ( tri_ok )
+ {
+ t.v[0] = integers[0]-1-vertex_cnt;
+ t.v[1] = integers[1]-1-vertex_cnt;
+ t.v[2] = integers[2]-1-vertex_cnt;
+ t.attr = 0;
+
+ if ( process_uv && has_uv )
+ {
+ std::vector<int> indices;
+ indices.push_back(integers[6]-1-vertex_cnt);
+ indices.push_back(integers[7]-1-vertex_cnt);
+ indices.push_back(integers[8]-1-vertex_cnt);
+ uvMap.push_back(indices);
+ t.attr |= TEXCOORD;
+ }
+
+ t.material = material;
+ //geo.triangles.push_back ( tri );
+ triangles.push_back(t);
+ //state_before = state;
+ //state ='f';
+ }
+ }
+ }
+
+ if ( process_uv && uvs.size() )
+ {
+ loopi(0,triangles.size())
+ {
+ loopj(0,3)
+ triangles[i].uvs[j] = uvs[uvMap[i][j]];
+ }
+ }
+
+ fclose(fn);
+
+ //printf("load_obj: vertices = %lu, triangles = %lu, uvs = %lu\n", vertices.size(), triangles.size(), uvs.size() );
+ } // load_obj()
+
+ // Optional : Store as OBJ
+
+ void write_obj(const char* filename)
+ {
+ FILE *file=fopen(filename, "w");
+ int cur_material = -1;
+ bool has_uv = (triangles.size() && (triangles[0].attr & TEXCOORD) == TEXCOORD);
+
+ if (!file)
+ {
+ printf("write_obj: can't write data file \"%s\".\n", filename);
+ exit(0);
+ }
+ if (!mtllib.empty())
+ {
+ fprintf(file, "mtllib %s\n", mtllib.c_str());
+ }
+ loopi(0,vertices.size())
+ {
+ //fprintf(file, "v %lf %lf %lf\n", vertices[i].p.x,vertices[i].p.y,vertices[i].p.z);
+ fprintf(file, "v %g %g %g\n", vertices[i].p.x,vertices[i].p.y,vertices[i].p.z); //more compact: remove trailing zeros
+ }
+ if (has_uv)
+ {
+ loopi(0,triangles.size()) if(!triangles[i].deleted)
+ {
+ fprintf(file, "vt %g %g\n", triangles[i].uvs[0].x, triangles[i].uvs[0].y);
+ fprintf(file, "vt %g %g\n", triangles[i].uvs[1].x, triangles[i].uvs[1].y);
+ fprintf(file, "vt %g %g\n", triangles[i].uvs[2].x, triangles[i].uvs[2].y);
+ }
+ }
+ int uv = 1;
+ loopi(0,triangles.size()) if(!triangles[i].deleted)
+ {
+ if (triangles[i].material != cur_material)
+ {
+ cur_material = triangles[i].material;
+ fprintf(file, "usemtl %s\n", materials[triangles[i].material].c_str());
+ }
+ if (has_uv)
+ {
+ fprintf(file, "f %d/%d %d/%d %d/%d\n", triangles[i].v[0]+1, uv, triangles[i].v[1]+1, uv+1, triangles[i].v[2]+1, uv+2);
+ uv += 3;
+ }
+ else
+ {
+ fprintf(file, "f %d %d %d\n", triangles[i].v[0]+1, triangles[i].v[1]+1, triangles[i].v[2]+1);
+ }
+ //fprintf(file, "f %d// %d// %d//\n", triangles[i].v[0]+1, triangles[i].v[1]+1, triangles[i].v[2]+1); //more compact: remove trailing zeros
+ }
+ fclose(file);
+ }
+};
+///////////////////////////////////////////
diff --git a/Source/Utility/gl_util.cpp b/Source/Utility/gl_util.cpp
new file mode 100644
index 00000000..a80cc5db
--- /dev/null
+++ b/Source/Utility/gl_util.cpp
@@ -0,0 +1,99 @@
+//-----------------------------------------------------------------------------
+// Name: gl_util.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "gl_util.h"
+
+const char* GLStringInternalFormat( const GLenum internal_format ) {
+ switch( internal_format ) {
+ case GL_COMPRESSED_RED:
+ return "COMPRESSED_RED RED Generic unorm";
+ case GL_COMPRESSED_RG:
+ return "COMPRESSED_RG RG Generic unorm";
+ case GL_COMPRESSED_RGB:
+ return "COMPRESSED_RGB RGB Generic unorm";
+ case GL_COMPRESSED_RGBA:
+ return "COMPRESSED_RGBA RGBA Generic unorm";
+ case GL_COMPRESSED_SRGB:
+ return "COMPRESSED_SRGB RGB Generic unorm";
+ case GL_COMPRESSED_SRGB_ALPHA:
+ return "COMPRESSED_SRGB_ALPHA RGBA Generic unorm";
+ case GL_COMPRESSED_RED_RGTC1:
+ return "COMPRESSED_RED_RGTC1 RED Specific unorm";
+ case GL_COMPRESSED_SIGNED_RED_RGTC1:
+ return "COMPRESSED_SIGNED_RED_RGTC1 RED Specific snorm";
+ case GL_COMPRESSED_RG_RGTC2:
+ return "COMPRESSED_RG_RGTC2 RG Specific unorm";
+ case GL_COMPRESSED_SIGNED_RG_RGTC2:
+ return "COMPRESSED_SIGNED_RG_RGTC2 RG Specific snorm";
+ case GL_COMPRESSED_RGBA_BPTC_UNORM:
+ return "COMPRESSED_RGBA_BPTC_UNORM RGBA Specific unorm";
+ case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:
+ return "COMPRESSED_SRGB_ALPHA_BPTC_UNORM RGBA Specific unorm";
+ case GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT:
+ return "COMPRESSED_RGB_BPTC_SIGNED_FLOAT RGB Specific float";
+ case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT:
+ return "COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT RGB Specific float";
+ case GL_COMPRESSED_RGB8_ETC2:
+ return "COMPRESSED_RGB8_ETC2 RGB Specific unorm";
+ case GL_COMPRESSED_SRGB8_ETC2:
+ return "COMPRESSED_SRGB8_ETC2 RGB Specific unorm";
+ case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
+ return "COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 RGB Specific unorm";
+ case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 :
+ return "COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 RGB Specific unorm";
+ case GL_COMPRESSED_RGBA8_ETC2_EAC:
+ return "COMPRESSED_RGBA8_ETC2_EAC RGBA Specific unorm";
+ case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
+ return "COMPRESSED_SRGB8_ALPHA8_ETC2_EAC RGBA Specific unorm";
+ case GL_COMPRESSED_R11_EAC:
+ return "COMPRESSED_R11_EAC RED Specific unorm";
+ case GL_COMPRESSED_SIGNED_R11_EAC:
+ return "COMPRESSED_SIGNED_R11_EAC RED Specific snorm";
+ case GL_COMPRESSED_RG11_EAC:
+ return "COMPRESSED_RG11_EAC RG Specific unorm";
+ case GL_COMPRESSED_SIGNED_RG11_EAC:
+ return "COMPRESSED_SIGNED_RG11_EAC RG Specific snorm";
+
+ case GL_SRGB_ALPHA:
+ return "GL_SRGB_ALPHA";
+ case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
+ return "GL_COMPRESSED_SRGB_S3TC_DXT1_EXT";
+ case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
+ return "GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT";
+ case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
+ return "GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT";
+ case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
+ return "GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT";
+
+ case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
+ return "GL_COMPRESSED_RGBA_S3TC_DXT5_EXT";
+ case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
+ return "GL_COMPRESSED_RGBA_S3TC_DXT3_EXT";
+ case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
+ return "GL_COMPRESSED_RGBA_S3TC_DXT1_EXT";
+ case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
+ return "GL_COMPRESSED_RGB_S3TC_DXT1_EXT";
+
+ default:
+ return "UNKNOWN internal format";
+ }
+}
diff --git a/Source/Utility/gl_util.h b/Source/Utility/gl_util.h
new file mode 100644
index 00000000..514cb0d9
--- /dev/null
+++ b/Source/Utility/gl_util.h
@@ -0,0 +1,30 @@
+//-----------------------------------------------------------------------------
+// Name: gl_util.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+#include <opengl.h>
+
+/*
+ * Simple utility file for working with opengl,
+ * mainly containing functions for translating enums to strings
+ */
+const char* GLStringInternalFormat( const GLenum internal_format );
diff --git a/Source/Utility/hash.cpp b/Source/Utility/hash.cpp
new file mode 100644
index 00000000..18fbea0e
--- /dev/null
+++ b/Source/Utility/hash.cpp
@@ -0,0 +1,56 @@
+//-----------------------------------------------------------------------------
+// Name: hash.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "hash.h"
+
+#include <Internal/filesystem.h>
+
+#include <murmurhash3/MurmurHash3.h>
+
+#include <sstream>
+#include <string>
+#include <iomanip>
+#include <cstring>
+
+std::string MurmurHash::ToString() {
+ std::stringstream ss;
+
+ ss << std::hex << hash[0] << hash[1];
+
+ return ss.str();
+}
+
+MurmurHash GetFileHash( const char* file ) {
+ std::vector<unsigned char> data = readFile(file);
+
+ MurmurHash hash;
+ memset(&hash, 0, sizeof(MurmurHash));
+
+ if( data.size() == 0 )
+ {
+ return hash;
+ }
+
+ MurmurHash3_x86_128( &data[0], data.size(), 1337, hash.hash );
+
+ return hash;
+}
diff --git a/Source/Utility/hash.h b/Source/Utility/hash.h
new file mode 100644
index 00000000..6c503b59
--- /dev/null
+++ b/Source/Utility/hash.h
@@ -0,0 +1,34 @@
+//-----------------------------------------------------------------------------
+// Name: hash.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+#include <Internal/integer.h>
+
+#include <string>
+
+struct MurmurHash {
+ uint64_t hash[2];
+
+ std::string ToString();
+};
+
+MurmurHash GetFileHash( const char* file );
diff --git a/Source/Utility/ieee.h b/Source/Utility/ieee.h
new file mode 100644
index 00000000..2feefb21
--- /dev/null
+++ b/Source/Utility/ieee.h
@@ -0,0 +1,29 @@
+//-----------------------------------------------------------------------------
+// Name: ieee.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+//This functions only returns expected results if the program isn't compiled with --fast-math in gcc as it removes strict IEEE standard compliance.
+inline bool IsNan( const float& val )
+{
+ //The IEEE 745 standard states that this only evaluates to true if val is NaN.
+ return val != val;
+}
diff --git a/Source/Utility/imgui_macros.cpp b/Source/Utility/imgui_macros.cpp
new file mode 100644
index 00000000..0df5d099
--- /dev/null
+++ b/Source/Utility/imgui_macros.cpp
@@ -0,0 +1,36 @@
+//-----------------------------------------------------------------------------
+// Name: imgui_macros.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "imgui_macros.h"
+
+#include <imgui.h>
+
+bool ImGui_TooltipMenuItem( const char* body, const char* shortcut, const char* tooltip ) {
+ bool ret = false;
+ if(ImGui::MenuItem(body, shortcut)) {
+ ret = true;
+ }
+ if(ImGui::IsItemHovered()) {
+ ImGui::SetTooltip("%s", tooltip);
+ }
+ return ret;
+}
diff --git a/Source/Utility/imgui_macros.h b/Source/Utility/imgui_macros.h
new file mode 100644
index 00000000..e9450892
--- /dev/null
+++ b/Source/Utility/imgui_macros.h
@@ -0,0 +1,25 @@
+//-----------------------------------------------------------------------------
+// Name: imgui_macros.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+bool ImGui_TooltipMenuItem( const char* body, const char* shortcut, const char* tooltip );
diff --git a/Source/Utility/math_macro.h b/Source/Utility/math_macro.h
new file mode 100644
index 00000000..fd04ed95
--- /dev/null
+++ b/Source/Utility/math_macro.h
@@ -0,0 +1,25 @@
+//-----------------------------------------------------------------------------
+// Name: math_macro.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#define IS_PWR2(x) (x && ((x & (x-1))==0))
diff --git a/Source/Utility/pcg_basic.cpp b/Source/Utility/pcg_basic.cpp
new file mode 100644
index 00000000..27147e2c
--- /dev/null
+++ b/Source/Utility/pcg_basic.cpp
@@ -0,0 +1,121 @@
+//-----------------------------------------------------------------------------
+// Name: pcg_basic.cpp
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+/*
+ * PCG Random Number Generation for C.
+ *
+ * Copyright 2014 Melissa O'Neill <oneill@pcg-random.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * For additional information about the PCG random number generation scheme,
+ * including its license and other licensing options, visit
+ *
+ * http://www.pcg-random.org
+ */
+
+/*
+ * This code is derived from the full C implementation, which is in turn
+ * derived from the canonical C++ PCG implementation. The C++ version
+ * has many additional features and is preferable if you can use C++ in
+ * your project.
+ */
+
+#include "pcg_basic.h"
+
+// state for global RNGs
+
+static pcg32_random_t pcg32_global = PCG32_INITIALIZER;
+
+// pcg32_srandom(initstate, initseq)
+// pcg32_srandom_r(rng, initstate, initseq):
+// Seed the rng. Specified in two parts, state initializer and a
+// sequence selection constant (a.k.a. stream id)
+
+void pcg32_srandom_r(pcg32_random_t* rng, uint64_t initstate, uint64_t initseq)
+{
+ rng->state = 0U;
+ rng->inc = (initseq << 1u) | 1u;
+ pcg32_random_r(rng);
+ rng->state += initstate;
+ pcg32_random_r(rng);
+}
+
+void pcg32_srandom(uint64_t seed, uint64_t seq)
+{
+ pcg32_srandom_r(&pcg32_global, seed, seq);
+}
+
+// pcg32_random()
+// pcg32_random_r(rng)
+// Generate a uniformly distributed 32-bit random number
+
+uint32_t pcg32_random_r(pcg32_random_t* rng)
+{
+ uint32_t oldstate = (uint32_t) rng->state;
+ rng->state = oldstate * 6364136223846793005ULL + rng->inc;
+ uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
+ uint32_t rot = oldstate >> 59u;
+ return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
+}
+
+uint32_t pcg32_random()
+{
+ return pcg32_random_r(&pcg32_global);
+}
+
+
+// pcg32_boundedrand(bound):
+// pcg32_boundedrand_r(rng, bound):
+// Generate a uniformly distributed number, r, where 0 <= r < bound
+
+uint32_t pcg32_boundedrand_r(pcg32_random_t* rng, uint32_t bound)
+{
+ // To avoid bias, we need to make the range of the RNG a multiple of
+ // bound, which we do by dropping output less than a threshold.
+ // A naive scheme to calculate the threshold would be to do
+ //
+ // uint32_t threshold = 0x100000000ull % bound;
+ //
+ // but 64-bit div/mod is slower than 32-bit div/mod (especially on
+ // 32-bit platforms). In essence, we do
+ //
+ // uint32_t threshold = (0x100000000ull-bound) % bound;
+ //
+ // because this version will calculate the same modulus, but the LHS
+ // value is less than 2^32.
+
+ uint32_t threshold = -bound % bound;
+
+ // Uniformity guarantees that this loop will terminate. In practice, it
+ // should usually terminate quickly; on average (assuming all bounds are
+ // equally likely), 82.25% of the time, we can expect it to require just
+ // one iteration. In the worst case, someone passes a bound of 2^31 + 1
+ // (i.e., 2147483649), which invalidates almost 50% of the range. In
+ // practice, bounds are typically small and only a tiny amount of the range
+ // is eliminated.
+ for (;;) {
+ uint32_t r = pcg32_random_r(rng);
+ if (r >= threshold)
+ return r % bound;
+ }
+}
+
+
+uint32_t pcg32_boundedrand(uint32_t bound)
+{
+ return pcg32_boundedrand_r(&pcg32_global, bound);
+}
+
diff --git a/Source/Utility/pcg_basic.h b/Source/Utility/pcg_basic.h
new file mode 100644
index 00000000..4daadc16
--- /dev/null
+++ b/Source/Utility/pcg_basic.h
@@ -0,0 +1,88 @@
+//-----------------------------------------------------------------------------
+// Name: pcg_basic.cpp
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+/*
+ * PCG Random Number Generation for C.
+ *
+ * Copyright 2014 Melissa O'Neill <oneill@pcg-random.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * For additional information about the PCG random number generation scheme,
+ * including its license and other licensing options, visit
+ *
+ * http://www.pcg-random.org
+ */
+
+/*
+ * This code is derived from the full C implementation, which is in turn
+ * derived from the canonical C++ PCG implementation. The C++ version
+ * has many additional features and is preferable if you can use C++ in
+ * your project.
+ */
+
+/*
+ * NOTE: This source has been slightly modified by Wolfire dev team to function with our version of c++
+ * //Max Danielsson
+ */
+
+#ifndef PCG_BASIC_H_INCLUDED
+#define PCG_BASIC_H_INCLUDED 1
+
+#include <Internal/integer.h>
+
+#if __cplusplus
+extern "C" {
+#endif
+
+struct pcg_state_setseq_64 { // Internals are *Private*.
+ uint64_t state; // RNG state. All values are possible.
+ uint64_t inc; // Controls which RNG sequence (stream) is
+ // selected. Must *always* be odd.
+};
+typedef struct pcg_state_setseq_64 pcg32_random_t;
+
+// If you *must* statically initialize it, here's one.
+
+#define PCG32_INITIALIZER { 0x853c49e6748fea9bULL, 0xda3e39cb94b95bdbULL }
+
+// pcg32_srandom(initstate, initseq)
+// pcg32_srandom_r(rng, initstate, initseq):
+// Seed the rng. Specified in two parts, state initializer and a
+// sequence selection constant (a.k.a. stream id)
+
+void pcg32_srandom(uint64_t initstate, uint64_t initseq);
+void pcg32_srandom_r(pcg32_random_t* rng, uint64_t initstate,
+ uint64_t initseq);
+
+// pcg32_random()
+// pcg32_random_r(rng)
+// Generate a uniformly distributed 32-bit random number
+
+uint32_t pcg32_random(void);
+uint32_t pcg32_random_r(pcg32_random_t* rng);
+
+// pcg32_boundedrand(bound):
+// pcg32_boundedrand_r(rng, bound):
+// Generate a uniformly distributed number, r, where 0 <= r < bound
+
+uint32_t pcg32_boundedrand(uint32_t bound);
+uint32_t pcg32_boundedrand_r(pcg32_random_t* rng, uint32_t bound);
+
+#if __cplusplus
+}
+#endif
+
+#endif // PCG_BASIC_H_INCLUDED
diff --git a/Source/Utility/sdl_util.h b/Source/Utility/sdl_util.h
new file mode 100644
index 00000000..1e1c1def
--- /dev/null
+++ b/Source/Utility/sdl_util.h
@@ -0,0 +1,38 @@
+//-----------------------------------------------------------------------------
+// Name: sdl_util.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <SDL.h>
+
+static const char* SDL_GLprofile_string( SDL_GLprofile v ) {
+ switch( v ) {
+ case SDL_GL_CONTEXT_PROFILE_CORE:
+ return "SDL_GL_CONTEXT_PROFILE_CORE";
+ case SDL_GL_CONTEXT_PROFILE_COMPATIBILITY:
+ return "SDL_GL_CONTEXT_PROFILE_COMPATIBILITY";
+ case SDL_GL_CONTEXT_PROFILE_ES:
+ return "SDL_GL_CONTEXT_PROFILE_ES";
+ default:
+ return "SDL_GL_CONTEXT_PROFILE_UNKNOWN";
+ }
+}
diff --git a/Source/Utility/serialize.cpp b/Source/Utility/serialize.cpp
new file mode 100644
index 00000000..b3e18429
--- /dev/null
+++ b/Source/Utility/serialize.cpp
@@ -0,0 +1,78 @@
+//-----------------------------------------------------------------------------
+// Name: serialize.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "serialize.h"
+
+#include <Internal/integer.h>
+
+#include <cstring>
+
+char to_hex( uint8_t v ) {
+ if( v <= 9 ) {
+ return '0' + v;
+ } else if( v < 16 ) {
+ return 'A' + v - 10;
+ } else {
+ return 'x';
+ }
+}
+
+uint8_t from_hex(char v) {
+ if( v >= '0' && v <= '9' ) {
+ return v - '0';
+ } else if( v >= 'A' && v <= 'F' ) {
+ return v - 'A' + 10;
+ } else {
+ return 0;
+ }
+}
+
+int flags_to_string(char* dest, uint32_t val) {
+ dest[0] = to_hex( (val & 0xF0000000) >> 28);
+ dest[1] = to_hex( (val & 0x0F000000) >> 24);
+ dest[2] = to_hex( (val & 0x00F00000) >> 20);
+ dest[3] = to_hex( (val & 0x000F0000) >> 16);
+ dest[4] = to_hex( (val & 0x0000F000) >> 12);
+ dest[5] = to_hex( (val & 0x00000F00) >> 8);
+ dest[6] = to_hex( (val & 0x000000F0) >> 4);
+ dest[7] = to_hex( (val & 0x0000000F) >> 0);
+ dest[8] = '\0';
+ return 0;
+}
+
+
+int string_flags_to_uint32(uint32_t* dest, const char* source ) {
+ if( strlen( source ) >= 8 ) {
+ *dest = 0x0;
+ *dest |= ((uint32_t)from_hex(source[0])) << 28;
+ *dest |= ((uint32_t)from_hex(source[1])) << 24;
+ *dest |= ((uint32_t)from_hex(source[2])) << 20;
+ *dest |= ((uint32_t)from_hex(source[3])) << 16;
+ *dest |= ((uint32_t)from_hex(source[4])) << 12;
+ *dest |= ((uint32_t)from_hex(source[5])) << 8;
+ *dest |= ((uint32_t)from_hex(source[6])) << 4;
+ *dest |= ((uint32_t)from_hex(source[7])) << 0;
+ return 0;
+ } else {
+ return -1;
+ }
+}
diff --git a/Source/Utility/serialize.h b/Source/Utility/serialize.h
new file mode 100644
index 00000000..592c0cc5
--- /dev/null
+++ b/Source/Utility/serialize.h
@@ -0,0 +1,31 @@
+//-----------------------------------------------------------------------------
+// Name: serialize.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/integer.h>
+
+uint8_t from_hex(char v);
+char to_hex( uint8_t v );
+
+int string_flags_to_uint32(uint32_t* dest, const char* source );
+int flags_to_string(char* dest, uint32_t val);
diff --git a/Source/Utility/set.h b/Source/Utility/set.h
new file mode 100644
index 00000000..74a3bfc3
--- /dev/null
+++ b/Source/Utility/set.h
@@ -0,0 +1,32 @@
+//-----------------------------------------------------------------------------
+// Name: set.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+template <typename T>
+void append( std::set<T>& dest, const std::set<T>& source ) {
+ typename std::set<T>::iterator it = source.begin();
+
+ for( ; it != source.end(); it++ ) {
+ dest.insert(*it);
+ }
+}
diff --git a/Source/Utility/simple_vector.h b/Source/Utility/simple_vector.h
new file mode 100644
index 00000000..3c4f9343
--- /dev/null
+++ b/Source/Utility/simple_vector.h
@@ -0,0 +1,99 @@
+//-----------------------------------------------------------------------------
+// Name: simple_vector.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <Internal/integer.h>
+#include <Utility/assert.h>
+#include <Memory/allocation.h>
+
+#include <algorithm>
+#include <vector>
+
+/*
+ * Very simple vector construct that doesn't call constructors or destructors on the memory allocated.
+ */
+
+static bool SortDescending( int a, int b ) { return a > b; }
+
+template<typename T>
+class SimpleVector {
+private:
+ T* v;
+ uint32_t v_count;
+ uint32_t v_size;
+ uint32_t step;
+
+public:
+ SimpleVector( uint32_t initial_size, uint32_t resize_step ) : v(NULL), v_count(0), v_size(0), step(resize_step)
+ {
+ v = static_cast<T*>(realloc(v,initial_size*sizeof(T)));
+ }
+
+ ~SimpleVector( )
+ {
+ OG_FREE(v);
+ v = NULL;
+ v_count = 0;
+ v_size = 0;
+ }
+
+ uint32_t Allocate()
+ {
+ if(v_count >= v_size)
+ {
+ v_size += step;
+ v = static_cast<T*>(realloc(v,v_size*sizeof(T)));
+ }
+
+ return v_count++;
+ }
+
+ void Deallocate( uint32_t index )
+ {
+ for( unsigned int i = index+1; i < v_count; i++ )
+ {
+ v[i-1] = v[i];
+ }
+ }
+
+ void Deallocate( std::vector<uint32_t> indexes )
+ {
+ std::sort(indexes.begin(), indexes.end(), SortDescending);
+
+ for( unsigned int i = 0; i < indexes.size(); i++ ) {
+ Deallocate(indexes[i]);
+ if( i > 0 ) {
+ LOG_ASSERT(indexes[i] < indexes[i-1]);
+ }
+ }
+
+ }
+
+ size_t Count()
+ {
+ return v_count;
+ }
+
+ T& operator[](uint32_t index)
+ {
+ return v[index];
+ }
+};
diff --git a/Source/Utility/stacktrace.h b/Source/Utility/stacktrace.h
new file mode 100644
index 00000000..dd0d69d1
--- /dev/null
+++ b/Source/Utility/stacktrace.h
@@ -0,0 +1,105 @@
+//-----------------------------------------------------------------------------
+// Name: stacktrace.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include <sstream>
+
+#if PLATFORM_LINUX || PLATFORM_MACOSX
+#include <Memory/allocation.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <execinfo.h>
+
+#include <csignal>
+#include <ctime>
+#include <cstdlib>
+#include <cstdio>
+inline std::string GenerateStacktrace()
+{
+ void *array[20];
+ size_t size;
+
+ // get void*'s for all entries on the stack
+ size = backtrace(array, 20);
+
+ char ** backtrace = backtrace_symbols(array, size);
+
+ std::stringstream ss;
+ for( size_t i = 0; i < size; i++ )
+ {
+ ss << backtrace[i] << std::endl;
+ }
+
+ OG_FREE(backtrace);
+
+ return ss.str();
+}
+#elif defined PLATFORM_WINDOWS && defined OG_DEBUG
+#define NOMINMAX
+#include <Windows.h>
+#include <DbgHelp.h>
+
+#include <sstream>
+#include <algorithm>
+
+inline std::string GenerateStacktrace()
+{
+ const int maxCallers = 62;
+
+ void* callerStack[maxCallers];
+ unsigned short frames = RtlCaptureStackBackTrace(0, maxCallers, callerStack, NULL);
+
+ IMAGEHLP_LINE64 line;
+ line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
+
+ HANDLE handle = GetCurrentProcess();
+
+ std::stringstream ss;
+
+ const static unsigned short MAX_CALLERS_SHOWN = 24;
+ frames = std::min(frames, MAX_CALLERS_SHOWN);
+ for (int i = 0; i < frames; ++i)
+ {
+ DWORD whyMicrosoft;
+ if (SymGetLineFromAddr64(handle, (DWORD64)callerStack[i], &whyMicrosoft, &line))
+ {
+ std::string filename(line.FileName);
+ filename = filename.substr(filename.find_last_of("/\\") + 1);
+ filename = filename.substr(0, filename.find_last_of('.'));
+
+ ss << line.FileName << "(" << line.LineNumber << ")" << std::endl;
+ }
+ else
+ ss << "Couldn't get line numbers. Possibly an external lib " << std::endl;
+ }
+
+ return ss.str();
+}
+#else
+inline std::string GenerateStacktrace()
+{
+ return std::string("No stacktrace available on this platform");
+}
+#endif
diff --git a/Source/Utility/strings.cpp b/Source/Utility/strings.cpp
new file mode 100644
index 00000000..d77a9a8d
--- /dev/null
+++ b/Source/Utility/strings.cpp
@@ -0,0 +1,366 @@
+#include "strings.h"
+
+#include <Logging/logdata.h>
+
+#include <iostream>
+#include <string>
+#include <vector>
+#include <algorithm>
+
+using std::string;
+using std::wstring;
+using std::vector;
+using std::pair;
+using std::endl;
+
+bool endswith(const char* str, const char* ending) {
+ size_t str_len = strlen(str);
+ size_t end_len = strlen(ending);
+
+ if( str_len >= end_len ) {
+ for( unsigned i = 0; i < end_len; i++ ) {
+ int str_len_index = i + str_len - end_len;
+ if( str[str_len_index] != ending[i] ) {
+ return false;
+ }
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool beginswith(const char* str, const char* begin) {
+ size_t str_len = strlen(str);
+ size_t begin_len = strlen(begin);
+
+ if( str_len >= begin_len ) {
+ for( unsigned i = 0; i < begin_len; i++ ) {
+ if( str[i] != begin[i] ) {
+ return false;
+ }
+ }
+ return true;
+ } else {
+ return false;
+ }
+
+}
+
+bool pstrmtch( const char* first, const char* second, size_t count) {
+ if( strlen(first) >= count && strlen(second) >= count ) {
+ for( size_t i = 0; i < count; i++ ) {
+ if( first[i] != second[i] ) {
+ return false;
+ }
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool strcont( const char* string, const char* substring ) {
+ size_t sub_len = strlen(substring);
+ size_t str_len = strlen(string);
+ for( size_t i = 0; i < str_len; i++ ) {
+ bool match = true;
+ if( (str_len - i) >= sub_len ) {
+ for( size_t k = 0; k < sub_len; k++ ) {
+ if( string[i] != substring[k] ) {
+ match = false;
+ break;
+ }
+ }
+ if( match ) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+#ifdef WIN32
+wstring fromUTF8( const string& path_utf8 ) {
+ int size = MultiByteToWideChar(CP_UTF8, 0, path_utf8.c_str(), -1, NULL, 0);
+ wstring path_utf16;
+ path_utf16.resize(size);
+ MultiByteToWideChar(CP_UTF8, 0, path_utf8.c_str(), -1, &path_utf16[0], size);
+ return path_utf16;
+}
+
+string fromUTF16( const wstring& path_utf16 ){
+ int size = WideCharToMultiByte(CP_UTF8, 0, path_utf16.c_str(), -1, NULL, 0, NULL, NULL );
+ string path_utf8;
+ path_utf8.resize(size);
+ WideCharToMultiByte(CP_UTF8, 0, path_utf16.c_str(), -1, &path_utf8[0], size, NULL, NULL);
+ return path_utf8;
+}
+#endif
+
+int FindStringInArray( const char** values, size_t values_size, const char* q )
+{
+ for( unsigned i = 0; i < values_size; i++ )
+ {
+ if( strmtch( values[i], q ) )
+ {
+ return i;
+ }
+ }
+ return -1;
+}
+
+int FindStringInArray( const vector<pair< const char*, const char* > >& values, const char* q )
+{
+ for( unsigned i = 0; i < values.size(); i++ )
+ {
+ if( strmtch( values[i].first, q ) )
+ {
+ return i;
+ }
+ }
+ return -1;
+}
+
+int FindStringInArray( const vector< const char* >& values, const char* q )
+{
+ for( unsigned i = 0; i < values.size(); i++ )
+ {
+ if( strmtch( values[i], q ) )
+ {
+ return i;
+ }
+ }
+ return -1;
+}
+
+void UTF8InPlaceLower( char* arr ) {
+ for( unsigned i = 0; i < strlen( arr ); i++ ) {
+ //A to Z in unicode, this is all the letters in the world, right?
+ if( arr[i] >= 0x41 && arr[i] <= 0x5A ) {
+ arr[i] = arr[i]+0x20;
+ } else {
+ arr[i] = arr[i];
+ }
+ }
+}
+
+int UTF8ToUpper( char* out, size_t outsize, const char* in )
+{
+ //We should be using the ICU lib for this, but good god, it's massive and difficult,
+ //so we just start of with using the first 128 characters which have simple rules. (ASCII).
+ //The version in ICU is much more context sensitive, handles some really interesting cases.
+ //http://site.icu-project.org/download
+ memset(out,'\0',outsize);
+ for( unsigned i = 0; i < strlen( in ) && i < outsize-1; i++ )
+ {
+ //A to Z in unicode, this is all the letters in the world, right?
+ if( in[i] >= 0x61 && in[i] <= 0x7a )
+ {
+ out[i] = in[i]-0x20;
+ }
+ else
+ {
+ out[i] = in[i];
+ }
+ }
+ return strlen(in)+1;
+ //Return how many bytes we wrote to the output string, inluding the terminating.
+ //this is a placeholder for a smart solution in the future, where expansion may occur;
+}
+
+string UTF8ToLower(const string& in) {
+ string out;
+ out.resize(in.size());
+ for( unsigned i = 0; i < out.size(); i++ )
+ {
+ //A to Z in unicode, this is all the letters in the world, right?
+ if( in[i] >= 0x41 && in[i] <= 0x5A )
+ {
+ out[i] = in[i]+0x20;
+ }
+ else
+ {
+ out[i] = in[i];
+ }
+ }
+ return out;
+}
+
+int UTF8ToLower( char* out, size_t outsize, const char* in ) {
+ //We should be using the ICU lib for this, but good god, it's massive and difficult,
+ //so we just start of with using the first 128 characters which have simple rules. (ASCII).
+ //The version in ICU is much more context sensitive, handles some really interesting cases.
+ //http://site.icu-project.org/download
+ memset(out,'\0',outsize);
+ for( unsigned i = 0; i < strlen( in ) && i < outsize-1; i++ )
+ {
+ //A to Z in unicode, this is all the letters in the world, right?
+ if( in[i] >= 0x41 && in[i] <= 0x5A )
+ {
+ out[i] = in[i]+0x20;
+ }
+ else
+ {
+ out[i] = in[i];
+ }
+ }
+ return strlen(in)+1;
+ //Return how many bytes we wrote to the output string, inluding the terminating.
+ //this is a placeholder for a smart solution in the future, where expansion may occur;
+}
+
+bool hasBeginning( const string &fullString, const string &beginning ) {
+ if( fullString.length() >= beginning.length() ) {
+ if( fullString.substr(0,beginning.length()) == beginning ) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool hasEnding (string const &fullString, string const &ending) {
+ if (fullString.length() >= ending.length()) {
+ return (0 == fullString.compare (fullString.length() - ending.length(), ending.length(), ending));
+ } else {
+ return false;
+ }
+}
+
+bool hasAnyOfEndings( const string &fullString, const vector<string> &endings)
+{
+ for( vector<string>::const_iterator sit = endings.begin();
+ sit != endings.end();
+ sit++ )
+ {
+ if( hasEnding( fullString, *sit ) )
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+int CountCharsInString(const char* str, char the_char) {
+ int count = 0;
+ int str_length = (int)strlen(str);
+ for(int i=0; i<str_length; ++i){
+ if(str[i] == the_char){
+ ++count;
+ }
+ }
+ return count;
+}
+
+int FindNthCharFromBack(const char* str, char the_char, int n){
+ int count = 0;
+ int str_length = (int)strlen(str);
+ for(int i= str_length-1; i>=0; --i){
+ if(str[i] == the_char){
+ ++count;
+ if(count == n){
+ return i;
+ }
+ }
+ }
+ return -1;
+}
+
+int saysTrue(const char* str) {
+ if(str) {
+ if(strmtch(str, "true")) {
+ return SAYS_TRUE;
+ } else if(strmtch(str, "false")) {
+ return SAYS_FALSE;
+ } else {
+ return SAYS_TRUE_NO_MATCH;
+ }
+ } else {
+ return SAYS_TRUE_NULL_INPUT;
+ }
+}
+
+const char* nullAsEmpty( const char* str ) {
+ if( str ) {
+ return str;
+ } else {
+ return "";
+ }
+}
+
+uint32_t GetLengthInBytesForNCodepoints( const string& utf8in, uint32_t codepoint_index ) {
+ uint32_t codepoints = 0;
+
+ if( codepoint_index == 0 )
+ return 0;
+
+ for( unsigned i = 0; i < utf8in.size(); i++ ) {
+ uint8_t c = utf8in[i];
+
+ if( ( c & 0x80) == 0 ) {
+ codepoints++;
+ } else if( (c & 0xC0) == 0xC0 ) {
+ codepoints++;
+ if( (c & 0x20) == 0) {
+ i += 1;
+ } else if( (c & 0x10) == 0 ) {
+ i += 2;
+ } else if( (c & 0x08) == 0 ) {
+ i += 3;
+ }
+ } else {
+ LOGE << "Malformed utf-8 string" << endl;
+ }
+
+ if( i >= utf8in.size() ) {
+ LOGE << "Malformed utf-8 string, codepoint indication doesn't match offset" << endl;
+ }
+
+ if( codepoints == codepoint_index ) {
+ return i+1;
+ }
+ }
+ return utf8in.size();
+}
+
+uint32_t GetCodepointCount( const string &utf8in ) {
+ uint32_t codepoints = 0;
+ for( unsigned i = 0; i < utf8in.size(); i++ ) {
+ uint8_t c = utf8in[i];
+ if( ( c & 0x80) == 0 ) {
+ codepoints++;
+ } else if( (c & 0xC0) == 0xC0 ) {
+ codepoints++;
+ if( (c & 0x20) == 0) {
+ i += 1;
+ } else if( (c & 0x10) == 0 ) {
+ i += 2;
+ } else if( (c & 0x08) == 0 ) {
+ i += 3;
+ }
+ } else {
+ LOGE << "Malformed utf-8 string" << endl;
+ }
+ }
+ return codepoints;
+}
+
+string RemoveFileEnding(string in) {
+ size_t last_point = in.size();
+
+ for( int i = in.size()-1; i >= 0; i-- ) {
+ if( in[i] == '.' ) {
+ last_point = i;
+ }
+ if( in[i] == '/' ) {
+ return in.substr(0,last_point);
+ }
+ }
+
+ if( last_point > 0 ) {
+ return in.substr(0,last_point);
+ } else {
+ return in;
+ }
+}
+
diff --git a/Source/Utility/strings.h b/Source/Utility/strings.h
new file mode 100644
index 00000000..0aff540d
--- /dev/null
+++ b/Source/Utility/strings.h
@@ -0,0 +1,138 @@
+//-----------------------------------------------------------------------------
+// Name: strings.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/integer.h>
+
+#include <string>
+#include <vector>
+#include <sstream>
+#include <cstring>
+
+#define ARRLEN(arr) sizeof(arr)/sizeof(arr[0])
+
+bool endswith(const char* str, const char* ending);
+bool beginswith(const char* str, const char* begin);
+
+inline bool strmtch( const char* f, const char* s ) {
+ return strcmp(f,s) == 0;
+}
+
+inline bool strmtch( const std::string& f, const char* s ) {
+ return strmtch( f.c_str(), s );
+}
+
+inline bool strmtch( const std::string& f, const std::string& s ) {
+ return strmtch( f.c_str(), s.c_str() );
+}
+
+inline bool strmtch( const char* f, const std::string& s ) {
+ return strmtch( f, s.c_str() );
+}
+
+inline bool strsfit( const char* str, size_t memsize ) {
+ return strlen(str) < memsize;
+}
+
+bool pstrmtch( const char* first, const char* second, size_t count);
+
+bool strcont( const char* string, const char* substring );
+
+struct StringEqual {
+ bool operator()(const char* lhs, const char* rhs) const {
+ return strmtch(lhs, rhs);
+ }
+};
+
+struct StringLess {
+ bool operator()(const char* lhs, const char* rhs) const {
+ return strcmp(lhs, rhs) < 0;
+ }
+};
+
+#define SOURCE_TOO_LONG 1
+#define SOURCE_IS_NULL 2
+#define DESTINATION_IS_ZERO_LENGTH 3
+
+inline int strscpy(char* dest, const char* src, size_t memsize) {
+ if( memsize > 0 ) {
+ if(src) {
+ if(strsfit(src,memsize)) {
+ strncpy(dest,src,memsize);
+ return 0;
+ } else {
+ strncpy(dest,src,memsize);
+ dest[memsize-1]='\0';
+ return SOURCE_TOO_LONG;
+ }
+ } else {
+ strncpy(dest,"",memsize);
+ dest[memsize-1]='\0';
+ return SOURCE_IS_NULL;
+ }
+ } else {
+ return DESTINATION_IS_ZERO_LENGTH;
+ }
+}
+
+int FindStringInArray( const char** values, size_t values_size, const char* q );
+int FindStringInArray( const std::vector< std::pair< const char*, const char* > >& values, const char* q );
+int FindStringInArray( const std::vector< const char* >& values, const char* q );
+void UTF8InPlaceLower( char* arr );
+std::string UTF8ToLower(const std::string& s);
+int UTF8ToLower( char* out, size_t outsize, const char* in );
+int UTF8ToUpper( char* out, size_t outsize, const char* in );
+bool hasBeginning( const std::string &fullString, const std::string &beginning );
+bool hasEnding (std::string const &fullString, std::string const &ending);
+bool hasAnyOfEndings( const std::string &fullString, const std::vector<std::string> &endings);
+
+int CountCharsInString(const char* str, char the_char);
+int FindNthCharFromBack(const char* str, char the_char, int n);
+
+#define SAYS_TRUE 1
+#define SAYS_FALSE 0
+#define SAYS_TRUE_NULL_INPUT -2
+#define SAYS_TRUE_NO_MATCH -1
+int saysTrue(const char* str);
+
+const char* nullAsEmpty(const char* v);
+
+uint32_t GetCodepointCount( const std::string &utf8in );
+uint32_t GetLengthInBytesForNCodepoints( const std::string& utf8in, uint32_t codepoint_index );
+
+#ifdef WIN32
+#define NOMINMAX
+#include <windows.h>
+std::wstring fromUTF8( const std::string& path_utf8 );
+std::string fromUTF16( const std::wstring& path_utf16 );
+#endif
+
+static std::vector<std::string>& split(const std::string &s, char delim, std::vector<std::string> &elems)
+{
+ std::stringstream ss(s);
+ std::string item;
+ while (std::getline(ss, item, delim)) {
+ elems.push_back(item);
+ }
+ return elems;
+}
diff --git a/Source/Utility/timing.h b/Source/Utility/timing.h
new file mode 100644
index 00000000..55363140
--- /dev/null
+++ b/Source/Utility/timing.h
@@ -0,0 +1,46 @@
+//-----------------------------------------------------------------------------
+// Name: timing.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#if defined(PLATFORM_WINDOWS) && _MSC_VER >= 1600
+#include <intrin.h>
+#endif
+
+inline uint64_t getCPUTSC() {
+ // Use rdtsc instruction to get the tsc or Time Stamp Counter
+#if (defined(PLATFORM_LINUX) || defined(PLATFORM_MACOSX)) && (defined(__i386__) || defined(__x86_64__))
+ uint32_t rax, rdx;
+ asm volatile ( "lfence" ::: "memory" ); //Fence memory load to everything is executed up to this point.
+ asm volatile ( "rdtsc\n" : "=a" (rax), "=d" (rdx) : : );
+ return ((uint64_t)rdx << 32) + rax;
+#elif defined(PLATFORM_WINDOWS) && _MSC_VER >= 1600
+ unsigned __int64 i;
+ _ReadBarrier();
+ i = __rdtsc();
+ return (uint64_t)i;
+#else
+
+#error "No timer implementation for current target platform"
+
+#endif
+}
diff --git a/Source/Utility/waveform_obj_serializer.cpp b/Source/Utility/waveform_obj_serializer.cpp
new file mode 100644
index 00000000..07333269
--- /dev/null
+++ b/Source/Utility/waveform_obj_serializer.cpp
@@ -0,0 +1,44 @@
+//-----------------------------------------------------------------------------
+// Name: waveform_obj_serializer.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "waveform_obj_serializer.h"
+
+#include <fstream>
+
+using std::ofstream;
+using std::endl;
+
+void SerializeToObj(const string& path, const vector<float>& vertices, const vector<uint32_t>& faces) {
+
+ ofstream output(path, ofstream::out);
+
+ for(int i = 0; i < vertices.size(); i += 3) {
+ output << "v " << vertices[i] << " " << vertices[i+1] << " " << vertices[i+2] << endl;
+ }
+
+ for(int i = 0; i < faces.size(); i += 3) {
+ output << "f " << faces[i]+1 << " " << faces[i+1]+1 << " " << faces[i+2]+1 << endl;
+ }
+
+ output.close();
+}
diff --git a/Source/Utility/waveform_obj_serializer.h b/Source/Utility/waveform_obj_serializer.h
new file mode 100644
index 00000000..49bdcb4f
--- /dev/null
+++ b/Source/Utility/waveform_obj_serializer.h
@@ -0,0 +1,32 @@
+//-----------------------------------------------------------------------------
+// Name: waveform_obj_serializer.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <string>
+#include <vector>
+
+using std::vector;
+using std::string;
+
+void SerializeToObj(const string& path, const vector<float>& vertices, const vector<uint32_t>& indices);
diff --git a/Source/Version/fallback_version.c b/Source/Version/fallback_version.c
new file mode 100644
index 00000000..0edce8f4
--- /dev/null
+++ b/Source/Version/fallback_version.c
@@ -0,0 +1,27 @@
+//-----------------------------------------------------------------------------
+// Name: fallback_version.c
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+extern const int phoenix_build_id=-1;
+const char* phoenix_platform="Unknown";
+const char* phoenix_arch="Unknown";
+const char* phoenix_git_version_string="Unknown";
+const char* phoenix_build_timestamp="0000-00-00 00:00:00 UTC";
diff --git a/Source/Version/version.cpp b/Source/Version/version.cpp
new file mode 100644
index 00000000..c2662190
--- /dev/null
+++ b/Source/Version/version.cpp
@@ -0,0 +1,149 @@
+//-----------------------------------------------------------------------------
+// Name: version.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "version.h"
+
+#include <Utility/strings.h>
+
+#include <vector>
+#include <sstream>
+#include <string>
+
+extern const int phoenix_build_id;
+extern const char* phoenix_platform;
+extern const char* phoenix_arch;
+extern const char* phoenix_git_version_string;
+extern const char* phoenix_build_timestamp;
+
+const int GetBuildID()
+{
+ return phoenix_build_id;
+}
+
+const char * GetBuildIDString()
+{
+ static bool init = false;
+ static std::string buf;
+
+ if( phoenix_build_id == -1 )
+ return "none";
+
+ if( !init )
+ {
+ std::stringstream ss;
+ ss << "build-" << phoenix_build_id;
+ buf = ss.str();
+ init = true;
+ }
+
+ return buf.c_str();
+}
+
+const char * GetPlatform()
+{
+ return phoenix_platform;
+}
+
+const char * GetArch()
+{
+ return phoenix_arch;
+}
+
+const char * GetBuildVersion()
+{
+ return phoenix_git_version_string;
+}
+
+const char * GetBuildTimestamp()
+{
+ return phoenix_build_timestamp;
+}
+
+std::string GetShortBuildTag()
+{
+ std::vector<std::string> split_string;
+ std::string buildVersion(GetBuildVersion());
+ split( buildVersion, '-', split_string );
+
+ return split_string[0];
+}
+
+std::string GetVersionMajor()
+{
+ std::vector<std::string> split_string;
+ split(GetShortBuildTag(), '.', split_string);
+
+ if( split_string.size() >= 1 ) {
+ return split_string[0];
+ } else {
+ return std::string();
+ }
+}
+
+std::string GetVersionMinor()
+{
+ std::vector<std::string> split_string;
+ split(GetShortBuildTag(), '.', split_string);
+
+ if( split_string.size() >= 2 ) {
+ return split_string[1];
+ } else {
+ return std::string();
+ }
+}
+
+std::string GetVersionPatch()
+{
+ std::vector<std::string> split_string;
+ split(GetShortBuildTag(), '.', split_string);
+ if( split_string.size() >= 3 ) {
+ return split_string[2];
+ } else {
+ return std::string();
+ }
+}
+
+std::string GetShortBuildTimestamp()
+{
+ std::vector<std::string> split_string;
+ std::string build_timestamp(GetBuildTimestamp());
+ split( build_timestamp, ' ', split_string );
+
+ return split_string[0];
+}
+
+const std::string& GetFullBuildString()
+{
+ static bool init = false;
+ static std::string buf;
+
+ if( !init )
+ {
+ std::stringstream ss;
+
+ ss << phoenix_git_version_string << "_" << phoenix_arch << "_" << phoenix_platform << "_" << phoenix_build_id << "_" << phoenix_build_timestamp;
+ init = true;
+ buf = ss.str();
+ }
+
+ return buf;
+}
diff --git a/Source/Version/version.h b/Source/Version/version.h
new file mode 100644
index 00000000..df638d6a
--- /dev/null
+++ b/Source/Version/version.h
@@ -0,0 +1,41 @@
+//-----------------------------------------------------------------------------
+// Name: version.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <string>
+
+const char * GetBuildIDString();
+const int GetBuildID();
+const char * GetPlatform();
+const char * GetArch();
+const char * GetBuildVersion();
+const char * GetBuildTimestamp();
+
+std::string GetShortBuildTag();
+std::string GetVersionMajor();
+std::string GetVersionMinor();
+std::string GetVersionPatch();
+std::string GetShortBuildTimestamp();
+
+const std::string& GetFullBuildString();
diff --git a/Source/Wrappers/crn.h b/Source/Wrappers/crn.h
new file mode 100644
index 00000000..228159ec
--- /dev/null
+++ b/Source/Wrappers/crn.h
@@ -0,0 +1,34 @@
+//-----------------------------------------------------------------------------
+// Name: crn.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+//crn uses size_t without including headers that contains it on mac
+#include <cstdlib>
+#include <crn_mipmapped_texture.h>
+#include <crn_texture_conversion.h>
+#include <crn_buffer_stream.h>
+
+//crnlib pulls in wingdi.h on Windows which defines a GetObject macro in some circumstances.
+//This collides with GetObject functions in angelscript, so we undefine it here.
+#ifdef GetObject
+#undef GetObject
+#endif
diff --git a/Source/Wrappers/glm.h b/Source/Wrappers/glm.h
new file mode 100644
index 00000000..9f898b94
--- /dev/null
+++ b/Source/Wrappers/glm.h
@@ -0,0 +1,36 @@
+//-----------------------------------------------------------------------------
+// Name: glm.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#if defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
+#endif
+
+#define GLM_SWIZZLE
+#include <glm/glm.hpp>
+#include <glm/gtc/matrix_transform.hpp>
+
+#if defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
diff --git a/Source/Wrappers/linux_gtk.h b/Source/Wrappers/linux_gtk.h
new file mode 100644
index 00000000..f0e2cea4
--- /dev/null
+++ b/Source/Wrappers/linux_gtk.h
@@ -0,0 +1,32 @@
+//-----------------------------------------------------------------------------
+// Name: linux_gtk.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#ifdef __GNUC__
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wpedantic"
+#endif
+#ifdef PLATFORM_LINUX
+#include <gtk/gtk.h>
+#endif
+#ifdef __GNUC__
+# pragma GCC diagnostic pop
+#endif
diff --git a/Source/Wrappers/openal.h b/Source/Wrappers/openal.h
new file mode 100644
index 00000000..491f203f
--- /dev/null
+++ b/Source/Wrappers/openal.h
@@ -0,0 +1,60 @@
+//-----------------------------------------------------------------------------
+// Name: openal.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#ifdef __APPLE__
+ #include <OpenAL/alc.h>
+ #include <OpenAL/al.h>
+#else
+ #include <alc.h>
+ #include <al.h>
+#endif
+
+inline const char* alErrString( const ALenum& e )
+{
+ switch( e )
+ {
+ case AL_NO_ERROR: return "There is not currently an error";
+ case AL_INVALID_NAME: return "A bad name (ID) was passed to an OpenAL function";
+ case AL_INVALID_ENUM: return "An invalid enum value was passed to an OpenAL function";
+ case AL_INVALID_VALUE: return "An invalid value was passed to an OpenAL function";
+ case AL_INVALID_OPERATION: return "The requested operation is not valid";
+ case AL_OUT_OF_MEMORY: return "The requested operation resulted in OpenAL running out of memory";
+ }
+
+ return "Unknown error";
+}
+
+inline const char* alcErrString( const ALCenum& e )
+{
+ switch( e )
+ {
+ case ALC_NO_ERROR: return "There is not currently an error.";
+ case ALC_INVALID_DEVICE: return "A bad device was passed to an OpenAL function.";
+ case ALC_INVALID_CONTEXT: return "A bad context was passed to an OpenAL function.";
+ case ALC_INVALID_ENUM: return "An unknown enum value was passed to an OpenAL function.";
+ case ALC_INVALID_VALUE: return "An invalid value was passed to an OpenAL function.";
+ case ALC_OUT_OF_MEMORY: return "The requested operation resulted in OpenAL running out of memory .";
+ default: return "Unknown error";
+ }
+}
diff --git a/Source/Wrappers/tut.h b/Source/Wrappers/tut.h
new file mode 100644
index 00000000..13cee367
--- /dev/null
+++ b/Source/Wrappers/tut.h
@@ -0,0 +1,34 @@
+//-----------------------------------------------------------------------------
+// Name: tut.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#if defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-function"
+#endif
+
+#include <tut/tut.hpp>
+
+#if defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
diff --git a/Source/Wrappers/vorbis.h b/Source/Wrappers/vorbis.h
new file mode 100644
index 00000000..5d406f7a
--- /dev/null
+++ b/Source/Wrappers/vorbis.h
@@ -0,0 +1,30 @@
+//-----------------------------------------------------------------------------
+// Name: vorbis.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#ifdef PLATFORM_UNIX
+#define OV_EXCLUDE_STATIC_CALLBACKS
+#endif
+
+#include <vorbis/vorbisfile.h>
+
diff --git a/Source/XML/Parsers/activemodsparser.cpp b/Source/XML/Parsers/activemodsparser.cpp
new file mode 100644
index 00000000..af082dc4
--- /dev/null
+++ b/Source/XML/Parsers/activemodsparser.cpp
@@ -0,0 +1,218 @@
+//-----------------------------------------------------------------------------
+// Name: activemodsparser.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "activemodsparser.h"
+
+#include <Internal/checksum.h>
+#include <Internal/filesystem.h>
+
+#include <Utility/commonregex.h>
+#include <Utility/strings.h>
+
+#include <XML/xml_helper.h>
+#include <Logging/logdata.h>
+
+#include <tinyxml.h>
+
+ActiveModsParser::ActiveModsParser() {
+
+}
+
+uint32_t ActiveModsParser::Load( const std::string& path ) {
+ load_checksum = Checksum(path);
+
+ Clear();
+ TiXmlDocument doc( path.c_str() );
+ doc.LoadFile();
+ if( !doc.Error() ) {
+ TiXmlElement* pRoot = doc.RootElement();
+ if( pRoot ) {
+ TiXmlElement* eModInstance = pRoot->FirstChildElement("ModInstance");
+ bool parse_error;
+ while( eModInstance ) {
+ ModInstance mi;
+ int err;
+ parse_error = false;
+
+ err = strscpy(mi.id, eModInstance->Attribute("id"), MOD_ID_MAX_LENGTH);
+ if( err == SOURCE_TOO_LONG ) {
+ parse_error = true;
+ } else if( err == SOURCE_IS_NULL ) {
+ parse_error = true;
+ }
+
+ int saystrue = saysTrue(eModInstance->Attribute("activated"));
+ if( saystrue == 1 ) {
+ mi.activated = true;
+ } else if( saystrue == 0 ) {
+ mi.activated = false;
+ } else if( saystrue == SAYS_TRUE_NULL_INPUT) {
+ parse_error = true;
+ } else if( saystrue == SAYS_TRUE_NO_MATCH) {
+ parse_error = true;
+ } else {
+ parse_error = true;
+ }
+
+ const char* modsource = eModInstance->Attribute("modsource");
+ if( modsource ) {
+ if( strmtch(modsource,"steamworks")) {
+ mi.modsource = ModSourceSteamworks;
+ } else if( strmtch(modsource,"local")) {
+ mi.modsource = ModSourceLocalModFolder;
+ } else {
+ LOGE << "Unknown modsource " << modsource << " on " << mi.id << std::endl;
+ parse_error = true;
+ }
+ } else {
+ LOGE << "Missing modsource attribute" << std::endl;
+ parse_error = true;
+ }
+
+ err = strscpy(mi.version, eModInstance->Attribute("version"), MOD_VERSION_MAX_LENGTH);
+ if( err == SOURCE_TOO_LONG ) {
+ parse_error = true;
+ } else if( err == SOURCE_IS_NULL ) {
+ parse_error = true;
+ }
+
+ if( parse_error == false ) {
+ mod_instances.push_back(mi);
+ }
+
+ eModInstance = eModInstance->NextSiblingElement();
+ }
+ }
+ }
+ return 1;
+}
+
+bool ActiveModsParser::SerializeInto( TiXmlDocument* doc ) {
+ TiXmlDeclaration * decl = new TiXmlDeclaration( "2.0", "", "" );
+ TiXmlElement * root = new TiXmlElement("ActiveMods");
+ for( unsigned i = 0; i < mod_instances.size(); i++ ) {
+ TiXmlElement * mi = new TiXmlElement("ModInstance");
+ mi->SetAttribute( "id", mod_instances[i].id );
+ mi->SetAttribute( "activated", mod_instances[i].activated ? "true" : "false" );
+ const char* modsource = "";
+ switch(mod_instances[i].modsource) {
+ case ModSourceLocalModFolder:
+ modsource = "local";
+ break;
+ case ModSourceSteamworks:
+ modsource = "steamworks";
+ break;
+ case ModSourceUnknown:
+ LOGE << "Serializing mod with modsource ModSourceUnknown" << std::endl;
+ modsource = "unknown";
+ break;
+ }
+ mi->SetAttribute("modsource", modsource);
+ mi->SetAttribute("version", mod_instances[i].version);
+ root->LinkEndChild(mi);
+ }
+ doc->LinkEndChild(decl);
+ doc->LinkEndChild(root);
+ return !doc->Error();
+}
+
+uint16_t ActiveModsParser::LocalChecksum()
+{
+ std::string path = AssemblePath(GetWritePath(CoreGameModID).c_str(),"Data/Temp/activemodsparser.temp.xml");
+ TiXmlDocument doc;
+
+ SerializeInto(&doc);
+
+ doc.SaveFile(path.c_str());
+
+ return Checksum(path);
+}
+
+bool ActiveModsParser::Save( const std::string& path ) {
+ TiXmlDocument doc;
+
+ SerializeInto(&doc);
+
+ doc.SaveFile(path.c_str());
+ save_checksum = Checksum(path);
+
+ return !doc.Error();
+}
+
+void ActiveModsParser::SetModInstanceActive(const char* id, ModSource modsource, bool activated, const char* version) {
+ ModInstance mi = ModInstance(id,modsource,activated,version);
+ int found = -1;
+ for( unsigned i = 0; i < mod_instances.size(); i++ ) {
+ if(strmtch(mod_instances[i].id,id) && mod_instances[i].modsource == modsource) {
+ found = i;
+ }
+ }
+ if( found == -1 ) {
+ mod_instances.push_back(mi);
+ } else {
+ mod_instances[found] = mi;
+ }
+}
+
+bool ActiveModsParser::HasModInstance(const char* id, ModSource modsource) {
+ for( unsigned i = 0; i < mod_instances.size(); i++ ) {
+ if(strmtch(mod_instances[i].id,id) && mod_instances[i].modsource == modsource) {
+ return true;
+ }
+ }
+ return false;
+}
+
+ActiveModsParser::ModInstance ActiveModsParser::GetModInstance(const char* id, ModSource modsource) {
+ for( unsigned i = 0; i < mod_instances.size(); i++ ) {
+ if(strmtch(mod_instances[i].id,id) && mod_instances[i].modsource == modsource) {
+ return mod_instances[i];
+ }
+ }
+ return ModInstance(id,modsource,false,"");
+}
+
+void ActiveModsParser::RemoveModInstance(const char* id, ModSource modsource) {
+ for( unsigned i = 0; i < mod_instances.size(); i++ ) {
+ if(strmtch(mod_instances[i].id,id) && mod_instances[i].modsource == modsource) {
+ mod_instances.erase(mod_instances.begin()+i);
+ }
+ }
+}
+
+void ActiveModsParser::Clear() {
+ mod_instances.clear();
+}
+
+ActiveModsParser::ModInstance::ModInstance() {
+ this->id[0] = '\0';
+ this->modsource = ModSourceUnknown;
+ this->activated = false;
+ this->version[0] = '\0';
+}
+
+ActiveModsParser::ModInstance::ModInstance(const char* id, ModSource modsource, bool activated, const char* version) {
+ strscpy(this->id,id,MOD_ID_MAX_LENGTH);
+ this->modsource = modsource;
+ this->activated = activated;
+ strscpy(this->version,version,MOD_VERSION_MAX_LENGTH);
+}
diff --git a/Source/XML/Parsers/activemodsparser.h b/Source/XML/Parsers/activemodsparser.h
new file mode 100644
index 00000000..8a55e5f3
--- /dev/null
+++ b/Source/XML/Parsers/activemodsparser.h
@@ -0,0 +1,73 @@
+//-----------------------------------------------------------------------------
+// Name: activemodsparser.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+#include <XML/Parsers/xmlparserbase.h>
+#include <Internal/modid.h>
+
+#include <map>
+#include <vector>
+#include <string>
+#include <iostream>
+
+class TiXmlDocument;
+
+class ActiveModsParser : public XMLParserBase
+{
+private:
+ uint16_t load_checksum;
+ uint16_t save_checksum;
+
+ bool SerializeInto( TiXmlDocument* doc );
+
+ uint16_t LocalChecksum();
+
+public:
+ ActiveModsParser();
+
+ virtual uint32_t Load( const std::string& path );
+ virtual bool Save( const std::string& path );
+
+ virtual void Clear();
+
+ bool FileChangedSinceLastLoad();
+ bool LocalChangedSinceLastSave();
+
+ class ModInstance
+ {
+ public:
+ ModInstance();
+ ModInstance(const char* id,ModSource modsource,bool activated, const char* version);
+
+ char id[MOD_ID_MAX_LENGTH];
+ ModSource modsource;
+ bool activated;
+ char version[MOD_VERSION_MAX_LENGTH];
+ };
+
+ std::vector<ModInstance> mod_instances;
+
+ void SetModInstanceActive(const char* id, ModSource modsource, bool activated, const char* version);
+ bool HasModInstance(const char* id, ModSource modsource);
+ ModInstance GetModInstance(const char* id, ModSource modsource);
+ void RemoveModInstance(const char* id, ModSource modsource);
+};
diff --git a/Source/XML/Parsers/assetloadwarningparser.cpp b/Source/XML/Parsers/assetloadwarningparser.cpp
new file mode 100644
index 00000000..64971ee4
--- /dev/null
+++ b/Source/XML/Parsers/assetloadwarningparser.cpp
@@ -0,0 +1,113 @@
+//-----------------------------------------------------------------------------
+// Name: assetloadwarningparser.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "assetloadwarningparser.h"
+
+#include <Utility/strings.h>
+#include <Utility/serialize.h>
+
+AssetLoadWarningParser::AssetWarning::AssetWarning(std::string path, uint32_t load_flags, std::string asset_type, std::string level_name) :
+path(path),
+load_flags(load_flags),
+asset_type(asset_type),
+level_name(level_name) {
+
+}
+
+AssetLoadWarningParser::AssetLoadWarningParser() {
+
+}
+
+uint32_t AssetLoadWarningParser::Load( const std::string& path ) {
+ TiXmlDocument doc( path.c_str() );
+ doc.LoadFile();
+ if( !doc.Error() ) {
+ TiXmlElement* pRoot = doc.RootElement();
+ if( pRoot ) {
+ TiXmlElement* e = pRoot->FirstChildElement("AssetWarning");
+ while(e) {
+ uint32_t flags;
+ string_flags_to_uint32(&flags, nullAsEmpty(e->Attribute("load_flags")));
+ asset_warnings.insert(AssetWarning(nullAsEmpty(e->Attribute("path")),flags, nullAsEmpty(e->Attribute("asset_type")),nullAsEmpty(e->Attribute("level_name"))));
+ e = e->NextSiblingElement("AssetWarning");
+ }
+ }
+ }
+ return 0;
+}
+
+bool AssetLoadWarningParser::Save( const std::string& path ) {
+ TiXmlDocument doc;
+ TiXmlDeclaration * decl = new TiXmlDeclaration( "2.0", "", "" );
+ TiXmlElement * root = new TiXmlElement("AssetWarnings");
+
+ for( std::set<AssetWarning>::iterator sit = asset_warnings.begin(); sit != asset_warnings.end(); sit++ ) {
+ TiXmlElement * e = new TiXmlElement("AssetWarning");
+ e->SetAttribute("asset_type",sit->asset_type.c_str());
+ e->SetAttribute("path",sit->path.c_str());
+ e->SetAttribute("level_name",sit->level_name.c_str());
+ char flags[9]; flags_to_string(flags,sit->load_flags); e->SetAttribute("load_flags",flags);
+ root->LinkEndChild(e);
+ }
+
+ doc.LinkEndChild(decl);
+ doc.LinkEndChild(root);
+
+ doc.SaveFile(path.c_str());
+ return true;
+}
+
+void AssetLoadWarningParser::Clear() {
+ asset_warnings.clear();
+}
+
+void AssetLoadWarningParser::AddAssetWarning(AssetWarning asset_warning) {
+ asset_warnings.insert(asset_warning);
+}
+
+bool operator<( const AssetLoadWarningParser::AssetWarning& lhs,const AssetLoadWarningParser::AssetWarning& rhs) {
+ if(lhs.asset_type < rhs.asset_type ) {
+ return true;
+ } else if( lhs.asset_type > rhs.asset_type ) {
+ return false;
+ } else {
+ if(lhs.load_flags < rhs.load_flags ) {
+ return true;
+ } else if( lhs.load_flags > rhs.load_flags ) {
+ return false;
+ } else {
+ if( lhs.path < rhs.path ) {
+ return true;
+ } else if( lhs.path > rhs.path ) {
+ return false;
+ } else {
+ if( lhs.level_name < rhs.level_name ) {
+ return true;
+ } else if (lhs.level_name > rhs.level_name) {
+ return false;
+ } else {
+ return false;
+ }
+ }
+ }
+ }
+}
diff --git a/Source/XML/Parsers/assetloadwarningparser.h b/Source/XML/Parsers/assetloadwarningparser.h
new file mode 100644
index 00000000..89c543ba
--- /dev/null
+++ b/Source/XML/Parsers/assetloadwarningparser.h
@@ -0,0 +1,57 @@
+//-----------------------------------------------------------------------------
+// Name: assetloadwarningparser.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <XML/xml_helper.h>
+#include <XML/Parsers/xmlparserbase.h>
+
+#include <tinyxml.h>
+
+#include <set>
+#include <string>
+
+class AssetLoadWarningParser : public XMLParserBase
+{
+public:
+ AssetLoadWarningParser();
+
+ struct AssetWarning {
+ AssetWarning(std::string path, uint32_t load_flags, std::string asset_type, std::string level_name);
+
+ std::string path;
+ uint32_t load_flags;
+ std::string asset_type;
+ std::string level_name;
+
+ };
+
+ std::set<AssetWarning> asset_warnings;
+
+ virtual uint32_t Load( const std::string& path );
+ virtual bool Save( const std::string& path );
+ virtual void Clear();
+
+ void AddAssetWarning(AssetWarning asset_warning);
+};
+
+bool operator<( const AssetLoadWarningParser::AssetWarning& lhs,const AssetLoadWarningParser::AssetWarning& rhs);
diff --git a/Source/XML/Parsers/assetmanifestparser.cpp b/Source/XML/Parsers/assetmanifestparser.cpp
new file mode 100644
index 00000000..c391eaf3
--- /dev/null
+++ b/Source/XML/Parsers/assetmanifestparser.cpp
@@ -0,0 +1,73 @@
+//-----------------------------------------------------------------------------
+// Name: assetmanifestparser.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "assetmanifestparser.h"
+
+#include <Utility/strings.h>
+#include <Utility/serialize.h>
+
+#include <Logging/logdata.h>
+#include <XML/xml_helper.h>
+
+#include <tinyxml.h>
+
+
+uint32_t AssetManifestParser::Load( const std::string& path ) {
+ TiXmlDocument doc( path.c_str() );
+ doc.LoadFile();
+ if(!doc.Error()) {
+ TiXmlElement* pRoot = doc.RootElement();
+ if(pRoot) {
+ TiXmlElement* e = pRoot->FirstChildElement("Asset");
+ while(e) {
+ Asset a;
+
+ strscpy(a.id,e->Attribute("id"),Asset::ID_LENGTH);
+ a.path = nullAsEmpty(e->Attribute("path"));
+ a.asset_type = GetAssetTypeValue(nullAsEmpty(e->Attribute("asset_type")));
+ a.preload = saysTrue(e->Attribute("preload"));
+ string_flags_to_uint32(&a.load_flags, nullAsEmpty(e->Attribute("load_flags")));
+
+ assets.push_back(a);
+
+ e = e->NextSiblingElement("Asset");
+ }
+ }
+ }
+ return 0;
+}
+
+bool AssetManifestParser::Save( const std::string& path ) {
+ return false;
+}
+
+void AssetManifestParser::Clear() {
+ assets.clear();
+}
+
+AssetManifestParser::Asset::Asset() :
+load_flags(0x0)
+{
+ id[0] = '\0';
+ asset_type = UNKNOWN;
+ preload = false;
+}
diff --git a/Source/XML/Parsers/assetmanifestparser.h b/Source/XML/Parsers/assetmanifestparser.h
new file mode 100644
index 00000000..5f7d1286
--- /dev/null
+++ b/Source/XML/Parsers/assetmanifestparser.h
@@ -0,0 +1,48 @@
+//-----------------------------------------------------------------------------
+// Name: assetmanifestparser.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <XML/Parsers/xmlparserbase.h>
+#include <Asset/assettypes.h>
+
+#include <vector>
+
+class AssetManifestParser : public XMLParserBase {
+public:
+ class Asset {
+ public:
+ Asset();
+
+ static const size_t ID_LENGTH = 32;
+ char id[ID_LENGTH];
+ AssetType asset_type;
+ std::string path;
+ bool preload;
+ uint32_t load_flags;
+ };
+
+ std::vector<Asset> assets;
+ virtual uint32_t Load( const std::string& path );
+ virtual bool Save( const std::string& path );
+ virtual void Clear();
+};
diff --git a/Source/XML/Parsers/jobxmlparser.cpp b/Source/XML/Parsers/jobxmlparser.cpp
new file mode 100644
index 00000000..ddaf8d26
--- /dev/null
+++ b/Source/XML/Parsers/jobxmlparser.cpp
@@ -0,0 +1,323 @@
+//-----------------------------------------------------------------------------
+// Name: jobxmlparser.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "jobxmlparser.h"
+
+#include <Utility/commonregex.h>
+#include <Logging/logdata.h>
+#include <XML/xml_helper.h>
+
+#include <tinyxml.h>
+
+JobXMLParser::JobXMLParser()
+{
+
+}
+
+uint32_t JobXMLParser::Load( const std::string& path )
+{
+ CommonRegex cr;
+ Clear();
+ TiXmlDocument doc( path.c_str() );
+ doc.LoadFile();
+
+ if( !doc.Error() )
+ {
+ TiXmlElement* pRoot = doc.RootElement();
+
+ if( pRoot )
+ {
+ TiXmlHandle hRoot(pRoot);
+
+ TiXmlElement* eInputs = hRoot.FirstChild("Inputs").Element();
+
+ if( eInputs )
+ {
+ TiXmlNode* nInputsIt = NULL;
+
+ while( (nInputsIt = eInputs->IterateChildren(nInputsIt)) )
+ {
+ if(strcmp(nInputsIt->Value(), "Input") == 0)
+ {
+ TiXmlElement* eInput = nInputsIt->ToElement();
+
+ if( eInput )
+ {
+ inputs.push_back(eInput->GetText());
+ }
+ }
+ else
+ {
+ if( nInputsIt->ToComment() == NULL )
+ LOGE << "Found invalid tag \"<" << nInputsIt->Value() << ">\" in <Inputs>" << std::endl;
+ }
+ }
+ }
+
+ TiXmlElement* eSearcher = hRoot.FirstChild("Searchers").Element();
+
+ if( eSearcher )
+ {
+ TiXmlNode * nSearcherIt = NULL;
+
+ while( (nSearcherIt = eSearcher->IterateChildren(nSearcherIt)) )
+ {
+ if( strcmp(nSearcherIt->Value(), "Searcher") == 0 )
+ {
+ TiXmlElement* eSearcher = nSearcherIt->ToElement();
+
+ if( eSearcher )
+ {
+ Searcher sSearcher;
+
+ const char *type_pattern_re = eSearcher->Attribute("type_pattern_re");
+ const char *path_ending = eSearcher->Attribute("path_ending");
+ const char *searcher = eSearcher->Attribute("searcher");
+
+ if( type_pattern_re )
+ sSearcher.type_pattern_re = std::string( type_pattern_re );
+ if( path_ending )
+ sSearcher.path_ending = std::string( path_ending );
+ if( searcher )
+ sSearcher.searcher = std::string( searcher );
+
+ sSearcher.row = eSearcher->Row();
+
+ searchers.push_back( sSearcher );
+ }
+ }
+ else
+ {
+ if( nSearcherIt->ToComment() == NULL )
+ LOGE << "Found invalid tag \"<" << nSearcherIt->Value() << ">\" in <Searchers>" << std::endl;
+ }
+ }
+ }
+
+ TiXmlElement* eItems = hRoot.FirstChild( "Items" ).Element();
+
+ if( eItems )
+ {
+ TiXmlNode * nItemsIt = NULL;
+
+ while( (nItemsIt = eItems->IterateChildren(nItemsIt)) )
+ {
+ if( strcmp(nItemsIt->Value(), "Item") == 0 )
+ {
+ TiXmlElement* eItem = nItemsIt->ToElement();
+
+ if( eItem )
+ {
+ Item item;
+
+ const char *path = eItem->GetText();
+ if( path )
+ {
+ item.path = std::string(path);
+ }
+
+ const char *type = eItem->Attribute("type");
+ if( type )
+ {
+ item.type = std::string(type);
+ }
+
+ bool recursive = cr.saysTrue(eItem->Attribute("recursive"));
+ item.recursive = recursive;
+
+ const char *path_ending = eItem->Attribute("path_ending");
+ if( path_ending )
+ item.path_ending = std::string(path_ending);
+
+ item.row = eItem->Row();
+ items.push_back(item);
+ }
+ }
+ else
+ {
+ if( nItemsIt->ToComment() == NULL )
+ LOGE << "Found invalid tag \"<" << nItemsIt->Value() << ">\" in <Items>" << std::endl;
+ }
+ }
+ }
+
+ TiXmlElement* eBuilders = hRoot.FirstChild( "Builders" ).Element();
+
+ if( eBuilders )
+ {
+ TiXmlNode* nBuilderIt = NULL;
+
+ while( (nBuilderIt = eBuilders->IterateChildren(nBuilderIt)) )
+ {
+ if( strcmp(nBuilderIt->Value(), "Builder") == 0 )
+ {
+ TiXmlElement* eBuilder = nBuilderIt->ToElement();
+
+ if( eBuilder )
+ {
+ Builder b;
+
+ const char *type_pattern_re = eBuilder->Attribute("type_pattern_re");
+ const char *path_ending = eBuilder->Attribute("path_ending");
+ const char *builder = eBuilder->Attribute("builder");
+
+ if( type_pattern_re )
+ b.type_pattern_re = std::string(type_pattern_re);
+ if( path_ending )
+ b.path_ending = std::string(path_ending);
+ if( builder )
+ b.builder = std::string(builder);
+
+ b.row = eBuilder->Row();
+
+ builders.push_back(b);
+ }
+ }
+ else
+ {
+ if( nBuilderIt->ToComment() == NULL )
+ LOGE << "Found invalid tag \"<" << nBuilderIt->Value() << ">\" in <Builders>" << std::endl;
+ }
+ }
+ }
+
+ TiXmlElement* eGenerators = hRoot.FirstChild( "Generators" ).Element();
+
+ if( eGenerators )
+ {
+ TiXmlNode* nGeneratorIt = NULL;
+
+ while( (nGeneratorIt = eGenerators->IterateChildren(nGeneratorIt)) )
+ {
+ if( strcmp(nGeneratorIt->Value(), "Generator") == 0 )
+ {
+ TiXmlElement* eGenerator = nGeneratorIt->ToElement();
+
+ if( eGenerator )
+ {
+ Generator b;
+
+ const char *generator = eGenerator->Attribute("generator");
+
+ if( generator )
+ b.generator = std::string(generator);
+
+ b.row = eGenerator->Row();
+
+ generators.push_back(b);
+ }
+ }
+ else
+ {
+ if( nGeneratorIt->ToComment() == NULL )
+ LOGE << "Found invalid tag \"<" << nGeneratorIt->Value() << ">\" in <Generators>" << std::endl;
+ }
+ }
+ }
+
+ return true;
+ }
+ else
+ {
+ LOGE << "Problem loading job file" << path << std::endl;
+ return false;
+ }
+ }
+ else
+ {
+ LOGE << "Error parsing job file: \"" << doc.ErrorDesc() << "\"" << std::endl;
+ return false;
+ }
+}
+
+bool JobXMLParser::Save( const std::string& path )
+{
+ return false;
+}
+
+void JobXMLParser::Clear()
+{
+ searchers.clear();
+ items.clear();
+ builders.clear();
+}
+
+
+bool JobXMLParser::Item::operator<(const Item& rhs) const
+{
+ if( row < rhs.row )
+ {
+ return true;
+ }
+ else if( row == rhs.row )
+ {
+ if( path_ending < rhs.path_ending )
+ {
+ return true;
+ }
+ else if( path_ending == rhs.path_ending )
+ {
+ if( path < rhs.path )
+ {
+ return true;
+ }
+ else if( path == rhs.path )
+ {
+ if( type < rhs.type )
+ {
+ return true;
+ }
+ else if( type == rhs.type )
+ {
+ if( recursive < rhs.recursive )
+ {
+ return true;
+ }
+ else if( recursive == rhs.recursive )
+ {
+ return false;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+}
diff --git a/Source/XML/Parsers/jobxmlparser.h b/Source/XML/Parsers/jobxmlparser.h
new file mode 100644
index 00000000..d066abf2
--- /dev/null
+++ b/Source/XML/Parsers/jobxmlparser.h
@@ -0,0 +1,146 @@
+//-----------------------------------------------------------------------------
+// Name: jobxmlparser.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <XML/Parsers/xmlparserbase.h>
+
+#include <map>
+#include <vector>
+#include <string>
+#include <iostream>
+
+class JobXMLParser : public XMLParserBase
+{
+public:
+ JobXMLParser();
+
+ virtual uint32_t Load( const std::string& path );
+ virtual bool Save( const std::string& path );
+ virtual void Clear();
+
+ class Searcher
+ {
+ public:
+ int row;
+ std::string type_pattern_re;
+ std::string path_ending;
+ std::string searcher;
+ };
+
+ class Item
+ {
+ public:
+ int row;
+ std::string type;
+ std::string path;
+ bool recursive;
+ std::string path_ending;
+ bool operator<(const Item& rhs) const;
+ };
+
+ class Builder
+ {
+ public:
+ int row;
+ std::string type_pattern_re;
+ std::string path_ending;
+ std::string builder;
+ };
+
+ class Generator
+ {
+ public:
+ int row;
+ std::string generator;
+ };
+
+ std::vector<std::string> inputs;
+ std::vector<Searcher> searchers;
+ std::vector<Item> items;
+ std::vector<Builder> builders;
+ std::vector<Generator> generators;
+};
+
+inline std::ostream& operator<<( std::ostream& out, const JobXMLParser::Searcher& s )
+{
+ out << "XmlSearcher(" << s.type_pattern_re << "," << s.path_ending << "," << s.searcher << ")";
+ return out;
+}
+
+inline std::ostream& operator<<( std::ostream& out, const JobXMLParser::Item& item )
+{
+ out << "XmlItem(" << item.path << ":" << item.row << "," << item.type << "," << item.recursive << "," << item.path_ending << ")";
+ return out;
+}
+
+inline std::ostream& operator<<( std::ostream& out, const JobXMLParser::Builder& b )
+{
+ out << "XmlBuilder(" << b.type_pattern_re << "," << b.path_ending << "," << b.builder << ")";
+ return out;
+}
+
+inline std::ostream& operator<<(std::ostream& out, const std::vector<JobXMLParser::Searcher>& filters)
+{
+ std::vector<JobXMLParser::Searcher>::const_iterator filter_it;
+ filter_it = filters.begin();
+ out << "XmlSearchers(";
+ for( ; filter_it != filters.end(); filter_it++ )
+ {
+ out << *filter_it << ",";
+ }
+ out << ")";
+ return out;
+}
+
+inline std::ostream& operator<<(std::ostream& out, const std::vector<JobXMLParser::Item>& items)
+{
+ out << "XmlItems(";
+ std::vector<JobXMLParser::Item>::const_iterator item_it;
+ item_it = items.begin();
+ for(; item_it != items.end(); item_it++ )
+ {
+ out << *item_it << ",";
+ }
+ out << ")";
+ return out;
+}
+
+inline std::ostream& operator<<(std::ostream& out, const std::vector<JobXMLParser::Builder>& builders)
+{
+ out << "XmlBuilders(";
+ std::vector<JobXMLParser::Builder>::const_iterator builder_it;
+ builder_it = builders.begin();
+ for(; builder_it != builders.end(); builder_it++ )
+ {
+ out << *builder_it << ",";
+ }
+ out << ")";
+ return out;
+}
+
+
+inline std::ostream& operator<<( std::ostream& out, const JobXMLParser& dat )
+{
+ out << "JobXMLParser(" << dat.searchers << "," << dat.items << "," << dat.builders << ")";
+ return out;
+}
diff --git a/Source/XML/Parsers/levelassetspreloadparser.cpp b/Source/XML/Parsers/levelassetspreloadparser.cpp
new file mode 100644
index 00000000..1184e17a
--- /dev/null
+++ b/Source/XML/Parsers/levelassetspreloadparser.cpp
@@ -0,0 +1,81 @@
+//-----------------------------------------------------------------------------
+// Name: levelassetpreloadparser.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "levelassetspreloadparser.h"
+
+#include <Utility/strings.h>
+#include <Utility/serialize.h>
+
+#include <XML/xml_helper.h>
+#include <Logging/logdata.h>
+
+#include <tinyxml.h>
+
+uint32_t LevelAssetPreloadParser::Load( const std::string& path ) {
+ TiXmlDocument doc( path.c_str() );
+ doc.LoadFile();
+ if(!doc.Error()) {
+ TiXmlElement* pRoot = doc.RootElement();
+ if(pRoot) {
+ TiXmlElement* e = pRoot->FirstChildElement("Asset");
+ while(e) {
+ Asset a;
+
+ a.path = nullAsEmpty(e->Attribute("path"));
+ a.level_name = nullAsEmpty(e->Attribute("level_name"));
+ a.asset_type = GetAssetTypeValue(nullAsEmpty(e->Attribute("asset_type")));
+
+ int all_levels = saysTrue(e->Attribute("all_levels"));
+ if( all_levels == SAYS_TRUE ) {
+ a.all_levels = true;
+ } else if( all_levels == SAYS_FALSE ) {
+ a.all_levels = false;
+ } else if( all_levels == SAYS_TRUE_NO_MATCH ) {
+ LOGW << "Unexpected string in all_levels attribute" << std::endl;
+ } else if( all_levels == SAYS_TRUE_NULL_INPUT ) {
+ a.all_levels = false;
+ }
+
+ string_flags_to_uint32(&a.load_flags, nullAsEmpty(e->Attribute("load_flags")));
+
+ assets.push_back(a);
+
+ e = e->NextSiblingElement("Asset");
+ }
+ }
+ }
+ return 0;
+}
+
+bool LevelAssetPreloadParser::Save( const std::string& path ) {
+ return false;
+}
+
+void LevelAssetPreloadParser::Clear() {
+ assets.clear();
+}
+
+LevelAssetPreloadParser::Asset::Asset() :
+load_flags(0x0)
+{
+ asset_type = UNKNOWN;
+}
diff --git a/Source/XML/Parsers/levelassetspreloadparser.h b/Source/XML/Parsers/levelassetspreloadparser.h
new file mode 100644
index 00000000..0e145612
--- /dev/null
+++ b/Source/XML/Parsers/levelassetspreloadparser.h
@@ -0,0 +1,48 @@
+//-----------------------------------------------------------------------------
+// Name: levelassetpreloadparser.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <XML/Parsers/xmlparserbase.h>
+
+#include <Asset/assettypes.h>
+
+#include <vector>
+
+class LevelAssetPreloadParser : public XMLParserBase {
+public:
+ class Asset {
+ public:
+ Asset();
+
+ std::string path;
+ std::string level_name;
+ bool all_levels;
+ AssetType asset_type;
+ uint32_t load_flags;
+ };
+
+ std::vector<Asset> assets;
+ virtual uint32_t Load( const std::string& path );
+ virtual bool Save( const std::string& path );
+ virtual void Clear();
+};
diff --git a/Source/XML/Parsers/levelxmlparser.cpp b/Source/XML/Parsers/levelxmlparser.cpp
new file mode 100644
index 00000000..b18a72b5
--- /dev/null
+++ b/Source/XML/Parsers/levelxmlparser.cpp
@@ -0,0 +1,210 @@
+//-----------------------------------------------------------------------------
+// Name: levelxmlparser.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "levelxmlparser.h"
+
+#include <XML/xml_helper.h>
+#include <Utility/strings.h>
+#include <Logging/logdata.h>
+#include <Asset/Asset/filehash.h>
+#include <Main/engine.h>
+
+#include <tinyxml.h>
+
+uint32_t LevelXMLParser::Load( const std::string& path ) {
+ Clear();
+ TiXmlDocument doc( path.c_str() );
+ doc.LoadFile();
+
+ if( !doc.Error() ) {
+ TiXmlElement* eType = doc.FirstChildElement("Type");
+ const char* c_type = "";
+ if( eType ) {
+ c_type = eType->GetText();
+ }
+
+ if( strmtch(c_type,"level_info_cache") ) {
+ TiXmlElement* eVersion = doc.FirstChildElement("Version");
+ if( eVersion ) {
+ const char* c_version = eVersion->GetText();
+ if( strmtch(c_version, "1" ) == false ) {
+ LOGI << "level info cache file " << path << " is out of date" << std::endl;
+ return false;
+ }
+ }
+
+ TiXmlElement* eLevelHash = doc.FirstChildElement("LevelHash");
+ if( eLevelHash ) {
+ const char* c_level_hash = eLevelHash->GetText();
+ if( c_level_hash ) {
+ hash = c_level_hash;
+ }
+ }
+ } else {
+ FileHashAssetRef level_hash = Engine::Instance()->GetAssetManager()->LoadSync<FileHashAsset>(path);
+ if( level_hash.valid() ) {
+ hash = level_hash->hash.ToString();
+ }
+ }
+
+ TiXmlElement* eName = doc.FirstChildElement("Name");
+ if( eName ) {
+ const char* c_name = eName->GetText();
+ if( c_name ) {
+ name = c_name;
+ } else {
+ LOGE << "Element \"Name\" in " << path << " is null." << std::endl;
+ }
+ } else {
+ LOGE << "Missing element \"Name\" in " << path << std::endl;
+ }
+
+ TiXmlElement* eDescription = doc.FirstChildElement("Description");
+ if( eDescription ) {
+ const char* c_description = eDescription->GetText();
+ if( c_description ) {
+ description = c_description;
+ }
+ }
+
+ TiXmlElement* eShader = doc.FirstChildElement("Shader");
+ if( eShader ) {
+ const char* c_shader = eShader->GetText();
+ if( c_shader ) {
+ shader = c_shader;
+ }
+ }
+
+ TiXmlElement* eScript = doc.FirstChildElement("Script");
+ if( eScript ) {
+ const char* c_script = eScript->GetText();
+ if( c_script ) {
+ script = c_script;
+ }
+ }
+
+ eScript = doc.FirstChildElement("PCScript");
+ if( eScript ) {
+ const char* c_script = eScript->GetText();
+ if( c_script ) {
+ player_script = c_script;
+ }
+ }
+
+ eScript = doc.FirstChildElement("NPCScript");
+ if( eScript ) {
+ const char* c_script = eScript->GetText();
+ if( c_script ) {
+ enemy_script = c_script;
+ }
+ }
+
+ TiXmlElement* eLoadingScreen = doc.FirstChildElement("LoadingScreen");
+ if( eLoadingScreen ) {
+ TiXmlElement* eImage = eLoadingScreen->FirstChildElement("Image");
+ if( eImage ){
+ const char* c_loading_screen_image = eImage->GetText();
+ if( c_loading_screen_image ) {
+ loading_screen.image = c_loading_screen_image;
+ } else {
+ LOGI << "Image sub element contents for LoadingScreen is null." << std::endl;
+ }
+ } else {
+ LOGW << "Missing Image sub element for LoadingScreen in level" << std::endl;
+ }
+ } else {
+ LOGW << "Missing LoadingScreen element in level" << std::endl;
+ }
+ } else {
+ LOGE << "Got a doc error " << doc.ErrorDesc() << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+bool LevelXMLParser::Save( const std::string& path )
+{
+ TiXmlDocument doc;
+ TiXmlDeclaration * decl = new TiXmlDeclaration( "2.0", "", "" );
+ doc.LinkEndChild(decl);
+
+ TiXmlElement * e;
+ TiXmlElement * e2;
+
+ e = new TiXmlElement("Type");
+ e->LinkEndChild(new TiXmlText("level_info_cache"));
+ doc.LinkEndChild(e);
+
+ e = new TiXmlElement("Version");
+ e->LinkEndChild(new TiXmlText("1"));
+ doc.LinkEndChild(e);
+
+ e = new TiXmlElement("LevelHash");
+ e->LinkEndChild(new TiXmlText(hash));
+ doc.LinkEndChild(e);
+
+ e = new TiXmlElement("Name");
+ e->LinkEndChild(new TiXmlText(name));
+ doc.LinkEndChild(e);
+
+ e = new TiXmlElement("Description");
+ e->LinkEndChild(new TiXmlText(description));
+ doc.LinkEndChild(e);
+
+ e = new TiXmlElement("Shader");
+ e->LinkEndChild(new TiXmlText(shader));
+ doc.LinkEndChild(e);
+
+ e = new TiXmlElement("Script");
+ e->LinkEndChild(new TiXmlText(script));
+ doc.LinkEndChild(e);
+
+ e = new TiXmlElement("PCScript");
+ e->LinkEndChild(new TiXmlText(player_script));
+ doc.LinkEndChild(e);
+
+ e = new TiXmlElement("NPCScript");
+ e->LinkEndChild(new TiXmlText(enemy_script));
+ doc.LinkEndChild(e);
+
+ e = new TiXmlElement("LoadingScreen");
+ e2 = new TiXmlElement("Image");
+ e2->LinkEndChild(new TiXmlText(loading_screen.image));
+ e->LinkEndChild(e2);
+ doc.LinkEndChild(e);
+
+ CreateParentDirs(path);
+ doc.SaveFile(path.c_str());
+ return true;
+}
+
+void LevelXMLParser::Clear()
+{
+ name.clear();
+ description.clear();
+ shader.clear();
+ script.clear();
+ player_script.clear();
+ enemy_script.clear();
+}
+
diff --git a/Source/XML/Parsers/levelxmlparser.h b/Source/XML/Parsers/levelxmlparser.h
new file mode 100644
index 00000000..dfd58be4
--- /dev/null
+++ b/Source/XML/Parsers/levelxmlparser.h
@@ -0,0 +1,65 @@
+//-----------------------------------------------------------------------------
+// Name: levelxmlparser.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+#include <XML/Parsers/xmlparserbase.h>
+
+//Note that this isn't used for level loading currently, only for extracting information from a levelxml file.
+//ofc this comment should be remove if this changes. The actual loading is in LevelLoader.cpp /Max
+class LevelXMLParser : public XMLParserBase
+{
+public:
+ struct Terrain
+ {
+ class DetailMap
+ {
+ std::string colorpath;
+ std::string normalpath;
+ std::string materialpath;
+ };
+
+ std::string heightmap;
+ std::string detailmap;
+ std::string colormap;
+ std::string weightmap;
+
+ };
+
+ struct LoadingScreen
+ {
+ std::string image;
+ };
+
+ virtual uint32_t Load( const std::string& path );
+ virtual bool Save( const std::string& path );
+ virtual void Clear();
+
+ std::string hash;
+ std::string name;
+ std::string description;
+ std::string shader;
+ std::string script;
+ std::string player_script;
+ std::string enemy_script;
+
+ LoadingScreen loading_screen;
+};
diff --git a/Source/XML/Parsers/manifestparser.cpp b/Source/XML/Parsers/manifestparser.cpp
new file mode 100644
index 00000000..10098d61
--- /dev/null
+++ b/Source/XML/Parsers/manifestparser.cpp
@@ -0,0 +1,233 @@
+//-----------------------------------------------------------------------------
+// Name: manifestparser.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include "manifestparser.h"
+
+#include <XML/xml_helper.h>
+#include <XML/Parsers/jobxmlparser.h>
+
+#include <Utility/commonregex.h>
+#include <Logging/logdata.h>
+#include <Internal/filesystem.h>
+
+#include <tinyxml.h>
+
+ManifestXMLParser::ManifestXMLParser()
+{
+
+}
+
+ManifestXMLParser::Item::Item( std::string path, std::string type, std::string hash ) :
+path(path),
+type(type),
+hash(hash)
+{
+
+}
+
+ManifestXMLParser::BuilderResult::BuilderResult( std::string dest, std::string dest_hash, std::string builder, std::string builder_version, std::string type, bool success, bool fresh_built, std::vector<Item> items ):
+dest(dest),
+dest_hash(dest_hash),
+builder(builder),
+builder_version(builder_version),
+type(type),
+success(success),
+fresh_built(fresh_built),
+items(items)
+{
+
+}
+
+ManifestXMLParser::GeneratorResult::GeneratorResult( std::string dest, std::string dest_hash, std::string generator, std::string generator_version, std::string type, bool success, bool fresh_built ) :
+dest(dest),
+dest_hash(dest_hash),
+generator(generator),
+generator_version(generator_version),
+type(type),
+success(success),
+fresh_built(fresh_built)
+{
+
+}
+
+
+ManifestXMLParser::Manifest::Manifest( std::vector<BuilderResult> builder_results, std::vector<GeneratorResult> generator_results ) :
+builder_results(builder_results),
+generator_results(generator_results)
+{
+
+}
+
+ManifestXMLParser::Manifest::Manifest()
+{
+
+}
+
+bool ManifestXMLParser::Load( const std::string& manifest_path )
+{
+ Clear();
+
+ CommonRegex cr;
+ TiXmlDocument doc( manifest_path.c_str() );
+ doc.LoadFile();
+
+ if( !doc.Error() )
+ {
+ TiXmlElement* pRoot = doc.RootElement();
+
+ if( pRoot )
+ {
+ TiXmlHandle hRoot(pRoot);
+
+ TiXmlNode* nResult = hRoot.FirstChild().ToNode();
+
+ std::vector<BuilderResult> builder_results;
+ std::vector<GeneratorResult> generator_results;
+
+ while( nResult )
+ {
+ TiXmlElement* eResult = nResult->ToElement();
+
+ if( eResult )
+ {
+ if( strcmp(eResult->Value(), "BuilderResult") == 0 )
+ {
+ const char* dest = eResult->Attribute("dest");
+ const char* dest_hash = eResult->Attribute("dest_hash");
+ const char* builder = eResult->Attribute("builder");
+ const char* builder_version = eResult->Attribute("builder_version");
+ const char* type = eResult->Attribute("type");
+ bool success = cr.saysTrue(eResult->Attribute("success"));
+ bool fresh_built = cr.saysTrue(eResult->Attribute("fresh_built"));
+
+ std::string dest_s;
+ if( dest )
+ dest_s = std::string( dest );
+ std::string dest_hash_s;
+ if( dest_hash )
+ dest_hash_s = std::string( dest_hash );
+ std::string builder_s;
+ if( builder )
+ builder_s = std::string( builder );
+ std::string builder_version_s;
+ if( builder_version )
+ builder_version_s = std::string(builder_version);
+ std::string type_s;
+ if( type )
+ type_s = std::string(type);
+
+ std::vector<Item> items;
+
+ TiXmlElement* eItem = nResult->FirstChild("Item")->ToElement();
+
+ while( eItem )
+ {
+ const char* item_path = eItem->Attribute("path");
+ const char* item_type = eItem->Attribute("type");
+ const char* item_hash = eItem->Attribute("hash");
+
+ std::string item_path_s;
+ if( item_path )
+ item_path_s = std::string( item_path );
+ std::string item_type_s;
+ if( item_type )
+ item_type_s = std::string( item_type );
+ std::string item_hash_s;
+ if( item_hash )
+ item_hash_s = std::string( item_hash );
+
+ items.push_back(Item(item_path_s, item_type_s, item_hash_s));
+
+ eItem = eItem->NextSiblingElement("Item");
+ }
+
+ builder_results.push_back( BuilderResult( dest_s, dest_hash_s, builder_s, builder_version_s, type_s, success, fresh_built, items) );
+ }
+ else if(strcmp(eResult->Value(), "GeneratorResult") == 0 )
+ {
+ const char* dest = eResult->Attribute("dest");
+ const char* dest_hash = eResult->Attribute("dest_hash");
+ const char* generator = eResult->Attribute("generator");
+ const char* generator_version = eResult->Attribute("generator_version");
+ const char* type = eResult->Attribute("type");
+ bool success = cr.saysTrue(eResult->Attribute("success"));
+ bool fresh_built = cr.saysTrue(eResult->Attribute("fresh_built"));
+
+ std::string dest_s;
+ if( dest )
+ dest_s = std::string( dest );
+ std::string dest_hash_s;
+ if( dest_hash )
+ dest_hash_s = std::string( dest_hash );
+ std::string generator_s;
+ if( generator )
+ generator_s = std::string( generator );
+ std::string generator_version_s;
+ if( generator_version )
+ generator_version_s = std::string(generator_version);
+ std::string type_s;
+ if( type )
+ type_s = std::string(type);
+
+ generator_results.push_back( GeneratorResult(dest_s, dest_hash_s, generator_s, generator_version_s, type_s, success, fresh_built ) );
+ }
+ else
+ {
+ LOGE << "Unknown element name: " << eResult->Value() << std::endl;
+ }
+ }
+ else
+ {
+ LOGE << "Malformed element in manifest" << std::endl;
+ }
+
+ nResult = nResult->NextSibling();
+ }
+
+ manifest = Manifest( builder_results, generator_results );
+ }
+ else
+ {
+ LOGE << "Problem loading manifest file" << manifest_path << std::endl;
+ return false;
+ }
+ }
+ else
+ {
+ LOGE << "Error parsing manifest file: \"" << doc.ErrorDesc() << "\"" << std::endl;
+ return false;
+ }
+ return true;
+}
+
+bool ManifestXMLParser::Save( const std::string& manifest )
+{
+ LOGE << "No manifest save function implemented" << std::endl;
+ return false;
+}
+
+void ManifestXMLParser::Clear()
+{
+ manifest = Manifest();
+}
+
diff --git a/Source/XML/Parsers/manifestparser.h b/Source/XML/Parsers/manifestparser.h
new file mode 100644
index 00000000..20e53be7
--- /dev/null
+++ b/Source/XML/Parsers/manifestparser.h
@@ -0,0 +1,80 @@
+//-----------------------------------------------------------------------------
+// Name: manifestparser.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <XML/Parsers/xmlparserbase.h>
+
+#include <map>
+#include <vector>
+#include <string>
+#include <iostream>
+
+
+class ManifestXMLParser
+{
+public:
+ ManifestXMLParser();
+
+ class Item
+ {
+ public:
+ Item( std::string path, std::string type, std::string hash );
+ std::string path, type, hash;
+ };
+
+ class BuilderResult
+ {
+ public:
+ BuilderResult( std::string dest, std::string dest_hash, std::string builder, std::string builder_version, std::string type, bool success, bool fresh_built, std::vector<Item> items );
+
+ std::string dest, dest_hash, builder, builder_version, type;
+ bool success, fresh_built;
+
+ std::vector<Item> items;
+ };
+
+ class GeneratorResult
+ {
+ public:
+ GeneratorResult( std::string dest, std::string dest_hash, std::string generator, std::string generator_version, std::string type, bool success, bool fresh_built );
+
+ std::string dest, dest_hash, generator, generator_version, type;
+ bool success, fresh_built;
+ };
+
+ class Manifest
+ {
+ public:
+ Manifest();
+ Manifest( std::vector<BuilderResult> builder_results, std::vector<GeneratorResult> generator_results );
+
+ std::vector<BuilderResult> builder_results;
+ std::vector<GeneratorResult> generator_results;
+ };
+
+ Manifest manifest;
+
+ virtual bool Load( const std::string& path );
+ virtual bool Save( const std::string& path );
+ virtual void Clear();
+};
diff --git a/Source/XML/Parsers/musicxmlparser.cpp b/Source/XML/Parsers/musicxmlparser.cpp
new file mode 100644
index 00000000..a89b94bc
--- /dev/null
+++ b/Source/XML/Parsers/musicxmlparser.cpp
@@ -0,0 +1,212 @@
+//-----------------------------------------------------------------------------
+// Name: musicxmlparser.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "musicxmlparser.h"
+
+#include <Utility/commonregex.h>
+#include <Utility/strings.h>
+
+#include <XML/xml_helper.h>
+#include <Logging/logdata.h>
+
+#include <tinyxml.h>
+
+#include <algorithm>
+
+MusicXMLParser::Segment::Segment() {
+ name[0] = '\0';
+ path[0] = '\0';
+}
+
+MusicXMLParser::SongRef::SongRef() {
+ name[0] = '\0';
+}
+
+MusicXMLParser::Song::Song() {
+ name[0] = '\0';
+ start_segment[0] = '\0';
+ type[0] = '\0';
+ file_path[0] = '\0';
+}
+
+MusicXMLParser::MusicXMLParser()
+{
+
+}
+
+uint32_t MusicXMLParser::Load( const std::string& path )
+{
+ Clear();
+
+ TiXmlDocument doc( path.c_str() );
+ doc.LoadFile();
+
+ uint32_t error = kErrorNoError;;
+ int err;
+
+ if( !doc.Error() )
+ {
+ TiXmlElement* pRoot = doc.RootElement();
+
+ if( pRoot )
+ {
+ TiXmlHandle hRoot(pRoot);
+
+ TiXmlElement* eSong = hRoot.FirstChild("Song").Element();
+
+ while( eSong )
+ {
+ Song song;
+
+ err = strscpy(song.name, eSong->Attribute("name"), NAME_MAX_LENGTH);
+ if( err == SOURCE_TOO_LONG ) {
+ error |= kErrorSongNameTooLong;
+ } else if( err == SOURCE_IS_NULL ) {
+ error |= kErrorSongNameMissing;
+ }
+
+ err = strscpy(song.type, eSong->Attribute("type"), NAME_MAX_LENGTH);
+ if( err == SOURCE_TOO_LONG ) {
+ error |= kErrorSongTypeTooLong;
+ } else if( err == SOURCE_IS_NULL ) {
+ error |= kErrorSongTypeMissing;
+ }
+
+ if( strmtch( song.type, "single" ) ) {
+ err = strscpy( song.file_path, eSong->Attribute("file_path"), PATH_MAX_LENGTH );
+ if( err == SOURCE_TOO_LONG ) {
+ error |= kErrorSongFilePathTooLong;
+ } else if( err == SOURCE_IS_NULL ) {
+ error |= kErrorSongFilePathMissing;
+ }
+ } else if( strmtch( song.type, "segmented" ) ) {
+ err = strscpy( song.start_segment, eSong->Attribute("start_segment"), NAME_MAX_LENGTH );
+ if( err == SOURCE_TOO_LONG ) {
+ error |= kErrorSongStartSegmentTooLong;
+ } else if( err == SOURCE_IS_NULL ) {
+ error |= kErrorSongStartSegmentMissing;
+ }
+
+ TiXmlElement *eSegment = eSong->FirstChildElement("Segment");
+
+ while( eSegment ) {
+ Segment segment;
+
+ err = strscpy(segment.name, eSegment->Attribute("name"), NAME_MAX_LENGTH);
+ if( err == SOURCE_TOO_LONG ) {
+ error |= kErrorSongSegmentNameTooLong;
+ } else if( err == SOURCE_IS_NULL ) {
+ error |= kErrorSongStartSegmentMissing;
+ }
+
+ err = strscpy(segment.path, eSegment->Attribute("path"), PATH_MAX_LENGTH);
+ if( err == SOURCE_TOO_LONG ) {
+ error |= kErrorSongSegmentPathTooLong;
+ } else if( err == SOURCE_IS_NULL ) {
+ error |= kErrorSongSegmentPathMissing;
+ }
+
+ if( std::find(song.segments.begin(),song.segments.end(),segment) == song.segments.end() ) {
+ song.segments.push_back(segment);
+ } else {
+ LOGE << "Segment " << segment << " already has a copy in the song, skipping." << std::endl;
+ }
+
+ eSegment = eSegment->NextSiblingElement("Segment");
+ }
+
+ if( song.segments.size() == 0 ) {
+ error |= kErrorSongSegmentMissing;
+ }
+
+ } else if( strmtch( song.type, "layered") ) {
+ TiXmlElement* eSongRef = eSong->FirstChildElement("SongRef");
+ while( eSongRef ) {
+ SongRef sr;
+ err = strscpy( sr.name, eSongRef->Attribute("name"), NAME_MAX_LENGTH );
+ if( err == SOURCE_TOO_LONG ) {
+ error |= kErrorSongSongRefNameTooLong;
+ } else if( err == SOURCE_IS_NULL ) {
+ error |= kErrorSongSongRefNameMissing;
+ }
+ song.songrefs.push_back(sr);
+
+ eSongRef = eSongRef->NextSiblingElement("SongRef");
+ }
+ } else {
+ error |= kErrorSongTypeInvalid;
+ }
+
+
+ if( std::find(music.songs.begin(), music.songs.end(), song) == music.songs.end() )
+ {
+ music.songs.push_back(song);
+ }
+ else
+ {
+ LOGE << "Song " << song << " already has matching instance in the song list structure, skipping." << std::endl;
+ }
+
+ eSong = eSong->NextSiblingElement("Song");
+ }
+
+ if( music.songs.size() == 0 ) {
+ error |= kErrorSongMissing;
+ }
+ } else {
+ error |= kErrorSongMissing;
+ }
+ }
+ else
+ {
+ error |= kErrorDocumentParseError;
+ LOGE << "Unable to parse xml document:" << path << " "<< doc.ErrorDesc() << std::endl;
+ }
+
+ return error;
+}
+
+bool MusicXMLParser::Save( const std::string &path )
+{
+ LOGE << "No saving routine implemented for MusixXMLParser" << std::endl;
+ return false;
+}
+
+void MusicXMLParser::Clear()
+{
+ music = Music();
+}
+
+MusicXMLParser::Segment MusicXMLParser::Song::GetStartSegment() const
+{
+ std::vector<MusicXMLParser::Segment>::const_iterator segit;
+
+ for( segit = segments.begin(); segit != segments.end(); segit++ )
+ {
+ if( strmtch(segit->name, start_segment ) )
+ {
+ return *segit;
+ }
+ }
+ LOGE << "Unable to find start segment with name " << start_segment << std::endl;
+ return MusicXMLParser::Segment();
+}
diff --git a/Source/XML/Parsers/musicxmlparser.h b/Source/XML/Parsers/musicxmlparser.h
new file mode 100644
index 00000000..0edc57c7
--- /dev/null
+++ b/Source/XML/Parsers/musicxmlparser.h
@@ -0,0 +1,185 @@
+//-----------------------------------------------------------------------------
+// Name: musicxmlparser.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <XML/Parsers/xmlparserbase.h>
+#include <Utility/strings.h>
+
+#include <map>
+#include <vector>
+#include <string>
+#include <iostream>
+
+
+class MusicXMLParser : public XMLParserBase
+{
+
+public:
+ static const uint32_t kErrorNoError = 0UL;
+ static const uint32_t kErrorMissingRootElement = 1UL << 1;
+ static const uint32_t kErrorDocumentParseError = 1UL << 2;
+ static const uint32_t kErrorSongNameTooLong = 1UL << 3;
+ static const uint32_t kErrorSongNameMissing = 1UL << 4;
+ static const uint32_t kErrorSongTypeTooLong = 1UL << 5;
+ static const uint32_t kErrorSongTypeMissing = 1UL << 6;
+ static const uint32_t kErrorSongTypeInvalid = 1UL << 7;
+ static const uint32_t kErrorSongSegmentMissing = 1UL << 8;
+ static const uint32_t kErrorSongFilePathTooLong = 1UL << 9;
+ static const uint32_t kErrorSongFilePathMissing = 1UL << 10;
+ static const uint32_t kErrorSongStartSegmentTooLong = 1UL << 11;
+ static const uint32_t kErrorSongStartSegmentMissing = 1UL << 12;
+ static const uint32_t kErrorSongSegmentNameTooLong = 1UL << 13;
+ static const uint32_t kErrorSongSegmentNameMissing = 1UL << 14;
+ static const uint32_t kErrorSongSegmentPathTooLong = 1UL << 15;
+ static const uint32_t kErrorSongSegmentPathMissing = 1UL << 16;
+ static const uint32_t kErrorSongMissing = 1UL << 17;
+ static const uint32_t kErrorSongSongRefNameTooLong = 1UL << 18;
+ static const uint32_t kErrorSongSongRefNameMissing = 1UL << 19;
+
+ //Applies to all types that hold a string path to disk data in the xml
+ static const size_t PATH_MAX_LENGTH = 256;
+ //Applies too all name attributes, and references to them
+ static const size_t NAME_MAX_LENGTH = 32;
+ //Applies to the type, controlling how a song is parsed
+ static const size_t TYPE_MAX_LENGTH = 16;
+
+ MusicXMLParser();
+
+ class Segment
+ {
+ public:
+ Segment();
+
+ char name[NAME_MAX_LENGTH];
+ char path[PATH_MAX_LENGTH];
+ };
+
+ class SongRef
+ {
+ public:
+ SongRef();
+
+ char name[NAME_MAX_LENGTH];
+ };
+
+ class Song
+ {
+ public:
+ Song();
+
+ char name[NAME_MAX_LENGTH];
+ char start_segment[NAME_MAX_LENGTH];
+ char type[NAME_MAX_LENGTH];
+ char file_path[PATH_MAX_LENGTH];
+
+ std::vector<Segment> segments;
+ std::vector<SongRef> songrefs;
+
+ Segment GetStartSegment() const;
+ };
+
+ class Music
+ {
+ public:
+ std::vector<Song> songs;
+ };
+
+ Music music;
+
+ virtual uint32_t Load( const std::string& path );
+ virtual bool Save( const std::string& path );
+ virtual void Clear();
+};
+
+inline std::ostream& operator<<( std::ostream& out, const MusicXMLParser::Segment& segment )
+{
+ out << "<Segment name=\"" << segment.name << "\" " << segment.path << "\"/>" << std::endl;
+
+ return out;
+}
+
+inline std::ostream& operator<<( std::ostream& out, const MusicXMLParser::Song& song )
+{
+ out << "<Song name = \"" << song.name << "\">" << std::endl;
+
+ out << "<Segments>" << std::endl;
+
+ for( unsigned i = 0; i < song.segments.size(); i++ )
+ {
+ out << song.segments[i] << std::endl;
+ }
+
+ out << "</Segments>" << std::endl;
+
+ out << "<Layers>" << std::endl;
+
+ out << "</Layers>" << std::endl;
+
+ out << "</Song>";
+
+ return out;
+}
+
+inline std::ostream& operator<<( std::ostream& out, const MusicXMLParser::Music& music )
+{
+ out << "<Music><Songs>" << std::endl;
+
+ for( unsigned i = 0; i < music.songs.size(); i++ )
+ {
+ out << music.songs[i] << std::endl;
+ }
+
+ out << "</Songs></Music>";
+
+ return out;
+}
+
+inline bool operator==( const MusicXMLParser::Song &lhs, const MusicXMLParser::Song &rhs )
+{
+ return strmtch(lhs.name, rhs.name);
+}
+
+inline bool operator!=( const MusicXMLParser::Song &lhs, const MusicXMLParser::Song &rhs )
+{
+ return !strmtch(lhs.name, rhs.name);
+}
+
+inline bool operator<( const MusicXMLParser::Song &lhs, const MusicXMLParser::Song &rhs )
+{
+ return strcmp(lhs.name, rhs.name);
+}
+
+inline bool operator==( const MusicXMLParser::Segment &lhs, const MusicXMLParser::Segment &rhs )
+{
+ return strmtch(lhs.name, rhs.name);
+}
+
+inline bool operator!=( const MusicXMLParser::Segment &lhs, const MusicXMLParser::Segment &rhs )
+{
+ return !strmtch(lhs.name, rhs.name);
+}
+
+inline bool operator<( const MusicXMLParser::Segment &lhs, const MusicXMLParser::Segment &rhs )
+{
+ return strcmp(lhs.name,rhs.name);
+}
diff --git a/Source/XML/Parsers/xmlparserbase.cpp b/Source/XML/Parsers/xmlparserbase.cpp
new file mode 100644
index 00000000..b17fb3f4
--- /dev/null
+++ b/Source/XML/Parsers/xmlparserbase.cpp
@@ -0,0 +1,24 @@
+//-----------------------------------------------------------------------------
+// Name: xmlparserbase.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#include "xmlparserbase.h"
+
diff --git a/Source/XML/Parsers/xmlparserbase.h b/Source/XML/Parsers/xmlparserbase.h
new file mode 100644
index 00000000..26505688
--- /dev/null
+++ b/Source/XML/Parsers/xmlparserbase.h
@@ -0,0 +1,34 @@
+//-----------------------------------------------------------------------------
+// Name: xmlparserbase.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/integer.h>
+
+#include <string>
+
+class XMLParserBase {
+public:
+ virtual uint32_t Load( const std::string& path ) = 0;
+ virtual bool Save( const std::string& path ) = 0;
+ virtual void Clear() = 0;
+};
diff --git a/Source/XML/level_loader.cpp b/Source/XML/level_loader.cpp
new file mode 100644
index 00000000..5464a46e
--- /dev/null
+++ b/Source/XML/level_loader.cpp
@@ -0,0 +1,815 @@
+//-----------------------------------------------------------------------------
+// Name: level_loader.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include <Internal/profiler.h>
+#include <Internal/common.h>
+#include <Internal/filesystem.h>
+#include <Internal/dialogues.h>
+#include <Internal/error.h>
+#include <Internal/datemodified.h>
+#include <Internal/locale.h>
+#include <Internal/memwrite.h>
+#include <Internal/levelxml.h>
+#include <Internal/config.h>
+
+#include <Objects/terrainobject.h>
+#include <Objects/cameraobject.h>
+#include <Objects/editorcameraobject.h>
+#include <Objects/envobject.h>
+#include <Objects/movementobject.h>
+#include <Objects/group.h>
+#include <Objects/hotspot.h>
+#include <Objects/decalobject.h>
+#include <Objects/hotspot.h>
+
+#include <XML/xml_helper.h>
+#include <XML/level_loader.h>
+
+#include <Graphics/models.h>
+#include <Graphics/camera.h>
+#include <Graphics/sky.h>
+#include <Graphics/graphics.h>
+#include <Graphics/shaders.h>
+#include <Graphics/font_renderer.h>
+#include <Graphics/particles.h>
+#include <Graphics/text.h>
+
+#include <Asset/Asset/ambientsounds.h>
+#include <Asset/Asset/skeletonasset.h>
+#include <Asset/Asset/syncedanimation.h>
+#include <Asset/Asset/voicefile.h>
+
+#include <Sound/sound.h>
+#include <Sound/threaded_sound_wrapper.h>
+
+#include <Main/scenegraph.h>
+#include <Math/vec4.h>
+#include <Editors/map_editor.h>
+#include <Game/level.h>
+#include <Logging/logdata.h>
+#include <GUI/widgetframework.h>
+#include <Online/online.h>
+
+#include <tinyxml.h>
+
+extern std::string script_dir_path;
+extern bool g_level_shadows;
+
+void LoadTerrain(SceneGraph& s, const TerrainInfo& ti) {
+ AddLoadingText("Loading terrain object...");
+
+ TerrainObject* terrain_object = NULL;
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Creating terrain object");
+ terrain_object = new TerrainObject(ti);
+ }
+ terrain_object->SetID(0);
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Adding terrain object to scenegraph");
+ s.addObject(terrain_object);
+ }
+ s.terrain_object_ = terrain_object;
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Preparing terrain physics");
+ terrain_object->PreparePhysicsMesh();
+ }
+}
+
+void AddCamera(SceneGraph &s) {
+ EditorCameraObject* new_object = new EditorCameraObject();
+ new_object->SetTranslation(vec3(0,0,0));
+ new_object->controlled = true;
+ new_object->has_position_initialized = false;
+ s.addObject(new_object);
+ ActiveCameras::Get()->SetCameraObject((CameraObject*)new_object);
+ ActiveCameras::Get()->SetFlags(Camera::kEditorCamera);
+}
+
+static void LoadAll(const PathSet &path_set, ThreadedSound* sound) {
+ std::list<std::string> skeleton_paths;
+ for(PathSet::const_iterator iter = path_set.begin(); iter != path_set.end(); ++iter) {
+ const std::string &entry = (*iter);
+ int space_pos = entry.find(' ');
+ const std::string &type = entry.substr(0,space_pos);
+ const std::string &path = entry.substr(space_pos+1, entry.size()-(space_pos+1));
+ static const int kBufSize = 1024;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "Loading %s from %s", type.c_str(), path.c_str());
+ PROFILER_ZONE_DYNAMIC_STRING(g_profiler_ctx, buf);
+ switch(type[0]){
+ case 'a':
+ switch(type[1]){
+ case 'm':
+ if(type == "ambient_sound"){Engine::Instance()->GetAssetManager()->LoadSync<AmbientSound>(path);}
+ break;
+ case 'n':
+ if(type == "animation"){Engine::Instance()->GetAssetManager()->LoadSync<Animation>(path);}
+ break;
+ case 't':
+ if(type == "attack"){Engine::Instance()->GetAssetManager()->LoadSync<Attack>(path);}
+ break;
+ }
+ break;
+ case 'c':
+ if(type == "character"){Engine::Instance()->GetAssetManager()->LoadSync<Character>(path);}
+ break;
+ case 'd':
+ if(type == "decal"){Engine::Instance()->GetAssetManager()->LoadSync<DecalFile>(path);}
+ break;
+ case 'f':
+ if(type == "font"){FontRenderer::Instance()->PreLoadFont(path);}
+ break;
+ case 'h'://heightmap
+ break;
+ case 'i':
+ if(type == "image_sample"){Engine::Instance()->GetAssetManager()->LoadSync<ImageSampler>(path);}
+ break;
+ case 'l'://level
+ break;
+ case 'm':
+ switch(type[1]){
+ case 'a':
+ if(type == "material"){Engine::Instance()->GetAssetManager()->LoadSync<Material>(path);}
+ break;
+ case 'o'://model
+ break;
+ }
+ break;
+ case 'o': //object
+ break;
+ case 'p'://particle
+ if(type == "particle"){Engine::Instance()->GetAssetManager()->LoadSync<ParticleType>(path);}
+ break;
+ case 's':
+ switch(type[1]){
+ case 'c'://script
+ break;
+ case 'h'://shader
+ break;
+ case 'k':
+ if(type == "skeleton"){
+ //SkeletonAssets::Instance()->ReturnRef(path);
+ Engine::Instance()->GetAssetManager()->LoadSync<SkeletonAsset>(path);
+ skeleton_paths.push_back(path);
+ }
+ break;
+ case 'o':
+ if(type == "sound"){
+ sound->LoadSoundFile(path);
+ } else if(type == "soundgroup"){
+ //SoundGroupInfoCollection::Instance()->ReturnRef(path);
+ Engine::Instance()->GetAssetManager()->LoadSync<SoundGroupInfo>(path);
+ }
+ break;
+ case 'y':
+ if(type == "synced_animation"){Engine::Instance()->GetAssetManager()->LoadSync<SyncedAnimationGroup>(path);}
+ break;
+ }
+ break;
+ case 't':
+ //Textures::Instance()->returnTextureAssetRef(path);
+ break;
+ case 'v':
+ if(type == "voice"){Engine::Instance()->GetAssetManager()->LoadSync<VoiceFile>(path);}
+ break;
+ }
+ }
+ LOGI << "Finished loading path set" << std::endl;
+}
+
+struct IDAndType {
+ int id;
+ EntityType type;
+ EntityDescription *desc;
+ uint32_t counter; //we use this counter to guarantee the same order on all systems when two objects are otherwise identical.
+};
+
+static int IDAndType_CompareID(const void *a, const void *b) {
+ IDAndType *a_ptr = (IDAndType*)a;
+ IDAndType *b_ptr = (IDAndType*)b;
+ int cmp = a_ptr->id - b_ptr->id;
+ if(cmp == 0) {
+ //We add to the type value, so that we have space to prioritize special types
+ int a_val = a_ptr->type + 3;
+ int b_val = b_ptr->type + 3;
+
+ //We override group and prefab to be prioritized first, unknown why.
+ if(a_ptr->type == _group) {
+ a_val = 0;
+ }
+ if(b_ptr->type == _group) {
+ b_val = 0;
+ }
+
+ if(a_ptr->type == _prefab) {
+ a_val = 1;
+ }
+ if(b_ptr->type == _prefab) {
+ b_val = 1;
+ }
+ //We prioritize movement_object, because there have been cases where it loses its id, breaking dialogues
+ if(a_ptr->type == _movement_object) {
+ a_val = 2;
+ }
+ if(b_ptr->type == _movement_object) {
+ b_val = 2;
+ }
+
+ cmp = a_val - b_val;
+
+ if( cmp == 0 ) {
+ return a_ptr->counter - b_ptr->counter;
+ }
+ }
+ return cmp;
+}
+
+// Finds entity IDs that are used more than once, and replaces repeats with -1 so they will be automatically reassigned
+static void ExtractFlatList(EntityDescriptionList *desc_list, std::vector<IDAndType> &id_used){
+ uint32_t counter = 1; //Zero is assigned to the terrain
+ //bool print_results = false;
+ for(EntityDescriptionList::iterator it = desc_list->begin();
+ it != desc_list->end();
+ ++it)
+ {
+ id_used.resize(id_used.size()+1);
+ IDAndType *id_and_type = &id_used.back();
+ id_and_type->desc = &(*it);
+ id_and_type->desc->GetEditableField(EDF_ENTITY_TYPE)->ReadInt((int*)&id_and_type->type);
+ id_and_type->desc->GetEditableField(EDF_ID)->ReadInt(&id_and_type->id);
+ id_and_type->counter = counter++;
+ if(!id_and_type->desc->children.empty()){
+ ExtractFlatList(&id_and_type->desc->children, id_used);
+ }
+ }
+}
+
+static void SetDescID(EntityDescription *desc, int id){
+ EntityDescriptionField* edf = desc->GetEditableField(EDF_ID);
+ if( edf )
+ {
+ edf->data.clear();
+ edf->WriteInt(id);
+ }
+ else
+ {
+ LOGE << "EDF_ID object returned is NULL, unexpected behaviour" << std::endl;
+ }
+}
+
+static void FixDuplicateIDs(EntityDescriptionList *desc_list, bool has_terrain){
+ std::vector<IDAndType> id_used;
+ ExtractFlatList(desc_list, id_used);
+ if(!id_used.empty()){
+ EntityDescription terrain_desc;
+ if(has_terrain){
+ IDAndType terrain_id_and_type;
+ terrain_id_and_type.id = 0;
+ terrain_id_and_type.counter = 0;
+ terrain_id_and_type.type = _terrain_type;
+ terrain_id_and_type.desc = &terrain_desc;
+ id_used.push_back(terrain_id_and_type);
+ }
+ qsort(&id_used[0], id_used.size(), sizeof(id_used[0]), IDAndType_CompareID);
+ int free_index = 1;
+ int free_id = -1;
+ for(int i=1, len=id_used.size(); i<len; ++i){
+ if(id_used[i-1].id == id_used[i].id){
+ while(free_index != len && (id_used[free_index].id < id_used[free_index-1].id+2 || id_used[free_index].id < free_id+2)){
+ free_id = id_used[free_index].id;
+ ++free_index;
+ }
+ ++free_id;
+ if(id_used[i-1].type == _group || id_used[i-1].type == _prefab){
+ LOGW << "Object loaded of type " << id_used[i-1].type << " is changing id from " << id_used[i-1].id << " to " << free_id << " due to collision." << std::endl;
+ SetDescID(id_used[i-1].desc, free_id);
+ } else {
+ LOGW << "Object loaded of type " << id_used[i].type << " is changing id from " << id_used[i].id << " to " << free_id << " due to collision." << std::endl;
+ SetDescID(id_used[i].desc, free_id);
+ }
+ }
+ }
+ }
+}
+
+extern TextAtlasRenderer g_text_atlas_renderer;
+extern TextAtlas g_text_atlas[kNumTextAtlas];
+extern ASTextContext g_as_text_context;
+extern const char* font_path;
+
+void AnalyzeForLineBreaks(char* str, int len){
+ FontRenderer* font_renderer = FontRenderer::Instance();
+ int font_size = int(max(18, min(Graphics::Instance()->window_dims[1] / 30, Graphics::Instance()->window_dims[0] / 50)));
+ TextMetrics metrics = g_as_text_context.ASGetTextAtlasMetrics( font_path, font_size, 0, str);
+ float threshold = (float) std::min(font_size*40, Graphics::Instance()->window_dims[0]-100);
+ std::string final;
+ std::string first_line = str;
+ std::string second_line;
+ while(first_line.length() > 0){
+ while(metrics.bounds[2] > threshold){
+ int last_space = first_line.find_last_of(" ");
+ second_line.insert(0, first_line.substr(last_space));
+ first_line.resize(last_space);
+ metrics = g_as_text_context.ASGetTextAtlasMetrics( font_path, font_size, 0, first_line.c_str());
+ }
+ final += first_line + "\n";
+ if(!second_line.empty()){
+ first_line = second_line.substr(1);
+ second_line = "";
+ } else {
+ first_line.clear();
+ }
+ metrics = g_as_text_context.ASGetTextAtlasMetrics( font_path, font_size, 0, first_line.c_str());
+ }
+ FormatString(str, len, "%s", final.c_str());
+}
+
+bool LevelLoader::LoadLevel(const Path& level_path, SceneGraph& s) {
+ Graphics* gi = Graphics::Instance();
+
+ if(level_path.isValid() == false) {
+ FatalError("Error", "Could not find level file: %s", level_path.GetFullPath());
+ }
+
+ LevelInfo li;
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Parsing level xml");
+ ParseLevelXML(level_path.GetFullPath(), li);
+ g_level_shadows = li.shadows_;
+ const char* localized_load_tip = GetLevelTip(config["language"].str().c_str(), FindShortestPath(level_path.GetFullPath()).c_str());
+ char temp_load_screen_tip[kPathSize] = {'\0'};
+ if(localized_load_tip) {
+ FormatString(temp_load_screen_tip, kPathSize, "%s", localized_load_tip);
+ } else {
+ // Simple fallback to en_us in case there is one available
+ const char* localized_load_tip = GetLevelTip("en_us", FindShortestPath(level_path.GetFullPath()).c_str());
+ if(localized_load_tip) {
+ FormatString(temp_load_screen_tip, kPathSize, "%s", localized_load_tip);
+ } else if (li.spm_.find("Load Tip") != li.spm_.end()){
+ FormatString(temp_load_screen_tip, kPathSize, li.spm_["Load Tip"].GetString().c_str());
+ }
+ }
+ AnalyzeForLineBreaks(temp_load_screen_tip, kPathSize);
+ FormatString(Engine::Instance()->load_screen_tip, kPathSize, "%s", temp_load_screen_tip);
+ if(!li.script_.empty()){
+ std::string path = li.script_.substr(0, li.script_.size()-3) + "_paths.xml";
+ if(!FileExists(path.c_str(), kAnyPath)){
+ path = script_dir_path + path;
+ }
+ Path level_script_path;
+ level_script_path = FindFilePath(path.c_str(), kAnyPath, false);
+ if(level_script_path.isValid()){
+ TiXmlDocument doc;
+ if (!doc.LoadFile(level_script_path.GetFullPath())) {
+ FatalError("Error", "Bad xml data in level script path file: %s", path.c_str());
+ }
+ const TiXmlElement* root = doc.RootElement();
+
+ LevelInfo::StrPair script_path;
+ for(const TiXmlElement* field = root; field; field = field->NextSiblingElement()){
+ const char* val = field->Value();
+ if(strcmp(val, "path") == 0) {
+ script_path.first.clear();
+ script_path.second.clear();
+ const TiXmlAttribute* attrib = field->FirstAttribute();
+ while(attrib){
+ const char* name = attrib->Name();
+ if(strcmp(name, "path") == 0){
+ script_path.second = attrib->Value();
+ } else if(strcmp(name, "key") == 0){
+ script_path.first = attrib->Value();
+ }
+ attrib = attrib->Next();
+ }
+ li.script_paths_.push_back(script_path);
+ }
+ }
+ }
+ }
+ }
+
+ s.level_path_ = FindShortestPath2(level_path.GetFullPath());
+ s.level_has_been_previously_saved_ = false;
+ s.level_name_ = li.level_name_;
+ s.level_visible_name_ = li.visible_name_;
+ s.level_visible_description_ = li.visible_description_;
+ gi->post_shader_name = li.shader_;
+
+ bool has_terrain = !li.terrain_info_.colormap.empty();
+ if(has_terrain){
+ PROFILER_ZONE(g_profiler_ctx, "Loading terrain");
+ LoadTerrain(s, li.terrain_info_);
+ }
+
+ FixDuplicateIDs(&li.desc_list_, has_terrain);
+ AddLoadingText("Adding loaded objects to scene...");
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Adding objects");
+
+ static const int kBufSize = 256;
+ char buf[kBufSize];
+ for(unsigned i=0, len=li.desc_list_.size(); i<len; ++i){
+ buf[0] = '\0';
+ const EntityDescriptionField* path_field = li.desc_list_[i].GetField(EDF_FILE_PATH);
+ if(path_field && !path_field->data.empty()){
+ FormatString(buf, kBufSize, "Adding object: %s", std::string(path_field->data.begin(), path_field->data.end()).c_str());
+ PROFILER_ENTER_DYNAMIC_STRING(g_profiler_ctx, buf);
+ } else {
+ PROFILER_ENTER(g_profiler_ctx, "Adding object: unknown");
+ }
+ Object * obj = MapEditor::AddEntityFromDesc(&s, li.desc_list_[i], true);
+ if( obj == NULL ) {
+ LOGE << "Failed to construct object \"" << buf << "\" for level load" << std::endl;
+ }
+ PROFILER_LEAVE(g_profiler_ctx);
+ if( Engine::Instance()->RequestedInterruptLoading() ) {
+ return false;
+ }
+ }
+ }
+ s.map_editor->SetUpSky(li.sky_info_);
+
+ if(ActiveCameras::Get()->m_camera_object == NULL) {
+ AddCamera(s);
+ }
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Setting up level info");
+ s.level->SetFromLevelInfo(li);
+ }
+ {
+ gi->nav_mesh_out_of_date = li.out_of_date_info_.nav_mesh;
+ gi->nav_mesh_out_of_date_chunk = -1;
+ }
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Loading nav mesh");
+ AddLoadingText("Loading nav mesh...");
+ if( s.LoadNavMesh() )
+ {
+ AddLoadingText("Nav mesh loaded!");
+ }
+ else
+ {
+ if(li.nav_mesh_parameters_.generate && config["no_auto_nav_mesh"].toBool() == false){
+ AddLoadingText("Navmesh needs to be rebuilt for some reason");
+ AddLoadingText("Generating new nav mesh...");
+ s.CreateNavMesh();
+ s.SaveNavMesh();
+ }
+ }
+ }
+ s.SendMessageToAllObjects(OBJECT_MSG::FINALIZE_LOADED_CONNECTIONS);
+
+ AddLoadingText("Loading ambient sounds and music...");
+ for(unsigned i=0; i<li.ambient_sounds_.size(); ++i){
+ Engine::Instance()->GetSound()->AddAmbientTriangle(li.ambient_sounds_[i]);
+ }
+ AddLoadingText("Getting path set...");
+ PathSet path_set;
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Returning paths to path set");
+ li.ReturnPaths(path_set);
+ }
+ AddLoadingText("Loading all data in advance...");
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Preloading all paths");
+ LoadAll(path_set, Engine::Instance()->GetSound());
+ }
+
+ PROFILER_ZONE(g_profiler_ctx, "Applying level script params");
+ // Add defaults if needed
+ ScriptParams sp;
+ sp.SetParameterMap(li.spm_);
+ sp.ASAddString("Objectives", "destroy_all");
+ sp.ASAddString("Achievements", "flawless, no_injuries, no_kills");
+ sp.ASAddInt("Level Boundaries", 1);
+ sp.ASAddInt("Shared Camera", 0);
+ sp.ASAddFloat("HDR White point", 0.7f);
+ sp.ASAddFloat("HDR Black point", 0.005f);
+ sp.ASAddFloat("HDR Bloom multiplier", 1.0f);
+ SceneGraph::ApplyScriptParams(&s, sp.GetParameterMap());
+
+ return true;
+}
+
+void LevelLoader::SaveTerrain(TiXmlNode* root, SceneGraph* s) {
+ TerrainObject *to = s->terrain_object_;
+ if(to){
+ const TerrainInfo &ti = to->terrain_info();
+
+ TiXmlElement* terrain_el = new TiXmlElement("Terrain");
+ root->LinkEndChild(terrain_el);
+
+ TiXmlElement* terrain_sub_el;
+ terrain_sub_el = new TiXmlElement("Heightmap");
+ terrain_sub_el->LinkEndChild( new TiXmlText(ti.heightmap.c_str()) );
+ terrain_el->LinkEndChild(terrain_sub_el);
+ terrain_sub_el = new TiXmlElement("ShaderExtra");
+ terrain_sub_el->LinkEndChild( new TiXmlText(ti.shader_extra.c_str()) );
+ terrain_el->LinkEndChild(terrain_sub_el);
+ terrain_sub_el = new TiXmlElement("DetailMap");
+ terrain_sub_el->LinkEndChild( new TiXmlText("") );
+ terrain_el->LinkEndChild(terrain_sub_el);
+ terrain_sub_el = new TiXmlElement("ColorMap");
+ terrain_sub_el->LinkEndChild( new TiXmlText(ti.colormap.c_str()) );
+ terrain_el->LinkEndChild(terrain_sub_el);
+ if(!ti.weightmap.empty()){
+ terrain_sub_el = new TiXmlElement("WeightMap");
+ terrain_sub_el->LinkEndChild( new TiXmlText(ti.weightmap.c_str()) );
+ terrain_el->LinkEndChild(terrain_sub_el);
+ }
+ if(!ti.model_override.empty()){
+ terrain_sub_el = new TiXmlElement("ModelOverride");
+ terrain_sub_el->LinkEndChild( new TiXmlText(ti.model_override.c_str()) );
+ terrain_el->LinkEndChild(terrain_sub_el);
+ }
+
+ terrain_sub_el = new TiXmlElement("DetailMaps");
+ for(unsigned i=0; i<4; ++i){
+ TiXmlElement* detail_map_el = new TiXmlElement("DetailMap");
+ detail_map_el->SetAttribute("colorpath",ti.detail_map_info[i].colorpath.c_str());
+ detail_map_el->SetAttribute("normalpath",ti.detail_map_info[i].normalpath.c_str());
+ detail_map_el->SetAttribute("materialpath",ti.detail_map_info[i].materialpath.c_str());
+ terrain_sub_el->LinkEndChild(detail_map_el);
+ }
+ terrain_el->LinkEndChild(terrain_sub_el);
+
+ TiXmlElement *does = new TiXmlElement("DetailObjects");
+ for(unsigned i=0; i<ti.detail_object_info.size(); ++i){
+ const DetailObjectLayer &dol = ti.detail_object_info[i];
+ TiXmlElement *doe = new TiXmlElement("DetailObject");
+ doe->SetAttribute("obj_path", dol.obj_path.c_str());
+ doe->SetAttribute("weight_path", dol.weight_path.c_str());
+ doe->SetDoubleAttribute("normal_conform", (double)dol.normal_conform);
+ doe->SetDoubleAttribute("density", (double)dol.density);
+ doe->SetDoubleAttribute("min_embed", (double)dol.min_embed);
+ doe->SetDoubleAttribute("max_embed", (double)dol.max_embed);
+ doe->SetDoubleAttribute("min_scale", (double)dol.min_scale);
+ doe->SetDoubleAttribute("max_scale", (double)dol.max_scale);
+ doe->SetDoubleAttribute("view_distance", (double)dol.view_dist);
+ doe->SetDoubleAttribute("jitter_degrees", (double)dol.jitter_degrees);
+ doe->SetDoubleAttribute("overbright", (double)dol.overbright);
+ doe->SetDoubleAttribute("tint_weight", (double)dol.tint_weight);
+ if(dol.collision_type == _static){
+ doe->SetAttribute("collision_type", "static");
+ } else if(dol.collision_type == _plant){
+ doe->SetAttribute("collision_type", "plant");
+ }
+ does->LinkEndChild(doe);
+ }
+ terrain_el->LinkEndChild(does);
+ }
+}
+
+void LevelLoader::SaveLevel(SceneGraph &s, SaveLevelType type) {
+ /*if(s.VerifySanity() == false ) {
+ SDL_MessageBoxData message_box_data;
+ message_box_data.title = "Overgrowth";
+ message_box_data.message = "Your level has some sanity warnings that should be fixed, are you sure you wish to save?";
+ message_box_data.colorScheme = NULL;
+ message_box_data.window = NULL;
+ message_box_data.flags = SDL_MESSAGEBOX_WARNING;
+ message_box_data.numbuttons = 2;
+ SDL_MessageBoxButtonData buttons[3];
+ buttons[0].text = "Yes";
+ buttons[0].flags = SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT;
+ buttons[0].buttonid = 0;
+ buttons[1].text = "No";
+ buttons[1].flags = SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT;
+ buttons[1].buttonid = 1;
+ message_box_data.buttons = buttons;
+ int button_id;
+ SDL_ShowMessageBox(&message_box_data, &button_id);
+ if(button_id == 1){
+ return;
+ }
+ }*/
+
+ std::string save_path;
+ std::string old_path;
+ if(s.level_path_.isValid()) {
+ save_path = s.level_path_.GetFullPath();
+ old_path = save_path;
+ }
+
+ if(type == kSaveAs || s.level_path_.isValid() == false || (((s.level_path_.source & kModPaths) || (s.level_path_.source & kDataPaths)) && (!s.level_has_been_previously_saved_ && config["allow_game_dir_save"].toBool() == false))) {
+ std::string start_dir;
+ if( config["allow_game_dir_save"].toBool() ) {
+ if( save_path.empty() ) {
+ start_dir = std::string(GetDataPath(0)) + "/Data/";
+ } else {
+ start_dir = SplitPathFileName( save_path ).first;
+ }
+ } else {
+ start_dir = std::string(GetWritePath(CoreGameModID).c_str()) + "/Data/";
+ }
+
+ bool valid = false;
+
+ while(!valid) {
+ const int BUF_SIZE = 512;
+ char buf[BUF_SIZE];
+ Dialog::DialogErr err = Dialog::writeFile("xml",1,start_dir.c_str(),buf,BUF_SIZE);
+ if(err){
+ LOGE << "Cancelling level save due to dialog close" << std::endl;
+ return;
+ } else {
+ save_path = buf;
+ old_path = save_path;
+
+ std::string temp = save_path.substr(0, save_path.find_last_of("\\/"));
+ if(CheckWritePermissions(temp.c_str()) != 0) {
+ LOGW << "Couldn't write to directory at " << temp << std::endl;
+ } else {
+ valid = true;
+ }
+ }
+ }
+ }
+
+ if(fileexists(old_path.c_str()) == 0) {
+ CreateBackup(old_path.c_str(), config["level_backup_count"].toNumber<int>());
+ }
+ createfile(save_path.c_str());
+
+ int slash_position = save_path.find_last_of("\\/")+1;
+ int dot_position = save_path.rfind('.');
+
+ s.level_path_ = FindShortestPath2(save_path);
+ s.level_has_been_previously_saved_ = true;
+ s.level_name_ = save_path.substr(slash_position, dot_position-slash_position);
+ s.level_visible_name_ = s.level_name_; // This isn't really used
+
+ Graphics* graphics = Graphics::Instance();
+ LOGI << "Saving level: " << s.level_name_ << std::endl;
+
+ TiXmlDocument doc;
+
+ TiXmlDeclaration* decl = new TiXmlDeclaration( "2.0", "", "" );
+ doc.LinkEndChild( decl );
+
+ //
+ //
+ TiXmlNode* eRoot = static_cast<TiXmlNode*>(&doc);
+ /* This should be activated in the future like alpha 213 or 214
+ TiXmlElement* eRoot = new TiXmlElement("Level");
+ doc.LinkEndChild( eRoot );
+ */
+
+
+ // write file type
+ TiXmlElement* version = new TiXmlElement("Type");
+ eRoot->LinkEndChild(version);
+ version->LinkEndChild(new TiXmlText("saved"));
+
+ TiXmlElement* name_el = new TiXmlElement("Name");
+ eRoot->LinkEndChild(name_el);
+ name_el->LinkEndChild(new TiXmlText(s.level_visible_name_.c_str()));
+
+ TiXmlElement* desc_el = new TiXmlElement("Description");
+ eRoot->LinkEndChild(desc_el);
+ desc_el->LinkEndChild(new TiXmlText(s.level_visible_description_.c_str()));
+
+ TiXmlElement* shader_el;
+ shader_el = new TiXmlElement("Shader");
+ shader_el->LinkEndChild( new TiXmlText(graphics->post_shader_name.c_str()) );
+ eRoot->LinkEndChild(shader_el);
+
+ TiXmlElement* shadow_el;
+ shadow_el = new TiXmlElement("Shadows");
+ shadow_el->LinkEndChild( new TiXmlText(g_level_shadows ? "true" : "false") );
+ eRoot->LinkEndChild(shadow_el);
+
+ TiXmlElement* loading_screen_el;
+ loading_screen_el = new TiXmlElement("LoadingScreen");
+ TiXmlElement* loading_screen_image_el = new TiXmlElement("Image");
+ loading_screen_image_el->LinkEndChild(new TiXmlText(s.level->loading_screen_.image.c_str()));
+ loading_screen_el->LinkEndChild( loading_screen_image_el );
+ eRoot->LinkEndChild(loading_screen_el);
+
+ LOGI << "Saving terrain and sky..." << std::endl;
+ SaveTerrain(eRoot, &s);
+
+ {
+ TiXmlElement* element = new TiXmlElement("OutOfDate");
+ eRoot->LinkEndChild(element);
+ element->SetAttribute("NavMesh",graphics->nav_mesh_out_of_date?"true":"false");
+ element->SetAttribute("NavMeshParamHash", (int)HashNavMeshParameters(s.level->nav_mesh_parameters_));
+ }
+
+ LOGI << "Saving script and spawn points..." << std::endl;
+ {
+ TiXmlElement* ambient_sound_element = new TiXmlElement("AmbientSounds");
+ eRoot->LinkEndChild(ambient_sound_element);
+
+ const std::vector<AmbientTriangle>& ambient_triangles =
+ Engine::Instance()->GetSound()->GetAmbientTriangles();
+ for(unsigned i=0; i<ambient_triangles.size(); ++i){
+ TiXmlElement* ambient;
+ ambient = new TiXmlElement("Ambient");
+ ambient_sound_element->LinkEndChild(ambient);
+ ambient->SetAttribute("path", ambient_triangles[i].path.c_str());
+ }
+ }
+
+ {
+ TiXmlElement* element = new TiXmlElement("Script");
+ element->LinkEndChild(new TiXmlText(s.level->GetLevelSpecificScript().c_str()));
+ eRoot->LinkEndChild(element);
+ }
+ {
+ if(s.level->GetPCScript(NULL) != Level::DEFAULT_PLAYER_SCRIPT) {
+ TiXmlElement* element = new TiXmlElement("PCScript");
+ element->LinkEndChild(new TiXmlText(s.level->GetPCScript(NULL).c_str()));
+ eRoot->LinkEndChild(element);
+ }
+ }
+ {
+ if(s.level->GetNPCScript(NULL) != Level::DEFAULT_ENEMY_SCRIPT) {
+ TiXmlElement* element = new TiXmlElement("NPCScript");
+ element->LinkEndChild(new TiXmlText(s.level->GetNPCScript(NULL).c_str()));
+ eRoot->LinkEndChild(element);
+ }
+ }
+ {
+ TiXmlElement* element = new TiXmlElement("LevelScriptParameters");
+ eRoot->LinkEndChild(element);
+ WriteScriptParamsToXML(s.level->script_params().GetParameterMap(), element);
+ }
+ {
+ TiXmlElement* element = new TiXmlElement("NavMeshParameters");
+ eRoot->LinkEndChild(element);
+ WriteNavMeshParametersToXML(s.level->nav_mesh_parameters_, element);
+ }
+
+ LOGI << "Saving entities..." << std::endl;
+ s.map_editor->SaveEntities(eRoot);
+
+ {
+ LOGI << "Saving recent spawner item list" << std::endl;
+ TiXmlElement* elements = new TiXmlElement("RecentItems");
+
+ std::vector<SpawnerItem> recent_items = s.level->GetRecentlyCreatedItems();
+
+ for( unsigned i = 0; i < recent_items.size(); i++ ) {
+ SpawnerItem* si = &recent_items[i];
+
+ TiXmlElement* element = new TiXmlElement("SpawnerItem");
+ element->SetAttribute("display_name", si->display_name.c_str());
+ element->SetAttribute("path", si->path.c_str());
+ element->SetAttribute("thumbnail_path", si->thumbnail_path.c_str());
+
+ elements->LinkEndChild(element);
+ }
+ eRoot->LinkEndChild(elements);
+ }
+
+ LOGI << "Full Level Save Path: " << save_path << std::endl;
+ doc.SaveFile(save_path);
+ LOGI << "Short Level Save Path: " << s.level_path_ << std::endl;
+}
+
+EntityType CheckGenericType(TiXmlDocument& doc) {
+ TiXmlHandle hDoc(&doc);
+ TiXmlNode* pNode;
+
+ pNode = hDoc.FirstChild("Object").ToNode();
+ if (pNode != NULL) return _env_object;
+
+ pNode = hDoc.FirstChild("Actor").ToNode();
+ if (pNode != NULL) return _movement_object;
+
+ pNode = hDoc.FirstChild("Sound").ToNode();
+ if (pNode != NULL) return _ambient_sound_object;
+
+ pNode = hDoc.FirstChild("item").ToNode();
+ if (pNode != NULL) return _item_object;
+
+ pNode = hDoc.FirstChild("DecalObject").ToNode();
+ if (pNode != NULL) return _decal_object;
+
+ pNode = hDoc.FirstChild("Hotspot").ToNode();
+ if (pNode != NULL) return _hotspot_object;
+
+ return _no_type;
+}
diff --git a/Source/XML/level_loader.h b/Source/XML/level_loader.h
new file mode 100644
index 00000000..1617cd55
--- /dev/null
+++ b/Source/XML/level_loader.h
@@ -0,0 +1,41 @@
+//-----------------------------------------------------------------------------
+// Name: level_loader.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Editors/entity_type.h>
+
+class TiXmlDocument;
+class TiXmlNode;
+class SceneGraph;
+
+namespace LevelLoader {
+ bool LoadLevel(const Path& level_path, SceneGraph& s);
+ enum SaveLevelType {
+ kSaveInPlace, kSaveAs
+ };
+
+ void SaveLevel(SceneGraph& s, SaveLevelType type);
+ void SaveTerrain(TiXmlNode* doc, SceneGraph* s);
+}
+
+EntityType CheckGenericType(TiXmlDocument& doc);
diff --git a/Source/XML/xml_helper.cpp b/Source/XML/xml_helper.cpp
new file mode 100644
index 00000000..cbe21253
--- /dev/null
+++ b/Source/XML/xml_helper.cpp
@@ -0,0 +1,292 @@
+//-----------------------------------------------------------------------------
+// Name: xml_helper.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include <Internal/error.h>
+#include <Internal/common.h>
+#include <Internal/filesystem.h>
+
+#include <Memory/allocation.h>
+#include <XML/xml_helper.h>
+#include <Compat/fileio.h>
+
+TiXmlElement *XmlHelper::findNode(TiXmlDocument &doc, std::string &item, TiXmlElement* element)
+{
+ //TiXmlElement* element = NULL;
+ while(item.size())
+ {
+ if (element == NULL)
+ {
+ element = doc.FirstChildElement(item.substr(0, item.find("/")));
+ }
+ else
+ {
+ element = element->FirstChildElement(item.substr(0, item.find("/")));
+ }
+
+ if (element == NULL)
+ return NULL;
+
+ if (std::string::npos != item.find("/"))
+ item = item.substr(item.find("/") + 1);
+ else
+ item = "";
+ }
+
+ return element;
+}
+
+TiXmlElement *XmlHelper::findNode(TiXmlDocument &doc, const char *item, TiXmlElement* element)
+{
+ std::string i = item;
+ return findNode(doc, i, element);
+}
+
+bool XmlHelper::getNodeValue(TiXmlDocument &doc, const char *item, std::string &text)
+{
+ std::string istr(item);
+ TiXmlElement* element = findNode(doc, istr);
+
+ if (element != NULL)
+ {
+ text = element->GetText();
+ return true;
+ }
+
+ return false;
+}
+
+bool XmlHelper::getNodeValue(TiXmlDocument &doc, const char *item, double &d)
+{
+ std::string text;
+ bool retval = getNodeValue(doc, item, text);
+
+ if (retval)
+ {
+ d = ::atof(text.c_str());
+ }
+
+ return retval;
+}
+
+bool XmlHelper::getNodeValue(TiXmlDocument &doc, const char *item, float &f)
+{
+ std::string text;
+ bool retval = getNodeValue(doc, item, text);
+
+ if (retval)
+ {
+ f = (float)(::atof(text.c_str()));
+ }
+
+ return retval;
+}
+
+bool XmlHelper::getNodeValue(TiXmlDocument &doc, TiXmlElement *element, const char *item, std::string &text)
+{
+ std::string istr(item);
+ element = findNode(doc, istr, element);
+
+ if (element != NULL)
+ {
+ text = element->GetText();
+ return true;
+ }
+
+ return false;
+}
+
+bool XmlHelper::getNodeValue(TiXmlDocument &doc, TiXmlElement *element, const char *item, double &d)
+{
+ std::string text;
+ bool retval = getNodeValue(doc, element, item, text);
+
+ if (retval)
+ {
+ d = ::atof(text.c_str());
+ }
+
+ return retval;
+}
+
+bool XmlHelper::getNodeValue(TiXmlDocument &doc, TiXmlElement *element, const char *item, float &f)
+{
+ std::string text;
+ bool retval = getNodeValue(doc, element, item, text);
+
+ if (retval)
+ {
+ f = (float)(::atof(text.c_str()));
+ }
+
+ return retval;
+}
+
+void GetRange(const TiXmlElement *el,
+ const std::string &val_str,
+ const std::string &min_str,
+ const std::string &max_str,
+ float &min_val,
+ float &max_val)
+{
+ int result = el->QueryFloatAttribute(val_str.c_str(), &min_val);
+ if(result != TIXML_SUCCESS){
+ result = el->QueryFloatAttribute(min_str.c_str(), &min_val);
+ result = el->QueryFloatAttribute(max_str.c_str(), &max_val);
+ } else {
+ max_val = min_val;
+ }
+}
+
+bool LoadXML( TiXmlDocument& doc,
+ const std::string &path,
+ const std::string type )
+{
+ char abs_path[kPathSize];
+ bool retry = true;
+ if (FindFilePath(path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths | kAbsPath) == -1){
+ return false;
+ }
+ doc.LoadFile(abs_path);
+ return true;
+}
+
+bool LoadXMLRetryable( TiXmlDocument& doc,
+ const std::string &path,
+ const std::string type )
+{
+ char abs_path[kPathSize];
+ bool retry = true;
+ if (FindFilePath(path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths | kAbsPath) == -1){
+ ErrorResponse err;
+ while(retry){
+ err = DisplayError("Error",
+ (type+" file \""+path+"\" did not load correctly.").c_str(),
+ _ok_cancel_retry);
+ if(err != _retry){
+ return false;
+ }
+ if (FindFilePath(path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths | kAbsPath) != -1){
+ retry = false;
+ }
+ }
+ }
+ doc.LoadFile(abs_path);
+ return true;
+}
+
+uint8_t* StackLoadText(const char* path, size_t* size_out) {
+ uint8_t* mem = NULL;
+ long file_size = 0;
+ char abs_path[kPathSize];
+ bool retry = true;
+ if (FindFilePath(path, abs_path, kPathSize, kDataPaths | kModPaths | kAbsPath) != -1) {
+ FILE* file = my_fopen(abs_path, "rb");
+ if(file) {
+ fseek (file, 0, SEEK_END);
+ file_size = ftell(file);
+ if( file_size > 0 ) {
+ rewind (file);
+
+ LOG_ASSERT(file_size < (1024 * 1024));
+
+ mem = (uint8_t*)alloc.stack.Alloc(file_size+1);
+ if(mem) {
+ size_t count = fread(mem, 1, file_size, file);
+ if( (long)count == file_size ) {
+ mem[file_size] = '\0';
+ } else {
+ LOGE << "Did not read expected amount of data from file: " << abs_path << std::endl;
+ alloc.stack.Free(mem);
+ mem = NULL;
+ }
+ } else {
+ LOGF << "Could not allocate " << file_size + 1 << " bytes on stack for file " << abs_path << std::endl;
+ }
+ }
+ fclose(file);
+ }
+ }
+
+ if( size_out ) {
+ *size_out = file_size;
+ }
+ return mem;
+}
+
+bool LoadText(void* &mem, const char* path){
+ FILE* file = my_fopen(path, "rb");
+ if(!file){
+ return false;
+ }
+
+ fseek (file, 0, SEEK_END);
+ long file_size = ftell(file);
+ rewind (file);
+
+ mem = OG_MALLOC(file_size+1);
+ if(!mem){
+ FatalError("Error", "Could not allocate memory for file: %s", path);
+ }
+ size_t read = fread(mem, 1, file_size, file);
+
+ if( read != file_size ) {
+ LOGW << "Read less than ftell told us we would!" << std::endl;
+ }
+
+ ((char*)mem)[read] = '\0';
+
+ fclose(file);
+
+ return true;
+}
+
+bool LoadTextRetryable(void* &mem,
+ const std::string &path,
+ const std::string type )
+{
+ char abs_path[kPathSize];
+ bool retry = true;
+ if (FindFilePath(path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths | kAbsPath) == -1) {
+ ErrorResponse err;
+ while(retry){
+ err = DisplayError("Error",
+ (type+" file \""+path+"\" did not load correctly.").c_str(),
+ _ok_cancel_retry);
+ if(err != _retry){
+ return false;
+ }
+ if (FindFilePath(path.c_str(), abs_path, kPathSize, kDataPaths | kModPaths | kAbsPath) != -1){
+ retry = false;
+ }
+ }
+ }
+ LoadText(mem, abs_path);
+ return true;
+}
+
+void LoadAttribIntoString(TiXmlElement* el, const char* attrib, std::string &str){
+ const char* label = el->Attribute(attrib);
+ if(label){
+ str = label;
+ }
+}
diff --git a/Source/XML/xml_helper.h b/Source/XML/xml_helper.h
new file mode 100644
index 00000000..530eb068
--- /dev/null
+++ b/Source/XML/xml_helper.h
@@ -0,0 +1,64 @@
+//-----------------------------------------------------------------------------
+// Name: xml_helper.h
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+#pragma once
+
+#include <Internal/integer.h>
+
+#include <tinyxml.h>
+
+#include <string>
+
+bool LoadXML( TiXmlDocument& doc,
+ const std::string &path,
+ const std::string type );
+bool LoadXMLRetryable(TiXmlDocument& doc,
+ const std::string &path,
+ const std::string type);
+
+void GetRange(const TiXmlElement *el,
+ const std::string &val_str,
+ const std::string &min_str,
+ const std::string &max_str,
+ float &min_val,
+ float &max_val);
+
+bool LoadText(void* &mem, const char* path);
+
+uint8_t* StackLoadText(const char* path, size_t* size_out);
+
+bool LoadTextRetryable(void* &mem, const std::string &path, const std::string type );
+
+class XmlHelper
+{
+public:
+ static TiXmlElement *findNode(TiXmlDocument &doc, std::string &item, TiXmlElement* element = NULL);
+ static TiXmlElement *findNode(TiXmlDocument &doc, const char *item, TiXmlElement* element = NULL);
+ static bool getNodeValue(TiXmlDocument &doc, const char *item, std::string &text);
+ static bool getNodeValue(TiXmlDocument &doc, const char *item, double &d);
+ static bool getNodeValue(TiXmlDocument &doc, const char *item, float &f);
+ static bool getNodeValue(TiXmlDocument &doc, TiXmlElement *element, const char *item, std::string &text);
+ static bool getNodeValue(TiXmlDocument &doc, TiXmlElement *element, const char *item, double &d);
+ static bool getNodeValue(TiXmlDocument &doc, TiXmlElement *element, const char *item, float &f);
+};
+
+void LoadAttribIntoString(TiXmlElement* el, const char* attrib, std::string &str);